Compare commits

..

20 Commits

Author SHA1 Message Date
Anurag Mishra
7539cfecd2 Merge branch 'overtime-feature' of https://github.com/anurag810/erpnext into overtime-feature 2021-08-11 15:18:08 +05:30
Anurag Mishra
18f04111d6 fix: Code optimisation 2021-08-11 15:17:42 +05:30
Anurag Mishra
b2ba9aacb2 Merge branch 'version-13-hotfix' into overtime-feature 2021-08-11 13:32:52 +05:30
Anurag Mishra
4a6694e80a fix: changes requested 2021-08-11 12:59:22 +05:30
Frappe PR Bot
f3ae956eae perf: various minor perf fixes for ledger postings (#26775) (#26896)
* perf: only validate if voucher is journal entry

* perf: optimize merge GLE

- Order fields such that comparison will fail faster
- Break out of loops if not matched

* perf: don't try to match SLE if count mismatch

* refactor: simplify initialize_previous_data

* perf: use cache for fetching valuation_method

These are set only once fields

* refactor: simplify get_future_stock_vouchers

* refactor: simplify get_voucherwise_gl_entries

* perf: fetch only required fields for GL comparison

`select *` fetches all fields, output of this function is only used for
comparing.

* perf: reorder conditions in PL cost center check

* perf: reduce query while validating new gle

* perf: use cache for validating warehouse props

These properties don't change often, no need to query everytime.

* perf: use cached stock settings to validate SLE

* docs: update misleading docstring

Co-authored-by: Marica <maricadsouza221197@gmail.com>
(cherry picked from commit 9152715f90)

Co-authored-by: Ankush <ankush@iwebnotes.com>
2021-08-11 12:18:58 +05:30
Rucha Mahabal
fd325a123c fix(style): apply svg container margin only in desktop view (#26894) 2021-08-10 23:49:56 +05:30
Anurag Mishra
8db493b2be chore: formatting 2021-08-10 12:45:51 +05:30
Anurag Mishra
21bc752da5 fix: Removed print statement 2021-08-10 12:45:51 +05:30
Anurag Mishra
96db78f7bc fix: Test Cases 2021-08-10 12:45:51 +05:30
Anurag Mishra
32f3b24c3a fix: Test Cases 2021-08-10 12:45:51 +05:30
Anurag Mishra
10647bfcd7 fix: test 2021-08-10 12:45:51 +05:30
Anurag Mishra
7d272dc648 fix: test Payroll Entry 2021-08-10 12:45:51 +05:30
Anurag Mishra
b96165883e fix: fixed test and resolved conflicts 2021-08-10 12:45:51 +05:30
Anurag Mishra
5683857f9f fix: sider 2021-08-10 12:45:50 +05:30
Anurag Mishra
73b3121127 fix: some enhancemets and test cases 2021-08-10 12:45:50 +05:30
Anurag Mishra
769d774ccc feat: Validation, sider and form dashbord 2021-08-10 12:45:50 +05:30
Anurag Mishra
da2e95dbcc feat: Overtime based on Attendance and Timesheet 2021-08-10 12:44:50 +05:30
Anurag Mishra
3851d15360 feat:overtime 2021-08-10 12:41:45 +05:30
Anurag Mishra
e4fd6d7763 feat: Overtime 2021-08-10 12:41:45 +05:30
Anurag Mishra
8f1850b21c feat: settings for Overtime 2021-08-10 12:38:33 +05:30
6159 changed files with 479669 additions and 300704 deletions

View File

@@ -9,13 +9,6 @@ trim_trailing_whitespace = true
charset = utf-8 charset = utf-8
# python, js indentation settings # python, js indentation settings
[{*.py,*.js,*.vue,*.css,*.scss,*.html}] [{*.py,*.js}]
indent_style = tab indent_style = tab
indent_size = 4 indent_size = 4
max_line_length = 110
# JSON files - mostly doctype schema files
[{*.json}]
insert_final_newline = false
indent_style = space
indent_size = 2

View File

@@ -2,32 +2,65 @@
"env": { "env": {
"browser": true, "browser": true,
"node": true, "node": true,
"es2022": true "es6": true
}, },
"parserOptions": { "parserOptions": {
"ecmaVersion": 9,
"sourceType": "module" "sourceType": "module"
}, },
"extends": "eslint:recommended", "extends": "eslint:recommended",
"rules": { "rules": {
"indent": "off", "indent": [
"brace-style": "off", "error",
"no-mixed-spaces-and-tabs": "off", "tab",
"no-useless-escape": "off", { "SwitchCase": 1 }
"space-unary-ops": ["error", { "words": true }], ],
"linebreak-style": "off", "brace-style": [
"quotes": ["off"], "error",
"semi": "off", "1tbs"
"camelcase": "off", ],
"no-unused-vars": "off", "space-unary-ops": [
"no-console": ["warn"], "error",
"no-extra-boolean-cast": ["off"], { "words": true }
"no-control-regex": ["off"] ],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"off"
],
"semi": [
"warn",
"always"
],
"camelcase": [
"off"
],
"no-unused-vars": [
"warn"
],
"no-redeclare": [
"warn"
],
"no-console": [
"warn"
],
"no-extra-boolean-cast": [
"off"
],
"no-control-regex": [
"off"
],
"space-before-blocks": "warn",
"keyword-spacing": "warn",
"comma-spacing": "warn",
"key-spacing": "warn"
}, },
"root": true, "root": true,
"globals": { "globals": {
"frappe": true, "frappe": true,
"Vue": true, "Vue": true,
"SetVueGlobals": true,
"erpnext": true, "erpnext": true,
"hub": true, "hub": true,
"$": true, "$": true,
@@ -64,10 +97,8 @@
"is_null": true, "is_null": true,
"in_list": true, "in_list": true,
"has_common": true, "has_common": true,
"posthog": true,
"has_words": true, "has_words": true,
"validate_email": true, "validate_email": true,
"open_web_template_values_editor": true,
"get_number_format": true, "get_number_format": true,
"format_number": true, "format_number": true,
"format_currency": true, "format_currency": true,
@@ -123,8 +154,8 @@
"before": true, "before": true,
"beforeEach": true, "beforeEach": true,
"onScan": true, "onScan": true,
"html2canvas": true,
"extend_cscript": true, "extend_cscript": true,
"localforage": true, "localforage": true
"Plaid": true
} }
} }

View File

@@ -28,10 +28,6 @@ ignore =
B007, B007,
B950, B950,
W191, W191,
E124, # closing bracket, irritating while writing QB code
E131, # continuation line unaligned for hanging indent
E123, # closing bracket does not match indentation of opening bracket's line
E101, # ensured by use of black
max-line-length = 200 max-line-length = 200
exclude=.github/helper/semgrep_rules exclude=.github/helper/semgrep_rules

View File

@@ -8,24 +8,5 @@
# #
# $ git config blame.ignoreRevsFile .git-blame-ignore-revs # $ git config blame.ignoreRevsFile .git-blame-ignore-revs
# Replace use of Class.extend with native JS class
1fe891b287a1b3f225d29ee3d07e7b1824aba9e7
# This commit just changes spaces to tabs for indentation in some files # This commit just changes spaces to tabs for indentation in some files
5f473611bd6ed57703716244a054d3fb5ba9cd23 5f473611bd6ed57703716244a054d3fb5ba9cd23
# Whitespace fix throughout codebase
4551d7d6029b6f587f6c99d4f8df5519241c6a86
b147b85e6ac19a9220cd1e2958a6ebd99373283a
# sort and cleanup imports
915b34391c2066dfc83e60a5813c5a877cebe7ac
# removing six compatibility layer
8fe5feb6a4372bf5f2dfaf65fca41bbcc25c8ce7
# bulk format python code with black
494bd9ef78313436f0424b918f200dab8fc7c20b
# bulk format python code with black
baec607ff5905b1c67531096a9cf50ec7ff00a5d

View File

@@ -23,7 +23,7 @@ If your issue is not clear or does not meet the guidelines, then it will be clos
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. 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.
1. **Version Number:** Please add the version number in your report. Often a bug is fixed in the latest version 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. **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`. 1. **Screenshots:** Screenshots are a great way of communicating the issues. Try adding annotations or using LiceCAP to take a screencast in `gif`.
### Feature Request Guidelines ### Feature Request Guidelines

47
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,47 @@
---
name: Bug report
about: Report a bug encountered while using ERPNext
labels: bug
---
<!--
Welcome to ERPNext issue tracker! Before creating an issue, please heed the following:
1. This tracker should only be used to report bugs and request features / enhancements to ERPNext
- For questions and general support, checkout the manual https://erpnext.com/docs/user/manual/en or use https://discuss.erpnext.com
- For documentation issues, refer to https://github.com/frappe/erpnext_com
2. Use the search function before creating a new issue. Duplicates will be closed and directed to
the original discussion.
3. When making a bug report, make sure you provide all required information. The easier it is for
maintainers to reproduce, the faster it'll be fixed.
4. If you think you know what the reason for the bug is, share it with us. Maybe put in a PR 😉
-->
## Description of the issue
## Context information (for bug reports)
**Output of `bench version`**
```
(paste here)
```
## Steps to reproduce the issue
1.
2.
3.
### Observed result
### Expected result
### Stacktrace / full error message
```
(paste here)
```
## Additional information
OS version / distribution, `ERPNext` install method, etc.

View File

@@ -1,89 +0,0 @@
name: Bug Report
description: Report a bug encountered while using ERPNext
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Welcome to ERPNext issue tracker! Before creating an issue, please heed the following:
1. This tracker should only be used to report bugs and request features / enhancements to ERPNext
- For questions and general support, checkout the [user manual](https://docs.erpnext.com/) or use [forum](https://discuss.erpnext.com)
- For documentation issues, propose edit on [documentation site](https://docs.erpnext.com/) directly.
2. When making a bug report, make sure you provide all required information. The easier it is for
maintainers to reproduce, the faster it'll be fixed.
3. If you think you know what the reason for the bug is, share it with us. Maybe put in a PR 😉
- type: textarea
id: bug-info
attributes:
label: Information about bug
description: Also tell us, what did you expect to happen?
placeholder: Please provide as much information as possible.
validations:
required: true
- type: dropdown
id: module
attributes:
label: Module
description: Select affected module of ERPNext.
multiple: true
options:
- accounts
- stock
- buying
- selling
- ecommerce
- manufacturing
- HR
- projects
- support
- CRM
- assets
- integrations
- quality
- regional
- portal
- agriculture
- education
- non-profit
- other
validations:
required: true
- type: textarea
id: exact-version
attributes:
label: Version
description: Share exact version number of Frappe and ERPNext you are using.
placeholder: |
Frappe version -
ERPNext Verion -
validations:
required: true
- type: dropdown
id: install-method
attributes:
label: Installation method
options:
- docker
- easy-install
- manual install
- FrappeCloud
validations:
required: false
- type: textarea
id: logs
attributes:
label: Relevant log output / Stack trace / Full Error Message.
description: Please copy and paste any relevant log output. This will be automatically formatted.
render: shell
- type: markdown
attributes:
value: |
By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/frappe/erpnext/blob/develop/CODE_OF_CONDUCT.md)

View File

@@ -1,10 +1,7 @@
--- ---
name: Feature request name: Feature request
about: Suggest an idea to improve ERPNext about: Suggest an idea to improve ERPNext
title: ''
labels: feature-request labels: feature-request
assignees: ''
--- ---
<!-- <!--
@@ -12,18 +9,10 @@ Welcome to ERPNext issue tracker! Before creating an issue, please heed the foll
1. This tracker should only be used to report bugs and request features / enhancements to ERPNext 1. This tracker should only be used to report bugs and request features / enhancements to ERPNext
- For questions and general support, checkout the manual https://erpnext.com/docs/user/manual/en or use https://discuss.erpnext.com - For questions and general support, checkout the manual https://erpnext.com/docs/user/manual/en or use https://discuss.erpnext.com
- For documentation issues, refer to https://github.com/frappe/erpnext_com
2. Use the search function before creating a new issue. Duplicates will be closed and directed to 2. Use the search function before creating a new issue. Duplicates will be closed and directed to
the original discussion. the original discussion.
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. 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 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 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.erpnext.com/c/developers/5
3. Telegram group for ERPNext/Frappe development work: https://t.me/erpnext_opps
--> -->
**Is your feature request related to a problem? Please describe.** **Is your feature request related to a problem? Please describe.**

View File

@@ -0,0 +1,17 @@
---
name: Question about using ERPNext
about: This is not the appropriate channel
labels: invalid
---
Please post on our forums:
for questions about using `ERPNext`: https://discuss.erpnext.com
for questions about using the `Frappe Framework`: ~~https://discuss.frappe.io~~ => [stackoverflow](https://stackoverflow.com/questions/tagged/frappe) tagged under `frappe`
for questions about using `bench`, probably the best place to start is the [bench repo](https://github.com/frappe/bench)
For documentation issues, use the [ERPNext Documentation](https://erpnext.com/docs/) or [Frappe Framework Documentation](https://frappe.io/docs/user/en) or the [developer cheetsheet](https://github.com/frappe/frappe/wiki/Developer-Cheatsheet)
> **Posts that are not bug reports or feature requests will not be addressed on this issue tracker.**

View File

@@ -1,74 +0,0 @@
[flake8]
ignore =
B007,
B009,
B010,
B950,
E101,
E111,
E114,
E116,
E117,
E121,
E122,
E123,
E124,
E125,
E126,
E127,
E128,
E131,
E201,
E202,
E203,
E211,
E221,
E222,
E223,
E224,
E225,
E226,
E228,
E231,
E241,
E242,
E251,
E261,
E262,
E265,
E266,
E271,
E272,
E273,
E274,
E301,
E302,
E303,
E305,
E306,
E402,
E501,
E502,
E701,
E702,
E703,
E741,
F403,
W191,
W291,
W292,
W293,
W391,
W503,
W504,
E711,
E129,
F841,
E713,
E712,
B023,
B028
max-line-length = 200
exclude=.github/helper/semgrep_rules,test_*.py

View File

@@ -3,71 +3,46 @@ import requests
from urllib.parse import urlparse from urllib.parse import urlparse
WEBSITE_REPOS = [ docs_repos = [
"frappe_docs",
"erpnext_documentation",
"erpnext_com", "erpnext_com",
"frappe_io", "frappe_io",
] ]
DOCUMENTATION_DOMAINS = [
"docs.erpnext.com",
"frappeframework.com",
]
def uri_validator(x):
result = urlparse(x)
return all([result.scheme, result.netloc, result.path])
def is_valid_url(url: str) -> bool: def docs_link_exists(body):
parts = urlparse(url) for line in body.splitlines():
return all((parts.scheme, parts.netloc, parts.path)) for word in line.split():
if word.startswith('http') and uri_validator(word):
parsed_url = urlparse(word)
def is_documentation_link(word: str) -> bool: if parsed_url.netloc == "github.com":
if not word.startswith("http") or not is_valid_url(word): parts = parsed_url.path.split('/')
return False if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos:
return True
parsed_url = urlparse(word)
if parsed_url.netloc in DOCUMENTATION_DOMAINS:
return True
if parsed_url.netloc == "github.com":
parts = parsed_url.path.split("/")
if len(parts) == 5 and parts[1] == "frappe" and parts[2] in WEBSITE_REPOS:
return True
return False
def contains_documentation_link(body: str) -> bool:
return any(
is_documentation_link(word)
for line in body.splitlines()
for word in line.split()
)
def check_pull_request(number: str) -> "tuple[int, str]":
response = requests.get(f"https://api.github.com/repos/frappe/erpnext/pulls/{number}")
if not response.ok:
return 1, "Pull Request Not Found! ⚠️"
payload = response.json()
title = (payload.get("title") or "").lower().strip()
head_sha = (payload.get("head") or {}).get("sha")
body = (payload.get("body") or "").lower()
if (
not title.startswith("feat")
or not head_sha
or "no-docs" in body
or "backport" in body
):
return 0, "Skipping documentation checks... 🏃"
if contains_documentation_link(body):
return 0, "Documentation Link Found. You're Awesome! 🎉"
return 1, "Documentation Link Not Found! ⚠️"
if __name__ == "__main__": if __name__ == "__main__":
exit_code, message = check_pull_request(sys.argv[1]) pr = sys.argv[1]
print(message) response = requests.get("https://api.github.com/repos/frappe/erpnext/pulls/{}".format(pr))
sys.exit(exit_code)
if response.ok:
payload = response.json()
title = payload.get("title", "").lower()
head_sha = payload.get("head", {}).get("sha")
body = payload.get("body", "").lower()
if title.startswith("feat") and head_sha and "no-docs" not in body:
if docs_link_exists(body):
print("Documentation Link Found. You're Awesome! 🎉")
else:
print("Documentation Link Not Found! ⚠️")
sys.exit(1)
else:
print("Skipping documentation checks... 🏃")

View File

@@ -4,57 +4,35 @@ set -e
cd ~ || exit cd ~ || exit
sudo apt update sudo apt-get install redis-server
sudo apt remove mysql-server mysql-client
sudo apt install libcups2-dev redis-server mariadb-client-10.6 sudo apt install nodejs
sudo apt install npm
pip install frappe-bench pip install frappe-bench
githubbranch=${GITHUB_BASE_REF:-${GITHUB_REF##*/}} git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" --depth 1
frappeuser=${FRAPPE_USER:-"frappe"}
frappebranch=${FRAPPE_BRANCH:-$githubbranch}
git clone "https://github.com/${frappeuser}/frappe" --branch "${frappebranch}" --depth 1
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
mkdir ~/frappe-bench/sites/test_site mkdir ~/frappe-bench/sites/test_site
cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config.json" ~/frappe-bench/sites/test_site/
if [ "$DB" == "mariadb" ];then mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"
cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config_mariadb.json" ~/frappe-bench/sites/test_site/site_config.json mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
else
cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config_postgres.json" ~/frappe-bench/sites/test_site/site_config.json
fi
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE DATABASE test_frappe"
mysql --host 127.0.0.1 --port 3306 -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
if [ "$DB" == "mariadb" ];then mysql --host 127.0.0.1 --port 3306 -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'" mysql --host 127.0.0.1 --port 3306 -u root -e "FLUSH PRIVILEGES"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE DATABASE test_frappe"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "FLUSH PRIVILEGES"
fi
if [ "$DB" == "postgres" ];then
echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE DATABASE test_frappe" -U postgres;
echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE USER test_frappe WITH PASSWORD 'test_frappe'" -U postgres;
fi
install_whktml() {
if [ "$(lsb_release -rs)" = "22.04" ]; then
wget -O /tmp/wkhtmltox.deb https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-2/wkhtmltox_0.12.6.1-2.jammy_amd64.deb
sudo apt install /tmp/wkhtmltox.deb
else
echo "Please update this script to support wkhtmltopdf for $(lsb_release -ds)"
exit 1
fi
}
install_whktml &
wkpid=$!
wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
sudo chmod o+x /usr/local/bin/wkhtmltopdf
sudo apt-get install libcups2-dev
cd ~/frappe-bench || exit cd ~/frappe-bench || exit
@@ -63,13 +41,6 @@ sed -i 's/schedule:/# schedule:/g' Procfile
sed -i 's/socketio:/# socketio:/g' Procfile sed -i 's/socketio:/# socketio:/g' Procfile
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
bench get-app payments --branch ${githubbranch%"-hotfix"}
bench get-app erpnext "${GITHUB_WORKSPACE}" bench get-app erpnext "${GITHUB_WORKSPACE}"
bench start &> bench_run_logs.txt &
if [ "$TYPE" == "server" ]; then bench setup requirements --dev; fi
wait $wkpid
bench start &>> ~/frappe-bench/bench_start.log &
CI=Yes bench build --app frappe &
bench --site test_site reinstall --yes bench --site test_site reinstall --yes

38
.github/helper/semgrep_rules/README.md vendored Normal file
View File

@@ -0,0 +1,38 @@
# Semgrep linting
## What is semgrep?
Semgrep or "semantic grep" is language agnostic static analysis tool. In simple terms semgrep is syntax-aware `grep`, so unlike regex it doesn't get confused by different ways of writing same thing or whitespaces or code split in multiple lines etc.
Example:
To check if a translate function is using f-string or not the regex would be `r"_\(\s*f[\"']"` while equivalent rule in semgrep would be `_(f"...")`. As semgrep knows grammer of language it takes care of unnecessary whitespace, type of quotation marks etc.
You can read more such examples in `.github/helper/semgrep_rules` directory.
# Why/when to use this?
We want to maintain quality of contributions, at the same time remembering all the good practices can be pain to deal with while evaluating contributions. Using semgrep if you can translate "best practice" into a rule then it can automate the task for us.
## Running locally
Install semgrep using homebrew `brew install semgrep` or pip `pip install semgrep`.
To run locally use following command:
`semgrep --config=.github/helper/semgrep_rules [file/folder names]`
## Testing
semgrep allows testing the tests. Refer to this page: https://semgrep.dev/docs/writing-rules/testing-rules/
When writing new rules you should write few positive and few negative cases as shown in the guide and current tests.
To run current tests: `semgrep --test --test-ignore-todo .github/helper/semgrep_rules`
## Reference
If you are new to Semgrep read following pages to get started on writing/modifying rules:
- https://semgrep.dev/docs/getting-started/
- https://semgrep.dev/docs/writing-rules/rule-syntax
- https://semgrep.dev/docs/writing-rules/pattern-examples/
- https://semgrep.dev/docs/writing-rules/rule-ideas/#common-use-cases

View File

@@ -0,0 +1,64 @@
import frappe
from frappe import _, flt
from frappe.model.document import Document
# ruleid: frappe-modifying-but-not-comitting
def on_submit(self):
if self.value_of_goods == 0:
frappe.throw(_('Value of goods cannot be 0'))
self.status = 'Submitted'
# ok: frappe-modifying-but-not-comitting
def on_submit(self):
if self.value_of_goods == 0:
frappe.throw(_('Value of goods cannot be 0'))
self.status = 'Submitted'
self.db_set('status', 'Submitted')
# ok: frappe-modifying-but-not-comitting
def on_submit(self):
if self.value_of_goods == 0:
frappe.throw(_('Value of goods cannot be 0'))
x = "y"
self.status = x
self.db_set('status', x)
# ok: frappe-modifying-but-not-comitting
def on_submit(self):
x = "y"
self.status = x
self.save()
# ruleid: frappe-modifying-but-not-comitting-other-method
class DoctypeClass(Document):
def on_submit(self):
self.good_method()
self.tainted_method()
def tainted_method(self):
self.status = "uptate"
# ok: frappe-modifying-but-not-comitting-other-method
class DoctypeClass(Document):
def on_submit(self):
self.good_method()
self.tainted_method()
def tainted_method(self):
self.status = "update"
self.db_set("status", "update")
# ok: frappe-modifying-but-not-comitting-other-method
class DoctypeClass(Document):
def on_submit(self):
self.good_method()
self.tainted_method()
self.save()
def tainted_method(self):
self.status = "uptate"

View File

@@ -0,0 +1,133 @@
# This file specifies rules for correctness according to how frappe doctype data model works.
rules:
- id: frappe-modifying-but-not-comitting
patterns:
- pattern: |
def $METHOD(self, ...):
...
self.$ATTR = ...
- pattern-not: |
def $METHOD(self, ...):
...
self.$ATTR = ...
...
self.db_set(..., self.$ATTR, ...)
- pattern-not: |
def $METHOD(self, ...):
...
self.$ATTR = $SOME_VAR
...
self.db_set(..., $SOME_VAR, ...)
- pattern-not: |
def $METHOD(self, ...):
...
self.$ATTR = $SOME_VAR
...
self.save()
- metavariable-regex:
metavariable: '$ATTR'
# this is negative look-ahead, add more attrs to ignore like (ignore|ignore_this_too|ignore_me)
regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$'
- metavariable-regex:
metavariable: "$METHOD"
regex: "(on_submit|on_cancel)"
message: |
DocType modified in self.$METHOD. Please check if modification of self.$ATTR is commited to database.
languages: [python]
severity: ERROR
- id: frappe-modifying-but-not-comitting-other-method
patterns:
- pattern: |
class $DOCTYPE(...):
def $METHOD(self, ...):
...
self.$ANOTHER_METHOD()
...
def $ANOTHER_METHOD(self, ...):
...
self.$ATTR = ...
- pattern-not: |
class $DOCTYPE(...):
def $METHOD(self, ...):
...
self.$ANOTHER_METHOD()
...
def $ANOTHER_METHOD(self, ...):
...
self.$ATTR = ...
...
self.db_set(..., self.$ATTR, ...)
- pattern-not: |
class $DOCTYPE(...):
def $METHOD(self, ...):
...
self.$ANOTHER_METHOD()
...
def $ANOTHER_METHOD(self, ...):
...
self.$ATTR = $SOME_VAR
...
self.db_set(..., $SOME_VAR, ...)
- pattern-not: |
class $DOCTYPE(...):
def $METHOD(self, ...):
...
self.$ANOTHER_METHOD()
...
self.save()
def $ANOTHER_METHOD(self, ...):
...
self.$ATTR = ...
- metavariable-regex:
metavariable: "$METHOD"
regex: "(on_submit|on_cancel)"
message: |
self.$ANOTHER_METHOD is called from self.$METHOD, check if changes to self.$ATTR are commited to database.
languages: [python]
severity: ERROR
- id: frappe-print-function-in-doctypes
pattern: print(...)
message: |
Did you mean to leave this print statement in? Consider using msgprint or logger instead of print statement.
languages: [python]
severity: WARNING
paths:
include:
- "*/**/doctype/*"
- id: frappe-modifying-child-tables-while-iterating
pattern-either:
- pattern: |
for $ROW in self.$TABLE:
...
self.remove(...)
- pattern: |
for $ROW in self.$TABLE:
...
self.append(...)
message: |
Child table being modified while iterating on it.
languages: [python]
severity: ERROR
paths:
include:
- "*/**/doctype/*"
- id: frappe-same-key-assigned-twice
pattern-either:
- pattern: |
{..., $X: $A, ..., $X: $B, ...}
- pattern: |
dict(..., ($X, $A), ..., ($X, $B), ...)
- pattern: |
_dict(..., ($X, $A), ..., ($X, $B), ...)
message: |
key `$X` is uselessly assigned twice. This could be a potential bug.
languages: [python]
severity: ERROR

View File

@@ -0,0 +1,6 @@
def function_name(input):
# ruleid: frappe-codeinjection-eval
eval(input)
# ok: frappe-codeinjection-eval
eval("1 + 1")

View File

@@ -0,0 +1,10 @@
rules:
- id: frappe-codeinjection-eval
patterns:
- pattern-not: eval("...")
- pattern: eval(...)
message: |
Detected the use of eval(). eval() can be dangerous if used to evaluate
dynamic content. Avoid it or use safe_eval().
languages: [python]
severity: ERROR

View File

@@ -0,0 +1,44 @@
// ruleid: frappe-translation-empty-string
__("")
// ruleid: frappe-translation-empty-string
__('')
// ok: frappe-translation-js-formatting
__('Welcome {0}, get started with ERPNext in just a few clicks.', [full_name]);
// ruleid: frappe-translation-js-formatting
__(`Welcome ${full_name}, get started with ERPNext in just a few clicks.`);
// ok: frappe-translation-js-formatting
__('This is fine');
// ok: frappe-translation-trailing-spaces
__('This is fine');
// ruleid: frappe-translation-trailing-spaces
__(' this is not ok ');
// ruleid: frappe-translation-trailing-spaces
__('this is not ok ');
// ruleid: frappe-translation-trailing-spaces
__(' this is not ok');
// ok: frappe-translation-js-splitting
__('You have {0} subscribers in your mailing list.', [subscribers.length])
// todoruleid: frappe-translation-js-splitting
__('You have') + subscribers.length + __('subscribers in your mailing list.')
// ruleid: frappe-translation-js-splitting
__('You have' + 'subscribers in your mailing list.')
// ruleid: frappe-translation-js-splitting
__('You have {0} subscribers' +
'in your mailing list', [subscribers.length])
// ok: frappe-translation-js-splitting
__("Ctrl+Enter to add comment")
// ruleid: frappe-translation-js-splitting
__('You have {0} subscribers \
in your mailing list', [subscribers.length])

View File

@@ -0,0 +1,61 @@
# Examples taken from https://frappeframework.com/docs/user/en/translations
# This file is used for testing the tests.
from frappe import _
full_name = "Jon Doe"
# ok: frappe-translation-python-formatting
_('Welcome {0}, get started with ERPNext in just a few clicks.').format(full_name)
# ruleid: frappe-translation-python-formatting
_('Welcome %s, get started with ERPNext in just a few clicks.' % full_name)
# ruleid: frappe-translation-python-formatting
_('Welcome %(name)s, get started with ERPNext in just a few clicks.' % {'name': full_name})
# ruleid: frappe-translation-python-formatting
_('Welcome {0}, get started with ERPNext in just a few clicks.'.format(full_name))
subscribers = ["Jon", "Doe"]
# ok: frappe-translation-python-formatting
_('You have {0} subscribers in your mailing list.').format(len(subscribers))
# ruleid: frappe-translation-python-splitting
_('You have') + len(subscribers) + _('subscribers in your mailing list.')
# ruleid: frappe-translation-python-splitting
_('You have {0} subscribers \
in your mailing list').format(len(subscribers))
# ok: frappe-translation-python-splitting
_('You have {0} subscribers') \
+ 'in your mailing list'
# ruleid: frappe-translation-trailing-spaces
msg = _(" You have {0} pending invoice ")
# ruleid: frappe-translation-trailing-spaces
msg = _("You have {0} pending invoice ")
# ruleid: frappe-translation-trailing-spaces
msg = _(" You have {0} pending invoice")
# ok: frappe-translation-trailing-spaces
msg = ' ' + _("You have {0} pending invoices") + ' '
# ruleid: frappe-translation-python-formatting
_(f"can not format like this - {subscribers}")
# ruleid: frappe-translation-python-splitting
_(f"what" + f"this is also not cool")
# ruleid: frappe-translation-empty-string
_("")
# ruleid: frappe-translation-empty-string
_('')
class Test:
# ok: frappe-translation-python-splitting
def __init__(
args
):
pass

View File

@@ -0,0 +1,64 @@
rules:
- id: frappe-translation-empty-string
pattern-either:
- pattern: _("")
- pattern: __("")
message: |
Empty string is useless for translation.
Please refer: https://frappeframework.com/docs/user/en/translations
languages: [python, javascript, json]
severity: ERROR
- id: frappe-translation-trailing-spaces
pattern-either:
- pattern: _("=~/(^[ \t]+|[ \t]+$)/")
- pattern: __("=~/(^[ \t]+|[ \t]+$)/")
message: |
Trailing or leading whitespace not allowed in translate strings.
Please refer: https://frappeframework.com/docs/user/en/translations
languages: [python, javascript, json]
severity: ERROR
- id: frappe-translation-python-formatting
pattern-either:
- pattern: _("..." % ...)
- pattern: _("...".format(...))
- pattern: _(f"...")
message: |
Only positional formatters are allowed and formatting should not be done before translating.
Please refer: https://frappeframework.com/docs/user/en/translations
languages: [python]
severity: ERROR
- id: frappe-translation-js-formatting
patterns:
- pattern: __(`...`)
- pattern-not: __("...")
message: |
Template strings are not allowed for text formatting.
Please refer: https://frappeframework.com/docs/user/en/translations
languages: [javascript, json]
severity: ERROR
- id: frappe-translation-python-splitting
pattern-either:
- pattern: _(...) + _(...)
- pattern: _("..." + "...")
- pattern-regex: '[\s\.]_\([^\)]*\\\s*' # lines broken by `\`
- pattern-regex: '[\s\.]_\(\s*\n' # line breaks allowed by python for using ( )
message: |
Do not split strings inside translate function. Do not concatenate using translate functions.
Please refer: https://frappeframework.com/docs/user/en/translations
languages: [python]
severity: ERROR
- id: frappe-translation-js-splitting
pattern-either:
- pattern-regex: '__\([^\)]*[\\]\s+'
- pattern: __('...' + '...', ...)
- pattern: __('...') + __('...')
message: |
Do not split strings inside translate function. Do not concatenate using translate functions.
Please refer: https://frappeframework.com/docs/user/en/translations
languages: [javascript, json]
severity: ERROR

9
.github/helper/semgrep_rules/ux.js vendored Normal file
View File

@@ -0,0 +1,9 @@
// ok: frappe-missing-translate-function-js
frappe.msgprint('{{ _("Both login and password required") }}');
// ruleid: frappe-missing-translate-function-js
frappe.msgprint('What');
// ok: frappe-missing-translate-function-js
frappe.throw(' {{ _("Both login and password required") }}. ');

31
.github/helper/semgrep_rules/ux.py vendored Normal file
View File

@@ -0,0 +1,31 @@
import frappe
from frappe import msgprint, throw, _
# ruleid: frappe-missing-translate-function-python
throw("Error Occured")
# ruleid: frappe-missing-translate-function-python
frappe.throw("Error Occured")
# ruleid: frappe-missing-translate-function-python
frappe.msgprint("Useful message")
# ruleid: frappe-missing-translate-function-python
msgprint("Useful message")
# ok: frappe-missing-translate-function-python
translatedmessage = _("Hello")
# ok: frappe-missing-translate-function-python
throw(translatedmessage)
# ok: frappe-missing-translate-function-python
msgprint(translatedmessage)
# ok: frappe-missing-translate-function-python
msgprint(_("Helpful message"))
# ok: frappe-missing-translate-function-python
frappe.throw(_("Error occured"))

30
.github/helper/semgrep_rules/ux.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
rules:
- id: frappe-missing-translate-function-python
pattern-either:
- patterns:
- pattern: frappe.msgprint("...", ...)
- pattern-not: frappe.msgprint(_("..."), ...)
- patterns:
- pattern: frappe.throw("...", ...)
- pattern-not: frappe.throw(_("..."), ...)
message: |
All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations
languages: [python]
severity: ERROR
- id: frappe-missing-translate-function-js
pattern-either:
- patterns:
- pattern: frappe.msgprint("...", ...)
- pattern-not: frappe.msgprint(__("..."), ...)
# ignore microtemplating e.g. msgprint("{{ _("server side translation") }}")
- pattern-not: frappe.msgprint("=~/\{\{.*\_.*\}\}/i", ...)
- patterns:
- pattern: frappe.throw("...", ...)
- pattern-not: frappe.throw(__("..."), ...)
# ignore microtemplating
- pattern-not: frappe.throw("=~/\{\{.*\_.*\}\}/i", ...)
message: |
All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations
languages: [javascript]
severity: ERROR

16
.github/helper/site_config.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
"db_host": "127.0.0.1",
"db_port": 3306,
"db_name": "test_frappe",
"db_password": "test_frappe",
"auto_email_id": "test@example.com",
"mail_server": "smtp.example.com",
"mail_login": "test@example.com",
"mail_password": "test",
"admin_password": "admin",
"root_login": "root",
"root_password": "travis",
"host_name": "http://test_site:8000",
"install_apps": ["erpnext"],
"throttle_user_limit": 100
}

View File

@@ -1,16 +0,0 @@
{
"db_host": "127.0.0.1",
"db_port": 3306,
"db_name": "test_frappe",
"db_password": "test_frappe",
"auto_email_id": "test@example.com",
"mail_server": "smtp.example.com",
"mail_login": "test@example.com",
"mail_password": "test",
"admin_password": "admin",
"root_login": "root",
"root_password": "root",
"host_name": "http://test_site:8000",
"install_apps": ["payments", "erpnext"],
"throttle_user_limit": 100
}

View File

@@ -1,18 +0,0 @@
{
"db_host": "127.0.0.1",
"db_port": 5432,
"db_name": "test_frappe",
"db_password": "test_frappe",
"db_type": "postgres",
"allow_tests": true,
"auto_email_id": "test@example.com",
"mail_server": "smtp.example.com",
"mail_login": "test@example.com",
"mail_password": "test",
"admin_password": "admin",
"root_login": "postgres",
"root_password": "travis",
"host_name": "http://test_site:8000",
"install_apps": ["erpnext"],
"throttle_user_limit": 100
}

60
.github/helper/translation.py vendored Normal file
View File

@@ -0,0 +1,60 @@
import re
import sys
errors_encounter = 0
pattern = re.compile(r"_\(([\"']{,3})(?P<message>((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P<py_context>((?!\5).)*)\5)*(\s*,(\s*?.*?\n*?)*(,\s*([\"'])(?P<js_context>((?!\11).)*)\11)*)*\)")
words_pattern = re.compile(r"_{1,2}\([\"'`]{1,3}.*?[a-zA-Z]")
start_pattern = re.compile(r"_{1,2}\([f\"'`]{1,3}")
f_string_pattern = re.compile(r"_\(f[\"']")
starts_with_f_pattern = re.compile(r"_\(f")
# skip first argument
files = sys.argv[1:]
files_to_scan = [_file for _file in files if _file.endswith(('.py', '.js'))]
for _file in files_to_scan:
with open(_file, 'r') as f:
print(f'Checking: {_file}')
file_lines = f.readlines()
for line_number, line in enumerate(file_lines, 1):
if 'frappe-lint: disable-translate' in line:
continue
start_matches = start_pattern.search(line)
if start_matches:
starts_with_f = starts_with_f_pattern.search(line)
if starts_with_f:
has_f_string = f_string_pattern.search(line)
if has_f_string:
errors_encounter += 1
print(f'\nF-strings are not supported for translations at line number {line_number}\n{line.strip()[:100]}')
continue
else:
continue
match = pattern.search(line)
error_found = False
if not match and line.endswith((',\n', '[\n')):
# concat remaining text to validate multiline pattern
line = "".join(file_lines[line_number - 1:])
line = line[start_matches.start() + 1:]
match = pattern.match(line)
if not match:
error_found = True
print(f'\nTranslation syntax error at line number {line_number}\n{line.strip()[:100]}')
if not error_found and not words_pattern.search(line):
error_found = True
print(f'\nTranslation is useless because it has no words at line number {line_number}\n{line.strip()[:100]}')
if error_found:
errors_encounter += 1
if errors_encounter > 0:
print('\nVisit "https://frappeframework.com/docs/user/en/translations" to learn about valid translation strings.')
sys.exit(1)
else:
print('\nGood To Go!')

55
.github/labeler.yml vendored
View File

@@ -1,55 +0,0 @@
accounts:
- erpnext/accounts/*
- erpnext/controllers/accounts_controller.py
- erpnext/controllers/taxes_and_totals.py
stock:
- erpnext/stock/*
- erpnext/controllers/stock_controller.py
- erpnext/controllers/item_variant.py
assets:
- erpnext/assets/*
regional:
- erpnext/regional/*
selling:
- erpnext/selling/*
- erpnext/controllers/selling_controller.py
buying:
- erpnext/buying/*
- erpnext/controllers/buying_controller.py
support:
- erpnext/support/*
POS:
- pos*
ecommerce:
- erpnext/e_commerce/*
maintenance:
- erpnext/maintenance/*
manufacturing:
- erpnext/manufacturing/*
crm:
- erpnext/crm/*
HR:
- erpnext/hr/*
payroll:
- erpnext/payroll*
projects:
- erpnext/projects/*
# Any python files modifed but no test files modified
needs-tests:
- any: ['erpnext/**/*.py']
all: ['!erpnext/**/test*.py']

39
.github/stale.yml vendored
View File

@@ -1,27 +1,34 @@
# Configuration for probot-stale - https://github.com/probot/stale # Configuration for probot-stale - https://github.com/probot/stale
# Label to use when marking as stale # Number of days of inactivity before an Issue or Pull Request becomes stale
staleLabel: inactive daysUntilStale: 30
# Limit the number of actions per hour, from 1-30. Default is 30 # Number of days of inactivity before a stale Issue or Pull Request is closed.
limitPerRun: 10 # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 7
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- hotfix
# Set to true to ignore issues in a project (defaults to false) # Set to true to ignore issues in a project (defaults to false)
exemptProjects: true exemptProjects: false
# Set to true to ignore issues in a milestone (defaults to false) # Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: true exemptMilestones: true
pulls: # Label to use when marking as stale
daysUntilStale: 15 staleLabel: inactive
daysUntilClose: 3
exemptLabels:
- hotfix
markComment: >
This pull request has been automatically marked as inactive because it has
not had recent activity. It will be closed within 3 days if no further
activity occurs, but it only takes a comment to keep a contribution alive
:) Also, even if it is closed, you can always reopen the PR when you're
ready. Thank you for contributing.
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This pull request has been automatically marked as stale because it has not had
recent activity. It will be closed within a week if no further activity occurs, but it
only takes a comment to keep a contribution alive :) Also, even if it is closed,
you can always reopen the PR when you're ready. Thank you for contributing.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Limit to only `issues` or `pulls`
only: pulls only: pulls

View File

@@ -1,32 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="4 2 193 52">
<g filter="url(#filter0_dd)">
<rect x="4" y="2" width="193" height="52" rx="6" fill="#2490EF"/>
<path d="M28 22.2891H32.8786V35.5H36.2088V22.2891H41.0874V19.5H28V22.2891Z" fill="white"/>
<path d="M41.6982 35.5H45.0129V28.7109C45.0129 27.2344 46.0866 26.2188 47.5494 26.2188C48.0085 26.2188 48.6388 26.2969 48.95 26.3984V23.4453C48.6543 23.375 48.2419 23.3281 47.9074 23.3281C46.5691 23.3281 45.472 24.1094 45.0362 25.5938H44.9117V23.5H41.6982V35.5Z" fill="white"/>
<path d="M52.8331 40C55.2996 40 56.6068 38.7344 57.2837 36.7969L61.9289 23.5156L58.4197 23.5L55.9221 32.3125H55.7976L53.3233 23.5H49.8374L54.1247 35.8437L53.9302 36.3516C53.4944 37.4766 52.6619 37.5312 51.4947 37.1719L50.7478 39.6562C51.2224 39.8594 51.9927 40 52.8331 40Z" fill="white"/>
<path d="M73.6142 35.7344C77.2401 35.7344 79.4966 33.2422 79.4966 29.5469C79.4966 25.8281 77.2401 23.3438 73.6142 23.3438C69.9883 23.3438 67.7319 25.8281 67.7319 29.5469C67.7319 33.2422 69.9883 35.7344 73.6142 35.7344ZM73.6298 33.1562C71.9569 33.1562 71.101 31.6171 71.101 29.5233C71.101 27.4296 71.9569 25.8827 73.6298 25.8827C75.2715 25.8827 76.1274 27.4296 76.1274 29.5233C76.1274 31.6171 75.2715 33.1562 73.6298 33.1562Z" fill="white"/>
<path d="M84.7253 28.5625C84.7331 27.0156 85.6512 26.1094 86.9895 26.1094C88.3201 26.1094 89.1215 26.9844 89.1137 28.4531V35.5H92.4284V27.8594C92.4284 25.0625 90.7945 23.3438 88.3046 23.3438C86.5306 23.3438 85.2466 24.2187 84.7097 25.6172H84.5697V23.5H81.4106V35.5H84.7253V28.5625Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M102.429 19.5H113.429V22.3141H102.429V19.5ZM102.429 35.5V26.6794H112.699V29.4982H105.94V35.5H102.429Z" fill="white"/>
<path d="M131.584 24.9625C131.09 21.5057 128.345 19.5 124.785 19.5C120.589 19.5 117.429 22.463 117.429 27.4924C117.429 32.5142 120.55 35.4848 124.785 35.4848C128.604 35.4848 131.137 33.0916 131.584 30.1211L128.651 30.1059C128.282 31.9293 126.745 32.9549 124.824 32.9549C122.22 32.9549 120.354 31.0632 120.354 27.4924C120.354 23.9824 122.204 22.0299 124.832 22.0299C126.784 22.0299 128.314 23.1011 128.651 24.9625H131.584Z" fill="white"/>
<path d="M136.409 19.7124H133.571V35.2718H136.409V19.7124Z" fill="white"/>
<path d="M144.031 35.5001C147.56 35.5001 149.803 33.0917 149.803 29.483C149.803 25.8667 147.56 23.4507 144.031 23.4507C140.502 23.4507 138.259 25.8667 138.259 29.483C138.259 33.0917 140.502 35.5001 144.031 35.5001ZM144.047 33.2969C142.094 33.2969 141.137 31.6103 141.137 29.4754C141.137 27.3406 142.094 25.6312 144.047 25.6312C145.968 25.6312 146.925 27.3406 146.925 29.4754C146.925 31.6103 145.968 33.2969 144.047 33.2969Z" fill="white"/>
<path d="M159.338 30.3641C159.338 32.1419 158.028 33.0232 156.773 33.0232C155.409 33.0232 154.499 32.0887 154.499 30.6072V23.6025H151.66V31.0327C151.66 33.8361 153.307 35.4239 155.675 35.4239C157.479 35.4239 158.749 34.5046 159.298 33.1979H159.424V35.272H162.176V23.6025H159.338V30.3641Z" fill="white"/>
<path d="M169.014 35.4769C171.084 35.4769 172.017 34.2841 172.464 33.4332H172.637V35.2718H175.429V19.7124H172.582V25.532H172.464C172.033 24.6887 171.147 23.4503 169.022 23.4503C166.238 23.4503 164.05 25.5624 164.05 29.4522C164.05 33.2965 166.175 35.4769 169.014 35.4769ZM169.806 33.2205C167.931 33.2205 166.943 31.6251 166.943 29.437C166.943 27.2642 167.916 25.7067 169.806 25.7067C171.633 25.7067 172.637 27.173 172.637 29.437C172.637 31.701 171.617 33.2205 169.806 33.2205Z" fill="white"/>
</g>
<defs>
<filter id="filter0_dd" x="0" y="0" width="201" height="60" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="0.25"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.13 0"/>
<feBlend mode="normal" in2="effect1_dropShadow" result="effect2_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow" result="shape"/>
</filter>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 4.3 KiB

25
.github/workflows/backport.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Backport
on:
pull_request_target:
types:
- closed
- labeled
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Checkout Actions
uses: actions/checkout@v2
with:
repository: "frappe/backport"
path: ./actions
ref: develop
- name: Install Actions
run: npm install --production --prefix ./actions
- name: Run backport
uses: ./actions/backport
with:
token: ${{secrets.BACKPORT_BOT_TOKEN}}
labelsToAdd: "backport"
title: "{{originalTitle}}"

View File

@@ -11,4 +11,4 @@ jobs:
- name: curl - name: curl
run: | run: |
apk add curl bash apk add curl bash
curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: Bearer ${{ secrets.CI_PAT }}" https://api.github.com/repos/frappe/frappe_docker/actions/workflows/build_stable.yml/dispatches -d '{"ref":"main"}' curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token ${{ secrets.TRAVIS_CI_TOKEN }}" -d '{"request":{"branch":"master"}}' https://api.travis-ci.com/repo/frappe%2Ffrappe_docker/requests

View File

@@ -6,13 +6,12 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10
steps: steps:
- name: 'Setup Environment' - name: 'Setup Environment'
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with: with:
python-version: '3.10' python-version: 3.6
- name: 'Clone repo' - name: 'Clone repo'
uses: actions/checkout@v2 uses: actions/checkout@v2

View File

@@ -1,12 +0,0 @@
name: "Pull Request Labeler"
on:
pull_request_target:
types: [opened, reopened]
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v3
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"

View File

@@ -1,30 +0,0 @@
name: Linters
on:
pull_request: { }
jobs:
linters:
name: linters
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.10
uses: actions/setup-python@v4
with:
python-version: '3.10'
cache: pip
- name: Install and Run Pre-commit
uses: pre-commit/action@v3.0.0
- name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
- name: Download semgrep
run: pip install semgrep
- name: Run Semgrep rules
run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness

View File

@@ -1,57 +1,30 @@
name: Patch name: Patch
on: on: [pull_request, workflow_dispatch]
pull_request:
paths-ignore:
- '**.js'
- '**.css'
- '**.md'
- '**.html'
- '**.csv'
workflow_dispatch:
concurrency:
group: patch-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }}
cancel-in-progress: true
jobs: jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-18.04
timeout-minutes: 60
name: Patch Test name: Patch Test
services: services:
mysql: mysql:
image: mariadb:10.6 image: mariadb:10.3
env: env:
MARIADB_ROOT_PASSWORD: 'root' MYSQL_ALLOW_EMPTY_PASSWORD: YES
ports: ports:
- 3306:3306 - 3306:3306
options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3 options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
steps: steps:
- name: Clone - name: Clone
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Check for valid Python & Merge Conflicts
run: |
python -m compileall -f "${GITHUB_WORKSPACE}"
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
then echo "Found merge conflicts"
exit 1
fi
- name: Setup Python - name: Setup Python
uses: "actions/setup-python@v4" uses: actions/setup-python@v2
with: with:
python-version: '3.10' python-version: 3.6
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 18
check-latest: true
- name: Add to Hosts - name: Add to Hosts
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
@@ -60,7 +33,7 @@ jobs:
uses: actions/cache@v2 uses: actions/cache@v2
with: with:
path: ~/.cache/pip path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }} key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: | restore-keys: |
${{ runner.os }}-pip- ${{ runner.os }}-pip-
${{ runner.os }}- ${{ runner.os }}-
@@ -90,70 +63,11 @@ jobs:
${{ runner.os }}-yarn- ${{ runner.os }}-yarn-
- name: Install - name: Install
run: | run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
pip install frappe-bench
bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
env:
DB: mariadb
TYPE: server
- name: Run Patch Tests - name: Run Patch Tests
run: | run: |
cd ~/frappe-bench/ cd ~/frappe-bench/
bench remove-app payments --force wget https://erpnext.com/files/v10-erpnext.sql.gz
jq 'del(.install_apps)' ~/frappe-bench/sites/test_site/site_config.json > tmp.json bench --site test_site --force restore ~/frappe-bench/v10-erpnext.sql.gz
mv tmp.json ~/frappe-bench/sites/test_site/site_config.json
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
function update_to_version() {
version=$1
branch_name="version-$version-hotfix"
echo "Updating to v$version"
# Fetch and checkout branches
git -C "apps/frappe" fetch --depth 1 upstream $branch_name:$branch_name
git -C "apps/erpnext" fetch --depth 1 upstream $branch_name:$branch_name
git -C "apps/frappe" checkout -q -f $branch_name
git -C "apps/erpnext" checkout -q -f $branch_name
# Resetup env and install apps
pgrep honcho | xargs kill
rm -rf ~/frappe-bench/env
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 14
echo "Updating to latest version"
git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA"
pgrep honcho | xargs kill
rm -rf ~/frappe-bench/env
bench -v setup env
bench pip install -e ./apps/erpnext
bench start &>> ~/frappe-bench/bench_start.log &
bench --site test_site migrate bench --site test_site migrate
- name: Show bench output
if: ${{ always() }}
run: |
cd ~/frappe-bench
cat bench_start.log || true
cd logs
for f in ./*.log*; do
echo "Printing log: $f";
cat $f
done

View File

@@ -1,33 +0,0 @@
name: Generate Semantic Release
on:
push:
branches:
- version-15
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout Entire Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 20
- name: Setup dependencies
run: |
npm install @semantic-release/git @semantic-release/exec --no-save
- name: Create Release
env:
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
GIT_AUTHOR_NAME: "Frappe PR Bot"
GIT_AUTHOR_EMAIL: "developers@frappe.io"
GIT_COMMITTER_NAME: "Frappe PR Bot"
GIT_COMMITTER_EMAIL: "developers@frappe.io"
run: npx semantic-release

View File

@@ -1,38 +0,0 @@
# This action:
#
# 1. Generates release notes using github API.
# 2. Strips unnecessary info like chore/style etc from notes.
# 3. Updates release info.
# This action needs to be maintained on all branches that do releases.
name: 'Release Notes'
on:
workflow_dispatch:
inputs:
tag_name:
description: 'Tag of release like v13.0.0'
required: true
type: string
release:
types: [released]
permissions:
contents: read
jobs:
regen-notes:
name: 'Regenerate release notes'
runs-on: ubuntu-latest
steps:
- name: Update notes
run: |
NEW_NOTES=$(gh api --method POST -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/generate-notes -f tag_name=$RELEASE_TAG | jq -r '.body' | sed -E '/^\* (chore|ci|test|docs|style)/d' )
RELEASE_ID=$(gh api -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/tags/$RELEASE_TAG | jq -r '.id')
gh api --method PATCH -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/$RELEASE_ID -f body="$NEW_NOTES"
env:
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
RELEASE_TAG: ${{ github.event.inputs.tag_name || github.event.release.tag_name }}

View File

@@ -1,30 +0,0 @@
name: Semantic Commits
on:
pull_request: {}
permissions:
contents: read
concurrency:
group: commitcheck-erpnext-${{ github.event.number }}
cancel-in-progress: true
jobs:
commitlint:
name: Check Commit Titles
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 200
- uses: actions/setup-node@v3
with:
node-version: 18
check-latest: true
- name: Check commit titles
run: |
npm install @commitlint/cli @commitlint/config-conventional
npx commitlint --verbose --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }}

18
.github/workflows/semgrep.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: Semgrep
on:
pull_request: { }
jobs:
semgrep:
name: Frappe Linter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: returntocorp/semgrep-action@v1
env:
SEMGREP_TIMEOUT: 120
with:
config: >-
r/python.lang.correctness
.github/helper/semgrep_rules

View File

@@ -1,128 +0,0 @@
name: Server (Mariadb)
on:
pull_request:
paths-ignore:
- '**.js'
- '**.css'
- '**.md'
- '**.html'
schedule:
# Run everday at midnight UTC / 5:30 IST
- cron: "0 0 * * *"
workflow_dispatch:
inputs:
user:
description: 'Frappe Framework repository user (add your username for forks)'
required: true
default: 'frappe'
type: string
branch:
description: 'Frappe Framework branch'
default: 'develop'
required: false
type: string
concurrency:
group: server-mariadb-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
container: [1, 2, 3, 4]
name: Python Unit Tests
services:
mysql:
image: mariadb:10.6
env:
MARIADB_ROOT_PASSWORD: 'root'
ports:
- 3306:3306
options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3
steps:
- name: Clone
uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: '3.11'
- name: Check for valid Python & Merge Conflicts
run: |
python -m compileall -f "${GITHUB_WORKSPACE}"
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
then echo "Found merge conflicts"
exit 1
fi
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 18
check-latest: true
- name: Add to Hosts
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
env:
DB: mariadb
TYPE: server
FRAPPE_USER: ${{ github.event.inputs.user }}
FRAPPE_BRANCH: ${{ github.event.inputs.branch }}
- name: Run Tests
run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --total-builds 4 --build-number ${{ matrix.container }}'
env:
TYPE: server
CI_BUILD_ID: ${{ github.run_id }}
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io
- name: Show bench output
if: ${{ always() }}
run: cat ~/frappe-bench/bench_start.log || true

View File

@@ -1,112 +0,0 @@
name: Server (Postgres)
on:
pull_request:
paths-ignore:
- '**.js'
- '**.md'
- '**.html'
types: [opened, labelled, synchronize, reopened]
concurrency:
group: server-postgres-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }}
cancel-in-progress: true
jobs:
test:
if: ${{ contains(github.event.pull_request.labels.*.name, 'postgres') }}
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
container: [1]
name: Python Unit Tests
services:
postgres:
image: postgres:13.3
env:
POSTGRES_PASSWORD: travis
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- name: Clone
uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: '3.10'
- name: Check for valid Python & Merge Conflicts
run: |
python -m compileall -f "${GITHUB_WORKSPACE}"
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
then echo "Found merge conflicts"
exit 1
fi
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 18
check-latest: true
- name: Add to Hosts
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
env:
DB: postgres
TYPE: server
- name: Run Tests
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator
env:
TYPE: server
CI_BUILD_ID: ${{ github.run_id }}
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io

110
.github/workflows/server-tests.yml vendored Normal file
View File

@@ -0,0 +1,110 @@
name: Server
on: [pull_request, workflow_dispatch]
jobs:
test:
runs-on: ubuntu-18.04
strategy:
fail-fast: false
matrix:
container: [1, 2, 3]
name: Python Unit Tests
services:
mysql:
image: mariadb:10.3
env:
MYSQL_ALLOW_EMPTY_PASSWORD: YES
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
steps:
- name: Clone
uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: 3.7
- name: Add to Hosts
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
- name: Run Tests
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage
env:
TYPE: server
CI_BUILD_ID: ${{ github.run_id }}
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io
- name: Upload Coverage Data
run: |
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
cd ${GITHUB_WORKSPACE}
pip3 install coverage==5.5
pip3 install coveralls==3.0.1
coveralls
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_FLAG_NAME: run-${{ matrix.container }}
COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }}
COVERALLS_PARALLEL: true
coveralls:
name: Coverage Wrap Up
needs: test
container: python:3-slim
runs-on: ubuntu-18.04
steps:
- name: Clone
uses: actions/checkout@v2
- name: Coveralls Finished
run: |
cd ${GITHUB_WORKSPACE}
pip3 install coverage==5.5
pip3 install coveralls==3.0.1
coveralls --finish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -0,0 +1,22 @@
name: Frappe Linter
on:
pull_request:
branches:
- develop
- version-12-hotfix
- version-11-hotfix
jobs:
check_translation:
name: Translation Syntax Check
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Setup python3
uses: actions/setup-python@v1
with:
python-version: 3.6
- name: Validating Translation Syntax
run: |
git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
python $GITHUB_WORKSPACE/.github/helper/translation.py $files

108
.github/workflows/ui-tests.yml vendored Normal file
View File

@@ -0,0 +1,108 @@
name: UI
on:
pull_request:
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-18.04
strategy:
fail-fast: false
name: UI Tests (Cypress)
services:
mysql:
image: mariadb:10.3
env:
MYSQL_ALLOW_EMPTY_PASSWORD: YES
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
steps:
- name: Clone
uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: 3.7
- uses: actions/setup-node@v2
with:
node-version: 14
check-latest: true
- name: Add to Hosts
run: |
echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Cache cypress binary
uses: actions/cache@v2
with:
path: ~/.cache
key: ${{ runner.os }}-cypress-
restore-keys: |
${{ runner.os }}-cypress-
${{ runner.os }}-
- name: Install
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
env:
DB: mariadb
TYPE: ui
- name: Site Setup
run: cd ~/frappe-bench/ && bench --site test_site execute erpnext.setup.utils.before_tests
- name: cypress pre-requisites
run: cd ~/frappe-bench/apps/frappe && yarn add cypress-file-upload@^5 --no-lockfile
- name: Build Assets
run: cd ~/frappe-bench/ && bench build
- name: UI Tests
run: cd ~/frappe-bench/ && bench --site test_site run-ui-tests erpnext --headless
env:
CYPRESS_RECORD_KEY: 60a8e3bf-08f5-45b1-9269-2b207d7d30cd
- name: Show bench console if tests failed
if: ${{ failure() }}
run: cat ~/frappe-bench/bench_run_logs.txt

2
.gitignore vendored
View File

@@ -7,7 +7,6 @@ latest_updates.json
.wnf-lang-status .wnf-lang-status
*.egg-info *.egg-info
dist/ dist/
erpnext/public/dist
erpnext/docs/current erpnext/docs/current
*.swp *.swp
*.swo *.swo
@@ -16,4 +15,3 @@ __pycache__
.idea/ .idea/
.vscode/ .vscode/
node_modules/ node_modules/
.backportrc.json

View File

@@ -1,139 +0,0 @@
pull_request_rules:
- name: Auto-close PRs on stable branch
conditions:
- and:
- and:
- author!=surajshetty3416
- author!=gavindsouza
- author!=rohitwaghchaure
- author!=nabinhait
- author!=ankush
- author!=deepeshgarg007
- author!=frappe-pr-bot
- author!=mergify[bot]
- or:
- base=version-13
- base=version-12
- base=version-14
- base=version-15
actions:
close:
comment:
message: |
@{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch.
https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch
- name: Auto-close PRs on pre-release branch
conditions:
- base=version-13-pre-release
actions:
close:
comment:
message: |
@{{author}}, pre-release branch is not maintained anymore. Releases are directly done by merging hotfix branch to stable branches.
- name: backport to develop
conditions:
- label="backport develop"
actions:
backport:
branches:
- develop
assignees:
- "{{ author }}"
- name: backport to version-14-hotfix
conditions:
- label="backport version-14-hotfix"
actions:
backport:
branches:
- version-14-hotfix
assignees:
- "{{ author }}"
- name: backport to version-14-pre-release
conditions:
- label="backport version-14-pre-release"
actions:
backport:
branches:
- version-14-pre-release
assignees:
- "{{ author }}"
- name: backport to version-13-hotfix
conditions:
- label="backport version-13-hotfix"
actions:
backport:
branches:
- version-13-hotfix
assignees:
- "{{ author }}"
- name: backport to version-13-pre-release
conditions:
- label="backport version-13-pre-release"
actions:
backport:
branches:
- version-13-pre-release
assignees:
- "{{ author }}"
- name: backport to version-12-hotfix
conditions:
- label="backport version-12-hotfix"
actions:
backport:
branches:
- version-12-hotfix
assignees:
- "{{ author }}"
- name: backport to version-12-pre-release
conditions:
- label="backport version-12-pre-release"
actions:
backport:
branches:
- version-12-pre-release
assignees:
- "{{ author }}"
- name: Automatic merge on CI success and review
conditions:
- status-success=linters
- status-success=Sider
- status-success=Semantic Pull Request
- status-success=Patch Test
- status-success=Python Unit Tests (1)
- status-success=Python Unit Tests (2)
- status-success=Python Unit Tests (3)
- label!=dont-merge
- label!=squash
- "#approved-reviews-by>=1"
actions:
merge:
method: merge
- name: Automatic squash on CI success and review
conditions:
- status-success=linters
- status-success=Sider
- status-success=Patch Test
- status-success=Python Unit Tests (1)
- status-success=Python Unit Tests (2)
- status-success=Python Unit Tests (3)
- label!=dont-merge
- label=squash
- "#approved-reviews-by>=1"
actions:
merge:
method: squash
commit_message_template: |
{{ title }} (#{{ number }})
{{ body }}

View File

@@ -1,85 +0,0 @@
exclude: 'node_modules|.git'
default_stages: [commit]
fail_fast: false
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: trailing-whitespace
files: "erpnext.*"
exclude: ".*json$|.*txt$|.*csv|.*md"
- id: check-yaml
- id: no-commit-to-branch
args: ['--branch', 'develop']
- id: check-merge-conflict
- id: check-ast
- id: check-json
- id: check-toml
- id: check-yaml
- id: debug-statements
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.7.1
hooks:
- id: prettier
types_or: [javascript, vue, scss]
# Ignore any files that might contain jinja / bundles
exclude: |
(?x)^(
erpnext/public/dist/.*|
cypress/.*|
.*node_modules.*|
.*boilerplate.*|
erpnext/public/js/controllers/.*|
erpnext/templates/pages/order.js|
erpnext/templates/includes/.*
)$
- repo: https://github.com/pre-commit/mirrors-eslint
rev: v8.44.0
hooks:
- id: eslint
types_or: [javascript]
args: ['--quiet']
# Ignore any files that might contain jinja / bundles
exclude: |
(?x)^(
erpnext/public/dist/.*|
cypress/.*|
.*node_modules.*|
.*boilerplate.*|
erpnext/public/js/controllers/.*|
erpnext/templates/pages/order.js|
erpnext/templates/includes/.*
)$
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
hooks:
- id: flake8
additional_dependencies: [
'flake8-bugbear',
'flake8-tuple',
]
args: ['--config', '.github/helper/.flake8_strict']
exclude: ".*setup.py$"
- repo: https://github.com/adityahase/black
rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119
hooks:
- id: black
additional_dependencies: ['click==8.0.4']
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
exclude: ".*setup.py$"
ci:
autoupdate_schedule: weekly
skip: []
submodules: false

View File

@@ -1,24 +0,0 @@
{
"branches": ["version-15"],
"plugins": [
"@semantic-release/commit-analyzer", {
"preset": "angular",
"releaseRules": [
{"breaking": true, "release": false}
]
},
"@semantic-release/release-notes-generator",
[
"@semantic-release/exec", {
"prepareCmd": 'sed -ir "s/[0-9]*\.[0-9]*\.[0-9]*/${nextRelease.version}/" erpnext/__init__.py'
}
],
[
"@semantic-release/git", {
"assets": ["erpnext/__init__.py"],
"message": "chore(release): Bumped to Version ${nextRelease.version}\n\n${nextRelease.notes}"
}
],
"@semantic-release/github"
]
}

8
.snyk Normal file
View File

@@ -0,0 +1,8 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.14.0
ignore: {}
# patches apply the minimum changes required to fix a vulnerability
patch:
SNYK-JS-LODASH-450202:
- cypress > getos > async > lodash:
patched: '2020-01-31T01:35:12.802Z'

View File

@@ -3,22 +3,33 @@
# These owners will be the default owners for everything in # These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence, # the repo. Unless a later match takes precedence,
erpnext/accounts/ @deepeshgarg007 @ruthra-kumar erpnext/accounts/ @nextchamp-saqib @deepeshgarg007
erpnext/assets/ @anandbaburajan @deepeshgarg007 erpnext/assets/ @nextchamp-saqib @deepeshgarg007
erpnext/regional @deepeshgarg007 @ruthra-kumar erpnext/erpnext_integrations/ @nextchamp-saqib
erpnext/selling @deepeshgarg007 @ruthra-kumar erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
erpnext/support/ @deepeshgarg007 erpnext/regional @nextchamp-saqib @deepeshgarg007
pos* erpnext/selling @nextchamp-saqib @deepeshgarg007
erpnext/support/ @nextchamp-saqib @deepeshgarg007
pos* @nextchamp-saqib
erpnext/buying/ @rohitwaghchaure @s-aga-r erpnext/buying/ @marination @rohitwaghchaure @ankush
erpnext/maintenance/ @rohitwaghchaure @s-aga-r erpnext/e_commerce/ @marination
erpnext/manufacturing/ @rohitwaghchaure @s-aga-r erpnext/maintenance/ @marination @rohitwaghchaure
erpnext/quality_management/ @rohitwaghchaure @s-aga-r erpnext/manufacturing/ @marination @rohitwaghchaure @ankush
erpnext/stock/ @rohitwaghchaure @s-aga-r erpnext/portal/ @marination
erpnext/subcontracting @rohitwaghchaure @s-aga-r erpnext/quality_management/ @marination @rohitwaghchaure
erpnext/shopping_cart/ @marination
erpnext/stock/ @marination @rohitwaghchaure @ankush
erpnext/controllers/ @deepeshgarg007 @rohitwaghchaure erpnext/crm/ @ruchamahabal @pateljannat
erpnext/patches/ @deepeshgarg007 erpnext/education/ @ruchamahabal @pateljannat
erpnext/healthcare/ @ruchamahabal @pateljannat @chillaranand
erpnext/hr/ @ruchamahabal @pateljannat
erpnext/non_profit/ @ruchamahabal
erpnext/payroll @ruchamahabal @pateljannat
erpnext/projects/ @ruchamahabal @pateljannat
.github/ @deepeshgarg007 erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
pyproject.toml @phot0n
.github/ @surajshetty3416 @ankush
requirements.txt @gavindsouza

View File

@@ -1,17 +1,13 @@
<div align="center"> <div align="center">
<a href="https://erpnext.com"> <img src="https://raw.githubusercontent.com/frappe/erpnext/develop/erpnext/public/images/erpnext-logo.png" height="128">
<img src="https://raw.githubusercontent.com/frappe/erpnext/develop/erpnext/public/images/erpnext-logo.png" height="128">
</a>
<h2>ERPNext</h2> <h2>ERPNext</h2>
<p align="center"> <p align="center">
<p>ERP made simple</p> <p>ERP made simple</p>
</p> </p>
[![CI](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml/badge.svg?branch=develop)](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml) [![CI](https://github.com/frappe/erpnext/actions/workflows/ci-tests.yml/badge.svg?branch=develop)](https://github.com/frappe/erpnext/actions/workflows/ci-tests.yml)
[![UI](https://github.com/erpnext/erpnext_ui_tests/actions/workflows/ui-tests.yml/badge.svg?branch=develop&event=schedule)](https://github.com/erpnext/erpnext_ui_tests/actions/workflows/ui-tests.yml)
[![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext) [![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext)
[![codecov](https://codecov.io/gh/frappe/erpnext/branch/develop/graph/badge.svg?token=0TwvyUg3I5)](https://codecov.io/gh/frappe/erpnext) [![Coverage Status](https://coveralls.io/repos/github/frappe/erpnext/badge.svg?branch=develop)](https://coveralls.io/github/frappe/erpnext?branch=develop)
[![docker pulls](https://img.shields.io/docker/pulls/frappe/erpnext-worker.svg)](https://hub.docker.com/r/frappe/erpnext-worker)
[https://erpnext.com](https://erpnext.com) [https://erpnext.com](https://erpnext.com)
@@ -34,47 +30,34 @@ ERPNext as a monolith includes the following areas for managing businesses:
1. [Customize ERPNext](https://erpnext.com/docs/user/manual/en/customize-erpnext) 1. [Customize ERPNext](https://erpnext.com/docs/user/manual/en/customize-erpnext)
1. [And More](https://erpnext.com/docs/user/manual/en/) 1. [And More](https://erpnext.com/docs/user/manual/en/)
ERPNext requires MariaDB.
ERPNext is built on the [Frappe Framework](https://github.com/frappe/frappe), a full-stack web app framework built with Python & JavaScript. ERPNext is built on the [Frappe Framework](https://github.com/frappe/frappe), a full-stack web app framework built with Python & JavaScript.
## Installation - [User Guide](https://erpnext.com/docs/user)
- [Discussion Forum](https://discuss.erpnext.com/)
<div align="center" style="max-height: 40px;"> ---
<a href="https://frappecloud.com/erpnext/signup">
<img src=".github/try-on-f-cloud-button.svg" height="40">
</a>
<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" height="37"/>
</a>
</div>
> Login for the PWD site: (username: Administrator, password: admin)
### Containerized Installation ### Containerized Installation
Use docker to deploy ERPNext in production or for development of [Frappe](https://github.com/frappe/frappe) apps. See https://github.com/frappe/frappe_docker for more details. Use docker to deploy ERPNext in production or for development of [Frappe](https://github.com/frappe/frappe) apps. See https://github.com/frappe/frappe_docker for more details.
### Manual Install ### Full Install
The Easy Way: our install script for bench will install all dependencies (e.g. MariaDB). See https://github.com/frappe/bench for more details. 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).
### Virtual Image
## Learning and community You can download a virtual image to run ERPNext in a virtual machine on your local system.
1. [Frappe School](https://frappe.school) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community. - [ERPNext Download](http://erpnext.com/download)
2. [Official documentation](https://docs.erpnext.com/) - Extensive documentation for ERPNext.
3. [Discussion Forum](https://discuss.erpnext.com/) - 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.
System and user credentials are listed on the download page.
## Contributing ---
1. [Issue Guidelines](https://github.com/frappe/erpnext/wiki/Issue-Guidelines)
1. [Report Security Vulnerabilities](https://erpnext.com/security)
1. [Pull Request Requirements](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines)
1. [Translations](https://translate.erpnext.com)
## License ## License
@@ -82,8 +65,51 @@ GNU/General Public License (see [license.txt](license.txt))
The ERPNext code is licensed as GNU General Public License (v3) and the Documentation is licensed as Creative Commons (CC-BY-SA-3.0) and the copyright is owned by Frappe Technologies Pvt Ltd (Frappe) and Contributors. The ERPNext code is licensed as GNU General Public License (v3) and the Documentation is licensed as Creative Commons (CC-BY-SA-3.0) and the copyright is owned by Frappe Technologies Pvt Ltd (Frappe) and Contributors.
By contributing to ERPNext, you agree that your contributions will be licensed under its GNU General Public License (v3). ---
## Logo and Trademark Policy ## Contributing
Please read our [Logo and Trademark Policy](TRADEMARK_POLICY.md). 1. [Issue Guidelines](https://github.com/frappe/erpnext/wiki/Issue-Guidelines)
1. [Report Security Vulnerabilities](https://erpnext.com/security)
1. [Pull Request Requirements](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines)
1. [Translations](https://translate.erpnext.com)
1. [Chart of Accounts](https://charts.erpnext.com)
---
## Logo and Trademark
The brand name ERPNext and the logo are trademarks of Frappe Technologies Pvt. Ltd.
### Introduction
Frappe Technologies Pvt. Ltd. (Frappe) owns and oversees the trademarks for the ERPNext name and logos. We have developed this trademark usage policy with the following goals in mind:
- Wed like to make it easy for anyone to use the ERPNext name or logo for community-oriented efforts that help spread and improve ERPNext.
- Wed like to make it clear how ERPNext-related businesses and projects can (and cannot) use the ERPNext name and logo.
- Wed like to make it hard for anyone to use the ERPNext name and logo to unfairly profit from, trick or confuse people who are looking for official ERPNext resources.
### Frappe Trademark Usage Policy
Permission from Frappe is required to use the ERPNext name or logo as part of any project, product, service, domain or company name.
We will grant permission to use the ERPNext name and logo for projects that meet the following criteria:
- 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.
Use of the ERPNext name and logo is additionally allowed in the following situations:
All other ERPNext-related businesses or projects can use the ERPNext name and logo to refer to and explain their services, but they cannot use them as part of a product, project, service, domain, or company name and they cannot use them in any way that suggests an affiliation with or endorsement by ERPNext or Frappe Technologies or the ERPNext open source project. For example, a consulting company can describe its business as “123 Web Services, offering ERPNext consulting for small businesses,” but cannot call its business “The ERPNext Consulting Company.”
Similarly, its OK to use the ERPNext logo as part of a page that describes your products or services, but it is not OK to use it as part of your company or product logo or branding itself. Under no circumstances is it permitted to use ERPNext as part of a top-level domain name.
We do not allow the use of the trademark in advertising, including AdSense/AdWords.
Please note that it is not the goal of this policy to limit commercial activity around ERPNext. We encourage ERPNext-based businesses, and we would love to see hundreds of them.
When in doubt about your use of the ERPNext name or logo, please contact Frappe Technologies for clarification.
(inspired by WordPress)

View File

@@ -1,36 +0,0 @@
## Logo and Trademark Policy
The brand name ERPNext and the logo are trademarks of Frappe Technologies Pvt. Ltd.
### Introduction
Frappe Technologies Pvt. Ltd. (Frappe) owns and oversees the trademarks for the ERPNext name and logos. We have developed this trademark usage policy with the following goals in mind:
- Wed like to make it easy for anyone to use the ERPNext name or logo for community-oriented efforts that help spread and improve ERPNext.
- Wed like to make it clear how ERPNext-related businesses and projects can (and cannot) use the ERPNext name and logo.
- Wed like to make it hard for anyone to use the ERPNext name and logo to unfairly profit from, trick or confuse people who are looking for official ERPNext resources.
### Frappe Trademark Usage Policy
Permission from Frappe is required to use the ERPNext name or logo as part of any project, product, service, domain or company name.
We will grant permission to use the ERPNext name and logo for projects that meet the following criteria:
- 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.
Use of the ERPNext name and logo is additionally allowed in the following situations:
All other ERPNext-related businesses or projects can use the ERPNext name and logo to refer to and explain their services, but they cannot use them as part of a product, project, service, domain, or company name and they cannot use them in any way that suggests an affiliation with or endorsement by ERPNext or Frappe Technologies or the ERPNext open source project. For example, a consulting company can describe its business as “123 Web Services, offering ERPNext consulting for small businesses,” but cannot call its business “The ERPNext Consulting Company.”
Similarly, its OK to use the ERPNext logo as part of a page that describes your products or services, but it is not OK to use it as part of your company or product logo or branding itself. Under no circumstances is it permitted to use ERPNext as part of a top-level domain name.
We do not allow the use of the trademark in advertising, including AdSense/AdWords.
Please note that it is not the goal of this policy to limit commercial activity around ERPNext. We encourage ERPNext-based businesses, and we would love to see hundreds of them.
When in doubt about your use of the ERPNext name or logo, please contact Frappe Technologies for clarification.
(inspired by WordPress)

View File

@@ -1,26 +0,0 @@
codecov:
require_ci_to_pass: yes
coverage:
status:
project:
default:
target: auto
threshold: 0.5%
patch:
default:
target: 85%
threshold: 0%
base: auto
branches:
- develop
if_ci_failed: ignore
only_pulls: true
comment:
layout: "diff, files"
require_changes: true
ignore:
- "erpnext/demo"

View File

@@ -1,13 +0,0 @@
module.exports = {
parserPreset: "conventional-changelog-conventionalcommits",
rules: {
"subject-empty": [2, "never"],
"type-case": [2, "always", "lower-case"],
"type-empty": [2, "never"],
"type-enum": [
2,
"always",
["build", "chore", "ci", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test"],
],
},
};

11
cypress.json Normal file
View File

@@ -0,0 +1,11 @@
{
"baseUrl": "http://test_site:8000",
"projectId": "da59y9",
"adminPassword": "admin",
"defaultCommandTimeout": 20000,
"pageLoadTimeout": 15000,
"retries": {
"runMode": 2,
"openMode": 2
}
}

View File

@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -0,0 +1,13 @@
context('Customer', () => {
before(() => {
cy.login();
});
it('Check Customer Group', () => {
cy.visit(`app/customer/`);
cy.get('.primary-action').click();
cy.wait(500);
cy.get('.custom-actions > .btn').click();
cy.get_field('customer_group', 'Link').should('have.value', 'All Customer Groups');
});
});

View File

@@ -0,0 +1,111 @@
context('Organizational Chart', () => {
before(() => {
cy.login();
cy.visit('/app/website');
cy.awesomebar('Organizational Chart');
cy.window().its('frappe.csrf_token').then(csrf_token => {
return cy.request({
url: `/api/method/erpnext.tests.ui_test_helpers.create_employee_records`,
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-Frappe-CSRF-Token': csrf_token
},
timeout: 60000
}).then(res => {
expect(res.status).eq(200);
cy.get('.frappe-control[data-fieldname=company] input').focus().as('input');
cy.get('@input')
.clear({ force: true })
.type('Test Org Chart{enter}', { force: true })
.blur({ force: true });
});
});
});
it('renders root nodes and loads children for the first expandable node', () => {
// check rendered root nodes and the node name, title, connections
cy.get('.hierarchy').find('.root-level ul.node-children').children()
.should('have.length', 2)
.first()
.as('first-child');
cy.get('@first-child').get('.node-name').contains('Test Employee 1');
cy.get('@first-child').get('.node-info').find('.node-title').contains('CEO');
cy.get('@first-child').get('.node-info').find('.node-connections').contains('· 2 Connections');
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
// children of 1st root visible
cy.get(`div[data-parent="${employee_records.message[0]}"]`).as('child-node');
cy.get('@child-node')
.should('have.length', 1)
.should('be.visible');
cy.get('@child-node').get('.node-name').contains('Test Employee 3');
// connectors between first root node and immediate child
cy.get(`path[data-parent="${employee_records.message[0]}"]`)
.should('be.visible')
.invoke('attr', 'data-child')
.should('equal', employee_records.message[2]);
});
});
it('hides active nodes children and connectors on expanding sibling node', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
// click sibling
cy.get(`#${employee_records.message[1]}`)
.click()
.should('have.class', 'active');
// child nodes and connectors hidden
cy.get(`[data-parent="${employee_records.message[0]}"]`).should('not.be.visible');
cy.get(`path[data-parent="${employee_records.message[0]}"]`).should('not.be.visible');
});
});
it('collapses previous level nodes and refreshes connectors on expanding child node', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
// click child node
cy.get(`#${employee_records.message[3]}`)
.click()
.should('have.class', 'active');
// previous level nodes: parent should be on active-path; other nodes should be collapsed
cy.get(`#${employee_records.message[0]}`).should('have.class', 'collapsed');
cy.get(`#${employee_records.message[1]}`).should('have.class', 'active-path');
// previous level connectors refreshed
cy.get(`path[data-parent="${employee_records.message[1]}"]`)
.should('have.class', 'collapsed-connector');
// child node's children and connectors rendered
cy.get(`[data-parent="${employee_records.message[3]}"]`).should('be.visible');
cy.get(`path[data-parent="${employee_records.message[3]}"]`).should('be.visible');
});
});
it('expands previous level nodes', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
cy.get(`#${employee_records.message[0]}`)
.click()
.should('have.class', 'active');
cy.get(`[data-parent="${employee_records.message[0]}"]`)
.should('be.visible');
cy.get('ul.hierarchy').children().should('have.length', 2);
cy.get(`#connectors`).children().should('have.length', 1);
});
});
it('edit node navigates to employee master', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
cy.get(`#${employee_records.message[0]}`).find('.btn-edit-node')
.click();
cy.url().should('include', `/employee/${employee_records.message[0]}`);
});
});
});

View File

@@ -0,0 +1,190 @@
context('Organizational Chart Mobile', () => {
before(() => {
cy.login();
cy.viewport(375, 667);
cy.visit('/app/website');
cy.awesomebar('Organizational Chart');
cy.window().its('frappe.csrf_token').then(csrf_token => {
return cy.request({
url: `/api/method/erpnext.tests.ui_test_helpers.create_employee_records`,
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-Frappe-CSRF-Token': csrf_token
},
timeout: 60000
}).then(res => {
expect(res.status).eq(200);
cy.get('.frappe-control[data-fieldname=company] input').focus().as('input');
cy.get('@input')
.clear({ force: true })
.type('Test Org Chart{enter}', { force: true })
.blur({ force: true });
});
});
});
it('renders root nodes', () => {
// check rendered root nodes and the node name, title, connections
cy.get('.hierarchy-mobile').find('.root-level').children()
.should('have.length', 2)
.first()
.as('first-child');
cy.get('@first-child').get('.node-name').contains('Test Employee 1');
cy.get('@first-child').get('.node-info').find('.node-title').contains('CEO');
cy.get('@first-child').get('.node-info').find('.node-connections').contains('· 2');
});
it('expands root node', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
cy.get(`#${employee_records.message[1]}`)
.click()
.should('have.class', 'active');
// other root node removed
cy.get(`#${employee_records.message[0]}`).should('not.exist');
// children of active root node
cy.get('.hierarchy-mobile').find('.level').first().find('ul.node-children').children()
.should('have.length', 2);
cy.get(`div[data-parent="${employee_records.message[1]}"]`).first().as('child-node');
cy.get('@child-node').should('be.visible');
cy.get('@child-node')
.get('.node-name')
.contains('Test Employee 4');
// connectors between root node and immediate children
cy.get(`path[data-parent="${employee_records.message[1]}"]`).as('connectors');
cy.get('@connectors')
.should('have.length', 2)
.should('be.visible');
cy.get('@connectors')
.first()
.invoke('attr', 'data-child')
.should('eq', employee_records.message[3]);
});
});
it('expands child node', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
cy.get(`#${employee_records.message[3]}`)
.click()
.should('have.class', 'active')
.as('expanded_node');
// 2 levels on screen; 1 on active path; 1 collapsed
cy.get('.hierarchy-mobile').children().should('have.length', 2);
cy.get(`#${employee_records.message[1]}`).should('have.class', 'active-path');
// children of expanded node visible
cy.get('@expanded_node')
.next()
.should('have.class', 'node-children')
.as('node-children');
cy.get('@node-children').children().should('have.length', 1);
cy.get('@node-children')
.first()
.get('.node-card')
.should('have.class', 'active-child')
.contains('Test Employee 7');
// orphan connectors removed
cy.get(`#connectors`).children().should('have.length', 2);
});
});
it('renders sibling group', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
// sibling group visible for parent
cy.get(`#${employee_records.message[1]}`)
.next()
.as('sibling_group');
cy.get('@sibling_group')
.should('have.attr', 'data-parent', 'undefined')
.should('have.class', 'node-group')
.and('have.class', 'collapsed');
cy.get('@sibling_group').get('.avatar-group').children().as('siblings');
cy.get('@siblings').should('have.length', 1);
cy.get('@siblings')
.first()
.should('have.attr', 'title', 'Test Employee 1');
});
});
it('expands previous level nodes', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
cy.get(`#${employee_records.message[6]}`)
.click()
.should('have.class', 'active');
// clicking on previous level node should remove all the nodes ahead
// and expand that node
cy.get(`#${employee_records.message[3]}`).click();
cy.get(`#${employee_records.message[3]}`)
.should('have.class', 'active')
.should('not.have.class', 'active-path');
cy.get(`#${employee_records.message[6]}`).should('have.class', 'active-child');
cy.get('.hierarchy-mobile').children().should('have.length', 2);
cy.get(`#connectors`).children().should('have.length', 2);
});
});
it('expands sibling group', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
// sibling group visible for parent
cy.get(`#${employee_records.message[6]}`).click();
cy.get(`#${employee_records.message[3]}`)
.next()
.click();
// siblings of parent should be visible
cy.get('.hierarchy-mobile').prev().as('sibling_group');
cy.get('@sibling_group')
.should('exist')
.should('have.class', 'sibling-group')
.should('not.have.class', 'collapsed');
cy.get(`#${employee_records.message[1]}`)
.should('be.visible')
.should('have.class', 'active');
cy.get(`[data-parent="${employee_records.message[1]}"]`)
.should('be.visible')
.should('have.length', 2)
.should('have.class', 'active-child');
});
});
it('goes to the respective level after clicking on non-collapsed sibling group', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(() => {
// click on non-collapsed sibling group
cy.get('.hierarchy-mobile')
.prev()
.click();
// should take you to that level
cy.get('.hierarchy-mobile').find('li.level .node-card').should('have.length', 2);
});
});
it('edit node navigates to employee master', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
cy.get(`#${employee_records.message[0]}`).find('.btn-edit-node')
.click();
cy.url().should('include', `/employee/${employee_records.message[0]}`);
});
});
});

17
cypress/plugins/index.js Normal file
View File

@@ -0,0 +1,17 @@
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
module.exports = () => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
};

View File

@@ -0,0 +1,31 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... });
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... });
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... });
//
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... });
const slug = (name) => name.toLowerCase().replace(" ", "-");
Cypress.Commands.add("go_to_doc", (doctype, name) => {
cy.visit(`/app/${slug(doctype)}/${encodeURIComponent(name)}`);
});

26
cypress/support/index.js Normal file
View File

@@ -0,0 +1,26 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands';
import '../../../frappe/cypress/support/commands' // eslint-disable-line
// Alternatively you can use CommonJS syntax:
// require('./commands')
Cypress.Cookies.defaults({
preserve: 'sid'
});

12
cypress/tsconfig.json Normal file
View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"allowJs": true,
"baseUrl": "../node_modules",
"types": [
"cypress"
]
},
"include": [
"**/*.*"
]
}

View File

@@ -6,4 +6,4 @@
"scss/at-rule-no-unknown": true, "scss/at-rule-no-unknown": true,
"no-descending-specificity": null "no-descending-specificity": null
} }
} }

View File

@@ -1,59 +1,53 @@
import functools # -*- coding: utf-8 -*-
from __future__ import unicode_literals
import inspect import inspect
import frappe import frappe
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
__version__ = "15.16.2" __version__ = '13.2.0'
def get_default_company(user=None): def get_default_company(user=None):
"""Get default company for user""" '''Get default company for user'''
from frappe.defaults import get_user_default_as_list from frappe.defaults import get_user_default_as_list
if not user: if not user:
user = frappe.session.user user = frappe.session.user
companies = get_user_default_as_list("company", user) companies = get_user_default_as_list(user, 'company')
if companies: if companies:
default_company = companies[0] default_company = companies[0]
else: else:
default_company = frappe.db.get_single_value("Global Defaults", "default_company") default_company = frappe.db.get_single_value('Global Defaults', 'default_company')
return default_company return default_company
def get_default_currency(): def get_default_currency():
"""Returns the currency of the default company""" '''Returns the currency of the default company'''
company = get_default_company() company = get_default_company()
if company: if company:
return frappe.get_cached_value("Company", company, "default_currency") return frappe.get_cached_value('Company', company, 'default_currency')
def get_default_cost_center(company): def get_default_cost_center(company):
"""Returns the default cost center of the company""" '''Returns the default cost center of the company'''
if not company: if not company:
return None return None
if not frappe.flags.company_cost_center: if not frappe.flags.company_cost_center:
frappe.flags.company_cost_center = {} frappe.flags.company_cost_center = {}
if not company in frappe.flags.company_cost_center: if not company in frappe.flags.company_cost_center:
frappe.flags.company_cost_center[company] = frappe.get_cached_value( frappe.flags.company_cost_center[company] = frappe.get_cached_value('Company', company, 'cost_center')
"Company", company, "cost_center"
)
return frappe.flags.company_cost_center[company] return frappe.flags.company_cost_center[company]
def get_company_currency(company): def get_company_currency(company):
"""Returns the default company currency""" '''Returns the default company currency'''
if not frappe.flags.company_currency: if not frappe.flags.company_currency:
frappe.flags.company_currency = {} frappe.flags.company_currency = {}
if not company in frappe.flags.company_currency: if not company in frappe.flags.company_currency:
frappe.flags.company_currency[company] = frappe.db.get_value( frappe.flags.company_currency[company] = frappe.db.get_value('Company', company, 'default_currency', cache=True)
"Company", company, "default_currency", cache=True
)
return frappe.flags.company_currency[company] return frappe.flags.company_currency[company]
def set_perpetual_inventory(enable=1, company=None): def set_perpetual_inventory(enable=1, company=None):
if not company: if not company:
company = "_Test Company" if frappe.flags.in_test else get_default_company() company = "_Test Company" if frappe.flags.in_test else get_default_company()
@@ -62,10 +56,9 @@ def set_perpetual_inventory(enable=1, company=None):
company.enable_perpetual_inventory = enable company.enable_perpetual_inventory = enable
company.save() company.save()
def encode_company_abbr(name, company):
def encode_company_abbr(name, company=None, abbr=None): '''Returns name encoded with company abbreviation'''
"""Returns name encoded with company abbreviation""" company_abbr = frappe.get_cached_value('Company', company, "abbr")
company_abbr = abbr or frappe.get_cached_value("Company", company, "abbr")
parts = name.rsplit(" - ", 1) parts = name.rsplit(" - ", 1)
if parts[-1].lower() != company_abbr.lower(): if parts[-1].lower() != company_abbr.lower():
@@ -73,81 +66,76 @@ def encode_company_abbr(name, company=None, abbr=None):
return " - ".join(parts) return " - ".join(parts)
def is_perpetual_inventory_enabled(company): def is_perpetual_inventory_enabled(company):
if not company: if not company:
company = "_Test Company" if frappe.flags.in_test else get_default_company() company = "_Test Company" if frappe.flags.in_test else get_default_company()
if not hasattr(frappe.local, "enable_perpetual_inventory"): if not hasattr(frappe.local, 'enable_perpetual_inventory'):
frappe.local.enable_perpetual_inventory = {} frappe.local.enable_perpetual_inventory = {}
if not company in frappe.local.enable_perpetual_inventory: if not company in frappe.local.enable_perpetual_inventory:
frappe.local.enable_perpetual_inventory[company] = ( frappe.local.enable_perpetual_inventory[company] = frappe.get_cached_value('Company',
frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0 company, "enable_perpetual_inventory") or 0
)
return frappe.local.enable_perpetual_inventory[company] return frappe.local.enable_perpetual_inventory[company]
def get_default_finance_book(company=None): def get_default_finance_book(company=None):
if not company: if not company:
company = get_default_company() company = get_default_company()
if not hasattr(frappe.local, "default_finance_book"): if not hasattr(frappe.local, 'default_finance_book'):
frappe.local.default_finance_book = {} frappe.local.default_finance_book = {}
if not company in frappe.local.default_finance_book: if not company in frappe.local.default_finance_book:
frappe.local.default_finance_book[company] = frappe.get_cached_value( frappe.local.default_finance_book[company] = frappe.get_cached_value('Company',
"Company", company, "default_finance_book" company, "default_finance_book")
)
return frappe.local.default_finance_book[company] return frappe.local.default_finance_book[company]
def get_party_account_type(party_type): def get_party_account_type(party_type):
if not hasattr(frappe.local, "party_account_types"): if not hasattr(frappe.local, 'party_account_types'):
frappe.local.party_account_types = {} frappe.local.party_account_types = {}
if not party_type in frappe.local.party_account_types: if not party_type in frappe.local.party_account_types:
frappe.local.party_account_types[party_type] = ( frappe.local.party_account_types[party_type] = frappe.db.get_value("Party Type",
frappe.db.get_value("Party Type", party_type, "account_type") or "" party_type, "account_type") or ''
)
return frappe.local.party_account_types[party_type] return frappe.local.party_account_types[party_type]
def get_region(company=None): def get_region(company=None):
"""Return the default country based on flag, company or global settings '''Return the default country based on flag, company or global settings
You can also set global company flag in `frappe.flags.company` You can also set global company flag in `frappe.flags.company`
""" '''
if company or frappe.flags.company:
if not company: return frappe.get_cached_value('Company',
company = frappe.local.flags.company company or frappe.flags.company, 'country')
elif frappe.flags.country:
if company: return frappe.flags.country
return frappe.get_cached_value("Company", company, "country") else:
return frappe.get_system_settings('country')
return frappe.flags.country or frappe.get_system_settings("country")
def allow_regional(fn): def allow_regional(fn):
"""Decorator to make a function regionally overridable '''Decorator to make a function regionally overridable
Example: Example:
@erpnext.allow_regional @erpnext.allow_regional
def myfunction(): def myfunction():
pass""" pass'''
@functools.wraps(fn)
def caller(*args, **kwargs): def caller(*args, **kwargs):
overrides = frappe.get_hooks("regional_overrides", {}).get(get_region()) region = get_region()
function_path = f"{inspect.getmodule(fn).__name__}.{fn.__name__}" fn_name = inspect.getmodule(fn).__name__ + '.' + fn.__name__
if region in regional_overrides and fn_name in regional_overrides[region]:
if not overrides or function_path not in overrides: return frappe.get_attr(regional_overrides[region][fn_name])(*args, **kwargs)
else:
return fn(*args, **kwargs) return fn(*args, **kwargs)
# Priority given to last installed app
return frappe.get_attr(overrides[function_path][-1])(*args, **kwargs)
return caller return caller
def get_last_membership(member):
'''Returns last membership if exists'''
last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
dict(member=member, paid=1), order_by='to_date desc', limit=1)
if last_membership:
return last_membership[0]

View File

@@ -10,42 +10,4 @@ Entries are:
- Sales Invoice (Itemised) - Sales Invoice (Itemised)
- Purchase Invoice (Itemised) - Purchase Invoice (Itemised)
All accounting entries are stored in the `General Ledger` All accounting entries are stored in the `General Ledger`
## Payment Ledger
Transactions on Receivable and Payable Account types will also be stored in `Payment Ledger`. This is so that payment reconciliation process only requires update on this ledger.
### Key Fields
| Field | Description |
|----------------------|----------------------------------|
| `account_type` | Receivable/Payable |
| `account` | Accounting head |
| `party` | Party Name |
| `voucher_no` | Voucher No |
| `against_voucher_no` | Linked voucher(secondary effect) |
| `amount` | can be +ve/-ve |
### Design
`debit` and `credit` have been replaced with `account_type` and `amount`. `against_voucher_no` is populated for all entries. So, outstanding amount can be calculated by summing up amount only using `against_voucher_no`.
Ex:
1. Consider an invoice for ₹100 and a partial payment of ₹80 against that invoice. Payment Ledger will have following entries.
| voucher_no | against_voucher_no | amount |
|------------|--------------------|--------|
| SINV-01 | SINV-01 | 100 |
| PAY-01 | SINV-01 | -80 |
2. Reconcile a Credit Note against an invoice using a Journal Entry
An invoice for ₹100 partially reconciled against a credit of ₹70 using a Journal Entry. Payment Ledger will have the following entries.
| voucher_no | against_voucher_no | amount |
|------------|--------------------|--------|
| SINV-01 | SINV-01 | 100 |
| | | |
| CR-NOTE-01 | CR-NOTE-01 | -70 |
| | | |
| JE-01 | CR-NOTE-01 | +70 |
| JE-01 | SINV-01 | -70 |

View File

@@ -1,16 +1,11 @@
import frappe import frappe
from frappe import _ from frappe import _
from frappe.contacts.doctype.address.address import ( from frappe.contacts.doctype.address.address import Address
Address, from frappe.contacts.doctype.address.address import get_address_templates
get_address_display,
get_address_templates,
)
class ERPNextAddress(Address): class ERPNextAddress(Address):
def validate(self): def validate(self):
self.validate_reference() self.validate_reference()
self.update_compnay_address()
super(ERPNextAddress, self).validate() super(ERPNextAddress, self).validate()
def link_address(self): def link_address(self):
@@ -20,40 +15,23 @@ class ERPNextAddress(Address):
return super(ERPNextAddress, self).link_address() return super(ERPNextAddress, self).link_address()
def update_compnay_address(self):
for link in self.get("links"):
if link.link_doctype == "Company":
self.is_your_company_address = 1
def validate_reference(self): def validate_reference(self):
if self.is_your_company_address and not [ if self.is_your_company_address and not [
row for row in self.links if row.link_doctype == "Company" row for row in self.links if row.link_doctype == "Company"
]: ]:
frappe.throw( frappe.throw(_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
_("Address needs to be linked to a Company. Please add a row for Company in the Links table."), title=_("Company Not Linked"))
title=_("Company Not Linked"),
)
def on_update(self):
"""
After Address is updated, update the related 'Primary Address' on Customer.
"""
address_display = get_address_display(self.as_dict())
filters = {"customer_primary_address": self.name}
customers = frappe.db.get_all("Customer", filters=filters, as_list=True)
for customer_name in customers:
frappe.db.set_value("Customer", customer_name[0], "primary_address", address_display)
@frappe.whitelist() @frappe.whitelist()
def get_shipping_address(company, address=None): def get_shipping_address(company, address = None):
filters = [ filters = [
["Dynamic Link", "link_doctype", "=", "Company"], ["Dynamic Link", "link_doctype", "=", "Company"],
["Dynamic Link", "link_name", "=", company], ["Dynamic Link", "link_name", "=", company],
["Address", "is_your_company_address", "=", 1], ["Address", "is_your_company_address", "=", 1]
] ]
fields = ["*"] fields = ["*"]
if address and frappe.db.get_value("Dynamic Link", {"parent": address, "link_name": company}): if address and frappe.db.get_value('Dynamic Link',
{'parent': address, 'link_name': company}):
filters.append(["Address", "name", "=", address]) filters.append(["Address", "name", "=", address])
if not address: if not address:
filters.append(["Address", "is_shipping_address", "=", 1]) filters.append(["Address", "is_shipping_address", "=", 1])

View File

@@ -4,19 +4,18 @@
"creation": "2020-07-17 11:25:34.593061", "creation": "2020-07-17 11:25:34.593061",
"docstatus": 0, "docstatus": 0,
"doctype": "Dashboard Chart", "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()\"}", "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
"filters_json": "{\"period\":\"Monthly\",\"budget_against\":\"Cost Center\",\"show_cumulative\":0}", "filters_json": "{\"period\":\"Monthly\",\"budget_against\":\"Cost Center\",\"show_cumulative\":0}",
"idx": 0, "idx": 0,
"is_public": 1, "is_public": 1,
"is_standard": 1, "is_standard": 1,
"modified": "2023-07-19 13:13:13.307073", "modified": "2020-07-22 12:24:49.144210",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Budget Variance", "name": "Budget Variance",
"number_of_groups": 0, "number_of_groups": 0,
"owner": "Administrator", "owner": "Administrator",
"report_name": "Budget Variance Report", "report_name": "Budget Variance Report",
"roles": [],
"timeseries": 0, "timeseries": 0,
"type": "Bar", "type": "Bar",
"use_report_chart": 1, "use_report_chart": 1,

View File

@@ -4,19 +4,18 @@
"creation": "2020-07-17 11:25:34.448572", "creation": "2020-07-17 11:25:34.448572",
"docstatus": 0, "docstatus": 0,
"doctype": "Dashboard Chart", "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()\"}", "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
"filters_json": "{\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"periodicity\":\"Yearly\",\"include_default_book_entries\":1}", "filters_json": "{\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"periodicity\":\"Yearly\",\"include_default_book_entries\":1}",
"idx": 0, "idx": 0,
"is_public": 1, "is_public": 1,
"is_standard": 1, "is_standard": 1,
"modified": "2023-07-19 13:08:56.470390", "modified": "2020-07-22 12:33:48.888943",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Profit and Loss", "name": "Profit and Loss",
"number_of_groups": 0, "number_of_groups": 0,
"owner": "Administrator", "owner": "Administrator",
"report_name": "Profit and Loss Statement", "report_name": "Profit and Loss Statement",
"roles": [],
"timeseries": 0, "timeseries": 0,
"type": "Bar", "type": "Bar",
"use_report_chart": 1, "use_report_chart": 1,

View File

@@ -1,4 +1,4 @@
frappe.provide("frappe.dashboards.chart_sources"); frappe.provide('frappe.dashboards.chart_sources');
frappe.dashboards.chart_sources["Account Balance Timeline"] = { frappe.dashboards.chart_sources["Account Balance Timeline"] = {
method: "erpnext.accounts.dashboard_chart_source.account_balance_timeline.account_balance_timeline.get", method: "erpnext.accounts.dashboard_chart_source.account_balance_timeline.account_balance_timeline.get",
@@ -9,14 +9,14 @@ frappe.dashboards.chart_sources["Account Balance Timeline"] = {
fieldtype: "Link", fieldtype: "Link",
options: "Company", options: "Company",
default: frappe.defaults.get_user_default("Company"), default: frappe.defaults.get_user_default("Company"),
reqd: 1, reqd: 1
}, },
{ {
fieldname: "account", fieldname: "account",
label: __("Account"), label: __("Account"),
fieldtype: "Link", fieldtype: "Link",
options: "Account", options: "Account",
reqd: 1, reqd: 1
}, },
], ]
}; };

View File

@@ -1,35 +1,26 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe import frappe, json
from frappe import _ from frappe import _
from frappe.utils import add_to_date, formatdate, get_link_to_form, getdate, nowdate from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day, formatdate, get_link_to_form
from erpnext.accounts.report.general_ledger.general_ledger import execute
from frappe.utils.dashboard import cache_source from frappe.utils.dashboard import cache_source
from frappe.utils.dateutils import get_from_date_from_timespan, get_period_ending from frappe.utils.dateutils import get_from_date_from_timespan, get_period_ending
from frappe.utils.nestedset import get_descendants_of from frappe.utils.nestedset import get_descendants_of
@frappe.whitelist() @frappe.whitelist()
@cache_source @cache_source
def get( def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None,
chart_name=None, to_date = None, timespan = None, time_interval = None, heatmap_year = 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: if chart_name:
chart = frappe.get_doc("Dashboard Chart", chart_name) chart = frappe.get_doc('Dashboard Chart', chart_name)
else: else:
chart = frappe._dict(frappe.parse_json(chart)) chart = frappe._dict(frappe.parse_json(chart))
timespan = chart.timespan timespan = chart.timespan
if chart.timespan == "Select Date Range": if chart.timespan == 'Select Date Range':
from_date = chart.from_date from_date = chart.from_date
to_date = chart.to_date to_date = chart.to_date
@@ -40,23 +31,17 @@ def get(
company = filters.get("company") company = filters.get("company")
if not account and chart_name: if not account and chart_name:
frappe.throw( frappe.throw(_("Account is not set for the dashboard chart {0}")
_("Account is not set for the dashboard chart {0}").format( .format(get_link_to_form("Dashboard Chart", chart_name)))
get_link_to_form("Dashboard Chart", chart_name)
)
)
if not frappe.db.exists("Account", account) and chart_name: if not frappe.db.exists("Account", account) and chart_name:
frappe.throw( frappe.throw(_("Account {0} does not exists in the dashboard chart {1}")
_("Account {0} does not exists in the dashboard chart {1}").format( .format(account, get_link_to_form("Dashboard Chart", chart_name)))
account, get_link_to_form("Dashboard Chart", chart_name)
)
)
if not to_date: if not to_date:
to_date = nowdate() to_date = nowdate()
if not from_date: if not from_date:
if timegrain in ("Monthly", "Quarterly"): if timegrain in ('Monthly', 'Quarterly'):
from_date = get_from_date_from_timespan(to_date, timespan) from_date = get_from_date_from_timespan(to_date, timespan)
# fetch dates to plot # fetch dates to plot
@@ -69,14 +54,16 @@ def get(
result = build_result(account, dates, gl_entries) result = build_result(account, dates, gl_entries)
return { return {
"labels": [formatdate(r[0].strftime("%Y-%m-%d")) for r in result], "labels": [formatdate(r[0].strftime('%Y-%m-%d')) for r in result],
"datasets": [{"name": account, "values": [r[1] for r in result]}], "datasets": [{
"name": account,
"values": [r[1] for r in result]
}]
} }
def build_result(account, dates, gl_entries): def build_result(account, dates, gl_entries):
result = [[getdate(date), 0.0] for date in dates] result = [[getdate(date), 0.0] for date in dates]
root_type = frappe.get_cached_value("Account", account, "root_type") root_type = frappe.db.get_value('Account', account, 'root_type')
# start with the first date # start with the first date
date_index = 0 date_index = 0
@@ -91,34 +78,30 @@ def build_result(account, dates, gl_entries):
result[date_index][1] += entry.debit - entry.credit result[date_index][1] += entry.debit - entry.credit
# if account type is credit, switch balances # if account type is credit, switch balances
if root_type not in ("Asset", "Expense"): if root_type not in ('Asset', 'Expense'):
for r in result: for r in result:
r[1] = -1 * r[1] r[1] = -1 * r[1]
# for balance sheet accounts, the totals are cumulative # for balance sheet accounts, the totals are cumulative
if root_type in ("Asset", "Liability", "Equity"): if root_type in ('Asset', 'Liability', 'Equity'):
for i, r in enumerate(result): for i, r in enumerate(result):
if i > 0: if i > 0:
r[1] = r[1] + result[i - 1][1] r[1] = r[1] + result[i-1][1]
return result return result
def get_gl_entries(account, to_date): def get_gl_entries(account, to_date):
child_accounts = get_descendants_of("Account", account, ignore_permissions=True) child_accounts = get_descendants_of('Account', account, ignore_permissions=True)
child_accounts.append(account) child_accounts.append(account)
return frappe.db.get_all( return frappe.db.get_all('GL Entry',
"GL Entry", fields = ['posting_date', 'debit', 'credit'],
fields=["posting_date", "debit", "credit"], filters = [
filters=[ dict(posting_date = ('<', to_date)),
dict(posting_date=("<", to_date)), dict(account = ('in', child_accounts)),
dict(account=("in", child_accounts)), dict(voucher_type = ('!=', 'Period Closing Voucher'))
dict(voucher_type=("!=", "Period Closing Voucher")),
], ],
order_by="posting_date asc", order_by = 'posting_date asc')
)
def get_dates_from_timegrain(from_date, to_date, timegrain): def get_dates_from_timegrain(from_date, to_date, timegrain):
days = months = years = 0 days = months = years = 0
@@ -133,8 +116,6 @@ def get_dates_from_timegrain(from_date, to_date, timegrain):
dates = [get_period_ending(from_date, timegrain)] dates = [get_period_ending(from_date, timegrain)]
while getdate(dates[-1]) < getdate(to_date): while getdate(dates[-1]) < getdate(to_date):
date = get_period_ending( date = get_period_ending(add_to_date(dates[-1], years=years, months=months, days=days), timegrain)
add_to_date(dates[-1], years=years, months=months, days=days), timegrain
)
dates.append(date) dates.append(date)
return dates return dates

View File

@@ -1,44 +1,28 @@
from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.email import sendmail_to_system_managers from frappe.utils import date_diff, add_months, today, getdate, add_days, flt, get_last_day, get_first_day, cint, get_link_to_form, rounded
from frappe.utils import (
add_days,
add_months,
cint,
date_diff,
flt,
get_first_day,
get_last_day,
get_link_to_form,
getdate,
rounded,
today,
)
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
from erpnext.accounts.utils import get_account_currency from erpnext.accounts.utils import get_account_currency
from frappe.email import sendmail_to_system_managers
from frappe.utils.background_jobs import enqueue
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
def validate_service_stop_date(doc): def validate_service_stop_date(doc):
"""Validates service_stop_date for Purchase Invoice and Sales Invoice""" ''' Validates service_stop_date for Purchase Invoice and Sales Invoice '''
enable_check = ( enable_check = "enable_deferred_revenue" \
"enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense" if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
)
old_stop_dates = {} old_stop_dates = {}
old_doc = frappe.db.get_all( old_doc = frappe.db.get_all("{0} Item".format(doc.doctype),
"{0} Item".format(doc.doctype), {"parent": doc.name}, ["name", "service_stop_date"] {"parent": doc.name}, ["name", "service_stop_date"])
)
for d in old_doc: for d in old_doc:
old_stop_dates[d.name] = d.service_stop_date or "" old_stop_dates[d.name] = d.service_stop_date or ""
for item in doc.items: for item in doc.items:
if not item.get(enable_check): if not item.get(enable_check): continue
continue
if item.service_stop_date: if item.service_stop_date:
if date_diff(item.service_stop_date, item.service_start_date) < 0: if date_diff(item.service_stop_date, item.service_start_date) < 0:
@@ -47,31 +31,21 @@ def validate_service_stop_date(doc):
if date_diff(item.service_stop_date, item.service_end_date) > 0: if date_diff(item.service_stop_date, item.service_end_date) > 0:
frappe.throw(_("Service Stop Date cannot be after Service End Date")) frappe.throw(_("Service Stop Date cannot be after Service End Date"))
if ( if old_stop_dates and old_stop_dates.get(item.name) and item.service_stop_date!=old_stop_dates.get(item.name):
old_stop_dates
and old_stop_dates.get(item.name)
and item.service_stop_date != old_stop_dates.get(item.name)
):
frappe.throw(_("Cannot change Service Stop Date for item in row {0}").format(item.idx)) frappe.throw(_("Cannot change Service Stop Date for item in row {0}").format(item.idx))
def build_conditions(process_type, account, company): def build_conditions(process_type, account, company):
conditions = "" conditions=''
deferred_account = ( deferred_account = "item.deferred_revenue_account" if process_type=="Income" else "item.deferred_expense_account"
"item.deferred_revenue_account" if process_type == "Income" else "item.deferred_expense_account"
)
if account: if account:
conditions += "AND %s='%s'" % (deferred_account, account) conditions += "AND %s='%s'"%(deferred_account, account)
elif company: elif company:
conditions += f"AND p.company = {frappe.db.escape(company)}" conditions += f"AND p.company = {frappe.db.escape(company)}"
return conditions return conditions
def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_date=None, conditions=''):
def convert_deferred_expense_to_expense(
deferred_process, start_date=None, end_date=None, conditions=""
):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
if not start_date: if not start_date:
@@ -80,19 +54,14 @@ def convert_deferred_expense_to_expense(
end_date = add_days(today(), -1) end_date = add_days(today(), -1)
# check for the purchase invoice for which GL entries has to be done # check for the purchase invoice for which GL entries has to be done
invoices = frappe.db.sql_list( invoices = frappe.db.sql_list('''
"""
select distinct item.parent select distinct item.parent
from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p
where item.service_start_date<=%s and item.service_end_date>=%s where item.service_start_date<=%s and item.service_end_date>=%s
and item.enable_deferred_expense = 1 and item.parent=p.name and item.enable_deferred_expense = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0 and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{0} {0}
""".format( '''.format(conditions), (end_date, start_date)) #nosec
conditions
),
(end_date, start_date),
) # nosec
# For each invoice, book deferred expense # For each invoice, book deferred expense
for invoice in invoices: for invoice in invoices:
@@ -102,10 +71,7 @@ def convert_deferred_expense_to_expense(
if frappe.flags.deferred_accounting_error: if frappe.flags.deferred_accounting_error:
send_mail(deferred_process) send_mail(deferred_process)
def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_date=None, conditions=''):
def convert_deferred_revenue_to_income(
deferred_process, start_date=None, end_date=None, conditions=""
):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
if not start_date: if not start_date:
@@ -114,19 +80,14 @@ def convert_deferred_revenue_to_income(
end_date = add_days(today(), -1) end_date = add_days(today(), -1)
# check for the sales invoice for which GL entries has to be done # check for the sales invoice for which GL entries has to be done
invoices = frappe.db.sql_list( invoices = frappe.db.sql_list('''
"""
select distinct item.parent select distinct item.parent
from `tabSales Invoice Item` item, `tabSales Invoice` p from `tabSales Invoice Item` item, `tabSales Invoice` p
where item.service_start_date<=%s and item.service_end_date>=%s where item.service_start_date<=%s and item.service_end_date>=%s
and item.enable_deferred_revenue = 1 and item.parent=p.name and item.enable_deferred_revenue = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0 and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{0} {0}
""".format( '''.format(conditions), (end_date, start_date)) #nosec
conditions
),
(end_date, start_date),
) # nosec
for invoice in invoices: for invoice in invoices:
doc = frappe.get_doc("Sales Invoice", invoice) doc = frappe.get_doc("Sales Invoice", invoice)
@@ -135,53 +96,37 @@ def convert_deferred_revenue_to_income(
if frappe.flags.deferred_accounting_error: if frappe.flags.deferred_accounting_error:
send_mail(deferred_process) send_mail(deferred_process)
def get_booking_dates(doc, item, posting_date=None):
def get_booking_dates(doc, item, posting_date=None, prev_posting_date=None):
if not posting_date: if not posting_date:
posting_date = add_days(today(), -1) posting_date = add_days(today(), -1)
last_gl_entry = False last_gl_entry = False
deferred_account = ( deferred_account = "deferred_revenue_account" if doc.doctype=="Sales Invoice" else "deferred_expense_account"
"deferred_revenue_account" if doc.doctype == "Sales Invoice" else "deferred_expense_account"
)
if not prev_posting_date: prev_gl_entry = frappe.db.sql('''
prev_gl_entry = frappe.db.sql( select name, posting_date from `tabGL Entry` where company=%s and account=%s and
""" voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
select name, posting_date from `tabGL Entry` where company=%s and account=%s and order by posting_date desc limit 1
voucher_type=%s and voucher_no=%s and voucher_detail_no=%s ''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
and is_cancelled = 0
order by posting_date desc limit 1
""",
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
prev_gl_via_je = frappe.db.sql( prev_gl_via_je = frappe.db.sql('''
""" SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c
SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c WHERE p.name = c.parent and p.company=%s and c.account=%s
WHERE p.name = c.parent and p.company=%s and c.account=%s and c.reference_type=%s and c.reference_name=%s
and c.reference_type=%s and c.reference_name=%s and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1
and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1 ''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
""",
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
if prev_gl_via_je: if prev_gl_via_je:
if (not prev_gl_entry) or ( if (not prev_gl_entry) or (prev_gl_entry and
prev_gl_entry and prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date):
): prev_gl_entry = prev_gl_via_je
prev_gl_entry = prev_gl_via_je
if prev_gl_entry:
start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
else:
start_date = item.service_start_date
if prev_gl_entry:
start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
else: else:
start_date = getdate(add_days(prev_posting_date, 1)) start_date = item.service_start_date
end_date = get_last_day(start_date) end_date = get_last_day(start_date)
if end_date >= item.service_end_date: if end_date >= item.service_end_date:
end_date = item.service_end_date end_date = item.service_end_date
@@ -198,94 +143,66 @@ def get_booking_dates(doc, item, posting_date=None, prev_posting_date=None):
else: else:
return None, None, None return None, None, None
def calculate_monthly_amount(doc, item, last_gl_entry, start_date, end_date, total_days, total_booking_days, account_currency):
def calculate_monthly_amount(
doc, item, last_gl_entry, start_date, end_date, total_days, total_booking_days, account_currency
):
amount, base_amount = 0, 0 amount, base_amount = 0, 0
if not last_gl_entry: if not last_gl_entry:
total_months = ( total_months = (item.service_end_date.year - item.service_start_date.year) * 12 + \
(item.service_end_date.year - item.service_start_date.year) * 12 (item.service_end_date.month - item.service_start_date.month) + 1
+ (item.service_end_date.month - item.service_start_date.month)
+ 1
)
prorate_factor = flt(date_diff(item.service_end_date, item.service_start_date)) / flt( prorate_factor = flt(date_diff(item.service_end_date, item.service_start_date)) \
date_diff(get_last_day(item.service_end_date), get_first_day(item.service_start_date)) / flt(date_diff(get_last_day(item.service_end_date), get_first_day(item.service_start_date)))
)
actual_months = rounded(total_months * prorate_factor, 1) actual_months = rounded(total_months * prorate_factor, 1)
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount( already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(doc, item)
doc, item
)
base_amount = flt(item.base_net_amount / actual_months, item.precision("base_net_amount")) base_amount = flt(item.base_net_amount / actual_months, item.precision("base_net_amount"))
if base_amount + already_booked_amount > item.base_net_amount: if base_amount + already_booked_amount > item.base_net_amount:
base_amount = item.base_net_amount - already_booked_amount base_amount = item.base_net_amount - already_booked_amount
if account_currency == doc.company_currency: if account_currency==doc.company_currency:
amount = base_amount amount = base_amount
else: else:
amount = flt(item.net_amount / actual_months, item.precision("net_amount")) amount = flt(item.net_amount/actual_months, item.precision("net_amount"))
if amount + already_booked_amount_in_account_currency > item.net_amount: if amount + already_booked_amount_in_account_currency > item.net_amount:
amount = item.net_amount - already_booked_amount_in_account_currency amount = item.net_amount - already_booked_amount_in_account_currency
if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date): if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date):
partial_month = flt(date_diff(end_date, start_date)) / flt( partial_month = flt(date_diff(end_date, start_date)) \
date_diff(get_last_day(end_date), get_first_day(start_date)) / flt(date_diff(get_last_day(end_date), get_first_day(start_date)))
)
base_amount = rounded(partial_month, 1) * base_amount base_amount = rounded(partial_month, 1) * base_amount
amount = rounded(partial_month, 1) * amount amount = rounded(partial_month, 1) * amount
else: else:
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount( already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(doc, item)
doc, item base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
) if account_currency==doc.company_currency:
base_amount = flt(
item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
)
if account_currency == doc.company_currency:
amount = base_amount amount = base_amount
else: else:
amount = flt( amount = flt(item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount"))
item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount")
)
return amount, base_amount return amount, base_amount
def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, account_currency): def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, account_currency):
amount, base_amount = 0, 0 amount, base_amount = 0, 0
if not last_gl_entry: if not last_gl_entry:
base_amount = flt( base_amount = flt(item.base_net_amount*total_booking_days/flt(total_days), item.precision("base_net_amount"))
item.base_net_amount * total_booking_days / flt(total_days), item.precision("base_net_amount") if account_currency==doc.company_currency:
)
if account_currency == doc.company_currency:
amount = base_amount amount = base_amount
else: else:
amount = flt( amount = flt(item.net_amount*total_booking_days/flt(total_days), item.precision("net_amount"))
item.net_amount * total_booking_days / flt(total_days), item.precision("net_amount")
)
else: else:
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount( already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(doc, item)
doc, item
)
base_amount = flt( base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
item.base_net_amount - already_booked_amount, item.precision("base_net_amount") if account_currency==doc.company_currency:
)
if account_currency == doc.company_currency:
amount = base_amount amount = base_amount
else: else:
amount = flt( amount = flt(item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount"))
item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount")
)
return amount, base_amount return amount, base_amount
def get_already_booked_amount(doc, item): def get_already_booked_amount(doc, item):
if doc.doctype == "Sales Invoice": if doc.doctype == "Sales Invoice":
total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency" total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency"
@@ -294,31 +211,20 @@ def get_already_booked_amount(doc, item):
total_credit_debit, total_credit_debit_currency = "credit", "credit_in_account_currency" total_credit_debit, total_credit_debit_currency = "credit", "credit_in_account_currency"
deferred_account = "deferred_expense_account" deferred_account = "deferred_expense_account"
gl_entries_details = frappe.db.sql( gl_entries_details = frappe.db.sql('''
"""
select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no
from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
and is_cancelled = 0
group by voucher_detail_no group by voucher_detail_no
""".format( '''.format(total_credit_debit, total_credit_debit_currency),
total_credit_debit, total_credit_debit_currency (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
),
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
journal_entry_details = frappe.db.sql( journal_entry_details = frappe.db.sql('''
"""
SELECT sum(c.{0}) as total_credit, sum(c.{1}) as total_credit_in_account_currency, reference_detail_no SELECT sum(c.{0}) as total_credit, sum(c.{1}) as total_credit_in_account_currency, reference_detail_no
FROM `tabJournal Entry` p , `tabJournal Entry Account` c WHERE p.name = c.parent and FROM `tabJournal Entry` p , `tabJournal Entry Account` c WHERE p.name = c.parent and
p.company = %s and c.account=%s and c.reference_type=%s and c.reference_name=%s and c.reference_detail_no=%s p.company = %s and c.account=%s and c.reference_type=%s and c.reference_name=%s and c.reference_detail_no=%s
and p.docstatus < 2 group by reference_detail_no and p.docstatus < 2 group by reference_detail_no
""".format( '''.format(total_credit_debit, total_credit_debit_currency),
total_credit_debit, total_credit_debit_currency (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
),
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
already_booked_amount = gl_entries_details[0].total_credit if gl_entries_details else 0 already_booked_amount = gl_entries_details[0].total_credit if gl_entries_details else 0
already_booked_amount += journal_entry_details[0].total_credit if journal_entry_details else 0 already_booked_amount += journal_entry_details[0].total_credit if journal_entry_details else 0
@@ -326,37 +232,20 @@ def get_already_booked_amount(doc, item):
if doc.currency == doc.company_currency: if doc.currency == doc.company_currency:
already_booked_amount_in_account_currency = already_booked_amount already_booked_amount_in_account_currency = already_booked_amount
else: else:
already_booked_amount_in_account_currency = ( already_booked_amount_in_account_currency = gl_entries_details[0].total_credit_in_account_currency if gl_entries_details else 0
gl_entries_details[0].total_credit_in_account_currency if gl_entries_details else 0 already_booked_amount_in_account_currency += journal_entry_details[0].total_credit_in_account_currency if journal_entry_details else 0
)
already_booked_amount_in_account_currency += (
journal_entry_details[0].total_credit_in_account_currency if journal_entry_details else 0
)
return already_booked_amount, already_booked_amount_in_account_currency return already_booked_amount, already_booked_amount_in_account_currency
def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
enable_check = ( enable_check = "enable_deferred_revenue" \
"enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense" if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
)
accounts_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto") def _book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on):
start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
if not (start_date and end_date): return
def _book_deferred_revenue_or_expense( account_currency = get_account_currency(item.expense_account)
item,
via_journal_entry,
submit_journal_entry,
book_deferred_entries_based_on,
prev_posting_date=None,
):
start_date, end_date, last_gl_entry = get_booking_dates(
doc, item, posting_date=posting_date, prev_posting_date=prev_posting_date
)
if not (start_date and end_date):
return
account_currency = get_account_currency(item.expense_account or item.income_account)
if doc.doctype == "Sales Invoice": if doc.doctype == "Sales Invoice":
against, project = doc.customer, doc.project against, project = doc.customer, doc.project
credit_account, debit_account = item.income_account, item.deferred_revenue_account credit_account, debit_account = item.income_account, item.deferred_revenue_account
@@ -367,299 +256,199 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
total_days = date_diff(item.service_end_date, item.service_start_date) + 1 total_days = date_diff(item.service_end_date, item.service_start_date) + 1
total_booking_days = date_diff(end_date, start_date) + 1 total_booking_days = date_diff(end_date, start_date) + 1
if book_deferred_entries_based_on == "Months": if book_deferred_entries_based_on == 'Months':
amount, base_amount = calculate_monthly_amount( amount, base_amount = calculate_monthly_amount(doc, item, last_gl_entry,
doc, start_date, end_date, total_days, total_booking_days, account_currency)
item,
last_gl_entry,
start_date,
end_date,
total_days,
total_booking_days,
account_currency,
)
else: else:
amount, base_amount = calculate_amount( amount, base_amount = calculate_amount(doc, item, last_gl_entry,
doc, item, last_gl_entry, total_days, total_booking_days, account_currency total_days, total_booking_days, account_currency)
)
if not amount: if not amount:
return return
gl_posting_date = end_date
prev_posting_date = None
# check if books nor frozen till endate:
if accounts_frozen_upto and getdate(end_date) <= getdate(accounts_frozen_upto):
gl_posting_date = get_last_day(add_days(accounts_frozen_upto, 1))
prev_posting_date = end_date
if via_journal_entry: if via_journal_entry:
book_revenue_via_journal_entry( book_revenue_via_journal_entry(doc, credit_account, debit_account, against, amount,
doc, base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process, submit_journal_entry)
credit_account,
debit_account,
amount,
base_amount,
gl_posting_date,
project,
account_currency,
item.cost_center,
item,
deferred_process,
submit_journal_entry,
)
else: else:
make_gl_entries( make_gl_entries(doc, credit_account, debit_account, against,
doc, amount, base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process)
credit_account,
debit_account,
against,
amount,
base_amount,
gl_posting_date,
project,
account_currency,
item.cost_center,
item,
deferred_process,
)
# Returned in case of any errors because it tries to submit the same record again and again in case of errors # Returned in case of any errors because it tries to submit the same record again and again in case of errors
if frappe.flags.deferred_accounting_error: if frappe.flags.deferred_accounting_error:
return return
if getdate(end_date) < getdate(posting_date) and not last_gl_entry: if getdate(end_date) < getdate(posting_date) and not last_gl_entry:
_book_deferred_revenue_or_expense( _book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on)
item,
via_journal_entry,
submit_journal_entry,
book_deferred_entries_based_on,
prev_posting_date,
)
via_journal_entry = cint( via_journal_entry = cint(frappe.db.get_singles_value('Accounts Settings', 'book_deferred_entries_via_journal_entry'))
frappe.db.get_singles_value("Accounts Settings", "book_deferred_entries_via_journal_entry") submit_journal_entry = cint(frappe.db.get_singles_value('Accounts Settings', 'submit_journal_entries'))
) book_deferred_entries_based_on = frappe.db.get_singles_value('Accounts Settings', 'book_deferred_entries_based_on')
submit_journal_entry = cint(
frappe.db.get_singles_value("Accounts Settings", "submit_journal_entries")
)
book_deferred_entries_based_on = frappe.db.get_singles_value(
"Accounts Settings", "book_deferred_entries_based_on"
)
for item in doc.get("items"): for item in doc.get('items'):
if item.get(enable_check): if item.get(enable_check):
_book_deferred_revenue_or_expense( _book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on)
item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
)
def process_deferred_accounting(posting_date=None): def process_deferred_accounting(posting_date=None):
"""Converts deferred income/expense into income/expense ''' Converts deferred income/expense into income/expense
Executed via background jobs on every month end""" Executed via background jobs on every month end '''
if not posting_date: if not posting_date:
posting_date = today() posting_date = today()
if not cint( if not cint(frappe.db.get_singles_value('Accounts Settings', 'automatically_process_deferred_accounting_entry')):
frappe.db.get_singles_value(
"Accounts Settings", "automatically_process_deferred_accounting_entry"
)
):
return return
start_date = add_months(today(), -1) start_date = add_months(today(), -1)
end_date = add_days(today(), -1) end_date = add_days(today(), -1)
companies = frappe.get_all("Company") companies = frappe.get_all('Company')
for company in companies: for company in companies:
for record_type in ("Income", "Expense"): for record_type in ('Income', 'Expense'):
doc = frappe.get_doc( doc = frappe.get_doc(dict(
dict( doctype='Process Deferred Accounting',
doctype="Process Deferred Accounting", company=company.name,
company=company.name, posting_date=posting_date,
posting_date=posting_date, start_date=start_date,
start_date=start_date, end_date=end_date,
end_date=end_date, type=record_type
type=record_type, ))
)
)
doc.insert() doc.insert()
doc.submit() doc.submit()
def make_gl_entries(doc, credit_account, debit_account, against,
def make_gl_entries( amount, base_amount, posting_date, project, account_currency, cost_center, item, deferred_process=None):
doc,
credit_account,
debit_account,
against,
amount,
base_amount,
posting_date,
project,
account_currency,
cost_center,
item,
deferred_process=None,
):
# GL Entry for crediting the amount in the deferred expense # GL Entry for crediting the amount in the deferred expense
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
if amount == 0: if amount == 0: return
return
gl_entries = [] gl_entries = []
gl_entries.append( gl_entries.append(
doc.get_gl_dict( doc.get_gl_dict({
{ "account": credit_account,
"account": credit_account, "against": against,
"against": against, "credit": base_amount,
"credit": base_amount, "credit_in_account_currency": amount,
"credit_in_account_currency": amount, "cost_center": cost_center,
"cost_center": cost_center, "voucher_detail_no": item.name,
"voucher_detail_no": item.name, 'posting_date': posting_date,
"posting_date": posting_date, 'project': project,
"project": project, 'against_voucher_type': 'Process Deferred Accounting',
"against_voucher_type": "Process Deferred Accounting", 'against_voucher': deferred_process
"against_voucher": deferred_process, }, account_currency, item=item)
},
account_currency,
item=item,
)
) )
# GL Entry to debit the amount from the expense # GL Entry to debit the amount from the expense
gl_entries.append( gl_entries.append(
doc.get_gl_dict( doc.get_gl_dict({
{ "account": debit_account,
"account": debit_account, "against": against,
"against": against, "debit": base_amount,
"debit": base_amount, "debit_in_account_currency": amount,
"debit_in_account_currency": amount, "cost_center": cost_center,
"cost_center": cost_center, "voucher_detail_no": item.name,
"voucher_detail_no": item.name, 'posting_date': posting_date,
"posting_date": posting_date, 'project': project,
"project": project, 'against_voucher_type': 'Process Deferred Accounting',
"against_voucher_type": "Process Deferred Accounting", 'against_voucher': deferred_process
"against_voucher": deferred_process, }, account_currency, item=item)
},
account_currency,
item=item,
)
) )
if gl_entries: if gl_entries:
try: try:
make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True) make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True)
frappe.db.commit() frappe.db.commit()
except Exception as e: except:
if frappe.flags.in_test: frappe.db.rollback()
doc.log_error(f"Error while processing deferred accounting for Invoice {doc.name}") traceback = frappe.get_traceback()
raise e frappe.log_error(message=traceback)
else:
frappe.db.rollback()
doc.log_error(f"Error while processing deferred accounting for Invoice {doc.name}")
frappe.flags.deferred_accounting_error = True
frappe.flags.deferred_accounting_error = True
def send_mail(deferred_process): def send_mail(deferred_process):
title = _("Error while processing deferred accounting for {0}").format(deferred_process) title = _("Error while processing deferred accounting for {0}").format(deferred_process)
link = get_link_to_form("Process Deferred Accounting", deferred_process) link = get_link_to_form('Process Deferred Accounting', deferred_process)
content = _("Deferred accounting failed for some invoices:") + "\n" content = _("Deferred accounting failed for some invoices:") + "\n"
content += _( content += _("Please check Process Deferred Accounting {0} and submit manually after resolving errors.").format(link)
"Please check Process Deferred Accounting {0} and submit manually after resolving errors."
).format(link)
sendmail_to_system_managers(title, content) sendmail_to_system_managers(title, content)
def book_revenue_via_journal_entry(doc, credit_account, debit_account, against,
amount, base_amount, posting_date, project, account_currency, cost_center, item,
deferred_process=None, submit='No'):
def book_revenue_via_journal_entry( if amount == 0: return
doc,
credit_account,
debit_account,
amount,
base_amount,
posting_date,
project,
account_currency,
cost_center,
item,
deferred_process=None,
submit="No",
):
if amount == 0: journal_entry = frappe.new_doc('Journal Entry')
return
journal_entry = frappe.new_doc("Journal Entry")
journal_entry.posting_date = posting_date journal_entry.posting_date = posting_date
journal_entry.company = doc.company journal_entry.company = doc.company
journal_entry.voucher_type = ( journal_entry.voucher_type = 'Deferred Revenue' if doc.doctype == 'Sales Invoice' \
"Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense" else 'Deferred Expense'
)
journal_entry.process_deferred_accounting = deferred_process
debit_entry = { debit_entry = {
"account": credit_account, 'account': credit_account,
"credit": base_amount, 'credit': base_amount,
"credit_in_account_currency": amount, 'credit_in_account_currency': amount,
"account_currency": account_currency, 'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier',
"reference_name": doc.name, 'party': against,
"reference_type": doc.doctype, 'account_currency': account_currency,
"reference_detail_no": item.name, 'reference_name': doc.name,
"cost_center": cost_center, 'reference_type': doc.doctype,
"project": project, 'reference_detail_no': item.name,
'cost_center': cost_center,
'project': project,
} }
credit_entry = { credit_entry = {
"account": debit_account, 'account': debit_account,
"debit": base_amount, 'debit': base_amount,
"debit_in_account_currency": amount, 'debit_in_account_currency': amount,
"account_currency": account_currency, 'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier',
"reference_name": doc.name, 'party': against,
"reference_type": doc.doctype, 'account_currency': account_currency,
"reference_detail_no": item.name, 'reference_name': doc.name,
"cost_center": cost_center, 'reference_type': doc.doctype,
"project": project, 'reference_detail_no': item.name,
'cost_center': cost_center,
'project': project,
} }
for dimension in get_accounting_dimensions(): for dimension in get_accounting_dimensions():
debit_entry.update({dimension: item.get(dimension)}) debit_entry.update({
dimension: item.get(dimension)
})
credit_entry.update({dimension: item.get(dimension)}) credit_entry.update({
dimension: item.get(dimension)
})
journal_entry.append("accounts", debit_entry) journal_entry.append('accounts', debit_entry)
journal_entry.append("accounts", credit_entry) journal_entry.append('accounts', credit_entry)
try: try:
journal_entry.save() journal_entry.save()
if submit: if submit:
journal_entry.submit() journal_entry.submit()
except:
frappe.db.commit()
except Exception:
frappe.db.rollback() frappe.db.rollback()
doc.log_error(f"Error while processing deferred accounting for Invoice {doc.name}") traceback = frappe.get_traceback()
frappe.flags.deferred_accounting_error = True frappe.log_error(message=traceback)
frappe.flags.deferred_accounting_error = True
def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr): def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr):
if doctype == "Sales Invoice": if doctype == 'Sales Invoice':
credit_account, debit_account = frappe.db.get_value( credit_account, debit_account = frappe.db.get_value('Sales Invoice Item', {'name': voucher_detail_no},
"Sales Invoice Item", ['income_account', 'deferred_revenue_account'])
{"name": voucher_detail_no},
["income_account", "deferred_revenue_account"],
)
else: else:
credit_account, debit_account = frappe.db.get_value( credit_account, debit_account = frappe.db.get_value('Purchase Invoice Item', {'name': voucher_detail_no},
"Purchase Invoice Item", ['deferred_expense_account', 'expense_account'])
{"name": voucher_detail_no},
["deferred_expense_account", "expense_account"],
)
if dr_or_cr == "Debit": if dr_or_cr == 'Debit':
return debit_account return debit_account
else: else:
return credit_account return credit_account

View File

@@ -0,0 +1 @@
from __future__ import unicode_literals

View File

@@ -0,0 +1 @@
from __future__ import unicode_literals

View File

@@ -1,32 +1,33 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
frappe.ui.form.on("Account", { frappe.ui.form.on('Account', {
setup: function (frm) { setup: function(frm) {
frm.add_fetch("parent_account", "report_type", "report_type"); frm.add_fetch('parent_account', 'report_type', 'report_type');
frm.add_fetch("parent_account", "root_type", "root_type"); frm.add_fetch('parent_account', 'root_type', 'root_type');
}, },
onload: function (frm) { onload: function(frm) {
frm.set_query("parent_account", function (doc) { frm.set_query('parent_account', function(doc) {
return { return {
filters: { filters: {
is_group: 1, "is_group": 1,
company: doc.company, "company": doc.company
}, }
}; };
}); });
}, },
refresh: function (frm) { refresh: function(frm) {
frm.toggle_display("account_name", frm.is_new()); frm.toggle_display('account_name', frm.is_new());
// hide fields if group // hide fields if group
frm.toggle_display(["tax_rate"], cint(frm.doc.is_group) == 0); frm.toggle_display(['account_type', 'tax_rate'], cint(frm.doc.is_group) == 0);
// disable fields // disable fields
frm.toggle_enable(["is_group", "company"], false); frm.toggle_enable(['is_group', 'company'], false);
if (cint(frm.doc.is_group) == 0) { if (cint(frm.doc.is_group) == 0) {
frm.toggle_display("freeze_account", frm.doc.__onload && frm.doc.__onload.can_freeze_account); frm.toggle_display('freeze_account', frm.doc.__onload
&& frm.doc.__onload.can_freeze_account);
} }
// read-only for root accounts // read-only for root accounts
@@ -37,147 +38,123 @@ frappe.ui.form.on("Account", {
} else { } else {
// credit days and type if customer or supplier // credit days and type if customer or supplier
frm.set_intro(null); frm.set_intro(null);
frm.trigger("account_type"); frm.trigger('account_type');
// show / hide convert buttons // show / hide convert buttons
frm.trigger("add_toolbar_buttons"); frm.trigger('add_toolbar_buttons');
} }
if (frm.has_perm("write")) { if (frm.has_perm('write')) {
frm.add_custom_button( frm.add_custom_button(__('Update Account Name / Number'), function () {
__("Merge Account"), frm.trigger("update_account_number");
function () { });
frm.trigger("merge_account"); frm.add_custom_button(__('Merge Account'), function () {
}, frm.trigger("merge_account");
__("Actions") });
);
frm.add_custom_button(
__("Update Account Name / Number"),
function () {
frm.trigger("update_account_number");
},
__("Actions")
);
} }
} }
}, },
account_type: function (frm) { account_type: function (frm) {
if (frm.doc.is_group == 0) { if (frm.doc.is_group == 0) {
frm.toggle_display(["tax_rate"], frm.doc.account_type == "Tax"); frm.toggle_display(['tax_rate'], frm.doc.account_type == 'Tax');
frm.toggle_display("warehouse", frm.doc.account_type == "Stock"); frm.toggle_display('warehouse', frm.doc.account_type == 'Stock');
} }
}, },
add_toolbar_buttons: function (frm) { add_toolbar_buttons: function(frm) {
frm.add_custom_button( frm.add_custom_button(__('Chart of Accounts'),
__("Chart of Accounts"), function () { frappe.set_route("Tree", "Account"); });
() => {
frappe.set_route("Tree", "Account");
},
__("View")
);
if (frm.doc.is_group == 1) { if (frm.doc.is_group == 1) {
frm.add_custom_button( frm.add_custom_button(__('Group to Non-Group'), function () {
__("Convert to Non-Group"), return frappe.call({
function () { doc: frm.doc,
return frappe.call({ method: 'convert_group_to_ledger',
doc: frm.doc, callback: function() {
method: "convert_group_to_ledger", frm.refresh();
callback: function () { }
frm.refresh(); });
}, });
}); } else if (cint(frm.doc.is_group) == 0
}, && frappe.boot.user.can_read.indexOf("GL Entry") !== -1) {
__("Actions") cur_frm.add_custom_button(__('Ledger'), function () {
); frappe.route_options = {
} else if (cint(frm.doc.is_group) == 0 && frappe.boot.user.can_read.indexOf("GL Entry") !== -1) { "account": frm.doc.name,
frm.add_custom_button( "from_date": frappe.sys_defaults.year_start_date,
__("General Ledger"), "to_date": frappe.sys_defaults.year_end_date,
function () { "company": frm.doc.company
frappe.route_options = { };
account: frm.doc.name, frappe.set_route("query-report", "General Ledger");
from_date: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1], });
to_date: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
company: frm.doc.company,
};
frappe.set_route("query-report", "General Ledger");
},
__("View")
);
frm.add_custom_button( frm.add_custom_button(__('Non-Group to Group'), function () {
__("Convert to Group"), return frappe.call({
function () { doc: frm.doc,
return frappe.call({ method: 'convert_ledger_to_group',
doc: frm.doc, callback: function() {
method: "convert_ledger_to_group", frm.refresh();
callback: function () { }
frm.refresh(); });
}, });
});
},
__("Actions")
);
} }
}, },
merge_account: function (frm) { merge_account: function(frm) {
var d = new frappe.ui.Dialog({ var d = new frappe.ui.Dialog({
title: __("Merge with Existing Account"), title: __('Merge with Existing Account'),
fields: [ fields: [
{ {
label: "Name", "label" : "Name",
fieldname: "name", "fieldname": "name",
fieldtype: "Data", "fieldtype": "Data",
reqd: 1, "reqd": 1,
default: frm.doc.name, "default": frm.doc.name
}, }
], ],
primary_action: function () { primary_action: function() {
var data = d.get_values(); var data = d.get_values();
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.account.account.merge_account", method: "erpnext.accounts.doctype.account.account.merge_account",
args: { args: {
old: frm.doc.name, old: frm.doc.name,
new: data.name, new: data.name,
is_group: frm.doc.is_group,
root_type: frm.doc.root_type,
company: frm.doc.company
}, },
callback: function (r) { callback: function(r) {
if (!r.exc) { if(!r.exc) {
if (r.message) { if(r.message) {
frappe.set_route("Form", "Account", r.message); frappe.set_route("Form", "Account", r.message);
} }
d.hide(); d.hide();
} }
}, }
}); });
}, },
primary_action_label: __("Merge"), primary_action_label: __('Merge')
}); });
d.show(); d.show();
}, },
update_account_number: function (frm) { update_account_number: function(frm) {
var d = new frappe.ui.Dialog({ var d = new frappe.ui.Dialog({
title: __("Update Account Number / Name"), title: __('Update Account Number / Name'),
fields: [ fields: [
{ {
label: "Account Name", "label": "Account Name",
fieldname: "account_name", "fieldname": "account_name",
fieldtype: "Data", "fieldtype": "Data",
reqd: 1, "reqd": 1,
default: frm.doc.account_name, "default": frm.doc.account_name
}, },
{ {
label: "Account Number", "label": "Account Number",
fieldname: "account_number", "fieldname": "account_number",
fieldtype: "Data", "fieldtype": "Data",
default: frm.doc.account_number, "default": frm.doc.account_number
}, }
], ],
primary_action: function () { primary_action: function() {
var data = d.get_values(); var data = d.get_values();
if ( if(data.account_number === frm.doc.account_number && data.account_name === frm.doc.account_name) {
data.account_number === frm.doc.account_number &&
data.account_name === frm.doc.account_name
) {
d.hide(); d.hide();
return; return;
} }
@@ -187,11 +164,11 @@ frappe.ui.form.on("Account", {
args: { args: {
account_number: data.account_number, account_number: data.account_number,
account_name: data.account_name, account_name: data.account_name,
name: frm.doc.name, name: frm.doc.name
}, },
callback: function (r) { callback: function(r) {
if (!r.exc) { if(!r.exc) {
if (r.message) { if(r.message) {
frappe.set_route("Form", "Account", r.message); frappe.set_route("Form", "Account", r.message);
} else { } else {
frm.set_value("account_number", data.account_number); frm.set_value("account_number", data.account_number);
@@ -199,11 +176,11 @@ frappe.ui.form.on("Account", {
} }
d.hide(); d.hide();
} }
}, }
}); });
}, },
primary_action_label: __("Update"), primary_action_label: __('Update')
}); });
d.show(); d.show();
}, }
}); });

View File

@@ -18,6 +18,7 @@
"root_type", "root_type",
"report_type", "report_type",
"account_currency", "account_currency",
"inter_company_account",
"column_break1", "column_break1",
"parent_account", "parent_account",
"account_type", "account_type",
@@ -33,11 +34,15 @@
{ {
"fieldname": "properties", "fieldname": "properties",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"oldfieldtype": "Section Break" "oldfieldtype": "Section Break",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "column_break0", "fieldname": "column_break0",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1,
"width": "50%" "width": "50%"
}, },
{ {
@@ -48,7 +53,9 @@
"no_copy": 1, "no_copy": 1,
"oldfieldname": "account_name", "oldfieldname": "account_name",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"reqd": 1 "reqd": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "account_number", "fieldname": "account_number",
@@ -56,13 +63,17 @@
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Account Number", "label": "Account Number",
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"default": "0", "default": "0",
"fieldname": "is_group", "fieldname": "is_group",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Group" "label": "Is Group",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "company", "fieldname": "company",
@@ -74,7 +85,9 @@
"options": "Company", "options": "Company",
"read_only": 1, "read_only": 1,
"remember_last_selected_value": 1, "remember_last_selected_value": 1,
"reqd": 1 "reqd": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "root_type", "fieldname": "root_type",
@@ -82,7 +95,9 @@
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Root Type", "label": "Root Type",
"options": "\nAsset\nLiability\nIncome\nExpense\nEquity", "options": "\nAsset\nLiability\nIncome\nExpense\nEquity",
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "report_type", "fieldname": "report_type",
@@ -90,18 +105,32 @@
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Report Type", "label": "Report Type",
"options": "\nBalance Sheet\nProfit and Loss", "options": "\nBalance Sheet\nProfit and Loss",
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"depends_on": "eval:doc.is_group==0", "depends_on": "eval:doc.is_group==0",
"fieldname": "account_currency", "fieldname": "account_currency",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Currency", "label": "Currency",
"options": "Currency" "options": "Currency",
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"fieldname": "inter_company_account",
"fieldtype": "Check",
"label": "Inter Company Account",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "column_break1", "fieldname": "column_break1",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1,
"width": "50%" "width": "50%"
}, },
{ {
@@ -113,7 +142,9 @@
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Account", "options": "Account",
"reqd": 1, "reqd": 1,
"search_index": 1 "search_index": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"description": "Setting Account Type helps in selecting this Account in transactions.", "description": "Setting Account Type helps in selecting this Account in transactions.",
@@ -123,7 +154,9 @@
"label": "Account Type", "label": "Account Type",
"oldfieldname": "account_type", "oldfieldname": "account_type",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nCurrent Asset\nCurrent Liability\nDepreciation\nDirect Expense\nDirect Income\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nIndirect Expense\nIndirect Income\nLiability\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary" "options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nDepreciation\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"description": "Rate at which this tax is applied", "description": "Rate at which this tax is applied",
@@ -131,7 +164,9 @@
"fieldtype": "Float", "fieldtype": "Float",
"label": "Rate", "label": "Rate",
"oldfieldname": "tax_rate", "oldfieldname": "tax_rate",
"oldfieldtype": "Currency" "oldfieldtype": "Currency",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"description": "If the account is frozen, entries are allowed to restricted users.", "description": "If the account is frozen, entries are allowed to restricted users.",
@@ -140,13 +175,17 @@
"label": "Frozen", "label": "Frozen",
"oldfieldname": "freeze_account", "oldfieldname": "freeze_account",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "No\nYes" "options": "No\nYes",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "balance_must_be", "fieldname": "balance_must_be",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Balance must be", "label": "Balance must be",
"options": "\nDebit\nCredit" "options": "\nDebit\nCredit",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "lft", "fieldname": "lft",
@@ -155,7 +194,9 @@
"label": "Lft", "label": "Lft",
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 1,
"search_index": 1 "search_index": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "rgt", "fieldname": "rgt",
@@ -164,7 +205,9 @@
"label": "Rgt", "label": "Rgt",
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 1,
"search_index": 1 "search_index": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "old_parent", "fieldname": "old_parent",
@@ -172,27 +215,33 @@
"hidden": 1, "hidden": 1,
"label": "Old Parent", "label": "Old Parent",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:(doc.report_type == 'Profit and Loss' && !doc.is_group)", "depends_on": "eval:(doc.report_type == 'Profit and Loss' && !doc.is_group)",
"fieldname": "include_in_gross", "fieldname": "include_in_gross",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Include in gross" "label": "Include in gross",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"default": "0", "default": "0",
"fieldname": "disabled", "fieldname": "disabled",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Disable" "label": "Disable",
"show_days": 1,
"show_seconds": 1
} }
], ],
"icon": "fa fa-money", "icon": "fa fa-money",
"idx": 1, "idx": 1,
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"modified": "2023-07-20 18:18:44.405723", "modified": "2020-06-11 15:15:54.338622",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Account", "name": "Account",
@@ -243,6 +292,7 @@
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Accounts Manager", "role": "Accounts Manager",
"set_user_permissions": 1,
"share": 1, "share": 1,
"write": 1 "write": 1
} }
@@ -251,6 +301,5 @@
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "ASC", "sort_order": "ASC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -1,89 +1,17 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe import frappe
from frappe import _, throw
from frappe.utils import cint, cstr from frappe.utils import cint, cstr
from frappe import throw, _
from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_of from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_of
import erpnext class RootNotEditable(frappe.ValidationError): pass
class BalanceMismatchError(frappe.ValidationError): pass
class RootNotEditable(frappe.ValidationError):
pass
class BalanceMismatchError(frappe.ValidationError):
pass
class InvalidAccountMergeError(frappe.ValidationError):
pass
class Account(NestedSet): class Account(NestedSet):
# begin: auto-generated types nsm_parent_field = 'parent_account'
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
account_currency: DF.Link | None
account_name: DF.Data
account_number: DF.Data | None
account_type: DF.Literal[
"",
"Accumulated Depreciation",
"Asset Received But Not Billed",
"Bank",
"Cash",
"Chargeable",
"Capital Work in Progress",
"Cost of Goods Sold",
"Current Asset",
"Current Liability",
"Depreciation",
"Direct Expense",
"Direct Income",
"Equity",
"Expense Account",
"Expenses Included In Asset Valuation",
"Expenses Included In Valuation",
"Fixed Asset",
"Income Account",
"Indirect Expense",
"Indirect Income",
"Liability",
"Payable",
"Receivable",
"Round Off",
"Stock",
"Stock Adjustment",
"Stock Received But Not Billed",
"Service Received But Not Billed",
"Tax",
"Temporary",
]
balance_must_be: DF.Literal["", "Debit", "Credit"]
company: DF.Link
disabled: DF.Check
freeze_account: DF.Literal["No", "Yes"]
include_in_gross: DF.Check
is_group: DF.Check
lft: DF.Int
old_parent: DF.Data | None
parent_account: DF.Link
report_type: DF.Literal["", "Balance Sheet", "Profit and Loss"]
rgt: DF.Int
root_type: DF.Literal["", "Asset", "Liability", "Income", "Expense", "Equity"]
tax_rate: DF.Float
# end: auto-generated types
nsm_parent_field = "parent_account"
def on_update(self): def on_update(self):
if frappe.local.flags.ignore_update_nsm: if frappe.local.flags.ignore_update_nsm:
return return
@@ -91,24 +19,20 @@ class Account(NestedSet):
super(Account, self).on_update() super(Account, self).on_update()
def onload(self): def onload(self):
frozen_accounts_modifier = frappe.db.get_value( frozen_accounts_modifier = frappe.db.get_value("Accounts Settings", "Accounts Settings",
"Accounts Settings", "Accounts Settings", "frozen_accounts_modifier" "frozen_accounts_modifier")
)
if not frozen_accounts_modifier or frozen_accounts_modifier in frappe.get_roles(): if not frozen_accounts_modifier or frozen_accounts_modifier in frappe.get_roles():
self.set_onload("can_freeze_account", True) self.set_onload("can_freeze_account", True)
def autoname(self): def autoname(self):
from erpnext.accounts.utils import get_autoname_with_number from erpnext.accounts.utils import get_autoname_with_number
self.name = get_autoname_with_number(self.account_number, self.account_name, None, self.company)
self.name = get_autoname_with_number(self.account_number, self.account_name, self.company)
def validate(self): def validate(self):
from erpnext.accounts.utils import validate_field_number from erpnext.accounts.utils import validate_field_number
if frappe.local.flags.allow_unverified_charts: if frappe.local.flags.allow_unverified_charts:
return return
self.validate_parent() self.validate_parent()
self.validate_parent_child_account_type()
self.validate_root_details() self.validate_root_details()
validate_field_number("Account", self.name, self.account_number, self.company, "account_number") validate_field_number("Account", self.name, self.account_number, self.company, "account_number")
self.validate_group_or_ledger() self.validate_group_or_ledger()
@@ -118,52 +42,26 @@ class Account(NestedSet):
self.validate_balance_must_be_debit_or_credit() self.validate_balance_must_be_debit_or_credit()
self.validate_account_currency() self.validate_account_currency()
self.validate_root_company_and_sync_account_to_children() self.validate_root_company_and_sync_account_to_children()
self.validate_receivable_payable_account_type()
def validate_parent_child_account_type(self):
if self.parent_account:
if self.account_type in [
"Direct Income",
"Indirect Income",
"Current Asset",
"Current Liability",
"Direct Expense",
"Indirect Expense",
]:
parent_account_type = frappe.db.get_value("Account", self.parent_account, ["account_type"])
if parent_account_type == self.account_type:
throw(_("Only Parent can be of type {0}").format(self.account_type))
def validate_parent(self): def validate_parent(self):
"""Fetch Parent Details and validate parent account""" """Fetch Parent Details and validate parent account"""
if self.parent_account: if self.parent_account:
par = frappe.get_cached_value( par = frappe.db.get_value("Account", self.parent_account,
"Account", self.parent_account, ["name", "is_group", "company"], as_dict=1 ["name", "is_group", "company"], as_dict=1)
)
if not par: if not par:
throw( throw(_("Account {0}: Parent account {1} does not exist").format(self.name, self.parent_account))
_("Account {0}: Parent account {1} does not exist").format(self.name, self.parent_account)
)
elif par.name == self.name: elif par.name == self.name:
throw(_("Account {0}: You can not assign itself as parent account").format(self.name)) throw(_("Account {0}: You can not assign itself as parent account").format(self.name))
elif not par.is_group: elif not par.is_group:
throw( throw(_("Account {0}: Parent account {1} can not be a ledger").format(self.name, self.parent_account))
_("Account {0}: Parent account {1} can not be a ledger").format(
self.name, self.parent_account
)
)
elif par.company != self.company: elif par.company != self.company:
throw( throw(_("Account {0}: Parent account {1} does not belong to company: {2}")
_("Account {0}: Parent account {1} does not belong to company: {2}").format( .format(self.name, self.parent_account, self.company))
self.name, self.parent_account, self.company
)
)
def set_root_and_report_type(self): def set_root_and_report_type(self):
if self.parent_account: if self.parent_account:
par = frappe.get_cached_value( par = frappe.db.get_value("Account", self.parent_account,
"Account", self.parent_account, ["report_type", "root_type"], as_dict=1 ["report_type", "root_type"], as_dict=1)
)
if par.report_type: if par.report_type:
self.report_type = par.report_type self.report_type = par.report_type
@@ -171,75 +69,45 @@ class Account(NestedSet):
self.root_type = par.root_type self.root_type = par.root_type
if self.is_group: if self.is_group:
db_value = self.get_doc_before_save() db_value = frappe.db.get_value("Account", self.name, ["report_type", "root_type"], as_dict=1)
if db_value: if db_value:
if self.report_type != db_value.report_type: if self.report_type != db_value.report_type:
frappe.db.sql( frappe.db.sql("update `tabAccount` set report_type=%s where lft > %s and rgt < %s",
"update `tabAccount` set report_type=%s where lft > %s and rgt < %s", (self.report_type, self.lft, self.rgt))
(self.report_type, self.lft, self.rgt),
)
if self.root_type != db_value.root_type: if self.root_type != db_value.root_type:
frappe.db.sql( frappe.db.sql("update `tabAccount` set root_type=%s where lft > %s and rgt < %s",
"update `tabAccount` set root_type=%s where lft > %s and rgt < %s", (self.root_type, self.lft, self.rgt))
(self.root_type, self.lft, self.rgt),
)
if self.root_type and not self.report_type: if self.root_type and not self.report_type:
self.report_type = ( self.report_type = "Balance Sheet" \
"Balance Sheet" if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss" if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss"
)
def validate_receivable_payable_account_type(self):
doc_before_save = self.get_doc_before_save()
receivable_payable_types = ["Receivable", "Payable"]
if (
doc_before_save
and doc_before_save.account_type in receivable_payable_types
and doc_before_save.account_type != self.account_type
):
# check for ledger entries
if frappe.db.get_all("GL Entry", filters={"account": self.name, "is_cancelled": 0}, limit=1):
msg = _(
"There are ledger entries against this account. Changing {0} to non-{1} in live system will cause incorrect output in 'Accounts {2}' report"
).format(
frappe.bold("Account Type"), doc_before_save.account_type, doc_before_save.account_type
)
frappe.msgprint(msg)
self.add_comment("Comment", msg)
def validate_root_details(self): def validate_root_details(self):
doc_before_save = self.get_doc_before_save() # does not exists parent
if frappe.db.exists("Account", self.name):
if doc_before_save and not doc_before_save.parent_account: if not frappe.db.get_value("Account", self.name, "parent_account"):
throw(_("Root cannot be edited."), RootNotEditable) throw(_("Root cannot be edited."), RootNotEditable)
if not self.parent_account and not self.is_group: if not self.parent_account and not self.is_group:
throw(_("The root account {0} must be a group").format(frappe.bold(self.name))) frappe.throw(_("The root account {0} must be a group").format(frappe.bold(self.name)))
def validate_root_company_and_sync_account_to_children(self): def validate_root_company_and_sync_account_to_children(self):
# ignore validation while creating new compnay or while syncing to child companies # ignore validation while creating new compnay or while syncing to child companies
if ( if frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation:
frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation
):
return return
ancestors = get_root_company(self.company) ancestors = get_root_company(self.company)
if ancestors: if ancestors:
if frappe.get_cached_value( if frappe.get_value("Company", self.company, "allow_account_creation_against_child_company"):
"Company", self.company, "allow_account_creation_against_child_company"
):
return return
if not frappe.db.get_value( if not frappe.db.get_value("Account",
"Account", {"account_name": self.account_name, "company": ancestors[0]}, "name" {'account_name': self.account_name, 'company': ancestors[0]}, 'name'):
):
frappe.throw(_("Please add the account to root level Company - {}").format(ancestors[0])) frappe.throw(_("Please add the account to root level Company - {}").format(ancestors[0]))
elif self.parent_account: elif self.parent_account:
descendants = get_descendants_of("Company", self.company) descendants = get_descendants_of('Company', self.company)
if not descendants: if not descendants: return
return
parent_acc_name_map = {} parent_acc_name_map = {}
parent_acc_name, parent_acc_number = frappe.get_cached_value( parent_acc_name, parent_acc_number = frappe.db.get_value('Account', self.parent_account, \
"Account", self.parent_account, ["account_name", "account_number"] ["account_name", "account_number"])
)
filters = { filters = {
"company": ["in", descendants], "company": ["in", descendants],
"account_name": parent_acc_name, "account_name": parent_acc_name,
@@ -247,69 +115,50 @@ class Account(NestedSet):
if parent_acc_number: if parent_acc_number:
filters["account_number"] = parent_acc_number filters["account_number"] = parent_acc_number
for d in frappe.db.get_values( for d in frappe.db.get_values('Account', filters=filters, fieldname=["company", "name"], as_dict=True):
"Account", filters=filters, fieldname=["company", "name"], as_dict=True
):
parent_acc_name_map[d["company"]] = d["name"] parent_acc_name_map[d["company"]] = d["name"]
if not parent_acc_name_map: if not parent_acc_name_map: return
return
self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name) self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name)
def validate_group_or_ledger(self): def validate_group_or_ledger(self):
doc_before_save = self.get_doc_before_save() if self.get("__islocal"):
if not doc_before_save or cint(doc_before_save.is_group) == cint(self.is_group):
return return
if self.check_gle_exists(): existing_is_group = frappe.db.get_value("Account", self.name, "is_group")
throw(_("Account with existing transaction cannot be converted to ledger")) if cint(self.is_group) != cint(existing_is_group):
elif self.is_group: if self.check_gle_exists():
if self.account_type and not self.flags.exclude_account_type_check: throw(_("Account with existing transaction cannot be converted to ledger"))
throw(_("Cannot covert to Group because Account Type is selected.")) elif self.is_group:
elif self.check_if_child_exists(): if self.account_type and not self.flags.exclude_account_type_check:
throw(_("Account with child nodes cannot be set as ledger")) throw(_("Cannot covert to Group because Account Type is selected."))
elif self.check_if_child_exists():
throw(_("Account with child nodes cannot be set as ledger"))
def validate_frozen_accounts_modifier(self): def validate_frozen_accounts_modifier(self):
doc_before_save = self.get_doc_before_save() old_value = frappe.db.get_value("Account", self.name, "freeze_account")
if not doc_before_save or doc_before_save.freeze_account == self.freeze_account: if old_value and old_value != self.freeze_account:
return frozen_accounts_modifier = frappe.db.get_value('Accounts Settings', None, 'frozen_accounts_modifier')
if not frozen_accounts_modifier or \
frozen_accounts_modifier = frappe.get_cached_value( frozen_accounts_modifier not in frappe.get_roles():
"Accounts Settings", "Accounts Settings", "frozen_accounts_modifier" throw(_("You are not authorized to set Frozen value"))
)
if not frozen_accounts_modifier or frozen_accounts_modifier not in frappe.get_roles():
throw(_("You are not authorized to set Frozen value"))
def validate_balance_must_be_debit_or_credit(self): def validate_balance_must_be_debit_or_credit(self):
from erpnext.accounts.utils import get_balance_on from erpnext.accounts.utils import get_balance_on
if not self.get("__islocal") and self.balance_must_be: if not self.get("__islocal") and self.balance_must_be:
account_balance = get_balance_on(self.name) account_balance = get_balance_on(self.name)
if account_balance > 0 and self.balance_must_be == "Credit": if account_balance > 0 and self.balance_must_be == "Credit":
frappe.throw( frappe.throw(_("Account balance already in Debit, you are not allowed to set 'Balance Must Be' as 'Credit'"))
_(
"Account balance already in Debit, you are not allowed to set 'Balance Must Be' as 'Credit'"
)
)
elif account_balance < 0 and self.balance_must_be == "Debit": elif account_balance < 0 and self.balance_must_be == "Debit":
frappe.throw( frappe.throw(_("Account balance already in Credit, you are not allowed to set 'Balance Must Be' as 'Debit'"))
_(
"Account balance already in Credit, you are not allowed to set 'Balance Must Be' as 'Debit'"
)
)
def validate_account_currency(self): def validate_account_currency(self):
self.currency_explicitly_specified = True
if not self.account_currency: if not self.account_currency:
self.account_currency = frappe.get_cached_value("Company", self.company, "default_currency") self.account_currency = frappe.get_cached_value('Company', self.company, "default_currency")
self.currency_explicitly_specified = False
gl_currency = frappe.db.get_value("GL Entry", {"account": self.name}, "account_currency") elif self.account_currency != frappe.db.get_value("Account", self.name, "account_currency"):
if gl_currency and self.account_currency != gl_currency:
if frappe.db.get_value("GL Entry", {"account": self.name}): if frappe.db.get_value("GL Entry", {"account": self.name}):
frappe.throw(_("Currency can not be changed after making entries using some other currency")) frappe.throw(_("Currency can not be changed after making entries using some other currency"))
@@ -318,54 +167,46 @@ class Account(NestedSet):
company_bold = frappe.bold(company) company_bold = frappe.bold(company)
parent_acc_name_bold = frappe.bold(parent_acc_name) parent_acc_name_bold = frappe.bold(parent_acc_name)
if not parent_acc_name_map.get(company): if not parent_acc_name_map.get(company):
frappe.throw( frappe.throw(_("While creating account for Child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA")
_( .format(company_bold, parent_acc_name_bold), title=_("Account Not Found"))
"While creating account for Child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA"
).format(company_bold, parent_acc_name_bold),
title=_("Account Not Found"),
)
# validate if parent of child company account to be added is a group # validate if parent of child company account to be added is a group
if frappe.get_cached_value( if (frappe.db.get_value("Account", self.parent_account, "is_group")
"Account", self.parent_account, "is_group" and not frappe.db.get_value("Account", parent_acc_name_map[company], "is_group")):
) and not frappe.get_cached_value("Account", parent_acc_name_map[company], "is_group"): msg = _("While creating account for Child Company {0}, parent account {1} found as a ledger account.").format(company_bold, parent_acc_name_bold)
msg = _(
"While creating account for Child Company {0}, parent account {1} found as a ledger account."
).format(company_bold, parent_acc_name_bold)
msg += "<br><br>" msg += "<br><br>"
msg += _( msg += _("Please convert the parent account in corresponding child company to a group account.")
"Please convert the parent account in corresponding child company to a group account."
)
frappe.throw(msg, title=_("Invalid Parent Account")) frappe.throw(msg, title=_("Invalid Parent Account"))
filters = {"account_name": self.account_name, "company": company} filters = {
"account_name": self.account_name,
"company": company
}
if self.account_number: if self.account_number:
filters["account_number"] = self.account_number filters["account_number"] = self.account_number
child_account = frappe.db.get_value("Account", filters, "name") child_account = frappe.db.get_value("Account", filters, 'name')
if not child_account: if not child_account:
doc = frappe.copy_doc(self) doc = frappe.copy_doc(self)
doc.flags.ignore_root_company_validation = True doc.flags.ignore_root_company_validation = True
doc.update( doc.update({
{ "company": company,
"company": company, # parent account's currency should be passed down to child account's curreny
# parent account's currency should be passed down to child account's curreny # if it is None, it picks it up from default company currency, which might be unintended
# if currency explicitly specified by user, child will inherit. else, default currency will be used. "account_currency": self.account_currency,
"account_currency": self.account_currency "parent_account": parent_acc_name_map[company]
if self.currency_explicitly_specified })
else erpnext.get_company_currency(company),
"parent_account": parent_acc_name_map[company],
}
)
doc.save() doc.save()
frappe.msgprint(_("Account {0} is added in the child company {1}").format(doc.name, company)) frappe.msgprint(_("Account {0} is added in the child company {1}")
.format(doc.name, company))
elif child_account: elif child_account:
# update the parent company's value in child companies # update the parent company's value in child companies
doc = frappe.get_doc("Account", child_account) doc = frappe.get_doc("Account", child_account)
parent_value_changed = False parent_value_changed = False
for field in ["account_type", "freeze_account", "balance_must_be"]: for field in ['account_type', 'account_currency',
'freeze_account', 'balance_must_be']:
if doc.get(field) != self.get(field): if doc.get(field) != self.get(field):
parent_value_changed = True parent_value_changed = True
doc.set(field, self.get(field)) doc.set(field, self.get(field))
@@ -400,11 +241,8 @@ class Account(NestedSet):
return frappe.db.get_value("GL Entry", {"account": self.name}) return frappe.db.get_value("GL Entry", {"account": self.name})
def check_if_child_exists(self): def check_if_child_exists(self):
return frappe.db.sql( return frappe.db.sql("""select name from `tabAccount` where parent_account = %s
"""select name from `tabAccount` where parent_account = %s and docstatus != 2""", self.name)
and docstatus != 2""",
self.name,
)
def validate_mandatory(self): def validate_mandatory(self):
if not self.root_type: if not self.root_type:
@@ -420,103 +258,73 @@ class Account(NestedSet):
super(Account, self).on_trash(True) super(Account, self).on_trash(True)
@frappe.whitelist() @frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs @frappe.validate_and_sanitize_search_inputs
def get_parent_account(doctype, txt, searchfield, start, page_len, filters): def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql( return frappe.db.sql("""select name from tabAccount
"""select name from tabAccount
where is_group = 1 and docstatus != 2 and company = %s where is_group = 1 and docstatus != 2 and company = %s
and %s like %s order by name limit %s offset %s""" and %s like %s order by name limit %s, %s""" %
% ("%s", searchfield, "%s", "%s", "%s"), ("%s", searchfield, "%s", "%s", "%s"),
(filters["company"], "%%%s%%" % txt, page_len, start), (filters["company"], "%%%s%%" % txt, start, page_len), as_list=1)
as_list=1,
)
def get_account_currency(account): def get_account_currency(account):
"""Helper function to get account currency""" """Helper function to get account currency"""
if not account: if not account:
return return
def generator(): def generator():
account_currency, company = frappe.get_cached_value( account_currency, company = frappe.get_cached_value("Account", account, ["account_currency", "company"])
"Account", account, ["account_currency", "company"]
)
if not account_currency: if not account_currency:
account_currency = frappe.get_cached_value("Company", company, "default_currency") account_currency = frappe.get_cached_value('Company', company, "default_currency")
return account_currency return account_currency
return frappe.local_cache("account_currency", account, generator) return frappe.local_cache("account_currency", account, generator)
def on_doctype_update(): def on_doctype_update():
frappe.db.add_index("Account", ["lft", "rgt"]) frappe.db.add_index("Account", ["lft", "rgt"])
def get_account_autoname(account_number, account_name, company): def get_account_autoname(account_number, account_name, company):
# first validate if company exists # first validate if company exists
company = frappe.get_cached_value("Company", company, ["abbr", "name"], as_dict=True) company = frappe.get_cached_value('Company', company, ["abbr", "name"], as_dict=True)
if not company: if not company:
frappe.throw(_("Company {0} does not exist").format(company)) frappe.throw(_('Company {0} does not exist').format(company))
parts = [account_name.strip(), company.abbr] parts = [account_name.strip(), company.abbr]
if cstr(account_number).strip(): if cstr(account_number).strip():
parts.insert(0, cstr(account_number).strip()) parts.insert(0, cstr(account_number).strip())
return " - ".join(parts) return ' - '.join(parts)
def validate_account_number(name, account_number, company): def validate_account_number(name, account_number, company):
if account_number: if account_number:
account_with_same_number = frappe.db.get_value( account_with_same_number = frappe.db.get_value("Account",
"Account", {"account_number": account_number, "company": company, "name": ["!=", name]} {"account_number": account_number, "company": company, "name": ["!=", name]})
)
if account_with_same_number: if account_with_same_number:
frappe.throw( frappe.throw(_("Account Number {0} already used in account {1}")
_("Account Number {0} already used in account {1}").format( .format(account_number, account_with_same_number))
account_number, account_with_same_number
)
)
@frappe.whitelist() @frappe.whitelist()
def update_account_number(name, account_name, account_number=None, from_descendant=False): def update_account_number(name, account_name, account_number=None, from_descendant=False):
account = frappe.get_cached_doc("Account", name) account = frappe.db.get_value("Account", name, "company", as_dict=True)
if not account: if not account: return
return
old_acc_name, old_acc_number = account.account_name, account.account_number old_acc_name, old_acc_number = frappe.db.get_value('Account', name, \
["account_name", "account_number"])
# check if account exists in parent company # check if account exists in parent company
ancestors = get_ancestors_of("Company", account.company) ancestors = get_ancestors_of("Company", account.company)
allow_independent_account_creation = frappe.get_cached_value( allow_independent_account_creation = frappe.get_value("Company", account.company, "allow_account_creation_against_child_company")
"Company", account.company, "allow_account_creation_against_child_company"
)
if ancestors and not allow_independent_account_creation: if ancestors and not allow_independent_account_creation:
for ancestor in ancestors: for ancestor in ancestors:
old_name = frappe.db.get_value( if frappe.db.get_value("Account", {'account_name': old_acc_name, 'company': ancestor}, 'name'):
"Account",
{"account_number": old_acc_number, "account_name": old_acc_name, "company": ancestor},
"name",
)
if old_name:
# same account in parent company exists # same account in parent company exists
allow_child_account_creation = _("Allow Account Creation Against Child Company") allow_child_account_creation = _("Allow Account Creation Against Child Company")
message = _("Account {0} exists in parent company {1}.").format( message = _("Account {0} exists in parent company {1}.").format(frappe.bold(old_acc_name), frappe.bold(ancestor))
frappe.bold(old_acc_name), frappe.bold(ancestor)
)
message += "<br>" message += "<br>"
message += _("Renaming it is only allowed via parent company {0}, to avoid mismatch.").format( message += _("Renaming it is only allowed via parent company {0}, to avoid mismatch.").format(frappe.bold(ancestor))
frappe.bold(ancestor)
)
message += "<br><br>" message += "<br><br>"
message += _("To overrule this, enable '{0}' in company {1}").format( message += _("To overrule this, enable '{0}' in company {1}").format(allow_child_account_creation, frappe.bold(account.company))
allow_child_account_creation, frappe.bold(account.company)
)
frappe.throw(message, title=_("Rename Not Allowed")) frappe.throw(message, title=_("Rename Not Allowed"))
@@ -529,64 +337,42 @@ def update_account_number(name, account_name, account_number=None, from_descenda
if not from_descendant: if not from_descendant:
# Update and rename in child company accounts as well # Update and rename in child company accounts as well
descendants = get_descendants_of("Company", account.company) descendants = get_descendants_of('Company', account.company)
if descendants: if descendants:
sync_update_account_number_in_child( sync_update_account_number_in_child(descendants, old_acc_name, account_name, account_number, old_acc_number)
descendants, old_acc_name, account_name, account_number, old_acc_number
)
new_name = get_account_autoname(account_number, account_name, account.company) new_name = get_account_autoname(account_number, account_name, account.company)
if name != new_name: if name != new_name:
frappe.rename_doc("Account", name, new_name, force=1) frappe.rename_doc("Account", name, new_name, force=1)
return new_name return new_name
@frappe.whitelist() @frappe.whitelist()
def merge_account(old, new): def merge_account(old, new, is_group, root_type, company):
# Validate properties before merging # Validate properties before merging
new_account = frappe.get_cached_doc("Account", new) if not frappe.db.exists("Account", new):
old_account = frappe.get_cached_doc("Account", old)
if not new_account:
throw(_("Account {0} does not exist").format(new)) throw(_("Account {0} does not exist").format(new))
if ( val = list(frappe.db.get_value("Account", new,
cint(new_account.is_group), ["is_group", "root_type", "company"]))
new_account.root_type,
new_account.company,
cstr(new_account.account_currency),
) != (
cint(old_account.is_group),
old_account.root_type,
old_account.company,
cstr(old_account.account_currency),
):
throw(
msg=_(
"""Merging is only possible if following properties are same in both records. Is Group, Root Type, Company and Account Currency"""
),
title=("Invalid Accounts"),
exc=InvalidAccountMergeError,
)
if old_account.is_group and new_account.parent_account == old: if val != [cint(is_group), root_type, company]:
new_account.db_set("parent_account", frappe.get_cached_value("Account", old, "parent_account")) throw(_("""Merging is only possible if following properties are same in both records. Is Group, Root Type, Company"""))
if is_group and frappe.db.get_value("Account", new, "parent_account") == old:
frappe.db.set_value("Account", new, "parent_account",
frappe.db.get_value("Account", old, "parent_account"))
frappe.rename_doc("Account", old, new, merge=1, force=1) frappe.rename_doc("Account", old, new, merge=1, force=1)
return new return new
@frappe.whitelist() @frappe.whitelist()
def get_root_company(company): def get_root_company(company):
# return the topmost company in the hierarchy # return the topmost company in the hierarchy
ancestors = get_ancestors_of("Company", company, "lft asc") ancestors = get_ancestors_of('Company', company, "lft asc")
return [ancestors[0]] if ancestors else [] return [ancestors[0]] if ancestors else []
def sync_update_account_number_in_child(descendants, old_acc_name, account_name, account_number=None, old_acc_number=None):
def sync_update_account_number_in_child(
descendants, old_acc_name, account_name, account_number=None, old_acc_number=None
):
filters = { filters = {
"company": ["in", descendants], "company": ["in", descendants],
"account_name": old_acc_name, "account_name": old_acc_name,
@@ -594,7 +380,5 @@ def sync_update_account_number_in_child(
if old_acc_number: if old_acc_number:
filters["account_number"] = old_acc_number filters["account_number"] = old_acc_number
for d in frappe.db.get_values( for d in frappe.db.get_values('Account', filters=filters, fieldname=["company", "name"], as_dict=True):
"Account", filters=filters, fieldname=["company", "name"], as_dict=True update_account_number(d["name"], account_name, account_number, from_descendant=True)
):
update_account_number(d["name"], account_name, account_number, from_descendant=True)

View File

@@ -1,4 +1,4 @@
frappe.provide("frappe.treeview_settings"); frappe.provide("frappe.treeview_settings")
frappe.treeview_settings["Account"] = { frappe.treeview_settings["Account"] = {
breadcrumb: "Accounts", breadcrumb: "Accounts",
@@ -7,12 +7,12 @@ frappe.treeview_settings["Account"] = {
filters: [ filters: [
{ {
fieldname: "company", fieldname: "company",
fieldtype: "Select", fieldtype:"Select",
options: erpnext.utils.get_tree_options("company"), options: erpnext.utils.get_tree_options("company"),
label: __("Company"), label: __("Company"),
default: erpnext.utils.get_tree_default("company"), default: erpnext.utils.get_tree_default("company"),
on_change: function () { on_change: function() {
var me = frappe.treeview_settings["Account"].treeview; var me = frappe.treeview_settings['Account'].treeview;
var company = me.page.fields_dict.company.get_value(); var company = me.page.fields_dict.company.get_value();
if (!company) { if (!company) {
frappe.throw(__("Please set a Company")); frappe.throw(__("Please set a Company"));
@@ -22,272 +22,155 @@ frappe.treeview_settings["Account"] = {
args: { args: {
company: company, company: company,
}, },
callback: function (r) { callback: function(r) {
if (r.message) { if(r.message) {
let root_company = r.message.length ? r.message[0] : ""; let root_company = r.message.length ? r.message[0] : "";
me.page.fields_dict.root_company.set_value(root_company); me.page.fields_dict.root_company.set_value(root_company);
frappe.db.get_value( frappe.db.get_value("Company", {"name": company}, "allow_account_creation_against_child_company", (r) => {
"Company", frappe.flags.ignore_root_company_validation = r.allow_account_creation_against_child_company;
{ name: company }, });
"allow_account_creation_against_child_company",
(r) => {
frappe.flags.ignore_root_company_validation =
r.allow_account_creation_against_child_company;
}
);
}
},
});
},
},
{
fieldname: "root_company",
fieldtype: "Data",
label: __("Root Company"),
hidden: true,
disable_onchange: true,
},
],
root_label: "Accounts",
get_tree_nodes: "erpnext.accounts.utils.get_children",
on_get_node: function (nodes, deep = false) {
if (frappe.boot.user.can_read.indexOf("GL Entry") == -1) return;
let accounts = [];
if (deep) {
// in case of `get_all_nodes`
accounts = nodes.reduce((acc, node) => [...acc, ...node.data], []);
} else {
accounts = nodes;
}
frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => {
if (value) {
const get_balances = frappe.call({
method: "erpnext.accounts.utils.get_account_balances",
args: {
accounts: accounts,
company: cur_tree.args.company,
},
});
get_balances.then((r) => {
if (!r.message || r.message.length == 0) return;
for (let account of r.message) {
const node = cur_tree.nodes && cur_tree.nodes[account.value];
if (!node || node.is_root) continue;
// show Dr if positive since balance is calculated as debit - credit else show Cr
const balance = account.balance_in_account_currency || account.balance;
const dr_or_cr = balance > 0 ? "Dr" : "Cr";
const format = (value, currency) => format_currency(Math.abs(value), currency);
if (account.balance !== undefined) {
node.parent && node.parent.find(".balance-area").remove();
$(
'<span class="balance-area pull-right">' +
(account.balance_in_account_currency
? format(
account.balance_in_account_currency,
account.account_currency
) + " / "
: "") +
format(account.balance, account.company_currency) +
" " +
dr_or_cr +
"</span>"
).insertBefore(node.$ul);
} }
} }
}); });
} }
});
},
add_tree_node: "erpnext.accounts.utils.add_ac",
menu_items: [
{
label: __("New Company"),
action: function () {
frappe.new_doc("Company", true);
},
condition: 'frappe.boot.user.can_create.indexOf("Company") !== -1',
}, },
{
fieldname: "root_company",
fieldtype:"Data",
label: __("Root Company"),
hidden: true,
disable_onchange: true
}
],
root_label: "Accounts",
get_tree_nodes: 'erpnext.accounts.utils.get_children',
add_tree_node: 'erpnext.accounts.utils.add_ac',
menu_items:[
{
label: __('New Company'),
action: function() { frappe.new_doc("Company", true) },
condition: 'frappe.boot.user.can_create.indexOf("Company") !== -1'
}
], ],
fields: [ fields: [
{ {fieldtype:'Data', fieldname:'account_name', label:__('New Account Name'), reqd:true,
fieldtype: "Data", description: __("Name of new Account. Note: Please don't create accounts for Customers and Suppliers")},
fieldname: "account_name", {fieldtype:'Data', fieldname:'account_number', label:__('Account Number'),
label: __("New Account Name"), description: __("Number of new Account, it will be included in the account name as a prefix")},
reqd: true, {fieldtype:'Check', fieldname:'is_group', label:__('Is Group'),
description: __( description: __('Further accounts can be made under Groups, but entries can be made against non-Groups')},
"Name of new Account. Note: Please don't create accounts for Customers and Suppliers" {fieldtype:'Select', fieldname:'root_type', label:__('Root Type'),
), options: ['Asset', 'Liability', 'Equity', 'Income', 'Expense'].join('\n'),
}, depends_on: 'eval:doc.is_group && !doc.parent_account'},
{ {fieldtype:'Select', fieldname:'account_type', label:__('Account Type'),
fieldtype: "Data", options: frappe.get_meta("Account").fields.filter(d => d.fieldname=='account_type')[0].options,
fieldname: "account_number", description: __("Optional. This setting will be used to filter in various transactions.")
label: __("Account Number"),
description: __("Number of new Account, it will be included in the account name as a prefix"),
},
{
fieldtype: "Check",
fieldname: "is_group",
label: __("Is Group"),
description: __(
"Further accounts can be made under Groups, but entries can be made against non-Groups"
),
},
{
fieldtype: "Select",
fieldname: "root_type",
label: __("Root Type"),
options: ["Asset", "Liability", "Equity", "Income", "Expense"].join("\n"),
depends_on: "eval:doc.is_group && !doc.parent_account",
},
{
fieldtype: "Select",
fieldname: "account_type",
label: __("Account Type"),
options: frappe.get_meta("Account").fields.filter((d) => d.fieldname == "account_type")[0]
.options,
description: __("Optional. This setting will be used to filter in various transactions."),
},
{
fieldtype: "Float",
fieldname: "tax_rate",
label: __("Tax Rate"),
depends_on: 'eval:doc.is_group==0&&doc.account_type=="Tax"',
},
{
fieldtype: "Link",
fieldname: "account_currency",
label: __("Currency"),
options: "Currency",
description: __("Optional. Sets company's default currency, if not specified."),
}, },
{fieldtype:'Float', fieldname:'tax_rate', label:__('Tax Rate'),
depends_on: 'eval:doc.is_group==0&&doc.account_type=="Tax"'},
{fieldtype:'Link', fieldname:'account_currency', label:__('Currency'), options:"Currency",
description: __("Optional. Sets company's default currency, if not specified.")}
], ],
ignore_fields: ["parent_account"], ignore_fields:["parent_account"],
onload: function (treeview) { onload: function(treeview) {
frappe.treeview_settings["Account"].treeview = {}; frappe.treeview_settings['Account'].treeview = {};
$.extend(frappe.treeview_settings["Account"].treeview, treeview); $.extend(frappe.treeview_settings['Account'].treeview, treeview);
function get_company() { function get_company() {
return treeview.page.fields_dict.company.get_value(); return treeview.page.fields_dict.company.get_value();
} }
// tools // tools
treeview.page.add_inner_button( treeview.page.add_inner_button(__("Chart of Cost Centers"), function() {
__("Chart of Cost Centers"), frappe.set_route('Tree', 'Cost Center', {company: get_company()});
function () { }, __('View'));
frappe.set_route("Tree", "Cost Center", { company: get_company() });
},
__("View")
);
treeview.page.add_inner_button( treeview.page.add_inner_button(__("Opening Invoice Creation Tool"), function() {
__("Opening Invoice Creation Tool"), frappe.set_route('Form', 'Opening Invoice Creation Tool', {company: get_company()});
function () { }, __('View'));
frappe.set_route("Form", "Opening Invoice Creation Tool", { company: get_company() });
},
__("View")
);
treeview.page.add_inner_button( treeview.page.add_inner_button(__("Period Closing Voucher"), function() {
__("Period Closing Voucher"), frappe.set_route('List', 'Period Closing Voucher', {company: get_company()});
function () { }, __('View'));
frappe.set_route("List", "Period Closing Voucher", { company: get_company() });
},
__("View")
);
treeview.page.add_inner_button(
__("Journal Entry"), treeview.page.add_inner_button(__("Journal Entry"), function() {
function () { frappe.new_doc('Journal Entry', {company: get_company()});
frappe.new_doc("Journal Entry", { company: get_company() }); }, __('Create'));
}, treeview.page.add_inner_button(__("Company"), function() {
__("Create") frappe.new_doc('Company');
); }, __('Create'));
treeview.page.add_inner_button(
__("Company"),
function () {
frappe.new_doc("Company");
},
__("Create")
);
// financial statements // financial statements
for (let report of [ for (let report of ['Trial Balance', 'General Ledger', 'Balance Sheet',
"Trial Balance", 'Profit and Loss Statement', 'Cash Flow Statement', 'Accounts Payable', 'Accounts Receivable']) {
"General Ledger", treeview.page.add_inner_button(__(report), function() {
"Balance Sheet", frappe.set_route('query-report', report, {company: get_company()});
"Profit and Loss Statement", }, __('Financial Statements'));
"Cash Flow Statement",
"Accounts Payable",
"Accounts Receivable",
]) {
treeview.page.add_inner_button(
__(report),
function () {
frappe.set_route("query-report", report, { company: get_company() });
},
__("Financial Statements")
);
} }
},
post_render: function (treeview) {
frappe.treeview_settings["Account"].treeview["tree"] = treeview.tree;
treeview.page.set_primary_action(
__("New"),
function () {
let root_company = treeview.page.fields_dict.root_company.get_value();
if (root_company) { },
frappe.throw(__("Please add the account to root level Company - {0}"), [root_company]); post_render: function(treeview) {
} else { frappe.treeview_settings['Account'].treeview["tree"] = treeview.tree;
treeview.new_node(); treeview.page.set_primary_action(__("New"), function() {
} let root_company = treeview.page.fields_dict.root_company.get_value();
},
"add" if(root_company) {
); frappe.throw(__("Please add the account to root level Company - ") + root_company);
} else {
treeview.new_node();
}
}, "add");
},
onrender: function(node) {
if (frappe.boot.user.can_read.indexOf("GL Entry") !== -1) {
// show Dr if positive since balance is calculated as debit - credit else show Cr
let balance = node.data.balance_in_account_currency || node.data.balance;
let dr_or_cr = balance > 0 ? "Dr": "Cr";
if (node.data && node.data.balance!==undefined) {
$('<span class="balance-area pull-right">'
+ (node.data.balance_in_account_currency ?
(format_currency(Math.abs(node.data.balance_in_account_currency),
node.data.account_currency) + " / ") : "")
+ format_currency(Math.abs(node.data.balance), node.data.company_currency)
+ " " + dr_or_cr
+ '</span>').insertBefore(node.$ul);
}
}
}, },
toolbar: [ toolbar: [
{ {
label: __("Add Child"), label:__("Add Child"),
condition: function (node) { condition: function(node) {
return ( return frappe.boot.user.can_create.indexOf("Account") !== -1
frappe.boot.user.can_create.indexOf("Account") !== -1 && && (!frappe.treeview_settings['Account'].treeview.page.fields_dict.root_company.get_value()
(!frappe.treeview_settings[ || frappe.flags.ignore_root_company_validation)
"Account" && node.expandable && !node.hide_add;
].treeview.page.fields_dict.root_company.get_value() ||
frappe.flags.ignore_root_company_validation) &&
node.expandable &&
!node.hide_add
);
}, },
click: function () { click: function() {
var me = frappe.views.trees["Account"]; var me = frappe.treeview_settings['Account'].treeview;
me.new_node(); me.new_node();
}, },
btnClass: "hidden-xs", btnClass: "hidden-xs"
}, },
{ {
condition: function (node) { condition: function(node) {
return !node.root && frappe.boot.user.can_read.indexOf("GL Entry") !== -1; return !node.root && frappe.boot.user.can_read.indexOf("GL Entry") !== -1
}, },
label: __("View Ledger"), label: __("View Ledger"),
click: function (node, btn) { click: function(node, btn) {
frappe.route_options = { frappe.route_options = {
account: node.label, "account": node.label,
from_date: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1], "from_date": frappe.sys_defaults.year_start_date,
to_date: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2], "to_date": frappe.sys_defaults.year_end_date,
company: "company": frappe.treeview_settings['Account'].treeview.page.fields_dict.company.get_value()
frappe.treeview_settings["Account"].treeview.page.fields_dict.company.get_value(),
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");
}, },
btnClass: "hidden-xs", btnClass: "hidden-xs"
}, }
], ],
extend_toolbar: true, extend_toolbar: true
}; }

View File

@@ -1,63 +1,48 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import json
import os
import frappe import frappe, os, json
from frappe.utils import cstr from frappe.utils import cstr
from frappe.utils.nestedset import rebuild_tree
from unidecode import unidecode from unidecode import unidecode
from six import iteritems
from frappe.utils.nestedset import rebuild_tree
def create_charts(company, chart_template=None, existing_company=None, custom_chart=None):
def create_charts(
company, chart_template=None, existing_company=None, custom_chart=None, from_coa_importer=None
):
chart = custom_chart or get_chart(chart_template, existing_company) chart = custom_chart or get_chart(chart_template, existing_company)
if chart: if chart:
accounts = [] accounts = []
def _import_accounts(children, parent, root_type, root_account=False): def _import_accounts(children, parent, root_type, root_account=False):
for account_name, child in children.items(): for account_name, child in iteritems(children):
if root_account: if root_account:
root_type = child.get("root_type") root_type = child.get("root_type")
if account_name not in [ if account_name not in ["account_number", "account_type",
"account_name", "root_type", "is_group", "tax_rate"]:
"account_number",
"account_type",
"root_type",
"is_group",
"tax_rate",
"account_currency",
]:
account_number = cstr(child.get("account_number")).strip() account_number = cstr(child.get("account_number")).strip()
account_name, account_name_in_db = add_suffix_if_duplicate( account_name, account_name_in_db = add_suffix_if_duplicate(account_name,
account_name, account_number, accounts account_number, accounts)
)
is_group = identify_is_group(child) is_group = identify_is_group(child)
report_type = ( report_type = "Balance Sheet" if root_type in ["Asset", "Liability", "Equity"] \
"Balance Sheet" if root_type in ["Asset", "Liability", "Equity"] else "Profit and Loss" else "Profit and Loss"
)
account = frappe.get_doc( account = frappe.get_doc({
{ "doctype": "Account",
"doctype": "Account", "account_name": account_name,
"account_name": child.get("account_name") if from_coa_importer else account_name, "company": company,
"company": company, "parent_account": parent,
"parent_account": parent, "is_group": is_group,
"is_group": is_group, "root_type": root_type,
"root_type": root_type, "report_type": report_type,
"report_type": report_type, "account_number": account_number,
"account_number": account_number, "account_type": child.get("account_type"),
"account_type": child.get("account_type"), "account_currency": child.get('account_currency') or frappe.db.get_value('Company', company, "default_currency"),
"account_currency": child.get("account_currency") "tax_rate": child.get("tax_rate")
or frappe.get_cached_value("Company", company, "default_currency"), })
"tax_rate": child.get("tax_rate"),
}
)
if root_account or frappe.local.flags.allow_unverified_charts: if root_account or frappe.local.flags.allow_unverified_charts:
account.flags.ignore_mandatory = True account.flags.ignore_mandatory = True
@@ -77,10 +62,10 @@ def create_charts(
rebuild_tree("Account", "parent_account") rebuild_tree("Account", "parent_account")
frappe.local.flags.ignore_update_nsm = False frappe.local.flags.ignore_update_nsm = False
def add_suffix_if_duplicate(account_name, account_number, accounts): def add_suffix_if_duplicate(account_name, account_number, accounts):
if account_number: if account_number:
account_name_in_db = unidecode(" - ".join([account_number, account_name.strip().lower()])) account_name_in_db = unidecode(" - ".join([account_number,
account_name.strip().lower()]))
else: else:
account_name_in_db = unidecode(account_name.strip().lower()) account_name_in_db = unidecode(account_name.strip().lower())
@@ -90,47 +75,27 @@ def add_suffix_if_duplicate(account_name, account_number, accounts):
return account_name, account_name_in_db return account_name, account_name_in_db
def identify_is_group(child): def identify_is_group(child):
if child.get("is_group"): if child.get("is_group"):
is_group = child.get("is_group") is_group = child.get("is_group")
elif len( elif len(set(child.keys()) - set(["account_type", "root_type", "is_group", "tax_rate", "account_number"])):
set(child.keys())
- set(
[
"account_name",
"account_type",
"root_type",
"is_group",
"tax_rate",
"account_number",
"account_currency",
]
)
):
is_group = 1 is_group = 1
else: else:
is_group = 0 is_group = 0
return is_group return is_group
def get_chart(chart_template, existing_company=None): def get_chart(chart_template, existing_company=None):
chart = {} chart = {}
if existing_company: if existing_company:
return get_account_tree_from_existing_company(existing_company) return get_account_tree_from_existing_company(existing_company)
elif chart_template == "Standard": elif chart_template == "Standard":
from erpnext.accounts.doctype.account.chart_of_accounts.verified import ( from erpnext.accounts.doctype.account.chart_of_accounts.verified import standard_chart_of_accounts
standard_chart_of_accounts,
)
return standard_chart_of_accounts.get() return standard_chart_of_accounts.get()
elif chart_template == "Standard with Numbers": elif chart_template == "Standard with Numbers":
from erpnext.accounts.doctype.account.chart_of_accounts.verified import ( from erpnext.accounts.doctype.account.chart_of_accounts.verified \
standard_chart_of_accounts_with_account_number, import standard_chart_of_accounts_with_account_number
)
return standard_chart_of_accounts_with_account_number.get() return standard_chart_of_accounts_with_account_number.get()
else: else:
folders = ("verified",) folders = ("verified",)
@@ -146,7 +111,6 @@ def get_chart(chart_template, existing_company=None):
if chart and json.loads(chart).get("name") == chart_template: if chart and json.loads(chart).get("name") == chart_template:
return json.loads(chart).get("tree") return json.loads(chart).get("tree")
@frappe.whitelist() @frappe.whitelist()
def get_charts_for_country(country, with_standard=False): def get_charts_for_country(country, with_standard=False):
charts = [] charts = []
@@ -154,12 +118,11 @@ def get_charts_for_country(country, with_standard=False):
def _get_chart_name(content): def _get_chart_name(content):
if content: if content:
content = json.loads(content) content = json.loads(content)
if ( if (content and content.get("disabled", "No") == "No") \
content and content.get("disabled", "No") == "No" or frappe.local.flags.allow_unverified_charts:
) or frappe.local.flags.allow_unverified_charts: charts.append(content["name"])
charts.append(content["name"])
country_code = frappe.get_cached_value("Country", country, "code") country_code = frappe.db.get_value("Country", country, "code")
if country_code: if country_code:
folders = ("verified",) folders = ("verified",)
if frappe.local.flags.allow_unverified_charts: if frappe.local.flags.allow_unverified_charts:
@@ -184,22 +147,11 @@ def get_charts_for_country(country, with_standard=False):
def get_account_tree_from_existing_company(existing_company): def get_account_tree_from_existing_company(existing_company):
all_accounts = frappe.get_all( all_accounts = frappe.get_all('Account',
"Account", filters={'company': existing_company},
filters={"company": existing_company}, fields = ["name", "account_name", "parent_account", "account_type",
fields=[ "is_group", "root_type", "tax_rate", "account_number"],
"name", order_by="lft, rgt")
"account_name",
"parent_account",
"account_type",
"is_group",
"root_type",
"tax_rate",
"account_number",
"account_currency",
],
order_by="lft, rgt",
)
account_tree = {} account_tree = {}
@@ -208,7 +160,6 @@ def get_account_tree_from_existing_company(existing_company):
build_account_tree(account_tree, None, all_accounts) build_account_tree(account_tree, None, all_accounts)
return account_tree return account_tree
def build_account_tree(tree, parent, all_accounts): def build_account_tree(tree, parent, all_accounts):
# find children # find children
parent_account = parent.name if parent else "" parent_account = parent.name if parent else ""
@@ -237,29 +188,27 @@ def build_account_tree(tree, parent, all_accounts):
# call recursively to build a subtree for current account # call recursively to build a subtree for current account
build_account_tree(tree[child.account_name], child, all_accounts) build_account_tree(tree[child.account_name], child, all_accounts)
@frappe.whitelist() @frappe.whitelist()
def validate_bank_account(coa, bank_account): def validate_bank_account(coa, bank_account):
accounts = [] accounts = []
chart = get_chart(coa) chart = get_chart(coa)
if chart: if chart:
def _get_account_names(account_master): def _get_account_names(account_master):
for account_name, child in account_master.items(): for account_name, child in iteritems(account_master):
if account_name not in ["account_number", "account_type", "root_type", "is_group", "tax_rate"]: if account_name not in ["account_number", "account_type",
"root_type", "is_group", "tax_rate"]:
accounts.append(account_name) accounts.append(account_name)
_get_account_names(child) _get_account_names(child)
_get_account_names(chart) _get_account_names(chart)
return bank_account in accounts return (bank_account in accounts)
@frappe.whitelist() @frappe.whitelist()
def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=False): def build_tree_from_json(chart_template, chart_data=None):
"""get chart template from its folder and parse the json to be rendered as tree""" ''' get chart template from its folder and parse the json to be rendered as tree '''
chart = chart_data or get_chart(chart_template) chart = chart_data or get_chart(chart_template)
# if no template selected, return as it is # if no template selected, return as it is
@@ -267,34 +216,19 @@ def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=Fals
return return
accounts = [] accounts = []
def _import_accounts(children, parent): def _import_accounts(children, parent):
"""recursively called to form a parent-child based list of dict from chart template""" ''' recursively called to form a parent-child based list of dict from chart template '''
for account_name, child in children.items(): for account_name, child in iteritems(children):
account = {} account = {}
if account_name in [ if account_name in ["account_number", "account_type",\
"account_name", "root_type", "is_group", "tax_rate"]: continue
"account_number",
"account_type",
"root_type",
"is_group",
"tax_rate",
"account_currency",
]:
continue
if from_coa_importer: account['parent_account'] = parent
account_name = child["account_name"] account['expandable'] = True if identify_is_group(child) else False
account['value'] = (cstr(child.get('account_number')).strip() + ' - ' + account_name) \
account["parent_account"] = parent if child.get('account_number') else account_name
account["expandable"] = True if identify_is_group(child) else False
account["value"] = (
(cstr(child.get("account_number")).strip() + " - " + account_name)
if child.get("account_number")
else account_name
)
accounts.append(account) accounts.append(account)
_import_accounts(child, account["value"]) _import_accounts(child, account['value'])
_import_accounts(chart, None) _import_accounts(chart, None)
return accounts return accounts

View File

@@ -0,0 +1,270 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
"""
Import chart of accounts from OpenERP sources
"""
from __future__ import print_function, unicode_literals
import os, json
import ast
from xml.etree import ElementTree as ET
from frappe.utils.csvutils import read_csv_content
import frappe
from six import iteritems
path = "/Users/nabinhait/projects/odoo/addons"
accounts = {}
charts = {}
all_account_types = []
all_roots = {}
def go():
global accounts, charts
default_account_types = get_default_account_types()
country_dirs = []
for basepath, folders, files in os.walk(path):
basename = os.path.basename(basepath)
if basename.startswith("l10n_"):
country_dirs.append(basename)
for country_dir in country_dirs:
accounts, charts = {}, {}
country_path = os.path.join(path, country_dir)
manifest = ast.literal_eval(open(os.path.join(country_path, "__openerp__.py")).read())
data_files = manifest.get("data", []) + manifest.get("init_xml", []) + \
manifest.get("update_xml", [])
files_path = [os.path.join(country_path, d) for d in data_files]
xml_roots = get_xml_roots(files_path)
csv_content = get_csv_contents(files_path)
prefix = country_dir if csv_content else None
account_types = get_account_types(xml_roots.get("account.account.type", []),
csv_content.get("account.account.type", []), prefix)
account_types.update(default_account_types)
if xml_roots:
make_maps_for_xml(xml_roots, account_types, country_dir)
if csv_content:
make_maps_for_csv(csv_content, account_types, country_dir)
make_account_trees()
make_charts()
create_all_roots_file()
def get_default_account_types():
default_types_root = []
default_types_root.append(ET.parse(os.path.join(path, "account", "data",
"data_account_type.xml")).getroot())
return get_account_types(default_types_root, None, prefix="account")
def get_xml_roots(files_path):
xml_roots = frappe._dict()
for filepath in files_path:
fname = os.path.basename(filepath)
if fname.endswith(".xml"):
tree = ET.parse(filepath)
root = tree.getroot()
for node in root[0].findall("record"):
if node.get("model") in ["account.account.template",
"account.chart.template", "account.account.type"]:
xml_roots.setdefault(node.get("model"), []).append(root)
break
return xml_roots
def get_csv_contents(files_path):
csv_content = {}
for filepath in files_path:
fname = os.path.basename(filepath)
for file_type in ["account.account.template", "account.account.type",
"account.chart.template"]:
if fname.startswith(file_type) and fname.endswith(".csv"):
with open(filepath, "r") as csvfile:
try:
csv_content.setdefault(file_type, [])\
.append(read_csv_content(csvfile.read()))
except Exception as e:
continue
return csv_content
def get_account_types(root_list, csv_content, prefix=None):
types = {}
account_type_map = {
'cash': 'Cash',
'bank': 'Bank',
'tr_cash': 'Cash',
'tr_bank': 'Bank',
'receivable': 'Receivable',
'tr_receivable': 'Receivable',
'account rec': 'Receivable',
'payable': 'Payable',
'tr_payable': 'Payable',
'equity': 'Equity',
'stocks': 'Stock',
'stock': 'Stock',
'tax': 'Tax',
'tr_tax': 'Tax',
'tax-out': 'Tax',
'tax-in': 'Tax',
'charges_personnel': 'Chargeable',
'fixed asset': 'Fixed Asset',
'cogs': 'Cost of Goods Sold',
}
for root in root_list:
for node in root[0].findall("record"):
if node.get("model")=="account.account.type":
data = {}
for field in node.findall("field"):
if field.get("name")=="code" and field.text.lower() != "none" \
and account_type_map.get(field.text):
data["account_type"] = account_type_map[field.text]
node_id = prefix + "." + node.get("id") if prefix else node.get("id")
types[node_id] = data
if csv_content and csv_content[0][0]=="id":
for row in csv_content[1:]:
row_dict = dict(zip(csv_content[0], row))
data = {}
if row_dict.get("code") and account_type_map.get(row_dict["code"]):
data["account_type"] = account_type_map[row_dict["code"]]
if data and data.get("id"):
node_id = prefix + "." + data.get("id") if prefix else data.get("id")
types[node_id] = data
return types
def make_maps_for_xml(xml_roots, account_types, country_dir):
"""make maps for `charts` and `accounts`"""
for model, root_list in iteritems(xml_roots):
for root in root_list:
for node in root[0].findall("record"):
if node.get("model")=="account.account.template":
data = {}
for field in node.findall("field"):
if field.get("name")=="name":
data["name"] = field.text
if field.get("name")=="parent_id":
parent_id = field.get("ref") or field.get("eval")
data["parent_id"] = parent_id
if field.get("name")=="user_type":
value = field.get("ref")
if account_types.get(value, {}).get("account_type"):
data["account_type"] = account_types[value]["account_type"]
if data["account_type"] not in all_account_types:
all_account_types.append(data["account_type"])
data["children"] = []
accounts[node.get("id")] = data
if node.get("model")=="account.chart.template":
data = {}
for field in node.findall("field"):
if field.get("name")=="name":
data["name"] = field.text
if field.get("name")=="account_root_id":
data["account_root_id"] = field.get("ref")
data["id"] = country_dir
charts.setdefault(node.get("id"), {}).update(data)
def make_maps_for_csv(csv_content, account_types, country_dir):
for content in csv_content.get("account.account.template", []):
for row in content[1:]:
data = dict(zip(content[0], row))
account = {
"name": data.get("name"),
"parent_id": data.get("parent_id:id") or data.get("parent_id/id"),
"children": []
}
user_type = data.get("user_type/id") or data.get("user_type:id")
if account_types.get(user_type, {}).get("account_type"):
account["account_type"] = account_types[user_type]["account_type"]
if account["account_type"] not in all_account_types:
all_account_types.append(account["account_type"])
accounts[data.get("id")] = account
if not account.get("parent_id") and data.get("chart_template_id:id"):
chart_id = data.get("chart_template_id:id")
charts.setdefault(chart_id, {}).update({"account_root_id": data.get("id")})
for content in csv_content.get("account.chart.template", []):
for row in content[1:]:
if row:
data = dict(zip(content[0], row))
charts.setdefault(data.get("id"), {}).update({
"account_root_id": data.get("account_root_id:id") or \
data.get("account_root_id/id"),
"name": data.get("name"),
"id": country_dir
})
def make_account_trees():
"""build tree hierarchy"""
for id in accounts.keys():
account = accounts[id]
if account.get("parent_id"):
if accounts.get(account["parent_id"]):
# accounts[account["parent_id"]]["children"].append(account)
accounts[account["parent_id"]][account["name"]] = account
del account["parent_id"]
del account["name"]
# remove empty children
for id in accounts.keys():
if "children" in accounts[id] and not accounts[id].get("children"):
del accounts[id]["children"]
def make_charts():
"""write chart files in app/setup/doctype/company/charts"""
for chart_id in charts:
src = charts[chart_id]
if not src.get("name") or not src.get("account_root_id"):
continue
if not src["account_root_id"] in accounts:
continue
filename = src["id"][5:] + "_" + chart_id
print("building " + filename)
chart = {}
chart["name"] = src["name"]
chart["country_code"] = src["id"][5:]
chart["tree"] = accounts[src["account_root_id"]]
for key, val in chart["tree"].items():
if key in ["name", "parent_id"]:
chart["tree"].pop(key)
if type(val) == dict:
val["root_type"] = ""
if chart:
fpath = os.path.join("erpnext", "erpnext", "accounts", "doctype", "account",
"chart_of_accounts", filename + ".json")
with open(fpath, "r") as chartfile:
old_content = chartfile.read()
if not old_content or (json.loads(old_content).get("is_active", "No") == "No" \
and json.loads(old_content).get("disabled", "No") == "No"):
with open(fpath, "w") as chartfile:
chartfile.write(json.dumps(chart, indent=4, sort_keys=True))
all_roots.setdefault(filename, chart["tree"].keys())
def create_all_roots_file():
with open('all_roots.txt', 'w') as f:
for filename, roots in sorted(all_roots.items()):
f.write(filename)
f.write('\n----------------------\n')
for r in sorted(roots):
f.write(r.encode('utf-8'))
f.write('\n')
f.write('\n\n\n')
if __name__=="__main__":
go()

View File

@@ -2,437 +2,397 @@
"country_code": "at", "country_code": "at",
"name": "Austria - Chart of Accounts", "name": "Austria - Chart of Accounts",
"tree": { "tree": {
"Klasse 0 Aktiva: Anlageverm\u00f6gen": { "Summe Abschreibungen und Aufwendungen": {
"0100 Konzessionen ": {"account_type": "Fixed Asset"}, "7010 bis 7080 Abschreibungen auf das Anlageverm\u00f6gen (ausgenommen Finanzanlagen)": {},
"0110 Patentrechte und Lizenzen ": {"account_type": "Fixed Asset"}, "7100 bis 7190 Sonstige Steuern": {
"0120 Datenverarbeitungsprogramme ": {"account_type": "Fixed Asset"}, "account_type": "Tax"
"0130 Marken, Warenzeichen und Musterschutzrechte, sonstige Urheberrechte ": {"account_type": "Fixed Asset"}, },
"0140 Pacht- und Mietrechte ": {"account_type": "Fixed Asset"}, "7200 bis 7290 Instandhaltung u. Reinigung durh Dritte, Entsorgung, Beleuchtung": {},
"0150 Bezugs- und ähnliche Rechte ": {"account_type": "Fixed Asset"}, "7300 bis 7310 Transporte durch Dritte": {},
"0160 Geschäfts-/Firmenwert ": {"account_type": "Fixed Asset"}, "7320 bis 7330 Kfz - Aufwand": {},
"0170 Umgründungsmehrwert ": {"account_type": "Fixed Asset"}, "7340 bis 7350 Reise- und Fahraufwand": {},
"0180 Geleistete Anzahlungen auf immaterielle Vermögensgegenstände": {"account_type": "Fixed Asset"}, "7360 bis 7370 Tag- und N\u00e4chtigungsgelder": {},
"0190 Kumulierte Abschreibungen zu immateriellen Vermögensgegenständen ": {"account_type": "Fixed Asset"}, "7380 bis 7390 Nachrichtenaufwand": {},
"0200 Unbebaute Grundstücke, soweit nicht landwirtschaftlich genutzt ": {"account_type": "Fixed Asset"}, "7400 bis 7430 Miet- und Pachtaufwand": {},
"0210 Bebaute Grundstücke (Grundwert) ": {"account_type": "Fixed Asset"}, "7440 bis 7470 Leasingaufwand": {},
"0220 Landwirtschaftlich genutzte Grundstücke ": {"account_type": "Fixed Asset"}, "7480 bis 7490 Lizenzaufwand": {},
"0230 Grundstücksgleiche Rechte ": {"account_type": "Fixed Asset"}, "7500 bis 7530 Aufwand f\u00fcr beigestelltes Personal": {},
"0300 Betriebs- und Geschäftsgebäude auf eigenem Grund ": {"account_type": "Fixed Asset"}, "7540 bis 7570 Provisionen an Dritte": {},
"0310 Wohn- und Sozialgebäude auf eigenem Grund ": {"account_type": "Fixed Asset"}, "7580 bis 7590 Aufsichtsratsverg\u00fctungen": {},
"0320 Betriebs- und Geschäftsgebäude auf fremdem Grund ": {"account_type": "Fixed Asset"}, "7610 bis 7620 Druckerzeugnisse und Vervielf\u00e4ltigungen": {},
"0330 Wohn- und Sozialgebäude auf fremdem Grund ": {"account_type": "Fixed Asset"}, "7650 bis 7680 Werbung und Repr\u00e4sentationen": {},
"0340 Grundstückseinrichtungen auf eigenem Grund ": {"account_type": "Fixed Asset"}, "7700 bis 7740 Versicherungen": {},
"0350 Grundstückseinrichtungen auf fremdem Grund ": {"account_type": "Fixed Asset"}, "7750 bis 7760 Beratungs- und Pr\u00fcfungsaufwand": {},
"0360 Bauliche Investitionen in fremden (gepachteten) Betriebs- und Geschäftsgebäuden": {"account_type": "Fixed Asset"}, "7800 bis 7810 Schadensf\u00e4lle": {},
"0370 Bauliche Investitionen in fremden (gepachteten) Wohn- und Sozialgebäuden": {"account_type": "Fixed Asset"}, "7840 bis 7880 Verschiedene betriebliche Aufwendungen": {},
"0390 Kumulierte Abschreibungen zu Grundstücken ": {"account_type": "Fixed Asset"}, "7910 bis 7950 Aufwandsstellenrechung der Hersteller": {},
"0400 Maschinen und Geräte ": {"account_type": "Fixed Asset"}, "Abschreibungen auf aktivierte Aufwendungen f\u00fcr das Ingangs. u. Erweitern des Betriebes": {},
"0500 Maschinenwerkzeuge ": {"account_type": "Fixed Asset"}, "Abschreibungen vom Umlaufverm\u00f6gen, soweit diese die im Unternehmen \u00fcblichen Abschreibungen \u00fcbersteigen": {},
"0510 Allgemeine Werkzeuge und Handwerkzeuge ": {"account_type": "Fixed Asset"}, "Aufwandsstellenrechnung": {},
"0520 Prototypen, Formen, Modelle ": {"account_type": "Fixed Asset"}, "Aus- und Fortbildung": {},
"0530 Andere Erzeugungshilfsmittel (auch Softwarewerkzeuge)": {"account_type": "Fixed Asset"}, "Buchwert abgegangener Anlagen, ausgenommen Finanzanlagen": {},
"0540 Hebezeuge und Montageanlagen ": {"account_type": "Fixed Asset"}, "B\u00fcromaterial und Drucksorten": {},
"0550 Geringwertige Vermögensgegenstände, soweit im Erzeugungsprozess ": {"account_type": "Fixed Asset"}, "Fachliteratur und Zeitungen ": {},
"0560 Festwerte technische Anlagen und Maschinen ": {"account_type": "Fixed Asset"}, "Herstellungskosten der zur Erzielung der Umsatzerl\u00f6se erbrachten Leistungen": {},
"0590 Kumulierte Abschreibungen zu technischen Anlagen und Maschinen ": {"account_type": "Fixed Asset"}, "Mitgliedsbeitr\u00e4ge": {},
"0600 Betriebs- und Geschäftsausstattung, soweit nicht gesondert angeführt ": {"account_type": "Fixed Asset"}, "Skontoertr\u00e4ge auf sonstige betriebliche Aufwendungen": {},
"0610 Andere Anlagen, soweit nicht gesondert angeführt ": {"account_type": "Fixed Asset"}, "Sonstige betrieblichen Aufwendungen": {},
"0620 Büromaschinen, EDV-Anlagen ": {"account_type": "Fixed Asset"}, "Spenden und Trinkgelder": {},
"0630 PKW und Kombis ": {"account_type": "Fixed Asset"}, "Spesen des Geldverkehrs": {},
"0640 LKW ": {"account_type": "Fixed Asset"}, "Verluste aus dem Abgang vom Anlageverm\u00f6gen, ausgenommen Finanzanlagen": {},
"0650 Andere Beförderungsmittel ": {"account_type": "Fixed Asset"}, "Vertriebskosten": {},
"0660 Gebinde ": {"account_type": "Fixed Asset"}, "Verwaltungskosten": {},
"0670 Geringwertige Vermögensgegenstände, soweit nicht im Erzeugungssprozess verwendet": {"account_type": "Fixed Asset"}, "root_type": "Expense"
"0680 Festwerte außer technische Anlagen und Maschinen ": {"account_type": "Fixed Asset"},
"0690 Kumulierte Abschreibungen zu anderen Anlagen, Betriebs- und Geschäftsausstattung": {"account_type": "Fixed Asset"},
"0700 Geleistete Anzahlungen auf Sachanlagen ": {"account_type": "Fixed Asset"},
"0710 Anlagen in Bau ": {"account_type": "Fixed Asset"},
"0790 Kumulierte Abschreibungen zu geleisteten Anzahlungen auf Sachanlagen ": {"account_type": "Fixed Asset"},
"0800 Anteile an verbundenen Unternehmen ": {"account_type": "Fixed Asset"},
"0810 Beteiligungen an Gemeinschaftsunternehmen ": {"account_type": "Fixed Asset"},
"0820 Beteiligungen an angeschlossenen (assoziierten) Unternehmen ": {"account_type": "Fixed Asset"},
"0830 Eigene Anteile, Anteile an herrschenden oder mit Mehrheit beteiligten ": {"account_type": "Fixed Asset"},
"0840 Sonstige Beteiligungen ": {"account_type": "Fixed Asset"},
"0850 Ausleihungen an verbundene Unternehmen ": {"account_type": "Fixed Asset"},
"0860 Ausleihungen an Unternehmen mit Beteiligungsverhältnis": {"account_type": "Fixed Asset"},
"0870 Ausleihungen an Gesellschafter ": {"account_type": "Fixed Asset"},
"0880 Sonstige Ausleihungen ": {"account_type": "Fixed Asset"},
"0890 Anteile an Kapitalgesellschaften ohne Beteiligungscharakter ": {"account_type": "Fixed Asset"},
"0900 Anteile an Personengesellschaften ohne Beteiligungscharakter ": {"account_type": "Fixed Asset"},
"0910 Genossenschaftsanteile ohne Beteiligungscharakter ": {"account_type": "Fixed Asset"},
"0920 Anteile an Investmentfonds ": {"account_type": "Fixed Asset"},
"0930 Festverzinsliche Wertpapiere des Anlagevermögens ": {"account_type": "Fixed Asset"},
"0980 Geleistete Anzahlungen auf Finanzanlagen ": {"account_type": "Fixed Asset"},
"0990 Kumulierte Abschreibungen zu Finanzanlagen ": {"account_type": "Fixed Asset"},
"root_type": "Asset"
}, },
"Klasse 1 Aktiva: Vorr\u00e4te": { "Summe Betriebliche Ertr\u00e4ge": {
"1000 Bezugsverrechnung": {"account_type": "Stock"}, "4400 bis 4490 Erl\u00f6sschm\u00e4lerungen": {},
"1100 Rohstoffe": {"account_type": "Stock"}, "4500 bis 4570 Ver\u00e4nderungen des Bestandes an fertigen und unfertigen Erzeugn. sowie an noch nicht abrechenbaren Leistungen": {},
"1200 Bezogene Teile": {"account_type": "Stock"}, "4580 bis 4590 andere aktivierte Eigenleistungen": {},
"1300 Hilfsstoffe": {"account_type": "Stock"}, "4600 bis 4620 Erl\u00f6se aus dem Abgang vom Anlageverm\u00f6gen, ausgen. Finanzanlagen": {},
"1350 Betriebsstoffe": {"account_type": "Stock"}, "4630 bis 4650 Ertr\u00e4ge aus dem Abgang vom Anlageverm\u00f6gen, ausgen. Finanzanlagen": {},
"1360 Vorrat Energietraeger": {"account_type": "Stock"}, "4660 bis 4670 Ertr\u00e4ge aus der Zuschreibung zum Anlageverm\u00f6gen, ausgen. Finanzanlagen": {},
"1400 Unfertige Erzeugnisse": {"account_type": "Stock"}, "4700 bis 4790 Ertr\u00e4ge aus der Aufl\u00f6sung von R\u00fcckstellungen": {},
"1500 Fertige Erzeugnisse": {"account_type": "Stock"}, "4800 bis 4990 \u00dcbrige betriebliche Ertr\u00e4ge": {},
"1600 Handelswarenvorrat": {"account_type": "Stock Received But Not Billed"}, "Erl\u00f6se 0 % Ausfuhrlieferungen/Drittl\u00e4nder": {},
"1700 Noch nicht abrechenbare Leistungen": {"account_type": "Stock"}, "Erl\u00f6se 10 %": {},
"1800 Geleistete Anzahlungen": {"account_type": "Stock"}, "Erl\u00f6se 20 %": {},
"1900 Wertberichtigungen": {"account_type": "Stock"}, "Erl\u00f6se aus im Inland stpfl. EG Lieferungen 10 % USt": {},
"root_type": "Asset" "Erl\u00f6se aus im Inland stpfl. EG Lieferungen 20 % USt": {},
"Erl\u00f6se i.g. Lieferungen (stfr)": {},
"root_type": "Income"
}, },
"Klasse 3 Passiva: Verbindlichkeiten": { "Summe Eigenkapital R\u00fccklagen Abschlusskonten": {
"3000 Allgemeine Verbindlichkeiten (Schuld)": {"account_type": "Payable"}, "9000 bis 9180 Gezeichnetes bzw. gewidmetes Kapital": {
"3010 R\u00fcckstellungen f\u00fcr Pensionen": {"account_type": "Payable"}, "account_type": "Equity"
"3020 Steuerr\u00fcckstellungen": {"account_type": "Tax"}, },
"3041 Sonstige R\u00fcckstellungen": {"account_type": "Payable"}, "9200 bis 9290 Kapitalr\u00fccklagen": {
"3110 Verbindlichkeiten gegen\u00fcber Bank": {"account_type": "Payable"}, "account_type": "Equity"
"3150 Verbindlichkeiten Darlehen": {"account_type": "Payable"}, },
"3185 Verbindlichkeiten Kreditkarte": {"account_type": "Payable"}, "9300 bis 9380 Gewinnr\u00fccklagen": {
"3380 Verbindlichkeiten aus der Annahme gezogener Wechsel u. d. Ausstellungen eigener Wechsel": { "account_type": "Equity"
},
"9400 bis 9590 Bewertungsreserven uns sonst. unversteuerte R\u00fccklagen": {
"account_type": "Equity"
},
"9600 bis 9690 Privat und Verrechnungskonten bei Einzelunternehmen und Personengesellschaften": {},
"9700 bis 9790 Einlagen stiller Gesellschafter ": {},
"9900 bis 9999 Evidenzkonten": {},
"Bilanzgewinn (-verlust )": {
"account_type": "Equity"
},
"Er\u00f6ffnungsbilanz": {},
"Gewinn- und Verlustrechnung": {},
"Schlussbilanz": {},
"nicht eingeforderte ausstehende Einlagen": {
"account_type": "Equity"
},
"root_type": "Equity"
},
"Summe Finanzertr\u00e4ge und Aufwendungen": {
"8000 bis 8040 Ertr\u00e4ge aus Beteiligungen": {},
"8050 bis 8090 Ertr\u00e4ge aus anderen Wertpapieren und Ausleihungen des Finanzanlageverm\u00f6gens": {},
"8100 bis 8130 Sonstige Zinsen und \u00e4hnliche Ertr\u00e4ge": {},
"8220 bis 8250 Aufwendungen aus Beteiligungen": {},
"8260 bis 8270 Aufwendungen aus sonst. Fiananzanlagen und aus Wertpapieren des Umlaufverm\u00f6gens": {},
"8280 bis 8340 Zinsen und \u00e4hnliche Aufwendungem": {},
"8400 bis 8440 Au\u00dferordentliche Ertr\u00e4ge": {},
"8450 bis 8490 Au\u00dferordentliche Aufwendungen": {},
"8500 bis 8590 Steuern vom Einkommen und vom Ertrag": {
"account_type": "Tax"
},
"8600 bis 8690 Aufl\u00f6sung unversteuerten R\u00fccklagen": {},
"8700 bis 8740 Aufl\u00f6sung von Kapitalr\u00fccklagen": {},
"8750 bis 8790 Aufl\u00f6sung von Gewinnr\u00fccklagen": {},
"8800 bis 8890 Zuweisung von unversteuerten R\u00fccklagen": {},
"Buchwert abgegangener Beteiligungen": {},
"Buchwert abgegangener Wertpapiere des Umlaufverm\u00f6gens": {},
"Buchwert abgegangener sonstiger Finanzanlagen": {},
"Erl\u00f6se aus dem Abgang von Beteiligungen": {},
"Erl\u00f6se aus dem Abgang von Wertpapieren des Umlaufverm\u00f6gens": {},
"Erl\u00f6se aus dem Abgang von sonstigen Finanzanlagen": {},
"Ertr\u00e4ge aus dem Abgang von und der Zuschreibung zu Finanzanlagen": {},
"Ertr\u00e4ge aus dem Abgang von und der Zuschreibung zu Wertpapieren des Umlaufverm\u00f6gens": {},
"Gewinabfuhr bzw. Verlust\u00fcberrechnung aus Ergebnisabf\u00fchrungsvertr\u00e4gen": {},
"nicht ausgenutzte Lieferantenskonti": {},
"root_type": "Income"
},
"Summe Fremdkapital": {
"3020 bis 3030 Steuerr\u00fcckstellungen": {},
"3040 bis 3090 Sonstige R\u00fcckstellungen": {},
"3110 bis 3170 Verbindlichkeiten gegen\u00fcber Kredidinstituten": {},
"3180 bis 3190 Verbindlichkeiten gegen\u00fcber Finanzinstituten": {},
"3380 bis 3390 Verbindlichkeiten aus der Annahme gezogener Wechsel u. d. Ausstellungen eigener Wechsel": {
"account_type": "Payable" "account_type": "Payable"
}, },
"3400 Verbindlichkeiten gegen\u00fc. verb. Untern., Verbindl. gegen\u00fc. Untern., mit denen eine Beteiligungsverh\u00e4lnis besteht": {}, "3400 bis 3470 Verbindlichkeiten gegen\u00fc. verb. Untern., Verbindl. gegen\u00fc. Untern., mit denen eine Beteiligungsverh\u00e4lnis besteht": {},
"3460 Verbindlichkeiten gegenueber Gesellschaftern": {"account_type": "Payable"}, "3600 bis 3690 Verbindlichkeiten im Rahmen der sozialen Sicherheit": {},
"3470 Einlagen stiller Gesellschafter": {"account_type": "Payable"}, "3700 bis 3890 \u00dcbrige sonstige Verbindlichkeiten": {},
"3585 Verbindlichkeiten Lohnsteuer": {"account_type": "Tax"}, "3900 bis 3990 Passive Rechnungsabgrenzungsposten": {},
"3590 Verbindlichkeiten Kommunalabgaben": {"account_type": "Tax"}, "Anleihen (einschlie\u00dflich konvertibler)": {},
"3595 Verbindlichkeiten Dienstgeberbeitrag": {"account_type": "Tax"}, "Erhaltene Anzahlungenauf Bestellungen": {},
"3600 Verbindlichkeiten Sozialversicherung": {"account_type": "Payable"}, "R\u00fcckstellungen f\u00fcr Abfertigung": {},
"3640 Verbindlichkeiten Loehne und Gehaelter": {"account_type": "Payable"}, "R\u00fcckstellungen f\u00fcr Pensionen": {},
"3700 Sonstige Verbindlichkeiten": {"account_type": "Payable"}, "USt. \u00a719 /art (reverse charge)": {
"3900 Passive Rechnungsabgrenzungsposten": {"account_type": "Payable"},
"3100 Anleihen (einschlie\u00dflich konvertibler)": {"account_type": "Payable"},
"3200 Erhaltene Anzahlungen auf Bestellungen": {"account_type": "Payable"},
"3040 R\u00fcckstellungen f\u00fcr Abfertigung": {"account_type": "Payable"},
"3530 USt. \u00a719 (reverse charge)": {
"account_type": "Tax" "account_type": "Tax"
}, },
"3500 Verbindlichkeiten aus Umsatzsteuer": {"account_type": "Tax"}, "Umsatzsteuer": {},
"3580 Umsatzsteuer Zahllast": { "Umsatzsteuer Zahllast": {
"account_type": "Tax" "account_type": "Tax"
}, },
"3510 Umsatzsteuer Inland 20%": { "Umsatzsteuer aus i.g. Erwerb 10%": {
"account_type": "Tax" "account_type": "Tax"
}, },
"3515 Umsatzsteuer Inland 10%": { "Umsatzsteuer aus i.g. Erwerb 20%": {
"account_type": "Tax" "account_type": "Tax"
}, },
"3520 Umsatzsteuer aus i.g. Erwerb 20%": { "Umsatzsteuer aus i.g. Lieferungen 10%": {
"account_type": "Tax" "account_type": "Tax"
}, },
"3525 Umsatzsteuer aus i.g. Erwerb 10%": { "Umsatzsteuer aus i.g. Lieferungen 20%": {
"account_type": "Tax" "account_type": "Tax"
}, },
"3560 Umsatzsteuer-Evidenzkonto f\u00fcr erhaltene Anzahlungen auf Bestellungen": {}, "Umsatzsteuer-Evidenzkonto f\u00fcr erhaltene Anzahlungen auf Bestellungen": {},
"3360 Verbindlichkeiten aus Lieferungen u. Leistungen EU": { "Verbindlichkeiten aus Lieferungen u. Leistungen EU": {
"account_type": "Payable" "account_type": "Payable"
}, },
"3000 Verbindlichkeiten aus Lieferungen u. Leistungen Inland": { "Verbindlichkeiten aus Lieferungen u. Leistungen Inland": {
"account_type": "Payable" "account_type": "Payable"
}, },
"3370 Verbindlichkeiten aus Lieferungen u. Leistungen sonst. Ausland": { "Verbindlichkeiten aus Lieferungen u. Leistungen sonst. Ausland": {
"account_type": "Payable" "account_type": "Payable"
}, },
"3400 Verbindlichkeiten gegen\u00fcber verbundenen Unternehmen": {}, "Verbindlichkeiten gegen\u00fcber Gesellschaften": {},
"3570 Verrechnung Finanzamt": { "Verrechnung Finanzamt": {
"account_type": "Tax" "account_type": "Tax"
}, },
"root_type": "Liability" "root_type": "Liability"
}, },
"Klasse 2 Aktiva: Umlaufverm\u00f6gen, Rechnungsabgrenzungen": { "Summe Kontoklasse 0 Anlageverm\u00f6gen": {
"2030 Forderungen aus Lieferungen und Leistungen Inland (0% USt, umsatzsteuerfrei)": { "44 bis 49 Sonstige Maschinen und maschinelle Anlagen": {},
"920 bis 930 Festverzinsliche Wertpapiere des Anlageverm\u00f6gens": {},
"940 bis 970 Sonstige Finanzanlagen, Wertrechte": {},
"Allgemeine Werkzeuge und Handwerkzeuge": {},
"Andere Bef\u00f6rderungsmittel": {},
"Andere Betriebs- und Gesch\u00e4ftsausstattung": {},
"Andere Erzeugungshilfsmittel": {},
"Anlagen im Bau": {},
"Anteile an Investmentfonds": {},
"Anteile an Kapitalgesellschaften ohne Beteiligungscharakter": {},
"Anteile an Personengesellschaften ohne Beteiligungscharakter": {},
"Anteile an verbundenen Unternehmen": {},
"Antriebsmaschinen": {},
"Aufwendungen f\u00fcs das Ingangssetzen u. Erweitern eines Betriebes": {},
"Ausleihungen an verbundene Unternehmen": {},
"Ausleihungen an verbundene Unternehmen, mit denen ein Beteiligungsverh\u00e4lnis besteht": {},
"Bauliche Investitionen in fremden (gepachteten) Betriebs- und Gesch\u00e4ftsgeb\u00e4uden": {},
"Bauliche Investitionen in fremden (gepachteten) Wohn- und Sozialgeb\u00e4uden": {},
"Bebaute Grundst\u00fccke (Grundwert)": {},
"Beheizungs- und Beleuchtungsanlagen": {},
"Beteiligungen an Gemeinschaftunternehmen": {},
"Beteiligungen an angeschlossenen (assoziierten) Unternehmen": {},
"Betriebs- und Gesch\u00e4ftsgeb\u00e4ude auf eigenem Grund": {},
"Betriebs- und Gesch\u00e4ftsgeb\u00e4ude auf fremdem Grund": {},
"B\u00fcromaschinen, EDV - Anlagen": {},
"Datenverarbeitungsprogramme": {},
"Energieversorgungsanlagen": {},
"Fertigungsmaschinen": {},
"Gebinde": {},
"Geleistete Anzahlungen": {},
"Genossenschaften ohne Beteiligungscharakter": {},
"Geringwertige Verm\u00f6gensgegenst\u00e4nde, soweit im Erzeugerprozess verwendet": {},
"Geringwertige Verm\u00f6gensgegenst\u00e4nde, soweit nicht im Erzeugungsprozess verwendet": {},
"Gesch\u00e4fts(Firmen)wert": {},
"Grundst\u00fcckseinrichtunten auf eigenem Grund": {},
"Grundst\u00fcckseinrichtunten auf fremdem Grund": {},
"Grundst\u00fccksgleiche Rechte": {},
"Hebezeuge und Montageanlagen": {},
"Konzessionen": {},
"Kumulierte Abschreibungen": {},
"LKW": {},
"Marken, Warenzeichen und Musterschutzrechte": {},
"Maschinenwerkzeuge": {},
"Nachrichten- und Kontrollanlagen": {},
"PKW": {},
"Pacht- und Mietrechte": {},
"Patentrechte und Lizenzen": {},
"Sonstige Ausleihungen": {},
"Sonstige Beteiligungen": {},
"Transportanlagen": {},
"Unbebaute Grundst\u00fccke": {},
"Vorrichtungen, Formen und Modelle": {},
"Wohn- und Sozialgeb\u00e4ude auf eigenem Grund": {},
"Wohn- und Sozialgeb\u00e4ude auf fremdem Grund": {},
"root_type": "Asset"
},
"Summe Personalaufwand": {
"6000 bis 6190 L\u00f6hne": {},
"6200 bis 6390 Geh\u00e4lter": {},
"6400 bis 6440 Aufwendungen f\u00fcr Abfertigungen": {},
"6450 bis 6490 Aufwendungen f\u00fcr Altersversorgung": {},
"6500 bis 6550 Gesetzlicher Sozialaufwand Arbeiter": {},
"6560 bis 6590 Gesetzlicher Sozialaufwand Angestellte": {},
"6600 bis 6650 Lohnabh\u00e4ngige Abgaben und Pflichtbeitr\u00e4gte": {},
"6660 bis 6690 Gehaltsabh\u00e4ngige Abgaben und Pflichtbeitr\u00e4gte": {},
"6700 bis 6890 Sonstige Sozialaufwendungen": {},
"Aufwandsstellenrechnung": {},
"root_type": "Expense"
},
"Summe Umlaufverm\u00f6gen": {
"2000 bis 2007 Forderungen aus Lief. und Leist. Inland": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2010 Forderungen aus Lieferungen und Leistungen Inland (10% USt, umsatzsteuerfrei)": { "2100 bis 2120 Forderungen aus Lief. und Leist. EU": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2000 Forderungen aus Lieferungen und Leistungen Inland (20% USt, umsatzsteuerfrei)": { "2150 bis 2170 Forderungen aus Lief. und Leist. Ausland": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2040 Forderungen aus Lieferungen und Leistungen Inland (sonstiger USt-Satz)": { "2200 bis 2220 Forderungen gegen\u00fcber verbundenen Unternehmen": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2100 Forderungen aus Lieferungen und Leistungen EU": { "2250 bis 2270 Forderungen gegen\u00fcber Unternehmen, mit denen ein Beteiligungsverh\u00e4ltnis besteht": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2150 Forderungen aus Lieferungen und Leistungen Ausland (Nicht-EU)": { "2300 bis 2460 Sonstige Forderungen und Verm\u00f6gensgegenst\u00e4nde": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2200 Forderungen gegen\u00fcber verbundenen Unternehmen": { "2630 bis 2670 Sonstige Wertpapiere": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2250 Forderungen gegen\u00fcber Unternehmen, mit denen ein Beteiligungsverh\u00e4ltnis besteht": { "2750 bis 2770 Kassenbest\u00e4nde in Fremdw\u00e4hrung": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2300 Sonstige Forderungen und Verm\u00f6gensgegenst\u00e4nde": { "Aktive Rechnungsabrenzungsposten": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2630 Sonstige Wertpapiere": { "Anteile an verbundenen Unternehmen": {
"account_type": "Stock"
},
"2750 Kassenbest\u00e4nde in Fremdw\u00e4hrung": {
"account_type": "Cash"
},
"2900 Aktive Rechnungsabrenzungsposten": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2600 Anteile an verbundenen Unternehmen": { "Bank / Guthaben bei Kreditinstituten": {
"account_type": "Equity"
},
"2680 Besitzwechsel ohne Forderungen": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2950 Aktiviertes Disagio": { "Besitzwechsel ...": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2610 Eigene Anteile und Wertpapiere an mit Mehrheit beteiligten Unternehmen": { "Disagio": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2570 Einfuhrumsatzsteuer (bezahlt)": {"account_type": "Tax"}, "Eigene Anteile (Wertpapiere)": {
"2460 Eingeforderte aber noch nicht eingezahlte Einlagen": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2180 Einzelwertberichtigungen zu Forderungen aus Lief. und Leist. Ausland": { "Einfuhrumsatzsteuer (bezahlt)": {},
"Eingeforderte aber noch nicht eingezahlte Einlagen": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2130 Einzelwertberichtigungen zu Forderungen aus Lief. und Leist. EU": { "Einzelwertberichtigungen zu Forderungen aus Lief. und Leist. Ausland": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2080 Einzelwertberichtigungen zu Forderungen aus Lief. und Leist. Inland ": { "Einzelwertberichtigungen zu Forderungen aus Lief. und Leist. EU": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2270 Einzelwertberichtigungen zu Forderungen gegen\u00fcber Unternehmen mit denen ein Beteiligungsverh\u00e4ltnis besteht": { "Einzelwertberichtigungen zu Forderungen aus Lief. und Leist. Inland ": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2230 Einzelwertberichtigungen zu Forderungen gegen\u00fcber verbundenen Unternehmen": { "Einzelwertberichtigungen zu Forderungen gegen\u00fcber Unternehmen mit denen ein Beteiligungsverh\u00e4ltnis besteht": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2470 Einzelwertberichtigungen zu sonstigen Forderungen und Verm\u00f6gensgegenst\u00e4nden": { "Einzelwertberichtigungen zu Forderungen gegen\u00fcber verbundenen Unternehmen": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2700 Kassenbestand": { "Einzelwertberichtigungen zu sonstigen Forderungen und Verm\u00f6gensgegenst\u00e4nden": {
"account_type": "Cash"
},
"2190 Pauschalwertberichtigungen zu Forderungen aus Lief. und Leist. sonstiges Ausland": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2130 Pauschalwertberichtigungen zu Forderungen aus Lief. und Leist. EU": { "Kassenbestand": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2100 Pauschalwertberichtigungen zu Forderungen aus Lief. und Leist. Inland ": { "Pauschalwertberichtigungen zu Forderungen aus Lief. und Leist. Ausland": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2280 Pauschalwertberichtigungen zu Forderungen gegen\u00fcber Unternehmen mit denen ein Beteiligungsverh\u00e4ltnis besteht": { "Pauschalwertberichtigungen zu Forderungen aus Lief. und Leist. EU": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2240 Pauschalwertberichtigungen zu Forderungen gegen\u00fcber verbundenen Unternehmen": { "Pauschalwertberichtigungen zu Forderungen aus Lief. und Leist. Inland ": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2480 Pauschalwertberichtigungen zu sonstigen Forderungen und Verm\u00f6gensgegenst\u00e4nden": { "Pauschalwertberichtigungen zu Forderungen gegen\u00fcber Unternehmen mit denen ein Beteiligungsverh\u00e4ltnis besteht": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2740 Postwertzeichen": { "Pauschalwertberichtigungen zu Forderungen gegen\u00fcber verbundenen Unternehmen": {
"account_type": "Cash"
},
"2780 Schecks in Euro": {
"account_type": "Cash"
},
"2800 Guthaben bei Bank": {
"account_type": "Bank"
},
"2801 Guthaben bei Bank - Sparkonto": {
"account_type": "Bank"
},
"2810 Guthaben bei Paypal": {
"account_type": "Bank"
},
"2930 Mietvorauszahlungen": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2980 Abgrenzung latenter Steuern": { "Pauschalwertberichtigungen zu sonstigen Forderungen und Verm\u00f6gensgegenst\u00e4nden": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2500 Vorsteuer": { "Postwertzeichen": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2510 Vorsteuer Inland 10%": { "Schecks in Inlandsw\u00e4hrung": {
"account_type": "Receivable"
},
"Sonstige Anteile": {
"account_type": "Receivable"
},
"Stempelmarken": {
"account_type": "Receivable"
},
"Steuerabgrenzung": {
"account_type": "Receivable"
},
"Unterschiedsbetrag gem. Abschnitt XII Pensionskassengesetz": {
"account_type": "Receivable"
},
"Unterschiedsbetrag zur gebotenen Pensionsr\u00fcckstellung": {
"account_type": "Receivable"
},
"Vorsteuer": {
"account_type": "Receivable"
},
"Vorsteuer aus ig. Erwerb 10%": {
"account_type": "Tax" "account_type": "Tax"
}, },
"2895 Schwebende Geldbewegugen": { "Vorsteuer aus ig. Erwerb 20%": {
"account_type": "Bank"
},
"2513 Vorsteuer Inland 5%": {
"account_type": "Tax" "account_type": "Tax"
}, },
"2515 Vorsteuer Inland 20%": { "Vorsteuer \u00a719/Art 19 ( reverse charge ) ": {
"account_type": "Tax" "account_type": "Tax"
}, },
"2520 Vorsteuer aus innergemeinschaftlichem Erwerb 10%": { "Wertberichtigungen": {
"account_type": "Tax"
},
"2525 Vorsteuer aus innergemeinschaftlichem Erwerb 20%": {
"account_type": "Tax"
},
"2530 Vorsteuer \u00a719/Art 19 ( reverse charge ) ": {
"account_type": "Tax"
},
"2690 Wertberichtigungen zu Wertpapieren und Anteilen": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"root_type": "Asset" "root_type": "Asset"
}, },
"Klasse 4: Betriebliche Erträge": { "Summe Vorr\u00e4te": {
"4000 Erlöse 20 %": {"account_type": "Income Account"}, "1000 bis 1090 Bezugsverrechnung": {},
"4020 Erl\u00f6se 0 % steuerbefreit": {"account_type": "Income Account"}, "1100 bis 1190 Rohstoffe": {},
"4010 Erl\u00f6se 10 %": {"account_type": "Income Account"}, "1200 bis 1290 Bezogene Teile": {},
"4030 Erl\u00f6se 13 %": {"account_type": "Income Account"}, "1300 bis 1340 Hilfsstoffe": {},
"4040 Erl\u00f6se 0 % innergemeinschaftliche Lieferungen": {"account_type": "Income Account"}, "1350 bis 1390 Betriebsstoffe": {},
"4400 Erl\u00f6sreduktion 0 % steuerbefreit": {"account_type": "Expense Account"}, "1400 bis 1490 Unfertige Erzeugniss": {},
"4410 Erl\u00f6sreduktion 10 %": {"account_type": "Expense Account"}, "1500 bis 1590 Fertige Erzeugniss": {},
"4420 Erl\u00f6sreduktion 20 %": {"account_type": "Expense Account"}, "1600 bis 1690 Waren": {},
"4430 Erl\u00f6sreduktion 13 %": {"account_type": "Expense Account"}, "1700 bis 1790 Noch nicht abgerechenbare Leistungen": {},
"4440 Erl\u00f6sreduktion 0 % innergemeinschaftliche Lieferungen": {"account_type": "Expense Account"}, "1900 bis 1990 Wertberichtigungen": {},
"4500 Ver\u00e4nderungen des Bestandes an fertigen und unfertigen Erzeugn. sowie an noch nicht abrechenbaren Leistungen": {"account_type": "Income Account"}, "geleistete Anzahlungen": {},
"4580 Aktivierte Eigenleistungen": {"account_type": "Income Account"}, "root_type": "Asset"
"4600 Erl\u00f6se aus dem Abgang vom Anlageverm\u00f6gen, ausgen. Finanzanlagen": {"account_type": "Income Account"},
"4630 Ertr\u00e4ge aus dem Abgang vom Anlageverm\u00f6gen, ausgen. Finanzanlagen": {"account_type": "Income Account"},
"4660 Ertr\u00e4ge aus der Zuschreibung zum Anlageverm\u00f6gen, ausgen. Finanzanlagen": {"account_type": "Income Account"},
"4700 Ertr\u00e4ge aus der Aufl\u00f6sung von R\u00fcckstellungen": {"account_type": "Income Account"},
"4800 \u00dcbrige betriebliche Ertr\u00e4ge": {"account_type": "Income Account"},
"root_type": "Income"
}, },
"Klasse 5: Aufwand f\u00fcr Material und Leistungen": { "Summe Wareneinsatz": {
"5000 Einkauf Partnerleistungen": {"account_type": "Cost of Goods Sold"}, "5100 bis 5190 Verbrauch an Rohstoffen": {},
"5100 Verbrauch an Rohstoffen": {"account_type": "Cost of Goods Sold"}, "5200 bis 5290 Verbrauch von bezogenen Fertig- und Einzelteilen": {},
"5200 Verbrauch von bezogenen Fertig- und Einzelteilen": {"account_type": "Cost of Goods Sold"}, "5300 bis 5390 Verbrauch von Hilfsstoffen": {},
"5300 Verbrauch von Hilfsstoffen": {"account_type": "Cost of Goods Sold"}, "5400 bis 5490 Verbrauch von Betriebsstoffen": {},
"5340 Verbrauch Verpackungsmaterial": {"account_type": "Cost of Goods Sold"}, "5500 bis 5590 Verbrauch von Werkzeugen und anderen Erzeugungshilfsmittel": {},
"5470 Verbrauch von Kleinmaterial": {"account_type": "Cost of Goods Sold"}, "5600 bis 5690 Verbrauch von Brenn- und Treibstoffen, Energie und Wasser": {},
"5450 Verbrauch von Reinigungsmaterial": {"account_type": "Cost of Goods Sold"}, "5700 bis 5790 Sonstige bezogene Herstellungsleistungen": {},
"5400 Verbrauch von Betriebsstoffen": {"account_type": "Cost of Goods Sold"}, "Aufwandsstellenrechnung": {},
"5500 Verbrauch von Werkzeugen und anderen Erzeugungshilfsmittel": {"account_type": "Cost of Goods Sold"}, "Skontoertr\u00e4ge auf Materialaufwand": {},
"5600 Verbrauch von Brenn- und Treibstoffen, Energie und Wasser": {"account_type": "Cost of Goods Sold"}, "Skontoertr\u00e4ge auf sonstige bezogene Herstellungsleistungen": {},
"5700 Bearbeitung durch Dritte": {"account_type": "Cost of Goods Sold"}, "Wareneinkauf 10 %": {},
"5900 Aufwandsstellenrechnung Material": {"account_type": "Cost of Goods Sold"}, "Wareneinkauf 20 %": {},
"5820 Skontoertr\u00e4ge (20% USt.)": {"account_type": "Income Account"}, "Wareneinkauf igErwerb 10 % VSt/10 % USt": {},
"5810 Skontoertr\u00e4ge (10% USt.)": {"account_type": "Income Account"}, "Wareneinkauf igErwerb 20 % VSt/20 % USt": {},
"5010 Handelswareneinkauf 10 %": {"account_type": "Cost of Goods Sold"}, "Wareneinkauf igErwerb ohne Vorsteuerabzug und 10 % USt": {},
"5020 Handelswareneinkauf 20 %": {"account_type": "Cost of Goods Sold"}, "Wareneinkauf igErwerb ohne Vorsteuerabzug und 20 % USt": {},
"5040 Handelswareneinkauf innergemeinschaftlicher Erwerb 10 % VSt/10 % USt": {"account_type": "Cost of Goods Sold"},
"5050 Handelswareneinkauf innergemeinschaftlicher Erwerb 20 % VSt/20 % USt": {"account_type": "Cost of Goods Sold"},
"5070 Handelswareneinkauf innergemeinschaftlicher Erwerb ohne Vorsteuerabzug und 10 % USt": {"account_type": "Cost of Goods Sold"},
"5080 Handelswareneinkauf innergemeinschaftlicher Erwerb ohne Vorsteuerabzug und 20 % USt": {"account_type": "Cost of Goods Sold"},
"root_type": "Expense" "root_type": "Expense"
},
"Klasse 6: Personalaufwand": {
"6000 L\u00f6hne": {"account_type": "Payable"},
"6200 Geh\u00e4lter": {"account_type": "Payable"},
"6400 Aufwendungen f\u00fcr Abfertigungen": {"account_type": "Payable"},
"6450 Aufwendungen f\u00fcr Altersversorgung": {"account_type": "Payable"},
"6500 Gesetzlicher Sozialaufwand Arbeiter": {"account_type": "Payable"},
"6560 Gesetzlicher Sozialaufwand Angestellte": {"account_type": "Payable"},
"6600 Lohnabh\u00e4ngige Abgaben und Pflichtbeitr\u00e4gte": {"account_type": "Payable"},
"6660 Gehaltsabh\u00e4ngige Abgaben und Pflichtbeitr\u00e4gte": {"account_type": "Payable"},
"6700 Sonstige Sozialaufwendungen": {"account_type": "Payable"},
"6900 Aufwandsstellenrechnung Personal": {"account_type": "Payable"},
"root_type": "Expense"
},
"Klasse 7: Abschreibungen und sonstige betriebliche Aufwendungen": {
"7010 Abschreibungen auf das Anlageverm\u00f6gen (ausgenommen Finanzanlagen)": {"account_type": "Depreciation"},
"7100 Sonstige Steuern und Geb\u00fchren": {"account_type": "Tax"},
"7200 Instandhaltung u. Reinigung durch Dritte, Entsorgung, Energie": {"account_type": "Expense Account"},
"7300 Transporte durch Dritte": {"account_type": "Expense Account"},
"7310 Fahrrad - Aufwand": {"account_type": "Expense Account"},
"7320 Kfz - Aufwand": {"account_type": "Expense Account"},
"7330 LKW - Aufwand": {"account_type": "Expense Account"},
"7340 Lastenrad - Aufwand": {"account_type": "Expense Account"},
"7350 Reise- und Fahraufwand": {"account_type": "Expense Account"},
"7360 Tag- und N\u00e4chtigungsgelder": {"account_type": "Expense Account"},
"7380 Nachrichtenaufwand": {"account_type": "Expense Account"},
"7400 Miet- und Pachtaufwand": {"account_type": "Expense Account"},
"7440 Leasingaufwand": {"account_type": "Expense Account"},
"7480 Lizenzaufwand": {"account_type": "Expense Account"},
"7500 Aufwand f\u00fcr beigestelltes Personal": {"account_type": "Expense Account"},
"7540 Provisionen an Dritte": {"account_type": "Expense Account"},
"7580 Aufsichtsratsverg\u00fctungen": {"account_type": "Expense Account"},
"7610 Druckerzeugnisse und Vervielf\u00e4ltigungen": {"account_type": "Expense Account"},
"7650 Werbung und Repr\u00e4sentationen": {"account_type": "Expense Account"},
"7700 Versicherungen": {"account_type": "Expense Account"},
"7750 Beratungs- und Pr\u00fcfungsaufwand": {"account_type": "Expense Account"},
"7800 Forderungsverluste und Schadensf\u00e4lle": {"account_type": "Expense Account"},
"7840 Verschiedene betriebliche Aufwendungen": {"account_type": "Expense Account"},
"7910 Aufwandsstellenrechung der Hersteller": {"account_type": "Expense Account"},
"7060 Sofortabschreibungen geringwertig": {"account_type": "Expense Account"},
"7070 Abschreibungen vom Umlaufverm\u00f6gen, soweit diese die im Unternehmen \u00fcblichen Abschreibungen \u00fcbersteigen": {"account_type": "Depreciation"},
"7900 Aufwandsstellenrechnung": {"account_type": "Expense Account"},
"7770 Aus- und Fortbildung": {"account_type": "Expense Account"},
"7820 Buchwert abgegangener Anlagen, ausgenommen Finanzanlagen": {"account_type": "Expense Account"},
"7600 B\u00fcromaterial und Drucksorten": {"account_type": "Expense Account"},
"7630 Fachliteratur und Zeitungen ": {"account_type": "Expense Account"},
"7960 Herstellungskosten der zur Erzielung der Umsatzerl\u00f6se erbrachten Leistungen": {"account_type": "Expense Account"},
"7780 Mitgliedsbeitr\u00e4ge": {"account_type": "Expense Account"},
"7880 Skontoertr\u00e4ge auf sonstige betriebliche Aufwendungen": {"account_type": "Expense Account"},
"7990 Sonstige betrieblichen Aufwendungen": {"account_type": "Expense Account"},
"7680 Spenden und Trinkgelder": {"account_type": "Expense Account"},
"7790 Spesen des Geldverkehrs": {"account_type": "Expense Account"},
"7830 Verluste aus dem Abgang vom Anlageverm\u00f6gen, ausgenommen Finanzanlagen": {"account_type": "Expense Account"},
"7970 Vertriebskosten": {"account_type": "Expense Account"},
"7980 Verwaltungskosten": {"account_type": "Expense Account"},
"root_type": "Expense"
},
"Klasse 8: Finanz- und ausserordentliche Ertr\u00e4ge und Aufwendungen": {
"8000 Ertr\u00e4ge aus Beteiligungen": {"account_type": "Income Account"},
"8050 Ertr\u00e4ge aus anderen Wertpapieren und Ausleihungen des Finanzanlageverm\u00f6gens": {"account_type": "Income Account"},
"8100 Zinsen aus Bankguthaben": {"account_type": "Income Account"},
"8110 Zinsen aus gewaehrten Darlehen": {"account_type": "Income Account"},
"8130 Verzugszinsenertraege": {"account_type": "Income Account"},
"8220 Aufwendungen aus Beteiligungen": {"account_type": "Expense Account"},
"8260 Aufwendungen aus sonst. Fiananzanlagen und aus Wertpapieren des Umlaufverm\u00f6gens": {},
"8280 Zinsen und \u00e4hnliche Aufwendungem": {"account_type": "Expense Account"},
"8400 Au\u00dferordentliche Ertr\u00e4ge": {"account_type": "Income Account"},
"8450 Au\u00dferordentliche Aufwendungen": {"account_type": "Expense Account"},
"8500 Steuern vom Einkommen und vom Ertrag": {
"account_type": "Tax"
},
"8600 Aufl\u00f6sung unversteuerten R\u00fccklagen": {"account_type": "Income Account"},
"8700 Aufl\u00f6sung von Kapitalr\u00fccklagen": {"account_type": "Income Account"},
"8750 Aufl\u00f6sung von Gewinnr\u00fccklagen": {"account_type": "Income Account"},
"8800 Zuweisung zu unversteuerten R\u00fccklagen": {"account_type": "Expense Account"},
"8900 Zuweisung zu Gewinnr\u00fccklagen": {"account_type": "Expense Account"},
"8100 Buchwert abgegangener Beteiligungen": {"account_type": "Expense Account"},
"8130 Buchwert abgegangener Wertpapiere des Umlaufverm\u00f6gens": {"account_type": "Expense Account"},
"8120 Buchwert abgegangener sonstiger Finanzanlagen": {"account_type": "Expense Account"},
"8990 Gewinnabfuhr bzw. Verlust\u00fcberrechnung aus Ergebnisabf\u00fchrungsvertr\u00e4gen": {"account_type": "Expense Account"},
"8350 nicht ausgenutzte Lieferantenskonti": {"account_type": "Expense Account"},
"root_type": "Income"
},
"Klasse 9 Passiva: Eigenkapital, R\u00fccklagen, stille Einlagen, Abschlusskonten": {
"9000 Gezeichnetes bzw. gewidmetes Kapital": {
"account_type": "Equity"
},
"9200 Kapitalr\u00fccklagen": {
"account_type": "Equity"
},
"9300 Gewinnr\u00fccklagen": {
"account_type": "Equity"
},
"9400 Bewertungsreserven uns sonst. unversteuerte R\u00fccklagen": {
"account_type": "Equity"
},
"9600 Private Entnahmen": {"account_type": "Equity"},
"9610 Privatsteuern": {"account_type": "Equity"},
"9700 Einlagen stiller Gesellschafter ": {"account_type": "Equity"},
"9900 Evidenzkonto": {"account_type": "Equity"},
"9800 Er\u00f6ffnungsbilanzkonto (EBK)": {"account_type": "Equity"},
"9880 Jahresergebnis laut Gewinn- und Verlustrechnung (G+V)": {"account_type": "Equity"},
"9850 Schlussbilanzkonto (SBK)": {"account_type": "Round Off"},
"9190 nicht eingeforderte ausstehende Einlagen und berechtigte Entnahmen von Gesellschaftern": {
"account_type": "Equity"
},
"root_type": "Equity"
} }
} }
} }

View File

@@ -437,20 +437,12 @@
}, },
"Sales": { "Sales": {
"Sales from Other Regions": { "Sales from Other Regions": {
"Sales from Other Region": { "Sales from Other Region": {}
"account_type": "Income Account"
}
}, },
"Sales of same region": { "Sales of same region": {
"Management Consultancy Fees 1": { "Management Consultancy Fees 1": {},
"account_type": "Income Account" "Sales Account": {},
}, "Sales of I/C": {}
"Sales Account": {
"account_type": "Income Account"
},
"Sales of I/C": {
"account_type": "Income Account"
}
} }
}, },
"root_type": "Income" "root_type": "Income"

View File

@@ -56,9 +56,7 @@
"Constru\u00e7\u00f5es em Andamento de Im\u00f3veis Destinados \u00e0 Venda": {}, "Constru\u00e7\u00f5es em Andamento de Im\u00f3veis Destinados \u00e0 Venda": {},
"Estoques Destinados \u00e0 Doa\u00e7\u00e3o": {}, "Estoques Destinados \u00e0 Doa\u00e7\u00e3o": {},
"Im\u00f3veis Destinados \u00e0 Venda": {}, "Im\u00f3veis Destinados \u00e0 Venda": {},
"Insumos (materiais diretos)": { "Insumos (materiais diretos)": {},
"account_type": "Stock"
},
"Insumos Agropecu\u00e1rios": {}, "Insumos Agropecu\u00e1rios": {},
"Mercadorias para Revenda": {}, "Mercadorias para Revenda": {},
"Outras 11": {}, "Outras 11": {},
@@ -148,65 +146,6 @@
"root_type": "Asset" "root_type": "Asset"
}, },
"CUSTOS DE PRODU\u00c7\u00c3O": { "CUSTOS DE PRODU\u00c7\u00c3O": {
"CUSTO DOS PRODUTOS E SERVI\u00c7OS VENDIDOS": {
"CUSTO DOS PRODUTOS VENDIDOS": {
"CUSTO DOS PRODUTOS VENDIDOS PARA AS DEMAIS ATIVIDADES": {
"Custos dos Produtos Vendidos em Geral": {
"account_type": "Cost of Goods Sold"
},
"Outros Custos 4": {},
"account_type": "Cost of Goods Sold"
},
"CUSTO DOS PRODUTOS VENDIDOS PARA ASSIST\u00caNCIA SOCIAL": {
"Custos dos Produtos para Assist\u00eancia Social - Gratuidades": {},
"Custos dos Produtos para Assist\u00eancia Social - Vendidos": {},
"Outras": {}
},
"CUSTO DOS PRODUTOS VENDIDOS PARA EDUCA\u00c7\u00c3O": {
"Custos dos Produtos para Educa\u00e7\u00e3o - Gratuidades": {},
"Custos dos Produtos para Educa\u00e7\u00e3o - Vendidos": {},
"Outros Custos 6": {}
},
"CUSTO DOS PRODUTOS VENDIDOS PARA SA\u00daDE": {
"Custos dos Produtos para Sa\u00fade - Gratuidades": {},
"Custos dos Produtos para Sa\u00fade \u2013 Vendidos": {},
"Outros Custos 5": {}
},
"account_type": "Cost of Goods Sold"
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS": {
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA AS DEMAIS ATIVIDADES": {
"Custo dos Servi\u00e7os Prestados em Geral": {},
"Outros Custos": {}
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA ASSIST\u00caNCIA SOCIAL": {
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 1": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 1": {},
"Custo dos Servi\u00e7os Prestados a Gratuidade 1": {},
"Custo dos Servi\u00e7os Prestados a Pacientes Particulares": {},
"Outros Custos 2": {}
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA EDUCA\u00c7\u00c3O": {
"Custo dos Servi\u00e7os Prestados a Alunos N\u00e3o Bolsistas": {},
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias (Exceto PROUNI)": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas": {},
"Custo dos Servi\u00e7os Prestados a Gratuidade": {},
"Custo dos Servi\u00e7os Prestados ao PROUNI": {},
"Outros Custos 1": {}
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA SA\u00daDE": {
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios SUS": {},
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias 1": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 2": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 2": {},
"Custo dos Servi\u00e7os Prestados a Gratuidade 2": {},
"Custo dos Servi\u00e7os Prestados a Pacientes Particulares 1": {},
"Outros Custos 3": {}
}
}
},
"CUSTO DOS BENS E SERVI\u00c7OS PRODUZIDOS": { "CUSTO DOS BENS E SERVI\u00c7OS PRODUZIDOS": {
"CUSTO DOS PRODUTOS DE FABRICA\u00c7\u00c3O PR\u00d3PRIA PRODUZIDOS": { "CUSTO DOS PRODUTOS DE FABRICA\u00c7\u00c3O PR\u00d3PRIA PRODUZIDOS": {
"Alimenta\u00e7\u00e3o do Trabalhador": {}, "Alimenta\u00e7\u00e3o do Trabalhador": {},
@@ -682,9 +621,7 @@
"Receita das Unidades Imobili\u00e1rias Vendidas": {}, "Receita das Unidades Imobili\u00e1rias Vendidas": {},
"Receita de Exporta\u00e7\u00e3o Direta de Mercadorias e Produtos": {}, "Receita de Exporta\u00e7\u00e3o Direta de Mercadorias e Produtos": {},
"Receita de Exporta\u00e7\u00e3o de Servi\u00e7os": {}, "Receita de Exporta\u00e7\u00e3o de Servi\u00e7os": {},
"Receita de Loca\u00e7\u00e3o de Bens M\u00f3veis e Im\u00f3veis": { "Receita de Loca\u00e7\u00e3o de Bens M\u00f3veis e Im\u00f3veis": {},
"account_type": "Income Account"
},
"Receita de Vendas de Mercadorias e Produtos a Comercial Exportadora com Fim Espec\u00edfico de Exporta\u00e7\u00e3o": {} "Receita de Vendas de Mercadorias e Produtos a Comercial Exportadora com Fim Espec\u00edfico de Exporta\u00e7\u00e3o": {}
} }
} }
@@ -708,6 +645,65 @@
} }
}, },
"RESULTADO OPERACIONAL": { "RESULTADO OPERACIONAL": {
"CUSTO DOS PRODUTOS E SERVI\u00c7OS VENDIDOS": {
"CUSTO DOS PRODUTOS VENDIDOS": {
"CUSTO DOS PRODUTOS VENDIDOS PARA AS DEMAIS ATIVIDADES": {
"Custos dos Produtos Vendidos em Geral": {
"account_type": "Cost of Goods Sold"
},
"Outros Custos 4": {},
"account_type": "Cost of Goods Sold"
},
"CUSTO DOS PRODUTOS VENDIDOS PARA ASSIST\u00caNCIA SOCIAL": {
"Custos dos Produtos para Assist\u00eancia Social - Gratuidades": {},
"Custos dos Produtos para Assist\u00eancia Social - Vendidos": {},
"Outras": {}
},
"CUSTO DOS PRODUTOS VENDIDOS PARA EDUCA\u00c7\u00c3O": {
"Custos dos Produtos para Educa\u00e7\u00e3o - Gratuidades": {},
"Custos dos Produtos para Educa\u00e7\u00e3o - Vendidos": {},
"Outros Custos 6": {}
},
"CUSTO DOS PRODUTOS VENDIDOS PARA SA\u00daDE": {
"Custos dos Produtos para Sa\u00fade - Gratuidades": {},
"Custos dos Produtos para Sa\u00fade \u2013 Vendidos": {},
"Outros Custos 5": {}
},
"account_type": "Cost of Goods Sold"
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS": {
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA AS DEMAIS ATIVIDADES": {
"Custo dos Servi\u00e7os Prestados em Geral": {},
"Outros Custos": {}
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA ASSIST\u00caNCIA SOCIAL": {
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 1": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 1": {},
"Custo dos Servi\u00e7os Prestados a Gratuidade 1": {},
"Custo dos Servi\u00e7os Prestados a Pacientes Particulares": {},
"Outros Custos 2": {}
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA EDUCA\u00c7\u00c3O": {
"Custo dos Servi\u00e7os Prestados a Alunos N\u00e3o Bolsistas": {},
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias (Exceto PROUNI)": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas": {},
"Custo dos Servi\u00e7os Prestados a Gratuidade": {},
"Custo dos Servi\u00e7os Prestados ao PROUNI": {},
"Outros Custos 1": {}
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA SA\u00daDE": {
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios SUS": {},
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias 1": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 2": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 2": {},
"Custo dos Servi\u00e7os Prestados a Gratuidade 2": {},
"Custo dos Servi\u00e7os Prestados a Pacientes Particulares 1": {},
"Outros Custos 3": {}
}
}
},
"DESPESAS OPERACIONAIS": { "DESPESAS OPERACIONAIS": {
"DESPESAS OPERACIONAIS 1": { "DESPESAS OPERACIONAIS 1": {
"DESPESAS OPERACIONAIS 2": { "DESPESAS OPERACIONAIS 2": {

View File

@@ -33,9 +33,7 @@
}, },
"Stocks": { "Stocks": {
"Mati\u00e8res premi\u00e8res": {}, "Mati\u00e8res premi\u00e8res": {},
"Stock de produits fini": { "Stock de produits fini": {},
"account_type": "Stock"
},
"Stock exp\u00e9di\u00e9 non-factur\u00e9": {}, "Stock exp\u00e9di\u00e9 non-factur\u00e9": {},
"Travaux en cours": {}, "Travaux en cours": {},
"account_type": "Stock" "account_type": "Stock"
@@ -397,11 +395,9 @@
}, },
"Produits": { "Produits": {
"Revenus de ventes": { "Revenus de ventes": {
"Escomptes de volume sur ventes": {}, " Escomptes de volume sur ventes": {},
"Autres produits d'exploitation": {}, "Autres produits d'exploitation": {},
"Ventes": { "Ventes": {},
"account_type": "Income Account"
},
"Ventes avec des provinces harmonis\u00e9es": {}, "Ventes avec des provinces harmonis\u00e9es": {},
"Ventes avec des provinces non-harmonis\u00e9es": {}, "Ventes avec des provinces non-harmonis\u00e9es": {},
"Ventes \u00e0 l'\u00e9tranger": {} "Ventes \u00e0 l'\u00e9tranger": {}

View File

@@ -1,38 +1,38 @@
{ {
"country_code": "de", "country_code": "de",
"name": "SKR03 mit Kontonummern", "name": "SKR03 mit Kontonummern",
"tree": { "tree": {
"Aktiva": { "Aktiva": {
"is_group": 1, "is_group": 1,
"root_type": "Asset", "root_type": "Asset",
"A - Anlagevermögen": { "A - Anlagevermögen": {
"is_group": 1, "is_group": 1,
"EDV-Software": { "EDV-Software": {
"account_number": "0027", "account_number": "0027",
"account_type": "Fixed Asset" "account_type": "Fixed Asset"
}, },
"Geschäftsausstattung": { "Gesch\u00e4ftsausstattung": {
"account_number": "0410", "account_number": "0410",
"account_type": "Fixed Asset" "account_type": "Fixed Asset"
}, },
"Büroeinrichtung": { "B\u00fcroeinrichtung": {
"account_number": "0420", "account_number": "0420",
"account_type": "Fixed Asset" "account_type": "Fixed Asset"
}, },
"Darlehen": { "Darlehen": {
"account_number": "0565" "account_number": "0565"
}, },
"Maschinen": { "Maschinen": {
"account_number": "0210", "account_number": "0210",
"account_type": "Fixed Asset" "account_type": "Fixed Asset"
}, },
"Betriebsausstattung": { "Betriebsausstattung": {
"account_number": "0400", "account_number": "0400",
"account_type": "Fixed Asset" "account_type": "Fixed Asset"
}, },
"Ladeneinrichtung": { "Ladeneinrichtung": {
"account_number": "0430", "account_number": "0430",
"account_type": "Fixed Asset" "account_type": "Fixed Asset"
}, },
"Accumulated Depreciation": { "Accumulated Depreciation": {
"account_type": "Accumulated Depreciation" "account_type": "Accumulated Depreciation"
@@ -53,58 +53,43 @@
}, },
"II. Forderungen und sonstige Vermögensgegenstände": { "II. Forderungen und sonstige Vermögensgegenstände": {
"is_group": 1, "is_group": 1,
"Forderungen aus Lieferungen und Leistungen mit Kontokorrent": { "Ford. a. Lieferungen und Leistungen": {
"account_number": "1400", "account_number": "1400",
"account_type": "Receivable",
"is_group": 1
},
"Forderungen aus Lieferungen und Leistungen ohne Kontokorrent": {
"account_number": "1410",
"account_type": "Receivable" "account_type": "Receivable"
}, },
"Durchlaufende Posten": { "Durchlaufende Posten": {
"account_number": "1590" "account_number": "1590"
}, },
"Verrechnungskonto Gewinnermittlung § 4 Abs. 3 EStG, nicht ergebniswirksam": { "Gewinnermittlung \u00a74/3 nicht Ergebniswirksam": {
"account_number": "1371" "account_number": "1371"
}, },
"Abziehbare Vorsteuer": { "Abziehbare Vorsteuer": {
"account_type": "Tax",
"is_group": 1, "is_group": 1,
"Abziehbare Vorsteuer 7 %": { "Abziehbare Vorsteuer 7%": {
"account_number": "1571", "account_number": "1571"
"account_type": "Tax",
"tax_rate": 7.0
}, },
"Abziehbare Vorsteuer 19 %": { "Abziehbare Vorsteuer 19%": {
"account_number": "1576", "account_number": "1576"
"account_type": "Tax",
"tax_rate": 19.0
}, },
"Abziehbare Vorsteuer nach § 13b UStG 19 %": { "Abziehbare Vorsteuer nach \u00a713b UStG 19%": {
"account_number": "1577", "account_number": "1577"
"account_type": "Tax", },
"tax_rate": 19.0 "Leistungen \u00a713b UStG 19% Vorsteuer, 19% Umsatzsteuer": {
"account_number": "3120"
} }
} }
}, },
"III. Wertpapiere": { "III. Wertpapiere": {
"is_group": 1, "is_group": 1
"Anteile an verbundenen Unternehmen (Umlaufvermögen)": {
"account_number": "1340"
},
"Anteile an herrschender oder mit Mehrheit beteiligter Gesellschaft": {
"account_number": "1344"
},
"Sonstige Wertpapiere": {
"account_number": "1348"
}
}, },
"IV. Kassenbestand, Bundesbankguthaben, Guthaben bei Kreditinstituten und Schecks.": { "IV. Kassenbestand, Bundesbankguthaben, Guthaben bei Kreditinstituten und Schecks.": {
"is_group": 1, "is_group": 1,
"Kasse": { "Kasse": {
"is_group": 1,
"account_type": "Cash", "account_type": "Cash",
"is_group": 1,
"Kasse": { "Kasse": {
"is_group": 1,
"account_number": "1000", "account_number": "1000",
"account_type": "Cash" "account_type": "Cash"
} }
@@ -126,21 +111,21 @@
"C - Rechnungsabgrenzungsposten": { "C - Rechnungsabgrenzungsposten": {
"is_group": 1, "is_group": 1,
"Aktive Rechnungsabgrenzung": { "Aktive Rechnungsabgrenzung": {
"account_number": "0980" "account_number": "0980"
} }
}, },
"D - Aktive latente Steuern": { "D - Aktive latente Steuern": {
"is_group": 1, "is_group": 1,
"Aktive latente Steuern": { "Aktive latente Steuern": {
"account_number": "0983" "account_number": "0983"
} }
}, },
"E - Aktiver Unterschiedsbetrag aus der Vermögensverrechnung": { "E - Aktiver Unterschiedsbetrag aus der Vermögensverrechnung": {
"is_group": 1 "is_group": 1
} }
}, },
"Passiva": { "Passiva": {
"is_group": 1, "is_group": 1,
"root_type": "Liability", "root_type": "Liability",
"A. Eigenkapital": { "A. Eigenkapital": {
"is_group": 1, "is_group": 1,
@@ -185,13 +170,8 @@
}, },
"IV. Verbindlichkeiten aus Lieferungen und Leistungen": { "IV. Verbindlichkeiten aus Lieferungen und Leistungen": {
"is_group": 1, "is_group": 1,
"Verbindlichkeiten aus Lieferungen und Leistungen mit Kontokorrent": { "Verbindlichkeiten aus Lieferungen u. Leistungen": {
"account_number": "1600", "account_number": "1600",
"account_type": "Payable",
"is_group": 1
},
"Verbindlichkeiten aus Lieferungen und Leistungen ohne Kontokorrent": {
"account_number": "1610",
"account_type": "Payable" "account_type": "Payable"
} }
}, },
@@ -220,32 +200,26 @@
}, },
"Umsatzsteuer": { "Umsatzsteuer": {
"is_group": 1, "is_group": 1,
"Umsatzsteuer 7 %": { "account_type": "Tax",
"account_number": "1771", "Umsatzsteuer 7%": {
"account_type": "Tax", "account_number": "1771"
"tax_rate": 7.0
}, },
"Umsatzsteuer 19 %": { "Umsatzsteuer 19%": {
"account_number": "1776", "account_number": "1776"
"account_type": "Tax",
"tax_rate": 19.0
}, },
"Umsatzsteuer-Vorauszahlung": { "Umsatzsteuer-Vorauszahlung": {
"account_number": "1780", "account_number": "1780"
"account_type": "Tax"
}, },
"Umsatzsteuer-Vorauszahlung 1/11": { "Umsatzsteuer-Vorauszahlung 1/11": {
"account_number": "1781" "account_number": "1781"
}, },
"Umsatzsteuer nach § 13b UStG 19 %": { "Umsatzsteuer \u00a7 13b UStG 19%": {
"account_number": "1787", "account_number": "1787"
"account_type": "Tax",
"tax_rate": 19.0
}, },
"Umsatzsteuer Vorjahr": { "Umsatzsteuer Vorjahr": {
"account_number": "1790" "account_number": "1790"
}, },
"Umsatzsteuer frühere Jahre": { "Umsatzsteuer fr\u00fchere Jahre": {
"account_number": "1791" "account_number": "1791"
} }
} }
@@ -260,56 +234,44 @@
"E. Passive latente Steuern": { "E. Passive latente Steuern": {
"is_group": 1 "is_group": 1
} }
}, },
"Erlöse u. Erträge 2/8": { "Erl\u00f6se u. Ertr\u00e4ge 2/8": {
"is_group": 1, "is_group": 1,
"root_type": "Income", "root_type": "Income",
"Erlöskonten 8": { "Erl\u00f6skonten 8": {
"is_group": 1, "is_group": 1,
"Erlöse": { "Erl\u00f6se": {
"account_number": "8200", "account_number": "8200",
"account_type": "Income Account" "account_type": "Income Account"
}, },
"Erlöse USt. 19 %": { "Erl\u00f6se USt. 19%": {
"account_number": "8400", "account_number": "8400",
"account_type": "Income Account" "account_type": "Income Account"
}, },
"Erlöse USt. 7 %": { "Erl\u00f6se USt. 7%": {
"account_number": "8300", "account_number": "8300",
"account_type": "Income Account" "account_type": "Income Account"
} }
}, },
"Ertragskonten 2": { "Ertragskonten 2": {
"is_group": 1, "is_group": 1,
"sonstige Zinsen und ähnliche Erträge": { "sonstige Zinsen und \u00e4hnliche Ertr\u00e4ge": {
"account_number": "2650", "account_number": "2650",
"account_type": "Income Account" "account_type": "Income Account"
}, },
"Außerordentliche Erträge": { "Au\u00dferordentliche Ertr\u00e4ge": {
"account_number": "2500", "account_number": "2500",
"account_type": "Income Account" "account_type": "Income Account"
}, },
"Sonstige Erträge": { "Sonstige Ertr\u00e4ge": {
"account_number": "2700", "account_number": "2700",
"account_type": "Income Account" "account_type": "Income Account"
} }
} }
}, },
"Aufwendungen 2/4": { "Aufwendungen 2/4": {
"is_group": 1, "is_group": 1,
"root_type": "Expense", "root_type": "Expense",
"Fremdleistungen": {
"account_number": "3100",
"account_type": "Expense Account"
},
"Fremdleistungen ohne Vorsteuer": {
"account_number": "3109",
"account_type": "Expense Account"
},
"Bauleistungen eines im Inland ansässigen Unternehmers 19 % Vorsteuer und 19 % Umsatzsteuer": {
"account_number": "3120",
"account_type": "Expense Account"
},
"Wareneingang": { "Wareneingang": {
"account_number": "3200" "account_number": "3200"
}, },
@@ -336,234 +298,234 @@
"Gegenkonto 4996-4998": { "Gegenkonto 4996-4998": {
"account_number": "4999" "account_number": "4999"
}, },
"Abschreibungen": { "Abschreibungen": {
"is_group": 1, "is_group": 1,
"Abschreibungen auf Sachanlagen (ohne AfA auf Kfz und Gebäude)": { "Abschreibungen auf Sachanlagen (ohne AfA auf Kfz und Gebäude)": {
"account_number": "4830", "account_number": "4830",
"account_type": "Accumulated Depreciation" "account_type": "Accumulated Depreciation"
}, },
"Abschreibungen auf Gebäude": { "Abschreibungen auf Gebäude": {
"account_number": "4831", "account_number": "4831",
"account_type": "Depreciation" "account_type": "Depreciation"
}, },
"Abschreibungen auf Kfz": { "Abschreibungen auf Kfz": {
"account_number": "4832", "account_number": "4832",
"account_type": "Depreciation" "account_type": "Depreciation"
}, },
"Sofortabschreibung GWG": { "Sofortabschreibung GWG": {
"account_number": "4855", "account_number": "4855",
"account_type": "Expense Account" "account_type": "Expense Account"
} }
}, },
"Kfz-Kosten": { "Kfz-Kosten": {
"is_group": 1, "is_group": 1,
"Kfz-Steuer": { "Kfz-Steuer": {
"account_number": "4510", "account_number": "4510",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Kfz-Versicherungen": { "Kfz-Versicherungen": {
"account_number": "4520", "account_number": "4520",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"laufende Kfz-Betriebskosten": { "laufende Kfz-Betriebskosten": {
"account_number": "4530", "account_number": "4530",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Kfz-Reparaturen": { "Kfz-Reparaturen": {
"account_number": "4540", "account_number": "4540",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Fremdfahrzeuge": { "Fremdfahrzeuge": {
"account_number": "4570", "account_number": "4570",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"sonstige Kfz-Kosten": { "sonstige Kfz-Kosten": {
"account_number": "4580", "account_number": "4580",
"account_type": "Expense Account" "account_type": "Expense Account"
} }
}, },
"Personalkosten": { "Personalkosten": {
"is_group": 1, "is_group": 1,
"Gehälter": { "Geh\u00e4lter": {
"account_number": "4120", "account_number": "4120",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"gesetzliche soziale Aufwendungen": { "gesetzliche soziale Aufwendungen": {
"account_number": "4130", "account_number": "4130",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Aufwendungen für Altersvorsorge": { "Aufwendungen f\u00fcr Altersvorsorge": {
"account_number": "4165", "account_number": "4165",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Vermögenswirksame Leistungen": { "Verm\u00f6genswirksame Leistungen": {
"account_number": "4170", "account_number": "4170",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Aushilfslöhne": { "Aushilfsl\u00f6hne": {
"account_number": "4190", "account_number": "4190",
"account_type": "Expense Account" "account_type": "Expense Account"
} }
}, },
"Raumkosten": { "Raumkosten": {
"is_group": 1, "is_group": 1,
"Miete und Nebenkosten": { "Miete und Nebenkosten": {
"account_number": "4210", "account_number": "4210",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Gas, Wasser, Strom (Verwaltung, Vertrieb)": { "Gas, Wasser, Strom (Verwaltung, Vertrieb)": {
"account_number": "4240", "account_number": "4240",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Reinigung": { "Reinigung": {
"account_number": "4250", "account_number": "4250",
"account_type": "Expense Account" "account_type": "Expense Account"
} }
}, },
"Reparatur/Instandhaltung": { "Reparatur/Instandhaltung": {
"is_group": 1, "is_group": 1,
"Reparaturen und Instandhaltungen von anderen Anlagen und Betriebs- und Geschäftsausstattung": { "Reparatur u. Instandh. von Anlagen/Maschinen u. Betriebs- u. Gesch\u00e4ftsausst.": {
"account_number": "4805", "account_number": "4805",
"account_type": "Expense Account" "account_type": "Expense Account"
} }
}, },
"Versicherungsbeiträge": { "Versicherungsbeitr\u00e4ge": {
"is_group": 1, "is_group": 1,
"Versicherungen": { "Versicherungen": {
"account_number": "4360", "account_number": "4360",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Beiträge": { "Beitr\u00e4ge": {
"account_number": "4380", "account_number": "4380",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"sonstige Ausgaben": { "sonstige Ausgaben": {
"account_number": "4390", "account_number": "4390",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"steuerlich abzugsfähige Verspätungszuschläge und Zwangsgelder": { "steuerlich abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": {
"account_number": "4396", "account_number": "4396",
"account_type": "Expense Account" "account_type": "Expense Account"
} }
}, },
"Werbe-/Reisekosten": { "Werbe-/Reisekosten": {
"is_group": 1, "is_group": 1,
"Werbekosten": { "Werbekosten": {
"account_number": "4610", "account_number": "4610",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Aufmerksamkeiten": { "Aufmerksamkeiten": {
"account_number": "4653", "account_number": "4653",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"nicht abzugsfähige Betriebsausg. aus Werbe-, Repräs.- u. Reisekosten": { "nicht abzugsf\u00e4hige Betriebsausg. aus Werbe-, Repr\u00e4s.- u. Reisekosten": {
"account_number": "4665", "account_number": "4665",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Reisekosten Unternehmer": { "Reisekosten Unternehmer": {
"account_number": "4670", "account_number": "4670",
"account_type": "Expense Account" "account_type": "Expense Account"
} }
}, },
"verschiedene Kosten": { "verschiedene Kosten": {
"is_group": 1, "is_group": 1,
"Porto": { "Porto": {
"account_number": "4910", "account_number": "4910",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Telekom": { "Telekom": {
"account_number": "4920", "account_number": "4920",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Mobilfunk D2": { "Mobilfunk D2": {
"account_number": "4921", "account_number": "4921",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Internet": { "Internet": {
"account_number": "4922", "account_number": "4922",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Bürobedarf": { "B\u00fcrobedarf": {
"account_number": "4930", "account_number": "4930",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Zeitschriften, Bücher": { "Zeitschriften, B\u00fccher": {
"account_number": "4940", "account_number": "4940",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Fortbildungskosten": { "Fortbildungskosten": {
"account_number": "4945", "account_number": "4945",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Buchführungskosten": { "Buchf\u00fchrungskosten": {
"account_number": "4955", "account_number": "4955",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Abschluß- u. Prüfungskosten": { "Abschlu\u00df- u. Pr\u00fcfungskosten": {
"account_number": "4957", "account_number": "4957",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Nebenkosten des Geldverkehrs": { "Nebenkosten des Geldverkehrs": {
"account_number": "4970", "account_number": "4970",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Werkzeuge und Kleingeräte": { "Werkzeuge und Kleinger\u00e4te": {
"account_number": "4985", "account_number": "4985",
"account_type": "Expense Account" "account_type": "Expense Account"
} }
}, },
"Zinsaufwendungen": { "Zinsaufwendungen": {
"is_group": 1, "is_group": 1,
"Zinsaufwendungen für kurzfristige Verbindlichkeiten": { "Zinsaufwendungen f\u00fcr kurzfristige Verbindlichkeiten": {
"account_number": "2110", "account_number": "2110",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Zinsaufwendungen für KFZ Finanzierung": { "Zinsaufwendungen f\u00fcr KFZ Finanzierung": {
"account_number": "2121", "account_number": "2121",
"account_type": "Expense Account" "account_type": "Expense Account"
} }
} }
}, },
"Anfangsbestand 9": { "Anfangsbestand 9": {
"is_group": 1, "is_group": 1,
"root_type": "Equity", "root_type": "Equity",
"Saldenvortragskonten": { "Saldenvortragskonten": {
"is_group": 1, "is_group": 1,
"Saldenvortrag Sachkonten": { "Saldenvortrag Sachkonten": {
"account_number": "9000" "account_number": "9000"
}, },
"Saldenvorträge Debitoren": { "Saldenvortr\u00e4ge Debitoren": {
"account_number": "9008" "account_number": "9008"
}, },
"Saldenvorträge Kreditoren": { "Saldenvortr\u00e4ge Kreditoren": {
"account_number": "9009" "account_number": "9009"
} }
} }
}, },
"Privatkonten 1": { "Privatkonten 1": {
"is_group": 1, "is_group": 1,
"root_type": "Equity", "root_type": "Equity",
"Privatentnahmen/-einlagen": { "Privatentnahmen/-einlagen": {
"is_group": 1, "is_group": 1,
"Privatentnahme allgemein": { "Privatentnahme allgemein": {
"account_number": "1800" "account_number": "1800"
}, },
"Privatsteuern": { "Privatsteuern": {
"account_number": "1810" "account_number": "1810"
}, },
"Sonderausgaben beschränkt abzugsfähig": { "Sonderausgaben beschr\u00e4nkt abzugsf\u00e4hig": {
"account_number": "1820" "account_number": "1820"
}, },
"Sonderausgaben unbeschränkt abzugsfähig": { "Sonderausgaben unbeschr\u00e4nkt abzugsf\u00e4hig": {
"account_number": "1830" "account_number": "1830"
}, },
"Außergewöhnliche Belastungen": { "Au\u00dfergew\u00f6hnliche Belastungen": {
"account_number": "1850" "account_number": "1850"
}, },
"Privateinlagen": { "Privateinlagen": {
"account_number": "1890" "account_number": "1890"
} }
} }
} }
} }
} }

View File

@@ -407,10 +407,13 @@
"Bewertungskorrektur zu Forderungen aus Lieferungen und Leistungen": { "Bewertungskorrektur zu Forderungen aus Lieferungen und Leistungen": {
"account_number": "9960" "account_number": "9960"
}, },
"Forderungen aus Lieferungen und Leistungen mit Kontokorrent": { "Debitoren": {
"is_group": 1,
"account_number": "10000"
},
"Forderungen aus Lieferungen und Leistungen": {
"account_number": "1200", "account_number": "1200",
"account_type": "Receivable", "account_type": "Receivable"
"is_group": 1
}, },
"Forderungen aus Lieferungen und Leistungen ohne Kontokorrent": { "Forderungen aus Lieferungen und Leistungen ohne Kontokorrent": {
"account_number": "1210" "account_number": "1210"
@@ -1135,15 +1138,18 @@
"Bewertungskorrektur zu Verb. aus Lieferungen und Leistungen": { "Bewertungskorrektur zu Verb. aus Lieferungen und Leistungen": {
"account_number": "9964" "account_number": "9964"
}, },
"Verb. aus Lieferungen und Leistungen mit Kontokorrent": { "Kreditoren": {
"account_number": "3300", "account_number": "70000",
"account_type": "Payable",
"is_group": 1, "is_group": 1,
"Wareneingangs-Verrechnungskonto" : { "Wareneingangs-­Verrechnungskonto" : {
"account_number": "70001", "account_number": "70001",
"account_type": "Stock Received But Not Billed" "account_type": "Stock Received But Not Billed"
} }
}, },
"Verb. aus Lieferungen und Leistungen": {
"account_number": "3300",
"account_type": "Payable"
},
"Verb. aus Lieferungen und Leistungen ohne Kontokorrent": { "Verb. aus Lieferungen und Leistungen ohne Kontokorrent": {
"account_number": "3310" "account_number": "3310"
}, },

View File

@@ -69,7 +69,8 @@
"Persediaan Barang": { "Persediaan Barang": {
"Persediaan Barang": { "Persediaan Barang": {
"account_number": "1141.000", "account_number": "1141.000",
"account_type": "Stock" "account_type": "Stock",
"is_group": 1
}, },
"Uang Muka Pembelian": { "Uang Muka Pembelian": {
"Uang Muka Pembelian": { "Uang Muka Pembelian": {
@@ -669,8 +670,7 @@
}, },
"Penjualan Barang Dagangan": { "Penjualan Barang Dagangan": {
"Penjualan": { "Penjualan": {
"account_number": "4110.000", "account_number": "4110.000"
"account_type": "Income Account"
}, },
"Potongan Penjualan": { "Potongan Penjualan": {
"account_number": "4130.000" "account_number": "4130.000"

View File

@@ -109,7 +109,8 @@
} }
}, },
"INVENTARIOS": { "INVENTARIOS": {
"account_type": "Stock" "account_type": "Stock",
"is_group": 1
} }
}, },
"ACTIVO LARGO PLAZO": { "ACTIVO LARGO PLAZO": {
@@ -397,18 +398,10 @@
"INGRESOS POR SERVICIOS 1": {} "INGRESOS POR SERVICIOS 1": {}
}, },
"VENTAS": { "VENTAS": {
"VENTAS EXPORTACION": { "VENTAS EXPORTACION": {},
"account_type": "Income Account" "VENTAS INMUEBLES": {},
}, "VENTAS NACIONALES": {},
"VENTAS INMUEBLES": { "VENTAS NACIONALES AL DETAL": {}
"account_type": "Income Account"
},
"VENTAS NACIONALES": {
"account_type": "Income Account"
},
"VENTAS NACIONALES AL DETAL": {
"account_type": "Income Account"
}
} }
} }
}, },

View File

@@ -2,13 +2,75 @@
"country_code": "nl", "country_code": "nl",
"name": "Netherlands - Grootboekschema", "name": "Netherlands - Grootboekschema",
"tree": { "tree": {
"FABRIKAGEREKENINGEN": {
"is_group": 1,
"root_type": "Expense"
},
"FINANCIELE REKENINGEN, KORTLOPENDE VORDERINGEN EN SCHULDEN": { "FINANCIELE REKENINGEN, KORTLOPENDE VORDERINGEN EN SCHULDEN": {
"Bank": { "Bank": {
"RABO Bank": { "RABO Bank": {
"account_type": "Bank" "account_type": "Bank"
}, },
"account_type": "Bank" "account_type": "Bank"
}, },
"KORTLOPENDE SCHULDEN": {
"Af te dragen Btw-verlegd": {
"account_type": "Tax"
},
"Afdracht loonheffing": {},
"Btw af te dragen hoog": {
"account_type": "Tax"
},
"Btw af te dragen laag": {
"account_type": "Tax"
},
"Btw af te dragen overig": {
"account_type": "Tax"
},
"Btw oude jaren": {
"account_type": "Tax"
},
"Btw te vorderen hoog": {
"account_type": "Tax"
},
"Btw te vorderen laag": {
"account_type": "Tax"
},
"Btw te vorderen overig": {
"account_type": "Tax"
},
"Btw-afdracht": {
"account_type": "Tax"
},
"Crediteuren": {
"account_type": "Payable"
},
"Dividend": {},
"Dividendbelasting": {},
"Energiekosten 1": {},
"Investeringsaftrek": {},
"Loonheffing": {},
"Overige te betalen posten": {},
"Pensioenpremies 1": {},
"Premie WIR": {},
"Rekening-courant inkoopvereniging": {},
"Rente": {},
"Sociale lasten 1": {},
"Stock Recieved niet gefactureerd": {
"account_type": "Stock Received But Not Billed"
},
"Tanti\u00e8mes 1": {},
"Te vorderen Btw-verlegd": {
"account_type": "Tax"
},
"Telefoon/telefax 1": {},
"Termijnen onderh. werk": {},
"Vakantiedagen": {},
"Vakantiegeld 1": {},
"Vakantiezegels": {},
"Vennootschapsbelasting": {},
"Vooruit ontvangen bedr.": {}
},
"LIQUIDE MIDDELEN": { "LIQUIDE MIDDELEN": {
"ABN-AMRO bank": {}, "ABN-AMRO bank": {},
"Bankbetaalkaarten": {}, "Bankbetaalkaarten": {},
@@ -29,110 +91,6 @@
}, },
"account_type": "Cash" "account_type": "Cash"
}, },
"TUSSENREKENINGEN": {
"Betaalwijze cadeaubonnen": {
"account_type": "Cash"
},
"Betaalwijze chipknip": {
"account_type": "Cash"
},
"Betaalwijze contant": {
"account_type": "Cash"
},
"Betaalwijze pin": {
"account_type": "Cash"
},
"Inkopen Nederland hoog": {
"account_type": "Cash"
},
"Inkopen Nederland laag": {
"account_type": "Cash"
},
"Inkopen Nederland onbelast": {
"account_type": "Cash"
},
"Inkopen Nederland overig": {
"account_type": "Cash"
},
"Inkopen Nederland verlegd": {
"account_type": "Cash"
},
"Inkopen binnen EU hoog": {
"account_type": "Cash"
},
"Inkopen binnen EU laag": {
"account_type": "Cash"
},
"Inkopen binnen EU overig": {
"account_type": "Cash"
},
"Inkopen buiten EU hoog": {
"account_type": "Cash"
},
"Inkopen buiten EU laag": {
"account_type": "Cash"
},
"Inkopen buiten EU overig": {
"account_type": "Cash"
},
"Kassa 1": {
"account_type": "Cash"
},
"Kassa 2": {
"account_type": "Cash"
},
"Netto lonen": {
"account_type": "Cash"
},
"Tegenrekening Inkopen": {
"account_type": "Cash"
},
"Tussenrek. autom. betalingen": {
"account_type": "Cash"
},
"Tussenrek. autom. loonbetalingen": {
"account_type": "Cash"
},
"Tussenrek. cadeaubonbetalingen": {
"account_type": "Cash"
},
"Tussenrekening balans": {
"account_type": "Cash"
},
"Tussenrekening chipknip": {
"account_type": "Cash"
},
"Tussenrekening correcties": {
"account_type": "Cash"
},
"Tussenrekening pin": {
"account_type": "Cash"
},
"Vraagposten": {
"account_type": "Cash"
},
"VOORRAAD GRONDSTOFFEN, HULPMATERIALEN EN HANDELSGOEDEREN": {
"Emballage": {},
"Gereed product 1": {},
"Gereed product 2": {},
"Goederen 1": {},
"Goederen 2": {},
"Goederen in consignatie": {},
"Goederen onderweg": {},
"Grondstoffen 1": {},
"Grondstoffen 2": {},
"Halffabrikaten 1": {},
"Halffabrikaten 2": {},
"Hulpstoffen 1": {},
"Hulpstoffen 2": {},
"Kantoorbenodigdheden": {},
"Onderhanden werk": {},
"Verpakkingsmateriaal": {},
"Zegels": {},
"root_type": "Asset"
},
"root_type": "Asset"
},
"VORDERINGEN": { "VORDERINGEN": {
"Debiteuren": { "Debiteuren": {
"account_type": "Receivable" "account_type": "Receivable"
@@ -146,299 +104,278 @@
"Voorziening dubieuze debiteuren": {} "Voorziening dubieuze debiteuren": {}
}, },
"root_type": "Asset" "root_type": "Asset"
}, },
"KORTLOPENDE SCHULDEN": { "INDIRECTE KOSTEN": {
"Af te dragen Btw-verlegd": {
"account_type": "Tax"
},
"Afdracht loonheffing": {},
"Btw af te dragen hoog": {
"account_type": "Tax"
},
"Btw af te dragen laag": {
"account_type": "Tax"
},
"Btw af te dragen overig": {
"account_type": "Tax"
},
"Btw oude jaren": {
"account_type": "Tax"
},
"Btw te vorderen hoog": {
"account_type": "Tax"
},
"Btw te vorderen laag": {
"account_type": "Tax"
},
"Btw te vorderen overig": {
"account_type": "Tax"
},
"Btw-afdracht": {
"account_type": "Tax"
},
"Crediteuren": {
"account_type": "Payable"
},
"Dividend": {},
"Dividendbelasting": {},
"Energiekosten 1": {},
"Investeringsaftrek": {},
"Loonheffing": {},
"Overige te betalen posten": {},
"Pensioenpremies 1": {},
"Premie WIR": {},
"Rekening-courant inkoopvereniging": {},
"Rente": {},
"Sociale lasten 1": {},
"Stock Recieved niet gefactureerd": {
"account_type": "Stock Received But Not Billed"
},
"Tanti\u00e8mes 1": {},
"Te vorderen Btw-verlegd": {
"account_type": "Tax"
},
"Telefoon/telefax 1": {},
"Termijnen onderh. werk": {},
"Vakantiedagen": {},
"Vakantiegeld 1": {},
"Vakantiezegels": {},
"Vennootschapsbelasting": {},
"Vooruit ontvangen bedr.": {},
"is_group": 1,
"root_type": "Liability"
},
"FABRIKAGEREKENINGEN": {
"is_group": 1, "is_group": 1,
"root_type": "Expense", "root_type": "Expense"
"INDIRECTE KOSTEN": { },
"is_group": 1, "KOSTENREKENINGEN": {
"root_type": "Expense" "AFSCHRIJVINGEN": {
}, "Aanhangwagens": {},
"KOSTENREKENINGEN": { "Aankoopkosten": {},
"AFSCHRIJVINGEN": { "Aanloopkosten": {},
"Aanhangwagens": {}, "Auteursrechten": {},
"Aankoopkosten": {}, "Bedrijfsgebouwen": {},
"Aanloopkosten": {}, "Bedrijfsinventaris": {
"Auteursrechten": {},
"Bedrijfsgebouwen": {},
"Bedrijfsinventaris": {
"account_type": "Depreciation"
},
"Drankvergunningen": {},
"Fabrieksinventaris": {
"account_type": "Depreciation"
},
"Gebouwen": {},
"Gereedschappen": {},
"Goodwill": {},
"Grondverbetering": {},
"Heftrucks": {},
"Kantine-inventaris": {},
"Kantoorinventaris": {
"account_type": "Depreciation"
},
"Kantoormachines": {},
"Licenties": {},
"Machines 1": {},
"Magazijninventaris": {},
"Octrooien": {},
"Ontwikkelingskosten": {},
"Pachtersinvestering": {},
"Parkeerplaats": {},
"Personenauto's": {
"account_type": "Depreciation"
},
"Rijwielen en bromfietsen": {},
"Tonnagevergunningen": {},
"Verbouwingen": {},
"Vergunningen": {},
"Voorraadverschillen": {},
"Vrachtauto's": {},
"Winkels": {},
"Woon-winkelhuis": {},
"account_type": "Depreciation" "account_type": "Depreciation"
}, },
"ALGEMENE KOSTEN": { "Drankvergunningen": {},
"Accountantskosten": {}, "Fabrieksinventaris": {
"Advieskosten": {}, "account_type": "Depreciation"
"Assuranties 1": {},
"Bankkosten": {},
"Juridische kosten": {},
"Overige algemene kosten": {},
"Toev. Ass. eigen risico": {}
}, },
"BEDRIJFSKOSTEN": { "Gebouwen": {},
"Assuranties 2": {}, "Gereedschappen": {},
"Energie (krachtstroom)": {}, "Goodwill": {},
"Gereedschappen 1": {}, "Grondverbetering": {},
"Hulpmaterialen 1": {}, "Heftrucks": {},
"Huur inventaris": {}, "Kantine-inventaris": {},
"Huur machines": {}, "Kantoorinventaris": {
"Leasing invent.operational": {}, "account_type": "Depreciation"
"Leasing mach. operational": {},
"Onderhoud inventaris": {},
"Onderhoud machines": {},
"Ophalen/vervoer afval": {},
"Overige bedrijfskosten": {}
}, },
"FINANCIERINGSKOSTEN 1": { "Kantoormachines": {},
"Overige rentebaten": {}, "Licenties": {},
"Overige rentelasten": {}, "Machines 1": {},
"Rente bankkrediet": {}, "Magazijninventaris": {},
"Rente huurkoopcontracten": {}, "Octrooien": {},
"Rente hypotheek": {}, "Ontwikkelingskosten": {},
"Rente leasecontracten": {}, "Pachtersinvestering": {},
"Rente lening o/g": {}, "Parkeerplaats": {},
"Rente lening u/g": {} "Personenauto's": {
"account_type": "Depreciation"
}, },
"HUISVESTINGSKOSTEN": { "Rijwielen en bromfietsen": {},
"Assurantie onroerend goed": {}, "Tonnagevergunningen": {},
"Belastingen onr. Goed": {}, "Verbouwingen": {},
"Energiekosten": {}, "Vergunningen": {},
"Groot onderhoud onr. Goed": {}, "Voorraadverschillen": {},
"Huur": {}, "Vrachtauto's": {},
"Huurwaarde woongedeelte": {}, "Winkels": {},
"Onderhoud onroerend goed": {}, "Woon-winkelhuis": {},
"Ontvangen huren": {}, "account_type": "Depreciation"
"Overige huisvestingskosten": {}, },
"Pacht": {}, "ALGEMENE KOSTEN": {
"Schoonmaakkosten": {}, "Accountantskosten": {},
"Toevoeging egalisatieres. Groot onderhoud": {} "Advieskosten": {},
}, "Assuranties 1": {},
"KANTOORKOSTEN": { "Bankkosten": {},
"Administratiekosten": {}, "Juridische kosten": {},
"Contributies/abonnementen": {}, "Overige algemene kosten": {},
"Huur kantoorapparatuur": {}, "Toev. Ass. eigen risico": {}
"Internetaansluiting": {}, },
"Kantoorbenodigdh./drukw.": {}, "BEDRIJFSKOSTEN": {
"Onderhoud kantoorinvent.": {}, "Assuranties 2": {},
"Overige kantoorkosten": {}, "Energie (krachtstroom)": {},
"Porti": {}, "Gereedschappen 1": {},
"Telefoon/telefax": {} "Hulpmaterialen 1": {},
}, "Huur inventaris": {},
"OVERIGE BATEN EN LASTEN": { "Huur machines": {},
"Betaalde schadevergoed.": {}, "Leasing invent.operational": {},
"Boekverlies vaste activa": {}, "Leasing mach. operational": {},
"Boekwinst van vaste activa": {}, "Onderhoud inventaris": {},
"K.O. regeling OB": {}, "Onderhoud machines": {},
"Kasverschillen": {}, "Ophalen/vervoer afval": {},
"Kosten loonbelasting": {}, "Overige bedrijfskosten": {}
"Kosten omzetbelasting": {}, },
"Nadelige koersverschillen": {}, "FINANCIERINGSKOSTEN 1": {
"Naheffing bedrijfsver.": {}, "Overige rentebaten": {},
"Ontvangen schadevergoed.": {}, "Overige rentelasten": {},
"Overige baten": {}, "Rente bankkrediet": {},
"Overige lasten": {}, "Rente huurkoopcontracten": {},
"Voordelige koersverschil.": {} "Rente hypotheek": {},
}, "Rente leasecontracten": {},
"PERSONEELSKOSTEN": { "Rente lening o/g": {},
"Autokostenvergoeding": {}, "Rente lening u/g": {}
"Bedrijfskleding": {}, },
"Belastingvrije uitkeringen": {}, "HUISVESTINGSKOSTEN": {
"Bijzondere beloningen": {}, "Assurantie onroerend goed": {},
"Congressen, seminars en symposia": {}, "Belastingen onr. Goed": {},
"Gereedschapsgeld": {}, "Energiekosten": {},
"Geschenken personeel": {}, "Groot onderhoud onr. Goed": {},
"Gratificaties": {}, "Huur": {},
"Inhouding pensioenpremies": {}, "Huurwaarde woongedeelte": {},
"Inhouding sociale lasten": {}, "Onderhoud onroerend goed": {},
"Kantinekosten": {}, "Ontvangen huren": {},
"Lonen en salarissen": {}, "Overige huisvestingskosten": {},
"Loonwerk": {}, "Pacht": {},
"Managementvergoedingen": {}, "Schoonmaakkosten": {},
"Opleidingskosten": {}, "Toevoeging egalisatieres. Groot onderhoud": {}
"Oprenting stamrechtverpl.": {}, },
"Overhevelingstoeslag": {}, "KANTOORKOSTEN": {
"Overige kostenverg.": {}, "Administratiekosten": {},
"Overige personeelskosten": {}, "Contributies/abonnementen": {},
"Overige uitkeringen": {}, "Huur kantoorapparatuur": {},
"Pensioenpremies": {}, "Internetaansluiting": {},
"Provisie 1": {}, "Kantoorbenodigdh./drukw.": {},
"Reiskosten": {}, "Onderhoud kantoorinvent.": {},
"Rijwielvergoeding": {}, "Overige kantoorkosten": {},
"Sociale lasten": {}, "Porti": {},
"Tanti\u00e8mes": {}, "Telefoon/telefax": {}
"Thuiswerkers": {}, },
"Toev. Backservice pens.verpl.": {}, "OVERIGE BATEN EN LASTEN": {
"Toevoeging pensioenverpl.": {}, "Betaalde schadevergoed.": {},
"Uitkering ziekengeld": {}, "Boekverlies vaste activa": {},
"Uitzendkrachten": {}, "Boekwinst van vaste activa": {},
"Vakantiebonnen": {}, "K.O. regeling OB": {},
"Vakantiegeld": {}, "Kasverschillen": {},
"Vergoeding studiekosten": {}, "Kosten loonbelasting": {},
"Wervingskosten personeel": {} "Kosten omzetbelasting": {},
}, "Nadelige koersverschillen": {},
"VERKOOPKOSTEN": { "Naheffing bedrijfsver.": {},
"Advertenties": {}, "Ontvangen schadevergoed.": {},
"Afschrijving dubieuze deb.": {}, "Overige baten": {},
"Beurskosten": {}, "Overige lasten": {},
"Etalagekosten": {}, "Voordelige koersverschil.": {}
"Exportkosten": {}, },
"Kascorrecties": {}, "PERSONEELSKOSTEN": {
"Overige verkoopkosten": {}, "Autokostenvergoeding": {},
"Provisie": {}, "Bedrijfskleding": {},
"Reclame": {}, "Belastingvrije uitkeringen": {},
"Reis en verblijfkosten": {}, "Bijzondere beloningen": {},
"Relatiegeschenken": {}, "Congressen, seminars en symposia": {},
"Representatiekosten": {}, "Gereedschapsgeld": {},
"Uitgaande vrachten": {}, "Geschenken personeel": {},
"Veilingkosten": {}, "Gratificaties": {},
"Verpakkingsmateriaal 1": {}, "Inhouding pensioenpremies": {},
"Websitekosten": {} "Inhouding sociale lasten": {},
}, "Kantinekosten": {},
"VERVOERSKOSTEN": { "Lonen en salarissen": {},
"Assuranties auto's": {}, "Loonwerk": {},
"Brandstoffen": {}, "Managementvergoedingen": {},
"Leasing auto's": {}, "Opleidingskosten": {},
"Onderhoud personenauto's": {}, "Oprenting stamrechtverpl.": {},
"Onderhoud vrachtauto's": {}, "Overhevelingstoeslag": {},
"Overige vervoerskosten": {}, "Overige kostenverg.": {},
"Priv\u00e9-gebruik auto's": {}, "Overige personeelskosten": {},
"Wegenbelasting": {} "Overige uitkeringen": {},
}, "Pensioenpremies": {},
"VOORRAAD GEREED PRODUCT EN ONDERHANDEN WERK": { "Provisie 1": {},
"Betalingskort. crediteuren": {}, "Reiskosten": {},
"Garantiekosten": {}, "Rijwielvergoeding": {},
"Hulpmaterialen": {}, "Sociale lasten": {},
"Inkomende vrachten": { "Tanti\u00e8mes": {},
"account_type": "Expenses Included In Valuation" "Thuiswerkers": {},
}, "Toev. Backservice pens.verpl.": {},
"Inkoop import buiten EU hoog": {}, "Toevoeging pensioenverpl.": {},
"Inkoop import buiten EU laag": {}, "Uitkering ziekengeld": {},
"Inkoop import buiten EU overig": {}, "Uitzendkrachten": {},
"Inkoopbonussen": {}, "Vakantiebonnen": {},
"Inkoopkosten": {}, "Vakantiegeld": {},
"Inkoopprovisie": {}, "Vergoeding studiekosten": {},
"Inkopen BTW verlegd": {}, "Wervingskosten personeel": {}
"Inkopen EU hoog tarief": {}, },
"Inkopen EU laag tarief": {}, "VERKOOPKOSTEN": {
"Inkopen EU overig": {}, "Advertenties": {},
"Inkopen hoog": {}, "Afschrijving dubieuze deb.": {},
"Inkopen laag": {}, "Beurskosten": {},
"Inkopen nul": {}, "Etalagekosten": {},
"Inkopen overig": {}, "Exportkosten": {},
"Invoerkosten": {}, "Kascorrecties": {},
"Kosten inkoopvereniging": {}, "Overige verkoopkosten": {},
"Kostprijs omzet grondstoffen": { "Provisie": {},
"account_type": "Cost of Goods Sold" "Reclame": {},
}, "Reis en verblijfkosten": {},
"Kostprijs omzet handelsgoederen": {}, "Relatiegeschenken": {},
"Onttrekking uitgev.garantie": {}, "Representatiekosten": {},
"Priv\u00e9-gebruik goederen": {}, "Uitgaande vrachten": {},
"Stock aanpassing": { "Veilingkosten": {},
"account_type": "Stock Adjustment" "Verpakkingsmateriaal 1": {},
}, "Websitekosten": {}
"Tegenrekening inkoop": {}, },
"Toev. Voorz. incour. grondst.": {}, "VERVOERSKOSTEN": {
"Toevoeging garantieverpl.": {}, "Assuranties auto's": {},
"Toevoeging voorz. incour. handelsgoed.": {}, "Brandstoffen": {},
"Uitbesteed werk": {}, "Leasing auto's": {},
"Voorz. Incourourant grondst.": {}, "Onderhoud personenauto's": {},
"Voorz.incour. handelsgoed.": {}, "Onderhoud vrachtauto's": {},
"root_type": "Expense" "Overige vervoerskosten": {},
}, "Priv\u00e9-gebruik auto's": {},
"root_type": "Expense" "Wegenbelasting": {}
} },
"root_type": "Expense"
},
"TUSSENREKENINGEN": {
"Betaalwijze cadeaubonnen": {
"account_type": "Cash"
},
"Betaalwijze chipknip": {
"account_type": "Cash"
},
"Betaalwijze contant": {
"account_type": "Cash"
},
"Betaalwijze pin": {
"account_type": "Cash"
},
"Inkopen Nederland hoog": {
"account_type": "Cash"
},
"Inkopen Nederland laag": {
"account_type": "Cash"
},
"Inkopen Nederland onbelast": {
"account_type": "Cash"
},
"Inkopen Nederland overig": {
"account_type": "Cash"
},
"Inkopen Nederland verlegd": {
"account_type": "Cash"
},
"Inkopen binnen EU hoog": {
"account_type": "Cash"
},
"Inkopen binnen EU laag": {
"account_type": "Cash"
},
"Inkopen binnen EU overig": {
"account_type": "Cash"
},
"Inkopen buiten EU hoog": {
"account_type": "Cash"
},
"Inkopen buiten EU laag": {
"account_type": "Cash"
},
"Inkopen buiten EU overig": {
"account_type": "Cash"
},
"Kassa 1": {
"account_type": "Cash"
},
"Kassa 2": {
"account_type": "Cash"
},
"Netto lonen": {
"account_type": "Cash"
},
"Tegenrekening Inkopen": {
"account_type": "Cash"
},
"Tussenrek. autom. betalingen": {
"account_type": "Cash"
},
"Tussenrek. autom. loonbetalingen": {
"account_type": "Cash"
},
"Tussenrek. cadeaubonbetalingen": {
"account_type": "Cash"
},
"Tussenrekening balans": {
"account_type": "Cash"
},
"Tussenrekening chipknip": {
"account_type": "Cash"
},
"Tussenrekening correcties": {
"account_type": "Cash"
},
"Tussenrekening pin": {
"account_type": "Cash"
},
"Vraagposten": {
"account_type": "Cash"
},
"root_type": "Asset"
}, },
"VASTE ACTIVA, EIGEN VERMOGEN, LANGLOPEND VREEMD VERMOGEN EN VOORZIENINGEN": { "VASTE ACTIVA, EIGEN VERMOGEN, LANGLOPEND VREEMD VERMOGEN EN VOORZIENINGEN": {
"EIGEN VERMOGEN": { "EIGEN VERMOGEN": {
@@ -665,7 +602,7 @@
"account_type": "Equity" "account_type": "Equity"
} }
}, },
"root_type": "Equity" "root_type": "Asset"
}, },
"VERKOOPRESULTATEN": { "VERKOOPRESULTATEN": {
"Diensten fabric. 0% niet-EU": {}, "Diensten fabric. 0% niet-EU": {},
@@ -690,6 +627,67 @@
"Verleende Kredietbep. fabricage": {}, "Verleende Kredietbep. fabricage": {},
"Verleende Kredietbep. handel": {}, "Verleende Kredietbep. handel": {},
"root_type": "Income" "root_type": "Income"
},
"VOORRAAD GEREED PRODUCT EN ONDERHANDEN WERK": {
"Betalingskort. crediteuren": {},
"Garantiekosten": {},
"Hulpmaterialen": {},
"Inkomende vrachten": {
"account_type": "Expenses Included In Valuation"
},
"Inkoop import buiten EU hoog": {},
"Inkoop import buiten EU laag": {},
"Inkoop import buiten EU overig": {},
"Inkoopbonussen": {},
"Inkoopkosten": {},
"Inkoopprovisie": {},
"Inkopen BTW verlegd": {},
"Inkopen EU hoog tarief": {},
"Inkopen EU laag tarief": {},
"Inkopen EU overig": {},
"Inkopen hoog": {},
"Inkopen laag": {},
"Inkopen nul": {},
"Inkopen overig": {},
"Invoerkosten": {},
"Kosten inkoopvereniging": {},
"Kostprijs omzet grondstoffen": {
"account_type": "Cost of Goods Sold"
},
"Kostprijs omzet handelsgoederen": {},
"Onttrekking uitgev.garantie": {},
"Priv\u00e9-gebruik goederen": {},
"Stock aanpassing": {
"account_type": "Stock Adjustment"
},
"Tegenrekening inkoop": {},
"Toev. Voorz. incour. grondst.": {},
"Toevoeging garantieverpl.": {},
"Toevoeging voorz. incour. handelsgoed.": {},
"Uitbesteed werk": {},
"Voorz. Incourourant grondst.": {},
"Voorz.incour. handelsgoed.": {},
"root_type": "Expense"
},
"VOORRAAD GRONDSTOFFEN, HULPMATERIALEN EN HANDELSGOEDEREN": {
"Emballage": {},
"Gereed product 1": {},
"Gereed product 2": {},
"Goederen 1": {},
"Goederen 2": {},
"Goederen in consignatie": {},
"Goederen onderweg": {},
"Grondstoffen 1": {},
"Grondstoffen 2": {},
"Halffabrikaten 1": {},
"Halffabrikaten 2": {},
"Hulpstoffen 1": {},
"Hulpstoffen 2": {},
"Kantoorbenodigdheden": {},
"Onderhanden werk": {},
"Verpakkingsmateriaal": {},
"Zegels": {},
"root_type": "Asset"
} }
} }
} }

View File

@@ -1,109 +1,187 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
from frappe import _ from frappe import _
def get(): def get():
return { return {
_("Application of Funds (Assets)"): { _("Application of Funds (Assets)"): {
_("Current Assets"): { _("Current Assets"): {
_("Accounts Receivable"): {_("Debtors"): {"account_type": "Receivable"}}, _("Accounts Receivable"): {
_("Bank Accounts"): {"account_type": "Bank", "is_group": 1}, _("Debtors"): {
_("Cash In Hand"): {_("Cash"): {"account_type": "Cash"}, "account_type": "Cash"}, "account_type": "Receivable"
_("Loans and Advances (Assets)"): { }
_("Employee Advances"): {}, },
_("Bank Accounts"): {
"account_type": "Bank",
"is_group": 1
},
_("Cash In Hand"): {
_("Cash"): {
"account_type": "Cash"
},
"account_type": "Cash"
},
_("Loans and Advances (Assets)"): {
_("Employee Advances"): {
},
},
_("Securities and Deposits"): {
_("Earnest Money"): {}
},
_("Stock Assets"): {
_("Stock In Hand"): {
"account_type": "Stock"
},
"account_type": "Stock",
},
_("Tax Assets"): {
"is_group": 1
}
},
_("Fixed Assets"): {
_("Capital Equipments"): {
"account_type": "Fixed Asset"
},
_("Electronic Equipments"): {
"account_type": "Fixed Asset"
},
_("Furnitures and Fixtures"): {
"account_type": "Fixed Asset"
},
_("Office Equipments"): {
"account_type": "Fixed Asset"
},
_("Plants and Machineries"): {
"account_type": "Fixed Asset"
},
_("Buildings"): {
"account_type": "Fixed Asset"
}, },
_("Securities and Deposits"): {_("Earnest Money"): {}}, _("Softwares"): {
_("Stock Assets"): { "account_type": "Fixed Asset"
_("Stock In Hand"): {"account_type": "Stock"},
"account_type": "Stock",
}, },
_("Tax Assets"): {"is_group": 1}, _("Accumulated Depreciation"): {
}, "account_type": "Accumulated Depreciation"
_("Fixed Assets"): { },
_("Capital Equipments"): {"account_type": "Fixed Asset"}, _("CWIP Account"): {
_("Electronic Equipments"): {"account_type": "Fixed Asset"}, "account_type": "Capital Work in Progress",
_("Furnitures and Fixtures"): {"account_type": "Fixed Asset"}, }
_("Office Equipments"): {"account_type": "Fixed Asset"}, },
_("Plants and Machineries"): {"account_type": "Fixed Asset"}, _("Investments"): {
_("Buildings"): {"account_type": "Fixed Asset"}, "is_group": 1
_("Softwares"): {"account_type": "Fixed Asset"}, },
_("Accumulated Depreciation"): {"account_type": "Accumulated Depreciation"}, _("Temporary Accounts"): {
_("CWIP Account"): { _("Temporary Opening"): {
"account_type": "Capital Work in Progress", "account_type": "Temporary"
}, }
}, },
_("Investments"): {"is_group": 1}, "root_type": "Asset"
_("Temporary Accounts"): {_("Temporary Opening"): {"account_type": "Temporary"}}, },
"root_type": "Asset", _("Expenses"): {
}, _("Direct Expenses"): {
_("Expenses"): { _("Stock Expenses"): {
_("Direct Expenses"): { _("Cost of Goods Sold"): {
_("Stock Expenses"): { "account_type": "Cost of Goods Sold"
_("Cost of Goods Sold"): {"account_type": "Cost of Goods Sold"}, },
_("Expenses Included In Asset Valuation"): { _("Expenses Included In Asset Valuation"): {
"account_type": "Expenses Included In Asset Valuation" "account_type": "Expenses Included In Asset Valuation"
}, },
_("Expenses Included In Valuation"): {"account_type": "Expenses Included In Valuation"}, _("Expenses Included In Valuation"): {
_("Stock Adjustment"): {"account_type": "Stock Adjustment"}, "account_type": "Expenses Included In Valuation"
}, },
}, _("Stock Adjustment"): {
_("Indirect Expenses"): { "account_type": "Stock Adjustment"
_("Administrative Expenses"): {}, }
_("Commission on Sales"): {}, },
_("Depreciation"): {"account_type": "Depreciation"}, },
_("Entertainment Expenses"): {}, _("Indirect Expenses"): {
_("Freight and Forwarding Charges"): {"account_type": "Chargeable"}, _("Administrative Expenses"): {},
_("Legal Expenses"): {}, _("Commission on Sales"): {},
_("Marketing Expenses"): {"account_type": "Chargeable"}, _("Depreciation"): {
_("Miscellaneous Expenses"): {"account_type": "Chargeable"}, "account_type": "Depreciation"
_("Office Maintenance Expenses"): {}, },
_("Office Rent"): {}, _("Entertainment Expenses"): {},
_("Postal Expenses"): {}, _("Freight and Forwarding Charges"): {
_("Print and Stationery"): {}, "account_type": "Chargeable"
_("Round Off"): {"account_type": "Round Off"}, },
_("Salary"): {}, _("Legal Expenses"): {},
_("Sales Expenses"): {}, _("Marketing Expenses"): {
_("Telephone Expenses"): {}, "account_type": "Chargeable"
_("Travel Expenses"): {}, },
_("Utility Expenses"): {}, _("Miscellaneous Expenses"): {
"account_type": "Chargeable"
},
_("Office Maintenance Expenses"): {},
_("Office Rent"): {},
_("Postal Expenses"): {},
_("Print and Stationery"): {},
_("Round Off"): {
"account_type": "Round Off"
},
_("Salary"): {},
_("Sales Expenses"): {},
_("Telephone Expenses"): {},
_("Travel Expenses"): {},
_("Utility Expenses"): {},
_("Write Off"): {}, _("Write Off"): {},
_("Exchange Gain/Loss"): {}, _("Exchange Gain/Loss"): {},
_("Gain/Loss on Asset Disposal"): {}, _("Gain/Loss on Asset Disposal"): {}
}, },
"root_type": "Expense", "root_type": "Expense"
}, },
_("Income"): { _("Income"): {
_("Direct Income"): {_("Sales"): {}, _("Service"): {}}, _("Direct Income"): {
_("Indirect Income"): {"is_group": 1}, _("Sales"): {},
"root_type": "Income", _("Service"): {}
}, },
_("Source of Funds (Liabilities)"): { _("Indirect Income"): {
_("Current Liabilities"): { "is_group": 1
_("Accounts Payable"): { },
_("Creditors"): {"account_type": "Payable"}, "root_type": "Income"
_("Payroll Payable"): {}, },
_("Source of Funds (Liabilities)"): {
_("Current Liabilities"): {
_("Accounts Payable"): {
_("Creditors"): {
"account_type": "Payable"
},
_("Payroll Payable"): {},
},
_("Stock Liabilities"): {
_("Stock Received But Not Billed"): {
"account_type": "Stock Received But Not Billed"
},
_("Asset Received But Not Billed"): {
"account_type": "Asset Received But Not Billed"
}
},
_("Duties and Taxes"): {
"account_type": "Tax",
"is_group": 1
}, },
_("Stock Liabilities"): {
_("Stock Received But Not Billed"): {"account_type": "Stock Received But Not Billed"},
_("Asset Received But Not Billed"): {"account_type": "Asset Received But Not Billed"},
},
_("Duties and Taxes"): {"account_type": "Tax", "is_group": 1},
_("Loans (Liabilities)"): { _("Loans (Liabilities)"): {
_("Secured Loans"): {}, _("Secured Loans"): {},
_("Unsecured Loans"): {}, _("Unsecured Loans"): {},
_("Bank Overdraft Account"): {}, _("Bank Overdraft Account"): {},
}, },
}, },
"root_type": "Liability", "root_type": "Liability"
}, },
_("Equity"): { _("Equity"): {
_("Capital Stock"): {"account_type": "Equity"}, _("Capital Stock"): {
_("Dividends Paid"): {"account_type": "Equity"}, "account_type": "Equity"
_("Opening Balance Equity"): {"account_type": "Equity"}, },
_("Retained Earnings"): {"account_type": "Equity"}, _("Dividends Paid"): {
"root_type": "Equity", "account_type": "Equity"
}, },
_("Opening Balance Equity"): {
"account_type": "Equity"
},
_("Retained Earnings"): {
"account_type": "Equity"
},
"root_type": "Equity"
}
} }

View File

@@ -1,158 +1,292 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
from frappe import _ from frappe import _
def get(): def get():
return { return {
_("Application of Funds (Assets)"): { _("Application of Funds (Assets)"): {
_("Current Assets"): { _("Current Assets"): {
_("Accounts Receivable"): { _("Accounts Receivable"): {
_("Debtors"): {"account_type": "Receivable", "account_number": "1310"}, _("Debtors"): {
"account_number": "1300", "account_type": "Receivable",
}, "account_number": "1310"
_("Bank Accounts"): {"account_type": "Bank", "is_group": 1, "account_number": "1200"}, },
_("Cash In Hand"): { "account_number": "1300"
_("Cash"): {"account_type": "Cash", "account_number": "1110"}, },
"account_type": "Cash", _("Bank Accounts"): {
"account_number": "1100", "account_type": "Bank",
}, "is_group": 1,
_("Loans and Advances (Assets)"): { "account_number": "1200"
_("Employee Advances"): {"account_number": "1610"}, },
"account_number": "1600", _("Cash In Hand"): {
}, _("Cash"): {
_("Securities and Deposits"): { "account_type": "Cash",
_("Earnest Money"): {"account_number": "1651"}, "account_number": "1110"
"account_number": "1650", },
}, "account_type": "Cash",
_("Stock Assets"): { "account_number": "1100"
_("Stock In Hand"): {"account_type": "Stock", "account_number": "1410"}, },
"account_type": "Stock", _("Loans and Advances (Assets)"): {
"account_number": "1400", _("Employee Advances"): {
}, "account_number": "1610"
_("Tax Assets"): {"is_group": 1, "account_number": "1500"}, },
"account_number": "1100-1600", "account_number": "1600"
}, },
_("Fixed Assets"): { _("Securities and Deposits"): {
_("Capital Equipments"): {"account_type": "Fixed Asset", "account_number": "1710"}, _("Earnest Money"): {
_("Electronic Equipments"): {"account_type": "Fixed Asset", "account_number": "1720"}, "account_number": "1651"
_("Furnitures and Fixtures"): {"account_type": "Fixed Asset", "account_number": "1730"}, },
_("Office Equipments"): {"account_type": "Fixed Asset", "account_number": "1740"}, "account_number": "1650"
_("Plants and Machineries"): {"account_type": "Fixed Asset", "account_number": "1750"}, },
_("Buildings"): {"account_type": "Fixed Asset", "account_number": "1760"}, _("Stock Assets"): {
_("Softwares"): {"account_type": "Fixed Asset", "account_number": "1770"}, _("Stock In Hand"): {
_("Accumulated Depreciation"): { "account_type": "Stock",
"account_type": "Accumulated Depreciation", "account_number": "1410"
"account_number": "1780", },
}, "account_type": "Stock",
_("CWIP Account"): {"account_type": "Capital Work in Progress", "account_number": "1790"}, "account_number": "1400"
"account_number": "1700", },
}, _("Tax Assets"): {
_("Investments"): {"is_group": 1, "account_number": "1800"}, "is_group": 1,
_("Temporary Accounts"): { "account_number": "1500"
_("Temporary Opening"): {"account_type": "Temporary", "account_number": "1910"}, },
"account_number": "1900", "account_number": "1100-1600"
}, },
"root_type": "Asset", _("Fixed Assets"): {
"account_number": "1000", _("Capital Equipments"): {
}, "account_type": "Fixed Asset",
_("Expenses"): { "account_number": "1710"
_("Direct Expenses"): { },
_("Stock Expenses"): { _("Electronic Equipments"): {
_("Cost of Goods Sold"): {"account_type": "Cost of Goods Sold", "account_number": "5111"}, "account_type": "Fixed Asset",
_("Expenses Included In Asset Valuation"): { "account_number": "1720"
"account_type": "Expenses Included In Asset Valuation", },
"account_number": "5112", _("Furnitures and Fixtures"): {
}, "account_type": "Fixed Asset",
_("Expenses Included In Valuation"): { "account_number": "1730"
"account_type": "Expenses Included In Valuation", },
"account_number": "5118", _("Office Equipments"): {
}, "account_type": "Fixed Asset",
_("Stock Adjustment"): {"account_type": "Stock Adjustment", "account_number": "5119"}, "account_number": "1740"
"account_number": "5110", },
}, _("Plants and Machineries"): {
"account_number": "5100", "account_type": "Fixed Asset",
}, "account_number": "1750"
_("Indirect Expenses"): { },
_("Administrative Expenses"): {"account_number": "5201"}, _("Buildings"): {
_("Commission on Sales"): {"account_number": "5202"}, "account_type": "Fixed Asset",
_("Depreciation"): {"account_type": "Depreciation", "account_number": "5203"}, "account_number": "1760"
_("Entertainment Expenses"): {"account_number": "5204"}, },
_("Freight and Forwarding Charges"): {"account_type": "Chargeable", "account_number": "5205"}, _("Softwares"): {
_("Legal Expenses"): {"account_number": "5206"}, "account_type": "Fixed Asset",
_("Marketing Expenses"): {"account_type": "Chargeable", "account_number": "5207"}, "account_number": "1770"
_("Office Maintenance Expenses"): {"account_number": "5208"}, },
_("Office Rent"): {"account_number": "5209"}, _("Accumulated Depreciation"): {
_("Postal Expenses"): {"account_number": "5210"}, "account_type": "Accumulated Depreciation",
_("Print and Stationery"): {"account_number": "5211"}, "account_number": "1780"
_("Round Off"): {"account_type": "Round Off", "account_number": "5212"}, },
_("Salary"): {"account_number": "5213"}, _("CWIP Account"): {
_("Sales Expenses"): {"account_number": "5214"}, "account_type": "Capital Work in Progress",
_("Telephone Expenses"): {"account_number": "5215"}, "account_number": "1790"
_("Travel Expenses"): {"account_number": "5216"}, },
_("Utility Expenses"): {"account_number": "5217"}, "account_number": "1700"
_("Write Off"): {"account_number": "5218"}, },
_("Exchange Gain/Loss"): {"account_number": "5219"}, _("Investments"): {
_("Gain/Loss on Asset Disposal"): {"account_number": "5220"}, "is_group": 1,
_("Miscellaneous Expenses"): {"account_type": "Chargeable", "account_number": "5221"}, "account_number": "1800"
"account_number": "5200", },
}, _("Temporary Accounts"): {
"root_type": "Expense", _("Temporary Opening"): {
"account_number": "5000", "account_type": "Temporary",
}, "account_number": "1910"
_("Income"): { },
_("Direct Income"): { "account_number": "1900"
_("Sales"): {"account_number": "4110"}, },
_("Service"): {"account_number": "4120"}, "root_type": "Asset",
"account_number": "4100", "account_number": "1000"
}, },
_("Indirect Income"): {"is_group": 1, "account_number": "4200"}, _("Expenses"): {
"root_type": "Income", _("Direct Expenses"): {
"account_number": "4000", _("Stock Expenses"): {
}, _("Cost of Goods Sold"): {
_("Source of Funds (Liabilities)"): { "account_type": "Cost of Goods Sold",
_("Current Liabilities"): { "account_number": "5111"
_("Accounts Payable"): { },
_("Creditors"): {"account_type": "Payable", "account_number": "2110"}, _("Expenses Included In Asset Valuation"): {
_("Payroll Payable"): {"account_number": "2120"}, "account_type": "Expenses Included In Asset Valuation",
"account_number": "2100", "account_number": "5112"
}, },
_("Stock Liabilities"): { _("Expenses Included In Valuation"): {
_("Stock Received But Not Billed"): { "account_type": "Expenses Included In Valuation",
"account_type": "Stock Received But Not Billed", "account_number": "5118"
"account_number": "2210", },
}, _("Stock Adjustment"): {
_("Asset Received But Not Billed"): { "account_type": "Stock Adjustment",
"account_type": "Asset Received But Not Billed", "account_number": "5119"
"account_number": "2211", },
}, "account_number": "5110"
"account_number": "2200", },
}, "account_number": "5100"
_("Duties and Taxes"): { },
_("TDS Payable"): {"account_number": "2310"}, _("Indirect Expenses"): {
"account_type": "Tax", _("Administrative Expenses"): {
"is_group": 1, "account_number": "5201"
"account_number": "2300", },
}, _("Commission on Sales"): {
_("Loans (Liabilities)"): { "account_number": "5202"
_("Secured Loans"): {"account_number": "2410"}, },
_("Unsecured Loans"): {"account_number": "2420"}, _("Depreciation"): {
_("Bank Overdraft Account"): {"account_number": "2430"}, "account_type": "Depreciation",
"account_number": "2400", "account_number": "5203"
}, },
"account_number": "2100-2400", _("Entertainment Expenses"): {
}, "account_number": "5204"
"root_type": "Liability", },
"account_number": "2000", _("Freight and Forwarding Charges"): {
}, "account_type": "Chargeable",
_("Equity"): { "account_number": "5205"
_("Capital Stock"): {"account_type": "Equity", "account_number": "3100"}, },
_("Dividends Paid"): {"account_type": "Equity", "account_number": "3200"}, _("Legal Expenses"): {
_("Opening Balance Equity"): {"account_type": "Equity", "account_number": "3300"}, "account_number": "5206"
_("Retained Earnings"): {"account_type": "Equity", "account_number": "3400"}, },
"root_type": "Equity", _("Marketing Expenses"): {
"account_number": "3000", "account_type": "Chargeable",
}, "account_number": "5207"
} },
_("Office Maintenance Expenses"): {
"account_number": "5208"
},
_("Office Rent"): {
"account_number": "5209"
},
_("Postal Expenses"): {
"account_number": "5210"
},
_("Print and Stationery"): {
"account_number": "5211"
},
_("Round Off"): {
"account_type": "Round Off",
"account_number": "5212"
},
_("Salary"): {
"account_number": "5213"
},
_("Sales Expenses"): {
"account_number": "5214"
},
_("Telephone Expenses"): {
"account_number": "5215"
},
_("Travel Expenses"): {
"account_number": "5216"
},
_("Utility Expenses"): {
"account_number": "5217"
},
_("Write Off"): {
"account_number": "5218"
},
_("Exchange Gain/Loss"): {
"account_number": "5219"
},
_("Gain/Loss on Asset Disposal"): {
"account_number": "5220"
},
_("Miscellaneous Expenses"): {
"account_type": "Chargeable",
"account_number": "5221"
},
"account_number": "5200"
},
"root_type": "Expense",
"account_number": "5000"
},
_("Income"): {
_("Direct Income"): {
_("Sales"): {
"account_number": "4110"
},
_("Service"): {
"account_number": "4120"
},
"account_number": "4100"
},
_("Indirect Income"): {
"is_group": 1,
"account_number": "4200"
},
"root_type": "Income",
"account_number": "4000"
},
_("Source of Funds (Liabilities)"): {
_("Current Liabilities"): {
_("Accounts Payable"): {
_("Creditors"): {
"account_type": "Payable",
"account_number": "2110"
},
_("Payroll Payable"): {
"account_number": "2120"
},
"account_number": "2100"
},
_("Stock Liabilities"): {
_("Stock Received But Not Billed"): {
"account_type": "Stock Received But Not Billed",
"account_number": "2210"
},
_("Asset Received But Not Billed"): {
"account_type": "Asset Received But Not Billed",
"account_number": "2211"
},
"account_number": "2200"
},
_("Duties and Taxes"): {
_("TDS Payable"): {
"account_number": "2310"
},
"account_type": "Tax",
"is_group": 1,
"account_number": "2300"
},
_("Loans (Liabilities)"): {
_("Secured Loans"): {
"account_number": "2410"
},
_("Unsecured Loans"): {
"account_number": "2420"
},
_("Bank Overdraft Account"): {
"account_number": "2430"
},
"account_number": "2400"
},
"account_number": "2100-2400"
},
"root_type": "Liability",
"account_number": "2000"
},
_("Equity"): {
_("Capital Stock"): {
"account_type": "Equity",
"account_number": "3100"
},
_("Dividends Paid"): {
"account_type": "Equity",
"account_number": "3200"
},
_("Opening Balance Equity"): {
"account_type": "Equity",
"account_number": "3300"
},
_("Retained Earnings"): {
"account_type": "Equity",
"account_number": "3400"
},
"root_type": "Equity",
"account_number": "3000"
}
}

View File

@@ -1,22 +1,11 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import unittest import unittest
import frappe import frappe
from frappe.test_runner import make_test_records from erpnext.stock import get_warehouse_account, get_company_default_inventory_account
from frappe.utils import nowdate from erpnext.accounts.doctype.account.account import update_account_number, merge_account
from erpnext.accounts.doctype.account.account import (
InvalidAccountMergeError,
merge_account,
update_account_number,
)
from erpnext.stock import get_company_default_inventory_account, get_warehouse_account
test_dependencies = ["Company"]
class TestAccount(unittest.TestCase): class TestAccount(unittest.TestCase):
def test_rename_account(self): def test_rename_account(self):
@@ -28,9 +17,8 @@ class TestAccount(unittest.TestCase):
acc.company = "_Test Company" acc.company = "_Test Company"
acc.insert() acc.insert()
account_number, account_name = frappe.db.get_value( account_number, account_name = frappe.db.get_value("Account", "1210 - Debtors - _TC",
"Account", "1210 - Debtors - _TC", ["account_number", "account_name"] ["account_number", "account_name"])
)
self.assertEqual(account_number, "1210") self.assertEqual(account_number, "1210")
self.assertEqual(account_name, "Debtors") self.assertEqual(account_name, "Debtors")
@@ -39,12 +27,8 @@ class TestAccount(unittest.TestCase):
update_account_number("1210 - Debtors - _TC", new_account_name, new_account_number) update_account_number("1210 - Debtors - _TC", new_account_name, new_account_number)
new_acc = frappe.db.get_value( new_acc = frappe.db.get_value("Account", "1211-11-4 - 6 - - Debtors 1 - Test - - _TC",
"Account", ["account_name", "account_number"], as_dict=1)
"1211-11-4 - 6 - - Debtors 1 - Test - - _TC",
["account_name", "account_number"],
as_dict=1,
)
self.assertEqual(new_acc.account_name, "Debtors 1 - Test -") self.assertEqual(new_acc.account_name, "Debtors 1 - Test -")
self.assertEqual(new_acc.account_number, "1211-11-4 - 6 -") self.assertEqual(new_acc.account_number, "1211-11-4 - 6 -")
@@ -52,53 +36,47 @@ class TestAccount(unittest.TestCase):
frappe.delete_doc("Account", "1211-11-4 - 6 - Debtors 1 - Test - - _TC") frappe.delete_doc("Account", "1211-11-4 - 6 - Debtors 1 - Test - - _TC")
def test_merge_account(self): def test_merge_account(self):
create_account( if not frappe.db.exists("Account", "Current Assets - _TC"):
account_name="Current Assets", acc = frappe.new_doc("Account")
is_group=1, acc.account_name = "Current Assets"
parent_account="Application of Funds (Assets) - _TC", acc.is_group = 1
company="_Test Company", acc.parent_account = "Application of Funds (Assets) - _TC"
) acc.company = "_Test Company"
acc.insert()
create_account( if not frappe.db.exists("Account", "Securities and Deposits - _TC"):
account_name="Securities and Deposits", acc = frappe.new_doc("Account")
is_group=1, acc.account_name = "Securities and Deposits"
parent_account="Current Assets - _TC", acc.parent_account = "Current Assets - _TC"
company="_Test Company", acc.is_group = 1
) acc.company = "_Test Company"
acc.insert()
create_account( if not frappe.db.exists("Account", "Earnest Money - _TC"):
account_name="Earnest Money", acc = frappe.new_doc("Account")
parent_account="Securities and Deposits - _TC", acc.account_name = "Earnest Money"
company="_Test Company", acc.parent_account = "Securities and Deposits - _TC"
) acc.company = "_Test Company"
acc.insert()
create_account( if not frappe.db.exists("Account", "Cash In Hand - _TC"):
account_name="Cash In Hand", acc = frappe.new_doc("Account")
is_group=1, acc.account_name = "Cash In Hand"
parent_account="Current Assets - _TC", acc.is_group = 1
company="_Test Company", acc.parent_account = "Current Assets - _TC"
) acc.company = "_Test Company"
acc.insert()
create_account( if not frappe.db.exists("Account", "Accumulated Depreciation - _TC"):
account_name="Receivable INR", acc = frappe.new_doc("Account")
parent_account="Current Assets - _TC", acc.account_name = "Accumulated Depreciation"
company="_Test Company", acc.parent_account = "Fixed Assets - _TC"
account_currency="INR", acc.company = "_Test Company"
) acc.account_type = "Accumulated Depreciation"
acc.insert()
create_account(
account_name="Receivable USD",
parent_account="Current Assets - _TC",
company="_Test Company",
account_currency="USD",
)
doc = frappe.get_doc("Account", "Securities and Deposits - _TC")
parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account") parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account")
self.assertEqual(parent, "Securities and Deposits - _TC") self.assertEqual(parent, "Securities and Deposits - _TC")
merge_account("Securities and Deposits - _TC", "Cash In Hand - _TC") merge_account("Securities and Deposits - _TC", "Cash In Hand - _TC", doc.is_group, doc.root_type, doc.company)
parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account") parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account")
# Parent account of the child account changes after merging # Parent account of the child account changes after merging
@@ -107,29 +85,17 @@ class TestAccount(unittest.TestCase):
# Old account doesn't exist after merging # Old account doesn't exist after merging
self.assertFalse(frappe.db.exists("Account", "Securities and Deposits - _TC")) self.assertFalse(frappe.db.exists("Account", "Securities and Deposits - _TC"))
doc = frappe.get_doc("Account", "Current Assets - _TC")
# Raise error as is_group property doesn't match # Raise error as is_group property doesn't match
self.assertRaises( self.assertRaises(frappe.ValidationError, merge_account, "Current Assets - _TC",\
InvalidAccountMergeError, "Accumulated Depreciation - _TC", doc.is_group, doc.root_type, doc.company)
merge_account,
"Current Assets - _TC", doc = frappe.get_doc("Account", "Capital Stock - _TC")
"Accumulated Depreciation - _TC",
)
# Raise error as root_type property doesn't match # Raise error as root_type property doesn't match
self.assertRaises( self.assertRaises(frappe.ValidationError, merge_account, "Capital Stock - _TC",\
InvalidAccountMergeError, "Softwares - _TC", doc.is_group, doc.root_type, doc.company)
merge_account,
"Capital Stock - _TC",
"Softwares - _TC",
)
# Raise error as currency doesn't match
self.assertRaises(
InvalidAccountMergeError,
merge_account,
"Receivable INR - _TC",
"Receivable USD - _TC",
)
def test_account_sync(self): def test_account_sync(self):
frappe.local.flags.pop("ignore_root_company_validation", None) frappe.local.flags.pop("ignore_root_company_validation", None)
@@ -140,12 +106,8 @@ class TestAccount(unittest.TestCase):
acc.company = "_Test Company 3" acc.company = "_Test Company 3"
acc.insert() acc.insert()
acc_tc_4 = frappe.db.get_value( acc_tc_4 = frappe.db.get_value('Account', {'account_name': "Test Sync Account", "company": "_Test Company 4"})
"Account", {"account_name": "Test Sync Account", "company": "_Test Company 4"} acc_tc_5 = frappe.db.get_value('Account', {'account_name': "Test Sync Account", "company": "_Test Company 5"})
)
acc_tc_5 = frappe.db.get_value(
"Account", {"account_name": "Test Sync Account", "company": "_Test Company 5"}
)
self.assertEqual(acc_tc_4, "Test Sync Account - _TC4") self.assertEqual(acc_tc_4, "Test Sync Account - _TC4")
self.assertEqual(acc_tc_5, "Test Sync Account - _TC5") self.assertEqual(acc_tc_5, "Test Sync Account - _TC5")
@@ -173,83 +135,13 @@ class TestAccount(unittest.TestCase):
update_account_number(acc.name, "Test Rename Sync Account", "1234") update_account_number(acc.name, "Test Rename Sync Account", "1234")
# Check if renamed in children # Check if renamed in children
self.assertTrue( self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Rename Sync Account", "company": "_Test Company 4", "account_number": "1234"}))
frappe.db.exists( self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Rename Sync Account", "company": "_Test Company 5", "account_number": "1234"}))
"Account",
{
"account_name": "Test Rename Sync Account",
"company": "_Test Company 4",
"account_number": "1234",
},
)
)
self.assertTrue(
frappe.db.exists(
"Account",
{
"account_name": "Test Rename Sync Account",
"company": "_Test Company 5",
"account_number": "1234",
},
)
)
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC3") frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC3")
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC4") frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC4")
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC5") frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC5")
def test_account_currency_sync(self):
"""
In a parent->child company setup, child should inherit parent account currency if explicitly specified.
"""
make_test_records("Company")
frappe.local.flags.pop("ignore_root_company_validation", None)
def create_bank_account():
acc = frappe.new_doc("Account")
acc.account_name = "_Test Bank JPY"
acc.parent_account = "Temporary Accounts - _TC6"
acc.company = "_Test Company 6"
return acc
acc = create_bank_account()
# Explicitly set currency
acc.account_currency = "JPY"
acc.insert()
self.assertTrue(
frappe.db.exists(
{
"doctype": "Account",
"account_name": "_Test Bank JPY",
"account_currency": "JPY",
"company": "_Test Company 7",
}
)
)
frappe.delete_doc("Account", "_Test Bank JPY - _TC6")
frappe.delete_doc("Account", "_Test Bank JPY - _TC7")
acc = create_bank_account()
# default currency is used
acc.insert()
self.assertTrue(
frappe.db.exists(
{
"doctype": "Account",
"account_name": "_Test Bank JPY",
"account_currency": "USD",
"company": "_Test Company 7",
}
)
)
frappe.delete_doc("Account", "_Test Bank JPY - _TC6")
frappe.delete_doc("Account", "_Test Bank JPY - _TC7")
def test_child_company_account_rename_sync(self): def test_child_company_account_rename_sync(self):
frappe.local.flags.pop("ignore_root_company_validation", None) frappe.local.flags.pop("ignore_root_company_validation", None)
@@ -260,84 +152,25 @@ class TestAccount(unittest.TestCase):
acc.company = "_Test Company 3" acc.company = "_Test Company 3"
acc.insert() acc.insert()
self.assertTrue( self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Group Account", "company": "_Test Company 4"}))
frappe.db.exists( self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Group Account", "company": "_Test Company 5"}))
"Account", {"account_name": "Test Group Account", "company": "_Test Company 4"}
)
)
self.assertTrue(
frappe.db.exists(
"Account", {"account_name": "Test Group Account", "company": "_Test Company 5"}
)
)
# Try renaming child company account # Try renaming child company account
acc_tc_5 = frappe.db.get_value( acc_tc_5 = frappe.db.get_value('Account', {'account_name': "Test Group Account", "company": "_Test Company 5"})
"Account", {"account_name": "Test Group Account", "company": "_Test Company 5"} self.assertRaises(frappe.ValidationError, update_account_number, acc_tc_5, "Test Modified Account")
)
self.assertRaises(
frappe.ValidationError, update_account_number, acc_tc_5, "Test Modified Account"
)
# Rename child company account with allow_account_creation_against_child_company enabled # Rename child company account with allow_account_creation_against_child_company enabled
frappe.db.set_value( frappe.db.set_value("Company", "_Test Company 5", "allow_account_creation_against_child_company", 1)
"Company", "_Test Company 5", "allow_account_creation_against_child_company", 1
)
update_account_number(acc_tc_5, "Test Modified Account") update_account_number(acc_tc_5, "Test Modified Account")
self.assertTrue( self.assertTrue(frappe.db.exists("Account", {'name': "Test Modified Account - _TC5", "company": "_Test Company 5"}))
frappe.db.exists(
"Account", {"name": "Test Modified Account - _TC5", "company": "_Test Company 5"}
)
)
frappe.db.set_value( frappe.db.set_value("Company", "_Test Company 5", "allow_account_creation_against_child_company", 0)
"Company", "_Test Company 5", "allow_account_creation_against_child_company", 0
)
to_delete = [ to_delete = ["Test Group Account - _TC3", "Test Group Account - _TC4", "Test Modified Account - _TC5"]
"Test Group Account - _TC3",
"Test Group Account - _TC4",
"Test Modified Account - _TC5",
]
for doc in to_delete: for doc in to_delete:
frappe.delete_doc("Account", doc) frappe.delete_doc("Account", doc)
def test_validate_account_currency(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
if not frappe.db.get_value("Account", "Test Currency Account - _TC"):
acc = frappe.new_doc("Account")
acc.account_name = "Test Currency Account"
acc.parent_account = "Tax Assets - _TC"
acc.company = "_Test Company"
acc.insert()
else:
acc = frappe.get_doc("Account", "Test Currency Account - _TC")
self.assertEqual(acc.account_currency, "INR")
# Make a JV against this account
make_journal_entry(
"Test Currency Account - _TC", "Miscellaneous Expenses - _TC", 100, submit=True
)
acc.account_currency = "USD"
self.assertRaises(frappe.ValidationError, acc.save)
def test_account_balance(self):
from erpnext.accounts.utils import get_balance_on
if not frappe.db.exists("Account", "Test Percent Account %5 - _TC"):
acc = frappe.new_doc("Account")
acc.account_name = "Test Percent Account %5"
acc.parent_account = "Tax Assets - _TC"
acc.company = "_Test Company"
acc.insert()
balance = get_balance_on(account="Test Percent Account %5 - _TC", date=nowdate())
self.assertEqual(balance, 0)
def _make_test_records(verbose=None): def _make_test_records(verbose=None):
from frappe.test_runner import make_test_objects from frappe.test_runner import make_test_objects
@@ -348,16 +181,20 @@ def _make_test_records(verbose=None):
["_Test Bank USD", "Bank Accounts", 0, "Bank", "USD"], ["_Test Bank USD", "Bank Accounts", 0, "Bank", "USD"],
["_Test Bank EUR", "Bank Accounts", 0, "Bank", "EUR"], ["_Test Bank EUR", "Bank Accounts", 0, "Bank", "EUR"],
["_Test Cash", "Cash In Hand", 0, "Cash", None], ["_Test Cash", "Cash In Hand", 0, "Cash", None],
["_Test Account Stock Expenses", "Direct Expenses", 1, None, None], ["_Test Account Stock Expenses", "Direct Expenses", 1, None, None],
["_Test Account Shipping Charges", "_Test Account Stock Expenses", 0, "Chargeable", 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 Customs Duty", "_Test Account Stock Expenses", 0, "Tax", None],
["_Test Account Insurance Charges", "_Test Account Stock Expenses", 0, "Chargeable", 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 Account Stock Adjustment", "_Test Account Stock Expenses", 0, "Stock Adjustment", None],
["_Test Employee Advance", "Current Liabilities", 0, None, None], ["_Test Employee Advance", "Current Liabilities", 0, None, None],
["_Test Account Tax Assets", "Current Assets", 1, None, None], ["_Test Account Tax Assets", "Current Assets", 1, None, None],
["_Test Account VAT", "_Test Account Tax Assets", 0, "Tax", None], ["_Test Account VAT", "_Test Account Tax Assets", 0, "Tax", None],
["_Test Account Service Tax", "_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 Reserves and Surplus", "Current Liabilities", 0, None, None],
["_Test Account Cost for Goods Sold", "Expenses", 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 Excise Duty", "_Test Account Tax Assets", 0, "Tax", None],
["_Test Account Education Cess", "_Test Account Tax Assets", 0, "Tax", None], ["_Test Account Education Cess", "_Test Account Tax Assets", 0, "Tax", None],
@@ -366,45 +203,38 @@ def _make_test_records(verbose=None):
["_Test Account Discount", "Direct Expenses", 0, None, None], ["_Test Account Discount", "Direct Expenses", 0, None, None],
["_Test Write Off", "Indirect Expenses", 0, None, None], ["_Test Write Off", "Indirect Expenses", 0, None, None],
["_Test Exchange Gain/Loss", "Indirect Expenses", 0, None, None], ["_Test Exchange Gain/Loss", "Indirect Expenses", 0, None, None],
["_Test Account Sales", "Direct Income", 0, None, None], ["_Test Account Sales", "Direct Income", 0, None, None],
# related to Account Inventory Integration # related to Account Inventory Integration
["_Test Account Stock In Hand", "Current Assets", 0, None, None], ["_Test Account Stock In Hand", "Current Assets", 0, None, None],
# fixed asset depreciation # fixed asset depreciation
["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None], ["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None],
["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None], ["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None],
["_Test Depreciations", "Expenses", 0, "Depreciation", None], ["_Test Depreciations", "Expenses", 0, None, None],
["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None], ["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None],
# Receivable / Payable Account # Receivable / Payable Account
["_Test Receivable", "Current Assets", 0, "Receivable", None], ["_Test Receivable", "Current Assets", 0, "Receivable", None],
["_Test Payable", "Current Liabilities", 0, "Payable", None], ["_Test Payable", "Current Liabilities", 0, "Payable", None],
["_Test Receivable USD", "Current Assets", 0, "Receivable", "USD"], ["_Test Receivable USD", "Current Assets", 0, "Receivable", "USD"],
["_Test Payable USD", "Current Liabilities", 0, "Payable", "USD"], ["_Test Payable USD", "Current Liabilities", 0, "Payable", "USD"]
] ]
for company, abbr in [ for company, abbr in [["_Test Company", "_TC"], ["_Test Company 1", "_TC1"], ["_Test Company with perpetual inventory", "TCP1"]]:
["_Test Company", "_TC"], test_objects = make_test_objects("Account", [{
["_Test Company 1", "_TC1"], "doctype": "Account",
["_Test Company with perpetual inventory", "TCP1"], "account_name": account_name,
]: "parent_account": parent_account + " - " + abbr,
test_objects = make_test_objects( "company": company,
"Account", "is_group": is_group,
[ "account_type": account_type,
{ "account_currency": currency
"doctype": "Account", } for account_name, parent_account, is_group, account_type, currency in accounts])
"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 return test_objects
def get_inventory_account(company, warehouse=None): def get_inventory_account(company, warehouse=None):
account = None account = None
if warehouse: if warehouse:
@@ -414,33 +244,19 @@ def get_inventory_account(company, warehouse=None):
return account return account
def create_account(**kwargs): def create_account(**kwargs):
account = frappe.db.get_value( account = frappe.db.get_value("Account", filters={"account_name": kwargs.get("account_name"), "company": kwargs.get("company")})
"Account", filters={"account_name": kwargs.get("account_name"), "company": kwargs.get("company")}
)
if account: if account:
account = frappe.get_doc("Account", account) return account
account.update(
dict(
is_group=kwargs.get("is_group", 0),
parent_account=kwargs.get("parent_account"),
)
)
account.save()
return account.name
else: else:
account = frappe.get_doc( account = frappe.get_doc(dict(
dict( doctype = "Account",
doctype="Account", account_name = kwargs.get('account_name'),
is_group=kwargs.get("is_group", 0), account_type = kwargs.get('account_type'),
account_name=kwargs.get("account_name"), parent_account = kwargs.get('parent_account'),
account_type=kwargs.get("account_type"), company = kwargs.get('company'),
parent_account=kwargs.get("parent_account"), account_currency = kwargs.get('account_currency')
company=kwargs.get("company"), ))
account_currency=kwargs.get("account_currency"),
)
)
account.save() account.save()
return account.name return account.name

Some files were not shown because too many files have changed in this diff Show More