Compare commits

..

3 Commits

Author SHA1 Message Date
Marica
5d2ad7fc38 fix: UOM was not fetching in purchase invoice (#19732) (#19737)
* fix: UOM was not fetching in purchase invoice

* fix: Changes requested

Co-authored-by: Marica <maricadsouza221197@gmail.com>
2019-11-28 20:00:51 +05:30
rohitwaghchaure
3347473aa1 fix: removed stock value and account balance out of sync validation (#19728) 2019-11-28 20:00:41 +05:30
Rohit Waghchaure
7f951b5595 fix: revert value out of sync feature 2019-11-28 20:00:33 +05:30
5858 changed files with 716735 additions and 1093794 deletions

View File

@@ -1,14 +0,0 @@
# Root editor config file
root = true
# Common settings
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
# python, js indentation settings
[{*.py,*.js}]
indent_style = tab
indent_size = 4

View File

@@ -5,7 +5,7 @@
"es6": true "es6": true
}, },
"parserOptions": { "parserOptions": {
"ecmaVersion": 9, "ecmaVersion": 6,
"sourceType": "module" "sourceType": "module"
}, },
"extends": "eslint:recommended", "extends": "eslint:recommended",
@@ -15,14 +15,6 @@
"tab", "tab",
{ "SwitchCase": 1 } { "SwitchCase": 1 }
], ],
"brace-style": [
"error",
"1tbs"
],
"space-unary-ops": [
"error",
{ "words": true }
],
"linebreak-style": [ "linebreak-style": [
"error", "error",
"unix" "unix"
@@ -52,10 +44,12 @@
"no-control-regex": [ "no-control-regex": [
"off" "off"
], ],
"space-before-blocks": "warn", "spaced-comment": [
"keyword-spacing": "warn", "warn"
"comma-spacing": "warn", ],
"key-spacing": "warn" "no-trailing-spaces": [
"warn"
]
}, },
"root": true, "root": true,
"globals": { "globals": {
@@ -92,7 +86,6 @@
"cur_page": true, "cur_page": true,
"cur_list": true, "cur_list": true,
"cur_tree": true, "cur_tree": true,
"cur_pos": true,
"msg_dialog": true, "msg_dialog": true,
"is_null": true, "is_null": true,
"in_list": true, "in_list": true,
@@ -147,15 +140,9 @@
"Chart": true, "Chart": true,
"Cypress": true, "Cypress": true,
"cy": true, "cy": true,
"describe": true,
"expect": true,
"it": true, "it": true,
"context": true, "context": true,
"before": true, "before": true,
"beforeEach": true, "beforeEach": true
"onScan": true,
"html2canvas": true,
"extend_cscript": true,
"localforage": true
} }
} }

37
.flake8
View File

@@ -1,37 +0,0 @@
[flake8]
ignore =
E121,
E126,
E127,
E128,
E203,
E225,
E226,
E231,
E241,
E251,
E261,
E265,
E302,
E303,
E305,
E402,
E501,
E741,
W291,
W292,
W293,
W391,
W503,
W504,
F403,
B007,
B950,
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
exclude=.github/helper/semgrep_rules

View File

@@ -1,22 +0,0 @@
# Since version 2.23 (released in August 2019), git-blame has a feature
# to ignore or bypass certain commits.
#
# This file contains a list of commits that are not likely what you
# are looking for in a blame, such as mass reformatting or renaming.
# You can set this file as a default ignore file for blame by running
# the following command.
#
# $ git config blame.ignoreRevsFile .git-blame-ignore-revs
# This commit just changes spaces to tabs for indentation in some files
5f473611bd6ed57703716244a054d3fb5ba9cd23
# Whitespace trimming throughout codebase
9bb69e711a5da43aaf8c8ecb5601aeffd89dbe5a
f0bcb753fb7ebbb64bb0d6906d431d002f0f7d8f
# imports cleanup
4b2be2999f2203493b49bf74c5b440d49e38b5e3
# formatting with black
c07713b860505211db2af685e2e950bf5dd7dd3a

View File

@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Community Forum
url: https://discuss.erpnext.com/
about: For general QnA, discussions and community help.

View File

