Compare commits

..

7 Commits

Author SHA1 Message Date
sahil28297
14018b3dea bumped to version 12.2.2 2019-12-02 13:05:27 +05:30
rohitwaghchaure
91f2cfb999 Merge pull request #19769 from rohitwaghchaure/sales_invoice_none_type_error_serial_no_validation
fix: Serial no validation against sales invoice
2019-12-02 09:46:23 +05:30
deepeshgarg007
c0a0331570 fix: Validation msg 2019-12-02 09:43:11 +05:30
deepeshgarg007
4ceba43e43 fix: Serial no validation against sales invoice 2019-12-02 09:43:04 +05:30
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
5351 changed files with 686800 additions and 828968 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
},
"parserOptions": {
"ecmaVersion": 9,
"ecmaVersion": 6,
"sourceType": "module"
},
"extends": "eslint:recommended",
@@ -15,14 +15,6 @@
"tab",
{ "SwitchCase": 1 }
],
"brace-style": [
"error",
"1tbs"
],
"space-unary-ops": [
"error",
{ "words": true }
],
"linebreak-style": [
"error",
"unix"
@@ -52,10 +44,12 @@
"no-control-regex": [
"off"
],
"space-before-blocks": "warn",
"keyword-spacing": "warn",
"comma-spacing": "warn",
"key-spacing": "warn"
"spaced-comment": [
"warn"
],
"no-trailing-spaces": [
"warn"
]
},
"root": true,
"globals": {
@@ -92,7 +86,6 @@
"cur_page": true,
"cur_list": true,
"cur_tree": true,
"cur_pos": true,
"msg_dialog": true,
"is_null": true,
"in_list": true,
@@ -147,15 +140,9 @@
"Chart": true,
"Cypress": true,
"cy": true,
"describe": true,
"expect": true,
"it": true,
"context": true,
"before": true,
"beforeEach": true,
"onScan": true,
"html2canvas": true,
"extend_cscript": true,
"localforage": true
"beforeEach": true
}
}

33
.flake8
View File

@@ -1,33 +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,
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
# Replace use of Class.extend with native JS class
1fe891b287a1b3f225d29ee3d07e7b1824aba9e7
# This commit just changes spaces to tabs for indentation in some files
5f473611bd6ed57703716244a054d3fb5ba9cd23
# Whitespace fix throughout codebase
4551d7d6029b6f587f6c99d4f8df5519241c6a86
b147b85e6ac19a9220cd1e2958a6ebd99373283a
# sort and cleanup imports
915b34391c2066dfc83e60a5813c5a877cebe7ac

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 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)

View File

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

View File

@@ -1,54 +0,0 @@
import sys
import requests
from urllib.parse import urlparse
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,45 +0,0 @@
#!/bin/bash
set -e
cd ~ || exit
sudo apt-get 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}"
if [ "$TYPE" == "server" ]; then bench setup requirements --dev; fi
bench start &> bench_run_logs.txt &
bench --site test_site reinstall --yes
bench build --app frappe

View File

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

View File

