Merge branch 'develop' of github.com:frappe/erpnext into refactor-website-routing
This commit is contained in:
commit
d1902dfadb
1
.flake8
1
.flake8
@ -30,3 +30,4 @@ ignore =
|
|||||||
W191,
|
W191,
|
||||||
|
|
||||||
max-line-length = 200
|
max-line-length = 200
|
||||||
|
exclude=.github/helper/semgrep_rules
|
||||||
|
|||||||
@ -4,25 +4,61 @@ from frappe import _, flt
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
# ruleid: frappe-modifying-but-not-comitting
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
if self.value_of_goods == 0:
|
if self.value_of_goods == 0:
|
||||||
frappe.throw(_('Value of goods cannot be 0'))
|
frappe.throw(_('Value of goods cannot be 0'))
|
||||||
# ruleid: frappe-modifying-after-submit
|
|
||||||
self.status = 'Submitted'
|
self.status = 'Submitted'
|
||||||
|
|
||||||
|
|
||||||
|
# ok: frappe-modifying-but-not-comitting
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
if flt(self.per_billed) < 100:
|
if self.value_of_goods == 0:
|
||||||
self.update_billing_status()
|
frappe.throw(_('Value of goods cannot be 0'))
|
||||||
else:
|
self.status = 'Submitted'
|
||||||
# todook: frappe-modifying-after-submit
|
self.db_set('status', 'Submitted')
|
||||||
self.status = "Completed"
|
|
||||||
self.db_set("status", "Completed")
|
|
||||||
|
|
||||||
class TestDoc(Document):
|
# ok: frappe-modifying-but-not-comitting
|
||||||
pass
|
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)
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
#ruleid: frappe-modifying-child-tables-while-iterating
|
# ok: frappe-modifying-but-not-comitting
|
||||||
for item in self.child_table:
|
def on_submit(self):
|
||||||
if item.value < 0:
|
x = "y"
|
||||||
self.remove(item)
|
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"
|
||||||
|
|||||||
@ -1,32 +1,93 @@
|
|||||||
# This file specifies rules for correctness according to how frappe doctype data model works.
|
# This file specifies rules for correctness according to how frappe doctype data model works.
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
- id: frappe-modifying-after-submit
|
- id: frappe-modifying-but-not-comitting
|
||||||
patterns:
|
patterns:
|
||||||
- pattern: self.$ATTR = ...
|
- pattern: |
|
||||||
- pattern-inside: |
|
def $METHOD(self, ...):
|
||||||
def on_submit(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-regex:
|
||||||
metavariable: '$ATTR'
|
metavariable: '$ATTR'
|
||||||
# this is negative look-ahead, add more attrs to ignore like (ignore|ignore_this_too|ignore_me)
|
# this is negative look-ahead, add more attrs to ignore like (ignore|ignore_this_too|ignore_me)
|
||||||
regex: '^(?!status_updater)(.*)$'
|
regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$'
|
||||||
|
- metavariable-regex:
|
||||||
|
metavariable: "$METHOD"
|
||||||
|
regex: "(on_submit|on_cancel)"
|
||||||
message: |
|
message: |
|
||||||
Doctype modified after submission. Please check if modification of self.$ATTR is commited to database.
|
DocType modified in self.$METHOD. Please check if modification of self.$ATTR is commited to database.
|
||||||
languages: [python]
|
languages: [python]
|
||||||
severity: ERROR
|
severity: ERROR
|
||||||
|
|
||||||
- id: frappe-modifying-after-cancel
|
- id: frappe-modifying-but-not-comitting-other-method
|
||||||
patterns:
|
patterns:
|
||||||
- pattern: self.$ATTR = ...
|
- pattern: |
|
||||||
- pattern-inside: |
|
class $DOCTYPE(...):
|
||||||
def on_cancel(self, ...):
|
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-regex:
|
||||||
metavariable: '$ATTR'
|
metavariable: "$METHOD"
|
||||||
regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$'
|
regex: "(on_submit|on_cancel)"
|
||||||
message: |
|
message: |
|
||||||
Doctype modified after cancellation. Please check if modification of self.$ATTR is commited to database.
|
self.$ANOTHER_METHOD is called from self.$METHOD, check if changes to self.$ATTR are commited to database.
|
||||||
languages: [python]
|
languages: [python]
|
||||||
severity: ERROR
|
severity: ERROR
|
||||||
|
|
||||||
|
|||||||
7
.github/helper/semgrep_rules/translate.js
vendored
7
.github/helper/semgrep_rules/translate.js
vendored
@ -35,3 +35,10 @@ __('You have' + 'subscribers in your mailing list.')
|
|||||||
// ruleid: frappe-translation-js-splitting
|
// ruleid: frappe-translation-js-splitting
|
||||||
__('You have {0} subscribers' +
|
__('You have {0} subscribers' +
|
||||||
'in your mailing list', [subscribers.length])
|
'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])
|
||||||
|
|||||||
8
.github/helper/semgrep_rules/translate.py
vendored
8
.github/helper/semgrep_rules/translate.py
vendored
@ -51,3 +51,11 @@ _(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
|
||||||
|
|||||||
9
.github/helper/semgrep_rules/translate.yml
vendored
9
.github/helper/semgrep_rules/translate.yml
vendored
@ -42,9 +42,10 @@ rules:
|
|||||||
|
|
||||||
- id: frappe-translation-python-splitting
|
- id: frappe-translation-python-splitting
|
||||||
pattern-either:
|
pattern-either:
|
||||||
- pattern: _(...) + ... + _(...)
|
- pattern: _(...) + _(...)
|
||||||
- pattern: _("..." + "...")
|
- pattern: _("..." + "...")
|
||||||
- pattern-regex: '_\([^\)]*\\\s*'
|
- pattern-regex: '[\s\.]_\([^\)]*\\\s*' # lines broken by `\`
|
||||||
|
- pattern-regex: '[\s\.]_\(\s*\n' # line breaks allowed by python for using ( )
|
||||||
message: |
|
message: |
|
||||||
Do not split strings inside translate function. Do not concatenate using translate functions.
|
Do not split strings inside translate function. Do not concatenate using translate functions.
|
||||||
Please refer: https://frappeframework.com/docs/user/en/translations
|
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||||
@ -53,8 +54,8 @@ rules:
|
|||||||
|
|
||||||
- id: frappe-translation-js-splitting
|
- id: frappe-translation-js-splitting
|
||||||
pattern-either:
|
pattern-either:
|
||||||
- pattern-regex: '__\([^\)]*[\+\\]\s*'
|
- pattern-regex: '__\([^\)]*[\\]\s+'
|
||||||
- pattern: __('...' + '...')
|
- pattern: __('...' + '...', ...)
|
||||||
- pattern: __('...') + __('...')
|
- pattern: __('...') + __('...')
|
||||||
message: |
|
message: |
|
||||||
Do not split strings inside translate function. Do not concatenate using translate functions.
|
Do not split strings inside translate function. Do not concatenate using translate functions.
|
||||||
|
|||||||
9
.github/helper/semgrep_rules/ux.js
vendored
Normal file
9
.github/helper/semgrep_rules/ux.js
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
// ok: frappe-missing-translate-function-js
|
||||||
|
frappe.msgprint('{{ _("Both login and password required") }}');
|
||||||
|
|
||||||
|
// ruleid: frappe-missing-translate-function-js
|
||||||
|
frappe.msgprint('What');
|
||||||
|
|
||||||
|
// ok: frappe-missing-translate-function-js
|
||||||
|
frappe.throw(' {{ _("Both login and password required") }}. ');
|
||||||
18
.github/helper/semgrep_rules/ux.py
vendored
18
.github/helper/semgrep_rules/ux.py
vendored
@ -2,30 +2,30 @@ import frappe
|
|||||||
from frappe import msgprint, throw, _
|
from frappe import msgprint, throw, _
|
||||||
|
|
||||||
|
|
||||||
# ruleid: frappe-missing-translate-function
|
# ruleid: frappe-missing-translate-function-python
|
||||||
throw("Error Occured")
|
throw("Error Occured")
|
||||||
|
|
||||||
# ruleid: frappe-missing-translate-function
|
# ruleid: frappe-missing-translate-function-python
|
||||||
frappe.throw("Error Occured")
|
frappe.throw("Error Occured")
|
||||||
|
|
||||||
# ruleid: frappe-missing-translate-function
|
# ruleid: frappe-missing-translate-function-python
|
||||||
frappe.msgprint("Useful message")
|
frappe.msgprint("Useful message")
|
||||||
|
|
||||||
# ruleid: frappe-missing-translate-function
|
# ruleid: frappe-missing-translate-function-python
|
||||||
msgprint("Useful message")
|
msgprint("Useful message")
|
||||||
|
|
||||||
|
|
||||||
# ok: frappe-missing-translate-function
|
# ok: frappe-missing-translate-function-python
|
||||||
translatedmessage = _("Hello")
|
translatedmessage = _("Hello")
|
||||||
|
|
||||||
# ok: frappe-missing-translate-function
|
# ok: frappe-missing-translate-function-python
|
||||||
throw(translatedmessage)
|
throw(translatedmessage)
|
||||||
|
|
||||||
# ok: frappe-missing-translate-function
|
# ok: frappe-missing-translate-function-python
|
||||||
msgprint(translatedmessage)
|
msgprint(translatedmessage)
|
||||||
|
|
||||||
# ok: frappe-missing-translate-function
|
# ok: frappe-missing-translate-function-python
|
||||||
msgprint(_("Helpful message"))
|
msgprint(_("Helpful message"))
|
||||||
|
|
||||||
# ok: frappe-missing-translate-function
|
# ok: frappe-missing-translate-function-python
|
||||||
frappe.throw(_("Error occured"))
|
frappe.throw(_("Error occured"))
|
||||||
|
|||||||
23
.github/helper/semgrep_rules/ux.yml
vendored
23
.github/helper/semgrep_rules/ux.yml
vendored
@ -1,15 +1,30 @@
|
|||||||
rules:
|
rules:
|
||||||
- id: frappe-missing-translate-function
|
- id: frappe-missing-translate-function-python
|
||||||
pattern-either:
|
pattern-either:
|
||||||
- patterns:
|
- patterns:
|
||||||
- pattern: frappe.msgprint("...", ...)
|
- pattern: frappe.msgprint("...", ...)
|
||||||
- pattern-not: frappe.msgprint(_("..."), ...)
|
- pattern-not: frappe.msgprint(_("..."), ...)
|
||||||
- pattern-not: frappe.msgprint(__("..."), ...)
|
|
||||||
- patterns:
|
- patterns:
|
||||||
- pattern: frappe.throw("...", ...)
|
- pattern: frappe.throw("...", ...)
|
||||||
- pattern-not: frappe.throw(_("..."), ...)
|
- pattern-not: frappe.throw(_("..."), ...)
|
||||||
- pattern-not: frappe.throw(__("..."), ...)
|
|
||||||
message: |
|
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
|
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, javascript, json]
|
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
|
severity: ERROR
|
||||||
|
|||||||
6
.github/workflows/patch.yml
vendored
6
.github/workflows/patch.yml
vendored
@ -66,4 +66,8 @@ jobs:
|
|||||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||||
|
|
||||||
- name: Run Patch Tests
|
- name: Run Patch Tests
|
||||||
run: cd ~/frappe-bench/ && 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 && bench --site test_site migrate
|
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
|
||||||
|
|||||||
12
.github/workflows/semgrep.yml
vendored
12
.github/workflows/semgrep.yml
vendored
@ -4,6 +4,8 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- develop
|
- develop
|
||||||
|
- version-13-hotfix
|
||||||
|
- version-13-pre-release
|
||||||
jobs:
|
jobs:
|
||||||
semgrep:
|
semgrep:
|
||||||
name: Frappe Linter
|
name: Frappe Linter
|
||||||
@ -14,11 +16,19 @@ jobs:
|
|||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: 3.8
|
||||||
- name: Run semgrep
|
|
||||||
|
- name: Setup semgrep
|
||||||
run: |
|
run: |
|
||||||
python -m pip install -q semgrep
|
python -m pip install -q semgrep
|
||||||
git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q
|
git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q
|
||||||
|
|
||||||
|
- name: Semgrep errors
|
||||||
|
run: |
|
||||||
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
|
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
|
||||||
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity ERROR --config=.github/helper/semgrep_rules --quiet --error $files
|
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity ERROR --config=.github/helper/semgrep_rules --quiet --error $files
|
||||||
semgrep --config="r/python.lang.correctness" --quiet --error $files
|
semgrep --config="r/python.lang.correctness" --quiet --error $files
|
||||||
|
|
||||||
|
- name: Semgrep warnings
|
||||||
|
run: |
|
||||||
|
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
|
||||||
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files
|
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files
|
||||||
|
|||||||
7
.github/workflows/server-tests.yml
vendored
7
.github/workflows/server-tests.yml
vendored
@ -1,6 +1,10 @@
|
|||||||
name: Server
|
name: Server
|
||||||
|
|
||||||
on: [pull_request, workflow_dispatch]
|
on:
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches: [ develop ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
@ -87,6 +91,7 @@ jobs:
|
|||||||
coveralls
|
coveralls
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
|
||||||
COVERALLS_FLAG_NAME: run-${{ matrix.container }}
|
COVERALLS_FLAG_NAME: run-${{ matrix.container }}
|
||||||
COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }}
|
COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }}
|
||||||
COVERALLS_PARALLEL: true
|
COVERALLS_PARALLEL: true
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<p>ERP made simple</p>
|
<p>ERP made simple</p>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://github.com/frappe/erpnext/actions/workflows/ci-tests.yml)
|
[](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml)
|
||||||
[](https://www.codetriage.com/frappe/erpnext)
|
[](https://www.codetriage.com/frappe/erpnext)
|
||||||
[](https://coveralls.io/github/frappe/erpnext?branch=develop)
|
[](https://coveralls.io/github/frappe/erpnext?branch=develop)
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import frappe
|
|||||||
from erpnext.hooks import regional_overrides
|
from erpnext.hooks import regional_overrides
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
|
|
||||||
__version__ = '13.3.1'
|
__version__ = '13.4.1'
|
||||||
|
|
||||||
def get_default_company(user=None):
|
def get_default_company(user=None):
|
||||||
'''Get default company for user'''
|
'''Get default company for user'''
|
||||||
|
|||||||
@ -41,7 +41,7 @@ def build_conditions(process_type, account, company):
|
|||||||
if account:
|
if account:
|
||||||
conditions += "AND %s='%s'"%(deferred_account, account)
|
conditions += "AND %s='%s'"%(deferred_account, account)
|
||||||
elif company:
|
elif company:
|
||||||
conditions += "AND p.company='%s'"%(company)
|
conditions += f"AND p.company = {frappe.db.escape(company)}"
|
||||||
|
|
||||||
return conditions
|
return conditions
|
||||||
|
|
||||||
@ -360,12 +360,10 @@ def make_gl_entries(doc, credit_account, debit_account, against,
|
|||||||
frappe.flags.deferred_accounting_error = True
|
frappe.flags.deferred_accounting_error = True
|
||||||
|
|
||||||
def send_mail(deferred_process):
|
def send_mail(deferred_process):
|
||||||
title = _("Error while processing deferred accounting for {0}".format(deferred_process))
|
title = _("Error while processing deferred accounting for {0}").format(deferred_process)
|
||||||
content = _("""
|
link = get_link_to_form('Process Deferred Accounting', deferred_process)
|
||||||
Deferred accounting failed for some invoices:
|
content = _("Deferred accounting failed for some invoices:") + "\n"
|
||||||
Please check Process Deferred Accounting {0}
|
content += _("Please check Process Deferred Accounting {0} and submit manually after resolving errors.").format(link)
|
||||||
and submit manually after resolving errors
|
|
||||||
""").format(get_link_to_form('Process Deferred Accounting', deferred_process))
|
|
||||||
sendmail_to_system_managers(title, content)
|
sendmail_to_system_managers(title, content)
|
||||||
|
|
||||||
def book_revenue_via_journal_entry(doc, credit_account, debit_account, against,
|
def book_revenue_via_journal_entry(doc, credit_account, debit_account, against,
|
||||||
|
|||||||
@ -54,7 +54,7 @@ class CForm(Document):
|
|||||||
frappe.throw(_("Please enter atleast 1 invoice in the table"))
|
frappe.throw(_("Please enter atleast 1 invoice in the table"))
|
||||||
|
|
||||||
def set_total_invoiced_amount(self):
|
def set_total_invoiced_amount(self):
|
||||||
total = sum([flt(d.grand_total) for d in self.get('invoices')])
|
total = sum(flt(d.grand_total) for d in self.get('invoices'))
|
||||||
frappe.db.set(self, 'total_invoiced_amount', total)
|
frappe.db.set(self, 'total_invoiced_amount', total)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@ -14,7 +14,7 @@ class CouponCode(Document):
|
|||||||
|
|
||||||
if not self.coupon_code:
|
if not self.coupon_code:
|
||||||
if self.coupon_type == "Promotional":
|
if self.coupon_type == "Promotional":
|
||||||
self.coupon_code =''.join([i for i in self.coupon_name if not i.isdigit()])[0:8].upper()
|
self.coupon_code =''.join(i for i in self.coupon_name if not i.isdigit())[0:8].upper()
|
||||||
elif self.coupon_type == "Gift Card":
|
elif self.coupon_type == "Gift Card":
|
||||||
self.coupon_code = frappe.generate_hash()[:10].upper()
|
self.coupon_code = frappe.generate_hash()[:10].upper()
|
||||||
|
|
||||||
|
|||||||
@ -75,8 +75,13 @@ class GLEntry(Document):
|
|||||||
def pl_must_have_cost_center(self):
|
def pl_must_have_cost_center(self):
|
||||||
if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss":
|
if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss":
|
||||||
if not self.cost_center and self.voucher_type != 'Period Closing Voucher':
|
if not self.cost_center and self.voucher_type != 'Period Closing Voucher':
|
||||||
frappe.throw(_("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}. Please set up a default Cost Center for the Company.")
|
msg = _("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}.").format(
|
||||||
.format(self.voucher_type, self.voucher_no, self.account))
|
self.voucher_type, self.voucher_no, self.account)
|
||||||
|
msg += " "
|
||||||
|
msg += _("Please set the cost center field in {0} or setup a default Cost Center for the Company.").format(
|
||||||
|
self.voucher_type)
|
||||||
|
|
||||||
|
frappe.throw(msg, title=_("Missing Cost Center"))
|
||||||
|
|
||||||
def validate_dimensions_for_pl_and_bs(self):
|
def validate_dimensions_for_pl_and_bs(self):
|
||||||
account_type = frappe.db.get_value("Account", self.account, "report_type")
|
account_type = frappe.db.get_value("Account", self.account, "report_type")
|
||||||
|
|||||||
@ -1,196 +1,82 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2018-01-02 15:48:58.768352",
|
"creation": "2018-01-02 15:48:58.768352",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"company",
|
||||||
|
"cgst_account",
|
||||||
|
"sgst_account",
|
||||||
|
"igst_account",
|
||||||
|
"cess_account",
|
||||||
|
"is_reverse_charge_account"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 1,
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Company",
|
"label": "Company",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Company",
|
"options": "Company",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 2,
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "cgst_account",
|
"fieldname": "cgst_account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "CGST Account",
|
"label": "CGST Account",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Account",
|
"options": "Account",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 2,
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "sgst_account",
|
"fieldname": "sgst_account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "SGST Account",
|
"label": "SGST Account",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Account",
|
"options": "Account",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 2,
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "igst_account",
|
"fieldname": "igst_account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "IGST Account",
|
"label": "IGST Account",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Account",
|
"options": "Account",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 2,
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "cess_account",
|
"fieldname": "cess_account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "CESS Account",
|
"label": "CESS Account",
|
||||||
"length": 0,
|
"options": "Account"
|
||||||
"no_copy": 0,
|
},
|
||||||
"options": "Account",
|
{
|
||||||
"permlevel": 0,
|
"columns": 1,
|
||||||
"precision": "",
|
"default": "0",
|
||||||
"print_hide": 0,
|
"fieldname": "is_reverse_charge_account",
|
||||||
"print_hide_if_no_value": 0,
|
"fieldtype": "Check",
|
||||||
"read_only": 0,
|
"in_list_view": 1,
|
||||||
"remember_last_selected_value": 0,
|
"label": "Is Reverse Charge Account"
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"index_web_pages_for_search": 1,
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 0,
|
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"links": [],
|
||||||
"modified": "2018-01-02 15:52:22.335988",
|
"modified": "2021-04-09 12:30:25.889993",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "GST Account",
|
"name": "GST Account",
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1,
|
"track_changes": 1
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@ class InvoiceDiscounting(AccountsController):
|
|||||||
record.idx, frappe.bold(actual_outstanding), frappe.bold(record.sales_invoice)))
|
record.idx, frappe.bold(actual_outstanding), frappe.bold(record.sales_invoice)))
|
||||||
|
|
||||||
def calculate_total_amount(self):
|
def calculate_total_amount(self):
|
||||||
self.total_amount = sum([flt(d.outstanding_amount) for d in self.invoices])
|
self.total_amount = sum(flt(d.outstanding_amount) for d in self.invoices)
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.update_sales_invoice()
|
self.update_sales_invoice()
|
||||||
|
|||||||
@ -39,7 +39,11 @@ class JournalEntry(AccountsController):
|
|||||||
self.validate_multi_currency()
|
self.validate_multi_currency()
|
||||||
self.set_amounts_in_company_currency()
|
self.set_amounts_in_company_currency()
|
||||||
self.validate_debit_credit_amount()
|
self.validate_debit_credit_amount()
|
||||||
|
|
||||||
|
# Do not validate while importing via data import
|
||||||
|
if not frappe.flags.in_import:
|
||||||
self.validate_total_debit_and_credit()
|
self.validate_total_debit_and_credit()
|
||||||
|
|
||||||
self.validate_against_jv()
|
self.validate_against_jv()
|
||||||
self.validate_reference_doc()
|
self.validate_reference_doc()
|
||||||
self.set_against_account()
|
self.set_against_account()
|
||||||
@ -192,8 +196,8 @@ class JournalEntry(AccountsController):
|
|||||||
frappe.throw(_("Row {0}: Party Type and Party is required for Receivable / Payable account {1}").format(d.idx, d.account))
|
frappe.throw(_("Row {0}: Party Type and Party is required for Receivable / Payable account {1}").format(d.idx, d.account))
|
||||||
|
|
||||||
def check_credit_limit(self):
|
def check_credit_limit(self):
|
||||||
customers = list(set([d.party for d in self.get("accounts")
|
customers = list(set(d.party for d in self.get("accounts")
|
||||||
if d.party_type=="Customer" and d.party and flt(d.debit) > 0]))
|
if d.party_type=="Customer" and d.party and flt(d.debit) > 0))
|
||||||
if customers:
|
if customers:
|
||||||
from erpnext.selling.doctype.customer.customer import check_credit_limit
|
from erpnext.selling.doctype.customer.customer import check_credit_limit
|
||||||
for customer in customers:
|
for customer in customers:
|
||||||
|
|||||||
17
erpnext/accounts/doctype/journal_entry/regional/india.js
Normal file
17
erpnext/accounts/doctype/journal_entry/regional/india.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
frappe.ui.form.on("Journal Entry", {
|
||||||
|
refresh: function(frm) {
|
||||||
|
frm.set_query('company_address', function(doc) {
|
||||||
|
if(!doc.company) {
|
||||||
|
frappe.throw(__('Please set Company'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
query: 'frappe.contacts.doctype.address.address.address_query',
|
||||||
|
filters: {
|
||||||
|
link_doctype: 'Company',
|
||||||
|
link_name: doc.company
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -21,7 +21,7 @@ class MonthlyDistribution(Document):
|
|||||||
idx += 1
|
idx += 1
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
total = sum([flt(d.percentage_allocation) for d in self.get("percentages")])
|
total = sum(flt(d.percentage_allocation) for d in self.get("percentages"))
|
||||||
|
|
||||||
if flt(total, 2) != 100.0:
|
if flt(total, 2) != 100.0:
|
||||||
frappe.throw(_("Percentage Allocation should be equal to 100%") + \
|
frappe.throw(_("Percentage Allocation should be equal to 100%") + \
|
||||||
|
|||||||
@ -303,7 +303,7 @@ class PaymentEntry(AccountsController):
|
|||||||
for k, v in no_oustanding_refs.items():
|
for k, v in no_oustanding_refs.items():
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.")
|
_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.")
|
||||||
.format(k, frappe.bold(", ".join([d.reference_name for d in v])), frappe.bold("negative outstanding amount"))
|
.format(k, frappe.bold(", ".join(d.reference_name for d in v)), frappe.bold("negative outstanding amount"))
|
||||||
+ "<br><br>" + _("If this is undesirable please cancel the corresponding Payment Entry."),
|
+ "<br><br>" + _("If this is undesirable please cancel the corresponding Payment Entry."),
|
||||||
title=_("Warning"), indicator="orange")
|
title=_("Warning"), indicator="orange")
|
||||||
|
|
||||||
@ -419,7 +419,7 @@ class PaymentEntry(AccountsController):
|
|||||||
def set_unallocated_amount(self):
|
def set_unallocated_amount(self):
|
||||||
self.unallocated_amount = 0
|
self.unallocated_amount = 0
|
||||||
if self.party:
|
if self.party:
|
||||||
total_deductions = sum([flt(d.amount) for d in self.get("deductions")])
|
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
|
||||||
if self.payment_type == "Receive" \
|
if self.payment_type == "Receive" \
|
||||||
and self.base_total_allocated_amount < self.base_received_amount + total_deductions \
|
and self.base_total_allocated_amount < self.base_received_amount + total_deductions \
|
||||||
and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate):
|
and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate):
|
||||||
@ -444,7 +444,7 @@ class PaymentEntry(AccountsController):
|
|||||||
else:
|
else:
|
||||||
self.difference_amount = self.base_paid_amount - flt(self.base_received_amount)
|
self.difference_amount = self.base_paid_amount - flt(self.base_received_amount)
|
||||||
|
|
||||||
total_deductions = sum([flt(d.amount) for d in self.get("deductions")])
|
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
|
||||||
|
|
||||||
self.difference_amount = flt(self.difference_amount - total_deductions,
|
self.difference_amount = flt(self.difference_amount - total_deductions,
|
||||||
self.precision("difference_amount"))
|
self.precision("difference_amount"))
|
||||||
@ -460,8 +460,8 @@ class PaymentEntry(AccountsController):
|
|||||||
if ((self.payment_type=="Pay" and self.party_type=="Customer")
|
if ((self.payment_type=="Pay" and self.party_type=="Customer")
|
||||||
or (self.payment_type=="Receive" and self.party_type=="Supplier")):
|
or (self.payment_type=="Receive" and self.party_type=="Supplier")):
|
||||||
|
|
||||||
total_negative_outstanding = sum([abs(flt(d.outstanding_amount))
|
total_negative_outstanding = sum(abs(flt(d.outstanding_amount))
|
||||||
for d in self.get("references") if flt(d.outstanding_amount) < 0])
|
for d in self.get("references") if flt(d.outstanding_amount) < 0)
|
||||||
|
|
||||||
paid_amount = self.paid_amount if self.payment_type=="Receive" else self.received_amount
|
paid_amount = self.paid_amount if self.payment_type=="Receive" else self.received_amount
|
||||||
additional_charges = sum([flt(d.amount) for d in self.deductions])
|
additional_charges = sum([flt(d.amount) for d in self.deductions])
|
||||||
|
|||||||
@ -112,7 +112,7 @@ class PaymentRequest(Document):
|
|||||||
if not data_of_completed_requests:
|
if not data_of_completed_requests:
|
||||||
return self.grand_total
|
return self.grand_total
|
||||||
|
|
||||||
request_amounts = sum([json.loads(d).get('request_amount') for d in data_of_completed_requests])
|
request_amounts = sum(json.loads(d).get('request_amount') for d in data_of_completed_requests)
|
||||||
return request_amounts
|
return request_amounts
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
|
|||||||
@ -46,6 +46,7 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "0",
|
||||||
"fieldname": "closing_amount",
|
"fieldname": "closing_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@ -57,7 +58,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-10-23 16:45:43.662034",
|
"modified": "2021-05-19 20:08:44.523861",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Closing Entry Detail",
|
"name": "POS Closing Entry Detail",
|
||||||
|
|||||||
@ -455,27 +455,21 @@ class POSInvoice(SalesInvoice):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_stock_availability(item_code, warehouse):
|
def get_stock_availability(item_code, warehouse):
|
||||||
latest_sle = frappe.db.sql("""select qty_after_transaction
|
bin_qty = frappe.db.sql("""select actual_qty from `tabBin`
|
||||||
from `tabStock Ledger Entry`
|
|
||||||
where item_code = %s and warehouse = %s
|
where item_code = %s and warehouse = %s
|
||||||
order by posting_date desc, posting_time desc
|
|
||||||
limit 1""", (item_code, warehouse), as_dict=1)
|
limit 1""", (item_code, warehouse), as_dict=1)
|
||||||
|
|
||||||
pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
|
pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
|
||||||
|
|
||||||
sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
|
bin_qty = bin_qty[0].actual_qty or 0 if bin_qty else 0
|
||||||
|
|
||||||
if sle_qty and pos_sales_qty:
|
return bin_qty - pos_sales_qty
|
||||||
return sle_qty - pos_sales_qty
|
|
||||||
else:
|
|
||||||
return sle_qty
|
|
||||||
|
|
||||||
def get_pos_reserved_qty(item_code, warehouse):
|
def get_pos_reserved_qty(item_code, warehouse):
|
||||||
reserved_qty = frappe.db.sql("""select sum(p_item.qty) as qty
|
reserved_qty = frappe.db.sql("""select sum(p_item.qty) as qty
|
||||||
from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item
|
from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item
|
||||||
where p.name = p_item.parent
|
where p.name = p_item.parent
|
||||||
and p.consolidated_invoice is NULL
|
and ifnull(p.consolidated_invoice, '') = ''
|
||||||
and p.docstatus = 1
|
|
||||||
and p_item.docstatus = 1
|
and p_item.docstatus = 1
|
||||||
and p_item.item_code = %s
|
and p_item.item_code = %s
|
||||||
and p_item.warehouse = %s
|
and p_item.warehouse = %s
|
||||||
|
|||||||
@ -44,6 +44,7 @@ class POSInvoiceMergeLog(Document):
|
|||||||
bold_unconsolidated = frappe.bold("not Consolidated")
|
bold_unconsolidated = frappe.bold("not Consolidated")
|
||||||
msg = (_("Row #{}: Original Invoice {} of return invoice {} is {}.")
|
msg = (_("Row #{}: Original Invoice {} of return invoice {} is {}.")
|
||||||
.format(d.idx, bold_return_against, bold_pos_invoice, bold_unconsolidated))
|
.format(d.idx, bold_return_against, bold_pos_invoice, bold_unconsolidated))
|
||||||
|
msg += " "
|
||||||
msg += _("Original invoice should be consolidated before or along with the return invoice.")
|
msg += _("Original invoice should be consolidated before or along with the return invoice.")
|
||||||
msg += "<br><br>"
|
msg += "<br><br>"
|
||||||
msg += _("You can add original invoice {} manually to proceed.").format(bold_return_against)
|
msg += _("You can add original invoice {} manually to proceed.").format(bold_return_against)
|
||||||
@ -56,12 +57,12 @@ class POSInvoiceMergeLog(Document):
|
|||||||
sales = [d for d in pos_invoice_docs if d.get('is_return') == 0]
|
sales = [d for d in pos_invoice_docs if d.get('is_return') == 0]
|
||||||
|
|
||||||
sales_invoice, credit_note = "", ""
|
sales_invoice, credit_note = "", ""
|
||||||
if sales:
|
|
||||||
sales_invoice = self.process_merging_into_sales_invoice(sales)
|
|
||||||
|
|
||||||
if returns:
|
if returns:
|
||||||
credit_note = self.process_merging_into_credit_note(returns)
|
credit_note = self.process_merging_into_credit_note(returns)
|
||||||
|
|
||||||
|
if sales:
|
||||||
|
sales_invoice = self.process_merging_into_sales_invoice(sales)
|
||||||
|
|
||||||
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
|
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
|
||||||
|
|
||||||
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
|
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
|
||||||
@ -274,9 +275,9 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
|
|||||||
closing_entry.db_set('error_message', '')
|
closing_entry.db_set('error_message', '')
|
||||||
closing_entry.update_opening_entry()
|
closing_entry.update_opening_entry()
|
||||||
|
|
||||||
except Exception:
|
except Exception as e:
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
message_log = frappe.message_log.pop()
|
message_log = frappe.message_log.pop() if frappe.message_log else str(e)
|
||||||
error_message = safe_load_json(message_log)
|
error_message = safe_load_json(message_log)
|
||||||
|
|
||||||
if closing_entry:
|
if closing_entry:
|
||||||
@ -300,9 +301,9 @@ def cancel_merge_logs(merge_logs, closing_entry=None):
|
|||||||
closing_entry.db_set('error_message', '')
|
closing_entry.db_set('error_message', '')
|
||||||
closing_entry.update_opening_entry(for_cancel=True)
|
closing_entry.update_opening_entry(for_cancel=True)
|
||||||
|
|
||||||
except Exception:
|
except Exception as e:
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
message_log = frappe.message_log.pop()
|
message_log = frappe.message_log.pop() if frappe.message_log else str(e)
|
||||||
error_message = safe_load_json(message_log)
|
error_message = safe_load_json(message_log)
|
||||||
|
|
||||||
if closing_entry:
|
if closing_entry:
|
||||||
@ -348,11 +349,9 @@ def job_already_enqueued(job_name):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def safe_load_json(message):
|
def safe_load_json(message):
|
||||||
JSONDecodeError = ValueError if six.PY2 else json.JSONDecodeError
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
json_message = json.loads(message).get('message')
|
json_message = json.loads(message).get('message')
|
||||||
except JSONDecodeError:
|
except Exception:
|
||||||
json_message = message
|
json_message = message
|
||||||
|
|
||||||
return json_message
|
return json_message
|
||||||
@ -183,7 +183,7 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True):
|
|||||||
condition = "ifnull({table}.{field}, '') in ({parent_groups})".format(
|
condition = "ifnull({table}.{field}, '') in ({parent_groups})".format(
|
||||||
table=table,
|
table=table,
|
||||||
field=field,
|
field=field,
|
||||||
parent_groups=", ".join([frappe.db.escape(d) for d in parent_groups])
|
parent_groups=", ".join(frappe.db.escape(d) for d in parent_groups)
|
||||||
)
|
)
|
||||||
|
|
||||||
frappe.flags.tree_conditions[key] = condition
|
frappe.flags.tree_conditions[key] = condition
|
||||||
@ -264,7 +264,7 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
|
|||||||
|
|
||||||
# find pricing rule with highest priority
|
# find pricing rule with highest priority
|
||||||
if pricing_rules:
|
if pricing_rules:
|
||||||
max_priority = max([cint(p.priority) for p in pricing_rules])
|
max_priority = max(cint(p.priority) for p in pricing_rules)
|
||||||
if max_priority:
|
if max_priority:
|
||||||
pricing_rules = list(filter(lambda x: cint(x.priority)==max_priority, pricing_rules))
|
pricing_rules = list(filter(lambda x: cint(x.priority)==max_priority, pricing_rules))
|
||||||
|
|
||||||
@ -272,14 +272,14 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
|
|||||||
pricing_rules = list(pricing_rules)
|
pricing_rules = list(pricing_rules)
|
||||||
|
|
||||||
if len(pricing_rules) > 1:
|
if len(pricing_rules) > 1:
|
||||||
rate_or_discount = list(set([d.rate_or_discount for d in pricing_rules]))
|
rate_or_discount = list(set(d.rate_or_discount for d in pricing_rules))
|
||||||
if len(rate_or_discount) == 1 and rate_or_discount[0] == "Discount Percentage":
|
if len(rate_or_discount) == 1 and rate_or_discount[0] == "Discount Percentage":
|
||||||
pricing_rules = list(filter(lambda x: x.for_price_list==args.price_list, pricing_rules)) \
|
pricing_rules = list(filter(lambda x: x.for_price_list==args.price_list, pricing_rules)) \
|
||||||
or pricing_rules
|
or pricing_rules
|
||||||
|
|
||||||
if len(pricing_rules) > 1 and not args.for_shopping_cart:
|
if len(pricing_rules) > 1 and not args.for_shopping_cart:
|
||||||
frappe.throw(_("Multiple Price Rules exists with same criteria, please resolve conflict by assigning priority. Price Rules: {0}")
|
frappe.throw(_("Multiple Price Rules exists with same criteria, please resolve conflict by assigning priority. Price Rules: {0}")
|
||||||
.format("\n".join([d.name for d in pricing_rules])), MultiplePricingRuleConflict)
|
.format("\n".join(d.name for d in pricing_rules)), MultiplePricingRuleConflict)
|
||||||
elif pricing_rules:
|
elif pricing_rules:
|
||||||
return pricing_rules[0]
|
return pricing_rules[0]
|
||||||
|
|
||||||
@ -541,7 +541,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
|||||||
|
|
||||||
def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
|
def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
|
||||||
if pricing_rule_args:
|
if pricing_rule_args:
|
||||||
items = tuple([(d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item])
|
items = tuple((d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item)
|
||||||
|
|
||||||
for args in pricing_rule_args:
|
for args in pricing_rule_args:
|
||||||
if not items or (args.get('item_code'), args.get('pricing_rules')) not in items:
|
if not items or (args.get('item_code'), args.get('pricing_rules')) not in items:
|
||||||
|
|||||||
@ -13,7 +13,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="text-center">{{ _("STATEMENTS OF ACCOUNTS") }}</h2>
|
<h2 class="text-center">{{ _("STATEMENTS OF ACCOUNTS") }}</h2>
|
||||||
<div>
|
<div>
|
||||||
<h5 style="float: left;">{{ _("Customer: ") }} <b>{{filters.party[0] }}</b></h5>
|
<h5 style="float: left;">{{ _("Customer: ") }} <b>{{filters.party[0] }}</b></h5>
|
||||||
|
|||||||
@ -286,7 +286,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-05-13 12:44:19.574844",
|
"modified": "2021-05-21 11:14:22.426672",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Process Statement Of Accounts",
|
"name": "Process Statement Of Accounts",
|
||||||
|
|||||||
@ -94,10 +94,11 @@ def get_report_pdf(doc, consolidated=True):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
html = frappe.render_template(template_path, \
|
html = frappe.render_template(template_path, \
|
||||||
{"filters": filters, "data": res, "ageing": ageing[0] if doc.include_ageing else None,
|
{"filters": filters, "data": res, "ageing": ageing[0] if (doc.include_ageing and ageing) else None,
|
||||||
"letter_head": letter_head if doc.letter_head else None,
|
"letter_head": letter_head if doc.letter_head else None,
|
||||||
"terms_and_conditions": frappe.db.get_value('Terms and Conditions', doc.terms_and_conditions, 'terms')
|
"terms_and_conditions": frappe.db.get_value('Terms and Conditions', doc.terms_and_conditions, 'terms')
|
||||||
if doc.terms_and_conditions else None})
|
if doc.terms_and_conditions else None})
|
||||||
|
|
||||||
html = frappe.render_template(base_template_path, {"body": html, \
|
html = frappe.render_template(base_template_path, {"body": html, \
|
||||||
"css": get_print_style(), "title": "Statement For " + entry.customer})
|
"css": get_print_style(), "title": "Statement For " + entry.customer})
|
||||||
statement_dict[entry.customer] = html
|
statement_dict[entry.customer] = html
|
||||||
|
|||||||
@ -631,7 +631,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(len(pi.get("supplied_items")), 2)
|
self.assertEqual(len(pi.get("supplied_items")), 2)
|
||||||
|
|
||||||
rm_supp_cost = sum([d.amount for d in pi.get("supplied_items")])
|
rm_supp_cost = sum(d.amount for d in pi.get("supplied_items"))
|
||||||
self.assertEqual(flt(pi.get("items")[0].rm_supp_cost, 2), flt(rm_supp_cost, 2))
|
self.assertEqual(flt(pi.get("items")[0].rm_supp_cost, 2), flt(rm_supp_cost, 2))
|
||||||
|
|
||||||
def test_rejected_serial_no(self):
|
def test_rejected_serial_no(self):
|
||||||
|
|||||||
@ -582,6 +582,16 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query("adjustment_against", function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frm.doc.company,
|
||||||
|
customer: frm.doc.customer,
|
||||||
|
docstatus: 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
frm.custom_make_buttons = {
|
frm.custom_make_buttons = {
|
||||||
'Delivery Note': 'Delivery',
|
'Delivery Note': 'Delivery',
|
||||||
'Sales Invoice': 'Return / Credit Note',
|
'Sales Invoice': 'Return / Credit Note',
|
||||||
@ -897,6 +907,10 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (frm.doc.is_debit_note) {
|
||||||
|
frm.set_df_property('return_against', 'label', 'Adjustment Against');
|
||||||
|
}
|
||||||
|
|
||||||
if (frappe.boot.active_domains.includes("Healthcare")) {
|
if (frappe.boot.active_domains.includes("Healthcare")) {
|
||||||
frm.set_df_property("patient", "hidden", 0);
|
frm.set_df_property("patient", "hidden", 0);
|
||||||
frm.set_df_property("patient_name", "hidden", 0);
|
frm.set_df_property("patient_name", "hidden", 0);
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
"is_pos",
|
"is_pos",
|
||||||
"is_consolidated",
|
"is_consolidated",
|
||||||
"is_return",
|
"is_return",
|
||||||
|
"is_debit_note",
|
||||||
"update_billed_amount_in_sales_order",
|
"update_billed_amount_in_sales_order",
|
||||||
"column_break1",
|
"column_break1",
|
||||||
"company",
|
"company",
|
||||||
@ -392,7 +393,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "return_against",
|
"depends_on": "eval:doc.return_against || doc.is_debit_note",
|
||||||
"fieldname": "return_against",
|
"fieldname": "return_against",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
@ -401,7 +402,7 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Sales Invoice",
|
"options": "Sales Invoice",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1,
|
"read_only_depends_on": "eval:doc.is_return",
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1137,7 +1137,6 @@ class SalesInvoice(SellingController):
|
|||||||
"""
|
"""
|
||||||
self.set_serial_no_against_delivery_note()
|
self.set_serial_no_against_delivery_note()
|
||||||
self.validate_serial_against_delivery_note()
|
self.validate_serial_against_delivery_note()
|
||||||
self.validate_serial_against_sales_invoice()
|
|
||||||
|
|
||||||
def set_serial_no_against_delivery_note(self):
|
def set_serial_no_against_delivery_note(self):
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
@ -1168,26 +1167,6 @@ class SalesInvoice(SellingController):
|
|||||||
frappe.throw(_("Row {0}: {1} Serial numbers required for Item {2}. You have provided {3}.").format(
|
frappe.throw(_("Row {0}: {1} Serial numbers required for Item {2}. You have provided {3}.").format(
|
||||||
item.idx, item.qty, item.item_code, len(si_serial_nos)))
|
item.idx, item.qty, item.item_code, len(si_serial_nos)))
|
||||||
|
|
||||||
def validate_serial_against_sales_invoice(self):
|
|
||||||
""" check if serial number is already used in other sales invoice """
|
|
||||||
for item in self.items:
|
|
||||||
if not item.serial_no:
|
|
||||||
continue
|
|
||||||
|
|
||||||
for serial_no in item.serial_no.split("\n"):
|
|
||||||
serial_no_details = frappe.db.get_value("Serial No", serial_no,
|
|
||||||
["sales_invoice", "item_code"], as_dict=1)
|
|
||||||
|
|
||||||
if not serial_no_details:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if serial_no_details.sales_invoice and serial_no_details.item_code == item.item_code \
|
|
||||||
and self.name != serial_no_details.sales_invoice:
|
|
||||||
sales_invoice_company = frappe.db.get_value("Sales Invoice", serial_no_details.sales_invoice, "company")
|
|
||||||
if sales_invoice_company == self.company:
|
|
||||||
frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}")
|
|
||||||
.format(serial_no, serial_no_details.sales_invoice))
|
|
||||||
|
|
||||||
def update_project(self):
|
def update_project(self):
|
||||||
if self.project:
|
if self.project:
|
||||||
project = frappe.get_doc("Project", self.project)
|
project = frappe.get_doc("Project", self.project)
|
||||||
|
|||||||
@ -933,12 +933,6 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"))
|
self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"))
|
||||||
self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0],
|
self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0],
|
||||||
"delivery_document_no"), si.name)
|
"delivery_document_no"), si.name)
|
||||||
self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0], "sales_invoice"),
|
|
||||||
si.name)
|
|
||||||
|
|
||||||
# check if the serial number is already linked with any other Sales Invoice
|
|
||||||
_si = frappe.copy_doc(si.as_dict())
|
|
||||||
self.assertRaises(frappe.ValidationError, _si.insert)
|
|
||||||
|
|
||||||
return si
|
return si
|
||||||
|
|
||||||
|
|||||||
@ -112,7 +112,7 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
|||||||
si = create_sales_invoice(customer = "Test TCS Customer", rate=5000)
|
si = create_sales_invoice(customer = "Test TCS Customer", rate=5000)
|
||||||
si.submit()
|
si.submit()
|
||||||
|
|
||||||
tcs_charged = sum([d.base_tax_amount for d in si.taxes if d.account_head == 'TCS - _TC'])
|
tcs_charged = sum(d.base_tax_amount for d in si.taxes if d.account_head == 'TCS - _TC')
|
||||||
self.assertEqual(tcs_charged, 500)
|
self.assertEqual(tcs_charged, 500)
|
||||||
invoices.append(si)
|
invoices.append(si)
|
||||||
|
|
||||||
|
|||||||
@ -143,7 +143,7 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False):
|
|||||||
validate_expense_against_budget(args)
|
validate_expense_against_budget(args)
|
||||||
|
|
||||||
def validate_cwip_accounts(gl_map):
|
def validate_cwip_accounts(gl_map):
|
||||||
cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")])
|
cwip_enabled = any(cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting"))
|
||||||
|
|
||||||
if cwip_enabled and gl_map[0].voucher_type == "Journal Entry":
|
if cwip_enabled and gl_map[0].voucher_type == "Journal Entry":
|
||||||
cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount
|
cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount
|
||||||
|
|||||||
@ -58,11 +58,9 @@ def get_conditions(filters):
|
|||||||
def get_data(filters):
|
def get_data(filters):
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
conditions = get_conditions(filters)
|
conditions = get_conditions(filters)
|
||||||
|
|
||||||
accounts = frappe.db.get_all("Account", fields=["name", "account_currency"],
|
accounts = frappe.db.get_all("Account", fields=["name", "account_currency"],
|
||||||
filters=conditions)
|
filters=conditions, order_by='name')
|
||||||
|
|
||||||
for d in accounts:
|
for d in accounts:
|
||||||
balance = get_balance_on(d.name, date=filters.report_date)
|
balance = get_balance_on(d.name, date=filters.report_date)
|
||||||
|
|||||||
@ -23,7 +23,7 @@ class TestAccountBalance(unittest.TestCase):
|
|||||||
|
|
||||||
expected_data = [
|
expected_data = [
|
||||||
{
|
{
|
||||||
"account": 'Sales - _TC2',
|
"account": 'Direct Income - _TC2',
|
||||||
"currency": 'EUR',
|
"currency": 'EUR',
|
||||||
"balance": -100.0,
|
"balance": -100.0,
|
||||||
},
|
},
|
||||||
@ -32,21 +32,21 @@ class TestAccountBalance(unittest.TestCase):
|
|||||||
"currency": 'EUR',
|
"currency": 'EUR',
|
||||||
"balance": -100.0,
|
"balance": -100.0,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"account": 'Service - _TC2',
|
|
||||||
"currency": 'EUR',
|
|
||||||
"balance": 0.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"account": 'Direct Income - _TC2',
|
|
||||||
"currency": 'EUR',
|
|
||||||
"balance": -100.0,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"account": 'Indirect Income - _TC2',
|
"account": 'Indirect Income - _TC2',
|
||||||
"currency": 'EUR',
|
"currency": 'EUR',
|
||||||
"balance": 0.0,
|
"balance": 0.0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"account": 'Sales - _TC2',
|
||||||
|
"currency": 'EUR',
|
||||||
|
"balance": -100.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"account": 'Service - _TC2',
|
||||||
|
"currency": 'EUR',
|
||||||
|
"balance": 0.0,
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
self.assertEqual(expected_data, report[1])
|
self.assertEqual(expected_data, report[1])
|
||||||
|
|||||||
@ -32,7 +32,7 @@ def get_accounts_in_mappers(mapping_names):
|
|||||||
join `tabCash Flow Mapping` cfm on cfma.parent=cfm.name
|
join `tabCash Flow Mapping` cfm on cfma.parent=cfm.name
|
||||||
where cfma.parent in (%s)
|
where cfma.parent in (%s)
|
||||||
order by cfm.is_working_capital
|
order by cfm.is_working_capital
|
||||||
''', (', '.join(['"%s"' % d for d in mapping_names])))
|
''', (', '.join('"%s"' % d for d in mapping_names)))
|
||||||
|
|
||||||
|
|
||||||
def setup_mappers(mappers):
|
def setup_mappers(mappers):
|
||||||
@ -83,8 +83,8 @@ def setup_mappers(mappers):
|
|||||||
|
|
||||||
account_types_labels = sorted(
|
account_types_labels = sorted(
|
||||||
set(
|
set(
|
||||||
[(d['label'], d['is_working_capital'], d['is_income_tax_liability'], d['is_income_tax_expense'])
|
(d['label'], d['is_working_capital'], d['is_income_tax_liability'], d['is_income_tax_expense'])
|
||||||
for d in account_types]
|
for d in account_types
|
||||||
),
|
),
|
||||||
key=lambda x: x[1]
|
key=lambda x: x[1]
|
||||||
)
|
)
|
||||||
@ -165,7 +165,7 @@ def add_data_for_operating_activities(
|
|||||||
if profit_data:
|
if profit_data:
|
||||||
profit_data.update({
|
profit_data.update({
|
||||||
"indent": 1,
|
"indent": 1,
|
||||||
"parent_account": get_mapper_for(light_mappers, position=0)['section_header']
|
"parent_account": get_mapper_for(light_mappers, position=1)['section_header']
|
||||||
})
|
})
|
||||||
data.append(profit_data)
|
data.append(profit_data)
|
||||||
section_data.append(profit_data)
|
section_data.append(profit_data)
|
||||||
@ -312,10 +312,10 @@ def add_data_for_other_activities(
|
|||||||
def compute_data(filters, company_currency, profit_data, period_list, light_mappers, full_mapper):
|
def compute_data(filters, company_currency, profit_data, period_list, light_mappers, full_mapper):
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
operating_activities_mapper = get_mapper_for(light_mappers, position=0)
|
operating_activities_mapper = get_mapper_for(light_mappers, position=1)
|
||||||
other_mappers = [
|
other_mappers = [
|
||||||
get_mapper_for(light_mappers, position=1),
|
get_mapper_for(light_mappers, position=2),
|
||||||
get_mapper_for(light_mappers, position=2)
|
get_mapper_for(light_mappers, position=3)
|
||||||
]
|
]
|
||||||
|
|
||||||
if operating_activities_mapper:
|
if operating_activities_mapper:
|
||||||
@ -375,7 +375,7 @@ def _get_account_type_based_data(filters, account_names, period_list, accumulate
|
|||||||
total = 0
|
total = 0
|
||||||
for period in period_list:
|
for period in period_list:
|
||||||
start_date = get_start_date(period, accumulated_values, company)
|
start_date = get_start_date(period, accumulated_values, company)
|
||||||
accounts = ', '.join(['"%s"' % d for d in account_names])
|
accounts = ', '.join('"%s"' % d for d in account_names)
|
||||||
|
|
||||||
if opening_balances:
|
if opening_balances:
|
||||||
date_info = dict(date=start_date)
|
date_info = dict(date=start_date)
|
||||||
|
|||||||
@ -145,7 +145,7 @@ class PartyLedgerSummaryReport(object):
|
|||||||
out = []
|
out = []
|
||||||
for party, row in iteritems(self.party_data):
|
for party, row in iteritems(self.party_data):
|
||||||
if row.opening_balance or row.invoiced_amount or row.paid_amount or row.return_amount or row.closing_amount:
|
if row.opening_balance or row.invoiced_amount or row.paid_amount or row.return_amount or row.closing_amount:
|
||||||
total_party_adjustment = sum([amount for amount in itervalues(self.party_adjustment_details.get(party, {}))])
|
total_party_adjustment = sum(amount for amount in itervalues(self.party_adjustment_details.get(party, {})))
|
||||||
row.paid_amount -= total_party_adjustment
|
row.paid_amount -= total_party_adjustment
|
||||||
|
|
||||||
adjustments = self.party_adjustment_details.get(party, {})
|
adjustments = self.party_adjustment_details.get(party, {})
|
||||||
|
|||||||
@ -0,0 +1,81 @@
|
|||||||
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||||
|
frappe.query_reports["Dimension-wise Accounts Balance Report"] = {
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"label": __("Company"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Company",
|
||||||
|
"default": frappe.defaults.get_user_default("Company"),
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fiscal_year",
|
||||||
|
"label": __("Fiscal Year"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Fiscal Year",
|
||||||
|
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||||
|
"reqd": 1,
|
||||||
|
"on_change": function(query_report) {
|
||||||
|
var fiscal_year = query_report.get_values().fiscal_year;
|
||||||
|
if (!fiscal_year) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||||
|
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||||
|
frappe.query_report.set_filter_value({
|
||||||
|
from_date: fy.year_start_date,
|
||||||
|
to_date: fy.year_end_date
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "from_date",
|
||||||
|
"label": __("From Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"default": frappe.defaults.get_user_default("year_start_date"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "to_date",
|
||||||
|
"label": __("To Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"default": frappe.defaults.get_user_default("year_end_date"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "finance_book",
|
||||||
|
"label": __("Finance Book"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Finance Book",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "dimension",
|
||||||
|
"label": __("Select Dimension"),
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": get_accounting_dimension_options(),
|
||||||
|
"reqd": 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"formatter": erpnext.financial_statements.formatter,
|
||||||
|
"tree": true,
|
||||||
|
"name_field": "account",
|
||||||
|
"parent_field": "parent_account",
|
||||||
|
"initial_depth": 3
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
function get_accounting_dimension_options() {
|
||||||
|
let options =["", "Cost Center", "Project"];
|
||||||
|
frappe.db.get_list('Accounting Dimension',
|
||||||
|
{fields:['document_type']}).then((res) => {
|
||||||
|
res.forEach((dimension) => {
|
||||||
|
options.push(dimension.document_type);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return options
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"add_total_row": 0,
|
||||||
|
"columns": [],
|
||||||
|
"creation": "2021-04-09 16:48:59.548018",
|
||||||
|
"disable_prepared_report": 0,
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"modified": "2021-04-09 16:48:59.548018",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Dimension-wise Accounts Balance Report",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "GL Entry",
|
||||||
|
"report_name": "Dimension-wise Accounts Balance Report",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": []
|
||||||
|
}
|
||||||
@ -0,0 +1,213 @@
|
|||||||
|
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe, erpnext
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import (flt, cstr)
|
||||||
|
|
||||||
|
from erpnext.accounts.report.financial_statements import filter_accounts, filter_out_zero_value_rows
|
||||||
|
from erpnext.accounts.report.trial_balance.trial_balance import validate_filters
|
||||||
|
|
||||||
|
from six import itervalues
|
||||||
|
|
||||||
|
def execute(filters=None):
|
||||||
|
validate_filters(filters)
|
||||||
|
dimension_items_list = get_dimension_items_list(filters.dimension, filters.company)
|
||||||
|
|
||||||
|
if not dimension_items_list:
|
||||||
|
return [], []
|
||||||
|
|
||||||
|
dimension_items_list = [''.join(d) for d in dimension_items_list]
|
||||||
|
columns = get_columns(dimension_items_list)
|
||||||
|
data = get_data(filters, dimension_items_list)
|
||||||
|
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
def get_data(filters, dimension_items_list):
|
||||||
|
company_currency = erpnext.get_company_currency(filters.company)
|
||||||
|
acc = frappe.db.sql("""
|
||||||
|
select
|
||||||
|
name, account_number, parent_account, lft, rgt, root_type,
|
||||||
|
report_type, account_name, include_in_gross, account_type, is_group
|
||||||
|
from
|
||||||
|
`tabAccount`
|
||||||
|
where
|
||||||
|
company=%s
|
||||||
|
order by lft""", (filters.company), as_dict=True)
|
||||||
|
|
||||||
|
if not acc:
|
||||||
|
return None
|
||||||
|
|
||||||
|
accounts, accounts_by_name, parent_children_map = filter_accounts(acc)
|
||||||
|
|
||||||
|
min_lft, max_rgt = frappe.db.sql("""select min(lft), max(rgt) from `tabAccount`
|
||||||
|
where company=%s""", (filters.company))[0]
|
||||||
|
|
||||||
|
account = frappe.db.sql_list("""select name from `tabAccount`
|
||||||
|
where lft >= %s and rgt <= %s and company = %s""", (min_lft, max_rgt, filters.company))
|
||||||
|
|
||||||
|
gl_entries_by_account = {}
|
||||||
|
set_gl_entries_by_account(dimension_items_list, filters, account, gl_entries_by_account)
|
||||||
|
format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_items_list)
|
||||||
|
accumulate_values_into_parents(accounts, accounts_by_name, dimension_items_list)
|
||||||
|
out = prepare_data(accounts, filters, parent_children_map, company_currency, dimension_items_list)
|
||||||
|
out = filter_out_zero_value_rows(out, parent_children_map)
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
def set_gl_entries_by_account(dimension_items_list, filters, account, gl_entries_by_account):
|
||||||
|
for item in dimension_items_list:
|
||||||
|
condition = get_condition(filters.from_date, item, filters.dimension)
|
||||||
|
if account:
|
||||||
|
condition += " and account in ({})"\
|
||||||
|
.format(", ".join([frappe.db.escape(d) for d in account]))
|
||||||
|
|
||||||
|
gl_filters = {
|
||||||
|
"company": filters.get("company"),
|
||||||
|
"from_date": filters.get("from_date"),
|
||||||
|
"to_date": filters.get("to_date"),
|
||||||
|
"finance_book": cstr(filters.get("finance_book"))
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_filters['item'] = ''.join(item)
|
||||||
|
|
||||||
|
if filters.get("include_default_book_entries"):
|
||||||
|
gl_filters["company_fb"] = frappe.db.get_value("Company",
|
||||||
|
filters.company, 'default_finance_book')
|
||||||
|
|
||||||
|
for key, value in filters.items():
|
||||||
|
if value:
|
||||||
|
gl_filters.update({
|
||||||
|
key: value
|
||||||
|
})
|
||||||
|
|
||||||
|
gl_entries = frappe.db.sql("""
|
||||||
|
select
|
||||||
|
posting_date, account, debit, credit, is_opening, fiscal_year,
|
||||||
|
debit_in_account_currency, credit_in_account_currency, account_currency
|
||||||
|
from
|
||||||
|
`tabGL Entry`
|
||||||
|
where
|
||||||
|
company=%(company)s
|
||||||
|
{condition}
|
||||||
|
and posting_date <= %(to_date)s
|
||||||
|
and is_cancelled = 0
|
||||||
|
order by account, posting_date""".format(
|
||||||
|
condition=condition),
|
||||||
|
gl_filters, as_dict=True) #nosec
|
||||||
|
|
||||||
|
for entry in gl_entries:
|
||||||
|
entry['dimension_item'] = ''.join(item)
|
||||||
|
gl_entries_by_account.setdefault(entry.account, []).append(entry)
|
||||||
|
|
||||||
|
def format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_items_list):
|
||||||
|
|
||||||
|
for entries in itervalues(gl_entries_by_account):
|
||||||
|
for entry in entries:
|
||||||
|
d = accounts_by_name.get(entry.account)
|
||||||
|
if not d:
|
||||||
|
frappe.msgprint(
|
||||||
|
_("Could not retrieve information for {0}.").format(entry.account), title="Error",
|
||||||
|
raise_exception=1
|
||||||
|
)
|
||||||
|
for item in dimension_items_list:
|
||||||
|
if item == entry.dimension_item:
|
||||||
|
d[frappe.scrub(item)] = d.get(frappe.scrub(item), 0.0) + flt(entry.debit) - flt(entry.credit)
|
||||||
|
|
||||||
|
def prepare_data(accounts, filters, parent_children_map, company_currency, dimension_items_list):
|
||||||
|
data = []
|
||||||
|
|
||||||
|
for d in accounts:
|
||||||
|
has_value = False
|
||||||
|
total = 0
|
||||||
|
row = {
|
||||||
|
"account": d.name,
|
||||||
|
"parent_account": d.parent_account,
|
||||||
|
"indent": d.indent,
|
||||||
|
"from_date": filters.from_date,
|
||||||
|
"to_date": filters.to_date,
|
||||||
|
"currency": company_currency,
|
||||||
|
"account_name": ('{} - {}'.format(d.account_number, d.account_name)
|
||||||
|
if d.account_number else d.account_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
for item in dimension_items_list:
|
||||||
|
row[frappe.scrub(item)] = flt(d.get(frappe.scrub(item), 0.0), 3)
|
||||||
|
|
||||||
|
if abs(row[frappe.scrub(item)]) >= 0.005:
|
||||||
|
# ignore zero values
|
||||||
|
has_value = True
|
||||||
|
total += flt(d.get(frappe.scrub(item), 0.0), 3)
|
||||||
|
|
||||||
|
row["has_value"] = has_value
|
||||||
|
row["total"] = total
|
||||||
|
data.append(row)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def accumulate_values_into_parents(accounts, accounts_by_name, dimension_items_list):
|
||||||
|
"""accumulate children's values in parent accounts"""
|
||||||
|
for d in reversed(accounts):
|
||||||
|
if d.parent_account:
|
||||||
|
for item in dimension_items_list:
|
||||||
|
accounts_by_name[d.parent_account][frappe.scrub(item)] = \
|
||||||
|
accounts_by_name[d.parent_account].get(frappe.scrub(item), 0.0) + d.get(frappe.scrub(item), 0.0)
|
||||||
|
|
||||||
|
def get_condition(from_date, item, dimension):
|
||||||
|
conditions = []
|
||||||
|
|
||||||
|
if from_date:
|
||||||
|
conditions.append("posting_date >= %(from_date)s")
|
||||||
|
if dimension:
|
||||||
|
if dimension not in ['Cost Center', 'Project']:
|
||||||
|
if dimension in ['Customer', 'Supplier']:
|
||||||
|
dimension = 'Party'
|
||||||
|
else:
|
||||||
|
dimension = 'Voucher No'
|
||||||
|
txt = "{0} = %(item)s".format(frappe.scrub(dimension))
|
||||||
|
conditions.append(txt)
|
||||||
|
|
||||||
|
return " and {}".format(" and ".join(conditions)) if conditions else ""
|
||||||
|
|
||||||
|
def get_dimension_items_list(dimension, company):
|
||||||
|
meta = frappe.get_meta(dimension, cached=False)
|
||||||
|
fieldnames = [d.fieldname for d in meta.get("fields")]
|
||||||
|
filters = {}
|
||||||
|
if 'company' in fieldnames:
|
||||||
|
filters['company'] = company
|
||||||
|
return frappe.get_all(dimension, filters, as_list=True)
|
||||||
|
|
||||||
|
def get_columns(dimension_items_list, accumulated_values=1, company=None):
|
||||||
|
columns = [{
|
||||||
|
"fieldname": "account",
|
||||||
|
"label": _("Account"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Account",
|
||||||
|
"width": 300
|
||||||
|
}]
|
||||||
|
if company:
|
||||||
|
columns.append({
|
||||||
|
"fieldname": "currency",
|
||||||
|
"label": _("Currency"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Currency",
|
||||||
|
"hidden": 1
|
||||||
|
})
|
||||||
|
for item in dimension_items_list:
|
||||||
|
columns.append({
|
||||||
|
"fieldname": frappe.scrub(item),
|
||||||
|
"label": item,
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 150
|
||||||
|
})
|
||||||
|
columns.append({
|
||||||
|
"fieldname": "total",
|
||||||
|
"label": "Total",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 150
|
||||||
|
})
|
||||||
|
|
||||||
|
return columns
|
||||||
@ -369,7 +369,7 @@ def set_gl_entries_by_account(
|
|||||||
|
|
||||||
if accounts:
|
if accounts:
|
||||||
additional_conditions += " and account in ({})"\
|
additional_conditions += " and account in ({})"\
|
||||||
.format(", ".join([frappe.db.escape(d) for d in accounts]))
|
.format(", ".join(frappe.db.escape(d) for d in accounts))
|
||||||
|
|
||||||
gl_filters = {
|
gl_filters = {
|
||||||
"company": company,
|
"company": company,
|
||||||
|
|||||||
@ -166,6 +166,11 @@ frappe.query_reports["General Ledger"] = {
|
|||||||
"fieldname": "show_cancelled_entries",
|
"fieldname": "show_cancelled_entries",
|
||||||
"label": __("Show Cancelled Entries"),
|
"label": __("Show Cancelled Entries"),
|
||||||
"fieldtype": "Check"
|
"fieldtype": "Check"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "show_net_values_in_party_account",
|
||||||
|
"label": __("Show Net Values in Party Account"),
|
||||||
|
"fieldtype": "Check"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -344,6 +344,9 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
|
|||||||
consolidated_gle = OrderedDict()
|
consolidated_gle = OrderedDict()
|
||||||
group_by = group_by_field(filters.get('group_by'))
|
group_by = group_by_field(filters.get('group_by'))
|
||||||
|
|
||||||
|
if filters.get('show_net_values_in_party_account'):
|
||||||
|
account_type_map = get_account_type_map(filters.get('company'))
|
||||||
|
|
||||||
def update_value_in_dict(data, key, gle):
|
def update_value_in_dict(data, key, gle):
|
||||||
data[key].debit += flt(gle.debit)
|
data[key].debit += flt(gle.debit)
|
||||||
data[key].credit += flt(gle.credit)
|
data[key].credit += flt(gle.credit)
|
||||||
@ -351,6 +354,24 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
|
|||||||
data[key].debit_in_account_currency += flt(gle.debit_in_account_currency)
|
data[key].debit_in_account_currency += flt(gle.debit_in_account_currency)
|
||||||
data[key].credit_in_account_currency += flt(gle.credit_in_account_currency)
|
data[key].credit_in_account_currency += flt(gle.credit_in_account_currency)
|
||||||
|
|
||||||
|
if filters.get('show_net_values_in_party_account') and \
|
||||||
|
account_type_map.get(data[key].account) in ('Receivable', 'Payable'):
|
||||||
|
net_value = flt(data[key].debit) - flt(data[key].credit)
|
||||||
|
net_value_in_account_currency = flt(data[key].debit_in_account_currency) \
|
||||||
|
- flt(data[key].credit_in_account_currency)
|
||||||
|
|
||||||
|
if net_value < 0:
|
||||||
|
dr_or_cr = 'credit'
|
||||||
|
rev_dr_or_cr = 'debit'
|
||||||
|
else:
|
||||||
|
dr_or_cr = 'debit'
|
||||||
|
rev_dr_or_cr = 'credit'
|
||||||
|
|
||||||
|
data[key][dr_or_cr] = abs(net_value)
|
||||||
|
data[key][dr_or_cr+'_in_account_currency'] = abs(net_value_in_account_currency)
|
||||||
|
data[key][rev_dr_or_cr] = 0
|
||||||
|
data[key][rev_dr_or_cr+'_in_account_currency'] = 0
|
||||||
|
|
||||||
if data[key].against_voucher and gle.against_voucher:
|
if data[key].against_voucher and gle.against_voucher:
|
||||||
data[key].against_voucher += ', ' + gle.against_voucher
|
data[key].against_voucher += ', ' + gle.against_voucher
|
||||||
|
|
||||||
@ -388,6 +409,12 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
|
|||||||
|
|
||||||
return totals, entries
|
return totals, entries
|
||||||
|
|
||||||
|
def get_account_type_map(company):
|
||||||
|
account_type_map = frappe._dict(frappe.get_all('Account', fields=['name', 'account_type'],
|
||||||
|
filters={'company': company}, as_list=1))
|
||||||
|
|
||||||
|
return account_type_map
|
||||||
|
|
||||||
def get_result_as_list(data, filters):
|
def get_result_as_list(data, filters):
|
||||||
balance, balance_in_account_currency = 0, 0
|
balance, balance_in_account_currency = 0, 0
|
||||||
inv_details = get_supplier_invoice_details()
|
inv_details = get_supplier_invoice_details()
|
||||||
|
|||||||
@ -334,7 +334,7 @@ def get_aii_accounts():
|
|||||||
|
|
||||||
def get_purchase_receipts_against_purchase_order(item_list):
|
def get_purchase_receipts_against_purchase_order(item_list):
|
||||||
po_pr_map = frappe._dict()
|
po_pr_map = frappe._dict()
|
||||||
po_item_rows = list(set([d.po_detail for d in item_list]))
|
po_item_rows = list(set(d.po_detail for d in item_list))
|
||||||
|
|
||||||
if po_item_rows:
|
if po_item_rows:
|
||||||
purchase_receipts = frappe.db.sql("""
|
purchase_receipts = frappe.db.sql("""
|
||||||
|
|||||||
@ -23,7 +23,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
|||||||
if item_list:
|
if item_list:
|
||||||
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
|
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
|
||||||
|
|
||||||
mode_of_payments = get_mode_of_payments(set([d.parent for d in item_list]))
|
mode_of_payments = get_mode_of_payments(set(d.parent for d in item_list))
|
||||||
so_dn_map = get_delivery_notes_against_sales_order(item_list)
|
so_dn_map = get_delivery_notes_against_sales_order(item_list)
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
|
|||||||
@ -77,14 +77,14 @@ def get_pos_entries(filters, group_by_field):
|
|||||||
), filters, as_dict=1)
|
), filters, as_dict=1)
|
||||||
|
|
||||||
def concat_mode_of_payments(pos_entries):
|
def concat_mode_of_payments(pos_entries):
|
||||||
mode_of_payments = get_mode_of_payments(set([d.pos_invoice for d in pos_entries]))
|
mode_of_payments = get_mode_of_payments(set(d.pos_invoice for d in pos_entries))
|
||||||
for entry in pos_entries:
|
for entry in pos_entries:
|
||||||
if mode_of_payments.get(entry.pos_invoice):
|
if mode_of_payments.get(entry.pos_invoice):
|
||||||
entry.mode_of_payment = ", ".join(mode_of_payments.get(entry.pos_invoice, []))
|
entry.mode_of_payment = ", ".join(mode_of_payments.get(entry.pos_invoice, []))
|
||||||
|
|
||||||
def add_subtotal_row(data, group_invoices, group_by_field, group_by_value):
|
def add_subtotal_row(data, group_invoices, group_by_field, group_by_value):
|
||||||
grand_total = sum([d.grand_total for d in group_invoices])
|
grand_total = sum(d.grand_total for d in group_invoices)
|
||||||
paid_amount = sum([d.paid_amount for d in group_invoices])
|
paid_amount = sum(d.paid_amount for d in group_invoices)
|
||||||
data.append({
|
data.append({
|
||||||
group_by_field: group_by_value,
|
group_by_field: group_by_value,
|
||||||
"grand_total": grand_total,
|
"grand_total": grand_total,
|
||||||
|
|||||||
@ -26,7 +26,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
|||||||
invoice_expense_map, invoice_tax_map = get_invoice_tax_map(invoice_list,
|
invoice_expense_map, invoice_tax_map = get_invoice_tax_map(invoice_list,
|
||||||
invoice_expense_map, expense_accounts)
|
invoice_expense_map, expense_accounts)
|
||||||
invoice_po_pr_map = get_invoice_po_pr_map(invoice_list)
|
invoice_po_pr_map = get_invoice_po_pr_map(invoice_list)
|
||||||
suppliers = list(set([d.supplier for d in invoice_list]))
|
suppliers = list(set(d.supplier for d in invoice_list))
|
||||||
supplier_details = get_supplier_details(suppliers)
|
supplier_details = get_supplier_details(suppliers)
|
||||||
|
|
||||||
company_currency = frappe.get_cached_value('Company', filters.company, "default_currency")
|
company_currency = frappe.get_cached_value('Company', filters.company, "default_currency")
|
||||||
@ -120,13 +120,13 @@ def get_columns(invoice_list, additional_table_columns):
|
|||||||
and docstatus = 1 and (account_head is not null and account_head != '')
|
and docstatus = 1 and (account_head is not null and account_head != '')
|
||||||
and category in ('Total', 'Valuation and Total')
|
and category in ('Total', 'Valuation and Total')
|
||||||
and parent in (%s) order by account_head""" %
|
and parent in (%s) order by account_head""" %
|
||||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
|
', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list))
|
||||||
|
|
||||||
unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
|
unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
|
||||||
from `tabPurchase Invoice` where docstatus = 1 and name in (%s)
|
from `tabPurchase Invoice` where docstatus = 1 and name in (%s)
|
||||||
and ifnull(unrealized_profit_loss_account, '') != ''
|
and ifnull(unrealized_profit_loss_account, '') != ''
|
||||||
order by unrealized_profit_loss_account""" %
|
order by unrealized_profit_loss_account""" %
|
||||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
|
', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list))
|
||||||
|
|
||||||
expense_columns = [(account + ":Currency/currency:120") for account in expense_accounts]
|
expense_columns = [(account + ":Currency/currency:120") for account in expense_accounts]
|
||||||
unrealized_profit_loss_account_columns = [(account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts]
|
unrealized_profit_loss_account_columns = [(account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts]
|
||||||
@ -208,7 +208,7 @@ def get_invoice_expense_map(invoice_list):
|
|||||||
from `tabPurchase Invoice Item`
|
from `tabPurchase Invoice Item`
|
||||||
where parent in (%s)
|
where parent in (%s)
|
||||||
group by parent, expense_account
|
group by parent, expense_account
|
||||||
""" % ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
|
""" % ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
|
||||||
|
|
||||||
invoice_expense_map = {}
|
invoice_expense_map = {}
|
||||||
for d in expense_details:
|
for d in expense_details:
|
||||||
@ -221,7 +221,7 @@ def get_internal_invoice_map(invoice_list):
|
|||||||
unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account,
|
unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account,
|
||||||
base_net_total as amount from `tabPurchase Invoice` where name in (%s)
|
base_net_total as amount from `tabPurchase Invoice` where name in (%s)
|
||||||
and is_internal_supplier = 1 and company = represents_company""" %
|
and is_internal_supplier = 1 and company = represents_company""" %
|
||||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
|
', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
|
||||||
|
|
||||||
internal_invoice_map = {}
|
internal_invoice_map = {}
|
||||||
for d in unrealized_amount_details:
|
for d in unrealized_amount_details:
|
||||||
@ -238,7 +238,7 @@ def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts):
|
|||||||
where parent in (%s) and category in ('Total', 'Valuation and Total')
|
where parent in (%s) and category in ('Total', 'Valuation and Total')
|
||||||
and base_tax_amount_after_discount_amount != 0
|
and base_tax_amount_after_discount_amount != 0
|
||||||
group by parent, account_head, add_deduct_tax
|
group by parent, account_head, add_deduct_tax
|
||||||
""" % ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
|
""" % ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
|
||||||
|
|
||||||
invoice_tax_map = {}
|
invoice_tax_map = {}
|
||||||
for d in tax_details:
|
for d in tax_details:
|
||||||
@ -258,7 +258,7 @@ def get_invoice_po_pr_map(invoice_list):
|
|||||||
select parent, purchase_order, purchase_receipt, po_detail, project
|
select parent, purchase_order, purchase_receipt, po_detail, project
|
||||||
from `tabPurchase Invoice Item`
|
from `tabPurchase Invoice Item`
|
||||||
where parent in (%s)
|
where parent in (%s)
|
||||||
""" % ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
|
""" % ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
|
||||||
|
|
||||||
invoice_po_pr_map = {}
|
invoice_po_pr_map = {}
|
||||||
for d in pi_items:
|
for d in pi_items:
|
||||||
|
|||||||
@ -158,7 +158,7 @@ def get_sales_invoice_data(filters):
|
|||||||
def get_mode_of_payments(filters):
|
def get_mode_of_payments(filters):
|
||||||
mode_of_payments = {}
|
mode_of_payments = {}
|
||||||
invoice_list = get_invoices(filters)
|
invoice_list = get_invoices(filters)
|
||||||
invoice_list_names = ",".join(['"' + invoice['name'] + '"' for invoice in invoice_list])
|
invoice_list_names = ",".join('"' + invoice['name'] + '"' for invoice in invoice_list)
|
||||||
if invoice_list:
|
if invoice_list:
|
||||||
inv_mop = frappe.db.sql("""select a.owner,a.posting_date, ifnull(b.mode_of_payment, '') as mode_of_payment
|
inv_mop = frappe.db.sql("""select a.owner,a.posting_date, ifnull(b.mode_of_payment, '') as mode_of_payment
|
||||||
from `tabSales Invoice` a, `tabSales Invoice Payment` b
|
from `tabSales Invoice` a, `tabSales Invoice Payment` b
|
||||||
@ -197,7 +197,7 @@ def get_invoices(filters):
|
|||||||
def get_mode_of_payment_details(filters):
|
def get_mode_of_payment_details(filters):
|
||||||
mode_of_payment_details = {}
|
mode_of_payment_details = {}
|
||||||
invoice_list = get_invoices(filters)
|
invoice_list = get_invoices(filters)
|
||||||
invoice_list_names = ",".join(['"' + invoice['name'] + '"' for invoice in invoice_list])
|
invoice_list_names = ",".join('"' + invoice['name'] + '"' for invoice in invoice_list)
|
||||||
if invoice_list:
|
if invoice_list:
|
||||||
inv_mop_detail = frappe.db.sql("""select a.owner, a.posting_date,
|
inv_mop_detail = frappe.db.sql("""select a.owner, a.posting_date,
|
||||||
ifnull(b.mode_of_payment, '') as mode_of_payment, sum(b.base_amount) as paid_amount
|
ifnull(b.mode_of_payment, '') as mode_of_payment, sum(b.base_amount) as paid_amount
|
||||||
|
|||||||
@ -248,19 +248,19 @@ def get_columns(invoice_list, additional_table_columns):
|
|||||||
income_accounts = frappe.db.sql_list("""select distinct income_account
|
income_accounts = frappe.db.sql_list("""select distinct income_account
|
||||||
from `tabSales Invoice Item` where docstatus = 1 and parent in (%s)
|
from `tabSales Invoice Item` where docstatus = 1 and parent in (%s)
|
||||||
order by income_account""" %
|
order by income_account""" %
|
||||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
|
', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list))
|
||||||
|
|
||||||
tax_accounts = frappe.db.sql_list("""select distinct account_head
|
tax_accounts = frappe.db.sql_list("""select distinct account_head
|
||||||
from `tabSales Taxes and Charges` where parenttype = 'Sales Invoice'
|
from `tabSales Taxes and Charges` where parenttype = 'Sales Invoice'
|
||||||
and docstatus = 1 and base_tax_amount_after_discount_amount != 0
|
and docstatus = 1 and base_tax_amount_after_discount_amount != 0
|
||||||
and parent in (%s) order by account_head""" %
|
and parent in (%s) order by account_head""" %
|
||||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
|
', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list))
|
||||||
|
|
||||||
unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
|
unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
|
||||||
from `tabSales Invoice` where docstatus = 1 and name in (%s)
|
from `tabSales Invoice` where docstatus = 1 and name in (%s)
|
||||||
and ifnull(unrealized_profit_loss_account, '') != ''
|
and ifnull(unrealized_profit_loss_account, '') != ''
|
||||||
order by unrealized_profit_loss_account""" %
|
order by unrealized_profit_loss_account""" %
|
||||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
|
', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list))
|
||||||
|
|
||||||
for account in income_accounts:
|
for account in income_accounts:
|
||||||
income_columns.append({
|
income_columns.append({
|
||||||
@ -406,7 +406,7 @@ def get_invoices(filters, additional_query_columns):
|
|||||||
def get_invoice_income_map(invoice_list):
|
def get_invoice_income_map(invoice_list):
|
||||||
income_details = frappe.db.sql("""select parent, income_account, sum(base_net_amount) as amount
|
income_details = frappe.db.sql("""select parent, income_account, sum(base_net_amount) as amount
|
||||||
from `tabSales Invoice Item` where parent in (%s) group by parent, income_account""" %
|
from `tabSales Invoice Item` where parent in (%s) group by parent, income_account""" %
|
||||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
|
', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
|
||||||
|
|
||||||
invoice_income_map = {}
|
invoice_income_map = {}
|
||||||
for d in income_details:
|
for d in income_details:
|
||||||
@ -419,7 +419,7 @@ def get_internal_invoice_map(invoice_list):
|
|||||||
unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account,
|
unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account,
|
||||||
base_net_total as amount from `tabSales Invoice` where name in (%s)
|
base_net_total as amount from `tabSales Invoice` where name in (%s)
|
||||||
and is_internal_customer = 1 and company = represents_company""" %
|
and is_internal_customer = 1 and company = represents_company""" %
|
||||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
|
', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
|
||||||
|
|
||||||
internal_invoice_map = {}
|
internal_invoice_map = {}
|
||||||
for d in unrealized_amount_details:
|
for d in unrealized_amount_details:
|
||||||
@ -432,7 +432,7 @@ def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts):
|
|||||||
tax_details = frappe.db.sql("""select parent, account_head,
|
tax_details = frappe.db.sql("""select parent, account_head,
|
||||||
sum(base_tax_amount_after_discount_amount) as tax_amount
|
sum(base_tax_amount_after_discount_amount) as tax_amount
|
||||||
from `tabSales Taxes and Charges` where parent in (%s) group by parent, account_head""" %
|
from `tabSales Taxes and Charges` where parent in (%s) group by parent, account_head""" %
|
||||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
|
', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
|
||||||
|
|
||||||
invoice_tax_map = {}
|
invoice_tax_map = {}
|
||||||
for d in tax_details:
|
for d in tax_details:
|
||||||
@ -451,7 +451,7 @@ def get_invoice_so_dn_map(invoice_list):
|
|||||||
si_items = frappe.db.sql("""select parent, sales_order, delivery_note, so_detail
|
si_items = frappe.db.sql("""select parent, sales_order, delivery_note, so_detail
|
||||||
from `tabSales Invoice Item` where parent in (%s)
|
from `tabSales Invoice Item` where parent in (%s)
|
||||||
and (ifnull(sales_order, '') != '' or ifnull(delivery_note, '') != '')""" %
|
and (ifnull(sales_order, '') != '' or ifnull(delivery_note, '') != '')""" %
|
||||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
|
', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
|
||||||
|
|
||||||
invoice_so_dn_map = {}
|
invoice_so_dn_map = {}
|
||||||
for d in si_items:
|
for d in si_items:
|
||||||
@ -475,7 +475,7 @@ def get_invoice_cc_wh_map(invoice_list):
|
|||||||
si_items = frappe.db.sql("""select parent, cost_center, warehouse
|
si_items = frappe.db.sql("""select parent, cost_center, warehouse
|
||||||
from `tabSales Invoice Item` where parent in (%s)
|
from `tabSales Invoice Item` where parent in (%s)
|
||||||
and (ifnull(cost_center, '') != '' or ifnull(warehouse, '') != '')""" %
|
and (ifnull(cost_center, '') != '' or ifnull(warehouse, '') != '')""" %
|
||||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
|
', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
|
||||||
|
|
||||||
invoice_cc_wh_map = {}
|
invoice_cc_wh_map = {}
|
||||||
for d in si_items:
|
for d in si_items:
|
||||||
|
|||||||
@ -71,10 +71,8 @@ class TestTaxDetail(unittest.TestCase):
|
|||||||
|
|
||||||
def rm_testdocs(self):
|
def rm_testdocs(self):
|
||||||
"Remove the Company and all data"
|
"Remove the Company and all data"
|
||||||
from erpnext.setup.doctype.company.delete_company_transactions import delete_company_transactions
|
from erpnext.setup.doctype.company.company import create_transaction_deletion_request
|
||||||
delete_company_transactions(self.company.name)
|
create_transaction_deletion_request(self.company.name)
|
||||||
self.company.delete()
|
|
||||||
|
|
||||||
|
|
||||||
def test_report(self):
|
def test_report(self):
|
||||||
self.load_testdocs()
|
self.load_testdocs()
|
||||||
|
|||||||
@ -78,7 +78,7 @@ def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date, f
|
|||||||
and company=%s and posting_date between %s and %s
|
and company=%s and posting_date between %s and %s
|
||||||
""", (supplier, company, from_date, to_date), as_dict=1)
|
""", (supplier, company, from_date, to_date), as_dict=1)
|
||||||
|
|
||||||
supplier_credit_amount = flt(sum([d.credit for d in entries]))
|
supplier_credit_amount = flt(sum(d.credit for d in entries))
|
||||||
|
|
||||||
vouchers = [d.voucher_no for d in entries]
|
vouchers = [d.voucher_no for d in entries]
|
||||||
vouchers += get_advance_vouchers([supplier], company=company,
|
vouchers += get_advance_vouchers([supplier], company=company,
|
||||||
@ -91,7 +91,7 @@ def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date, f
|
|||||||
from `tabGL Entry`
|
from `tabGL Entry`
|
||||||
where account=%s and posting_date between %s and %s
|
where account=%s and posting_date between %s and %s
|
||||||
and company=%s and credit > 0 and voucher_no in ({0})
|
and company=%s and credit > 0 and voucher_no in ({0})
|
||||||
""".format(', '.join(["'%s'" % d for d in vouchers])),
|
""".format(', '.join("'%s'" % d for d in vouchers)),
|
||||||
(account, from_date, to_date, company))[0][0])
|
(account, from_date, to_date, company))[0][0])
|
||||||
|
|
||||||
date_range_filter = [fiscal_year, from_date, to_date]
|
date_range_filter = [fiscal_year, from_date, to_date]
|
||||||
|
|||||||
@ -81,8 +81,7 @@ def convert_to_presentation_currency(gl_entries, currency_info, company):
|
|||||||
presentation_currency = currency_info['presentation_currency']
|
presentation_currency = currency_info['presentation_currency']
|
||||||
company_currency = currency_info['company_currency']
|
company_currency = currency_info['company_currency']
|
||||||
|
|
||||||
pl_accounts = [d.name for d in frappe.get_list('Account',
|
account_currencies = list(set(entry['account_currency'] for entry in gl_entries))
|
||||||
filters={'report_type': 'Profit and Loss', 'company': company})]
|
|
||||||
|
|
||||||
for entry in gl_entries:
|
for entry in gl_entries:
|
||||||
account = entry['account']
|
account = entry['account']
|
||||||
@ -92,10 +91,15 @@ def convert_to_presentation_currency(gl_entries, currency_info, company):
|
|||||||
credit_in_account_currency = flt(entry['credit_in_account_currency'])
|
credit_in_account_currency = flt(entry['credit_in_account_currency'])
|
||||||
account_currency = entry['account_currency']
|
account_currency = entry['account_currency']
|
||||||
|
|
||||||
if account_currency != presentation_currency:
|
if len(account_currencies) == 1 and account_currency == presentation_currency:
|
||||||
value = debit or credit
|
if entry.get('debit'):
|
||||||
|
entry['debit'] = debit_in_account_currency
|
||||||
|
|
||||||
date = entry['posting_date'] if account in pl_accounts else currency_info['report_date']
|
if entry.get('credit'):
|
||||||
|
entry['credit'] = credit_in_account_currency
|
||||||
|
else:
|
||||||
|
value = debit or credit
|
||||||
|
date = currency_info['report_date']
|
||||||
converted_value = convert(value, presentation_currency, company_currency, date)
|
converted_value = convert(value, presentation_currency, company_currency, date)
|
||||||
|
|
||||||
if entry.get('debit'):
|
if entry.get('debit'):
|
||||||
@ -104,13 +108,6 @@ def convert_to_presentation_currency(gl_entries, currency_info, company):
|
|||||||
if entry.get('credit'):
|
if entry.get('credit'):
|
||||||
entry['credit'] = converted_value
|
entry['credit'] = converted_value
|
||||||
|
|
||||||
elif account_currency == presentation_currency:
|
|
||||||
if entry.get('debit'):
|
|
||||||
entry['debit'] = debit_in_account_currency
|
|
||||||
|
|
||||||
if entry.get('credit'):
|
|
||||||
entry['credit'] = credit_in_account_currency
|
|
||||||
|
|
||||||
converted_gl_list.append(entry)
|
converted_gl_list.append(entry)
|
||||||
|
|
||||||
return converted_gl_list
|
return converted_gl_list
|
||||||
@ -142,6 +139,6 @@ def get_invoiced_item_gross_margin(sales_invoice=None, item_code=None, company=N
|
|||||||
gross_profit_data = GrossProfitGenerator(filters)
|
gross_profit_data = GrossProfitGenerator(filters)
|
||||||
result = gross_profit_data.grouped_data
|
result = gross_profit_data.grouped_data
|
||||||
if not with_item_data:
|
if not with_item_data:
|
||||||
result = sum([d.gross_profit for d in result])
|
result = sum(d.gross_profit for d in result)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@ -635,7 +635,7 @@ def get_held_invoices(party_type, party):
|
|||||||
'select name from `tabPurchase Invoice` where release_date IS NOT NULL and release_date > CURDATE()',
|
'select name from `tabPurchase Invoice` where release_date IS NOT NULL and release_date > CURDATE()',
|
||||||
as_dict=1
|
as_dict=1
|
||||||
)
|
)
|
||||||
held_invoices = set([d['name'] for d in held_invoices])
|
held_invoices = set(d['name'] for d in held_invoices)
|
||||||
|
|
||||||
return held_invoices
|
return held_invoices
|
||||||
|
|
||||||
|
|||||||
@ -470,7 +470,7 @@ class TestAsset(unittest.TestCase):
|
|||||||
})
|
})
|
||||||
asset.insert()
|
asset.insert()
|
||||||
accumulated_depreciation_after_full_schedule = \
|
accumulated_depreciation_after_full_schedule = \
|
||||||
max([d.accumulated_depreciation_amount for d in asset.get("schedules")])
|
max(d.accumulated_depreciation_amount for d in asset.get("schedules"))
|
||||||
|
|
||||||
asset_value_after_full_schedule = (flt(asset.gross_purchase_amount) -
|
asset_value_after_full_schedule = (flt(asset.gross_purchase_amount) -
|
||||||
flt(accumulated_depreciation_after_full_schedule))
|
flt(accumulated_depreciation_after_full_schedule))
|
||||||
|
|||||||
@ -92,7 +92,7 @@ class AssetValueAdjustment(Document):
|
|||||||
d.value_after_depreciation = asset_value
|
d.value_after_depreciation = asset_value
|
||||||
|
|
||||||
if d.depreciation_method in ("Straight Line", "Manual"):
|
if d.depreciation_method in ("Straight Line", "Manual"):
|
||||||
end_date = max([s.schedule_date for s in asset.schedules if cint(s.finance_book_id) == d.idx])
|
end_date = max(s.schedule_date for s in asset.schedules if cint(s.finance_book_id) == d.idx)
|
||||||
total_days = date_diff(end_date, self.date)
|
total_days = date_diff(end_date, self.date)
|
||||||
rate_per_day = flt(d.value_after_depreciation) / flt(total_days)
|
rate_per_day = flt(d.value_after_depreciation) / flt(total_days)
|
||||||
from_date = self.date
|
from_date = self.date
|
||||||
|
|||||||
@ -104,7 +104,7 @@ class PurchaseOrder(BuyingController):
|
|||||||
|
|
||||||
def validate_minimum_order_qty(self):
|
def validate_minimum_order_qty(self):
|
||||||
if not self.get("items"): return
|
if not self.get("items"): return
|
||||||
items = list(set([d.item_code for d in self.get("items")]))
|
items = list(set(d.item_code for d in self.get("items")))
|
||||||
|
|
||||||
itemwise_min_order_qty = frappe._dict(frappe.db.sql("""select name, min_order_qty
|
itemwise_min_order_qty = frappe._dict(frappe.db.sql("""select name, min_order_qty
|
||||||
from tabItem where name in ({0})""".format(", ".join(["%s"] * len(items))), items))
|
from tabItem where name in ({0})""".format(", ".join(["%s"] * len(items))), items))
|
||||||
@ -291,10 +291,10 @@ class PurchaseOrder(BuyingController):
|
|||||||
so.notify_update()
|
so.notify_update()
|
||||||
|
|
||||||
def has_drop_ship_item(self):
|
def has_drop_ship_item(self):
|
||||||
return any([d.delivered_by_supplier for d in self.items])
|
return any(d.delivered_by_supplier for d in self.items)
|
||||||
|
|
||||||
def is_against_so(self):
|
def is_against_so(self):
|
||||||
return any([d.sales_order for d in self.items if d.sales_order])
|
return any(d.sales_order for d in self.items if d.sales_order)
|
||||||
|
|
||||||
def set_received_qty_for_drop_ship_items(self):
|
def set_received_qty_for_drop_ship_items(self):
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
|
|||||||
@ -359,7 +359,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
update_child_qty_rate('Purchase Order', trans_item, po.name)
|
update_child_qty_rate('Purchase Order', trans_item, po.name)
|
||||||
po.reload()
|
po.reload()
|
||||||
|
|
||||||
total_reqd_qty_after_change = sum([d.get("required_qty") for d in po.as_dict().get("supplied_items")])
|
total_reqd_qty_after_change = sum(d.get("required_qty") for d in po.as_dict().get("supplied_items"))
|
||||||
|
|
||||||
self.assertEqual(total_reqd_qty_after_change, 2 * total_reqd_qty)
|
self.assertEqual(total_reqd_qty_after_change, 2 * total_reqd_qty)
|
||||||
|
|
||||||
|
|||||||
@ -62,6 +62,7 @@ class RequestforQuotation(BuyingController):
|
|||||||
for supplier in self.suppliers:
|
for supplier in self.suppliers:
|
||||||
supplier.email_sent = 0
|
supplier.email_sent = 0
|
||||||
supplier.quote_status = 'Pending'
|
supplier.quote_status = 'Pending'
|
||||||
|
self.send_to_supplier()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
frappe.db.set(self, 'status', 'Cancelled')
|
frappe.db.set(self, 'status', 'Cancelled')
|
||||||
@ -81,7 +82,7 @@ class RequestforQuotation(BuyingController):
|
|||||||
def send_to_supplier(self):
|
def send_to_supplier(self):
|
||||||
"""Sends RFQ mail to involved suppliers."""
|
"""Sends RFQ mail to involved suppliers."""
|
||||||
for rfq_supplier in self.suppliers:
|
for rfq_supplier in self.suppliers:
|
||||||
if rfq_supplier.send_email:
|
if rfq_supplier.email_id is not None and rfq_supplier.send_email:
|
||||||
self.validate_email_id(rfq_supplier)
|
self.validate_email_id(rfq_supplier)
|
||||||
|
|
||||||
# make new user if required
|
# make new user if required
|
||||||
@ -390,7 +391,7 @@ def get_item_from_material_requests_based_on_supplier(source_name, target_doc =
|
|||||||
def get_supplier_tag():
|
def get_supplier_tag():
|
||||||
if not frappe.cache().hget("Supplier", "Tags"):
|
if not frappe.cache().hget("Supplier", "Tags"):
|
||||||
filters = {"document_type": "Supplier"}
|
filters = {"document_type": "Supplier"}
|
||||||
tags = list(set([tag.tag for tag in frappe.get_all("Tag Link", filters=filters, fields=["tag"]) if tag]))
|
tags = list(set(tag.tag for tag in frappe.get_all("Tag Link", filters=filters, fields=["tag"]) if tag))
|
||||||
frappe.cache().hset("Supplier", "Tags", tags)
|
frappe.cache().hset("Supplier", "Tags", tags)
|
||||||
|
|
||||||
return frappe.cache().hget("Supplier", "Tags")
|
return frappe.cache().hget("Supplier", "Tags")
|
||||||
|
|||||||
@ -383,8 +383,14 @@
|
|||||||
"icon": "fa fa-user",
|
"icon": "fa fa-user",
|
||||||
"idx": 370,
|
"idx": 370,
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"links": [],
|
"links": [
|
||||||
"modified": "2021-01-06 19:51:40.939087",
|
{
|
||||||
|
"group": "Item Group",
|
||||||
|
"link_doctype": "Supplier Item Group",
|
||||||
|
"link_fieldname": "supplier"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modified": "2021-05-18 15:10:11.087191",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Supplier",
|
"name": "Supplier",
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Supplier Item Group', {
|
||||||
|
// refresh: function(frm) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
});
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2021-05-07 18:16:40.621421",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"supplier",
|
||||||
|
"item_group"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "supplier",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Supplier",
|
||||||
|
"options": "Supplier",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "item_group",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Item Group",
|
||||||
|
"options": "Item Group",
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-05-19 13:48:16.742303",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Buying",
|
||||||
|
"name": "Supplier Item Group",
|
||||||
|
"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": "Purchase User",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Purchase Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class SupplierItemGroup(Document):
|
||||||
|
def validate(self):
|
||||||
|
exists = frappe.db.exists({
|
||||||
|
'doctype': 'Supplier Item Group',
|
||||||
|
'supplier': self.supplier,
|
||||||
|
'item_group': self.item_group
|
||||||
|
})
|
||||||
|
if exists:
|
||||||
|
frappe.throw(_("Item Group has already been linked to this supplier."))
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestSupplierItemGroup(unittest.TestCase):
|
||||||
|
pass
|
||||||
54
erpnext/change_log/v13/v13_4_0.md
Normal file
54
erpnext/change_log/v13/v13_4_0.md
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# Version 13.4.0 Release Notes
|
||||||
|
|
||||||
|
### Features & Enhancements
|
||||||
|
|
||||||
|
- Multiple GST enhancement and fixes ([#25249](https://github.com/frappe/erpnext/pull/25249))
|
||||||
|
- Linking supplier with an item group for filtering items ([#25683](https://github.com/frappe/erpnext/pull/25683))
|
||||||
|
- Leave Policy Assignment Refactor ([#24327](https://github.com/frappe/erpnext/pull/24327))
|
||||||
|
- Dimension-wise Accounts Balance Report ([#25260](https://github.com/frappe/erpnext/pull/25260))
|
||||||
|
- Show net values in Party Accounts ([#25714](https://github.com/frappe/erpnext/pull/25714))
|
||||||
|
- Add pending qty section to batch/serial selector dialog ([#25519](https://github.com/frappe/erpnext/pull/25519))
|
||||||
|
- enhancements in Training Event ([#25782](https://github.com/frappe/erpnext/pull/25782))
|
||||||
|
- Refactored timesheet ([#25701](https://github.com/frappe/erpnext/pull/25701))
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Process Statement of Accounts formatting ([#25777](https://github.com/frappe/erpnext/pull/25777))
|
||||||
|
- Removed serial no validation for sales invoice ([#25817](https://github.com/frappe/erpnext/pull/25817))
|
||||||
|
- Fetch email id from dialog box in pos past order summary ([#25808](https://github.com/frappe/erpnext/pull/25808))
|
||||||
|
- Don't map set warehouse from delivery note to purchase receipt ([#25672](https://github.com/frappe/erpnext/pull/25672))
|
||||||
|
- Apply permission while selecting projects ([#25765](https://github.com/frappe/erpnext/pull/25765))
|
||||||
|
- Error on adding bank account to plaid ([#25658](https://github.com/frappe/erpnext/pull/25658))
|
||||||
|
- Set disable rounded total if it is globally enabled ([#25789](https://github.com/frappe/erpnext/pull/25789))
|
||||||
|
- Wrong amount on CR side in general ledger report for customer when different account currencies are involved ([#25654](https://github.com/frappe/erpnext/pull/25654))
|
||||||
|
- Stock move dialog duplicate submit actions (V13) ([#25486](https://github.com/frappe/erpnext/pull/25486))
|
||||||
|
- Cashflow mapper not showing data ([#25815](https://github.com/frappe/erpnext/pull/25815))
|
||||||
|
- Ignore rounding diff while importing JV using data import ([#25816](https://github.com/frappe/erpnext/pull/25816))
|
||||||
|
- Woocommerce order sync issue ([#25688](https://github.com/frappe/erpnext/pull/25688))
|
||||||
|
- Expected amount in pos closing payments table ([#25737](https://github.com/frappe/erpnext/pull/25737))
|
||||||
|
- Show only company addresses for ITC reversal entry ([#25867](https://github.com/frappe/erpnext/pull/25867))
|
||||||
|
- Timeout error while loading warehouse tree ([#25694](https://github.com/frappe/erpnext/pull/25694))
|
||||||
|
- Plaid Withdrawals and Deposits are recorded incorrectly ([#25784](https://github.com/frappe/erpnext/pull/25784))
|
||||||
|
- Return case for item with available qty equal to one ([#25760](https://github.com/frappe/erpnext/pull/25760))
|
||||||
|
- The status of repost item valuation showing In Progress since long time ([#25754](https://github.com/frappe/erpnext/pull/25754))
|
||||||
|
- Updated applicable charges form in landed cost voucher ([#25732](https://github.com/frappe/erpnext/pull/25732))
|
||||||
|
- Rearrange buttons for Company DocType ([#25617](https://github.com/frappe/erpnext/pull/25617))
|
||||||
|
- Show uom for item in selector dialog ([#25697](https://github.com/frappe/erpnext/pull/25697))
|
||||||
|
- Warehouse not found in stock entry ([#25776](https://github.com/frappe/erpnext/pull/25776))
|
||||||
|
- Use dictionary filter instead of list (bp #25874 pre-release) ([#25875](https://github.com/frappe/erpnext/pull/25875))
|
||||||
|
- Send emails on rfq submit ([#25695](https://github.com/frappe/erpnext/pull/25695))
|
||||||
|
- Cannot bypass e-invoicing for non gst item invoices ([#25759](https://github.com/frappe/erpnext/pull/25759))
|
||||||
|
- Validation message of quality inspection in purchase receipt ([#25666](https://github.com/frappe/erpnext/pull/25666))
|
||||||
|
- Dialog variable assignment after definition in POS ([#25681](https://github.com/frappe/erpnext/pull/25681))
|
||||||
|
- Wrong quantity after transaction for parallel stock transactions ([#25779](https://github.com/frappe/erpnext/pull/25779))
|
||||||
|
- Item Variant Details Report ([#25797](https://github.com/frappe/erpnext/pull/25797))
|
||||||
|
- Duplicate stock entry on multiple click ([#25742](https://github.com/frappe/erpnext/pull/25742))
|
||||||
|
- Bank statement import via google sheet ([#25676](https://github.com/frappe/erpnext/pull/25676))
|
||||||
|
- Change today to now to get data for reposting ([#25702](https://github.com/frappe/erpnext/pull/25702))
|
||||||
|
- Parameter for get_filtered_list_for_consolidated_report in consolidated balance sheet ([#25698](https://github.com/frappe/erpnext/pull/25698))
|
||||||
|
- Ageing error in PSOA ([#25857](https://github.com/frappe/erpnext/pull/25857))
|
||||||
|
- Breaking cost center validation ([#25660](https://github.com/frappe/erpnext/pull/25660))
|
||||||
|
- Project filter for Kanban Board ([#25744](https://github.com/frappe/erpnext/pull/25744))
|
||||||
|
- Show allow zero valuation only when auto checked ([#25778](https://github.com/frappe/erpnext/pull/25778))
|
||||||
|
- Missing cost center message on creating gl entries ([#25755](https://github.com/frappe/erpnext/pull/25755))
|
||||||
|
- Address template with upper filter throws jinja error ([#25756](https://github.com/frappe/erpnext/pull/25756))
|
||||||
@ -368,6 +368,11 @@ class AccountsController(TransactionBase):
|
|||||||
if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field('is_fixed_asset'):
|
if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field('is_fixed_asset'):
|
||||||
item.set('is_fixed_asset', ret.get('is_fixed_asset', 0))
|
item.set('is_fixed_asset', ret.get('is_fixed_asset', 0))
|
||||||
|
|
||||||
|
# Double check for cost center
|
||||||
|
# Items add via promotional scheme may not have cost center set
|
||||||
|
if hasattr(item, 'cost_center') and not item.get('cost_center'):
|
||||||
|
item.set('cost_center', self.get('cost_center') or erpnext.get_default_cost_center(self.company))
|
||||||
|
|
||||||
if ret.get("pricing_rules"):
|
if ret.get("pricing_rules"):
|
||||||
self.apply_pricing_rule_on_items(item, ret)
|
self.apply_pricing_rule_on_items(item, ret)
|
||||||
self.set_pricing_rule_details(item, ret)
|
self.set_pricing_rule_details(item, ret)
|
||||||
@ -603,8 +608,8 @@ class AccountsController(TransactionBase):
|
|||||||
order_field = "purchase_order"
|
order_field = "purchase_order"
|
||||||
order_doctype = "Purchase Order"
|
order_doctype = "Purchase Order"
|
||||||
|
|
||||||
order_list = list(set([d.get(order_field)
|
order_list = list(set(d.get(order_field)
|
||||||
for d in self.get("items") if d.get(order_field)]))
|
for d in self.get("items") if d.get(order_field)))
|
||||||
|
|
||||||
journal_entries = get_advance_journal_entries(party_type, party, party_account,
|
journal_entries = get_advance_journal_entries(party_type, party, party_account,
|
||||||
amount_field, order_doctype, order_list, include_unallocated)
|
amount_field, order_doctype, order_list, include_unallocated)
|
||||||
@ -628,8 +633,8 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
def validate_advance_entries(self):
|
def validate_advance_entries(self):
|
||||||
order_field = "sales_order" if self.doctype == "Sales Invoice" else "purchase_order"
|
order_field = "sales_order" if self.doctype == "Sales Invoice" else "purchase_order"
|
||||||
order_list = list(set([d.get(order_field)
|
order_list = list(set(d.get(order_field)
|
||||||
for d in self.get("items") if d.get(order_field)]))
|
for d in self.get("items") if d.get(order_field)))
|
||||||
|
|
||||||
if not order_list: return
|
if not order_list: return
|
||||||
|
|
||||||
|
|||||||
@ -181,8 +181,8 @@ class BuyingController(StockController):
|
|||||||
stock_and_asset_items_amount += flt(d.base_net_amount)
|
stock_and_asset_items_amount += flt(d.base_net_amount)
|
||||||
last_item_idx = d.idx
|
last_item_idx = d.idx
|
||||||
|
|
||||||
total_valuation_amount = sum([flt(d.base_tax_amount_after_discount_amount) for d in self.get("taxes")
|
total_valuation_amount = sum(flt(d.base_tax_amount_after_discount_amount) for d in self.get("taxes")
|
||||||
if d.category in ["Valuation", "Valuation and Total"]])
|
if d.category in ["Valuation", "Valuation and Total"])
|
||||||
|
|
||||||
valuation_amount_adjustment = total_valuation_amount
|
valuation_amount_adjustment = total_valuation_amount
|
||||||
for i, item in enumerate(self.get("items")):
|
for i, item in enumerate(self.get("items")):
|
||||||
@ -325,7 +325,7 @@ class BuyingController(StockController):
|
|||||||
def update_raw_materials_supplied_based_on_stock_entries(self):
|
def update_raw_materials_supplied_based_on_stock_entries(self):
|
||||||
self.set('supplied_items', [])
|
self.set('supplied_items', [])
|
||||||
|
|
||||||
purchase_orders = set([d.purchase_order for d in self.items])
|
purchase_orders = set(d.purchase_order for d in self.items)
|
||||||
|
|
||||||
# qty of raw materials backflushed (for each item per purchase order)
|
# qty of raw materials backflushed (for each item per purchase order)
|
||||||
backflushed_raw_materials_map = get_backflushed_subcontracted_raw_materials(purchase_orders)
|
backflushed_raw_materials_map = get_backflushed_subcontracted_raw_materials(purchase_orders)
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
import erpnext
|
import erpnext
|
||||||
|
import json
|
||||||
from frappe.desk.reportview import get_match_cond, get_filters_cond
|
from frappe.desk.reportview import get_match_cond, get_filters_cond
|
||||||
from frappe.utils import nowdate, getdate
|
from frappe.utils import nowdate, getdate
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
@ -87,7 +88,7 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
fields = get_fields("Customer", fields)
|
fields = get_fields("Customer", fields)
|
||||||
|
|
||||||
searchfields = frappe.get_meta("Customer").get_search_fields()
|
searchfields = frappe.get_meta("Customer").get_search_fields()
|
||||||
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
|
searchfields = " or ".join(field + " like %(txt)s" for field in searchfields)
|
||||||
|
|
||||||
return frappe.db.sql("""select {fields} from `tabCustomer`
|
return frappe.db.sql("""select {fields} from `tabCustomer`
|
||||||
where docstatus < 2
|
where docstatus < 2
|
||||||
@ -198,6 +199,9 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
|
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
|
||||||
conditions = []
|
conditions = []
|
||||||
|
|
||||||
|
if isinstance(filters, str):
|
||||||
|
filters = json.loads(filters)
|
||||||
|
|
||||||
#Get searchfields from meta and use in Item Link field query
|
#Get searchfields from meta and use in Item Link field query
|
||||||
meta = frappe.get_meta("Item", cached=True)
|
meta = frappe.get_meta("Item", cached=True)
|
||||||
searchfields = meta.get_search_fields()
|
searchfields = meta.get_search_fields()
|
||||||
@ -216,11 +220,23 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
|||||||
if not field in searchfields]
|
if not field in searchfields]
|
||||||
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
|
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
|
||||||
|
|
||||||
|
if filters and isinstance(filters, dict) and filters.get('supplier'):
|
||||||
|
item_group_list = frappe.get_all('Supplier Item Group',
|
||||||
|
filters = {'supplier': filters.get('supplier')}, fields = ['item_group'])
|
||||||
|
|
||||||
|
item_groups = []
|
||||||
|
for i in item_group_list:
|
||||||
|
item_groups.append(i.item_group)
|
||||||
|
|
||||||
|
del filters['supplier']
|
||||||
|
|
||||||
|
if item_groups:
|
||||||
|
filters['item_group'] = ['in', item_groups]
|
||||||
|
|
||||||
description_cond = ''
|
description_cond = ''
|
||||||
if frappe.db.count('Item', cache=True) < 50000:
|
if frappe.db.count('Item', cache=True) < 50000:
|
||||||
# scan description only if items are less than 50000
|
# scan description only if items are less than 50000
|
||||||
description_cond = 'or tabItem.description LIKE %(txt)s'
|
description_cond = 'or tabItem.description LIKE %(txt)s'
|
||||||
|
|
||||||
return frappe.db.sql("""select tabItem.name,
|
return frappe.db.sql("""select tabItem.name,
|
||||||
if(length(tabItem.item_name) > 40,
|
if(length(tabItem.item_name) > 40,
|
||||||
concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name,
|
concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name,
|
||||||
|
|||||||
@ -428,7 +428,7 @@ class SellingController(StockController):
|
|||||||
self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
|
self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
|
||||||
|
|
||||||
def get_po_nos(self, ref_doctype, ref_fieldname, po_nos):
|
def get_po_nos(self, ref_doctype, ref_fieldname, po_nos):
|
||||||
doc_list = list(set([d.get(ref_fieldname) for d in self.items if d.get(ref_fieldname)]))
|
doc_list = list(set(d.get(ref_fieldname) for d in self.items if d.get(ref_fieldname)))
|
||||||
if doc_list:
|
if doc_list:
|
||||||
po_nos += [d.po_no for d in frappe.get_all(ref_doctype, 'po_no', filters = {'name': ('in', doc_list)}) if d.get('po_no')]
|
po_nos += [d.po_no for d in frappe.get_all(ref_doctype, 'po_no', filters = {'name': ('in', doc_list)}) if d.get('po_no')]
|
||||||
|
|
||||||
|
|||||||
@ -76,12 +76,12 @@ status_map = {
|
|||||||
["Stopped", "eval:self.status == 'Stopped'"],
|
["Stopped", "eval:self.status == 'Stopped'"],
|
||||||
["Cancelled", "eval:self.docstatus == 2"],
|
["Cancelled", "eval:self.docstatus == 2"],
|
||||||
["Pending", "eval:self.status != 'Stopped' and self.per_ordered == 0 and self.docstatus == 1"],
|
["Pending", "eval:self.status != 'Stopped' and self.per_ordered == 0 and self.docstatus == 1"],
|
||||||
["Partially Ordered", "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1"],
|
|
||||||
["Ordered", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
|
["Ordered", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
|
||||||
["Transferred", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'"],
|
["Transferred", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'"],
|
||||||
["Issued", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'"],
|
["Issued", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'"],
|
||||||
["Received", "eval:self.status != 'Stopped' and self.per_received == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
|
["Received", "eval:self.status != 'Stopped' and self.per_received == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
|
||||||
["Partially Received", "eval:self.status != 'Stopped' and self.per_received > 0 and self.per_received < 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
|
["Partially Received", "eval:self.status != 'Stopped' and self.per_received > 0 and self.per_received < 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
|
||||||
|
["Partially Ordered", "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1"],
|
||||||
["Manufactured", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'"]
|
["Manufactured", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'"]
|
||||||
],
|
],
|
||||||
"Bank Transaction": [
|
"Bank Transaction": [
|
||||||
@ -299,8 +299,8 @@ class StatusUpdater(Document):
|
|||||||
args['name'] = self.get(args['percent_join_field_parent'])
|
args['name'] = self.get(args['percent_join_field_parent'])
|
||||||
self._update_percent_field(args, update_modified)
|
self._update_percent_field(args, update_modified)
|
||||||
else:
|
else:
|
||||||
distinct_transactions = set([d.get(args['percent_join_field'])
|
distinct_transactions = set(d.get(args['percent_join_field'])
|
||||||
for d in self.get_all_children(args['source_dt'])])
|
for d in self.get_all_children(args['source_dt']))
|
||||||
|
|
||||||
for name in distinct_transactions:
|
for name in distinct_transactions:
|
||||||
if name:
|
if name:
|
||||||
|
|||||||
@ -310,7 +310,7 @@ class StockController(AccountsController):
|
|||||||
|
|
||||||
def get_serialized_items(self):
|
def get_serialized_items(self):
|
||||||
serialized_items = []
|
serialized_items = []
|
||||||
item_codes = list(set([d.item_code for d in self.get("items")]))
|
item_codes = list(set(d.item_code for d in self.get("items")))
|
||||||
if item_codes:
|
if item_codes:
|
||||||
serialized_items = frappe.db.sql_list("""select name from `tabItem`
|
serialized_items = frappe.db.sql_list("""select name from `tabItem`
|
||||||
where has_serial_no=1 and name in ({})""".format(", ".join(["%s"]*len(item_codes))),
|
where has_serial_no=1 and name in ({})""".format(", ".join(["%s"]*len(item_codes))),
|
||||||
@ -321,8 +321,8 @@ class StockController(AccountsController):
|
|||||||
def validate_warehouse(self):
|
def validate_warehouse(self):
|
||||||
from erpnext.stock.utils import validate_warehouse_company, validate_disabled_warehouse
|
from erpnext.stock.utils import validate_warehouse_company, validate_disabled_warehouse
|
||||||
|
|
||||||
warehouses = list(set([d.warehouse for d in
|
warehouses = list(set(d.warehouse for d in
|
||||||
self.get("items") if getattr(d, "warehouse", None)]))
|
self.get("items") if getattr(d, "warehouse", None)))
|
||||||
|
|
||||||
target_warehouses = list(set([d.target_warehouse for d in
|
target_warehouses = list(set([d.target_warehouse for d in
|
||||||
self.get("items") if getattr(d, "target_warehouse", None)]))
|
self.get("items") if getattr(d, "target_warehouse", None)]))
|
||||||
|
|||||||
@ -375,10 +375,10 @@ class calculate_taxes_and_totals(object):
|
|||||||
|
|
||||||
def manipulate_grand_total_for_inclusive_tax(self):
|
def manipulate_grand_total_for_inclusive_tax(self):
|
||||||
# if fully inclusive taxes and diff
|
# if fully inclusive taxes and diff
|
||||||
if self.doc.get("taxes") and any([cint(t.included_in_print_rate) for t in self.doc.get("taxes")]):
|
if self.doc.get("taxes") and any(cint(t.included_in_print_rate) for t in self.doc.get("taxes")):
|
||||||
last_tax = self.doc.get("taxes")[-1]
|
last_tax = self.doc.get("taxes")[-1]
|
||||||
non_inclusive_tax_amount = sum([flt(d.tax_amount_after_discount_amount)
|
non_inclusive_tax_amount = sum(flt(d.tax_amount_after_discount_amount)
|
||||||
for d in self.doc.get("taxes") if not d.included_in_print_rate])
|
for d in self.doc.get("taxes") if not d.included_in_print_rate)
|
||||||
|
|
||||||
diff = self.doc.total + non_inclusive_tax_amount \
|
diff = self.doc.total + non_inclusive_tax_amount \
|
||||||
- flt(last_tax.total, last_tax.precision("total"))
|
- flt(last_tax.total, last_tax.precision("total"))
|
||||||
@ -518,8 +518,8 @@ class calculate_taxes_and_totals(object):
|
|||||||
|
|
||||||
def calculate_total_advance(self):
|
def calculate_total_advance(self):
|
||||||
if self.doc.docstatus < 2:
|
if self.doc.docstatus < 2:
|
||||||
total_allocated_amount = sum([flt(adv.allocated_amount, adv.precision("allocated_amount"))
|
total_allocated_amount = sum(flt(adv.allocated_amount, adv.precision("allocated_amount"))
|
||||||
for adv in self.doc.get("advances")])
|
for adv in self.doc.get("advances"))
|
||||||
|
|
||||||
self.doc.total_advance = flt(total_allocated_amount, self.doc.precision("total_advance"))
|
self.doc.total_advance = flt(total_allocated_amount, self.doc.precision("total_advance"))
|
||||||
|
|
||||||
@ -619,7 +619,7 @@ class calculate_taxes_and_totals(object):
|
|||||||
|
|
||||||
if self.doc.doctype == "Sales Invoice" \
|
if self.doc.doctype == "Sales Invoice" \
|
||||||
and self.doc.paid_amount > self.doc.grand_total and not self.doc.is_return \
|
and self.doc.paid_amount > self.doc.grand_total and not self.doc.is_return \
|
||||||
and any([d.type == "Cash" for d in self.doc.payments]):
|
and any(d.type == "Cash" for d in self.doc.payments):
|
||||||
grand_total = self.doc.rounded_total or self.doc.grand_total
|
grand_total = self.doc.rounded_total or self.doc.grand_total
|
||||||
base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
|
base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
|
||||||
|
|
||||||
|
|||||||
@ -26,8 +26,8 @@ class TestMapper(unittest.TestCase):
|
|||||||
|
|
||||||
# Assert that all inserted items are present in updated sales order
|
# Assert that all inserted items are present in updated sales order
|
||||||
src_items = item_list_1 + item_list_2 + item_list_3
|
src_items = item_list_1 + item_list_2 + item_list_3
|
||||||
self.assertEqual(set([d for d in src_items]),
|
self.assertEqual(set(d for d in src_items),
|
||||||
set([d.item_code for d in updated_so.items]))
|
set(d.item_code for d in updated_so.items))
|
||||||
|
|
||||||
|
|
||||||
def make_quotation(self, item_list, customer):
|
def make_quotation(self, item_list, customer):
|
||||||
|
|||||||
@ -113,7 +113,7 @@ def post_process(doctype, data):
|
|||||||
doc.set_indicator()
|
doc.set_indicator()
|
||||||
|
|
||||||
doc.status_display = ", ".join(doc.status_display)
|
doc.status_display = ", ".join(doc.status_display)
|
||||||
doc.items_preview = ", ".join([d.item_name for d in doc.items if d.item_name])
|
doc.items_preview = ", ".join(d.item_name for d in doc.items if d.item_name)
|
||||||
result.append(doc)
|
result.append(doc)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@ -280,7 +280,6 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:",
|
|
||||||
"fieldname": "territory",
|
"fieldname": "territory",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Territory",
|
"label": "Territory",
|
||||||
@ -431,7 +430,7 @@
|
|||||||
"icon": "fa fa-info-sign",
|
"icon": "fa fa-info-sign",
|
||||||
"idx": 195,
|
"idx": 195,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-01-06 19:42:46.190051",
|
"modified": "2021-06-04 10:11:22.831139",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "CRM",
|
"module": "CRM",
|
||||||
"name": "Opportunity",
|
"name": "Opportunity",
|
||||||
|
|||||||
@ -6,7 +6,8 @@ frappe.ui.form.on('Assessment Result', {
|
|||||||
if (!frm.doc.__islocal) {
|
if (!frm.doc.__islocal) {
|
||||||
frm.trigger('setup_chart');
|
frm.trigger('setup_chart');
|
||||||
}
|
}
|
||||||
frm.set_df_property('details', 'read_only', 1);
|
|
||||||
|
frm.get_field('details').grid.cannot_add_rows = true;
|
||||||
|
|
||||||
frm.set_query('course', function() {
|
frm.set_query('course', function() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -219,7 +219,6 @@ def get_quiz(quiz_name, course):
|
|||||||
try:
|
try:
|
||||||
quiz = frappe.get_doc("Quiz", quiz_name)
|
quiz = frappe.get_doc("Quiz", quiz_name)
|
||||||
questions = quiz.get_questions()
|
questions = quiz.get_questions()
|
||||||
duration = quiz.duration
|
|
||||||
except:
|
except:
|
||||||
frappe.throw(_("Quiz {0} does not exist").format(quiz_name), frappe.DoesNotExistError)
|
frappe.throw(_("Quiz {0} does not exist").format(quiz_name), frappe.DoesNotExistError)
|
||||||
return None
|
return None
|
||||||
@ -236,7 +235,8 @@ def get_quiz(quiz_name, course):
|
|||||||
return {
|
return {
|
||||||
'questions': questions,
|
'questions': questions,
|
||||||
'activity': None,
|
'activity': None,
|
||||||
'duration':duration
|
'is_time_bound': quiz.is_time_bound,
|
||||||
|
'duration':quiz.duration
|
||||||
}
|
}
|
||||||
|
|
||||||
student = get_current_student()
|
student = get_current_student()
|
||||||
@ -245,6 +245,7 @@ def get_quiz(quiz_name, course):
|
|||||||
return {
|
return {
|
||||||
'questions': questions,
|
'questions': questions,
|
||||||
'activity': {'is_complete': status, 'score': score, 'result': result, 'time_taken': time_taken},
|
'activity': {'is_complete': status, 'score': score, 'result': result, 'time_taken': time_taken},
|
||||||
|
'is_time_bound': quiz.is_time_bound,
|
||||||
'duration': quiz.duration
|
'duration': quiz.duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import urllib
|
import urllib
|
||||||
|
from urllib.parse import quote
|
||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
import base64
|
import base64
|
||||||
@ -68,8 +69,9 @@ def calc_md5(string):
|
|||||||
"""
|
"""
|
||||||
md = hashlib.md5()
|
md = hashlib.md5()
|
||||||
md.update(string)
|
md.update(string)
|
||||||
return base64.encodestring(md.digest()).strip('\n') if six.PY2 \
|
return base64.encodebytes(md.digest()).decode().strip()
|
||||||
else base64.encodebytes(md.digest()).decode().strip()
|
|
||||||
|
|
||||||
|
|
||||||
def remove_empty(d):
|
def remove_empty(d):
|
||||||
"""
|
"""
|
||||||
@ -177,7 +179,6 @@ class MWS(object):
|
|||||||
'SignatureMethod': 'HmacSHA256',
|
'SignatureMethod': 'HmacSHA256',
|
||||||
}
|
}
|
||||||
params.update(extra_data)
|
params.update(extra_data)
|
||||||
quote = urllib.quote if six.PY2 else urllib.parse.quote
|
|
||||||
request_description = '&'.join(['%s=%s' % (k, quote(params[k], safe='-_.~')) for k in sorted(params)])
|
request_description = '&'.join(['%s=%s' % (k, quote(params[k], safe='-_.~')) for k in sorted(params)])
|
||||||
signature = self.calc_signature(method, request_description)
|
signature = self.calc_signature(method, request_description)
|
||||||
url = '%s%s?%s&Signature=%s' % (self.domain, self.uri, request_description, quote(signature))
|
url = '%s%s?%s&Signature=%s' % (self.domain, self.uri, request_description, quote(signature))
|
||||||
|
|||||||
@ -90,9 +90,9 @@ def add_bank_accounts(response, bank, company):
|
|||||||
"bank": bank["bank_name"],
|
"bank": bank["bank_name"],
|
||||||
"account": default_gl_account.account,
|
"account": default_gl_account.account,
|
||||||
"account_name": account["name"],
|
"account_name": account["name"],
|
||||||
"account_type": account["type"] or "",
|
"account_type": account.get("type", ""),
|
||||||
"account_subtype": account["subtype"] or "",
|
"account_subtype": account.get("subtype", ""),
|
||||||
"mask": account["mask"] or "",
|
"mask": account.get("mask", ""),
|
||||||
"integration_id": account["id"],
|
"integration_id": account["id"],
|
||||||
"is_company_account": 1,
|
"is_company_account": 1,
|
||||||
"company": company
|
"company": company
|
||||||
@ -183,11 +183,11 @@ def new_bank_transaction(transaction):
|
|||||||
bank_account = frappe.db.get_value("Bank Account", dict(integration_id=transaction["account_id"]))
|
bank_account = frappe.db.get_value("Bank Account", dict(integration_id=transaction["account_id"]))
|
||||||
|
|
||||||
if float(transaction["amount"]) >= 0:
|
if float(transaction["amount"]) >= 0:
|
||||||
debit = float(transaction["amount"])
|
|
||||||
credit = 0
|
|
||||||
else:
|
|
||||||
debit = 0
|
debit = 0
|
||||||
credit = abs(float(transaction["amount"]))
|
credit = float(transaction["amount"])
|
||||||
|
else:
|
||||||
|
debit = abs(float(transaction["amount"]))
|
||||||
|
credit = 0
|
||||||
|
|
||||||
status = "Pending" if transaction["pending"] == "True" else "Settled"
|
status = "Pending" if transaction["pending"] == "True" else "Settled"
|
||||||
|
|
||||||
|
|||||||
@ -33,13 +33,13 @@ class Patient(Document):
|
|||||||
self.reload() # self.notify_update()
|
self.reload() # self.notify_update()
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
|
if frappe.db.get_single_value('Healthcare Settings', 'link_customer_to_patient'):
|
||||||
if self.customer:
|
if self.customer:
|
||||||
customer = frappe.get_doc('Customer', self.customer)
|
customer = frappe.get_doc('Customer', self.customer)
|
||||||
if self.customer_group:
|
if self.customer_group:
|
||||||
customer.customer_group = self.customer_group
|
customer.customer_group = self.customer_group
|
||||||
if self.territory:
|
if self.territory:
|
||||||
customer.territory = self.territory
|
customer.territory = self.territory
|
||||||
|
|
||||||
customer.customer_name = self.patient_name
|
customer.customer_name = self.patient_name
|
||||||
customer.default_price_list = self.default_price_list
|
customer.default_price_list = self.default_price_list
|
||||||
customer.default_currency = self.default_currency
|
customer.default_currency = self.default_currency
|
||||||
@ -47,7 +47,6 @@ class Patient(Document):
|
|||||||
customer.ignore_mandatory = True
|
customer.ignore_mandatory = True
|
||||||
customer.save(ignore_permissions=True)
|
customer.save(ignore_permissions=True)
|
||||||
else:
|
else:
|
||||||
if frappe.db.get_single_value('Healthcare Settings', 'link_customer_to_patient'):
|
|
||||||
create_customer(self)
|
create_customer(self)
|
||||||
|
|
||||||
def set_full_name(self):
|
def set_full_name(self):
|
||||||
|
|||||||
@ -269,9 +269,11 @@ doc_events = {
|
|||||||
},
|
},
|
||||||
"Purchase Invoice": {
|
"Purchase Invoice": {
|
||||||
"validate": [
|
"validate": [
|
||||||
"erpnext.regional.india.utils.update_grand_total_for_rcm",
|
"erpnext.regional.india.utils.validate_reverse_charge_transaction",
|
||||||
|
"erpnext.regional.india.utils.update_itc_availed_fields",
|
||||||
"erpnext.regional.united_arab_emirates.utils.update_grand_total_for_rcm",
|
"erpnext.regional.united_arab_emirates.utils.update_grand_total_for_rcm",
|
||||||
"erpnext.regional.united_arab_emirates.utils.validate_returns"
|
"erpnext.regional.united_arab_emirates.utils.validate_returns",
|
||||||
|
"erpnext.regional.india.utils.update_taxable_values"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Payment Entry": {
|
"Payment Entry": {
|
||||||
@ -331,7 +333,9 @@ scheduler_events = {
|
|||||||
"erpnext.projects.doctype.project.project.collect_project_status",
|
"erpnext.projects.doctype.project.project.collect_project_status",
|
||||||
"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
|
"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
|
||||||
"erpnext.support.doctype.issue.issue.set_service_level_agreement_variance",
|
"erpnext.support.doctype.issue.issue.set_service_level_agreement_variance",
|
||||||
"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders",
|
"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders"
|
||||||
|
],
|
||||||
|
"hourly_long": [
|
||||||
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries"
|
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries"
|
||||||
],
|
],
|
||||||
"daily": [
|
"daily": [
|
||||||
@ -366,10 +370,8 @@ scheduler_events = {
|
|||||||
"erpnext.setup.doctype.email_digest.email_digest.send",
|
"erpnext.setup.doctype.email_digest.email_digest.send",
|
||||||
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms",
|
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms",
|
||||||
"erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation",
|
"erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation",
|
||||||
"erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.automatically_allocate_leaves_based_on_leave_policy",
|
|
||||||
"erpnext.hr.utils.generate_leave_encashment",
|
"erpnext.hr.utils.generate_leave_encashment",
|
||||||
"erpnext.hr.utils.allocate_earned_leaves",
|
"erpnext.hr.utils.allocate_earned_leaves",
|
||||||
"erpnext.hr.utils.grant_leaves_automatically",
|
|
||||||
"erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall",
|
"erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall",
|
||||||
"erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
|
"erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
|
||||||
"erpnext.crm.doctype.lead.lead.daily_open_lead"
|
"erpnext.crm.doctype.lead.lead.daily_open_lead"
|
||||||
@ -426,7 +428,6 @@ regional_overrides = {
|
|||||||
'erpnext.controllers.taxes_and_totals.get_regional_round_off_accounts': 'erpnext.regional.india.utils.get_regional_round_off_accounts',
|
'erpnext.controllers.taxes_and_totals.get_regional_round_off_accounts': 'erpnext.regional.india.utils.get_regional_round_off_accounts',
|
||||||
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
|
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
|
||||||
'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
|
'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
|
||||||
'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries',
|
|
||||||
'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields',
|
'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields',
|
||||||
'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount'
|
'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount'
|
||||||
},
|
},
|
||||||
|
|||||||
@ -11,8 +11,12 @@ def get_data():
|
|||||||
},
|
},
|
||||||
'transactions': [
|
'transactions': [
|
||||||
{
|
{
|
||||||
'label': _('Leave and Attendance'),
|
'label': _('Attendance'),
|
||||||
'items': ['Attendance', 'Attendance Request', 'Leave Application', 'Leave Allocation', 'Employee Checkin']
|
'items': ['Attendance', 'Attendance Request', 'Employee Checkin']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Leave'),
|
||||||
|
'items': ['Leave Application', 'Leave Allocation', 'Leave Policy Assignment']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'label': _('Lifecycle'),
|
'label': _('Lifecycle'),
|
||||||
@ -30,10 +34,6 @@ def get_data():
|
|||||||
'label': _('Benefit'),
|
'label': _('Benefit'),
|
||||||
'items': ['Employee Benefit Application', 'Employee Benefit Claim']
|
'items': ['Employee Benefit Application', 'Employee Benefit Claim']
|
||||||
},
|
},
|
||||||
{
|
|
||||||
'label': _('Evaluation'),
|
|
||||||
'items': ['Appraisal']
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
'label': _('Payroll'),
|
'label': _('Payroll'),
|
||||||
'items': ['Salary Structure Assignment', 'Salary Slip', 'Additional Salary', 'Timesheet','Employee Incentive', 'Retention Bonus', 'Bank Account']
|
'items': ['Salary Structure Assignment', 'Salary Slip', 'Additional Salary', 'Timesheet','Employee Incentive', 'Retention Bonus', 'Bank Account']
|
||||||
@ -42,5 +42,9 @@ def get_data():
|
|||||||
'label': _('Training'),
|
'label': _('Training'),
|
||||||
'items': ['Training Event', 'Training Result', 'Training Feedback', 'Employee Skill Map']
|
'items': ['Training Event', 'Training Result', 'Training Feedback', 'Employee Skill Map']
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'label': _('Evaluation'),
|
||||||
|
'items': ['Appraisal']
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,6 @@
|
|||||||
"show_leaves_of_all_department_members_in_calendar",
|
"show_leaves_of_all_department_members_in_calendar",
|
||||||
"auto_leave_encashment",
|
"auto_leave_encashment",
|
||||||
"restrict_backdated_leave_application",
|
"restrict_backdated_leave_application",
|
||||||
"automatically_allocate_leaves_based_on_leave_policy",
|
|
||||||
"hiring_settings",
|
"hiring_settings",
|
||||||
"check_vacancies"
|
"check_vacancies"
|
||||||
],
|
],
|
||||||
@ -133,12 +132,6 @@
|
|||||||
"label": "Role Allowed to Create Backdated Leave Application",
|
"label": "Role Allowed to Create Backdated Leave Application",
|
||||||
"options": "Role"
|
"options": "Role"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "automatically_allocate_leaves_based_on_leave_policy",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Automatically Allocate Leaves Based On Leave Policy"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"default": "1",
|
"default": "1",
|
||||||
"fieldname": "send_leave_notification",
|
"fieldname": "send_leave_notification",
|
||||||
@ -155,7 +148,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-04-26 10:52:56.192773",
|
"modified": "2021-05-11 10:52:56.192773",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "HR Settings",
|
"name": "HR Settings",
|
||||||
|
|||||||
@ -446,8 +446,6 @@ class TestLeaveApplication(unittest.TestCase):
|
|||||||
|
|
||||||
leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
|
leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
|
||||||
|
|
||||||
frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]).grant_leave_alloc_for_employee()
|
|
||||||
|
|
||||||
from erpnext.hr.utils import allocate_earned_leaves
|
from erpnext.hr.utils import allocate_earned_leaves
|
||||||
i = 0
|
i = 0
|
||||||
while(i<14):
|
while(i<14):
|
||||||
|
|||||||
@ -44,10 +44,6 @@ class TestLeaveEncashment(unittest.TestCase):
|
|||||||
salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", self.employee,
|
salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", self.employee,
|
||||||
other_details={"leave_encashment_amount_per_day": 50})
|
other_details={"leave_encashment_amount_per_day": 50})
|
||||||
|
|
||||||
#grant Leaves
|
|
||||||
frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]).grant_leave_alloc_for_employee()
|
|
||||||
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
for dt in ["Leave Period", "Leave Allocation", "Leave Ledger Entry", "Additional Salary", "Leave Encashment", "Salary Structure", "Leave Policy"]:
|
for dt in ["Leave Period", "Leave Allocation", "Leave Ledger Entry", "Additional Salary", "Leave Encashment", "Salary Structure", "Leave Policy"]:
|
||||||
frappe.db.sql("delete from `tab%s`" % dt)
|
frappe.db.sql("delete from `tab%s`" % dt)
|
||||||
|
|||||||
@ -7,7 +7,7 @@ def get_data():
|
|||||||
'transactions': [
|
'transactions': [
|
||||||
{
|
{
|
||||||
'label': _('Leaves'),
|
'label': _('Leaves'),
|
||||||
'items': ['Leave Allocation']
|
'items': ['Leave Policy Assignment', 'Leave Allocation']
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -4,35 +4,22 @@
|
|||||||
frappe.ui.form.on('Leave Policy Assignment', {
|
frappe.ui.form.on('Leave Policy Assignment', {
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
|
frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
|
||||||
},
|
|
||||||
|
|
||||||
refresh: function(frm) {
|
frm.set_query('leave_policy', function() {
|
||||||
if (frm.doc.docstatus === 1 && frm.doc.leaves_allocated === 0) {
|
return {
|
||||||
frm.add_custom_button(__("Grant Leave"), function() {
|
filters: {
|
||||||
|
"docstatus": 1
|
||||||
frappe.call({
|
|
||||||
doc: frm.doc,
|
|
||||||
method: "grant_leave_alloc_for_employee",
|
|
||||||
callback: function(r) {
|
|
||||||
let leave_allocations = r.message;
|
|
||||||
let msg = frm.events.get_success_message(leave_allocations);
|
|
||||||
frappe.msgprint(msg);
|
|
||||||
cur_frm.refresh();
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
frm.set_query('leave_period', function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
"is_active": 1,
|
||||||
|
"company": frm.doc.company
|
||||||
|
}
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
get_success_message: function(leave_allocations) {
|
|
||||||
let msg = __("Leaves has been granted successfully");
|
|
||||||
msg += "<br><table class='table table-bordered'>";
|
|
||||||
msg += "<tr><th>"+__('Leave Type')+"</th><th>"+__("Leave Allocation")+"</th><th>"+__("Leaves Granted")+"</th><tr>";
|
|
||||||
for (let key in leave_allocations) {
|
|
||||||
msg += "<tr><th>"+key+"</th><td>"+leave_allocations[key]["name"]+"</td><td>"+leave_allocations[key]["leaves"]+"</td></tr>";
|
|
||||||
}
|
|
||||||
msg += "</table>";
|
|
||||||
return msg;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
assignment_based_on: function(frm) {
|
assignment_based_on: function(frm) {
|
||||||
|
|||||||
@ -17,6 +17,9 @@ class LeavePolicyAssignment(Document):
|
|||||||
self.validate_policy_assignment_overlap()
|
self.validate_policy_assignment_overlap()
|
||||||
self.set_dates()
|
self.set_dates()
|
||||||
|
|
||||||
|
def on_submit(self):
|
||||||
|
self.grant_leave_alloc_for_employee()
|
||||||
|
|
||||||
def set_dates(self):
|
def set_dates(self):
|
||||||
if self.assignment_based_on == "Leave Period":
|
if self.assignment_based_on == "Leave Period":
|
||||||
self.effective_from, self.effective_to = frappe.db.get_value("Leave Period", self.leave_period, ["from_date", "to_date"])
|
self.effective_from, self.effective_to = frappe.db.get_value("Leave Period", self.leave_period, ["from_date", "to_date"])
|
||||||
@ -75,7 +78,7 @@ class LeavePolicyAssignment(Document):
|
|||||||
from_date=self.effective_from,
|
from_date=self.effective_from,
|
||||||
to_date=self.effective_to,
|
to_date=self.effective_to,
|
||||||
new_leaves_allocated=new_leaves_allocated,
|
new_leaves_allocated=new_leaves_allocated,
|
||||||
leave_period=self.leave_period or None,
|
leave_period=self.leave_period if self.assignment_based_on == "Leave Policy" else '',
|
||||||
leave_policy_assignment = self.name,
|
leave_policy_assignment = self.name,
|
||||||
leave_policy = self.leave_policy,
|
leave_policy = self.leave_policy,
|
||||||
carry_forward=carry_forward
|
carry_forward=carry_forward
|
||||||
@ -131,22 +134,6 @@ class LeavePolicyAssignment(Document):
|
|||||||
return new_leaves_allocated
|
return new_leaves_allocated
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def grant_leave_for_multiple_employees(leave_policy_assignments):
|
|
||||||
leave_policy_assignments = json.loads(leave_policy_assignments)
|
|
||||||
not_granted = []
|
|
||||||
for assignment in leave_policy_assignments:
|
|
||||||
try:
|
|
||||||
frappe.get_doc("Leave Policy Assignment", assignment).grant_leave_alloc_for_employee()
|
|
||||||
except Exception:
|
|
||||||
not_granted.append(assignment)
|
|
||||||
|
|
||||||
if len(not_granted):
|
|
||||||
msg = _("Leave not Granted for Assignments:")+ bold(comma_and(not_granted)) + _(". Please Check documents")
|
|
||||||
else:
|
|
||||||
msg = _("Leave granted Successfully")
|
|
||||||
frappe.msgprint(msg)
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_assignment_for_multiple_employees(employees, data):
|
def create_assignment_for_multiple_employees(employees, data):
|
||||||
|
|
||||||
@ -166,29 +153,18 @@ def create_assignment_for_multiple_employees(employees, data):
|
|||||||
assignment.effective_to = getdate(data.effective_to) or None
|
assignment.effective_to = getdate(data.effective_to) or None
|
||||||
assignment.leave_period = data.leave_period or None
|
assignment.leave_period = data.leave_period or None
|
||||||
assignment.carry_forward = data.carry_forward
|
assignment.carry_forward = data.carry_forward
|
||||||
|
|
||||||
assignment.save()
|
assignment.save()
|
||||||
|
try:
|
||||||
assignment.submit()
|
assignment.submit()
|
||||||
|
except frappe.exceptions.ValidationError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
docs_name.append(assignment.name)
|
docs_name.append(assignment.name)
|
||||||
|
|
||||||
return docs_name
|
return docs_name
|
||||||
|
|
||||||
|
|
||||||
def automatically_allocate_leaves_based_on_leave_policy():
|
|
||||||
today = getdate()
|
|
||||||
automatically_allocate_leaves_based_on_leave_policy = frappe.db.get_single_value(
|
|
||||||
'HR Settings', 'automatically_allocate_leaves_based_on_leave_policy'
|
|
||||||
)
|
|
||||||
|
|
||||||
pending_assignments = frappe.get_list(
|
|
||||||
"Leave Policy Assignment",
|
|
||||||
filters = {"docstatus": 1, "leaves_allocated": 0, "effective_from": today}
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(pending_assignments) and automatically_allocate_leaves_based_on_leave_policy:
|
|
||||||
for assignment in pending_assignments:
|
|
||||||
frappe.get_doc("Leave Policy Assignment", assignment.name).grant_leave_alloc_for_employee()
|
|
||||||
|
|
||||||
|
|
||||||
def get_leave_type_details():
|
def get_leave_type_details():
|
||||||
leave_type_details = frappe._dict()
|
leave_type_details = frappe._dict()
|
||||||
leave_types = frappe.get_all("Leave Type",
|
leave_types = frappe.get_all("Leave Type",
|
||||||
@ -197,4 +173,3 @@ def get_leave_type_details():
|
|||||||
for d in leave_types:
|
for d in leave_types:
|
||||||
leave_type_details.setdefault(d.name, d)
|
leave_type_details.setdefault(d.name, d)
|
||||||
return leave_type_details
|
return leave_type_details
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
def get_data():
|
||||||
|
return {
|
||||||
|
'fieldname': 'leave_policy_assignment',
|
||||||
|
'transactions': [
|
||||||
|
{
|
||||||
|
'label': _('Leaves'),
|
||||||
|
'items': ['Leave Allocation']
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@ frappe.listview_settings['Leave Policy Assignment'] = {
|
|||||||
doctype: "Employee",
|
doctype: "Employee",
|
||||||
target: cur_list,
|
target: cur_list,
|
||||||
setters: {
|
setters: {
|
||||||
|
employee_name: '',
|
||||||
company: '',
|
company: '',
|
||||||
department: '',
|
department: '',
|
||||||
},
|
},
|
||||||
@ -92,37 +93,6 @@ frappe.listview_settings['Leave Policy Assignment'] = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
list_view.page.add_inner_button(__("Grant Leaves"), function () {
|
|
||||||
me.dialog = new frappe.ui.form.MultiSelectDialog({
|
|
||||||
doctype: "Leave Policy Assignment",
|
|
||||||
target: cur_list,
|
|
||||||
setters: {
|
|
||||||
company: '',
|
|
||||||
employee: '',
|
|
||||||
},
|
|
||||||
get_query() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
docstatus: ['=', 1],
|
|
||||||
leaves_allocated: ['=', 0]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
add_filters_group: 1,
|
|
||||||
primary_action_label: "Grant Leaves",
|
|
||||||
action(leave_policy_assignments) {
|
|
||||||
frappe.call({
|
|
||||||
method: 'erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.grant_leave_for_multiple_employees',
|
|
||||||
async: false,
|
|
||||||
args: {
|
|
||||||
leave_policy_assignments: leave_policy_assignments
|
|
||||||
}
|
|
||||||
});
|
|
||||||
me.dialog.hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
set_effective_date: function () {
|
set_effective_date: function () {
|
||||||
|
|||||||
@ -35,7 +35,6 @@ class TestLeavePolicyAssignment(unittest.TestCase):
|
|||||||
leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
|
leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
|
||||||
|
|
||||||
leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
|
leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
|
||||||
leave_policy_assignment_doc.grant_leave_alloc_for_employee()
|
|
||||||
leave_policy_assignment_doc.reload()
|
leave_policy_assignment_doc.reload()
|
||||||
|
|
||||||
self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 1)
|
self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 1)
|
||||||
@ -73,7 +72,6 @@ class TestLeavePolicyAssignment(unittest.TestCase):
|
|||||||
leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
|
leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
|
||||||
|
|
||||||
leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
|
leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
|
||||||
leave_policy_assignment_doc.grant_leave_alloc_for_employee()
|
|
||||||
leave_policy_assignment_doc.reload()
|
leave_policy_assignment_doc.reload()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -10,15 +10,33 @@ frappe.ui.form.on('Training Event', {
|
|||||||
frm.add_custom_button(__("Training Result"), function () {
|
frm.add_custom_button(__("Training Result"), function () {
|
||||||
frappe.route_options = {
|
frappe.route_options = {
|
||||||
training_event: frm.doc.name
|
training_event: frm.doc.name
|
||||||
}
|
};
|
||||||
frappe.set_route("List", "Training Result");
|
frappe.set_route("List", "Training Result");
|
||||||
});
|
});
|
||||||
frm.add_custom_button(__("Training Feedback"), function () {
|
frm.add_custom_button(__("Training Feedback"), function () {
|
||||||
frappe.route_options = {
|
frappe.route_options = {
|
||||||
training_event: frm.doc.name
|
training_event: frm.doc.name
|
||||||
}
|
};
|
||||||
frappe.set_route("List", "Training Feedback");
|
frappe.set_route("List", "Training Feedback");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on("Training Event Employee", {
|
||||||
|
employee: function (frm) {
|
||||||
|
let emp = [];
|
||||||
|
for (let d in frm.doc.employees) {
|
||||||
|
if (frm.doc.employees[d].employee) {
|
||||||
|
emp.push(frm.doc.employees[d].employee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frm.set_query("employee", "employees", function () {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
name: ["NOT IN", emp]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user