@@ -8,7 +8,7 @@ Please post on our forums:
for questions about using `ERPNext`: https://discuss.erpnext.com 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 the `Frappe Framework`: https://discuss.frappe.io
for questions about using `bench`, probably the best place to start is the [bench repo](https://github.com/frappe/bench) for questions about using `bench`, probably the best place to start is the [bench repo](https://github.com/frappe/bench)

View File

@@ -1,73 +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
max-line-length = 200
exclude=.github/helper/semgrep_rules,test_*.py

View File

@@ -1,54 +0,0 @@
import sys
from urllib.parse import urlparse
import requests
docs_repos = [
"frappe_docs",
"erpnext_documentation",
"erpnext_com",
"frappe_io",
]
def uri_validator(x):
result = urlparse(x)
return all([result.scheme, result.netloc, result.path])
def docs_link_exists(body):
for line in body.splitlines():
for word in line.split():
if word.startswith('http') and uri_validator(word):
parsed_url = urlparse(word)
if parsed_url.netloc == "github.com":
parts = parsed_url.path.split('/')
if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos:
return True
elif parsed_url.netloc == "docs.erpnext.com":
return True
if __name__ == "__main__":
pr = sys.argv[1]
response = requests.get("https://api.github.com/repos/frappe/erpnext/pulls/{}".format(pr))
if response.ok:
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 (title.startswith("feat")
and head_sha
and "no-docs" not in body
and "backport" not in body
):
if docs_link_exists(body):
print("Documentation Link Found. You're Awesome! 🎉")
else:
print("Documentation Link Not Found! ⚠️")
sys.exit(1)
else:
print("Skipping documentation checks... 🏃")

View File

@@ -1,48 +0,0 @@
#!/bin/bash
set -e
# Check for merge conflicts before proceeding
python -m compileall -f "${GITHUB_WORKSPACE}"
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
then echo "Found merge conflicts"
exit 1
fi
cd ~ || exit
sudo apt update && sudo apt install redis-server libcups2-dev
pip install frappe-bench
git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" --depth 1
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
mkdir ~/frappe-bench/sites/test_site
cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config.json" ~/frappe-bench/sites/test_site/
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
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'"
mysql --host 127.0.0.1 --port 3306 -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'"
mysql --host 127.0.0.1 --port 3306 -u root -e "FLUSH PRIVILEGES"
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
cd ~/frappe-bench || exit
sed -i 's/watch:/# watch:/g' Procfile
sed -i 's/schedule:/# schedule:/g' Procfile
sed -i 's/socketio:/# socketio:/g' Procfile
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
bench get-app erpnext "${GITHUB_WORKSPACE}"
bench start &> bench_run_logs.txt &
bench --site test_site reinstall --yes

View File

@@ -1,63 +0,0 @@
import frappe
from frappe import _
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

@@ -1,151 +0,0 @@
# 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
- id: frappe-manual-commit
patterns:
- pattern: frappe.db.commit()
- pattern-not-inside: |
try:
...
except ...:
...
message: |
Manually commiting a transaction is highly discouraged. Read about the transaction model implemented by Frappe Framework before adding manual commits: https://frappeframework.com/docs/user/en/api/database#database-transaction-model If you think manual commit is required then add a comment explaining why and `// nosemgrep` on the same line.
paths:
exclude:
- "**/patches/**"
- "**/demo/**"
languages: [python]
severity: ERROR

View File

@@ -1,14 +0,0 @@
from frappe import _
# ruleid: frappe-missing-translate-function-in-report-python
{"label": "Field Label"}
# ruleid: frappe-missing-translate-function-in-report-python
dict(label="Field Label")
# ok: frappe-missing-translate-function-in-report-python
{"label": _("Field Label")}
# ok: frappe-missing-translate-function-in-report-python
dict(label=_("Field Label"))

View File

@@ -1,30 +0,0 @@
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"))

View File

@@ -1,60 +0,0 @@
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!')

View File

@@ -1,26 +0,0 @@
name: Backport
on:
pull_request_target:
types:
- closed
- labeled
jobs:
main:
runs-on: ubuntu-latest
timeout-minutes: 60
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

@@ -1,14 +0,0 @@
name: Trigger Docker build on release
on:
release:
types: [released]
jobs:
curl:
runs-on: ubuntu-latest
container:
image: alpine:latest
steps:
- name: curl
run: |
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"}'

View File

@@ -1,25 +0,0 @@
name: 'Documentation Required'
on:
pull_request:
types: [ opened, synchronize, reopened, edited ]
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: 'Setup Environment'
uses: actions/setup-python@v2
with:
python-version: 3.6
- name: 'Clone repo'
uses: actions/checkout@v2
- name: Validate Docs
env:
PR_NUMBER: ${{ github.event.number }}
run: |
pip install requests --quiet
python $GITHUB_WORKSPACE/.github/helper/documentation.py $PR_NUMBER

View File

@@ -1,31 +0,0 @@
name: Linters
on:
pull_request: { }
jobs:
linters:
name: linters
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install and Run Pre-commit
uses: pre-commit/action@v2.0.3
- name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
- uses: returntocorp/semgrep-action@v1
env:
SEMGREP_TIMEOUT: 120
with:
config: >-
r/python.lang.correctness
./frappe-semgrep-rules/rules

View File

@@ -1,90 +0,0 @@
name: Patch
on:
pull_request:
paths-ignore:
- '**.js'
- '**.md'
workflow_dispatch:
concurrency:
group: patch-mariadb-v13-${{ github.event.number }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-18.04
timeout-minutes: 60
name: Patch Test
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: Setup Node
uses: actions/setup-node@v2
with:
node-version: 12
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: Install
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
- name: Run Patch Tests
run: |
cd ~/frappe-bench/
wget https://erpnext.com/files/v10-erpnext.sql.gz
bench --site test_site --force restore ~/frappe-bench/v10-erpnext.sql.gz
bench --site test_site migrate

View File

@@ -1,31 +0,0 @@
name: Generate Semantic Release
on:
push:
branches:
- version-13
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout Entire Repository
uses: actions/checkout@v2
with:
fetch-depth: 0
persist-credentials: false
- name: Setup Node.js v14
uses: actions/setup-node@v2
with:
node-version: 14
- 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,100 +0,0 @@
name: Server
on:
pull_request:
paths-ignore:
- '**.js'
- '**.md'
workflow_dispatch:
push:
branches: [ develop ]
paths-ignore:
- '**.js'
- '**.md'
concurrency:
group: server-mariadb-v13-${{ github.event.number }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-18.04
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
container: [1, 2]
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: Setup Node
uses: actions/setup-node@v2
with:
node-version: 12
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: 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 --total-builds 2 --build-number ${{ matrix.container }}'
env:
TYPE: server
CI_BUILD_ID: ${{ github.run_id }}
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io

View File

@@ -1,22 +0,0 @@
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

View File

@@ -1,58 +0,0 @@
pull_request_rules:
- name: Auto-close PRs on stable branch
conditions:
- and:
- and:
- author!=surajshetty3416
- author!=gavindsouza
- author!=rohitwaghchaure
- author!=nabinhait
- or:
- base=version-13
- base=version-12
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: 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 }}"

View File

@@ -1,45 +0,0 @@
exclude: 'node_modules|.git'
default_stages: [commit]
fail_fast: false
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
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
- repo: https://gitlab.com/pycqa/flake8
rev: 3.9.2
hooks:
- id: flake8
additional_dependencies: [
'flake8-bugbear',
]
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/timothycrosley/isort
rev: 5.9.1
hooks:
- id: isort
exclude: ".*setup.py$"
ci:
autoupdate_schedule: weekly
skip: []
submodules: false

1
.pylintrc Normal file
View File

@@ -0,0 +1 @@
disable=access-member-before-definition

View File

@@ -1,24 +0,0 @@
{
"branches": ["version-13"],
"plugins": [
"@semantic-release/commit-analyzer", {
"preset": "angular",
"releaseRules": [
{"breaking": true, "release": false}
]
},
"@semantic-release/release-notes-generator",
[
"@semantic-release/exec", {
"prepareCmd": 'sed -ir -E "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"
]
}

80
.travis.yml Normal file
View File

@@ -0,0 +1,80 @@
dist: trusty
language: python
git:
depth: 1
cache:
- pip
addons:
hosts: test_site
mariadb: 10.3
jobs:
include:
- name: "Python 2.7 Server Side Test"
python: 2.7
script: bench --site test_site run-tests --app erpnext --coverage
- name: "Python 3.6 Server Side Test"
python: 3.6
script: bench --site test_site run-tests --app erpnext --coverage
- name: "Python 2.7 Patch Test"
python: 2.7
before_script:
- wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz
- bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz
script: bench --site test_site migrate
- name: "Python 3.6 Patch Test"
python: 3.6
before_script:
- wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz
- bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz
script: bench --site test_site migrate
install:
- cd ~
- nvm install 10
- git clone https://github.com/frappe/bench --depth 1
- pip install -e ./bench
- git clone https://github.com/frappe/frappe --branch $TRAVIS_BRANCH --depth 1
- bench init --skip-assets --frappe-path ~/frappe --python $(which python) frappe-bench
- mkdir ~/frappe-bench/sites/test_site
- cp -r $TRAVIS_BUILD_DIR/.travis/site_config.json ~/frappe-bench/sites/test_site/
- mysql -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"
- mysql -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
- mysql -u root -e "CREATE DATABASE test_frappe"
- mysql -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
- mysql -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
- mysql -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'"
- mysql -u root -e "FLUSH PRIVILEGES"
- 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
- cd ~/frappe-bench
- sed -i 's/watch:/# watch:/g' Procfile
- sed -i 's/schedule:/# schedule:/g' Procfile
- sed -i 's/socketio:/# socketio:/g' Procfile
- sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
- bench get-app erpnext $TRAVIS_BUILD_DIR
- bench start &
- bench --site test_site reinstall --yes
after_script:
- pip install python-coveralls
- coveralls -b apps/erpnext -d ../../sites/.coverage

View File

@@ -1,6 +1,4 @@
{ {
"db_host": "127.0.0.1",
"db_port": 3306,
"db_name": "test_frappe", "db_name": "test_frappe",
"db_password": "test_frappe", "db_password": "test_frappe",
"auto_email_id": "test@example.com", "auto_email_id": "test@example.com",
@@ -11,6 +9,5 @@
"root_login": "root", "root_login": "root",
"root_password": "travis", "root_password": "travis",
"host_name": "http://test_site:8000", "host_name": "http://test_site:8000",
"install_apps": ["erpnext"], "install_apps": ["erpnext"]
"throttle_user_limit": 100
} }

View File

@@ -1,33 +0,0 @@
# Each line is a file pattern followed by one or more owners.
# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,
erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/assets/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/erpnext_integrations/ @nextchamp-saqib
erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
erpnext/regional @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/selling @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/support/ @nextchamp-saqib @deepeshgarg007
pos* @nextchamp-saqib
erpnext/buying/ @rohitwaghchaure @s-aga-r
erpnext/maintenance/ @rohitwaghchaure @s-aga-r
erpnext/manufacturing/ @rohitwaghchaure @s-aga-r
erpnext/quality_management/ @rohitwaghchaure @s-aga-r
erpnext/stock/ @rohitwaghchaure @s-aga-r
erpnext/healthcare/ @chillaranand
erpnext/hr/ @ruchamahabal
erpnext/non_profit/ @ruchamahabal
erpnext/payroll @ruchamahabal
erpnext/projects/ @ruchamahabal
erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
erpnext/patches/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure
erpnext/public/ @nextchamp-saqib @marination
.github/ @ankush
requirements.txt @gavindsouza @ankush

View File

@@ -5,7 +5,7 @@
<p>ERP made simple</p> <p>ERP made simple</p>
</p> </p>
[![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) [![Build Status](https://travis-ci.com/frappe/erpnext.svg)](https://travis-ci.com/frappe/erpnext)
[![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)
[![Coverage Status](https://coveralls.io/repos/github/frappe/erpnext/badge.svg?branch=develop)](https://coveralls.io/github/frappe/erpnext?branch=develop) [![Coverage Status](https://coveralls.io/repos/github/frappe/erpnext/badge.svg?branch=develop)](https://coveralls.io/github/frappe/erpnext?branch=develop)
@@ -13,36 +13,15 @@
</div> </div>
ERPNext as a monolith includes the following areas for managing businesses: Includes: Accounting, Inventory, Manufacturing, CRM, Sales, Purchase, Project Management, HRMS. Requires MariaDB.
1. [Accounting](https://erpnext.com/open-source-accounting) ERPNext is built on the [Frappe](https://github.com/frappe/frappe) Framework, a full-stack web app framework in Python & JavaScript.
1. [Warehouse Management](https://erpnext.com/distribution/warehouse-management-system)
1. [CRM](https://erpnext.com/open-source-crm)
1. [Sales](https://erpnext.com/open-source-sales-purchase)
1. [Purchase](https://erpnext.com/open-source-sales-purchase)
1. [HRMS](https://erpnext.com/open-source-hrms)
1. [Project Management](https://erpnext.com/open-source-projects)
1. [Support](https://erpnext.com/open-source-help-desk-software)
1. [Asset Management](https://erpnext.com/open-source-asset-management-software)
1. [Quality Management](https://erpnext.com/docs/user/manual/en/quality-management)
1. [Manufacturing](https://erpnext.com/open-source-manufacturing-erp-software)
1. [Website Management](https://erpnext.com/open-source-website-builder-software)
1. [Customize ERPNext](https://erpnext.com/docs/user/manual/en/customize-erpnext)
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.
- [User Guide](https://erpnext.com/docs/user) - [User Guide](https://erpnext.com/docs/user)
- [Discussion Forum](https://discuss.erpnext.com/) - [Discussion Forum](https://discuss.erpnext.com/)
--- ---
### 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.
### Full 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.
@@ -65,8 +44,6 @@ 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).
--- ---
## Contributing ## Contributing

View File

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

View File

@@ -0,0 +1,31 @@
context('Form', () => {
before(() => {
cy.login('Administrator', 'qwe');
cy.visit('/desk');
});
it('create a new opportunity', () => {
cy.visit('/desk#Form/Opportunity/New Opportunity 1');
cy.get('.page-title').should('contain', 'Not Saved');
cy.fill_field('opportunity_from', 'Customer', 'Select');
cy.fill_field('party_name', 'Test Customer', 'Link').blur();
cy.get('.primary-action').click();
cy.get('.page-title').should('contain', 'Open');
cy.get('.form-inner-toolbar button:contains("Lost")').click({ force: true });
cy.get('.modal input[data-fieldname="lost_reason"]').as('input');
cy.get('@input').focus().type('Higher', { delay: 200 });
cy.get('.modal .awesomplete ul')
.should('be.visible')
.get('li:contains("Higher Price")')
.click({ force: true });
cy.get('@input').focus().type('No Followup', { delay: 200 });
cy.get('.modal .awesomplete ul')
.should('be.visible')
.get('li:contains("No Followup")')
.click();
cy.fill_field('detailed_reason', 'Test Detailed Reason', 'Text');
cy.get('.modal button:contains("Declare Lost")').click({ force: true });
cy.get('.page-title').should('contain', 'Lost');
});
});

View File

@@ -1,13 +0,0 @@
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

@@ -1,116 +0,0 @@
context('Organizational Chart', () => {
before(() => {
cy.login();
cy.visit('/app/website');
});
it('navigates to org chart', () => {
cy.visit('/app');
cy.visit('/app/organizational-chart');
cy.url().should('include', '/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{downarrow}{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

@@ -1,195 +0,0 @@
context('Organizational Chart Mobile', () => {
before(() => {
cy.login();
cy.visit('/app/website');
});
it('navigates to org chart', () => {
cy.viewport(375, 667);
cy.visit('/app');
cy.visit('/app/organizational-chart');
cy.url().should('include', '/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{downarrow}{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]}`);
});
});
});

View File

@@ -11,7 +11,7 @@
// This function is called when a project is opened or re-opened (e.g. due to // This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing) // the project's config changing)
module.exports = () => { // module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits // `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config // `config` is the resolved Cypress config
}; // }

View File

@@ -10,22 +10,16 @@
// //
// //
// -- This is a parent command -- // -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... }); // Cypress.Commands.add("login", (email, password) => { ... })
// //
// //
// -- This is a child command -- // -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }); // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
// //
// //
// -- This is a dual command -- // -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }); // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
// //
// //
// -- This is will overwrite an existing command -- // -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }); // 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)}`);
});

View File

@@ -13,14 +13,10 @@
// https://on.cypress.io/configuration // https://on.cypress.io/configuration
// *********************************************************** // ***********************************************************
// import frappe commands
import '../../../frappe/cypress/support/index';
// Import commands.js using ES2015 syntax: // Import commands.js using ES2015 syntax:
import './commands'; import './commands';
import '../../../frappe/cypress/support/commands' // eslint-disable-line
// Alternatively you can use CommonJS syntax: // Alternatively you can use CommonJS syntax:
// require('./commands') // require('./commands')
Cypress.Cookies.defaults({
preserve: 'sid'
});

View File

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

View File

@@ -1,9 +0,0 @@
{
"extends": ["stylelint-config-recommended"],
"plugins": ["stylelint-scss"],
"rules": {
"at-rule-no-unknown": null,
"scss/at-rule-no-unknown": true,
"no-descending-specificity": null
}
}

View File

@@ -1,60 +1,53 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import inspect import inspect
import frappe import frappe
from erpnext.hooks import regional_overrides from erpnext.hooks import regional_overrides
from frappe.utils import getdate
__version__ = "13.36.1" __version__ = '12.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(user, "company") 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()
@@ -63,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():
@@ -74,73 +66,65 @@ 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 company or frappe.flags.company:
return frappe.get_cached_value("Company", company or frappe.flags.company, "country") return frappe.get_cached_value('Company',
company or frappe.flags.company, 'country')
elif frappe.flags.country: elif frappe.flags.country:
return frappe.flags.country return frappe.flags.country
else: else:
return frappe.get_system_settings("country") return 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'''
def caller(*args, **kwargs): def caller(*args, **kwargs):
region = get_region() region = get_region()
fn_name = 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 region in regional_overrides and fn_name in regional_overrides[region]:
return frappe.get_attr(regional_overrides[region][fn_name])(*args, **kwargs) return frappe.get_attr(regional_overrides[region][fn_name])(*args, **kwargs)
else: else:
@@ -148,17 +132,16 @@ def allow_regional(fn):
return caller return caller
def get_last_membership():
'''Returns last membership if exists'''
last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
dict(member=frappe.session.user, paid=1), order_by='to_date desc', limit=1)
@frappe.whitelist() return last_membership and last_membership[0]
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: def is_member():
return last_membership[0] '''Returns true if the user is still a member'''
last_membership = get_last_membership()
if last_membership and getdate(last_membership.to_date) > getdate():
return True
return False

View File

@@ -1,58 +0,0 @@
{
"cards": [
{
"card": "Total Outgoing Bills"
},
{
"card": "Total Incoming Bills"
},
{
"card": "Total Incoming Payment"
},
{
"card": "Total Outgoing Payment"
}
],
"charts": [
{
"chart": "Profit and Loss",
"width": "Full"
},
{
"chart": "Incoming Bills (Purchase Invoice)",
"width": "Half"
},
{
"chart": "Outgoing Bills (Sales Invoice)",
"width": "Half"
},
{
"chart": "Accounts Receivable Ageing",
"width": "Half"
},
{
"chart": "Accounts Payable Ageing",
"width": "Half"
},
{
"chart": "Budget Variance",
"width": "Full"
},
{
"chart": "Bank Balance",
"width": "Full"
}
],
"creation": "2020-07-17 11:25:34.796608",
"dashboard_name": "Accounts",
"docstatus": 0,
"doctype": "Dashboard",
"idx": 0,
"is_default": 0,
"is_standard": 1,
"modified": "2020-07-22 13:07:34.540574",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts",
"owner": "Administrator"
}

View File

@@ -5,7 +5,6 @@
"_comments": null, "_comments": null,
"_liked_by": null, "_liked_by": null,
"_user_tags": null, "_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -18,24 +17,17 @@
"docstatus": 0, "docstatus": 0,
"dt": "Address", "dt": "Address",
"fetch_from": null, "fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "tax_category", "fieldname": "tax_category",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"hide_border": 0, "idx": 14,
"hide_days": 0,
"hide_seconds": 0,
"idx": 15,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"insert_after": "fax", "insert_after": "fax",
"label": "Tax Category", "label": "Tax Category",
"length": 0,
"mandatory_depends_on": null,
"modified": "2018-12-28 22:29:21.828090", "modified": "2018-12-28 22:29:21.828090",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Address-tax_category", "name": "Address-tax_category",
@@ -51,66 +43,6 @@
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"print_width": null, "print_width": null,
"read_only": 0, "read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"translatable": 0,
"unique": 0,
"width": null
},
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2020-10-14 17:41:40.878179",
"default": "0",
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "Address",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "is_your_company_address",
"fieldtype": "Check",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 20,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "linked_with",
"label": "Is Your Company Address",
"length": 0,
"mandatory_depends_on": null,
"modified": "2020-10-14 17:41:40.878179",
"modified_by": "Administrator",
"name": "Address-is_your_company_address",
"no_copy": 0,
"options": null,
"owner": "Administrator",
"parent": null,
"parentfield": null,
"parenttype": null,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,

View File

@@ -1,60 +0,0 @@
import frappe
from frappe import _
from frappe.contacts.doctype.address.address import (
Address,
get_address_display,
get_address_templates,
)
class ERPNextAddress(Address):
def validate(self):
self.validate_reference()
super(ERPNextAddress, self).validate()
def link_address(self):
"""Link address based on owner"""
if self.is_your_company_address:
return
return super(ERPNextAddress, self).link_address()
def validate_reference(self):
if self.is_your_company_address and not [
row for row in self.links if row.link_doctype == "Company"
]:
frappe.throw(
_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
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()
def get_shipping_address(company, address=None):
filters = [
["Dynamic Link", "link_doctype", "=", "Company"],
["Dynamic Link", "link_name", "=", company],
["Address", "is_your_company_address", "=", 1],
]
fields = ["*"]
if address and frappe.db.get_value("Dynamic Link", {"parent": address, "link_name": company}):
filters.append(["Address", "name", "=", address])
if not address:
filters.append(["Address", "is_shipping_address", "=", 1])
address = frappe.get_all("Address", filters=filters, fields=fields) or {}
if address:
address_as_dict = address[0]
name, address_template = get_address_templates(address_as_dict)
return address_as_dict.get("name"), frappe.render_template(address_template, address_as_dict)

View File

@@ -1,23 +0,0 @@
{
"chart_name": "Accounts Payable Ageing",
"chart_type": "Report",
"creation": "2020-07-17 11:25:34.564015",
"docstatus": 0,
"doctype": "Dashboard Chart",
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"report_date\":\"frappe.datetime.now_date()\"}",
"filters_json": "{\"ageing_based_on\":\"Due Date\",\"range1\":30,\"range2\":60,\"range3\":90,\"range4\":120,\"group_by_party\":0,\"based_on_payment_terms\":0}",
"idx": 0,
"is_public": 1,
"is_standard": 1,
"modified": "2020-07-22 12:29:33.584419",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Payable Ageing",
"number_of_groups": 0,
"owner": "Administrator",
"report_name": "Accounts Payable",
"timeseries": 0,
"type": "Donut",
"use_report_chart": 1,
"y_axis": []
}

View File

@@ -1,23 +0,0 @@
{
"chart_name": "Accounts Receivable Ageing",
"chart_type": "Report",
"creation": "2020-07-17 11:25:34.535388",
"docstatus": 0,
"doctype": "Dashboard Chart",
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"report_date\":\"frappe.datetime.now_date()\"}",
"filters_json": "{\"ageing_based_on\":\"Due Date\",\"range1\":30,\"range2\":60,\"range3\":90,\"range4\":120,\"group_by_party\":0,\"based_on_payment_terms\":0,\"show_future_payments\":0,\"show_delivery_notes\":0,\"show_sales_person\":0}",
"idx": 0,
"is_public": 1,
"is_standard": 1,
"modified": "2020-07-22 12:28:42.743551",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Receivable Ageing",
"number_of_groups": 0,
"owner": "Administrator",
"report_name": "Accounts Receivable",
"timeseries": 0,
"type": "Donut",
"use_report_chart": 1,
"y_axis": []
}

View File

@@ -1,26 +0,0 @@
{
"chart_name": "Bank Balance",
"chart_type": "Custom",
"creation": "2020-07-17 11:25:34.620221",
"docstatus": 0,
"doctype": "Dashboard Chart",
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"account\":\"locals[\\\":Company\\\"][frappe.defaults.get_user_default(\\\"Company\\\")][\\\"default_bank_account\\\"]\"}",
"filters_json": "{}",
"idx": 0,
"is_public": 1,
"is_standard": 1,
"last_synced_on": "2020-07-22 12:19:59.879476",
"modified": "2020-07-22 12:21:48.780513",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Balance",
"number_of_groups": 0,
"owner": "Administrator",
"source": "Account Balance Timeline",
"time_interval": "Quarterly",
"timeseries": 0,
"timespan": "Last Year",
"type": "Line",
"use_report_chart": 0,
"y_axis": []
}

View File

@@ -1,23 +0,0 @@
{
"chart_name": "Budget Variance",
"chart_type": "Report",
"creation": "2020-07-17 11:25:34.593061",
"docstatus": 0,
"doctype": "Dashboard Chart",
"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}",
"idx": 0,
"is_public": 1,
"is_standard": 1,
"modified": "2020-07-22 12:24:49.144210",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Budget Variance",
"number_of_groups": 0,
"owner": "Administrator",
"report_name": "Budget Variance Report",
"timeseries": 0,
"type": "Bar",
"use_report_chart": 1,
"y_axis": []
}

View File

@@ -1,29 +0,0 @@
{
"based_on": "posting_date",
"chart_name": "Incoming Bills (Purchase Invoice)",
"chart_type": "Sum",
"color": "#a83333",
"creation": "2020-07-17 11:25:34.479703",
"docstatus": 0,
"doctype": "Dashboard Chart",
"document_type": "Purchase Invoice",
"dynamic_filters_json": "",
"filters_json": "[[\"Purchase Invoice\",\"docstatus\",\"=\",1]]",
"idx": 0,
"is_public": 1,
"is_standard": 1,
"last_synced_on": "2020-07-21 17:37:30.727306",
"modified": "2020-07-21 17:51:07.374917",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Incoming Bills (Purchase Invoice)",
"number_of_groups": 0,
"owner": "Administrator",
"time_interval": "Monthly",
"timeseries": 1,
"timespan": "Last Year",
"type": "Bar",
"use_report_chart": 0,
"value_based_on": "base_net_total",
"y_axis": []
}

View File

@@ -1,28 +0,0 @@
{
"based_on": "posting_date",
"chart_name": "Outgoing Bills (Sales Invoice)",
"chart_type": "Sum",
"color": "#7b933d",
"creation": "2020-07-17 11:25:34.507547",
"docstatus": 0,
"doctype": "Dashboard Chart",
"document_type": "Sales Invoice",
"filters_json": "[[\"Sales Invoice\",\"docstatus\",\"=\",1]]",
"idx": 0,
"is_public": 1,
"is_standard": 1,
"last_synced_on": "2020-07-21 17:37:31.574666",
"modified": "2020-07-21 17:52:03.970530",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Outgoing Bills (Sales Invoice)",
"number_of_groups": 0,
"owner": "Administrator",
"time_interval": "Monthly",
"timeseries": 1,
"timespan": "Last Year",
"type": "Bar",
"use_report_chart": 0,
"value_based_on": "base_net_total",
"y_axis": []
}

View File

@@ -1,23 +0,0 @@
{
"chart_name": "Profit and Loss",
"chart_type": "Report",
"creation": "2020-07-17 11:25:34.448572",
"docstatus": 0,
"doctype": "Dashboard Chart",
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"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}",
"idx": 0,
"is_public": 1,
"is_standard": 1,
"modified": "2020-07-22 12:33:48.888943",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Profit and Loss",
"number_of_groups": 0,
"owner": "Administrator",
"report_name": "Profit and Loss Statement",
"timeseries": 0,
"type": "Bar",
"use_report_chart": 1,
"y_axis": []
}

View File

@@ -1,62 +1,42 @@
# 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
from frappe.utils.dashboard import cache_source from erpnext.accounts.report.general_ledger.general_ledger import execute
from frappe.utils.dateutils import get_from_date_from_timespan, get_period_ending from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan
from frappe.utils.nestedset import get_descendants_of from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending
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, from_date = None, to_date = None):
chart_name=None,
chart=None,
no_cache=None,
filters=None,
from_date=None,
to_date=None,
timespan=None,
time_interval=None,
heatmap_year=None,
):
if chart_name: 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
timegrain = chart.time_interval timegrain = chart.time_interval
filters = frappe.parse_json(filters) or frappe.parse_json(chart.filters_json) filters = frappe.parse_json(chart.filters_json)
account = filters.get("account") account = filters.get("account")
company = filters.get("company") company = filters.get("company")
if not account and chart_name: if not account and chart:
frappe.throw( frappe.throw(_("Account is not set for the dashboard chart {0}").format(chart))
_("Account is not set for the dashboard chart {0}").format(
get_link_to_form("Dashboard Chart", chart_name)
)
)
if not frappe.db.exists("Account", account) and chart_name:
frappe.throw(
_("Account {0} does not exists in the dashboard chart {1}").format(
account, get_link_to_form("Dashboard Chart", chart_name)
)
)
if not to_date: 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 +49,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.db.get_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 +73,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 +111,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,26 @@
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
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
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,94 +29,45 @@ 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 frappe.throw(_("Cannot change Service Stop Date for item in row {0}".format(item.idx)))
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))
def convert_deferred_expense_to_expense(start_date=None, end_date=None):
def build_conditions(process_type, account, company):
conditions = ""
deferred_account = (
"item.deferred_revenue_account" if process_type == "Income" else "item.deferred_expense_account"
)
if account:
conditions += "AND %s='%s'" % (deferred_account, account)
elif company:
conditions += f"AND p.company = {frappe.db.escape(company)}"
return 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:
start_date = add_months(today(), -1) start_date = add_months(today(), -1)
if not end_date: if not end_date:
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 parent from `tabPurchase Invoice Item`
select distinct item.parent where service_start_date<=%s and service_end_date>=%s
from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p and enable_deferred_expense = 1 and docstatus = 1 and ifnull(amount, 0) > 0
where item.service_start_date<=%s and item.service_end_date>=%s ''', (end_date, start_date))
and item.enable_deferred_expense = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{0}
""".format(
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:
doc = frappe.get_doc("Purchase Invoice", invoice) doc = frappe.get_doc("Purchase Invoice", invoice)
book_deferred_income_or_expense(doc, deferred_process, end_date) book_deferred_income_or_expense(doc, end_date)
if frappe.flags.deferred_accounting_error: def convert_deferred_revenue_to_income(start_date=None, end_date=None):
send_mail(deferred_process)
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:
start_date = add_months(today(), -1) start_date = add_months(today(), -1)
if not end_date: if not end_date:
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 parent from `tabSales Invoice Item`
select distinct item.parent where service_start_date<=%s and service_end_date>=%s
from `tabSales Invoice Item` item, `tabSales Invoice` p and enable_deferred_revenue = 1 and docstatus = 1 and ifnull(amount, 0) > 0
where item.service_start_date<=%s and item.service_end_date>=%s ''', (end_date, start_date))
and item.enable_deferred_revenue = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{0}
""".format(
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)
book_deferred_income_or_expense(doc, deferred_process, end_date) book_deferred_income_or_expense(doc, end_date)
if frappe.flags.deferred_accounting_error:
send_mail(deferred_process)
def get_booking_dates(doc, item, posting_date=None): def get_booking_dates(doc, item, posting_date=None):
if not posting_date: if not posting_date:
@@ -142,37 +75,13 @@ def get_booking_dates(doc, item, posting_date=None):
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"
)
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 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 voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
and is_cancelled = 0
order by posting_date desc limit 1 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,
)
prev_gl_via_je = frappe.db.sql(
"""
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
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
""",
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
if prev_gl_via_je:
if (not prev_gl_entry) or (
prev_gl_entry and prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date
):
prev_gl_entry = prev_gl_via_je
if prev_gl_entry: if prev_gl_entry:
start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1)) start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
@@ -195,95 +104,7 @@ def get_booking_dates(doc, item, 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
):
amount, base_amount = 0, 0
if not last_gl_entry:
total_months = (
(item.service_end_date.year - item.service_start_date.year) * 12
+ (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(
date_diff(get_last_day(item.service_end_date), get_first_day(item.service_start_date))
)
actual_months = rounded(total_months * prorate_factor, 1)
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
doc, item
)
base_amount = flt(item.base_net_amount / actual_months, item.precision("base_net_amount"))
if base_amount + already_booked_amount > item.base_net_amount:
base_amount = item.base_net_amount - already_booked_amount
if account_currency == doc.company_currency:
amount = base_amount
else:
amount = flt(item.net_amount / actual_months, item.precision("net_amount"))
if amount + already_booked_amount_in_account_currency > item.net_amount:
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):
partial_month = flt(date_diff(end_date, start_date)) / flt(
date_diff(get_last_day(end_date), get_first_day(start_date))
)
base_amount = rounded(partial_month, 1) * base_amount
amount = rounded(partial_month, 1) * amount
else:
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
doc, item
)
base_amount = flt(
item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
)
if account_currency == doc.company_currency:
amount = base_amount
else:
amount = flt(
item.net_amount - already_booked_amount_in_account_currency, item.precision("net_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
if not last_gl_entry:
base_amount = flt(
item.base_net_amount * total_booking_days / flt(total_days), item.precision("base_net_amount")
)
if account_currency == doc.company_currency:
amount = base_amount
else:
amount = flt(
item.net_amount * total_booking_days / flt(total_days), item.precision("net_amount")
)
else:
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
doc, item
)
base_amount = flt(
item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
)
if account_currency == doc.company_currency:
amount = base_amount
else:
amount = flt(
item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount")
)
return amount, base_amount
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"
deferred_account = "deferred_revenue_account" deferred_account = "deferred_revenue_account"
@@ -291,63 +112,39 @@ 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( amount, base_amount = 0, 0
""" if not last_gl_entry:
base_amount = flt(item.base_net_amount*total_booking_days/flt(total_days), item.precision("base_net_amount"))
if account_currency==doc.company_currency:
amount = base_amount
else:
amount = flt(item.net_amount*total_booking_days/flt(total_days), item.precision("net_amount"))
else:
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(
"""
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
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
""".format(
total_credit_debit, total_credit_debit_currency
),
(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 base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
if account_currency==doc.company_currency:
if doc.currency == doc.company_currency: amount = base_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 amount = flt(item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount"))
)
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 amount, base_amount
def book_deferred_income_or_expense(doc, posting_date=None):
enable_check = "enable_deferred_revenue" \
if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): def _book_deferred_revenue_or_expense(item):
enable_check = (
"enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
)
accounts_frozen_upto = frappe.get_cached_value("Accounts Settings", "None", "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) start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
if not (start_date and end_date): if not (start_date and end_date): return
return
account_currency = get_account_currency(item.expense_account or item.income_account) account_currency = get_account_currency(item.expense_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
@@ -358,305 +155,61 @@ 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": amount, base_amount = calculate_amount(doc, item, last_gl_entry,
amount, base_amount = calculate_monthly_amount( total_days, total_booking_days, account_currency)
doc,
item,
last_gl_entry,
start_date,
end_date,
total_days,
total_booking_days,
account_currency,
)
else:
amount, base_amount = calculate_amount(
doc, item, last_gl_entry, total_days, total_booking_days, account_currency
)
if not amount: make_gl_entries(doc, credit_account, debit_account, against,
return amount, base_amount, end_date, project, account_currency, item.cost_center, item.name)
# check if books nor frozen till endate:
if accounts_frozen_upto and (end_date) <= getdate(accounts_frozen_upto):
end_date = get_last_day(add_days(accounts_frozen_upto, 1))
if via_journal_entry:
book_revenue_via_journal_entry(
doc,
credit_account,
debit_account,
amount,
base_amount,
end_date,
project,
account_currency,
item.cost_center,
item,
deferred_process,
submit_journal_entry,
)
else:
make_gl_entries(
doc,
credit_account,
debit_account,
against,
amount,
base_amount,
end_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
if frappe.flags.deferred_accounting_error:
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)
item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
)
via_journal_entry = cint(
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"
)
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)
item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
)
def make_gl_entries(doc, credit_account, debit_account, against,
def process_deferred_accounting(posting_date=None): amount, base_amount, posting_date, project, account_currency, cost_center, voucher_detail_no):
"""Converts deferred income/expense into income/expense
Executed via background jobs on every month end"""
if not posting_date:
posting_date = today()
if not cint(
frappe.db.get_singles_value(
"Accounts Settings", "automatically_process_deferred_accounting_entry"
)
):
return
start_date = add_months(today(), -1)
end_date = add_days(today(), -1)
companies = frappe.get_all("Company")
for company in companies:
for record_type in ("Income", "Expense"):
doc = frappe.get_doc(
dict(
doctype="Process Deferred Accounting",
company=company.name,
posting_date=posting_date,
start_date=start_date,
end_date=end_date,
type=record_type,
)
)
doc.insert()
doc.submit()
def make_gl_entries(
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": voucher_detail_no,
"posting_date": posting_date, 'posting_date': posting_date,
"project": project, 'project': project
"against_voucher_type": "Process Deferred Accounting", }, account_currency)
"against_voucher": deferred_process,
},
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": voucher_detail_no,
"posting_date": posting_date, 'posting_date': posting_date,
"project": project, 'project': project
"against_voucher_type": "Process Deferred Accounting", }, account_currency)
"against_voucher": deferred_process,
},
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:
traceback = frappe.get_traceback()
frappe.log_error(
title=_("Error while processing deferred accounting for Invoice {0}").format(doc.name),
message=traceback,
)
raise e
else:
frappe.db.rollback() frappe.db.rollback()
title = _("Error while processing deferred accounting for {0}").format(doc.name)
traceback = frappe.get_traceback() traceback = frappe.get_traceback()
frappe.log_error( frappe.log_error(message=traceback , title=title)
title=_("Error while processing deferred accounting for Invoice {0}").format(doc.name), sendmail_to_system_managers(title, traceback)
message=traceback,
)
frappe.flags.deferred_accounting_error = True
def send_mail(deferred_process):
title = _("Error while processing deferred accounting for {0}").format(deferred_process)
link = get_link_to_form("Process Deferred Accounting", deferred_process)
content = _("Deferred accounting failed for some invoices:") + "\n"
content += _(
"Please check Process Deferred Accounting {0} and submit manually after resolving errors."
).format(link)
sendmail_to_system_managers(title, content)
def book_revenue_via_journal_entry(
doc,
credit_account,
debit_account,
amount,
base_amount,
posting_date,
project,
account_currency,
cost_center,
item,
deferred_process=None,
submit="No",
):
if amount == 0:
return
journal_entry = frappe.new_doc("Journal Entry")
journal_entry.posting_date = posting_date
journal_entry.company = doc.company
journal_entry.voucher_type = (
"Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense"
)
journal_entry.process_deferred_accounting = deferred_process
debit_entry = {
"account": credit_account,
"credit": base_amount,
"credit_in_account_currency": amount,
"account_currency": account_currency,
"reference_name": doc.name,
"reference_type": doc.doctype,
"reference_detail_no": item.name,
"cost_center": cost_center,
"project": project,
}
credit_entry = {
"account": debit_account,
"debit": base_amount,
"debit_in_account_currency": amount,
"account_currency": account_currency,
"reference_name": doc.name,
"reference_type": doc.doctype,
"reference_detail_no": item.name,
"cost_center": cost_center,
"project": project,
}
for dimension in get_accounting_dimensions():
debit_entry.update({dimension: item.get(dimension)})
credit_entry.update({dimension: item.get(dimension)})
journal_entry.append("accounts", debit_entry)
journal_entry.append("accounts", credit_entry)
try:
journal_entry.save()
if submit:
journal_entry.submit()
frappe.db.commit()
except Exception:
frappe.db.rollback()
traceback = frappe.get_traceback()
frappe.log_error(
title=_("Error while processing deferred accounting for Invoice {0}").format(doc.name),
message=traceback,
)
frappe.flags.deferred_accounting_error = True
def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr):
if doctype == "Sales Invoice":
credit_account, debit_account = frappe.db.get_value(
"Sales Invoice Item",
{"name": voucher_detail_no},
["income_account", "deferred_revenue_account"],
)
else:
credit_account, debit_account = frappe.db.get_value(
"Purchase Invoice Item",
{"name": voucher_detail_no},
["deferred_expense_account", "expense_account"],
)
if dr_or_cr == "Debit":
return debit_account
else:
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

@@ -43,12 +43,12 @@ frappe.ui.form.on('Account', {
frm.trigger('add_toolbar_buttons'); frm.trigger('add_toolbar_buttons');
} }
if (frm.has_perm('write')) { if (frm.has_perm('write')) {
frm.add_custom_button(__('Merge Account'), function () {
frm.trigger("merge_account");
}, __('Actions'));
frm.add_custom_button(__('Update Account Name / Number'), function () { frm.add_custom_button(__('Update Account Name / Number'), function () {
frm.trigger("update_account_number"); frm.trigger("update_account_number");
}, __('Actions')); });
frm.add_custom_button(__('Merge Account'), function () {
frm.trigger("merge_account");
});
} }
} }
}, },
@@ -59,12 +59,11 @@ frappe.ui.form.on('Account', {
} }
}, },
add_toolbar_buttons: function(frm) { add_toolbar_buttons: function(frm) {
frm.add_custom_button(__('Chart of Accounts'), () => { frm.add_custom_button(__('Chart of Accounts'),
frappe.set_route("Tree", "Account"); function () { frappe.set_route("Tree", "Account"); });
}, __('View'));
if (frm.doc.is_group == 1) { if (frm.doc.is_group == 1) {
frm.add_custom_button(__('Convert to Non-Group'), function () { frm.add_custom_button(__('Group to Non-Group'), function () {
return frappe.call({ return frappe.call({
doc: frm.doc, doc: frm.doc,
method: 'convert_group_to_ledger', method: 'convert_group_to_ledger',
@@ -72,11 +71,10 @@ frappe.ui.form.on('Account', {
frm.refresh(); frm.refresh();
} }
}); });
}, __('Actions')); });
} else if (cint(frm.doc.is_group) == 0 } else if (cint(frm.doc.is_group) == 0
&& frappe.boot.user.can_read.indexOf("GL Entry") !== -1) { && frappe.boot.user.can_read.indexOf("GL Entry") !== -1) {
frm.add_custom_button(__('General Ledger'), function () { cur_frm.add_custom_button(__('Ledger'), function () {
frappe.route_options = { frappe.route_options = {
"account": frm.doc.name, "account": frm.doc.name,
"from_date": frappe.sys_defaults.year_start_date, "from_date": frappe.sys_defaults.year_start_date,
@@ -84,9 +82,9 @@ frappe.ui.form.on('Account', {
"company": frm.doc.company "company": frm.doc.company
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");
}, __('View')); });
frm.add_custom_button(__('Convert to Group'), function () { frm.add_custom_button(__('Non-Group to Group'), function () {
return frappe.call({ return frappe.call({
doc: frm.doc, doc: frm.doc,
method: 'convert_ledger_to_group', method: 'convert_ledger_to_group',
@@ -94,7 +92,7 @@ frappe.ui.form.on('Account', {
frm.refresh(); frm.refresh();
} }
}); });
}, __('Actions')); });
} }
}, },

View File

@@ -1,5 +1,4 @@
{ {
"actions": [],
"allow_copy": 1, "allow_copy": 1,
"allow_import": 1, "allow_import": 1,
"creation": "2013-01-30 12:49:46", "creation": "2013-01-30 12:49:46",
@@ -34,15 +33,11 @@
{ {
"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%"
}, },
{ {
@@ -53,9 +48,7 @@
"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",
@@ -63,17 +56,13 @@
"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",
@@ -85,9 +74,7 @@
"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",
@@ -95,9 +82,7 @@
"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",
@@ -105,32 +90,24 @@
"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", "default": "0",
"fieldname": "inter_company_account", "fieldname": "inter_company_account",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Inter Company Account", "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%"
}, },
{ {
@@ -142,9 +119,7 @@
"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.",
@@ -154,9 +129,7 @@
"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\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", "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\nTax\nTemporary"
"show_days": 1,
"show_seconds": 1
}, },
{ {
"description": "Rate at which this tax is applied", "description": "Rate at which this tax is applied",
@@ -164,9 +137,7 @@
"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.",
@@ -175,17 +146,13 @@
"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",
@@ -194,9 +161,7 @@
"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",
@@ -205,9 +170,7 @@
"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",
@@ -215,37 +178,28 @@
"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, "modified": "2019-10-10 19:10:02.967554",
"links": [],
"modified": "2020-06-11 15:15:54.338622",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Account", "name": "Account",
"nsm_parent_field": "parent_account",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {

View File

@@ -1,47 +1,35 @@
# 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 Account(NestedSet): class Account(NestedSet):
nsm_parent_field = "parent_account" 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_on_update:
return return
else: else:
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, None, 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()
@@ -58,33 +46,22 @@ class Account(NestedSet):
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.db.get_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.db.get_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
@@ -95,20 +72,15 @@ class Account(NestedSet):
db_value = frappe.db.get_value("Account", self.name, ["report_type", "root_type"], as_dict=1) 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_root_details(self): def validate_root_details(self):
# does not exists parent # does not exists parent
@@ -117,44 +89,33 @@ class Account(NestedSet):
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:
frappe.throw(_("The root account {0} must be a group").format(frappe.bold(self.name))) frappe.throw(_("Root Account must be a group"))
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_value("Company", self.company, "allow_account_creation_against_child_company"): if frappe.get_value("Company", self.company, "allow_account_creation_against_child_company"):
return return
if not frappe.db.get_value(
"Account", {"account_name": self.account_name, "company": ancestors[0]}, "name"
):
frappe.throw(_("Please add the account to root level Company - {}").format(ancestors[0]))
elif self.parent_account:
descendants = get_descendants_of("Company", self.company)
if not descendants:
return
parent_acc_name_map = {}
parent_acc_name, parent_acc_number = frappe.db.get_value(
"Account", self.parent_account, ["account_name", "account_number"]
)
filters = {
"company": ["in", descendants],
"account_name": parent_acc_name,
}
if parent_acc_number:
filters["account_number"] = parent_acc_number
for d in frappe.db.get_values( if not frappe.db.get_value("Account",
"Account", filters=filters, fieldname=["company", "name"], as_dict=True {'account_name': self.account_name, 'company': ancestors[0]}, 'name'):
): frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0]))
else:
descendants = get_descendants_of('Company', self.company)
if not descendants: return
parent_acc_name_map = {}
parent_acc_name = frappe.db.get_value('Account', self.parent_account, "account_name")
for d in frappe.db.get_values('Account',
{"company": ["in", descendants], "account_name": parent_acc_name},
["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)
@@ -175,92 +136,65 @@ class Account(NestedSet):
def validate_frozen_accounts_modifier(self): def validate_frozen_accounts_modifier(self):
old_value = frappe.db.get_value("Account", self.name, "freeze_account") old_value = frappe.db.get_value("Account", self.name, "freeze_account")
if old_value and old_value != self.freeze_account: if old_value and old_value != self.freeze_account:
frozen_accounts_modifier = frappe.db.get_value( frozen_accounts_modifier = frappe.db.get_value('Accounts Settings', None, 'frozen_accounts_modifier')
"Accounts Settings", None, "frozen_accounts_modifier" if not frozen_accounts_modifier or \
) frozen_accounts_modifier not in frappe.get_roles():
if not frozen_accounts_modifier or frozen_accounts_modifier not in frappe.get_roles():
throw(_("You are not authorized to set Frozen value")) 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):
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")
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"))
def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name): def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name):
for company in descendants: for company in descendants:
company_bold = frappe.bold(company)
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, parent_acc_name))
"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 filters = {
if frappe.db.get_value("Account", self.parent_account, "is_group") and not frappe.db.get_value( "account_name": self.account_name,
"Account", parent_acc_name_map[company], "is_group" "company": company
): }
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 += _(
"Please convert the parent account in corresponding child company to a group account."
)
frappe.throw(msg, title=_("Invalid Parent Account"))
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 it is None, it picks it up from default company currency, which might be unintended
"account_currency": erpnext.get_company_currency(company), "account_currency": self.account_currency,
"parent_account": parent_acc_name_map[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))
@@ -268,7 +202,6 @@ class Account(NestedSet):
if parent_value_changed: if parent_value_changed:
doc.save() doc.save()
@frappe.whitelist()
def convert_group_to_ledger(self): def convert_group_to_ledger(self):
if self.check_if_child_exists(): if self.check_if_child_exists():
throw(_("Account with child nodes cannot be converted to ledger")) throw(_("Account with child nodes cannot be converted to ledger"))
@@ -279,12 +212,11 @@ class Account(NestedSet):
self.save() self.save()
return 1 return 1
@frappe.whitelist()
def convert_ledger_to_group(self): def convert_ledger_to_group(self):
if self.check_gle_exists(): if self.check_gle_exists():
throw(_("Account with existing transaction can not be converted to group.")) throw(_("Account with existing transaction can not be converted to group."))
elif self.account_type and not self.flags.exclude_account_type_check: elif self.account_type and not self.flags.exclude_account_type_check:
throw(_("Cannot convert to Group because Account Type is selected.")) throw(_("Cannot covert to Group because Account Type is selected."))
else: else:
self.is_group = 1 self.is_group = 1
self.save() self.save()
@@ -295,11 +227,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:
@@ -315,102 +244,53 @@ class Account(NestedSet):
super(Account, self).on_trash(True) super(Account, self).on_trash(True)
@frappe.whitelist()
@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, %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, start, page_len), (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):
account = frappe.db.get_value("Account", name, "company", as_dict=True) 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 = frappe.db.get_value(
"Account", name, ["account_name", "account_number"]
)
# check if account exists in parent company
ancestors = get_ancestors_of("Company", account.company)
allow_independent_account_creation = frappe.get_value(
"Company", account.company, "allow_account_creation_against_child_company"
)
if ancestors and not allow_independent_account_creation:
for ancestor in ancestors:
if frappe.db.get_value("Account", {"account_name": old_acc_name, "company": ancestor}, "name"):
# same account in parent company exists
allow_child_account_creation = _("Allow Account Creation Against Child Company")
message = _("Account {0} exists in parent company {1}.").format(
frappe.bold(old_acc_name), frappe.bold(ancestor)
)
message += "<br>"
message += _("Renaming it is only allowed via parent company {0}, to avoid mismatch.").format(
frappe.bold(ancestor)
)
message += "<br><br>"
message += _("To overrule this, enable '{0}' in company {1}").format(
allow_child_account_creation, frappe.bold(account.company)
)
frappe.throw(message, title=_("Rename Not Allowed"))
validate_account_number(name, account_number, account.company) validate_account_number(name, account_number, account.company)
if account_number: if account_number:
frappe.db.set_value("Account", name, "account_number", account_number.strip()) frappe.db.set_value("Account", name, "account_number", account_number.strip())
@@ -418,63 +298,33 @@ def update_account_number(name, account_name, account_number=None, from_descenda
frappe.db.set_value("Account", name, "account_number", "") frappe.db.set_value("Account", name, "account_number", "")
frappe.db.set_value("Account", name, "account_name", account_name.strip()) frappe.db.set_value("Account", name, "account_name", account_name.strip())
if not from_descendant:
# Update and rename in child company accounts as well
descendants = get_descendants_of("Company", account.company)
if descendants:
sync_update_account_number_in_child(
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, is_group, root_type, company): def merge_account(old, new, is_group, root_type, company):
# Validate properties before merging # Validate properties before merging
if not frappe.db.exists("Account", new): if not frappe.db.exists("Account", new):
throw(_("Account {0} does not exist").format(new)) throw(_("Account {0} does not exist").format(new))
val = list(frappe.db.get_value("Account", new, ["is_group", "root_type", "company"])) val = list(frappe.db.get_value("Account", new,
["is_group", "root_type", "company"]))
if val != [cint(is_group), root_type, company]: if val != [cint(is_group), root_type, company]:
throw( throw(_("""Merging is only possible if following properties are same in both records. Is Group, Root Type, Company"""))
_(
"""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: if is_group and frappe.db.get_value("Account", new, "parent_account") == old:
frappe.db.set_value( frappe.db.set_value("Account", new, "parent_account",
"Account", new, "parent_account", frappe.db.get_value("Account", old, "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
):
filters = {
"company": ["in", descendants],
"account_name": old_acc_name,
}
if old_acc_number:
filters["account_number"] = old_acc_number
for d in frappe.db.get_values(
"Account", filters=filters, fieldname=["company", "name"], as_dict=True
):
update_account_number(d["name"], account_name, account_number, from_descendant=True)

View File

@@ -1,8 +1,8 @@
frappe.provide("frappe.treeview_settings") frappe.provide("frappe.treeview_settings")
frappe.treeview_settings["Account"] = { frappe.treeview_settings["Account"] = {
breadcrumb: "Accounts", breadcrumbs: "Accounts",
title: __("Chart of Accounts"), title: __("Chart Of Accounts"),
get_tree_root: false, get_tree_root: false,
filters: [ filters: [
{ {
@@ -14,9 +14,6 @@ frappe.treeview_settings["Account"] = {
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) {
frappe.throw(__("Please set a Company"));
}
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.account.account.get_root_company", method: "erpnext.accounts.doctype.account.account.get_root_company",
args: { args: {
@@ -45,50 +42,6 @@ frappe.treeview_settings["Account"] = {
], ],
root_label: "Accounts", root_label: "Accounts",
get_tree_nodes: 'erpnext.accounts.utils.get_children', 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;
}
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', add_tree_node: 'erpnext.accounts.utils.add_ac',
menu_items:[ menu_items:[
{ {
@@ -141,7 +94,7 @@ frappe.treeview_settings["Account"] = {
treeview.page.add_inner_button(__("Journal Entry"), function() { treeview.page.add_inner_button(__("Journal Entry"), function() {
frappe.new_doc('Journal Entry', {company: get_company()}); frappe.new_doc('Journal Entry', {company: get_company()});
}, __('Create')); }, __('Create'));
treeview.page.add_inner_button(__("Company"), function() { treeview.page.add_inner_button(__("New Company"), function() {
frappe.new_doc('Company'); frappe.new_doc('Company');
}, __('Create')); }, __('Create'));
@@ -164,7 +117,25 @@ frappe.treeview_settings["Account"] = {
} else { } else {
treeview.new_node(); treeview.new_node();
} }
}, "add"); }, "octicon octicon-plus");
},
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 text-muted small">'
+ (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: [
{ {
@@ -176,7 +147,7 @@ frappe.treeview_settings["Account"] = {
&& node.expandable && !node.hide_add; && 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"

View File

@@ -1,19 +1,15 @@
# 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 six import iteritems
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 = []
@@ -23,29 +19,20 @@ def create_charts(
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_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": child.get("account_name") if from_coa_importer else account_name, "account_name": account_name,
"company": company, "company": company,
"parent_account": parent, "parent_account": parent,
"is_group": is_group, "is_group": is_group,
@@ -53,11 +40,9 @@ def create_charts(
"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") "account_currency": child.get('account_currency') or frappe.db.get_value('Company', company, "default_currency"),
or frappe.db.get_value("Company", company, "default_currency"), "tax_rate": child.get("tax_rate")
"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
@@ -72,15 +57,15 @@ def create_charts(
# Rebuild NestedSet HSM tree for Account Doctype # Rebuild NestedSet HSM tree for Account Doctype
# after all accounts are already inserted. # after all accounts are already inserted.
frappe.local.flags.ignore_update_nsm = True frappe.local.flags.ignore_on_update = True
_import_accounts(chart, None, None, root_account=True) _import_accounts(chart, None, None, root_account=True)
rebuild_tree("Account", "parent_account") rebuild_tree("Account", "parent_account")
frappe.local.flags.ignore_update_nsm = False frappe.local.flags.ignore_on_update = 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,37 +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"])
):
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",)
@@ -136,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 = []
@@ -144,9 +118,8 @@ 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.db.get_value("Country", country, "code") country_code = frappe.db.get_value("Country", country, "code")
@@ -174,21 +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",
],
order_by="lft, rgt",
)
account_tree = {} account_tree = {}
@@ -197,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 ""
@@ -226,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 iteritems(account_master): 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
@@ -256,33 +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 iteritems(children): 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",
]:
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'] = (child.get('account_number') + ' - ' + 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

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

View File

@@ -82,7 +82,9 @@
}, },
"Inventory": { "Inventory": {
"Consigned Stock": { "Consigned Stock": {
"Handling Difference in Inventory": {}, "Handling Difference in Inventory": {
"account_type": "Stock Adjustment"
},
"Items Delivered to Customs on temporary Base": {} "Items Delivered to Customs on temporary Base": {}
}, },
"Stock in Hand": { "Stock in Hand": {
@@ -188,9 +190,6 @@
}, },
"Expenses Included In Valuation": { "Expenses Included In Valuation": {
"account_type": "Expenses Included In Valuation" "account_type": "Expenses Included In Valuation"
},
"Stock Adjustment": {
"account_type": "Stock Adjustment"
} }
}, },
"Depreciation": { "Depreciation": {

View File

@@ -1,531 +0,0 @@
{
"country_code": "de",
"name": "SKR03 mit Kontonummern",
"tree": {
"Aktiva": {
"is_group": 1,
"root_type": "Asset",
"A - Anlagevermögen": {
"is_group": 1,
"EDV-Software": {
"account_number": "0027",
"account_type": "Fixed Asset"
},
"Gesch\u00e4ftsausstattung": {
"account_number": "0410",
"account_type": "Fixed Asset"
},
"B\u00fcroeinrichtung": {
"account_number": "0420",
"account_type": "Fixed Asset"
},
"Darlehen": {
"account_number": "0565"
},
"Maschinen": {
"account_number": "0210",
"account_type": "Fixed Asset"
},
"Betriebsausstattung": {
"account_number": "0400",
"account_type": "Fixed Asset"
},
"Ladeneinrichtung": {
"account_number": "0430",
"account_type": "Fixed Asset"
},
"Accumulated Depreciation": {
"account_type": "Accumulated Depreciation"
}
},
"B - Umlaufvermögen": {
"is_group": 1,
"I. Vorräte": {
"is_group": 1,
"Roh-, Hilfs- und Betriebsstoffe (Bestand)": {
"account_number": "3970",
"account_type": "Stock"
},
"Waren (Bestand)": {
"account_number": "3980",
"account_type": "Stock"
}
},
"II. Forderungen und sonstige Vermögensgegenstände": {
"is_group": 1,
"Ford. a. Lieferungen und Leistungen": {
"account_number": "1400",
"account_type": "Receivable"
},
"Durchlaufende Posten": {
"account_number": "1590"
},
"Gewinnermittlung \u00a74/3 nicht Ergebniswirksam": {
"account_number": "1371"
},
"Abziehbare Vorsteuer": {
"account_type": "Tax",
"is_group": 1,
"Abziehbare Vorsteuer 7%": {
"account_number": "1571"
},
"Abziehbare Vorsteuer 19%": {
"account_number": "1576"
},
"Abziehbare Vorsteuer nach \u00a713b UStG 19%": {
"account_number": "1577"
},
"Leistungen \u00a713b UStG 19% Vorsteuer, 19% Umsatzsteuer": {
"account_number": "3120"
}
}
},
"III. Wertpapiere": {
"is_group": 1
},
"IV. Kassenbestand, Bundesbankguthaben, Guthaben bei Kreditinstituten und Schecks.": {
"is_group": 1,
"Kasse": {
"account_type": "Cash",
"is_group": 1,
"Kasse": {
"is_group": 1,
"account_number": "1000",
"account_type": "Cash"
}
},
"Bank": {
"is_group": 1,
"account_type": "Bank",
"Postbank": {
"account_number": "1100",
"account_type": "Bank"
},
"Bankkonto": {
"account_number": "1200",
"account_type": "Bank"
}
}
}
},
"C - Rechnungsabgrenzungsposten": {
"is_group": 1,
"Aktive Rechnungsabgrenzung": {
"account_number": "0980"
}
},
"D - Aktive latente Steuern": {
"is_group": 1,
"Aktive latente Steuern": {
"account_number": "0983"
}
},
"E - Aktiver Unterschiedsbetrag aus der Vermögensverrechnung": {
"is_group": 1
}
},
"Passiva": {
"is_group": 1,
"root_type": "Liability",
"A. Eigenkapital": {
"is_group": 1,
"I. Gezeichnetes Kapital": {
"is_group": 1
},
"II. Kapitalrücklage": {
"is_group": 1
},
"III. Gewinnrücklagen": {
"is_group": 1
},
"IV. Gewinnvortrag/Verlustvortrag": {
"is_group": 1
},
"V. Jahresüberschuß/Jahresfehlbetrag": {
"is_group": 1
}
},
"B. Rückstellungen": {
"is_group": 1,
"I. Rückstellungen für Pensionen und ähnliche Verpflichtungen": {
"is_group": 1
},
"II. Steuerrückstellungen": {
"is_group": 1
},
"III. sonstige Rückstellungen": {
"is_group": 1
}
},
"C. Verbindlichkeiten": {
"is_group": 1,
"I. Anleihen": {
"is_group": 1
},
"II. Verbindlichkeiten gegenüber Kreditinstituten": {
"is_group": 1
},
"III. Erhaltene Anzahlungen auf Bestellungen": {
"is_group": 1
},
"IV. Verbindlichkeiten aus Lieferungen und Leistungen": {
"is_group": 1,
"Verbindlichkeiten aus Lieferungen u. Leistungen": {
"account_number": "1600",
"account_type": "Payable"
}
},
"V. Verbindlichkeiten aus der Annahme gezogener Wechsel und der Ausstellung eigener Wechsel": {
"is_group": 1
},
"VI. Verbindlichkeiten gegenüber verbundenen Unternehmen": {
"is_group": 1
},
"VII. Verbindlichkeiten gegenüber Unternehmen, mit denen ein Beteiligungsverhältnis besteht": {
"is_group": 1
},
"VIII. sonstige Verbindlichkeiten": {
"is_group": 1,
"Sonstige Verbindlichkeiten": {
"account_number": "1700",
"account_type": "Asset Received But Not Billed"
},
"Sonstige Verbindlichkeiten (1 bis 5 Jahre)": {
"account_number": "1702",
"account_type": "Stock Received But Not Billed"
},
"Verbindlichkeiten aus Lohn und Gehalt": {
"account_number": "1740",
"account_type": "Payable"
},
"Umsatzsteuer": {
"is_group": 1,
"account_type": "Tax",
"Umsatzsteuer 7%": {
"account_number": "1771"
},
"Umsatzsteuer 19%": {
"account_number": "1776"
},
"Umsatzsteuer-Vorauszahlung": {
"account_number": "1780"
},
"Umsatzsteuer-Vorauszahlung 1/11": {
"account_number": "1781"
},
"Umsatzsteuer \u00a7 13b UStG 19%": {
"account_number": "1787"
},
"Umsatzsteuer Vorjahr": {
"account_number": "1790"
},
"Umsatzsteuer fr\u00fchere Jahre": {
"account_number": "1791"
}
}
}
},
"D. Rechnungsabgrenzungsposten": {
"is_group": 1,
"Passive Rechnungsabgrenzung": {
"account_number": "0990"
}
},
"E. Passive latente Steuern": {
"is_group": 1
}
},
"Erl\u00f6se u. Ertr\u00e4ge 2/8": {
"is_group": 1,
"root_type": "Income",
"Erl\u00f6skonten 8": {
"is_group": 1,
"Erl\u00f6se": {
"account_number": "8200",
"account_type": "Income Account"
},
"Erl\u00f6se USt. 19%": {
"account_number": "8400",
"account_type": "Income Account"
},
"Erl\u00f6se USt. 7%": {
"account_number": "8300",
"account_type": "Income Account"
}
},
"Ertragskonten 2": {
"is_group": 1,
"sonstige Zinsen und \u00e4hnliche Ertr\u00e4ge": {
"account_number": "2650",
"account_type": "Income Account"
},
"Au\u00dferordentliche Ertr\u00e4ge": {
"account_number": "2500",
"account_type": "Income Account"
},
"Sonstige Ertr\u00e4ge": {
"account_number": "2700",
"account_type": "Income Account"
}
}
},
"Aufwendungen 2/4": {
"is_group": 1,
"root_type": "Expense",
"Wareneingang": {
"account_number": "3200"
},
"Bezugsnebenkosten": {
"account_number": "3800",
"account_type": "Expenses Included In Asset Valuation"
},
"Herstellungskosten": {
"account_number": "4996",
"account_type": "Cost of Goods Sold"
},
"Verluste aus dem Abgang von Gegenständen des Anlagevermögens": {
"account_number": "2320",
"account_type": "Stock Adjustment"
},
"Verwaltungskosten": {
"account_number": "4997",
"account_type": "Expenses Included In Valuation"
},
"Vertriebskosten": {
"account_number": "4998",
"account_type": "Expenses Included In Valuation"
},
"Gegenkonto 4996-4998": {
"account_number": "4999"
},
"Abschreibungen": {
"is_group": 1,
"Abschreibungen auf Sachanlagen (ohne AfA auf Kfz und Gebäude)": {
"account_number": "4830",
"account_type": "Accumulated Depreciation"
},
"Abschreibungen auf Gebäude": {
"account_number": "4831",
"account_type": "Depreciation"
},
"Abschreibungen auf Kfz": {
"account_number": "4832",
"account_type": "Depreciation"
},
"Sofortabschreibung GWG": {
"account_number": "4855",
"account_type": "Expense Account"
}
},
"Kfz-Kosten": {
"is_group": 1,
"Kfz-Steuer": {
"account_number": "4510",
"account_type": "Expense Account"
},
"Kfz-Versicherungen": {
"account_number": "4520",
"account_type": "Expense Account"
},
"laufende Kfz-Betriebskosten": {
"account_number": "4530",
"account_type": "Expense Account"
},
"Kfz-Reparaturen": {
"account_number": "4540",
"account_type": "Expense Account"
},
"Fremdfahrzeuge": {
"account_number": "4570",
"account_type": "Expense Account"
},
"sonstige Kfz-Kosten": {
"account_number": "4580",
"account_type": "Expense Account"
}
},
"Personalkosten": {
"is_group": 1,
"Geh\u00e4lter": {
"account_number": "4120",
"account_type": "Expense Account"
},
"gesetzliche soziale Aufwendungen": {
"account_number": "4130",
"account_type": "Expense Account"
},
"Aufwendungen f\u00fcr Altersvorsorge": {
"account_number": "4165",
"account_type": "Expense Account"
},
"Verm\u00f6genswirksame Leistungen": {
"account_number": "4170",
"account_type": "Expense Account"
},
"Aushilfsl\u00f6hne": {
"account_number": "4190",
"account_type": "Expense Account"
}
},
"Raumkosten": {
"is_group": 1,
"Miete und Nebenkosten": {
"account_number": "4210",
"account_type": "Expense Account"
},
"Gas, Wasser, Strom (Verwaltung, Vertrieb)": {
"account_number": "4240",
"account_type": "Expense Account"
},
"Reinigung": {
"account_number": "4250",
"account_type": "Expense Account"
}
},
"Reparatur/Instandhaltung": {
"is_group": 1,
"Reparatur u. Instandh. von Anlagen/Maschinen u. Betriebs- u. Gesch\u00e4ftsausst.": {
"account_number": "4805",
"account_type": "Expense Account"
}
},
"Versicherungsbeitr\u00e4ge": {
"is_group": 1,
"Versicherungen": {
"account_number": "4360",
"account_type": "Expense Account"
},
"Beitr\u00e4ge": {
"account_number": "4380",
"account_type": "Expense Account"
},
"sonstige Ausgaben": {
"account_number": "4390",
"account_type": "Expense Account"
},
"steuerlich abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": {
"account_number": "4396",
"account_type": "Expense Account"
}
},
"Werbe-/Reisekosten": {
"is_group": 1,
"Werbekosten": {
"account_number": "4610",
"account_type": "Expense Account"
},
"Aufmerksamkeiten": {
"account_number": "4653",
"account_type": "Expense Account"
},
"nicht abzugsf\u00e4hige Betriebsausg. aus Werbe-, Repr\u00e4s.- u. Reisekosten": {
"account_number": "4665",
"account_type": "Expense Account"
},
"Reisekosten Unternehmer": {
"account_number": "4670",
"account_type": "Expense Account"
}
},
"verschiedene Kosten": {
"is_group": 1,
"Porto": {
"account_number": "4910",
"account_type": "Expense Account"
},
"Telekom": {
"account_number": "4920",
"account_type": "Expense Account"
},
"Mobilfunk D2": {
"account_number": "4921",
"account_type": "Expense Account"
},
"Internet": {
"account_number": "4922",
"account_type": "Expense Account"
},
"B\u00fcrobedarf": {
"account_number": "4930",
"account_type": "Expense Account"
},
"Zeitschriften, B\u00fccher": {
"account_number": "4940",
"account_type": "Expense Account"
},
"Fortbildungskosten": {
"account_number": "4945",
"account_type": "Expense Account"
},
"Buchf\u00fchrungskosten": {
"account_number": "4955",
"account_type": "Expense Account"
},
"Abschlu\u00df- u. Pr\u00fcfungskosten": {
"account_number": "4957",
"account_type": "Expense Account"
},
"Nebenkosten des Geldverkehrs": {
"account_number": "4970",
"account_type": "Expense Account"
},
"Werkzeuge und Kleinger\u00e4te": {
"account_number": "4985",
"account_type": "Expense Account"
}
},
"Zinsaufwendungen": {
"is_group": 1,
"Zinsaufwendungen f\u00fcr kurzfristige Verbindlichkeiten": {
"account_number": "2110",
"account_type": "Expense Account"
},
"Zinsaufwendungen f\u00fcr KFZ Finanzierung": {
"account_number": "2121",
"account_type": "Expense Account"
}
}
},
"Anfangsbestand 9": {
"is_group": 1,
"root_type": "Equity",
"Saldenvortragskonten": {
"is_group": 1,
"Saldenvortrag Sachkonten": {
"account_number": "9000"
},
"Saldenvortr\u00e4ge Debitoren": {
"account_number": "9008"
},
"Saldenvortr\u00e4ge Kreditoren": {
"account_number": "9009"
}
}
},
"Privatkonten 1": {
"is_group": 1,
"root_type": "Equity",
"Privatentnahmen/-einlagen": {
"is_group": 1,
"Privatentnahme allgemein": {
"account_number": "1800"
},
"Privatsteuern": {
"account_number": "1810"
},
"Sonderausgaben beschr\u00e4nkt abzugsf\u00e4hig": {
"account_number": "1820"
},
"Sonderausgaben unbeschr\u00e4nkt abzugsf\u00e4hig": {
"account_number": "1830"
},
"Au\u00dfergew\u00f6hnliche Belastungen": {
"account_number": "1850"
},
"Privateinlagen": {
"account_number": "1890"
}
}
}
}
}

View File

@@ -292,9 +292,7 @@
"Umsatzsteuerforderungen fr\u00fchere Jahre": {} "Umsatzsteuerforderungen fr\u00fchere Jahre": {}
}, },
"Sonstige Verm\u00f6gensgegenst\u00e4nde oder sonstige Verbindlichkeiten": { "Sonstige Verm\u00f6gensgegenst\u00e4nde oder sonstige Verbindlichkeiten": {
"Abziehbare Vorsteuer": { "Abziehbare Vorsteuer": {},
"account_type": "Tax",
"is_group": 1,
"Abziehbare Vorsteuer 16%": {}, "Abziehbare Vorsteuer 16%": {},
"Abziehbare Vorsteuer 19%": {}, "Abziehbare Vorsteuer 19%": {},
"Abziehbare Vorsteuer 7%": {}, "Abziehbare Vorsteuer 7%": {},
@@ -305,8 +303,7 @@
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb von Neufahrzeugen von Lieferanten ohne Ust-Identifikationsnummer": {}, "Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb von Neufahrzeugen von Lieferanten ohne Ust-Identifikationsnummer": {},
"Abziehbare Vorsteuer nach \u00a7 13b UStG ": {}, "Abziehbare Vorsteuer nach \u00a7 13b UStG ": {},
"Abziehbare Vorsteuer nach \u00a7 13b UStG 16%": {}, "Abziehbare Vorsteuer nach \u00a7 13b UStG 16%": {},
"Abziehbare Vorsteuer nach \u00a7 13b UStG 19%": {} "Abziehbare Vorsteuer nach \u00a7 13b UStG 19%": {},
},
"Aufl\u00f6sung Vorsteuer aus Vorjahr \u00a7 4/3 EStG": {}, "Aufl\u00f6sung Vorsteuer aus Vorjahr \u00a7 4/3 EStG": {},
"Aufzuteilende Vorsteuer": {}, "Aufzuteilende Vorsteuer": {},
"Aufzuteilende Vorsteuer 16%": {}, "Aufzuteilende Vorsteuer 16%": {},
@@ -676,9 +673,7 @@
"Sonstige Verrechnungskonten (Interimskonto)": { "Sonstige Verrechnungskonten (Interimskonto)": {
"account_type": "Stock Received But Not Billed" "account_type": "Stock Received But Not Billed"
}, },
"Umsatzsteuer": { "Umsatzsteuer": {},
"account_type": "Tax",
"is_group": 1,
"Umsatzsteuer 16%": {}, "Umsatzsteuer 16%": {},
"Umsatzsteuer 19%": {}, "Umsatzsteuer 19%": {},
"Umsatzsteuer 7%": {}, "Umsatzsteuer 7%": {},
@@ -694,8 +689,7 @@
"Umsatzsteuer laufendes Jahr": {}, "Umsatzsteuer laufendes Jahr": {},
"Umsatzsteuer nach \u00a713b UStG": {}, "Umsatzsteuer nach \u00a713b UStG": {},
"Umsatzsteuer nach \u00a713b UStG 16%": {}, "Umsatzsteuer nach \u00a713b UStG 16%": {},
"Umsatzsteuer nach \u00a713b UStG 19%": {} "Umsatzsteuer nach \u00a713b UStG 19%": {},
},
"Umsatzsteuer- Vorauszahlungen": {}, "Umsatzsteuer- Vorauszahlungen": {},
"Umsatzsteuer- Vorauszahlungen 1/11": {}, "Umsatzsteuer- Vorauszahlungen 1/11": {},
"Verbindlichkeiten aus Lohn- und Kirchensteuer": {} "Verbindlichkeiten aus Lohn- und Kirchensteuer": {}

View File

@@ -659,27 +659,20 @@
}, },
"Abziehbare Vorsteuer (Gruppe)": { "Abziehbare Vorsteuer (Gruppe)": {
"is_group": 1, "is_group": 1,
"account_type": "Tax",
"Abziehbare Vorsteuer": { "Abziehbare Vorsteuer": {
"account_number": "1400" "account_number": "1400"
}, },
"Abziehbare Vorsteuer 7 %": { "Abziehbare Vorsteuer 7 %": {
"account_number": "1401", "account_number": "1401"
"account_type": "Tax",
"tax_rate": 7.0
}, },
"Abziehbare Vorsteuer aus innergem. Erwerb": { "Abziehbare Vorsteuer aus innergem. Erwerb": {
"account_number": "1402" "account_number": "1402"
}, },
"Abziehbare Vorsteuer aus innergem. Erwerb 19%": { "Abziehbare Vorsteuer aus innergem. Erwerb 19%": {
"account_number": "1404", "account_number": "1404"
"account_type": "Tax",
"tax_rate": 19.0
}, },
"Abziehbare Vorsteuer 19 %": { "Abziehbare Vorsteuer 19 %": {
"account_number": "1406", "account_number": "1406"
"account_type": "Tax",
"tax_rate": 19.0
}, },
"Abziehbare Vorsteuer nach \u00a7 13b UStG 19 %": { "Abziehbare Vorsteuer nach \u00a7 13b UStG 19 %": {
"account_number": "1407" "account_number": "1407"
@@ -911,8 +904,75 @@
}, },
"is_group": 1 "is_group": 1
}, },
"Passiva - Verbindlichkeiten": { "Passiva": {
"root_type": "Liability", "root_type": "Liability",
"A - Eigenkapital": {
"account_type": "Equity",
"is_group": 1,
"I - Gezeichnetes Kapital": {
"account_type": "Equity",
"is_group": 1
},
"II - Kapitalr\u00fccklage": {
"account_type": "Equity",
"is_group": 1
},
"III - Gewinnr\u00fccklagen": {
"account_type": "Equity",
"1 - gesetzliche R\u00fccklage": {
"account_type": "Equity",
"is_group": 1
},
"2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
"account_type": "Equity",
"is_group": 1
},
"3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
"account_type": "Equity",
"is_group": 1
},
"4 - andere Gewinnr\u00fccklagen": {
"account_type": "Equity",
"is_group": 1,
"Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
"is_group": 1,
"Gewinnr\u00fccklagen (BilMoG)": {
"account_number": "2963"
},
"Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
"account_number": "2964"
},
"Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
"account_number": "2965"
},
"Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
"account_number": "2966"
}
},
"Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
"account_number": "2967"
},
"Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
"account_number": "2968"
},
"Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
"account_number": "2969"
}
},
"is_group": 1
},
"IV - Gewinnvortrag/Verlustvortrag": {
"account_type": "Equity",
"is_group": 1
},
"V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
"account_type": "Equity",
"is_group": 1
},
"Einlagen stiller Gesellschafter": {
"account_number": "9295"
}
},
"B - R\u00fcckstellungen": { "B - R\u00fcckstellungen": {
"is_group": 1, "is_group": 1,
"1 - R\u00fcckstellungen f. Pensionen und \u00e4hnliche Verplicht.": { "1 - R\u00fcckstellungen f. Pensionen und \u00e4hnliche Verplicht.": {
@@ -1428,21 +1488,17 @@
}, },
"Umsatzsteuer 7 %": { "Umsatzsteuer 7 %": {
"account_number": "3801", "account_number": "3801",
"account_type": "Tax", "account_type": "Tax"
"tax_rate": 7.0
}, },
"Umsatzsteuer aus innergem. Erwerb": { "Umsatzsteuer aus innergem. Erwerb": {
"account_number": "3802" "account_number": "3802"
}, },
"Umsatzsteuer aus innergem. Erwerb 19 %": { "Umsatzsteuer aus innergem. Erwerb 19 %": {
"account_number": "3804", "account_number": "3804"
"account_type": "Tax",
"tax_rate": 19.0
}, },
"Umsatzsteuer 19 %": { "Umsatzsteuer 19 %": {
"account_number": "3806", "account_number": "3806",
"account_type": "Tax", "account_type": "Tax"
"tax_rate": 19.0
}, },
"Umsatzsteuer aus im Inland steuerpfl. EU-Lieferungen": { "Umsatzsteuer aus im Inland steuerpfl. EU-Lieferungen": {
"account_number": "3807" "account_number": "3807"
@@ -1529,143 +1585,6 @@
}, },
"is_group": 1 "is_group": 1
}, },
"Passiva - Eigenkapital": {
"root_type": "Equity",
"A - Eigenkapital": {
"account_type": "Equity",
"is_group": 1,
"I - Gezeichnetes Kapital": {
"account_type": "Equity",
"is_group": 1,
"Gezeichnetes Kapital": {
"account_number": "2900",
"account_type": "Equity"
},
"Gesch\u00e4ftsguthaben der verbleibenden Mitglieder": {
"account_number": "2901"
},
"Gesch\u00e4ftsguthaben der ausscheidenden Mitglieder": {
"account_number": "2902"
},
"Gesch\u00e4ftsguthaben aus gek\u00fcndigten Gesch\u00e4ftsanteilen": {
"account_number": "2903"
},
"R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
"account_number": "2906"
},
"Gegenkonto R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
"account_number": "2907"
},
"Kapitalerh\u00f6hung aus Gesellschaftsmitteln": {
"account_number": "2908"
},
"Ausstehende Einlagen auf das gezeichnete Kapital, nicht eingefordert": {
"account_number": "2910"
}
},
"II - Kapitalr\u00fccklage": {
"account_type": "Equity",
"is_group": 1,
"Kapitalr\u00fccklage": {
"account_number": "2920"
},
"Kapitalr\u00fccklage durch Ausgabe von Anteilen \u00fcber Nennbetrag": {
"account_number": "2925"
},
"Kapitalr\u00fccklage durch Ausgabe von Schuldverschreibungen": {
"account_number": "2926"
},
"Kapitalr\u00fccklage durch Zuzahlungen gegen Gew\u00e4hrung eines Vorzugs": {
"account_number": "2927"
},
"Kapitalr\u00fccklage durch Zuzahlungen in das Eigenkapital": {
"account_number": "2928"
},
"Nachschusskapital (Gegenkonto 1299)": {
"account_number": "2929"
}
},
"III - Gewinnr\u00fccklagen": {
"account_type": "Equity",
"1 - gesetzliche R\u00fccklage": {
"account_type": "Equity",
"is_group": 1,
"Gesetzliche R\u00fccklage": {
"account_number": "2930"
}
},
"2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
"account_type": "Equity",
"is_group": 1,
"R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
"account_number": "2935"
}
},
"3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
"account_type": "Equity",
"is_group": 1,
"Satzungsm\u00e4\u00dfige R\u00fccklagen": {
"account_number": "2950"
}
},
"4 - andere Gewinnr\u00fccklagen": {
"account_type": "Equity",
"is_group": 1,
"Andere Gewinnr\u00fccklagen": {
"account_number": "2960"
},
"Andere Gewinnr\u00fccklagen aus dem Erwerb eigener Anteile": {
"account_number": "2961"
},
"Eigenkapitalanteil von Wertaufholungen": {
"account_number": "2962"
},
"Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
"is_group": 1,
"Gewinnr\u00fccklagen (BilMoG)": {
"account_number": "2963"
},
"Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
"account_number": "2964"
},
"Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
"account_number": "2965"
},
"Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
"account_number": "2966"
}
},
"Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
"account_number": "2967"
},
"Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
"account_number": "2968"
},
"Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
"account_number": "2969"
}
},
"is_group": 1
},
"IV - Gewinnvortrag/Verlustvortrag": {
"account_type": "Equity",
"is_group": 1,
"Gewinnvortrag vor Verwendung": {
"account_number": "2970"
},
"Verlustvortrag vor Verwendung": {
"account_number": "2978"
}
},
"V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
"account_type": "Equity",
"is_group": 1
},
"Einlagen stiller Gesellschafter": {
"account_number": "9295"
}
}
},
"1 - Umsatzerl\u00f6se": { "1 - Umsatzerl\u00f6se": {
"root_type": "Income", "root_type": "Income",
"is_group": 1, "is_group": 1,
@@ -1735,14 +1654,6 @@
"account_number": "4300", "account_number": "4300",
"account_type": "Income Account" "account_type": "Income Account"
}, },
"Erl\u00f6se 16 % USt": {
"account_number": "4340",
"account_type": "Income Account"
},
"Erl\u00f6se 19 % USt": {
"account_number": "4400",
"account_type": "Income Account"
},
"Erl\u00f6se aus im Inland steuerpfl. EU-Lieferungen 7 % USt": { "Erl\u00f6se aus im Inland steuerpfl. EU-Lieferungen 7 % USt": {
"account_number": "4310" "account_number": "4310"
}, },
@@ -1770,6 +1681,19 @@
"Erl\u00f6se aus im anderen EU-Land steuerbaren Leistungen, im Inland nicht steuerbare Ums\u00e4tze": { "Erl\u00f6se aus im anderen EU-Land steuerbaren Leistungen, im Inland nicht steuerbare Ums\u00e4tze": {
"account_number": "4339" "account_number": "4339"
}, },
"Erl\u00f6se 16 % USt (Gruppe)": {
"is_group": 1,
"Erl\u00f6se 16 % USt": {
"account_number": "4340"
}
},
"Erl\u00f6se 19 % USt (Gruppe)": {
"is_group": 1,
"Erl\u00f6se 19 % USt": {
"account_number": "4400",
"account_type": "Income Account"
}
},
"Grundst\u00fccksertr\u00e4ge (Gruppe)": { "Grundst\u00fccksertr\u00e4ge (Gruppe)": {
"is_group": 1, "is_group": 1,
"Grundst\u00fccksertr\u00e4ge": { "Grundst\u00fccksertr\u00e4ge": {
@@ -1828,12 +1752,14 @@
"2 - Herstellungskosten der zur Erzielung der Umsatzerl\u00f6se erbrachten Leistungen": { "2 - Herstellungskosten der zur Erzielung der Umsatzerl\u00f6se erbrachten Leistungen": {
"root_type": "Expense", "root_type": "Expense",
"is_group": 1, "is_group": 1,
"Herstellungskosten (Gruppe)": {
"Herstellungskosten": { "Herstellungskosten": {
"account_number": "6990", "account_number": "6990",
"account_type": "Cost of Goods Sold" "account_type": "Cost of Goods Sold"
}, },
"Herstellungskosten: Schwund": { "Herstellungskosten: Schwund": {
"account_type": "Stock Adjustment" "account_type": "Stock Adjustment"
}
}, },
"Aufwendungen f. Roh-, Hilfs- und Betriebsstoffe und f. bezogene Waren": { "Aufwendungen f. Roh-, Hilfs- und Betriebsstoffe und f. bezogene Waren": {
"account_number": "5000", "account_number": "5000",
@@ -1883,10 +1809,10 @@
"Energiestoffe (Fertigung)": { "Energiestoffe (Fertigung)": {
"account_number": "5190" "account_number": "5190"
}, },
"Energiestoffe (Fertigung) 7% Vorsteuer": { "Energiestoffe (Fertigung)7% Vorsteuer": {
"account_number": "5191" "account_number": "5191"
}, },
"Energiestoffe (Fertigung) 19% Vorsteuer": { "Energiestoffe (Fertigung)19% Vorsteuer": {
"account_number": "5192" "account_number": "5192"
} }
} }
@@ -2009,7 +1935,6 @@
}, },
"Nachl\u00e4sse aus innergem. Erwerb 15 % Vorsteuer und 15 % Umsatzsteuer": { "Nachl\u00e4sse aus innergem. Erwerb 15 % Vorsteuer und 15 % Umsatzsteuer": {
"account_number": "5727" "account_number": "5727"
}
}, },
"Erhaltene Skonti (Gruppe)": { "Erhaltene Skonti (Gruppe)": {
"is_group": 1, "is_group": 1,
@@ -2052,6 +1977,7 @@
"Erh. Skonti aus Erwerb Waren als letzter Abnehmer innerh. Dreiecksgesch. 19% Vorst. u. 19% Ust.": { "Erh. Skonti aus Erwerb Waren als letzter Abnehmer innerh. Dreiecksgesch. 19% Vorst. u. 19% Ust.": {
"account_number": "5793" "account_number": "5793"
} }
}
}, },
"Bezugsnebenkosten (Gruppe)": { "Bezugsnebenkosten (Gruppe)": {
"is_group": 1, "is_group": 1,
@@ -2481,6 +2407,8 @@
"Erl\u00f6se aus Verk\u00e4ufen Sachanlageverm\u00f6gen (bei Buchgewinn)": { "Erl\u00f6se aus Verk\u00e4ufen Sachanlageverm\u00f6gen (bei Buchgewinn)": {
"account_number": "4849" "account_number": "4849"
}, },
"Erl\u00f6se aus Verk\u00e4ufen immaterieller VG (bei Buchgewinn) (Gruppe)": {
"is_group": 1,
"Erl\u00f6se aus Verk\u00e4ufen immaterieller VG (bei Buchgewinn)": { "Erl\u00f6se aus Verk\u00e4ufen immaterieller VG (bei Buchgewinn)": {
"account_number": "4850" "account_number": "4850"
}, },
@@ -2501,6 +2429,7 @@
}, },
"Anlagenabg\u00e4nge Finanzanlagen (inl\u00e4ndische Kap.Ges., Restbuchwert bei Buchgewinn)": { "Anlagenabg\u00e4nge Finanzanlagen (inl\u00e4ndische Kap.Ges., Restbuchwert bei Buchgewinn)": {
"account_number": "4858" "account_number": "4858"
}
}, },
"Ertr\u00e4ge aus Zuschreibungen des Sachanlageverm\u00f6gens": { "Ertr\u00e4ge aus Zuschreibungen des Sachanlageverm\u00f6gens": {
"account_number": "4910", "account_number": "4910",
@@ -2623,6 +2552,8 @@
"Entnahme von Gegenst\u00e4nden ohne USt": { "Entnahme von Gegenst\u00e4nden ohne USt": {
"account_number": "4605" "account_number": "4605"
}, },
"Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 7 % USt (Gruppe)": {
"is_group": 1,
"Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 7 % USt": { "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 7 % USt": {
"account_number": "4630" "account_number": "4630"
}, },
@@ -2634,6 +2565,7 @@
}, },
"Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens ohne USt (Kfz-Nutzung)": { "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens ohne USt (Kfz-Nutzung)": {
"account_number": "4639" "account_number": "4639"
}
}, },
"Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 19 % USt (Gruppe)": { "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 19 % USt (Gruppe)": {
"is_group": 1, "is_group": 1,
@@ -2671,11 +2603,14 @@
"Unentgeltliche Zuwendung von Gegenst\u00e4nden ohne USt": { "Unentgeltliche Zuwendung von Gegenst\u00e4nden ohne USt": {
"account_number": "4689" "account_number": "4689"
}, },
"Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze) (Gruppe)": {
"is_group": 1,
"Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze)": { "Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze)": {
"account_number": "4690" "account_number": "4690"
}, },
"Umsatzsteuerverg\u00fctungen, z.B. nach \u00a7 24 UStG": { "Umsatzsteuerverg\u00fctungen, z.B. nach \u00a7 24 UStG": {
"account_number": "4695" "account_number": "4695"
}
}, },
"Au\u00dferordentliche Ertr\u00e4ge (Gruppe)": { "Au\u00dferordentliche Ertr\u00e4ge (Gruppe)": {
"is_group": 1, "is_group": 1,
@@ -2685,6 +2620,8 @@
"Au\u00dferordentliche Ertr\u00e4ge finanzwirksam": { "Au\u00dferordentliche Ertr\u00e4ge finanzwirksam": {
"account_number": "7401" "account_number": "7401"
}, },
"Au\u00dferordentliche Ertr\u00e4ge nicht finanzwirksam (Gruppe)": {
"is_group": 1,
"Au\u00dferordentliche Ertr\u00e4ge nicht finanzwirksam": { "Au\u00dferordentliche Ertr\u00e4ge nicht finanzwirksam": {
"account_number": "7450" "account_number": "7450"
}, },
@@ -2699,7 +2636,10 @@
}, },
"Gewinn aus der Ver\u00e4u\u00dferung oder der Aufgabe von Gesch\u00e4ftsaktivit\u00e4ten nach Steuern": { "Gewinn aus der Ver\u00e4u\u00dferung oder der Aufgabe von Gesch\u00e4ftsaktivit\u00e4ten nach Steuern": {
"account_number": "7454" "account_number": "7454"
}
}, },
"Au\u00dferordentliche Ertr\u00e4ge aus der Anwendung von \u00dcbergangsvorschriften (Gruppe)": {
"is_group": 1,
"Au\u00dferordentliche Ertr\u00e4ge aus der Anwendung von \u00dcbergangsvorschriften": { "Au\u00dferordentliche Ertr\u00e4ge aus der Anwendung von \u00dcbergangsvorschriften": {
"account_number": "7460" "account_number": "7460"
}, },
@@ -2716,6 +2656,7 @@
"account_number": "7464" "account_number": "7464"
} }
} }
}
}, },
"7 - sonstige betriebliche Aufwendungen": { "7 - sonstige betriebliche Aufwendungen": {
"root_type": "Expense", "root_type": "Expense",
@@ -2751,7 +2692,6 @@
}, },
"Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 16 % USt": { "Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 16 % USt": {
"account_number": "4729" "account_number": "4729"
}
}, },
"Gew\u00e4hrte Skonti (Gruppe)": { "Gew\u00e4hrte Skonti (Gruppe)": {
"is_group": 1, "is_group": 1,
@@ -2786,8 +2726,6 @@
"account_number": "4748" "account_number": "4748"
} }
}, },
"Gew\u00e4hrte Boni (Gruppe)": {
"is_group": 1,
"Gew\u00e4hrte Boni 7 % USt": { "Gew\u00e4hrte Boni 7 % USt": {
"account_number": "4750" "account_number": "4750"
}, },
@@ -2900,6 +2838,8 @@
"account_number": "6398" "account_number": "6398"
} }
}, },
"Versicherungen (Gruppe)": {
"is_group": 1,
"Versicherungen": { "Versicherungen": {
"account_number": "6400" "account_number": "6400"
}, },
@@ -2947,32 +2887,54 @@
}, },
"Mietleasing (bewegliche Wirtschaftsg\u00fcter)": { "Mietleasing (bewegliche Wirtschaftsg\u00fcter)": {
"account_number": "6498" "account_number": "6498"
}
}, },
"Fahrzeugkosten (Gruppe)": { "Fahrzeugkosten (Gruppe)": {
"is_group": 1, "is_group": 1,
"Fahrzeugkosten": { "Fahrzeugkosten": {
"account_number": "6500" "account_number": "6500"
}, },
"Kfz-Versicherungen (Gruppe)": {
"is_group": 1,
"Kfz-Versicherungen": { "Kfz-Versicherungen": {
"account_number": "6520" "account_number": "6520"
}
}, },
"Laufende Kfz-Betriebskosten (Gruppe)": {
"is_group": 1,
"Laufende Kfz-Betriebskosten": { "Laufende Kfz-Betriebskosten": {
"account_number": "6530" "account_number": "6530"
}
}, },
"Kfz-Reparaturen (Gruppe)": {
"is_group": 1,
"Kfz-Reparaturen": { "Kfz-Reparaturen": {
"account_number": "6540" "account_number": "6540"
}
}, },
"Garagenmiete (Gruppe)": {
"is_group": 1,
"Garagenmiete": { "Garagenmiete": {
"account_number": "6550" "account_number": "6550"
}
}, },
"Mietleasing Kfz (Gruppe)": {
"is_group": 1,
"Mietleasing Kfz": { "Mietleasing Kfz": {
"account_number": "6560" "account_number": "6560"
}
}, },
"Sonstige Kfz-Kosten (Gruppe)": {
"is_group": 1,
"Sonstige Kfz-Kosten": { "Sonstige Kfz-Kosten": {
"account_number": "6570" "account_number": "6570"
}
}, },
"Mautgeb\u00fchren (Gruppe)": {
"is_group": 1,
"Mautgeb\u00fchren": { "Mautgeb\u00fchren": {
"account_number": "6580" "account_number": "6580"
}
}, },
"Kfz-Kosten f. betrieblich genutzte zum Privatverm\u00f6gen geh\u00f6rende Kraftfahrzeuge": { "Kfz-Kosten f. betrieblich genutzte zum Privatverm\u00f6gen geh\u00f6rende Kraftfahrzeuge": {
"account_number": "6590" "account_number": "6590"
@@ -3034,8 +2996,6 @@
"Nicht abzugsf\u00e4hige Betriebsausgaben aus Werbe- und Repr\u00e4sentationskosten": { "Nicht abzugsf\u00e4hige Betriebsausgaben aus Werbe- und Repr\u00e4sentationskosten": {
"account_number": "6645" "account_number": "6645"
}, },
"Reisekosten Arbeitnehmer (Gruppe)": {
"is_group": 1,
"Reisekosten Arbeitnehmer": { "Reisekosten Arbeitnehmer": {
"account_number": "6650" "account_number": "6650"
}, },
@@ -3050,7 +3010,6 @@
}, },
"Kilometergelderstattung Arbeitnehmer": { "Kilometergelderstattung Arbeitnehmer": {
"account_number": "6668" "account_number": "6668"
}
}, },
"Reisekosten Unternehmer (Gruppe)": { "Reisekosten Unternehmer (Gruppe)": {
"is_group": 1, "is_group": 1,

View File

@@ -1,69 +1,125 @@
# 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"): {}
}, },
_("Securities and Deposits"): {_("Earnest Money"): {}},
_("Stock Assets"): { _("Stock Assets"): {
_("Stock In Hand"): {"account_type": "Stock"}, _("Stock In Hand"): {
"account_type": "Stock"
},
"account_type": "Stock", "account_type": "Stock",
}, },
_("Tax Assets"): {"is_group": 1}, _("Tax Assets"): {
"is_group": 1
}
}, },
_("Fixed Assets"): { _("Fixed Assets"): {
_("Capital Equipments"): {"account_type": "Fixed Asset"}, _("Capital Equipments"): {
_("Electronic Equipments"): {"account_type": "Fixed Asset"}, "account_type": "Fixed Asset"
_("Furnitures and Fixtures"): {"account_type": "Fixed Asset"}, },
_("Office Equipments"): {"account_type": "Fixed Asset"}, _("Electronic Equipments"): {
_("Plants and Machineries"): {"account_type": "Fixed Asset"}, "account_type": "Fixed Asset"
_("Buildings"): {"account_type": "Fixed Asset"}, },
_("Softwares"): {"account_type": "Fixed Asset"}, _("Furnitures and Fixtures"): {
_("Accumulated Depreciation"): {"account_type": "Accumulated Depreciation"}, "account_type": "Fixed Asset"
},
_("Office Equipments"): {
"account_type": "Fixed Asset"
},
_("Plants and Machineries"): {
"account_type": "Fixed Asset"
},
_("Buildings"): {
"account_type": "Fixed Asset"
},
_("Softwares"): {
"account_type": "Fixed Asset"
},
_("Accumulated Depreciation"): {
"account_type": "Accumulated Depreciation"
},
_("CWIP Account"): { _("CWIP Account"): {
"account_type": "Capital Work in Progress", "account_type": "Capital Work in Progress",
}
}, },
_("Investments"): {
"is_group": 1
}, },
_("Investments"): {"is_group": 1}, _("Temporary Accounts"): {
_("Temporary Accounts"): {_("Temporary Opening"): {"account_type": "Temporary"}}, _("Temporary Opening"): {
"root_type": "Asset", "account_type": "Temporary"
}
},
"root_type": "Asset"
}, },
_("Expenses"): { _("Expenses"): {
_("Direct Expenses"): { _("Direct Expenses"): {
_("Stock Expenses"): { _("Stock Expenses"): {
_("Cost of Goods Sold"): {"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"): {
"account_type": "Stock Adjustment"
}
}, },
}, },
_("Indirect Expenses"): { _("Indirect Expenses"): {
_("Administrative Expenses"): {}, _("Administrative Expenses"): {},
_("Commission on Sales"): {}, _("Commission on Sales"): {},
_("Depreciation"): {"account_type": "Depreciation"}, _("Depreciation"): {
"account_type": "Depreciation"
},
_("Entertainment Expenses"): {}, _("Entertainment Expenses"): {},
_("Freight and Forwarding Charges"): {"account_type": "Chargeable"}, _("Freight and Forwarding Charges"): {
"account_type": "Chargeable"
},
_("Legal Expenses"): {}, _("Legal Expenses"): {},
_("Marketing Expenses"): {"account_type": "Chargeable"}, _("Marketing Expenses"): {
_("Miscellaneous Expenses"): {"account_type": "Chargeable"}, "account_type": "Chargeable"
},
_("Miscellaneous Expenses"): {
"account_type": "Chargeable"
},
_("Office Maintenance Expenses"): {}, _("Office Maintenance Expenses"): {},
_("Office Rent"): {}, _("Office Rent"): {},
_("Postal Expenses"): {}, _("Postal Expenses"): {},
_("Print and Stationery"): {}, _("Print and Stationery"): {},
_("Round Off"): {"account_type": "Round Off"}, _("Round Off"): {
"account_type": "Round Off"
},
_("Salary"): {}, _("Salary"): {},
_("Sales Expenses"): {}, _("Sales Expenses"): {},
_("Telephone Expenses"): {}, _("Telephone Expenses"): {},
@@ -71,39 +127,61 @@ def get():
_("Utility 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"): {}
},
_("Indirect Income"): {
"is_group": 1
},
"root_type": "Income"
}, },
_("Source of Funds (Liabilities)"): { _("Source of Funds (Liabilities)"): {
_("Current Liabilities"): { _("Current Liabilities"): {
_("Accounts Payable"): { _("Accounts Payable"): {
_("Creditors"): {"account_type": "Payable"}, _("Creditors"): {
"account_type": "Payable"
},
_("Payroll Payable"): {}, _("Payroll Payable"): {},
}, },
_("Stock Liabilities"): { _("Stock Liabilities"): {
_("Stock Received But Not Billed"): {"account_type": "Stock Received But Not Billed"}, _("Stock Received But Not Billed"): {
_("Asset Received But Not Billed"): {"account_type": "Asset 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
}, },
_("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"},
"root_type": "Equity",
}, },
_("Dividends Paid"): {
"account_type": "Equity"
},
_("Opening Balance Equity"): {
"account_type": "Equity"
},
_("Retained Earnings"): {
"account_type": "Equity"
},
"root_type": "Equity"
}
} }

View File

@@ -1,158 +1,289 @@
# 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"
},
"account_number": "1300"
},
_("Bank Accounts"): {
"account_type": "Bank",
"is_group": 1,
"account_number": "1200"
}, },
_("Bank Accounts"): {"account_type": "Bank", "is_group": 1, "account_number": "1200"},
_("Cash In Hand"): { _("Cash In Hand"): {
_("Cash"): {"account_type": "Cash", "account_number": "1110"}, _("Cash"): {
"account_type": "Cash", "account_type": "Cash",
"account_number": "1100", "account_number": "1110"
},
"account_type": "Cash",
"account_number": "1100"
}, },
_("Loans and Advances (Assets)"): { _("Loans and Advances (Assets)"): {
_("Employee Advances"): {"account_number": "1610"}, _("Employee Advances"): {
"account_number": "1600", "account_number": "1610"
},
"account_number": "1600"
}, },
_("Securities and Deposits"): { _("Securities and Deposits"): {
_("Earnest Money"): {"account_number": "1651"}, _("Earnest Money"): {
"account_number": "1650", "account_number": "1651"
},
"account_number": "1650"
}, },
_("Stock Assets"): { _("Stock Assets"): {
_("Stock In Hand"): {"account_type": "Stock", "account_number": "1410"}, _("Stock In Hand"): {
"account_type": "Stock", "account_type": "Stock",
"account_number": "1400", "account_number": "1410"
}, },
_("Tax Assets"): {"is_group": 1, "account_number": "1500"}, "account_type": "Stock",
"account_number": "1100-1600", "account_number": "1400"
},
_("Tax Assets"): {
"is_group": 1,
"account_number": "1500"
},
"account_number": "1100-1600"
}, },
_("Fixed Assets"): { _("Fixed Assets"): {
_("Capital Equipments"): {"account_type": "Fixed Asset", "account_number": "1710"}, _("Capital Equipments"): {
_("Electronic Equipments"): {"account_type": "Fixed Asset", "account_number": "1720"}, "account_type": "Fixed Asset",
_("Furnitures and Fixtures"): {"account_type": "Fixed Asset", "account_number": "1730"}, "account_number": "1710"
_("Office Equipments"): {"account_type": "Fixed Asset", "account_number": "1740"}, },
_("Plants and Machineries"): {"account_type": "Fixed Asset", "account_number": "1750"}, _("Electronic Equipments"): {
_("Buildings"): {"account_type": "Fixed Asset", "account_number": "1760"}, "account_type": "Fixed Asset",
_("Softwares"): {"account_type": "Fixed Asset", "account_number": "1770"}, "account_number": "1720"
},
_("Furnitures and Fixtures"): {
"account_type": "Fixed Asset",
"account_number": "1730"
},
_("Office Equipments"): {
"account_type": "Fixed Asset",
"account_number": "1740"
},
_("Plants and Machineries"): {
"account_type": "Fixed Asset",
"account_number": "1750"
},
_("Buildings"): {
"account_type": "Fixed Asset",
"account_number": "1760"
},
_("Softwares"): {
"account_type": "Fixed Asset",
"account_number": "1770"
},
_("Accumulated Depreciation"): { _("Accumulated Depreciation"): {
"account_type": "Accumulated Depreciation", "account_type": "Accumulated Depreciation",
"account_number": "1780", "account_number": "1780"
}, },
_("CWIP Account"): {"account_type": "Capital Work in Progress", "account_number": "1790"}, _("CWIP Account"): {
"account_number": "1700", "account_type": "Capital Work in Progress",
"account_number": "1790"
},
"account_number": "1700"
},
_("Investments"): {
"is_group": 1,
"account_number": "1800"
}, },
_("Investments"): {"is_group": 1, "account_number": "1800"},
_("Temporary Accounts"): { _("Temporary Accounts"): {
_("Temporary Opening"): {"account_type": "Temporary", "account_number": "1910"}, _("Temporary Opening"): {
"account_number": "1900", "account_type": "Temporary",
"account_number": "1910"
},
"account_number": "1900"
}, },
"root_type": "Asset", "root_type": "Asset",
"account_number": "1000", "account_number": "1000"
}, },
_("Expenses"): { _("Expenses"): {
_("Direct Expenses"): { _("Direct Expenses"): {
_("Stock Expenses"): { _("Stock Expenses"): {
_("Cost of Goods Sold"): {"account_type": "Cost of Goods Sold", "account_number": "5111"}, _("Cost of Goods Sold"): {
"account_type": "Cost of Goods Sold",
"account_number": "5111"
},
_("Expenses Included In Asset Valuation"): { _("Expenses Included In Asset Valuation"): {
"account_type": "Expenses Included In Asset Valuation", "account_type": "Expenses Included In Asset Valuation",
"account_number": "5112", "account_number": "5112"
}, },
_("Expenses Included In Valuation"): { _("Expenses Included In Valuation"): {
"account_type": "Expenses Included In Valuation", "account_type": "Expenses Included In Valuation",
"account_number": "5118", "account_number": "5118"
}, },
_("Stock Adjustment"): {"account_type": "Stock Adjustment", "account_number": "5119"}, _("Stock Adjustment"): {
"account_number": "5110", "account_type": "Stock Adjustment",
"account_number": "5119"
}, },
"account_number": "5100", "account_number": "5110"
},
"account_number": "5100"
}, },
_("Indirect Expenses"): { _("Indirect Expenses"): {
_("Administrative Expenses"): {"account_number": "5201"}, _("Administrative Expenses"): {
_("Commission on Sales"): {"account_number": "5202"}, "account_number": "5201"
_("Depreciation"): {"account_type": "Depreciation", "account_number": "5203"}, },
_("Entertainment Expenses"): {"account_number": "5204"}, _("Commission on Sales"): {
_("Freight and Forwarding Charges"): {"account_type": "Chargeable", "account_number": "5205"}, "account_number": "5202"
_("Legal Expenses"): {"account_number": "5206"}, },
_("Marketing Expenses"): {"account_type": "Chargeable", "account_number": "5207"}, _("Depreciation"): {
_("Office Maintenance Expenses"): {"account_number": "5208"}, "account_type": "Depreciation",
_("Office Rent"): {"account_number": "5209"}, "account_number": "5203"
_("Postal Expenses"): {"account_number": "5210"}, },
_("Print and Stationery"): {"account_number": "5211"}, _("Entertainment Expenses"): {
_("Round Off"): {"account_type": "Round Off", "account_number": "5212"}, "account_number": "5204"
_("Salary"): {"account_number": "5213"}, },
_("Sales Expenses"): {"account_number": "5214"}, _("Freight and Forwarding Charges"): {
_("Telephone Expenses"): {"account_number": "5215"}, "account_type": "Chargeable",
_("Travel Expenses"): {"account_number": "5216"}, "account_number": "5205"
_("Utility Expenses"): {"account_number": "5217"}, },
_("Write Off"): {"account_number": "5218"}, _("Legal Expenses"): {
_("Exchange Gain/Loss"): {"account_number": "5219"}, "account_number": "5206"
_("Gain/Loss on Asset Disposal"): {"account_number": "5220"}, },
_("Miscellaneous Expenses"): {"account_type": "Chargeable", "account_number": "5221"}, _("Marketing Expenses"): {
"account_number": "5200", "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", "root_type": "Expense",
"account_number": "5000", "account_number": "5000"
}, },
_("Income"): { _("Income"): {
_("Direct Income"): { _("Direct Income"): {
_("Sales"): {"account_number": "4110"}, _("Sales"): {
_("Service"): {"account_number": "4120"}, "account_number": "4110"
"account_number": "4100", },
_("Service"): {
"account_number": "4120"
},
"account_number": "4100"
},
_("Indirect Income"): {
"is_group": 1,
"account_number": "4200"
}, },
_("Indirect Income"): {"is_group": 1, "account_number": "4200"},
"root_type": "Income", "root_type": "Income",
"account_number": "4000", "account_number": "4000"
}, },
_("Source of Funds (Liabilities)"): { _("Source of Funds (Liabilities)"): {
_("Current Liabilities"): { _("Current Liabilities"): {
_("Accounts Payable"): { _("Accounts Payable"): {
_("Creditors"): {"account_type": "Payable", "account_number": "2110"}, _("Creditors"): {
_("Payroll Payable"): {"account_number": "2120"}, "account_type": "Payable",
"account_number": "2100", "account_number": "2110"
},
_("Payroll Payable"): {
"account_number": "2120"
},
"account_number": "2100"
}, },
_("Stock Liabilities"): { _("Stock Liabilities"): {
_("Stock Received But Not Billed"): { _("Stock Received But Not Billed"): {
"account_type": "Stock Received But Not Billed", "account_type": "Stock Received But Not Billed",
"account_number": "2210", "account_number": "2210"
}, },
_("Asset Received But Not Billed"): { _("Asset Received But Not Billed"): {
"account_type": "Asset Received But Not Billed", "account_type": "Asset Received But Not Billed",
"account_number": "2211", "account_number": "2211"
}, },
"account_number": "2200", "account_number": "2200"
}, },
_("Duties and Taxes"): { _("Duties and Taxes"): {
_("TDS Payable"): {"account_number": "2310"},
"account_type": "Tax", "account_type": "Tax",
"is_group": 1, "is_group": 1,
"account_number": "2300", "account_number": "2300"
}, },
_("Loans (Liabilities)"): { _("Loans (Liabilities)"): {
_("Secured Loans"): {"account_number": "2410"}, _("Secured Loans"): {
_("Unsecured Loans"): {"account_number": "2420"}, "account_number": "2410"
_("Bank Overdraft Account"): {"account_number": "2430"},
"account_number": "2400",
}, },
"account_number": "2100-2400", _("Unsecured Loans"): {
"account_number": "2420"
},
_("Bank Overdraft Account"): {
"account_number": "2430"
},
"account_number": "2400"
},
"account_number": "2100-2400"
}, },
"root_type": "Liability", "root_type": "Liability",
"account_number": "2000", "account_number": "2000"
}, },
_("Equity"): { _("Equity"): {
_("Capital Stock"): {"account_type": "Equity", "account_number": "3100"}, _("Capital Stock"): {
_("Dividends Paid"): {"account_type": "Equity", "account_number": "3200"}, "account_type": "Equity",
_("Opening Balance Equity"): {"account_type": "Equity", "account_number": "3300"}, "account_number": "3100"
_("Retained Earnings"): {"account_type": "Equity", "account_number": "3400"},
"root_type": "Equity",
"account_number": "3000",
}, },
_("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,14 +1,12 @@
# 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 erpnext.stock import get_warehouse_account, get_company_default_inventory_account
from erpnext.accounts.doctype.account.account import merge_account, update_account_number from erpnext.accounts.doctype.account.account import update_account_number
from erpnext.stock import get_company_default_inventory_account, get_warehouse_account from erpnext.accounts.doctype.account.account import merge_account
class TestAccount(unittest.TestCase): class TestAccount(unittest.TestCase):
def test_rename_account(self): def test_rename_account(self):
@@ -20,9 +18,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")
@@ -31,12 +28,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 -")
@@ -76,7 +69,6 @@ class TestAccount(unittest.TestCase):
acc.account_name = "Accumulated Depreciation" acc.account_name = "Accumulated Depreciation"
acc.parent_account = "Fixed Assets - _TC" acc.parent_account = "Fixed Assets - _TC"
acc.company = "_Test Company" acc.company = "_Test Company"
acc.account_type = "Accumulated Depreciation"
acc.insert() acc.insert()
doc = frappe.get_doc("Account", "Securities and Deposits - _TC") doc = frappe.get_doc("Account", "Securities and Deposits - _TC")
@@ -84,9 +76,7 @@ class TestAccount(unittest.TestCase):
self.assertEqual(parent, "Securities and Deposits - _TC") self.assertEqual(parent, "Securities and Deposits - _TC")
merge_account( merge_account("Securities and Deposits - _TC", "Cash In Hand - _TC", doc.is_group, doc.root_type, doc.company)
"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
@@ -98,173 +88,29 @@ class TestAccount(unittest.TestCase):
doc = frappe.get_doc("Account", "Current Assets - _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",\
frappe.ValidationError, "Accumulated Depreciation - _TC", doc.is_group, doc.root_type, doc.company)
merge_account,
"Current Assets - _TC",
"Accumulated Depreciation - _TC",
doc.is_group,
doc.root_type,
doc.company,
)
doc = frappe.get_doc("Account", "Capital Stock - _TC") doc = frappe.get_doc("Account", "Capital Stock - _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",\
frappe.ValidationError, "Softwares - _TC", doc.is_group, doc.root_type, doc.company)
merge_account,
"Capital Stock - _TC",
"Softwares - _TC",
doc.is_group,
doc.root_type,
doc.company,
)
def test_account_sync(self): def test_account_sync(self):
frappe.local.flags.pop("ignore_root_company_validation", None) del frappe.local.flags["ignore_root_company_validation"]
acc = frappe.new_doc("Account") acc = frappe.new_doc("Account")
acc.account_name = "Test Sync Account" acc.account_name = "Test Sync Account"
acc.parent_account = "Temporary Accounts - _TC3" acc.parent_account = "Temporary Accounts - _TC3"
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")
def test_add_account_to_a_group(self): def _make_test_records(verbose):
frappe.db.set_value("Account", "Office Rent - _TC3", "is_group", 1)
acc = frappe.new_doc("Account")
acc.account_name = "Test Group Account"
acc.parent_account = "Office Rent - _TC3"
acc.company = "_Test Company 3"
self.assertRaises(frappe.ValidationError, acc.insert)
frappe.db.set_value("Account", "Office Rent - _TC3", "is_group", 0)
def test_account_rename_sync(self):
frappe.local.flags.pop("ignore_root_company_validation", None)
acc = frappe.new_doc("Account")
acc.account_name = "Test Rename Account"
acc.parent_account = "Temporary Accounts - _TC3"
acc.company = "_Test Company 3"
acc.insert()
# Rename account in parent company
update_account_number(acc.name, "Test Rename Sync Account", "1234")
# Check if renamed in children
self.assertTrue(
frappe.db.exists(
"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 - _TC4")
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC5")
def test_child_company_account_rename_sync(self):
frappe.local.flags.pop("ignore_root_company_validation", None)
acc = frappe.new_doc("Account")
acc.account_name = "Test Group Account"
acc.parent_account = "Temporary Accounts - _TC3"
acc.is_group = 1
acc.company = "_Test Company 3"
acc.insert()
self.assertTrue(
frappe.db.exists(
"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
acc_tc_5 = frappe.db.get_value(
"Account", {"account_name": "Test Group Account", "company": "_Test Company 5"}
)
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
frappe.db.set_value(
"Company", "_Test Company 5", "allow_account_creation_against_child_company", 1
)
update_account_number(acc_tc_5, "Test Modified Account")
self.assertTrue(
frappe.db.exists(
"Account", {"name": "Test Modified Account - _TC5", "company": "_Test Company 5"}
)
)
frappe.db.set_value(
"Company", "_Test Company 5", "allow_account_creation_against_child_company", 0
)
to_delete = [
"Test Group Account - _TC3",
"Test Group Account - _TC4",
"Test Modified Account - _TC5",
]
for doc in to_delete:
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 _make_test_records(verbose=None):
from frappe.test_runner import make_test_objects from frappe.test_runner import make_test_objects
accounts = [ accounts = [
@@ -273,16 +119,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],
@@ -291,45 +141,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, None, None],
["_Test Depreciations", "Expenses", 0, None, 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"],
["_Test Company with perpetual inventory", "TCP1"],
]:
test_objects = make_test_objects(
"Account",
[
{
"doctype": "Account", "doctype": "Account",
"account_name": account_name, "account_name": account_name,
"parent_account": parent_account + " - " + abbr, "parent_account": parent_account + " - " + abbr,
"company": company, "company": company,
"is_group": is_group, "is_group": is_group,
"account_type": account_type, "account_type": account_type,
"account_currency": currency, "account_currency": currency
} } for account_name, parent_account, is_group, account_type, currency in accounts])
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:
@@ -339,24 +182,18 @@ 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:
return account return account
else: else:
account = frappe.get_doc( account = frappe.get_doc(dict(
dict( doctype = "Account",
doctype="Account", account_name = kwargs.get('account_name'),
account_name=kwargs.get("account_name"), account_type = kwargs.get('account_type'),
account_type=kwargs.get("account_type"), parent_account = kwargs.get('parent_account'),
parent_account=kwargs.get("parent_account"), company = kwargs.get('company')
company=kwargs.get("company"), ))
account_currency=kwargs.get("account_currency"),
)
)
account.save() account.save()
return account.name return account.name

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Account Subtype', {
refresh: function() {
}
});

View File

@@ -0,0 +1,134 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:account_subtype",
"beta": 0,
"creation": "2018-10-25 15:46:08.054586",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "account_subtype",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Account Subtype",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-10-25 15:47:03.841390",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account Subtype",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
}

View File

@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class AccountSubtype(Document):
pass

View File

@@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Account Subtype", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Account Subtype
() => frappe.tests.make('Account Subtype', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import unittest
class TestAccountSubtype(unittest.TestCase):
pass

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Account Type', {
refresh: function() {
}
});

View File

@@ -0,0 +1,134 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:account_type",
"beta": 0,
"creation": "2018-10-25 15:45:45.789963",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "account_type",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Account Type",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-10-25 15:46:51.042604",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account Type",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
}

View File

@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class AccountType(Document):
pass

View File

@@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Account Type", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Account Type
() => frappe.tests.make('Account Type', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import unittest
class TestAccountType(unittest.TestCase):
pass

View File

@@ -2,11 +2,12 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Accounting Dimension', { frappe.ui.form.on('Accounting Dimension', {
refresh: function(frm) { refresh: function(frm) {
frm.set_query('document_type', () => { frm.set_query('document_type', () => {
let invalid_doctypes = frappe.model.core_doctypes_list; let invalid_doctypes = frappe.model.core_doctypes_list;
invalid_doctypes.push('Accounting Dimension', 'Project', invalid_doctypes.push('Accounting Dimension', 'Project',
'Cost Center', 'Accounting Dimension Detail', 'Company'); 'Cost Center', 'Accounting Dimension Detail');
return { return {
filters: { filters: {
@@ -47,6 +48,12 @@ frappe.ui.form.on('Accounting Dimension', {
frm.set_value('label', frm.doc.document_type); frm.set_value('label', frm.doc.document_type);
frm.set_value('fieldname', frappe.model.scrub(frm.doc.document_type)); frm.set_value('fieldname', frappe.model.scrub(frm.doc.document_type));
if (frm.is_new()){
let row = frappe.model.add_child(frm.doc, "Accounting Dimension Detail", "dimension_defaults");
row.reference_document = frm.doc.document_type;
frm.refresh_fields("dimension_defaults");
}
frappe.db.get_value('Accounting Dimension', {'document_type': frm.doc.document_type}, 'document_type', (r) => { frappe.db.get_value('Accounting Dimension', {'document_type': frm.doc.document_type}, 'document_type', (r) => {
if (r && r.document_type) { if (r && r.document_type) {
frm.set_df_property('document_type', 'description', "Document type is already set as dimension"); frm.set_df_property('document_type', 'description', "Document type is already set as dimension");

View File

@@ -1,5 +1,4 @@
{ {
"actions": [],
"autoname": "field:label", "autoname": "field:label",
"creation": "2019-05-04 18:13:37.002352", "creation": "2019-05-04 18:13:37.002352",
"doctype": "DocType", "doctype": "DocType",
@@ -30,7 +29,6 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Reference Document Type", "label": "Reference Document Type",
"options": "DocType", "options": "DocType",
"read_only_depends_on": "eval:!doc.__islocal",
"reqd": 1 "reqd": 1
}, },
{ {
@@ -48,8 +46,7 @@
"options": "Accounting Dimension Detail" "options": "Accounting Dimension Detail"
} }
], ],
"links": [], "modified": "2019-07-17 16:49:31.134385",
"modified": "2021-02-08 16:37:53.936656",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounting Dimension", "name": "Accounting Dimension",
@@ -66,20 +63,9 @@
"role": "System Manager", "role": "System Manager",
"share": 1, "share": 1,
"write": 1 "write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1,
"write": 1
} }
], ],
"quick_entry": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "ASC", "sort_order": "ASC",
"track_changes": 1 "track_changes": 1

View File

@@ -1,62 +1,45 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals
import json
import frappe import frappe
from frappe import _, scrub from frappe import _
from frappe.custom.doctype.custom_field.custom_field import create_custom_field import json
from frappe.model import core_doctypes_list
from frappe.model.document import Document from frappe.model.document import Document
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe import scrub
from frappe.utils import cstr from frappe.utils import cstr
from frappe.utils.background_jobs import enqueue
from frappe.model import core_doctypes_list
class AccountingDimension(Document): class AccountingDimension(Document):
def before_insert(self): def before_insert(self):
self.set_fieldname_and_label() self.set_fieldname_and_label()
def validate(self): def validate(self):
if self.document_type in core_doctypes_list + ( if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project',
"Accounting Dimension", 'Cost Center', 'Accounting Dimension Detail') :
"Project",
"Cost Center",
"Accounting Dimension Detail",
"Company",
"Account",
):
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type) msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
frappe.throw(msg) frappe.throw(msg)
exists = frappe.db.get_value( exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name'])
"Accounting Dimension", {"document_type": self.document_type}, ["name"]
)
if exists and self.is_new(): if exists and self.is_new():
frappe.throw(_("Document Type already used as a dimension")) frappe.throw("Document Type already used as a dimension")
if not self.is_new():
self.validate_document_type_change()
def validate_document_type_change(self):
doctype_before_save = frappe.db.get_value("Accounting Dimension", self.name, "document_type")
if doctype_before_save != self.document_type:
message = _("Cannot change Reference Document Type.")
message += _("Please create a new Accounting Dimension if required.")
frappe.throw(message)
def after_insert(self): def after_insert(self):
if frappe.flags.in_test: if frappe.flags.in_test:
make_dimension_in_accounting_doctypes(doc=self) make_dimension_in_accounting_doctypes(doc=self)
else: else:
frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self, queue="long") frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self)
def on_trash(self): def on_trash(self):
if frappe.flags.in_test: if frappe.flags.in_test:
delete_accounting_dimension(doc=self) delete_accounting_dimension(doc=self)
else: else:
frappe.enqueue(delete_accounting_dimension, doc=self, queue="long") frappe.enqueue(delete_accounting_dimension, doc=self)
def set_fieldname_and_label(self): def set_fieldname_and_label(self):
if not self.label: if not self.label:
@@ -65,23 +48,17 @@ class AccountingDimension(Document):
if not self.fieldname: if not self.fieldname:
self.fieldname = scrub(self.label) self.fieldname = scrub(self.label)
def on_update(self): def make_dimension_in_accounting_doctypes(doc):
frappe.flags.accounting_dimensions = None
def make_dimension_in_accounting_doctypes(doc, doclist=None):
if not doclist:
doclist = get_doctypes_with_dimensions() doclist = get_doctypes_with_dimensions()
doc_count = len(get_accounting_dimensions()) doc_count = len(get_accounting_dimensions())
count = 0 count = 0
for doctype in doclist: for doctype in doclist:
if (doc_count + 1) % 2 == 0: if (doc_count + 1) % 2 == 0:
insert_after_field = "dimension_col_break" insert_after_field = 'dimension_col_break'
else: else:
insert_after_field = "accounting_dimensions_section" insert_after_field = 'accounting_dimensions_section'
df = { df = {
"fieldname": doc.fieldname, "fieldname": doc.fieldname,
@@ -89,33 +66,26 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
"fieldtype": "Link", "fieldtype": "Link",
"options": doc.document_type, "options": doc.document_type,
"insert_after": insert_after_field, "insert_after": insert_after_field,
"owner": "Administrator", "owner": "Administrator"
} }
meta = frappe.get_meta(doctype, cached=False)
fieldnames = [d.fieldname for d in meta.get("fields")]
if df["fieldname"] not in fieldnames:
if doctype == "Budget": if doctype == "Budget":
add_dimension_to_budget_doctype(df.copy(), doc) add_dimension_to_budget_doctype(df, doc)
else: else:
create_custom_field(doctype, df, ignore_validate=True) create_custom_field(doctype, df)
count += 1 count += 1
frappe.publish_progress(count * 100 / len(doclist), title=_("Creating Dimensions...")) frappe.publish_progress(count*100/len(doclist), title = _("Creating Dimensions..."))
frappe.clear_cache(doctype=doctype) frappe.clear_cache(doctype=doctype)
def add_dimension_to_budget_doctype(df, doc): def add_dimension_to_budget_doctype(df, doc):
df.update( df.update({
{
"insert_after": "cost_center", "insert_after": "cost_center",
"depends_on": "eval:doc.budget_against == '{0}'".format(doc.document_type), "depends_on": "eval:doc.budget_against == '{0}'".format(doc.document_type)
} })
)
create_custom_field("Budget", df, ignore_validate=True) create_custom_field("Budget", df)
property_setter = frappe.db.exists("Property Setter", "Budget-budget_against-options") property_setter = frappe.db.exists("Property Setter", "Budget-budget_against-options")
@@ -124,44 +94,36 @@ def add_dimension_to_budget_doctype(df, doc):
property_setter_doc.value = property_setter_doc.value + "\n" + doc.document_type property_setter_doc.value = property_setter_doc.value + "\n" + doc.document_type
property_setter_doc.save() property_setter_doc.save()
frappe.clear_cache(doctype="Budget") frappe.clear_cache(doctype='Budget')
else: else:
frappe.get_doc( frappe.get_doc({
{
"doctype": "Property Setter", "doctype": "Property Setter",
"doctype_or_field": "DocField", "doctype_or_field": "DocField",
"doc_type": "Budget", "doc_type": "Budget",
"field_name": "budget_against", "field_name": "budget_against",
"property": "options", "property": "options",
"property_type": "Text", "property_type": "Text",
"value": "\nCost Center\nProject\n" + doc.document_type, "value": "\nCost Center\nProject\n" + doc.document_type
} }).insert(ignore_permissions=True)
).insert(ignore_permissions=True)
def delete_accounting_dimension(doc): def delete_accounting_dimension(doc):
doclist = get_doctypes_with_dimensions() doclist = get_doctypes_with_dimensions()
frappe.db.sql( frappe.db.sql("""
"""
DELETE FROM `tabCustom Field` DELETE FROM `tabCustom Field`
WHERE fieldname = %s WHERE fieldname = %s
AND dt IN (%s)""" AND dt IN (%s)""" % #nosec
% ("%s", ", ".join(["%s"] * len(doclist))), # nosec ('%s', ', '.join(['%s']* len(doclist))), tuple([doc.fieldname] + doclist))
tuple([doc.fieldname] + doclist),
)
frappe.db.sql( frappe.db.sql("""
"""
DELETE FROM `tabProperty Setter` DELETE FROM `tabProperty Setter`
WHERE field_name = %s WHERE field_name = %s
AND doc_type IN (%s)""" AND doc_type IN (%s)""" % #nosec
% ("%s", ", ".join(["%s"] * len(doclist))), # nosec ('%s', ', '.join(['%s']* len(doclist))), tuple([doc.fieldname] + doclist))
tuple([doc.fieldname] + doclist),
)
budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options") budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options")
value_list = budget_against_property.value.split("\n")[3:] value_list = budget_against_property.value.split('\n')[3:]
if doc.document_type in value_list: if doc.document_type in value_list:
value_list.remove(doc.document_type) value_list.remove(doc.document_type)
@@ -172,7 +134,6 @@ def delete_accounting_dimension(doc):
for doctype in doclist: for doctype in doclist:
frappe.clear_cache(doctype=doctype) frappe.clear_cache(doctype=doctype)
@frappe.whitelist() @frappe.whitelist()
def disable_dimension(doc): def disable_dimension(doc):
if frappe.flags.in_test: if frappe.flags.in_test:
@@ -180,11 +141,10 @@ def disable_dimension(doc):
else: else:
frappe.enqueue(toggle_disabling, doc=doc) frappe.enqueue(toggle_disabling, doc=doc)
def toggle_disabling(doc): def toggle_disabling(doc):
doc = json.loads(doc) doc = json.loads(doc)
if doc.get("disabled"): if doc.get('disabled'):
df = {"read_only": 1} df = {"read_only": 1}
else: else:
df = {"read_only": 0} df = {"read_only": 0}
@@ -192,7 +152,7 @@ def toggle_disabling(doc):
doclist = get_doctypes_with_dimensions() doclist = get_doctypes_with_dimensions()
for doctype in doclist: for doctype in doclist:
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": doc.get("fieldname")}) field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": doc.get('fieldname')})
if field: if field:
custom_field = frappe.get_doc("Custom Field", field) custom_field = frappe.get_doc("Custom Field", field)
custom_field.update(df) custom_field.update(df)
@@ -200,86 +160,46 @@ def toggle_disabling(doc):
frappe.clear_cache(doctype=doctype) frappe.clear_cache(doctype=doctype)
def get_doctypes_with_dimensions(): def get_doctypes_with_dimensions():
return frappe.get_hooks("accounting_dimension_doctypes") doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset",
"Expense Claim", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Sales Invoice Item", "Purchase Invoice Item",
"Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item",
"Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
"Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation",
"Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription",
"Subscription Plan"]
return doclist
def get_accounting_dimensions(as_list=True, filters=None): def get_accounting_dimensions(as_list=True):
accounting_dimensions = frappe.get_all("Accounting Dimension", fields=["label", "fieldname", "disabled"])
if not filters:
filters = {"disabled": 0}
if frappe.flags.accounting_dimensions is None:
frappe.flags.accounting_dimensions = frappe.get_all(
"Accounting Dimension",
fields=["label", "fieldname", "disabled", "document_type"],
filters=filters,
)
if as_list: if as_list:
return [d.fieldname for d in frappe.flags.accounting_dimensions] return [d.fieldname for d in accounting_dimensions]
else: else:
return frappe.flags.accounting_dimensions return accounting_dimensions
def get_checks_for_pl_and_bs_accounts(): def get_checks_for_pl_and_bs_accounts():
dimensions = frappe.db.sql( dimensions = frappe.db.sql("""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
"""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
WHERE p.name = c.parent""", WHERE p.name = c.parent""", as_dict=1)
as_dict=1,
)
return dimensions return dimensions
def get_dimension_with_children(doctype, dimensions):
if isinstance(dimensions, str):
dimensions = [dimensions]
all_dimensions = []
for dimension in dimensions:
lft, rgt = frappe.db.get_value(doctype, dimension, ["lft", "rgt"])
children = frappe.get_all(
doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft"
)
all_dimensions += [c.name for c in children]
return all_dimensions
@frappe.whitelist() @frappe.whitelist()
def get_dimensions(with_cost_center_and_project=False): def get_dimension_filters():
dimension_filters = frappe.db.sql( dimension_filters = frappe.db.sql("""
"""
SELECT label, fieldname, document_type SELECT label, fieldname, document_type
FROM `tabAccounting Dimension` FROM `tabAccounting Dimension`
WHERE disabled = 0 WHERE disabled = 0
""", """, as_dict=1)
as_dict=1,
)
default_dimensions = frappe.db.sql( default_dimensions = frappe.db.sql("""SELECT parent, company, default_dimension
"""SELECT p.fieldname, c.company, c.default_dimension FROM `tabAccounting Dimension Detail`""", as_dict=1)
FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p
WHERE c.parent = p.name""",
as_dict=1,
)
if with_cost_center_and_project:
dimension_filters.extend(
[
{"fieldname": "cost_center", "document_type": "Cost Center"},
{"fieldname": "project", "document_type": "Project"},
]
)
default_dimensions_map = {} default_dimensions_map = {}
for dimension in default_dimensions: for dimension in default_dimensions:
default_dimensions_map.setdefault(dimension.company, {}) default_dimensions_map.setdefault(dimension['company'], {})
default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension default_dimensions_map[dimension['company']][dimension['parent']] = dimension['default_dimension']
return dimension_filters, default_dimensions_map return dimension_filters, default_dimensions_map

View File

@@ -1,27 +1,53 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
from __future__ import unicode_literals
import unittest
import frappe import frappe
import unittest
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
test_dependencies = ["Cost Center", "Location", "Warehouse", "Department"] from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import delete_accounting_dimension
class TestAccountingDimension(unittest.TestCase): class TestAccountingDimension(unittest.TestCase):
def setUp(self): def setUp(self):
create_dimension() frappe.set_user("Administrator")
if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
dimension = frappe.get_doc({
"doctype": "Accounting Dimension",
"document_type": "Department",
}).insert()
else:
dimension1 = frappe.get_doc("Accounting Dimension", "Department")
dimension1.disabled = 0
dimension1.save()
if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
dimension1 = frappe.get_doc({
"doctype": "Accounting Dimension",
"document_type": "Location",
})
dimension1.append("dimension_defaults", {
"company": "_Test Company",
"reference_document": "Location",
"default_dimension": "Block 1",
"mandatory_for_bs": 1
})
dimension1.insert()
dimension1.save()
else:
dimension1 = frappe.get_doc("Accounting Dimension", "Location")
dimension1.disabled = 0
dimension1.save()
def test_dimension_against_sales_invoice(self): def test_dimension_against_sales_invoice(self):
si = create_sales_invoice(do_not_save=1) si = create_sales_invoice(do_not_save=1)
si.location = "Block 1" si.location = "Block 1"
si.append( si.append("items", {
"items",
{
"item_code": "_Test Item", "item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC", "warehouse": "_Test Warehouse - _TC",
"qty": 1, "qty": 1,
@@ -30,16 +56,15 @@ class TestAccountingDimension(unittest.TestCase):
"expense_account": "Cost of Goods Sold - _TC", "expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"department": "_Test Department - _TC", "department": "_Test Department - _TC",
"location": "Block 1", "location": "Block 1"
}, })
)
si.save() si.save()
si.submit() si.submit()
gle = frappe.get_doc("GL Entry", {"voucher_no": si.name, "account": "Sales - _TC"}) gle = frappe.get_doc("GL Entry", {"voucher_no": si.name, "account": "Sales - _TC"})
self.assertEqual(gle.get("department"), "_Test Department - _TC") self.assertEqual(gle.get('department'), "_Test Department - _TC")
def test_dimension_against_journal_entry(self): def test_dimension_against_journal_entry(self):
je = make_journal_entry("Sales - _TC", "Sales Expenses - _TC", 500, save=False) je = make_journal_entry("Sales - _TC", "Sales Expenses - _TC", 500, save=False)
@@ -54,14 +79,12 @@ class TestAccountingDimension(unittest.TestCase):
gle = frappe.get_doc("GL Entry", {"voucher_no": je.name, "account": "Sales - _TC"}) gle = frappe.get_doc("GL Entry", {"voucher_no": je.name, "account": "Sales - _TC"})
gle1 = frappe.get_doc("GL Entry", {"voucher_no": je.name, "account": "Sales Expenses - _TC"}) gle1 = frappe.get_doc("GL Entry", {"voucher_no": je.name, "account": "Sales Expenses - _TC"})
self.assertEqual(gle.get("department"), "_Test Department - _TC") self.assertEqual(gle.get('department'), "_Test Department - _TC")
self.assertEqual(gle1.get("department"), "_Test Department - _TC") self.assertEqual(gle1.get('department'), "_Test Department - _TC")
def test_mandatory(self): def test_mandatory(self):
si = create_sales_invoice(do_not_save=1) si = create_sales_invoice(do_not_save=1)
si.append( si.append("items", {
"items",
{
"item_code": "_Test Item", "item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC", "warehouse": "_Test Warehouse - _TC",
"qty": 1, "qty": 1,
@@ -69,9 +92,8 @@ class TestAccountingDimension(unittest.TestCase):
"income_account": "Sales - _TC", "income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC", "expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"location": "", "location": ""
}, })
)
si.save() si.save()
self.assertRaises(frappe.ValidationError, si.submit) self.assertRaises(frappe.ValidationError, si.submit)
@@ -80,47 +102,6 @@ class TestAccountingDimension(unittest.TestCase):
disable_dimension() disable_dimension()
def create_dimension():
frappe.set_user("Administrator")
if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
frappe.get_doc(
{
"doctype": "Accounting Dimension",
"document_type": "Department",
}
).insert()
else:
dimension = frappe.get_doc("Accounting Dimension", "Department")
dimension.disabled = 0
dimension.save()
if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
dimension1 = frappe.get_doc(
{
"doctype": "Accounting Dimension",
"document_type": "Location",
}
)
dimension1.append(
"dimension_defaults",
{
"company": "_Test Company",
"reference_document": "Location",
"default_dimension": "Block 1",
"mandatory_for_bs": 1,
},
)
dimension1.insert()
dimension1.save()
else:
dimension1 = frappe.get_doc("Accounting Dimension", "Location")
dimension1.disabled = 0
dimension1.save()
def disable_dimension(): def disable_dimension():
dimension1 = frappe.get_doc("Accounting Dimension", "Department") dimension1 = frappe.get_doc("Accounting Dimension", "Department")
dimension1.disabled = 1 dimension1.disabled = 1
@@ -129,3 +110,5 @@ def disable_dimension():
dimension2 = frappe.get_doc("Accounting Dimension", "Location") dimension2 = frappe.get_doc("Accounting Dimension", "Location")
dimension2.disabled = 1 dimension2.disabled = 1
dimension2.save() dimension2.save()

View File

@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals
# import frappe # import frappe
from frappe.model.document import Document from frappe.model.document import Document
class AccountingDimensionDetail(Document): class AccountingDimensionDetail(Document):
pass pass

View File

@@ -1,82 +0,0 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Accounting Dimension Filter', {
refresh: function(frm, cdt, cdn) {
if (frm.doc.accounting_dimension) {
frm.set_df_property('dimensions', 'label', frm.doc.accounting_dimension, cdn, 'dimension_value');
}
let help_content =
`<table class="table table-bordered" style="background-color: var(--scrollbar-track-color);">
<tr><td>
<p>
<i class="fa fa-hand-right"></i>
{{__('Note: On checking Is Mandatory the accounting dimension will become mandatory against that specific account for all accounting transactions')}}
</p>
</td></tr>
</table>`;
frm.set_df_property('dimension_filter_help', 'options', help_content);
},
onload: function(frm) {
frm.set_query('applicable_on_account', 'accounts', function() {
return {
filters: {
'company': frm.doc.company
}
};
});
frappe.db.get_list('Accounting Dimension',
{fields: ['document_type']}).then((res) => {
let options = ['Cost Center', 'Project'];
res.forEach((dimension) => {
options.push(dimension.document_type);
});
frm.set_df_property('accounting_dimension', 'options', options);
});
frm.trigger('setup_filters');
},
setup_filters: function(frm) {
let filters = {};
if (frm.doc.accounting_dimension) {
frappe.model.with_doctype(frm.doc.accounting_dimension, function() {
if (frappe.model.is_tree(frm.doc.accounting_dimension)) {
filters['is_group'] = 0;
}
if (frappe.meta.has_field(frm.doc.accounting_dimension, 'company')) {
filters['company'] = frm.doc.company;
}
frm.set_query('dimension_value', 'dimensions', function() {
return {
filters: filters
};
});
});
}
},
accounting_dimension: function(frm) {
frm.clear_table("dimensions");
let row = frm.add_child("dimensions");
row.accounting_dimension = frm.doc.accounting_dimension;
frm.refresh_field("dimensions");
frm.trigger('setup_filters');
},
});
frappe.ui.form.on('Allowed Dimension', {
dimensions_add: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
row.accounting_dimension = frm.doc.accounting_dimension;
frm.refresh_field("dimensions");
}
});

View File

@@ -1,158 +0,0 @@
{
"actions": [],
"autoname": "format:{accounting_dimension}-{#####}",
"creation": "2020-11-08 18:28:11.906146",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"accounting_dimension",
"disabled",
"column_break_2",
"company",
"allow_or_restrict",
"section_break_4",
"accounts",
"column_break_6",
"dimensions",
"section_break_10",
"dimension_filter_help"
],
"fields": [
{
"fieldname": "accounting_dimension",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Accounting Dimension",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "section_break_4",
"fieldtype": "Section Break",
"hide_border": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "column_break_6",
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "allow_or_restrict",
"fieldtype": "Select",
"label": "Allow Or Restrict Dimension",
"options": "Allow\nRestrict",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "accounts",
"fieldtype": "Table",
"label": "Applicable On Account",
"options": "Applicable On Account",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "eval:doc.accounting_dimension",
"fieldname": "dimensions",
"fieldtype": "Table",
"label": "Applicable Dimension",
"options": "Allowed Dimension",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "dimension_filter_help",
"fieldtype": "HTML",
"label": "Dimension Filter Help",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"show_days": 1,
"show_seconds": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-02-03 12:04:58.678402",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting Dimension Filter",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -1,79 +0,0 @@
# Copyright, (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _, scrub
from frappe.model.document import Document
class AccountingDimensionFilter(Document):
def validate(self):
self.validate_applicable_accounts()
def validate_applicable_accounts(self):
accounts = frappe.db.sql(
"""
SELECT a.applicable_on_account as account
FROM `tabApplicable On Account` a, `tabAccounting Dimension Filter` d
WHERE d.name = a.parent
and d.name != %s
and d.accounting_dimension = %s
""",
(self.name, self.accounting_dimension),
as_dict=1,
)
account_list = [d.account for d in accounts]
for account in self.get("accounts"):
if account.applicable_on_account in account_list:
frappe.throw(
_("Row {0}: {1} account already applied for Accounting Dimension {2}").format(
account.idx,
frappe.bold(account.applicable_on_account),
frappe.bold(self.accounting_dimension),
)
)
def get_dimension_filter_map():
filters = frappe.db.sql(
"""
SELECT
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
p.allow_or_restrict, a.is_mandatory
FROM
`tabApplicable On Account` a, `tabAllowed Dimension` d,
`tabAccounting Dimension Filter` p
WHERE
p.name = a.parent
AND p.disabled = 0
AND p.name = d.parent
""",
as_dict=1,
)
dimension_filter_map = {}
for f in filters:
f.fieldname = scrub(f.accounting_dimension)
build_map(
dimension_filter_map,
f.fieldname,
f.applicable_on_account,
f.dimension_value,
f.allow_or_restrict,
f.is_mandatory,
)
return dimension_filter_map
def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):
map_object.setdefault(
(dimension, account),
{"allowed_dimensions": [], "is_mandatory": is_mandatory, "allow_or_restrict": allow_or_restrict},
)
map_object[(dimension, account)]["allowed_dimensions"].append(filter_value)

View File

@@ -1,106 +0,0 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
create_dimension,
disable_dimension,
)
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
test_dependencies = ["Location", "Cost Center", "Department"]
class TestAccountingDimensionFilter(unittest.TestCase):
def setUp(self):
create_dimension()
create_accounting_dimension_filter()
self.invoice_list = []
def test_allowed_dimension_validation(self):
si = create_sales_invoice(do_not_save=1)
si.items[0].cost_center = "Main - _TC"
si.department = "Accounts - _TC"
si.location = "Block 1"
si.save()
self.assertRaises(InvalidAccountDimensionError, si.submit)
self.invoice_list.append(si)
def test_mandatory_dimension_validation(self):
si = create_sales_invoice(do_not_save=1)
si.department = ""
si.location = "Block 1"
# Test with no department for Sales Account
si.items[0].department = ""
si.items[0].cost_center = "_Test Cost Center 2 - _TC"
si.save()
self.assertRaises(MandatoryAccountDimensionError, si.submit)
self.invoice_list.append(si)
def tearDown(self):
disable_dimension_filter()
disable_dimension()
for si in self.invoice_list:
si.load_from_db()
if si.docstatus == 1:
si.cancel()
def create_accounting_dimension_filter():
if not frappe.db.get_value(
"Accounting Dimension Filter", {"accounting_dimension": "Cost Center"}
):
frappe.get_doc(
{
"doctype": "Accounting Dimension Filter",
"accounting_dimension": "Cost Center",
"allow_or_restrict": "Allow",
"company": "_Test Company",
"accounts": [
{
"applicable_on_account": "Sales - _TC",
}
],
"dimensions": [
{"accounting_dimension": "Cost Center", "dimension_value": "_Test Cost Center 2 - _TC"}
],
}
).insert()
else:
doc = frappe.get_doc("Accounting Dimension Filter", {"accounting_dimension": "Cost Center"})
doc.disabled = 0
doc.save()
if not frappe.db.get_value("Accounting Dimension Filter", {"accounting_dimension": "Department"}):
frappe.get_doc(
{
"doctype": "Accounting Dimension Filter",
"accounting_dimension": "Department",
"allow_or_restrict": "Allow",
"company": "_Test Company",
"accounts": [{"applicable_on_account": "Sales - _TC", "is_mandatory": 1}],
"dimensions": [{"accounting_dimension": "Department", "dimension_value": "Accounts - _TC"}],
}
).insert()
else:
doc = frappe.get_doc("Accounting Dimension Filter", {"accounting_dimension": "Department"})
doc.disabled = 0
doc.save()
def disable_dimension_filter():
doc = frappe.get_doc("Accounting Dimension Filter", {"accounting_dimension": "Cost Center"})
doc.disabled = 1
doc.save()
doc = frappe.get_doc("Accounting Dimension Filter", {"accounting_dimension": "Department"})
doc.disabled = 1
doc.save()

View File

@@ -1,15 +1,13 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals
import frappe import frappe
from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe import _
class OverlapError(frappe.ValidationError): pass
class OverlapError(frappe.ValidationError):
pass
class AccountingPeriod(Document): class AccountingPeriod(Document):
def validate(self): def validate(self):
@@ -19,12 +17,11 @@ class AccountingPeriod(Document):
self.bootstrap_doctypes_for_closing() self.bootstrap_doctypes_for_closing()
def autoname(self): def autoname(self):
company_abbr = frappe.get_cached_value("Company", self.company, "abbr") company_abbr = frappe.get_cached_value('Company', self.company, "abbr")
self.name = " - ".join([self.period_name, company_abbr]) self.name = " - ".join([self.period_name, company_abbr])
def validate_overlap(self): def validate_overlap(self):
existing_accounting_period = frappe.db.sql( existing_accounting_period = frappe.db.sql("""select name from `tabAccounting Period`
"""select name from `tabAccounting Period`
where ( where (
(%(start_date)s between start_date and end_date) (%(start_date)s between start_date and end_date)
or (%(end_date)s between start_date and end_date) or (%(end_date)s between start_date and end_date)
@@ -35,29 +32,17 @@ class AccountingPeriod(Document):
"start_date": self.start_date, "start_date": self.start_date,
"end_date": self.end_date, "end_date": self.end_date,
"name": self.name, "name": self.name,
"company": self.company, "company": self.company
}, }, as_dict=True)
as_dict=True,
)
if len(existing_accounting_period) > 0: if len(existing_accounting_period) > 0:
frappe.throw( frappe.throw(_("Accounting Period overlaps with {0}")
_("Accounting Period overlaps with {0}").format(existing_accounting_period[0].get("name")), .format(existing_accounting_period[0].get("name")), OverlapError)
OverlapError,
)
@frappe.whitelist()
def get_doctypes_for_closing(self): def get_doctypes_for_closing(self):
docs_for_closing = [] docs_for_closing = []
doctypes = [ doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", "Bank Reconciliation",
"Sales Invoice", "Asset", "Purchase Order", "Sales Order", "Leave Application", "Leave Allocation", "Stock Entry"]
"Purchase Invoice",
"Journal Entry",
"Payroll Entry",
"Bank Clearance",
"Asset",
"Stock Entry",
]
closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes] closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes]
for closed_doctype in closed_doctypes: for closed_doctype in closed_doctypes:
docs_for_closing.append(closed_doctype) docs_for_closing.append(closed_doctype)
@@ -67,7 +52,7 @@ class AccountingPeriod(Document):
def bootstrap_doctypes_for_closing(self): def bootstrap_doctypes_for_closing(self):
if len(self.closed_documents) == 0: if len(self.closed_documents) == 0:
for doctype_for_closing in self.get_doctypes_for_closing(): for doctype_for_closing in self.get_doctypes_for_closing():
self.append( self.append('closed_documents', {
"closed_documents", "document_type": doctype_for_closing.document_type,
{"document_type": doctype_for_closing.document_type, "closed": doctype_for_closing.closed}, "closed": doctype_for_closing.closed
) })

View File

@@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Accounting Period", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Accounting Period
() => frappe.tests.make('Accounting Period', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,47 +1,36 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
from __future__ import unicode_literals
import unittest
import frappe import frappe
from frappe.utils import add_months, nowdate import unittest
from frappe.utils import nowdate, add_months
from erpnext.accounts.general_ledger import ClosedAccountingPeriod
from erpnext.accounts.doctype.accounting_period.accounting_period import OverlapError from erpnext.accounts.doctype.accounting_period.accounting_period import OverlapError
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.general_ledger import ClosedAccountingPeriod
test_dependencies = ["Item"]
class TestAccountingPeriod(unittest.TestCase): class TestAccountingPeriod(unittest.TestCase):
def test_overlap(self): def test_overlap(self):
ap1 = create_accounting_period( ap1 = create_accounting_period(start_date = "2018-04-01",
start_date="2018-04-01", end_date="2018-06-30", company="Wind Power LLC" end_date = "2018-06-30", company = "Wind Power LLC")
)
ap1.save() ap1.save()
ap2 = create_accounting_period( ap2 = create_accounting_period(start_date = "2018-06-30",
start_date="2018-06-30", end_date = "2018-07-10", company = "Wind Power LLC", period_name = "Test Accounting Period 1")
end_date="2018-07-10",
company="Wind Power LLC",
period_name="Test Accounting Period 1",
)
self.assertRaises(OverlapError, ap2.save) self.assertRaises(OverlapError, ap2.save)
def test_accounting_period(self): def test_accounting_period(self):
ap1 = create_accounting_period(period_name="Test Accounting Period 2") ap1 = create_accounting_period(period_name = "Test Accounting Period 2")
ap1.save() ap1.save()
doc = create_sales_invoice( doc = create_sales_invoice(do_not_submit=1, cost_center = "_Test Company - _TC", warehouse = "Stores - _TC")
do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC"
)
self.assertRaises(ClosedAccountingPeriod, doc.submit) self.assertRaises(ClosedAccountingPeriod, doc.submit)
def tearDown(self): def tearDown(self):
for d in frappe.get_all("Accounting Period"): for d in frappe.get_all("Accounting Period"):
frappe.delete_doc("Accounting Period", d.name) frappe.delete_doc("Accounting Period", d.name)
def create_accounting_period(**args): def create_accounting_period(**args):
args = frappe._dict(args) args = frappe._dict(args)
@@ -49,7 +38,9 @@ def create_accounting_period(**args):
accounting_period.start_date = args.start_date or nowdate() accounting_period.start_date = args.start_date or nowdate()
accounting_period.end_date = args.end_date or add_months(nowdate(), 1) accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
accounting_period.company = args.company or "_Test Company" accounting_period.company = args.company or "_Test Company"
accounting_period.period_name = args.period_name or "_Test_Period_Name_1" accounting_period.period_name =args.period_name or "_Test_Period_Name_1"
accounting_period.append("closed_documents", {"document_type": "Sales Invoice", "closed": 1}) accounting_period.append("closed_documents", {
"document_type": 'Sales Invoice', "closed": 1
})
return accounting_period return accounting_period

View File

@@ -6,46 +6,3 @@ frappe.ui.form.on('Accounts Settings', {
} }
}); });
frappe.tour['Accounts Settings'] = [
{
fieldname: "acc_frozen_upto",
title: "Accounts Frozen Upto",
description: __("Freeze accounting transactions up to specified date, nobody can make/modify entry except the specified Role."),
},
{
fieldname: "frozen_accounts_modifier",
title: "Role Allowed to Set Frozen Accounts & Edit Frozen Entries",
description: __("Users with this Role are allowed to set frozen accounts and create/modify accounting entries against frozen accounts.")
},
{
fieldname: "determine_address_tax_category_from",
title: "Determine Address Tax Category From",
description: __("Tax category can be set on Addresses. An address can be Shipping or Billing address. Set which addres to select when applying Tax Category.")
},
{
fieldname: "over_billing_allowance",
title: "Over Billing Allowance Percentage",
description: __("The percentage by which you can overbill transactions. For example, if the order value is $100 for an Item and percentage here is set as 10% then you are allowed to bill for $110.")
},
{
fieldname: "credit_controller",
title: "Credit Controller",
description: __("Select the role that is allowed to submit transactions that exceed credit limits set. The credit limit can be set in the Customer form.")
},
{
fieldname: "make_payment_via_journal_entry",
title: "Make Payment via Journal Entry",
description: __("When checked, if user proceeds to make payment from an invoice, the system will open a Journal Entry instead of a Payment Entry.")
},
{
fieldname: "unlink_payment_on_cancellation_of_invoice",
title: "Unlink Payment on Cancellation of Invoice",
description: __("If checked, system will unlink the payment against the respective invoice.")
},
{
fieldname: "unlink_advance_payment_on_cancelation_of_order",
title: "Unlink Advance Payment on Cancellation of Order",
description: __("Similar to the previous option, this unlinks any advance payments made against Purchase/Sales Orders.")
}
];

