mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-06 21:59:13 +00:00
Merge branch 'develop' into asset-credit-note
This commit is contained in:
1
.flake8
1
.flake8
@@ -30,3 +30,4 @@ ignore =
|
|||||||
W191,
|
W191,
|
||||||
|
|
||||||
max-line-length = 200
|
max-line-length = 200
|
||||||
|
exclude=.github/helper/semgrep_rules
|
||||||
|
|||||||
@@ -10,3 +10,6 @@
|
|||||||
|
|
||||||
# Replace use of Class.extend with native JS class
|
# Replace use of Class.extend with native JS class
|
||||||
1fe891b287a1b3f225d29ee3d07e7b1824aba9e7
|
1fe891b287a1b3f225d29ee3d07e7b1824aba9e7
|
||||||
|
|
||||||
|
# This commit just changes spaces to tabs for indentation in some files
|
||||||
|
5f473611bd6ed57703716244a054d3fb5ba9cd23
|
||||||
|
|||||||
1
.github/helper/install.sh
vendored
1
.github/helper/install.sh
vendored
@@ -44,3 +44,4 @@ sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
|
|||||||
bench get-app erpnext "${GITHUB_WORKSPACE}"
|
bench get-app erpnext "${GITHUB_WORKSPACE}"
|
||||||
bench start &
|
bench start &
|
||||||
bench --site test_site reinstall --yes
|
bench --site test_site reinstall --yes
|
||||||
|
bench build --app frappe
|
||||||
|
|||||||
@@ -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, ...):
|
||||||
...
|
...
|
||||||
- metavariable-regex:
|
self.$ANOTHER_METHOD()
|
||||||
metavariable: '$ATTR'
|
...
|
||||||
regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$'
|
|
||||||
|
def $ANOTHER_METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ATTR = ...
|
||||||
|
- pattern-not: |
|
||||||
|
class $DOCTYPE(...):
|
||||||
|
def $METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ANOTHER_METHOD()
|
||||||
|
...
|
||||||
|
|
||||||
|
def $ANOTHER_METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ATTR = ...
|
||||||
|
...
|
||||||
|
self.db_set(..., self.$ATTR, ...)
|
||||||
|
- pattern-not: |
|
||||||
|
class $DOCTYPE(...):
|
||||||
|
def $METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ANOTHER_METHOD()
|
||||||
|
...
|
||||||
|
|
||||||
|
def $ANOTHER_METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ATTR = $SOME_VAR
|
||||||
|
...
|
||||||
|
self.db_set(..., $SOME_VAR, ...)
|
||||||
|
- pattern-not: |
|
||||||
|
class $DOCTYPE(...):
|
||||||
|
def $METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ANOTHER_METHOD()
|
||||||
|
...
|
||||||
|
self.save()
|
||||||
|
def $ANOTHER_METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ATTR = ...
|
||||||
|
- metavariable-regex:
|
||||||
|
metavariable: "$METHOD"
|
||||||
|
regex: "(on_submit|on_cancel)"
|
||||||
message: |
|
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.5.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,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class AccountingDimension(Document):
|
|||||||
exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name'])
|
exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name'])
|
||||||
|
|
||||||
if exists and self.is_new():
|
if exists and self.is_new():
|
||||||
frappe.throw("Document Type already used as a dimension")
|
frappe.throw(_("Document Type already used as a dimension"))
|
||||||
|
|
||||||
if not self.is_new():
|
if not self.is_new():
|
||||||
self.validate_document_type_change()
|
self.validate_document_type_change()
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"delete_linked_ledger_entries",
|
"delete_linked_ledger_entries",
|
||||||
"book_asset_depreciation_entry_automatically",
|
"book_asset_depreciation_entry_automatically",
|
||||||
"unlink_advance_payment_on_cancelation_of_order",
|
"unlink_advance_payment_on_cancelation_of_order",
|
||||||
|
"post_change_gl_entries",
|
||||||
"tax_settings_section",
|
"tax_settings_section",
|
||||||
"determine_address_tax_category_from",
|
"determine_address_tax_category_from",
|
||||||
"column_break_19",
|
"column_break_19",
|
||||||
@@ -253,6 +254,13 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_19",
|
"fieldname": "column_break_19",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"description": "If enabled, ledger entries will be posted for change amount in POS transactions",
|
||||||
|
"fieldname": "post_change_gl_entries",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Create Ledger Entries for Change Amount"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@@ -260,7 +268,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-04-30 15:25:10.381008",
|
"modified": "2021-06-17 20:26:03.721202",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import _
|
||||||
from frappe.utils import cint
|
from frappe.utils import cint
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||||
@@ -24,7 +25,7 @@ class AccountsSettings(Document):
|
|||||||
def validate_stale_days(self):
|
def validate_stale_days(self):
|
||||||
if not self.allow_stale and cint(self.stale_days) <= 0:
|
if not self.allow_stale and cint(self.stale_days) <= 0:
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
"Stale Days should start from 1.", title='Error', indicator='red',
|
_("Stale Days should start from 1."), title='Error', indicator='red',
|
||||||
raise_exception=1)
|
raise_exception=1)
|
||||||
|
|
||||||
def enable_payment_schedule_in_print(self):
|
def enable_payment_schedule_in_print(self):
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-09-12 22:26:19.594367",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"document_type": "Setup",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"add_deduct_tax",
|
||||||
|
"charge_type",
|
||||||
|
"row_id",
|
||||||
|
"account_head",
|
||||||
|
"col_break_1",
|
||||||
|
"description",
|
||||||
|
"included_in_paid_amount",
|
||||||
|
"accounting_dimensions_section",
|
||||||
|
"cost_center",
|
||||||
|
"dimension_col_break",
|
||||||
|
"section_break_8",
|
||||||
|
"rate",
|
||||||
|
"section_break_9",
|
||||||
|
"currency",
|
||||||
|
"tax_amount",
|
||||||
|
"total",
|
||||||
|
"allocated_amount",
|
||||||
|
"column_break_13",
|
||||||
|
"base_tax_amount",
|
||||||
|
"base_total",
|
||||||
|
"base_allocated_amount"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "charge_type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Type",
|
||||||
|
"oldfieldname": "charge_type",
|
||||||
|
"oldfieldtype": "Select",
|
||||||
|
"options": "\nActual\nOn Paid Amount\nOn Previous Row Amount\nOn Previous Row Total",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:[\"On Previous Row Amount\", \"On Previous Row Total\"].indexOf(doc.charge_type)!==-1",
|
||||||
|
"fieldname": "row_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Reference Row #",
|
||||||
|
"oldfieldname": "row_id",
|
||||||
|
"oldfieldtype": "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "account_head",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Account Head",
|
||||||
|
"oldfieldname": "account_head",
|
||||||
|
"oldfieldtype": "Link",
|
||||||
|
"options": "Account",
|
||||||
|
"reqd": 1,
|
||||||
|
"search_index": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "col_break_1",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"width": "50%"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "description",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Description",
|
||||||
|
"oldfieldname": "description",
|
||||||
|
"oldfieldtype": "Small Text",
|
||||||
|
"print_width": "300px",
|
||||||
|
"reqd": 1,
|
||||||
|
"width": "300px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "accounting_dimensions_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Accounting Dimensions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": ":Company",
|
||||||
|
"fieldname": "cost_center",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Cost Center",
|
||||||
|
"oldfieldname": "cost_center_other_charges",
|
||||||
|
"oldfieldtype": "Link",
|
||||||
|
"options": "Cost Center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "dimension_col_break",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_8",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "rate",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Rate",
|
||||||
|
"oldfieldname": "rate",
|
||||||
|
"oldfieldtype": "Currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_9",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "tax_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Amount",
|
||||||
|
"options": "currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "total",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Total",
|
||||||
|
"options": "currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_13",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "base_tax_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Amount (Company Currency)",
|
||||||
|
"oldfieldname": "tax_amount",
|
||||||
|
"oldfieldtype": "Currency",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "base_total",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Total (Company Currency)",
|
||||||
|
"oldfieldname": "total",
|
||||||
|
"oldfieldtype": "Currency",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "add_deduct_tax",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Add Or Deduct",
|
||||||
|
"options": "Add\nDeduct",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "included_in_paid_amount",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Considered In Paid Amount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "allocated_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Allocated Amount",
|
||||||
|
"options": "currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "base_allocated_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Allocated Amount (Company Currency)",
|
||||||
|
"options": "Company:company:default_currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "account_head.account_currency",
|
||||||
|
"fieldname": "currency",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Account Currency",
|
||||||
|
"options": "Currency",
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-06-09 11:46:58.373170",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Advance Taxes and Charges",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "ASC"
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class AdvanceTaxesandCharges(Document):
|
||||||
|
pass
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ def validate_company(company):
|
|||||||
'allow_account_creation_against_child_company'])
|
'allow_account_creation_against_child_company'])
|
||||||
|
|
||||||
if parent_company and (not allow_account_creation_against_child_company):
|
if parent_company and (not allow_account_creation_against_child_company):
|
||||||
msg = _("{} is a child company. ").format(frappe.bold(company))
|
msg = _("{} is a child company.").format(frappe.bold(company)) + " "
|
||||||
msg += _("Please import accounts against parent company or enable {} in company master.").format(
|
msg += _("Please import accounts against parent company or enable {} in company master.").format(
|
||||||
frappe.bold('Allow Account Creation Against Child Company'))
|
frappe.bold('Allow Account Creation Against Child Company'))
|
||||||
frappe.throw(msg, title=_('Wrong Company'))
|
frappe.throw(msg, title=_('Wrong Company'))
|
||||||
@@ -56,7 +56,7 @@ def get_file(file_name):
|
|||||||
extension = extension.lstrip(".")
|
extension = extension.lstrip(".")
|
||||||
|
|
||||||
if extension not in ('csv', 'xlsx', 'xls'):
|
if extension not in ('csv', 'xlsx', 'xls'):
|
||||||
frappe.throw("Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload")
|
frappe.throw(_("Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload"))
|
||||||
|
|
||||||
return file_doc, extension
|
return file_doc, extension
|
||||||
|
|
||||||
@@ -293,7 +293,7 @@ def validate_accounts(file_name):
|
|||||||
accounts_dict = {}
|
accounts_dict = {}
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
accounts_dict.setdefault(account["account_name"], account)
|
accounts_dict.setdefault(account["account_name"], account)
|
||||||
if not hasattr(account, "parent_account"):
|
if "parent_account" not in account:
|
||||||
msg = _("Please make sure the file you are using has 'Parent Account' column present in the header.")
|
msg = _("Please make sure the file you are using has 'Parent Account' column present in the header.")
|
||||||
msg += "<br><br>"
|
msg += "<br><br>"
|
||||||
msg += _("Alternatively, you can download the template and fill your data in.")
|
msg += _("Alternatively, you can download the template and fill your data in.")
|
||||||
|
|||||||
@@ -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,18 +42,18 @@ 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()
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.set_status()
|
self.set_status(cancel=1)
|
||||||
self.update_sales_invoice()
|
self.update_sales_invoice()
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
|
||||||
def set_status(self, status=None):
|
def set_status(self, status=None, cancel=0):
|
||||||
if status:
|
if status:
|
||||||
self.status = status
|
self.status = status
|
||||||
self.db_set("status", status)
|
self.db_set("status", status)
|
||||||
@@ -66,6 +66,9 @@ class InvoiceDiscounting(AccountsController):
|
|||||||
elif self.docstatus == 2:
|
elif self.docstatus == 2:
|
||||||
self.status = "Cancelled"
|
self.status = "Cancelled"
|
||||||
|
|
||||||
|
if cancel:
|
||||||
|
self.db_set('status', self.status, update_modified = True)
|
||||||
|
|
||||||
def update_sales_invoice(self):
|
def update_sales_invoice(self):
|
||||||
for d in self.invoices:
|
for d in self.invoices:
|
||||||
if self.docstatus == 1:
|
if self.docstatus == 1:
|
||||||
|
|||||||
@@ -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()
|
||||||
self.validate_total_debit_and_credit()
|
|
||||||
|
# Do not validate while importing via data import
|
||||||
|
if not frappe.flags.in_import:
|
||||||
|
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%") + \
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
{% include "erpnext/public/js/controllers/accounts.js" %}
|
{% include "erpnext/public/js/controllers/accounts.js" %}
|
||||||
frappe.provide("erpnext.accounts.dimensions");
|
frappe.provide("erpnext.accounts.dimensions");
|
||||||
|
|
||||||
|
cur_frm.cscript.tax_table = "Advance Taxes and Charges";
|
||||||
|
|
||||||
frappe.ui.form.on('Payment Entry', {
|
frappe.ui.form.on('Payment Entry', {
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
if(frm.doc.__islocal) {
|
if(frm.doc.__islocal) {
|
||||||
@@ -91,6 +93,16 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query("advance_tax_account", function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
"company": frm.doc.company,
|
||||||
|
"root_type": ["in", ["Asset", "Liability"]],
|
||||||
|
"is_group": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
frm.set_query("reference_doctype", "references", function() {
|
frm.set_query("reference_doctype", "references", function() {
|
||||||
if (frm.doc.party_type == "Customer") {
|
if (frm.doc.party_type == "Customer") {
|
||||||
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
|
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
|
||||||
@@ -182,6 +194,8 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency));
|
frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency));
|
||||||
|
|
||||||
frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency);
|
frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency);
|
||||||
|
frm.toggle_display("base_total_taxes_and_charges", frm.doc.total_taxes_and_charges &&
|
||||||
|
(frm.doc.paid_from_account_currency != company_currency));
|
||||||
|
|
||||||
frm.toggle_display("base_received_amount", (
|
frm.toggle_display("base_received_amount", (
|
||||||
frm.doc.paid_to_account_currency != company_currency
|
frm.doc.paid_to_account_currency != company_currency
|
||||||
@@ -216,7 +230,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
var company_currency = frm.doc.company? frappe.get_doc(":Company", frm.doc.company).default_currency: "";
|
var company_currency = frm.doc.company? frappe.get_doc(":Company", frm.doc.company).default_currency: "";
|
||||||
|
|
||||||
frm.set_currency_labels(["base_paid_amount", "base_received_amount", "base_total_allocated_amount",
|
frm.set_currency_labels(["base_paid_amount", "base_received_amount", "base_total_allocated_amount",
|
||||||
"difference_amount"], company_currency);
|
"difference_amount", "base_paid_amount_after_tax", "base_received_amount_after_tax"], company_currency);
|
||||||
|
|
||||||
frm.set_currency_labels(["paid_amount"], frm.doc.paid_from_account_currency);
|
frm.set_currency_labels(["paid_amount"], frm.doc.paid_from_account_currency);
|
||||||
frm.set_currency_labels(["received_amount"], frm.doc.paid_to_account_currency);
|
frm.set_currency_labels(["received_amount"], frm.doc.paid_to_account_currency);
|
||||||
@@ -224,11 +238,13 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
var party_account_currency = frm.doc.payment_type=="Receive" ?
|
var party_account_currency = frm.doc.payment_type=="Receive" ?
|
||||||
frm.doc.paid_from_account_currency : frm.doc.paid_to_account_currency;
|
frm.doc.paid_from_account_currency : frm.doc.paid_to_account_currency;
|
||||||
|
|
||||||
frm.set_currency_labels(["total_allocated_amount", "unallocated_amount"], party_account_currency);
|
frm.set_currency_labels(["total_allocated_amount", "unallocated_amount",
|
||||||
|
"total_taxes_and_charges"], party_account_currency);
|
||||||
|
|
||||||
var currency_field = (frm.doc.payment_type=="Receive") ? "paid_from_account_currency" : "paid_to_account_currency"
|
var currency_field = (frm.doc.payment_type=="Receive") ? "paid_from_account_currency" : "paid_to_account_currency"
|
||||||
frm.set_df_property("total_allocated_amount", "options", currency_field);
|
frm.set_df_property("total_allocated_amount", "options", currency_field);
|
||||||
frm.set_df_property("unallocated_amount", "options", currency_field);
|
frm.set_df_property("unallocated_amount", "options", currency_field);
|
||||||
|
frm.set_df_property("total_taxes_and_charges", "options", currency_field);
|
||||||
frm.set_df_property("party_balance", "options", currency_field);
|
frm.set_df_property("party_balance", "options", currency_field);
|
||||||
|
|
||||||
frm.set_currency_labels(["total_amount", "outstanding_amount", "allocated_amount"],
|
frm.set_currency_labels(["total_amount", "outstanding_amount", "allocated_amount"],
|
||||||
@@ -364,6 +380,16 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
apply_tax_withholding_amount: function(frm) {
|
||||||
|
if (!frm.doc.apply_tax_withholding_amount) {
|
||||||
|
frm.set_value("tax_withholding_category", '');
|
||||||
|
} else {
|
||||||
|
frappe.db.get_value('Supplier', frm.doc.party, 'tax_withholding_category', (values) => {
|
||||||
|
frm.set_value("tax_withholding_category", values.tax_withholding_category);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
paid_from: function(frm) {
|
paid_from: function(frm) {
|
||||||
if(frm.set_party_account_based_on_party) return;
|
if(frm.set_party_account_based_on_party) return;
|
||||||
|
|
||||||
@@ -843,12 +869,12 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
if(frm.doc.payment_type == "Receive"
|
if(frm.doc.payment_type == "Receive"
|
||||||
&& frm.doc.base_total_allocated_amount < frm.doc.base_received_amount + total_deductions
|
&& frm.doc.base_total_allocated_amount < frm.doc.base_received_amount + total_deductions
|
||||||
&& frm.doc.total_allocated_amount < frm.doc.paid_amount + (total_deductions / frm.doc.source_exchange_rate)) {
|
&& frm.doc.total_allocated_amount < frm.doc.paid_amount + (total_deductions / frm.doc.source_exchange_rate)) {
|
||||||
unallocated_amount = (frm.doc.base_received_amount + total_deductions
|
unallocated_amount = (frm.doc.base_received_amount + total_deductions + frm.doc.base_total_taxes_and_charges
|
||||||
- frm.doc.base_total_allocated_amount) / frm.doc.source_exchange_rate;
|
+ frm.doc.base_total_allocated_amount) / frm.doc.source_exchange_rate;
|
||||||
} else if (frm.doc.payment_type == "Pay"
|
} else if (frm.doc.payment_type == "Pay"
|
||||||
&& frm.doc.base_total_allocated_amount < frm.doc.base_paid_amount - total_deductions
|
&& frm.doc.base_total_allocated_amount < frm.doc.base_paid_amount - total_deductions
|
||||||
&& frm.doc.total_allocated_amount < frm.doc.received_amount + (total_deductions / frm.doc.target_exchange_rate)) {
|
&& frm.doc.total_allocated_amount < frm.doc.received_amount + (total_deductions / frm.doc.target_exchange_rate)) {
|
||||||
unallocated_amount = (frm.doc.base_paid_amount - (total_deductions
|
unallocated_amount = (frm.doc.base_paid_amount + frm.doc.base_total_taxes_and_charges - (total_deductions
|
||||||
+ frm.doc.base_total_allocated_amount)) / frm.doc.target_exchange_rate;
|
+ frm.doc.base_total_allocated_amount)) / frm.doc.target_exchange_rate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -874,7 +900,8 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
|
var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
|
||||||
function(d) { return flt(d.amount) }));
|
function(d) { return flt(d.amount) }));
|
||||||
|
|
||||||
frm.set_value("difference_amount", difference_amount - total_deductions);
|
frm.set_value("difference_amount", difference_amount - total_deductions +
|
||||||
|
frm.doc.base_total_taxes_and_charges);
|
||||||
|
|
||||||
frm.events.hide_unhide_fields(frm);
|
frm.events.hide_unhide_fields(frm);
|
||||||
},
|
},
|
||||||
@@ -1002,7 +1029,266 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
|
sales_taxes_and_charges_template: function(frm) {
|
||||||
|
frm.trigger('fetch_taxes_from_template');
|
||||||
|
},
|
||||||
|
|
||||||
|
purchase_taxes_and_charges_template: function(frm) {
|
||||||
|
frm.trigger('fetch_taxes_from_template');
|
||||||
|
},
|
||||||
|
|
||||||
|
fetch_taxes_from_template: function(frm) {
|
||||||
|
let master_doctype = '';
|
||||||
|
let taxes_and_charges = '';
|
||||||
|
|
||||||
|
if (frm.doc.party_type == 'Supplier') {
|
||||||
|
master_doctype = 'Purchase Taxes and Charges Template';
|
||||||
|
taxes_and_charges = frm.doc.purchase_taxes_and_charges_template;
|
||||||
|
} else if (frm.doc.party_type == 'Customer') {
|
||||||
|
master_doctype = 'Sales Taxes and Charges Template';
|
||||||
|
taxes_and_charges = frm.doc.sales_taxes_and_charges_template;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!taxes_and_charges) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.controllers.accounts_controller.get_taxes_and_charges",
|
||||||
|
args: {
|
||||||
|
"master_doctype": master_doctype,
|
||||||
|
"master_name": taxes_and_charges
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if(!r.exc && r.message) {
|
||||||
|
// set taxes table
|
||||||
|
if(r.message) {
|
||||||
|
for (let tax of r.message) {
|
||||||
|
if (tax.charge_type === 'On Net Total') {
|
||||||
|
tax.charge_type = 'On Paid Amount';
|
||||||
|
}
|
||||||
|
me.frm.add_child("taxes", tax);
|
||||||
|
}
|
||||||
|
frm.events.apply_taxes(frm);
|
||||||
|
frm.events.set_unallocated_amount(frm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
apply_taxes: function(frm) {
|
||||||
|
frm.events.initialize_taxes(frm);
|
||||||
|
frm.events.determine_exclusive_rate(frm);
|
||||||
|
frm.events.calculate_taxes(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize_taxes: function(frm) {
|
||||||
|
$.each(frm.doc["taxes"] || [], function(i, tax) {
|
||||||
|
frm.events.validate_taxes_and_charges(tax);
|
||||||
|
frm.events.validate_inclusive_tax(tax);
|
||||||
|
tax.item_wise_tax_detail = {};
|
||||||
|
let tax_fields = ["total", "tax_fraction_for_current_item",
|
||||||
|
"grand_total_fraction_for_current_item"];
|
||||||
|
|
||||||
|
if (cstr(tax.charge_type) != "Actual") {
|
||||||
|
tax_fields.push("tax_amount");
|
||||||
|
}
|
||||||
|
|
||||||
|
$.each(tax_fields, function(i, fieldname) { tax[fieldname] = 0.0; });
|
||||||
|
|
||||||
|
frm.doc.paid_amount_after_tax = frm.doc.paid_amount;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
validate_taxes_and_charges: function(d) {
|
||||||
|
let msg = "";
|
||||||
|
|
||||||
|
if (d.account_head && !d.description) {
|
||||||
|
// set description from account head
|
||||||
|
d.description = d.account_head.split(' - ').slice(0, -1).join(' - ');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!d.charge_type && (d.row_id || d.rate || d.tax_amount)) {
|
||||||
|
msg = __("Please select Charge Type first");
|
||||||
|
d.row_id = "";
|
||||||
|
d.rate = d.tax_amount = 0.0;
|
||||||
|
} else if ((d.charge_type == 'Actual' || d.charge_type == 'On Net Total' || d.charge_type == 'On Paid Amount') && d.row_id) {
|
||||||
|
msg = __("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'");
|
||||||
|
d.row_id = "";
|
||||||
|
} else if ((d.charge_type == 'On Previous Row Amount' || d.charge_type == 'On Previous Row Total') && d.row_id) {
|
||||||
|
if (d.idx == 1) {
|
||||||
|
msg = __("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row");
|
||||||
|
d.charge_type = '';
|
||||||
|
} else if (!d.row_id) {
|
||||||
|
msg = __("Please specify a valid Row ID for row {0} in table {1}", [d.idx, __(d.doctype)]);
|
||||||
|
d.row_id = "";
|
||||||
|
} else if (d.row_id && d.row_id >= d.idx) {
|
||||||
|
msg = __("Cannot refer row number greater than or equal to current row number for this Charge type");
|
||||||
|
d.row_id = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (msg) {
|
||||||
|
frappe.validated = false;
|
||||||
|
refresh_field("taxes");
|
||||||
|
frappe.throw(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
validate_inclusive_tax: function(tax) {
|
||||||
|
let actual_type_error = function() {
|
||||||
|
let msg = __("Actual type tax cannot be included in Item rate in row {0}", [tax.idx])
|
||||||
|
frappe.throw(msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
let on_previous_row_error = function(row_range) {
|
||||||
|
let msg = __("For row {0} in {1}. To include {2} in Item rate, rows {3} must also be included",
|
||||||
|
[tax.idx, __(tax.doctype), tax.charge_type, row_range])
|
||||||
|
frappe.throw(msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
if(cint(tax.included_in_paid_amount)) {
|
||||||
|
if(tax.charge_type == "Actual") {
|
||||||
|
// inclusive tax cannot be of type Actual
|
||||||
|
actual_type_error();
|
||||||
|
} else if(tax.charge_type == "On Previous Row Amount" &&
|
||||||
|
!cint(this.frm.doc["taxes"][tax.row_id - 1].included_in_paid_amount)
|
||||||
|
) {
|
||||||
|
// referred row should also be an inclusive tax
|
||||||
|
on_previous_row_error(tax.row_id);
|
||||||
|
} else if(tax.charge_type == "On Previous Row Total") {
|
||||||
|
let taxes_not_included = $.map(this.frm.doc["taxes"].slice(0, tax.row_id),
|
||||||
|
function(t) { return cint(t.included_in_paid_amount) ? null : t; });
|
||||||
|
if(taxes_not_included.length > 0) {
|
||||||
|
// all rows above this tax should be inclusive
|
||||||
|
on_previous_row_error(tax.row_id == 1 ? "1" : "1 - " + tax.row_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
determine_exclusive_rate: function(frm) {
|
||||||
|
let has_inclusive_tax = false;
|
||||||
|
$.each(frm.doc["taxes"] || [], function(i, row) {
|
||||||
|
if(cint(row.included_in_paid_amount)) has_inclusive_tax = true;
|
||||||
|
});
|
||||||
|
if(has_inclusive_tax==false) return;
|
||||||
|
|
||||||
|
let cumulated_tax_fraction = 0.0;
|
||||||
|
$.each(frm.doc["taxes"] || [], function(i, tax) {
|
||||||
|
tax.tax_fraction_for_current_item = frm.events.get_current_tax_fraction(frm, tax);
|
||||||
|
|
||||||
|
if(i==0) {
|
||||||
|
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item;
|
||||||
|
} else {
|
||||||
|
tax.grand_total_fraction_for_current_item =
|
||||||
|
me.frm.doc["taxes"][i-1].grand_total_fraction_for_current_item +
|
||||||
|
tax.tax_fraction_for_current_item;
|
||||||
|
}
|
||||||
|
|
||||||
|
cumulated_tax_fraction += tax.tax_fraction_for_current_item;
|
||||||
|
frm.doc.paid_amount_after_tax = flt(frm.doc.paid_amount/(1+cumulated_tax_fraction))
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
get_current_tax_fraction: function(frm, tax) {
|
||||||
|
let current_tax_fraction = 0.0;
|
||||||
|
|
||||||
|
if(cint(tax.included_in_paid_amount)) {
|
||||||
|
let tax_rate = tax.rate;
|
||||||
|
|
||||||
|
if(tax.charge_type == "On Paid Amount") {
|
||||||
|
current_tax_fraction = (tax_rate / 100.0);
|
||||||
|
} else if(tax.charge_type == "On Previous Row Amount") {
|
||||||
|
current_tax_fraction = (tax_rate / 100.0) *
|
||||||
|
frm.doc["taxes"][cint(tax.row_id) - 1].tax_fraction_for_current_item;
|
||||||
|
} else if(tax.charge_type == "On Previous Row Total") {
|
||||||
|
current_tax_fraction = (tax_rate / 100.0) *
|
||||||
|
frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_fraction_for_current_item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tax.add_deduct_tax && tax.add_deduct_tax == "Deduct") {
|
||||||
|
current_tax_fraction *= -1;
|
||||||
|
}
|
||||||
|
return current_tax_fraction;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
calculate_taxes: function(frm) {
|
||||||
|
frm.doc.total_taxes_and_charges = 0.0;
|
||||||
|
frm.doc.base_total_taxes_and_charges = 0.0;
|
||||||
|
|
||||||
|
let actual_tax_dict = {};
|
||||||
|
|
||||||
|
// maintain actual tax rate based on idx
|
||||||
|
$.each(frm.doc["taxes"] || [], function(i, tax) {
|
||||||
|
if (tax.charge_type == "Actual") {
|
||||||
|
actual_tax_dict[tax.idx] = flt(tax.tax_amount, precision("tax_amount", tax));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$.each(me.frm.doc["taxes"] || [], function(i, tax) {
|
||||||
|
let current_tax_amount = frm.events.get_current_tax_amount(frm, tax);
|
||||||
|
|
||||||
|
// Adjust divisional loss to the last item
|
||||||
|
if (tax.charge_type == "Actual") {
|
||||||
|
actual_tax_dict[tax.idx] -= current_tax_amount;
|
||||||
|
if (i == frm.doc["taxes"].length - 1) {
|
||||||
|
current_tax_amount += actual_tax_dict[tax.idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tax.tax_amount = current_tax_amount;
|
||||||
|
tax.base_tax_amount = tax.tax_amount * frm.doc.source_exchange_rate;
|
||||||
|
current_tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0;
|
||||||
|
|
||||||
|
if(i==0) {
|
||||||
|
tax.total = flt(frm.doc.paid_amount_after_tax + current_tax_amount, precision("total", tax));
|
||||||
|
} else {
|
||||||
|
tax.total = flt(frm.doc["taxes"][i-1].total + current_tax_amount, precision("total", tax));
|
||||||
|
}
|
||||||
|
|
||||||
|
tax.base_total = tax.total * frm.doc.source_exchange_rate;
|
||||||
|
frm.doc.total_taxes_and_charges += current_tax_amount;
|
||||||
|
frm.doc.base_total_taxes_and_charges += current_tax_amount * frm.doc.source_exchange_rate;
|
||||||
|
|
||||||
|
frm.refresh_field('taxes');
|
||||||
|
frm.refresh_field('total_taxes_and_charges');
|
||||||
|
frm.refresh_field('base_total_taxes_and_charges');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
get_current_tax_amount: function(frm, tax) {
|
||||||
|
let tax_rate = tax.rate;
|
||||||
|
let current_tax_amount = 0.0;
|
||||||
|
|
||||||
|
// To set row_id by default as previous row.
|
||||||
|
if(["On Previous Row Amount", "On Previous Row Total"].includes(tax.charge_type)) {
|
||||||
|
if (tax.idx === 1) {
|
||||||
|
frappe.throw(
|
||||||
|
__("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tax.charge_type == "Actual") {
|
||||||
|
current_tax_amount = flt(tax.tax_amount, precision("tax_amount", tax))
|
||||||
|
} else if(tax.charge_type == "On Paid Amount") {
|
||||||
|
current_tax_amount = flt((tax_rate / 100.0) * frm.doc.paid_amount_after_tax);
|
||||||
|
} else if(tax.charge_type == "On Previous Row Amount") {
|
||||||
|
current_tax_amount = flt((tax_rate / 100.0) *
|
||||||
|
frm.doc["taxes"][cint(tax.row_id) - 1].tax_amount);
|
||||||
|
|
||||||
|
} else if(tax.charge_type == "On Previous Row Total") {
|
||||||
|
current_tax_amount = flt((tax_rate / 100.0) *
|
||||||
|
frm.doc["taxes"][cint(tax.row_id) - 1].total);
|
||||||
|
}
|
||||||
|
|
||||||
|
return current_tax_amount;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -1049,6 +1335,38 @@ frappe.ui.form.on('Payment Entry Reference', {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
frappe.ui.form.on('Advance Taxes and Charges', {
|
||||||
|
rate: function(frm) {
|
||||||
|
frm.events.apply_taxes(frm);
|
||||||
|
frm.events.set_unallocated_amount(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
tax_amount : function(frm) {
|
||||||
|
frm.events.apply_taxes(frm);
|
||||||
|
frm.events.set_unallocated_amount(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
row_id: function(frm) {
|
||||||
|
frm.events.apply_taxes(frm);
|
||||||
|
frm.events.set_unallocated_amount(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
taxes_remove: function(frm) {
|
||||||
|
frm.events.apply_taxes(frm);
|
||||||
|
frm.events.set_unallocated_amount(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
included_in_paid_amount: function(frm) {
|
||||||
|
frm.events.apply_taxes(frm);
|
||||||
|
frm.events.set_unallocated_amount(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
charge_type: function(frm) {
|
||||||
|
frm.events.apply_taxes(frm);
|
||||||
|
frm.events.set_unallocated_amount(frm);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
frappe.ui.form.on('Payment Entry Deduction', {
|
frappe.ui.form.on('Payment Entry Deduction', {
|
||||||
amount: function(frm) {
|
amount: function(frm) {
|
||||||
frm.events.set_unallocated_amount(frm);
|
frm.events.set_unallocated_amount(frm);
|
||||||
|
|||||||
@@ -35,12 +35,16 @@
|
|||||||
"paid_to_account_balance",
|
"paid_to_account_balance",
|
||||||
"payment_amounts_section",
|
"payment_amounts_section",
|
||||||
"paid_amount",
|
"paid_amount",
|
||||||
|
"paid_amount_after_tax",
|
||||||
"source_exchange_rate",
|
"source_exchange_rate",
|
||||||
"base_paid_amount",
|
"base_paid_amount",
|
||||||
|
"base_paid_amount_after_tax",
|
||||||
"column_break_21",
|
"column_break_21",
|
||||||
"received_amount",
|
"received_amount",
|
||||||
|
"received_amount_after_tax",
|
||||||
"target_exchange_rate",
|
"target_exchange_rate",
|
||||||
"base_received_amount",
|
"base_received_amount",
|
||||||
|
"base_received_amount_after_tax",
|
||||||
"section_break_14",
|
"section_break_14",
|
||||||
"get_outstanding_invoice",
|
"get_outstanding_invoice",
|
||||||
"references",
|
"references",
|
||||||
@@ -52,6 +56,17 @@
|
|||||||
"unallocated_amount",
|
"unallocated_amount",
|
||||||
"difference_amount",
|
"difference_amount",
|
||||||
"write_off_difference_amount",
|
"write_off_difference_amount",
|
||||||
|
"taxes_and_charges_section",
|
||||||
|
"purchase_taxes_and_charges_template",
|
||||||
|
"sales_taxes_and_charges_template",
|
||||||
|
"advance_tax_account",
|
||||||
|
"column_break_55",
|
||||||
|
"apply_tax_withholding_amount",
|
||||||
|
"tax_withholding_category",
|
||||||
|
"section_break_56",
|
||||||
|
"taxes",
|
||||||
|
"base_total_taxes_and_charges",
|
||||||
|
"total_taxes_and_charges",
|
||||||
"deductions_or_loss_section",
|
"deductions_or_loss_section",
|
||||||
"deductions",
|
"deductions",
|
||||||
"transaction_references",
|
"transaction_references",
|
||||||
@@ -320,6 +335,7 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "doc.received_amount",
|
||||||
"fieldname": "base_received_amount",
|
"fieldname": "base_received_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Received Amount (Company Currency)",
|
"label": "Received Amount (Company Currency)",
|
||||||
@@ -584,12 +600,114 @@
|
|||||||
"fieldname": "custom_remarks",
|
"fieldname": "custom_remarks",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Custom Remarks"
|
"label": "Custom Remarks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.apply_tax_withholding_amount",
|
||||||
|
"fieldname": "tax_withholding_category",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Tax Withholding Category",
|
||||||
|
"mandatory_depends_on": "eval:doc.apply_tax_withholding_amount",
|
||||||
|
"options": "Tax Withholding Category"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:doc.party_type == 'Supplier'",
|
||||||
|
"fieldname": "apply_tax_withholding_amount",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Apply Tax Withholding Amount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "taxes_and_charges_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Taxes and Charges"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.party_type == 'Supplier'",
|
||||||
|
"fieldname": "purchase_taxes_and_charges_template",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Taxes and Charges Template",
|
||||||
|
"options": "Purchase Taxes and Charges Template"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.party_type == 'Customer'",
|
||||||
|
"fieldname": "sales_taxes_and_charges_template",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Taxes and Charges Template",
|
||||||
|
"options": "Sales Taxes and Charges Template"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.party_type == 'Supplier' || doc.party_type == 'Customer'",
|
||||||
|
"fieldname": "taxes",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Advance Taxes and Charges",
|
||||||
|
"options": "Advance Taxes and Charges"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "base_total_taxes_and_charges",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Total Taxes and Charges (Company Currency)",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "total_taxes_and_charges",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Total Taxes and Charges",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "paid_amount_after_tax",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Paid Amount After Tax",
|
||||||
|
"options": "paid_from_account_currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "base_paid_amount_after_tax",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Paid Amount After Tax (Company Currency)",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_55",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_56",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"hide_border": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.apply_tax_withholding_amount",
|
||||||
|
"description": "Provisional tax account for advance tax. Taxes are parked in this account until payments are allocated to invoices",
|
||||||
|
"fieldname": "advance_tax_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Advance Tax Account",
|
||||||
|
"mandatory_depends_on": "eval:doc.apply_tax_withholding_amount",
|
||||||
|
"options": "Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.received_amount",
|
||||||
|
"fieldname": "received_amount_after_tax",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Received Amount After Tax",
|
||||||
|
"options": "paid_to_account_currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "doc.received_amount",
|
||||||
|
"fieldname": "base_received_amount_after_tax",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Received Amount After Tax (Company Currency)",
|
||||||
|
"options": "Company:company:default_currency"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-03-08 13:05:16.958866",
|
"modified": "2021-06-09 11:55:04.215050",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry",
|
"name": "Payment Entry",
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, erpnext, json
|
import frappe, erpnext, json
|
||||||
from frappe import _, scrub, ValidationError
|
from frappe import _, scrub, ValidationError, throw
|
||||||
from frappe.utils import flt, comma_or, nowdate, getdate
|
from frappe.utils import flt, comma_or, nowdate, getdate, cint
|
||||||
from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on
|
from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on
|
||||||
from erpnext.accounts.party import get_party_account
|
from erpnext.accounts.party import get_party_account
|
||||||
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
|
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
|
||||||
@@ -15,9 +15,11 @@ from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amo
|
|||||||
from erpnext.accounts.doctype.bank_account.bank_account import get_party_bank_account, get_bank_account_details
|
from erpnext.accounts.doctype.bank_account.bank_account import get_party_bank_account, get_bank_account_details
|
||||||
from erpnext.controllers.accounts_controller import AccountsController, get_supplier_block_status
|
from erpnext.controllers.accounts_controller import AccountsController, get_supplier_block_status
|
||||||
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting
|
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting
|
||||||
|
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
|
||||||
from six import string_types, iteritems
|
from six import string_types, iteritems
|
||||||
|
|
||||||
|
from erpnext.controllers.accounts_controller import validate_taxes_and_charges
|
||||||
|
|
||||||
class InvalidPaymentEntry(ValidationError):
|
class InvalidPaymentEntry(ValidationError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -52,6 +54,8 @@ class PaymentEntry(AccountsController):
|
|||||||
self.set_exchange_rate()
|
self.set_exchange_rate()
|
||||||
self.validate_mandatory()
|
self.validate_mandatory()
|
||||||
self.validate_reference_documents()
|
self.validate_reference_documents()
|
||||||
|
self.set_tax_withholding()
|
||||||
|
self.apply_taxes()
|
||||||
self.set_amounts()
|
self.set_amounts()
|
||||||
self.clear_unallocated_reference_document_rows()
|
self.clear_unallocated_reference_document_rows()
|
||||||
self.validate_payment_against_negative_invoice()
|
self.validate_payment_against_negative_invoice()
|
||||||
@@ -65,7 +69,6 @@ class PaymentEntry(AccountsController):
|
|||||||
self.set_status()
|
self.set_status()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.setup_party_account_field()
|
|
||||||
if self.difference_amount:
|
if self.difference_amount:
|
||||||
frappe.throw(_("Difference Amount must be zero"))
|
frappe.throw(_("Difference Amount must be zero"))
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
@@ -78,7 +81,6 @@ class PaymentEntry(AccountsController):
|
|||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
|
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
|
||||||
self.setup_party_account_field()
|
|
||||||
self.make_gl_entries(cancel=1)
|
self.make_gl_entries(cancel=1)
|
||||||
self.update_outstanding_amounts()
|
self.update_outstanding_amounts()
|
||||||
self.update_advance_paid()
|
self.update_advance_paid()
|
||||||
@@ -122,6 +124,11 @@ class PaymentEntry(AccountsController):
|
|||||||
if flt(d.allocated_amount) > flt(d.outstanding_amount):
|
if flt(d.allocated_amount) > flt(d.outstanding_amount):
|
||||||
frappe.throw(_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx))
|
frappe.throw(_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx))
|
||||||
|
|
||||||
|
# Check for negative outstanding invoices as well
|
||||||
|
if flt(d.allocated_amount) < 0:
|
||||||
|
if flt(d.allocated_amount) < flt(d.outstanding_amount):
|
||||||
|
frappe.throw(_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx))
|
||||||
|
|
||||||
def delink_advance_entry_references(self):
|
def delink_advance_entry_references(self):
|
||||||
for reference in self.references:
|
for reference in self.references:
|
||||||
if reference.reference_doctype in ("Sales Invoice", "Purchase Invoice"):
|
if reference.reference_doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||||
@@ -177,7 +184,7 @@ class PaymentEntry(AccountsController):
|
|||||||
|
|
||||||
for field, value in iteritems(ref_details):
|
for field, value in iteritems(ref_details):
|
||||||
if field == 'exchange_rate' or not d.get(field) or force:
|
if field == 'exchange_rate' or not d.get(field) or force:
|
||||||
d.set(field, value)
|
d.db_set(field, value)
|
||||||
|
|
||||||
def validate_payment_type(self):
|
def validate_payment_type(self):
|
||||||
if self.payment_type not in ("Receive", "Pay", "Internal Transfer"):
|
if self.payment_type not in ("Receive", "Pay", "Internal Transfer"):
|
||||||
@@ -303,11 +310,10 @@ 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")
|
||||||
|
|
||||||
|
|
||||||
def validate_journal_entry(self):
|
def validate_journal_entry(self):
|
||||||
for d in self.get("references"):
|
for d in self.get("references"):
|
||||||
if d.allocated_amount and d.reference_doctype == "Journal Entry":
|
if d.allocated_amount and d.reference_doctype == "Journal Entry":
|
||||||
@@ -386,12 +392,98 @@ class PaymentEntry(AccountsController):
|
|||||||
else:
|
else:
|
||||||
self.status = 'Draft'
|
self.status = 'Draft'
|
||||||
|
|
||||||
|
self.db_set('status', self.status, update_modified = True)
|
||||||
|
|
||||||
|
def set_tax_withholding(self):
|
||||||
|
if not self.party_type == 'Supplier':
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.apply_tax_withholding_amount:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.advance_tax_account:
|
||||||
|
frappe.throw(_("Advance TDS account is mandatory for advance TDS deduction"))
|
||||||
|
|
||||||
|
reference_doclist = []
|
||||||
|
net_total = self.paid_amount
|
||||||
|
included_in_paid_amount = 0
|
||||||
|
|
||||||
|
# Adding args as purchase invoice to get TDS amount
|
||||||
|
args = frappe._dict({
|
||||||
|
'company': self.company,
|
||||||
|
'doctype': 'Purchase Invoice',
|
||||||
|
'supplier': self.party,
|
||||||
|
'posting_date': self.posting_date,
|
||||||
|
'net_total': net_total
|
||||||
|
})
|
||||||
|
|
||||||
|
tax_withholding_details = get_party_tax_withholding_details(args, self.tax_withholding_category)
|
||||||
|
|
||||||
|
if not tax_withholding_details:
|
||||||
|
return
|
||||||
|
|
||||||
|
tax_withholding_details.update({
|
||||||
|
'included_in_paid_amount': included_in_paid_amount,
|
||||||
|
'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company)
|
||||||
|
})
|
||||||
|
|
||||||
|
accounts = []
|
||||||
|
for d in self.taxes:
|
||||||
|
if d.account_head == tax_withholding_details.get("account_head"):
|
||||||
|
|
||||||
|
# Preserve user updated included in paid amount
|
||||||
|
if d.included_in_paid_amount:
|
||||||
|
tax_withholding_details.update({'included_in_paid_amount': d.included_in_paid_amount})
|
||||||
|
|
||||||
|
d.update(tax_withholding_details)
|
||||||
|
accounts.append(d.account_head)
|
||||||
|
|
||||||
|
if not accounts or tax_withholding_details.get("account_head") not in accounts:
|
||||||
|
self.append("taxes", tax_withholding_details)
|
||||||
|
|
||||||
|
to_remove = [d for d in self.taxes
|
||||||
|
if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")]
|
||||||
|
|
||||||
|
for d in to_remove:
|
||||||
|
self.remove(d)
|
||||||
|
|
||||||
|
def apply_taxes(self):
|
||||||
|
self.initialize_taxes()
|
||||||
|
self.determine_exclusive_rate()
|
||||||
|
self.calculate_taxes()
|
||||||
|
|
||||||
def set_amounts(self):
|
def set_amounts(self):
|
||||||
|
self.set_received_amount()
|
||||||
self.set_amounts_in_company_currency()
|
self.set_amounts_in_company_currency()
|
||||||
|
self.set_amounts_after_tax()
|
||||||
self.set_total_allocated_amount()
|
self.set_total_allocated_amount()
|
||||||
self.set_unallocated_amount()
|
self.set_unallocated_amount()
|
||||||
self.set_difference_amount()
|
self.set_difference_amount()
|
||||||
|
|
||||||
|
def set_received_amount(self):
|
||||||
|
self.base_received_amount = self.base_paid_amount
|
||||||
|
|
||||||
|
def set_amounts_after_tax(self):
|
||||||
|
applicable_tax = 0
|
||||||
|
base_applicable_tax = 0
|
||||||
|
for tax in self.get('taxes'):
|
||||||
|
if not tax.included_in_paid_amount:
|
||||||
|
amount = -1 * tax.tax_amount if tax.add_deduct_tax == 'Deduct' else tax.tax_amount
|
||||||
|
base_amount = -1 * tax.base_tax_amount if tax.add_deduct_tax == 'Deduct' else tax.base_tax_amount
|
||||||
|
|
||||||
|
applicable_tax += amount
|
||||||
|
base_applicable_tax += base_amount
|
||||||
|
|
||||||
|
self.paid_amount_after_tax = flt(flt(self.paid_amount) + flt(applicable_tax),
|
||||||
|
self.precision("paid_amount_after_tax"))
|
||||||
|
self.base_paid_amount_after_tax = flt(flt(self.paid_amount_after_tax) * flt(self.source_exchange_rate),
|
||||||
|
self.precision("base_paid_amount_after_tax"))
|
||||||
|
|
||||||
|
self.received_amount_after_tax = flt(flt(self.received_amount) + flt(applicable_tax),
|
||||||
|
self.precision("paid_amount_after_tax"))
|
||||||
|
self.base_received_amount_after_tax = flt(flt(self.received_amount_after_tax) * flt(self.target_exchange_rate),
|
||||||
|
self.precision("base_paid_amount_after_tax"))
|
||||||
|
|
||||||
def set_amounts_in_company_currency(self):
|
def set_amounts_in_company_currency(self):
|
||||||
self.base_paid_amount, self.base_received_amount, self.difference_amount = 0, 0, 0
|
self.base_paid_amount, self.base_received_amount, self.difference_amount = 0, 0, 0
|
||||||
if self.paid_amount:
|
if self.paid_amount:
|
||||||
@@ -419,17 +511,17 @@ 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_after_tax + total_deductions \
|
||||||
and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate):
|
and self.total_allocated_amount < self.paid_amount_after_tax + (total_deductions / self.source_exchange_rate):
|
||||||
self.unallocated_amount = (self.base_received_amount + total_deductions -
|
self.unallocated_amount = (self.received_amount_after_tax + total_deductions -
|
||||||
self.base_total_allocated_amount) / self.source_exchange_rate
|
self.base_total_allocated_amount) / self.source_exchange_rate
|
||||||
elif self.payment_type == "Pay" \
|
elif self.payment_type == "Pay" \
|
||||||
and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \
|
and self.base_total_allocated_amount < (self.base_paid_amount_after_tax - total_deductions) \
|
||||||
and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate):
|
and self.total_allocated_amount < self.received_amount_after_tax + (total_deductions / self.target_exchange_rate):
|
||||||
self.unallocated_amount = (self.base_paid_amount - (total_deductions +
|
self.unallocated_amount = (self.base_paid_amount_after_tax - (total_deductions +
|
||||||
self.base_total_allocated_amount)) / self.target_exchange_rate
|
self.base_total_allocated_amount)) / self.target_exchange_rate
|
||||||
|
|
||||||
def set_difference_amount(self):
|
def set_difference_amount(self):
|
||||||
base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate)
|
base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate)
|
||||||
@@ -438,13 +530,13 @@ class PaymentEntry(AccountsController):
|
|||||||
base_party_amount = flt(self.base_total_allocated_amount) + flt(base_unallocated_amount)
|
base_party_amount = flt(self.base_total_allocated_amount) + flt(base_unallocated_amount)
|
||||||
|
|
||||||
if self.payment_type == "Receive":
|
if self.payment_type == "Receive":
|
||||||
self.difference_amount = base_party_amount - self.base_received_amount
|
self.difference_amount = base_party_amount - self.base_received_amount_after_tax
|
||||||
elif self.payment_type == "Pay":
|
elif self.payment_type == "Pay":
|
||||||
self.difference_amount = self.base_paid_amount - base_party_amount
|
self.difference_amount = self.base_paid_amount_after_tax - base_party_amount
|
||||||
else:
|
else:
|
||||||
self.difference_amount = self.base_paid_amount - flt(self.base_received_amount)
|
self.difference_amount = self.base_paid_amount_after_tax - flt(self.base_received_amount_after_tax)
|
||||||
|
|
||||||
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 +552,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])
|
||||||
@@ -532,6 +624,7 @@ class PaymentEntry(AccountsController):
|
|||||||
self.add_party_gl_entries(gl_entries)
|
self.add_party_gl_entries(gl_entries)
|
||||||
self.add_bank_gl_entries(gl_entries)
|
self.add_bank_gl_entries(gl_entries)
|
||||||
self.add_deductions_gl_entries(gl_entries)
|
self.add_deductions_gl_entries(gl_entries)
|
||||||
|
self.add_tax_gl_entries(gl_entries)
|
||||||
|
|
||||||
make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj)
|
make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj)
|
||||||
|
|
||||||
@@ -571,7 +664,7 @@ class PaymentEntry(AccountsController):
|
|||||||
gl_entries.append(gle)
|
gl_entries.append(gle)
|
||||||
|
|
||||||
if self.unallocated_amount:
|
if self.unallocated_amount:
|
||||||
base_unallocated_amount = base_unallocated_amount = self.unallocated_amount * \
|
base_unallocated_amount = self.unallocated_amount * \
|
||||||
(self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate)
|
(self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate)
|
||||||
|
|
||||||
gle = party_gl_dict.copy()
|
gle = party_gl_dict.copy()
|
||||||
@@ -590,8 +683,8 @@ class PaymentEntry(AccountsController):
|
|||||||
"account": self.paid_from,
|
"account": self.paid_from,
|
||||||
"account_currency": self.paid_from_account_currency,
|
"account_currency": self.paid_from_account_currency,
|
||||||
"against": self.party if self.payment_type=="Pay" else self.paid_to,
|
"against": self.party if self.payment_type=="Pay" else self.paid_to,
|
||||||
"credit_in_account_currency": self.paid_amount,
|
"credit_in_account_currency": self.paid_amount_after_tax,
|
||||||
"credit": self.base_paid_amount,
|
"credit": self.base_paid_amount_after_tax,
|
||||||
"cost_center": self.cost_center
|
"cost_center": self.cost_center
|
||||||
}, item=self)
|
}, item=self)
|
||||||
)
|
)
|
||||||
@@ -601,12 +694,50 @@ class PaymentEntry(AccountsController):
|
|||||||
"account": self.paid_to,
|
"account": self.paid_to,
|
||||||
"account_currency": self.paid_to_account_currency,
|
"account_currency": self.paid_to_account_currency,
|
||||||
"against": self.party if self.payment_type=="Receive" else self.paid_from,
|
"against": self.party if self.payment_type=="Receive" else self.paid_from,
|
||||||
"debit_in_account_currency": self.received_amount,
|
"debit_in_account_currency": self.received_amount_after_tax,
|
||||||
"debit": self.base_received_amount,
|
"debit": self.base_received_amount_after_tax,
|
||||||
"cost_center": self.cost_center
|
"cost_center": self.cost_center
|
||||||
}, item=self)
|
}, item=self)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def add_tax_gl_entries(self, gl_entries):
|
||||||
|
for d in self.get('taxes'):
|
||||||
|
account_currency = get_account_currency(d.account_head)
|
||||||
|
if account_currency != self.company_currency:
|
||||||
|
frappe.throw(_("Currency for {0} must be {1}").format(d.account_head, self.company_currency))
|
||||||
|
|
||||||
|
if self.payment_type == 'Pay':
|
||||||
|
dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit"
|
||||||
|
elif self.payment_type == 'Receive':
|
||||||
|
dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit"
|
||||||
|
|
||||||
|
payment_or_advance_account = self.get_party_account_for_taxes()
|
||||||
|
|
||||||
|
gl_entries.append(
|
||||||
|
self.get_gl_dict({
|
||||||
|
"account": d.account_head,
|
||||||
|
"against": self.party if self.payment_type=="Receive" else self.paid_from,
|
||||||
|
dr_or_cr: d.base_tax_amount,
|
||||||
|
dr_or_cr + "_in_account_currency": d.base_tax_amount
|
||||||
|
if account_currency==self.company_currency
|
||||||
|
else d.tax_amount,
|
||||||
|
"cost_center": d.cost_center
|
||||||
|
}, account_currency, item=d))
|
||||||
|
|
||||||
|
#Intentionally use -1 to get net values in party account
|
||||||
|
gl_entries.append(
|
||||||
|
self.get_gl_dict({
|
||||||
|
"account": payment_or_advance_account,
|
||||||
|
"against": self.party if self.payment_type=="Receive" else self.paid_from,
|
||||||
|
dr_or_cr: -1 * d.base_tax_amount,
|
||||||
|
dr_or_cr + "_in_account_currency": -1*d.base_tax_amount
|
||||||
|
if account_currency==self.company_currency
|
||||||
|
else d.tax_amount,
|
||||||
|
"cost_center": self.cost_center,
|
||||||
|
"party_type": self.party_type,
|
||||||
|
"party": self.party
|
||||||
|
}, account_currency, item=d))
|
||||||
|
|
||||||
def add_deductions_gl_entries(self, gl_entries):
|
def add_deductions_gl_entries(self, gl_entries):
|
||||||
for d in self.get("deductions"):
|
for d in self.get("deductions"):
|
||||||
if d.amount:
|
if d.amount:
|
||||||
@@ -625,6 +756,14 @@ class PaymentEntry(AccountsController):
|
|||||||
}, item=d)
|
}, item=d)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_party_account_for_taxes(self):
|
||||||
|
if self.advance_tax_account:
|
||||||
|
return self.advance_tax_account
|
||||||
|
elif self.payment_type == 'Receive':
|
||||||
|
return self.paid_from
|
||||||
|
elif self.payment_type == 'Pay':
|
||||||
|
return self.paid_to
|
||||||
|
|
||||||
def update_advance_paid(self):
|
def update_advance_paid(self):
|
||||||
if self.payment_type in ("Receive", "Pay") and self.party:
|
if self.payment_type in ("Receive", "Pay") and self.party:
|
||||||
for d in self.get("references"):
|
for d in self.get("references"):
|
||||||
@@ -671,6 +810,139 @@ class PaymentEntry(AccountsController):
|
|||||||
self.append('deductions', row)
|
self.append('deductions', row)
|
||||||
self.set_unallocated_amount()
|
self.set_unallocated_amount()
|
||||||
|
|
||||||
|
def initialize_taxes(self):
|
||||||
|
for tax in self.get("taxes"):
|
||||||
|
validate_taxes_and_charges(tax)
|
||||||
|
validate_inclusive_tax(tax, self)
|
||||||
|
|
||||||
|
tax_fields = ["total", "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
|
||||||
|
|
||||||
|
if tax.charge_type != "Actual":
|
||||||
|
tax_fields.append("tax_amount")
|
||||||
|
|
||||||
|
for fieldname in tax_fields:
|
||||||
|
tax.set(fieldname, 0.0)
|
||||||
|
|
||||||
|
self.paid_amount_after_tax = self.paid_amount
|
||||||
|
|
||||||
|
def determine_exclusive_rate(self):
|
||||||
|
if not any((cint(tax.included_in_paid_amount) for tax in self.get("taxes"))):
|
||||||
|
return
|
||||||
|
|
||||||
|
cumulated_tax_fraction = 0
|
||||||
|
for i, tax in enumerate(self.get("taxes")):
|
||||||
|
tax.tax_fraction_for_current_item = self.get_current_tax_fraction(tax)
|
||||||
|
if i==0:
|
||||||
|
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
|
||||||
|
else:
|
||||||
|
tax.grand_total_fraction_for_current_item = \
|
||||||
|
self.get("taxes")[i-1].grand_total_fraction_for_current_item \
|
||||||
|
+ tax.tax_fraction_for_current_item
|
||||||
|
|
||||||
|
cumulated_tax_fraction += tax.tax_fraction_for_current_item
|
||||||
|
|
||||||
|
self.paid_amount_after_tax = flt(self.paid_amount/(1+cumulated_tax_fraction))
|
||||||
|
|
||||||
|
def calculate_taxes(self):
|
||||||
|
self.total_taxes_and_charges = 0.0
|
||||||
|
self.base_total_taxes_and_charges = 0.0
|
||||||
|
|
||||||
|
actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
|
||||||
|
for tax in self.get("taxes") if tax.charge_type == "Actual"])
|
||||||
|
|
||||||
|
for i, tax in enumerate(self.get('taxes')):
|
||||||
|
current_tax_amount = self.get_current_tax_amount(tax)
|
||||||
|
|
||||||
|
if tax.charge_type == "Actual":
|
||||||
|
actual_tax_dict[tax.idx] -= current_tax_amount
|
||||||
|
if i == len(self.get("taxes")) - 1:
|
||||||
|
current_tax_amount += actual_tax_dict[tax.idx]
|
||||||
|
|
||||||
|
tax.tax_amount = current_tax_amount
|
||||||
|
tax.base_tax_amount = tax.tax_amount * self.source_exchange_rate
|
||||||
|
|
||||||
|
if tax.add_deduct_tax == "Deduct":
|
||||||
|
current_tax_amount *= -1.0
|
||||||
|
else:
|
||||||
|
current_tax_amount *= 1.0
|
||||||
|
|
||||||
|
if i == 0:
|
||||||
|
tax.total = flt(self.paid_amount_after_tax + current_tax_amount, self.precision("total", tax))
|
||||||
|
else:
|
||||||
|
tax.total = flt(self.get('taxes')[i-1].total + current_tax_amount, self.precision("total", tax))
|
||||||
|
|
||||||
|
tax.base_total = tax.total * self.source_exchange_rate
|
||||||
|
|
||||||
|
self.total_taxes_and_charges += current_tax_amount
|
||||||
|
self.base_total_taxes_and_charges += current_tax_amount * self.source_exchange_rate
|
||||||
|
|
||||||
|
if self.get('taxes'):
|
||||||
|
self.paid_amount_after_tax = self.get('taxes')[-1].base_total
|
||||||
|
|
||||||
|
def get_current_tax_amount(self, tax):
|
||||||
|
tax_rate = tax.rate
|
||||||
|
|
||||||
|
# To set row_id by default as previous row.
|
||||||
|
if tax.charge_type in ["On Previous Row Amount", "On Previous Row Total"]:
|
||||||
|
if tax.idx == 1:
|
||||||
|
frappe.throw(_("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"))
|
||||||
|
|
||||||
|
if not tax.row_id:
|
||||||
|
tax.row_id = tax.idx - 1
|
||||||
|
|
||||||
|
if tax.charge_type == "Actual":
|
||||||
|
current_tax_amount = flt(tax.tax_amount, self.precision("tax_amount", tax))
|
||||||
|
elif tax.charge_type == "On Paid Amount":
|
||||||
|
current_tax_amount = (tax_rate / 100.0) * self.paid_amount_after_tax
|
||||||
|
elif tax.charge_type == "On Previous Row Amount":
|
||||||
|
current_tax_amount = (tax_rate / 100.0) * \
|
||||||
|
self.get('taxes')[cint(tax.row_id) - 1].tax_amount
|
||||||
|
|
||||||
|
elif tax.charge_type == "On Previous Row Total":
|
||||||
|
current_tax_amount = (tax_rate / 100.0) * \
|
||||||
|
self.get('taxes')[cint(tax.row_id) - 1].total
|
||||||
|
|
||||||
|
return current_tax_amount
|
||||||
|
|
||||||
|
def get_current_tax_fraction(self, tax):
|
||||||
|
current_tax_fraction = 0
|
||||||
|
|
||||||
|
if cint(tax.included_in_paid_amount):
|
||||||
|
tax_rate = tax.rate
|
||||||
|
|
||||||
|
if tax.charge_type == "On Paid Amount":
|
||||||
|
current_tax_fraction = tax_rate / 100.0
|
||||||
|
elif tax.charge_type == "On Previous Row Amount":
|
||||||
|
current_tax_fraction = (tax_rate / 100.0) * \
|
||||||
|
self.get("taxes")[cint(tax.row_id) - 1].tax_fraction_for_current_item
|
||||||
|
elif tax.charge_type == "On Previous Row Total":
|
||||||
|
current_tax_fraction = (tax_rate / 100.0) * \
|
||||||
|
self.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
|
||||||
|
|
||||||
|
if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct":
|
||||||
|
current_tax_fraction *= -1.0
|
||||||
|
|
||||||
|
return current_tax_fraction
|
||||||
|
|
||||||
|
def validate_inclusive_tax(tax, doc):
|
||||||
|
def _on_previous_row_error(row_range):
|
||||||
|
throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, row_range))
|
||||||
|
|
||||||
|
if cint(getattr(tax, "included_in_paid_amount", None)):
|
||||||
|
if tax.charge_type == "Actual":
|
||||||
|
# inclusive tax cannot be of type Actual
|
||||||
|
throw(_("Charge of type 'Actual' in row {0} cannot be included in Item Rate or Paid Amount").format(tax.idx))
|
||||||
|
elif tax.charge_type == "On Previous Row Amount" and \
|
||||||
|
not cint(doc.get("taxes")[cint(tax.row_id) - 1].included_in_paid_amount):
|
||||||
|
# referred row should also be inclusive
|
||||||
|
_on_previous_row_error(tax.row_id)
|
||||||
|
elif tax.charge_type == "On Previous Row Total" and \
|
||||||
|
not all([cint(t.included_in_paid_amount for t in doc.get("taxes")[:cint(tax.row_id) - 1])]):
|
||||||
|
# all rows about the referred tax should be inclusive
|
||||||
|
_on_previous_row_error("1 - %d" % (cint(tax.row_id),))
|
||||||
|
elif tax.get("category") == "Valuation":
|
||||||
|
frappe.throw(_("Valuation type charges can not be marked as Inclusive"))
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_outstanding_reference_documents(args):
|
def get_outstanding_reference_documents(args):
|
||||||
|
|
||||||
@@ -989,6 +1261,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
|||||||
outstanding_amount = ref_doc.get("outstanding_amount")
|
outstanding_amount = ref_doc.get("outstanding_amount")
|
||||||
elif reference_doctype == "Donation":
|
elif reference_doctype == "Donation":
|
||||||
total_amount = ref_doc.get("amount")
|
total_amount = ref_doc.get("amount")
|
||||||
|
outstanding_amount = total_amount
|
||||||
exchange_rate = 1
|
exchange_rate = 1
|
||||||
elif reference_doctype == "Dunning":
|
elif reference_doctype == "Dunning":
|
||||||
total_amount = ref_doc.get("dunning_amount")
|
total_amount = ref_doc.get("dunning_amount")
|
||||||
@@ -1235,6 +1508,13 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
|||||||
})
|
})
|
||||||
pe.set_difference_amount()
|
pe.set_difference_amount()
|
||||||
|
|
||||||
|
if doc.doctype == 'Purchase Order' and doc.apply_tds:
|
||||||
|
pe.apply_tax_withholding_amount = 1
|
||||||
|
pe.tax_withholding_category = doc.tax_withholding_category
|
||||||
|
|
||||||
|
if not pe.advance_tax_account:
|
||||||
|
pe.advance_tax_account = frappe.db.get_value('Company', pe.company, 'unrealized_profit_loss_account')
|
||||||
|
|
||||||
return pe
|
return pe
|
||||||
|
|
||||||
def get_bank_cash_account(doc, bank_account):
|
def get_bank_cash_account(doc, bank_account):
|
||||||
@@ -1353,6 +1633,13 @@ def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outsta
|
|||||||
paid_amount = received_amount * doc.get('conversion_rate', 1)
|
paid_amount = received_amount * doc.get('conversion_rate', 1)
|
||||||
if dt == "Employee Advance":
|
if dt == "Employee Advance":
|
||||||
paid_amount = received_amount * doc.get('exchange_rate', 1)
|
paid_amount = received_amount * doc.get('exchange_rate', 1)
|
||||||
|
|
||||||
|
if dt == "Purchase Order" and doc.apply_tds:
|
||||||
|
if party_account_currency == bank.account_currency:
|
||||||
|
paid_amount = received_amount = doc.base_net_total
|
||||||
|
else:
|
||||||
|
paid_amount = received_amount = doc.base_net_total * doc.get('exchange_rate', 1)
|
||||||
|
|
||||||
return paid_amount, received_amount
|
return paid_amount, received_amount
|
||||||
|
|
||||||
def apply_early_payment_discount(paid_amount, received_amount, doc):
|
def apply_early_payment_discount(paid_amount, received_amount, doc):
|
||||||
|
|||||||
@@ -1,140 +1,70 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_events_in_timeline": 0,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2016-06-15 15:56:30.815503",
|
"creation": "2016-06-15 15:56:30.815503",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
|
"field_order": [
|
||||||
|
"account",
|
||||||
|
"cost_center",
|
||||||
|
"amount",
|
||||||
|
"column_break_2",
|
||||||
|
"description"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "account",
|
"fieldname": "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": "Account",
|
"label": "Account",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Account",
|
"options": "Account",
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"show_days": 1,
|
||||||
"set_only_once": 0,
|
"show_seconds": 1
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "cost_center",
|
"fieldname": "cost_center",
|
||||||
"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": "Cost Center",
|
"label": "Cost Center",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Cost Center",
|
"options": "Cost Center",
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"show_days": 1,
|
||||||
"set_only_once": 0,
|
"show_seconds": 1
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "amount",
|
"fieldname": "amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"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": "Amount",
|
"label": "Amount",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"show_days": 1,
|
||||||
"set_only_once": 0,
|
"show_seconds": 1
|
||||||
"translatable": 0,
|
},
|
||||||
"unique": 0
|
{
|
||||||
|
"fieldname": "column_break_2",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "description",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Description",
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"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": "2019-01-07 16:52:07.040146",
|
"modified": "2020-09-12 20:38:08.110674",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry Deduction",
|
"name": "Payment Entry Deduction",
|
||||||
"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": 0,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
||||||
@@ -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):
|
||||||
@@ -492,7 +492,6 @@ def update_payment_req_status(doc, method):
|
|||||||
status = 'Requested'
|
status = 'Requested'
|
||||||
|
|
||||||
pay_req_doc.db_set('status', status)
|
pay_req_doc.db_set('status', status)
|
||||||
frappe.db.commit()
|
|
||||||
|
|
||||||
def get_dummy_message(doc):
|
def get_dummy_message(doc):
|
||||||
return frappe.render_template("""{% if doc.contact_person -%}
|
return frappe.render_template("""{% if doc.contact_person -%}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class PaymentTermsTemplate(Document):
|
|||||||
def check_duplicate_terms(self):
|
def check_duplicate_terms(self):
|
||||||
terms = []
|
terms = []
|
||||||
for term in self.terms:
|
for term in self.terms:
|
||||||
term_info = (term.credit_days, term.credit_months, term.due_date_based_on)
|
term_info = (term.payment_term, term.credit_days, term.credit_months, term.due_date_based_on)
|
||||||
if term_info in terms:
|
if term_info in terms:
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_('The Payment Term at row {0} is possibly a duplicate.').format(term.idx),
|
_('The Payment Term at row {0} is possibly a duplicate.').format(term.idx),
|
||||||
|
|||||||
@@ -1,297 +1,102 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"autoname": "ACC-PCV-.YYYY.-.#####",
|
"autoname": "ACC-PCV-.YYYY.-.#####",
|
||||||
"beta": 0,
|
|
||||||
"creation": "2013-01-10 16:34:07",
|
"creation": "2013-01-10 16:34:07",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 0,
|
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"transaction_date",
|
||||||
|
"posting_date",
|
||||||
|
"fiscal_year",
|
||||||
|
"amended_from",
|
||||||
|
"company",
|
||||||
|
"cost_center_wise_pnl",
|
||||||
|
"column_break1",
|
||||||
|
"closing_account_head",
|
||||||
|
"remarks"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "transaction_date",
|
"fieldname": "transaction_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Transaction Date",
|
"label": "Transaction Date",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "transaction_date",
|
"oldfieldname": "transaction_date",
|
||||||
"oldfieldtype": "Date",
|
"oldfieldtype": "Date"
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "posting_date",
|
"fieldname": "posting_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Posting Date",
|
"label": "Posting Date",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "posting_date",
|
"oldfieldname": "posting_date",
|
||||||
"oldfieldtype": "Date",
|
"oldfieldtype": "Date",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"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,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "fiscal_year",
|
"fieldname": "fiscal_year",
|
||||||
"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": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Closing Fiscal Year",
|
"label": "Closing Fiscal Year",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "fiscal_year",
|
"oldfieldname": "fiscal_year",
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"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,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "amended_from",
|
"fieldname": "amended_from",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 1,
|
"ignore_user_permissions": 1,
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Amended From",
|
"label": "Amended From",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "amended_from",
|
"oldfieldname": "amended_from",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"options": "Period Closing Voucher",
|
"options": "Period Closing Voucher",
|
||||||
"permlevel": 0,
|
"read_only": 1
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"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": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Company",
|
"label": "Company",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "company",
|
"oldfieldname": "company",
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"options": "Company",
|
"options": "Company",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"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,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break1",
|
"fieldname": "column_break1",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break",
|
||||||
"hidden": 0,
|
"oldfieldtype": "Column Break"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldtype": "Column Break",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"description": "The account head under Liability or Equity, in which Profit/Loss will be booked",
|
"description": "The account head under Liability or Equity, in which Profit/Loss will be booked",
|
||||||
"fieldname": "closing_account_head",
|
"fieldname": "closing_account_head",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Closing Account Head",
|
"label": "Closing Account Head",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "closing_account_head",
|
"oldfieldname": "closing_account_head",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "Account",
|
"options": "Account",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"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,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "remarks",
|
"fieldname": "remarks",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Remarks",
|
"label": "Remarks",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "remarks",
|
"oldfieldname": "remarks",
|
||||||
"oldfieldtype": "Small Text",
|
"oldfieldtype": "Small Text",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"print_hide": 0,
|
},
|
||||||
"print_hide_if_no_value": 0,
|
{
|
||||||
"read_only": 0,
|
"default": "0",
|
||||||
"remember_last_selected_value": 0,
|
"fieldname": "cost_center_wise_pnl",
|
||||||
"report_hide": 0,
|
"fieldtype": "Check",
|
||||||
"reqd": 1,
|
"label": "Book Cost Center Wise Profit/Loss"
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"issingle": 0,
|
"links": [],
|
||||||
"istable": 0,
|
"modified": "2021-05-20 15:27:37.210458",
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2020-09-18 17:26:09.703215",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Period Closing Voucher",
|
"name": "Period Closing Voucher",
|
||||||
@@ -303,15 +108,10 @@
|
|||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 1,
|
"submit": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
@@ -322,29 +122,17 @@
|
|||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "Accounts Manager",
|
"role": "Accounts Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 1,
|
"submit": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"search_fields": "posting_date, fiscal_year",
|
"search_fields": "posting_date, fiscal_year",
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"title_field": "closing_account_head",
|
"title_field": "closing_account_head"
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
||||||
@@ -52,62 +52,95 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
def make_gl_entries(self):
|
def make_gl_entries(self):
|
||||||
gl_entries = []
|
gl_entries = []
|
||||||
net_pl_balance = 0
|
net_pl_balance = 0
|
||||||
dimension_fields = ['t1.cost_center']
|
|
||||||
|
|
||||||
accounting_dimensions = get_accounting_dimensions()
|
pl_accounts = self.get_pl_balances()
|
||||||
for dimension in accounting_dimensions:
|
|
||||||
dimension_fields.append('t1.{0}'.format(dimension))
|
|
||||||
|
|
||||||
dimension_filters, default_dimensions = get_dimensions()
|
|
||||||
|
|
||||||
pl_accounts = self.get_pl_balances(dimension_fields)
|
|
||||||
|
|
||||||
for acc in pl_accounts:
|
for acc in pl_accounts:
|
||||||
if flt(acc.balance_in_company_currency):
|
if flt(acc.bal_in_company_currency):
|
||||||
gl_entries.append(self.get_gl_dict({
|
gl_entries.append(self.get_gl_dict({
|
||||||
"account": acc.account,
|
"account": acc.account,
|
||||||
"cost_center": acc.cost_center,
|
"cost_center": acc.cost_center,
|
||||||
"account_currency": acc.account_currency,
|
"account_currency": acc.account_currency,
|
||||||
"debit_in_account_currency": abs(flt(acc.balance_in_account_currency)) \
|
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) < 0 else 0,
|
||||||
if flt(acc.balance_in_account_currency) < 0 else 0,
|
"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0,
|
||||||
"debit": abs(flt(acc.balance_in_company_currency)) \
|
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0,
|
||||||
if flt(acc.balance_in_company_currency) < 0 else 0,
|
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0
|
||||||
"credit_in_account_currency": abs(flt(acc.balance_in_account_currency)) \
|
|
||||||
if flt(acc.balance_in_account_currency) > 0 else 0,
|
|
||||||
"credit": abs(flt(acc.balance_in_company_currency)) \
|
|
||||||
if flt(acc.balance_in_company_currency) > 0 else 0
|
|
||||||
}, item=acc))
|
}, item=acc))
|
||||||
|
|
||||||
net_pl_balance += flt(acc.balance_in_company_currency)
|
net_pl_balance += flt(acc.bal_in_company_currency)
|
||||||
|
|
||||||
if net_pl_balance:
|
if net_pl_balance:
|
||||||
cost_center = frappe.db.get_value("Company", self.company, "cost_center")
|
if self.cost_center_wise_pnl:
|
||||||
gl_entry = self.get_gl_dict({
|
costcenter_wise_gl_entries = self.get_costcenter_wise_pnl_gl_entries(pl_accounts)
|
||||||
"account": self.closing_account_head,
|
gl_entries += costcenter_wise_gl_entries
|
||||||
"debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0,
|
else:
|
||||||
"debit": abs(net_pl_balance) if net_pl_balance > 0 else 0,
|
gl_entry = self.get_pnl_gl_entry(net_pl_balance)
|
||||||
"credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0,
|
gl_entries.append(gl_entry)
|
||||||
"credit": abs(net_pl_balance) if net_pl_balance < 0 else 0,
|
|
||||||
"cost_center": cost_center
|
|
||||||
})
|
|
||||||
|
|
||||||
for dimension in accounting_dimensions:
|
|
||||||
gl_entry.update({
|
|
||||||
dimension: default_dimensions.get(self.company, {}).get(dimension)
|
|
||||||
})
|
|
||||||
|
|
||||||
gl_entries.append(gl_entry)
|
|
||||||
|
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries
|
from erpnext.accounts.general_ledger import make_gl_entries
|
||||||
make_gl_entries(gl_entries)
|
make_gl_entries(gl_entries)
|
||||||
|
|
||||||
def get_pl_balances(self, dimension_fields):
|
def get_pnl_gl_entry(self, net_pl_balance):
|
||||||
"""Get balance for Profit and Loss accounts, only including valid transactions (not cancelled)"""
|
cost_center = frappe.db.get_value("Company", self.company, "cost_center")
|
||||||
|
gl_entry = self.get_gl_dict({
|
||||||
|
"account": self.closing_account_head,
|
||||||
|
"debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0,
|
||||||
|
"debit": abs(net_pl_balance) if net_pl_balance > 0 else 0,
|
||||||
|
"credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0,
|
||||||
|
"credit": abs(net_pl_balance) if net_pl_balance < 0 else 0,
|
||||||
|
"cost_center": cost_center
|
||||||
|
})
|
||||||
|
|
||||||
|
self.update_default_dimensions(gl_entry)
|
||||||
|
|
||||||
|
return gl_entry
|
||||||
|
|
||||||
|
def get_costcenter_wise_pnl_gl_entries(self, pl_accounts):
|
||||||
|
company_cost_center = frappe.db.get_value("Company", self.company, "cost_center")
|
||||||
|
gl_entries = []
|
||||||
|
|
||||||
|
for acc in pl_accounts:
|
||||||
|
if flt(acc.bal_in_company_currency):
|
||||||
|
gl_entry = self.get_gl_dict({
|
||||||
|
"account": self.closing_account_head,
|
||||||
|
"cost_center": acc.cost_center or company_cost_center,
|
||||||
|
"account_currency": acc.account_currency,
|
||||||
|
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0,
|
||||||
|
"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0,
|
||||||
|
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) < 0 else 0,
|
||||||
|
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0
|
||||||
|
}, item=acc)
|
||||||
|
|
||||||
|
self.update_default_dimensions(gl_entry)
|
||||||
|
|
||||||
|
gl_entries.append(gl_entry)
|
||||||
|
|
||||||
|
return gl_entries
|
||||||
|
|
||||||
|
def update_default_dimensions(self, gl_entry):
|
||||||
|
if not self.accounting_dimensions:
|
||||||
|
self.accounting_dimensions = get_accounting_dimensions()
|
||||||
|
|
||||||
|
_, default_dimensions = get_dimensions()
|
||||||
|
for dimension in self.accounting_dimensions:
|
||||||
|
gl_entry.update({
|
||||||
|
dimension: default_dimensions.get(self.company, {}).get(dimension)
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_pl_balances(self):
|
||||||
|
"""Get balance for dimension-wise pl accounts"""
|
||||||
|
|
||||||
|
dimension_fields = ['t1.cost_center']
|
||||||
|
|
||||||
|
self.accounting_dimensions = get_accounting_dimensions()
|
||||||
|
for dimension in self.accounting_dimensions:
|
||||||
|
dimension_fields.append('t1.{0}'.format(dimension))
|
||||||
|
|
||||||
return frappe.db.sql("""
|
return frappe.db.sql("""
|
||||||
select
|
select
|
||||||
t1.account, t2.account_currency, {dimension_fields},
|
t1.account, t2.account_currency, {dimension_fields},
|
||||||
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as balance_in_account_currency,
|
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency,
|
||||||
sum(t1.debit) - sum(t1.credit) as balance_in_company_currency
|
sum(t1.debit) - sum(t1.credit) as bal_in_company_currency
|
||||||
from `tabGL Entry` t1, `tabAccount` t2
|
from `tabGL Entry` t1, `tabAccount` t2
|
||||||
where t1.is_cancelled = 0 and t1.account = t2.name and t2.report_type = 'Profit and Loss'
|
where t1.is_cancelled = 0 and t1.account = t2.name and t2.report_type = 'Profit and Loss'
|
||||||
and t2.docstatus < 2 and t2.company = %s
|
and t2.docstatus < 2 and t2.company = %s
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import frappe
|
|||||||
from frappe.utils import flt, today
|
from frappe.utils import flt, today
|
||||||
from erpnext.accounts.utils import get_fiscal_year, now
|
from erpnext.accounts.utils import get_fiscal_year, now
|
||||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
|
||||||
class TestPeriodClosingVoucher(unittest.TestCase):
|
class TestPeriodClosingVoucher(unittest.TestCase):
|
||||||
def test_closing_entry(self):
|
def test_closing_entry(self):
|
||||||
@@ -65,6 +66,58 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
|||||||
self.assertEqual(gle_for_random_expense_account[0].amount_in_account_currency,
|
self.assertEqual(gle_for_random_expense_account[0].amount_in_account_currency,
|
||||||
-1*random_expense_account[0].balance_in_account_currency)
|
-1*random_expense_account[0].balance_in_account_currency)
|
||||||
|
|
||||||
|
def test_cost_center_wise_posting(self):
|
||||||
|
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
|
||||||
|
|
||||||
|
company = create_company()
|
||||||
|
surplus_account = create_account()
|
||||||
|
|
||||||
|
cost_center1 = create_cost_center("Test Cost Center 1")
|
||||||
|
cost_center2 = create_cost_center("Test Cost Center 2")
|
||||||
|
|
||||||
|
create_sales_invoice(
|
||||||
|
company=company,
|
||||||
|
cost_center=cost_center1,
|
||||||
|
income_account="Sales - TPC",
|
||||||
|
expense_account="Cost of Goods Sold - TPC",
|
||||||
|
rate=400,
|
||||||
|
debit_to="Debtors - TPC"
|
||||||
|
)
|
||||||
|
create_sales_invoice(
|
||||||
|
company=company,
|
||||||
|
cost_center=cost_center2,
|
||||||
|
income_account="Sales - TPC",
|
||||||
|
expense_account="Cost of Goods Sold - TPC",
|
||||||
|
rate=200,
|
||||||
|
debit_to="Debtors - TPC"
|
||||||
|
)
|
||||||
|
|
||||||
|
pcv = frappe.get_doc({
|
||||||
|
"transaction_date": today(),
|
||||||
|
"posting_date": today(),
|
||||||
|
"fiscal_year": get_fiscal_year(today())[0],
|
||||||
|
"company": "Test PCV Company",
|
||||||
|
"cost_center_wise_pnl": 1,
|
||||||
|
"closing_account_head": surplus_account,
|
||||||
|
"remarks": "Test",
|
||||||
|
"doctype": "Period Closing Voucher"
|
||||||
|
})
|
||||||
|
pcv.insert()
|
||||||
|
pcv.submit()
|
||||||
|
|
||||||
|
expected_gle = (
|
||||||
|
('Sales - TPC', 200.0, 0.0, cost_center2),
|
||||||
|
(surplus_account, 0.0, 200.0, cost_center2),
|
||||||
|
('Sales - TPC', 400.0, 0.0, cost_center1),
|
||||||
|
(surplus_account, 0.0, 400.0, cost_center1)
|
||||||
|
)
|
||||||
|
|
||||||
|
pcv_gle = frappe.db.sql("""
|
||||||
|
select account, debit, credit, cost_center from `tabGL Entry` where voucher_no=%s
|
||||||
|
""", (pcv.name))
|
||||||
|
|
||||||
|
self.assertTrue(pcv_gle, expected_gle)
|
||||||
|
|
||||||
def make_period_closing_voucher(self):
|
def make_period_closing_voucher(self):
|
||||||
pcv = frappe.get_doc({
|
pcv = frappe.get_doc({
|
||||||
"doctype": "Period Closing Voucher",
|
"doctype": "Period Closing Voucher",
|
||||||
@@ -80,6 +133,38 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
|||||||
|
|
||||||
return pcv
|
return pcv
|
||||||
|
|
||||||
|
def create_company():
|
||||||
|
company = frappe.get_doc({
|
||||||
|
'doctype': 'Company',
|
||||||
|
'company_name': "Test PCV Company",
|
||||||
|
'country': 'United States',
|
||||||
|
'default_currency': 'USD'
|
||||||
|
})
|
||||||
|
company.insert(ignore_if_duplicate = True)
|
||||||
|
return company.name
|
||||||
|
|
||||||
|
def create_account():
|
||||||
|
account = frappe.get_doc({
|
||||||
|
"account_name": "Reserve and Surplus",
|
||||||
|
"is_group": 0,
|
||||||
|
"company": "Test PCV Company",
|
||||||
|
"root_type": "Liability",
|
||||||
|
"report_type": "Balance Sheet",
|
||||||
|
"account_currency": "USD",
|
||||||
|
"parent_account": "Current Liabilities - TPC",
|
||||||
|
"doctype": "Account"
|
||||||
|
}).insert(ignore_if_duplicate = True)
|
||||||
|
return account.name
|
||||||
|
|
||||||
|
def create_cost_center(cc_name):
|
||||||
|
costcenter = frappe.get_doc({
|
||||||
|
"company": "Test PCV Company",
|
||||||
|
"cost_center_name": cc_name,
|
||||||
|
"doctype": "Cost Center",
|
||||||
|
"parent_cost_center": "Test PCV Company - TPC"
|
||||||
|
})
|
||||||
|
costcenter.insert(ignore_if_duplicate = True)
|
||||||
|
return costcenter.name
|
||||||
|
|
||||||
test_dependencies = ["Customer", "Cost Center"]
|
test_dependencies = ["Customer", "Cost Center"]
|
||||||
test_records = frappe.get_test_records("Period Closing Voucher")
|
test_records = frappe.get_test_records("Period Closing Voucher")
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ frappe.ui.form.on('POS Closing Entry', {
|
|||||||
frm.set_value("taxes", []);
|
frm.set_value("taxes", []);
|
||||||
|
|
||||||
for (let row of frm.doc.payment_reconciliation) {
|
for (let row of frm.doc.payment_reconciliation) {
|
||||||
row.expected_amount = 0;
|
row.expected_amount = row.opening_amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let row of frm.doc.pos_transactions) {
|
for (let row of frm.doc.pos_transactions) {
|
||||||
@@ -154,6 +154,9 @@ function add_to_pos_transaction(d, frm) {
|
|||||||
function refresh_payments(d, frm) {
|
function refresh_payments(d, frm) {
|
||||||
d.payments.forEach(p => {
|
d.payments.forEach(p => {
|
||||||
const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment);
|
const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment);
|
||||||
|
if (p.account == d.account_for_change_amount) {
|
||||||
|
p.amount -= flt(d.change_amount);
|
||||||
|
}
|
||||||
if (payment) {
|
if (payment) {
|
||||||
payment.expected_amount += flt(p.amount);
|
payment.expected_amount += flt(p.amount);
|
||||||
payment.difference = payment.closing_amount - payment.expected_amount;
|
payment.difference = payment.closing_amount - payment.expected_amount;
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
return
|
return
|
||||||
|
|
||||||
available_stock = get_stock_availability(d.item_code, d.warehouse)
|
available_stock = get_stock_availability(d.item_code, d.warehouse)
|
||||||
|
|
||||||
item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)
|
item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)
|
||||||
if flt(available_stock) <= 0:
|
if flt(available_stock) <= 0:
|
||||||
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.')
|
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.')
|
||||||
@@ -213,8 +214,9 @@ class POSInvoice(SalesInvoice):
|
|||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item")
|
is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item")
|
||||||
if not is_stock_item:
|
if not is_stock_item:
|
||||||
frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ")
|
if not frappe.db.exists('Product Bundle', d.item_code):
|
||||||
.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice.")
|
||||||
|
.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
||||||
|
|
||||||
def validate_mode_of_payment(self):
|
def validate_mode_of_payment(self):
|
||||||
if len(self.payments) == 0:
|
if len(self.payments) == 0:
|
||||||
@@ -455,27 +457,42 @@ 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
|
if frappe.db.get_value('Item', item_code, 'is_stock_item'):
|
||||||
from `tabStock Ledger Entry`
|
bin_qty = get_bin_qty(item_code, warehouse)
|
||||||
|
pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
|
||||||
|
return bin_qty - pos_sales_qty
|
||||||
|
else:
|
||||||
|
if frappe.db.exists('Product Bundle', item_code):
|
||||||
|
return get_bundle_availability(item_code, warehouse)
|
||||||
|
|
||||||
|
def get_bundle_availability(bundle_item_code, warehouse):
|
||||||
|
product_bundle = frappe.get_doc('Product Bundle', bundle_item_code)
|
||||||
|
|
||||||
|
bundle_bin_qty = 1000000
|
||||||
|
for item in product_bundle.items:
|
||||||
|
item_bin_qty = get_bin_qty(item.item_code, warehouse)
|
||||||
|
item_pos_reserved_qty = get_pos_reserved_qty(item.item_code, warehouse)
|
||||||
|
available_qty = item_bin_qty - item_pos_reserved_qty
|
||||||
|
|
||||||
|
max_available_bundles = available_qty / item.qty
|
||||||
|
if bundle_bin_qty > max_available_bundles:
|
||||||
|
bundle_bin_qty = max_available_bundles
|
||||||
|
|
||||||
|
pos_sales_qty = get_pos_reserved_qty(bundle_item_code, warehouse)
|
||||||
|
return bundle_bin_qty - pos_sales_qty
|
||||||
|
|
||||||
|
def get_bin_qty(item_code, warehouse):
|
||||||
|
bin_qty = frappe.db.sql("""select actual_qty from `tabBin`
|
||||||
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)
|
return bin_qty[0].actual_qty or 0 if bin_qty else 0
|
||||||
|
|
||||||
sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
|
|
||||||
|
|
||||||
if sle_qty and 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
|
||||||
|
|||||||
@@ -42,8 +42,9 @@ class POSInvoiceMergeLog(Document):
|
|||||||
if return_against_status != "Consolidated":
|
if return_against_status != "Consolidated":
|
||||||
# if return entry is not getting merged in the current pos closing and if it is not consolidated
|
# if return entry is not getting merged in the current pos closing and if it is not consolidated
|
||||||
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
|
||||||
@@ -20,9 +20,9 @@ from frappe.utils import cint, flt, get_link_to_form, getdate, today, fmt_money
|
|||||||
class MultiplePricingRuleConflict(frappe.ValidationError): pass
|
class MultiplePricingRuleConflict(frappe.ValidationError): pass
|
||||||
|
|
||||||
apply_on_table = {
|
apply_on_table = {
|
||||||
'Item Code': 'items',
|
'Item Code': 'items',
|
||||||
'Item Group': 'item_groups',
|
'Item Group': 'item_groups',
|
||||||
'Brand': 'brands'
|
'Brand': 'brands'
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_pricing_rules(args, doc=None):
|
def get_pricing_rules(args, doc=None):
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
|||||||
frappe.show_alert({message: __('Emails Queued'), indicator: 'blue'});
|
frappe.show_alert({message: __('Emails Queued'), indicator: 'blue'});
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
frappe.msgprint('No Records for these settings.')
|
frappe.msgprint(__('No Records for these settings.'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -33,7 +33,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
|||||||
type: 'GET',
|
type: 'GET',
|
||||||
success: function(result) {
|
success: function(result) {
|
||||||
if(jQuery.isEmptyObject(result)){
|
if(jQuery.isEmptyObject(result)){
|
||||||
frappe.msgprint('No Records for these settings.');
|
frappe.msgprint(__('No Records for these settings.'));
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
window.location = url;
|
window.location = url;
|
||||||
@@ -92,7 +92,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
|||||||
frm.refresh_field('customers');
|
frm.refresh_field('customers');
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
frappe.throw('No Customers found with selected options.');
|
frappe.throw(__('No Customers found with selected options.'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -592,11 +592,12 @@
|
|||||||
"label": "Raw Materials Supplied"
|
"label": "Raw Materials Supplied"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "update_stock",
|
||||||
"fieldname": "supplied_items",
|
"fieldname": "supplied_items",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Supplied Items",
|
"label": "Supplied Items",
|
||||||
"options": "Purchase Receipt Item Supplied",
|
"no_copy": 1,
|
||||||
"read_only": 1
|
"options": "Purchase Receipt Item Supplied"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_26",
|
"fieldname": "section_break_26",
|
||||||
@@ -837,6 +838,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:!doc.disable_rounded_total",
|
||||||
"fieldname": "base_rounding_adjustment",
|
"fieldname": "base_rounding_adjustment",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Rounding Adjustment (Company Currency)",
|
"label": "Rounding Adjustment (Company Currency)",
|
||||||
@@ -883,6 +885,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:!doc.disable_rounded_total",
|
||||||
"fieldname": "rounding_adjustment",
|
"fieldname": "rounding_adjustment",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Rounding Adjustment",
|
"label": "Rounding Adjustment",
|
||||||
@@ -1380,7 +1383,7 @@
|
|||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-04-30 22:45:58.334107",
|
"modified": "2021-06-15 18:20:56.806195",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
|||||||
@@ -68,9 +68,6 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
super(PurchaseInvoice, self).validate()
|
super(PurchaseInvoice, self).validate()
|
||||||
|
|
||||||
# apply tax withholding only if checked and applicable
|
|
||||||
self.set_tax_withholding()
|
|
||||||
|
|
||||||
if not self.is_return:
|
if not self.is_return:
|
||||||
self.po_required()
|
self.po_required()
|
||||||
self.pr_required()
|
self.pr_required()
|
||||||
@@ -251,11 +248,9 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
if self.update_stock and (not item.from_warehouse):
|
if self.update_stock and (not item.from_warehouse):
|
||||||
if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
|
if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
|
||||||
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]))
|
msg = _("Row {0}: Expense Head changed to {1} because account {2} is not linked to warehouse {3} or it is not the default inventory account").format(
|
||||||
msg += _("because account {} is not linked to warehouse {} ").format(frappe.bold(item.expense_account), frappe.bold(item.warehouse))
|
item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]), frappe.bold(item.expense_account), frappe.bold(item.warehouse))
|
||||||
msg += _("or it is not the default inventory account")
|
|
||||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||||
|
|
||||||
item.expense_account = warehouse_account[item.warehouse]["account"]
|
item.expense_account = warehouse_account[item.warehouse]["account"]
|
||||||
else:
|
else:
|
||||||
# check if 'Stock Received But Not Billed' account is credited in Purchase receipt or not
|
# check if 'Stock Received But Not Billed' account is credited in Purchase receipt or not
|
||||||
@@ -266,8 +261,8 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
if negative_expense_booked_in_pr:
|
if negative_expense_booked_in_pr:
|
||||||
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
||||||
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account))
|
msg = _("Row {0}: Expense Head changed to {1} because expense is booked against this account in Purchase Receipt {2}").format(
|
||||||
msg += _("because expense is booked against this account in Purchase Receipt {}").format(frappe.bold(item.purchase_receipt))
|
item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.purchase_receipt))
|
||||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||||
|
|
||||||
item.expense_account = stock_not_billed_account
|
item.expense_account = stock_not_billed_account
|
||||||
@@ -275,8 +270,9 @@ class PurchaseInvoice(BuyingController):
|
|||||||
# If no purchase receipt present then book expense in 'Stock Received But Not Billed'
|
# If no purchase receipt present then book expense in 'Stock Received But Not Billed'
|
||||||
# This is done in cases when Purchase Invoice is created before Purchase Receipt
|
# This is done in cases when Purchase Invoice is created before Purchase Receipt
|
||||||
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
||||||
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account))
|
msg = _("Row {0}: Expense Head changed to {1} as no Purchase Receipt is created against Item {2}.").format(
|
||||||
msg += _("as no Purchase Receipt is created against Item {}. ").format(frappe.bold(item.item_code))
|
item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.item_code))
|
||||||
|
msg += "<br>"
|
||||||
msg += _("This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice")
|
msg += _("This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice")
|
||||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||||
|
|
||||||
@@ -308,8 +304,8 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if not d.purchase_order:
|
if not d.purchase_order:
|
||||||
msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code))
|
msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code))
|
||||||
msg += "<br><br>"
|
msg += "<br><br>"
|
||||||
msg += _("To submit the invoice without purchase order please set {} ").format(frappe.bold(_('Purchase Order Required')))
|
msg += _("To submit the invoice without purchase order please set {0} as {1} in {2}").format(
|
||||||
msg += _("as {} in {}").format(frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
|
frappe.bold(_('Purchase Order Required')), frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
|
||||||
throw(msg, title=_("Mandatory Purchase Order"))
|
throw(msg, title=_("Mandatory Purchase Order"))
|
||||||
|
|
||||||
def pr_required(self):
|
def pr_required(self):
|
||||||
@@ -323,8 +319,8 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if not d.purchase_receipt and d.item_code in stock_items:
|
if not d.purchase_receipt and d.item_code in stock_items:
|
||||||
msg = _("Purchase Receipt Required for item {}").format(frappe.bold(d.item_code))
|
msg = _("Purchase Receipt Required for item {}").format(frappe.bold(d.item_code))
|
||||||
msg += "<br><br>"
|
msg += "<br><br>"
|
||||||
msg += _("To submit the invoice without purchase receipt please set {} ").format(frappe.bold(_('Purchase Receipt Required')))
|
msg += _("To submit the invoice without purchase receipt please set {0} as {1} in {2}").format(
|
||||||
msg += _("as {} in {}").format(frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
|
frappe.bold(_('Purchase Receipt Required')), frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
|
||||||
throw(msg, title=_("Mandatory Purchase Receipt"))
|
throw(msg, title=_("Mandatory Purchase Receipt"))
|
||||||
|
|
||||||
def validate_write_off_account(self):
|
def validate_write_off_account(self):
|
||||||
@@ -404,6 +400,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
# because updating ordered qty in bin depends upon updated ordered qty in PO
|
# because updating ordered qty in bin depends upon updated ordered qty in PO
|
||||||
if self.update_stock == 1:
|
if self.update_stock == 1:
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
|
self.set_consumed_qty_in_po()
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
|
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
|
||||||
update_serial_nos_after_submit(self, "items")
|
update_serial_nos_after_submit(self, "items")
|
||||||
|
|
||||||
@@ -456,6 +453,8 @@ class PurchaseInvoice(BuyingController):
|
|||||||
self.make_tax_gl_entries(gl_entries)
|
self.make_tax_gl_entries(gl_entries)
|
||||||
self.make_internal_transfer_gl_entries(gl_entries)
|
self.make_internal_transfer_gl_entries(gl_entries)
|
||||||
|
|
||||||
|
self.allocate_advance_taxes(gl_entries)
|
||||||
|
|
||||||
gl_entries = make_regional_gl_entries(gl_entries, self)
|
gl_entries = make_regional_gl_entries(gl_entries, self)
|
||||||
|
|
||||||
gl_entries = merge_similar_entries(gl_entries)
|
gl_entries = merge_similar_entries(gl_entries)
|
||||||
@@ -1000,6 +999,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if self.update_stock == 1:
|
if self.update_stock == 1:
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
self.delete_auto_created_batches()
|
self.delete_auto_created_batches()
|
||||||
|
self.set_consumed_qty_in_po()
|
||||||
|
|
||||||
self.make_gl_entries_on_cancel()
|
self.make_gl_entries_on_cancel()
|
||||||
|
|
||||||
@@ -1090,6 +1090,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
for d in self.taxes:
|
for d in self.taxes:
|
||||||
if d.account_head == tax_withholding_details.get("account_head"):
|
if d.account_head == tax_withholding_details.get("account_head"):
|
||||||
d.update(tax_withholding_details)
|
d.update(tax_withholding_details)
|
||||||
|
|
||||||
accounts.append(d.account_head)
|
accounts.append(d.account_head)
|
||||||
|
|
||||||
if not accounts or tax_withholding_details.get("account_head") not in accounts:
|
if not accounts or tax_withholding_details.get("account_head") not in accounts:
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_tra
|
|||||||
from erpnext.projects.doctype.project.test_project import make_project
|
from erpnext.projects.doctype.project.test_project import make_project
|
||||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account
|
from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account
|
||||||
from erpnext.stock.doctype.item.test_item import create_item
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
|
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||||
|
|
||||||
test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"]
|
test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"]
|
||||||
test_ignore = ["Serial No"]
|
test_ignore = ["Serial No"]
|
||||||
@@ -620,8 +621,10 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(actual_qty_0, get_qty_after_transaction())
|
self.assertEqual(actual_qty_0, get_qty_after_transaction())
|
||||||
|
|
||||||
def test_subcontracting_via_purchase_invoice(self):
|
def test_subcontracting_via_purchase_invoice(self):
|
||||||
|
from erpnext.buying.doctype.purchase_order.test_purchase_order import update_backflush_based_on
|
||||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
|
|
||||||
|
update_backflush_based_on('BOM')
|
||||||
make_stock_entry(item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100)
|
make_stock_entry(item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100)
|
||||||
make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse 1 - _TC",
|
make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse 1 - _TC",
|
||||||
qty=100, basic_rate=100)
|
qty=100, basic_rate=100)
|
||||||
@@ -631,7 +634,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):
|
||||||
@@ -950,6 +953,102 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
acc_settings.submit_journal_entriessubmit_journal_entries = 0
|
acc_settings.submit_journal_entriessubmit_journal_entries = 0
|
||||||
acc_settings.save()
|
acc_settings.save()
|
||||||
|
|
||||||
|
def test_purchase_invoice_advance_taxes(self):
|
||||||
|
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||||
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
|
from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice
|
||||||
|
|
||||||
|
# create a new supplier to test
|
||||||
|
supplier = create_supplier(supplier_name = '_Test TDS Advance Supplier',
|
||||||
|
tax_withholding_category = 'TDS - 194 - Dividends - Individual')
|
||||||
|
|
||||||
|
# Update tax withholding category with current fiscal year and rate details
|
||||||
|
update_tax_witholding_category('_Test Company', 'TDS Payable - _TC', nowdate())
|
||||||
|
|
||||||
|
# Create Purchase Order with TDS applied
|
||||||
|
po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000)
|
||||||
|
po.apply_tds = 1
|
||||||
|
po.tax_withholding_category = 'TDS - 194 - Dividends - Individual'
|
||||||
|
po.save()
|
||||||
|
po.submit()
|
||||||
|
|
||||||
|
# Update Unrealized Profit / Loss Account which is used as default advance tax account
|
||||||
|
frappe.db.set_value('Company', '_Test Company', 'unrealized_profit_loss_account', '_Test Account Excise Duty - _TC')
|
||||||
|
|
||||||
|
# Create Payment Entry Against the order
|
||||||
|
payment_entry = get_payment_entry(dt='Purchase Order', dn=po.name)
|
||||||
|
payment_entry.paid_from = 'Cash - _TC'
|
||||||
|
payment_entry.save()
|
||||||
|
payment_entry.submit()
|
||||||
|
|
||||||
|
# Check GLE for Payment Entry
|
||||||
|
expected_gle = [
|
||||||
|
['_Test Account Excise Duty - _TC', 3000, 0],
|
||||||
|
['Cash - _TC', 0, 27000],
|
||||||
|
['Creditors - _TC', 27000, 0],
|
||||||
|
['TDS Payable - _TC', 0, 3000],
|
||||||
|
]
|
||||||
|
|
||||||
|
gl_entries = frappe.db.sql("""select account, debit, credit
|
||||||
|
from `tabGL Entry`
|
||||||
|
where voucher_type='Payment Entry' and voucher_no=%s
|
||||||
|
order by account asc""", (payment_entry.name), as_dict=1)
|
||||||
|
|
||||||
|
for i, gle in enumerate(gl_entries):
|
||||||
|
self.assertEqual(expected_gle[i][0], gle.account)
|
||||||
|
self.assertEqual(expected_gle[i][1], gle.debit)
|
||||||
|
self.assertEqual(expected_gle[i][2], gle.credit)
|
||||||
|
|
||||||
|
# Create Purchase Invoice against Purchase Order
|
||||||
|
purchase_invoice = get_mapped_purchase_invoice(po.name)
|
||||||
|
purchase_invoice.allocate_advances_automatically = 1
|
||||||
|
purchase_invoice.items[0].expense_account = '_Test Account Cost for Goods Sold - _TC'
|
||||||
|
purchase_invoice.save()
|
||||||
|
purchase_invoice.submit()
|
||||||
|
|
||||||
|
# Check GLE for Purchase Invoice
|
||||||
|
# Zero net effect on final TDS Payable on invoice
|
||||||
|
expected_gle = [
|
||||||
|
['_Test Account Cost for Goods Sold - _TC', 30000, 0],
|
||||||
|
['_Test Account Excise Duty - _TC', 0, 3000],
|
||||||
|
['Creditors - _TC', 0, 27000],
|
||||||
|
['TDS Payable - _TC', 3000, 3000]
|
||||||
|
]
|
||||||
|
|
||||||
|
gl_entries = frappe.db.sql("""select account, debit, credit
|
||||||
|
from `tabGL Entry`
|
||||||
|
where voucher_type='Purchase Invoice' and voucher_no=%s
|
||||||
|
order by account asc""", (purchase_invoice.name), as_dict=1)
|
||||||
|
|
||||||
|
for i, gle in enumerate(gl_entries):
|
||||||
|
self.assertEqual(expected_gle[i][0], gle.account)
|
||||||
|
self.assertEqual(expected_gle[i][1], gle.debit)
|
||||||
|
self.assertEqual(expected_gle[i][2], gle.credit)
|
||||||
|
|
||||||
|
def update_tax_witholding_category(company, account, date):
|
||||||
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
|
|
||||||
|
fiscal_year = get_fiscal_year(date=date, company=company)
|
||||||
|
|
||||||
|
if not frappe.db.get_value('Tax Withholding Rate',
|
||||||
|
{'parent': 'TDS - 194 - Dividends - Individual', 'fiscal_year': fiscal_year[0]}):
|
||||||
|
tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual')
|
||||||
|
tds_category.append('rates', {
|
||||||
|
'fiscal_year': fiscal_year[0],
|
||||||
|
'tax_withholding_rate': 10,
|
||||||
|
'single_threshold': 2500,
|
||||||
|
'cumulative_threshold': 0
|
||||||
|
})
|
||||||
|
tds_category.save()
|
||||||
|
|
||||||
|
if not frappe.db.get_value('Tax Withholding Account',
|
||||||
|
{'parent': 'TDS - 194 - Dividends - Individual', 'account': account}):
|
||||||
|
tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual')
|
||||||
|
tds_category.append('accounts', {
|
||||||
|
'company': company,
|
||||||
|
'account': account
|
||||||
|
})
|
||||||
|
tds_category.save()
|
||||||
|
|
||||||
def unlink_payment_on_cancel_of_invoice(enable=1):
|
def unlink_payment_on_cancel_of_invoice(enable=1):
|
||||||
accounts_settings = frappe.get_doc("Accounts Settings")
|
accounts_settings = frappe.get_doc("Accounts Settings")
|
||||||
|
|||||||
@@ -272,7 +272,7 @@
|
|||||||
"fieldname": "rate",
|
"fieldname": "rate",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Rate ",
|
"label": "Rate",
|
||||||
"oldfieldname": "import_rate",
|
"oldfieldname": "import_rate",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"options": "currency",
|
"options": "currency",
|
||||||
@@ -854,7 +854,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-03-30 09:02:39.256602",
|
"modified": "2021-06-16 19:33:51.099386",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice Item",
|
"name": "Purchase Invoice Item",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"charge_type",
|
"charge_type",
|
||||||
"row_id",
|
"row_id",
|
||||||
"included_in_print_rate",
|
"included_in_print_rate",
|
||||||
|
"included_in_paid_amount",
|
||||||
"col_break1",
|
"col_break1",
|
||||||
"account_head",
|
"account_head",
|
||||||
"description",
|
"description",
|
||||||
@@ -21,6 +22,7 @@
|
|||||||
"cost_center",
|
"cost_center",
|
||||||
"dimension_col_break",
|
"dimension_col_break",
|
||||||
"section_break_9",
|
"section_break_9",
|
||||||
|
"currency",
|
||||||
"tax_amount",
|
"tax_amount",
|
||||||
"tax_amount_after_discount_amount",
|
"tax_amount_after_discount_amount",
|
||||||
"total",
|
"total",
|
||||||
@@ -205,12 +207,28 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "dimension_col_break",
|
"fieldname": "dimension_col_break",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "account_head.account_currency",
|
||||||
|
"fieldname": "currency",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Account Currency",
|
||||||
|
"options": "Currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:['Purchase Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)",
|
||||||
|
"description": "If checked, the tax amount will be considered as already included in the Paid Amount in Payment Entry",
|
||||||
|
"fieldname": "included_in_paid_amount",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Considered In Paid Amount"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-09-18 17:26:09.703215",
|
"modified": "2021-06-14 01:43:50.750455",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Taxes and Charges",
|
"name": "Purchase Taxes and Charges",
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
|||||||
var me = this;
|
var me = this;
|
||||||
super.onload();
|
super.onload();
|
||||||
|
|
||||||
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet'];
|
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log', 'POS Closing Entry'];
|
||||||
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
||||||
// show debit_to in print format
|
// show debit_to in print format
|
||||||
this.frm.set_df_property("debit_to", "print_hide", 0);
|
this.frm.set_df_property("debit_to", "print_hide", 0);
|
||||||
@@ -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
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -531,7 +531,7 @@ class SalesInvoice(SellingController):
|
|||||||
# set pos values in items
|
# set pos values in items
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if item.get('item_code'):
|
if item.get('item_code'):
|
||||||
profile_details = get_pos_profile_item_details(pos, frappe._dict(item.as_dict()), pos)
|
profile_details = get_pos_profile_item_details(pos, frappe._dict(item.as_dict()), pos, update_data=True)
|
||||||
for fname, val in iteritems(profile_details):
|
for fname, val in iteritems(profile_details):
|
||||||
if (not for_validate) or (for_validate and not item.get(fname)):
|
if (not for_validate) or (for_validate and not item.get(fname)):
|
||||||
item.set(fname, val)
|
item.set(fname, val)
|
||||||
@@ -842,6 +842,8 @@ class SalesInvoice(SellingController):
|
|||||||
self.make_tax_gl_entries(gl_entries)
|
self.make_tax_gl_entries(gl_entries)
|
||||||
self.make_internal_transfer_gl_entries(gl_entries)
|
self.make_internal_transfer_gl_entries(gl_entries)
|
||||||
|
|
||||||
|
self.allocate_advance_taxes(gl_entries)
|
||||||
|
|
||||||
self.make_item_gl_entries(gl_entries)
|
self.make_item_gl_entries(gl_entries)
|
||||||
|
|
||||||
# merge gl entries before adding pos entries
|
# merge gl entries before adding pos entries
|
||||||
@@ -849,7 +851,6 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
self.make_loyalty_point_redemption_gle(gl_entries)
|
self.make_loyalty_point_redemption_gle(gl_entries)
|
||||||
self.make_pos_gl_entries(gl_entries)
|
self.make_pos_gl_entries(gl_entries)
|
||||||
self.make_gle_for_change_amount(gl_entries)
|
|
||||||
|
|
||||||
self.make_write_off_gl_entry(gl_entries)
|
self.make_write_off_gl_entry(gl_entries)
|
||||||
self.make_gle_for_rounding_adjustment(gl_entries)
|
self.make_gle_for_rounding_adjustment(gl_entries)
|
||||||
@@ -987,7 +988,13 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
def make_pos_gl_entries(self, gl_entries):
|
def make_pos_gl_entries(self, gl_entries):
|
||||||
if cint(self.is_pos):
|
if cint(self.is_pos):
|
||||||
|
|
||||||
|
skip_change_gl_entries = not cint(frappe.db.get_single_value('Accounts Settings', 'post_change_gl_entries'))
|
||||||
|
|
||||||
for payment_mode in self.payments:
|
for payment_mode in self.payments:
|
||||||
|
if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount:
|
||||||
|
payment_mode.base_amount -= flt(self.change_amount)
|
||||||
|
|
||||||
if payment_mode.amount:
|
if payment_mode.amount:
|
||||||
# POS, make payment entries
|
# POS, make payment entries
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
@@ -1019,8 +1026,11 @@ class SalesInvoice(SellingController):
|
|||||||
}, payment_mode_account_currency, item=self)
|
}, payment_mode_account_currency, item=self)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not skip_change_gl_entries:
|
||||||
|
self.make_gle_for_change_amount(gl_entries)
|
||||||
|
|
||||||
def make_gle_for_change_amount(self, gl_entries):
|
def make_gle_for_change_amount(self, gl_entries):
|
||||||
if cint(self.is_pos) and self.change_amount:
|
if self.change_amount:
|
||||||
if self.account_for_change_amount:
|
if self.account_for_change_amount:
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict({
|
self.get_gl_dict({
|
||||||
@@ -1141,7 +1151,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:
|
||||||
@@ -1172,26 +1181,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)
|
||||||
|
|||||||
@@ -713,7 +713,7 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
si.submit()
|
si.submit()
|
||||||
self.assertEqual(si.paid_amount, 100.0)
|
self.assertEqual(si.paid_amount, 100.0)
|
||||||
|
|
||||||
self.pos_gl_entry(si, pos, 50)
|
self.validate_pos_gl_entry(si, pos, 50)
|
||||||
|
|
||||||
def test_pos_returns_with_repayment(self):
|
def test_pos_returns_with_repayment(self):
|
||||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
|
||||||
@@ -749,7 +749,7 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||||
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
|
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
|
||||||
|
|
||||||
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
|
make_purchase_receipt(company= "_Test Company with perpetual inventory",
|
||||||
item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
|
item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
|
||||||
|
|
||||||
pos = create_sales_invoice(company= "_Test Company with perpetual inventory",
|
pos = create_sales_invoice(company= "_Test Company with perpetual inventory",
|
||||||
@@ -770,7 +770,45 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(pos.grand_total, 100.0)
|
self.assertEqual(pos.grand_total, 100.0)
|
||||||
self.assertEqual(pos.write_off_amount, -5)
|
self.assertEqual(pos.write_off_amount, -5)
|
||||||
|
|
||||||
def pos_gl_entry(self, si, pos, cash_amount):
|
def test_pos_with_no_gl_entry_for_change_amount(self):
|
||||||
|
frappe.db.set_value('Accounts Settings', None, 'post_change_gl_entries', 0)
|
||||||
|
|
||||||
|
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||||
|
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
|
||||||
|
|
||||||
|
make_purchase_receipt(company= "_Test Company with perpetual inventory",
|
||||||
|
item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
|
||||||
|
|
||||||
|
pos = create_sales_invoice(company= "_Test Company with perpetual inventory",
|
||||||
|
debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1",
|
||||||
|
income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1",
|
||||||
|
cost_center = "Main - TCP1", do_not_save=True)
|
||||||
|
|
||||||
|
pos.is_pos = 1
|
||||||
|
pos.update_stock = 1
|
||||||
|
|
||||||
|
taxes = get_taxes_and_charges()
|
||||||
|
pos.taxes = []
|
||||||
|
for tax in taxes:
|
||||||
|
pos.append("taxes", tax)
|
||||||
|
|
||||||
|
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50})
|
||||||
|
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 60})
|
||||||
|
|
||||||
|
pos.insert()
|
||||||
|
pos.submit()
|
||||||
|
|
||||||
|
self.assertEqual(pos.grand_total, 100.0)
|
||||||
|
self.assertEqual(pos.change_amount, 10)
|
||||||
|
|
||||||
|
self.validate_pos_gl_entry(pos, pos, 60, validate_without_change_gle=True)
|
||||||
|
|
||||||
|
frappe.db.set_value('Accounts Settings', None, 'post_change_gl_entries', 1)
|
||||||
|
|
||||||
|
def validate_pos_gl_entry(self, si, pos, cash_amount, validate_without_change_gle=False):
|
||||||
|
if validate_without_change_gle:
|
||||||
|
cash_amount -= pos.change_amount
|
||||||
|
|
||||||
# check stock ledger entries
|
# check stock ledger entries
|
||||||
sle = frappe.db.sql("""select * from `tabStock Ledger Entry`
|
sle = frappe.db.sql("""select * from `tabStock Ledger Entry`
|
||||||
where voucher_type = 'Sales Invoice' and voucher_no = %s""",
|
where voucher_type = 'Sales Invoice' and voucher_no = %s""",
|
||||||
@@ -933,12 +971,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
|
||||||
|
|
||||||
@@ -1905,69 +1937,80 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
frappe.flags.country = country
|
frappe.flags.country = country
|
||||||
|
|
||||||
def test_einvoice_json(self):
|
def test_einvoice_json(self):
|
||||||
from erpnext.regional.india.e_invoice.utils import make_einvoice
|
from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals
|
||||||
|
|
||||||
si = make_sales_invoice_for_ewaybill()
|
si = get_sales_invoice_for_e_invoice()
|
||||||
si.naming_series = 'INV-2020-.#####'
|
|
||||||
si.items = []
|
|
||||||
si.append("items", {
|
|
||||||
"item_code": "_Test Item",
|
|
||||||
"uom": "Nos",
|
|
||||||
"warehouse": "_Test Warehouse - _TC",
|
|
||||||
"qty": 2000,
|
|
||||||
"rate": 12,
|
|
||||||
"income_account": "Sales - _TC",
|
|
||||||
"expense_account": "Cost of Goods Sold - _TC",
|
|
||||||
"cost_center": "_Test Cost Center - _TC",
|
|
||||||
})
|
|
||||||
si.append("items", {
|
|
||||||
"item_code": "_Test Item 2",
|
|
||||||
"uom": "Nos",
|
|
||||||
"warehouse": "_Test Warehouse - _TC",
|
|
||||||
"qty": 420,
|
|
||||||
"rate": 15,
|
|
||||||
"income_account": "Sales - _TC",
|
|
||||||
"expense_account": "Cost of Goods Sold - _TC",
|
|
||||||
"cost_center": "_Test Cost Center - _TC",
|
|
||||||
})
|
|
||||||
si.discount_amount = 100
|
si.discount_amount = 100
|
||||||
si.save()
|
si.save()
|
||||||
|
|
||||||
einvoice = make_einvoice(si)
|
einvoice = make_einvoice(si)
|
||||||
|
|
||||||
total_item_ass_value = 0
|
|
||||||
total_item_cgst_value = 0
|
|
||||||
total_item_sgst_value = 0
|
|
||||||
total_item_igst_value = 0
|
|
||||||
total_item_value = 0
|
|
||||||
|
|
||||||
for item in einvoice['ItemList']:
|
|
||||||
total_item_ass_value += item['AssAmt']
|
|
||||||
total_item_cgst_value += item['CgstAmt']
|
|
||||||
total_item_sgst_value += item['SgstAmt']
|
|
||||||
total_item_igst_value += item['IgstAmt']
|
|
||||||
total_item_value += item['TotItemVal']
|
|
||||||
|
|
||||||
self.assertTrue(item['AssAmt'], item['TotAmt'] - item['Discount'])
|
|
||||||
self.assertTrue(item['TotItemVal'], item['AssAmt'] + item['CgstAmt'] + item['SgstAmt'] + item['IgstAmt'])
|
|
||||||
|
|
||||||
value_details = einvoice['ValDtls']
|
|
||||||
|
|
||||||
self.assertEqual(einvoice['Version'], '1.1')
|
|
||||||
self.assertEqual(value_details['AssVal'], total_item_ass_value)
|
|
||||||
self.assertEqual(value_details['CgstVal'], total_item_cgst_value)
|
|
||||||
self.assertEqual(value_details['SgstVal'], total_item_sgst_value)
|
|
||||||
self.assertEqual(value_details['IgstVal'], total_item_igst_value)
|
|
||||||
|
|
||||||
calculated_invoice_value = \
|
|
||||||
value_details['AssVal'] + value_details['CgstVal'] \
|
|
||||||
+ value_details['SgstVal'] + value_details['IgstVal'] \
|
|
||||||
+ value_details['OthChrg'] - value_details['Discount']
|
|
||||||
|
|
||||||
self.assertTrue(value_details['TotInvVal'] - calculated_invoice_value < 0.1)
|
|
||||||
|
|
||||||
self.assertEqual(value_details['TotInvVal'], si.base_grand_total)
|
|
||||||
self.assertTrue(einvoice['EwbDtls'])
|
self.assertTrue(einvoice['EwbDtls'])
|
||||||
|
validate_totals(einvoice)
|
||||||
|
|
||||||
|
si.apply_discount_on = 'Net Total'
|
||||||
|
si.save()
|
||||||
|
einvoice = make_einvoice(si)
|
||||||
|
validate_totals(einvoice)
|
||||||
|
|
||||||
|
[d.set('included_in_print_rate', 1) for d in si.taxes]
|
||||||
|
si.save()
|
||||||
|
einvoice = make_einvoice(si)
|
||||||
|
validate_totals(einvoice)
|
||||||
|
|
||||||
|
def get_sales_invoice_for_e_invoice():
|
||||||
|
si = make_sales_invoice_for_ewaybill()
|
||||||
|
si.naming_series = 'INV-2020-.#####'
|
||||||
|
si.items = []
|
||||||
|
si.append("items", {
|
||||||
|
"item_code": "_Test Item",
|
||||||
|
"uom": "Nos",
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"qty": 2000,
|
||||||
|
"rate": 12,
|
||||||
|
"income_account": "Sales - _TC",
|
||||||
|
"expense_account": "Cost of Goods Sold - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
})
|
||||||
|
|
||||||
|
si.append("items", {
|
||||||
|
"item_code": "_Test Item 2",
|
||||||
|
"uom": "Nos",
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"qty": 420,
|
||||||
|
"rate": 15,
|
||||||
|
"income_account": "Sales - _TC",
|
||||||
|
"expense_account": "Cost of Goods Sold - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
})
|
||||||
|
|
||||||
|
return si
|
||||||
|
|
||||||
|
def test_item_tax_net_range(self):
|
||||||
|
item = create_item("T Shirt")
|
||||||
|
|
||||||
|
item.set('taxes', [])
|
||||||
|
item.append("taxes", {
|
||||||
|
"item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
|
||||||
|
"minimum_net_rate": 0,
|
||||||
|
"maximum_net_rate": 500
|
||||||
|
})
|
||||||
|
|
||||||
|
item.append("taxes", {
|
||||||
|
"item_tax_template": "_Test Account Excise Duty @ 12 - _TC",
|
||||||
|
"minimum_net_rate": 501,
|
||||||
|
"maximum_net_rate": 1000
|
||||||
|
})
|
||||||
|
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
sales_invoice = create_sales_invoice(item = "T Shirt", rate=700, do_not_submit=True)
|
||||||
|
self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC")
|
||||||
|
|
||||||
|
# Apply discount
|
||||||
|
sales_invoice.apply_discount_on = 'Net Total'
|
||||||
|
sales_invoice.discount_amount = 300
|
||||||
|
sales_invoice.save()
|
||||||
|
self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC")
|
||||||
|
|
||||||
def make_test_address_for_ewaybill():
|
def make_test_address_for_ewaybill():
|
||||||
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
|
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
|
||||||
@@ -2091,27 +2134,6 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
|||||||
doc.assertEqual(expected_gle[i][2], gle.credit)
|
doc.assertEqual(expected_gle[i][2], gle.credit)
|
||||||
doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
|
doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
|
||||||
|
|
||||||
def test_item_tax_validity(self):
|
|
||||||
item = frappe.get_doc("Item", "_Test Item 2")
|
|
||||||
|
|
||||||
if item.taxes:
|
|
||||||
item.taxes = []
|
|
||||||
item.save()
|
|
||||||
|
|
||||||
item.append("taxes", {
|
|
||||||
"item_tax_template": "_Test Item Tax Template 1 - _TC",
|
|
||||||
"valid_from": add_days(nowdate(), 1)
|
|
||||||
})
|
|
||||||
|
|
||||||
item.save()
|
|
||||||
|
|
||||||
sales_invoice = create_sales_invoice(item = "_Test Item 2", do_not_save=1)
|
|
||||||
sales_invoice.items[0].item_tax_template = "_Test Item Tax Template 1 - _TC"
|
|
||||||
self.assertRaises(frappe.ValidationError, sales_invoice.save)
|
|
||||||
|
|
||||||
item.taxes = []
|
|
||||||
item.save()
|
|
||||||
|
|
||||||
def create_sales_invoice(**args):
|
def create_sales_invoice(**args):
|
||||||
si = frappe.new_doc("Sales Invoice")
|
si = frappe.new_doc("Sales Invoice")
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"creation": "2013-04-24 11:39:32",
|
"creation": "2013-04-24 11:39:32",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"charge_type",
|
"charge_type",
|
||||||
"row_id",
|
"row_id",
|
||||||
@@ -10,12 +12,14 @@
|
|||||||
"col_break_1",
|
"col_break_1",
|
||||||
"description",
|
"description",
|
||||||
"included_in_print_rate",
|
"included_in_print_rate",
|
||||||
|
"included_in_paid_amount",
|
||||||
"accounting_dimensions_section",
|
"accounting_dimensions_section",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
"dimension_col_break",
|
"dimension_col_break",
|
||||||
"section_break_8",
|
"section_break_8",
|
||||||
"rate",
|
"rate",
|
||||||
"section_break_9",
|
"section_break_9",
|
||||||
|
"currency",
|
||||||
"tax_amount",
|
"tax_amount",
|
||||||
"total",
|
"total",
|
||||||
"tax_amount_after_discount_amount",
|
"tax_amount_after_discount_amount",
|
||||||
@@ -23,8 +27,7 @@
|
|||||||
"base_tax_amount",
|
"base_tax_amount",
|
||||||
"base_total",
|
"base_total",
|
||||||
"base_tax_amount_after_discount_amount",
|
"base_tax_amount_after_discount_amount",
|
||||||
"item_wise_tax_detail",
|
"item_wise_tax_detail"
|
||||||
"parenttype"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -173,17 +176,6 @@
|
|||||||
"oldfieldtype": "Small Text",
|
"oldfieldtype": "Small Text",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "parenttype",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 1,
|
|
||||||
"in_filter": 1,
|
|
||||||
"label": "Parenttype",
|
|
||||||
"oldfieldname": "parenttype",
|
|
||||||
"oldfieldtype": "Data",
|
|
||||||
"print_hide": 1,
|
|
||||||
"search_index": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "accounting_dimensions_section",
|
"fieldname": "accounting_dimensions_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
@@ -192,15 +184,34 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "dimension_col_break",
|
"fieldname": "dimension_col_break",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "account_head.account_currency",
|
||||||
|
"fieldname": "currency",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Account Currency",
|
||||||
|
"options": "Currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:['Sales Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)",
|
||||||
|
"description": "If checked, the tax amount will be considered as already included in the Paid Amount in Payment Entry",
|
||||||
|
"fieldname": "included_in_paid_amount",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Considered In Paid Amount"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2019-05-25 22:59:38.740883",
|
"links": [],
|
||||||
|
"modified": "2021-06-14 01:44:36.899147",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Taxes and Charges",
|
"name": "Sales Taxes and Charges",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
"sort_order": "ASC"
|
"sort_order": "ASC"
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
|
|||||||
if not parties:
|
if not parties:
|
||||||
parties.append(party)
|
parties.append(party)
|
||||||
|
|
||||||
fiscal_year = get_fiscal_year(inv.posting_date, company=inv.company)
|
fiscal_year = get_fiscal_year(inv.get('posting_date') or inv.get('transaction_date'), company=inv.company)
|
||||||
tax_details = get_tax_withholding_details(tax_withholding_category, fiscal_year[0], inv.company)
|
tax_details = get_tax_withholding_details(tax_withholding_category, fiscal_year[0], inv.company)
|
||||||
|
|
||||||
if not tax_details:
|
if not tax_details:
|
||||||
@@ -154,7 +154,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, p
|
|||||||
tax_deducted = get_deducted_tax(taxable_vouchers, fiscal_year, tax_details)
|
tax_deducted = get_deducted_tax(taxable_vouchers, fiscal_year, tax_details)
|
||||||
|
|
||||||
tax_amount = 0
|
tax_amount = 0
|
||||||
posting_date = inv.posting_date
|
posting_date = inv.get('posting_date') or inv.get('transaction_date')
|
||||||
if party_type == 'Supplier':
|
if party_type == 'Supplier':
|
||||||
ldc = get_lower_deduction_certificate(fiscal_year, pan_no)
|
ldc = get_lower_deduction_certificate(fiscal_year, pan_no)
|
||||||
if tax_deducted:
|
if tax_deducted:
|
||||||
@@ -257,7 +257,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu
|
|||||||
if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
|
if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
|
||||||
if ldc and is_valid_certificate(
|
if ldc and is_valid_certificate(
|
||||||
ldc.valid_from, ldc.valid_upto,
|
ldc.valid_from, ldc.valid_upto,
|
||||||
inv.posting_date, tax_deducted,
|
inv.get('posting_date') or inv.get('transaction_date'), tax_deducted,
|
||||||
inv.net_total, ldc.certificate_limit
|
inv.net_total, ldc.certificate_limit
|
||||||
):
|
):
|
||||||
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
|
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -185,10 +185,10 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
|
|||||||
for d in gl_map:
|
for d in gl_map:
|
||||||
if d.account == round_off_account:
|
if d.account == round_off_account:
|
||||||
round_off_gle = d
|
round_off_gle = d
|
||||||
if d.debit_in_account_currency:
|
if d.debit:
|
||||||
debit_credit_diff -= flt(d.debit_in_account_currency)
|
debit_credit_diff -= flt(d.debit)
|
||||||
else:
|
else:
|
||||||
debit_credit_diff += flt(d.credit_in_account_currency)
|
debit_credit_diff += flt(d.credit)
|
||||||
round_off_account_exists = True
|
round_off_account_exists = True
|
||||||
|
|
||||||
if round_off_account_exists and abs(debit_credit_diff) <= (1.0 / (10**precision)):
|
if round_off_account_exists and abs(debit_credit_diff) <= (1.0 / (10**precision)):
|
||||||
|
|||||||
@@ -457,7 +457,7 @@ def validate_party_frozen_disabled(party_type, party_name):
|
|||||||
frappe.throw(_("{0} {1} is frozen").format(party_type, party_name), PartyFrozen)
|
frappe.throw(_("{0} {1} is frozen").format(party_type, party_name), PartyFrozen)
|
||||||
|
|
||||||
elif party_type == "Employee":
|
elif party_type == "Employee":
|
||||||
if frappe.db.get_value("Employee", party_name, "status") == "Left":
|
if frappe.db.get_value("Employee", party_name, "status") != "Active":
|
||||||
frappe.msgprint(_("{0} {1} is not active").format(party_type, party_name), alert=True)
|
frappe.msgprint(_("{0} {1} is not active").format(party_type, party_name), alert=True)
|
||||||
|
|
||||||
def get_timeline_data(doctype, name):
|
def get_timeline_data(doctype, name):
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|||||||
@@ -584,6 +584,7 @@ class ReceivablePayableReport(object):
|
|||||||
`tabGL Entry`
|
`tabGL Entry`
|
||||||
where
|
where
|
||||||
docstatus < 2
|
docstatus < 2
|
||||||
|
and is_cancelled = 0
|
||||||
and party_type=%s
|
and party_type=%s
|
||||||
and (party is not null and party != '')
|
and (party is not null and party != '')
|
||||||
{1} {2} {3}"""
|
{1} {2} {3}"""
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -54,6 +54,32 @@ frappe.query_reports["TDS Payable Monthly"] = {
|
|||||||
frappe.query_report.refresh();
|
frappe.query_report.refresh();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"purchase_order",
|
||||||
|
"label": __("Purchase Order"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Purchase Order",
|
||||||
|
"get_query": function() {
|
||||||
|
return {
|
||||||
|
"filters": {
|
||||||
|
"name": ["in", frappe.query_report.invoices]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
on_change: function() {
|
||||||
|
let supplier = frappe.query_report.get_filter_value('supplier');
|
||||||
|
if(!supplier) return; // return if no supplier selected
|
||||||
|
|
||||||
|
// filter invoices based on selected supplier
|
||||||
|
let invoices = [];
|
||||||
|
frappe.query_report.invoice_data.map(d => {
|
||||||
|
if(d.supplier==supplier)
|
||||||
|
invoices.push(d.name)
|
||||||
|
});
|
||||||
|
frappe.query_report.invoices = invoices;
|
||||||
|
frappe.query_report.refresh();
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"from_date",
|
"fieldname":"from_date",
|
||||||
"label": __("From Date"),
|
"label": __("From Date"),
|
||||||
@@ -75,15 +101,17 @@ frappe.query_reports["TDS Payable Monthly"] = {
|
|||||||
onload: function(report) {
|
onload: function(report) {
|
||||||
// fetch all tds applied invoices
|
// fetch all tds applied invoices
|
||||||
frappe.call({
|
frappe.call({
|
||||||
"method": "erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly.get_tds_invoices",
|
"method": "erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly.get_tds_invoices_and_orders",
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
let invoices = [];
|
let invoices = [];
|
||||||
|
|
||||||
r.message.map(d => {
|
r.message.map(d => {
|
||||||
invoices.push(d.name);
|
invoices.push(d.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
report["invoice_data"] = r.message;
|
report["invoice_data"] = r.message.invoices;
|
||||||
report["invoices"] = invoices;
|
report["invoices"] = invoices;
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,14 @@ def execute(filters=None):
|
|||||||
validate_filters(filters)
|
validate_filters(filters)
|
||||||
set_filters(filters)
|
set_filters(filters)
|
||||||
|
|
||||||
|
# TDS payment entries
|
||||||
|
payment_entries = get_payment_entires(filters)
|
||||||
|
|
||||||
columns = get_columns(filters)
|
columns = get_columns(filters)
|
||||||
if not filters["invoices"]:
|
if not filters.get("invoices"):
|
||||||
return columns, []
|
return columns, []
|
||||||
|
|
||||||
res = get_result(filters)
|
res = get_result(filters, payment_entries)
|
||||||
|
|
||||||
return columns, res
|
return columns, res
|
||||||
|
|
||||||
@@ -27,8 +30,9 @@ def validate_filters(filters):
|
|||||||
def set_filters(filters):
|
def set_filters(filters):
|
||||||
invoices = []
|
invoices = []
|
||||||
|
|
||||||
if not filters["invoices"]:
|
if not filters.get("invoices"):
|
||||||
filters["invoices"] = get_tds_invoices()
|
filters["invoices"] = get_tds_invoices_and_orders()
|
||||||
|
|
||||||
if filters.supplier and filters.purchase_invoice:
|
if filters.supplier and filters.purchase_invoice:
|
||||||
for d in filters["invoices"]:
|
for d in filters["invoices"]:
|
||||||
if d.name == filters.purchase_invoice and d.supplier == filters.supplier:
|
if d.name == filters.purchase_invoice and d.supplier == filters.supplier:
|
||||||
@@ -41,13 +45,29 @@ def set_filters(filters):
|
|||||||
for d in filters["invoices"]:
|
for d in filters["invoices"]:
|
||||||
if d.name == filters.purchase_invoice:
|
if d.name == filters.purchase_invoice:
|
||||||
invoices.append(d)
|
invoices.append(d)
|
||||||
|
elif filters.supplier and filters.purchase_order:
|
||||||
|
for d in filters.get("invoices"):
|
||||||
|
if d.name == filters.purchase_order and d.supplier == filters.supplier:
|
||||||
|
invoices.append(d)
|
||||||
|
elif filters.supplier and not filters.purchase_order:
|
||||||
|
for d in filters.get("invoices"):
|
||||||
|
if d.supplier == filters.supplier:
|
||||||
|
invoices.append(d)
|
||||||
|
elif filters.purchase_order and not filters.supplier:
|
||||||
|
for d in filters.get("invoices"):
|
||||||
|
if d.name == filters.purchase_order:
|
||||||
|
invoices.append(d)
|
||||||
|
|
||||||
filters["invoices"] = invoices if invoices else filters["invoices"]
|
filters["invoices"] = invoices if invoices else filters["invoices"]
|
||||||
filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name')
|
filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name')
|
||||||
|
|
||||||
def get_result(filters):
|
#print(filters.get('invoices'))
|
||||||
supplier_map, tds_docs = get_supplier_map(filters)
|
|
||||||
gle_map = get_gle_map(filters)
|
def get_result(filters, payment_entries):
|
||||||
|
supplier_map, tds_docs = get_supplier_map(filters, payment_entries)
|
||||||
|
documents = [d.get('name') for d in filters.get('invoices')] + [d.get('name') for d in payment_entries]
|
||||||
|
|
||||||
|
gle_map = get_gle_map(filters, documents)
|
||||||
|
|
||||||
out = []
|
out = []
|
||||||
for d in gle_map:
|
for d in gle_map:
|
||||||
@@ -62,10 +82,11 @@ def get_result(filters):
|
|||||||
|
|
||||||
for k in gle_map[d]:
|
for k in gle_map[d]:
|
||||||
if k.party == supplier_map[d] and k.credit > 0:
|
if k.party == supplier_map[d] and k.credit > 0:
|
||||||
total_amount_credited += k.credit
|
total_amount_credited += (k.credit - k.debit)
|
||||||
elif account_list and k.account == account and k.credit > 0:
|
elif account_list and k.account == account and (k.credit - k.debit) > 0:
|
||||||
tds_deducted = k.credit
|
tds_deducted = (k.credit - k.debit)
|
||||||
total_amount_credited += k.credit
|
total_amount_credited += (k.credit - k.debit)
|
||||||
|
voucher_type = k.voucher_type
|
||||||
|
|
||||||
rate = [i.tax_withholding_rate for i in tds_doc.rates
|
rate = [i.tax_withholding_rate for i in tds_doc.rates
|
||||||
if i.fiscal_year == gle_map[d][0].fiscal_year]
|
if i.fiscal_year == gle_map[d][0].fiscal_year]
|
||||||
@@ -73,32 +94,36 @@ def get_result(filters):
|
|||||||
if rate and len(rate) > 0 and tds_deducted:
|
if rate and len(rate) > 0 and tds_deducted:
|
||||||
rate = rate[0]
|
rate = rate[0]
|
||||||
|
|
||||||
if getdate(filters.from_date) <= gle_map[d][0].posting_date \
|
row = [supplier.pan, supplier.name]
|
||||||
and getdate(filters.to_date) >= gle_map[d][0].posting_date:
|
|
||||||
row = [supplier.pan, supplier.name]
|
|
||||||
|
|
||||||
if filters.naming_series == 'Naming Series':
|
if filters.naming_series == 'Naming Series':
|
||||||
row.append(supplier.supplier_name)
|
row.append(supplier.supplier_name)
|
||||||
|
|
||||||
row.extend([tds_doc.name, supplier.supplier_type, rate, total_amount_credited,
|
row.extend([tds_doc.name, supplier.supplier_type, rate, total_amount_credited,
|
||||||
tds_deducted, gle_map[d][0].posting_date, "Purchase Invoice", d])
|
tds_deducted, gle_map[d][0].posting_date, voucher_type, d])
|
||||||
out.append(row)
|
out.append(row)
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def get_supplier_map(filters):
|
def get_supplier_map(filters, payment_entries):
|
||||||
# create a supplier_map of the form {"purchase_invoice": {supplier_name, pan, tds_name}}
|
# create a supplier_map of the form {"purchase_invoice": {supplier_name, pan, tds_name}}
|
||||||
# pre-fetch all distinct applicable tds docs
|
# pre-fetch all distinct applicable tds docs
|
||||||
supplier_map, tds_docs = {}, {}
|
supplier_map, tds_docs = {}, {}
|
||||||
pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id"
|
pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id"
|
||||||
|
supplier_list = [d.supplier for d in filters["invoices"]]
|
||||||
|
|
||||||
supplier_detail = frappe.db.get_all('Supplier',
|
supplier_detail = frappe.db.get_all('Supplier',
|
||||||
{"name": ["in", [d.supplier for d in filters["invoices"]]]},
|
{"name": ["in", supplier_list]},
|
||||||
["tax_withholding_category", "name", pan+" as pan", "supplier_type", "supplier_name"])
|
["tax_withholding_category", "name", pan+" as pan", "supplier_type", "supplier_name"])
|
||||||
|
|
||||||
for d in filters["invoices"]:
|
for d in filters["invoices"]:
|
||||||
supplier_map[d.get("name")] = [k for k in supplier_detail
|
supplier_map[d.get("name")] = [k for k in supplier_detail
|
||||||
if k.name == d.get("supplier")][0]
|
if k.name == d.get("supplier")][0]
|
||||||
|
|
||||||
|
for d in payment_entries:
|
||||||
|
supplier_map[d.get("name")] = [k for k in supplier_detail
|
||||||
|
if k.name == d.get("supplier")][0]
|
||||||
|
|
||||||
for d in supplier_detail:
|
for d in supplier_detail:
|
||||||
if d.get("tax_withholding_category") not in tds_docs:
|
if d.get("tax_withholding_category") not in tds_docs:
|
||||||
tds_docs[d.get("tax_withholding_category")] = \
|
tds_docs[d.get("tax_withholding_category")] = \
|
||||||
@@ -106,13 +131,19 @@ def get_supplier_map(filters):
|
|||||||
|
|
||||||
return supplier_map, tds_docs
|
return supplier_map, tds_docs
|
||||||
|
|
||||||
def get_gle_map(filters):
|
def get_gle_map(filters, documents):
|
||||||
# create gle_map of the form
|
# create gle_map of the form
|
||||||
# {"purchase_invoice": list of dict of all gle created for this invoice}
|
# {"purchase_invoice": list of dict of all gle created for this invoice}
|
||||||
gle_map = {}
|
gle_map = {}
|
||||||
gle = frappe.db.get_all('GL Entry',\
|
|
||||||
{"voucher_no": ["in", [d.get("name") for d in filters["invoices"]]], 'is_cancelled': 0},
|
gle = frappe.db.get_all('GL Entry',
|
||||||
["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date"])
|
{
|
||||||
|
"voucher_no": ["in", documents],
|
||||||
|
'is_cancelled': 0,
|
||||||
|
'posting_date': ("between", [filters.get('from_date'), filters.get('to_date')]),
|
||||||
|
},
|
||||||
|
["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date", "voucher_type"],
|
||||||
|
)
|
||||||
|
|
||||||
for d in gle:
|
for d in gle:
|
||||||
if not d.voucher_no in gle_map:
|
if not d.voucher_no in gle_map:
|
||||||
@@ -201,8 +232,26 @@ def get_columns(filters):
|
|||||||
|
|
||||||
return columns
|
return columns
|
||||||
|
|
||||||
|
def get_payment_entires(filters):
|
||||||
|
filter_dict = {
|
||||||
|
'posting_date': ("between", [filters.get('from_date'), filters.get('to_date')]),
|
||||||
|
'party_type': 'Supplier',
|
||||||
|
'apply_tax_withholding_amount': 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if filters.get('purchase_invoice') or filters.get('purchase_order'):
|
||||||
|
parent = frappe.db.get_all('Payment Entry Reference',
|
||||||
|
{'reference_name': ('in', [d.get('name') for d in filters.get('invoices')])}, ['parent'])
|
||||||
|
|
||||||
|
filter_dict.update({'name': ('in', [d.get('parent') for d in parent])})
|
||||||
|
|
||||||
|
payment_entries = frappe.get_all('Payment Entry', fields=['name', 'party_name as supplier'],
|
||||||
|
filters=filter_dict)
|
||||||
|
|
||||||
|
return payment_entries
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_tds_invoices():
|
def get_tds_invoices_and_orders():
|
||||||
# fetch tds applicable supplier and fetch invoices for these suppliers
|
# fetch tds applicable supplier and fetch invoices for these suppliers
|
||||||
suppliers = [d.name for d in frappe.db.get_list("Supplier",
|
suppliers = [d.name for d in frappe.db.get_list("Supplier",
|
||||||
{"tax_withholding_category": ["!=", ""]}, ["name"])]
|
{"tax_withholding_category": ["!=", ""]}, ["name"])]
|
||||||
@@ -210,7 +259,12 @@ def get_tds_invoices():
|
|||||||
invoices = frappe.db.get_list("Purchase Invoice",
|
invoices = frappe.db.get_list("Purchase Invoice",
|
||||||
{"supplier": ["in", suppliers]}, ["name", "supplier"])
|
{"supplier": ["in", suppliers]}, ["name", "supplier"])
|
||||||
|
|
||||||
|
orders = frappe.db.get_list("Purchase Order",
|
||||||
|
{"supplier": ["in", suppliers]}, ["name", "supplier"])
|
||||||
|
|
||||||
|
invoices = invoices + orders
|
||||||
invoices = [d for d in invoices if d.supplier]
|
invoices = [d for d in invoices if d.supplier]
|
||||||
|
|
||||||
frappe.cache().hset("invoices", frappe.session.user, invoices)
|
frappe.cache().hset("invoices", frappe.session.user, invoices)
|
||||||
|
|
||||||
return invoices
|
return invoices
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -707,6 +707,7 @@
|
|||||||
"link_to": "GST Settings",
|
"link_to": "GST Settings",
|
||||||
"link_type": "DocType",
|
"link_type": "DocType",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -717,6 +718,7 @@
|
|||||||
"link_to": "GST HSN Code",
|
"link_to": "GST HSN Code",
|
||||||
"link_type": "DocType",
|
"link_type": "DocType",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -727,6 +729,7 @@
|
|||||||
"link_to": "GSTR-1",
|
"link_to": "GSTR-1",
|
||||||
"link_type": "Report",
|
"link_type": "Report",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -737,6 +740,7 @@
|
|||||||
"link_to": "GSTR-2",
|
"link_to": "GSTR-2",
|
||||||
"link_type": "Report",
|
"link_type": "Report",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -747,6 +751,7 @@
|
|||||||
"link_to": "GSTR 3B Report",
|
"link_to": "GSTR 3B Report",
|
||||||
"link_type": "DocType",
|
"link_type": "DocType",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -757,6 +762,7 @@
|
|||||||
"link_to": "GST Sales Register",
|
"link_to": "GST Sales Register",
|
||||||
"link_type": "Report",
|
"link_type": "Report",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -767,6 +773,7 @@
|
|||||||
"link_to": "GST Purchase Register",
|
"link_to": "GST Purchase Register",
|
||||||
"link_type": "Report",
|
"link_type": "Report",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -777,6 +784,7 @@
|
|||||||
"link_to": "GST Itemised Sales Register",
|
"link_to": "GST Itemised Sales Register",
|
||||||
"link_type": "Report",
|
"link_type": "Report",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -787,6 +795,7 @@
|
|||||||
"link_to": "GST Itemised Purchase Register",
|
"link_to": "GST Itemised Purchase Register",
|
||||||
"link_type": "Report",
|
"link_type": "Report",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -797,6 +806,7 @@
|
|||||||
"link_to": "C-Form",
|
"link_to": "C-Form",
|
||||||
"link_type": "DocType",
|
"link_type": "DocType",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -807,6 +817,7 @@
|
|||||||
"link_to": "Lower Deduction Certificate",
|
"link_to": "Lower Deduction Certificate",
|
||||||
"link_type": "DocType",
|
"link_type": "DocType",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1065,7 +1076,7 @@
|
|||||||
"type": "Link"
|
"type": "Link"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2021-05-13 13:44:56.249888",
|
"modified": "2021-06-10 03:17:31.427945",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounting",
|
"name": "Accounting",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -45,6 +45,47 @@ frappe.ui.form.on("Purchase Order", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
|
},
|
||||||
|
|
||||||
|
apply_tds: function(frm) {
|
||||||
|
if (!frm.doc.apply_tds) {
|
||||||
|
frm.set_value("tax_withholding_category", '');
|
||||||
|
} else {
|
||||||
|
frm.set_value("tax_withholding_category", frm.supplier_tds);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh: function(frm) {
|
||||||
|
frm.trigger('get_materials_from_supplier');
|
||||||
|
},
|
||||||
|
|
||||||
|
get_materials_from_supplier: function(frm) {
|
||||||
|
let po_details = [];
|
||||||
|
|
||||||
|
if (frm.doc.supplied_items && (frm.doc.per_received == 100 || frm.doc.status === 'Closed')) {
|
||||||
|
frm.doc.supplied_items.forEach(d => {
|
||||||
|
if (d.total_supplied_qty && d.total_supplied_qty != d.consumed_qty) {
|
||||||
|
po_details.push(d.name)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (po_details && po_details.length) {
|
||||||
|
frm.add_custom_button(__('Return of Components'), () => {
|
||||||
|
frm.call({
|
||||||
|
method: 'erpnext.buying.doctype.purchase_order.purchase_order.get_materials_from_supplier',
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __('Creating Stock Entry'),
|
||||||
|
args: { purchase_order: frm.doc.name, po_details: po_details },
|
||||||
|
callback: function(r) {
|
||||||
|
if (r && r.message) {
|
||||||
|
const doc = frappe.model.sync(r.message);
|
||||||
|
frappe.set_route("Form", doc[0].doctype, doc[0].name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, __('Create'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -209,7 +250,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
|||||||
}
|
}
|
||||||
|
|
||||||
has_unsupplied_items() {
|
has_unsupplied_items() {
|
||||||
return this.frm.doc['supplied_items'].some(item => item.required_qty != item.supplied_qty)
|
return this.frm.doc['supplied_items'].some(item => item.required_qty > item.supplied_qty);
|
||||||
}
|
}
|
||||||
|
|
||||||
make_stock_entry() {
|
make_stock_entry() {
|
||||||
@@ -313,7 +354,8 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
|||||||
if(me.values) {
|
if(me.values) {
|
||||||
me.values.sub_con_rm_items.map((row,i) => {
|
me.values.sub_con_rm_items.map((row,i) => {
|
||||||
if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) {
|
if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) {
|
||||||
frappe.throw(__("Item Code, warehouse, quantity are required on row {0}", [i+1]));
|
let row_id = i+1;
|
||||||
|
frappe.throw(__("Item Code, warehouse and quantity are required on row {0}", [row_id]));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
me._make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children())
|
me._make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children())
|
||||||
@@ -504,12 +546,14 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
|||||||
],
|
],
|
||||||
primary_action: function() {
|
primary_action: function() {
|
||||||
var data = d.get_values();
|
var data = d.get_values();
|
||||||
|
let reason_for_hold = 'Reason for hold: ' + data.reason_for_hold;
|
||||||
|
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "frappe.desk.form.utils.add_comment",
|
method: "frappe.desk.form.utils.add_comment",
|
||||||
args: {
|
args: {
|
||||||
reference_doctype: me.frm.doctype,
|
reference_doctype: me.frm.doctype,
|
||||||
reference_name: me.frm.docname,
|
reference_name: me.frm.docname,
|
||||||
content: __('Reason for hold: ')+data.reason_for_hold,
|
content: __(reason_for_hold),
|
||||||
comment_email: frappe.session.user,
|
comment_email: frappe.session.user,
|
||||||
comment_by: frappe.session.user_fullname
|
comment_by: frappe.session.user_fullname
|
||||||
},
|
},
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -14,11 +14,11 @@ from frappe.desk.notifications import clear_doctype_notifications
|
|||||||
from erpnext.buying.utils import validate_for_items, check_on_hold_or_closed_status
|
from erpnext.buying.utils import validate_for_items, check_on_hold_or_closed_status
|
||||||
from erpnext.stock.utils import get_bin
|
from erpnext.stock.utils import get_bin
|
||||||
from erpnext.accounts.party import get_party_account_currency
|
from erpnext.accounts.party import get_party_account_currency
|
||||||
from six import string_types
|
|
||||||
from erpnext.stock.doctype.item.item import get_item_defaults
|
from erpnext.stock.doctype.item.item import get_item_defaults
|
||||||
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\
|
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
|
||||||
unlink_inter_company_doc
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (validate_inter_company_party,
|
||||||
|
update_linked_doc, unlink_inter_company_doc)
|
||||||
|
|
||||||
form_grid_templates = {
|
form_grid_templates = {
|
||||||
"items": "templates/form_grid/item_grid.html"
|
"items": "templates/form_grid/item_grid.html"
|
||||||
@@ -39,11 +39,18 @@ class PurchaseOrder(BuyingController):
|
|||||||
'percent_join_field': 'material_request'
|
'percent_join_field': 'material_request'
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
def onload(self):
|
||||||
|
supplier_tds = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
|
||||||
|
self.set_onload("supplier_tds", supplier_tds)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
super(PurchaseOrder, self).validate()
|
super(PurchaseOrder, self).validate()
|
||||||
|
|
||||||
self.set_status()
|
self.set_status()
|
||||||
|
|
||||||
|
# apply tax withholding only if checked and applicable
|
||||||
|
self.set_tax_withholding()
|
||||||
|
|
||||||
self.validate_supplier()
|
self.validate_supplier()
|
||||||
self.validate_schedule_date()
|
self.validate_schedule_date()
|
||||||
validate_for_items(self)
|
validate_for_items(self)
|
||||||
@@ -87,6 +94,33 @@ class PurchaseOrder(BuyingController):
|
|||||||
if cint(frappe.db.get_single_value('Buying Settings', 'maintain_same_rate')):
|
if cint(frappe.db.get_single_value('Buying Settings', 'maintain_same_rate')):
|
||||||
self.validate_rate_with_reference_doc([["Supplier Quotation", "supplier_quotation", "supplier_quotation_item"]])
|
self.validate_rate_with_reference_doc([["Supplier Quotation", "supplier_quotation", "supplier_quotation_item"]])
|
||||||
|
|
||||||
|
def set_tax_withholding(self):
|
||||||
|
if not self.apply_tds:
|
||||||
|
return
|
||||||
|
|
||||||
|
tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category)
|
||||||
|
|
||||||
|
if not tax_withholding_details:
|
||||||
|
return
|
||||||
|
|
||||||
|
accounts = []
|
||||||
|
for d in self.taxes:
|
||||||
|
if d.account_head == tax_withholding_details.get("account_head"):
|
||||||
|
d.update(tax_withholding_details)
|
||||||
|
accounts.append(d.account_head)
|
||||||
|
|
||||||
|
if not accounts or tax_withholding_details.get("account_head") not in accounts:
|
||||||
|
self.append("taxes", tax_withholding_details)
|
||||||
|
|
||||||
|
to_remove = [d for d in self.taxes
|
||||||
|
if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")]
|
||||||
|
|
||||||
|
for d in to_remove:
|
||||||
|
self.remove(d)
|
||||||
|
|
||||||
|
# calculate totals again after applying TDS
|
||||||
|
self.calculate_taxes_and_totals()
|
||||||
|
|
||||||
def validate_supplier(self):
|
def validate_supplier(self):
|
||||||
prevent_po = frappe.db.get_value("Supplier", self.supplier, 'prevent_pos')
|
prevent_po = frappe.db.get_value("Supplier", self.supplier, 'prevent_pos')
|
||||||
if prevent_po:
|
if prevent_po:
|
||||||
@@ -104,7 +138,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 +325,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:
|
||||||
@@ -468,9 +502,11 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_rm_stock_entry(purchase_order, rm_items):
|
def make_rm_stock_entry(purchase_order, rm_items):
|
||||||
if isinstance(rm_items, string_types):
|
rm_items_list = rm_items
|
||||||
|
|
||||||
|
if isinstance(rm_items, str):
|
||||||
rm_items_list = json.loads(rm_items)
|
rm_items_list = json.loads(rm_items)
|
||||||
else:
|
elif not rm_items:
|
||||||
frappe.throw(_("No Items available for transfer"))
|
frappe.throw(_("No Items available for transfer"))
|
||||||
|
|
||||||
if rm_items_list:
|
if rm_items_list:
|
||||||
@@ -508,6 +544,8 @@ def make_rm_stock_entry(purchase_order, rm_items):
|
|||||||
'qty': rm_item_data["qty"],
|
'qty': rm_item_data["qty"],
|
||||||
'from_warehouse': rm_item_data["warehouse"],
|
'from_warehouse': rm_item_data["warehouse"],
|
||||||
'stock_uom': rm_item_data["stock_uom"],
|
'stock_uom': rm_item_data["stock_uom"],
|
||||||
|
'serial_no': rm_item_data.get('serial_no'),
|
||||||
|
'batch_no': rm_item_data.get('batch_no'),
|
||||||
'main_item_code': rm_item_data["item_code"],
|
'main_item_code': rm_item_data["item_code"],
|
||||||
'allow_alternative_item': item_wh.get(rm_item_code, {}).get('allow_alternative_item')
|
'allow_alternative_item': item_wh.get(rm_item_code, {}).get('allow_alternative_item')
|
||||||
}
|
}
|
||||||
@@ -547,3 +585,58 @@ def update_status(status, name):
|
|||||||
def make_inter_company_sales_order(source_name, target_doc=None):
|
def make_inter_company_sales_order(source_name, target_doc=None):
|
||||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
|
||||||
return make_inter_company_transaction("Purchase Order", source_name, target_doc)
|
return make_inter_company_transaction("Purchase Order", source_name, target_doc)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_materials_from_supplier(purchase_order, po_details):
|
||||||
|
if isinstance(po_details, str):
|
||||||
|
po_details = json.loads(po_details)
|
||||||
|
|
||||||
|
doc = frappe.get_cached_doc('Purchase Order', purchase_order)
|
||||||
|
doc.initialized_fields()
|
||||||
|
doc.purchase_orders = [doc.name]
|
||||||
|
doc.get_available_materials()
|
||||||
|
|
||||||
|
if not doc.available_materials:
|
||||||
|
frappe.throw(_('Materials are already received against the purchase order {0}')
|
||||||
|
.format(purchase_order))
|
||||||
|
|
||||||
|
return make_return_stock_entry_for_subcontract(doc.available_materials, doc, po_details)
|
||||||
|
|
||||||
|
def make_return_stock_entry_for_subcontract(available_materials, po_doc, po_details):
|
||||||
|
ste_doc = frappe.new_doc('Stock Entry')
|
||||||
|
ste_doc.purpose = 'Material Transfer'
|
||||||
|
ste_doc.purchase_order = po_doc.name
|
||||||
|
ste_doc.company = po_doc.company
|
||||||
|
ste_doc.is_return = 1
|
||||||
|
|
||||||
|
for key, value in available_materials.items():
|
||||||
|
if not value.qty:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if value.batch_no:
|
||||||
|
for batch_no, qty in value.batch_no.items():
|
||||||
|
if qty > 0:
|
||||||
|
add_items_in_ste(ste_doc, value, value.qty, po_details, batch_no)
|
||||||
|
else:
|
||||||
|
add_items_in_ste(ste_doc, value, value.qty, po_details)
|
||||||
|
|
||||||
|
ste_doc.set_stock_entry_type()
|
||||||
|
ste_doc.calculate_rate_and_amount()
|
||||||
|
|
||||||
|
return ste_doc
|
||||||
|
|
||||||
|
def add_items_in_ste(ste_doc, row, qty, po_details, batch_no=None):
|
||||||
|
item = ste_doc.append('items', row.item_details)
|
||||||
|
|
||||||
|
po_detail = list(set(row.po_details).intersection(po_details))
|
||||||
|
item.update({
|
||||||
|
'qty': qty,
|
||||||
|
'batch_no': batch_no,
|
||||||
|
'basic_rate': row.item_details['rate'],
|
||||||
|
'po_detail': po_detail[0] if po_detail else '',
|
||||||
|
's_warehouse': row.item_details['t_warehouse'],
|
||||||
|
't_warehouse': row.item_details['s_warehouse'],
|
||||||
|
'item_code': row.item_details['rm_item_code'],
|
||||||
|
'subcontracted_item': row.item_details['main_item_code'],
|
||||||
|
'serial_no': '\n'.join(row.serial_no) if row.serial_no else ''
|
||||||
|
})
|
||||||
@@ -20,7 +20,6 @@ from erpnext.controllers.status_updater import OverAllowanceError
|
|||||||
from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order
|
from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order
|
||||||
|
|
||||||
from erpnext.stock.doctype.batch.test_batch import make_new_batch
|
from erpnext.stock.doctype.batch.test_batch import make_new_batch
|
||||||
from erpnext.controllers.buying_controller import get_backflushed_subcontracted_raw_materials
|
|
||||||
|
|
||||||
class TestPurchaseOrder(unittest.TestCase):
|
class TestPurchaseOrder(unittest.TestCase):
|
||||||
def test_make_purchase_receipt(self):
|
def test_make_purchase_receipt(self):
|
||||||
@@ -359,7 +358,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)
|
||||||
|
|
||||||
@@ -771,7 +770,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
self.assertEqual(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
|
self.assertEqual(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
|
||||||
|
|
||||||
def test_exploded_items_in_subcontracted(self):
|
def test_exploded_items_in_subcontracted(self):
|
||||||
item_code = "_Test Subcontracted FG Item 1"
|
item_code = "_Test Subcontracted FG Item 11"
|
||||||
make_subcontracted_item(item_code=item_code)
|
make_subcontracted_item(item_code=item_code)
|
||||||
|
|
||||||
po = create_purchase_order(item_code=item_code, qty=1,
|
po = create_purchase_order(item_code=item_code, qty=1,
|
||||||
@@ -848,79 +847,6 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
for item in rm_items:
|
for item in rm_items:
|
||||||
transferred_rm_map[item.get('rm_item_code')] = item
|
transferred_rm_map[item.get('rm_item_code')] = item
|
||||||
|
|
||||||
for item in pr.get('supplied_items'):
|
|
||||||
self.assertEqual(item.get('required_qty'), (transferred_rm_map[item.get('rm_item_code')].get('qty') / order_qty) * received_qty)
|
|
||||||
|
|
||||||
update_backflush_based_on("BOM")
|
|
||||||
|
|
||||||
def test_backflushed_based_on_for_multiple_batches(self):
|
|
||||||
item_code = "_Test Subcontracted FG Item 2"
|
|
||||||
make_item('Sub Contracted Raw Material 2', {
|
|
||||||
'is_stock_item': 1,
|
|
||||||
'is_sub_contracted_item': 1
|
|
||||||
})
|
|
||||||
|
|
||||||
make_subcontracted_item(item_code=item_code, has_batch_no=1, create_new_batch=1,
|
|
||||||
raw_materials=["Sub Contracted Raw Material 2"])
|
|
||||||
|
|
||||||
update_backflush_based_on("Material Transferred for Subcontract")
|
|
||||||
|
|
||||||
order_qty = 500
|
|
||||||
po = create_purchase_order(item_code=item_code, qty=order_qty,
|
|
||||||
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
|
|
||||||
|
|
||||||
make_stock_entry(target="_Test Warehouse - _TC",
|
|
||||||
item_code = "Sub Contracted Raw Material 2", qty=552, basic_rate=100)
|
|
||||||
|
|
||||||
rm_items = [
|
|
||||||
{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 2","item_name":"_Test Item",
|
|
||||||
"qty":552,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}]
|
|
||||||
|
|
||||||
rm_item_string = json.dumps(rm_items)
|
|
||||||
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
|
|
||||||
se.submit()
|
|
||||||
|
|
||||||
for batch in ["ABCD1", "ABCD2", "ABCD3", "ABCD4"]:
|
|
||||||
make_new_batch(batch_id=batch, item_code=item_code)
|
|
||||||
|
|
||||||
pr = make_purchase_receipt(po.name)
|
|
||||||
|
|
||||||
# partial receipt
|
|
||||||
pr.get('items')[0].qty = 30
|
|
||||||
pr.get('items')[0].batch_no = "ABCD1"
|
|
||||||
|
|
||||||
purchase_order = po.name
|
|
||||||
purchase_order_item = po.items[0].name
|
|
||||||
|
|
||||||
for batch_no, qty in {"ABCD2": 60, "ABCD3": 70, "ABCD4":40}.items():
|
|
||||||
pr.append("items", {
|
|
||||||
"item_code": pr.get('items')[0].item_code,
|
|
||||||
"item_name": pr.get('items')[0].item_name,
|
|
||||||
"uom": pr.get('items')[0].uom,
|
|
||||||
"stock_uom": pr.get('items')[0].stock_uom,
|
|
||||||
"warehouse": pr.get('items')[0].warehouse,
|
|
||||||
"conversion_factor": pr.get('items')[0].conversion_factor,
|
|
||||||
"cost_center": pr.get('items')[0].cost_center,
|
|
||||||
"rate": pr.get('items')[0].rate,
|
|
||||||
"qty": qty,
|
|
||||||
"batch_no": batch_no,
|
|
||||||
"purchase_order": purchase_order,
|
|
||||||
"purchase_order_item": purchase_order_item
|
|
||||||
})
|
|
||||||
|
|
||||||
pr.submit()
|
|
||||||
|
|
||||||
pr1 = make_purchase_receipt(po.name)
|
|
||||||
pr1.get('items')[0].qty = 300
|
|
||||||
pr1.get('items')[0].batch_no = "ABCD1"
|
|
||||||
pr1.save()
|
|
||||||
|
|
||||||
pr_key = ("Sub Contracted Raw Material 2", po.name)
|
|
||||||
consumed_qty = get_backflushed_subcontracted_raw_materials([po.name]).get(pr_key)
|
|
||||||
|
|
||||||
self.assertTrue(pr1.supplied_items[0].consumed_qty > 0)
|
|
||||||
self.assertTrue(pr1.supplied_items[0].consumed_qty, flt(552.0) - flt(consumed_qty))
|
|
||||||
|
|
||||||
update_backflush_based_on("BOM")
|
update_backflush_based_on("BOM")
|
||||||
|
|
||||||
def test_supplied_qty_against_subcontracted_po(self):
|
def test_supplied_qty_against_subcontracted_po(self):
|
||||||
@@ -1111,28 +1037,35 @@ def create_purchase_order(**args):
|
|||||||
|
|
||||||
po.schedule_date = add_days(nowdate(), 1)
|
po.schedule_date = add_days(nowdate(), 1)
|
||||||
po.company = args.company or "_Test Company"
|
po.company = args.company or "_Test Company"
|
||||||
po.supplier = args.customer or "_Test Supplier"
|
po.supplier = args.supplier or "_Test Supplier"
|
||||||
po.is_subcontracted = args.is_subcontracted or "No"
|
po.is_subcontracted = args.is_subcontracted or "No"
|
||||||
po.currency = args.currency or frappe.get_cached_value('Company', po.company, "default_currency")
|
po.currency = args.currency or frappe.get_cached_value('Company', po.company, "default_currency")
|
||||||
po.conversion_factor = args.conversion_factor or 1
|
po.conversion_factor = args.conversion_factor or 1
|
||||||
po.supplier_warehouse = args.supplier_warehouse or None
|
po.supplier_warehouse = args.supplier_warehouse or None
|
||||||
|
|
||||||
po.append("items", {
|
if args.rm_items:
|
||||||
"item_code": args.item or args.item_code or "_Test Item",
|
for row in args.rm_items:
|
||||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
po.append("items", row)
|
||||||
"qty": args.qty or 10,
|
else:
|
||||||
"rate": args.rate or 500,
|
po.append("items", {
|
||||||
"schedule_date": add_days(nowdate(), 1),
|
"item_code": args.item or args.item_code or "_Test Item",
|
||||||
"include_exploded_items": args.get('include_exploded_items', 1),
|
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||||
"against_blanket_order": args.against_blanket_order
|
"qty": args.qty or 10,
|
||||||
})
|
"rate": args.rate or 500,
|
||||||
|
"schedule_date": add_days(nowdate(), 1),
|
||||||
|
"include_exploded_items": args.get('include_exploded_items', 1),
|
||||||
|
"against_blanket_order": args.against_blanket_order
|
||||||
|
})
|
||||||
|
|
||||||
|
po.set_missing_values()
|
||||||
if not args.do_not_save:
|
if not args.do_not_save:
|
||||||
po.insert()
|
po.insert()
|
||||||
if not args.do_not_submit:
|
if not args.do_not_submit:
|
||||||
if po.is_subcontracted == "Yes":
|
if po.is_subcontracted == "Yes":
|
||||||
supp_items = po.get("supplied_items")
|
supp_items = po.get("supplied_items")
|
||||||
for d in supp_items:
|
for d in supp_items:
|
||||||
d.reserve_warehouse = args.warehouse or "_Test Warehouse - _TC"
|
if not d.reserve_warehouse:
|
||||||
|
d.reserve_warehouse = args.warehouse or "_Test Warehouse - _TC"
|
||||||
po.submit()
|
po.submit()
|
||||||
|
|
||||||
return po
|
return po
|
||||||
|
|||||||
@@ -6,21 +6,25 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"main_item_code",
|
"main_item_code",
|
||||||
"bom_detail_no",
|
"rm_item_code",
|
||||||
|
"column_break_3",
|
||||||
"stock_uom",
|
"stock_uom",
|
||||||
|
"reserve_warehouse",
|
||||||
"conversion_factor",
|
"conversion_factor",
|
||||||
"column_break_6",
|
"column_break_6",
|
||||||
"rm_item_code",
|
"bom_detail_no",
|
||||||
"reference_name",
|
"reference_name",
|
||||||
"reserve_warehouse",
|
|
||||||
"section_break2",
|
"section_break2",
|
||||||
"rate",
|
"rate",
|
||||||
"col_break2",
|
"col_break2",
|
||||||
"amount",
|
"amount",
|
||||||
"section_break1",
|
"section_break1",
|
||||||
"required_qty",
|
"required_qty",
|
||||||
|
"supplied_qty",
|
||||||
"col_break1",
|
"col_break1",
|
||||||
"supplied_qty"
|
"consumed_qty",
|
||||||
|
"returned_qty",
|
||||||
|
"total_supplied_qty"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -125,6 +129,8 @@
|
|||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Supplied Qty",
|
"label": "Supplied Qty",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -142,13 +148,42 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "col_break2",
|
"fieldname": "col_break2",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "consumed_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Consumed Qty",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "returned_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Returned Qty",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "total_supplied_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Total Supplied Qty",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_3",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hide_toolbar": 1,
|
"hide_toolbar": 1,
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-09-18 17:26:09.703215",
|
"modified": "2021-06-09 15:17:58.128242",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order Item Supplied",
|
"name": "Purchase Order Item Supplied",
|
||||||
|
|||||||
@@ -6,10 +6,11 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"main_item_code",
|
"main_item_code",
|
||||||
"description",
|
"rm_item_code",
|
||||||
|
"item_name",
|
||||||
"bom_detail_no",
|
"bom_detail_no",
|
||||||
"col_break1",
|
"col_break1",
|
||||||
"rm_item_code",
|
"description",
|
||||||
"stock_uom",
|
"stock_uom",
|
||||||
"conversion_factor",
|
"conversion_factor",
|
||||||
"reference_name",
|
"reference_name",
|
||||||
@@ -25,7 +26,8 @@
|
|||||||
"secbreak_3",
|
"secbreak_3",
|
||||||
"batch_no",
|
"batch_no",
|
||||||
"col_break4",
|
"col_break4",
|
||||||
"serial_no"
|
"serial_no",
|
||||||
|
"purchase_order"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -52,7 +54,6 @@
|
|||||||
"fieldname": "description",
|
"fieldname": "description",
|
||||||
"fieldtype": "Text Editor",
|
"fieldtype": "Text Editor",
|
||||||
"in_global_search": 1,
|
"in_global_search": 1,
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Description",
|
"label": "Description",
|
||||||
"oldfieldname": "description",
|
"oldfieldname": "description",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
@@ -81,18 +82,20 @@
|
|||||||
"fieldname": "required_qty",
|
"fieldname": "required_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Required Qty",
|
"label": "Available Qty For Consumption",
|
||||||
"oldfieldname": "required_qty",
|
"oldfieldname": "required_qty",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"columns": 2,
|
||||||
"fieldname": "consumed_qty",
|
"fieldname": "consumed_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Consumed Qty",
|
"in_list_view": 1,
|
||||||
|
"label": "Qty to Be Consumed",
|
||||||
"oldfieldname": "consumed_qty",
|
"oldfieldname": "consumed_qty",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"read_only": 1,
|
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -183,12 +186,28 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "col_break4",
|
"fieldname": "col_break4",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "item_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Item Name",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "purchase_order",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Purchase Order",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Purchase Order",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-09-18 17:26:09.703215",
|
"modified": "2021-06-19 19:33:04.431213",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Receipt Item Supplied",
|
"name": "Purchase Receipt Item Supplied",
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -316,19 +317,21 @@ def add_items(sq_doc, supplier, items):
|
|||||||
create_rfq_items(sq_doc, supplier, data)
|
create_rfq_items(sq_doc, supplier, data)
|
||||||
|
|
||||||
def create_rfq_items(sq_doc, supplier, data):
|
def create_rfq_items(sq_doc, supplier, data):
|
||||||
sq_doc.append('items', {
|
args = {}
|
||||||
"item_code": data.item_code,
|
|
||||||
"item_name": data.item_name,
|
for field in ['item_code', 'item_name', 'description', 'qty', 'rate', 'conversion_factor',
|
||||||
"description": data.description,
|
'warehouse', 'material_request', 'material_request_item', 'stock_qty']:
|
||||||
"qty": data.qty,
|
args[field] = data.get(field)
|
||||||
"rate": data.rate,
|
|
||||||
"conversion_factor": data.conversion_factor if data.conversion_factor else None,
|
args.update({
|
||||||
"supplier_part_no": frappe.db.get_value("Item Supplier", {'parent': data.item_code, 'supplier': supplier}, "supplier_part_no"),
|
|
||||||
"warehouse": data.warehouse or '',
|
|
||||||
"request_for_quotation_item": data.name,
|
"request_for_quotation_item": data.name,
|
||||||
"request_for_quotation": data.parent
|
"request_for_quotation": data.parent,
|
||||||
|
"supplier_part_no": frappe.db.get_value("Item Supplier",
|
||||||
|
{'parent': data.item_code, 'supplier': supplier}, "supplier_part_no")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
sq_doc.append('items', args)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_pdf(doctype, name, supplier):
|
def get_pdf(doctype, name, supplier):
|
||||||
doc = get_rfq_doc(doctype, name, supplier)
|
doc = get_rfq_doc(doctype, name, supplier)
|
||||||
@@ -390,7 +393,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",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user