@@ -1,64 +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,163 +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
- id: frappe-using-db-sql
pattern-either:
- pattern: frappe.db.sql(...)
- pattern: frappe.db.sql_ddl(...)
- pattern: frappe.db.sql_list(...)
paths:
exclude:
- "test_*.py"
message: |
The PR contains a SQL query that may be re-written with frappe.qb (https://frappeframework.com/docs/user/en/api/query-builder) or the Database API (https://frappeframework.com/docs/user/en/api/database)
languages: [python]
severity: ERROR

View File

@@ -1,15 +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,34 +0,0 @@
rules:
- id: frappe-missing-translate-function-in-report-python
paths:
include:
- "**/report"
exclude:
- "**/regional"
pattern-either:
- patterns:
- pattern: |
{..., "label": "...", ...}
- pattern-not: |
{..., "label": _("..."), ...}
- patterns:
- pattern: dict(..., label="...", ...)
- pattern-not: dict(..., label=_("..."), ...)
message: |
All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations
languages: [python]
severity: ERROR
- id: frappe-translated-values-in-business-logic
paths:
include:
- "**/report"
patterns:
- pattern-inside: |
{..., filters: [...], ...}
- pattern: |
{..., options: [..., __("..."), ...], ...}
message: |
Using translated values in options field will require you to translate the values while comparing in business logic. Instead of passing translated labels provide objects that contain both label and value. e.g. { label: __("Option value"), value: "Option value"}
languages: [javascript]
severity: ERROR

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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!')

4
.github/stale.yml vendored
View File

@@ -1,11 +1,11 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 15
daysUntilStale: 30
# Number of days of inactivity before a stale Issue or Pull Request is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 3
daysUntilClose: 7
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:

View File

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

Before

Width:  |  Height:  |  Size: 4.4 KiB

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 -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token ${{ secrets.TRAVIS_CI_TOKEN }}" -d '{"request":{"branch":"master"}}' https://api.travis-ci.com/repo/frappe%2Ffrappe_docker/requests

View File

@@ -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,28 +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
- uses: returntocorp/semgrep-action@v1
env:
SEMGREP_TIMEOUT: 120
with:
config: >-
r/python.lang.correctness
.github/helper/semgrep_rules

View File

@@ -1,89 +0,0 @@
name: Patch
on:
pull_request:
paths-ignore:
- '**.js'
- '**.md'
workflow_dispatch:
concurrency:
group: patch-develop-${{ github.event.number }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
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: 14
check-latest: true
- name: Add to Hosts
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: 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,110 +0,0 @@
name: Server
on:
pull_request:
paths-ignore:
- '**.js'
- '**.md'
workflow_dispatch:
push:
branches: [ develop ]
paths-ignore:
- '**.js'
- '**.md'
concurrency:
group: server-develop-${{ github.event.number }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
container: [1, 2, 3]
name: Python Unit Tests
services:
mysql:
image: mariadb:10.3
env:
MYSQL_ALLOW_EMPTY_PASSWORD: YES
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
steps:
- name: Clone
uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: 3.7
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 14
check-latest: true
- name: Add to Hosts
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
env:
TYPE: server
- name: Run Tests
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage
env:
TYPE: server
CI_BUILD_ID: ${{ github.run_id }}
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io
- name: Upload coverage data
uses: codecov/codecov-action@v2
with:
name: MariaDB
fail_ci_if_error: true
files: /home/runner/frappe-bench/sites/coverage.xml
verbose: true

View File

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

2
.gitignore vendored
View File

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

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,38 +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/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

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_password": "test_frappe",
"auto_email_id": "test@example.com",
@@ -11,6 +9,5 @@
"root_login": "root",
"root_password": "travis",
"host_name": "http://test_site:8000",
"install_apps": ["erpnext"],
"throttle_user_limit": 100
"install_apps": ["erpnext"]
}

View File

@@ -1,35 +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
erpnext/assets/ @nextchamp-saqib @deepeshgarg007
erpnext/erpnext_integrations/ @nextchamp-saqib
erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
erpnext/regional @nextchamp-saqib @deepeshgarg007
erpnext/selling @nextchamp-saqib @deepeshgarg007
erpnext/support/ @nextchamp-saqib @deepeshgarg007
pos* @nextchamp-saqib
erpnext/buying/ @marination @rohitwaghchaure @ankush
erpnext/e_commerce/ @marination
erpnext/maintenance/ @marination @rohitwaghchaure
erpnext/manufacturing/ @marination @rohitwaghchaure @ankush
erpnext/portal/ @marination
erpnext/quality_management/ @marination @rohitwaghchaure
erpnext/shopping_cart/ @marination
erpnext/stock/ @marination @rohitwaghchaure @ankush
erpnext/crm/ @ruchamahabal @pateljannat
erpnext/education/ @ruchamahabal @pateljannat
erpnext/healthcare/ @ruchamahabal @pateljannat @chillaranand
erpnext/hr/ @ruchamahabal @pateljannat
erpnext/non_profit/ @ruchamahabal
erpnext/payroll @ruchamahabal @pateljannat
erpnext/projects/ @ruchamahabal @pateljannat
erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
.github/ @surajshetty3416 @ankush
requirements.txt @gavindsouza

View File

@@ -5,50 +5,23 @@
<p>ERP made simple</p>
</p>
[![CI](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml/badge.svg?branch=develop)](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml)
[![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)
[![codecov](https://codecov.io/gh/frappe/erpnext/branch/develop/graph/badge.svg?token=0TwvyUg3I5)](https://codecov.io/gh/frappe/erpnext)
[![Coverage Status](https://coveralls.io/repos/github/frappe/erpnext/badge.svg?branch=develop)](https://coveralls.io/github/frappe/erpnext?branch=develop)
[https://erpnext.com](https://erpnext.com)
</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)
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.
ERPNext is built on the [Frappe](https://github.com/frappe/frappe) Framework, a full-stack web app framework in Python & JavaScript.
- [User Guide](https://erpnext.com/docs/user)
- [Discussion Forum](https://discuss.erpnext.com/)
---
<div align="center">
<a href="https://frappecloud.com/deploy?apps=frappe,erpnext&source=erpnext_readme">
<img src=".github/try-on-f-cloud-button.svg" height="40">
</a>
</div>
### 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
The Easy Way: our install script for bench will install all dependencies (e.g. MariaDB). See https://github.com/frappe/bench for more details.
@@ -83,12 +56,6 @@ The ERPNext code is licensed as GNU General Public License (v3) and the Document
---
## Learning
1. [Frappe School](https://frappe.school) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community.
---
## Logo and Trademark
The brand name ERPNext and the logo are trademarks of Frappe Technologies Pvt. Ltd.

View File

@@ -1,17 +0,0 @@
codecov:
require_ci_to_pass: yes
coverage:
status:
project:
default:
target: auto
threshold: 0.5%
comment:
layout: "diff, files"
require_changes: true
after_n_builds: 3
ignore:
- "erpnext/demo"

View File

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

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,44 +0,0 @@
describe("Test Item Dashboard", () => {
before(() => {
cy.login();
cy.visit("/app/item");
cy.insert_doc(
"Item",
{
item_code: "e2e_test_item",
item_group: "All Item Groups",
opening_stock: 42,
valuation_rate: 100,
},
true
);
cy.go_to_doc("item", "e2e_test_item");
});
it("should show dashboard with correct data on first load", () => {
cy.get(".stock-levels").contains("Stock Levels").should("be.visible");
cy.get(".stock-levels").contains("e2e_test_item").should("exist");
// reserved and available qty
cy.get(".stock-levels .inline-graph-count")
.eq(0)
.contains("0")
.should("exist");
cy.get(".stock-levels .inline-graph-count")
.eq(1)
.contains("42")
.should("exist");
});
it("should persist on field change", () => {
cy.get('input[data-fieldname="disabled"]').check();
cy.wait(500);
cy.get(".stock-levels").contains("Stock Levels").should("be.visible");
cy.get(".stock-levels").should("have.length", 1);
});
it("should persist on reload", () => {
cy.reload();
cy.get(".stock-levels").contains("Stock Levels").should("be.visible");
});
});

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
// the project's config changing)
module.exports = () => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
};
// module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
// }

View File

@@ -10,22 +10,16 @@
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... });
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- 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 --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... });
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... });
const slug = (name) => name.toLowerCase().replace(" ", "-");
Cypress.Commands.add("go_to_doc", (doctype, name) => {
cy.visit(`/app/${slug(doctype)}/${encodeURIComponent(name)}`);
});
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

View File

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

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,13 +1,11 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import inspect
import frappe
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
__version__ = '13.9.0'
__version__ = '12.2.2'
def get_default_company(user=None):
'''Get default company for user'''
@@ -111,7 +109,7 @@ def get_region(company=None):
'''
if company or frappe.flags.company:
return frappe.get_cached_value('Company',
company or frappe.flags.company, 'country')
company or frappe.flags.company, 'country')
elif frappe.flags.country:
return frappe.flags.country
else:
@@ -134,10 +132,16 @@ def allow_regional(fn):
return caller
def get_last_membership(member):
def get_last_membership():
'''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)
dict(member=frappe.session.user, paid=1), order_by='to_date desc', limit=1)
if last_membership:
return last_membership[0]
return last_membership and last_membership[0]
def is_member():
'''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

@@ -1,126 +1,58 @@
{
"custom_fields": [
{
"_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": "2018-12-28 22:29:21.828090",
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "Address",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "tax_category",
"fieldtype": "Link",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 15,
"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": "fax",
"label": "Tax Category",
"length": 0,
"mandatory_depends_on": null,
"modified": "2018-12-28 22:29:21.828090",
"modified_by": "Administrator",
"name": "Address-tax_category",
"no_copy": 0,
"options": "Tax Category",
"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,
"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,
"reqd": 0,
"search_index": 0,
"translatable": 0,
"unique": 0,
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2018-12-28 22:29:21.828090",
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "Address",
"fetch_from": null,
"fieldname": "tax_category",
"fieldtype": "Link",
"hidden": 0,
"idx": 14,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"insert_after": "fax",
"label": "Tax Category",
"modified": "2018-12-28 22:29:21.828090",
"modified_by": "Administrator",
"name": "Address-tax_category",
"no_copy": 0,
"options": "Tax Category",
"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,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"translatable": 0,
"unique": 0,
"width": null
}
],
"custom_perms": [],
"doctype": "Address",
"property_setters": [],
],
"custom_perms": [],
"doctype": "Address",
"property_setters": [],
"sync_on_migrate": 1
}

View File

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

@@ -19,4 +19,4 @@ frappe.dashboards.chart_sources["Account Balance Timeline"] = {
reqd: 1
},
]
};
};

View File

@@ -2,19 +2,18 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
import frappe, json
from frappe import _
from frappe.utils import add_to_date, formatdate, get_link_to_form, getdate, nowdate
from frappe.utils.dashboard import cache_source
from frappe.utils.dateutils import get_from_date_from_timespan, get_period_ending
from frappe.utils.nestedset import get_descendants_of
from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day, formatdate
from erpnext.accounts.report.general_ledger.general_ledger import execute
from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending
from frappe.utils.nestedset import get_descendants_of
@frappe.whitelist()
@cache_source
def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None,
to_date = None, timespan = None, time_interval = None, heatmap_year = None):
def get(chart_name = None, chart = None, no_cache = None, from_date = None, to_date = None):
if chart_name:
chart = frappe.get_doc('Dashboard Chart', chart_name)
else:
@@ -26,18 +25,13 @@ def get(chart_name = None, chart = None, no_cache = None, filters = None, from_d
to_date = chart.to_date
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")
company = filters.get("company")
if not account and chart_name:
frappe.throw(_("Account is not set for the dashboard chart {0}")
.format(get_link_to_form("Dashboard Chart", chart_name)))
if not frappe.db.exists("Account", account) and chart_name:
frappe.throw(_("Account {0} does not exists in the dashboard chart {1}")
.format(account, get_link_to_form("Dashboard Chart", chart_name)))
if not account and chart:
frappe.throw(_("Account is not set for the dashboard chart {0}").format(chart))
if not to_date:
to_date = nowdate()

View File

@@ -2,26 +2,9 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.email import sendmail_to_system_managers
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 frappe.utils import date_diff, add_months, today, getdate, add_days, flt, get_last_day
from erpnext.accounts.utils import get_account_currency
from frappe.email import sendmail_to_system_managers
def validate_service_stop_date(doc):
''' Validates service_stop_date for Purchase Invoice and Sales Invoice '''
@@ -47,22 +30,10 @@ def validate_service_stop_date(doc):
frappe.throw(_("Service Stop Date cannot be after Service End Date"))
if old_stop_dates and old_stop_dates.get(item.name) and item.service_stop_date!=old_stop_dates.get(item.name):
frappe.throw(_("Cannot change Service Stop Date for item in row {0}").format(item.idx))
frappe.throw(_("Cannot change Service Stop Date for item in row {0}".format(item.idx)))
def build_conditions(process_type, account, company):
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=''):
def convert_deferred_expense_to_expense(start_date=None, end_date=None):
# 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:
start_date = add_months(today(), -1)
if not end_date:
@@ -70,25 +41,18 @@ def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_d
# check for the purchase invoice for which GL entries has to be done
invoices = frappe.db.sql_list('''
select distinct item.parent
from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p
where item.service_start_date<=%s and item.service_end_date>=%s
and item.enable_deferred_expense = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{0}
'''.format(conditions), (end_date, start_date)) #nosec
select distinct parent from `tabPurchase Invoice Item`
where service_start_date<=%s and service_end_date>=%s
and enable_deferred_expense = 1 and docstatus = 1 and ifnull(amount, 0) > 0
''', (end_date, start_date))
# For each invoice, book deferred expense
for invoice in invoices:
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:
send_mail(deferred_process)
def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_date=None, conditions=''):
def convert_deferred_revenue_to_income(start_date=None, end_date=None):
# 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:
start_date = add_months(today(), -1)
if not end_date:
@@ -96,20 +60,14 @@ def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_da
# check for the sales invoice for which GL entries has to be done
invoices = frappe.db.sql_list('''
select distinct item.parent
from `tabSales Invoice Item` item, `tabSales Invoice` p
where item.service_start_date<=%s and item.service_end_date>=%s
and item.enable_deferred_revenue = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{0}
'''.format(conditions), (end_date, start_date)) #nosec
select distinct parent from `tabSales Invoice Item`
where service_start_date<=%s and service_end_date>=%s
and enable_deferred_revenue = 1 and docstatus = 1 and ifnull(amount, 0) > 0
''', (end_date, start_date))
for invoice in invoices:
doc = frappe.get_doc("Sales Invoice", invoice)
book_deferred_income_or_expense(doc, deferred_process, end_date)
if frappe.flags.deferred_accounting_error:
send_mail(deferred_process)
book_deferred_income_or_expense(doc, end_date)
def get_booking_dates(doc, item, posting_date=None):
if not posting_date:
@@ -125,18 +83,6 @@ def get_booking_dates(doc, item, posting_date=None):
order by posting_date desc limit 1
''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
prev_gl_via_je = frappe.db.sql('''
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:
start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
else:
@@ -158,48 +104,14 @@ def get_booking_dates(doc, item, posting_date=None):
else:
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):
if doc.doctype == "Sales Invoice":
total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency"
deferred_account = "deferred_revenue_account"
else:
total_credit_debit, total_credit_debit_currency = "credit", "credit_in_account_currency"
deferred_account = "deferred_expense_account"
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"))
@@ -208,55 +120,27 @@ def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, a
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)
gl_entries_details = frappe.db.sql('''
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
group by voucher_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
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:
already_booked_amount_in_account_currency = 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"))
return amount, base_amount
def get_already_booked_amount(doc, item):
if doc.doctype == "Sales Invoice":
total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency"
deferred_account = "deferred_revenue_account"
else:
total_credit_debit, total_credit_debit_currency = "credit", "credit_in_account_currency"
deferred_account = "deferred_expense_account"
gl_entries_details = frappe.db.sql('''
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
group by voucher_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)
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 += journal_entry_details[0].total_credit if journal_entry_details else 0
if doc.currency == doc.company_currency:
already_booked_amount_in_account_currency = already_booked_amount
else:
already_booked_amount_in_account_currency = gl_entries_details[0].total_credit_in_account_currency if gl_entries_details else 0
already_booked_amount_in_account_currency += journal_entry_details[0].total_credit_in_account_currency if journal_entry_details else 0
return already_booked_amount, already_booked_amount_in_account_currency
def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
def book_deferred_income_or_expense(doc, posting_date=None):
enable_check = "enable_deferred_revenue" \
if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
def _book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on):
def _book_deferred_revenue_or_expense(item):
start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
if not (start_date and end_date): return
@@ -271,69 +155,22 @@ 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_booking_days = date_diff(end_date, start_date) + 1
if book_deferred_entries_based_on == 'Months':
amount, base_amount = calculate_monthly_amount(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)
amount, base_amount = calculate_amount(doc, item, last_gl_entry,
total_days, total_booking_days, account_currency)
if not amount:
return
if via_journal_entry:
book_revenue_via_journal_entry(doc, credit_account, debit_account, against, 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
make_gl_entries(doc, credit_account, debit_account, against,
amount, base_amount, end_date, project, account_currency, item.cost_center, item.name)
if getdate(end_date) < getdate(posting_date) and not last_gl_entry:
_book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on)
_book_deferred_revenue_or_expense(item)
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'):
if item.get(enable_check):
_book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on)
def process_deferred_accounting(posting_date=None):
''' 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()
_book_deferred_revenue_or_expense(item)
def make_gl_entries(doc, credit_account, debit_account, against,
amount, base_amount, posting_date, project, account_currency, cost_center, item, deferred_process=None):
amount, base_amount, posting_date, project, account_currency, cost_center, voucher_detail_no):
# GL Entry for crediting the amount in the deferred expense
from erpnext.accounts.general_ledger import make_gl_entries
@@ -347,12 +184,10 @@ def make_gl_entries(doc, credit_account, debit_account, against,
"credit": base_amount,
"credit_in_account_currency": amount,
"cost_center": cost_center,
"voucher_detail_no": item.name,
"voucher_detail_no": voucher_detail_no,
'posting_date': posting_date,
'project': project,
'against_voucher_type': 'Process Deferred Accounting',
'against_voucher': deferred_process
}, account_currency, item=item)
'project': project
}, account_currency)
)
# GL Entry to debit the amount from the expense
gl_entries.append(
@@ -362,109 +197,19 @@ def make_gl_entries(doc, credit_account, debit_account, against,
"debit": base_amount,
"debit_in_account_currency": amount,
"cost_center": cost_center,
"voucher_detail_no": item.name,
"voucher_detail_no": voucher_detail_no,
'posting_date': posting_date,
'project': project,
'against_voucher_type': 'Process Deferred Accounting',
'against_voucher': deferred_process
}, account_currency, item=item)
'project': project
}, account_currency)
)
if gl_entries:
try:
make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True)
frappe.db.commit()
except Exception as e:
if frappe.flags.in_test:
raise e
else:
frappe.db.rollback()
traceback = frappe.get_traceback()
frappe.log_error(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, against,
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'
debit_entry = {
'account': credit_account,
'credit': base_amount,
'credit_in_account_currency': amount,
'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier',
'party': against,
'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,
'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier',
'party': against,
'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()
except Exception:
frappe.db.rollback()
traceback = frappe.get_traceback()
frappe.log_error(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
except:
frappe.db.rollback()
title = _("Error while processing deferred accounting for {0}").format(doc.name)
traceback = frappe.get_traceback()
frappe.log_error(message=traceback , title=title)
sendmail_to_system_managers(title, traceback)

View File

@@ -74,7 +74,7 @@ frappe.ui.form.on('Account', {
});
} else if (cint(frm.doc.is_group) == 0
&& frappe.boot.user.can_read.indexOf("GL Entry") !== -1) {
frm.add_custom_button(__('Ledger'), function () {
cur_frm.add_custom_button(__('Ledger'), function () {
frappe.route_options = {
"account": frm.doc.name,
"from_date": frappe.sys_defaults.year_start_date,

View File

@@ -1,5 +1,4 @@
{
"actions": [],
"allow_copy": 1,
"allow_import": 1,
"creation": "2013-01-30 12:49:46",
@@ -34,15 +33,11 @@
{
"fieldname": "properties",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
"show_days": 1,
"show_seconds": 1
"oldfieldtype": "Section Break"
},
{
"fieldname": "column_break0",
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1,
"width": "50%"
},
{
@@ -53,9 +48,7 @@
"no_copy": 1,
"oldfieldname": "account_name",
"oldfieldtype": "Data",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
"reqd": 1
},
{
"fieldname": "account_number",
@@ -63,17 +56,13 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Account Number",
"read_only": 1,
"show_days": 1,
"show_seconds": 1
"read_only": 1
},
{
"default": "0",
"fieldname": "is_group",
"fieldtype": "Check",
"label": "Is Group",
"show_days": 1,
"show_seconds": 1
"label": "Is Group"
},
{
"fieldname": "company",
@@ -85,9 +74,7 @@
"options": "Company",
"read_only": 1,
"remember_last_selected_value": 1,
"reqd": 1,
"show_days": 1,
"show_seconds": 1
"reqd": 1
},
{
"fieldname": "root_type",
@@ -95,9 +82,7 @@
"in_standard_filter": 1,
"label": "Root Type",
"options": "\nAsset\nLiability\nIncome\nExpense\nEquity",
"read_only": 1,
"show_days": 1,
"show_seconds": 1
"read_only": 1
},
{
"fieldname": "report_type",
@@ -105,32 +90,24 @@
"in_standard_filter": 1,
"label": "Report Type",
"options": "\nBalance Sheet\nProfit and Loss",
"read_only": 1,
"show_days": 1,
"show_seconds": 1
"read_only": 1
},
{
"depends_on": "eval:doc.is_group==0",
"fieldname": "account_currency",
"fieldtype": "Link",
"label": "Currency",
"options": "Currency",
"show_days": 1,
"show_seconds": 1
"options": "Currency"
},
{
"default": "0",
"fieldname": "inter_company_account",
"fieldtype": "Check",
"label": "Inter Company Account",
"show_days": 1,
"show_seconds": 1
"label": "Inter Company Account"
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1,
"width": "50%"
},
{
@@ -142,9 +119,7 @@
"oldfieldtype": "Link",
"options": "Account",
"reqd": 1,
"search_index": 1,
"show_days": 1,
"show_seconds": 1
"search_index": 1
},
{
"description": "Setting Account Type helps in selecting this Account in transactions.",
@@ -154,9 +129,7 @@
"label": "Account Type",
"oldfieldname": "account_type",
"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",
"show_days": 1,
"show_seconds": 1
"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"
},
{
"description": "Rate at which this tax is applied",
@@ -164,9 +137,7 @@
"fieldtype": "Float",
"label": "Rate",
"oldfieldname": "tax_rate",
"oldfieldtype": "Currency",
"show_days": 1,
"show_seconds": 1
"oldfieldtype": "Currency"
},
{
"description": "If the account is frozen, entries are allowed to restricted users.",
@@ -175,17 +146,13 @@
"label": "Frozen",
"oldfieldname": "freeze_account",
"oldfieldtype": "Select",
"options": "No\nYes",
"show_days": 1,
"show_seconds": 1
"options": "No\nYes"
},
{
"fieldname": "balance_must_be",
"fieldtype": "Select",
"label": "Balance must be",
"options": "\nDebit\nCredit",
"show_days": 1,
"show_seconds": 1
"options": "\nDebit\nCredit"
},
{
"fieldname": "lft",
@@ -194,9 +161,7 @@
"label": "Lft",
"print_hide": 1,
"read_only": 1,
"search_index": 1,
"show_days": 1,
"show_seconds": 1
"search_index": 1
},
{
"fieldname": "rgt",
@@ -205,9 +170,7 @@
"label": "Rgt",
"print_hide": 1,
"read_only": 1,
"search_index": 1,
"show_days": 1,
"show_seconds": 1
"search_index": 1
},
{
"fieldname": "old_parent",
@@ -215,37 +178,28 @@
"hidden": 1,
"label": "Old Parent",
"print_hide": 1,
"read_only": 1,
"show_days": 1,
"show_seconds": 1
"read_only": 1
},
{
"default": "0",
"depends_on": "eval:(doc.report_type == 'Profit and Loss' && !doc.is_group)",
"fieldname": "include_in_gross",
"fieldtype": "Check",
"label": "Include in gross",
"show_days": 1,
"show_seconds": 1
"label": "Include in gross"
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disable",
"show_days": 1,
"show_seconds": 1
"label": "Disable"
}
],
"icon": "fa fa-money",
"idx": 1,
"is_tree": 1,
"links": [],
"modified": "2020-06-11 15:15:54.338622",
"modified": "2019-10-10 19:10:02.967554",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account",
"nsm_parent_field": "parent_account",
"owner": "Administrator",
"permissions": [
{

View File

@@ -2,22 +2,18 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe import _, throw
from frappe.utils import cint, cstr
from frappe import throw, _
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 Account(NestedSet):
nsm_parent_field = 'parent_account'
def on_update(self):
if frappe.local.flags.ignore_update_nsm:
if frappe.local.flags.ignore_on_update:
return
else:
super(Account, self).on_update()
@@ -93,33 +89,30 @@ class Account(NestedSet):
throw(_("Root cannot be edited."), RootNotEditable)
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):
# ignore validation while creating new compnay or while syncing to child companies
if frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation:
return
ancestors = get_root_company(self.company)
if ancestors:
if frappe.get_value("Company", self.company, "allow_account_creation_against_child_company"):
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:
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, 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('Account', filters=filters, fieldname=["company", "name"], as_dict=True):
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"]
if not parent_acc_name_map: return
@@ -168,19 +161,9 @@ class Account(NestedSet):
def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name):
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):
frappe.throw(_("While creating account for Child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA")
.format(company_bold, parent_acc_name_bold), title=_("Account Not Found"))
# validate if parent of child company account to be added is a group
if (frappe.db.get_value("Account", self.parent_account, "is_group")
and not frappe.db.get_value("Account", parent_acc_name_map[company], "is_group")):
msg = _("While creating account for Child Company {0}, parent account {1} found as a ledger account.").format(company_bold, parent_acc_name_bold)
msg += "<br><br>"
msg += _("Please convert the parent account in corresponding child company to a group account.")
frappe.throw(msg, title=_("Invalid Parent Account"))
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))
filters = {
"account_name": self.account_name,
@@ -191,6 +174,7 @@ class Account(NestedSet):
filters["account_number"] = self.account_number
child_account = frappe.db.get_value("Account", filters, 'name')
if not child_account:
doc = frappe.copy_doc(self)
doc.flags.ignore_root_company_validation = True
@@ -198,7 +182,7 @@ class Account(NestedSet):
"company": company,
# 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
"account_currency": erpnext.get_company_currency(company),
"account_currency": self.account_currency,
"parent_account": parent_acc_name_map[company]
})
@@ -209,7 +193,8 @@ class Account(NestedSet):
# update the parent company's value in child companies
doc = frappe.get_doc("Account", child_account)
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):
parent_value_changed = True
doc.set(field, self.get(field))
@@ -217,7 +202,6 @@ class Account(NestedSet):
if parent_value_changed:
doc.save()
@frappe.whitelist()
def convert_group_to_ledger(self):
if self.check_if_child_exists():
throw(_("Account with child nodes cannot be converted to ledger"))
@@ -228,12 +212,11 @@ class Account(NestedSet):
self.save()
return 1
@frappe.whitelist()
def convert_ledger_to_group(self):
if self.check_gle_exists():
throw(_("Account with existing transaction can not be converted to group."))
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:
self.is_group = 1
self.save()
@@ -261,8 +244,6 @@ class Account(NestedSet):
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):
return frappe.db.sql("""select name from tabAccount
where is_group = 1 and docstatus != 2 and company = %s
@@ -306,31 +287,10 @@ def validate_account_number(name, account_number, company):
.format(account_number, account_with_same_number))
@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)
if not account: 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)
if account_number:
frappe.db.set_value("Account", name, "account_number", account_number.strip())
@@ -338,12 +298,6 @@ 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_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)
if name != new_name:
frappe.rename_doc("Account", name, new_name, force=1)
@@ -374,14 +328,3 @@ def get_root_company(company):
# return the topmost company in the hierarchy
ancestors = get_ancestors_of('Company', company, "lft asc")
return [ancestors[0]] if ancestors else []
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.treeview_settings["Account"] = {
breadcrumb: "Accounts",
title: __("Chart of Accounts"),
breadcrumbs: "Accounts",
title: __("Chart Of Accounts"),
get_tree_root: false,
filters: [
{
@@ -14,9 +14,6 @@ frappe.treeview_settings["Account"] = {
on_change: function() {
var me = frappe.treeview_settings['Account'].treeview;
var company = me.page.fields_dict.company.get_value();
if (!company) {
frappe.throw(__("Please set a Company"));
}
frappe.call({
method: "erpnext.accounts.doctype.account.account.get_root_company",
args: {
@@ -45,49 +42,6 @@ frappe.treeview_settings["Account"] = {
],
root_label: "Accounts",
get_tree_nodes: 'erpnext.accounts.utils.get_children',
on_get_node: function(nodes, deep=false) {
if (frappe.boot.user.can_read.indexOf("GL Entry") == -1) return;
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) {
$('<span class="balance-area pull-right">'
+ (account.balance_in_account_currency ?
(format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
+ format(account.balance, account.company_currency)
+ " " + dr_or_cr
+ '</span>').insertBefore(node.$ul);
}
}
});
},
add_tree_node: 'erpnext.accounts.utils.add_ac',
menu_items:[
{
@@ -140,7 +94,7 @@ frappe.treeview_settings["Account"] = {
treeview.page.add_inner_button(__("Journal Entry"), function() {
frappe.new_doc('Journal Entry', {company: get_company()});
}, __('Create'));
treeview.page.add_inner_button(__("Company"), function() {
treeview.page.add_inner_button(__("New Company"), function() {
frappe.new_doc('Company');
}, __('Create'));
@@ -163,7 +117,25 @@ frappe.treeview_settings["Account"] = {
} else {
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: [
{

View File

@@ -2,17 +2,14 @@
# 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.nestedset import rebuild_tree
from six import iteritems
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, from_coa_importer=None):
def create_charts(company, chart_template=None, existing_company=None, custom_chart=None):
chart = custom_chart or get_chart(chart_template, existing_company)
if chart:
accounts = []
@@ -22,7 +19,7 @@ def create_charts(company, chart_template=None, existing_company=None, custom_ch
if root_account:
root_type = child.get("root_type")
if account_name not in ["account_name", "account_number", "account_type",
if account_name not in ["account_number", "account_type",
"root_type", "is_group", "tax_rate"]:
account_number = cstr(child.get("account_number")).strip()
@@ -35,7 +32,7 @@ def create_charts(company, chart_template=None, existing_company=None, custom_ch
account = frappe.get_doc({
"doctype": "Account",
"account_name": child.get('account_name') if from_coa_importer else account_name,
"account_name": account_name,
"company": company,
"parent_account": parent,
"is_group": is_group,
@@ -60,10 +57,10 @@ def create_charts(company, chart_template=None, existing_company=None, custom_ch
# Rebuild NestedSet HSM tree for Account Doctype
# 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)
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):
if account_number:
@@ -94,14 +91,11 @@ def get_chart(chart_template, existing_company=None):
return get_account_tree_from_existing_company(existing_company)
elif chart_template == "Standard":
from erpnext.accounts.doctype.account.chart_of_accounts.verified import (
standard_chart_of_accounts,
)
from erpnext.accounts.doctype.account.chart_of_accounts.verified import standard_chart_of_accounts
return standard_chart_of_accounts.get()
elif chart_template == "Standard with Numbers":
from erpnext.accounts.doctype.account.chart_of_accounts.verified import (
standard_chart_of_accounts_with_account_number,
)
from erpnext.accounts.doctype.account.chart_of_accounts.verified \
import standard_chart_of_accounts_with_account_number
return standard_chart_of_accounts_with_account_number.get()
else:
folders = ("verified",)
@@ -213,7 +207,7 @@ def validate_bank_account(coa, bank_account):
return (bank_account in accounts)
@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 '''
chart = chart_data or get_chart(chart_template)
@@ -226,15 +220,12 @@ def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=Fals
''' recursively called to form a parent-child based list of dict from chart template '''
for account_name, child in iteritems(children):
account = {}
if account_name in ["account_name", "account_number", "account_type",\
if account_name in ["account_number", "account_type",\
"root_type", "is_group", "tax_rate"]: continue
if from_coa_importer:
account_name = child['account_name']
account['parent_account'] = parent
account['expandable'] = True if identify_is_group(child) else False
account['value'] = (cstr(child.get('account_number')).strip() + ' - ' + account_name) \
account['value'] = (child.get('account_number') + ' - ' + account_name) \
if child.get('account_number') else account_name
accounts.append(account)
_import_accounts(child, account['value'])

View File

@@ -6,13 +6,12 @@ Import chart of accounts from OpenERP sources
"""
from __future__ import print_function, unicode_literals
import os, json
import ast
import json
import os
from xml.etree import ElementTree as ET
import frappe
from frappe.utils.csvutils import read_csv_content
import frappe
from six import iteritems
path = "/Users/nabinhait/projects/odoo/addons"

View File

@@ -1,466 +1,465 @@
{
"country_code": "ae",
"name": "U.A.E - Chart of Accounts",
"country_code": "ae",
"name": "U.A.E - Chart of Accounts",
"tree": {
"Assets": {
"Current Assets": {
"Accounts Receivable": {
"Corporate Credit Cards": {
"account_type": "Receivable"
},
},
"Other Receivable": {
"Accrued Rebates Due from Suppliers": {
"account_type": "Receivable"
},
},
"Accrued Income from Suppliers": {
"account_type": "Receivable"
},
},
"Other Debtors": {
"account_type": "Receivable"
},
},
"account_type": "Receivable"
},
},
"Post Dated Cheques Received": {
"account_type": "Receivable"
},
},
"Staff Receivable": {
"account_type": "Receivable"
},
},
"Trade Receivable": {
"account_type": "Receivable"
},
},
"Trade in Opening Fees": {
"account_type": "Receivable"
},
},
"account_type": "Receivable"
},
},
"Cash in Hand & Banks": {
"Banks": {
"Bank Margin On LC & LG": {},
"Banks Blocked Deposits": {},
"Banks Call Deposit Accounts": {},
"Bank Margin On LC & LG": {},
"Banks Blocked Deposits": {},
"Banks Call Deposit Accounts": {},
"Banks Current Accounts": {
"account_type": "Bank"
},
},
"account_type": "Bank"
},
},
"Cash in Hand": {
"Cash in Safe": {
"Main Safe": {
"account_type": "Cash"
},
},
"Main Safe - Foreign Currency": {
"account_type": "Cash"
}
},
},
"Petty Cash": {
"Petty Cash - Administration": {
"account_type": "Cash"
},
},
"Petty Cash - Others": {
"account_type": "Cash"
}
},
},
"account_type": "Cash"
},
},
"Cash in Transit": {
"Credit Cards": {
"Gateway Credit Cards": {
"account_type": "Bank"
},
},
"Manual Visa & Master Cards": {
"account_type": "Bank"
},
},
"PayPal Account": {
"account_type": "Bank"
},
},
"Visa & Master Credit Cards": {
"account_type": "Bank"
}
}
}
},
},
"Inventory": {
"Consigned Stock": {
"Handling Difference in Inventory": {},
"Handling Difference in Inventory": {
"account_type": "Stock Adjustment"
},
"Items Delivered to Customs on temporary Base": {}
},
},
"Stock in Hand": {
"account_type": "Stock"
}
},
},
"Preliminary and Preoperating Expenses": {
"Preoperating Expenses": {}
},
},
"Prepayments & Deposits": {
"Deposits": {
"Deposit - Office Rent": {},
"Deposit Others": {},
"Deposit to Immigration (Visa)": {},
"Deposit - Office Rent": {},
"Deposit Others": {},
"Deposit to Immigration (Visa)": {},
"Deposits - Customs": {}
},
},
"Prepaid Taxes": {
"Sales Taxes Receivables": {},
"Sales Taxes Receivables": {},
"Withholding Tax Receivables": {}
},
},
"Prepayments": {
"Other Prepayments": {},
"PrePaid Advertisement Expenses": {},
"Prepaid Bank Guarantee": {},
"Prepaid Consultancy Fees": {},
"Prepaid Employees Housing": {},
"Prepaid Finance charge for Loans": {},
"Prepaid Legal Fees": {},
"Prepaid License Fees": {},
"Prepaid Life Insurance": {},
"Prepaid Maintenance": {},
"Prepaid Medical Insurance": {},
"Prepaid Office Rent": {},
"Prepaid Other Insurance": {},
"Prepaid Schooling Fees": {},
"Prepaid Site Hosting Fees": {},
"Other Prepayments": {},
"PrePaid Advertisement Expenses": {},
"Prepaid Bank Guarantee": {},
"Prepaid Consultancy Fees": {},
"Prepaid Employees Housing": {},
"Prepaid Finance charge for Loans": {},
"Prepaid Legal Fees": {},
"Prepaid License Fees": {},
"Prepaid Life Insurance": {},
"Prepaid Maintenance": {},
"Prepaid Medical Insurance": {},
"Prepaid Office Rent": {},
"Prepaid Other Insurance": {},
"Prepaid Schooling Fees": {},
"Prepaid Site Hosting Fees": {},
"Prepaid Sponsorship Fees": {}
}
}
},
},
"Long Term Assets": {
"Fixed Assets": {
"Accumulated Depreciation": {
"Acc. Depreciation of Motor Vehicles": {
"account_type": "Accumulated Depreciation"
},
},
"Acc. Deprn.Computer Hardware & Software": {
"account_type": "Accumulated Depreciation"
},
},
"Acc.Deprn.of Furniture & Office Equipment": {
"account_type": "Accumulated Depreciation"
},
},
"Amortisation on Leasehold Improvement": {
"account_type": "Accumulated Depreciation"
},
},
"account_type": "Accumulated Depreciation"
},
},
"Fixed Assets (Cost Price)": {
"Computer Hardware & Software": {
"account_type": "Fixed Asset"
},
},
"Furniture and Equipment": {
"account_type": "Fixed Asset"
},
"Leasehold Improvement": {},
},
"Leasehold Improvement": {},
"Motor Vehicles": {
"account_type": "Fixed Asset"
},
"Work In Progress": {},
},
"Work In Progress": {},
"account_type": "Fixed Asset"
}
},
},
"Intangible Assets": {
"Computer Card Renewal": {},
"Disposal of Outlets": {},
"Computer Card Renewal": {},
"Disposal of Outlets": {},
"Registration of Trademarks": {}
},
"Intercompany Accounts": {},
},
"Intercompany Accounts": {},
"Investments": {
"Investments in Subsidiaries": {}
}
},
},
"root_type": "Asset"
},
},
"Closing And Temporary Accounts": {
"Closing Accounts": {
"Closing Account": {}
},
},
"root_type": "Liability"
},
},
"Expenses": {
"Commercial Expenses": {
"Consultancy Fees": {},
"Consultancy Fees": {},
"Provision for Doubtful Debts": {}
},
},
"Cost of Sale": {
"Cost Of Goods Sold": {
"Cost Of Goods Sold I/C Sales": {},
"Cost Of Goods Sold I/C Sales": {},
"Cost of Goods Sold in Trading": {
"account_type": "Cost of Goods Sold"
},
},
"account_type": "Cost of Goods Sold"
},
},
"Expenses Included In Valuation": {
"account_type": "Expenses Included In Valuation"
},
"Stock Adjustment": {
"account_type": "Stock Adjustment"
}
},
},
"Depreciation": {
"Depreciation & Amortization": {
"Amortization on Leasehold Improvement": {},
"Amortization on Leasehold Improvement": {},
"Depreciation Of Computer Hard & Soft": {
"account_type": "Depreciation"
},
},
"Depreciation Of Furniture & Office Equipment\n\t\t\t": {
"account_type": "Depreciation"
},
},
"Depreciation Of Motor Vehicles": {
"account_type": "Depreciation"
}
}
},
},
"Direct Expenses": {
"Financial Charges": {
"Air Miles Card Charges": {},
"Amex Credit Cards Charges": {},
"Bank Finance & Loan Charges": {},
"Credit Card Charges": {},
"Credit Card Swipe Charges": {},
"Air Miles Card Charges": {},
"Amex Credit Cards Charges": {},
"Bank Finance & Loan Charges": {},
"Credit Card Charges": {},
"Credit Card Swipe Charges": {},
"PayPal Charges": {}
}
},
},
"MISC Charges": {
"Other Charges": {
"Capital Loss": {
"Disposal of Business Branch": {},
"Loss On Fixed Assets Disposal": {},
"Disposal of Business Branch": {},
"Loss On Fixed Assets Disposal": {},
"Loss on Difference on Exchange": {}
},
},
"Other Non Operating Exp": {
"Other Non Operating Expenses": {}
},
},
"Previous Year Adjustments": {
"Previous Year Adjustments Account": {}
},
},
"Royalty Fees": {
"Royalty to Parent Co.": {}
},
},
"Tax / Zakat Expenses": {
"Income Tax": {
"account_type": "Tax"
},
"Zakat": {},
},
"Zakat": {},
"account_type": "Tax"
}
}
},
},
"Share Resources": {
"Share Resource Expenses Account": {}
},
},
"Store Operating Expenses": {
"Selling, General & Admin Expenses": {
"Advertising Expenses": {
"Other - Advertising Expenses": {}
},
},
"Bank & Finance Charges": {
"Other Bank Charges": {}
},
},
"Communications": {
"Courier": {},
"Others - Communication": {},
"Telephone": {},
"Courier": {},
"Others - Communication": {},
"Telephone": {},
"Web Site Hosting Fees": {}
},
},
"Office & Various Expenses": {
"Cleaning": {},
"Conveyance Expenses": {},
"Gifts & Donations": {},
"Insurance": {},
"Kitchen and Buffet Expenses": {},
"Maintenance": {},
"Others - Office Various Expenses": {},
"Security & Guard": {},
"Stationary From Suppliers": {},
"Stationary Out Of Stock": {},
"Subscriptions": {},
"Training": {},
"Cleaning": {},
"Conveyance Expenses": {},
"Gifts & Donations": {},
"Insurance": {},
"Kitchen and Buffet Expenses": {},
"Maintenance": {},
"Others - Office Various Expenses": {},
"Security & Guard": {},
"Stationary From Suppliers": {},
"Stationary Out Of Stock": {},
"Subscriptions": {},
"Training": {},
"Vehicle Expenses": {}
},
},
"Personnel Cost": {
"Basic Salary": {},
"End Of Service Indemnity": {},
"Housing Allowance": {},
"Leave Salary": {},
"Leave Ticket": {},
"Life Insurance": {},
"Medical Insurance": {},
"Personnel Cost Others": {},
"Sales Commission": {},
"Staff School Allowances": {},
"Transportation Allowance": {},
"Uniform": {},
"Basic Salary": {},
"End Of Service Indemnity": {},
"Housing Allowance": {},
"Leave Salary": {},
"Leave Ticket": {},
"Life Insurance": {},
"Medical Insurance": {},
"Personnel Cost Others": {},
"Sales Commission": {},
"Staff School Allowances": {},
"Transportation Allowance": {},
"Uniform": {},
"Visa Expenses": {}
},
},
"Professional & Legal Fees": {
"Audit Fees": {},
"Legal fees": {},
"Others - Professional Fees": {},
"Sponsorship Fees": {},
"Audit Fees": {},
"Legal fees": {},
"Others - Professional Fees": {},
"Sponsorship Fees": {},
"Trade License Fees": {}
},
},
"Provision & Write Off": {
"Amortisation of Preoperating Expenses": {},
"Cash Shortage": {},
"Others - Provision & Write off": {},
"Write Off Inventory": {},
"Amortisation of Preoperating Expenses": {},
"Cash Shortage": {},
"Others - Provision & Write off": {},
"Write Off Inventory": {},
"Write Off Receivables & Payables": {}
},
},
"Rent Expenses": {
"Office Rent": {},
"Office Rent": {},
"Warehouse Rent": {}
},
},
"Travel Expenses": {
"Air tickets": {},
"Hotel": {},
"Meals": {},
"Others": {},
"Air tickets": {},
"Hotel": {},
"Meals": {},
"Others": {},
"Per Diem": {}
},
},
"Utilities": {
"Other Utility Cahrges": {},
"Other Utility Cahrges": {},
"Water & Electricity": {}
}
}
},
},
"root_type": "Expense"
},
},
"Liabilities": {
"Current Liabilities": {
"Accounts Payable": {
"Payables": {
"Advance Payable to Suppliers": {
"account_type": "Payable"
},
},
"Consigned Payable": {
"account_type": "Payable"
},
},
"Other Payable": {
"account_type": "Payable"
},
},
"Post Dated Cheques Paid": {
"account_type": "Payable"
},
"Staff Payable": {},
},
"Staff Payable": {},
"Suppliers Price Protection": {
"account_type": "Payable"
},
},
"Trade Payable": {
"account_type": "Payable"
},
},
"account_type": "Payable"
}
},
},
"Accruals & Provisions": {
"Accruals": {
"Accrued Personnel Cost": {
"Accrued - Commissions": {},
"Accrued - Leave Salary": {},
"Accrued - Leave Tickets": {},
"Accrued - Salaries": {},
"Accrued Other Personnel Cost": {},
"Accrued Salaries Increment": {},
"Accrued - Commissions": {},
"Accrued - Leave Salary": {},
"Accrued - Leave Tickets": {},
"Accrued - Salaries": {},
"Accrued Other Personnel Cost": {},
"Accrued Salaries Increment": {},
"Accrued-Staff Bonus": {}
}
},
},
"Accrued Expenses": {
"Accrued Other Expenses": {
"Accrued - Audit Fees": {},
"Accrued - Office Rent": {},
"Accrued - Sponsorship": {},
"Accrued - Telephone": {},
"Accrued - Utilities": {},
"Accrued - Audit Fees": {},
"Accrued - Office Rent": {},
"Accrued - Sponsorship": {},
"Accrued - Telephone": {},
"Accrued - Utilities": {},
"Accrued Others": {}
}
},
},
"Other Current Liabilities": {
"Accrued Dubai Customs": {},
"Deferred income": {},
"Accrued Dubai Customs": {},
"Deferred income": {},
"Shipping & Handling": {}
},
},
"Provisions": {
"Tax Payables": {
"Income Tax Payable": {},
"Sales Tax Payable": {},
"Income Tax Payable": {},
"Sales Tax Payable": {},
"Withholding Tax Payable": {}
}
},
},
"Short Term Loan": {}
},
},
"Duties and Taxes": {
"account_type": "Tax",
"account_type": "Tax",
"is_group": 1
},
},
"Reservations & Credit Notes": {
"Credit Notes": {
"Credit Notes to Customers": {},
"Credit Notes to Customers": {},
"Reservations": {}
}
},
},
"Stock Liabilities": {
"Stock Received But Not Billed": {
"account_type": "Stock Received But Not Billed"
}
},
},
"Unearned Income": {}
},
},
"Long Term Liabilities": {
"Long Term Loans & Provisions": {}
},
},
"root_type": "Liability"
},
},
"Revenue": {
"Direct Revenue": {
"Other Direct Revenue": {
"Other Revenue - Operating": {
"Advertising Income": {},
"Branding Income": {},
"Early Setmt Margin from Suppliers": {},
"Marketing Rebate from Suppliers": {},
"Rebate from Suppliers": {},
"Service Income": {},
"Advertising Income": {},
"Branding Income": {},
"Early Setmt Margin from Suppliers": {},
"Marketing Rebate from Suppliers": {},
"Rebate from Suppliers": {},
"Service Income": {},
"Space Rental Income": {}
}
}
},
},
"Indirect Revenue": {
"Other Indirect Revenue": {
"Capital Gain": {},
"Excess In Till": {},
"Gain On Difference Of Exchange": {},
"Management Consultancy Fees": {},
"Capital Gain": {},
"Excess In Till": {},
"Gain On Difference Of Exchange": {},
"Management Consultancy Fees": {},
"Other Income": {}
},
},
"Other Revenue - Non Operating": {
"Interest Revenue": {},
"Interest from FD": {},
"Products Listing Fees from Suppliers": {},
"Interest Revenue": {},
"Interest from FD": {},
"Products Listing Fees from Suppliers": {},
"Trade Opening Fees from suppliers": {}
}
},
},
"Sales": {
"Sales from Other Regions": {
"Sales from Other Region": {}
},
},
"Sales of same region": {
"Management Consultancy Fees 1": {},
"Sales Account": {},
"Management Consultancy Fees 1": {},
"Sales Account": {},
"Sales of I/C": {}
}
},
},
"root_type": "Income"
},
},
"Share Holder Equity": {
"Capital": {
"Contributed Capital": {},
"Share Capital": {},
"Shareholders Current A/c": {},
"Sub Ordinated Loan": {},
"Contributed Capital": {},
"Share Capital": {},
"Shareholders Current A/c": {},
"Sub Ordinated Loan": {},
"Treasury Stocks": {}
},
},
"Retained Earnings": {
"Current Year Results": {},
"Dividends Paid": {},
"Current Year Results": {},
"Dividends Paid": {},
"Previous Years Results": {}
},
"account_type": "Equity",
},
"account_type": "Equity",
"root_type": "Equity"
}
}

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,21 +292,18 @@
"Umsatzsteuerforderungen fr\u00fchere Jahre": {}
},
"Sonstige Verm\u00f6gensgegenst\u00e4nde oder sonstige Verbindlichkeiten": {
"Abziehbare Vorsteuer": {
"account_type": "Tax",
"is_group": 1,
"Abziehbare Vorsteuer 16%": {},
"Abziehbare Vorsteuer 19%": {},
"Abziehbare Vorsteuer 7%": {},
"Abziehbare Vorsteuer aus der Auslagerung von Gegenst\u00e4nden aus einem Unsatzsteuerlager": {},
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb": {},
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 16%": {},
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 19%": {},
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb von Neufahrzeugen von Lieferanten ohne Ust-Identifikationsnummer": {},
"Abziehbare Vorsteuer nach \u00a7 13b UStG ": {},
"Abziehbare Vorsteuer nach \u00a7 13b UStG 16%": {},
"Abziehbare Vorsteuer nach \u00a7 13b UStG 19%": {}
},
"Abziehbare Vorsteuer": {},
"Abziehbare Vorsteuer 16%": {},
"Abziehbare Vorsteuer 19%": {},
"Abziehbare Vorsteuer 7%": {},
"Abziehbare Vorsteuer aus der Auslagerung von Gegenst\u00e4nden aus einem Unsatzsteuerlager": {},
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb": {},
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 16%": {},
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 19%": {},
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb von Neufahrzeugen von Lieferanten ohne Ust-Identifikationsnummer": {},
"Abziehbare Vorsteuer nach \u00a7 13b UStG ": {},
"Abziehbare Vorsteuer nach \u00a7 13b UStG 16%": {},
"Abziehbare Vorsteuer nach \u00a7 13b UStG 19%": {},
"Aufl\u00f6sung Vorsteuer aus Vorjahr \u00a7 4/3 EStG": {},
"Aufzuteilende Vorsteuer": {},
"Aufzuteilende Vorsteuer 16%": {},
@@ -676,26 +673,23 @@
"Sonstige Verrechnungskonten (Interimskonto)": {
"account_type": "Stock Received But Not Billed"
},
"Umsatzsteuer": {
"account_type": "Tax",
"is_group": 1,
"Umsatzsteuer 16%": {},
"Umsatzsteuer 19%": {},
"Umsatzsteuer 7%": {},
"Umsatzsteuer Vorjahr": {},
"Umsatzsteuer aus der Auslagerung von Gegenst\u00e4nden aus einem Umsatzsteuerlager": {},
"Umsatzsteuer aus im Inland steuerpflichtigen EG-Lieferungen": {},
"Umsatzsteuer aus im Inland steuerpflichtigen EG-Lieferungen 19%": {},
"Umsatzsteuer aus innergemeinschaftlichem Erwerb ": {},
"Umsatzsteuer aus innergemeinschaftlichem Erwerb 16%": {},
"Umsatzsteuer aus innergemeinschaftlichem Erwerb 19%": {},
"Umsatzsteuer aus innergemeinschaftlichem Erwerb ohne Vorsteuerabzug": {},
"Umsatzsteuer fr\u00fchere Jahre": {},
"Umsatzsteuer laufendes Jahr": {},
"Umsatzsteuer nach \u00a713b UStG": {},
"Umsatzsteuer nach \u00a713b UStG 16%": {},
"Umsatzsteuer nach \u00a713b UStG 19%": {}
},
"Umsatzsteuer": {},
"Umsatzsteuer 16%": {},
"Umsatzsteuer 19%": {},
"Umsatzsteuer 7%": {},
"Umsatzsteuer Vorjahr": {},
"Umsatzsteuer aus der Auslagerung von Gegenst\u00e4nden aus einem Umsatzsteuerlager": {},
"Umsatzsteuer aus im Inland steuerpflichtigen EG-Lieferungen": {},
"Umsatzsteuer aus im Inland steuerpflichtigen EG-Lieferungen 19%": {},
"Umsatzsteuer aus innergemeinschaftlichem Erwerb ": {},
"Umsatzsteuer aus innergemeinschaftlichem Erwerb 16%": {},
"Umsatzsteuer aus innergemeinschaftlichem Erwerb 19%": {},
"Umsatzsteuer aus innergemeinschaftlichem Erwerb ohne Vorsteuerabzug": {},
"Umsatzsteuer fr\u00fchere Jahre": {},
"Umsatzsteuer laufendes Jahr": {},
"Umsatzsteuer nach \u00a713b UStG": {},
"Umsatzsteuer nach \u00a713b UStG 16%": {},
"Umsatzsteuer nach \u00a713b UStG 19%": {},
"Umsatzsteuer- Vorauszahlungen": {},
"Umsatzsteuer- Vorauszahlungen 1/11": {},
"Verbindlichkeiten aus Lohn- und Kirchensteuer": {}

View File

@@ -2,10 +2,8 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
from frappe import _
def get():
return {
_("Application of Funds (Assets)"): {

View File

@@ -2,10 +2,8 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
from frappe import _
def get():
return {
_("Application of Funds (Assets)"): {
@@ -247,9 +245,6 @@ def get():
"account_number": "2200"
},
_("Duties and Taxes"): {
_("TDS Payable"): {
"account_number": "2310"
},
"account_type": "Tax",
"is_group": 1,
"account_number": "2300"

View File

@@ -2,14 +2,11 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import unittest
import frappe
from erpnext.accounts.doctype.account.account import merge_account, update_account_number
from erpnext.stock import get_company_default_inventory_account, get_warehouse_account
from erpnext.stock import get_warehouse_account, get_company_default_inventory_account
from erpnext.accounts.doctype.account.account import update_account_number
from erpnext.accounts.doctype.account.account import merge_account
class TestAccount(unittest.TestCase):
def test_rename_account(self):
@@ -72,7 +69,6 @@ class TestAccount(unittest.TestCase):
acc.account_name = "Accumulated Depreciation"
acc.parent_account = "Fixed Assets - _TC"
acc.company = "_Test Company"
acc.account_type = "Accumulated Depreciation"
acc.insert()
doc = frappe.get_doc("Account", "Securities and Deposits - _TC")
@@ -102,8 +98,7 @@ class TestAccount(unittest.TestCase):
"Softwares - _TC", doc.is_group, doc.root_type, doc.company)
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.account_name = "Test Sync Account"
acc.parent_account = "Temporary Accounts - _TC3"
@@ -115,68 +110,7 @@ class TestAccount(unittest.TestCase):
self.assertEqual(acc_tc_4, "Test Sync Account - _TC4")
self.assertEqual(acc_tc_5, "Test Sync Account - _TC5")
def test_add_account_to_a_group(self):
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 _make_test_records(verbose=None):
def _make_test_records(verbose):
from frappe.test_runner import make_test_objects
accounts = [
@@ -215,7 +149,7 @@ def _make_test_records(verbose=None):
# fixed asset depreciation
["_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 Gain/Loss on Asset Disposal", "Expenses", 0, None, None],
@@ -258,8 +192,7 @@ def create_account(**kwargs):
account_name = kwargs.get('account_name'),
account_type = kwargs.get('account_type'),
parent_account = kwargs.get('parent_account'),
company = kwargs.get('company'),
account_currency = kwargs.get('account_currency')
company = kwargs.get('company')
))
account.save()

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
frappe.ui.form.on('Accounting Dimension', {
refresh: function(frm) {
frm.set_query('document_type', () => {
let invalid_doctypes = frappe.model.core_doctypes_list;
invalid_doctypes.push('Accounting Dimension', 'Project',
'Cost Center', 'Accounting Dimension Detail', 'Company');
'Cost Center', 'Accounting Dimension Detail');
return {
filters: {
@@ -47,6 +48,12 @@ frappe.ui.form.on('Accounting Dimension', {
frm.set_value('label', 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) => {
if (r && r.document_type) {
frm.set_df_property('document_type', 'description', "Document type is already set as dimension");
@@ -60,4 +67,4 @@ frappe.ui.form.on('Accounting Dimension Detail', {
let row = locals[cdt][cdn];
row.reference_document = frm.doc.document_type;
}
});
});

View File

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

View File

@@ -3,16 +3,15 @@
# For license information, please see license.txt
from __future__ import unicode_literals
import json
import frappe
from frappe import _, scrub
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.model import core_doctypes_list
from frappe import _
import json
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.background_jobs import enqueue
from frappe.model import core_doctypes_list
class AccountingDimension(Document):
def before_insert(self):
@@ -20,7 +19,7 @@ class AccountingDimension(Document):
def validate(self):
if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project',
'Cost Center', 'Accounting Dimension Detail', 'Company', 'Account') :
'Cost Center', 'Accounting Dimension Detail') :
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
frappe.throw(msg)
@@ -28,29 +27,19 @@ class AccountingDimension(Document):
exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name'])
if exists and self.is_new():
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)
frappe.throw("Document Type already used as a dimension")
def after_insert(self):
if frappe.flags.in_test:
make_dimension_in_accounting_doctypes(doc=self)
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):
if frappe.flags.in_test:
delete_accounting_dimension(doc=self)
else:
frappe.enqueue(delete_accounting_dimension, doc=self, queue='long')
frappe.enqueue(delete_accounting_dimension, doc=self)
def set_fieldname_and_label(self):
if not self.label:
@@ -59,13 +48,8 @@ class AccountingDimension(Document):
if not self.fieldname:
self.fieldname = scrub(self.label)
def on_update(self):
frappe.flags.accounting_dimensions = None
def make_dimension_in_accounting_doctypes(doc, doclist=None):
if not doclist:
doclist = get_doctypes_with_dimensions()
def make_dimension_in_accounting_doctypes(doc):
doclist = get_doctypes_with_dimensions()
doc_count = len(get_accounting_dimensions())
count = 0
@@ -85,14 +69,10 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
"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":
add_dimension_to_budget_doctype(df.copy(), doc)
else:
create_custom_field(doctype, df)
if doctype == "Budget":
add_dimension_to_budget_doctype(df, doc)
else:
create_custom_field(doctype, df)
count += 1
@@ -181,17 +161,23 @@ def toggle_disabling(doc):
frappe.clear_cache(doctype=doctype)
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):
if frappe.flags.accounting_dimensions is None:
frappe.flags.accounting_dimensions = frappe.get_all("Accounting Dimension",
fields=["label", "fieldname", "disabled", "document_type"])
accounting_dimensions = frappe.get_all("Accounting Dimension", fields=["label", "fieldname", "disabled"])
if as_list:
return [d.fieldname for d in frappe.flags.accounting_dimensions]
return [d.fieldname for d in accounting_dimensions]
else:
return frappe.flags.accounting_dimensions
return accounting_dimensions
def get_checks_for_pl_and_bs_accounts():
dimensions = frappe.db.sql("""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
@@ -200,45 +186,20 @@ def get_checks_for_pl_and_bs_accounts():
return dimensions
def get_dimension_with_children(doctype, dimension):
if isinstance(dimension, list):
dimension = dimension[0]
all_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()
def get_dimensions(with_cost_center_and_project=False):
def get_dimension_filters():
dimension_filters = frappe.db.sql("""
SELECT label, fieldname, document_type
FROM `tabAccounting Dimension`
WHERE disabled = 0
""", as_dict=1)
default_dimensions = frappe.db.sql("""SELECT p.fieldname, c.company, c.default_dimension
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 = frappe.db.sql("""SELECT parent, company, default_dimension
FROM `tabAccounting Dimension Detail`""", as_dict=1)
default_dimensions_map = {}
for dimension in default_dimensions:
default_dimensions_map.setdefault(dimension.company, {})
default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension
default_dimensions_map.setdefault(dimension['company'], {})
default_dimensions_map[dimension['company']][dimension['parent']] = dimension['default_dimension']
return dimension_filters, default_dimensions_map

View File

@@ -3,18 +3,45 @@
# See license.txt
from __future__ import unicode_literals
import unittest
import frappe
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
import unittest
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
test_dependencies = ['Cost Center', 'Location', 'Warehouse', 'Department']
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import delete_accounting_dimension
class TestAccountingDimension(unittest.TestCase):
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):
si = create_sales_invoice(do_not_save=1)
@@ -74,38 +101,6 @@ class TestAccountingDimension(unittest.TestCase):
def tearDown(self):
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():
dimension1 = frappe.get_doc("Accounting Dimension", "Department")
@@ -115,3 +110,5 @@ def disable_dimension():
dimension2 = frappe.get_doc("Accounting Dimension", "Location")
dimension2.disabled = 1
dimension2.save()

View File

@@ -3,10 +3,8 @@
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class AccountingDimensionDetail(Document):
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: #f9f9f9;">
<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
}

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