View File

@@ -1,5 +1,4 @@
{ {
"actions": [],
"creation": "2013-06-24 15:49:57", "creation": "2013-06-24 15:49:57",
"description": "Settings for Accounts", "description": "Settings for Accounts",
"doctype": "DocType", "doctype": "DocType",
@@ -7,36 +6,21 @@
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"accounts_transactions_settings_section", "auto_accounting_for_stock",
"over_billing_allowance",
"role_allowed_to_over_bill",
"make_payment_via_journal_entry",
"column_break_11",
"check_supplier_invoice_uniqueness",
"unlink_payment_on_cancellation_of_invoice",
"automatically_fetch_payment_terms",
"delete_linked_ledger_entries",
"book_asset_depreciation_entry_automatically",
"unlink_advance_payment_on_cancelation_of_order",
"enable_common_party_accounting",
"allow_multi_currency_invoices_against_single_party_account",
"post_change_gl_entries",
"enable_discount_accounting",
"tax_settings_section",
"determine_address_tax_category_from",
"column_break_19",
"add_taxes_from_item_tax_template",
"period_closing_settings_section",
"acc_frozen_upto", "acc_frozen_upto",
"frozen_accounts_modifier", "frozen_accounts_modifier",
"determine_address_tax_category_from",
"over_billing_allowance",
"column_break_4", "column_break_4",
"credit_controller", "credit_controller",
"deferred_accounting_settings_section", "check_supplier_invoice_uniqueness",
"book_deferred_entries_based_on", "make_payment_via_journal_entry",
"column_break_18", "unlink_payment_on_cancellation_of_invoice",
"automatically_process_deferred_accounting_entry", "unlink_advance_payment_on_cancelation_of_order",
"book_deferred_entries_via_journal_entry", "book_asset_depreciation_entry_automatically",
"submit_journal_entries", "allow_cost_center_in_entry_of_bs_account",
"add_taxes_from_item_tax_template",
"automatically_fetch_payment_terms",
"print_settings", "print_settings",
"show_inclusive_tax_in_print", "show_inclusive_tax_in_print",
"column_break_12", "column_break_12",
@@ -49,23 +33,32 @@
], ],
"fields": [ "fields": [
{ {
"description": "Accounting entries are frozen up to this date. Nobody can create or modify entries except users with the role specified below", "default": "1",
"description": "If enabled, the system will post accounting entries for inventory automatically.",
"fieldname": "auto_accounting_for_stock",
"fieldtype": "Check",
"hidden": 1,
"in_list_view": 1,
"label": "Make Accounting Entry For Every Stock Movement"
},
{
"description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.",
"fieldname": "acc_frozen_upto", "fieldname": "acc_frozen_upto",
"fieldtype": "Date", "fieldtype": "Date",
"in_list_view": 1, "in_list_view": 1,
"label": "Accounts Frozen Till Date" "label": "Accounts Frozen Upto"
}, },
{ {
"description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts", "description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts",
"fieldname": "frozen_accounts_modifier", "fieldname": "frozen_accounts_modifier",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
"label": "Role Allowed to Set Frozen Accounts and Edit Frozen Entries", "label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries",
"options": "Role" "options": "Role"
}, },
{ {
"default": "Billing Address", "default": "Billing Address",
"description": "Address used to determine Tax Category in transactions", "description": "Address used to determine Tax Category in transactions.",
"fieldname": "determine_address_tax_category_from", "fieldname": "determine_address_tax_category_from",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Determine Address Tax Category From", "label": "Determine Address Tax Category From",
@@ -76,7 +69,7 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"description": "This role is allowed to submit transactions that exceed credit limits", "description": "Role that is allowed to submit transactions that exceed credit limits set.",
"fieldname": "credit_controller", "fieldname": "credit_controller",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
@@ -84,16 +77,13 @@
"options": "Role" "options": "Role"
}, },
{ {
"default": "0",
"fieldname": "check_supplier_invoice_uniqueness", "fieldname": "check_supplier_invoice_uniqueness",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Check Supplier Invoice Number Uniqueness" "label": "Check Supplier Invoice Number Uniqueness"
}, },
{ {
"default": "0",
"fieldname": "make_payment_via_journal_entry", "fieldname": "make_payment_via_journal_entry",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 1,
"label": "Make Payment via Journal Entry" "label": "Make Payment via Journal Entry"
}, },
{ {
@@ -106,7 +96,7 @@
"default": "1", "default": "1",
"fieldname": "unlink_advance_payment_on_cancelation_of_order", "fieldname": "unlink_advance_payment_on_cancelation_of_order",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Unlink Advance Payment on Cancellation of Order" "label": "Unlink Advance Payment on Cancelation of Order"
}, },
{ {
"default": "1", "default": "1",
@@ -114,6 +104,11 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Book Asset Depreciation Entry Automatically" "label": "Book Asset Depreciation Entry Automatically"
}, },
{
"fieldname": "allow_cost_center_in_entry_of_bs_account",
"fieldtype": "Check",
"label": "Allow Cost Center In Entry of Balance Sheet Account"
},
{ {
"default": "1", "default": "1",
"fieldname": "add_taxes_from_item_tax_template", "fieldname": "add_taxes_from_item_tax_template",
@@ -126,17 +121,15 @@
"label": "Print Settings" "label": "Print Settings"
}, },
{ {
"default": "0",
"fieldname": "show_inclusive_tax_in_print", "fieldname": "show_inclusive_tax_in_print",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Show Inclusive Tax in Print" "label": "Show Inclusive Tax In Print"
}, },
{ {
"fieldname": "column_break_12", "fieldname": "column_break_12",
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"default": "0",
"fieldname": "show_payment_schedule_in_print", "fieldname": "show_payment_schedule_in_print",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Show Payment Schedule in Print" "label": "Show Payment Schedule in Print"
@@ -167,131 +160,27 @@
}, },
{ {
"default": "0", "default": "0",
"description": "Only select this if you have set up the Cash Flow Mapper documents", "description": "Only select if you have setup Cash Flow Mapper documents",
"fieldname": "use_custom_cash_flow", "fieldname": "use_custom_cash_flow",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Use Custom Cash Flow Format" "label": "Use Custom Cash Flow Format"
}, },
{ {
"default": "0",
"fieldname": "automatically_fetch_payment_terms", "fieldname": "automatically_fetch_payment_terms",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Automatically Fetch Payment Terms from Order" "label": "Automatically Fetch Payment Terms"
}, },
{ {
"description": "The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ", "description": "Percentage you are allowed to bill more against the amount ordered. For example: If the order value is $100 for an item and tolerance is set as 10% then you are allowed to bill for $110.",
"fieldname": "over_billing_allowance", "fieldname": "over_billing_allowance",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Over Billing Allowance (%)" "label": "Over Billing Allowance (%)"
},
{
"default": "1",
"fieldname": "automatically_process_deferred_accounting_entry",
"fieldtype": "Check",
"label": "Automatically Process Deferred Accounting Entry"
},
{
"fieldname": "deferred_accounting_settings_section",
"fieldtype": "Section Break",
"label": "Deferred Accounting Settings"
},
{
"fieldname": "column_break_18",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "If this is unchecked, direct GL entries will be created to book deferred revenue or expense",
"fieldname": "book_deferred_entries_via_journal_entry",
"fieldtype": "Check",
"label": "Book Deferred Entries Via Journal Entry"
},
{
"default": "0",
"depends_on": "eval:doc.book_deferred_entries_via_journal_entry",
"description": "If this is unchecked Journal Entries will be saved in a Draft state and will have to be submitted manually",
"fieldname": "submit_journal_entries",
"fieldtype": "Check",
"label": "Submit Journal Entries"
},
{
"default": "Days",
"description": "If \"Months\" is selected, a fixed amount will be booked as deferred revenue or expense for each month irrespective of the number of days in a month. It will be prorated if deferred revenue or expense is not booked for an entire month",
"fieldname": "book_deferred_entries_based_on",
"fieldtype": "Select",
"label": "Book Deferred Entries Based On",
"options": "Days\nMonths"
},
{
"default": "0",
"fieldname": "delete_linked_ledger_entries",
"fieldtype": "Check",
"label": "Delete Accounting and Stock Ledger Entries on deletion of Transaction"
},
{
"description": "Users with this role are allowed to over bill above the allowance percentage",
"fieldname": "role_allowed_to_over_bill",
"fieldtype": "Link",
"label": "Role Allowed to Over Bill ",
"options": "Role"
},
{
"fieldname": "period_closing_settings_section",
"fieldtype": "Section Break",
"label": "Period Closing Settings"
},
{
"fieldname": "accounts_transactions_settings_section",
"fieldtype": "Section Break",
"label": "Transactions Settings"
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"fieldname": "tax_settings_section",
"fieldtype": "Section Break",
"label": "Tax Settings"
},
{
"fieldname": "column_break_19",
"fieldtype": "Column Break"
},
{
"default": "1",
"description": "If enabled, ledger entries will be posted for change amount in POS transactions",
"fieldname": "post_change_gl_entries",
"fieldtype": "Check",
"label": "Create Ledger Entries for Change Amount"
},
{
"default": "0",
"description": "If enabled, additional ledger entries will be made for discounts in a separate Discount Account",
"fieldname": "enable_discount_accounting",
"fieldtype": "Check",
"label": "Enable Discount Accounting"
},
{
"default": "0",
"fieldname": "enable_common_party_accounting",
"fieldtype": "Check",
"label": "Enable Common Party Accounting"
},
{
"default": "0",
"description": "Enabling this will allow creation of multi-currency invoices against single party account in company currency",
"fieldname": "allow_multi_currency_invoices_against_single_party_account",
"fieldtype": "Check",
"label": "Allow multi-currency invoices against single party account"
} }
], ],
"icon": "icon-cog", "icon": "icon-cog",
"idx": 1, "idx": 1,
"index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "modified": "2019-07-04 18:20:55.789946",
"modified": "2022-07-11 13:37:50.605141",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounts Settings", "name": "Accounts Settings",
@@ -316,7 +205,6 @@
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"sort_field": "modified",
"sort_order": "ASC", "sort_order": "ASC",
"track_changes": 1 "track_changes": 1
} }

View File

@@ -3,14 +3,11 @@
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals
import frappe import frappe
from frappe import _
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.model.document import Document
from frappe.utils import cint from frappe.utils import cint
from frappe.model.document import Document
from erpnext.stock.utils import check_pending_reposting from frappe.custom.doctype.property_setter.property_setter import make_property_setter
class AccountsSettings(Document): class AccountsSettings(Document):
@@ -18,108 +15,26 @@ class AccountsSettings(Document):
frappe.clear_cache() frappe.clear_cache()
def validate(self): def validate(self):
frappe.db.set_default( frappe.db.set_default("add_taxes_from_item_tax_template",
"add_taxes_from_item_tax_template", self.get("add_taxes_from_item_tax_template", 0) self.get("add_taxes_from_item_tax_template", 0))
)
frappe.db.set_default(
"enable_common_party_accounting", self.get("enable_common_party_accounting", 0)
)
self.validate_stale_days() self.validate_stale_days()
self.enable_payment_schedule_in_print() self.enable_payment_schedule_in_print()
self.toggle_discount_accounting_fields() self.enable_fields_for_cost_center_settings()
self.validate_pending_reposts()
def validate_stale_days(self): def validate_stale_days(self):
if not self.allow_stale and cint(self.stale_days) <= 0: if not self.allow_stale and cint(self.stale_days) <= 0:
frappe.msgprint( frappe.msgprint(
_("Stale Days should start from 1."), title="Error", indicator="red", raise_exception=1 "Stale Days should start from 1.", title='Error', indicator='red',
) raise_exception=1)
def enable_payment_schedule_in_print(self): def enable_payment_schedule_in_print(self):
show_in_print = cint(self.show_payment_schedule_in_print) show_in_print = cint(self.show_payment_schedule_in_print)
for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"): for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"):
make_property_setter( make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check")
doctype, "due_date", "print_hide", show_in_print, "Check", validate_fields_for_doctype=False make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check")
)
make_property_setter(
doctype,
"payment_schedule",
"print_hide",
0 if show_in_print else 1,
"Check",
validate_fields_for_doctype=False,
)
def toggle_discount_accounting_fields(self): def enable_fields_for_cost_center_settings(self):
enable_discount_accounting = cint(self.enable_discount_accounting) show_field = 0 if cint(self.allow_cost_center_in_entry_of_bs_account) else 1
for doctype in ("Sales Invoice", "Purchase Invoice", "Payment Entry"):
for doctype in ["Sales Invoice Item", "Purchase Invoice Item"]: make_property_setter(doctype, "cost_center", "hidden", show_field, "Check")
make_property_setter(
doctype,
"discount_account",
"hidden",
not (enable_discount_accounting),
"Check",
validate_fields_for_doctype=False,
)
if enable_discount_accounting:
make_property_setter(
doctype,
"discount_account",
"mandatory_depends_on",
"eval: doc.discount_amount",
"Code",
validate_fields_for_doctype=False,
)
else:
make_property_setter(
doctype,
"discount_account",
"mandatory_depends_on",
"",
"Code",
validate_fields_for_doctype=False,
)
for doctype in ["Sales Invoice", "Purchase Invoice"]:
make_property_setter(
doctype,
"additional_discount_account",
"hidden",
not (enable_discount_accounting),
"Check",
validate_fields_for_doctype=False,
)
if enable_discount_accounting:
make_property_setter(
doctype,
"additional_discount_account",
"mandatory_depends_on",
"eval: doc.discount_amount",
"Code",
validate_fields_for_doctype=False,
)
else:
make_property_setter(
doctype,
"additional_discount_account",
"mandatory_depends_on",
"",
"Code",
validate_fields_for_doctype=False,
)
make_property_setter(
"Item",
"default_discount_account",
"hidden",
not (enable_discount_accounting),
"Check",
validate_fields_for_doctype=False,
)
def validate_pending_reposts(self):
if self.acc_frozen_upto:
check_pending_reposting(self.acc_frozen_upto)

View File

@@ -1,8 +0,0 @@
frappe.ui.form.on('Accounts Settings', {
refresh: function(frm) {
frm.set_df_property("acc_frozen_upto", "label", "Books Closed Through");
frm.set_df_property("frozen_accounts_modifier", "label", "Role Allowed to Close Books & Make Changes to Closed Periods");
frm.set_df_property("credit_controller", "label", "Credit Manager");
}
});

View File

@@ -1,3 +1,4 @@
from __future__ import unicode_literals
import unittest import unittest
import frappe import frappe
@@ -7,12 +8,12 @@ class TestAccountsSettings(unittest.TestCase):
def tearDown(self): def tearDown(self):
# Just in case `save` method succeeds, we need to take things back to default so that other tests # Just in case `save` method succeeds, we need to take things back to default so that other tests
# don't break # don't break
cur_settings = frappe.get_doc("Accounts Settings", "Accounts Settings") cur_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
cur_settings.allow_stale = 1 cur_settings.allow_stale = 1
cur_settings.save() cur_settings.save()
def test_stale_days(self): def test_stale_days(self):
cur_settings = frappe.get_doc("Accounts Settings", "Accounts Settings") cur_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
cur_settings.allow_stale = 0 cur_settings.allow_stale = 0
cur_settings.stale_days = 0 cur_settings.stale_days = 0

View File

@@ -1,56 +0,0 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2021-11-25 10:24:39.836195",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"reference_type",
"reference_name",
"reference_detail",
"account_head",
"allocated_amount"
],
"fields": [
{
"fieldname": "reference_type",
"fieldtype": "Link",
"label": "Reference Type",
"options": "DocType"
},
{
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"label": "Reference Name",
"options": "reference_type"
},
{
"fieldname": "reference_detail",
"fieldtype": "Data",
"label": "Reference Detail"
},
{
"fieldname": "account_head",
"fieldtype": "Link",
"label": "Account Head",
"options": "Account"
},
{
"fieldname": "allocated_amount",
"fieldtype": "Currency",
"label": "Allocated Amount",
"options": "party_account_currency"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-11-25 10:27:51.712286",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Advance Tax",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@@ -1,9 +0,0 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class AdvanceTax(Document):
pass

View File

@@ -1,190 +0,0 @@
{
"actions": [],
"creation": "2020-09-12 22:26:19.594367",
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"add_deduct_tax",
"charge_type",
"row_id",
"account_head",
"col_break_1",
"description",
"included_in_paid_amount",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"section_break_8",
"rate",
"section_break_9",
"currency",
"tax_amount",
"total",
"allocated_amount",
"column_break_13",
"base_tax_amount",
"base_total"
],
"fields": [
{
"columns": 2,
"fieldname": "charge_type",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Type",
"oldfieldname": "charge_type",
"oldfieldtype": "Select",
"options": "\nActual\nOn Paid Amount\nOn Previous Row Amount\nOn Previous Row Total",
"reqd": 1
},
{
"depends_on": "eval:[\"On Previous Row Amount\", \"On Previous Row Total\"].indexOf(doc.charge_type)!==-1",
"fieldname": "row_id",
"fieldtype": "Data",
"label": "Reference Row #",
"oldfieldname": "row_id",
"oldfieldtype": "Data"
},
{
"columns": 2,
"fieldname": "account_head",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Account Head",
"oldfieldname": "account_head",
"oldfieldtype": "Link",
"options": "Account",
"reqd": 1,
"search_index": 1
},
{
"fieldname": "col_break_1",
"fieldtype": "Column Break",
"width": "50%"
},
{
"fieldname": "description",
"fieldtype": "Small Text",
"label": "Description",
"oldfieldname": "description",
"oldfieldtype": "Small Text",
"print_width": "300px",
"reqd": 1,
"width": "300px"
},
{
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"default": ":Company",
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"oldfieldname": "cost_center_other_charges",
"oldfieldtype": "Link",
"options": "Cost Center"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_8",
"fieldtype": "Section Break"
},
{
"columns": 2,
"fieldname": "rate",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Rate",
"oldfieldname": "rate",
"oldfieldtype": "Currency"
},
{
"fieldname": "section_break_9",
"fieldtype": "Section Break"
},
{
"columns": 2,
"fieldname": "tax_amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
"options": "currency"
},
{
"columns": 2,
"fieldname": "total",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Total",
"options": "currency",
"read_only": 1
},
{
"fieldname": "column_break_13",
"fieldtype": "Column Break"
},
{
"fieldname": "base_tax_amount",
"fieldtype": "Currency",
"label": "Amount (Company Currency)",
"oldfieldname": "tax_amount",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "base_total",
"fieldtype": "Currency",
"label": "Total (Company Currency)",
"oldfieldname": "total",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "add_deduct_tax",
"fieldtype": "Select",
"label": "Add Or Deduct",
"options": "Add\nDeduct",
"reqd": 1
},
{
"default": "0",
"fieldname": "included_in_paid_amount",
"fieldtype": "Check",
"label": "Considered In Paid Amount"
},
{
"fieldname": "allocated_amount",
"fieldtype": "Currency",
"label": "Allocated Amount",
"options": "currency"
},
{
"fetch_from": "account_head.account_currency",
"fieldname": "currency",
"fieldtype": "Link",
"label": "Account Currency",
"options": "Currency",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-11-25 11:10:10.945027",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Advance Taxes and Charges",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "ASC"
}

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