Merge pull request #25358 from noahjacob/maintenance_feat
refactor: maintenance schedule and visit enhancements.
diff --git a/.flake8 b/.flake8
index 399b176..56c9b9a 100644
--- a/.flake8
+++ b/.flake8
@@ -29,4 +29,5 @@
B950,
W191,
-max-line-length = 200
\ No newline at end of file
+max-line-length = 200
+exclude=.github/helper/semgrep_rules
diff --git a/.github/helper/semgrep_rules/README.md b/.github/helper/semgrep_rules/README.md
new file mode 100644
index 0000000..670d8d2
--- /dev/null
+++ b/.github/helper/semgrep_rules/README.md
@@ -0,0 +1,38 @@
+# Semgrep linting
+
+## What is semgrep?
+Semgrep or "semantic grep" is language agnostic static analysis tool. In simple terms semgrep is syntax-aware `grep`, so unlike regex it doesn't get confused by different ways of writing same thing or whitespaces or code split in multiple lines etc.
+
+Example:
+
+To check if a translate function is using f-string or not the regex would be `r"_\(\s*f[\"']"` while equivalent rule in semgrep would be `_(f"...")`. As semgrep knows grammer of language it takes care of unnecessary whitespace, type of quotation marks etc.
+
+You can read more such examples in `.github/helper/semgrep_rules` directory.
+
+# Why/when to use this?
+We want to maintain quality of contributions, at the same time remembering all the good practices can be pain to deal with while evaluating contributions. Using semgrep if you can translate "best practice" into a rule then it can automate the task for us.
+
+## Running locally
+
+Install semgrep using homebrew `brew install semgrep` or pip `pip install semgrep`.
+
+To run locally use following command:
+
+`semgrep --config=.github/helper/semgrep_rules [file/folder names]`
+
+## Testing
+semgrep allows testing the tests. Refer to this page: https://semgrep.dev/docs/writing-rules/testing-rules/
+
+When writing new rules you should write few positive and few negative cases as shown in the guide and current tests.
+
+To run current tests: `semgrep --test --test-ignore-todo .github/helper/semgrep_rules`
+
+
+## Reference
+
+If you are new to Semgrep read following pages to get started on writing/modifying rules:
+
+- https://semgrep.dev/docs/getting-started/
+- https://semgrep.dev/docs/writing-rules/rule-syntax
+- https://semgrep.dev/docs/writing-rules/pattern-examples/
+- https://semgrep.dev/docs/writing-rules/rule-ideas/#common-use-cases
diff --git a/.github/helper/semgrep_rules/frappe_correctness.py b/.github/helper/semgrep_rules/frappe_correctness.py
new file mode 100644
index 0000000..745e646
--- /dev/null
+++ b/.github/helper/semgrep_rules/frappe_correctness.py
@@ -0,0 +1,64 @@
+import frappe
+from frappe import _, flt
+
+from frappe.model.document import Document
+
+
+# ruleid: frappe-modifying-but-not-comitting
+def on_submit(self):
+ if self.value_of_goods == 0:
+ frappe.throw(_('Value of goods cannot be 0'))
+ self.status = 'Submitted'
+
+
+# ok: frappe-modifying-but-not-comitting
+def on_submit(self):
+ if self.value_of_goods == 0:
+ frappe.throw(_('Value of goods cannot be 0'))
+ self.status = 'Submitted'
+ self.db_set('status', 'Submitted')
+
+# ok: frappe-modifying-but-not-comitting
+def on_submit(self):
+ if self.value_of_goods == 0:
+ frappe.throw(_('Value of goods cannot be 0'))
+ x = "y"
+ self.status = x
+ self.db_set('status', x)
+
+
+# ok: frappe-modifying-but-not-comitting
+def on_submit(self):
+ x = "y"
+ self.status = x
+ self.save()
+
+# ruleid: frappe-modifying-but-not-comitting-other-method
+class DoctypeClass(Document):
+ def on_submit(self):
+ self.good_method()
+ self.tainted_method()
+
+ def tainted_method(self):
+ self.status = "uptate"
+
+
+# ok: frappe-modifying-but-not-comitting-other-method
+class DoctypeClass(Document):
+ def on_submit(self):
+ self.good_method()
+ self.tainted_method()
+
+ def tainted_method(self):
+ self.status = "update"
+ self.db_set("status", "update")
+
+# ok: frappe-modifying-but-not-comitting-other-method
+class DoctypeClass(Document):
+ def on_submit(self):
+ self.good_method()
+ self.tainted_method()
+ self.save()
+
+ def tainted_method(self):
+ self.status = "uptate"
diff --git a/.github/helper/semgrep_rules/frappe_correctness.yml b/.github/helper/semgrep_rules/frappe_correctness.yml
new file mode 100644
index 0000000..faab334
--- /dev/null
+++ b/.github/helper/semgrep_rules/frappe_correctness.yml
@@ -0,0 +1,135 @@
+# This file specifies rules for correctness according to how frappe doctype data model works.
+
+rules:
+- id: frappe-modifying-but-not-comitting
+ patterns:
+ - pattern: |
+ def $METHOD(self, ...):
+ ...
+ self.$ATTR = ...
+ - pattern-not: |
+ def $METHOD(self, ...):
+ ...
+ self.$ATTR = ...
+ ...
+ self.db_set(..., self.$ATTR, ...)
+ - pattern-not: |
+ def $METHOD(self, ...):
+ ...
+ self.$ATTR = $SOME_VAR
+ ...
+ self.db_set(..., $SOME_VAR, ...)
+ - pattern-not: |
+ def $METHOD(self, ...):
+ ...
+ self.$ATTR = $SOME_VAR
+ ...
+ self.save()
+ - metavariable-regex:
+ metavariable: '$ATTR'
+ # this is negative look-ahead, add more attrs to ignore like (ignore|ignore_this_too|ignore_me)
+ regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$'
+ - metavariable-regex:
+ metavariable: "$METHOD"
+ regex: "(on_submit|on_cancel)"
+ message: |
+ DocType modified in self.$METHOD. Please check if modification of self.$ATTR is commited to database.
+ languages: [python]
+ severity: ERROR
+
+- id: frappe-modifying-but-not-comitting-other-method
+ patterns:
+ - pattern: |
+ class $DOCTYPE(...):
+ def $METHOD(self, ...):
+ ...
+ self.$ANOTHER_METHOD()
+ ...
+
+ def $ANOTHER_METHOD(self, ...):
+ ...
+ self.$ATTR = ...
+ - pattern-not: |
+ class $DOCTYPE(...):
+ def $METHOD(self, ...):
+ ...
+ self.$ANOTHER_METHOD()
+ ...
+
+ def $ANOTHER_METHOD(self, ...):
+ ...
+ self.$ATTR = ...
+ ...
+ self.db_set(..., self.$ATTR, ...)
+ - pattern-not: |
+ class $DOCTYPE(...):
+ def $METHOD(self, ...):
+ ...
+ self.$ANOTHER_METHOD()
+ ...
+
+ def $ANOTHER_METHOD(self, ...):
+ ...
+ self.$ATTR = $SOME_VAR
+ ...
+ self.db_set(..., $SOME_VAR, ...)
+ - pattern-not: |
+ class $DOCTYPE(...):
+ def $METHOD(self, ...):
+ ...
+ self.$ANOTHER_METHOD()
+ ...
+ self.save()
+ def $ANOTHER_METHOD(self, ...):
+ ...
+ self.$ATTR = ...
+ - metavariable-regex:
+ metavariable: "$METHOD"
+ regex: "(on_submit|on_cancel)"
+ message: |
+ self.$ANOTHER_METHOD is called from self.$METHOD, check if changes to self.$ATTR are commited to database.
+ languages: [python]
+ severity: ERROR
+
+- id: frappe-print-function-in-doctypes
+ pattern: print(...)
+ message: |
+ Did you mean to leave this print statement in? Consider using msgprint or logger instead of print statement.
+ languages: [python]
+ severity: WARNING
+ paths:
+ exclude:
+ - test_*.py
+ include:
+ - "*/**/doctype/*"
+
+- id: frappe-modifying-child-tables-while-iterating
+ pattern-either:
+ - pattern: |
+ for $ROW in self.$TABLE:
+ ...
+ self.remove(...)
+ - pattern: |
+ for $ROW in self.$TABLE:
+ ...
+ self.append(...)
+ message: |
+ Child table being modified while iterating on it.
+ languages: [python]
+ severity: ERROR
+ paths:
+ include:
+ - "*/**/doctype/*"
+
+- id: frappe-same-key-assigned-twice
+ pattern-either:
+ - pattern: |
+ {..., $X: $A, ..., $X: $B, ...}
+ - pattern: |
+ dict(..., ($X, $A), ..., ($X, $B), ...)
+ - pattern: |
+ _dict(..., ($X, $A), ..., ($X, $B), ...)
+ message: |
+ key `$X` is uselessly assigned twice. This could be a potential bug.
+ languages: [python]
+ severity: ERROR
diff --git a/.github/helper/semgrep_rules/security.py b/.github/helper/semgrep_rules/security.py
new file mode 100644
index 0000000..f477d7c
--- /dev/null
+++ b/.github/helper/semgrep_rules/security.py
@@ -0,0 +1,6 @@
+def function_name(input):
+ # ruleid: frappe-codeinjection-eval
+ eval(input)
+
+# ok: frappe-codeinjection-eval
+eval("1 + 1")
diff --git a/.github/helper/semgrep_rules/security.yml b/.github/helper/semgrep_rules/security.yml
new file mode 100644
index 0000000..5a5098b
--- /dev/null
+++ b/.github/helper/semgrep_rules/security.yml
@@ -0,0 +1,25 @@
+rules:
+- id: frappe-codeinjection-eval
+ patterns:
+ - pattern-not: eval("...")
+ - pattern: eval(...)
+ message: |
+ Detected the use of eval(). eval() can be dangerous if used to evaluate
+ dynamic content. Avoid it or use safe_eval().
+ languages: [python]
+ severity: ERROR
+
+- id: frappe-sqli-format-strings
+ patterns:
+ - pattern-inside: |
+ @frappe.whitelist()
+ def $FUNC(...):
+ ...
+ - pattern-either:
+ - pattern: frappe.db.sql("..." % ...)
+ - pattern: frappe.db.sql(f"...", ...)
+ - pattern: frappe.db.sql("...".format(...), ...)
+ message: |
+ Detected use of raw string formatting for SQL queries. This can lead to sql injection vulnerabilities. Refer security guidelines - https://github.com/frappe/erpnext/wiki/Code-Security-Guidelines
+ languages: [python]
+ severity: WARNING
diff --git a/.github/helper/semgrep_rules/translate.js b/.github/helper/semgrep_rules/translate.js
new file mode 100644
index 0000000..9cdfb75
--- /dev/null
+++ b/.github/helper/semgrep_rules/translate.js
@@ -0,0 +1,44 @@
+// ruleid: frappe-translation-empty-string
+__("")
+// ruleid: frappe-translation-empty-string
+__('')
+
+// ok: frappe-translation-js-formatting
+__('Welcome {0}, get started with ERPNext in just a few clicks.', [full_name]);
+
+// ruleid: frappe-translation-js-formatting
+__(`Welcome ${full_name}, get started with ERPNext in just a few clicks.`);
+
+// ok: frappe-translation-js-formatting
+__('This is fine');
+
+
+// ok: frappe-translation-trailing-spaces
+__('This is fine');
+
+// ruleid: frappe-translation-trailing-spaces
+__(' this is not ok ');
+// ruleid: frappe-translation-trailing-spaces
+__('this is not ok ');
+// ruleid: frappe-translation-trailing-spaces
+__(' this is not ok');
+
+// ok: frappe-translation-js-splitting
+__('You have {0} subscribers in your mailing list.', [subscribers.length])
+
+// todoruleid: frappe-translation-js-splitting
+__('You have') + subscribers.length + __('subscribers in your mailing list.')
+
+// ruleid: frappe-translation-js-splitting
+__('You have' + 'subscribers in your mailing list.')
+
+// ruleid: frappe-translation-js-splitting
+__('You have {0} subscribers' +
+ 'in your mailing list', [subscribers.length])
+
+// ok: frappe-translation-js-splitting
+__("Ctrl+Enter to add comment")
+
+// ruleid: frappe-translation-js-splitting
+__('You have {0} subscribers \
+ in your mailing list', [subscribers.length])
diff --git a/.github/helper/semgrep_rules/translate.py b/.github/helper/semgrep_rules/translate.py
new file mode 100644
index 0000000..9de6aa9
--- /dev/null
+++ b/.github/helper/semgrep_rules/translate.py
@@ -0,0 +1,61 @@
+# Examples taken from https://frappeframework.com/docs/user/en/translations
+# This file is used for testing the tests.
+
+from frappe import _
+
+full_name = "Jon Doe"
+# ok: frappe-translation-python-formatting
+_('Welcome {0}, get started with ERPNext in just a few clicks.').format(full_name)
+
+# ruleid: frappe-translation-python-formatting
+_('Welcome %s, get started with ERPNext in just a few clicks.' % full_name)
+# ruleid: frappe-translation-python-formatting
+_('Welcome %(name)s, get started with ERPNext in just a few clicks.' % {'name': full_name})
+
+# ruleid: frappe-translation-python-formatting
+_('Welcome {0}, get started with ERPNext in just a few clicks.'.format(full_name))
+
+
+subscribers = ["Jon", "Doe"]
+# ok: frappe-translation-python-formatting
+_('You have {0} subscribers in your mailing list.').format(len(subscribers))
+
+# ruleid: frappe-translation-python-splitting
+_('You have') + len(subscribers) + _('subscribers in your mailing list.')
+
+# ruleid: frappe-translation-python-splitting
+_('You have {0} subscribers \
+ in your mailing list').format(len(subscribers))
+
+# ok: frappe-translation-python-splitting
+_('You have {0} subscribers') \
+ + 'in your mailing list'
+
+# ruleid: frappe-translation-trailing-spaces
+msg = _(" You have {0} pending invoice ")
+# ruleid: frappe-translation-trailing-spaces
+msg = _("You have {0} pending invoice ")
+# ruleid: frappe-translation-trailing-spaces
+msg = _(" You have {0} pending invoice")
+
+# ok: frappe-translation-trailing-spaces
+msg = ' ' + _("You have {0} pending invoices") + ' '
+
+# ruleid: frappe-translation-python-formatting
+_(f"can not format like this - {subscribers}")
+# ruleid: frappe-translation-python-splitting
+_(f"what" + f"this is also not cool")
+
+
+# ruleid: frappe-translation-empty-string
+_("")
+# ruleid: frappe-translation-empty-string
+_('')
+
+
+class Test:
+ # ok: frappe-translation-python-splitting
+ def __init__(
+ args
+ ):
+ pass
diff --git a/.github/helper/semgrep_rules/translate.yml b/.github/helper/semgrep_rules/translate.yml
new file mode 100644
index 0000000..5f03fb9
--- /dev/null
+++ b/.github/helper/semgrep_rules/translate.yml
@@ -0,0 +1,64 @@
+rules:
+- id: frappe-translation-empty-string
+ pattern-either:
+ - pattern: _("")
+ - pattern: __("")
+ message: |
+ Empty string is useless for translation.
+ Please refer: https://frappeframework.com/docs/user/en/translations
+ languages: [python, javascript, json]
+ severity: ERROR
+
+- id: frappe-translation-trailing-spaces
+ pattern-either:
+ - pattern: _("=~/(^[ \t]+|[ \t]+$)/")
+ - pattern: __("=~/(^[ \t]+|[ \t]+$)/")
+ message: |
+ Trailing or leading whitespace not allowed in translate strings.
+ Please refer: https://frappeframework.com/docs/user/en/translations
+ languages: [python, javascript, json]
+ severity: ERROR
+
+- id: frappe-translation-python-formatting
+ pattern-either:
+ - pattern: _("..." % ...)
+ - pattern: _("...".format(...))
+ - pattern: _(f"...")
+ message: |
+ Only positional formatters are allowed and formatting should not be done before translating.
+ Please refer: https://frappeframework.com/docs/user/en/translations
+ languages: [python]
+ severity: ERROR
+
+- id: frappe-translation-js-formatting
+ patterns:
+ - pattern: __(`...`)
+ - pattern-not: __("...")
+ message: |
+ Template strings are not allowed for text formatting.
+ Please refer: https://frappeframework.com/docs/user/en/translations
+ languages: [javascript, json]
+ severity: ERROR
+
+- id: frappe-translation-python-splitting
+ pattern-either:
+ - pattern: _(...) + _(...)
+ - pattern: _("..." + "...")
+ - pattern-regex: '[\s\.]_\([^\)]*\\\s*' # lines broken by `\`
+ - pattern-regex: '[\s\.]_\(\s*\n' # line breaks allowed by python for using ( )
+ message: |
+ Do not split strings inside translate function. Do not concatenate using translate functions.
+ Please refer: https://frappeframework.com/docs/user/en/translations
+ languages: [python]
+ severity: ERROR
+
+- id: frappe-translation-js-splitting
+ pattern-either:
+ - pattern-regex: '__\([^\)]*[\\]\s+'
+ - pattern: __('...' + '...', ...)
+ - pattern: __('...') + __('...')
+ message: |
+ Do not split strings inside translate function. Do not concatenate using translate functions.
+ Please refer: https://frappeframework.com/docs/user/en/translations
+ languages: [javascript, json]
+ severity: ERROR
diff --git a/.github/helper/semgrep_rules/ux.js b/.github/helper/semgrep_rules/ux.js
new file mode 100644
index 0000000..ae73f9c
--- /dev/null
+++ b/.github/helper/semgrep_rules/ux.js
@@ -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") }}. ');
diff --git a/.github/helper/semgrep_rules/ux.py b/.github/helper/semgrep_rules/ux.py
new file mode 100644
index 0000000..a00d3cd
--- /dev/null
+++ b/.github/helper/semgrep_rules/ux.py
@@ -0,0 +1,31 @@
+import frappe
+from frappe import msgprint, throw, _
+
+
+# ruleid: frappe-missing-translate-function-python
+throw("Error Occured")
+
+# ruleid: frappe-missing-translate-function-python
+frappe.throw("Error Occured")
+
+# ruleid: frappe-missing-translate-function-python
+frappe.msgprint("Useful message")
+
+# ruleid: frappe-missing-translate-function-python
+msgprint("Useful message")
+
+
+# ok: frappe-missing-translate-function-python
+translatedmessage = _("Hello")
+
+# ok: frappe-missing-translate-function-python
+throw(translatedmessage)
+
+# ok: frappe-missing-translate-function-python
+msgprint(translatedmessage)
+
+# ok: frappe-missing-translate-function-python
+msgprint(_("Helpful message"))
+
+# ok: frappe-missing-translate-function-python
+frappe.throw(_("Error occured"))
diff --git a/.github/helper/semgrep_rules/ux.yml b/.github/helper/semgrep_rules/ux.yml
new file mode 100644
index 0000000..dd667f3
--- /dev/null
+++ b/.github/helper/semgrep_rules/ux.yml
@@ -0,0 +1,30 @@
+rules:
+- id: frappe-missing-translate-function-python
+ pattern-either:
+ - patterns:
+ - pattern: frappe.msgprint("...", ...)
+ - pattern-not: frappe.msgprint(_("..."), ...)
+ - patterns:
+ - pattern: frappe.throw("...", ...)
+ - pattern-not: frappe.throw(_("..."), ...)
+ message: |
+ All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations
+ languages: [python]
+ severity: ERROR
+
+- id: frappe-missing-translate-function-js
+ pattern-either:
+ - patterns:
+ - pattern: frappe.msgprint("...", ...)
+ - pattern-not: frappe.msgprint(__("..."), ...)
+ # ignore microtemplating e.g. msgprint("{{ _("server side translation") }}")
+ - pattern-not: frappe.msgprint("=~/\{\{.*\_.*\}\}/i", ...)
+ - patterns:
+ - pattern: frappe.throw("...", ...)
+ - pattern-not: frappe.throw(__("..."), ...)
+ # ignore microtemplating
+ - pattern-not: frappe.throw("=~/\{\{.*\_.*\}\}/i", ...)
+ message: |
+ All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations
+ languages: [javascript]
+ severity: ERROR
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/patch.yml
similarity index 60%
copy from .github/workflows/ci-tests.yml
copy to .github/workflows/patch.yml
index 78c2f5a..7c9e027 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/patch.yml
@@ -1,24 +1,12 @@
-name: CI
+name: Patch
-on: [pull_request, workflow_dispatch, push]
+on: [pull_request, workflow_dispatch]
jobs:
test:
runs-on: ubuntu-18.04
- strategy:
- fail-fast: false
-
- matrix:
- include:
- - TYPE: "server"
- JOB_NAME: "Server"
- RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-tests --app erpnext --coverage
- - TYPE: "patch"
- JOB_NAME: "Patch"
- RUN_COMMAND: 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
-
- name: ${{ matrix.JOB_NAME }}
+ name: Patch Test
services:
mysql:
@@ -49,6 +37,7 @@
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
+
- name: Cache node modules
uses: actions/cache@v2
env:
@@ -60,6 +49,7 @@
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
+
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
@@ -75,20 +65,5 @@
- name: Install
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
- - name: Run Tests
- run: ${{ matrix.RUN_COMMAND }}
- env:
- TYPE: ${{ matrix.TYPE }}
-
- - name: Coverage
- if: matrix.TYPE == 'server'
- run: |
- cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
- cd ${GITHUB_WORKSPACE}
- pip install coveralls==2.2.0
- pip install coverage==4.5.4
- coveralls
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
-
+ - 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
diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml
new file mode 100644
index 0000000..389524e
--- /dev/null
+++ b/.github/workflows/semgrep.yml
@@ -0,0 +1,34 @@
+name: Semgrep
+
+on:
+ pull_request:
+ branches:
+ - develop
+ - version-13-hotfix
+ - version-13-pre-release
+jobs:
+ semgrep:
+ name: Frappe Linter
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup python3
+ uses: actions/setup-python@v2
+ with:
+ python-version: 3.8
+
+ - name: Setup semgrep
+ run: |
+ python -m pip install -q semgrep
+ 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)
+ [[ -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
+
+ - 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
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/server-tests.yml
similarity index 63%
rename from .github/workflows/ci-tests.yml
rename to .github/workflows/server-tests.yml
index 78c2f5a..92685e2 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/server-tests.yml
@@ -1,6 +1,6 @@
-name: CI
+name: Server
-on: [pull_request, workflow_dispatch, push]
+on: [pull_request, workflow_dispatch]
jobs:
test:
@@ -10,15 +10,9 @@
fail-fast: false
matrix:
- include:
- - TYPE: "server"
- JOB_NAME: "Server"
- RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-tests --app erpnext --coverage
- - TYPE: "patch"
- JOB_NAME: "Patch"
- RUN_COMMAND: 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
+ container: [1, 2, 3]
- name: ${{ matrix.JOB_NAME }}
+ name: Python Unit Tests
services:
mysql:
@@ -36,7 +30,7 @@
- name: Setup Python
uses: actions/setup-python@v2
with:
- python-version: 3.6
+ python-version: 3.7
- name: Add to Hosts
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
@@ -49,6 +43,7 @@
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
+
- name: Cache node modules
uses: actions/cache@v2
env:
@@ -60,6 +55,7 @@
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
+
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
@@ -76,19 +72,39 @@
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
- name: Run Tests
- run: ${{ matrix.RUN_COMMAND }}
+ run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage
env:
- TYPE: ${{ matrix.TYPE }}
+ TYPE: server
+ CI_BUILD_ID: ${{ github.run_id }}
+ ORCHESTRATOR_URL: http://test-orchestrator.frappe.io
- - name: Coverage
- if: matrix.TYPE == 'server'
+ - name: Upload Coverage Data
run: |
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
cd ${GITHUB_WORKSPACE}
- pip install coveralls==2.2.0
- pip install coverage==4.5.4
+ pip3 install coverage==5.5
+ pip3 install coveralls==3.0.1
coveralls
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
+ COVERALLS_FLAG_NAME: run-${{ matrix.container }}
+ COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }}
+ COVERALLS_PARALLEL: true
+ coveralls:
+ name: Coverage Wrap Up
+ needs: test
+ container: python:3-slim
+ runs-on: ubuntu-18.04
+ steps:
+ - name: Clone
+ uses: actions/checkout@v2
+
+ - name: Coveralls Finished
+ run: |
+ cd ${GITHUB_WORKSPACE}
+ pip3 install coverage==5.5
+ pip3 install coveralls==3.0.1
+ coveralls --finish
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.pylintrc b/.pylintrc
deleted file mode 100644
index 4b2ea0a..0000000
--- a/.pylintrc
+++ /dev/null
@@ -1 +0,0 @@
-disable=access-member-before-definition
\ No newline at end of file
diff --git a/README.md b/README.md
index bb592ae..0a556f5 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,10 @@
---
+### Containerized Installation
+
+Use docker to deploy ERPNext in production or for development of [Frappe](https://github.com/frappe/frappe) apps. See https://github.com/frappe/frappe_docker for more details.
+
### Full Install
The Easy Way: our install script for bench will install all dependencies (e.g. MariaDB). See https://github.com/frappe/bench for more details.
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 199a183..a988d72 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
-__version__ = '13.0.0-dev'
+__version__ = '13.2.0'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py
index d5ab1c1..dd346bc 100644
--- a/erpnext/accounts/deferred_revenue.py
+++ b/erpnext/accounts/deferred_revenue.py
@@ -41,7 +41,7 @@
if account:
conditions += "AND %s='%s'"%(deferred_account, account)
elif company:
- conditions += "AND p.company='%s'"%(company)
+ conditions += f"AND p.company = {frappe.db.escape(company)}"
return conditions
@@ -360,12 +360,10 @@
frappe.flags.deferred_accounting_error = True
def send_mail(deferred_process):
- title = _("Error while processing deferred accounting for {0}".format(deferred_process))
- content = _("""
- Deferred accounting failed for some invoices:
- Please check Process Deferred Accounting {0}
- and submit manually after resolving errors
- """).format(get_link_to_form('Process Deferred Accounting', deferred_process))
+ title = _("Error while processing deferred accounting for {0}").format(deferred_process)
+ link = get_link_to_form('Process Deferred Accounting', deferred_process)
+ content = _("Deferred accounting failed for some invoices:") + "\n"
+ content += _("Please check Process Deferred Accounting {0} and submit manually after resolving errors.").format(link)
sendmail_to_system_managers(title, content)
def book_revenue_via_journal_entry(doc, credit_account, debit_account, against,
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index 0606823..1be2fbf 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -13,7 +13,7 @@
class Account(NestedSet):
nsm_parent_field = 'parent_account'
def on_update(self):
- if frappe.local.flags.ignore_on_update:
+ if frappe.local.flags.ignore_update_nsm:
return
else:
super(Account, self).on_update()
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
index 0e3b24c..927adc7 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
@@ -57,10 +57,10 @@
# Rebuild NestedSet HSM tree for Account Doctype
# after all accounts are already inserted.
- frappe.local.flags.ignore_on_update = True
+ frappe.local.flags.ignore_update_nsm = True
_import_accounts(chart, None, None, root_account=True)
rebuild_tree("Account", "parent_account")
- frappe.local.flags.ignore_on_update = False
+ frappe.local.flags.ignore_update_nsm = False
def add_suffix_if_duplicate(account_name, account_number, accounts):
if account_number:
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index 0ebf0eb..7cd1e77 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -27,7 +27,7 @@
exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name'])
if exists and self.is_new():
- frappe.throw("Document Type already used as a dimension")
+ frappe.throw(_("Document Type already used as a dimension"))
if not self.is_new():
self.validate_document_type_change()
diff --git a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
index fc1d7e3..e657a9a 100644
--- a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
@@ -7,7 +7,8 @@
import unittest
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
-from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import delete_accounting_dimension
+
+test_dependencies = ['Cost Center', 'Location', 'Warehouse', 'Department']
class TestAccountingDimension(unittest.TestCase):
def setUp(self):
diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
index 7877abd..7f6254f 100644
--- a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
+++ b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
@@ -9,6 +9,8 @@
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import create_dimension, disable_dimension
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
+test_dependencies = ['Location', 'Cost Center', 'Department']
+
class TestAccountingDimensionFilter(unittest.TestCase):
def setUp(self):
create_dimension()
diff --git a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py
index 10cd939..dc472c7 100644
--- a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py
+++ b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py
@@ -10,6 +10,8 @@
from erpnext.accounts.doctype.accounting_period.accounting_period import OverlapError
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+test_dependencies = ['Item']
+
class TestAccountingPeriod(unittest.TestCase):
def test_overlap(self):
ap1 = create_accounting_period(start_date = "2018-04-01",
@@ -38,7 +40,7 @@
accounting_period.start_date = args.start_date or nowdate()
accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
accounting_period.company = args.company or "_Test Company"
- accounting_period.period_name =args.period_name or "_Test_Period_Name_1"
+ accounting_period.period_name = args.period_name or "_Test_Period_Name_1"
accounting_period.append("closed_documents", {
"document_type": 'Sales Invoice', "closed": 1
})
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index a3c29b6..781f94e 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -7,25 +7,30 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
- "auto_accounting_for_stock",
- "acc_frozen_upto",
- "frozen_accounts_modifier",
- "determine_address_tax_category_from",
+ "accounts_transactions_settings_section",
"over_billing_allowance",
- "column_break_4",
- "credit_controller",
- "check_supplier_invoice_uniqueness",
+ "role_allowed_to_over_bill",
"make_payment_via_journal_entry",
+ "column_break_11",
+ "check_supplier_invoice_uniqueness",
"unlink_payment_on_cancellation_of_invoice",
- "unlink_advance_payment_on_cancelation_of_order",
- "book_asset_depreciation_entry_automatically",
- "add_taxes_from_item_tax_template",
"automatically_fetch_payment_terms",
"delete_linked_ledger_entries",
+ "book_asset_depreciation_entry_automatically",
+ "unlink_advance_payment_on_cancelation_of_order",
+ "tax_settings_section",
+ "determine_address_tax_category_from",
+ "column_break_19",
+ "add_taxes_from_item_tax_template",
+ "period_closing_settings_section",
+ "acc_frozen_upto",
+ "frozen_accounts_modifier",
+ "column_break_4",
+ "credit_controller",
"deferred_accounting_settings_section",
- "automatically_process_deferred_accounting_entry",
"book_deferred_entries_based_on",
"column_break_18",
+ "automatically_process_deferred_accounting_entry",
"book_deferred_entries_via_journal_entry",
"submit_journal_entries",
"print_settings",
@@ -40,15 +45,6 @@
],
"fields": [
{
- "default": "1",
- "description": "If enabled, the system will post accounting entries for inventory automatically",
- "fieldname": "auto_accounting_for_stock",
- "fieldtype": "Check",
- "hidden": 1,
- "in_list_view": 1,
- "label": "Make Accounting Entry For Every Stock Movement"
- },
- {
"description": "Accounting entries are frozen up to this date. Nobody can create or modify entries except users with the role specified below",
"fieldname": "acc_frozen_upto",
"fieldtype": "Date",
@@ -93,6 +89,7 @@
"default": "0",
"fieldname": "make_payment_via_journal_entry",
"fieldtype": "Check",
+ "hidden": 1,
"label": "Make Payment via Journal Entry"
},
{
@@ -226,6 +223,36 @@
"fieldname": "delete_linked_ledger_entries",
"fieldtype": "Check",
"label": "Delete Accounting and Stock Ledger Entries on deletion of Transaction"
+ },
+ {
+ "description": "Users with this role are allowed to over bill above the allowance percentage",
+ "fieldname": "role_allowed_to_over_bill",
+ "fieldtype": "Link",
+ "label": "Role Allowed to Over Bill ",
+ "options": "Role"
+ },
+ {
+ "fieldname": "period_closing_settings_section",
+ "fieldtype": "Section Break",
+ "label": "Period Closing Settings"
+ },
+ {
+ "fieldname": "accounts_transactions_settings_section",
+ "fieldtype": "Section Break",
+ "label": "Transactions Settings"
+ },
+ {
+ "fieldname": "column_break_11",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "tax_settings_section",
+ "fieldtype": "Section Break",
+ "label": "Tax Settings"
+ },
+ {
+ "fieldname": "column_break_19",
+ "fieldtype": "Column Break"
}
],
"icon": "icon-cog",
@@ -233,7 +260,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-01-05 13:04:00.118892",
+ "modified": "2021-04-30 15:25:10.381008",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
index 5593466..ac4a2d6 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
@@ -5,6 +5,7 @@
from __future__ import unicode_literals
import frappe
+from frappe import _
from frappe.utils import cint
from frappe.model.document import Document
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
@@ -24,11 +25,11 @@
def validate_stale_days(self):
if not self.allow_stale and cint(self.stale_days) <= 0:
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)
def enable_payment_schedule_in_print(self):
show_in_print = cint(self.show_payment_schedule_in_print)
for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"):
- make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check")
- make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check")
+ make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check", validate_fields_for_doctype=False)
+ make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check", validate_fields_for_doctype=False)
diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js
index 49b2b18..19041a3 100644
--- a/erpnext/accounts/doctype/bank/bank.js
+++ b/erpnext/accounts/doctype/bank/bank.js
@@ -42,10 +42,9 @@
});
});
- frappe.meta.get_docfield("Bank Transaction Mapping", "bank_transaction_field",
- frm.doc.name).options = options;
-
- frm.fields_dict.bank_transaction_mapping.grid.refresh();
+ frm.fields_dict.bank_transaction_mapping.grid.update_docfield_property(
+ 'bank_transaction_field', 'options', options
+ );
};
erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
@@ -121,4 +120,4 @@
plaid_success(token, response) {
frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
}
-};
+};
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
index 10f660a..f7d471b 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
@@ -78,8 +78,7 @@
if (
frm.doc.bank_account &&
frm.doc.bank_statement_from_date &&
- frm.doc.bank_statement_to_date &&
- frm.doc.bank_statement_closing_balance
+ frm.doc.bank_statement_to_date
) {
frm.trigger("render_chart");
frm.trigger("render");
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json
index 4837db3..b643e6e 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json
@@ -39,13 +39,13 @@
"depends_on": "eval: doc.bank_account",
"fieldname": "bank_statement_from_date",
"fieldtype": "Date",
- "label": "Bank Statement From Date"
+ "label": "From Date"
},
{
"depends_on": "eval: doc.bank_statement_from_date",
"fieldname": "bank_statement_to_date",
"fieldtype": "Date",
- "label": "Bank Statement To Date"
+ "label": "To Date"
},
{
"fieldname": "column_break_2",
@@ -63,11 +63,10 @@
"depends_on": "eval: doc.bank_statement_to_date",
"fieldname": "bank_statement_closing_balance",
"fieldtype": "Currency",
- "label": "Bank Statement Closing Balance",
+ "label": "Closing Balance",
"options": "Currency"
},
{
- "depends_on": "eval: doc.bank_statement_closing_balance",
"fieldname": "section_break_1",
"fieldtype": "Section Break",
"label": "Reconcile"
@@ -90,7 +89,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-02-02 01:35:53.043578",
+ "modified": "2021-04-21 11:13:49.831769",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Reconciliation Tool",
diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js
index 3dbd605..016f29a 100644
--- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js
+++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js
@@ -239,6 +239,7 @@
"withdrawal",
"description",
"reference_number",
+ "bank_account"
],
},
});
diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json
index 5e913cc..7ffff02 100644
--- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json
+++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json
@@ -146,7 +146,7 @@
},
{
"depends_on": "eval:!doc.__islocal && !doc.import_file\n",
- "description": "Must be a publicly accessible Google Sheets URL",
+ "description": "Must be a publicly accessible Google Sheets URL and adding Bank Account column is necessary for importing via Google Sheets",
"fieldname": "google_sheets_url",
"fieldtype": "Data",
"label": "Import from Google Sheets"
@@ -202,7 +202,7 @@
],
"hide_toolbar": 1,
"links": [],
- "modified": "2021-02-10 19:29:59.027325",
+ "modified": "2021-05-12 14:17:37.777246",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Statement Import",
@@ -224,4 +224,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
index 9f41b13..5f110e2 100644
--- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
+++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
@@ -47,6 +47,13 @@
def start_import(self):
+ preview = frappe.get_doc("Bank Statement Import", self.name).get_preview_from_template(
+ self.import_file, self.google_sheets_url
+ )
+
+ if 'Bank Account' not in json.dumps(preview):
+ frappe.throw(_("Please add the Bank Account column"))
+
from frappe.core.page.background_jobs.background_jobs import get_info
from frappe.utils.scheduler import is_scheduler_inactive
@@ -67,6 +74,7 @@
data_import=self.name,
bank_account=self.bank_account,
import_file_path=self.import_file,
+ google_sheets_url=self.google_sheets_url,
bank=self.bank,
template_options=self.template_options,
now=frappe.conf.developer_mode or frappe.flags.in_test,
@@ -90,18 +98,20 @@
data_import = frappe.get_doc("Bank Statement Import", data_import_name)
data_import.export_errored_rows()
-def start_import(data_import, bank_account, import_file_path, bank, template_options):
+def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options):
"""This method runs in background job"""
update_mapping_db(bank, template_options)
data_import = frappe.get_doc("Bank Statement Import", data_import)
+ file = import_file_path if import_file_path else google_sheets_url
- import_file = ImportFile("Bank Transaction", file = import_file_path, import_type="Insert New Records")
+ import_file = ImportFile("Bank Transaction", file = file, import_type="Insert New Records")
data = import_file.raw_data
- add_bank_account(data, bank_account)
- write_files(import_file, data)
+ if import_file_path:
+ add_bank_account(data, bank_account)
+ write_files(import_file, data)
try:
i = Importer(data_import.reference_doctype, data_import=data_import)
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json
index 69ee497..88aa7ef 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json
@@ -175,22 +175,24 @@
},
{
"fieldname": "deposit",
- "oldfieldname": "debit",
"fieldtype": "Currency",
"in_list_view": 1,
- "label": "Deposit"
+ "label": "Deposit",
+ "oldfieldname": "debit",
+ "options": "currency"
},
{
"fieldname": "withdrawal",
- "oldfieldname": "credit",
"fieldtype": "Currency",
"in_list_view": 1,
- "label": "Withdrawal"
+ "label": "Withdrawal",
+ "oldfieldname": "credit",
+ "options": "currency"
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-12-30 19:40:54.221070",
+ "modified": "2021-04-14 17:31:58.963529",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Transaction",
diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py
index c5ec23c..603e21e 100644
--- a/erpnext/accounts/doctype/budget/test_budget.py
+++ b/erpnext/accounts/doctype/budget/test_budget.py
@@ -11,6 +11,8 @@
from erpnext.accounts.doctype.budget.budget import get_actual_expense, BudgetError
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
+test_dependencies = ['Monthly Distribution']
+
class TestBudget(unittest.TestCase):
def test_monthly_budget_crossed_ignore(self):
set_total_expense_zero(nowdate(), "cost_center")
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
index f96f591..ef44626 100644
--- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
@@ -22,7 +22,7 @@
'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(
frappe.bold('Allow Account Creation Against Child Company'))
frappe.throw(msg, title=_('Wrong Company'))
@@ -56,7 +56,7 @@
extension = extension.lstrip(".")
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
diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py
index cb18309..e2d4d82 100644
--- a/erpnext/accounts/doctype/dunning/test_dunning.py
+++ b/erpnext/accounts/doctype/dunning/test_dunning.py
@@ -29,7 +29,7 @@
self.assertEqual(round(amounts.get('interest_amount'), 2), 0.44)
self.assertEqual(round(amounts.get('dunning_amount'), 2), 20.44)
self.assertEqual(round(amounts.get('grand_total'), 2), 120.44)
-
+
def test_gl_entries(self):
dunning = create_dunning()
dunning.submit()
@@ -42,9 +42,9 @@
['Sales - _TC', 0.0, 20.44]
])
for gle in gl_entries:
- self.assertEquals(expected_values[gle.account][0], gle.account)
- self.assertEquals(expected_values[gle.account][1], gle.debit)
- self.assertEquals(expected_values[gle.account][2], gle.credit)
+ self.assertEqual(expected_values[gle.account][0], gle.account)
+ self.assertEqual(expected_values[gle.account][1], gle.debit)
+ self.assertEqual(expected_values[gle.account][2], gle.credit)
def test_payment_entry(self):
dunning = create_dunning()
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js
index 1092f4c..b7b6020 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js
@@ -21,21 +21,17 @@
refresh: function(frm) {
if(frm.doc.docstatus==1) {
- frappe.db.get_value("Journal Entry Account", {
- 'reference_type': 'Exchange Rate Revaluation',
- 'reference_name': frm.doc.name,
- 'docstatus': 1
- }, "sum(debit) as sum", (r) =>{
- let total_amt = 0;
- frm.doc.accounts.forEach(d=> {
- total_amt = total_amt + d['new_balance_in_base_currency'];
- });
- if(total_amt !== r.sum) {
- frm.add_custom_button(__('Journal Entry'), function() {
- return frm.events.make_jv(frm);
- }, __('Create'));
+ frappe.call({
+ method: 'check_journal_entry_condition',
+ doc: frm.doc,
+ callback: function(r) {
+ if (r.message) {
+ frm.add_custom_button(__('Journal Entry'), function() {
+ return frm.events.make_jv(frm);
+ }, __('Create'));
+ }
}
- }, 'Journal Entry');
+ });
}
},
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
index c1b8ba7..5619321 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
@@ -28,6 +28,23 @@
frappe.throw(_("Please select Company and Posting Date to getting entries"))
@frappe.whitelist()
+ def check_journal_entry_condition(self):
+ total_debit = frappe.db.get_value("Journal Entry Account", {
+ 'reference_type': 'Exchange Rate Revaluation',
+ 'reference_name': self.name,
+ 'docstatus': 1
+ }, "sum(debit) as sum")
+
+ total_amt = 0
+ for d in self.accounts:
+ total_amt = total_amt + d.new_balance_in_base_currency
+
+ if total_amt != total_debit:
+ return True
+
+ return False
+
+ @frappe.whitelist()
def get_accounts_data(self, account=None):
accounts = []
self.validate_mandatory()
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index 78febf9..948c513 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -75,8 +75,13 @@
def pl_must_have_cost_center(self):
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':
- frappe.throw(_("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}. Please set up a default Cost Center for the Company.")
- .format(self.voucher_type, self.voucher_no, self.account))
+ msg = _("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}.").format(
+ 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):
account_type = frappe.db.get_value("Account", self.account, "report_type")
diff --git a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py
index b4a547b..4167ca7 100644
--- a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py
@@ -54,4 +54,4 @@
self.assertTrue(all(new.name != old.name for new, old in zip(gl_entries, new_gl_entries)))
new_naming_series_current_value = frappe.db.sql("SELECT current from tabSeries where name = %s", naming_series)[0][0]
- self.assertEquals(old_naming_series_current_value + 2, new_naming_series_current_value)
+ self.assertEqual(old_naming_series_current_value + 2, new_naming_series_current_value)
diff --git a/erpnext/accounts/doctype/gst_account/gst_account.json b/erpnext/accounts/doctype/gst_account/gst_account.json
index 7067338..b6ec884 100644
--- a/erpnext/accounts/doctype/gst_account/gst_account.json
+++ b/erpnext/accounts/doctype/gst_account/gst_account.json
@@ -1,196 +1,82 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2018-01-02 15:48:58.768352",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2018-01-02 15:48:58.768352",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "company",
+ "cgst_account",
+ "sgst_account",
+ "igst_account",
+ "cess_account",
+ "is_reverse_charge_account"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "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,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "columns": 1,
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "cgst_account",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "CGST Account",
- "length": 0,
- "no_copy": 0,
- "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,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "columns": 2,
+ "fieldname": "cgst_account",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "CGST Account",
+ "options": "Account",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sgst_account",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "SGST Account",
- "length": 0,
- "no_copy": 0,
- "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,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "columns": 2,
+ "fieldname": "sgst_account",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "SGST Account",
+ "options": "Account",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "igst_account",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "IGST Account",
- "length": 0,
- "no_copy": 0,
- "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,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "columns": 2,
+ "fieldname": "igst_account",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "IGST Account",
+ "options": "Account",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "cess_account",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "CESS Account",
- "length": 0,
- "no_copy": 0,
- "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": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "columns": 2,
+ "fieldname": "cess_account",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "CESS Account",
+ "options": "Account"
+ },
+ {
+ "columns": 1,
+ "default": "0",
+ "fieldname": "is_reverse_charge_account",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Is Reverse Charge Account"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-01-02 15:52:22.335988",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "GST Account",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-04-09 12:30:25.889993",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "GST Account",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 37b03f3..d76641d 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -327,18 +327,16 @@
},
setup_balance_formatter: function() {
- var me = this;
- $.each(["balance", "party_balance"], function(i, field) {
- var df = frappe.meta.get_docfield("Journal Entry Account", field, me.frm.doc.name);
- df.formatter = function(value, df, options, doc) {
- var currency = frappe.meta.get_field_currency(df, doc);
- var dr_or_cr = value ? ('<label>' + (value > 0.0 ? __("Dr") : __("Cr")) + '</label>') : "";
- return "<div style='text-align: right'>"
- + ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency))
- + " " + dr_or_cr
- + "</div>";
- }
- })
+ const formatter = function(value, df, options, doc) {
+ var currency = frappe.meta.get_field_currency(df, doc);
+ var dr_or_cr = value ? ('<label>' + (value > 0.0 ? __("Dr") : __("Cr")) + '</label>') : "";
+ return "<div style='text-align: right'>"
+ + ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency))
+ + " " + dr_or_cr
+ + "</div>";
+ };
+ this.frm.fields_dict.accounts.grid.update_docfield_property('balance', 'formatter', formatter);
+ this.frm.fields_dict.accounts.grid.update_docfield_property('party_balance', 'formatter', formatter);
},
reference_name: function(doc, cdt, cdn) {
@@ -431,15 +429,6 @@
cur_frm.cscript.update_totals(doc);
}
-cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
- if(doc.select_print_heading){
- // print heading
- cur_frm.pformat.print_heading = doc.select_print_heading;
- }
- else
- cur_frm.pformat.print_heading = __("Journal Entry");
-}
-
frappe.ui.form.on("Journal Entry Account", {
party: function(frm, cdt, cdn) {
var d = frappe.get_doc(cdt, cdn);
@@ -511,8 +500,11 @@
};
$.each(field_label_map, function (fieldname, label) {
- var df = frappe.meta.get_docfield("Journal Entry Account", fieldname, frm.doc.name);
- df.label = frm.doc.multi_currency ? (label + " in Account Currency") : label;
+ frm.fields_dict.accounts.grid.update_docfield_property(
+ fieldname,
+ 'label',
+ frm.doc.multi_currency ? (label + " in Account Currency") : label
+ );
})
},
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index ff2c8c2..ed1bd28 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -39,7 +39,11 @@
self.validate_multi_currency()
self.set_amounts_in_company_currency()
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_reference_doc()
self.set_against_account()
@@ -592,6 +596,7 @@
self.validate_total_debit_and_credit()
+ @frappe.whitelist()
def get_outstanding_invoices(self):
self.set('accounts', [])
total = 0
diff --git a/erpnext/accounts/doctype/journal_entry/regional/india.js b/erpnext/accounts/doctype/journal_entry/regional/india.js
new file mode 100644
index 0000000..75a69ac
--- /dev/null
+++ b/erpnext/accounts/doctype/journal_entry/regional/india.js
@@ -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
+ }
+ };
+ });
+ }
+});
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
index 774159d..a89fefd 100644
--- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
+++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
@@ -280,7 +280,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-06-24 14:06:54.833738",
+ "modified": "2020-06-26 14:06:54.833738",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",
diff --git a/erpnext/accounts/doctype/party_account/party_account.json b/erpnext/accounts/doctype/party_account/party_account.json
index aa32d95..c9f15a6 100644
--- a/erpnext/accounts/doctype/party_account/party_account.json
+++ b/erpnext/accounts/doctype/party_account/party_account.json
@@ -1,87 +1,39 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2014-08-29 16:02:39.740505",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2014-08-29 16:02:39.740505",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "field_order": [
+ "company",
+ "account"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "account",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Account",
- "length": 0,
- "no_copy": 0,
- "options": "Account",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "account",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Account",
+ "options": "Account"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2016-07-11 03:28:03.348246",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Party Account",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_seen": 0
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-04-07 18:13:08.833822",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Party Account",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index c2e804e..b80e8ad 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -561,7 +561,7 @@
flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate));
if(frm.doc.payment_type == "Pay")
- frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount);
+ frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount, 1);
else
frm.events.set_unallocated_amount(frm);
@@ -582,7 +582,7 @@
}
if(frm.doc.payment_type == "Receive")
- frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount);
+ frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount, 1);
else
frm.events.set_unallocated_amount(frm);
},
@@ -606,9 +606,9 @@
{fieldtype:"Float", label: __("Less Than Amount"), fieldname:"outstanding_amt_less_than"},
{fieldtype:"Section Break"},
{fieldtype:"Link", label:__("Cost Center"), fieldname:"cost_center", options:"Cost Center",
- "get_query": function() {
- return {
- "filters": {"company": frm.doc.company}
+ "get_query": function() {
+ return {
+ "filters": {"company": frm.doc.company}
}
}
},
@@ -743,7 +743,7 @@
});
},
- allocate_party_amount_against_ref_docs: function(frm, paid_amount) {
+ allocate_party_amount_against_ref_docs: function(frm, paid_amount, paid_amount_change) {
var total_positive_outstanding_including_order = 0;
var total_negative_outstanding = 0;
var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
@@ -800,22 +800,15 @@
//If allocate payment amount checkbox is unchecked, set zero to allocate amount
row.allocated_amount = 0;
- } else if (frappe.flags.allocate_payment_amount != 0 && !row.allocated_amount) {
- if (row.outstanding_amount > 0 && allocated_positive_outstanding > 0) {
- if (row.outstanding_amount >= allocated_positive_outstanding) {
- row.allocated_amount = allocated_positive_outstanding;
- } else {
- row.allocated_amount = row.outstanding_amount;
- }
-
+ } else if (frappe.flags.allocate_payment_amount != 0 && (!row.allocated_amount || paid_amount_change)) {
+ if (row.outstanding_amount > 0 && allocated_positive_outstanding >= 0) {
+ row.allocated_amount = (row.outstanding_amount >= allocated_positive_outstanding) ?
+ allocated_positive_outstanding : row.outstanding_amount;
allocated_positive_outstanding -= flt(row.allocated_amount);
- } else if (row.outstanding_amount < 0 && allocated_negative_outstanding) {
- if (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) {
- row.allocated_amount = -1*allocated_negative_outstanding;
- } else {
- row.allocated_amount = row.outstanding_amount;
- };
+ } else if (row.outstanding_amount < 0 && allocated_negative_outstanding) {
+ row.allocated_amount = (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) ?
+ -1*allocated_negative_outstanding : row.outstanding_amount;
allocated_negative_outstanding -= Math.abs(flt(row.allocated_amount));
}
}
diff --git a/erpnext/accounts/doctype/payment_order/test_payment_order.py b/erpnext/accounts/doctype/payment_order/test_payment_order.py
index 1c23e2a..5fdde07 100644
--- a/erpnext/accounts/doctype/payment_order/test_payment_order.py
+++ b/erpnext/accounts/doctype/payment_order/test_payment_order.py
@@ -31,10 +31,10 @@
doc = create_payment_order_against_payment_entry(payment_entry, "Payment Entry")
reference_doc = doc.get("references")[0]
- self.assertEquals(reference_doc.reference_name, payment_entry.name)
- self.assertEquals(reference_doc.reference_doctype, "Payment Entry")
- self.assertEquals(reference_doc.supplier, "_Test Supplier")
- self.assertEquals(reference_doc.amount, 250)
+ self.assertEqual(reference_doc.reference_name, payment_entry.name)
+ self.assertEqual(reference_doc.reference_doctype, "Payment Entry")
+ self.assertEqual(reference_doc.supplier, "_Test Supplier")
+ self.assertEqual(reference_doc.amount, 250)
def create_payment_order_against_payment_entry(ref_doc, order_type):
payment_order = frappe.get_doc(dict(
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
index 6b07197..d1523cd 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
@@ -234,8 +234,9 @@
});
if (invoices) {
- frappe.meta.get_docfield("Payment Reconciliation Payment", "invoice_number",
- me.frm.doc.name).options = "\n" + invoices.join("\n");
+ this.frm.fields_dict.payments.grid.update_docfield_property(
+ 'invoice_number', 'options', "\n" + invoices.join("\n")
+ );
$.each(me.frm.doc.payments || [], function(i, p) {
if(!in_list(invoices, cstr(p.invoice_number))) p.invoice_number = null;
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index cf6ec18..6635128 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -114,7 +114,7 @@
'party_type': self.party_type,
'voucher_type': voucher_type,
'account': self.receivable_payable_account
- }, as_dict=1, debug=1)
+ }, as_dict=1)
def add_payment_entries(self, entries):
self.set('payments', [])
diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json
index e362566..6ed7a31 100644
--- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json
+++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json
@@ -20,10 +20,11 @@
"discount",
"section_break_9",
"payment_amount",
+ "outstanding",
+ "paid_amount",
"discounted_amount",
"column_break_3",
- "outstanding",
- "paid_amount"
+ "base_payment_amount"
],
"fields": [
{
@@ -78,7 +79,8 @@
"depends_on": "paid_amount",
"fieldname": "paid_amount",
"fieldtype": "Currency",
- "label": "Paid Amount"
+ "label": "Paid Amount",
+ "options": "currency"
},
{
"fieldname": "column_break_3",
@@ -97,6 +99,7 @@
"fieldname": "outstanding",
"fieldtype": "Currency",
"label": "Outstanding",
+ "options": "currency",
"read_only": 1
},
{
@@ -145,12 +148,18 @@
{
"fieldname": "section_break_4",
"fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "base_payment_amount",
+ "fieldtype": "Currency",
+ "label": "Payment Amount (Company Currency)",
+ "options": "Company:company:default_currency"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-02-15 21:03:12.540546",
+ "modified": "2021-04-28 05:41:35.084233",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Schedule",
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
index 9ea616f..8c5a34a 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
@@ -22,7 +22,43 @@
});
if (frm.doc.docstatus === 0 && !frm.doc.amended_from) frm.set_value("period_end_date", frappe.datetime.now_datetime());
- if (frm.doc.docstatus === 1) set_html_data(frm);
+
+ frappe.realtime.on('closing_process_complete', async function(data) {
+ await frm.reload_doc();
+ if (frm.doc.status == 'Failed' && frm.doc.error_message && data.user == frappe.session.user) {
+ frappe.msgprint({
+ title: __('POS Closing Failed'),
+ message: frm.doc.error_message,
+ indicator: 'orange',
+ clear: true
+ });
+ }
+ });
+
+ set_html_data(frm);
+ },
+
+ refresh: function(frm) {
+ if (frm.doc.docstatus == 1 && frm.doc.status == 'Failed') {
+ const issue = '<a id="jump_to_error" style="text-decoration: underline;">issue</a>';
+ frm.dashboard.set_headline(
+ __('POS Closing failed while running in a background process. You can resolve the {0} and retry the process again.', [issue]));
+
+ $('#jump_to_error').on('click', (e) => {
+ e.preventDefault();
+ frappe.utils.scroll_to(
+ cur_frm.get_field("error_message").$wrapper,
+ true,
+ 30
+ );
+ });
+
+ frm.add_custom_button(__('Retry'), function () {
+ frm.call('retry', {}, () => {
+ frm.reload_doc();
+ });
+ });
+ }
},
pos_opening_entry(frm) {
@@ -61,48 +97,37 @@
refresh_fields(frm);
set_html_data(frm);
}
- })
+ });
+ },
+
+ before_save: function(frm) {
+ frm.set_value("grand_total", 0);
+ frm.set_value("net_total", 0);
+ frm.set_value("total_quantity", 0);
+ frm.set_value("taxes", []);
+
+ for (let row of frm.doc.payment_reconciliation) {
+ row.expected_amount = 0;
+ }
+
+ for (let row of frm.doc.pos_transactions) {
+ frappe.db.get_doc("POS Invoice", row.pos_invoice).then(doc => {
+ frm.doc.grand_total += flt(doc.grand_total);
+ frm.doc.net_total += flt(doc.net_total);
+ frm.doc.total_quantity += flt(doc.total_qty);
+ refresh_payments(doc, frm);
+ refresh_taxes(doc, frm);
+ refresh_fields(frm);
+ set_html_data(frm);
+ });
+ }
}
});
-cur_frm.cscript.before_pos_transactions_remove = function(doc, cdt, cdn) {
- const removed_row = locals[cdt][cdn];
-
- if (!removed_row.pos_invoice) return;
-
- frappe.db.get_doc("POS Invoice", removed_row.pos_invoice).then(doc => {
- cur_frm.doc.grand_total -= flt(doc.grand_total);
- cur_frm.doc.net_total -= flt(doc.net_total);
- cur_frm.doc.total_quantity -= flt(doc.total_qty);
- refresh_payments(doc, cur_frm, 1);
- refresh_taxes(doc, cur_frm, 1);
- refresh_fields(cur_frm);
- set_html_data(cur_frm);
- });
-}
-
-frappe.ui.form.on('POS Invoice Reference', {
- pos_invoice(frm, cdt, cdn) {
- const added_row = locals[cdt][cdn];
-
- if (!added_row.pos_invoice) return;
-
- frappe.db.get_doc("POS Invoice", added_row.pos_invoice).then(doc => {
- frm.doc.grand_total += flt(doc.grand_total);
- frm.doc.net_total += flt(doc.net_total);
- frm.doc.total_quantity += flt(doc.total_qty);
- refresh_payments(doc, frm);
- refresh_taxes(doc, frm);
- refresh_fields(frm);
- set_html_data(frm);
- });
- }
-})
-
frappe.ui.form.on('POS Closing Entry Detail', {
closing_amount: (frm, cdt, cdn) => {
const row = locals[cdt][cdn];
- frappe.model.set_value(cdt, cdn, "difference", flt(row.expected_amount - row.closing_amount))
+ frappe.model.set_value(cdt, cdn, "difference", flt(row.expected_amount - row.closing_amount));
}
})
@@ -126,28 +151,28 @@
})
}
-function refresh_payments(d, frm, remove) {
+function refresh_payments(d, frm) {
d.payments.forEach(p => {
const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment);
if (payment) {
- if (!remove) payment.expected_amount += flt(p.amount);
- else payment.expected_amount -= flt(p.amount);
+ payment.expected_amount += flt(p.amount);
+ payment.difference = payment.closing_amount - payment.expected_amount;
} else {
frm.add_child("payment_reconciliation", {
mode_of_payment: p.mode_of_payment,
opening_amount: 0,
- expected_amount: p.amount
+ expected_amount: p.amount,
+ closing_amount: 0
})
}
})
}
-function refresh_taxes(d, frm, remove) {
+function refresh_taxes(d, frm) {
d.taxes.forEach(t => {
const tax = frm.doc.taxes.find(tx => tx.account_head === t.account_head && tx.rate === t.rate);
if (tax) {
- if (!remove) tax.amount += flt(t.tax_amount);
- else tax.amount -= flt(t.tax_amount);
+ tax.amount += flt(t.tax_amount);
} else {
frm.add_child("taxes", {
account_head: t.account_head,
@@ -177,11 +202,13 @@
}
function set_html_data(frm) {
- frappe.call({
- method: "get_payment_reconciliation_details",
- doc: frm.doc,
- callback: (r) => {
- frm.get_field("payment_reconciliation_details").$wrapper.html(r.message);
- }
- })
+ if (frm.doc.docstatus === 1 && frm.doc.status == 'Submitted') {
+ frappe.call({
+ method: "get_payment_reconciliation_details",
+ doc: frm.doc,
+ callback: (r) => {
+ frm.get_field("payment_reconciliation_details").$wrapper.html(r.message);
+ }
+ });
+ }
}
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json
index a9b91e0..4d6e4a2 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json
@@ -30,6 +30,8 @@
"total_quantity",
"column_break_16",
"taxes",
+ "failure_description_section",
+ "error_message",
"section_break_14",
"amended_from"
],
@@ -195,7 +197,7 @@
"fieldtype": "Select",
"hidden": 1,
"label": "Status",
- "options": "Draft\nSubmitted\nQueued\nCancelled",
+ "options": "Draft\nSubmitted\nQueued\nFailed\nCancelled",
"print_hide": 1,
"read_only": 1
},
@@ -203,6 +205,21 @@
"fieldname": "period_details_section",
"fieldtype": "Section Break",
"label": "Period Details"
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "error_message",
+ "depends_on": "error_message",
+ "fieldname": "failure_description_section",
+ "fieldtype": "Section Break",
+ "label": "Failure Description"
+ },
+ {
+ "depends_on": "error_message",
+ "fieldname": "error_message",
+ "fieldtype": "Small Text",
+ "label": "Error",
+ "read_only": 1
}
],
"is_submittable": 1,
@@ -212,7 +229,7 @@
"link_fieldname": "pos_closing_entry"
}
],
- "modified": "2021-02-01 13:47:20.722104",
+ "modified": "2021-05-05 16:59:49.723261",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Closing Entry",
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
index a05e598..8252872 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
@@ -16,28 +16,8 @@
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
- self.validate_pos_closing()
self.validate_pos_invoices()
- def validate_pos_closing(self):
- user = frappe.db.sql("""
- SELECT name FROM `tabPOS Closing Entry`
- WHERE
- user = %(user)s AND docstatus = 1 AND pos_profile = %(profile)s AND
- (period_start_date between %(start)s and %(end)s OR period_end_date between %(start)s and %(end)s)
- """, {
- 'user': self.user,
- 'profile': self.pos_profile,
- 'start': self.period_start_date,
- 'end': self.period_end_date
- })
-
- if user:
- bold_already_exists = frappe.bold(_("already exists"))
- bold_user = frappe.bold(self.user)
- frappe.throw(_("POS Closing Entry {} against {} between selected period")
- .format(bold_already_exists, bold_user), title=_("Invalid Period"))
-
def validate_pos_invoices(self):
invalid_rows = []
for d in self.pos_transactions:
@@ -80,6 +60,10 @@
def on_cancel(self):
unconsolidate_pos_invoices(closing_entry=self)
+ @frappe.whitelist()
+ def retry(self):
+ consolidate_pos_invoices(closing_entry=self)
+
def update_opening_entry(self, for_cancel=False):
opening_entry = frappe.get_doc("POS Opening Entry", self.pos_opening_entry)
opening_entry.pos_closing_entry = self.name if not for_cancel else None
@@ -89,8 +73,8 @@
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
- cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'])
- return [c['user'] for c in cashiers_list]
+ cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'], as_list=1)
+ return [c for c in cashiers_list]
@frappe.whitelist()
def get_pos_invoices(start, end, pos_profile, user):
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js
index 20fd610..cffeb4d 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js
@@ -8,6 +8,7 @@
"Draft": "red",
"Submitted": "blue",
"Queued": "orange",
+ "Failed": "red",
"Cancelled": "red"
};
diff --git a/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.json b/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.json
index 6e7768d..bbf1ba0 100644
--- a/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.json
+++ b/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.json
@@ -46,6 +46,7 @@
"reqd": 1
},
{
+ "default": "0",
"fieldname": "closing_amount",
"fieldtype": "Currency",
"in_list_view": 1,
@@ -57,7 +58,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2020-10-23 16:45:43.662034",
+ "modified": "2021-05-19 20:08:44.523861",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Closing Entry Detail",
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index 832fb80..f55fdab 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -96,31 +96,45 @@
if paid_amt and pay.amount != paid_amt:
return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment))
+ def validate_pos_reserved_serial_nos(self, item):
+ serial_nos = get_serial_nos(item.serial_no)
+ filters = {"item_code": item.item_code, "warehouse": item.warehouse}
+ if item.batch_no:
+ filters["batch_no"] = item.batch_no
+
+ reserved_serial_nos = get_pos_reserved_serial_nos(filters)
+ invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos]
+
+ bold_invalid_serial_nos = frappe.bold(', '.join(invalid_serial_nos))
+ if len(invalid_serial_nos) == 1:
+ frappe.throw(_("Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.")
+ .format(item.idx, bold_invalid_serial_nos), title=_("Item Unavailable"))
+ elif invalid_serial_nos:
+ frappe.throw(_("Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.")
+ .format(item.idx, bold_invalid_serial_nos), title=_("Item Unavailable"))
+
+ def validate_delivered_serial_nos(self, item):
+ serial_nos = get_serial_nos(item.serial_no)
+ delivered_serial_nos = frappe.db.get_list('Serial No', {
+ 'item_code': item.item_code,
+ 'name': ['in', serial_nos],
+ 'sales_invoice': ['is', 'set']
+ }, pluck='name')
+
+ if delivered_serial_nos:
+ bold_delivered_serial_nos = frappe.bold(', '.join(delivered_serial_nos))
+ frappe.throw(_("Row #{}: Serial No. {} has already been transacted into another Sales Invoice. Please select valid serial no.")
+ .format(item.idx, bold_delivered_serial_nos), title=_("Item Unavailable"))
+
def validate_stock_availablility(self):
if self.is_return:
return
- allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock')
- error_msg = []
+ allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')
for d in self.get('items'):
- msg = ""
if d.serial_no:
- filters = { "item_code": d.item_code, "warehouse": d.warehouse }
- if d.batch_no:
- filters["batch_no"] = d.batch_no
-
- reserved_serial_nos = get_pos_reserved_serial_nos(filters)
- serial_nos = get_serial_nos(d.serial_no)
- invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos]
-
- bold_invalid_serial_nos = frappe.bold(', '.join(invalid_serial_nos))
- if len(invalid_serial_nos) == 1:
- msg = (_("Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.")
- .format(d.idx, bold_invalid_serial_nos))
- elif invalid_serial_nos:
- msg = (_("Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.")
- .format(d.idx, bold_invalid_serial_nos))
-
+ self.validate_pos_reserved_serial_nos(d)
+ self.validate_delivered_serial_nos(d)
else:
if allow_negative_stock:
return
@@ -128,15 +142,11 @@
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)
if flt(available_stock) <= 0:
- msg = (_('Row #{}: Item Code: {} is not available under warehouse {}.').format(d.idx, item_code, warehouse))
+ frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.')
+ .format(d.idx, item_code, warehouse), title=_("Item Unavailable"))
elif flt(available_stock) < flt(d.qty):
- msg = (_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.')
- .format(d.idx, item_code, warehouse, qty))
- if msg:
- error_msg.append(msg)
-
- if error_msg:
- frappe.throw(error_msg, title=_("Item Unavailable"), as_list=True)
+ frappe.throw(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.')
+ .format(d.idx, item_code, warehouse, available_stock), title=_("Item Unavailable"))
def validate_serialised_or_batched_item(self):
error_msg = []
@@ -203,9 +213,8 @@
for d in self.get("items"):
is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "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. ").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):
if len(self.payments) == 0:
@@ -446,29 +455,27 @@
@frappe.whitelist()
def get_stock_availability(item_code, warehouse):
- latest_sle = frappe.db.sql("""select qty_after_transaction
- from `tabStock Ledger Entry`
+ bin_qty = frappe.db.sql("""select actual_qty from `tabBin`
where item_code = %s and warehouse = %s
- order by posting_date desc, posting_time desc
limit 1""", (item_code, warehouse), as_dict=1)
- pos_sales_qty = frappe.db.sql("""select sum(p_item.qty) as qty
+ pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
+
+ bin_qty = bin_qty[0].actual_qty or 0 if bin_qty else 0
+
+ return bin_qty - pos_sales_qty
+
+def get_pos_reserved_qty(item_code, warehouse):
+ reserved_qty = frappe.db.sql("""select sum(p_item.qty) as qty
from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item
where p.name = p_item.parent
- and p.consolidated_invoice is NULL
- and p.docstatus = 1
+ and ifnull(p.consolidated_invoice, '') = ''
and p_item.docstatus = 1
and p_item.item_code = %s
and p_item.warehouse = %s
""", (item_code, warehouse), as_dict=1)
- sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
- pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0
-
- if sle_qty and pos_sales_qty:
- return sle_qty - pos_sales_qty
- else:
- return sle_qty
+ return reserved_qty[0].qty or 0 if reserved_qty else 0
@frappe.whitelist()
def make_sales_return(source_name, target_doc=None):
diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
index 6d388c4..6172796 100644
--- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
@@ -10,10 +10,12 @@
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
class TestPOSInvoice(unittest.TestCase):
@classmethod
def setUpClass(cls):
+ make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=800, basic_rate=100)
frappe.db.sql("delete from `tabTax Rule`")
def tearDown(self):
@@ -320,6 +322,34 @@
self.assertRaises(frappe.ValidationError, pos2.insert)
+ def test_delivered_serialized_item_transaction(self):
+ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
+ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+ se = make_serialized_item(company='_Test Company',
+ target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC')
+
+ serial_nos = get_serial_nos(se.get("items")[0].serial_no)
+
+ si = create_sales_invoice(company='_Test Company', debit_to='Debtors - _TC',
+ account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
+ expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
+ item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
+
+ si.get("items")[0].serial_no = serial_nos[0]
+ si.insert()
+ si.submit()
+
+ pos2 = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
+ account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
+ expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
+ item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
+
+ pos2.get("items")[0].serial_no = serial_nos[0]
+ pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
+
+ self.assertRaises(frappe.ValidationError, pos2.insert)
+
def test_loyalty_points(self):
from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records
from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
index 40f77b4..08e072e 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
@@ -12,8 +12,8 @@
from frappe.model.mapper import map_doc, map_child_doc
from frappe.utils.scheduler import is_scheduler_inactive
from frappe.core.page.background_jobs.background_jobs import get_info
-
-from six import iteritems
+import json
+import six
class POSInvoiceMergeLog(Document):
def validate(self):
@@ -42,8 +42,9 @@
if return_against_status != "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")
- 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))
+ msg += " "
msg += _("Original invoice should be consolidated before or along with the return invoice.")
msg += "<br><br>"
msg += _("You can add original invoice {} manually to proceed.").format(bold_return_against)
@@ -56,12 +57,12 @@
sales = [d for d in pos_invoice_docs if d.get('is_return') == 0]
sales_invoice, credit_note = "", ""
- if sales:
- sales_invoice = self.process_merging_into_sales_invoice(sales)
-
if 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.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
@@ -78,8 +79,11 @@
sales_invoice = self.merge_pos_invoice_into(sales_invoice, data)
sales_invoice.is_consolidated = 1
+ sales_invoice.set_posting_time = 1
+ sales_invoice.posting_date = getdate(self.posting_date)
sales_invoice.save()
sales_invoice.submit()
+
self.consolidated_invoice = sales_invoice.name
return sales_invoice.name
@@ -91,10 +95,13 @@
credit_note = self.merge_pos_invoice_into(credit_note, data)
credit_note.is_consolidated = 1
+ credit_note.set_posting_time = 1
+ credit_note.posting_date = getdate(self.posting_date)
# TODO: return could be against multiple sales invoice which could also have been consolidated?
# credit_note.return_against = self.consolidated_invoice
credit_note.save()
credit_note.submit()
+
self.consolidated_credit_note = credit_note.name
return credit_note.name
@@ -131,12 +138,14 @@
if t.account_head == tax.account_head and t.cost_center == tax.cost_center:
t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount)
t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount)
+ update_item_wise_tax_detail(t, tax)
found = True
if not found:
tax.charge_type = 'Actual'
tax.included_in_print_rate = 0
tax.tax_amount = tax.tax_amount_after_discount_amount
tax.base_tax_amount = tax.base_tax_amount_after_discount_amount
+ tax.item_wise_tax_detail = tax.item_wise_tax_detail
taxes.append(tax)
for payment in doc.get('payments'):
@@ -168,11 +177,9 @@
sales_invoice = frappe.new_doc('Sales Invoice')
sales_invoice.customer = self.customer
sales_invoice.is_pos = 1
- # date can be pos closing date?
- sales_invoice.posting_date = getdate(nowdate())
return sales_invoice
-
+
def update_pos_invoices(self, invoice_docs, sales_invoice='', credit_note=''):
for doc in invoice_docs:
doc.load_from_db()
@@ -187,6 +194,26 @@
si.flags.ignore_validate = True
si.cancel()
+def update_item_wise_tax_detail(consolidate_tax_row, tax_row):
+ consolidated_tax_detail = json.loads(consolidate_tax_row.item_wise_tax_detail)
+ tax_row_detail = json.loads(tax_row.item_wise_tax_detail)
+
+ if not consolidated_tax_detail:
+ consolidated_tax_detail = {}
+
+ for item_code, tax_data in tax_row_detail.items():
+ if consolidated_tax_detail.get(item_code):
+ consolidated_tax_data = consolidated_tax_detail.get(item_code)
+ consolidated_tax_detail.update({
+ item_code: [consolidated_tax_data[0], consolidated_tax_data[1] + tax_data[1]]
+ })
+ else:
+ consolidated_tax_detail.update({
+ item_code: [tax_data[0], tax_data[1]]
+ })
+
+ consolidate_tax_row.item_wise_tax_detail = json.dumps(consolidated_tax_detail, separators=(',', ':'))
+
def get_all_unconsolidated_invoices():
filters = {
'consolidated_invoice': [ 'in', [ '', None ]],
@@ -208,13 +235,13 @@
return pos_invoice_customer_map
-def consolidate_pos_invoices(pos_invoices=[], closing_entry={}):
- invoices = pos_invoices or closing_entry.get('pos_transactions') or get_all_unconsolidated_invoices()
+def consolidate_pos_invoices(pos_invoices=None, closing_entry=None):
+ invoices = pos_invoices or (closing_entry and closing_entry.get('pos_transactions')) or get_all_unconsolidated_invoices()
invoice_by_customer = get_invoice_customer_map(invoices)
- if len(invoices) >= 5 and closing_entry:
+ if len(invoices) >= 10 and closing_entry:
closing_entry.set_status(update=True, status='Queued')
- enqueue_job(create_merge_logs, invoice_by_customer, closing_entry)
+ enqueue_job(create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry)
else:
create_merge_logs(invoice_by_customer, closing_entry)
@@ -225,50 +252,83 @@
pluck='name'
)
- if len(merge_logs) >= 5:
+ if len(merge_logs) >= 10:
closing_entry.set_status(update=True, status='Queued')
- enqueue_job(cancel_merge_logs, merge_logs, closing_entry)
+ enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry)
else:
cancel_merge_logs(merge_logs, closing_entry)
-def create_merge_logs(invoice_by_customer, closing_entry={}):
- for customer, invoices in iteritems(invoice_by_customer):
- merge_log = frappe.new_doc('POS Invoice Merge Log')
- merge_log.posting_date = getdate(nowdate())
- merge_log.customer = customer
- merge_log.pos_closing_entry = closing_entry.get('name', None)
+def create_merge_logs(invoice_by_customer, closing_entry=None):
+ try:
+ for customer, invoices in six.iteritems(invoice_by_customer):
+ merge_log = frappe.new_doc('POS Invoice Merge Log')
+ merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate()
+ merge_log.customer = customer
+ merge_log.pos_closing_entry = closing_entry.get('name') if closing_entry else None
- merge_log.set('pos_invoices', invoices)
- merge_log.save(ignore_permissions=True)
- merge_log.submit()
-
- if closing_entry:
- closing_entry.set_status(update=True, status='Submitted')
- closing_entry.update_opening_entry()
+ merge_log.set('pos_invoices', invoices)
+ merge_log.save(ignore_permissions=True)
+ merge_log.submit()
-def cancel_merge_logs(merge_logs, closing_entry={}):
- for log in merge_logs:
- merge_log = frappe.get_doc('POS Invoice Merge Log', log)
- merge_log.flags.ignore_permissions = True
- merge_log.cancel()
+ if closing_entry:
+ closing_entry.set_status(update=True, status='Submitted')
+ closing_entry.db_set('error_message', '')
+ closing_entry.update_opening_entry()
- if closing_entry:
- closing_entry.set_status(update=True, status='Cancelled')
- closing_entry.update_opening_entry(for_cancel=True)
+ except Exception as e:
+ frappe.db.rollback()
+ message_log = frappe.message_log.pop() if frappe.message_log else str(e)
+ error_message = safe_load_json(message_log)
-def enqueue_job(job, invoice_by_customer, closing_entry):
+ if closing_entry:
+ closing_entry.set_status(update=True, status='Failed')
+ closing_entry.db_set('error_message', error_message)
+ raise
+
+ finally:
+ frappe.db.commit()
+ frappe.publish_realtime('closing_process_complete', {'user': frappe.session.user})
+
+def cancel_merge_logs(merge_logs, closing_entry=None):
+ try:
+ for log in merge_logs:
+ merge_log = frappe.get_doc('POS Invoice Merge Log', log)
+ merge_log.flags.ignore_permissions = True
+ merge_log.cancel()
+
+ if closing_entry:
+ closing_entry.set_status(update=True, status='Cancelled')
+ closing_entry.db_set('error_message', '')
+ closing_entry.update_opening_entry(for_cancel=True)
+
+ except Exception as e:
+ frappe.db.rollback()
+ message_log = frappe.message_log.pop() if frappe.message_log else str(e)
+ error_message = safe_load_json(message_log)
+
+ if closing_entry:
+ closing_entry.set_status(update=True, status='Submitted')
+ closing_entry.db_set('error_message', error_message)
+ raise
+
+ finally:
+ frappe.db.commit()
+ frappe.publish_realtime('closing_process_complete', {'user': frappe.session.user})
+
+def enqueue_job(job, **kwargs):
check_scheduler_status()
+ closing_entry = kwargs.get('closing_entry') or {}
+
job_name = closing_entry.get("name")
if not job_already_enqueued(job_name):
enqueue(
job,
+ **kwargs,
queue="long",
timeout=10000,
event="processing_merge_logs",
job_name=job_name,
- closing_entry=closing_entry,
- invoice_by_customer=invoice_by_customer,
now=frappe.conf.developer_mode or frappe.flags.in_test
)
@@ -286,4 +346,12 @@
def job_already_enqueued(job_name):
enqueued_jobs = [d.get("job_name") for d in get_info()]
if job_name in enqueued_jobs:
- return True
\ No newline at end of file
+ return True
+
+def safe_load_json(message):
+ try:
+ json_message = json.loads(message).get('message')
+ except Exception:
+ json_message = message
+
+ return json_message
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
index d880caa..040a815 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
@@ -5,6 +5,7 @@
import frappe
import unittest
+import json
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
@@ -99,4 +100,51 @@
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
+ def test_consolidated_invoice_item_taxes(self):
+ frappe.db.sql("delete from `tabPOS Invoice`")
+
+ try:
+ inv = create_pos_invoice(qty=1, rate=100, do_not_save=True)
+
+ inv.append("taxes", {
+ "account_head": "_Test Account VAT - _TC",
+ "charge_type": "On Net Total",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "VAT",
+ "doctype": "Sales Taxes and Charges",
+ "rate": 9
+ })
+ inv.insert()
+ inv.submit()
+
+ inv2 = create_pos_invoice(qty=1, rate=100, do_not_save=True)
+ inv2.get('items')[0].item_code = '_Test Item 2'
+ inv2.append("taxes", {
+ "account_head": "_Test Account VAT - _TC",
+ "charge_type": "On Net Total",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "VAT",
+ "doctype": "Sales Taxes and Charges",
+ "rate": 5
+ })
+ inv2.insert()
+ inv2.submit()
+
+ consolidate_pos_invoices()
+ inv.load_from_db()
+
+ consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
+ item_wise_tax_detail = json.loads(consolidated_invoice.get('taxes')[0].item_wise_tax_detail)
+
+ tax_rate, amount = item_wise_tax_detail.get('_Test Item')
+ self.assertEqual(tax_rate, 9)
+ self.assertEqual(amount, 9)
+
+ tax_rate2, amount2 = item_wise_tax_detail.get('_Test Item 2')
+ self.assertEqual(tax_rate2, 5)
+ self.assertEqual(amount2, 5)
+ finally:
+ frappe.set_user("Administrator")
+ frappe.db.sql("delete from `tabPOS Profile`")
+ frappe.db.sql("delete from `tabPOS Invoice`")
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py
index ee76bba..cf7ed26 100644
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.py
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py
@@ -62,14 +62,15 @@
if len(default_mode) > 1:
frappe.throw(_("You can only select one mode of payment as default"))
-
+
invalid_modes = []
for d in self.payments:
account = frappe.db.get_value(
- "Mode of Payment Account",
+ "Mode of Payment Account",
{"parent": d.mode_of_payment, "company": self.company},
"default_account"
)
+
if not account:
invalid_modes.append(get_link_to_form("Mode of Payment", d.mode_of_payment))
diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
index 62dc1fc..0033965 100644
--- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
+++ b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
@@ -92,11 +92,21 @@
"write_off_cost_center": args.write_off_cost_center or "_Test Write Off Cost Center - _TC"
})
- payments = [{
+ mode_of_payment = frappe.get_doc("Mode of Payment", "Cash")
+ company = args.company or "_Test Company"
+ default_account = args.income_account or "Sales - _TC"
+
+ if not frappe.db.get_value("Mode of Payment Account", {"company": company, "parent": "Cash"}):
+ mode_of_payment.append("accounts", {
+ "company": company,
+ "default_account": default_account
+ })
+ mode_of_payment.save()
+
+ pos_profile.append("payments", {
'mode_of_payment': 'Cash',
'default': 1
- }]
- pos_profile.set("payments", payments)
+ })
if not frappe.db.exists("POS Profile", args.name or "_Test POS Profile"):
pos_profile.insert()
diff --git a/erpnext/accounts/doctype/pos_search_fields/__init__.py b/erpnext/accounts/doctype/pos_search_fields/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_search_fields/__init__.py
diff --git a/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json
new file mode 100644
index 0000000..a627f5b
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json
@@ -0,0 +1,37 @@
+{
+ "actions": [],
+ "creation": "2021-04-19 14:56:06.652327",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "field",
+ "fieldname"
+ ],
+ "fields": [
+ {
+ "fieldname": "fieldname",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Fieldname"
+ },
+ {
+ "fieldname": "field",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Field"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-04-21 11:12:54.632093",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "POS Search Fields",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.py b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.py
new file mode 100644
index 0000000..720ea77
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class POSSearchFields(Document):
+ pass
diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.js b/erpnext/accounts/doctype/pos_settings/pos_settings.js
index 8890d59..9003af5 100644
--- a/erpnext/accounts/doctype/pos_settings/pos_settings.js
+++ b/erpnext/accounts/doctype/pos_settings/pos_settings.js
@@ -1,9 +1,17 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
+let search_fields_datatypes = ['Data', 'Link', 'Dynamic Link', 'Long Text', 'Select', 'Small Text', 'Text', 'Text Editor'];
+let do_not_include_fields = ["naming_series", "item_code", "item_name", "stock_uom", "hub_sync_id", "asset_naming_series",
+ "default_material_request_type", "valuation_method", "warranty_period", "weight_uom", "batch_number_series",
+ "serial_no_series", "purchase_uom", "customs_tariff_number", "sales_uom", "deferred_revenue_account",
+ "deferred_expense_account", "quality_inspection_template", "route", "slideshow", "website_image_alt", "thumbnail",
+ "web_long_description", "hub_sync_id"]
+
frappe.ui.form.on('POS Settings', {
onload: function(frm) {
frm.trigger("get_invoice_fields");
+ frm.trigger("add_search_options");
},
get_invoice_fields: function(frm) {
@@ -16,8 +24,43 @@
}
});
- frappe.meta.get_docfield("POS Field", "fieldname", frm.doc.name).options = [""].concat(fields);
+ frm.fields_dict.invoice_fields.grid.update_docfield_property(
+ 'fieldname', 'options', [""].concat(fields)
+ );
});
+
+ },
+
+ add_search_options: function(frm) {
+ frappe.model.with_doctype("Item", () => {
+ var fields = $.map(frappe.get_doc("DocType", "Item").fields, function(d) {
+ if (search_fields_datatypes.includes(d.fieldtype) && !(do_not_include_fields.includes(d.fieldname))) {
+ return [d.label];
+ } else {
+ return null;
+ }
+ });
+
+ fields.unshift('');
+ frm.fields_dict.pos_search_fields.grid.update_docfield_property('field', 'options', fields);
+ });
+
+ }
+});
+
+frappe.ui.form.on("POS Search Fields", {
+ field: function(frm, doctype, name) {
+ var doc = frappe.get_doc(doctype, name);
+ var df = $.map(frappe.get_doc("DocType", "Item").fields, function(d) {
+ if (doc.field == d.label && search_fields_datatypes.includes(d.fieldtype)) {
+ return d;
+ } else {
+ return null;
+ }
+ })[0];
+
+ doc.fieldname = df.fieldname;
+ frm.refresh_field("fields");
}
});
diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.json b/erpnext/accounts/doctype/pos_settings/pos_settings.json
index 3539588..962eb94 100644
--- a/erpnext/accounts/doctype/pos_settings/pos_settings.json
+++ b/erpnext/accounts/doctype/pos_settings/pos_settings.json
@@ -5,7 +5,8 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
- "invoice_fields"
+ "invoice_fields",
+ "pos_search_fields"
],
"fields": [
{
@@ -13,11 +14,17 @@
"fieldtype": "Table",
"label": "POS Field",
"options": "POS Field"
+ },
+ {
+ "fieldname": "pos_search_fields",
+ "fieldtype": "Table",
+ "label": "POS Search Fields",
+ "options": "POS Search Fields"
}
],
"issingle": 1,
"links": [],
- "modified": "2020-06-01 15:46:41.478928",
+ "modified": "2021-04-19 14:56:24.465218",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Settings",
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index aedf1c6..556f49d 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -152,7 +152,7 @@
frappe.throw(_("Valid from date must be less than valid upto date"))
def validate_condition(self):
- if self.condition and ("=" in self.condition) and re.match("""[\w\.:_]+\s*={1}\s*[\w\.@'"]+""", self.condition):
+ if self.condition and ("=" in self.condition) and re.match(r'[\w\.:_]+\s*={1}\s*[\w\.@\'"]+', self.condition):
frappe.throw(_("Invalid condition expression"))
#--------------------------------------------------------------------------------
diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
index f28cee7..ffe8be1 100644
--- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
@@ -99,7 +99,7 @@
args.item_code = "_Test Item 2"
details = get_item_details(args)
- self.assertEquals(details.get("discount_percentage"), 15)
+ self.assertEqual(details.get("discount_percentage"), 15)
def test_pricing_rule_for_margin(self):
from erpnext.stock.get_item_details import get_item_details
@@ -145,8 +145,8 @@
"name": None
})
details = get_item_details(args)
- self.assertEquals(details.get("margin_type"), "Percentage")
- self.assertEquals(details.get("margin_rate_or_amount"), 10)
+ self.assertEqual(details.get("margin_type"), "Percentage")
+ self.assertEqual(details.get("margin_rate_or_amount"), 10)
def test_mixed_conditions_for_item_group(self):
for item in ["Mixed Cond Item 1", "Mixed Cond Item 2"]:
@@ -192,7 +192,7 @@
"name": None
})
details = get_item_details(args)
- self.assertEquals(details.get("discount_percentage"), 10)
+ self.assertEqual(details.get("discount_percentage"), 10)
def test_pricing_rule_for_variants(self):
from erpnext.stock.get_item_details import get_item_details
@@ -322,11 +322,26 @@
si.insert(ignore_permissions=True)
item = si.items[0]
- self.assertEquals(item.margin_rate_or_amount, 10)
- self.assertEquals(item.rate_with_margin, 1100)
+ self.assertEqual(item.margin_rate_or_amount, 10)
+ self.assertEqual(item.rate_with_margin, 1100)
self.assertEqual(item.discount_percentage, 10)
- self.assertEquals(item.discount_amount, 110)
- self.assertEquals(item.rate, 990)
+ self.assertEqual(item.discount_amount, 110)
+ self.assertEqual(item.rate, 990)
+
+ def test_pricing_rule_with_margin_and_discount_amount(self):
+ frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
+ make_pricing_rule(selling=1, margin_type="Percentage", margin_rate_or_amount=10,
+ rate_or_discount="Discount Amount", discount_amount=110)
+ si = create_sales_invoice(do_not_save=True)
+ si.items[0].price_list_rate = 1000
+ si.payment_schedule = []
+ si.insert(ignore_permissions=True)
+
+ item = si.items[0]
+ self.assertEqual(item.margin_rate_or_amount, 10)
+ self.assertEqual(item.rate_with_margin, 1100)
+ self.assertEqual(item.discount_amount, 110)
+ self.assertEqual(item.rate, 990)
def test_pricing_rule_for_product_discount_on_same_item(self):
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
@@ -443,21 +458,21 @@
si.items[0].price_list_rate = 1000
si.submit()
item = si.items[0]
- self.assertEquals(item.rate, 100)
+ self.assertEqual(item.rate, 100)
# Correct Customer and Incorrect is_return value
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=1, qty=-1)
si.items[0].price_list_rate = 1000
si.submit()
item = si.items[0]
- self.assertEquals(item.rate, 100)
+ self.assertEqual(item.rate, 100)
# Correct Customer and correct is_return value
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=0)
si.items[0].price_list_rate = 1000
si.submit()
item = si.items[0]
- self.assertEquals(item.rate, 900)
+ self.assertEqual(item.rate, 900)
def test_multiple_pricing_rules(self):
make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1,
@@ -530,11 +545,11 @@
apply_on="Transaction", free_item="Water Flask 1", free_qty=1, free_item_rate=10)
si = create_sales_invoice(qty=5, do_not_submit=True)
- self.assertEquals(len(si.items), 2)
- self.assertEquals(si.items[1].rate, 10)
+ self.assertEqual(len(si.items), 2)
+ self.assertEqual(si.items[1].rate, 10)
si1 = create_sales_invoice(qty=2, do_not_submit=True)
- self.assertEquals(len(si1.items), 1)
+ self.assertEqual(len(si1.items), 1)
for doc in [si, si1]:
doc.delete()
@@ -560,6 +575,7 @@
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
"condition": args.condition or '',
"priority": 1,
+ "discount_amount": args.discount_amount or 0.0,
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
})
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index c676abd..d23b952 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -173,7 +173,7 @@
if parenttype in ["Customer Group", "Item Group", "Territory"]:
parent_field = "parent_{0}".format(frappe.scrub(parenttype))
root_name = frappe.db.get_list(parenttype,
- {"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1)
+ {"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1, ignore_permissions=True)
if root_name and root_name[0][0]:
parent_groups.append(root_name[0][0])
@@ -471,7 +471,7 @@
if not d.get(pr_field): continue
- if d.validate_applied_rule and doc.get(field) < d.get(pr_field):
+ if d.validate_applied_rule and doc.get(field) is not None and doc.get(field) < d.get(pr_field):
frappe.msgprint(_("User has not applied rule on the invoice {0}")
.format(doc.name))
else:
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
index e1ddeff..7328f16 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
@@ -1,25 +1,43 @@
-<h1 class="text-center" style="page-break-before:always">{{ filters.party[0] }}</h1>
-<h3 class="text-center">{{ _("Statement of Accounts") }}</h3>
+<div class="page-break">
+ <div id="header-html" class="hidden-pdf">
+ {% if letter_head %}
+ <div class="letter-head text-center">{{ letter_head.content }}</div>
+ <hr style="height:2px;border-width:0;color:black;background-color:black;">
+ {% endif %}
+ </div>
+ <div id="footer-html" class="visible-pdf">
+ {% if letter_head.footer %}
+ <div class="letter-head-footer">
+ <hr style="border-width:0;color:black;background-color:black;padding-bottom:2px;">
+ {{ letter_head.footer }}
+ </div>
+ {% endif %}
+ </div>
+ <h2 class="text-center">{{ _("STATEMENTS OF ACCOUNTS") }}</h2>
+ <div>
+ <h5 style="float: left;">{{ _("Customer: ") }} <b>{{filters.party[0] }}</b></h5>
+ <h5 style="float: right;">
+ {{ _("Date: ") }}
+ <b>{{ frappe.format(filters.from_date, 'Date')}}
+ {{ _("to") }}
+ {{ frappe.format(filters.to_date, 'Date')}}</b>
+ </h5>
+ </div>
+ <br>
-<h5 class="text-center">
- {{ frappe.format(filters.from_date, 'Date')}}
- {{ _("to") }}
- {{ frappe.format(filters.to_date, 'Date')}}
-</h5>
-
-<table class="table table-bordered">
- <thead>
- <tr>
- <th style="width: 12%">{{ _("Date") }}</th>
- <th style="width: 15%">{{ _("Ref") }}</th>
- <th style="width: 25%">{{ _("Party") }}</th>
- <th style="width: 15%">{{ _("Debit") }}</th>
- <th style="width: 15%">{{ _("Credit") }}</th>
- <th style="width: 18%">{{ _("Balance (Dr - Cr)") }}</th>
- </tr>
- </thead>
- <tbody>
- {% for row in data %}
+ <table class="table table-bordered">
+ <thead>
+ <tr>
+ <th style="width: 12%">{{ _("Date") }}</th>
+ <th style="width: 15%">{{ _("Reference") }}</th>
+ <th style="width: 25%">{{ _("Remarks") }}</th>
+ <th style="width: 15%">{{ _("Debit") }}</th>
+ <th style="width: 15%">{{ _("Credit") }}</th>
+ <th style="width: 18%">{{ _("Balance (Dr - Cr)") }}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for row in data %}
<tr>
{% if(row.posting_date) %}
<td>{{ frappe.format(row.posting_date, 'Date') }}</td>
@@ -38,52 +56,54 @@
{% endif %}
</td>
<td style="text-align: right">
- {{ frappe.utils.fmt_money(row.debit, filters.presentation_currency) }}</td>
+ {{ frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }}</td>
<td style="text-align: right">
- {{ frappe.utils.fmt_money(row.credit, filters.presentation_currency) }}</td>
+ {{ frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }}</td>
{% else %}
<td></td>
<td></td>
<td><b>{{ frappe.format(row.account, {fieldtype: "Link"}) or " " }}</b></td>
<td style="text-align: right">
- {{ row.account and frappe.utils.fmt_money(row.debit, filters.presentation_currency) }}
+ {{ row.account and frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }}
</td>
<td style="text-align: right">
- {{ row.account and frappe.utils.fmt_money(row.credit, filters.presentation_currency) }}
+ {{ row.account and frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }}
</td>
{% endif %}
<td style="text-align: right">
- {{ frappe.utils.fmt_money(row.balance, filters.presentation_currency) }}
+ {{ frappe.utils.fmt_money(row.balance, currency=filters.presentation_currency) }}
</td>
</tr>
{% endfor %}
</tbody>
-</table>
-<br><br>
-{% if aging %}
-<h3 class="text-center">{{ _("Ageing Report Based On ") }} {{ aging.ageing_based_on }}</h3>
-<h5 class="text-center">
- {{ _("Up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
-</h5>
-<br>
-
-<table class="table table-bordered">
- <thead>
- <tr>
- <th style="width: 12%">30 Days</th>
- <th style="width: 15%">60 Days</th>
- <th style="width: 25%">90 Days</th>
- <th style="width: 15%">120 Days</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>{{ aging.range1 }}</td>
- <td>{{ aging.range2 }}</td>
- <td>{{ aging.range3 }}</td>
- <td>{{ aging.range4 }}</td>
- </tr>
- </tbody>
-</table>
-{% endif %}
-<p class="text-right text-muted">Printed On {{ frappe.format(frappe.utils.get_datetime(), 'Datetime') }}</p>
\ No newline at end of file
+ </table>
+ <br>
+ {% if ageing %}
+ <h4 class="text-center">{{ _("Ageing Report based on ") }} {{ ageing.ageing_based_on }}
+ {{ _("up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
+ </h4>
+ <table class="table table-bordered">
+ <thead>
+ <tr>
+ <th style="width: 25%">30 Days</th>
+ <th style="width: 25%">60 Days</th>
+ <th style="width: 25%">90 Days</th>
+ <th style="width: 25%">120 Days</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>{{ frappe.utils.fmt_money(ageing.range1, currency=filters.presentation_currency) }}</td>
+ <td>{{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }}</td>
+ <td>{{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }}</td>
+ <td>{{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }}</td>
+ </tr>
+ </tbody>
+ </table>
+ {% endif %}
+ {% if terms_and_conditions %}
+ <div>
+ {{ terms_and_conditions }}
+ </div>
+ {% endif %}
+</div>
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js
index 7425132..088c190 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js
@@ -19,7 +19,7 @@
frappe.show_alert({message: __('Emails Queued'), indicator: 'blue'});
}
else{
- frappe.msgprint('No Records for these settings.')
+ frappe.msgprint(__('No Records for these settings.'))
}
}
});
@@ -33,7 +33,7 @@
type: 'GET',
success: function(result) {
if(jQuery.isEmptyObject(result)){
- frappe.msgprint('No Records for these settings.');
+ frappe.msgprint(__('No Records for these settings.'));
}
else{
window.location = url;
@@ -92,7 +92,7 @@
frm.refresh_field('customers');
}
else{
- frappe.msgprint('No Customers found with selected options.');
+ frappe.throw(__('No Customers found with selected options.'));
}
}
}
@@ -129,4 +129,4 @@
}
})
}
-});
\ No newline at end of file
+});
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
index 4be0e2e..27a5f50 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
@@ -1,6 +1,5 @@
{
"actions": [],
- "allow_workflow": 1,
"autoname": "Prompt",
"creation": "2020-05-22 16:46:18.712954",
"doctype": "DocType",
@@ -28,9 +27,11 @@
"customers",
"preferences",
"orientation",
- "section_break_14",
"include_ageing",
"ageing_based_on",
+ "section_break_14",
+ "letter_head",
+ "terms_and_conditions",
"section_break_1",
"enable_auto_email",
"section_break_18",
@@ -270,10 +271,22 @@
"fieldname": "body",
"fieldtype": "Text Editor",
"label": "Body"
+ },
+ {
+ "fieldname": "letter_head",
+ "fieldtype": "Link",
+ "label": "Letter Head",
+ "options": "Letter Head"
+ },
+ {
+ "fieldname": "terms_and_conditions",
+ "fieldtype": "Link",
+ "label": "Terms and Conditions",
+ "options": "Terms and Conditions"
}
],
"links": [],
- "modified": "2020-08-08 08:47:09.185728",
+ "modified": "2021-05-21 10:14:22.426672",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Statement Of Accounts",
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
index d50e4a8..0b0ee90 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
@@ -4,10 +4,12 @@
from __future__ import unicode_literals
import frappe
+from frappe import _
from frappe.model.document import Document
from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa
from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing
-from frappe.core.doctype.communication.email import make
+from erpnext import get_company_currency
+from erpnext.accounts.party import get_party_account_currency
from frappe.utils.print_format import report_to_pdf
from frappe.utils.pdf import get_pdf
@@ -29,7 +31,7 @@
validate_template(self.body)
if not self.customers:
- frappe.throw(frappe._('Customers not selected.'))
+ frappe.throw(_('Customers not selected.'))
if self.enable_auto_email:
self.to_date = self.start_date
@@ -38,7 +40,7 @@
def get_report_pdf(doc, consolidated=True):
statement_dict = {}
- aging = ''
+ ageing = ''
base_template_path = "frappe/www/printview.html"
template_path = "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
@@ -54,26 +56,33 @@
'range4': 120,
'customer': entry.customer
})
- col1, aging = get_ageing(ageing_filters)
- aging[0]['ageing_based_on'] = doc.ageing_based_on
+ col1, ageing = get_ageing(ageing_filters)
+
+ if ageing:
+ ageing[0]['ageing_based_on'] = doc.ageing_based_on
tax_id = frappe.get_doc('Customer', entry.customer).tax_id
+ presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \
+ or doc.currency or get_company_currency(doc.company)
+ if doc.letter_head:
+ from frappe.www.printview import get_letter_head
+ letter_head = get_letter_head(doc, 0)
filters= frappe._dict({
'from_date': doc.from_date,
'to_date': doc.to_date,
'company': doc.company,
'finance_book': doc.finance_book if doc.finance_book else None,
- "account": doc.account if doc.account else None,
+ 'account': doc.account if doc.account else None,
'party_type': 'Customer',
'party': [entry.customer],
+ 'presentation_currency': presentation_currency,
'group_by': doc.group_by,
'currency': doc.currency,
'cost_center': [cc.cost_center_name for cc in doc.cost_center],
'project': [p.project_name for p in doc.project],
'show_opening_entries': 0,
'include_default_book_entries': 0,
- 'show_cancelled_entries': 1,
'tax_id': tax_id if tax_id else None
})
col, res = get_soa(filters)
@@ -83,11 +92,17 @@
if len(res) == 3:
continue
+
html = frappe.render_template(template_path, \
- {"filters": filters, "data": res, "aging": aging[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,
+ "terms_and_conditions": frappe.db.get_value('Terms and Conditions', doc.terms_and_conditions, 'terms')
+ if doc.terms_and_conditions else None})
+
html = frappe.render_template(base_template_path, {"body": html, \
"css": get_print_style(), "title": "Statement For " + entry.customer})
statement_dict[entry.customer] = html
+
if not bool(statement_dict):
return False
elif consolidated:
@@ -126,9 +141,11 @@
sales_person_records = frappe._dict()
for d in records:
sales_person_records.setdefault(d.parenttype, set()).add(d.parent)
- customers = frappe.get_list('Customer', fields=['name', 'email_id'], \
+ if sales_person_records.get('Customer'):
+ return frappe.get_list('Customer', fields=['name', 'email_id'], \
filters=[['name', 'in', list(sales_person_records['Customer'])]])
- return customers
+ else:
+ return []
def get_recipients_and_cc(customer, doc):
recipients = []
@@ -165,7 +182,7 @@
if customer_collection == 'Sales Person':
customers = get_customers_based_on_sales_person(collection_name)
if not bool(customers):
- frappe.throw('No Customers found with selected options.')
+ frappe.throw(_('No Customers found with selected options.'))
else:
if customer_collection == 'Sales Partner':
customers = frappe.get_list('Customer', fields=['name', 'email_id'], \
@@ -197,14 +214,14 @@
if len(billing_email) == 0 or (billing_email[0][0] is None):
if billing_and_primary:
- frappe.throw('No billing email found for customer: '+ customer_name)
+ frappe.throw(_("No billing email found for customer: {0}").format(customer_name))
else:
return ''
if billing_and_primary:
primary_email = frappe.get_value('Customer', customer_name, 'email_id')
if primary_email is None and int(primary_mandatory):
- frappe.throw('No primary email found for customer: '+ customer_name)
+ frappe.throw(_("No primary email found for customer: {0}").format(customer_name))
return [primary_email or '', billing_email[0][0]]
else:
return billing_email[0][0] or ''
diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
index 523e9ee..7d93023 100644
--- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
+++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
@@ -9,7 +9,7 @@
from frappe.model.naming import make_autoname
from frappe.model.document import Document
-pricing_rule_fields = ['apply_on', 'mixed_conditions', 'is_cumulative', 'other_item_code', 'other_item_group'
+pricing_rule_fields = ['apply_on', 'mixed_conditions', 'is_cumulative', 'other_item_code', 'other_item_group',
'apply_rule_on_other', 'other_brand', 'selling', 'buying', 'applicable_for', 'valid_from',
'valid_upto', 'customer', 'customer_group', 'territory', 'sales_partner', 'campaign', 'supplier',
'supplier_group', 'company', 'currency', 'apply_multiple_pricing_rules']
@@ -111,4 +111,4 @@
for d in pricing_rule_fields:
args[d] = doc.get(d)
- return args
\ No newline at end of file
+ return args
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 66a8e20..f58c8f4 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -496,15 +496,6 @@
}
}
-cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
- if(doc.select_print_heading){
- // print heading
- cur_frm.pformat.print_heading = doc.select_print_heading;
- }
- else
- cur_frm.pformat.print_heading = __("Purchase Invoice");
-}
-
frappe.ui.form.on("Purchase Invoice", {
setup: function(frm) {
frm.custom_make_buttons = {
@@ -523,6 +514,28 @@
}
},
+ refresh: function(frm) {
+ frm.events.add_custom_buttons(frm);
+ },
+
+ add_custom_buttons: function(frm) {
+ if (frm.doc.per_received < 100) {
+ frm.add_custom_button(__('Purchase Receipt'), () => {
+ frm.events.make_purchase_receipt(frm);
+ }, __('Create'));
+ }
+
+ if (frm.doc.docstatus == 1 && frm.doc.per_received > 0) {
+ frm.add_custom_button(__('Purchase Receipt'), () => {
+ frappe.route_options = {
+ 'purchase_invoice': frm.doc.name
+ }
+
+ frappe.set_route("List", "Purchase Receipt", "List")
+ }, __('View'));
+ }
+ },
+
onload: function(frm) {
if(frm.doc.__onload && frm.is_new()) {
if(frm.doc.supplier) {
@@ -548,5 +561,13 @@
update_stock: function(frm) {
hide_fields(frm.doc);
frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock? true: false);
+ },
+
+ make_purchase_receipt: function(frm) {
+ frappe.model.open_mapped_doc({
+ method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt",
+ frm: frm,
+ freeze_message: __("Creating Purchase Receipt ...")
+ })
}
})
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 739bd67..d3d3ffa 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -163,7 +163,8 @@
"to_date",
"column_break_114",
"auto_repeat",
- "update_auto_repeat_reference"
+ "update_auto_repeat_reference",
+ "per_received"
],
"fields": [
{
@@ -1364,13 +1365,22 @@
"print_hide": 1,
"print_width": "50px",
"width": "50px"
+ },
+ {
+ "fieldname": "per_received",
+ "fieldtype": "Percent",
+ "hidden": 1,
+ "label": "Per Received",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2021-03-30 21:45:58.334107",
+ "modified": "2021-04-30 22:45:58.334107",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 5c4e32e..83e9f75 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -1207,3 +1207,41 @@
def on_doctype_update():
frappe.db.add_index("Purchase Invoice", ["supplier", "is_return", "return_against"])
+
+@frappe.whitelist()
+def make_purchase_receipt(source_name, target_doc=None):
+ def update_item(obj, target, source_parent):
+ target.qty = flt(obj.qty) - flt(obj.received_qty)
+ target.received_qty = flt(obj.qty) - flt(obj.received_qty)
+ target.stock_qty = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.conversion_factor)
+ target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate)
+ target.base_amount = (flt(obj.qty) - flt(obj.received_qty)) * \
+ flt(obj.rate) * flt(source_parent.conversion_rate)
+
+ doc = get_mapped_doc("Purchase Invoice", source_name, {
+ "Purchase Invoice": {
+ "doctype": "Purchase Receipt",
+ "validation": {
+ "docstatus": ["=", 1],
+ }
+ },
+ "Purchase Invoice Item": {
+ "doctype": "Purchase Receipt Item",
+ "field_map": {
+ "name": "purchase_invoice_item",
+ "parent": "purchase_invoice",
+ "bom": "bom",
+ "purchase_order": "purchase_order",
+ "po_detail": "purchase_order_item",
+ "material_request": "material_request",
+ "material_request_item": "material_request_item"
+ },
+ "postprocess": update_item,
+ "condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
+ },
+ "Purchase Taxes and Charges": {
+ "doctype": "Purchase Taxes and Charges"
+ }
+ }, target_doc)
+
+ return doc
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 50492f5..53db689 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -397,7 +397,7 @@
pi.update({
"payment_schedule": get_payment_terms("_Test Payment Term Template",
- pi.posting_date, pi.grand_total)
+ pi.posting_date, pi.grand_total, pi.base_grand_total)
})
pi.save()
@@ -636,8 +636,8 @@
def test_rejected_serial_no(self):
pi = make_purchase_invoice(item_code="_Test Serialized Item With Series", received_qty=2, qty=1,
- rejected_qty=1, rate=500, update_stock=1,
- rejected_warehouse = "_Test Rejected Warehouse - _TC")
+ rejected_qty=1, rate=500, update_stock=1, rejected_warehouse = "_Test Rejected Warehouse - _TC",
+ allow_zero_valuation_rate=1)
self.assertEqual(frappe.db.get_value("Serial No", pi.get("items")[0].serial_no, "warehouse"),
pi.get("items")[0].warehouse)
@@ -994,7 +994,8 @@
"project": args.project,
"rejected_warehouse": args.rejected_warehouse or "",
"rejected_serial_no": args.rejected_serial_no or "",
- "asset_location": args.location or ""
+ "asset_location": args.location or "",
+ "allow_zero_valuation_rate": args.get("allow_zero_valuation_rate") or 0
})
if args.get_taxes_and_charges:
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index 96ad0fd..10e1c73 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -607,6 +607,7 @@
"oldfieldname": "purchase_order",
"oldfieldtype": "Link",
"options": "Purchase Order",
+ "print_hide": 1,
"read_only": 1,
"search_index": 1
},
@@ -853,7 +854,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2021-02-23 00:59:52.614805",
+ "modified": "2021-03-30 09:02:39.256602",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india_list.js b/erpnext/accounts/doctype/sales_invoice/regional/india_list.js
index 3e1c522..ada665a 100644
--- a/erpnext/accounts/doctype/sales_invoice/regional/india_list.js
+++ b/erpnext/accounts/doctype/sales_invoice/regional/india_list.js
@@ -1,14 +1,14 @@
var globalOnload = frappe.listview_settings['Sales Invoice'].onload;
-frappe.listview_settings['Sales Invoice'].onload = function (doclist) {
+frappe.listview_settings['Sales Invoice'].onload = function (list_view) {
// Provision in case onload event is added to sales_invoice.js in future
if (globalOnload) {
- globalOnload(doclist);
+ globalOnload(list_view);
}
const action = () => {
- const selected_docs = doclist.get_checked_items();
- const docnames = doclist.get_checked_items(true);
+ const selected_docs = list_view.get_checked_items();
+ const docnames = list_view.get_checked_items(true);
for (let doc of selected_docs) {
if (doc.docstatus !== 1) {
@@ -19,7 +19,7 @@
frappe.call({
method: 'erpnext.regional.india.utils.generate_ewb_json',
args: {
- 'dt': doclist.doctype,
+ 'dt': list_view.doctype,
'dn': docnames
},
callback: function(r) {
@@ -35,5 +35,140 @@
});
};
- doclist.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false);
+ list_view.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false);
+
+ const generate_irns = () => {
+ const docnames = list_view.get_checked_items(true);
+ if (docnames && docnames.length) {
+ frappe.call({
+ method: 'erpnext.regional.india.e_invoice.utils.generate_einvoices',
+ args: { docnames },
+ freeze: true,
+ freeze_message: __('Generating E-Invoices...')
+ });
+ } else {
+ frappe.msgprint({
+ message: __('Please select at least one sales invoice to generate IRN'),
+ title: __('No Invoice Selected'),
+ indicator: 'red'
+ });
+ }
+ };
+
+ const cancel_irns = () => {
+ const docnames = list_view.get_checked_items(true);
+
+ const fields = [
+ {
+ "label": "Reason",
+ "fieldname": "reason",
+ "fieldtype": "Select",
+ "reqd": 1,
+ "default": "1-Duplicate",
+ "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
+ },
+ {
+ "label": "Remark",
+ "fieldname": "remark",
+ "fieldtype": "Data",
+ "reqd": 1
+ }
+ ];
+
+ const d = new frappe.ui.Dialog({
+ title: __("Cancel IRN"),
+ fields: fields,
+ primary_action: function() {
+ const data = d.get_values();
+ frappe.call({
+ method: 'erpnext.regional.india.e_invoice.utils.cancel_irns',
+ args: {
+ doctype: list_view.doctype,
+ docnames,
+ reason: data.reason.split('-')[0],
+ remark: data.remark
+ },
+ freeze: true,
+ freeze_message: __('Cancelling E-Invoices...'),
+ });
+ d.hide();
+ },
+ primary_action_label: __('Submit')
+ });
+ d.show();
+ };
+
+ let einvoicing_enabled = false;
+ frappe.db.get_single_value("E Invoice Settings", "enable").then(enabled => {
+ einvoicing_enabled = enabled;
+ });
+
+ list_view.$result.on("change", "input[type=checkbox]", () => {
+ if (einvoicing_enabled) {
+ const docnames = list_view.get_checked_items(true);
+ // show/hide e-invoicing actions when no sales invoices are checked
+ if (docnames && docnames.length) {
+ // prevent adding actions twice if e-invoicing action group already exists
+ if (list_view.page.get_inner_group_button(__('E-Invoicing')).length == 0) {
+ list_view.page.add_inner_button(__('Generate IRNs'), generate_irns, __('E-Invoicing'));
+ list_view.page.add_inner_button(__('Cancel IRNs'), cancel_irns, __('E-Invoicing'));
+ }
+ } else {
+ list_view.page.remove_inner_button(__('Generate IRNs'), __('E-Invoicing'));
+ list_view.page.remove_inner_button(__('Cancel IRNs'), __('E-Invoicing'));
+ }
+ }
+ });
+
+ frappe.realtime.on("bulk_einvoice_generation_complete", (data) => {
+ const { failures, user, invoices } = data;
+
+ if (invoices.length != failures.length) {
+ frappe.msgprint({
+ message: __('{0} e-invoices generated successfully', [invoices.length]),
+ title: __('Bulk E-Invoice Generation Complete'),
+ indicator: 'orange'
+ });
+ }
+
+ if (failures && failures.length && user == frappe.session.user) {
+ let message = `
+ Failed to generate IRNs for following ${failures.length} sales invoices:
+ <ul style="padding-left: 20px; padding-top: 5px;">
+ ${failures.map(d => `<li>${d.docname}</li>`).join('')}
+ </ul>
+ `;
+ frappe.msgprint({
+ message: message,
+ title: __('Bulk E-Invoice Generation Complete'),
+ indicator: 'orange'
+ });
+ }
+ });
+
+ frappe.realtime.on("bulk_einvoice_cancellation_complete", (data) => {
+ const { failures, user, invoices } = data;
+
+ if (invoices.length != failures.length) {
+ frappe.msgprint({
+ message: __('{0} e-invoices cancelled successfully', [invoices.length]),
+ title: __('Bulk E-Invoice Cancellation Complete'),
+ indicator: 'orange'
+ });
+ }
+
+ if (failures && failures.length && user == frappe.session.user) {
+ let message = `
+ Failed to cancel IRNs for following ${failures.length} sales invoices:
+ <ul style="padding-left: 20px; padding-top: 5px;">
+ ${failures.map(d => `<li>${d.docname}</li>`).join('')}
+ </ul>
+ `;
+ frappe.msgprint({
+ message: message,
+ title: __('Bulk E-Invoice Cancellation Complete'),
+ indicator: 'orange'
+ });
+ }
+ });
};
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index b361c0c..1808005 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -1,9 +1,6 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-// print heading
-cur_frm.pformat.print_heading = 'Invoice';
-
{% include 'erpnext/selling/sales_common.js' %};
frappe.provide("erpnext.accounts");
@@ -20,7 +17,7 @@
var me = this;
this._super();
- this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice'];
+ this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet'];
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
// show debit_to in print format
this.frm.set_df_property("debit_to", "print_hide", 0);
@@ -359,11 +356,11 @@
},
items_on_form_rendered: function() {
- erpnext.setup_serial_no();
+ erpnext.setup_serial_or_batch_no();
},
packed_items_on_form_rendered: function(doc, grid_row) {
- erpnext.setup_serial_no();
+ erpnext.setup_serial_or_batch_no();
},
make_sales_return: function() {
@@ -585,6 +582,16 @@
};
});
+ frm.set_query("adjustment_against", function() {
+ return {
+ filters: {
+ company: frm.doc.company,
+ customer: frm.doc.customer,
+ docstatus: 1
+ }
+ };
+ });
+
frm.custom_make_buttons = {
'Delivery Note': 'Delivery',
'Sales Invoice': 'Return / Credit Note',
@@ -688,14 +695,16 @@
},
project: function(frm){
- frm.call({
- method: "add_timesheet_data",
- doc: frm.doc,
- callback: function(r, rt) {
- refresh_field(['timesheets'])
- }
- })
- frm.refresh();
+ if (!frm.doc.is_return) {
+ frm.call({
+ method: "add_timesheet_data",
+ doc: frm.doc,
+ callback: function(r, rt) {
+ refresh_field(['timesheets'])
+ }
+ })
+ frm.refresh();
+ }
},
onload: function(frm) {
@@ -810,14 +819,27 @@
}
},
+ add_timesheet_row: function(frm, row, exchange_rate) {
+ frm.add_child('timesheets', {
+ 'activity_type': row.activity_type,
+ 'description': row.description,
+ 'time_sheet': row.parent,
+ 'billing_hours': row.billing_hours,
+ 'billing_amount': flt(row.billing_amount) * flt(exchange_rate),
+ 'timesheet_detail': row.name
+ });
+ frm.refresh_field('timesheets');
+ calculate_total_billing_amount(frm);
+ },
+
refresh: function(frm) {
- if (frm.doc.project) {
+ if (frm.doc.docstatus===0 && !frm.doc.is_return) {
frm.add_custom_button(__('Fetch Timesheet'), function() {
let d = new frappe.ui.Dialog({
title: __('Fetch Timesheet'),
fields: [
{
- "label" : "From",
+ "label" : __("From"),
"fieldname": "from_time",
"fieldtype": "Date",
"reqd": 1,
@@ -827,11 +849,18 @@
fieldname: 'col_break_1',
},
{
- "label" : "To",
+ "label" : __("To"),
"fieldname": "to_time",
"fieldtype": "Date",
"reqd": 1,
- }
+ },
+ {
+ "label" : __("Project"),
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "options": "Project",
+ "default": frm.doc.project
+ },
],
primary_action: function() {
let data = d.get_values();
@@ -840,27 +869,35 @@
args: {
from_time: data.from_time,
to_time: data.to_time,
- project: frm.doc.project
+ project: data.project
},
callback: function(r) {
- if(!r.exc) {
- if(r.message.length > 0) {
- frm.clear_table('timesheets')
- r.message.forEach((d) => {
- frm.add_child('timesheets',{
- 'time_sheet': d.parent,
- 'billing_hours': d.billing_hours,
- 'billing_amount': d.billing_amt,
- 'timesheet_detail': d.name
+ if (!r.exc && r.message.length > 0) {
+ frm.clear_table('timesheets')
+ r.message.forEach((d) => {
+ let exchange_rate = 1.0;
+ if (frm.doc.currency != d.currency) {
+ frappe.call({
+ method: 'erpnext.setup.utils.get_exchange_rate',
+ args: {
+ from_currency: d.currency,
+ to_currency: frm.doc.currency
+ },
+ callback: function(r) {
+ if (r.message) {
+ exchange_rate = r.message;
+ frm.events.add_timesheet_row(frm, d, exchange_rate);
+ }
+ }
});
- });
- frm.refresh_field('timesheets')
- }
- else {
- frappe.msgprint(__('No Timesheet Found.'))
- }
- d.hide();
+ } else {
+ frm.events.add_timesheet_row(frm, d, exchange_rate);
+ }
+ });
+ } else {
+ frappe.msgprint(__('No Timesheets found with the selected filters.'))
}
+ d.hide();
}
});
},
@@ -870,6 +907,10 @@
})
}
+ if (frm.doc.is_debit_note) {
+ frm.set_df_property('return_against', 'label', 'Adjustment Against');
+ }
+
if (frappe.boot.active_domains.includes("Healthcare")) {
frm.set_df_property("patient", "hidden", 0);
frm.set_df_property("patient_name", "hidden", 0);
@@ -916,7 +957,7 @@
},
callback: function(r, rt) {
if(r.message){
- data = r.message;
+ let data = r.message;
frappe.model.set_value(cdt, cdn, "billing_hours", data.billing_hours);
frappe.model.set_value(cdt, cdn, "billing_amount", data.billing_amount);
frappe.model.set_value(cdt, cdn, "timesheet_detail", data.timesheet_detail);
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index d382386..e7dd6b8 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -16,6 +16,7 @@
"is_pos",
"is_consolidated",
"is_return",
+ "is_debit_note",
"update_billed_amount_in_sales_order",
"column_break1",
"company",
@@ -118,6 +119,7 @@
"in_words",
"total_advance",
"outstanding_amount",
+ "disable_rounded_total",
"advances_section",
"allocate_advances_automatically",
"get_advances",
@@ -391,7 +393,7 @@
"read_only": 1
},
{
- "depends_on": "return_against",
+ "depends_on": "eval:doc.return_against || doc.is_debit_note",
"fieldname": "return_against",
"fieldtype": "Link",
"hide_days": 1,
@@ -400,7 +402,7 @@
"no_copy": 1,
"options": "Sales Invoice",
"print_hide": 1,
- "read_only": 1,
+ "read_only_depends_on": "eval:doc.is_return",
"search_index": 1
},
{
@@ -747,6 +749,7 @@
{
"collapsible": 1,
"collapsible_depends_on": "eval:doc.total_billing_amount > 0",
+ "depends_on": "eval: !doc.is_return",
"fieldname": "time_sheet_list",
"fieldtype": "Section Break",
"hide_days": 1,
@@ -769,6 +772,7 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Total Billing Amount",
+ "options": "currency",
"print_hide": 1,
"read_only": 1
},
@@ -1109,6 +1113,7 @@
"reqd": 1
},
{
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "base_rounding_adjustment",
"fieldtype": "Currency",
"hide_days": 1,
@@ -1120,6 +1125,7 @@
"read_only": 1
},
{
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "base_rounded_total",
"fieldtype": "Currency",
"hide_days": 1,
@@ -1168,6 +1174,7 @@
"reqd": 1
},
{
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "rounding_adjustment",
"fieldtype": "Currency",
"hide_days": 1,
@@ -1180,6 +1187,7 @@
},
{
"bold": 1,
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "rounded_total",
"fieldtype": "Currency",
"hide_days": 1,
@@ -1945,6 +1953,19 @@
"fieldtype": "Link",
"label": "Set Target Warehouse",
"options": "Warehouse"
+ },
+ {
+ "default": "0",
+ "fieldname": "is_debit_note",
+ "fieldtype": "Check",
+ "label": "Is Debit Note"
+ },
+ {
+ "default": "0",
+ "depends_on": "grand_total",
+ "fieldname": "disable_rounded_total",
+ "fieldtype": "Check",
+ "label": "Disable Rounded Total"
}
],
"icon": "fa fa-file-text",
@@ -1957,7 +1978,7 @@
"link_fieldname": "consolidated_invoice"
}
],
- "modified": "2021-03-31 15:42:26.261540",
+ "modified": "2021-05-20 22:48:33.988881",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 21d550a..023f4b0 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -24,6 +24,7 @@
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
from frappe.model.utils import get_fetch_values
from frappe.contacts.doctype.address.address import get_address_display
+from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
from erpnext.healthcare.utils import manage_invoice_submit_cancel
@@ -45,7 +46,6 @@
'target_parent_dt': 'Sales Order',
'target_parent_field': 'per_billed',
'source_field': 'amount',
- 'join_field': 'so_detail',
'percent_join_field': 'sales_order',
'status_field': 'billing_status',
'keyword': 'Billed',
@@ -125,6 +125,8 @@
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items")
if not self.is_return:
self.validate_serial_numbers()
+ else:
+ self.timesheets = []
self.update_packing_list()
self.set_billing_hours_and_amount()
self.update_timesheet_billing_for_project()
@@ -214,6 +216,9 @@
if self.update_stock == 1:
self.repost_future_sle_and_gle()
+ if self.update_stock == 1:
+ self.repost_future_sle_and_gle()
+
if not self.is_return:
self.update_billing_status_for_zero_amount_refdoc("Delivery Note")
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
@@ -272,7 +277,7 @@
pluck="pos_closing_entry"
)
if pos_closing_entry:
- msg = _("To cancel a {} you need to cancel the POS Closing Entry {}. ").format(
+ msg = _("To cancel a {} you need to cancel the POS Closing Entry {}.").format(
frappe.bold("Consolidated Sales Invoice"),
get_link_to_form("POS Closing Entry", pos_closing_entry[0])
)
@@ -334,7 +339,7 @@
if "Healthcare" in active_domains:
manage_invoice_submit_cancel(self, "on_cancel")
-
+ self.unlink_sales_invoice_from_timesheets()
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
def update_status_updater_args(self):
@@ -390,6 +395,18 @@
if validate_against_credit_limit:
check_credit_limit(self.customer, self.company, bypass_credit_limit_check_at_sales_order)
+ def unlink_sales_invoice_from_timesheets(self):
+ for row in self.timesheets:
+ timesheet = frappe.get_doc('Timesheet', row.time_sheet)
+ for time_log in timesheet.time_logs:
+ if time_log.sales_invoice == self.name:
+ time_log.sales_invoice = None
+ timesheet.calculate_total_amounts()
+ timesheet.calculate_percentage_billed()
+ timesheet.flags.ignore_validate_update_after_submit = True
+ timesheet.set_status()
+ timesheet.db_update_all()
+
@frappe.whitelist()
def set_missing_values(self, for_validate=False):
pos = self.set_pos_fields(for_validate)
@@ -424,7 +441,7 @@
timesheet.calculate_percentage_billed()
timesheet.flags.ignore_validate_update_after_submit = True
timesheet.set_status()
- timesheet.save()
+ timesheet.db_update_all()
def update_time_sheet_detail(self, timesheet, args, sales_invoice):
for data in timesheet.time_logs:
@@ -545,12 +562,12 @@
frappe.throw(_("Debit To is required"), title=_("Account Missing"))
if account.report_type != "Balance Sheet":
- msg = _("Please ensure {} account is a Balance Sheet account. ").format(frappe.bold("Debit To"))
+ msg = _("Please ensure {} account is a Balance Sheet account.").format(frappe.bold("Debit To")) + " "
msg += _("You can change the parent account to a Balance Sheet account or select a different account.")
frappe.throw(msg, title=_("Invalid Account"))
if self.customer and account.account_type != "Receivable":
- msg = _("Please ensure {} account is a Receivable account. ").format(frappe.bold("Debit To"))
+ msg = _("Please ensure {} account is a Receivable account.").format(frappe.bold("Debit To")) + " "
msg += _("Change the account type to Receivable or select a different account.")
frappe.throw(msg, title=_("Invalid Account"))
@@ -738,8 +755,10 @@
self.append('timesheets', {
'time_sheet': data.parent,
'billing_hours': data.billing_hours,
- 'billing_amount': data.billing_amt,
- 'timesheet_detail': data.name
+ 'billing_amount': data.billing_amount,
+ 'timesheet_detail': data.name,
+ 'activity_type': data.activity_type,
+ 'description': data.description
})
self.calculate_billing_amount_for_timesheet()
@@ -1108,7 +1127,7 @@
if not item.serial_no:
continue
- for serial_no in item.serial_no.split("\n"):
+ for serial_no in get_serial_nos(item.serial_no):
if serial_no and frappe.db.get_value('Serial No', serial_no, 'item_code') == item.item_code:
frappe.db.set_value('Serial No', serial_no, 'sales_invoice', invoice)
@@ -1118,7 +1137,6 @@
"""
self.set_serial_no_against_delivery_note()
self.validate_serial_against_delivery_note()
- self.validate_serial_against_sales_invoice()
def set_serial_no_against_delivery_note(self):
for item in self.items:
@@ -1149,26 +1167,6 @@
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)))
- 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):
if self.project:
project = frappe.get_doc("Project", self.project)
@@ -1752,15 +1750,10 @@
item.purchase_order = parent_child_map.get(sales_item_map.get(item.delivery_note_item))
def get_delivery_note_details(internal_reference):
- so_item_map = {}
-
si_item_details = frappe.get_all('Delivery Note Item', fields=['name', 'so_detail'],
filters={'parent': internal_reference})
- for d in si_item_details:
- so_item_map.setdefault(d.name, d.so_detail)
-
- return so_item_map
+ return {d.name: d.so_detail for d in si_item_details if d.so_detail}
def get_sales_invoice_details(internal_reference):
dn_item_map = {}
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index f09cc5a..df6d483 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -933,12 +933,6 @@
self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"))
self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0],
"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
@@ -1166,10 +1160,12 @@
def test_create_so_with_margin(self):
si = create_sales_invoice(item_code="_Test Item", qty=1, do_not_submit=True)
- price_list_rate = 100
+ price_list_rate = flt(100) * flt(si.plc_conversion_rate)
si.items[0].price_list_rate = price_list_rate
si.items[0].margin_type = 'Percentage'
si.items[0].margin_rate_or_amount = 25
+ si.items[0].discount_amount = 0.0
+ si.items[0].discount_percentage = 0.0
si.save()
self.assertEqual(si.get("items")[0].rate, flt((price_list_rate*25)/100 + price_list_rate))
@@ -1877,7 +1873,17 @@
def test_einvoice_submission_without_irn(self):
# init
- frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1)
+ einvoice_settings = frappe.get_doc('E Invoice Settings')
+ einvoice_settings.enable = 1
+ einvoice_settings.applicable_from = nowdate()
+ einvoice_settings.append('credentials', {
+ 'company': '_Test Company',
+ 'gstin': '27AAECE4835E1ZR',
+ 'username': 'test',
+ 'password': 'test'
+ })
+ einvoice_settings.save()
+
country = frappe.flags.country
frappe.flags.country = 'India'
@@ -1888,7 +1894,8 @@
si.submit()
# reset
- frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0)
+ einvoice_settings = frappe.get_doc('E Invoice Settings')
+ einvoice_settings.enable = 0
frappe.flags.country = country
def test_einvoice_json(self):
diff --git a/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json b/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json
index f7b9aef..f069e8d 100644
--- a/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json
+++ b/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json
@@ -1,172 +1,78 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-06-14 19:21:34.321662",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2016-06-14 19:21:34.321662",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "activity_type",
+ "description",
+ "billing_hours",
+ "billing_amount",
+ "time_sheet",
+ "timesheet_detail"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "time_sheet",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Time Sheet",
- "length": 0,
- "no_copy": 0,
- "options": "Timesheet",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "time_sheet",
+ "fieldtype": "Link",
+ "in_global_search": 1,
+ "in_list_view": 1,
+ "label": "Time Sheet",
+ "options": "Timesheet",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "billing_hours",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Billing Hours",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "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
- },
+ "fieldname": "billing_hours",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Billing Hours",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "billing_amount",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Billing Amount",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "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
- },
+ "fieldname": "billing_amount",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Billing Amount",
+ "options": "currency",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "timesheet_detail",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Timesheet Detail",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "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_on_submit": 1,
+ "fieldname": "timesheet_detail",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Timesheet Detail",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "activity_type",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Activity Type",
+ "options": "Activity Type",
+ "read_only": 1
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Small Text",
+ "in_list_view": 1,
+ "label": "Description",
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2019-02-18 18:50:44.770361",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Sales Invoice Timesheet",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2021-05-20 22:33:57.234846",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Sales Invoice Timesheet",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/subscription/subscription.json b/erpnext/accounts/doctype/subscription/subscription.json
index e80df2a..c4e4be7 100644
--- a/erpnext/accounts/doctype/subscription/subscription.json
+++ b/erpnext/accounts/doctype/subscription/subscription.json
@@ -36,6 +36,7 @@
"additional_discount_percentage",
"additional_discount_amount",
"sb_3",
+ "submit_invoice",
"invoices",
"accounting_dimensions_section",
"cost_center",
@@ -45,9 +46,7 @@
{
"allow_on_submit": 1,
"fieldname": "cb_1",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "status",
@@ -55,97 +54,73 @@
"label": "Status",
"no_copy": 1,
"options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid\nCompleted",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "subscription_period",
"fieldtype": "Section Break",
- "label": "Subscription Period",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Subscription Period"
},
{
"fieldname": "cancelation_date",
"fieldtype": "Date",
"label": "Cancelation Date",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"allow_on_submit": 1,
"fieldname": "trial_period_start",
"fieldtype": "Date",
"label": "Trial Period Start Date",
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"depends_on": "eval:doc.trial_period_start",
"fieldname": "trial_period_end",
"fieldtype": "Date",
"label": "Trial Period End Date",
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"fieldname": "column_break_11",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "current_invoice_start",
"fieldtype": "Date",
"label": "Current Invoice Start Date",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "current_invoice_end",
"fieldtype": "Date",
"label": "Current Invoice End Date",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"default": "0",
"description": "Number of days that the subscriber has to pay invoices generated by this subscription",
"fieldname": "days_until_due",
"fieldtype": "Int",
- "label": "Days Until Due",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Days Until Due"
},
{
"default": "0",
"fieldname": "cancel_at_period_end",
"fieldtype": "Check",
- "label": "Cancel At End Of Period",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Cancel At End Of Period"
},
{
"default": "0",
"fieldname": "generate_invoice_at_period_start",
"fieldtype": "Check",
- "label": "Generate Invoice At Beginning Of Period",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Generate Invoice At Beginning Of Period"
},
{
"allow_on_submit": 1,
"fieldname": "sb_4",
"fieldtype": "Section Break",
- "label": "Plans",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Plans"
},
{
"allow_on_submit": 1,
@@ -153,84 +128,62 @@
"fieldtype": "Table",
"label": "Plans",
"options": "Subscription Plan Detail",
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"depends_on": "eval:['Customer', 'Supplier'].includes(doc.party_type)",
"fieldname": "sb_1",
"fieldtype": "Section Break",
- "label": "Taxes",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Taxes"
},
{
"fieldname": "sb_2",
"fieldtype": "Section Break",
- "label": "Discounts",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Discounts"
},
{
"fieldname": "apply_additional_discount",
"fieldtype": "Select",
"label": "Apply Additional Discount On",
- "options": "\nGrand Total\nNet Total",
- "show_days": 1,
- "show_seconds": 1
+ "options": "\nGrand Total\nNet Total"
},
{
"fieldname": "cb_2",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "additional_discount_percentage",
"fieldtype": "Percent",
- "label": "Additional DIscount Percentage",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Additional DIscount Percentage"
},
{
"collapsible": 1,
"fieldname": "additional_discount_amount",
"fieldtype": "Currency",
- "label": "Additional DIscount Amount",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Additional DIscount Amount"
},
{
"depends_on": "eval:doc.invoices",
"fieldname": "sb_3",
"fieldtype": "Section Break",
- "label": "Invoices",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Invoices"
},
{
"collapsible": 1,
"fieldname": "invoices",
"fieldtype": "Table",
"label": "Invoices",
- "options": "Subscription Invoice",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Subscription Invoice"
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
- "label": "Accounting Dimensions",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Accounting Dimensions"
},
{
"fieldname": "dimension_col_break",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "party_type",
@@ -238,9 +191,7 @@
"label": "Party Type",
"options": "DocType",
"reqd": 1,
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"fieldname": "party",
@@ -249,27 +200,21 @@
"label": "Party",
"options": "party_type",
"reqd": 1,
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"depends_on": "eval:doc.party_type === 'Customer'",
"fieldname": "sales_tax_template",
"fieldtype": "Link",
"label": "Sales Taxes and Charges Template",
- "options": "Sales Taxes and Charges Template",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Sales Taxes and Charges Template"
},
{
"depends_on": "eval:doc.party_type === 'Supplier'",
"fieldname": "purchase_tax_template",
"fieldtype": "Link",
"label": "Purchase Taxes and Charges Template",
- "options": "Purchase Taxes and Charges Template",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Purchase Taxes and Charges Template"
},
{
"default": "0",
@@ -277,55 +222,49 @@
"fieldname": "follow_calendar_months",
"fieldtype": "Check",
"label": "Follow Calendar Months",
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"default": "0",
"description": "New invoices will be generated as per schedule even if current invoices are unpaid or past due date",
"fieldname": "generate_new_invoices_past_due_date",
"fieldtype": "Check",
- "label": "Generate New Invoices Past Due Date",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Generate New Invoices Past Due Date"
},
{
"fieldname": "end_date",
"fieldtype": "Date",
"label": "Subscription End Date",
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"fieldname": "start_date",
"fieldtype": "Date",
"label": "Subscription Start Date",
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
- "options": "Cost Center",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Cost Center"
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
- "options": "Company",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Company"
+ },
+ {
+ "default": "1",
+ "fieldname": "submit_invoice",
+ "fieldtype": "Check",
+ "label": "Submit Invoice Automatically"
}
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2021-02-09 15:44:20.024789",
+ "modified": "2021-04-19 15:24:27.550797",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription",
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index 826044a..7c4ff73 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -276,7 +276,7 @@
frappe.throw(_('Subscription End Date is mandatory to follow calendar months'))
if billing_info[0]['billing_interval'] != 'Month':
- frappe.throw('Billing Interval in Subscription Plan must be Month to follow calendar months')
+ frappe.throw(_('Billing Interval in Subscription Plan must be Month to follow calendar months'))
def after_insert(self):
# todo: deal with users who collect prepayments. Maybe a new Subscription Invoice doctype?
@@ -383,7 +383,9 @@
invoice.flags.ignore_mandatory = True
invoice.save()
- invoice.submit()
+
+ if self.submit_invoice:
+ invoice.submit()
return invoice
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index 961bdb1..5c1cbaa 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -21,7 +21,10 @@
else:
party_type = 'Supplier'
party = inv.supplier
-
+
+ if not party:
+ frappe.throw(_("Please select {0} first").format(party_type))
+
return party_type, party
def get_party_tax_withholding_details(inv, tax_withholding_category=None):
@@ -251,7 +254,7 @@
threshold = tax_details.get('threshold', 0)
cumulative_threshold = tax_details.get('cumulative_threshold', 0)
- if ((threshold and supp_credit_amt >= 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(
ldc.valid_from, ldc.valid_upto,
inv.posting_date, tax_deducted,
@@ -324,7 +327,7 @@
net_total, ldc.certificate_limit
):
tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details)
-
+
return tds_amount
def get_debit_note_amount(suppliers, fiscal_year_details, company=None):
diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
index dd3b49a..0cea761 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
@@ -87,50 +87,6 @@
for d in invoices:
d.cancel()
- def test_single_threshold_tds_with_previous_vouchers(self):
- invoices = []
- frappe.db.set_value("Supplier", "Test TDS Supplier2", "tax_withholding_category", "Single Threshold TDS")
- pi = create_purchase_invoice(supplier="Test TDS Supplier2")
- pi.submit()
- invoices.append(pi)
-
- pi = create_purchase_invoice(supplier="Test TDS Supplier2")
- pi.submit()
- invoices.append(pi)
-
- self.assertEqual(pi.taxes_and_charges_deducted, 2000)
- self.assertEqual(pi.grand_total, 8000)
-
- # delete invoices to avoid clashing
- for d in invoices:
- d.cancel()
-
- def test_single_threshold_tds_with_previous_vouchers_and_no_tds(self):
- invoices = []
- doc = create_supplier(supplier_name = "Test TDS Supplier ABC",
- tax_withholding_category="Single Threshold TDS")
- supplier = doc.name
-
- pi = create_purchase_invoice(supplier=supplier)
- pi.submit()
- invoices.append(pi)
-
- # TDS not applied
- pi = create_purchase_invoice(supplier=supplier, do_not_apply_tds=True)
- pi.submit()
- invoices.append(pi)
-
- pi = create_purchase_invoice(supplier=supplier)
- pi.submit()
- invoices.append(pi)
-
- self.assertEqual(pi.taxes_and_charges_deducted, 2000)
- self.assertEqual(pi.grand_total, 8000)
-
- # delete invoices to avoid clashing
- for d in invoices:
- d.cancel()
-
def test_cumulative_threshold_tcs(self):
frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS")
invoices = []
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index dac0c21..f1717c5 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -18,7 +18,8 @@
gl_map = process_gl_map(gl_map, merge_entries)
if gl_map and len(gl_map) > 1:
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
- else:
+ # Post GL Map proccess there may no be any GL Entries
+ elif gl_map:
frappe.throw(_("Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction."))
else:
make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
@@ -170,7 +171,7 @@
else:
allowance = .5
- if abs(debit_credit_diff) >= allowance:
+ if abs(debit_credit_diff) > allowance:
frappe.throw(_("Debit and Credit not equal for {0} #{1}. Difference is {2}.")
.format(gl_map[0].voucher_type, gl_map[0].voucher_no, debit_credit_diff))
diff --git a/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json b/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json
index bd7a126..4c7faf4 100644
--- a/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json
+++ b/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json
@@ -1,5 +1,6 @@
{
"attach_print": 0,
+ "channel": "Email",
"condition": "doc.auto_created",
"creation": "2018-04-25 14:19:05.440361",
"days_in_advance": 0,
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 444b40e..db605f7 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -364,7 +364,7 @@
payment_terms_details = frappe.db.sql("""
select
si.name, si.party_account_currency, si.currency, si.conversion_rate,
- ps.due_date, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount
+ ps.due_date, ps.payment_term, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount
from `tab{0}` si, `tabPayment Schedule` ps
where
si.name = ps.parent and
@@ -394,7 +394,7 @@
"due_date": d.due_date,
"invoiced": invoiced,
"invoice_grand_total": row.invoiced,
- "payment_term": d.description,
+ "payment_term": d.description or d.payment_term,
"paid": d.paid_amount + d.discounted_amount,
"credit_note": 0.0,
"outstanding": invoiced - d.paid_amount - d.discounted_amount
diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py
index 1729abc..26bb44f 100644
--- a/erpnext/accounts/report/balance_sheet/balance_sheet.py
+++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py
@@ -5,7 +5,8 @@
import frappe
from frappe import _
from frappe.utils import flt, cint
-from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
+from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data,
+ get_filtered_list_for_consolidated_report)
def execute(filters=None):
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
@@ -132,6 +133,10 @@
if filters.get('accumulated_values'):
period_list = [period_list[-1]]
+ # from consolidated financial statement
+ if filters.get('accumulated_in_group_company'):
+ period_list = get_filtered_list_for_consolidated_report(filters, period_list)
+
for period in period_list:
key = period if consolidated else period.key
if asset:
diff --git a/erpnext/accounts/report/billed_items_to_be_received/__init__.py b/erpnext/accounts/report/billed_items_to_be_received/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/report/billed_items_to_be_received/__init__.py
diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js
new file mode 100644
index 0000000..e1fccb6
--- /dev/null
+++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js
@@ -0,0 +1,29 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports['Billed Items To Be Received'] = {
+ 'filters': [
+ {
+ 'label': __('Company'),
+ 'fieldname': 'company',
+ 'fieldtype': 'Link',
+ 'options': 'Company',
+ 'reqd': 1,
+ 'default': frappe.defaults.get_default('Company')
+ },
+ {
+ 'label': __('As on Date'),
+ 'fieldname': 'posting_date',
+ 'fieldtype': 'Date',
+ 'reqd': 1,
+ 'default': get_today()
+ },
+ {
+ 'label': __('Purchase Invoice'),
+ 'fieldname': 'purchase_invoice',
+ 'fieldtype': 'Link',
+ 'options': 'Purchase Invoice'
+ }
+ ]
+};
diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json
new file mode 100644
index 0000000..de09b33
--- /dev/null
+++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json
@@ -0,0 +1,39 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-03-30 09:35:38.683028",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-03-31 08:48:30.944429",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Billed Items To Be Received",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "query": "",
+ "ref_doctype": "Purchase Invoice",
+ "report_name": "Billed Items To Be Received",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Accounts User"
+ },
+ {
+ "role": "Purchase User"
+ },
+ {
+ "role": "Accounts Manager"
+ },
+ {
+ "role": "Auditor"
+ },
+ {
+ "role": "Stock User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py
new file mode 100644
index 0000000..2ce5d50
--- /dev/null
+++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py
@@ -0,0 +1,107 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+def execute(filters=None):
+ data = get_data(filters) or []
+ columns = get_columns()
+
+ return columns, data
+
+def get_data(report_filters):
+ filters = get_report_filters(report_filters)
+ fields = get_report_fields()
+
+ return frappe.get_all('Purchase Invoice',
+ fields= fields, filters=filters)
+
+def get_report_filters(report_filters):
+ filters = [['Purchase Invoice','company','=',report_filters.get('company')],
+ ['Purchase Invoice','posting_date','<=',report_filters.get('posting_date')], ['Purchase Invoice','docstatus','=',1],
+ ['Purchase Invoice','per_received','<',100], ['Purchase Invoice','update_stock','=',0]]
+
+ if report_filters.get('purchase_invoice'):
+ filters.append(['Purchase Invoice','per_received','in',[report_filters.get('purchase_invoice')]])
+
+ return filters
+
+def get_report_fields():
+ fields = []
+ for p_field in ['name', 'supplier', 'company', 'posting_date', 'currency']:
+ fields.append('`tabPurchase Invoice`.`{}`'.format(p_field))
+
+ for c_field in ['item_code', 'item_name', 'uom', 'qty', 'received_qty', 'rate', 'amount']:
+ fields.append('`tabPurchase Invoice Item`.`{}`'.format(c_field))
+
+ return fields
+
+def get_columns():
+ return [
+ {
+ 'label': _('Purchase Invoice'),
+ 'fieldname': 'name',
+ 'fieldtype': 'Link',
+ 'options': 'Purchase Invoice',
+ 'width': 170
+ },
+ {
+ 'label': _('Supplier'),
+ 'fieldname': 'supplier',
+ 'fieldtype': 'Link',
+ 'options': 'Supplier',
+ 'width': 120
+ },
+ {
+ 'label': _('Posting Date'),
+ 'fieldname': 'posting_date',
+ 'fieldtype': 'Date',
+ 'width': 100
+ },
+ {
+ 'label': _('Item Code'),
+ 'fieldname': 'item_code',
+ 'fieldtype': 'Link',
+ 'options': 'Item',
+ 'width': 100
+ },
+ {
+ 'label': _('Item Name'),
+ 'fieldname': 'item_name',
+ 'fieldtype': 'Data',
+ 'width': 100
+ },
+ {
+ 'label': _('UOM'),
+ 'fieldname': 'uom',
+ 'fieldtype': 'Link',
+ 'options': 'UOM',
+ 'width': 100
+ },
+ {
+ 'label': _('Invoiced Qty'),
+ 'fieldname': 'qty',
+ 'fieldtype': 'Float',
+ 'width': 100
+ },
+ {
+ 'label': _('Received Qty'),
+ 'fieldname': 'received_qty',
+ 'fieldtype': 'Float',
+ 'width': 100
+ },
+ {
+ 'label': _('Rate'),
+ 'fieldname': 'rate',
+ 'fieldtype': 'Currency',
+ 'width': 100
+ },
+ {
+ 'label': _('Amount'),
+ 'fieldname': 'amount',
+ 'fieldtype': 'Currency',
+ 'width': 100
+ }
+ ]
\ No newline at end of file
diff --git a/erpnext/accounts/report/cash_flow/cash_flow.py b/erpnext/accounts/report/cash_flow/cash_flow.py
index cf0946b..3577457 100644
--- a/erpnext/accounts/report/cash_flow/cash_flow.py
+++ b/erpnext/accounts/report/cash_flow/cash_flow.py
@@ -5,7 +5,7 @@
import frappe
from frappe import _
from frappe.utils import cint, cstr
-from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
+from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data, get_filtered_list_for_consolidated_report)
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import get_net_profit_loss
from erpnext.accounts.utils import get_fiscal_year
from six import iteritems
@@ -67,9 +67,9 @@
section_data.append(account_data)
add_total_row_account(data, section_data, cash_flow_account['section_footer'],
- period_list, company_currency, summary_data)
+ period_list, company_currency, summary_data, filters)
- add_total_row_account(data, data, _("Net Change in Cash"), period_list, company_currency, summary_data)
+ add_total_row_account(data, data, _("Net Change in Cash"), period_list, company_currency, summary_data, filters)
columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, filters.company)
chart = get_chart_data(columns, data)
@@ -162,18 +162,26 @@
return start_date
-def add_total_row_account(out, data, label, period_list, currency, summary_data, consolidated = False):
+def add_total_row_account(out, data, label, period_list, currency, summary_data, filters, consolidated=False):
total_row = {
"account_name": "'" + _("{0}").format(label) + "'",
"account": "'" + _("{0}").format(label) + "'",
"currency": currency
}
+
+ summary_data[label] = 0
+
+ # from consolidated financial statement
+ if filters.get('accumulated_in_group_company'):
+ period_list = get_filtered_list_for_consolidated_report(filters, period_list)
+
for row in data:
if row.get("parent_account"):
for period in period_list:
key = period if consolidated else period['key']
total_row.setdefault(key, 0.0)
total_row[key] += row.get(key, 0.0)
+ summary_data[label] += row.get(key)
total_row.setdefault("total", 0.0)
total_row["total"] += row["total"]
@@ -181,7 +189,6 @@
out.append(total_row)
out.append({})
- summary_data[label] = total_row["total"]
def get_report_summary(summary_data, currency):
report_summary = []
diff --git a/erpnext/accounts/report/cash_flow/custom_cash_flow.py b/erpnext/accounts/report/cash_flow/custom_cash_flow.py
index fe2bc72..ff87276 100644
--- a/erpnext/accounts/report/cash_flow/custom_cash_flow.py
+++ b/erpnext/accounts/report/cash_flow/custom_cash_flow.py
@@ -165,7 +165,7 @@
if profit_data:
profit_data.update({
"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)
section_data.append(profit_data)
@@ -312,10 +312,10 @@
def compute_data(filters, company_currency, profit_data, period_list, light_mappers, full_mapper):
data = []
- operating_activities_mapper = get_mapper_for(light_mappers, position=0)
+ operating_activities_mapper = get_mapper_for(light_mappers, position=1)
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:
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
index 0947922..1363b53 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
@@ -2,118 +2,128 @@
// For license information, please see license.txt
/* eslint-disable */
-frappe.query_reports["Consolidated Financial Statement"] = {
- "filters": [
- {
- "fieldname":"company",
- "label": __("Company"),
- "fieldtype": "Link",
- "options": "Company",
- "default": frappe.defaults.get_user_default("Company"),
- "reqd": 1
- },
- {
- "fieldname":"filter_based_on",
- "label": __("Filter Based On"),
- "fieldtype": "Select",
- "options": ["Fiscal Year", "Date Range"],
- "default": ["Fiscal Year"],
- "reqd": 1,
- on_change: function() {
- let filter_based_on = frappe.query_report.get_filter_value('filter_based_on');
- frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range');
- frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range');
- frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year');
- frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year');
+frappe.require("assets/erpnext/js/financial_statements.js", function() {
+ frappe.query_reports["Consolidated Financial Statement"] = {
+ "filters": [
+ {
+ "fieldname":"company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "default": frappe.defaults.get_user_default("Company"),
+ "reqd": 1
+ },
+ {
+ "fieldname":"filter_based_on",
+ "label": __("Filter Based On"),
+ "fieldtype": "Select",
+ "options": ["Fiscal Year", "Date Range"],
+ "default": ["Fiscal Year"],
+ "reqd": 1,
+ on_change: function() {
+ let filter_based_on = frappe.query_report.get_filter_value('filter_based_on');
+ frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range');
+ frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range');
+ frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year');
+ frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year');
- frappe.query_report.refresh();
+ frappe.query_report.refresh();
+ }
+ },
+ {
+ "fieldname":"period_start_date",
+ "label": __("Start Date"),
+ "fieldtype": "Date",
+ "hidden": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname":"period_end_date",
+ "label": __("End Date"),
+ "fieldtype": "Date",
+ "hidden": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname":"from_fiscal_year",
+ "label": __("Start Year"),
+ "fieldtype": "Link",
+ "options": "Fiscal Year",
+ "default": frappe.defaults.get_user_default("fiscal_year"),
+ "reqd": 1
+ },
+ {
+ "fieldname":"to_fiscal_year",
+ "label": __("End Year"),
+ "fieldtype": "Link",
+ "options": "Fiscal Year",
+ "default": frappe.defaults.get_user_default("fiscal_year"),
+ "reqd": 1
+ },
+ {
+ "fieldname":"finance_book",
+ "label": __("Finance Book"),
+ "fieldtype": "Link",
+ "options": "Finance Book"
+ },
+ {
+ "fieldname":"report",
+ "label": __("Report"),
+ "fieldtype": "Select",
+ "options": ["Profit and Loss Statement", "Balance Sheet", "Cash Flow"],
+ "default": "Balance Sheet",
+ "reqd": 1
+ },
+ {
+ "fieldname": "presentation_currency",
+ "label": __("Currency"),
+ "fieldtype": "Select",
+ "options": erpnext.get_presentation_currency_list(),
+ "default": frappe.defaults.get_user_default("Currency")
+ },
+ {
+ "fieldname":"accumulated_in_group_company",
+ "label": __("Accumulated Values in Group Company"),
+ "fieldtype": "Check",
+ "default": 0
+ },
+ {
+ "fieldname": "include_default_book_entries",
+ "label": __("Include Default Book Entries"),
+ "fieldtype": "Check",
+ "default": 1
}
- },
- {
- "fieldname":"period_start_date",
- "label": __("Start Date"),
- "fieldtype": "Date",
- "hidden": 1,
- "reqd": 1
- },
- {
- "fieldname":"period_end_date",
- "label": __("End Date"),
- "fieldtype": "Date",
- "hidden": 1,
- "reqd": 1
- },
- {
- "fieldname":"from_fiscal_year",
- "label": __("Start Year"),
- "fieldtype": "Link",
- "options": "Fiscal Year",
- "default": frappe.defaults.get_user_default("fiscal_year"),
- "reqd": 1
- },
- {
- "fieldname":"to_fiscal_year",
- "label": __("End Year"),
- "fieldtype": "Link",
- "options": "Fiscal Year",
- "default": frappe.defaults.get_user_default("fiscal_year"),
- "reqd": 1
- },
- {
- "fieldname":"finance_book",
- "label": __("Finance Book"),
- "fieldtype": "Link",
- "options": "Finance Book"
- },
- {
- "fieldname":"report",
- "label": __("Report"),
- "fieldtype": "Select",
- "options": ["Profit and Loss Statement", "Balance Sheet", "Cash Flow"],
- "default": "Balance Sheet",
- "reqd": 1
- },
- {
- "fieldname": "presentation_currency",
- "label": __("Currency"),
- "fieldtype": "Select",
- "options": erpnext.get_presentation_currency_list(),
- "default": frappe.defaults.get_user_default("Currency")
- },
- {
- "fieldname":"accumulated_in_group_company",
- "label": __("Accumulated Values in Group Company"),
- "fieldtype": "Check",
- "default": 0
- },
- {
- "fieldname": "include_default_book_entries",
- "label": __("Include Default Book Entries"),
- "fieldtype": "Check",
- "default": 1
- }
- ],
- "formatter": function(value, row, column, data, default_formatter) {
- value = default_formatter(value, row, column, data);
+ ],
+ "formatter": function(value, row, column, data, default_formatter) {
+ if (data && column.fieldname=="account") {
+ value = data.account_name || value;
+
+ column.link_onclick =
+ "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")";
+ column.is_tree = true;
+ }
- if (!data.parent_account) {
- value = $(`<span>${value}</span>`);
+ value = default_formatter(value, row, column, data);
- var $value = $(value).css("font-weight", "bold");
+ if (!data.parent_account) {
+ value = $(`<span>${value}</span>`);
- value = $value.wrap("<p></p>").parent().html();
- }
- return value;
- },
- onload: function() {
- let fiscal_year = frappe.defaults.get_user_default("fiscal_year")
+ var $value = $(value).css("font-weight", "bold");
- 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({
- period_start_date: fy.year_start_date,
- period_end_date: fy.year_end_date
+ value = $value.wrap("<p></p>").parent().html();
+ }
+ return value;
+ },
+ onload: function() {
+ let fiscal_year = frappe.defaults.get_user_default("fiscal_year")
+
+ 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({
+ period_start_date: fy.year_start_date,
+ period_end_date: fy.year_end_date
+ });
});
- });
+ }
}
-}
+});
\ No newline at end of file
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
index 0c4a422..7793af7 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
@@ -94,7 +94,7 @@
chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss)
- report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, company_currency, True)
+ report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, company_currency, filters, True)
return data, None, chart, report_summary
@@ -149,9 +149,9 @@
section_data.append(account_data)
add_total_row_account(data, section_data, cash_flow_account['section_footer'],
- companies, company_currency, summary_data, True)
+ companies, company_currency, summary_data, filters, True)
- add_total_row_account(data, data, _("Net Change in Cash"), companies, company_currency, summary_data, True)
+ add_total_row_account(data, data, _("Net Change in Cash"), companies, company_currency, summary_data, filters, True)
report_summary = get_cash_flow_summary(summary_data, company_currency)
@@ -329,8 +329,9 @@
has_value = False
total = 0
row = frappe._dict({
- "account_name": _(d.account_name),
- "account": _(d.account_name),
+ "account_name": ('%s - %s' %(_(d.account_number), _(d.account_name))
+ if d.account_number else _(d.account_name)),
+ "account": _(d.name),
"parent_account": _(d.parent_account),
"indent": flt(d.indent),
"year_start_date": start_date,
diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/__init__.py b/erpnext/accounts/report/dimension_wise_accounts_balance_report/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/__init__.py
diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js
new file mode 100644
index 0000000..6a03948
--- /dev/null
+++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js
@@ -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
+}
diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.json b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.json
new file mode 100644
index 0000000..6141944
--- /dev/null
+++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.json
@@ -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": []
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py
new file mode 100644
index 0000000..de7ed49
--- /dev/null
+++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py
@@ -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
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 14efa1f..d20ddbd 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -119,10 +119,10 @@
def validate_dates(from_date, to_date):
if not from_date or not to_date:
- frappe.throw("From Date and To Date are mandatory")
+ frappe.throw(_("From Date and To Date are mandatory"))
if to_date < from_date:
- frappe.throw("To Date cannot be less than From Date")
+ frappe.throw(_("To Date cannot be less than From Date"))
def get_months(start_date, end_date):
diff = (12 * end_date.year + end_date.month) - (12 * start_date.year + start_date.month)
@@ -522,4 +522,12 @@
"width": 150
})
- return columns
\ No newline at end of file
+ return columns
+
+def get_filtered_list_for_consolidated_report(filters, period_list):
+ filtered_summary_list = []
+ for period in period_list:
+ if period == filters.get('company'):
+ filtered_summary_list.append(period)
+
+ return filtered_summary_list
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js
index fb0d359..84f7868 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.js
+++ b/erpnext/accounts/report/general_ledger/general_ledger.js
@@ -166,6 +166,11 @@
"fieldname": "show_cancelled_entries",
"label": __("Show Cancelled Entries"),
"fieldtype": "Check"
+ },
+ {
+ "fieldname": "show_net_values_in_party_account",
+ "label": __("Show Net Values in Party Account"),
+ "fieldtype": "Check"
}
]
}
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index b5d7992..562df4f 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -344,6 +344,9 @@
consolidated_gle = OrderedDict()
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):
data[key].debit += flt(gle.debit)
data[key].credit += flt(gle.credit)
@@ -351,6 +354,24 @@
data[key].debit_in_account_currency += flt(gle.debit_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:
data[key].against_voucher += ', ' + gle.against_voucher
@@ -388,6 +409,12 @@
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):
balance, balance_in_account_currency = 0, 0
inv_details = get_supplier_invoice_details()
diff --git a/erpnext/accounts/report/pos_register/pos_register.py b/erpnext/accounts/report/pos_register/pos_register.py
index 52f7fe2..cfbd7fd 100644
--- a/erpnext/accounts/report/pos_register/pos_register.py
+++ b/erpnext/accounts/report/pos_register/pos_register.py
@@ -116,22 +116,19 @@
frappe.throw(_("Can not filter based on Payment Method, if grouped by Payment Method"))
def get_conditions(filters):
- conditions = "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s".format(
- company=filters.get("company"),
- from_date=filters.get("from_date"),
- to_date=filters.get("to_date"))
+ conditions = "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s"
if filters.get("pos_profile"):
- conditions += " AND pos_profile = %(pos_profile)s".format(pos_profile=filters.get("pos_profile"))
+ conditions += " AND pos_profile = %(pos_profile)s"
if filters.get("owner"):
- conditions += " AND owner = %(owner)s".format(owner=filters.get("owner"))
+ conditions += " AND owner = %(owner)s"
if filters.get("customer"):
- conditions += " AND customer = %(customer)s".format(customer=filters.get("customer"))
+ conditions += " AND customer = %(customer)s"
if filters.get("is_return"):
- conditions += " AND is_return = %(is_return)s".format(is_return=filters.get("is_return"))
+ conditions += " AND is_return = %(is_return)s"
if filters.get("mode_of_payment"):
conditions += """
diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py
index fe261b3..5d04824 100644
--- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py
+++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py
@@ -5,7 +5,8 @@
import frappe
from frappe import _
from frappe.utils import flt
-from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
+from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data,
+ get_filtered_list_for_consolidated_report)
def execute(filters=None):
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
@@ -33,13 +34,17 @@
chart = get_chart_data(filters, columns, income, expense, net_profit_loss)
currency = filters.presentation_currency or frappe.get_cached_value('Company', filters.company, "default_currency")
- report_summary = get_report_summary(period_list, filters.periodicity, income, expense, net_profit_loss, currency)
+ report_summary = get_report_summary(period_list, filters.periodicity, income, expense, net_profit_loss, currency, filters)
return columns, data, None, chart, report_summary
-def get_report_summary(period_list, periodicity, income, expense, net_profit_loss, currency, consolidated=False):
+def get_report_summary(period_list, periodicity, income, expense, net_profit_loss, currency, filters, consolidated=False):
net_income, net_expense, net_profit = 0.0, 0.0, 0.0
+ # from consolidated financial statement
+ if filters.get('accumulated_in_group_company'):
+ period_list = get_filtered_list_for_consolidated_report(filters, period_list)
+
for period in period_list:
key = period if consolidated else period.key
if income:
diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py
index 9de8d19..b020d0a 100644
--- a/erpnext/accounts/report/utils.py
+++ b/erpnext/accounts/report/utils.py
@@ -81,8 +81,7 @@
presentation_currency = currency_info['presentation_currency']
company_currency = currency_info['company_currency']
- pl_accounts = [d.name for d in frappe.get_list('Account',
- filters={'report_type': 'Profit and Loss', 'company': company})]
+ account_currencies = list(set(entry['account_currency'] for entry in gl_entries))
for entry in gl_entries:
account = entry['account']
@@ -92,10 +91,15 @@
credit_in_account_currency = flt(entry['credit_in_account_currency'])
account_currency = entry['account_currency']
- if account_currency != presentation_currency:
- value = debit or credit
+ if len(account_currencies) == 1 and account_currency == presentation_currency:
+ 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)
if entry.get('debit'):
@@ -104,13 +108,6 @@
if entry.get('credit'):
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)
return converted_gl_list
diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json
index fadb665..df68318 100644
--- a/erpnext/accounts/workspace/accounting/accounting.json
+++ b/erpnext/accounts/workspace/accounting/accounting.json
@@ -15,6 +15,7 @@
"hide_custom": 0,
"icon": "accounting",
"idx": 0,
+ "is_default": 0,
"is_standard": 1,
"label": "Accounting",
"links": [
@@ -444,6 +445,16 @@
"type": "Link"
},
{
+ "dependencies": "GL Entry",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "UAE VAT 201",
+ "link_to": "UAE VAT 201",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
"hidden": 0,
"is_query_report": 0,
"label": "Financial Statements",
@@ -615,9 +626,9 @@
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
- "label": "Bank Reconciliation",
- "link_to": "bank-reconciliation",
- "link_type": "Page",
+ "label": "Bank Reconciliation Tool",
+ "link_to": "Bank Reconciliation Tool",
+ "link_type": "DocType",
"onboard": 0,
"type": "Link"
},
@@ -632,26 +643,6 @@
"type": "Link"
},
{
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Bank Statement Transaction Entry",
- "link_to": "Bank Statement Transaction Entry",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Bank Statement Settings",
- "link_to": "Bank Statement Settings",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
"hidden": 0,
"is_query_report": 0,
"label": "Subscription Management",
@@ -1061,7 +1052,7 @@
"type": "Link"
}
],
- "modified": "2021-03-04 00:38:35.349024",
+ "modified": "2021-05-12 11:48:01.905144",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 9aff144..8799275 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -195,8 +195,7 @@
# If depreciation is already completed (for double declining balance)
if skip_row: continue
- depreciation_amount = self.get_depreciation_amount(value_after_depreciation,
- d.total_number_of_depreciations, d)
+ depreciation_amount = get_depreciation_amount(self, value_after_depreciation, d)
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
schedule_date = add_months(d.depreciation_start_date,
@@ -208,7 +207,7 @@
# For first row
if has_pro_rata and n==0:
- depreciation_amount, days, months = get_pro_rata_amt(d, depreciation_amount,
+ depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount,
self.available_for_use_date, d.depreciation_start_date)
# For first depr schedule date will be the start date
@@ -220,7 +219,7 @@
to_date = add_months(self.available_for_use_date,
n * cint(d.frequency_of_depreciation))
- depreciation_amount, days, months = get_pro_rata_amt(d,
+ depreciation_amount, days, months = self.get_pro_rata_amt(d,
depreciation_amount, schedule_date, to_date)
monthly_schedule_date = add_months(schedule_date, 1)
@@ -365,24 +364,6 @@
def get_value_after_depreciation(self, idx):
return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation)
- def get_depreciation_amount(self, depreciable_value, total_number_of_depreciations, row):
- precision = self.precision("gross_purchase_amount")
-
- if row.depreciation_method in ("Straight Line", "Manual"):
- depreciation_left = (cint(row.total_number_of_depreciations) - cint(self.number_of_depreciations_booked))
-
- if not depreciation_left:
- frappe.msgprint(_("All the depreciations has been booked"))
- depreciation_amount = flt(row.expected_value_after_useful_life)
- return depreciation_amount
-
- depreciation_amount = (flt(row.value_after_depreciation) -
- flt(row.expected_value_after_useful_life)) / depreciation_left
- else:
- depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100), precision)
-
- return depreciation_amount
-
def validate_expected_value_after_useful_life(self):
for row in self.get('finance_books'):
accumulated_depreciation_after_full_schedule = [d.accumulated_depreciation_amount
@@ -575,6 +556,13 @@
return 100 * (1 - flt(depreciation_rate, float_precision))
+ def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date):
+ days = date_diff(to_date, from_date)
+ months = month_diff(to_date, from_date)
+ total_days = get_total_days(to_date, row.frequency_of_depreciation)
+
+ return (depreciation_amount * flt(days)) / flt(total_days), days, months
+
def update_maintenance_status():
assets = frappe.get_all(
"Asset", filters={"docstatus": 1, "maintenance_required": 1}
@@ -758,15 +746,20 @@
def is_cwip_accounting_enabled(asset_category):
return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting"))
-def get_pro_rata_amt(row, depreciation_amount, from_date, to_date):
- days = date_diff(to_date, from_date)
- months = month_diff(to_date, from_date)
- total_days = get_total_days(to_date, row.frequency_of_depreciation)
-
- return (depreciation_amount * flt(days)) / flt(total_days), days, months
-
def get_total_days(date, frequency):
period_start_date = add_months(date,
cint(frequency) * -1)
return date_diff(date, period_start_date)
+
+@erpnext.allow_regional
+def get_depreciation_amount(asset, depreciable_value, row):
+ depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked)
+
+ if row.depreciation_method in ("Straight Line", "Manual"):
+ depreciation_amount = (flt(row.value_after_depreciation) -
+ flt(row.expected_value_after_useful_life)) / depreciation_left
+ else:
+ depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
+
+ return depreciation_amount
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index a0d7603..3cd4b80 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -78,7 +78,7 @@
})
doc.set_missing_values()
- self.assertEquals(doc.items[0].is_fixed_asset, 1)
+ self.assertEqual(doc.items[0].is_fixed_asset, 1)
def test_schedule_for_straight_line_method(self):
pr = make_purchase_receipt(item_code="Macbook Pro",
@@ -565,8 +565,8 @@
doc = make_invoice(pr.name)
- self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
-
+ self.assertEqual('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
+
def test_asset_cwip_toggling_cases(self):
cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting")
name = frappe.db.get_value("Asset Category Account", filters={"parent": "Computers"}, fieldname=["name"])
@@ -635,6 +635,45 @@
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc)
frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", cwip_acc)
+ def test_discounted_wdv_depreciation_rate_for_indian_region(self):
+ # set indian company
+ company_flag = frappe.flags.company
+ frappe.flags.company = "_Test Company"
+
+ pr = make_purchase_receipt(item_code="Macbook Pro",
+ qty=1, rate=8000.0, location="Test Location")
+
+ asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
+ asset = frappe.get_doc('Asset', asset_name)
+ asset.calculate_depreciation = 1
+ asset.available_for_use_date = '2030-06-12'
+ asset.purchase_date = '2030-01-01'
+ asset.append("finance_books", {
+ "expected_value_after_useful_life": 1000,
+ "depreciation_method": "Written Down Value",
+ "total_number_of_depreciations": 3,
+ "frequency_of_depreciation": 12,
+ "depreciation_start_date": "2030-12-31"
+ })
+ asset.save(ignore_permissions=True)
+
+ self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
+
+ expected_schedules = [
+ ["2030-12-31", 1106.85, 1106.85],
+ ["2031-12-31", 3446.58, 4553.43],
+ ["2032-12-31", 1723.29, 6276.72],
+ ["2033-06-12", 723.28, 7000.00]
+ ]
+
+ schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
+ for d in asset.get("schedules")]
+
+ self.assertEqual(schedules, expected_schedules)
+
+ # reset indian company
+ frappe.flags.company = company_flag
+
def create_asset_data():
if not frappe.db.exists("Asset Category", "Computers"):
create_asset_category()
diff --git a/erpnext/assets/doctype/asset_category/asset_category.js b/erpnext/assets/doctype/asset_category/asset_category.js
index 74963c2..51ce157 100644
--- a/erpnext/assets/doctype/asset_category/asset_category.js
+++ b/erpnext/assets/doctype/asset_category/asset_category.js
@@ -4,7 +4,7 @@
frappe.ui.form.on('Asset Category', {
onload: function(frm) {
frm.add_fetch('company_name', 'accumulated_depreciation_account', 'accumulated_depreciation_account');
- frm.add_fetch('company_name', 'depreciation_expense_account', 'accumulated_depreciation_account');
+ frm.add_fetch('company_name', 'depreciation_expense_account', 'depreciation_expense_account');
frm.set_query('fixed_asset_account', 'accounts', function(doc, cdt, cdn) {
var d = locals[cdt][cdn];
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json
index 248cb9a..630a1dc 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.json
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.json
@@ -13,6 +13,8 @@
"po_required",
"pr_required",
"maintain_same_rate",
+ "maintain_same_rate_action",
+ "role_to_override_stop_action",
"allow_multiple_items",
"subcontract",
"backflush_raw_materials_of_subcontract_based_on",
@@ -89,6 +91,23 @@
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
+ },
+ {
+ "default": "Stop",
+ "depends_on": "maintain_same_rate",
+ "description": "Configure the action to stop the transaction or just warn if the same rate is not maintained.",
+ "fieldname": "maintain_same_rate_action",
+ "fieldtype": "Select",
+ "label": "Action If Same Rate is Not Maintained",
+ "mandatory_depends_on": "maintain_same_rate",
+ "options": "Stop\nWarn"
+ },
+ {
+ "depends_on": "eval:doc.maintain_same_rate_action == 'Stop'",
+ "fieldname": "role_to_override_stop_action",
+ "fieldtype": "Link",
+ "label": "Role Allowed to Override Stop Action",
+ "options": "Role"
}
],
"icon": "fa fa-cog",
@@ -96,7 +115,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-03-02 17:34:04.190677",
+ "modified": "2021-04-04 20:01:44.087066",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 3c4f908..aaa98f2 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -187,7 +187,7 @@
update_child_qty_rate('Purchase Order', trans_item, po.name)
po.reload()
- self.assertEquals(len(po.get('items')), 2)
+ self.assertEqual(len(po.get('items')), 2)
self.assertEqual(po.status, 'To Receive and Bill')
# ordered qty should increase on row addition
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 7)
@@ -234,7 +234,7 @@
update_child_qty_rate('Purchase Order', trans_item, po.name)
po.reload()
- self.assertEquals(len(po.get('items')), 1)
+ self.assertEqual(len(po.get('items')), 1)
self.assertEqual(po.status, 'To Receive and Bill')
# ordered qty should decrease (back to initial) on row deletion
@@ -435,6 +435,35 @@
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 5)
+ def test_purchase_order_invoice_receipt_workflow(self):
+ from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_purchase_receipt
+
+ po = create_purchase_order()
+ pi = make_pi_from_po(po.name)
+
+ pi.submit()
+
+ pr = make_purchase_receipt(pi.name)
+ pr.submit()
+
+ pi.load_from_db()
+
+ self.assertEqual(pi.per_received, 100.00)
+ self.assertEqual(pi.items[0].qty, pi.items[0].received_qty)
+
+ po.load_from_db()
+
+ self.assertEqual(po.per_received, 100.00)
+ self.assertEqual(po.per_billed, 100.00)
+
+ pr.cancel()
+
+ pi.load_from_db()
+ pi.cancel()
+
+ po.load_from_db()
+ po.cancel()
+
def test_make_purchase_invoice(self):
po = create_purchase_order(do_not_submit=True)
@@ -645,8 +674,8 @@
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname=["reserved_qty_for_sub_contract", "projected_qty"], as_dict=1)
- self.assertEquals(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
- self.assertEquals(bin2.projected_qty, bin1.projected_qty - 10)
+ self.assertEqual(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
+ self.assertEqual(bin2.projected_qty, bin1.projected_qty - 10)
# Create stock transfer
rm_item = [{"item_code":"_Test FG Item","rm_item_code":"_Test Item","item_name":"_Test Item",
@@ -661,7 +690,7 @@
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
- self.assertEquals(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
+ self.assertEqual(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
# close PO
po.update_status("Closed")
@@ -669,7 +698,7 @@
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
- self.assertEquals(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
+ self.assertEqual(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
# Re-open PO
po.update_status("Submitted")
@@ -677,7 +706,7 @@
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
- self.assertEquals(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
+ self.assertEqual(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
make_stock_entry(target="_Test Warehouse 1 - _TC", item_code="_Test Item",
qty=40, basic_rate=100)
@@ -694,7 +723,7 @@
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
- self.assertEquals(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
+ self.assertEqual(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
# Cancel PR
pr.cancel()
@@ -702,7 +731,7 @@
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
- self.assertEquals(bin7.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
+ self.assertEqual(bin7.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
# Make Purchase Invoice
pi = make_pi_from_po(po.name)
@@ -714,7 +743,7 @@
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
- self.assertEquals(bin8.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
+ self.assertEqual(bin8.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
# Cancel PR
pi.cancel()
@@ -722,7 +751,7 @@
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
- self.assertEquals(bin9.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
+ self.assertEqual(bin9.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
# Cancel Stock Entry
se.cancel()
@@ -730,7 +759,7 @@
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
- self.assertEquals(bin10.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
+ self.assertEqual(bin10.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
# Cancel PO
po.reload()
@@ -739,7 +768,7 @@
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
- self.assertEquals(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):
item_code = "_Test Subcontracted FG Item 1"
@@ -753,7 +782,7 @@
exploded_items = sorted([d.item_code for d in bom.exploded_items if not d.get('sourced_by_supplier')])
supplied_items = sorted([d.rm_item_code for d in po.supplied_items])
- self.assertEquals(exploded_items, supplied_items)
+ self.assertEqual(exploded_items, supplied_items)
po1 = create_purchase_order(item_code=item_code, qty=1,
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=0)
@@ -761,7 +790,7 @@
supplied_items1 = sorted([d.rm_item_code for d in po1.supplied_items])
bom_items = sorted([d.item_code for d in bom.items if not d.get('sourced_by_supplier')])
- self.assertEquals(supplied_items1, bom_items)
+ self.assertEqual(supplied_items1, bom_items)
def test_backflush_based_on_stock_entry(self):
item_code = "_Test Subcontracted FG Item 1"
@@ -811,8 +840,8 @@
transferred_items = sorted([d.item_code for d in se.get('items') if se.purchase_order == po.name])
issued_items = sorted([d.rm_item_code for d in pr.get('supplied_items')])
- self.assertEquals(transferred_items, issued_items)
- self.assertEquals(pr.get('items')[0].rm_supp_cost, 2000)
+ self.assertEqual(transferred_items, issued_items)
+ self.assertEqual(pr.get('items')[0].rm_supp_cost, 2000)
transferred_rm_map = frappe._dict()
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
index 5baf693..1dbd7c6 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -56,6 +56,8 @@
"base_net_amount",
"warehouse_and_reference",
"warehouse",
+ "actual_qty",
+ "company_total_stock",
"material_request",
"material_request_item",
"sales_order",
@@ -744,6 +746,22 @@
"read_only": 1
},
{
+ "allow_on_submit": 1,
+ "fieldname": "actual_qty",
+ "fieldtype": "Float",
+ "label": "Available Qty at Warehouse",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "company_total_stock",
+ "fieldtype": "Float",
+ "label": "Available Qty at Company",
+ "no_copy": 1,
+ "read_only": 1
+ },
+ {
"collapsible": 1,
"fieldname": "discount_and_margin_section",
"fieldtype": "Section Break",
@@ -791,7 +809,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-02-23 01:00:27.132705",
+ "modified": "2021-03-22 11:46:12.357435",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index b530d1a..180ba93 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -62,6 +62,7 @@
for supplier in self.suppliers:
supplier.email_sent = 0
supplier.quote_status = 'Pending'
+ self.send_to_supplier()
def on_cancel(self):
frappe.db.set(self, 'status', 'Cancelled')
@@ -81,7 +82,7 @@
def send_to_supplier(self):
"""Sends RFQ mail to involved 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)
# make new user if required
diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json
index 4cc5753..38b8dfd 100644
--- a/erpnext/buying/doctype/supplier/supplier.json
+++ b/erpnext/buying/doctype/supplier/supplier.json
@@ -383,8 +383,14 @@
"icon": "fa fa-user",
"idx": 370,
"image_field": "image",
- "links": [],
- "modified": "2021-01-06 19:51:40.939087",
+ "links": [
+ {
+ "group": "Item Group",
+ "link_doctype": "Supplier Item Group",
+ "link_fieldname": "supplier"
+ }
+ ],
+ "modified": "2021-05-18 15:10:11.087191",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier",
diff --git a/erpnext/buying/doctype/supplier_item_group/__init__.py b/erpnext/buying/doctype/supplier_item_group/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/buying/doctype/supplier_item_group/__init__.py
diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.js b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.js
new file mode 100644
index 0000000..f7da90d
--- /dev/null
+++ b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Supplier Item Group', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json
new file mode 100644
index 0000000..1971458
--- /dev/null
+++ b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json
@@ -0,0 +1,77 @@
+{
+ "actions": [],
+ "creation": "2021-05-07 18:16:40.621421",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "supplier",
+ "item_group"
+ ],
+ "fields": [
+ {
+ "fieldname": "supplier",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Supplier",
+ "options": "Supplier",
+ "reqd": 1
+ },
+ {
+ "fieldname": "item_group",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Item Group",
+ "options": "Item Group",
+ "reqd": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-05-19 13:48:16.742303",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Supplier Item Group",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Purchase User",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Purchase Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py
new file mode 100644
index 0000000..3a2e5d6
--- /dev/null
+++ b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.model.document import Document
+
+class SupplierItemGroup(Document):
+ def validate(self):
+ exists = frappe.db.exists({
+ 'doctype': 'Supplier Item Group',
+ 'supplier': self.supplier,
+ 'item_group': self.item_group
+ })
+ if exists:
+ frappe.throw(_("Item Group has already been linked to this supplier."))
\ No newline at end of file
diff --git a/erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py b/erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py
new file mode 100644
index 0000000..c75044d
--- /dev/null
+++ b/erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestSupplierItemGroup(unittest.TestCase):
+ pass
diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
index 6900938..c1fc6fb 100644
--- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
+++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
@@ -9,12 +9,12 @@
from erpnext.buying.report.subcontracted_raw_materials_to_be_transferred.subcontracted_raw_materials_to_be_transferred import execute
import json, frappe, unittest
-class TestSubcontractedItemToBeReceived(unittest.TestCase):
+class TestSubcontractedItemToBeTransferred(unittest.TestCase):
- def test_pending_and_received_qty(self):
+ def test_pending_and_transferred_qty(self):
po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes')
- 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', qty=100, basic_rate=100)
+ make_stock_entry(item_code='_Test Item', target='_Test Warehouse - _TC', qty=100, basic_rate=100)
+ make_stock_entry(item_code='_Test Item Home Desktop 100', target='_Test Warehouse - _TC', qty=100, basic_rate=100)
transfer_subcontracted_raw_materials(po.name)
col, data = execute(filters=frappe._dict({'supplier': po.supplier,
'from_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=-10)),
@@ -38,7 +38,8 @@
'warehouse': '_Test Warehouse - _TC', 'rate': 100, 'amount': 200, 'stock_uom': 'Nos'}]
rm_item_string = json.dumps(rm_item)
se = frappe.get_doc(make_rm_stock_entry(po, rm_item_string))
+ se.from_warehouse = '_Test Warehouse 1 - _TC'
se.to_warehouse = '_Test Warehouse 1 - _TC'
se.stock_entry_type = 'Send to Subcontractor'
se.save()
- se.submit()
\ No newline at end of file
+ se.submit()
diff --git a/erpnext/change_log/v13/v13.0.2.md b/erpnext/change_log/v13/v13.0.2.md
new file mode 100644
index 0000000..2bfbdfc
--- /dev/null
+++ b/erpnext/change_log/v13/v13.0.2.md
@@ -0,0 +1,7 @@
+## Version 13.0.2 Release Notes
+
+### Fixes
+- fix: frappe.whitelist for doc methods ([#25231](https://github.com/frappe/erpnext/pull/25231))
+- fix: incorrect incoming rate for the sales return ([#25306](https://github.com/frappe/erpnext/pull/25306))
+- fix(e-invoicing): validations & tax calculation fixes ([#25314](https://github.com/frappe/erpnext/pull/25314))
+- fix: update scheduler check time ([#25295](https://github.com/frappe/erpnext/pull/25295))
\ No newline at end of file
diff --git a/erpnext/change_log/v13/v13_0_0.md b/erpnext/change_log/v13/v13_0_0.md
new file mode 100644
index 0000000..a6cebab
--- /dev/null
+++ b/erpnext/change_log/v13/v13_0_0.md
@@ -0,0 +1,471 @@
+# Version 13.0.0 Release Notes
+
+### Accounting
+- [New and refreshed POS](https://github.com/frappe/erpnext/pull/20789)
+- [GST E-invoicing for India](https://docs.erpnext.com/docs/user/manual/en/regional/india/setup-e-invoicing)
+- [Distributed Cost Center](https://docs.erpnext.com/docs/user/manual/en/accounts/distributed-cost-center)
+- [Process Bulk Statement Of Accounts](https://docs.erpnext.com/docs/user/manual/en/accounts/process-statement-of-accounts)
+- [More controlled deferred revenue booking](https://docs.erpnext.com/docs/user/manual/en/accounts/process-deferred-accounting)
+- [Dunning](https://docs.erpnext.com/docs/user/manual/en/accounts/dunning)
+- [Journal Entry Template](https://docs.erpnext.com/docs/user/manual/en/accounts/journal-entry-template)
+- [POS Register report](https://github.com/frappe/erpnext/pull/23313)
+- [UAE VAT 201 Report](https://github.com/frappe/erpnext/pull/23447)
+
+
+### Loan Management
+- [Loan Application](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-application)
+- [Loan](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan)
+- [Loan Security Pledge](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-security-pledge)
+- [Loan Disbursement](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-disbursement)
+- [Loan Repayment](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-repayment)
+- [Loan Interest Accrual](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-interest-accrual)
+- [Loan Write Off](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-write-off)
+
+### Healthcare
+- [Refactored Healthcare Module](https://docs.erpnext.com/docs/user/manual/en/healthcare)
+- [Rehabilitation Module](https://docs.erpnext.com/docs/user/manual/en/healthcare/exercise_type)
+- [Laboratory Module](https://docs.erpnext.com/docs/user/manual/en/healthcare/setup_laboratory)
+- [Patient Progress Page](https://github.com/frappe/erpnext/pull/22474)
+- [Inpatient Medication Order and Entry](https://docs.erpnext.com/docs/user/manual/en/healthcare/inpatient_medication_entry)
+- [Therapy Plan Template](https://docs.erpnext.com/docs/user/manual/en/healthcare/therapy_plan)
+- [Multi company support in Healthcare](https://github.com/frappe/erpnext/pull/21290)
+- [Inpatient Medication Orders Script Report](https://github.com/frappe/erpnext/pull/23984)
+- [Patient History Enhancements](https://github.com/frappe/erpnext/pull/24033)
+
+
+### Stock
+- [Putaway](https://docs.erpnext.com/docs/user/manual/en/stock/putaway-rule)
+- [More accurate stock valuation in case of back-dated stock transactions](https://github.com/frappe/erpnext/pull/24183)
+- [Repost item costing via background job](https://github.com/frappe/erpnext/pull/24183)
+- [Item valuation for internal stock transfers](https://github.com/frappe/erpnext/pull/24200)
+- [Multi currency in Landed Cost Voucher](https://github.com/frappe/erpnext/pull/24127)
+- [Formula based Quality Inspection](https://docs.erpnext.com/docs/user/manual/en/stock/quality-inspection)
+- [Value Based and Numeric Quality Inspection](https://github.com/frappe/erpnext/pull/24181)
+- [Shipment](https://github.com/frappe/erpnext/pull/22914)
+- [Return tracking in PR/DN](https://github.com/frappe/erpnext/pull/22859)
+
+### Manufacturing
+- [Production forecasting using Exponential Smoothing method](https://docs.erpnext.com/docs/user/manual/en/manufacturing/reports/demand-driven-forecasting)
+- [BOM Template](https://docs.erpnext.com/docs/user/manual/en/manufacturing/bill-of-materials#34-bom-template)
+- [Downtime Entry](https://docs.erpnext.com/docs/user/manual/en/manufacturing/downtime-entry)
+- [Quality Inspection on Job Card](https://github.com/frappe/erpnext/pull/23964)
+- New Reports
+ - Production Planning Report ([#21763](https://github.com/frappe/erpnext/pull/21763))
+ - BOM Operations Time ([#21763](https://github.com/frappe/erpnext/pull/21763))
+ - Work Order Summary ([#21430](https://github.com/frappe/erpnext/pull/21430))
+ - Job card Summary ([#21430](https://github.com/frappe/erpnext/pull/21430))
+ - Downtime Analysis ([#21430](https://github.com/frappe/erpnext/pull/21430))
+ - Quality Inspection ([#21430](https://github.com/frappe/erpnext/pull/21430))
+
+### HR
+- [Leave policy assignment](https://github.com/frappe/erpnext/pull/23112)
+- [In and Out time in attendance](https://github.com/frappe/erpnext/pull/21547)
+- [Shift management](https://docs.erpnext.com/docs/user/manual/en/human-resources/shift-management)
+- [Recruitment analytics](https://github.com/frappe/erpnext/pull/21732)
+- [Bulk Mark Attendance](https://github.com/frappe/erpnext/pull/20062)
+- [Leave type with partial payment](https://github.com/frappe/erpnext/pull/23173)
+- New and enhanced reports
+ - Employee Analytics ([#21705](https://github.com/frappe/erpnext/pull/21705))
+ - Employee Leave Balance ([#20754](https://github.com/frappe/erpnext/pull/20754))
+ - Employee Leave Balance Summary ([#20754](https://github.com/frappe/erpnext/pull/20754))
+
+### Payroll
+- [Multi-currency payroll](https://github.com/frappe/erpnext/pull/23519)
+- [Payroll based on attendance](https://github.com/frappe/erpnext/pull/21258)
+- [Payroll based on employee cost center](https://github.com/frappe/erpnext/pull/21609)
+- [Recurring Additional Salary](https://github.com/frappe/erpnext/pull/20936)
+- [Compute Year to Date for Salary Slip components](https://github.com/frappe/erpnext/pull/24362)
+- New Reports
+ - Income Tax Deductions
+ - Professional Tax Deductions
+ - Provident Fund Deductions
+ - Total Salary Payments Based on Payment Mode
+ - Salary Payments via ECS
+
+### CRM
+- [Social Media Post](https://docs.erpnext.com/docs/user/manual/en/CRM/social-media-post)
+- [Make Quotation against Blanket Order](https://docs.erpnext.com/docs/user/manual/en/selling/blanket-order)
+- [Calendar View for Opportunity](https://github.com/frappe/erpnext/pull/21280)
+
+### Selling
+- [Batch wise item pricing](https://github.com/frappe/erpnext/pull/24470)
+- [Refreshed shopping cart](https://github.com/frappe/erpnext/pull/22617)
+- [Territory-wise Sales Report](https://github.com/frappe/erpnext/pull/20428)
+
+#### Buying
+- [Multi UOM support in Request for Quotation](https://github.com/frappe/erpnext/pull/22249)
+- [Provision to make RFQ against Opportunity](https://github.com/frappe/erpnext/pull/22765)
+- [Item Rate in Stock UOM in purchase cycle](https://github.com/frappe/erpnext/pull/24315)
+- New Reports
+ - Requested Items To Order ([#21611](https://github.com/frappe/erpnext/pull/21611))
+ - Purchase Order Analysis ([#21611](https://github.com/frappe/erpnext/pull/21611))
+ - Supplier Quotation Comparison report ([#23323](https://github.com/frappe/erpnext/pull/23323))
+
+### Project
+- [Project template with dependent tasks](https://github.com/frappe/erpnext/pull/24092)
+- [Project Summary Report](https://github.com/frappe/erpnext/pull/21587)
+
+### Support
+- [Help Articles on support portal](https://github.com/frappe/erpnext/pull/22194)
+- [Issue Metrics and SLA Enhancements](https://github.com/frappe/erpnext/pull/21617)
+- [Issue Summary Script Report](https://docs.erpnext.com/docs/user/manual/en/support/support_reports)
+- [Issue Analytics Script Report](https://docs.erpnext.com/docs/user/manual/en/support/support_reports)
+
+### Non-Profits
+- [80G Certificates and Donations](https://docs.erpnext.com/docs/user/manual/en/non_profit/tax_exemption_80g_certificate)
+
+#### Integrations
+- [Woocommerce Integration](https://docs.erpnext.com/docs/user/manual/en/erpnext_integration/woocommerce_integration)
+- [Taxjar Integration](https://github.com/frappe/erpnext/pull/21047)
+- [M-pesa Integration](https://docs.erpnext.com/docs/user/manual/en/erpnext_integration/mpesa-integration)
+- [Telephony feature using Twillio](https://github.com/frappe/erpnext/pull/24032)
+- [Voice Call Settings](https://github.com/frappe/erpnext/pull/24126)
+
+
+#### Other Enhancements and Fixes
+- Accounting Dimensions in Budget Variance Report ([#19973](https://github.com/frappe/erpnext/pull/19973))
+- "Sync Now" option in Plaid Settings ([#23602](https://github.com/frappe/erpnext/pull/23602))
+- Custom Fields in POS ([#19876](https://github.com/frappe/erpnext/pull/19876))
+- [Inter Warehouse Stock Transfer in Purchase Receipt](https://docs.erpnext.com/docs/user/manual/en/stock/articles/material-transfer-from-delivery-note)
+- [Accounts Payable Report based on Payment Terms](https://docs.erpnext.com/docs/user/manual/en/accounts/accounting-reports)
+- Configurable accounting dimension filters and validations ([#23912](https://github.com/frappe/erpnext/pull/23912))
+- Territory tree in Customer Acquisition and Loyalty report ([#21668](https://github.com/frappe/erpnext/pull/21668))
+- Allow Purchase Invoice Creation Without Purchase Order Checkbox in Supplier ([#20864](https://github.com/frappe/erpnext/pull/20864))
+- Gross Profit In Quotation ([#21795](https://github.com/frappe/erpnext/pull/21795))
+- Notify credit controller users for credit limit extension via Email ([#22213](https://github.com/frappe/erpnext/pull/22213))
+- Run MRP at parent level in the production plan and make material transfer based upon materials availability ([#21545](https://github.com/frappe/erpnext/pull/21545))
+- Balance Serial Nos in Stock Ledger report ([#23675](https://github.com/frappe/erpnext/pull/23675))
+- Youtube interactions via Video ([#22867](https://github.com/frappe/erpnext/pull/22867))
+- Consider Holiday List in Student Leave Application and Attendance ([#23388](https://github.com/frappe/erpnext/pull/23388))
+- Patient appointment status changes ([#24201](https://github.com/frappe/erpnext/pull/24201))
+- Sales order status filter added for production plan ([#23805](https://github.com/frappe/erpnext/pull/23805))
+- Monthly attendance sheet report group by Department, Designation, Employee Grade and Branch ([#21331](https://github.com/frappe/erpnext/pull/21331))
+- Upload Attendance template now have pre-filled holiday status ([#20947](https://github.com/frappe/erpnext/pull/20947))
+- Provision to disable serial no and batch selector ([#24398](https://github.com/frappe/erpnext/pull/24398))
+
+<details>
+<summary>More</summary>
+
+- Fetch Items from BOM in Stock Entry([#19498](https://github.com/frappe/erpnext/pull/19498))
+- Supplier Sourced Items in BOM ([#23557](https://github.com/frappe/erpnext/pull/23557))
+- Close Production Plan ([#23728](https://github.com/frappe/erpnext/pull/23728))
+- Button to create Stock Entry for Drug Shortage ([#24012](https://github.com/frappe/erpnext/pull/24012))
+- Added column cost center in Accounts Receivable report ([#23835](https://github.com/frappe/erpnext/pull/23835))
+- Added jinja templating in Contract Template ([#24046](https://github.com/frappe/erpnext/pull/24046))
+- Make account number length configurable ([#23845](https://github.com/frappe/erpnext/pull/23845))
+- Add company and correct filter in bank reconciliation statement ([#23614](https://github.com/frappe/erpnext/pull/23614))
+- Added Condition field in Pricing Rule ([#23014](https://github.com/frappe/erpnext/pull/23014))
+- Open lead status on next contact date ([#23445](https://github.com/frappe/erpnext/pull/23445))
+- [Tax Category in POS Profile](https://docs.erpnext.com/docs/user/manual/en/accounts/pos-profile)
+- Added phone field in product Inquiry ([#23170](https://github.com/frappe/erpnext/pull/23170))
+- Allow Discharge despite Unbilled Healthcare Services ([#24281](https://github.com/frappe/erpnext/pull/24281))
+- Do Not Bill Patient Encounters for Inpatients ([#24355](https://github.com/frappe/erpnext/pull/24355))
+- Autofill Supplier pop-up when only 1 Supplier in RFQ ([#22512](https://github.com/frappe/erpnext/pull/22512))
+- Accounting entries for service item in Purchase receipt ([#22223](https://github.com/frappe/erpnext/pull/22223))
+- Added Project in Sales Analytics report ([#23309](https://github.com/frappe/erpnext/pull/23309))
+- Added all companies option in employee tree to view employee across all companies ([#22573](https://github.com/frappe/erpnext/pull/22573))
+- Email Group Option In Email Campaign ([#22731](https://github.com/frappe/erpnext/pull/22731))
+- Stock Report Enhancements ([#21727](https://github.com/frappe/erpnext/pull/21727))
+- Added range for age in stock ageing ([#22622](https://github.com/frappe/erpnext/pull/22622))
+- Report Summary in Financial Statement([#20876](https://github.com/frappe/erpnext/pull/20876))
+- Added sequence id in routing for the completion of operations sequentially ([#23641](https://github.com/frappe/erpnext/pull/23641))
+- Nested Set filtering for Accounting Dimension
+- Add/Remove Items from submitted Sales/Purchase Order
+- Provision to edit Item Details from Marketplace
+- Scan Barcode in Purchase Receipt
+- Disable Rounded Totals Checkbox for Salary Slips in HR Settings
+
+- Renamed Loan Management to Loan on Desk Page ([#21877](https://github.com/frappe/erpnext/pull/21877))
+- Added Expense Approver field in Employee master ([#22244](https://github.com/frappe/erpnext/pull/22244))
+- Bill all hours by default on Timesheet ([#22155](https://github.com/frappe/erpnext/pull/22155))
+- Unable to cancel employee advance ([#22374](https://github.com/frappe/erpnext/pull/22374))
+- Status error in purchase invoice ([#22351](https://github.com/frappe/erpnext/pull/22351))
+- Item-wise sales and purchase register export ([#22184](https://github.com/frappe/erpnext/pull/22184))
+- Billing address in for Purchase documents ([#22233](https://github.com/frappe/erpnext/pull/22233))
+- Handle canceled entries in financial statements ([#22231](https://github.com/frappe/erpnext/pull/22231))
+- Default period start date and period end date for financial statements ([#22011](https://github.com/frappe/erpnext/pull/22011))
+- Update Packed Items via Update Items in Sales Order ([#22392](https://github.com/frappe/erpnext/pull/22392))
+- Hide delete company transactions button if not system manager ([#21839](https://github.com/frappe/erpnext/pull/21839))
+- Skipping total row for tree-view reports ([#22350](https://github.com/frappe/erpnext/pull/22350))
+- Cancelled entries in tds payable monthly report ([#22131](https://github.com/frappe/erpnext/pull/22131))
+- Inter-company Invoice currency for multicurrency transactions ([#21984](https://github.com/frappe/erpnext/pull/21984))
+- Filter batches based on item and warehouse in Pick List (develop) ([#21780](https://github.com/frappe/erpnext/pull/21780))
+- Set cost center in Expense Claim child based on parent (if missing) ([#22175](https://github.com/frappe/erpnext/pull/22175))
+- Item wise backdated stock entry posting for immutable ledger ([#22366](https://github.com/frappe/erpnext/pull/22366))
+- Shopping cart UI fixes ([#22137](https://github.com/frappe/erpnext/pull/22137))
+- Filter Leave Type based on allocation for a particular employee ([#22050](https://github.com/frappe/erpnext/pull/22050))
+- Party validation for inter-warehouse transaction ([#22186](https://github.com/frappe/erpnext/pull/22186))
+- Manufacturing dashboard and work order summary chart ([#21946](https://github.com/frappe/erpnext/pull/21946))
+- IP Admission and Discharge, Minor fixes ([#21817](https://github.com/frappe/erpnext/pull/21817))
+- Validation of Purchase Order against Material Request missing ([#22192](https://github.com/frappe/erpnext/pull/22192))
+- Staffing Plan validation ([#22379](https://github.com/frappe/erpnext/pull/22379))
+- Do not allow backdated stock transactions in previous fiscal year ([#21967](https://github.com/frappe/erpnext/pull/21967))
+- Employee Advance Return not working ([#21812](https://github.com/frappe/erpnext/pull/21812))
+- Added card for reports on education desk ([#21853](https://github.com/frappe/erpnext/pull/21853))
+- Refactored project summary report ([#21943](https://github.com/frappe/erpnext/pull/21943))
+- Revenue and Customer Count only in date range in Customer Acquitition Report ([#22210](https://github.com/frappe/erpnext/pull/22210))
+- Alternative item not working for subcontract ([#22386](https://github.com/frappe/erpnext/pull/22386))
+- Unable to create batched Item ([#22393](https://github.com/frappe/erpnext/pull/22393))
+- Filters for the manufacturing reports ([#21960](https://github.com/frappe/erpnext/pull/21960))
+- Raw material warehouse in Production Planning Report ([#21982](https://github.com/frappe/erpnext/pull/21982))
+- Allowed LWP leave types to select in Leave Application even if there is no allocation against them ([#22197](https://github.com/frappe/erpnext/pull/22197))
+- Report not working on parameter Grade ([#21951](https://github.com/frappe/erpnext/pull/21951))
+- Allow to enter Relieving date if employee status is Left ([#22242](https://github.com/frappe/erpnext/pull/22242))
+- Resetting lost reason in opportunity and quotation ([#22378](https://github.com/frappe/erpnext/pull/22378))
+- Filtering issues in opening invoice creation tool ([#21969](https://github.com/frappe/erpnext/pull/21969))
+- Set default reference Id for "On Previous Row Amount" and "On Previous Row Total" ([#22346](https://github.com/frappe/erpnext/pull/22346))
+- UX date range field separated in from and to date fields. ([#21765](https://github.com/frappe/erpnext/pull/21765))
+- Enable show_configure_button when shopping cart is enabled ([#22468](https://github.com/frappe/erpnext/pull/22468))
+- Setup status indicators for Job Offer and Job Applicant (develop) ([#22445](https://github.com/frappe/erpnext/pull/22445))
+- Item-wise sales history report ([#22783](https://github.com/frappe/erpnext/pull/22783))
+- Setting filter for project in kanban board ([#22717](https://github.com/frappe/erpnext/pull/22717))
+- Dashboard For Timesheet ([#22750](https://github.com/frappe/erpnext/pull/22750))
+- Handle custom statuses for the pause SLA configuration ([#22349](https://github.com/frappe/erpnext/pull/22349))
+- Quality Feedback and Template ([#22571](https://github.com/frappe/erpnext/pull/22571))
+- Unable to change link from new lead to existing customer ([#22787](https://github.com/frappe/erpnext/pull/22787))
+- Move Issue List actions under 'Actions' dropdown (ux) ([#22710](https://github.com/frappe/erpnext/pull/22710))
+- Cost center should only show option of selected company ([#22598](https://github.com/frappe/erpnext/pull/22598))
+- Serial No Rename does not affect Stock Ledger Entry ([#22746](https://github.com/frappe/erpnext/pull/22746))
+- Descriptions not copied while creating Fees from Fee Structure ([#22792](https://github.com/frappe/erpnext/pull/22792))
+- Company filter for cost_center and expense_account in all sales and purchase transactions ([#22478](https://github.com/frappe/erpnext/pull/22478))
+- Arrangements of filters for reports accounts payable & receivable ([#22636](https://github.com/frappe/erpnext/pull/22636))
+- Update the project after task deletion so that the % completed shows correct value ([#22591](https://github.com/frappe/erpnext/pull/22591))
+- Block Invalid Serial No updates in Maintenance Schedule ([#22665](https://github.com/frappe/erpnext/pull/22665))
+- Fetch item price in sales invoice based on it's validity ([#22563](https://github.com/frappe/erpnext/pull/22563))
+- Add view ledger button for cancelled docs ([#22432](https://github.com/frappe/erpnext/pull/22432))
+- Allow creating SLA documents even if SLA tracking is not enabled ([#22608](https://github.com/frappe/erpnext/pull/22608))
+- Quotation list view blank if quotation_to field not set as a standard filter ([#22672](https://github.com/frappe/erpnext/pull/22672))
+- Salary deductions report fixes ([#22397](https://github.com/frappe/erpnext/pull/22397))
+22727))
+- Incorrect delivered qty in Supplier-Wise Sales Analytics ([#22631](https://github.com/frappe/erpnext/pull/22631))
+- Moved parent warehouse to top section also added a section break ([#22708](https://github.com/frappe/erpnext/pull/22708))
+- Skip Progress and Completed by fields on Task Duplication ([#22565](https://github.com/frappe/erpnext/pull/22565))
+- Incorrect stock after merging the items ([#22526](https://github.com/frappe/erpnext/pull/22526))
+- Letter head not found in opening invoice creation tool ([#22488](https://github.com/frappe/erpnext/pull/22488))
+- Cannot cancel asset and asset movement ([#22441](https://github.com/frappe/erpnext/pull/22441))
+- Fetch project-related info in Timesheet ([#22423](https://github.com/frappe/erpnext/pull/22423))
+- Currency symbol not showing as per company currency in stock balance report ([#22724](https://github.com/frappe/erpnext/pull/22724))
+- Add default cost center in payment reconciliation JV ([#22614](https://github.com/frappe/erpnext/pull/22614))
+- Stock Reconciliation Invalid Quantity for Batched Item ([#22726](https://github.com/frappe/erpnext/pull/22726))
+- Project link not set in accounts other than profit and loss accounts ([#22051](https://github.com/frappe/erpnext/pull/22051))
+- Buying price for non stock item in gross profit report ([#22616](https://github.com/frappe/erpnext/pull/22616))
+- Multi currency payment reconciliation ([#22738](https://github.com/frappe/erpnext/pull/22738))
+- Cannot cancel assets with repair pending ([#22440](https://github.com/frappe/erpnext/pull/22440))
+- Reset homepage to home after unchecking products page ([#22736](https://github.com/frappe/erpnext/pull/22736))
+- Generic Message in previous doc validation for buying and selling ([#22546](https://github.com/frappe/erpnext/pull/22546))
+- Expense claim outstanding while making payment entry ([#22735](https://github.com/frappe/erpnext/pull/22735))
+- Take parent cost center for child if no cost center at child in expense claim ([#22496](https://github.com/frappe/erpnext/pull/22496))
+- Consider company fiscal year for getting balance ([#22577](https://github.com/frappe/erpnext/pull/22577))
+- Pick List empty table and Serial-Batch items handling ([#22426](https://github.com/frappe/erpnext/pull/22426))
+- Show total row in print format of financial statement ([#22693](https://github.com/frappe/erpnext/pull/22693))
+- Set Root as Parent if no parent in new tree view node ([#22497](https://github.com/frappe/erpnext/pull/22497))
+- Multiple pos issues ([#23725](https://github.com/frappe/erpnext/pull/23725))
+- Calculate taxes if tax is based on item quantity and inclusive on item price ([#23001](https://github.com/frappe/erpnext/pull/23001))
+- Contact us button not visible in the website for the non variant items ([#23217](https://github.com/frappe/erpnext/pull/23217))
+- Not able to make Material Request from Sales Order ([#23669](https://github.com/frappe/erpnext/pull/23669))
+- Capture advance payments in payment order ([#23256](https://github.com/frappe/erpnext/pull/23256))
+- Program and Course Enrollment fixes ([#23333](https://github.com/frappe/erpnext/pull/23333))
+- Cannot create asset if cwip disabled and account not set ([#23580](https://github.com/frappe/erpnext/pull/23580))
+- Cannot merge pos invoices with inclusive tax ([#23541](https://github.com/frappe/erpnext/pull/23541))
+- Do not allow Company as accounting dimension ([#23755](https://github.com/frappe/erpnext/pull/23755))
+- Set value of wrong Bank Account field in Payment Entry ([#22302](https://github.com/frappe/erpnext/pull/22302))
+- Reverse journal entry for multi-currency ([#23165](https://github.com/frappe/erpnext/pull/23165))
+- Updated integrations desk page ([#23772](https://github.com/frappe/erpnext/pull/23772))
+- Assessment Result child table not visible when accessed via Assessment Plan dashboard ([#22880](https://github.com/frappe/erpnext/pull/22880))
+- Conversion factor fixes in Stock Entry ([#23407](https://github.com/frappe/erpnext/pull/23407))
+- Total calculations for multi-currency RCM invoices ([#23072](https://github.com/frappe/erpnext/pull/23072))
+- Show accounts in financial statements upto level 20 ([#23718](https://github.com/frappe/erpnext/pull/23718))
+- Consolidated financial statement sums values into wrong parent ([#23288](https://github.com/frappe/erpnext/pull/23288))
+- Set SLA variance in seconds for Duration fieldtype ([#23765](https://github.com/frappe/erpnext/pull/23765))
+- Added missing reports on selling desk ([#23548](https://github.com/frappe/erpnext/pull/23548))
+- Fixed heading in the mobile view ([#23145](https://github.com/frappe/erpnext/pull/23145))
+- Misleading filters on Item tax Template Link field ([#22918](https://github.com/frappe/erpnext/pull/22918))
+- Do not consider opening entries for TDS calculation ([#23597](https://github.com/frappe/erpnext/pull/23597))
+- Attendance calendar map fix ([#23245](https://github.com/frappe/erpnext/pull/23245))
+- Post cancellation accounting entry on posting date instead of current ([#23361](https://github.com/frappe/erpnext/pull/23361))
+- Set Customer only if Contact is present ([#23704](https://github.com/frappe/erpnext/pull/23704))
+- Add Delivery Note Count in Sales Invoice Dashboard ([#23161](https://github.com/frappe/erpnext/pull/23161))
+- Breadcrumbs for Maintenance Visit and Schedule ([#23369](https://github.com/frappe/erpnext/pull/23369))
+- Raise Error on over receipt/consumption for sub-contracted PR ([#23195](https://github.com/frappe/erpnext/pull/23195))
+- Validate if company not set in the Payment Entry ([#23419](https://github.com/frappe/erpnext/pull/23419))
+- Ignore company and bank account doctype while deleting company transactions ([#22953](https://github.com/frappe/erpnext/pull/22953))
+- Sales funnel data is inconsistent ([#23110](https://github.com/frappe/erpnext/pull/23110))
+- Credit Limit Email not working ([#23059](https://github.com/frappe/erpnext/pull/23059))
+- Add Company in list fields to fetch for Expense Claim ([#23007](https://github.com/frappe/erpnext/pull/23007))
+- Issue form cleaned up and renamed Minutes to First Response field ([#23066](https://github.com/frappe/erpnext/pull/23066))
+- Quotation lost reason options fix ([#22814](https://github.com/frappe/erpnext/pull/22814))
+- Tax amounts in HSN Wise Outward summary ([#23076](https://github.com/frappe/erpnext/pull/23076))
+- Patient Appointment not able to save ([#23434](https://github.com/frappe/erpnext/pull/23434))
+- Removed Working Hours field from Company ([#23009](https://github.com/frappe/erpnext/pull/23009))
+- Added check-in time validation in the Inpatient Record - Transfer ([#22958](https://github.com/frappe/erpnext/pull/22958))
+- Handle Blank from/to range in Numeric Item Attribute ([#23483](https://github.com/frappe/erpnext/pull/23483))
+- Sequence Matcher error in Bank Reconciliation ([#23539](https://github.com/frappe/erpnext/pull/23539))
+- Fixed Conversion Factor rate for the BOM Exploded Item ([#23151](https://github.com/frappe/erpnext/pull/23151))
+- Payment Schedule not fetching ([#23476](https://github.com/frappe/erpnext/pull/23476))
+- Validate if removed Item Attributes exist in variant items ([#22911](https://github.com/frappe/erpnext/pull/22911))
+- Set default billing address for purchase documents ([#22950](https://github.com/frappe/erpnext/pull/22950))
+- Added help link in navbar settings ([#22943](https://github.com/frappe/erpnext/pull/22943))
+- Apply TDS on Purchase Invoice creation from Purchase Order and Purchase Receipt ([#23282](https://github.com/frappe/erpnext/pull/23282))
+- Education Module fixes ([#23714](https://github.com/frappe/erpnext/pull/23714))
+- Filter out cancelled entries in customer ledger summary ([#23205](https://github.com/frappe/erpnext/pull/23205))
+- Fiscal Year and Tax Rates for Italy ([#23623](https://github.com/frappe/erpnext/pull/23623))
+- Production Plan incorrect Work Order qty ([#23264](https://github.com/frappe/erpnext/pull/23264))
+- Added new filters in the Batch-wise Balance History report ([#23676](https://github.com/frappe/erpnext/pull/23676))
+- Update state code and union territory for Daman and Diu ([#22988](https://github.com/frappe/erpnext/pull/22988))
+- Set Stock UOM in item while creating Material Request from Stock Entry ([#23436](https://github.com/frappe/erpnext/pull/23436))
+- Sales Order to Purchase Order flow improvement ([#23357](https://github.com/frappe/erpnext/pull/23357))
+- Student Admission and Student Applicant fixes ([#23515](https://github.com/frappe/erpnext/pull/23515))
+- Loan disbursement amount validation ([#24000](https://github.com/frappe/erpnext/pull/24000))
+- Making company address read-only in delivery note ([#23890](https://github.com/frappe/erpnext/pull/23890))
+- BOM stock report color showing always red ([#23994](https://github.com/frappe/erpnext/pull/23994))
+- Added filter for customer field in Issue ([#24051](https://github.com/frappe/erpnext/pull/24051))
+- Added project link in timesheet form ([#23764](https://github.com/frappe/erpnext/pull/23764))
+- Update integrations desk page ([#23767](https://github.com/frappe/erpnext/pull/23767))
+- Place of supply change on address change ([#23941](https://github.com/frappe/erpnext/pull/23941))
+- TDS calculation, skip invoices with "Apply Tax Withholding Amount" has disabled ([#23672](https://github.com/frappe/erpnext/pull/23672))
+- Auto fetch serial nos with modified conversion factor ([#23854](https://github.com/frappe/erpnext/pull/23854))
+- Default cost center in item master not set in stock entry ([#23877](https://github.com/frappe/erpnext/pull/23877))
+- Incorrect de-link serial no and batch ([#23947](https://github.com/frappe/erpnext/pull/23947))
+- Accounting for internal transfer invoices within same company ([#24021](https://github.com/frappe/erpnext/pull/24021))
+- Multiple pricing rule with margin type as Percentage is not working ([#24205](https://github.com/frappe/erpnext/pull/24205))
+- Added Purchase Order to Global Search ([#24055](https://github.com/frappe/erpnext/pull/24055))
+- Cannot expand row in update items dialog ([#23839](https://github.com/frappe/erpnext/pull/23839))
+- Maintain stock can't be changed it there is product bundle ([#23989](https://github.com/frappe/erpnext/pull/23989))
+- SO to PO Mapping Issue ([#23820](https://github.com/frappe/erpnext/pull/23820))
+- Asset with value zero doesn't show up in fixed asset register ([#24091](https://github.com/frappe/erpnext/pull/24091))
+- Cannot save customer email & phone ([#23797](https://github.com/frappe/erpnext/pull/23797))
+- Incorrect balance value in stock balance report ([#24048](https://github.com/frappe/erpnext/pull/24048))
+- Payment Terms not fetched in Purchase Invoice from Purchase Receipt ([#23735](https://github.com/frappe/erpnext/pull/23735))
+- Fix for LMS Sign Up link ([#23743](https://github.com/frappe/erpnext/pull/23743))
+- Incorrect stock quantity if 'Allow Multiple Material Consumption… ([#24116](https://github.com/frappe/erpnext/pull/24116))
+- Added wrong absent days calculation in salary slip ([#23897](https://github.com/frappe/erpnext/pull/23897))
+- Purchase receipt to purchase invoice bill date mapping ([#23967](https://github.com/frappe/erpnext/pull/23967))
+- Overriding po ([#24022](https://github.com/frappe/erpnext/pull/24022))
+- Do not cancel reference document on Quality Inspection cancellation ([#24198](https://github.com/frappe/erpnext/pull/24198))
+- Get formatted value in 'taxes' print template ([#24035](https://github.com/frappe/erpnext/pull/24035))
+- Don't overrule Item Price via Pricing Rule Rate if 0 ([#23636](https://github.com/frappe/erpnext/pull/23636))
+- Job card error handling for operations field ([#23991](https://github.com/frappe/erpnext/pull/23991))
+- Validation for journal entry with 0 debit and credit values ([#23975](https://github.com/frappe/erpnext/pull/23975))
+- Check if customer exists in product listing ([#24030](https://github.com/frappe/erpnext/pull/24030))
+- Asset finance book posting date fix ([#23778](https://github.com/frappe/erpnext/pull/23778))
+- Same source and target tables in Status Updater's update query ([#24110](https://github.com/frappe/erpnext/pull/24110))
+- Asset finance book depreciation posting date fix ([#23833](https://github.com/frappe/erpnext/pull/23833))
+- Ignore exception during leave ledger creation from patch ([#24005](https://github.com/frappe/erpnext/pull/24005))
+- Added link of bank reconciliation and clearance in accounting desk page ([#23850](https://github.com/frappe/erpnext/pull/23850))
+- Sales invoice add button from sales order dashboard ([#24077](https://github.com/frappe/erpnext/pull/24077))
+- Incorrect calculation for consumed qty for subcontract item ([#23257](https://github.com/frappe/erpnext/pull/23257))
+- Incorrect required_qty in Production Planning Report ([#24074](https://github.com/frappe/erpnext/pull/24074))
+- Email digest user not found ([#23949](https://github.com/frappe/erpnext/pull/23949))
+- Delete Receive at Warehouse entry on cancellation of Send to War… ([#24115](https://github.com/frappe/erpnext/pull/24115))
+- Added TDS Payable account number and an error message ([#24065](https://github.com/frappe/erpnext/pull/24065))
+- Override field_map for job card gantt ([#24155](https://github.com/frappe/erpnext/pull/24155))
+- Old shopify order syncing date ([#23990](https://github.com/frappe/erpnext/pull/23990))
+- Shipping chanrges not sync in erpnext from shopify ([#24114](https://github.com/frappe/erpnext/pull/24114))
+- GSTR B2C report ([#24039](https://github.com/frappe/erpnext/pull/24039))
+- Ignore cancelled entries in stock balance report ([#23757](https://github.com/frappe/erpnext/pull/23757))
+- Stock ageing report not working ([#23923](https://github.com/frappe/erpnext/pull/23923))
+- Incorrect assign to in Maintenance Schedule ([#23831](https://github.com/frappe/erpnext/pull/23831))
+- Improve UX of DATEV report ([#23892](https://github.com/frappe/erpnext/pull/23892))
+- Set SLA variance in seconds for Duration fieldtype ([#23765](https://github.com/frappe/erpnext/pull/23765))
+- dDouble exception in payroll ([#24078](https://github.com/frappe/erpnext/pull/24078))
+- Make asset dashboard charts public ([#23751](https://github.com/frappe/erpnext/pull/23751))
+- Don't copy terms and discount from SO to PO ([#23903](https://github.com/frappe/erpnext/pull/23903))
+- Ignore doctypes on company transaction delete ([#23864](https://github.com/frappe/erpnext/pull/23864))
+- Error handling in Upload Attendance ([#23907](https://github.com/frappe/erpnext/pull/23907))
+- Tax template update on customer address change ([#24160](https://github.com/frappe/erpnext/pull/24160))
+- Not able to save bom ([#23910](https://github.com/frappe/erpnext/pull/23910))
+- Enable Allow Auto Repeat for standard doctypes having auto_repeat field ([#23776](https://github.com/frappe/erpnext/pull/23776))
+- Place of Supply fix in Sales Invoices ([#23785](https://github.com/frappe/erpnext/pull/23785))
+- Opening invoices in GSTR-1 report ([#24117](https://github.com/frappe/erpnext/pull/24117))
+- Partial serial no return issue ([#24208](https://github.com/frappe/erpnext/pull/24208))
+- Import taxjar globally in the taxjar_integration module ([#24027](https://github.com/frappe/erpnext/pull/24027))
+- Payroll attendance error ([#23887](https://github.com/frappe/erpnext/pull/23887))
+- Loan application link on creating loan ([#23937](https://github.com/frappe/erpnext/pull/23937))
+- POS item search includes non stock items ([#23914](https://github.com/frappe/erpnext/pull/23914))
+- Paid amount in Sales Invoice POS return resets to 0 ([#24057](https://github.com/frappe/erpnext/pull/24057))
+- Fiscal year can be shorter than 12 months ([#23838](https://github.com/frappe/erpnext/pull/23838))
+- Loan repayment type option remove ([#23582](https://github.com/frappe/erpnext/pull/23582))
+- Item wise tax calculation ([#23744](https://github.com/frappe/erpnext/pull/23744))
+- Enabling track changes for stock settings ([#23982](https://github.com/frappe/erpnext/pull/23982))
+- Added link of bank reconciliation and clearance in accounting desk page ([#23809](https://github.com/frappe/erpnext/pull/23809))
+- Location data on Asset to use command(make_demo) ([#23825](https://github.com/frappe/erpnext/pull/23825))
+- Handle Account and Item None not found in Opening Invoice Creation Tool ([#23559](https://github.com/frappe/erpnext/pull/23559))
+- Multiple subcontracting issues ([#23662](https://github.com/frappe/erpnext/pull/23662))
+- Sequence id override with workstation column ([#23810](https://github.com/frappe/erpnext/pull/23810))
+- Leave policy dashboard fix and roles ([#24170](https://github.com/frappe/erpnext/pull/24170))
+- Scan barcode does not update barcode item field in sales order ([#24090](https://github.com/frappe/erpnext/pull/24090))
+- Item price duplicate checking ([#23408](https://github.com/frappe/erpnext/pull/23408))
+- Tax template update on supplier change for India ([#24060](https://github.com/frappe/erpnext/pull/24060))
+- Consumed qty logic for subcontracted raw materials ([#23314](https://github.com/frappe/erpnext/pull/23314))
+- Finance book not getting added in journal Entry of asset value adjustment ([#24100](https://github.com/frappe/erpnext/pull/24100))
+- Set proper state code in ewaybill JSON when GST category is SEZ ([#23953](https://github.com/frappe/erpnext/pull/23953))
+- Copying po no when mapping doc ([#23729](https://github.com/frappe/erpnext/pull/23729))
+- Duplicate items validation for POS Invoice when allow multiple items is disabled ([#23896](https://github.com/frappe/erpnext/pull/23896))
+- Do not allow Company as accounting dimension ([#23749](https://github.com/frappe/erpnext/pull/23749))
+- Validation for duplicate Tax Category ([#23978](https://github.com/frappe/erpnext/pull/23978))
+- Therapy plan and session fixes ([#23817](https://github.com/frappe/erpnext/pull/23817))
+- Pricing rule with transaction not working for additional product ([#24053](https://github.com/frappe/erpnext/pull/24053))
+- Inpatient Medication Order and Entry fixes ([#23799](https://github.com/frappe/erpnext/pull/23799))
+- Avoid using SQL query to get fiscal year dates ([#24050](https://github.com/frappe/erpnext/pull/24050))
+- Auto Statewise gst tax template ([#23832](https://github.com/frappe/erpnext/pull/23832))
+- On save sequence id column override with workstation ([#23812](https://github.com/frappe/erpnext/pull/23812))
+- Multiple pricing rules are not working on selling side ([#22711](https://github.com/frappe/erpnext/pull/22711))
+- Salary slip popup error ([#24192](https://github.com/frappe/erpnext/pull/24192))
+- Multiple pricing rule with margin type as Percentage is not working ([#24204](https://github.com/frappe/erpnext/pull/24204))
+- Allow statistical component in salary structure. ([#24424](https://github.com/frappe/erpnext/pull/24424))
+- Set current asset value before calculating difference amount ([#24119](https://github.com/frappe/erpnext/pull/24119))
+- To use Stock UoM in BOM Stock Report ([#24339](https://github.com/frappe/erpnext/pull/24339))
+- Accounting entries of asset when submitting purchase receipt ([#24191](https://github.com/frappe/erpnext/pull/24191))
+- Batch/Serial Selector for Scanned Batched Item ([#24338](https://github.com/frappe/erpnext/pull/24338))
+- Link timesheets with corresponding projects ([#24346](https://github.com/frappe/erpnext/pull/24346))
+- Material request wrong status issue ([#24019](https://github.com/frappe/erpnext/pull/24019))
+- UX issues in e-invoicing ([#24358](https://github.com/frappe/erpnext/pull/24358))
+- Company Wise Valuation Rate for RM in BOM ([#24324](https://github.com/frappe/erpnext/pull/24324))
+- Stock ageing should not take cancelled stock entries. ([#24437](https://github.com/frappe/erpnext/pull/24437))
+- Partial loan security unpledging ([#24252](https://github.com/frappe/erpnext/pull/24252))
+- Asset depreciation ledger ([#24226](https://github.com/frappe/erpnext/pull/24226))
+- Back Update from QC based on Batch No ([#24329](https://github.com/frappe/erpnext/pull/24329))
+- Fix for not having fiscal year while creating new company ([#24130](https://github.com/frappe/erpnext/pull/24130))
+- E-invoice print format not showing other charges ([#24474](https://github.com/frappe/erpnext/pull/24474))
+- Tax template update on customer address change ([#24146](https://github.com/frappe/erpnext/pull/24146))
+- Do not manufacture same serial no multiple times ([#24164](https://github.com/frappe/erpnext/pull/24164))
+- Ignore group cost center validation for period closing voucher ([#24375](https://github.com/frappe/erpnext/pull/24375))
+- Partial serial no return issue ([#24207](https://github.com/frappe/erpnext/pull/24207))
+- GSTR-1 double entry issue ([#24376](https://github.com/frappe/erpnext/pull/24376))
+- Not able to create dunning from sales invoice ([#24349](https://github.com/frappe/erpnext/pull/24349))
+- Set company in leave allocation and leave ledger entry ([#24296](https://github.com/frappe/erpnext/pull/24296))
+- Allow leave policy assignment to be canceled. ([#24265](https://github.com/frappe/erpnext/pull/24265))
+- Removed all day event from shift assignment calendar ([#24397](https://github.com/frappe/erpnext/pull/24397))
+- Tax calculation on salary slip for the first month ([#24272](https://github.com/frappe/erpnext/pull/24272))
+- Validate tax template for tax category ([#24402](https://github.com/frappe/erpnext/pull/24402))
+- Numeric/Non-numeric QI UX ([#24517](https://github.com/frappe/erpnext/pull/24517))
+- Finished good produced qty validation ([#24220](https://github.com/frappe/erpnext/pull/24220))
+- Incorrect serial no in the subcontracted purchase receipt ([#24354](https://github.com/frappe/erpnext/pull/24354))
+- Don't validate warehouse values between Material Request and Stock Entry ([#24294](https://github.com/frappe/erpnext/pull/24294))
+- Don't cancel job card if manufacturing entry has made ([#24063](https://github.com/frappe/erpnext/pull/24063))
+- Subscription prepaid date validation ([#24356](https://github.com/frappe/erpnext/pull/24356))
+- Payment Period based on invoice date report fix/refactor ([#24378](https://github.com/frappe/erpnext/pull/24378))
+- Drop ship partial order fixed ([#24072](https://github.com/frappe/erpnext/pull/24072))
+- Payment entry multi-currency issue ([#24332](https://github.com/frappe/erpnext/pull/24332))
+- Multiple pricing rule issue ([#24515](https://github.com/frappe/erpnext/pull/24515))
+- Last purchase rate not updating when voucher cancelled if only one voucher is present ([#24322](https://github.com/frappe/erpnext/pull/24322))
+- Do not cancel reference document on Quality Inspection cancellation ([#24197](https://github.com/frappe/erpnext/pull/24197))
+- Refactored fetching & validating address from erpnext rather than gst portal ([#24297](https://github.com/frappe/erpnext/pull/24297))
+- Opportunity Status fix ([#22944](https://github.com/frappe/erpnext/pull/22944))
+- Fixed stock and account balance syncing ([#24644](https://github.com/frappe/erpnext/pull/24644))
+- Fixed incorrect stock ledger qty in the stock ledger report and bin ([#24649](https://github.com/frappe/erpnext/pull/24649))
+- Fixed Consolidated Financial Statement report ([#24580](https://github.com/frappe/erpnext/pull/24580))
+- Repost incompleted backdated transactions ([#24991](https://github.com/frappe/erpnext/pull/24991))
+- Unequal debit and credit issue on RCM Invoice ([#24838](https://github.com/frappe/erpnext/pull/24838))
+- Period list for exponential smoothing forecasting report ([#24983](https://github.com/frappe/erpnext/pull/24983))
+- POS Opening Entry with empty balance detail rows ([#24891](https://github.com/frappe/erpnext/pull/24891))
+- Use account_name only in consolidated report ([#24840](https://github.com/frappe/erpnext/pull/24840))
+- Validation of job card in stock entry ([#24882](https://github.com/frappe/erpnext/pull/24882))
+- Incorrect Nil Exempt and Non GST amount in GSTR3B report ([#24918](https://github.com/frappe/erpnext/pull/24918))
+- TDS check getting checked after reload ([#24973](https://github.com/frappe/erpnext/pull/24973))
+- Membership and Donation API fixes ([#24900](https://github.com/frappe/erpnext/pull/24900))
+- Allow zero valuation in stock reconciliation ([#24985](https://github.com/frappe/erpnext/pull/24985))
+- Simplified logic for additional salary ([#24907](https://github.com/frappe/erpnext/pull/24907))
+- Allow to select item code in batch naming ([#24825](https://github.com/frappe/erpnext/pull/24825))
+- Membership renewal validation (#24963) ([#24964](https://github.com/frappe/erpnext/pull/24964))
+</details>
\ No newline at end of file
diff --git a/erpnext/change_log/v13/v13_1_0.md b/erpnext/change_log/v13/v13_1_0.md
new file mode 100644
index 0000000..d991034
--- /dev/null
+++ b/erpnext/change_log/v13/v13_1_0.md
@@ -0,0 +1,129 @@
+# Version 13.1.0 Release Notes
+
+### Features
+
+- Recursive pricing rule ([#24922](https://github.com/frappe/erpnext/pull/24922))
+- Discount configuration on early payments ([#24586](https://github.com/frappe/erpnext/pull/24586))
+- Bulk e-invoice generation ([#24969](https://github.com/frappe/erpnext/pull/24969))
+- Employee Self Service ([#24408](https://github.com/frappe/erpnext/pull/24408))
+- Share doc with employee approvers if they don't have access ([#25190](https://github.com/frappe/erpnext/pull/25190))
+- Price margin in buying ([#24685](https://github.com/frappe/erpnext/pull/24685))
+- Allow changing Work Stations in Work Order & Job Card ([#24897](https://github.com/frappe/erpnext/pull/24897))
+- Add document type field for e-invoicing (Italy) ([#25256](https://github.com/frappe/erpnext/pull/25256))
+- Add checkbox for disabling leave notification in HR Settings ([#24877](https://github.com/frappe/erpnext/pull/24877))
+- Enhancements in Material Request Plan Item in Production Plan ([#25025](https://github.com/frappe/erpnext/pull/25025))
+
+
+### Fixes and Enhancements
+- Mode of payments disappear on loading draft pos invoice ([#24917](https://github.com/frappe/erpnext/pull/24917))
+- Sales order not saving due type mismatch in promo scheme (#24748) ([#25222](https://github.com/frappe/erpnext/pull/25222))
+- Zero amount completed delivery notes being shown in Sales Invoice get items ([#25317](https://github.com/frappe/erpnext/pull/25317))
+- Incorrect status creating PR from PO after creating PI ([#25109](https://github.com/frappe/erpnext/pull/25109))
+- Precision and formatted document for stock level in item dashboard. ([#24921](https://github.com/frappe/erpnext/pull/24921))
+- Precision issues while allocating advance amount ([#25086](https://github.com/frappe/erpnext/pull/25086))
+- Round off final tax amount instead of current tax amount ([#25188](https://github.com/frappe/erpnext/pull/25188))
+- Redesign fixes ([#24896](https://github.com/frappe/erpnext/pull/24896))
+- TDS check getting checked after reload ([#24972](https://github.com/frappe/erpnext/pull/24972))
+- Github Action not failing when tests fail ([#24867](https://github.com/frappe/erpnext/pull/24867))
+- Calculate 80g certificate amount on validate for memberships ([#24925](https://github.com/frappe/erpnext/pull/24925))
+- Purchase from registered composition dealer ([#25040](https://github.com/frappe/erpnext/pull/25040))
+- Reduce number of queries for checking if future SL entry exists ([#24881](https://github.com/frappe/erpnext/pull/24881))
+- Remove unwanted parameter in calculate_rate_and_amount ([#24883](https://github.com/frappe/erpnext/pull/24883))
+- Membership renewal validation ([#24963](https://github.com/frappe/erpnext/pull/24963))
+- Not able to save material request ([#25112](https://github.com/frappe/erpnext/pull/25112))
+- POS print receipt ([#25330](https://github.com/frappe/erpnext/pull/25330))
+- Supplier was not able to Submit RFQ due to insufficient permission ([#24622](https://github.com/frappe/erpnext/pull/24622))
+- Unequal debit and credit issue on RCM Invoice ([#24836](https://github.com/frappe/erpnext/pull/24836))
+- Picked Qty conversion from Stock Qty to Qty while creating DN from Pick List ([#25105](https://github.com/frappe/erpnext/pull/25105))
+- Salary Structure object has no attribute set_totals ([#25113](https://github.com/frappe/erpnext/pull/25113))
+- Incorrect Nil Exempt and Non GST amount in GSTR3B report ([#24916](https://github.com/frappe/erpnext/pull/24916))
+- Add method for regional round off account back ([#24893](https://github.com/frappe/erpnext/pull/24893))
+- Employee profile pic upload access for erpnext user ([#25022](https://github.com/frappe/erpnext/pull/25022))
+- Make filters for payroll entry ([#25386](https://github.com/frappe/erpnext/pull/25386))
+- Fix dynamically changing grid properties ([#25310](https://github.com/frappe/erpnext/pull/25310))
+- Consider paid repayment entries in subsequent loan repayments ([#25271](https://github.com/frappe/erpnext/pull/25271))
+- Allow duplicate additional salaries ([#24842](https://github.com/frappe/erpnext/pull/24842))
+- Object referencing the same address issue ([#25159](https://github.com/frappe/erpnext/pull/25159))
+- Validating party currency with doc currency ([#24318](https://github.com/frappe/erpnext/pull/24318))
+- Non Profit fixes ([#25060](https://github.com/frappe/erpnext/pull/25060))
+- Additional Salary component amount not getting set ([#25356](https://github.com/frappe/erpnext/pull/25356))
+- Allow user to update exchange rate in Multi-currency LCV ([#24912](https://github.com/frappe/erpnext/pull/24912))
+- Allow creating stock entry based on work order for customer provided items ([#24885](https://github.com/frappe/erpnext/pull/24885))
+- Create property setters for shorter naming series on setup ([#25128](https://github.com/frappe/erpnext/pull/25128))
+- Add GST category field in Delivery Note ([#25053](https://github.com/frappe/erpnext/pull/25053))
+- Ignore Permission for Leave Ledger Entry ([#25172](https://github.com/frappe/erpnext/pull/25172))
+- Pending shortfall update on processing loan security shortfall ([#24971](https://github.com/frappe/erpnext/pull/24971))
+- Added flag for dont_fetch_price_list_rate in transaction ([#25041](https://github.com/frappe/erpnext/pull/25041))
+- Exchange Rate not getting set in Salary Slip ([#25004](https://github.com/frappe/erpnext/pull/25004))
+- Repost not completed backdated transactions ([#24980](https://github.com/frappe/erpnext/pull/24980))
+- frappe.whitelist for doc methods ([#25230](https://github.com/frappe/erpnext/pull/25230))
+- Opportunity-quotation mapping order status ([#25001](https://github.com/frappe/erpnext/pull/25001))
+- GST on freight charge in e-invoicing ([#25000](https://github.com/frappe/erpnext/pull/25000))
+- Role to override maintain same rate check in transactions ([#25193](https://github.com/frappe/erpnext/pull/25193))
+- Added blank option for status in report related to issue ([#25082](https://github.com/frappe/erpnext/pull/25082))
+- Cashier query in POS Opening/Closing Entry ([#25399](https://github.com/frappe/erpnext/pull/25399))
+- Lead Source's module ([#24583](https://github.com/frappe/erpnext/pull/24583))
+- Hide alt tag if item is not shown in website ([#24937](https://github.com/frappe/erpnext/pull/24937))
+- Ignore Customer Group Perm on All Products page ([#25397](https://github.com/frappe/erpnext/pull/25397))
+- Give first preference to loan security on repayment ([#25212](https://github.com/frappe/erpnext/pull/25212))
+- Add shortfall ratio in Loan Security Shortfall ([#25138](https://github.com/frappe/erpnext/pull/25138))
+- Condition for SLA status banner ([#25261](https://github.com/frappe/erpnext/pull/25261))
+- Component amount calculation based on formula with abbr not working ([#25117](https://github.com/frappe/erpnext/pull/25117))
+- Remove gst name validation for purchase Invoice ([#25235](https://github.com/frappe/erpnext/pull/25235))
+- Do not fetch stopped MR in production plan ([#25063](https://github.com/frappe/erpnext/pull/25063))
+- Backport missing commits to develop branch ([#25305](https://github.com/frappe/erpnext/pull/25305))
+- UOM length unit in global setup list is empty ([#24855](https://github.com/frappe/erpnext/pull/24855))
+- Round total quantity in job card ([#25240](https://github.com/frappe/erpnext/pull/25240))
+- Default total_estimated_cost to zero ([#24939](https://github.com/frappe/erpnext/pull/24939))
+- Serial no refresh issue ([#25127](https://github.com/frappe/erpnext/pull/25127))
+- Correct calculation for discount amount when margin is set ([#25179](https://github.com/frappe/erpnext/pull/25179))
+- Get correct holiday list when calculating dates; test fixes ([#24901](https://github.com/frappe/erpnext/pull/24901))
+- POS print receipt ([#24924](https://github.com/frappe/erpnext/pull/24924))
+- Condition for setting agreement status ([#25255](https://github.com/frappe/erpnext/pull/25255))
+- Loan Repayment entry cancellation on salary slip cancel ([#24879](https://github.com/frappe/erpnext/pull/24879))
+- Add company validation for e-invoicing ([#25349](https://github.com/frappe/erpnext/pull/25349))
+- Query values incorrectly escaped while back updating Quality Inspection ([#25118](https://github.com/frappe/erpnext/pull/25118))
+- Update Bin via Update Item on Purchase/Sales Order ([#23509](https://github.com/frappe/erpnext/pull/23509))
+- Declare data before assigning ([#25287](https://github.com/frappe/erpnext/pull/25287))
+- Do not set standard link in Sales Invoice as custom ([#25096](https://github.com/frappe/erpnext/pull/25096))
+- Hide serial and batch selector in Stock Entry ([#25107](https://github.com/frappe/erpnext/pull/25107))
+- Taxable value including Freight and Forwarding charges in GSTR-1 Report ([#25290](https://github.com/frappe/erpnext/pull/25290))
+- Remove nonexistent method from pick list ([#25279](https://github.com/frappe/erpnext/pull/25279))
+- Allow zero valuation in stock reconciliation ([#24888](https://github.com/frappe/erpnext/pull/24888))
+- Place of supply of e-invoicing ([#25148](https://github.com/frappe/erpnext/pull/25148))
+- Delivery note print error ([#25080](https://github.com/frappe/erpnext/pull/25080))
+- Fix Payment references from disappearing on adding Cost Center in Payment Entry ([#24831](https://github.com/frappe/erpnext/pull/24831))
+- Company field in Warehouse ([#25196](https://github.com/frappe/erpnext/pull/25196))
+- Available employee for selection ([#25378](https://github.com/frappe/erpnext/pull/25378))
+- Cannot set qty to less than zero ([#25258](https://github.com/frappe/erpnext/pull/25258))
+- Don't delete mode of payment account details while deleting comp… ([#25217](https://github.com/frappe/erpnext/pull/25217))
+- Exclude current doc while validation. ([#24914](https://github.com/frappe/erpnext/pull/24914))
+- POS Opening Entry with empty balance detail rows ([#24876](https://github.com/frappe/erpnext/pull/24876))
+- Unable to submit stock entry ([#25033](https://github.com/frappe/erpnext/pull/25033))
+- BOM cost test case ([#25242](https://github.com/frappe/erpnext/pull/25242))
+- Filter for employees in salary slip ([#25361](https://github.com/frappe/erpnext/pull/25361))
+- Added correct path in hooks ([#24862](https://github.com/frappe/erpnext/pull/24862))
+- Patch regional fields for old companies ([#24988](https://github.com/frappe/erpnext/pull/24988))
+- consolidated sales invoice posting date ([#25119](https://github.com/frappe/erpnext/pull/25119))
+- Don't set "Company:company:default_currency" as default for currency link fields ([#25095](https://github.com/frappe/erpnext/pull/25095))
+- Healthcare lab module rename fields ([#25276](https://github.com/frappe/erpnext/pull/25276))
+- Error message compensatory leave request ([#25206](https://github.com/frappe/erpnext/pull/25206))
+- Adding company link to e invoice settings patch condition ([#25301](https://github.com/frappe/erpnext/pull/25301))
+- Membership and Donation API fixes ([#24900](https://github.com/frappe/erpnext/pull/24900))
+- Set correct ack no. on irn generation ([#25251](https://github.com/frappe/erpnext/pull/25251))
+- Report Issue Summary fix for zero issues ([#24934](https://github.com/frappe/erpnext/pull/24934))
+- Validation msg for TransDocNo e-invoicing ([#25121](https://github.com/frappe/erpnext/pull/25121))
+- Correct state code for 'Other Territory' ([#24993](https://github.com/frappe/erpnext/pull/24993))
+- Commit individual SLE rename for large datasets (develop) ([#25084](https://github.com/frappe/erpnext/pull/25084))
+- Remove shipping address GSTIN validation for e-invoice ([#25153](https://github.com/frappe/erpnext/pull/25153))
+- Period list for exponential smoothing forecasting report ([#24982](https://github.com/frappe/erpnext/pull/24982))
+- Customer creation from shopping cart ([#25136](https://github.com/frappe/erpnext/pull/25136))
+- Simplified logic for additional salary ([#24824](https://github.com/frappe/erpnext/pull/24824))
+- Item wise tax rate for consolidated POS invoice ([#25029](https://github.com/frappe/erpnext/pull/25029))
+- Column width in Recruitment analytics report ([#25003](https://github.com/frappe/erpnext/pull/25003))
+- Filter Bank Account drop-down list in Bank Reconciliation Tool ([#24873](https://github.com/frappe/erpnext/pull/24873))
+- Payroll issues ([#24540](https://github.com/frappe/erpnext/pull/24540))
+- PO not created against all selected suppliers (drop shipping) ([#24863](https://github.com/frappe/erpnext/pull/24863))
+- Can't multiply sequence by non-int of type 'float' ([#25092](https://github.com/frappe/erpnext/pull/25092))
+- Make Discharge Schedule Date as Datetime ([#24940](https://github.com/frappe/erpnext/pull/24940))
+- Serial no trim issue ([#24949](https://github.com/frappe/erpnext/pull/24949))
diff --git a/erpnext/change_log/v13/v13_2_0.md b/erpnext/change_log/v13/v13_2_0.md
new file mode 100644
index 0000000..eb9499d
--- /dev/null
+++ b/erpnext/change_log/v13/v13_2_0.md
@@ -0,0 +1,56 @@
+# Version 13.2.0 Release Notes
+
+### Features & Enhancements
+
+- Employee Hours Utilization Report ([#25209](https://github.com/frappe/erpnext/pull/25209))
+- Delayed Tasks Summary Report ([#25024](https://github.com/frappe/erpnext/pull/25024))
+- Project Profitability Report ([#24944](https://github.com/frappe/erpnext/pull/24944))
+- Timer in LMS Quiz ([#24246](https://github.com/frappe/erpnext/pull/24246))
+- Role to allow over billing, delivery, receipt ([#24854](https://github.com/frappe/erpnext/pull/24854))
+- Auto calculate distance for e-way bill generations ([#25480](https://github.com/frappe/erpnext/pull/25480))
+- Add total available stock field in PO ([#24878](https://github.com/frappe/erpnext/pull/24878))
+- Refactored Setup Taxes and Charges ([#24805](https://github.com/frappe/erpnext/pull/24805))
+- Inpatient Occupancy Table Editable for Healthcare Admin ([#24989](https://github.com/frappe/erpnext/pull/24989))
+- Added Disable Rounded Total in sales transactions ([#25362](https://github.com/frappe/erpnext/pull/25362))
+
+
+### Fixes
+
+- Incorrect GL Entry validation ([#25474](https://github.com/frappe/erpnext/pull/25474))
+- Cannot create item variants ([#25433](https://github.com/frappe/erpnext/pull/25433))
+- Leave policy in leave allocation ([#25334](https://github.com/frappe/erpnext/pull/25334))
+- Let Administrator delete company transactions ([#25300](https://github.com/frappe/erpnext/pull/25300))
+- Display reconcile tool when closing balance 0 ([#25417](https://github.com/frappe/erpnext/pull/25417))
+- Bulk Salary Structure Assignment ([#25389](https://github.com/frappe/erpnext/pull/25389))
+- Payment amount showing in foreign currency ([#25518](https://github.com/frappe/erpnext/pull/25518))
+- Commit changes to shipment status in database ([#25374](https://github.com/frappe/erpnext/pull/25374))
+- Add amend perm for loan and system manager for loan doctypes ([#25393](https://github.com/frappe/erpnext/pull/25393))
+- Cashier query in POS Opening/Closing Entry ([#25398](https://github.com/frappe/erpnext/pull/25398))
+- Apply single transaction threshold on net_total instead of supplier credit amount ([#25243](https://github.com/frappe/erpnext/pull/25243))
+- Update allocated amount after paid amount is changed in PE ([#25528](https://github.com/frappe/erpnext/pull/25528))
+- Remove non-standard module cards from Home Workspace ([#25391](https://github.com/frappe/erpnext/pull/25391))
+- Cannot scan spacebar character in pos ([#25479](https://github.com/frappe/erpnext/pull/25479))
+- Permission error after submitting exchange rate revaluation ([#25432](https://github.com/frappe/erpnext/pull/25432))
+- Equality check instead of assignment in cart ([#25372](https://github.com/frappe/erpnext/pull/25372))
+- Disable auto naming of customer during import ([#25152](https://github.com/frappe/erpnext/pull/25152))
+- Additional Salary component amount not getting set ([#25355](https://github.com/frappe/erpnext/pull/25355))
+- Round off values near to zero ([#25304](https://github.com/frappe/erpnext/pull/25304))
+- Allow to cancel loan with cancelled repayment entry ([#25508](https://github.com/frappe/erpnext/pull/25508))
+- Currency symbol in bank transaction list view ([#25336](https://github.com/frappe/erpnext/pull/25336))
+- Incorrect batch picked in subcontracted purchase receipt ([#25186](https://github.com/frappe/erpnext/pull/25186))
+- Issue in project custom status ([#25452](https://github.com/frappe/erpnext/pull/25452))
+- Shipment pickup_to, pickup_from functionality. ([#25359](https://github.com/frappe/erpnext/pull/25359))
+- Stock ledger entry created against draft stock entry ([#25539](https://github.com/frappe/erpnext/pull/25539))
+- Ageing errors in PSOA ([#25529](https://github.com/frappe/erpnext/pull/25529))
+- Permission error while adding weekly holidays ([#25450](https://github.com/frappe/erpnext/pull/25450))
+- Filter for employees in salary slip ([#25360](https://github.com/frappe/erpnext/pull/25360))
+- Backward compatibility for GSTR-1 report ([#25444](https://github.com/frappe/erpnext/pull/25444))
+- Incorrect incoming rate for the sales return ([#25145](https://github.com/frappe/erpnext/pull/25145))
+- POS print receipt ([#25328](https://github.com/frappe/erpnext/pull/25328))
+- Laboratory Module patch ([#25431](https://github.com/frappe/erpnext/pull/25431))
+- Performance: fetching exchange rate on every line item slows down PO ([#25345](https://github.com/frappe/erpnext/pull/25345))
+- Presentation currency in statement of accounts ([#25367](https://github.com/frappe/erpnext/pull/25367))
+- Serial No not updated correctly via Inter Company Stock Transfer ([#25006](https://github.com/frappe/erpnext/pull/25006))
+- Ignore Customer Group Perm on All Products page ([#25396](https://github.com/frappe/erpnext/pull/25396))
+- Change subcontracted item display ([#25425](https://github.com/frappe/erpnext/pull/25425))
+- Add company validation for e-invoicing ([#25348](https://github.com/frappe/erpnext/pull/25348))
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 33fbf1c..f88e8df 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -90,6 +90,8 @@
self.ensure_supplier_is_not_blocked()
self.validate_date_with_fiscal_year()
+ self.validate_party_accounts()
+
self.validate_inter_company_reference()
self.set_incoming_rate()
@@ -233,6 +235,23 @@
validate_fiscal_year(self.get(date_field), self.fiscal_year, self.company,
self.meta.get_label(date_field), self)
+ def validate_party_accounts(self):
+ if self.doctype not in ('Sales Invoice', 'Purchase Invoice'):
+ return
+
+ if self.doctype == 'Sales Invoice':
+ party_account_field = 'debit_to'
+ item_field = 'income_account'
+ else:
+ party_account_field = 'credit_to'
+ item_field = 'expense_account'
+
+ for item in self.get('items'):
+ if item.get(item_field) == self.get(party_account_field):
+ frappe.throw(_("Row {0}: {1} {2} cannot be same as {3} (Party Account) {4}").format(item.idx,
+ frappe.bold(frappe.unscrub(item_field)), item.get(item_field),
+ frappe.bold(frappe.unscrub(party_account_field)), self.get(party_account_field)))
+
def validate_inter_company_reference(self):
if self.doctype not in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
return
@@ -240,7 +259,7 @@
if self.is_internal_transfer():
if not (self.get('inter_company_reference') or self.get('inter_company_invoice_reference')
or self.get('inter_company_order_reference')):
- msg = _("Internal Sale or Delivery Reference missing. ")
+ msg = _("Internal Sale or Delivery Reference missing.")
msg += _("Please create purchase from internal sale or delivery document itself")
frappe.throw(msg, title=_("Internal Sales Reference Missing"))
@@ -349,6 +368,11 @@
if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field('is_fixed_asset'):
item.set('is_fixed_asset', ret.get('is_fixed_asset', 0))
+ # Double check for cost center
+ # Items add via promotional scheme may not have cost center set
+ if hasattr(item, 'cost_center') and not item.get('cost_center'):
+ item.set('cost_center', self.get('cost_center') or erpnext.get_default_cost_center(self.company))
+
if ret.get("pricing_rules"):
self.apply_pricing_rule_on_items(item, ret)
self.set_pricing_rule_details(item, ret)
@@ -717,7 +741,9 @@
total_billed_amt = abs(total_billed_amt)
max_allowed_amt = abs(max_allowed_amt)
- if total_billed_amt - max_allowed_amt > 0.01:
+ role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
+
+ if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles():
frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
.format(item.item_code, item.idx, max_allowed_amt))
@@ -902,29 +928,34 @@
date = self.get("due_date")
due_date = date or posting_date
- if party_account_currency == self.company_currency:
- grand_total = self.get("base_rounded_total") or self.base_grand_total
- else:
- grand_total = self.get("rounded_total") or self.grand_total
+ base_grand_total = self.get("base_rounded_total") or self.base_grand_total
+ grand_total = self.get("rounded_total") or self.grand_total
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
+ base_grand_total = base_grand_total - flt(self.base_write_off_amount)
grand_total = grand_total - flt(self.write_off_amount)
if self.get("total_advance"):
- grand_total -= self.get("total_advance")
+ if party_account_currency == self.company_currency:
+ base_grand_total -= self.get("total_advance")
+ grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total"))
+ else:
+ grand_total -= self.get("total_advance")
+ base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
if not self.get("payment_schedule"):
if self.get("payment_terms_template"):
- data = get_payment_terms(self.payment_terms_template, posting_date, grand_total)
+ data = get_payment_terms(self.payment_terms_template, posting_date, grand_total, base_grand_total)
for item in data:
self.append("payment_schedule", item)
else:
- data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total)
+ data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total, base_payment_amount=base_grand_total)
self.append("payment_schedule", data)
else:
for d in self.get("payment_schedule"):
if d.invoice_portion:
d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
+ d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
d.outstanding = d.payment_amount
def set_due_date(self):
@@ -961,22 +992,27 @@
if self.get("payment_schedule"):
total = 0
+ base_total = 0
for d in self.get("payment_schedule"):
total += flt(d.payment_amount)
+ base_total += flt(d.base_payment_amount)
- if party_account_currency == self.company_currency:
- total = flt(total, self.precision("base_grand_total"))
- grand_total = flt(self.get("base_rounded_total") or self.base_grand_total, self.precision('base_grand_total'))
- else:
- total = flt(total, self.precision("grand_total"))
- grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total'))
-
- if self.get("total_advance"):
- grand_total -= self.get("total_advance")
+ base_grand_total = self.get("base_rounded_total") or self.base_grand_total
+ grand_total = self.get("rounded_total") or self.grand_total
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
+ base_grand_total = base_grand_total - flt(self.base_write_off_amount)
grand_total = grand_total - flt(self.write_off_amount)
- if total != flt(grand_total, self.precision("grand_total")):
+
+ if self.get("total_advance"):
+ if party_account_currency == self.company_currency:
+ base_grand_total -= self.get("total_advance")
+ grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total"))
+ else:
+ grand_total -= self.get("total_advance")
+ base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
+ if total != flt(grand_total, self.precision("grand_total")) or \
+ base_total != flt(base_grand_total, self.precision("base_grand_total")):
frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total"))
def is_rounded_total_disabled(self):
@@ -1216,7 +1252,7 @@
@frappe.whitelist()
-def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_date=None):
+def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None):
if not terms_template:
return
@@ -1224,14 +1260,14 @@
schedule = []
for d in terms_doc.get("terms"):
- term_details = get_payment_term_details(d, posting_date, grand_total, bill_date)
+ term_details = get_payment_term_details(d, posting_date, grand_total, base_grand_total, bill_date)
schedule.append(term_details)
return schedule
@frappe.whitelist()
-def get_payment_term_details(term, posting_date=None, grand_total=None, bill_date=None):
+def get_payment_term_details(term, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None):
term_details = frappe._dict()
if isinstance(term, text_type):
term = frappe.get_doc("Payment Term", term)
@@ -1240,9 +1276,9 @@
term_details.description = term.description
term_details.invoice_portion = term.invoice_portion
term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100
+ term_details.base_payment_amount = flt(term.invoice_portion) * flt(base_grand_total) / 100
term_details.discount_type = term.discount_type
term_details.discount = term.discount
- # term_details.discounted_amount = flt(grand_total) * (term.discount / 100) if term.discount_type == 'Percentage' else discount
term_details.outstanding = term_details.payment_amount
term_details.mode_of_payment = term.mode_of_payment
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 219d529..3f2d339 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -6,6 +6,7 @@
from frappe import _, msgprint
from frappe.utils import flt,cint, cstr, getdate
from six import iteritems
+from collections import OrderedDict
from erpnext.accounts.party import get_party_details
from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.buying.utils import validate_for_items, update_last_purchase_rate
@@ -391,10 +392,12 @@
batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code,
qty, transferred_batch_qty_map, backflushed_batch_qty_map, item.purchase_order)
+
for batch_data in batches_qty:
qty = batch_data['qty']
raw_material.batch_no = batch_data['batch']
- self.append_raw_material_to_be_backflushed(item, raw_material, qty)
+ if qty > 0:
+ self.append_raw_material_to_be_backflushed(item, raw_material, qty)
else:
self.append_raw_material_to_be_backflushed(item, raw_material, qty)
@@ -835,9 +838,10 @@
if not self.get("items"):
return
- earliest_schedule_date = min([d.schedule_date for d in self.get("items")])
- if earliest_schedule_date:
- self.schedule_date = earliest_schedule_date
+ if any(d.schedule_date for d in self.get("items")):
+ # Select earliest schedule_date.
+ self.schedule_date = min(d.schedule_date for d in self.get("items")
+ if d.schedule_date is not None)
if self.schedule_date:
for d in self.get('items'):
@@ -1056,7 +1060,7 @@
for batch_data in transferred_batches:
key = ((batch_data.item_code, fg_item)
if batch_data.subcontracted_item else (batch_data.item_code, purchase_order))
- transferred_batch_qty_map.setdefault(key, {})
+ transferred_batch_qty_map.setdefault(key, OrderedDict())
transferred_batch_qty_map[key][batch_data.batch_no] = batch_data.qty
return transferred_batch_qty_map
@@ -1109,8 +1113,14 @@
if available_qty >= required_qty:
available_batches.append({'batch': batch, 'qty': required_qty})
break
- else:
+ elif available_qty != 0:
available_batches.append({'batch': batch, 'qty': available_qty})
required_qty -= available_qty
+ for row in available_batches:
+ if backflushed_batches.get(row.get('batch'), 0) > 0:
+ backflushed_batches[row.get('batch')] += row.get('qty')
+ else:
+ backflushed_batches[row.get('batch')] = row.get('qty')
+
return available_batches
diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py
index 1f95e00..051481f 100644
--- a/erpnext/controllers/item_variant.py
+++ b/erpnext/controllers/item_variant.py
@@ -262,7 +262,8 @@
# copy non no-copy fields
exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website",
- "show_variant_in_website", "opening_stock", "variant_of", "valuation_rate"]
+ "show_variant_in_website", "opening_stock", "variant_of", "valuation_rate",
+ "has_variants", "attributes"]
if item.variant_based_on=='Manufacturer':
# don't copy manufacturer values if based on part no
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 81f0ad3..46301b7 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -204,7 +204,7 @@
if "description" in searchfields:
searchfields.remove("description")
-
+
columns = ''
extra_searchfields = [field for field in searchfields
if not field in ["name", "item_group", "description"]]
@@ -216,11 +216,22 @@
if not field in searchfields]
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
+ if filters.get('supplier'):
+ item_group_list = frappe.get_all('Supplier Item Group', filters = {'supplier': filters.get('supplier')}, fields = ['item_group'])
+
+ item_groups = []
+ for i in item_group_list:
+ item_groups.append(i.item_group)
+
+ del filters['supplier']
+
+ if item_groups:
+ filters['item_group'] = ['in', item_groups]
+
description_cond = ''
if frappe.db.count('Item', cache=True) < 50000:
# scan description only if items are less than 50000
description_cond = 'or tabItem.description LIKE %(txt)s'
-
return frappe.db.sql("""select tabItem.name,
if(length(tabItem.item_name) > 40,
concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name,
@@ -292,11 +303,14 @@
cond = """(`tabProject`.customer = %s or
ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer")))
- fields = get_fields("Project", ["name"])
+ fields = get_fields("Project", ["name", "project_name"])
+ searchfields = frappe.get_meta("Project").get_search_fields()
+ searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
return frappe.db.sql("""select {fields} from `tabProject`
- where `tabProject`.status not in ("Completed", "Cancelled")
- and {cond} `tabProject`.name like %(txt)s {match_cond}
+ where
+ `tabProject`.status not in ("Completed", "Cancelled")
+ and {cond} {match_cond} {scond}
order by
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
idx desc,
@@ -304,6 +318,7 @@
limit {start}, {page_len}""".format(
fields=", ".join(['`tabProject`.{0}'.format(f) for f in fields]),
cond=cond,
+ scond=searchfields,
match_cond=get_match_cond(doctype),
start=start,
page_len=page_len), {
@@ -325,7 +340,7 @@
and status not in ("Stopped", "Closed") %(fcond)s
and (
(`tabDelivery Note`.is_return = 0 and `tabDelivery Note`.per_billed < 100)
- or `tabDelivery Note`.grand_total = 0
+ or (`tabDelivery Note`.grand_total = 0 and `tabDelivery Note`.per_billed < 100)
or (
`tabDelivery Note`.is_return = 1
and return_against in (select name from `tabDelivery Note` where per_billed < 100)
@@ -713,7 +728,9 @@
return [(d,) for d in set(taxes)]
-def get_fields(doctype, fields=[]):
+def get_fields(doctype, fields=None):
+ if fields is None:
+ fields = []
meta = frappe.get_meta(doctype)
fields.extend(meta.get_search_fields())
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index de61b35..5f759b4 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -5,6 +5,7 @@
import frappe, erpnext
from frappe import _
from frappe.model.meta import get_field_precision
+from erpnext.stock.utils import get_incoming_rate
from frappe.utils import flt, get_datetime, format_datetime
class StockOverReturnError(frappe.ValidationError): pass
@@ -389,10 +390,24 @@
return doclist
-def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None, item_row=None, voucher_detail_no=None):
+def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None,
+ item_row=None, voucher_detail_no=None, sle=None):
if not return_against:
return_against = frappe.get_cached_value(voucher_type, voucher_no, "return_against")
+ if not return_against and voucher_type == 'Sales Invoice' and sle:
+ return get_incoming_rate({
+ "item_code": sle.item_code,
+ "warehouse": sle.warehouse,
+ "posting_date": sle.get('posting_date'),
+ "posting_time": sle.get('posting_time'),
+ "qty": sle.actual_qty,
+ "serial_no": sle.get('serial_no'),
+ "company": sle.company,
+ "voucher_type": sle.voucher_type,
+ "voucher_no": sle.voucher_no
+ }, raise_error_if_no_rate=False)
+
return_against_item_field = get_return_against_item_fields(voucher_type)
filters = get_filters(voucher_type, voucher_no, voucher_detail_no,
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index edc40c4..54156f37 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -311,14 +311,16 @@
items = self.get("items") + (self.get("packed_items") or [])
for d in items:
- if not cint(self.get("is_return")):
+ if not self.get("return_against"):
# Get incoming rate based on original item cost based on valuation method
+ qty = flt(d.get('stock_qty') or d.get('actual_qty'))
+
d.incoming_rate = get_incoming_rate({
"item_code": d.item_code,
"warehouse": d.warehouse,
"posting_date": self.get('posting_date') or self.get('transaction_date'),
"posting_time": self.get('posting_time') or nowtime(),
- "qty": -1 * flt(d.get('stock_qty') or d.get('actual_qty')),
+ "qty": qty if cint(self.get("is_return")) else (-1 * qty),
"serial_no": d.get('serial_no'),
"company": self.company,
"voucher_type": self.doctype,
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index 0987d09..83d4c33 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -76,12 +76,12 @@
["Stopped", "eval:self.status == 'Stopped'"],
["Cancelled", "eval:self.docstatus == 2"],
["Pending", "eval:self.status != 'Stopped' and self.per_ordered == 0 and self.docstatus == 1"],
- ["Partially Ordered", "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1"],
["Ordered", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
["Transferred", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'"],
["Issued", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'"],
["Received", "eval:self.status != 'Stopped' and self.per_received == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
["Partially Received", "eval:self.status != 'Stopped' and self.per_received > 0 and self.per_received < 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
+ ["Partially Ordered", "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1"],
["Manufactured", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'"]
],
"Bank Transaction": [
@@ -98,7 +98,12 @@
["Draft", None],
["Submitted", "eval:self.docstatus == 1"],
["Queued", "eval:self.status == 'Queued'"],
+ ["Failed", "eval:self.status == 'Failed'"],
["Cancelled", "eval:self.docstatus == 2"],
+ ],
+ "Transaction Deletion Record": [
+ ["Draft", None],
+ ["Completed", "eval:self.docstatus == 1"],
]
}
@@ -201,10 +206,14 @@
get_allowance_for(item['item_code'], self.item_allowance,
self.global_qty_allowance, self.global_amount_allowance, qty_or_amount)
- overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) /
- item[args['target_ref_field']]) * 100
+ role_allowed_to_over_deliver_receive = frappe.db.get_single_value('Stock Settings', 'role_allowed_to_over_deliver_receive')
+ role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
+ role = role_allowed_to_over_deliver_receive if qty_or_amount == 'qty' else role_allowed_to_over_bill
- if overflow_percent - allowance > 0.01:
+ overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) /
+ item[args['target_ref_field']]) * 100
+
+ if overflow_percent - allowance > 0.01 and role not in frappe.get_roles():
item['max_allowed'] = flt(item[args['target_ref_field']] * (100+allowance)/100)
item['reduce_by'] = item[args['target_field']] - item['max_allowed']
@@ -371,10 +380,12 @@
ref_doc.db_set("per_billed", per_billed)
ref_doc.set_status(update=True)
-def get_allowance_for(item_code, item_allowance={}, global_qty_allowance=None, global_amount_allowance=None, qty_or_amount="qty"):
+def get_allowance_for(item_code, item_allowance=None, global_qty_allowance=None, global_amount_allowance=None, qty_or_amount="qty"):
"""
Returns the allowance for the item, if not set, returns global allowance
"""
+ if item_allowance is None:
+ item_allowance = {}
if qty_or_amount == "qty":
if item_allowance.get(item_code, frappe._dict()).get("qty"):
return item_allowance[item_code].qty, item_allowance, global_qty_allowance, global_amount_allowance
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index f352bae..41ca404 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -117,7 +117,6 @@
"account": expense_account,
"against": warehouse_account[sle.warehouse]["account"],
"cost_center": item_row.cost_center,
- "project": item_row.project or self.get('project'),
"remarks": self.get("remarks") or "Accounting Entry for Stock",
"credit": flt(sle.stock_value_difference, precision),
"project": item_row.get("project") or self.get("project"),
@@ -380,8 +379,7 @@
link = frappe.utils.get_link_to_form('Quality Inspection', d.quality_inspection)
frappe.throw(_("Quality Inspection: {0} is not submitted for the item: {1} in row {2}").format(link, d.item_code, d.idx), QualityInspectionNotSubmittedError)
- qa_failed = any([r.status=="Rejected" for r in qa_doc.readings])
- if qa_failed:
+ if qa_doc.status != 'Accepted':
frappe.throw(_("Row {0}: Quality Inspection rejected for item {1}")
.format(d.idx, d.item_code), QualityInspectionRejectedError)
elif qa_required :
@@ -406,8 +404,7 @@
def set_rate_of_stock_uom(self):
if self.doctype in ["Purchase Receipt", "Purchase Invoice", "Purchase Order", "Sales Invoice", "Sales Order", "Delivery Note", "Quotation"]:
for d in self.get("items"):
- if d.conversion_factor:
- d.stock_uom_rate = d.rate / d.conversion_factor
+ d.stock_uom_rate = d.rate / (d.conversion_factor or 1)
def validate_internal_transfer(self):
if self.doctype in ('Sales Invoice', 'Delivery Note', 'Purchase Invoice', 'Purchase Receipt') \
@@ -484,7 +481,7 @@
)
message += "<br><br>"
rule_link = frappe.utils.get_link_to_form("Putaway Rule", rule)
- message += _(" Please adjust the qty or edit {0} to proceed.").format(rule_link)
+ message += _("Please adjust the qty or edit {0} to proceed.").format(rule_link)
return message
def repost_future_sle_and_gle(self):
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 5f73c55..9fae494 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -113,10 +113,12 @@
item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
if flt(item.rate_with_margin) > 0:
item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
+
if item.discount_amount and not item.discount_percentage:
- item.rate -= item.discount_amount
+ item.rate = item.rate_with_margin - item.discount_amount
else:
item.discount_amount = item.rate_with_margin - item.rate
+
elif flt(item.price_list_rate) > 0:
item.discount_amount = item.price_list_rate - item.rate
elif flt(item.price_list_rate) > 0 and not item.discount_amount:
@@ -147,7 +149,9 @@
validate_taxes_and_charges(tax)
validate_inclusive_tax(tax, self.doc)
- tax.item_wise_tax_detail = {}
+ if not self.doc.get('is_consolidated'):
+ tax.item_wise_tax_detail = {}
+
tax_fields = ["total", "tax_amount_after_discount_amount",
"tax_amount_for_current_item", "grand_total_for_current_item",
"tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
@@ -287,10 +291,13 @@
# set precision in the last item iteration
if n == len(self.doc.get("items")) - 1:
self.round_off_totals(tax)
+ self._set_in_company_currency(tax,
+ ["tax_amount", "tax_amount_after_discount_amount"])
+
+ self.round_off_base_values(tax)
self.set_cumulative_total(i, tax)
- self._set_in_company_currency(tax,
- ["total", "tax_amount", "tax_amount_after_discount_amount"])
+ self._set_in_company_currency(tax, ["total"])
# adjust Discount Amount loss in last tax iteration
if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
@@ -337,18 +344,11 @@
elif tax.charge_type == "On Item Quantity":
current_tax_amount = tax_rate * item.qty
- current_tax_amount = self.get_final_current_tax_amount(tax, current_tax_amount)
- self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
+ if not self.doc.get("is_consolidated"):
+ self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
return current_tax_amount
- def get_final_current_tax_amount(self, tax, current_tax_amount):
- # Some countries need individual tax components to be rounded
- # Handeled via regional doctypess
- if tax.account_head in frappe.flags.round_off_applicable_accounts:
- current_tax_amount = round(current_tax_amount, 0)
- return current_tax_amount
-
def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
# store tax breakup for each item
key = item.item_code or item.item_name
@@ -359,10 +359,20 @@
tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount)]
def round_off_totals(self, tax):
+ if tax.account_head in frappe.flags.round_off_applicable_accounts:
+ tax.tax_amount = round(tax.tax_amount, 0)
+ tax.tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount, 0)
+
tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount"))
tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount,
tax.precision("tax_amount"))
+ def round_off_base_values(self, tax):
+ # Round off to nearest integer based on regional settings
+ if tax.account_head in frappe.flags.round_off_applicable_accounts:
+ tax.base_tax_amount = round(tax.base_tax_amount, 0)
+ tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0)
+
def manipulate_grand_total_for_inclusive_tax(self):
# if fully inclusive taxes and diff
if self.doc.get("taxes") and any([cint(t.included_in_print_rate) for t in self.doc.get("taxes")]):
@@ -440,8 +450,9 @@
self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
def _cleanup(self):
- for tax in self.doc.get("taxes"):
- tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
+ if not self.doc.get('is_consolidated'):
+ for tax in self.doc.get("taxes"):
+ tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
def set_discount_amount(self):
if self.doc.additional_discount_percentage:
diff --git a/erpnext/controllers/website_list_for_contact.py b/erpnext/controllers/website_list_for_contact.py
index 801c405..ecf041e 100644
--- a/erpnext/controllers/website_list_for_contact.py
+++ b/erpnext/controllers/website_list_for_contact.py
@@ -25,7 +25,7 @@
if not filters: filters = []
- if doctype in ['Supplier Quotation', 'Purchase Invoice', 'Quotation']:
+ if doctype in ['Supplier Quotation', 'Purchase Invoice']:
filters.append((doctype, 'docstatus', '<', 2))
else:
filters.append((doctype, 'docstatus', '=', 1))
diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py
index 2009ebf..df73f09 100644
--- a/erpnext/crm/doctype/appointment/appointment.py
+++ b/erpnext/crm/doctype/appointment/appointment.py
@@ -38,7 +38,7 @@
number_of_agents = frappe.db.get_single_value('Appointment Booking Settings', 'number_of_agents')
if not number_of_agents == 0:
if (number_of_appointments_in_same_slot >= number_of_agents):
- frappe.throw('Time slot is not available')
+ frappe.throw(_('Time slot is not available'))
# Link lead
if not self.party:
lead = self.find_lead_by_email()
@@ -75,10 +75,10 @@
subject=_('Appointment Confirmation'))
if frappe.session.user == "Guest":
frappe.msgprint(
- 'Please check your email to confirm the appointment')
+ _('Please check your email to confirm the appointment'))
else :
frappe.msgprint(
- 'Appointment was created. But no lead was found. Please check the email to confirm')
+ _('Appointment was created. But no lead was found. Please check the email to confirm'))
def on_change(self):
# Sync Calendar
@@ -91,7 +91,7 @@
def set_verified(self, email):
if not email == self.customer_email:
- frappe.throw('Email verification failed.')
+ frappe.throw(_('Email verification failed.'))
# Create new lead
self.create_lead_and_link()
# Remove unverified status
@@ -184,7 +184,7 @@
appointment_event.insert(ignore_permissions=True)
self.calendar_event = appointment_event.name
self.save(ignore_permissions=True)
-
+
def _get_verify_url(self):
verify_route = '/book_appointment/verify'
params = {
diff --git a/erpnext/education/doctype/course_enrollment/course_enrollment.py b/erpnext/education/doctype/course_enrollment/course_enrollment.py
index f7aa6e9..2b3acf1 100644
--- a/erpnext/education/doctype/course_enrollment/course_enrollment.py
+++ b/erpnext/education/doctype/course_enrollment/course_enrollment.py
@@ -41,7 +41,7 @@
frappe.throw(_("Student is already enrolled via Course Enrollment {0}").format(
get_link_to_form("Course Enrollment", enrollment)), title=_('Duplicate Entry'))
- def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status):
+ def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status, time_taken):
result = {k: ('Correct' if v else 'Wrong') for k,v in answers.items()}
result_data = []
for key in answers:
@@ -66,7 +66,8 @@
"activity_date": frappe.utils.datetime.datetime.now(),
"result": result_data,
"score": score,
- "status": status
+ "status": status,
+ "time_taken": time_taken
}).insert(ignore_permissions = True)
def add_activity(self, content_type, content):
diff --git a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py
index 6a0dcf4..0f2ea96 100644
--- a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py
+++ b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py
@@ -75,7 +75,7 @@
"""Validates if Course Start Date is greater than Course End Date"""
if self.course_start_date > self.course_end_date:
frappe.throw(
- "Course Start Date cannot be greater than Course End Date.")
+ _("Course Start Date cannot be greater than Course End Date."))
def delete_course_schedule(self, rescheduled, reschedule_errors):
"""Delete all course schedule within the Date range and specified filters"""
diff --git a/erpnext/education/doctype/education_settings/education_settings.py b/erpnext/education/doctype/education_settings/education_settings.py
index a85d3e7..658380e 100644
--- a/erpnext/education/doctype/education_settings/education_settings.py
+++ b/erpnext/education/doctype/education_settings/education_settings.py
@@ -31,9 +31,9 @@
def validate(self):
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
if self.get('instructor_created_by')=='Naming Series':
- make_property_setter('Instructor', "naming_series", "hidden", 0, "Check")
+ make_property_setter('Instructor', "naming_series", "hidden", 0, "Check", validate_fields_for_doctype=False)
else:
- make_property_setter('Instructor', "naming_series", "hidden", 1, "Check")
+ make_property_setter('Instructor', "naming_series", "hidden", 1, "Check", validate_fields_for_doctype=False)
def update_website_context(context):
context["lms_enabled"] = frappe.get_doc("Education Settings").enable_lms
\ No newline at end of file
diff --git a/erpnext/education/doctype/fees/test_fees.py b/erpnext/education/doctype/fees/test_fees.py
index eedc2ae..c6bb704 100644
--- a/erpnext/education/doctype/fees/test_fees.py
+++ b/erpnext/education/doctype/fees/test_fees.py
@@ -9,8 +9,7 @@
from frappe.utils.make_random import get_random
from erpnext.education.doctype.program.test_program import make_program_and_linked_courses
-# test_records = frappe.get_test_records('Fees')
-
+test_dependencies = ['Company']
class TestFees(unittest.TestCase):
def test_fees(self):
diff --git a/erpnext/education/doctype/quiz/quiz.json b/erpnext/education/doctype/quiz/quiz.json
index 569c281..16d7d7e 100644
--- a/erpnext/education/doctype/quiz/quiz.json
+++ b/erpnext/education/doctype/quiz/quiz.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:title",
@@ -12,7 +13,10 @@
"quiz_configuration_section",
"passing_score",
"max_attempts",
- "grading_basis"
+ "grading_basis",
+ "column_break_7",
+ "is_time_bound",
+ "duration"
],
"fields": [
{
@@ -58,9 +62,26 @@
"fieldtype": "Select",
"label": "Grading Basis",
"options": "Latest Highest Score\nLatest Attempt"
+ },
+ {
+ "default": "0",
+ "fieldname": "is_time_bound",
+ "fieldtype": "Check",
+ "label": "Is Time-Bound"
+ },
+ {
+ "depends_on": "is_time_bound",
+ "fieldname": "duration",
+ "fieldtype": "Duration",
+ "label": "Duration"
+ },
+ {
+ "fieldname": "column_break_7",
+ "fieldtype": "Column Break"
}
],
- "modified": "2019-06-12 12:23:57.020508",
+ "links": [],
+ "modified": "2020-12-24 15:41:35.043262",
"modified_by": "Administrator",
"module": "Education",
"name": "Quiz",
diff --git a/erpnext/education/doctype/quiz_activity/quiz_activity.json b/erpnext/education/doctype/quiz_activity/quiz_activity.json
index e78db42..742c887 100644
--- a/erpnext/education/doctype/quiz_activity/quiz_activity.json
+++ b/erpnext/education/doctype/quiz_activity/quiz_activity.json
@@ -1,490 +1,163 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
+ "actions": [],
"autoname": "format:EDU-QA-{YYYY}-{#####}",
"beta": 1,
"creation": "2018-10-15 15:48:40.482821",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
- "document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
+ "field_order": [
+ "enrollment",
+ "student",
+ "column_break_3",
+ "course",
+ "section_break_5",
+ "quiz",
+ "column_break_7",
+ "status",
+ "section_break_9",
+ "result",
+ "section_break_11",
+ "activity_date",
+ "score",
+ "column_break_14",
+ "time_taken"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "enrollment",
"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": "Enrollment",
- "length": 0,
- "no_copy": 0,
"options": "Course Enrollment",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 1,
- "translatable": 0,
- "unique": 0
+ "set_only_once": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "enrollment.student",
"fieldname": "student",
"fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Student",
- "length": 0,
- "no_copy": 0,
"options": "Student",
- "permlevel": 0,
- "precision": "",
- "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
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "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,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "enrollment.course",
"fieldname": "course",
"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": "Course",
- "length": 0,
- "no_copy": 0,
"options": "Course",
- "permlevel": 0,
- "precision": "",
- "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": 1,
- "translatable": 0,
- "unique": 0
+ "set_only_once": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "section_break_5",
- "fieldtype": "Section Break",
- "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,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldtype": "Section Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "quiz",
"fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Quiz",
- "length": 0,
- "no_copy": 0,
"options": "Quiz",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 1,
- "translatable": 0,
- "unique": 0
+ "set_only_once": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "column_break_7",
- "fieldtype": "Column Break",
- "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,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "status",
"fieldtype": "Select",
- "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": "Status",
- "length": 0,
- "no_copy": 0,
"options": "\nPass\nFail",
- "permlevel": 0,
- "precision": "",
- "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
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "section_break_9",
- "fieldtype": "Section Break",
- "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,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldtype": "Section Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "result",
"fieldtype": "Table",
- "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": "Result",
- "length": 0,
- "no_copy": 0,
"options": "Quiz Result",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 1,
- "translatable": 0,
- "unique": 0
+ "set_only_once": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "activity_date",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Activity Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 1,
- "translatable": 0,
- "unique": 0
+ "set_only_once": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "score",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Score",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 1,
- "translatable": 0,
- "unique": 0
+ "set_only_once": 1
+ },
+ {
+ "fieldname": "time_taken",
+ "fieldtype": "Duration",
+ "label": "Time Taken",
+ "set_only_once": 1
+ },
+ {
+ "fieldname": "section_break_11",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_14",
+ "fieldtype": "Column Break"
}
],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-11-25 19:05:52.434437",
+ "links": [],
+ "modified": "2020-12-24 15:41:20.085380",
"modified_by": "Administrator",
"module": "Education",
"name": "Quiz Activity",
- "name_case": "",
"owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Academics User",
- "set_user_permissions": 0,
"share": 1,
- "submit": 0,
"write": 1
},
{
- "amend": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS User",
- "set_user_permissions": 0,
"share": 1,
- "submit": 0,
"write": 1
},
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Instructor",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 0
+ "share": 1
}
],
"quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py
index 81626f1..6be9e71 100644
--- a/erpnext/education/doctype/student/student.py
+++ b/erpnext/education/doctype/student/student.py
@@ -74,7 +74,6 @@
student_user.flags.ignore_permissions = True
student_user.add_roles("Student")
student_user.save()
- update_password_link = student_user.reset_password()
def update_applicant_status(self):
"""Updates Student Applicant status to Admitted"""
@@ -114,7 +113,7 @@
status = check_content_completion(content.name, content.doctype, course_enrollment_name)
progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status})
elif content.doctype == 'Quiz':
- status, score, result = check_quiz_completion(content, course_enrollment_name)
+ status, score, result, time_taken = check_quiz_completion(content, course_enrollment_name)
progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status, 'score': score, 'result': result})
return progress
diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py
index cffc396..8f51fef 100644
--- a/erpnext/education/utils.py
+++ b/erpnext/education/utils.py
@@ -194,7 +194,7 @@
return enrollment.add_activity(content_type, content)
@frappe.whitelist()
-def evaluate_quiz(quiz_response, quiz_name, course, program):
+def evaluate_quiz(quiz_response, quiz_name, course, program, time_taken):
import json
student = get_current_student()
@@ -209,7 +209,7 @@
if student:
enrollment = get_or_create_course_enrollment(course, program)
if quiz.allowed_attempt(enrollment, quiz_name):
- enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status)
+ enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status, time_taken)
return {'result': result, 'score': score, 'status': status}
else:
return None
@@ -219,8 +219,9 @@
try:
quiz = frappe.get_doc("Quiz", quiz_name)
questions = quiz.get_questions()
+ duration = quiz.duration
except:
- frappe.throw(_("Quiz {0} does not exist").format(quiz_name))
+ frappe.throw(_("Quiz {0} does not exist").format(quiz_name), frappe.DoesNotExistError)
return None
questions = [{
@@ -232,12 +233,20 @@
} for question in questions]
if has_super_access():
- return {'questions': questions, 'activity': None}
+ return {
+ 'questions': questions,
+ 'activity': None,
+ 'duration':duration
+ }
student = get_current_student()
course_enrollment = get_enrollment("course", course, student.name)
- status, score, result = check_quiz_completion(quiz, course_enrollment)
- return {'questions': questions, 'activity': {'is_complete': status, 'score': score, 'result': result}}
+ status, score, result, time_taken = check_quiz_completion(quiz, course_enrollment)
+ return {
+ 'questions': questions,
+ 'activity': {'is_complete': status, 'score': score, 'result': result, 'time_taken': time_taken},
+ 'duration': quiz.duration
+ }
def get_topic_progress(topic, course_name, program):
"""
@@ -361,15 +370,23 @@
return False
def check_quiz_completion(quiz, enrollment_name):
- attempts = frappe.get_all("Quiz Activity", filters={'enrollment': enrollment_name, 'quiz': quiz.name}, fields=["name", "activity_date", "score", "status"])
+ attempts = frappe.get_all("Quiz Activity",
+ filters={
+ 'enrollment': enrollment_name,
+ 'quiz': quiz.name
+ },
+ fields=["name", "activity_date", "score", "status", "time_taken"]
+ )
status = False if quiz.max_attempts == 0 else bool(len(attempts) >= quiz.max_attempts)
score = None
result = None
+ time_taken = None
if attempts:
if quiz.grading_basis == 'Last Highest Score':
attempts = sorted(attempts, key = lambda i: int(i.score), reverse=True)
score = attempts[0]['score']
result = attempts[0]['status']
+ time_taken = attempts[0]['time_taken']
if result == 'Pass':
status = True
- return status, score, result
\ No newline at end of file
+ return status, score, result, time_taken
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/connectors/shopify_connection.py b/erpnext/erpnext_integrations/connectors/shopify_connection.py
index f0a05ed..5d5b2e1 100644
--- a/erpnext/erpnext_integrations/connectors/shopify_connection.py
+++ b/erpnext/erpnext_integrations/connectors/shopify_connection.py
@@ -335,13 +335,13 @@
if not last_order_id:
if shopify_settings.sync_based_on == 'Date':
- url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&created_at_min={0}&since_id=0".format(
+ url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&created_at_min={0}&since_id=0".format(
get_datetime(shopify_settings.from_date)), shopify_settings)
else:
- url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&since_id={0}".format(
+ url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(
shopify_settings.from_order_id), shopify_settings)
else:
- url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings)
+ url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings)
return url
diff --git a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
index 6dedaa8..a505ee0 100644
--- a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
+++ b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
@@ -1,6 +1,7 @@
from __future__ import unicode_literals
import frappe, base64, hashlib, hmac, json
+from frappe.utils import cstr
from frappe import _
def verify_request():
@@ -146,22 +147,19 @@
def link_items(items_list, woocommerce_settings, sys_lang):
for item_data in items_list:
- item_woo_com_id = item_data.get("product_id")
+ item_woo_com_id = cstr(item_data.get("product_id"))
- if frappe.get_value("Item", {"woocommerce_id": item_woo_com_id}):
- #Edit Item
- item = frappe.get_doc("Item", {"woocommerce_id": item_woo_com_id})
- else:
+ if not frappe.db.get_value("Item", {"woocommerce_id": item_woo_com_id}, 'name'):
#Create Item
item = frappe.new_doc("Item")
+ item.item_code = _("woocommerce - {0}", sys_lang).format(item_woo_com_id)
+ item.stock_uom = woocommerce_settings.uom or _("Nos", sys_lang)
+ item.item_group = _("WooCommerce Products", sys_lang)
- item.item_name = item_data.get("name")
- item.item_code = _("woocommerce - {0}", sys_lang).format(item_data.get("product_id"))
- item.woocommerce_id = item_data.get("product_id")
- item.item_group = _("WooCommerce Products", sys_lang)
- item.stock_uom = woocommerce_settings.uom or _("Nos", sys_lang)
- item.flags.ignore_mandatory = True
- item.save()
+ item.item_name = item_data.get("name")
+ item.woocommerce_id = item_woo_com_id
+ item.flags.ignore_mandatory = True
+ item.save()
def create_sales_order(order, woocommerce_settings, customer_name, sys_lang):
new_sales_order = frappe.new_doc("Sales Order")
@@ -194,12 +192,12 @@
for item in order.get("line_items"):
woocomm_item_id = item.get("product_id")
- found_item = frappe.get_doc("Item", {"woocommerce_id": woocomm_item_id})
+ found_item = frappe.get_doc("Item", {"woocommerce_id": cstr(woocomm_item_id)})
ordered_items_tax = item.get("total_tax")
- new_sales_order.append("items",{
- "item_code": found_item.item_code,
+ new_sales_order.append("items", {
+ "item_code": found_item.name,
"item_name": found_item.item_name,
"description": found_item.item_name,
"delivery_date": new_sales_order.delivery_date,
@@ -207,7 +205,7 @@
"qty": item.get("quantity"),
"rate": item.get("price"),
"warehouse": woocommerce_settings.warehouse or default_warehouse
- })
+ })
add_tax_details(new_sales_order, ordered_items_tax, "Ordered Item tax", woocommerce_settings.tax_account)
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py
index f713684..7fd3b34 100755
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py
+++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py
@@ -7,6 +7,7 @@
from __future__ import unicode_literals
import urllib
+from urllib.parse import quote
import hashlib
import hmac
import base64
@@ -68,8 +69,9 @@
"""
md = hashlib.md5()
md.update(string)
- return base64.encodestring(md.digest()).strip('\n') if six.PY2 \
- else base64.encodebytes(md.digest()).decode().strip()
+ return base64.encodebytes(md.digest()).decode().strip()
+
+
def remove_empty(d):
"""
@@ -177,7 +179,6 @@
'SignatureMethod': 'HmacSHA256',
}
params.update(extra_data)
- quote = urllib.quote if six.PY2 else urllib.parse.quote
request_description = '&'.join(['%s=%s' % (k, quote(params[k], safe='-_.~')) for k in sorted(params)])
signature = self.calc_signature(method, request_description)
url = '%s%s?%s&Signature=%s' % (self.domain, self.uri, request_description, quote(signature))
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py
index 899b7ff..9c59840 100644
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py
+++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py
@@ -17,10 +17,12 @@
else:
self.enable_sync = 0
+ @frappe.whitelist()
def get_products_details(self):
if self.enable_amazon == 1:
frappe.enqueue('erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_methods.get_products_details')
+ @frappe.whitelist()
def get_order_details(self):
if self.enable_amazon == 1:
after_date = dateutil.parser.parse(self.after_date).strftime("%Y-%m-%d")
@@ -40,4 +42,4 @@
fieldtype='Data', insert_after='title', read_only=1, print_hide=1)]
}
- create_custom_fields(custom_fields)
\ No newline at end of file
+ create_custom_fields(custom_fields)
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py
index 2948796..3c2e59a 100644
--- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py
@@ -19,7 +19,7 @@
mode_of_payment = frappe.get_doc("Mode of Payment", "Mpesa-_Test")
self.assertTrue(frappe.db.exists("Payment Gateway Account", {'payment_gateway': "Mpesa-_Test"}))
self.assertTrue(mode_of_payment.name)
- self.assertEquals(mode_of_payment.type, "Phone")
+ self.assertEqual(mode_of_payment.type, "Phone")
def test_processing_of_account_balance(self):
mpesa_doc = create_mpesa_settings(payment_gateway_name="_Account Balance")
@@ -31,11 +31,11 @@
# test integration request creation and successful update of the status on receiving callback response
self.assertTrue(integration_request)
- self.assertEquals(integration_request.status, "Completed")
+ self.assertEqual(integration_request.status, "Completed")
# test formatting of account balance received as string to json with appropriate currency symbol
mpesa_doc.reload()
- self.assertEquals(mpesa_doc.account_balance, dumps({
+ self.assertEqual(mpesa_doc.account_balance, dumps({
"Working Account": {
"current_balance": "Sh 481,000.00",
"available_balance": "Sh 481,000.00",
@@ -60,7 +60,7 @@
pr = pos_invoice.create_payment_request()
# test payment request creation
- self.assertEquals(pr.payment_gateway, "Mpesa-Payment")
+ self.assertEqual(pr.payment_gateway, "Mpesa-Payment")
# submitting payment request creates integration requests with random id
integration_req_ids = frappe.get_all("Integration Request", filters={
@@ -75,13 +75,13 @@
# test integration request creation and successful update of the status on receiving callback response
self.assertTrue(integration_request)
- self.assertEquals(integration_request.status, "Completed")
+ self.assertEqual(integration_request.status, "Completed")
pos_invoice.reload()
integration_request.reload()
- self.assertEquals(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R")
- self.assertEquals(integration_request.status, "Completed")
-
+ self.assertEqual(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R")
+ self.assertEqual(integration_request.status, "Completed")
+
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
integration_request.delete()
pr.reload()
@@ -104,7 +104,7 @@
pr = pos_invoice.create_payment_request()
# test payment request creation
- self.assertEquals(pr.payment_gateway, "Mpesa-Payment")
+ self.assertEqual(pr.payment_gateway, "Mpesa-Payment")
# submitting payment request creates integration requests with random id
integration_req_ids = frappe.get_all("Integration Request", filters={
@@ -126,12 +126,12 @@
verify_transaction(**callback_response)
# test completion of integration request
integration_request = frappe.get_doc("Integration Request", integration_req_ids[i])
- self.assertEquals(integration_request.status, "Completed")
+ self.assertEqual(integration_request.status, "Completed")
integration_requests.append(integration_request)
# check receipt number once all the integration requests are completed
pos_invoice.reload()
- self.assertEquals(pos_invoice.mpesa_receipt_number, ', '.join(mpesa_receipt_numbers))
+ self.assertEqual(pos_invoice.mpesa_receipt_number, ', '.join(mpesa_receipt_numbers))
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
[d.delete() for d in integration_requests]
@@ -139,7 +139,7 @@
pr.cancel()
pr.delete()
pos_invoice.delete()
-
+
def test_processing_of_only_one_succes_callback_payload(self):
create_mpesa_settings(payment_gateway_name="Payment")
mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account")
@@ -155,7 +155,7 @@
pr = pos_invoice.create_payment_request()
# test payment request creation
- self.assertEquals(pr.payment_gateway, "Mpesa-Payment")
+ self.assertEqual(pr.payment_gateway, "Mpesa-Payment")
# submitting payment request creates integration requests with random id
integration_req_ids = frappe.get_all("Integration Request", filters={
@@ -175,7 +175,7 @@
verify_transaction(**callback_response)
# test completion of integration request
integration_request = frappe.get_doc("Integration Request", integration_req_ids[0])
- self.assertEquals(integration_request.status, "Completed")
+ self.assertEqual(integration_request.status, "Completed")
# now one request is completed
# second integration request fails
@@ -187,7 +187,7 @@
'name': ['not in', integration_req_ids]
}, pluck="name")
- self.assertEquals(len(new_integration_req_ids), 1)
+ self.assertEqual(len(new_integration_req_ids), 1)
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
frappe.db.sql("delete from `tabIntegration Request` where integration_request_service = 'Mpesa'")
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py
index 5f990cd..42d4b9b 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py
@@ -99,5 +99,7 @@
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, offset=len(transactions))
transactions.extend(response["transactions"])
return transactions
+ except ItemError as e:
+ raise e
except Exception:
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
index bbc2ca8..37bf282 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
@@ -16,6 +16,10 @@
new erpnext.integrations.plaidLink(frm);
});
+ frm.add_custom_button(__('Reset Plaid Link'), () => {
+ new erpnext.integrations.plaidLink(frm);
+ });
+
frm.add_custom_button(__("Sync Now"), () => {
frappe.call({
method: "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.enqueue_synchronization",
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
index 16c6573..3ef069b 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
@@ -12,6 +12,7 @@
from frappe.model.document import Document
from frappe.utils import add_months, formatdate, getdate, today
+from plaid.errors import ItemError
class PlaidSettings(Document):
@staticmethod
@@ -51,7 +52,7 @@
})
bank.insert()
except Exception:
- frappe.throw(frappe.get_traceback())
+ frappe.log_error(frappe.get_traceback(), title=_('Plaid Link Error'))
else:
bank = frappe.get_doc("Bank", response["institution"]["name"])
bank.plaid_access_token = access_token
@@ -83,16 +84,21 @@
if not acc_subtype:
add_account_subtype(account["subtype"])
- if not frappe.db.exists("Bank Account", dict(integration_id=account["id"])):
+ existing_bank_account = frappe.db.exists("Bank Account", {
+ 'account_name': account["name"],
+ 'bank': bank["bank_name"]
+ })
+
+ if not existing_bank_account:
try:
new_account = frappe.get_doc({
"doctype": "Bank Account",
"bank": bank["bank_name"],
"account": default_gl_account.account,
"account_name": account["name"],
- "account_type": account["type"] or "",
- "account_subtype": account["subtype"] or "",
- "mask": account["mask"] or "",
+ "account_type": account.get("type", ""),
+ "account_subtype": account.get("subtype", ""),
+ "mask": account.get("mask", ""),
"integration_id": account["id"],
"is_company_account": 1,
"company": company
@@ -103,10 +109,27 @@
except frappe.UniqueValidationError:
frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(account["name"]))
except Exception:
- frappe.throw(frappe.get_traceback())
+ frappe.log_error(frappe.get_traceback(), title=_("Plaid Link Error"))
+ frappe.throw(_("There was an error creating Bank Account while linking with Plaid."),
+ title=_("Plaid Link Failed"))
else:
- result.append(frappe.db.get_value("Bank Account", dict(integration_id=account["id"]), "name"))
+ try:
+ existing_account = frappe.get_doc('Bank Account', existing_bank_account)
+ existing_account.update({
+ "bank": bank["bank_name"],
+ "account_name": account["name"],
+ "account_type": account.get("type", ""),
+ "account_subtype": account.get("subtype", ""),
+ "mask": account.get("mask", ""),
+ "integration_id": account["id"]
+ })
+ existing_account.save()
+ result.append(existing_bank_account)
+ except Exception:
+ frappe.log_error(frappe.get_traceback(), title=_("Plaid Link Error"))
+ frappe.throw(_("There was an error updating Bank Account {} while linking with Plaid.").format(
+ existing_bank_account), title=_("Plaid Link Failed"))
return result
@@ -172,9 +195,16 @@
account_id = None
plaid = PlaidConnector(access_token)
- transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id)
- return transactions
+ try:
+ transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id)
+ except ItemError as e:
+ if e.code == "ITEM_LOGIN_REQUIRED":
+ msg = _("There was an error syncing transactions.") + " "
+ msg += _("Please refresh or reset the Plaid linking of the Bank {}.").format(bank) + " "
+ frappe.log_error(msg, title=_("Plaid Link Refresh Required"))
+
+ return transactions or []
def new_bank_transaction(transaction):
@@ -183,11 +213,11 @@
bank_account = frappe.db.get_value("Bank Account", dict(integration_id=transaction["account_id"]))
if float(transaction["amount"]) >= 0:
- debit = float(transaction["amount"])
- credit = 0
- else:
debit = 0
- credit = abs(float(transaction["amount"]))
+ credit = float(transaction["amount"])
+ else:
+ debit = abs(float(transaction["amount"]))
+ credit = 0
status = "Pending" if transaction["pending"] == "True" else "Settled"
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
index cbdf906..381c5e5 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
@@ -30,14 +30,14 @@
webhooks = ["orders/create", "orders/paid", "orders/fulfilled"]
# url = get_shopify_url('admin/webhooks.json', self)
created_webhooks = [d.method for d in self.webhooks]
- url = get_shopify_url('admin/api/2020-04/webhooks.json', self)
+ url = get_shopify_url('admin/api/2021-04/webhooks.json', self)
for method in webhooks:
session = get_request_session()
try:
res = session.post(url, data=json.dumps({
"webhook": {
"topic": method,
- "address": get_webhook_address(connector_name='shopify_connection', method='store_request_data'),
+ "address": get_webhook_address(connector_name='shopify_connection', method='store_request_data', force_https=True),
"format": "json"
}
}), headers=get_header(self))
@@ -56,7 +56,7 @@
deleted_webhooks = []
for d in self.webhooks:
- url = get_shopify_url('admin/api/2020-04/webhooks/{0}.json'.format(d.webhook_id), self)
+ url = get_shopify_url('admin/api/2021-04/webhooks/{0}.json'.format(d.webhook_id), self)
try:
res = session.delete(url, headers=get_header(self))
res.raise_for_status()
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py
index 7866fde..2af57f4 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py
@@ -32,10 +32,12 @@
raise e
def create_customer_address(customer, shopify_customer):
- if not shopify_customer.get("addresses"):
- return
+ addresses = shopify_customer.get("addresses", [])
- for i, address in enumerate(shopify_customer.get("addresses")):
+ if not addresses and "default_address" in shopify_customer:
+ addresses.append(shopify_customer["default_address"])
+
+ for i, address in enumerate(addresses):
address_title, address_type = get_address_title_and_type(customer.customer_name, i)
try :
frappe.get_doc({
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
index f9f0bb3..16efb6c 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
@@ -8,7 +8,7 @@
shopify_variants_attr_list = ["option1", "option2", "option3"]
def sync_item_from_shopify(shopify_settings, item):
- url = get_shopify_url("admin/api/2020-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
+ url = get_shopify_url("admin/api/2021-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
session = get_request_session()
try:
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py
index 5f471ab..6bec301 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py
@@ -22,7 +22,7 @@
frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 1)
# use the fixture data
- import_doc(frappe.get_app_path("erpnext", "erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json"))
+ import_doc(path=frappe.get_app_path("erpnext", "erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json"))
frappe.reload_doctype("Customer")
frappe.reload_doctype("Sales Order")
diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py
index 362f6cf..3840e78 100644
--- a/erpnext/erpnext_integrations/utils.py
+++ b/erpnext/erpnext_integrations/utils.py
@@ -28,7 +28,7 @@
return innerfn
-def get_webhook_address(connector_name, method, exclude_uri=False):
+def get_webhook_address(connector_name, method, exclude_uri=False, force_https=False):
endpoint = "erpnext.erpnext_integrations.connectors.{0}.{1}".format(connector_name, method)
if exclude_uri:
@@ -39,7 +39,11 @@
except RuntimeError:
url = "http://localhost:8000"
- server_url = '{uri.scheme}://{uri.netloc}/api/method/{endpoint}'.format(uri=urlparse(url), endpoint=endpoint)
+ url_data = urlparse(url)
+ scheme = "https" if force_https else url_data.scheme
+ netloc = url_data.netloc
+
+ server_url = f"{scheme}://{netloc}/api/method/{endpoint}"
return server_url
diff --git a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py
index fb72073..03e96a4 100644
--- a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py
+++ b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py
@@ -17,7 +17,7 @@
procedure_template.disabled = 1
procedure_template.save()
- self.assertEquals(frappe.db.get_value('Item', procedure_template.item, 'disabled'), 1)
+ self.assertEqual(frappe.db.get_value('Item', procedure_template.item, 'disabled'), 1)
def test_consumables(self):
patient, medical_department, practitioner = create_healthcare_docs()
diff --git a/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json b/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json
index 818f125..3fa98b6 100644
--- a/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json
+++ b/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json
@@ -1,206 +1,64 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2018-07-12 12:07:36.932333",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2018-07-12 12:07:36.932333",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "service_unit",
+ "check_in",
+ "left",
+ "check_out",
+ "invoiced"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "service_unit",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Healthcare Service Unit",
- "length": 0,
- "no_copy": 0,
- "options": "Healthcare Service Unit",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "service_unit",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Healthcare Service Unit",
+ "options": "Healthcare Service Unit",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "check_in",
- "fieldtype": "Datetime",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Check In",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "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
- },
+ "fieldname": "check_in",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "Check In"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "left",
- "fieldtype": "Check",
- "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": "Left",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "left",
+ "fieldtype": "Check",
+ "label": "Left",
+ "read_only": 1,
+ "search_index": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "check_out",
- "fieldtype": "Datetime",
- "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": "Check Out",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "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
- },
+ "fieldname": "check_out",
+ "fieldtype": "Datetime",
+ "label": "Check Out"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "fieldname": "invoiced",
- "fieldtype": "Check",
- "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": "Invoiced",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "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
+ "default": "0",
+ "fieldname": "invoiced",
+ "fieldtype": "Check",
+ "label": "Invoiced",
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-11-04 03:33:26.958713",
- "modified_by": "Administrator",
- "module": "Healthcare",
- "name": "Inpatient Occupancy",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Healthcare",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-03-18 15:08:54.634132",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Inpatient Occupancy",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "restrict_to_domain": "Healthcare",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
index aaf0e85..0e1c2ba 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
@@ -185,7 +185,7 @@
"fieldtype": "Datetime",
"in_list_view": 1,
"label": "Admitted Datetime",
- "read_only": 1
+ "permlevel": 2
},
{
"depends_on": "eval:(doc.expected_length_of_stay > 0)",
@@ -312,7 +312,7 @@
"fieldname": "inpatient_occupancies",
"fieldtype": "Table",
"options": "Inpatient Occupancy",
- "read_only": 1
+ "permlevel": 2
},
{
"fieldname": "btn_transfer",
@@ -407,12 +407,12 @@
"fieldname": "discharge_datetime",
"fieldtype": "Datetime",
"label": "Discharge Date",
- "read_only": 1
+ "permlevel": 2
}
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2021-03-18 14:44:11.689956",
+ "modified": "2021-03-18 15:59:17.318988",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Inpatient Record",
@@ -465,6 +465,37 @@
"read": 1,
"report": 1,
"role": "Nursing User"
+ },
+ {
+ "email": 1,
+ "export": 1,
+ "permlevel": 2,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Healthcare Administrator",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "email": 1,
+ "export": 1,
+ "permlevel": 2,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Physician",
+ "share": 1
+ },
+ {
+ "email": 1,
+ "export": 1,
+ "permlevel": 2,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Nursing User",
+ "share": 1
}
],
"restrict_to_domain": "Healthcare",
diff --git a/erpnext/healthcare/doctype/lab_test/test_lab_test.py b/erpnext/healthcare/doctype/lab_test/test_lab_test.py
index 79ab8a4..c9f0029 100644
--- a/erpnext/healthcare/doctype/lab_test/test_lab_test.py
+++ b/erpnext/healthcare/doctype/lab_test/test_lab_test.py
@@ -18,7 +18,7 @@
lab_template.disabled = 1
lab_template.save()
- self.assertEquals(frappe.db.get_value('Item', lab_template.item, 'disabled'), 1)
+ self.assertEqual(frappe.db.get_value('Item', lab_template.item, 'disabled'), 1)
lab_template.reload()
@@ -57,7 +57,7 @@
# sample collection should not be created
lab_test.reload()
- self.assertEquals(lab_test.sample, None)
+ self.assertEqual(lab_test.sample, None)
def test_create_lab_tests_from_sales_invoice(self):
sales_invoice = create_sales_invoice()
diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
index 2bb8a53..5f2dc48 100644
--- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
@@ -20,13 +20,13 @@
patient, medical_department, practitioner = create_healthcare_docs()
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 0)
appointment = create_appointment(patient, practitioner, nowdate())
- self.assertEquals(appointment.status, 'Open')
+ self.assertEqual(appointment.status, 'Open')
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2))
- self.assertEquals(appointment.status, 'Scheduled')
+ self.assertEqual(appointment.status, 'Scheduled')
encounter = create_encounter(appointment)
- self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
+ self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
encounter.cancel()
- self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open')
+ self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open')
def test_start_encounter(self):
patient, medical_department, practitioner = create_healthcare_docs()
diff --git a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.js b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.js
index c7074e8..f28d32c 100644
--- a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.js
+++ b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.js
@@ -39,11 +39,13 @@
},
set_score_range: function(frm) {
- let options = [];
+ let options = [''];
for(let i = frm.doc.scale_min; i <= frm.doc.scale_max; i++) {
options.push(i);
}
- frappe.meta.get_docfield('Patient Assessment Sheet', 'score', frm.doc.name).options = [''].concat(options);
+ frm.fields_dict.assessment_sheet.grid.update_docfield_property(
+ 'score', 'options', options
+ );
},
calculate_total_score: function(frm, cdt, cdn) {
@@ -83,4 +85,4 @@
score: function(frm, cdt, cdn) {
frm.events.calculate_total_score(frm, cdt, cdn);
}
-});
\ No newline at end of file
+});
diff --git a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
index 7fb159d..113fa51 100644
--- a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
+++ b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
@@ -18,24 +18,24 @@
def test_status(self):
plan = create_therapy_plan()
- self.assertEquals(plan.status, 'Not Started')
+ self.assertEqual(plan.status, 'Not Started')
session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company')
frappe.get_doc(session).submit()
- self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'In Progress')
+ self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'In Progress')
session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company')
frappe.get_doc(session).submit()
- self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed')
+ self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed')
patient, medical_department, practitioner = create_healthcare_docs()
- appointment = create_appointment(patient, practitioner, nowdate())
+ appointment = create_appointment(patient, practitioner, nowdate())
session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company', appointment.name)
session = frappe.get_doc(session)
session.submit()
- self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
+ self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
session.cancel()
- self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open')
+ self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open')
def test_therapy_plan_from_template(self):
patient = create_patient()
@@ -49,7 +49,7 @@
si.save()
therapy_plan_template_amt = frappe.db.get_value('Therapy Plan Template', template, 'total_amount')
- self.assertEquals(si.items[0].amount, therapy_plan_template_amt)
+ self.assertEqual(si.items[0].amount, therapy_plan_template_amt)
def create_therapy_plan(template=None):
diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js
index d1f72d6..42e231d 100644
--- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js
+++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js
@@ -58,8 +58,12 @@
}
if (frm.doc.therapy_plan_template) {
- frappe.meta.get_docfield('Therapy Plan Detail', 'therapy_type', frm.doc.name).read_only = 1;
- frappe.meta.get_docfield('Therapy Plan Detail', 'no_of_sessions', frm.doc.name).read_only = 1;
+ frm.fields_dict.therapy_plan_details.grid.update_docfield_property(
+ 'therapy_type', 'read_only', 1
+ );
+ frm.fields_dict.therapy_plan_details.grid.update_docfield_property(
+ 'no_of_sessions', 'read_only', 1
+ );
}
},
@@ -126,4 +130,4 @@
frm.set_value('total_sessions', total);
refresh_field('total_sessions');
}
-});
\ No newline at end of file
+});
diff --git a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py
index 03a1be8..21f6369 100644
--- a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py
+++ b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py
@@ -13,7 +13,7 @@
therapy_type.disabled = 1
therapy_type.save()
- self.assertEquals(frappe.db.get_value('Item', therapy_type.item, 'disabled'), 1)
+ self.assertEqual(frappe.db.get_value('Item', therapy_type.item, 'disabled'), 1)
def create_therapy_type():
exercise = create_exercise_type()
diff --git a/erpnext/healthcare/doctype/therapy_type/therapy_type.py b/erpnext/healthcare/doctype/therapy_type/therapy_type.py
index 6c825b8..3f6a36a 100644
--- a/erpnext/healthcare/doctype/therapy_type/therapy_type.py
+++ b/erpnext/healthcare/doctype/therapy_type/therapy_type.py
@@ -50,6 +50,7 @@
self.db_set('change_in_item', 0)
+ @frappe.whitelist()
def add_exercises(self):
exercises = self.get_exercises_for_body_parts()
last_idx = max([cint(d.idx) for d in self.get('exercises')] or [0,])
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 2e26fd2..55169df 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -262,15 +262,18 @@
],
"on_trash": "erpnext.regional.check_deletion_permission",
"validate": [
- "erpnext.regional.india.utils.validate_document_name"
+ "erpnext.regional.india.utils.validate_document_name",
+ "erpnext.regional.india.utils.update_taxable_values"
]
},
"Purchase Invoice": {
"validate": [
- "erpnext.regional.india.utils.update_grand_total_for_rcm",
+ "erpnext.regional.india.utils.validate_reverse_charge_transaction",
+ "erpnext.regional.india.utils.update_itc_availed_fields",
"erpnext.regional.united_arab_emirates.utils.update_grand_total_for_rcm",
- "erpnext.regional.united_arab_emirates.utils.validate_returns"
- ]
+ "erpnext.regional.united_arab_emirates.utils.validate_returns",
+ "erpnext.regional.india.utils.update_taxable_values"
+ ]
},
"Payment Entry": {
"on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status", "erpnext.accounts.doctype.dunning.dunning.resolve_dunning"],
@@ -306,6 +309,8 @@
"Inpatient Medication Entry"
]
+after_migrate = ["erpnext.setup.install.update_select_perm_after_install"]
+
scheduler_events = {
"cron": {
"0/30 * * * *": [
@@ -362,10 +367,8 @@
"erpnext.setup.doctype.email_digest.email_digest.send",
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms",
"erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation",
- "erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.automatically_allocate_leaves_based_on_leave_policy",
"erpnext.hr.utils.generate_leave_encashment",
"erpnext.hr.utils.allocate_earned_leaves",
- "erpnext.hr.utils.grant_leaves_automatically",
"erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall",
"erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
"erpnext.crm.doctype.lead.lead.daily_open_lead"
@@ -422,8 +425,8 @@
'erpnext.controllers.taxes_and_totals.get_regional_round_off_accounts': 'erpnext.regional.india.utils.get_regional_round_off_accounts',
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
- 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries',
- 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields'
+ 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields',
+ 'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount'
},
'United Arab Emirates': {
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data',
diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py
index 18a4fe5..f3b8a79 100644
--- a/erpnext/hr/doctype/attendance/attendance.py
+++ b/erpnext/hr/doctype/attendance/attendance.py
@@ -35,7 +35,8 @@
and docstatus != 2
""", (self.employee, getdate(self.attendance_date), self.name))
if res:
- frappe.throw(_("Attendance for employee {0} is already marked").format(self.employee))
+ frappe.throw(_("Attendance for employee {0} is already marked for the date {1}").format(
+ frappe.bold(self.employee), frappe.bold(self.attendance_date)))
def check_leave_record(self):
leave_record = frappe.db.sql("""
diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
index aa5a67f..a6fe429 100644
--- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
+++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
@@ -66,7 +66,7 @@
else:
leave_allocation = self.create_leave_allocation(leave_period, date_difference)
- self.leave_allocation=leave_allocation.name
+ self.db_set("leave_allocation", leave_allocation.name)
else:
frappe.throw(_("There is no leave period in between {0} and {1}").format(format_date(self.work_from_date), format_date(self.work_end_date)))
@@ -124,4 +124,4 @@
))
allocation.insert(ignore_permissions=True)
allocation.submit()
- return allocation
\ No newline at end of file
+ return allocation
diff --git a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
index 74ce301..3b99c57 100644
--- a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
+++ b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
@@ -68,19 +68,19 @@
filters = dict(transaction_name=compensatory_leave_request.leave_allocation)
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters)
- self.assertEquals(len(leave_ledger_entry), 1)
- self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
- self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
- self.assertEquals(leave_ledger_entry[0].leaves, 1)
+ self.assertEqual(len(leave_ledger_entry), 1)
+ self.assertEqual(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
+ self.assertEqual(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
+ self.assertEqual(leave_ledger_entry[0].leaves, 1)
# check reverse leave ledger entry on cancellation
compensatory_leave_request.cancel()
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters, order_by = 'creation desc')
- self.assertEquals(len(leave_ledger_entry), 2)
- self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
- self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
- self.assertEquals(leave_ledger_entry[0].leaves, -1)
+ self.assertEqual(len(leave_ledger_entry), 2)
+ self.assertEqual(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
+ self.assertEqual(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
+ self.assertEqual(leave_ledger_entry[0].leaves, -1)
def get_compensatory_leave_request(employee, leave_date=today()):
prev_comp_leave_req = frappe.db.get_value('Compensatory Leave Request',
diff --git a/erpnext/hr/doctype/department/department.py b/erpnext/hr/doctype/department/department.py
index 2cef509..539a360 100644
--- a/erpnext/hr/doctype/department/department.py
+++ b/erpnext/hr/doctype/department/department.py
@@ -31,7 +31,8 @@
return new
def on_update(self):
- NestedSet.on_update(self)
+ if not frappe.local.flags.ignore_update_nsm:
+ super(Department, self).on_update()
def on_trash(self):
super(Department, self).on_trash()
diff --git a/erpnext/hr/doctype/designation/designation.json b/erpnext/hr/doctype/designation/designation.json
index 4c3888b..bab6b90 100644
--- a/erpnext/hr/doctype/designation/designation.json
+++ b/erpnext/hr/doctype/designation/designation.json
@@ -182,6 +182,10 @@
"share": 1,
"submit": 0,
"write": 1
+ },
+ {
+ "read": 1,
+ "role": "Sales User"
}
],
"quick_entry": 1,
@@ -191,4 +195,4 @@
"track_changes": 0,
"track_seen": 0,
"track_views": 0
-}
\ No newline at end of file
+}
diff --git a/erpnext/hr/doctype/employee/employee_dashboard.py b/erpnext/hr/doctype/employee/employee_dashboard.py
index 0203332..285374d 100644
--- a/erpnext/hr/doctype/employee/employee_dashboard.py
+++ b/erpnext/hr/doctype/employee/employee_dashboard.py
@@ -11,8 +11,12 @@
},
'transactions': [
{
- 'label': _('Leave and Attendance'),
- 'items': ['Attendance', 'Attendance Request', 'Leave Application', 'Leave Allocation', 'Employee Checkin']
+ 'label': _('Attendance'),
+ 'items': ['Attendance', 'Attendance Request', 'Employee Checkin']
+ },
+ {
+ 'label': _('Leave'),
+ 'items': ['Leave Application', 'Leave Allocation', 'Leave Policy Assignment']
},
{
'label': _('Lifecycle'),
@@ -31,10 +35,6 @@
'items': ['Employee Benefit Application', 'Employee Benefit Claim']
},
{
- 'label': _('Evaluation'),
- 'items': ['Appraisal']
- },
- {
'label': _('Payroll'),
'items': ['Salary Structure Assignment', 'Salary Slip', 'Additional Salary', 'Timesheet','Employee Incentive', 'Retention Bonus', 'Bank Account']
},
@@ -42,5 +42,9 @@
'label': _('Training'),
'items': ['Training Event', 'Training Result', 'Training Feedback', 'Employee Skill Map']
},
+ {
+ 'label': _('Evaluation'),
+ 'items': ['Appraisal']
+ },
]
}
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.js b/erpnext/hr/doctype/employee_advance/employee_advance.js
index 5037ceb..fa4b06a 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.js
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.js
@@ -34,7 +34,7 @@
};
});
- frm.set_query('salary_component', function(doc) {
+ frm.set_query('salary_component', function() {
return {
filters: {
"type": "Deduction"
@@ -44,48 +44,49 @@
},
refresh: function(frm) {
- if (frm.doc.docstatus===1
- && (flt(frm.doc.paid_amount) < flt(frm.doc.advance_amount))
- && frappe.model.can_create("Payment Entry")) {
+ if (frm.doc.docstatus === 1 &&
+ (flt(frm.doc.paid_amount) < flt(frm.doc.advance_amount)) &&
+ frappe.model.can_create("Payment Entry")) {
frm.add_custom_button(__('Payment'),
- function() { frm.events.make_payment_entry(frm); }, __('Create'));
- }
- else if (
- frm.doc.docstatus === 1
- && flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount)
- && frappe.model.can_create("Expense Claim")
+ function () {
+ frm.events.make_payment_entry(frm);
+ }, __('Create'));
+ } else if (
+ frm.doc.docstatus === 1 &&
+ flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount) &&
+ frappe.model.can_create("Expense Claim")
) {
frm.add_custom_button(
__("Expense Claim"),
- function() {
+ function () {
frm.events.make_expense_claim(frm);
},
__('Create')
);
}
- if (frm.doc.docstatus === 1
- && (flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) && flt(frm.doc.paid_amount) != flt(frm.doc.return_amount))) {
+ if (frm.doc.docstatus === 1 &&
+ (flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) && flt(frm.doc.paid_amount) != flt(frm.doc.return_amount))) {
- if (frm.doc.repay_unclaimed_amount_from_salary == 0 && frappe.model.can_create("Journal Entry")){
- frm.add_custom_button(__("Return"), function() {
+ if (frm.doc.repay_unclaimed_amount_from_salary == 0 && frappe.model.can_create("Journal Entry")) {
+ frm.add_custom_button(__("Return"), function() {
frm.trigger('make_return_entry');
}, __('Create'));
- }else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")){
- frm.add_custom_button(__("Deduction from salary"), function() {
+ } else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")) {
+ frm.add_custom_button(__("Deduction from salary"), function() {
frm.events.make_deduction_via_additional_salary(frm);
}, __('Create'));
}
}
},
- make_deduction_via_additional_salary: function(frm){
+ make_deduction_via_additional_salary: function(frm) {
frappe.call({
method: "erpnext.hr.doctype.employee_advance.employee_advance.create_return_through_additional_salary",
args: {
doc: frm.doc
},
- callback: function (r){
+ callback: function(r) {
var doclist = frappe.model.sync(r.message);
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
}
@@ -94,7 +95,7 @@
make_payment_entry: function(frm) {
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
- if(frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) {
+ if (frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) {
method = "erpnext.hr.doctype.employee_advance.employee_advance.make_bank_entry";
}
return frappe.call({
@@ -148,11 +149,11 @@
});
},
- employee: function (frm) {
+ employee: function(frm) {
if (frm.doc.employee) {
frappe.run_serially([
- () => frm.trigger('get_employee_currency'),
- () => frm.trigger('get_pending_amount')
+ () => frm.trigger('get_employee_currency'),
+ () => frm.trigger('get_pending_amount')
]);
}
},
@@ -199,7 +200,7 @@
} else {
frm.set_value("exchange_rate", 1.0);
frm.set_df_property('exchange_rate', 'hidden', 1);
- frm.set_df_property("exchange_rate", "description", "" );
+ frm.set_df_property("exchange_rate", "description", "");
}
frm.refresh_fields();
}
@@ -215,8 +216,8 @@
callback: function(r) {
frm.set_value("exchange_rate", flt(r.message));
frm.set_df_property('exchange_rate', 'hidden', 0);
- frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
- + " = [?] " + company_currency);
+ frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency +
+ " = [?] " + company_currency);
}
});
}
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.json b/erpnext/hr/doctype/employee_advance/employee_advance.json
index 04f98d1..ea25aa7 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.json
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.json
@@ -200,7 +200,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 14:42:47.321368",
+ "modified": "2021-03-31 22:31:53.746659",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Advance",
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py b/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py
index c3b4a3a..2f493e2 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py
@@ -4,10 +4,10 @@
def get_data():
return {
'fieldname': 'employee_advance',
- 'non_standard_fieldnames': {
- 'Payment Entry': 'reference_name',
- 'Journal Entry': 'reference_name'
- },
+ 'non_standard_fieldnames': {
+ 'Payment Entry': 'reference_name',
+ 'Journal Entry': 'reference_name'
+ },
'transactions': [
{
'items': ['Expense Claim']
diff --git a/erpnext/hr/doctype/employee_referral/__init__.py b/erpnext/hr/doctype/employee_referral/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/__init__.py
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.js b/erpnext/hr/doctype/employee_referral/employee_referral.js
new file mode 100644
index 0000000..9c99bbb
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/employee_referral.js
@@ -0,0 +1,68 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on("Employee Referral", {
+ refresh: function(frm) {
+ if (frm.doc.docstatus === 1 && frm.doc.status === "Pending") {
+ frm.add_custom_button(__("Reject Employee Referral"), function() {
+ frappe.confirm(
+ __("Are you sure you want to reject the Employee Referral?"),
+ function() {
+ frm.doc.status = "Rejected";
+ frm.dirty();
+ frm.save_or_update();
+ },
+ function() {
+ window.close();
+ }
+ );
+ });
+
+ frm.add_custom_button(__("Create Job Applicant"), function() {
+ frm.events.create_job_applicant(frm);
+ }).addClass("btn-primary");
+ }
+
+ // To check whether Payment is done or not
+ if (frm.doc.docstatus === 1 && frm.doc.status === "Accepted") {
+ frappe.db.get_list("Additional Salary", {
+ filters: {
+ ref_docname: cur_frm.doc.name,
+ docstatus: 1
+ },
+ fields: ["count(name) as additional_salary_count"]
+ }).then((data) => {
+
+ let additional_salary_count = data[0].additional_salary_count;
+
+ if (frm.doc.is_applicable_for_referral_bonus && !additional_salary_count) {
+ frm.add_custom_button(__("Create Additional Salary"), function() {
+ frm.events.create_additional_salary(frm);
+ }).addClass("btn-primary");
+ }
+ });
+ }
+
+
+
+ },
+ create_job_applicant: function(frm) {
+ frappe.model.open_mapped_doc({
+ method: "erpnext.hr.doctype.employee_referral.employee_referral.create_job_applicant",
+ frm: frm
+ });
+ },
+
+ create_additional_salary: function(frm) {
+ frappe.call({
+ method: "erpnext.hr.doctype.employee_referral.employee_referral.create_additional_salary",
+ args: {
+ doc: frm.doc
+ },
+ callback: function (r) {
+ var doclist = frappe.model.sync(r.message);
+ frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
+ }
+ });
+ },
+});
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.json b/erpnext/hr/doctype/employee_referral/employee_referral.json
new file mode 100644
index 0000000..bfd404b
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/employee_referral.json
@@ -0,0 +1,294 @@
+{
+ "actions": [],
+ "autoname": "format:HR-REF-{####}",
+ "creation": "2021-03-23 14:54:45.047051",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "first_name",
+ "last_name",
+ "full_name",
+ "email",
+ "contact_no",
+ "resume",
+ "resume_link",
+ "column_break_6",
+ "date",
+ "status",
+ "for_designation",
+ "current_employer",
+ "current_job_title",
+ "referrer_details_section",
+ "referrer",
+ "referrer_name",
+ "column_break_14",
+ "is_applicable_for_referral_bonus",
+ "referral_payment_status",
+ "department",
+ "additional_information_section",
+ "qualification_reason",
+ "work_references",
+ "amended_from"
+ ],
+ "fields": [
+ {
+ "fieldname": "first_name",
+ "fieldtype": "Data",
+ "label": "First Name ",
+ "reqd": 1
+ },
+ {
+ "fieldname": "last_name",
+ "fieldtype": "Data",
+ "label": "Last Name",
+ "reqd": 1
+ },
+ {
+ "fieldname": "full_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Full Name",
+ "read_only": 1
+ },
+ {
+ "fieldname": "contact_no",
+ "fieldtype": "Data",
+ "in_standard_filter": 1,
+ "label": "Contact No.",
+ "options": "Phone"
+ },
+ {
+ "fieldname": "current_employer",
+ "fieldtype": "Data",
+ "label": "Current Employer "
+ },
+ {
+ "fieldname": "column_break_6",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "in_standard_filter": 1,
+ "label": "Date",
+ "reqd": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_standard_filter": 1,
+ "label": "Status",
+ "no_copy": 1,
+ "options": "Pending\nIn Process\nAccepted\nRejected",
+ "permlevel": 1,
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "current_job_title",
+ "fieldtype": "Data",
+ "label": "Current Job Title"
+ },
+ {
+ "fieldname": "resume",
+ "fieldtype": "Attach",
+ "label": "Resume"
+ },
+ {
+ "fieldname": "referrer_details_section",
+ "fieldtype": "Section Break",
+ "label": "Referrer Details"
+ },
+ {
+ "fetch_from": "employee.department",
+ "fieldname": "department",
+ "fieldtype": "Link",
+ "label": "Department",
+ "options": "Department",
+ "read_only": 1
+ },
+ {
+ "fieldname": "additional_information_section",
+ "fieldtype": "Section Break",
+ "label": "Additional Information "
+ },
+ {
+ "fieldname": "work_references",
+ "fieldtype": "Text Editor",
+ "label": "Work References"
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Employee Referral",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_14",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "for_designation",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "For Designation ",
+ "options": "Designation",
+ "reqd": 1
+ },
+ {
+ "fieldname": "email",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Email",
+ "options": "Email",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "default": "1",
+ "fieldname": "is_applicable_for_referral_bonus",
+ "fieldtype": "Check",
+ "label": "Is Applicable for Referral Bonus"
+ },
+ {
+ "fieldname": "qualification_reason",
+ "fieldtype": "Text Editor",
+ "label": "Why is this Candidate Qualified for this Position?"
+ },
+ {
+ "fieldname": "referrer",
+ "fieldtype": "Link",
+ "in_standard_filter": 1,
+ "label": "Referrer",
+ "options": "Employee",
+ "reqd": 1
+ },
+ {
+ "fetch_from": "referrer.employee_name",
+ "fieldname": "referrer_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Referrer Name",
+ "read_only": 1
+ },
+ {
+ "fieldname": "resume_link",
+ "fieldtype": "Data",
+ "label": "Resume Link"
+ },
+ {
+ "fieldname": "referral_payment_status",
+ "fieldtype": "Select",
+ "label": "Referral Bonus Payment Status",
+ "options": "\nUnpaid\nPaid",
+ "read_only": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2021-04-26 21:21:38.094086",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Employee Referral",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "create": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Employee",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR Manager",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR User",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "permlevel": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "permlevel": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR User",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "email": 1,
+ "export": 1,
+ "permlevel": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Employee",
+ "share": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "full_name"
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.py b/erpnext/hr/doctype/employee_referral/employee_referral.py
new file mode 100644
index 0000000..45d6872
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/employee_referral.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.utils import get_link_to_form
+from frappe.model.document import Document
+
+class EmployeeReferral(Document):
+ def validate(self):
+ self.set_full_name()
+ self.set_referral_bonus_payment_status()
+
+ def set_full_name(self):
+ self.full_name = " ".join(filter(None, [self.first_name, self.last_name]))
+
+ def set_referral_bonus_payment_status(self):
+ if not self.is_applicable_for_referral_bonus:
+ self.referral_payment_status = ""
+ else:
+ if not self.referral_payment_status:
+ self.referral_payment_status = "Unpaid"
+
+
+@frappe.whitelist()
+def create_job_applicant(source_name, target_doc=None):
+ emp_ref = frappe.get_doc("Employee Referral", source_name)
+ #just for Api call if some set status apart from default Status
+ status = emp_ref.status
+ if emp_ref.status in ["Pending", "In process"]:
+ status = "Open"
+
+ job_applicant = frappe.new_doc("Job Applicant")
+ job_applicant.employee_referral = emp_ref.name
+ job_applicant.status = status
+ job_applicant.applicant_name = emp_ref.full_name
+ job_applicant.email_id = emp_ref.email
+ job_applicant.phone_number = emp_ref.contact_no
+ job_applicant.resume_attachment = emp_ref.resume
+ job_applicant.resume_link = emp_ref.resume_link
+ job_applicant.save()
+
+ frappe.msgprint(_("Job Applicant {0} created successfully.").format(
+ get_link_to_form("Job Applicant", job_applicant.name)),
+ title=_("Success"), indicator="green")
+
+ emp_ref.db_set("status", "In Process")
+
+ return job_applicant
+
+
+@frappe.whitelist()
+def create_additional_salary(doc):
+ import json
+ from six import string_types
+
+ if isinstance(doc, string_types):
+ doc = frappe._dict(json.loads(doc))
+
+ if not frappe.db.exists("Additional Salary", {"ref_docname": doc.name}):
+ additional_salary = frappe.new_doc("Additional Salary")
+ additional_salary.employee = doc.referrer
+ additional_salary.company = frappe.db.get_value("Employee", doc.referrer, "company")
+ additional_salary.overwrite_salary_structure_amount = 0
+ additional_salary.ref_doctype = doc.doctype
+ additional_salary.ref_docname = doc.name
+
+ return additional_salary
+
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py
new file mode 100644
index 0000000..afa2a1f
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py
@@ -0,0 +1,15 @@
+from __future__ import unicode_literals
+
+def get_data():
+ return {
+ 'fieldname': 'employee_referral',
+ 'non_standard_fieldnames': {
+ 'Additional Salary': 'ref_docname'
+ },
+ 'transactions': [
+ {
+ 'items': ['Job Applicant', 'Additional Salary']
+ },
+
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral_list.js b/erpnext/hr/doctype/employee_referral/employee_referral_list.js
new file mode 100644
index 0000000..7533ab6
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/employee_referral_list.js
@@ -0,0 +1,14 @@
+frappe.listview_settings['Employee Referral'] = {
+ add_fields: ["status"],
+ get_indicator: function (doc) {
+ if (doc.status == "Pending") {
+ return [__(doc.status), "grey", "status,=," + doc.status];
+ } else if (doc.status == "In Process") {
+ return [__(doc.status), "orange", "status,=," + doc.status];
+ } else if (doc.status == "Accepted") {
+ return [__(doc.status), "green", "status,=," + doc.status];
+ } else if (doc.status == "Rejected") {
+ return [__(doc.status), "red", "status,=," + doc.status];
+ }
+ },
+};
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_referral/test_employee_referral.py b/erpnext/hr/doctype/employee_referral/test_employee_referral.py
new file mode 100644
index 0000000..a674f39
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/test_employee_referral.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+from frappe.utils import today
+from erpnext.hr.doctype.designation.test_designation import create_designation
+from erpnext.hr.doctype.employee_referral.employee_referral import create_job_applicant, create_additional_salary
+from erpnext.hr.doctype.employee.test_employee import make_employee
+import unittest
+
+class TestEmployeeReferral(unittest.TestCase):
+ def test_workflow_and_status_sync(self):
+ emp_ref = create_employee_referral()
+
+ #Check Initial status
+ self.assertTrue(emp_ref.status, "Pending")
+
+ job_applicant = create_job_applicant(emp_ref.name)
+
+
+ #Check status sync
+ emp_ref.reload()
+ self.assertTrue(emp_ref.status, "In Process")
+
+ job_applicant.reload()
+ job_applicant.status = "Rejected"
+ job_applicant.save()
+
+ emp_ref.reload()
+ self.assertTrue(emp_ref.status, "Rejected")
+
+ job_applicant.reload()
+ job_applicant.status = "Accepted"
+ job_applicant.save()
+
+ emp_ref.reload()
+ self.assertTrue(emp_ref.status, "Accepted")
+
+
+ # Check for Referral reference in additional salary
+
+ add_sal = create_additional_salary(emp_ref)
+ self.assertTrue(add_sal.ref_docname, emp_ref.name)
+
+
+def create_employee_referral():
+ emp_ref = frappe.new_doc("Employee Referral")
+ emp_ref.first_name = "Mahesh"
+ emp_ref.last_name = "Singh"
+ emp_ref.email = "a@b.c"
+ emp_ref.date = today()
+ emp_ref.for_designation = create_designation().name
+ emp_ref.referrer = make_employee("testassetmovemp@example.com", company="_Test Company")
+ emp_ref.is_applicable_for_employee_referral_compensation = 1
+ emp_ref.save()
+ emp_ref.submit()
+
+ return emp_ref
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.json b/erpnext/hr/doctype/employee_separation/employee_separation.json
index f44d830..7af20988 100644
--- a/erpnext/hr/doctype/employee_separation/employee_separation.json
+++ b/erpnext/hr/doctype/employee_separation/employee_separation.json
@@ -1,626 +1,177 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "HR-EMP-SEP-.YYYY.-.#####",
- "beta": 0,
- "creation": "2018-05-10 02:29:16.740490",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "HR-EMP-SEP-.YYYY.-.#####",
+ "creation": "2018-05-10 02:29:16.740490",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "employee",
+ "employee_name",
+ "department",
+ "designation",
+ "employee_grade",
+ "column_break_7",
+ "company",
+ "boarding_status",
+ "resignation_letter_date",
+ "project",
+ "table_for_activity",
+ "employee_separation_template",
+ "activities",
+ "notify_users_by_email",
+ "section_break_14",
+ "exit_interview",
+ "amended_from"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "employee",
- "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": "Employee",
- "length": 0,
- "no_copy": 0,
- "options": "Employee",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.employee_name",
- "fieldname": "employee_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Employee Name",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.resignation_letter_date",
- "fieldname": "resignation_letter_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Resignation Letter Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "boarding_status",
- "fieldtype": "Select",
- "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": "Status",
- "length": 0,
- "no_copy": 0,
- "options": "\nPending\nIn Process\nCompleted",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "employee",
+ "fieldtype": "Link",
+ "label": "Employee",
+ "options": "Employee",
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_bulk_edit": 0,
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_in_quick_entry": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "notify_users_by_email",
- "fieldtype": "Check",
- "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": "Notify users by email",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_7",
- "fieldtype": "Column Break",
- "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,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "employee_separation_template",
- "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": "Employee Separation Template",
- "length": 0,
- "no_copy": 0,
- "options": "Employee Separation Template",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.company",
- "fieldname": "company",
- "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",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fetch_from": "employee.employee_name",
+ "fieldname": "employee_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Employee Name",
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "project",
- "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": "Project",
- "length": 0,
- "no_copy": 0,
- "options": "Project",
- "permlevel": 0,
- "precision": "",
- "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
- },
+ "fetch_from": "employee.resignation_letter_date",
+ "fieldname": "resignation_letter_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Resignation Letter Date",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.department",
- "fieldname": "department",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Department",
- "length": 0,
- "no_copy": 0,
- "options": "Department",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "fieldname": "boarding_status",
+ "fieldtype": "Select",
+ "label": "Status",
+ "options": "\nPending\nIn Process\nCompleted",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.designation",
- "fieldname": "designation",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Designation",
- "length": 0,
- "no_copy": 0,
- "options": "Designation",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "default": "0",
+ "fieldname": "notify_users_by_email",
+ "fieldtype": "Check",
+ "label": "Notify users by email"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.grade",
- "fieldname": "employee_grade",
- "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": "Employee Grade",
- "length": 0,
- "no_copy": 0,
- "options": "Employee Grade",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_7",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "table_for_activity",
- "fieldtype": "Section Break",
- "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": "",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "employee_separation_template",
+ "fieldtype": "Link",
+ "label": "Employee Separation Template",
+ "options": "Employee Separation Template"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "activities",
- "fieldtype": "Table",
- "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": "Activities",
- "length": 0,
- "no_copy": 0,
- "options": "Employee Boarding Activity",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "employee.company",
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_14",
- "fieldtype": "Section Break",
- "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,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "exit_interview",
- "fieldtype": "Text Editor",
- "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": "Exit Interview Summary",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "employee.department",
+ "fieldname": "department",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Department",
+ "options": "Department",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "amended_from",
- "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": "Amended From",
- "length": 0,
- "no_copy": 1,
- "options": "Employee Separation",
- "permlevel": 0,
- "print_hide": 1,
- "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
+ "fetch_from": "employee.designation",
+ "fieldname": "designation",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Designation",
+ "options": "Designation",
+ "read_only": 1
+ },
+ {
+ "fetch_from": "employee.grade",
+ "fieldname": "employee_grade",
+ "fieldtype": "Link",
+ "label": "Employee Grade",
+ "options": "Employee Grade",
+ "read_only": 1
+ },
+ {
+ "fieldname": "table_for_activity",
+ "fieldtype": "Section Break",
+ "label": "Separation Activities"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "activities",
+ "fieldtype": "Table",
+ "label": "Activities",
+ "options": "Employee Boarding Activity"
+ },
+ {
+ "fieldname": "section_break_14",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "exit_interview",
+ "fieldtype": "Text Editor",
+ "label": "Exit Interview Summary"
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Employee Separation",
+ "print_hide": 1,
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-08-03 16:15:39.025898",
- "modified_by": "Administrator",
- "module": "HR",
- "name": "Employee Separation",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2021-04-28 15:58:36.020196",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Employee Separation",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "submit": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "employee_name",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "employee_name",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.py b/erpnext/hr/doctype/employee_separation/test_employee_separation.py
index 2fa114d..713fcf5 100644
--- a/erpnext/hr/doctype/employee_separation/test_employee_separation.py
+++ b/erpnext/hr/doctype/employee_separation/test_employee_separation.py
@@ -18,7 +18,7 @@
'activity_name': 'Deactivate Employee',
'role': 'HR User'
})
- separation.status = 'Pending'
+ separation.boarding_status = 'Pending'
separation.insert()
separation.submit()
self.assertEqual(separation.docstatus, 1)
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json
index e3e6e80..a268c15 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.json
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.json
@@ -14,6 +14,7 @@
"column_break_5",
"expense_approver",
"approval_status",
+ "delivery_trip",
"is_paid",
"expense_details",
"expenses",
@@ -365,13 +366,20 @@
"label": "Total Taxes and Charges",
"options": "Company:company:default_currency",
"read_only": 1
+ },
+ {
+ "depends_on": "eval: doc.delivery_trip",
+ "fieldname": "delivery_trip",
+ "fieldtype": "Link",
+ "label": "Delivery Trip",
+ "options": "Delivery Trip"
}
],
"icon": "fa fa-money",
"idx": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-09-18 17:26:09.703215",
+ "modified": "2021-05-04 05:35:12.040199",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim",
diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
index 3f22ca2..578eccf 100644
--- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
@@ -88,9 +88,9 @@
])
for gle in gl_entries:
- self.assertEquals(expected_values[gle.account][0], gle.account)
- self.assertEquals(expected_values[gle.account][1], gle.debit)
- self.assertEquals(expected_values[gle.account][2], gle.credit)
+ self.assertEqual(expected_values[gle.account][0], gle.account)
+ self.assertEqual(expected_values[gle.account][1], gle.debit)
+ self.assertEqual(expected_values[gle.account][2], gle.credit)
def test_rejected_expense_claim(self):
payable_account = get_payable_account(company_name)
@@ -104,11 +104,11 @@
})
expense_claim.submit()
- self.assertEquals(expense_claim.status, 'Rejected')
- self.assertEquals(expense_claim.total_sanctioned_amount, 0.0)
+ self.assertEqual(expense_claim.status, 'Rejected')
+ self.assertEqual(expense_claim.total_sanctioned_amount, 0.0)
gl_entry = frappe.get_all('GL Entry', {'voucher_type': 'Expense Claim', 'voucher_no': expense_claim.name})
- self.assertEquals(len(gl_entry), 0)
+ self.assertEqual(len(gl_entry), 0)
def test_expense_approver_perms(self):
user = "test_approver_perm_emp@example.com"
diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py
index 6df7bc8..8af8cea 100644
--- a/erpnext/hr/doctype/holiday_list/holiday_list.py
+++ b/erpnext/hr/doctype/holiday_list/holiday_list.py
@@ -16,6 +16,7 @@
self.validate_days()
self.total_holidays = len(self.holidays)
+ @frappe.whitelist()
def get_weekly_off_dates(self):
self.validate_values()
date_list = self.get_weekly_off_date_list(self.from_date, self.to_date)
@@ -61,6 +62,7 @@
return date_list
+ @frappe.whitelist()
def clear_table(self):
self.set('holidays', [])
diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json
index 09666c5..2396a8e 100644
--- a/erpnext/hr/doctype/hr_settings/hr_settings.json
+++ b/erpnext/hr/doctype/hr_settings/hr_settings.json
@@ -10,6 +10,7 @@
"retirement_age",
"emp_created_by",
"column_break_4",
+ "standard_working_hours",
"stop_birthday_reminders",
"expense_approver_mandatory_in_expense_claim",
"leave_settings",
@@ -22,7 +23,6 @@
"show_leaves_of_all_department_members_in_calendar",
"auto_leave_encashment",
"restrict_backdated_leave_application",
- "automatically_allocate_leaves_based_on_leave_policy",
"hiring_settings",
"check_vacancies"
],
@@ -133,23 +133,22 @@
"options": "Role"
},
{
- "default": "0",
- "fieldname": "automatically_allocate_leaves_based_on_leave_policy",
- "fieldtype": "Check",
- "label": "Automatically Allocate Leaves Based On Leave Policy"
- },
- {
"default": "1",
"fieldname": "send_leave_notification",
"fieldtype": "Check",
"label": "Send Leave Notification"
+ },
+ {
+ "fieldname": "standard_working_hours",
+ "fieldtype": "Int",
+ "label": "Standard Working Hours"
}
],
"icon": "fa fa-cog",
"idx": 1,
"issingle": 1,
"links": [],
- "modified": "2021-03-14 02:04:22.907159",
+ "modified": "2021-05-11 10:52:56.192773",
"modified_by": "Administrator",
"module": "HR",
"name": "HR Settings",
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.json b/erpnext/hr/doctype/job_applicant/job_applicant.json
index 1360fd1..bcea5f5 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant.json
+++ b/erpnext/hr/doctype/job_applicant/job_applicant.json
@@ -18,6 +18,7 @@
"job_title",
"source",
"source_name",
+ "employee_referral",
"applicant_rating",
"section_break_6",
"notes",
@@ -152,13 +153,20 @@
"fieldtype": "Link",
"label": "Currency",
"options": "Currency"
+ },
+ {
+ "fieldname": "employee_referral",
+ "fieldtype": "Link",
+ "label": "Employee Referral",
+ "options": "Employee Referral",
+ "read_only": 1
}
],
"icon": "fa fa-user",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2020-09-18 12:39:02.557563",
+ "modified": "2021-03-24 15:51:11.117517",
"modified_by": "Administrator",
"module": "HR",
"name": "Job Applicant",
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py
index a6aef04..0594ba3 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant.py
+++ b/erpnext/hr/doctype/job_applicant/job_applicant.py
@@ -28,10 +28,21 @@
if self.email_id:
validate_email_address(self.email_id, True)
+ if self.employee_referral:
+ self.set_status_for_employee_referral()
+
if not self.applicant_name and self.email_id:
guess = self.email_id.split('@')[0]
self.applicant_name = ' '.join([p.capitalize() for p in guess.split('.')])
+ def set_status_for_employee_referral(self):
+ emp_ref = frappe.get_doc("Employee Referral", self.employee_referral)
+ if self.status in ["Open", "Replied", "Hold"]:
+ emp_ref.db_set("status", "In Process")
+ elif self.status in ["Accepted", "Rejected"]:
+ emp_ref.db_set("status", self.status)
+
+
def check_email_id_is_unique(self):
if self.email_id:
names = frappe.db.sql_list("""select name from `tabJob Applicant`
diff --git a/erpnext/hr/doctype/job_offer/test_job_offer.py b/erpnext/hr/doctype/job_offer/test_job_offer.py
index 690a692..b3e1dc8 100644
--- a/erpnext/hr/doctype/job_offer/test_job_offer.py
+++ b/erpnext/hr/doctype/job_offer/test_job_offer.py
@@ -35,13 +35,13 @@
job_offer = create_job_offer(job_applicant=job_applicant.name)
job_offer.submit()
job_applicant.reload()
- self.assertEquals(job_applicant.status, "Accepted")
+ self.assertEqual(job_applicant.status, "Accepted")
# status update after rejection
job_offer.status = "Rejected"
job_offer.submit()
job_applicant.reload()
- self.assertEquals(job_applicant.status, "Rejected")
+ self.assertEqual(job_applicant.status, "Rejected")
def create_job_offer(**args):
args = frappe._dict(args)
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
index 3a300c0..ae02c51 100644
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
@@ -218,8 +218,7 @@
"fieldname": "leave_policy_assignment",
"fieldtype": "Link",
"label": "Leave Policy Assignment",
- "options": "Leave Policy Assignment",
- "read_only": 1
+ "options": "Leave Policy Assignment"
},
{
"fetch_from": "employee.company",
@@ -236,7 +235,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-01-04 18:46:13.184104",
+ "modified": "2021-04-14 15:28:26.335104",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Allocation",
diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
index 0b71036..6e7ae87 100644
--- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
@@ -96,7 +96,7 @@
carry_forward=1)
leave_allocation_1.submit()
- self.assertEquals(leave_allocation_1.unused_leaves, 10)
+ self.assertEqual(leave_allocation_1.unused_leaves, 10)
leave_allocation_1.cancel()
@@ -108,7 +108,7 @@
new_leaves_allocated=25)
leave_allocation_2.submit()
- self.assertEquals(leave_allocation_2.unused_leaves, 5)
+ self.assertEqual(leave_allocation_2.unused_leaves, 5)
def test_carry_forward_leaves_expiry(self):
frappe.db.sql("delete from `tabLeave Allocation`")
@@ -145,7 +145,7 @@
to_date=add_months(nowdate(), 12))
leave_allocation_1.submit()
- self.assertEquals(leave_allocation_1.unused_leaves, leave_allocation.new_leaves_allocated)
+ self.assertEqual(leave_allocation_1.unused_leaves, leave_allocation.new_leaves_allocated)
def test_creation_of_leave_ledger_entry_on_submit(self):
frappe.db.sql("delete from `tabLeave Allocation`")
@@ -155,10 +155,10 @@
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_allocation.name))
- self.assertEquals(len(leave_ledger_entry), 1)
- self.assertEquals(leave_ledger_entry[0].employee, leave_allocation.employee)
- self.assertEquals(leave_ledger_entry[0].leave_type, leave_allocation.leave_type)
- self.assertEquals(leave_ledger_entry[0].leaves, leave_allocation.new_leaves_allocated)
+ self.assertEqual(len(leave_ledger_entry), 1)
+ self.assertEqual(leave_ledger_entry[0].employee, leave_allocation.employee)
+ self.assertEqual(leave_ledger_entry[0].leave_type, leave_allocation.leave_type)
+ self.assertEqual(leave_ledger_entry[0].leaves, leave_allocation.new_leaves_allocated)
# check if leave ledger entry is deleted on cancellation
leave_allocation.cancel()
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index b54c971..2832e2f 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -16,36 +16,36 @@
test_dependencies = ["Leave Allocation", "Leave Block List", "Employee"]
_test_records = [
- {
- "company": "_Test Company",
- "doctype": "Leave Application",
- "employee": "_T-Employee-00001",
- "from_date": "2013-05-01",
- "description": "_Test Reason",
- "leave_type": "_Test Leave Type",
- "posting_date": "2013-01-02",
- "to_date": "2013-05-05"
- },
- {
- "company": "_Test Company",
- "doctype": "Leave Application",
- "employee": "_T-Employee-00002",
- "from_date": "2013-05-01",
- "description": "_Test Reason",
- "leave_type": "_Test Leave Type",
- "posting_date": "2013-01-02",
- "to_date": "2013-05-05"
- },
- {
- "company": "_Test Company",
- "doctype": "Leave Application",
- "employee": "_T-Employee-00001",
- "from_date": "2013-01-15",
- "description": "_Test Reason",
- "leave_type": "_Test Leave Type LWP",
- "posting_date": "2013-01-02",
- "to_date": "2013-01-15"
- }
+ {
+ "company": "_Test Company",
+ "doctype": "Leave Application",
+ "employee": "_T-Employee-00001",
+ "from_date": "2013-05-01",
+ "description": "_Test Reason",
+ "leave_type": "_Test Leave Type",
+ "posting_date": "2013-01-02",
+ "to_date": "2013-05-05"
+ },
+ {
+ "company": "_Test Company",
+ "doctype": "Leave Application",
+ "employee": "_T-Employee-00002",
+ "from_date": "2013-05-01",
+ "description": "_Test Reason",
+ "leave_type": "_Test Leave Type",
+ "posting_date": "2013-01-02",
+ "to_date": "2013-05-05"
+ },
+ {
+ "company": "_Test Company",
+ "doctype": "Leave Application",
+ "employee": "_T-Employee-00001",
+ "from_date": "2013-01-15",
+ "description": "_Test Reason",
+ "leave_type": "_Test Leave Type LWP",
+ "posting_date": "2013-01-02",
+ "to_date": "2013-01-15"
+ }
]
@@ -446,8 +446,6 @@
leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
- frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]).grant_leave_alloc_for_employee()
-
from erpnext.hr.utils import allocate_earned_leaves
i = 0
while(i<14):
@@ -516,9 +514,9 @@
leave_application.submit()
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_application.name))
- self.assertEquals(leave_ledger_entry[0].employee, leave_application.employee)
- self.assertEquals(leave_ledger_entry[0].leave_type, leave_application.leave_type)
- self.assertEquals(leave_ledger_entry[0].leaves, leave_application.total_leave_days * -1)
+ self.assertEqual(leave_ledger_entry[0].employee, leave_application.employee)
+ self.assertEqual(leave_ledger_entry[0].leave_type, leave_application.leave_type)
+ self.assertEqual(leave_ledger_entry[0].leaves, leave_application.total_leave_days * -1)
# check if leave ledger entry is deleted on cancellation
leave_application.cancel()
@@ -549,11 +547,11 @@
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', '*', filters=dict(transaction_name=leave_application.name))
- self.assertEquals(len(leave_ledger_entry), 2)
- self.assertEquals(leave_ledger_entry[0].employee, leave_application.employee)
- self.assertEquals(leave_ledger_entry[0].leave_type, leave_application.leave_type)
- self.assertEquals(leave_ledger_entry[0].leaves, -9)
- self.assertEquals(leave_ledger_entry[1].leaves, -2)
+ self.assertEqual(len(leave_ledger_entry), 2)
+ self.assertEqual(leave_ledger_entry[0].employee, leave_application.employee)
+ self.assertEqual(leave_ledger_entry[0].leave_type, leave_application.leave_type)
+ self.assertEqual(leave_ledger_entry[0].leaves, -9)
+ self.assertEqual(leave_ledger_entry[1].leaves, -2)
def test_leave_application_creation_after_expiry(self):
# test leave balance for carry forwarded allocation
@@ -566,7 +564,7 @@
create_carry_forwarded_allocation(employee, leave_type)
- self.assertEquals(get_leave_balance_on(employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)), 0)
+ self.assertEqual(get_leave_balance_on(employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)), 0)
def test_leave_approver_perms(self):
employee = get_employee()
diff --git a/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py b/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py
index 57e61b5..7401402 100644
--- a/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py
+++ b/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py
@@ -29,6 +29,7 @@
frappe.throw(_("{0} is required").format(self.meta.get_label(f)))
self.validate_from_to_dates('from_date', 'to_date')
+ @frappe.whitelist()
def allocate_leave(self):
self.validate_values()
leave_allocated_for = []
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.json b/erpnext/hr/doctype/leave_encashment/leave_encashment.json
index dcb5874..1f6c03f 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.json
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.json
@@ -154,7 +154,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 14:45:27.948207",
+ "modified": "2021-03-31 22:32:55.492327",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Encashment",
diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
index aafc964..c1da8b4 100644
--- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
@@ -44,10 +44,6 @@
salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", self.employee,
other_details={"leave_encashment_amount_per_day": 50})
- #grant Leaves
- frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]).grant_leave_alloc_for_employee()
-
-
def tearDown(self):
for dt in ["Leave Period", "Leave Allocation", "Leave Ledger Entry", "Additional Salary", "Leave Encashment", "Salary Structure", "Leave Policy"]:
frappe.db.sql("delete from `tab%s`" % dt)
@@ -88,10 +84,10 @@
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_encashment.name))
- self.assertEquals(len(leave_ledger_entry), 1)
- self.assertEquals(leave_ledger_entry[0].employee, leave_encashment.employee)
- self.assertEquals(leave_ledger_entry[0].leave_type, leave_encashment.leave_type)
- self.assertEquals(leave_ledger_entry[0].leaves, leave_encashment.encashable_days * -1)
+ self.assertEqual(len(leave_ledger_entry), 1)
+ self.assertEqual(leave_ledger_entry[0].employee, leave_encashment.employee)
+ self.assertEqual(leave_ledger_entry[0].leave_type, leave_encashment.leave_type)
+ self.assertEqual(leave_ledger_entry[0].leaves, leave_encashment.encashable_days * -1)
# check if leave ledger entry is deleted on cancellation
diff --git a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py
index e0ec4be..ff7f042 100644
--- a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py
+++ b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py
@@ -7,7 +7,7 @@
'transactions': [
{
'label': _('Leaves'),
- 'items': ['Leave Allocation']
+ 'items': ['Leave Policy Assignment', 'Leave Allocation']
},
]
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.js b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.js
index 7c32a0d..0aaf4cf 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.js
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.js
@@ -4,35 +4,22 @@
frappe.ui.form.on('Leave Policy Assignment', {
onload: function(frm) {
frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
- },
- refresh: function(frm) {
- if (frm.doc.docstatus === 1 && frm.doc.leaves_allocated === 0) {
- frm.add_custom_button(__("Grant Leave"), function() {
-
- frappe.call({
- doc: frm.doc,
- method: "grant_leave_alloc_for_employee",
- callback: function(r) {
- let leave_allocations = r.message;
- let msg = frm.events.get_success_message(leave_allocations);
- frappe.msgprint(msg);
- cur_frm.refresh();
- }
- });
- });
- }
- },
-
- get_success_message: function(leave_allocations) {
- let msg = __("Leaves has been granted successfully");
- msg += "<br><table class='table table-bordered'>";
- msg += "<tr><th>"+__('Leave Type')+"</th><th>"+__("Leave Allocation")+"</th><th>"+__("Leaves Granted")+"</th><tr>";
- for (let key in leave_allocations) {
- msg += "<tr><th>"+key+"</th><td>"+leave_allocations[key]["name"]+"</td><td>"+leave_allocations[key]["leaves"]+"</td></tr>";
- }
- msg += "</table>";
- return msg;
+ frm.set_query('leave_policy', function() {
+ return {
+ filters: {
+ "docstatus": 1
+ }
+ };
+ });
+ frm.set_query('leave_period', function() {
+ return {
+ filters: {
+ "is_active": 1,
+ "company": frm.doc.company
+ }
+ };
+ });
},
assignment_based_on: function(frm) {
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
index 462b81d..d7cb1c8 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
@@ -17,6 +17,9 @@
self.validate_policy_assignment_overlap()
self.set_dates()
+ def on_submit(self):
+ self.grant_leave_alloc_for_employee()
+
def set_dates(self):
if self.assignment_based_on == "Leave Period":
self.effective_from, self.effective_to = frappe.db.get_value("Leave Period", self.leave_period, ["from_date", "to_date"])
@@ -75,7 +78,7 @@
from_date=self.effective_from,
to_date=self.effective_to,
new_leaves_allocated=new_leaves_allocated,
- leave_period=self.leave_period or None,
+ leave_period=self.leave_period if self.assignment_based_on == "Leave Policy" else '',
leave_policy_assignment = self.name,
leave_policy = self.leave_policy,
carry_forward=carry_forward
@@ -132,22 +135,6 @@
@frappe.whitelist()
-def grant_leave_for_multiple_employees(leave_policy_assignments):
- leave_policy_assignments = json.loads(leave_policy_assignments)
- not_granted = []
- for assignment in leave_policy_assignments:
- try:
- frappe.get_doc("Leave Policy Assignment", assignment).grant_leave_alloc_for_employee()
- except Exception:
- not_granted.append(assignment)
-
- if len(not_granted):
- msg = _("Leave not Granted for Assignments:")+ bold(comma_and(not_granted)) + _(". Please Check documents")
- else:
- msg = _("Leave granted Successfully")
- frappe.msgprint(msg)
-
-@frappe.whitelist()
def create_assignment_for_multiple_employees(employees, data):
if isinstance(employees, string_types):
@@ -166,29 +153,18 @@
assignment.effective_to = getdate(data.effective_to) or None
assignment.leave_period = data.leave_period or None
assignment.carry_forward = data.carry_forward
-
assignment.save()
- assignment.submit()
+ try:
+ assignment.submit()
+ except frappe.exceptions.ValidationError:
+ continue
+
+ frappe.db.commit()
+
docs_name.append(assignment.name)
+
return docs_name
-
-def automatically_allocate_leaves_based_on_leave_policy():
- today = getdate()
- automatically_allocate_leaves_based_on_leave_policy = frappe.db.get_single_value(
- 'HR Settings', 'automatically_allocate_leaves_based_on_leave_policy'
- )
-
- pending_assignments = frappe.get_list(
- "Leave Policy Assignment",
- filters = {"docstatus": 1, "leaves_allocated": 0, "effective_from": today}
- )
-
- if len(pending_assignments) and automatically_allocate_leaves_based_on_leave_policy:
- for assignment in pending_assignments:
- frappe.get_doc("Leave Policy Assignment", assignment.name).grant_leave_alloc_for_employee()
-
-
def get_leave_type_details():
leave_type_details = frappe._dict()
leave_types = frappe.get_all("Leave Type",
@@ -197,4 +173,3 @@
for d in leave_types:
leave_type_details.setdefault(d.name, d)
return leave_type_details
-
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py
new file mode 100644
index 0000000..4bb0535
--- /dev/null
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py
@@ -0,0 +1,13 @@
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+ return {
+ 'fieldname': 'leave_policy_assignment',
+ 'transactions': [
+ {
+ 'label': _('Leaves'),
+ 'items': ['Leave Allocation']
+ },
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js
index 468f243..8fe4b8f 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js
@@ -6,6 +6,7 @@
doctype: "Employee",
target: cur_list,
setters: {
+ employee_name: '',
company: '',
department: '',
},
@@ -92,37 +93,6 @@
}
});
});
-
- list_view.page.add_inner_button(__("Grant Leaves"), function () {
- me.dialog = new frappe.ui.form.MultiSelectDialog({
- doctype: "Leave Policy Assignment",
- target: cur_list,
- setters: {
- company: '',
- employee: '',
- },
- get_query() {
- return {
- filters: {
- docstatus: ['=', 1],
- leaves_allocated: ['=', 0]
- }
- };
- },
- add_filters_group: 1,
- primary_action_label: "Grant Leaves",
- action(leave_policy_assignments) {
- frappe.call({
- method: 'erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.grant_leave_for_multiple_employees',
- async: false,
- args: {
- leave_policy_assignments: leave_policy_assignments
- }
- });
- me.dialog.hide();
- }
- });
- });
},
set_effective_date: function () {
diff --git a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
index 838e794..9a14e35 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
@@ -35,7 +35,6 @@
leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
- leave_policy_assignment_doc.grant_leave_alloc_for_employee()
leave_policy_assignment_doc.reload()
self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 1)
@@ -73,7 +72,6 @@
leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
- leave_policy_assignment_doc.grant_leave_alloc_for_employee()
leave_policy_assignment_doc.reload()
diff --git a/erpnext/hr/doctype/training_event/training_event.js b/erpnext/hr/doctype/training_event/training_event.js
index 12bc920..b7d34b1 100644
--- a/erpnext/hr/doctype/training_event/training_event.js
+++ b/erpnext/hr/doctype/training_event/training_event.js
@@ -2,23 +2,41 @@
// For license information, please see license.txt
frappe.ui.form.on('Training Event', {
- onload_post_render: function(frm) {
+ onload_post_render: function (frm) {
frm.get_field("employees").grid.set_multiple_add("employee");
},
- refresh: function(frm) {
- if(!frm.doc.__islocal) {
- frm.add_custom_button(__("Training Result"), function() {
+ refresh: function (frm) {
+ if (!frm.doc.__islocal) {
+ frm.add_custom_button(__("Training Result"), function () {
frappe.route_options = {
training_event: frm.doc.name
- }
+ };
frappe.set_route("List", "Training Result");
});
- frm.add_custom_button(__("Training Feedback"), function() {
+ frm.add_custom_button(__("Training Feedback"), function () {
frappe.route_options = {
training_event: frm.doc.name
- }
+ };
frappe.set_route("List", "Training Feedback");
});
}
}
});
+
+frappe.ui.form.on("Training Event Employee", {
+ employee: function (frm) {
+ let emp = [];
+ for (let d in frm.doc.employees) {
+ if (frm.doc.employees[d].employee) {
+ emp.push(frm.doc.employees[d].employee);
+ }
+ }
+ frm.set_query("employee", "employees", function () {
+ return {
+ filters: {
+ name: ["NOT IN", emp]
+ }
+ };
+ });
+ }
+});
diff --git a/erpnext/hr/doctype/training_event_employee/training_event_employee.json b/erpnext/hr/doctype/training_event_employee/training_event_employee.json
index e3a4064..2d313e9 100644
--- a/erpnext/hr/doctype/training_event_employee/training_event_employee.json
+++ b/erpnext/hr/doctype/training_event_employee/training_event_employee.json
@@ -1,241 +1,80 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-08-08 05:33:39.965305",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2016-08-08 05:33:39.965305",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "employee",
+ "employee_name",
+ "department",
+ "column_break_3",
+ "status",
+ "attendance",
+ "is_mandatory"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "employee",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Employee",
- "length": 0,
- "no_copy": 0,
- "options": "Employee",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "employee",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Employee",
+ "options": "Employee"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.employee_name",
- "fieldname": "employee_name",
- "fieldtype": "Read Only",
- "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": "Employee Name",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "employee.employee_name",
+ "fieldname": "employee_name",
+ "fieldtype": "Read Only",
+ "label": "Employee Name"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.department",
- "fieldname": "department",
- "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": "Department",
- "length": 0,
- "no_copy": 0,
- "options": "Department",
- "permlevel": 0,
- "precision": "",
- "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
- },
+ "fetch_from": "employee.department",
+ "fieldname": "department",
+ "fieldtype": "Link",
+ "label": "Department",
+ "options": "Department",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "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,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Open",
- "fieldname": "status",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Status",
- "length": 0,
- "no_copy": 1,
- "options": "Open\nInvited\nCompleted\nFeedback Submitted",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "default": "Open",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Status",
+ "no_copy": 1,
+ "options": "Open\nInvited\nCompleted\nFeedback Submitted"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "attendance",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Attendance",
- "length": 0,
- "no_copy": 0,
- "options": "Mandatory\nOptional",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "attendance",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Attendance",
+ "options": "Present\nAbsent"
+ },
+ {
+ "columns": 2,
+ "default": "1",
+ "fieldname": "is_mandatory",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Is Mandatory"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2019-01-30 11:28:16.170333",
- "modified_by": "Administrator",
- "module": "HR",
- "name": "Training Event Employee",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-05-21 12:41:59.336237",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Training Event Employee",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py b/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py
index 6e151d0..03b0cf3 100644
--- a/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py
+++ b/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py
@@ -5,11 +5,18 @@
import frappe
import unittest
+import erpnext
from frappe.utils import getdate
from erpnext.hr.doctype.upload_attendance.upload_attendance import get_data
from erpnext.hr.doctype.employee.test_employee import make_employee
+test_dependencies = ['Holiday List']
+
class TestUploadAttendance(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", '_Test Holiday List')
+
def test_date_range(self):
employee = make_employee("test_employee@company.com")
employee_doc = frappe.get_doc("Employee", employee)
diff --git a/erpnext/hr/notification/training_feedback/training_feedback.json b/erpnext/hr/notification/training_feedback/training_feedback.json
index 2cc064f..92b68a9 100644
--- a/erpnext/hr/notification/training_feedback/training_feedback.json
+++ b/erpnext/hr/notification/training_feedback/training_feedback.json
@@ -1,5 +1,6 @@
{
"attach_print": 0,
+ "channel": "Email",
"creation": "2017-08-11 03:17:11.769210",
"days_in_advance": 0,
"docstatus": 0,
diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.json b/erpnext/hr/notification/training_scheduled/training_scheduled.json
index 966b887..e49541e 100644
--- a/erpnext/hr/notification/training_scheduled/training_scheduled.json
+++ b/erpnext/hr/notification/training_scheduled/training_scheduled.json
@@ -11,16 +11,18 @@
"event": "Submit",
"idx": 0,
"is_standard": 1,
- "message": "<table class=\"panel-header\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div class=\"text-medium text-muted\">\n <span>{{_(\"Training Event:\")}} {{ doc.event_name }}</span>\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>\n\n<table class=\"panel-body\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div>\n <ul class=\"list-unstyled\" style=\"line-height: 1.7\">\n <li>{{ doc.introduction }}</li>\n <li>{{_(\"Event Location\")}}: <b>{{ doc.location }}</b></li>\n {% set start = frappe.utils.get_datetime(doc.start_time) %}\n {% set end = frappe.utils.get_datetime(doc.end_time) %}\n {% if start.date() == end.date() %}\n <li>{{_(\"Date\")}}: <b>{{ start.strftime(\"%A, %d %b %Y\") }}</b></li>\n <li>\n {{_(\"Timing\")}}: <b>{{ start.strftime(\"%I:%M %p\") + ' to ' + end.strftime(\"%I:%M %p\") }}</b>\n </li>\n {% else %}\n <li>{{_(\"Start Time\")}}: <b>{{ start.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n </li>\n <li>{{_(\"End Time\")}}: <b>{{ end.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n </li>\n {% endif %}\n </ul>\n {{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>",
- "modified": "2019-11-29 15:38:31.805409",
+ "message": "<table class=\"panel-header\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div class=\"text-medium text-muted\">\n <span>{{_(\"Training Event:\")}} {{ doc.event_name }}</span>\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>\n\n<table class=\"panel-body\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div>\n {{ doc.introduction }}\n <ul class=\"list-unstyled\" style=\"line-height: 1.7\">\n <li>{{_(\"Event Location\")}}: <b>{{ doc.location }}</b></li>\n {% set start = frappe.utils.get_datetime(doc.start_time) %}\n {% set end = frappe.utils.get_datetime(doc.end_time) %}\n {% if start.date() == end.date() %}\n <li>{{_(\"Date\")}}: <b>{{ start.strftime(\"%A, %d %b %Y\") }}</b></li>\n <li>\n {{_(\"Timing\")}}: <b>{{ start.strftime(\"%I:%M %p\") + ' to ' + end.strftime(\"%I:%M %p\") }}</b>\n </li>\n {% else %}\n <li>{{_(\"Start Time\")}}: <b>{{ start.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n </li>\n <li>{{_(\"End Time\")}}: <b>{{ end.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n </li>\n {% endif %}\n <li>{{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>\n {% if doc.is_mandatory %}\n <li>Note: This Training Event is mandatory</li>\n {% endif %}\n </ul>\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>",
+ "modified": "2021-05-24 16:29:13.165930",
"modified_by": "Administrator",
"module": "HR",
"name": "Training Scheduled",
"owner": "Administrator",
"recipients": [
{
- "email_by_document_field": "employee_emails"
+ "receiver_by_document_field": "employee_emails"
}
],
+ "send_system_notification": 0,
+ "send_to_all_assignees": 0,
"subject": "Training Scheduled: {{ doc.name }}"
}
\ No newline at end of file
diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.md b/erpnext/hr/notification/training_scheduled/training_scheduled.md
index 374038a..418fd49 100644
--- a/erpnext/hr/notification/training_scheduled/training_scheduled.md
+++ b/erpnext/hr/notification/training_scheduled/training_scheduled.md
@@ -35,6 +35,9 @@
</li>
{% endif %}
<li>{{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>
+ {% if doc.is_mandatory %}
+ <li>Note: This Training Event is mandatory</li>
+ {% endif %}
</ul>
</div>
</td>
diff --git a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py
index f5fece8..303c829 100644
--- a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py
+++ b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py
@@ -187,4 +187,4 @@
else:
ja_joff_map[offer.job_applicant].append(offer)
- return ja_joff_map
\ No newline at end of file
+ return ja_joff_map
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index 190eb4f..80189e8 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -32,13 +32,15 @@
project_name += self.job_applicant
else:
project_name += self.employee
+
project = frappe.get_doc({
"doctype": "Project",
"project_name": project_name,
"expected_start_date": self.date_of_joining if self.doctype == "Employee Onboarding" else self.resignation_letter_date,
"department": self.department,
"company": self.company
- }).insert(ignore_permissions=True)
+ }).insert(ignore_permissions=True, ignore_mandatory=True)
+
self.db_set("project", project.name)
self.db_set("boarding_status", "Pending")
self.reload()
@@ -498,13 +500,6 @@
total_claimed_amount = sum_of_claimed_amount[0].total_amount
return total_claimed_amount
-def grant_leaves_automatically():
- automatically_allocate_leaves_based_on_leave_policy = frappe.db.get_singles_value("HR Settings", "automatically_allocate_leaves_based_on_leave_policy")
- if automatically_allocate_leaves_based_on_leave_policy:
- lpa = frappe.db.get_all("Leave Policy Assignment", filters={"effective_from": getdate(), "docstatus": 1, "leaves_allocated":0})
- for assignment in lpa:
- frappe.get_doc("Leave Policy Assignment", assignment.name).grant_leave_alloc_for_employee()
-
def share_doc_with_approver(doc, user):
# if approver does not have permissions, share
if not frappe.has_permission(doc=doc, ptype="submit", user=user):
diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json
index f4b56a0..c5201c2 100644
--- a/erpnext/hr/workspace/hr/hr.json
+++ b/erpnext/hr/workspace/hr/hr.json
@@ -521,6 +521,15 @@
"type": "Link"
},
{
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Referral",
+ "link_to": "Employee Referral",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
@@ -814,7 +823,7 @@
"type": "Link"
}
],
- "modified": "2021-03-24 17:35:21.483297",
+ "modified": "2021-04-26 13:36:15.413819",
"modified_by": "Administrator",
"module": "HR",
"name": "HR",
diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json
index acf09f5..c9f23ca 100644
--- a/erpnext/loan_management/doctype/loan/loan.json
+++ b/erpnext/loan_management/doctype/loan/loan.json
@@ -23,6 +23,7 @@
"rate_of_interest",
"is_secured_loan",
"disbursement_date",
+ "closure_date",
"disbursed_amount",
"column_break_11",
"maximum_loan_amount",
@@ -348,18 +349,25 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"read_only": 1
+ },
+ {
+ "fieldname": "closure_date",
+ "fieldtype": "Date",
+ "label": "Closure Date",
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-11-24 12:27:23.208240",
+ "modified": "2021-04-19 18:10:32.360818",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan",
"owner": "Administrator",
"permissions": [
{
+ "amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index 83a813f..69d11a8 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -44,6 +44,7 @@
def on_cancel(self):
self.unlink_loan_security_pledge()
+ self.ignore_linked_doctypes = ['GL Entry']
def set_missing_fields(self):
if not self.company:
@@ -70,7 +71,6 @@
frappe.throw(_("Repay From Salary can be selected only for term loans"))
def make_repayment_schedule(self):
-
if not self.repayment_start_date:
frappe.throw(_("Repayment Start Date is mandatory for term loans"))
@@ -78,10 +78,9 @@
payment_date = self.repayment_start_date
balance_amount = self.loan_amount
while(balance_amount > 0):
- interest_amount = rounded(balance_amount * flt(self.rate_of_interest) / (12*100))
+ interest_amount = flt(balance_amount * flt(self.rate_of_interest) / (12*100))
principal_amount = self.monthly_repayment_amount - interest_amount
- balance_amount = rounded(balance_amount + interest_amount - self.monthly_repayment_amount)
-
+ balance_amount = flt(balance_amount + interest_amount - self.monthly_repayment_amount)
if balance_amount < 0:
principal_amount += balance_amount
balance_amount = 0.0
@@ -195,7 +194,8 @@
posting_date = getdate()
amounts = calculate_amounts(loan, posting_date)
- pending_amount = amounts['payable_amount'] + amounts['unaccrued_interest']
+ pending_amount = amounts['pending_principal_amount'] + amounts['unaccrued_interest'] + \
+ amounts['interest_amount'] + amounts['penalty_amount']
loan_type = frappe.get_value('Loan', loan, 'loan_type')
write_off_limit = frappe.get_value('Loan Type', loan_type, 'write_off_amount')
@@ -264,7 +264,7 @@
pending_amount = amounts['pending_principal_amount']
if amount and (amount > pending_amount):
- frappe.throw('Write Off amount cannot be greater than pending loan amount')
+ frappe.throw(_('Write Off amount cannot be greater than pending loan amount'))
if not amount:
amount = pending_amount
@@ -359,4 +359,4 @@
return {
"value": len(applicants),
"fieldtype": "Int"
- }
\ No newline at end of file
+ }
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index 4b9a894..fa4707c 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -55,26 +55,26 @@
def test_loan(self):
loan = frappe.get_doc("Loan", {"applicant":self.applicant1})
- self.assertEquals(loan.monthly_repayment_amount, 15052)
- self.assertEquals(loan.total_interest_payable, 21034)
- self.assertEquals(loan.total_payment, 301034)
+ self.assertEqual(loan.monthly_repayment_amount, 15052)
+ self.assertEqual(flt(loan.total_interest_payable, 0), 21034)
+ self.assertEqual(flt(loan.total_payment, 0), 301034)
schedule = loan.repayment_schedule
self.assertEqual(len(schedule), 20)
- for idx, principal_amount, interest_amount, balance_loan_amount in [[3, 13369, 1683, 227079], [19, 14941, 105, 0], [17, 14740, 312, 29785]]:
- self.assertEqual(schedule[idx].principal_amount, principal_amount)
- self.assertEqual(schedule[idx].interest_amount, interest_amount)
- self.assertEqual(schedule[idx].balance_loan_amount, balance_loan_amount)
+ for idx, principal_amount, interest_amount, balance_loan_amount in [[3, 13369, 1683, 227080], [19, 14941, 105, 0], [17, 14740, 312, 29785]]:
+ self.assertEqual(flt(schedule[idx].principal_amount, 0), principal_amount)
+ self.assertEqual(flt(schedule[idx].interest_amount, 0), interest_amount)
+ self.assertEqual(flt(schedule[idx].balance_loan_amount, 0), balance_loan_amount)
loan.repayment_method = "Repay Fixed Amount per Period"
loan.monthly_repayment_amount = 14000
loan.save()
- self.assertEquals(len(loan.repayment_schedule), 22)
- self.assertEquals(loan.total_interest_payable, 22712)
- self.assertEquals(loan.total_payment, 302712)
+ self.assertEqual(len(loan.repayment_schedule), 22)
+ self.assertEqual(flt(loan.total_interest_payable, 0), 22712)
+ self.assertEqual(flt(loan.total_payment, 0), 302712)
def test_loan_with_security(self):
@@ -89,7 +89,7 @@
loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods",
12, loan_application)
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
def test_loan_disbursement(self):
pledge = [{
@@ -102,7 +102,7 @@
create_pledge(loan_application)
loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application)
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
loan.submit()
@@ -120,8 +120,8 @@
filters = {'voucher_type': 'Loan Disbursement', 'voucher_no': loan_disbursement_entry2.name}
)
- self.assertEquals(loan.status, "Disbursed")
- self.assertEquals(loan.disbursed_amount, 1000000)
+ self.assertEqual(loan.status, "Disbursed")
+ self.assertEqual(loan.disbursed_amount, 1000000)
self.assertTrue(gl_entries1)
self.assertTrue(gl_entries2)
@@ -137,7 +137,7 @@
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
@@ -156,15 +156,15 @@
repayment_entry.submit()
penalty_amount = (accrued_interest_amount * 5 * 25) / 100
- self.assertEquals(flt(repayment_entry.penalty_amount,0), flt(penalty_amount, 0))
+ self.assertEqual(flt(repayment_entry.penalty_amount,0), flt(penalty_amount, 0))
amounts = frappe.db.get_all('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount'])
loan.load_from_db()
total_interest_paid = amounts[0]['paid_interest_amount'] + amounts[1]['paid_interest_amount']
- self.assertEquals(amounts[1]['paid_interest_amount'], repayment_entry.interest_payable)
- self.assertEquals(flt(loan.total_principal_paid, 0), flt(repayment_entry.amount_paid -
+ self.assertEqual(amounts[1]['paid_interest_amount'], repayment_entry.interest_payable)
+ self.assertEqual(flt(loan.total_principal_paid, 0), flt(repayment_entry.amount_paid -
penalty_amount - total_interest_paid, 0))
def test_loan_closure(self):
@@ -179,7 +179,7 @@
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
@@ -204,12 +204,12 @@
amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)'])
- self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0))
- self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0)
+ self.assertEqual(flt(amount, 0),flt(accrued_interest_amount, 0))
+ self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0)
request_loan_closure(loan.name)
loan.load_from_db()
- self.assertEquals(loan.status, "Loan Closure Requested")
+ self.assertEqual(loan.status, "Loan Closure Requested")
def test_loan_repayment_for_term_loan(self):
pledges = [{
@@ -241,8 +241,8 @@
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
'paid_principal_amount'])
- self.assertEquals(amounts[0], 11250.00)
- self.assertEquals(amounts[1], 78303.00)
+ self.assertEqual(amounts[0], 11250.00)
+ self.assertEqual(amounts[1], 78303.00)
def test_security_shortfall(self):
pledges = [{
@@ -268,17 +268,17 @@
loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name})
self.assertTrue(loan_security_shortfall)
- self.assertEquals(loan_security_shortfall.loan_amount, 1000000.00)
- self.assertEquals(loan_security_shortfall.security_value, 800000.00)
- self.assertEquals(loan_security_shortfall.shortfall_amount, 600000.00)
+ self.assertEqual(loan_security_shortfall.loan_amount, 1000000.00)
+ self.assertEqual(loan_security_shortfall.security_value, 800000.00)
+ self.assertEqual(loan_security_shortfall.shortfall_amount, 600000.00)
frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250
where loan_security='Test Security 2'""")
create_process_loan_security_shortfall()
loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name})
- self.assertEquals(loan_security_shortfall.status, "Completed")
- self.assertEquals(loan_security_shortfall.shortfall_amount, 0)
+ self.assertEqual(loan_security_shortfall.status, "Completed")
+ self.assertEqual(loan_security_shortfall.shortfall_amount, 0)
def test_loan_security_unpledge(self):
pledge = [{
@@ -292,7 +292,7 @@
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
@@ -312,7 +312,7 @@
request_loan_closure(loan.name)
loan.load_from_db()
- self.assertEquals(loan.status, "Loan Closure Requested")
+ self.assertEqual(loan.status, "Loan Closure Requested")
unpledge_request = unpledge_security(loan=loan.name, save=1)
unpledge_request.submit()
@@ -323,11 +323,11 @@
pledged_qty = get_pledged_security_qty(loan.name)
self.assertEqual(loan.status, 'Closed')
- self.assertEquals(sum(pledged_qty.values()), 0)
+ self.assertEqual(sum(pledged_qty.values()), 0)
amounts = amounts = calculate_amounts(loan.name, add_days(last_date, 5))
self.assertEqual(amounts['pending_principal_amount'], 0)
- self.assertEquals(amounts['payable_principal_amount'], 0.0)
+ self.assertEqual(amounts['payable_principal_amount'], 0.0)
self.assertEqual(amounts['interest_amount'], 0)
def test_partial_loan_security_unpledge(self):
@@ -346,7 +346,7 @@
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
@@ -379,7 +379,7 @@
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
unpledge_map = {'Test Security 1': 4000}
unpledge_request = unpledge_security(loan=loan.name, security_map = unpledge_map, save=1)
@@ -450,7 +450,7 @@
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
@@ -475,7 +475,7 @@
request_loan_closure(loan.name)
loan.load_from_db()
- self.assertEquals(loan.status, "Loan Closure Requested")
+ self.assertEqual(loan.status, "Loan Closure Requested")
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
self.assertEqual(amounts['pending_principal_amount'], 0.0)
@@ -492,7 +492,7 @@
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
@@ -523,33 +523,7 @@
self.assertEqual(flt(repayment_entry.total_interest_paid, 0), flt(interest_amount, 0))
def test_penalty(self):
- pledge = [{
- "loan_security": "Test Security 1",
- "qty": 4000.00
- }]
-
- loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
- create_pledge(loan_application)
-
- loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
- loan.submit()
-
- self.assertEquals(loan.loan_amount, 1000000)
-
- first_date = '2019-10-01'
- last_date = '2019-10-30'
-
- make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
- process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
-
- amounts = calculate_amounts(loan.name, add_days(last_date, 1))
- paid_amount = amounts['interest_amount']/2
-
- repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
- paid_amount)
-
- repayment_entry.submit()
-
+ loan, amounts = create_loan_scenario_for_penalty(self)
# 30 days - grace period
penalty_days = 30 - 4
penalty_applicable_amount = flt(amounts['interest_amount']/2)
@@ -559,7 +533,27 @@
calculated_penalty_amount = frappe.db.get_value('Loan Interest Accrual',
{'process_loan_interest_accrual': process, 'loan': loan.name}, 'penalty_amount')
- self.assertEquals(calculated_penalty_amount, penalty_amount)
+ self.assertEqual(loan.loan_amount, 1000000)
+ self.assertEqual(calculated_penalty_amount, penalty_amount)
+
+ def test_penalty_repayment(self):
+ loan, dummy = create_loan_scenario_for_penalty(self)
+ amounts = calculate_amounts(loan.name, '2019-11-30 00:00:00')
+
+ first_penalty = 10000
+ second_penalty = amounts['penalty_amount'] - 10000
+
+ repayment_entry = create_repayment_entry(loan.name, self.applicant2, '2019-11-30 00:00:00', 10000)
+ repayment_entry.submit()
+
+ amounts = calculate_amounts(loan.name, '2019-11-30 00:00:01')
+ self.assertEqual(amounts['penalty_amount'], second_penalty)
+
+ repayment_entry = create_repayment_entry(loan.name, self.applicant2, '2019-11-30 00:00:01', second_penalty)
+ repayment_entry.submit()
+
+ amounts = calculate_amounts(loan.name, '2019-11-30 00:00:02')
+ self.assertEqual(amounts['penalty_amount'], 0)
def test_loan_write_off_limit(self):
pledge = [{
@@ -573,7 +567,7 @@
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
@@ -595,15 +589,15 @@
amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)'])
- self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0))
- self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0)
+ self.assertEqual(flt(amount, 0),flt(accrued_interest_amount, 0))
+ self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0)
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
- self.assertEquals(flt(amounts['pending_principal_amount'], 0), 50)
+ self.assertEqual(flt(amounts['pending_principal_amount'], 0), 50)
request_loan_closure(loan.name)
loan.load_from_db()
- self.assertEquals(loan.status, "Loan Closure Requested")
+ self.assertEqual(loan.status, "Loan Closure Requested")
def test_loan_amount_write_off(self):
pledge = [{
@@ -617,7 +611,7 @@
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
@@ -639,18 +633,44 @@
amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)'])
- self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0))
- self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0)
+ self.assertEqual(flt(amount, 0),flt(accrued_interest_amount, 0))
+ self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0)
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
- self.assertEquals(flt(amounts['pending_principal_amount'], 0), 100)
+ self.assertEqual(flt(amounts['pending_principal_amount'], 0), 100)
we = make_loan_write_off(loan.name, amount=amounts['pending_principal_amount'])
we.submit()
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
- self.assertEquals(flt(amounts['pending_principal_amount'], 0), 0)
+ self.assertEqual(flt(amounts['pending_principal_amount'], 0), 0)
+def create_loan_scenario_for_penalty(doc):
+ pledge = [{
+ "loan_security": "Test Security 1",
+ "qty": 4000.00
+ }]
+
+ loan_application = create_loan_application('_Test Company', doc.applicant2, 'Demand Loan', pledge)
+ create_pledge(loan_application)
+ loan = create_demand_loan(doc.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
+ loan.submit()
+
+ first_date = '2019-10-01'
+ last_date = '2019-10-30'
+
+ make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
+ process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
+
+ amounts = calculate_amounts(loan.name, add_days(last_date, 1))
+ paid_amount = amounts['interest_amount']/2
+
+ repayment_entry = create_repayment_entry(loan.name, doc.applicant2, add_days(last_date, 5),
+ paid_amount)
+
+ repayment_entry.submit()
+
+ return loan, amounts
def create_loan_accounts():
if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"):
diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.json b/erpnext/loan_management/doctype/loan_application/loan_application.json
index a353a77..f91fa07 100644
--- a/erpnext/loan_management/doctype/loan_application/loan_application.json
+++ b/erpnext/loan_management/doctype/loan_application/loan_application.json
@@ -212,15 +212,17 @@
"read_only": 1
}
],
+ "index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-03-01 10:21:44.413353",
+ "modified": "2021-04-19 18:24:40.119647",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Application",
"owner": "Administrator",
"permissions": [
{
+ "amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
@@ -235,6 +237,7 @@
"write": 1
},
{
+ "amend": 1,
"create": 1,
"delete": 1,
"email": 1,
diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
index cd5df4d..7811d56 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
+++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
@@ -20,6 +20,10 @@
"cost_center",
"customer_details_section",
"bank_account",
+ "disbursement_references_section",
+ "reference_date",
+ "column_break_17",
+ "reference_number",
"amended_from"
],
"fields": [
@@ -126,18 +130,38 @@
{
"fieldname": "column_break_8",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "disbursement_references_section",
+ "fieldtype": "Section Break",
+ "label": "Disbursement References"
+ },
+ {
+ "fieldname": "reference_date",
+ "fieldtype": "Date",
+ "label": "Reference Date"
+ },
+ {
+ "fieldname": "column_break_17",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "reference_number",
+ "fieldtype": "Data",
+ "label": "Reference Number"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-11-06 10:04:30.882322",
+ "modified": "2021-04-19 18:09:32.175355",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Disbursement",
"owner": "Administrator",
"permissions": [
{
+ "amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
@@ -152,6 +176,7 @@
"write": 1
},
{
+ "amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
diff --git a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py
index a875387..da56710 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py
+++ b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py
@@ -87,7 +87,7 @@
loan = create_demand_loan(self.applicant, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
@@ -114,5 +114,5 @@
per_day_interest = get_per_day_interest(1500000, 13.5, '2019-10-30')
interest = per_day_interest * 15
- self.assertEquals(amounts['pending_principal_amount'], 1500000)
- self.assertEquals(amounts['interest_amount'], flt(interest + previous_interest, 2))
+ self.assertEqual(amounts['pending_principal_amount'], 1500000)
+ self.assertEqual(amounts['interest_amount'], flt(interest + previous_interest, 2))
diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json
index 185bf7a..30e2328 100644
--- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json
+++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json
@@ -185,13 +185,14 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-01-10 00:15:21.544140",
+ "modified": "2021-04-19 18:26:38.871889",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Interest Accrual",
"owner": "Administrator",
"permissions": [
{
+ "amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
@@ -206,6 +207,7 @@
"write": 1
},
{
+ "amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py
index 85e008a..eb626f3 100644
--- a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py
+++ b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py
@@ -52,7 +52,7 @@
process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
loan_interest_accural = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name})
- self.assertEquals(flt(loan_interest_accural.interest_amount, 0), flt(accrued_interest_amount, 0))
+ self.assertEqual(flt(loan_interest_accural.interest_amount, 0), flt(accrued_interest_amount, 0))
def test_accumulated_amounts(self):
pledge = [{
@@ -76,7 +76,7 @@
process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
loan_interest_accrual = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name})
- self.assertEquals(flt(loan_interest_accrual.interest_amount, 0), flt(accrued_interest_amount, 0))
+ self.assertEqual(flt(loan_interest_accrual.interest_amount, 0), flt(accrued_interest_amount, 0))
next_start_date = '2019-10-31'
next_end_date = '2019-11-29'
@@ -90,4 +90,4 @@
loan_interest_accrual = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name,
'process_loan_interest_accrual': process})
- self.assertEquals(flt(loan_interest_accrual.total_pending_interest_amount, 0), total_pending_interest_amount)
+ self.assertEqual(flt(loan_interest_accrual.total_pending_interest_amount, 0), total_pending_interest_amount)
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
index 86ea59d..6479853 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
@@ -239,20 +239,23 @@
{
"fieldname": "total_penalty_paid",
"fieldtype": "Currency",
+ "hidden": 1,
"label": "Total Penalty Paid",
- "options": "Company:company:default_currency"
+ "options": "Company:company:default_currency",
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-04-05 13:45:19.137896",
+ "modified": "2021-04-19 18:10:00.935364",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Repayment",
"owner": "Administrator",
"permissions": [
{
+ "amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
@@ -267,6 +270,7 @@
"write": 1
},
{
+ "amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 5d57ced..3d99b1f 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -75,7 +75,7 @@
"docstatus": 1, "against_loan": self.against_loan}, 'posting_date')
if future_repayment_date:
- frappe.throw("Repayment already made till date {0}".format(getdate(future_repayment_date)))
+ frappe.throw("Repayment already made till date {0}".format(get_datetime(future_repayment_date)))
def validate_amount(self):
precision = cint(frappe.db.get_default("currency_precision")) or 2
@@ -83,10 +83,6 @@
if not self.amount_paid:
frappe.throw(_("Amount paid cannot be zero"))
- if not self.shortfall_amount and self.amount_paid < self.penalty_amount:
- msg = _("Paid amount cannot be less than {0}").format(self.penalty_amount)
- frappe.throw(msg)
-
def book_unaccrued_interest(self):
precision = cint(frappe.db.get_default("currency_precision")) or 2
if self.total_interest_paid > self.interest_payable:
@@ -231,6 +227,14 @@
gle_map = []
loan_details = frappe.get_doc("Loan", self.against_loan)
+ if self.shortfall_amount and self.amount_paid > self.shortfall_amount:
+ remarks = _("Shortfall Repayment of {0}.\nRepayment against Loan: {1}").format(self.shortfall_amount,
+ self.against_loan)
+ elif self.shortfall_amount:
+ remarks = _("Shortfall Repayment of {0}").format(self.shortfall_amount)
+ else:
+ remarks = _("Repayment against Loan: ") + self.against_loan
+
if self.total_penalty_paid:
gle_map.append(
self.get_gl_dict({
@@ -271,7 +275,7 @@
"debit_in_account_currency": self.amount_paid,
"against_voucher_type": "Loan",
"against_voucher": self.against_loan,
- "remarks": _("Repayment against Loan: ") + self.against_loan,
+ "remarks": remarks,
"cost_center": self.cost_center,
"posting_date": getdate(self.posting_date)
})
@@ -287,7 +291,7 @@
"credit_in_account_currency": self.amount_paid,
"against_voucher_type": "Loan",
"against_voucher": self.against_loan,
- "remarks": _("Repayment against Loan: ") + self.against_loan,
+ "remarks": remarks,
"cost_center": self.cost_center,
"posting_date": getdate(self.posting_date)
})
@@ -338,6 +342,18 @@
return unpaid_accrued_entries
+def get_penalty_details(against_loan):
+ penalty_details = frappe.db.sql("""
+ SELECT posting_date, (penalty_amount - total_penalty_paid) as pending_penalty_amount
+ FROM `tabLoan Repayment` where posting_date >= (SELECT MAX(posting_date) from `tabLoan Repayment`
+ where against_loan = %s) and docstatus = 1 and against_loan = %s
+ """, (against_loan, against_loan))
+
+ if penalty_details:
+ return penalty_details[0][0], flt(penalty_details[0][1])
+ else:
+ return None, 0
+
# This function returns the amounts that are payable at the time of loan repayment based on posting date
# So it pulls all the unpaid Loan Interest Accrual Entries and calculates the penalty if applicable
@@ -348,6 +364,7 @@
loan_type_details = frappe.get_doc("Loan Type", against_loan_doc.loan_type)
accrued_interest_entries = get_accrued_interest_entries(against_loan_doc.name, posting_date)
+ computed_penalty_date, pending_penalty_amount = get_penalty_details(against_loan)
pending_accrual_entries = {}
total_pending_interest = 0
@@ -362,8 +379,13 @@
# and if no_of_late days are positive then penalty is levied
due_date = add_days(entry.posting_date, 1)
- no_of_late_days = date_diff(posting_date,
- add_days(due_date, loan_type_details.grace_period_in_days)) + 1
+ due_date_after_grace_period = add_days(due_date, loan_type_details.grace_period_in_days)
+
+ # Consider one day after already calculated penalty
+ if computed_penalty_date and getdate(computed_penalty_date) >= due_date_after_grace_period:
+ due_date_after_grace_period = add_days(computed_penalty_date, 1)
+
+ no_of_late_days = date_diff(posting_date, due_date_after_grace_period) + 1
if no_of_late_days > 0 and (not against_loan_doc.repay_from_salary) and entry.accrual_type == 'Regular':
penalty_amount += (entry.interest_amount * (loan_type_details.penalty_interest_rate / 100) * no_of_late_days)
@@ -401,7 +423,7 @@
amounts["pending_principal_amount"] = flt(pending_principal_amount, precision)
amounts["payable_principal_amount"] = flt(payable_principal_amount, precision)
amounts["interest_amount"] = flt(total_pending_interest, precision)
- amounts["penalty_amount"] = flt(penalty_amount, precision)
+ amounts["penalty_amount"] = flt(penalty_amount + pending_penalty_amount, precision)
amounts["payable_amount"] = flt(payable_principal_amount + total_pending_interest + penalty_amount, precision)
amounts["pending_accrual_entries"] = pending_accrual_entries
amounts["unaccrued_interest"] = flt(unaccrued_interest, precision)
@@ -413,7 +435,6 @@
@frappe.whitelist()
def calculate_amounts(against_loan, posting_date, payment_type=''):
-
amounts = {
'penalty_amount': 0.0,
'interest_amount': 0.0,
diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json
index 7dd5725..18bd4ae 100644
--- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json
+++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json
@@ -160,13 +160,14 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-09-04 22:38:19.894488",
+ "modified": "2021-04-19 18:23:16.953305",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Security Pledge",
"owner": "Administrator",
"permissions": [
{
+ "amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
@@ -181,6 +182,7 @@
"write": 1
},
{
+ "amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json
index 2e2b251..92923bb 100644
--- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json
+++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json
@@ -126,13 +126,14 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-09-04 22:39:57.756146",
+ "modified": "2021-04-19 18:12:01.401744",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Security Unpledge",
"owner": "Administrator",
"permissions": [
{
+ "amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
@@ -147,6 +148,7 @@
"write": 1
},
{
+ "amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
index c4c2d68..b24dc2f 100644
--- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
+++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
@@ -6,7 +6,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
-from frappe.utils import get_datetime, flt
+from frappe.utils import get_datetime, flt, getdate
import json
from six import iteritems
from erpnext.loan_management.doctype.loan_security_price.loan_security_price import get_loan_security_price
@@ -113,7 +113,11 @@
pledged_qty += qty
if not pledged_qty:
- frappe.db.set_value('Loan', self.loan, 'status', 'Closed')
+ frappe.db.set_value('Loan', self.loan,
+ {
+ 'status': 'Closed',
+ 'closure_date': getdate()
+ })
@frappe.whitelist()
def get_pledged_security_qty(loan):
diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.json b/erpnext/loan_management/doctype/loan_type/loan_type.json
index 3ef5304..c0a5d2c 100644
--- a/erpnext/loan_management/doctype/loan_type/loan_type.json
+++ b/erpnext/loan_management/doctype/loan_type/loan_type.json
@@ -154,13 +154,14 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-01-17 06:51:26.082879",
+ "modified": "2021-04-19 18:10:57.368490",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Type",
"owner": "Administrator",
"permissions": [
{
+ "amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
diff --git a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json
index 4617a62..4ca9ef1 100644
--- a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json
+++ b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json
@@ -116,13 +116,14 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-10-26 07:13:43.663924",
+ "modified": "2021-04-19 18:11:27.759862",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Write Off",
"owner": "Administrator",
"permissions": [
{
+ "amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
@@ -137,6 +138,7 @@
"write": 1
},
{
+ "amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
diff --git a/erpnext/manufacturing/dashboard_fixtures.py b/erpnext/manufacturing/dashboard_fixtures.py
index 0e9a21c..7ba43d6 100644
--- a/erpnext/manufacturing/dashboard_fixtures.py
+++ b/erpnext/manufacturing/dashboard_fixtures.py
@@ -43,7 +43,6 @@
return [{
"doctype": "Dashboard Chart",
"based_on": "modified",
- "time_interval": "Yearly",
"chart_type": "Sum",
"chart_name": _("Produced Quantity"),
"name": "Produced Quantity",
@@ -60,7 +59,6 @@
}, {
"doctype": "Dashboard Chart",
"based_on": "creation",
- "time_interval": "Yearly",
"chart_type": "Sum",
"chart_name": _("Completed Operation"),
"name": "Completed Operation",
@@ -238,4 +236,4 @@
"label": _("Monthly Quality Inspections"),
"show_percentage_stats": 1,
"stats_time_interval": "Weekly"
- }]
\ No newline at end of file
+ }]
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index fbfd801..a09a5e3 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -29,7 +29,10 @@
frm.set_query("item", function() {
return {
- query: "erpnext.manufacturing.doctype.bom.bom.item_query"
+ query: "erpnext.manufacturing.doctype.bom.bom.item_query",
+ filters: {
+ "is_stock_item": 1
+ }
};
});
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 979f7ca..d1f6385 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -973,6 +973,9 @@
if not has_variants:
query_filters["has_variants"] = 0
+ if filters and filters.get("is_stock_item"):
+ query_filters["is_stock_item"] = 1
+
return frappe.get_all("Item",
fields = fields, filters=query_filters,
or_filters = or_cond_filters, order_by=order_by,
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index cd61d2a..e1cca9e 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -93,15 +93,15 @@
base_raw_material_cost = raw_material_cost * flt(bom.conversion_rate, bom.precision("conversion_rate"))
base_op_cost = op_cost * flt(bom.conversion_rate, bom.precision("conversion_rate"))
- # test amounts in selected currency
- self.assertEqual(bom.operating_cost, op_cost)
- self.assertEqual(bom.raw_material_cost, raw_material_cost)
- self.assertEqual(bom.total_cost, raw_material_cost + op_cost)
+ # test amounts in selected currency, almostEqual checks for 7 digits by default
+ self.assertAlmostEqual(bom.operating_cost, op_cost)
+ self.assertAlmostEqual(bom.raw_material_cost, raw_material_cost)
+ self.assertAlmostEqual(bom.total_cost, raw_material_cost + op_cost)
# test amounts in selected currency
- self.assertEqual(bom.base_operating_cost, base_op_cost)
- self.assertEqual(bom.base_raw_material_cost, base_raw_material_cost)
- self.assertEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost)
+ self.assertAlmostEqual(bom.base_operating_cost, base_op_cost)
+ self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost)
+ self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost)
def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self):
frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1)
@@ -223,7 +223,7 @@
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
bom_items = sorted([d.item_code for d in bom.items if d.sourced_by_supplier != 1])
supplied_items = sorted([d.rm_item_code for d in po.supplied_items])
- self.assertEquals(bom_items, supplied_items)
+ self.assertEqual(bom_items, supplied_items)
def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
index 742d18c..8fbcd4e 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
+++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
@@ -53,7 +53,9 @@
rate=%s, amount=stock_qty*%s where bom_no = %s and docstatus < 2 and parenttype='BOM'""",
(self.new_bom, unit_cost, unit_cost, self.current_bom))
- def get_parent_boms(self, bom, bom_list=[]):
+ def get_parent_boms(self, bom, bom_list=None):
+ if bom_list is None:
+ bom_list = []
data = frappe.db.sql("""SELECT DISTINCT parent FROM `tabBOM Item`
WHERE bom_no = %s AND docstatus < 2 AND parenttype='BOM'""", bom)
@@ -106,4 +108,4 @@
for bom in bom_list:
frappe.get_doc("BOM", bom).update_cost(update_parent=False, from_child_bom=True)
- frappe.db.auto_commit_on_many_writes = 0
\ No newline at end of file
+ frappe.db.auto_commit_on_many_writes = 0
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
index ac9a409..80d1cdf 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
+++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
@@ -45,16 +45,16 @@
else:
doc = frappe.get_doc("BOM", bom_no)
- self.assertEquals(doc.total_cost, 200)
+ self.assertEqual(doc.total_cost, 200)
frappe.db.set_value("Item", "BOM Cost Test Item 2", "valuation_rate", 200)
update_cost()
doc.load_from_db()
- self.assertEquals(doc.total_cost, 300)
+ self.assertEqual(doc.total_cost, 300)
frappe.db.set_value("Item", "BOM Cost Test Item 2", "valuation_rate", 100)
update_cost()
doc.load_from_db()
- self.assertEquals(doc.total_cost, 200)
+ self.assertEqual(doc.total_cost, 200)
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 92074c6..fb26062 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -433,6 +433,7 @@
def make_stock_entry(source_name, target_doc=None):
def update_item(obj, target, source_parent):
target.t_warehouse = source_parent.wip_warehouse
+ target.conversion_factor = 1
def set_missing_values(source, target):
target.purpose = "Material Transfer for Manufacture"
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index cef2d8b..a3e23a6 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -561,7 +561,6 @@
'item_name': row.item_name,
'quantity': required_qty,
'required_bom_qty': total_qty,
- 'description': row.description,
'stock_uom': row.get("stock_uom"),
'warehouse': warehouse or row.get('source_warehouse') \
or row.get('default_warehouse') or item_group_defaults.get("default_warehouse"),
@@ -766,7 +765,7 @@
to_enable = frappe.bold(_("Ignore Existing Projected Quantity"))
warehouse = frappe.bold(doc.get('for_warehouse'))
message = _("As there are sufficient raw materials, Material Request is not required for Warehouse {0}.").format(warehouse) + "<br><br>"
- message += _(" If you still want to proceed, please enable {0}.").format(to_enable)
+ message += _("If you still want to proceed, please enable {0}.").format(to_enable)
frappe.msgprint(message, title=_("Note"))
diff --git a/erpnext/manufacturing/doctype/routing/routing.js b/erpnext/manufacturing/doctype/routing/routing.js
index 9b1a8ca..032c9cd 100644
--- a/erpnext/manufacturing/doctype/routing/routing.js
+++ b/erpnext/manufacturing/doctype/routing/routing.js
@@ -11,10 +11,9 @@
},
display_sequence_id_column: function(frm) {
- frappe.meta.get_docfield("BOM Operation", "sequence_id",
- frm.doc.name).in_list_view = true;
-
- frm.fields_dict.operations.grid.refresh();
+ frm.fields_dict.operations.grid.update_docfield_property(
+ 'sequence_id', 'in_list_view', 1
+ );
},
calculate_operating_cost: function(frm, child) {
@@ -69,4 +68,4 @@
const d = locals[cdt][cdn];
frm.events.calculate_operating_cost(frm, d);
}
-});
\ No newline at end of file
+});
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 6b1fafe..cb1ee92 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -473,7 +473,7 @@
def test_cost_center_for_manufacture(self):
wo_order = make_wo_order_test_record()
ste = make_stock_entry(wo_order.name, "Material Transfer for Manufacture", wo_order.qty)
- self.assertEquals(ste.get("items")[0].get("cost_center"), "_Test Cost Center - _TC")
+ self.assertEqual(ste.get("items")[0].get("cost_center"), "_Test Cost Center - _TC")
def test_operation_time_with_batch_size(self):
fg_item = "Test Batch Size Item For BOM"
@@ -539,11 +539,11 @@
ste_cancel_list.append(ste1)
ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Material Consumption for Manufacture", 2))
- self.assertEquals(ste3.fg_completed_qty, 2)
+ self.assertEqual(ste3.fg_completed_qty, 2)
expected_qty = {"_Test Item": 2, "_Test Item Home Desktop 100": 4}
for row in ste3.items:
- self.assertEquals(row.qty, expected_qty.get(row.item_code))
+ self.assertEqual(row.qty, expected_qty.get(row.item_code))
ste_cancel_list.reverse()
for ste_doc in ste_cancel_list:
ste_doc.cancel()
@@ -577,7 +577,7 @@
ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2))
for ste_row in ste3.items:
if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse:
- self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2)
+ self.assertEqual(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2)
ste3.submit()
ste_cancel_list.append(ste3)
@@ -585,7 +585,7 @@
ste2 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2))
for ste_row in ste2.items:
if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse:
- self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2)
+ self.assertEqual(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2)
ste_cancel_list.reverse()
for ste_doc in ste_cancel_list:
ste_doc.cancel()
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index a6086fb..3e5a72d 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -76,9 +76,9 @@
frm.set_query("production_item", function() {
return {
query: "erpnext.controllers.queries.item_query",
- filters:[
- ['is_stock_item', '=',1]
- ]
+ filters: {
+ "is_stock_item": 1,
+ }
};
});
diff --git a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json
index 7b5747e..7317152 100644
--- a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json
+++ b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json
@@ -19,7 +19,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/manufacturing",
"idx": 0,
"is_complete": 0,
- "modified": "2020-07-08 14:05:56.197563",
+ "modified": "2020-06-29 20:25:36.899106",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Manufacturing",
@@ -53,4 +53,4 @@
"subtitle": "Products, Raw Materials, BOM, Work Order, and more.",
"success_message": "Manufacturing module is all set up!",
"title": "Let's Set Up the Manufacturing Module."
-}
\ No newline at end of file
+}
diff --git a/erpnext/non_profit/doctype/donation/test_donation.py b/erpnext/non_profit/doctype/donation/test_donation.py
index c6a534d..bbe9bf5 100644
--- a/erpnext/non_profit/doctype/donation/test_donation.py
+++ b/erpnext/non_profit/doctype/donation/test_donation.py
@@ -39,7 +39,7 @@
donation.on_payment_authorized()
donation.reload()
- self.assertEquals(donation.paid, 1)
+ self.assertEqual(donation.paid, 1)
self.assertTrue(frappe.db.exists('Payment Entry', {'reference_no': donation.name}))
diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py
index efc072e..30be585 100644
--- a/erpnext/non_profit/doctype/member/member.py
+++ b/erpnext/non_profit/doctype/member/member.py
@@ -28,7 +28,7 @@
def setup_subscription(self):
non_profit_settings = frappe.get_doc('Non Profit Settings')
if not non_profit_settings.enable_razorpay_for_memberships:
- frappe.throw('Please check Enable Razorpay for Memberships in {0} to setup subscription').format(
+ frappe.throw(_('Please check Enable Razorpay for Memberships in {0} to setup subscription')).format(
get_link_to_form('Non Profit Settings', 'Non Profit Settings'))
controller = get_payment_gateway_controller("Razorpay")
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 1686314..3a7aa1b 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -693,7 +693,7 @@
execute:frappe.reload_doc('desk', 'doctype', 'number_card_link')
execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts')
erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo
-erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2020-05-25
+erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2021-04-16
erpnext.patches.v12_0.update_bom_in_so_mr
execute:frappe.delete_doc("Report", "Department Analytics")
execute:frappe.rename_doc("Desk Page", "Loan Management", "Loan", force=True)
@@ -756,11 +756,29 @@
erpnext.patches.v12_0.add_state_code_for_ladakh
erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes
-erpnext.patches.v13_0.update_vehicle_no_reqd_condition
+erpnext.patches.v12_0.update_vehicle_no_reqd_condition
+erpnext.patches.v12_0.add_einvoice_status_field #2021-03-17
+erpnext.patches.v12_0.add_einvoice_summary_report_permissions
erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation
erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings
erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae
erpnext.patches.v13_0.setup_uae_vat_fields
execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext')
+erpnext.patches.v12_0.add_company_link_to_einvoice_settings
erpnext.patches.v13_0.rename_discharge_date_in_ip_record
+erpnext.patches.v12_0.create_taxable_value_field
+erpnext.patches.v12_0.add_gst_category_in_delivery_note
erpnext.patches.v12_0.purchase_receipt_status
+erpnext.patches.v12_0.create_itc_reversal_custom_fields
+erpnext.patches.v13_0.fix_non_unique_represents_company
+erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing
+erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021
+erpnext.patches.v13_0.update_shipment_status
+erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting
+erpnext.patches.v12_0.add_ewaybill_validity_field
+erpnext.patches.v13_0.germany_make_custom_fields
+erpnext.patches.v13_0.germany_fill_debtor_creditor_number
+erpnext.patches.v13_0.set_pos_closing_as_failed
+erpnext.patches.v13_0.update_timesheet_changes
+erpnext.patches.v13_0.set_training_event_attendance
+erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold
diff --git a/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py
new file mode 100644
index 0000000..b6bd5fa
--- /dev/null
+++ b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py
@@ -0,0 +1,16 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company or not frappe.db.count('E Invoice User'):
+ return
+
+ frappe.reload_doc("regional", "doctype", "e_invoice_user")
+ for creds in frappe.db.get_all('E Invoice User', fields=['name', 'gstin']):
+ company_name = frappe.db.sql("""
+ select dl.link_name from `tabAddress` a, `tabDynamic Link` dl
+ where a.gstin = %s and dl.parent = a.name and dl.link_doctype = 'Company'
+ """, (creds.get('gstin')))
+ if company_name and len(company_name) > 0:
+ frappe.db.set_value('E Invoice User', creds.get('name'), 'company', company_name[0][0])
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py b/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py
new file mode 100644
index 0000000..4d649dd
--- /dev/null
+++ b/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py
@@ -0,0 +1,18 @@
+from __future__ import unicode_literals
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+import frappe
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'Italy'})
+ if not company:
+ return
+
+ custom_fields = {
+ 'Sales Invoice': [
+ dict(fieldname='type_of_document', label='Type of Document',
+ fieldtype='Select', insert_after='customer_fiscal_code',
+ options='\nTD01\nTD02\nTD03\nTD04\nTD05\nTD06\nTD16\nTD17\nTD18\nTD19\nTD20\nTD21\nTD22\nTD23\nTD24\nTD25\nTD26\nTD27'),
+ ]
+ }
+
+ create_custom_fields(custom_fields, update=True)
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/add_einvoice_status_field.py b/erpnext/patches/v12_0/add_einvoice_status_field.py
new file mode 100644
index 0000000..387e885
--- /dev/null
+++ b/erpnext/patches/v12_0/add_einvoice_status_field.py
@@ -0,0 +1,69 @@
+from __future__ import unicode_literals
+import json
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company:
+ return
+
+ # move hidden einvoice fields to a different section
+ custom_fields = {
+ 'Sales Invoice': [
+ dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type',
+ print_hide=1, hidden=1),
+
+ dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section',
+ no_copy=1, print_hide=1),
+
+ dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
+
+ dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date',
+ no_copy=1, print_hide=1),
+
+ dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date',
+ no_copy=1, print_hide=1, read_only=1),
+
+ dict(fieldname='signed_qr_code', label='Signed QRCode', fieldtype='Code', options='JSON', hidden=1, insert_after='signed_einvoice',
+ no_copy=1, print_hide=1, read_only=1),
+
+ dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, insert_after='signed_qr_code',
+ no_copy=1, print_hide=1, read_only=1),
+
+ dict(fieldname='einvoice_status', label='E-Invoice Status', fieldtype='Select', insert_after='qrcode_image',
+ options='\nPending\nGenerated\nCancelled\nFailed', default=None, hidden=1, no_copy=1, print_hide=1, read_only=1),
+
+ dict(fieldname='failure_description', label='E-Invoice Failure Description', fieldtype='Code', options='JSON',
+ hidden=1, insert_after='einvoice_status', no_copy=1, print_hide=1, read_only=1)
+ ]
+ }
+ create_custom_fields(custom_fields, update=True)
+
+ if frappe.db.exists('E Invoice Settings') and frappe.db.get_single_value('E Invoice Settings', 'enable'):
+ frappe.db.sql('''
+ UPDATE `tabSales Invoice` SET einvoice_status = 'Pending'
+ WHERE
+ posting_date >= '2021-04-01'
+ AND ifnull(irn, '') = ''
+ AND ifnull(`billing_address_gstin`, '') != ifnull(`company_gstin`, '')
+ AND ifnull(gst_category, '') in ('Registered Regular', 'SEZ', 'Overseas', 'Deemed Export')
+ ''')
+
+ # set appropriate statuses
+ frappe.db.sql('''UPDATE `tabSales Invoice` SET einvoice_status = 'Generated'
+ WHERE ifnull(irn, '') != '' AND ifnull(irn_cancelled, 0) = 0''')
+
+ frappe.db.sql('''UPDATE `tabSales Invoice` SET einvoice_status = 'Cancelled'
+ WHERE ifnull(irn_cancelled, 0) = 1''')
+
+ # set correct acknowledgement in e-invoices
+ einvoices = frappe.get_all('Sales Invoice', {'irn': ['is', 'set']}, ['name', 'signed_einvoice'])
+
+ if einvoices:
+ for inv in einvoices:
+ signed_einvoice = inv.get('signed_einvoice')
+ if signed_einvoice:
+ signed_einvoice = json.loads(signed_einvoice)
+ frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_no', signed_einvoice.get('AckNo'), update_modified=False)
+ frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_date', signed_einvoice.get('AckDt'), update_modified=False)
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py b/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py
new file mode 100644
index 0000000..bf8f566
--- /dev/null
+++ b/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py
@@ -0,0 +1,18 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company:
+ return
+
+ if frappe.db.exists('Report', 'E-Invoice Summary') and \
+ not frappe.db.get_value('Custom Role', dict(report='E-Invoice Summary')):
+ frappe.get_doc(dict(
+ doctype='Custom Role',
+ report='E-Invoice Summary',
+ roles= [
+ dict(role='Accounts User'),
+ dict(role='Accounts Manager')
+ ]
+ )).insert()
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/add_ewaybill_validity_field.py b/erpnext/patches/v12_0/add_ewaybill_validity_field.py
new file mode 100644
index 0000000..87d98f1
--- /dev/null
+++ b/erpnext/patches/v12_0/add_ewaybill_validity_field.py
@@ -0,0 +1,16 @@
+from __future__ import unicode_literals
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company:
+ return
+
+ custom_fields = {
+ 'Sales Invoice': [
+ dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1,
+ depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill')
+ ]
+ }
+ create_custom_fields(custom_fields, update=True)
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py b/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py
new file mode 100644
index 0000000..1208222
--- /dev/null
+++ b/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py
@@ -0,0 +1,19 @@
+from __future__ import unicode_literals
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company:
+ return
+
+ custom_fields = {
+ 'Delivery Note': [
+ dict(fieldname='gst_category', label='GST Category',
+ fieldtype='Select', insert_after='gst_vehicle_type', print_hide=1,
+ options='\nRegistered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders',
+ fetch_from='customer.gst_category', fetch_if_empty=1),
+ ]
+ }
+
+ create_custom_fields(custom_fields, update=True)
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py b/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py
new file mode 100644
index 0000000..0078a53
--- /dev/null
+++ b/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py
@@ -0,0 +1,115 @@
+from __future__ import unicode_literals
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+from frappe.custom.doctype.property_setter.property_setter import make_property_setter
+from erpnext.regional.india.utils import get_gst_accounts
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'}, fields=['name'])
+ if not company:
+ return
+
+ frappe.reload_doc("regional", "doctype", "gst_settings")
+ frappe.reload_doc("accounts", "doctype", "gst_account")
+
+ journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + ['Reversal Of ITC']
+ make_property_setter('Journal Entry', 'voucher_type', 'options', '\n'.join(journal_entry_types), '')
+
+ custom_fields = {
+ 'Journal Entry': [
+ dict(fieldname='reversal_type', label='Reversal Type',
+ fieldtype='Select', insert_after='voucher_type', print_hide=1,
+ options="As per rules 42 & 43 of CGST Rules\nOthers",
+ depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
+ mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"),
+ dict(fieldname='company_address', label='Company Address',
+ fieldtype='Link', options='Address', insert_after='reversal_type',
+ print_hide=1, depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
+ mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"),
+ dict(fieldname='company_gstin', label='Company GSTIN',
+ fieldtype='Data', read_only=1, insert_after='company_address', print_hide=1,
+ fetch_from='company_address.gstin',
+ depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
+ mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'")
+ ],
+ 'Purchase Invoice': [
+ dict(fieldname='eligibility_for_itc', label='Eligibility For ITC',
+ fieldtype='Select', insert_after='reason_for_issuing_document', print_hide=1,
+ options='Input Service Distributor\nImport Of Service\nImport Of Capital Goods\nITC on Reverse Charge\nIneligible As Per Section 17(5)\nIneligible Others\nAll Other ITC',
+ default="All Other ITC")
+ ],
+ 'Purchase Invoice Item': [
+ dict(fieldname='taxable_value', label='Taxable Value',
+ fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency",
+ print_hide=1)
+ ]
+ }
+
+ create_custom_fields(custom_fields, update=True)
+
+ # Patch ITC Availed fields from Data to Currency
+ # Patch Availed ITC for current fiscal_year
+
+ gst_accounts = get_gst_accounts(only_non_reverse_charge=1)
+
+ frappe.db.sql("""
+ UPDATE `tabCustom Field` SET fieldtype='Currency', options='Company:company:default_currency'
+ WHERE dt = 'Purchase Invoice' and fieldname in ('itc_integrated_tax', 'itc_state_tax', 'itc_central_tax',
+ 'itc_cess_amount')
+ """)
+
+ frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_integrated_tax = '0'
+ WHERE trim(coalesce(itc_integrated_tax, '')) = '' """)
+
+ frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_state_tax = '0'
+ WHERE trim(coalesce(itc_state_tax, '')) = '' """)
+
+ frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_central_tax = '0'
+ WHERE trim(coalesce(itc_central_tax, '')) = '' """)
+
+ frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_cess_amount = '0'
+ WHERE trim(coalesce(itc_cess_amount, '')) = '' """)
+
+ # Get purchase invoices
+ invoices = frappe.get_all('Purchase Invoice',
+ {'posting_date': ('>=', '2021-04-01'), 'eligibility_for_itc': ('!=', 'Ineligible')},
+ ['name'])
+
+ amount_map = {}
+
+ if invoices:
+ invoice_list = set([d.name for d in invoices])
+
+ # Get GST applied
+ amounts = frappe.db.sql("""
+ SELECT parent, account_head, sum(base_tax_amount_after_discount_amount) as amount
+ FROM `tabPurchase Taxes and Charges`
+ where parent in %s
+ GROUP BY parent, account_head
+ """, (invoice_list), as_dict=1)
+
+ for d in amounts:
+ amount_map.setdefault(d.parent,
+ {
+ 'itc_integrated_tax': 0,
+ 'itc_state_tax': 0,
+ 'itc_central_tax': 0,
+ 'itc_cess_amount': 0
+ })
+
+ if d.account_head in gst_accounts.get('igst_account'):
+ amount_map[d.parent]['itc_integrated_tax'] += d.amount
+ if d.account_head in gst_accounts.get('cgst_account'):
+ amount_map[d.parent]['itc_central_tax'] += d.amount
+ if d.account_head in gst_accounts.get('sgst_account'):
+ amount_map[d.parent]['itc_state_tax'] += d.amount
+ if d.account_head in gst_accounts.get('cess_account'):
+ amount_map[d.parent]['itc_cess_amount'] += d.amount
+
+ for invoice, values in amount_map.items():
+ frappe.db.set_value('Purchase Invoice', invoice, {
+ 'itc_integrated_tax': values.get('itc_integrated_tax'),
+ 'itc_central_tax': values.get('itc_central_tax'),
+ 'itc_state_tax': values['itc_state_tax'],
+ 'itc_cess_amount': values['itc_cess_amount'],
+ })
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/create_taxable_value_field.py b/erpnext/patches/v12_0/create_taxable_value_field.py
new file mode 100644
index 0000000..a0c9fcf
--- /dev/null
+++ b/erpnext/patches/v12_0/create_taxable_value_field.py
@@ -0,0 +1,18 @@
+from __future__ import unicode_literals
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company:
+ return
+
+ custom_fields = {
+ 'Sales Invoice Item': [
+ dict(fieldname='taxable_value', label='Taxable Value',
+ fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency",
+ print_hide=1)
+ ]
+ }
+
+ create_custom_fields(custom_fields, update=True)
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
index 06331d7..a6471eb 100644
--- a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
+++ b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
@@ -44,9 +44,11 @@
# make current item's tax map
item_tax_map = {}
for d in old_item_taxes[item_code]:
- item_tax_map[d.tax_type] = d.tax_rate
+ if d.tax_type not in item_tax_map:
+ item_tax_map[d.tax_type] = d.tax_rate
- item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, item_code)
+ tax_types = []
+ item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, item_code, tax_types=tax_types)
# update the item tax table
frappe.db.sql("delete from `tabItem Tax` where parent=%s and parenttype='Item'", item_code)
@@ -68,7 +70,7 @@
and item_tax_template is NULL""".format(dt), as_dict=1):
item_tax_map = json.loads(d.item_tax_rate)
item_tax_template_name = get_item_tax_template(item_tax_templates,
- item_tax_map, d.item_code, d.parenttype, d.parent)
+ item_tax_map, d.item_code, d.parenttype, d.parent, tax_types=tax_types)
frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template_name)
frappe.db.auto_commit_on_many_writes = False
@@ -78,7 +80,7 @@
settings.determine_address_tax_category_from = "Billing Address"
settings.save()
-def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None):
+def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None, tax_types=None):
# search for previously created item tax template by comparing tax maps
for template, item_tax_template_map in iteritems(item_tax_templates):
if item_tax_map == item_tax_template_map:
@@ -126,7 +128,9 @@
account_type = frappe.get_cached_value("Account", tax_type, "account_type")
if tax_type and account_type in ('Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation'):
- item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate})
+ if tax_type not in tax_types:
+ item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate})
+ tax_types.append(tax_type)
item_tax_templates.setdefault(item_tax_template.title, {})
item_tax_templates[item_tax_template.title][tax_type] = tax_rate
if item_tax_template.get("taxes"):
diff --git a/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py b/erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py
similarity index 83%
rename from erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py
rename to erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py
index c26cddb..01a4ae0 100644
--- a/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py
+++ b/erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py
@@ -1,6 +1,7 @@
import frappe
def execute():
+ frappe.reload_doc('custom', 'doctype', 'custom_field')
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
diff --git a/erpnext/patches/v13_0/check_is_income_tax_component.py b/erpnext/patches/v13_0/check_is_income_tax_component.py
index 9ad48e2..c92d52d 100644
--- a/erpnext/patches/v13_0/check_is_income_tax_component.py
+++ b/erpnext/patches/v13_0/check_is_income_tax_component.py
@@ -8,36 +8,39 @@
def execute():
- doctypes = ['salary_component',
- 'Employee Tax Exemption Declaration',
- 'Employee Tax Exemption Proof Submission',
- 'Employee Tax Exemption Declaration Category',
- 'Employee Tax Exemption Proof Submission Detail'
- ]
+ doctypes = ['salary_component',
+ 'Employee Tax Exemption Declaration',
+ 'Employee Tax Exemption Proof Submission',
+ 'Employee Tax Exemption Declaration Category',
+ 'Employee Tax Exemption Proof Submission Detail',
+ 'gratuity_rule',
+ 'gratuity_rule_slab',
+ 'gratuity_applicable_component'
+ ]
- for doctype in doctypes:
- frappe.reload_doc('Payroll', 'doctype', doctype)
+ for doctype in doctypes:
+ frappe.reload_doc('Payroll', 'doctype', doctype)
- reports = ['Professional Tax Deductions', 'Provident Fund Deductions']
- for report in reports:
- frappe.reload_doc('Regional', 'Report', report)
- frappe.reload_doc('Regional', 'Report', report)
+ reports = ['Professional Tax Deductions', 'Provident Fund Deductions']
+ for report in reports:
+ frappe.reload_doc('Regional', 'Report', report)
+ frappe.reload_doc('Regional', 'Report', report)
- if erpnext.get_region() == "India":
- setup(patch=True)
+ if erpnext.get_region() == "India":
+ setup(patch=True)
- if frappe.db.exists("Salary Component", "Income Tax"):
- frappe.db.set_value("Salary Component", "Income Tax", "is_income_tax_component", 1)
- if frappe.db.exists("Salary Component", "TDS"):
- frappe.db.set_value("Salary Component", "TDS", "is_income_tax_component", 1)
+ if frappe.db.exists("Salary Component", "Income Tax"):
+ frappe.db.set_value("Salary Component", "Income Tax", "is_income_tax_component", 1)
+ if frappe.db.exists("Salary Component", "TDS"):
+ frappe.db.set_value("Salary Component", "TDS", "is_income_tax_component", 1)
- components = frappe.db.sql("select name from `tabSalary Component` where variable_based_on_taxable_salary = 1", as_dict=1)
- for component in components:
- frappe.db.set_value("Salary Component", component.name, "is_income_tax_component", 1)
+ components = frappe.db.sql("select name from `tabSalary Component` where variable_based_on_taxable_salary = 1", as_dict=1)
+ for component in components:
+ frappe.db.set_value("Salary Component", component.name, "is_income_tax_component", 1)
- if erpnext.get_region() == "India":
- if frappe.db.exists("Salary Component", "Provident Fund"):
- frappe.db.set_value("Salary Component", "Provident Fund", "component_type", "Provident Fund")
- if frappe.db.exists("Salary Component", "Professional Tax"):
- frappe.db.set_value("Salary Component", "Professional Tax", "component_type", "Professional Tax")
\ No newline at end of file
+ if erpnext.get_region() == "India":
+ if frappe.db.exists("Salary Component", "Provident Fund"):
+ frappe.db.set_value("Salary Component", "Provident Fund", "component_type", "Provident Fund")
+ if frappe.db.exists("Salary Component", "Professional Tax"):
+ frappe.db.set_value("Salary Component", "Professional Tax", "component_type", "Professional Tax")
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py b/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py
index 48d5cb4..59b2e49 100644
--- a/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py
+++ b/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py
@@ -11,4 +11,8 @@
if not company:
return
+
+ frappe.reload_doc('accounts', 'doctype', 'pos_invoice')
+ frappe.reload_doc('accounts', 'doctype', 'pos_invoice_item')
+
make_custom_fields()
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py b/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py
index af1f6e7..77a23cf 100644
--- a/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py
+++ b/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py
@@ -22,5 +22,7 @@
frappe.delete_doc("Page", "bank-reconciliation", force=1)
+ frappe.reload_doc('accounts', 'doctype', 'bank_transaction')
+
rename_field("Bank Transaction", "debit", "deposit")
rename_field("Bank Transaction", "credit", "withdrawal")
diff --git a/erpnext/patches/v13_0/fix_non_unique_represents_company.py b/erpnext/patches/v13_0/fix_non_unique_represents_company.py
new file mode 100644
index 0000000..61dc824
--- /dev/null
+++ b/erpnext/patches/v13_0/fix_non_unique_represents_company.py
@@ -0,0 +1,8 @@
+import frappe
+
+def execute():
+ frappe.db.sql("""
+ update tabCustomer
+ set represents_company = NULL
+ where represents_company = ''
+ """)
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py
new file mode 100644
index 0000000..11e1e9b
--- /dev/null
+++ b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py
@@ -0,0 +1,31 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+
+
+def execute():
+ """Move account number into the new custom field debtor_creditor_number.
+
+ German companies used to use a dedicated payable/receivable account for
+ every party to mimick party accounts in the external accounting software
+ "DATEV". This is no longer necessary. The reference ID for DATEV will be
+ stored in a new custom field "debtor_creditor_number".
+ """
+ company_list = frappe.get_all('Company', filters={'country': 'Germany'})
+
+ for company in company_list:
+ party_account_list = frappe.get_all('Party Account', filters={'company': company.name}, fields=['name', 'account', 'debtor_creditor_number'])
+ for party_account in party_account_list:
+ if (not party_account.account) or party_account.debtor_creditor_number:
+ # account empty or debtor_creditor_number already filled
+ continue
+
+ account_number = frappe.db.get_value('Account', party_account.account, 'account_number')
+ if not account_number:
+ continue
+
+ frappe.db.set_value('Party Account', party_account.name, 'debtor_creditor_number', account_number)
+ frappe.db.set_value('Party Account', party_account.name, 'account', '')
diff --git a/erpnext/patches/v13_0/germany_make_custom_fields.py b/erpnext/patches/v13_0/germany_make_custom_fields.py
new file mode 100644
index 0000000..41ab945
--- /dev/null
+++ b/erpnext/patches/v13_0/germany_make_custom_fields.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+from erpnext.regional.germany.setup import make_custom_fields
+
+
+def execute():
+ """Execute the make_custom_fields method for german companies.
+
+ It is usually run once at setup of a new company. Since it's new, run it
+ once for existing companies as well.
+ """
+ company_list = frappe.get_all('Company', filters = {'country': 'Germany'})
+ if not company_list:
+ return
+
+ make_custom_fields()
diff --git a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py
index 5920bf1..9af0a8d 100644
--- a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py
+++ b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py
@@ -18,6 +18,7 @@
for old_dt, new_dt in doctypes.items():
if not frappe.db.table_exists(new_dt) and frappe.db.table_exists(old_dt):
+ frappe.reload_doc('healthcare', 'doctype', frappe.scrub(old_dt))
frappe.rename_doc('DocType', old_dt, new_dt, force=True)
frappe.reload_doc('healthcare', 'doctype', frappe.scrub(new_dt))
frappe.delete_doc_if_exists('DocType', old_dt)
@@ -36,6 +37,39 @@
SET parentfield = %(parentfield)s
""".format(doctype), {'parentfield': parentfield})
+ # copy renamed child table fields (fields were already renamed in old doctype json, hence sql)
+ rename_fields = {
+ 'lab_test_name': 'test_name',
+ 'lab_test_event': 'test_event',
+ 'lab_test_uom': 'test_uom',
+ 'lab_test_comment': 'test_comment'
+ }
+
+ for new, old in rename_fields.items():
+ if frappe.db.has_column('Normal Test Result', old):
+ frappe.db.sql("""UPDATE `tabNormal Test Result` SET {} = {}"""
+ .format(new, old))
+
+ if frappe.db.has_column('Normal Test Template', 'test_event'):
+ frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""")
+
+ if frappe.db.has_column('Normal Test Template', 'test_uom'):
+ frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_uom = test_uom""")
+
+ if frappe.db.has_column('Descriptive Test Result', 'test_particulars'):
+ frappe.db.sql("""UPDATE `tabDescriptive Test Result` SET lab_test_particulars = test_particulars""")
+
+ rename_fields = {
+ 'lab_test_template': 'test_template',
+ 'lab_test_description': 'test_description',
+ 'lab_test_rate': 'test_rate'
+ }
+
+ for new, old in rename_fields.items():
+ if frappe.db.has_column('Lab Test Group Template', old):
+ frappe.db.sql("""UPDATE `tabLab Test Group Template` SET {} = {}"""
+ .format(new, old))
+
# rename field
frappe.reload_doc('healthcare', 'doctype', 'lab_test')
if frappe.db.has_column('Lab Test', 'special_toggle'):
diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py
index d968e1f..021bb72 100644
--- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py
+++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py
@@ -20,9 +20,11 @@
frappe.clear_cache()
frappe.flags.warehouse_account_map = {}
+ company_list = []
+
data = frappe.db.sql('''
SELECT
- name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time
+ name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time, company
FROM
`tabStock Ledger Entry`
WHERE
@@ -36,6 +38,9 @@
total_sle = len(data)
i = 0
for d in data:
+ if d.company not in company_list:
+ company_list.append(d.company)
+
update_entries_after({
"item_code": d.item_code,
"warehouse": d.warehouse,
@@ -53,8 +58,10 @@
print("Reposting General Ledger Entries...")
- for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}):
- update_gl_entries_after(posting_date, posting_time, company=row.name)
+ if data:
+ for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}):
+ if row.name in company_list:
+ update_gl_entries_after(posting_date, posting_time, company=row.name)
frappe.db.auto_commit_on_many_writes = 0
diff --git a/erpnext/patches/v13_0/make_non_standard_user_type.py b/erpnext/patches/v13_0/make_non_standard_user_type.py
new file mode 100644
index 0000000..a9d7883
--- /dev/null
+++ b/erpnext/patches/v13_0/make_non_standard_user_type.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from six import iteritems
+from erpnext.setup.install import add_non_standard_user_types
+
+def execute():
+ doctype_dict = {
+ 'projects': ['Timesheet'],
+ 'payroll': ['Salary Slip', 'Employee Tax Exemption Declaration', 'Employee Tax Exemption Proof Submission'],
+ 'hr': ['Employee', 'Expense Claim', 'Leave Application', 'Attendance Request', 'Compensatory Leave Request']
+ }
+
+ for module, doctypes in iteritems(doctype_dict):
+ for doctype in doctypes:
+ frappe.reload_doc(module, 'doctype', doctype)
+
+
+ frappe.flags.ignore_select_perm = True
+ frappe.flags.update_select_perm_after_migrate = True
+
+ add_non_standard_user_types()
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/remove_attribute_field_from_item_variant_setting.py b/erpnext/patches/v13_0/remove_attribute_field_from_item_variant_setting.py
new file mode 100644
index 0000000..53da700
--- /dev/null
+++ b/erpnext/patches/v13_0/remove_attribute_field_from_item_variant_setting.py
@@ -0,0 +1,8 @@
+import frappe
+
+def execute():
+ """Remove has_variants and attribute fields from item variant settings."""
+ frappe.reload_doc("stock", "doctype", "Item Variant Settings")
+
+ frappe.db.sql("""delete from `tabVariant Field`
+ where field_name in ('attributes', 'has_variants')""")
diff --git a/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py b/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py
new file mode 100644
index 0000000..48325fc
--- /dev/null
+++ b/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2020, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ if frappe.db.exists('DocType', 'Issue'):
+ frappe.reload_doc("support", "doctype", "issue")
+ rename_status()
+
+def rename_status():
+ frappe.db.sql("""
+ UPDATE
+ `tabIssue`
+ SET
+ status = 'On Hold'
+ WHERE
+ status = 'Hold'
+ """)
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py
index be5e30f..a5b93f6 100644
--- a/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py
+++ b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py
@@ -3,7 +3,7 @@
def execute():
company = frappe.db.get_single_value('Global Defaults', 'default_company')
- doctypes = ['Clinical Procedure', 'Inpatient Record', 'Lab Test', 'Sample Collection' 'Patient Appointment', 'Patient Encounter', 'Vital Signs', 'Therapy Session', 'Therapy Plan', 'Patient Assessment']
+ doctypes = ['Clinical Procedure', 'Inpatient Record', 'Lab Test', 'Sample Collection', 'Patient Appointment', 'Patient Encounter', 'Vital Signs', 'Therapy Session', 'Therapy Plan', 'Patient Assessment']
for entry in doctypes:
if frappe.db.exists('DocType', entry):
frappe.reload_doc('Healthcare', 'doctype', entry)
diff --git a/erpnext/patches/v13_0/set_pos_closing_as_failed.py b/erpnext/patches/v13_0/set_pos_closing_as_failed.py
new file mode 100644
index 0000000..1c576db
--- /dev/null
+++ b/erpnext/patches/v13_0/set_pos_closing_as_failed.py
@@ -0,0 +1,7 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ frappe.reload_doc('accounts', 'doctype', 'pos_closing_entry')
+
+ frappe.db.sql("update `tabPOS Closing Entry` set `status` = 'Failed' where `status` = 'Queued'")
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/set_training_event_attendance.py b/erpnext/patches/v13_0/set_training_event_attendance.py
new file mode 100644
index 0000000..18cad8d
--- /dev/null
+++ b/erpnext/patches/v13_0/set_training_event_attendance.py
@@ -0,0 +1,9 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ frappe.reload_doc('hr', 'doctype', 'training_event')
+ frappe.reload_doc('hr', 'doctype', 'training_event_employee')
+
+ frappe.db.sql("update `tabTraining Event Employee` set `attendance` = 'Present'")
+ frappe.db.sql("update `tabTraining Event Employee` set `is_mandatory` = 1 where `attendance` = 'Mandatory'")
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py
index 2d3b096..d927524 100644
--- a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py
+++ b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py
@@ -6,8 +6,9 @@
if "Healthcare" not in frappe.get_active_domains():
return
- frappe.reload_doc("healthcare", "doctype", "Therapy Session")
frappe.reload_doc("healthcare", "doctype", "Inpatient Medication Order")
+ frappe.reload_doc("healthcare", "doctype", "Therapy Session")
+ frappe.reload_doc("healthcare", "doctype", "Clinical Procedure")
frappe.reload_doc("healthcare", "doctype", "Patient History Settings")
frappe.reload_doc("healthcare", "doctype", "Patient History Standard Document Type")
frappe.reload_doc("healthcare", "doctype", "Patient History Custom Document Type")
diff --git a/erpnext/patches/v13_0/setup_uae_vat_fields.py b/erpnext/patches/v13_0/setup_uae_vat_fields.py
index d7a5c68..1830bab 100644
--- a/erpnext/patches/v13_0/setup_uae_vat_fields.py
+++ b/erpnext/patches/v13_0/setup_uae_vat_fields.py
@@ -2,11 +2,15 @@
# License: GNU General Public License v3. See license.txt
import frappe
-from erpnext.regional.united_arab_emirates.setup import setup
+from erpnext.regional.united_arab_emirates.setup import setup
def execute():
company = frappe.get_all('Company', filters = {'country': 'United Arab Emirates'})
if not company:
return
- setup()
\ No newline at end of file
+ frappe.reload_doc('regional', 'report', 'uae_vat_201')
+ frappe.reload_doc('regional', 'doctype', 'uae_vat_settings')
+ frappe.reload_doc('regional', 'doctype', 'uae_vat_account')
+
+ setup()
diff --git a/erpnext/patches/v13_0/update_shipment_status.py b/erpnext/patches/v13_0/update_shipment_status.py
new file mode 100644
index 0000000..c425599
--- /dev/null
+++ b/erpnext/patches/v13_0/update_shipment_status.py
@@ -0,0 +1,14 @@
+import frappe
+
+def execute():
+ frappe.reload_doc("stock", "doctype", "shipment")
+
+ # update submitted status
+ frappe.db.sql("""UPDATE `tabShipment`
+ SET status = "Submitted"
+ WHERE status = "Draft" AND docstatus = 1""")
+
+ # update cancelled status
+ frappe.db.sql("""UPDATE `tabShipment`
+ SET status = "Cancelled"
+ WHERE status = "Draft" AND docstatus = 2""")
diff --git a/erpnext/patches/v13_0/update_timesheet_changes.py b/erpnext/patches/v13_0/update_timesheet_changes.py
new file mode 100644
index 0000000..93b7f8e
--- /dev/null
+++ b/erpnext/patches/v13_0/update_timesheet_changes.py
@@ -0,0 +1,25 @@
+from __future__ import unicode_literals
+import frappe
+from frappe.model.utils.rename_field import rename_field
+
+def execute():
+ frappe.reload_doc("projects", "doctype", "timesheet")
+ frappe.reload_doc("projects", "doctype", "timesheet_detail")
+
+ if frappe.db.has_column("Timesheet Detail", "billable"):
+ rename_field("Timesheet Detail", "billable", "is_billable")
+
+ base_currency = frappe.defaults.get_global_default('currency')
+
+ frappe.db.sql("""UPDATE `tabTimesheet Detail`
+ SET base_billing_rate = billing_rate,
+ base_billing_amount = billing_amount,
+ base_costing_rate = costing_rate,
+ base_costing_amount = costing_amount""")
+
+ frappe.db.sql("""UPDATE `tabTimesheet`
+ SET currency = '{0}',
+ exchange_rate = 1.0,
+ base_total_billable_amount = total_billable_amount,
+ base_total_billed_amount = total_billed_amount,
+ base_total_costing_amount = total_costing_amount""".format(base_currency))
\ No newline at end of file
diff --git a/erpnext/patches/v7_0/convert_timelog_to_timesheet.py b/erpnext/patches/v7_0/convert_timelog_to_timesheet.py
index 3af6622..8c60b5b 100644
--- a/erpnext/patches/v7_0/convert_timelog_to_timesheet.py
+++ b/erpnext/patches/v7_0/convert_timelog_to_timesheet.py
@@ -51,7 +51,7 @@
def get_timelog_data(data):
return {
- 'billable': data.billable,
+ 'is_billable': data.billable,
'from_time': data.from_time,
'hours': data.hours,
'to_time': data.to_time,
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.json b/erpnext/payroll/doctype/additional_salary/additional_salary.json
index 61ae7e4..d9efe45 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.json
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.json
@@ -7,25 +7,30 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
+ "employee_details_section",
"naming_series",
"employee",
"employee_name",
- "salary_component",
- "type",
- "amount",
- "ref_doctype",
- "ref_docname",
- "amended_from",
"column_break_5",
"company",
"department",
+ "salary_details_section",
+ "salary_component",
+ "type",
"currency",
+ "amount",
+ "column_break_13",
+ "is_recurring",
+ "payroll_date",
"from_date",
"to_date",
- "payroll_date",
- "is_recurring",
+ "properties_and_references_section",
+ "deduct_full_tax_on_selected_payroll_date",
+ "ref_doctype",
+ "ref_docname",
+ "column_break_22",
"overwrite_salary_structure_amount",
- "deduct_full_tax_on_selected_payroll_date"
+ "amended_from"
],
"fields": [
{
@@ -81,7 +86,7 @@
},
{
"depends_on": "eval:(doc.is_recurring==0)",
- "description": "Date on which this component is applied",
+ "description": "The date on which Salary Component with Amount will contribute for Earnings/Deduction in Salary Slip. ",
"fieldname": "payroll_date",
"fieldtype": "Date",
"in_list_view": 1,
@@ -159,6 +164,7 @@
"fieldname": "ref_docname",
"fieldtype": "Dynamic Link",
"label": "Reference Document",
+ "no_copy": 1,
"options": "ref_doctype",
"read_only": 1
},
@@ -171,11 +177,34 @@
"print_hide": 1,
"read_only": 1,
"reqd": 1
+ },
+ {
+ "fieldname": "employee_details_section",
+ "fieldtype": "Section Break",
+ "label": "Employee Details"
+ },
+ {
+ "fieldname": "column_break_13",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_22",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "salary_details_section",
+ "fieldtype": "Section Break",
+ "label": "Salary Details"
+ },
+ {
+ "fieldname": "properties_and_references_section",
+ "fieldtype": "Section Break",
+ "label": "Properties and References"
}
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 14:45:48.566756",
+ "modified": "2021-05-26 11:10:00.812698",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Additional Salary",
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py
index 13b6c05..ebeddf9 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.py
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py
@@ -13,12 +13,19 @@
if self.ref_doctype == "Employee Advance" and self.ref_docname:
frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", self.amount)
+ self.update_employee_referral()
+
+ def on_cancel(self):
+ self.update_employee_referral(cancel=True)
+
def validate(self):
self.validate_dates()
self.validate_salary_structure()
self.validate_recurring_additional_salary_overlap()
+ self.validate_employee_referral()
+
if self.amount < 0:
- frappe.throw(_("Amount should not be less than zero."))
+ frappe.throw(_("Amount should not be less than zero"))
def validate_salary_structure(self):
if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}):
@@ -70,6 +77,27 @@
if self.payroll_date and getdate(self.payroll_date) > getdate(relieving_date):
frappe.throw(_("Payroll date can not be greater than employee's relieving date."))
+ def validate_employee_referral(self):
+ if self.ref_doctype == "Employee Referral":
+ referral_details = frappe.db.get_value("Employee Referral", self.ref_docname,
+ ["is_applicable_for_referral_bonus", "status"], as_dict=1)
+
+ if not referral_details.is_applicable_for_referral_bonus:
+ frappe.throw(_("Employee Referral {0} is not applicable for referral bonus.").format(
+ self.ref_docname))
+
+ if self.type == "Deduction":
+ frappe.throw(_("Earning Salary Component is required for Employee Referral Bonus."))
+
+ if referral_details.status != "Accepted":
+ frappe.throw(_("Additional Salary for referral bonus can only be created against Employee Referral with status {0}").format(
+ frappe.bold("Accepted")))
+
+ def update_employee_referral(self, cancel=False):
+ if self.ref_doctype == "Employee Referral":
+ status = "Unpaid" if cancel else "Paid"
+ frappe.db.set_value("Employee Referral", self.ref_docname, "referral_payment_status", status)
+
def get_amount(self, sal_start_date, sal_end_date):
start_date = getdate(sal_start_date)
end_date = getdate(sal_end_date)
@@ -110,8 +138,7 @@
for d in additional_salary_list:
if d.overwrite:
if d.component in components_to_overwrite:
- frappe.throw(_("Multiple Additional Salaries with overwrite "
- "property exist for Salary Component {0} between {1} and {2}.").format(
+ frappe.throw(_("Multiple Additional Salaries with overwrite property exist for Salary Component {0} between {1} and {2}.").format(
frappe.bold(d.component), start_date, end_date), title=_("Error"))
components_to_overwrite.append(d.component)
diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
index c6f764c..8332697 100644
--- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
+++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
@@ -147,7 +147,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 14:46:22.465521",
+ "modified": "2021-03-31 22:35:08.940087",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Benefit Application",
diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json
index e331b7a..b3bac01 100644
--- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json
+++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json
@@ -144,7 +144,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 15:51:51.489269",
+ "modified": "2021-03-31 22:37:21.024625",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Benefit Claim",
diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.json b/erpnext/payroll/doctype/employee_incentive/employee_incentive.json
index 51346c6..0d10b2c 100644
--- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.json
+++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.json
@@ -94,7 +94,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 14:48:00.919839",
+ "modified": "2021-03-31 22:38:20.332316",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Incentive",
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
index 873bf88..b247d26 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
+++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
@@ -119,7 +119,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 20:41:57.387749",
+ "modified": "2021-03-31 22:39:59.237361",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Tax Exemption Declaration",
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
index f32202a..77b107e 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
+++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
@@ -142,7 +142,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 20:48:32.639885",
+ "modified": "2021-03-31 22:41:13.723339",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Tax Exemption Proof Submission",
diff --git a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json
index c343a44..5a7de37 100644
--- a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json
+++ b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json
@@ -104,7 +104,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 20:53:33.323712",
+ "modified": "2021-03-31 22:42:08.139520",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Income Tax Slab",
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
index 85bb651..f289260 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
@@ -151,6 +151,10 @@
filters['company'] = frm.doc.company;
filters['start_date'] = frm.doc.start_date;
filters['end_date'] = frm.doc.end_date;
+ filters['salary_slip_based_on_timesheet'] = frm.doc.salary_slip_based_on_timesheet;
+ filters['payroll_frequency'] = frm.doc.payroll_frequency;
+ filters['payroll_payable_account'] = frm.doc.payroll_payable_account;
+ filters['currency'] = frm.doc.currency;
if (frm.doc.department) {
filters['department'] = frm.doc.department;
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index fde2e07..3953b46 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -52,49 +52,32 @@
Returns list of active employees based on selected criteria
and for which salary structure exists
"""
- cond = self.get_filter_condition()
- cond += self.get_joining_relieving_condition()
+ self.check_mandatory()
+ filters = self.make_filters()
+ cond = get_filter_condition(filters)
+ cond += get_joining_relieving_condition(self.start_date, self.end_date)
condition = ''
if self.payroll_frequency:
condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": self.payroll_frequency}
- sal_struct = frappe.db.sql_list("""
- select
- name from `tabSalary Structure`
- where
- docstatus = 1 and
- is_active = 'Yes'
- and company = %(company)s
- and currency = %(currency)s and
- ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
- {condition}""".format(condition=condition),
- {"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
-
+ sal_struct = get_sal_struct(self.company, self.currency, self.salary_slip_based_on_timesheet, condition)
if sal_struct:
cond += "and t2.salary_structure IN %(sal_struct)s "
cond += "and t2.payroll_payable_account = %(payroll_payable_account)s "
cond += "and %(from_date)s >= t2.from_date"
- emp_list = frappe.db.sql("""
- select
- distinct t1.name as employee, t1.employee_name, t1.department, t1.designation
- from
- `tabEmployee` t1, `tabSalary Structure Assignment` t2
- where
- t1.name = t2.employee
- and t2.docstatus = 1
- %s order by t2.from_date desc
- """ % cond, {"sal_struct": tuple(sal_struct), "from_date": self.end_date, "payroll_payable_account": self.payroll_payable_account}, as_dict=True)
-
- emp_list = self.remove_payrolled_employees(emp_list)
+ emp_list = get_emp_list(sal_struct, cond, self.end_date, self.payroll_payable_account)
+ emp_list = remove_payrolled_employees(emp_list, self.start_date, self.end_date)
return emp_list
- def remove_payrolled_employees(self, emp_list):
- for employee_details in emp_list:
- if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": self.start_date, "end_date": self.end_date, "docstatus": 1}):
- emp_list.remove(employee_details)
+ def make_filters(self):
+ filters = frappe._dict()
+ filters['company'] = self.company
+ filters['branch'] = self.branch
+ filters['department'] = self.department
+ filters['designation'] = self.designation
- return emp_list
+ return filters
@frappe.whitelist()
def fill_employee_details(self):
@@ -122,23 +105,6 @@
if self.validate_attendance:
return self.validate_employee_attendance()
- def get_filter_condition(self):
- self.check_mandatory()
-
- cond = ''
- for f in ['company', 'branch', 'department', 'designation']:
- if self.get(f):
- cond += " and t1." + f + " = " + frappe.db.escape(self.get(f))
-
- return cond
-
- def get_joining_relieving_condition(self):
- cond = """
- and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s'
- and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s'
- """ % {"start_date": self.start_date, "end_date": self.end_date}
- return cond
-
def check_mandatory(self):
for fieldname in ['company', 'start_date', 'end_date']:
if not self.get(fieldname):
@@ -451,6 +417,53 @@
marked_days = attendances[0][0]
return marked_days
+def get_sal_struct(company, currency, salary_slip_based_on_timesheet, condition):
+ return frappe.db.sql_list("""
+ select
+ name from `tabSalary Structure`
+ where
+ docstatus = 1 and
+ is_active = 'Yes'
+ and company = %(company)s
+ and currency = %(currency)s and
+ ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
+ {condition}""".format(condition=condition),
+ {"company": company, "currency": currency, "salary_slip_based_on_timesheet": salary_slip_based_on_timesheet})
+
+def get_filter_condition(filters):
+ cond = ''
+ for f in ['company', 'branch', 'department', 'designation']:
+ if filters.get(f):
+ cond += " and t1." + f + " = " + frappe.db.escape(filters.get(f))
+
+ return cond
+
+def get_joining_relieving_condition(start_date, end_date):
+ cond = """
+ and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s'
+ and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s'
+ """ % {"start_date": start_date, "end_date": end_date}
+ return cond
+
+def get_emp_list(sal_struct, cond, end_date, payroll_payable_account):
+ return frappe.db.sql("""
+ select
+ distinct t1.name as employee, t1.employee_name, t1.department, t1.designation
+ from
+ `tabEmployee` t1, `tabSalary Structure Assignment` t2
+ where
+ t1.name = t2.employee
+ and t2.docstatus = 1
+ %s order by t2.from_date desc
+ """ % cond, {"sal_struct": tuple(sal_struct), "from_date": end_date, "payroll_payable_account": payroll_payable_account}, as_dict=True)
+
+def remove_payrolled_employees(emp_list, start_date, end_date):
+ for employee_details in emp_list:
+ if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": start_date, "end_date": end_date, "docstatus": 1}):
+ emp_list.remove(employee_details)
+
+ return emp_list
+
@frappe.whitelist()
def get_start_end_dates(payroll_frequency, start_date=None, company=None):
'''Returns dict of start and end dates for given payroll frequency based on start_date'''
@@ -567,6 +580,7 @@
if publish_progress:
frappe.publish_progress(count*100/len(set(employees) - set(salary_slips_exists_for)),
title = _("Creating Salary Slips..."))
+
else:
salary_slips_not_created.append(emp)
@@ -638,39 +652,41 @@
'start': start, 'page_len': page_len
})
-def get_employee_with_existing_salary_slip(start_date, end_date, company):
- return frappe.db.sql_list("""
- select employee from `tabSalary Slip`
- where
- (start_date between %(start_date)s and %(end_date)s
- or
- end_date between %(start_date)s and %(end_date)s
- or
- %(start_date)s between start_date and end_date)
- and company = %(company)s
- and docstatus = 1
- """, {'start_date': start_date, 'end_date': end_date, 'company': company})
+def get_employee_list(filters):
+ cond = get_filter_condition(filters)
+ cond += get_joining_relieving_condition(filters.start_date, filters.end_date)
+ condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": filters.payroll_frequency}
+ sal_struct = get_sal_struct(filters.company, filters.currency, filters.salary_slip_based_on_timesheet, condition)
+ if sal_struct:
+ cond += "and t2.salary_structure IN %(sal_struct)s "
+ cond += "and t2.payroll_payable_account = %(payroll_payable_account)s "
+ cond += "and %(from_date)s >= t2.from_date"
+ emp_list = get_emp_list(sal_struct, cond, filters.end_date, filters.payroll_payable_account)
+ emp_list = remove_payrolled_employees(emp_list, filters.start_date, filters.end_date)
+ return emp_list
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def employee_query(doctype, txt, searchfield, start, page_len, filters):
filters = frappe._dict(filters)
conditions = []
- exclude_employees = []
+ include_employees = []
emp_cond = ''
if filters.start_date and filters.end_date:
- employee_list = get_employee_with_existing_salary_slip(filters.start_date, filters.end_date, filters.company)
+ employee_list = get_employee_list(filters)
emp = filters.get('employees')
+ include_employees = [employee.employee for employee in employee_list if employee.employee not in emp]
filters.pop('start_date')
filters.pop('end_date')
+ filters.pop('salary_slip_based_on_timesheet')
+ filters.pop('payroll_frequency')
+ filters.pop('payroll_payable_account')
+ filters.pop('currency')
if filters.employees is not None:
filters.pop('employees')
- if employee_list:
- exclude_employees.extend(employee_list)
- if emp:
- exclude_employees.extend(emp)
- if exclude_employees:
- emp_cond += 'and employee not in %(exclude_employees)s'
+
+ if include_employees:
+ emp_cond += 'and employee in %(include_employees)s'
return frappe.db.sql("""select name, employee_name from `tabEmployee`
where status = 'Active'
@@ -694,4 +710,4 @@
'_txt': txt.replace("%", ""),
'start': start,
'page_len': page_len,
- 'exclude_employees': exclude_employees})
+ 'include_employees': include_employees})
diff --git a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
index 7528bf7..b80b320 100644
--- a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
@@ -15,7 +15,13 @@
from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry, create_loan_type, create_loan_accounts
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
+test_dependencies = ['Holiday List']
+
class TestPayrollEntry(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", '_Test Holiday List')
+
def setUp(self):
for dt in ["Salary Slip", "Salary Component", "Salary Component Account",
"Payroll Entry", "Salary Structure", "Salary Structure Assignment", "Payroll Employee Detail", "Additional Salary"]:
diff --git a/erpnext/payroll/doctype/payroll_settings/payroll_settings.py b/erpnext/payroll/doctype/payroll_settings/payroll_settings.py
index 5efa41d..459b7ea 100644
--- a/erpnext/payroll/doctype/payroll_settings/payroll_settings.py
+++ b/erpnext/payroll/doctype/payroll_settings/payroll_settings.py
@@ -28,5 +28,5 @@
def toggle_rounded_total(self):
self.disable_rounded_total = cint(self.disable_rounded_total)
- make_property_setter("Salary Slip", "rounded_total", "hidden", self.disable_rounded_total, "Check")
- make_property_setter("Salary Slip", "rounded_total", "print_hide", self.disable_rounded_total, "Check")
+ make_property_setter("Salary Slip", "rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False)
+ make_property_setter("Salary Slip", "rounded_total", "print_hide", self.disable_rounded_total, "Check", validate_fields_for_doctype=False)
diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.json b/erpnext/payroll/doctype/retention_bonus/retention_bonus.json
index cd563bc..7ea6210 100644
--- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.json
+++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.json
@@ -105,7 +105,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 14:50:29.401020",
+ "modified": "2021-03-31 22:43:28.363644",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Retention Bonus",
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js
index e3993fa..5258f3a 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.js
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js
@@ -40,7 +40,9 @@
frm.set_query("employee", function() {
return {
query: "erpnext.controllers.queries.employee_query",
- filters: frm.doc.company
+ filters: {
+ company: frm.doc.company
+ }
};
});
},
@@ -119,6 +121,7 @@
frm.set_df_property('exchange_rate', 'hidden', 1);
frm.set_df_property("exchange_rate", "description", "" );
}
+ }
}
},
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json
index ec56076..42a0f29 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.json
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json
@@ -631,7 +631,7 @@
"idx": 9,
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 15:39:28.817166",
+ "modified": "2021-03-31 22:44:09.772331",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Slip",
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index f6d4c7b..afdf081 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -598,10 +598,10 @@
continue
if (
- not d.additional_salary
- and (not additional_salary or additional_salary.overwrite)
- or additional_salary
- and additional_salary.name == d.additional_salary
+ (not d.additional_salary
+ and (not additional_salary or additional_salary.overwrite))
+ or (additional_salary
+ and additional_salary.name == d.additional_salary)
):
component_row = d
break
@@ -611,7 +611,7 @@
self.set(component_type, [
d for d in self.get(component_type)
if d.salary_component != component_data.salary_component
- or d.additional_salary and additional_salary.name != d.additional_salary
+ or (d.additional_salary and additional_salary.name != d.additional_salary)
or d == component_row
])
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 7672695..01e4170 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -312,7 +312,7 @@
frappe.db.sql("DELETE FROM `tabSalary Slip` where employee_name = 'test_ytd@salary.com'")
create_salary_slips_for_payroll_period(applicant, salary_structure.name,
- payroll_period, deduct_random=False)
+ payroll_period, deduct_random=False, num=6)
salary_slips = frappe.get_all('Salary Slip', fields=['year_to_date', 'net_pay'], filters={'employee_name':
'test_ytd@salary.com'}, order_by = 'posting_date')
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js
index 6aa1387..d5c20dc 100755
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.js
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js
@@ -16,11 +16,11 @@
onload: function(frm) {
let help_button = $(`<a class = 'control-label'>
- Condition and Formula Help
+ ${__("Condition and Formula Help")}
</a>`).click(()=>{
let d = new frappe.ui.Dialog({
- title: 'Condition and Formula Help',
+ title: __('Condition and Formula Help'),
fields: [
{
fieldname: 'msg_wrapper',
@@ -111,12 +111,19 @@
frappe.set_route('Form', 'Salary Structure Assignment', doc.name);
});
frm.add_custom_button(__("Assign to Employees"),function () {
- frm.trigger('assign_to_employees')
- })
+ frm.trigger('assign_to_employees')
+ })
}
+
+ // set columns read-only
let fields_read_only = ["is_tax_applicable", "is_flexible_benefit", "variable_based_on_taxable_salary"];
fields_read_only.forEach(function(field) {
- frappe.meta.get_docfield("Salary Detail", field, frm.doc.name).read_only = 1;
+ frm.fields_dict.earnings.grid.update_docfield_property(
+ field, 'read_only', 1
+ );
+ frm.fields_dict.deductions.grid.update_docfield_property(
+ field, 'read_only', 1
+ );
});
frm.trigger('set_earning_deduction_component');
},
@@ -126,8 +133,6 @@
title: __("Assign to Employees"),
fields: [
{fieldname: "sec_break", fieldtype: "Section Break", label: __("Filter Employees By (Optional)")},
- {fieldname: "company", fieldtype: "Link", options: "Company", label: __("Company"), default: frm.doc.company, read_only:1},
- {fieldname: "currency", fieldtype: "Link", options: "Currency", label: __("Currency"), default: frm.doc.currency, read_only:1},
{fieldname: "grade", fieldtype: "Link", options: "Employee Grade", label: __("Employee Grade")},
{fieldname:'department', fieldtype:'Link', options: 'Department', label: __('Department')},
{fieldname:'designation', fieldtype:'Link', options: 'Designation', label: __('Designation')},
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.py b/erpnext/payroll/doctype/salary_structure/salary_structure.py
index 352c180..58c445f 100644
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.py
@@ -88,7 +88,7 @@
return employees
@frappe.whitelist()
- def assign_salary_structure(self, grade=None, department=None, designation=None,employee=None,
+ def assign_salary_structure(self, grade=None, department=None, designation=None, employee=None,
payroll_payable_account=None, from_date=None, base=None, variable=None, income_tax_slab=None):
employees = self.get_employees(company= self.company, grade= grade,department= department,designation= designation,name=employee)
diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
index f2fb558..36387f2 100644
--- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
@@ -164,7 +164,13 @@
salary_structure_assignment.employee = employee
salary_structure_assignment.base = 50000
salary_structure_assignment.variable = 5000
- salary_structure_assignment.from_date = from_date or add_days(nowdate(), -1)
+
+ if getdate(nowdate()).day == 1:
+ date = from_date or nowdate()
+ else:
+ date = from_date or add_days(nowdate(), -1)
+
+ salary_structure_assignment.from_date = date
salary_structure_assignment.salary_structure = salary_structure
salary_structure_assignment.currency = currency
salary_structure_assignment.payroll_payable_account = get_payable_account(company)
diff --git a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
index 50fabed..c8b98e5 100644
--- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
+++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
@@ -145,7 +145,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 15:49:36.361253",
+ "modified": "2021-03-31 22:44:46.267974",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Structure Assignment",
diff --git a/erpnext/payroll/notification/retention_bonus/retention_bonus.json b/erpnext/payroll/notification/retention_bonus/retention_bonus.json
index 50db033..37381fa 100644
--- a/erpnext/payroll/notification/retention_bonus/retention_bonus.json
+++ b/erpnext/payroll/notification/retention_bonus/retention_bonus.json
@@ -1,5 +1,6 @@
{
"attach_print": 0,
+ "channel": "Email",
"condition": "doc.docstatus==1",
"creation": "2018-05-15 18:52:36.362838",
"date_changed": "bonus_payment_date",
diff --git a/erpnext/payroll/onboarding_step/create_payroll_period/create_payroll_period.json b/erpnext/payroll/onboarding_step/create_payroll_period/create_payroll_period.json
index 4bae675..b1a7cc2 100644
--- a/erpnext/payroll/onboarding_step/create_payroll_period/create_payroll_period.json
+++ b/erpnext/payroll/onboarding_step/create_payroll_period/create_payroll_period.json
@@ -8,7 +8,7 @@
"is_mandatory": 1,
"is_single": 0,
"is_skipped": 0,
- "modified": "2020-06-01 11:53:54.553947",
+ "modified": "2020-06-29 11:53:54.553947",
"modified_by": "Administrator",
"name": "Create Payroll Period",
"owner": "Administrator",
diff --git a/erpnext/payroll/onboarding_step/payroll_settings/payroll_settings.json b/erpnext/payroll/onboarding_step/payroll_settings/payroll_settings.json
index 946b8c8..a7cf7bf 100644
--- a/erpnext/payroll/onboarding_step/payroll_settings/payroll_settings.json
+++ b/erpnext/payroll/onboarding_step/payroll_settings/payroll_settings.json
@@ -1,19 +1,19 @@
{
- "action": "Go to Page",
+ "action": "Update Settings",
"creation": "2020-06-04 16:34:29.664917",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_mandatory": 0,
- "is_single": 0,
+ "is_single": 1,
"is_skipped": 0,
- "modified": "2020-06-04 16:34:29.664917",
+ "modified": "2020-06-29 16:34:29.664917",
"modified_by": "Administrator",
"name": "Payroll Settings",
"owner": "Administrator",
- "path": "#Form/Payroll Settings",
+ "reference_document": "Payroll Settings",
"show_full_form": 0,
"title": "Payroll Settings",
- "validate_action": 1
+ "validate_action": 0
}
\ No newline at end of file
diff --git a/erpnext/portal/doctype/homepage/test_homepage.py b/erpnext/portal/doctype/homepage/test_homepage.py
index bf5c402..b717491 100644
--- a/erpnext/portal/doctype/homepage/test_homepage.py
+++ b/erpnext/portal/doctype/homepage/test_homepage.py
@@ -13,7 +13,7 @@
set_request(method='GET', path='home')
response = render()
- self.assertEquals(response.status_code, 200)
+ self.assertEqual(response.status_code, 200)
html = frappe.safe_decode(response.get_data())
self.assertTrue('<section class="hero-section' in html)
diff --git a/erpnext/portal/doctype/homepage_section/test_homepage_section.py b/erpnext/portal/doctype/homepage_section/test_homepage_section.py
index 5b3196d..f0aa554 100644
--- a/erpnext/portal/doctype/homepage_section/test_homepage_section.py
+++ b/erpnext/portal/doctype/homepage_section/test_homepage_section.py
@@ -28,7 +28,7 @@
set_request(method='GET', path='home')
response = render()
- self.assertEquals(response.status_code, 200)
+ self.assertEqual(response.status_code, 200)
html = frappe.safe_decode(response.get_data())
@@ -61,7 +61,7 @@
set_request(method='GET', path='home')
response = render()
- self.assertEquals(response.status_code, 200)
+ self.assertEqual(response.status_code, 200)
html = frappe.safe_decode(response.get_data())
diff --git a/erpnext/portal/doctype/products_settings/products_settings.js b/erpnext/portal/doctype/products_settings/products_settings.js
index b68b5d7..2f8b037 100644
--- a/erpnext/portal/doctype/products_settings/products_settings.js
+++ b/erpnext/portal/doctype/products_settings/products_settings.js
@@ -10,10 +10,12 @@
df => ['Link', 'Table MultiSelect'].includes(df.fieldtype) && !df.hidden
).map(df => ({ label: df.label, value: df.fieldname }));
- const field = frappe.meta.get_docfield("Website Filter Field", "fieldname", frm.docname);
- field.fieldtype = 'Select';
- field.options = valid_fields;
- frm.fields_dict.filter_fields.grid.refresh();
+ frm.fields_dict.filter_fields.grid.update_docfield_property(
+ 'fieldname', 'fieldtype', 'Select'
+ );
+ frm.fields_dict.filter_fields.grid.update_docfield_property(
+ 'fieldname', 'options', valid_fields
+ );
});
}
});
diff --git a/erpnext/projects/doctype/activity_type/activity_type.js b/erpnext/projects/doctype/activity_type/activity_type.js
index 7eb3571..f1ba882 100644
--- a/erpnext/projects/doctype/activity_type/activity_type.js
+++ b/erpnext/projects/doctype/activity_type/activity_type.js
@@ -1,4 +1,8 @@
frappe.ui.form.on("Activity Type", {
+ onload: function(frm) {
+ frm.set_currency_labels(["billing_rate", "costing_rate"], frappe.defaults.get_global_default('currency'));
+ },
+
refresh: function(frm) {
frm.add_custom_button(__("Activity Cost per Employee"), function() {
frappe.route_options = {"activity_type": frm.doc.name};
diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js
index c5265e2..31460f6 100644
--- a/erpnext/projects/doctype/project/project.js
+++ b/erpnext/projects/doctype/project/project.js
@@ -87,7 +87,7 @@
frm.add_custom_button(__("Kanban Board"), () => {
frappe.call('erpnext.projects.doctype.project.project.create_kanban_board_if_not_exists', {
- project: frm.doc.project_name
+ project: frm.doc.name
}).then(() => {
frappe.set_route('List', 'Task', 'Kanban', frm.doc.project_name);
});
diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json
index 3cdfcb2..2570df7 100644
--- a/erpnext/projects/doctype/project/project.json
+++ b/erpnext/projects/doctype/project/project.json
@@ -458,7 +458,7 @@
"index_web_pages_for_search": 1,
"links": [],
"max_attachments": 4,
- "modified": "2020-09-02 11:54:01.223620",
+ "modified": "2021-04-28 16:36:11.654632",
"modified_by": "Administrator",
"module": "Projects",
"name": "Project",
@@ -495,11 +495,11 @@
}
],
"quick_entry": 1,
- "search_fields": "customer, status, priority, is_active",
+ "search_fields": "project_name,customer, status, priority, is_active",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"timeline_field": "customer",
"title_field": "project_name",
"track_seen": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index f9e1359..c8fbe0b 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -179,9 +179,6 @@
if self.percent_complete == 100:
self.status = "Completed"
- else:
- self.status = "Open"
-
def update_costing(self):
from_time_sheet = frappe.db.sql("""select
sum(costing_amount) as costing_amount,
@@ -526,8 +523,9 @@
def create_kanban_board_if_not_exists(project):
from frappe.desk.doctype.kanban_board.kanban_board import quick_kanban_board
- if not frappe.db.exists('Kanban Board', project):
- quick_kanban_board('Task', project, 'status', project)
+ project = frappe.get_doc('Project', project)
+ if not frappe.db.exists('Kanban Board', project.project_name):
+ quick_kanban_board('Task', project.project_name, 'status', project.name)
return True
diff --git a/erpnext/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js
index 002ddb2..3cd92ee 100644
--- a/erpnext/projects/doctype/task/task.js
+++ b/erpnext/projects/doctype/task/task.js
@@ -5,12 +5,6 @@
frappe.ui.form.on("Task", {
setup: function (frm) {
- frm.set_query("project", function () {
- return {
- query: "erpnext.projects.doctype.task.task.get_project"
- }
- });
-
frm.make_methods = {
'Timesheet': () => frappe.model.open_mapped_doc({
method: 'erpnext.projects.doctype.task.task.make_timesheet',
@@ -32,7 +26,8 @@
frm.set_query("parent_task", function () {
let filters = {
- "is_group": 1
+ "is_group": 1,
+ "name": ["!=", frm.doc.name]
};
if (frm.doc.project) filters["project"] = frm.doc.project;
return {
diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json
index 160cc58..ef4740d 100644
--- a/erpnext/projects/doctype/task/task.json
+++ b/erpnext/projects/doctype/task/task.json
@@ -11,15 +11,16 @@
"project",
"issue",
"type",
+ "color",
"is_group",
"is_template",
"column_break0",
"status",
"priority",
"task_weight",
- "completed_by",
- "color",
"parent_task",
+ "completed_by",
+ "completed_on",
"sb_timeline",
"exp_start_date",
"expected_time",
@@ -358,6 +359,7 @@
"read_only": 1
},
{
+ "depends_on": "eval: doc.status == \"Completed\"",
"fieldname": "completed_by",
"fieldtype": "Link",
"label": "Completed By",
@@ -381,6 +383,13 @@
"fieldname": "duration",
"fieldtype": "Int",
"label": "Duration (Days)"
+ },
+ {
+ "depends_on": "eval: doc.status == \"Completed\"",
+ "fieldname": "completed_on",
+ "fieldtype": "Date",
+ "label": "Completed On",
+ "mandatory_depends_on": "eval: doc.status == \"Completed\""
}
],
"icon": "fa fa-check",
@@ -388,7 +397,7 @@
"is_tree": 1,
"links": [],
"max_attachments": 5,
- "modified": "2020-12-28 11:32:58.714991",
+ "modified": "2021-04-16 12:46:51.556741",
"modified_by": "Administrator",
"module": "Projects",
"name": "Task",
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index 855ff5f..d1583f1 100755
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -36,6 +36,7 @@
self.validate_status()
self.update_depends_on()
self.validate_dependencies_for_template_task()
+ self.validate_completed_on()
def validate_dates(self):
if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date):
@@ -100,6 +101,10 @@
dependent_task_format = """<a href="#Form/Task/{0}">{0}</a>""".format(task.task)
frappe.throw(_("Dependent Task {0} is not a Template Task").format(dependent_task_format))
+ def validate_completed_on(self):
+ if self.completed_on and getdate(self.completed_on) > getdate():
+ frappe.throw(_("Completed On cannot be greater than Today"))
+
def update_depends_on(self):
depends_on_tasks = self.depends_on_tasks or ""
for d in self.depends_on:
diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py
index f7c764e..2b0c3ab 100644
--- a/erpnext/projects/doctype/timesheet/test_timesheet.py
+++ b/erpnext/projects/doctype/timesheet/test_timesheet.py
@@ -37,7 +37,7 @@
emp = make_employee("test_employee_6@salary.com")
make_salary_structure_for_timesheet(emp)
- timesheet = make_timesheet(emp, simulate=True, billable=1)
+ timesheet = make_timesheet(emp, simulate=True, is_billable=1)
self.assertEqual(timesheet.total_hours, 2)
self.assertEqual(timesheet.total_billable_hours, 2)
@@ -49,7 +49,7 @@
emp = make_employee("test_employee_6@salary.com")
make_salary_structure_for_timesheet(emp)
- timesheet = make_timesheet(emp, simulate=True, billable=0)
+ timesheet = make_timesheet(emp, simulate=True, is_billable=0)
self.assertEqual(timesheet.total_hours, 2)
self.assertEqual(timesheet.total_billable_hours, 0)
@@ -61,7 +61,7 @@
emp = make_employee("test_employee_6@salary.com", company="_Test Company")
salary_structure = make_salary_structure_for_timesheet(emp)
- timesheet = make_timesheet(emp, simulate = True, billable=1)
+ timesheet = make_timesheet(emp, simulate = True, is_billable=1)
salary_slip = make_salary_slip(timesheet.name)
salary_slip.submit()
@@ -82,7 +82,7 @@
def test_sales_invoice_from_timesheet(self):
emp = make_employee("test_employee_6@salary.com")
- timesheet = make_timesheet(emp, simulate=True, billable=1)
+ timesheet = make_timesheet(emp, simulate=True, is_billable=1)
sales_invoice = make_sales_invoice(timesheet.name, '_Test Item', '_Test Customer')
sales_invoice.due_date = nowdate()
sales_invoice.submit()
@@ -100,7 +100,7 @@
emp = make_employee("test_employee_6@salary.com")
project = frappe.get_value("Project", {"project_name": "_Test Project"})
- timesheet = make_timesheet(emp, simulate=True, billable=1, project=project, company='_Test Company')
+ timesheet = make_timesheet(emp, simulate=True, is_billable=1, project=project, company='_Test Company')
sales_invoice = create_sales_invoice(do_not_save=True)
sales_invoice.project = project
sales_invoice.submit()
@@ -151,11 +151,11 @@
settings.save()
-def make_salary_structure_for_timesheet(employee):
+def make_salary_structure_for_timesheet(employee, company=None):
salary_structure_name = "Timesheet Salary Structure Test"
frequency = "Monthly"
- salary_structure = make_salary_structure(salary_structure_name, frequency, dont_submit=True)
+ salary_structure = make_salary_structure(salary_structure_name, frequency, company=company, dont_submit=True)
salary_structure.salary_component = "Timesheet Component"
salary_structure.salary_slip_based_on_timesheet = 1
salary_structure.hour_rate = 50.0
@@ -171,13 +171,13 @@
return salary_structure
-def make_timesheet(employee, simulate=False, billable = 0, activity_type="_Test Activity Type", project=None, task=None, company=None):
+def make_timesheet(employee, simulate=False, is_billable = 0, activity_type="_Test Activity Type", project=None, task=None, company=None):
update_activity_type(activity_type)
timesheet = frappe.new_doc("Timesheet")
timesheet.employee = employee
timesheet.company = company or '_Test Company'
timesheet_detail = timesheet.append('time_logs', {})
- timesheet_detail.billable = billable
+ timesheet_detail.is_billable = is_billable
timesheet_detail.activity_type = activity_type
timesheet_detail.from_time = now_datetime()
timesheet_detail.hours = 2
diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js
index b123af5..84c7b81 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.js
+++ b/erpnext/projects/doctype/timesheet/timesheet.js
@@ -90,17 +90,99 @@
}
if(frm.doc.per_billed > 0) {
frm.fields_dict["time_logs"].grid.toggle_enable("billing_hours", false);
- frm.fields_dict["time_logs"].grid.toggle_enable("billable", false);
+ frm.fields_dict["time_logs"].grid.toggle_enable("is_billable", false);
}
+ frm.trigger('setup_filters');
+ frm.trigger('set_dynamic_field_label');
+ },
+
+ customer: function(frm) {
+ frm.set_query('parent_project', function(doc) {
+ return {
+ filters: {
+ "customer": doc.customer
+ }
+ };
+ });
+ frm.set_query('project', 'time_logs', function(doc) {
+ return {
+ filters: {
+ "customer": doc.customer
+ }
+ };
+ });
+ frm.refresh();
+ },
+
+ currency: function(frm) {
+ let base_currency = frappe.defaults.get_global_default('currency');
+ if (base_currency != frm.doc.currency) {
+ frappe.call({
+ method: "erpnext.setup.utils.get_exchange_rate",
+ args: {
+ from_currency: frm.doc.currency,
+ to_currency: base_currency
+ },
+ callback: function(r) {
+ if (r.message) {
+ frm.set_value('exchange_rate', flt(r.message));
+ frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency + " = [?] " + base_currency);
+ }
+ }
+ });
+ }
+ frm.trigger('set_dynamic_field_label');
+ },
+
+ exchange_rate: function(frm) {
+ $.each(frm.doc.time_logs, function(i, d) {
+ calculate_billing_costing_amount(frm, d.doctype, d.name);
+ });
+ calculate_time_and_amount(frm);
+ },
+
+ set_dynamic_field_label: function(frm) {
+ let base_currency = frappe.defaults.get_global_default('currency');
+ frm.set_currency_labels(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], base_currency);
+ frm.set_currency_labels(["total_costing_amount", "total_billable_amount", "total_billed_amount"], frm.doc.currency);
+
+ frm.toggle_display(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"],
+ frm.doc.currency != base_currency);
+
+ if (frm.doc.time_logs.length > 0) {
+ frm.set_currency_labels(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], base_currency, "time_logs");
+ frm.set_currency_labels(["billing_rate", "billing_amount", "costing_rate", "costing_amount"], frm.doc.currency, "time_logs");
+
+ let time_logs_grid = frm.fields_dict.time_logs.grid;
+ $.each(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], function(i, d) {
+ if (frappe.meta.get_docfield(time_logs_grid.doctype, d))
+ time_logs_grid.set_column_disp(d, frm.doc.currency != base_currency);
+ });
+ }
+ frm.refresh_fields();
},
make_invoice: function(frm) {
+ let fields = [{
+ "fieldtype": "Link",
+ "label": __("Item Code"),
+ "fieldname": "item_code",
+ "options": "Item"
+ }];
+
+ if (!frm.doc.customer) {
+ fields.push({
+ "fieldtype": "Link",
+ "label": __("Customer"),
+ "fieldname": "customer",
+ "options": "Customer",
+ "default": frm.doc.customer
+ });
+ }
+
let dialog = new frappe.ui.Dialog({
- title: __("Select Item (optional)"),
- fields: [
- {"fieldtype": "Link", "label": __("Item Code"), "fieldname": "item_code", "options":"Item"},
- {"fieldtype": "Link", "label": __("Customer"), "fieldname": "customer", "options":"Customer"}
- ]
+ title: __("Create Sales Invoice"),
+ fields: fields
});
dialog.set_primary_action(__('Create Sales Invoice'), () => {
@@ -113,7 +195,8 @@
args: {
"source_name": frm.doc.name,
"item_code": args.item_code,
- "customer": args.customer
+ "customer": frm.doc.customer || args.customer,
+ "currency": frm.doc.currency
},
freeze: true,
callback: function(r) {
@@ -136,8 +219,7 @@
parent_project: function(frm) {
set_project_in_timelog(frm);
- },
-
+ }
});
frappe.ui.form.on("Timesheet Detail", {
@@ -171,35 +253,34 @@
if(frm.doc.parent_project) {
frappe.model.set_value(cdt, cdn, 'project', frm.doc.parent_project);
}
-
- var $trigger_again = $('.form-grid').find('.grid-row').find('.btn-open-row');
- $trigger_again.on('click', () => {
- $('.form-grid')
- .find('[data-fieldname="timer"]')
- .append(frappe.render_template("timesheet"));
- frm.trigger("control_timer");
- });
},
+
hours: function(frm, cdt, cdn) {
calculate_end_time(frm, cdt, cdn);
+ calculate_billing_costing_amount(frm, cdt, cdn);
+ calculate_time_and_amount(frm);
},
billing_hours: function(frm, cdt, cdn) {
calculate_billing_costing_amount(frm, cdt, cdn);
+ calculate_time_and_amount(frm);
},
billing_rate: function(frm, cdt, cdn) {
calculate_billing_costing_amount(frm, cdt, cdn);
+ calculate_time_and_amount(frm);
},
costing_rate: function(frm, cdt, cdn) {
calculate_billing_costing_amount(frm, cdt, cdn);
+ calculate_time_and_amount(frm);
},
- billable: function(frm, cdt, cdn) {
+ is_billable: function(frm, cdt, cdn) {
update_billing_hours(frm, cdt, cdn);
update_time_rates(frm, cdt, cdn);
calculate_billing_costing_amount(frm, cdt, cdn);
+ calculate_time_and_amount(frm);
},
activity_type: function(frm, cdt, cdn) {
@@ -207,7 +288,8 @@
method: "erpnext.projects.doctype.timesheet.timesheet.get_activity_cost",
args: {
employee: frm.doc.employee,
- activity_type: frm.selected_doc.activity_type
+ activity_type: frm.selected_doc.activity_type,
+ currency: frm.doc.currency
},
callback: function(r){
if(r.message){
@@ -239,9 +321,9 @@
}
};
-var update_billing_hours = function(frm, cdt, cdn){
- var child = locals[cdt][cdn];
- if(!child.billable) {
+var update_billing_hours = function(frm, cdt, cdn) {
+ let child = frappe.get_doc(cdt, cdn);
+ if (!child.is_billable) {
frappe.model.set_value(cdt, cdn, 'billing_hours', 0.0);
} else {
// bill all hours by default
@@ -249,40 +331,44 @@
}
};
-var update_time_rates = function(frm, cdt, cdn){
- var child = locals[cdt][cdn];
- if(!child.billable){
+var update_time_rates = function(frm, cdt, cdn) {
+ let child = frappe.get_doc(cdt, cdn);
+ if (!child.is_billable) {
frappe.model.set_value(cdt, cdn, 'billing_rate', 0.0);
}
};
-var calculate_billing_costing_amount = function(frm, cdt, cdn){
- var child = locals[cdt][cdn];
- var billing_amount = 0.0;
- var costing_amount = 0.0;
-
- if(child.billing_hours && child.billable){
- billing_amount = (child.billing_hours * child.billing_rate);
+var calculate_billing_costing_amount = function(frm, cdt, cdn) {
+ let row = frappe.get_doc(cdt, cdn);
+ let billing_amount = 0.0;
+ let base_billing_amount = 0.0;
+ let exchange_rate = flt(frm.doc.exchange_rate);
+ frappe.model.set_value(cdt, cdn, 'base_billing_rate', flt(row.billing_rate) * exchange_rate);
+ frappe.model.set_value(cdt, cdn, 'base_costing_rate', flt(row.costing_rate) * exchange_rate);
+ if (row.billing_hours && row.is_billable) {
+ base_billing_amount = flt(row.billing_hours) * flt(row.base_billing_rate);
+ billing_amount = flt(row.billing_hours) * flt(row.billing_rate);
}
- costing_amount = flt(child.costing_rate * child.hours);
+
+ frappe.model.set_value(cdt, cdn, 'base_billing_amount', base_billing_amount);
+ frappe.model.set_value(cdt, cdn, 'base_costing_amount', flt(row.base_costing_rate) * flt(row.hours));
frappe.model.set_value(cdt, cdn, 'billing_amount', billing_amount);
- frappe.model.set_value(cdt, cdn, 'costing_amount', costing_amount);
- calculate_time_and_amount(frm);
+ frappe.model.set_value(cdt, cdn, 'costing_amount', flt(row.costing_rate) * flt(row.hours));
};
var calculate_time_and_amount = function(frm) {
- var tl = frm.doc.time_logs || [];
- var total_working_hr = 0;
- var total_billing_hr = 0;
- var total_billable_amount = 0;
- var total_costing_amount = 0;
+ let tl = frm.doc.time_logs || [];
+ let total_working_hr = 0;
+ let total_billing_hr = 0;
+ let total_billable_amount = 0;
+ let total_costing_amount = 0;
for(var i=0; i<tl.length; i++) {
if (tl[i].hours) {
total_working_hr += tl[i].hours;
total_billable_amount += tl[i].billing_amount;
total_costing_amount += tl[i].costing_amount;
- if(tl[i].billable){
+ if (tl[i].is_billable) {
total_billing_hr += tl[i].billing_hours;
}
}
diff --git a/erpnext/projects/doctype/timesheet/timesheet.json b/erpnext/projects/doctype/timesheet/timesheet.json
index b286821..75f7478 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.json
+++ b/erpnext/projects/doctype/timesheet/timesheet.json
@@ -11,6 +11,9 @@
"title",
"naming_series",
"company",
+ "customer",
+ "currency",
+ "exchange_rate",
"sales_invoice",
"column_break_3",
"salary_slip",
@@ -30,11 +33,14 @@
"total_hours",
"billing_details",
"total_billable_hours",
- "total_billed_hours",
- "total_costing_amount",
+ "base_total_billable_amount",
+ "base_total_billed_amount",
+ "base_total_costing_amount",
"column_break_10",
+ "total_billed_hours",
"total_billable_amount",
"total_billed_amount",
+ "total_costing_amount",
"per_billed",
"section_break_18",
"note",
@@ -176,7 +182,6 @@
"default": "0",
"fieldname": "total_hours",
"fieldtype": "Float",
- "in_list_view": 1,
"label": "Total Working Hours",
"read_only": 1
},
@@ -199,7 +204,6 @@
"allow_on_submit": 1,
"fieldname": "total_billed_hours",
"fieldtype": "Float",
- "in_list_view": 1,
"label": "Total Billed Hours",
"print_hide": 1,
"read_only": 1
@@ -209,6 +213,7 @@
"fieldname": "total_costing_amount",
"fieldtype": "Currency",
"label": "Total Costing Amount",
+ "options": "currency",
"print_hide": 1,
"read_only": 1
},
@@ -222,6 +227,7 @@
"fieldname": "total_billable_amount",
"fieldtype": "Currency",
"label": "Total Billable Amount",
+ "options": "currency",
"read_only": 1
},
{
@@ -229,6 +235,7 @@
"fieldname": "total_billed_amount",
"fieldtype": "Currency",
"label": "Total Billed Amount",
+ "options": "currency",
"print_hide": 1,
"read_only": 1
},
@@ -236,6 +243,7 @@
"allow_on_submit": 1,
"fieldname": "per_billed",
"fieldtype": "Percent",
+ "in_list_view": 1,
"label": "% Amount Billed",
"no_copy": 1,
"print_hide": 1,
@@ -265,13 +273,53 @@
"fieldtype": "Link",
"label": "Project",
"options": "Project"
+ },
+ {
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "label": "Customer",
+ "options": "Customer"
+ },
+ {
+ "fetch_from": "customer.default_currency",
+ "fetch_if_empty": 1,
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "label": "Currency",
+ "options": "Currency"
+ },
+ {
+ "fieldname": "base_total_costing_amount",
+ "fieldtype": "Currency",
+ "label": "Total Costing Amount",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "base_total_billable_amount",
+ "fieldtype": "Currency",
+ "label": "Total Billable Amount",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "base_total_billed_amount",
+ "fieldtype": "Currency",
+ "label": "Total Billed Amount",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "exchange_rate",
+ "fieldtype": "Float",
+ "label": "Exchange Rate"
}
],
"icon": "fa fa-clock-o",
"idx": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-01-08 20:51:14.590080",
+ "modified": "2021-05-18 16:10:08.249619",
"modified_by": "Administrator",
"module": "Projects",
"name": "Timesheet",
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index ed02f79..a3e4577 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -14,6 +14,7 @@
from erpnext.manufacturing.doctype.workstation.workstation import (check_if_within_operating_hours,
WorkstationHolidayError)
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
+from erpnext.setup.utils import get_exchange_rate
class OverlapError(frappe.ValidationError): pass
class OverWorkLoggedError(frappe.ValidationError): pass
@@ -37,9 +38,9 @@
self.total_hours = 0.0
self.total_billable_hours = 0.0
self.total_billed_hours = 0.0
- self.total_billable_amount = 0.0
- self.total_costing_amount = 0.0
- self.total_billed_amount = 0.0
+ self.total_billable_amount = self.base_total_billable_amount = 0.0
+ self.total_costing_amount = self.base_total_costing_amount = 0.0
+ self.total_billed_amount = self.base_total_billed_amount = 0.0
for d in self.get("time_logs"):
self.update_billing_hours(d)
@@ -47,10 +48,13 @@
self.total_hours += flt(d.hours)
self.total_costing_amount += flt(d.costing_amount)
- if d.billable:
+ self.base_total_costing_amount += flt(d.base_costing_amount)
+ if d.is_billable:
self.total_billable_hours += flt(d.billing_hours)
self.total_billable_amount += flt(d.billing_amount)
+ self.base_total_billable_amount += flt(d.base_billing_amount)
self.total_billed_amount += flt(d.billing_amount) if d.sales_invoice else 0.0
+ self.base_total_billed_amount += flt(d.base_billing_amount) if d.sales_invoice else 0.0
self.total_billed_hours += flt(d.billing_hours) if d.sales_invoice else 0.0
def calculate_percentage_billed(self):
@@ -59,7 +63,7 @@
self.per_billed = (self.total_billed_amount * 100) / self.total_billable_amount
def update_billing_hours(self, args):
- if args.billable:
+ if args.is_billable:
if flt(args.billing_hours) == 0.0:
args.billing_hours = args.hours
else:
@@ -133,16 +137,20 @@
def validate_time_logs(self):
for data in self.get('time_logs'):
self.validate_overlap(data)
- self.validate_task_project()
+ self.set_project(data)
+ self.validate_project(data)
def validate_overlap(self, data):
settings = frappe.get_single('Projects Settings')
self.validate_overlap_for("user", data, self.user, settings.ignore_user_time_overlap)
self.validate_overlap_for("employee", data, self.employee, settings.ignore_employee_time_overlap)
- def validate_task_project(self):
- for log in self.time_logs:
- log.project = log.project or frappe.db.get_value("Task", log.task, "project")
+ def set_project(self, data):
+ data.project = data.project or frappe.db.get_value("Task", data.task, "project")
+
+ def validate_project(self, data):
+ if self.parent_project and self.parent_project != data.project:
+ frappe.throw(_("Row {0}: Project must be same as the one set in the Timesheet: {1}.").format(data.idx, self.parent_project))
def validate_overlap_for(self, fieldname, args, value, ignore_validation=False):
if not value or ignore_validation:
@@ -189,7 +197,7 @@
def update_cost(self):
for data in self.time_logs:
- if data.activity_type or data.billable:
+ if data.activity_type or data.is_billable:
rate = get_activity_cost(self.employee, data.activity_type)
hours = data.billing_hours or 0
costing_hours = data.billing_hours or data.hours or 0
@@ -200,20 +208,29 @@
data.costing_amount = data.costing_rate * costing_hours
def update_time_rates(self, ts_detail):
- if not ts_detail.billable:
+ if not ts_detail.is_billable:
ts_detail.billing_rate = 0.0
@frappe.whitelist()
-def get_projectwise_timesheet_data(project, parent=None, from_time=None, to_time=None):
+def get_projectwise_timesheet_data(project=None, parent=None, from_time=None, to_time=None):
condition = ''
+ if project:
+ condition += "and tsd.project = %(project)s"
if parent:
- condition = "AND parent = %(parent)s"
+ condition += "AND tsd.parent = %(parent)s"
if from_time and to_time:
- condition += "AND from_time BETWEEN %(from_time)s AND %(to_time)s"
+ condition += "AND CAST(tsd.from_time as DATE) BETWEEN %(from_time)s AND %(to_time)s"
- return frappe.db.sql("""select name, parent, billing_hours, billing_amount as billing_amt
- from `tabTimesheet Detail` where parenttype = 'Timesheet' and docstatus=1 and project = %(project)s {0} and billable = 1
- and sales_invoice is null""".format(condition), {'project': project, 'parent': parent, 'from_time': from_time, 'to_time': to_time}, as_dict=1)
+ return frappe.db.sql("""SELECT tsd.name as name,
+ tsd.parent as parent, tsd.billing_hours as billing_hours,
+ tsd.billing_amount as billing_amount, tsd.activity_type as activity_type,
+ tsd.description as description, ts.currency as currency
+ FROM `tabTimesheet Detail` tsd
+ INNER JOIN `tabTimesheet` ts ON ts.name = tsd.parent
+ WHERE tsd.parenttype = 'Timesheet'
+ and tsd.docstatus=1 {0}
+ and tsd.is_billable = 1
+ and tsd.sales_invoice is null""".format(condition), {'project': project, 'parent': parent, 'from_time': from_time, 'to_time': to_time}, as_dict=1)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
@@ -250,7 +267,7 @@
}
@frappe.whitelist()
-def make_sales_invoice(source_name, item_code=None, customer=None):
+def make_sales_invoice(source_name, item_code=None, customer=None, currency=None):
target = frappe.new_doc("Sales Invoice")
timesheet = frappe.get_doc('Timesheet', source_name)
@@ -268,6 +285,9 @@
if customer:
target.customer = customer
+ if currency:
+ target.currency = currency
+
if item_code:
target.append('items', {
'item_code': item_code,
@@ -275,11 +295,16 @@
'rate': billing_rate
})
- target.append('timesheets', {
- 'time_sheet': timesheet.name,
- 'billing_hours': hours,
- 'billing_amount': billing_amount
- })
+ for time_log in timesheet.time_logs:
+ if time_log.is_billable:
+ target.append('timesheets', {
+ 'time_sheet': timesheet.name,
+ 'billing_hours': time_log.billing_hours,
+ 'billing_amount': time_log.billing_amount,
+ 'timesheet_detail': time_log.name,
+ 'activity_type': time_log.activity_type,
+ 'description': time_log.description
+ })
target.run_method("calculate_billing_amount_for_timesheet")
target.run_method("set_missing_values")
@@ -309,12 +334,17 @@
})
@frappe.whitelist()
-def get_activity_cost(employee=None, activity_type=None):
+def get_activity_cost(employee=None, activity_type=None, currency=None):
+ base_currency = frappe.defaults.get_global_default('currency')
rate = frappe.db.get_values("Activity Cost", {"employee": employee,
"activity_type": activity_type}, ["costing_rate", "billing_rate"], as_dict=True)
if not rate:
rate = frappe.db.get_values("Activity Type", {"activity_type": activity_type},
["costing_rate", "billing_rate"], as_dict=True)
+ if rate and currency and currency!=base_currency:
+ exchange_rate = get_exchange_rate(base_currency, currency)
+ rate[0]["costing_rate"] = rate[0]["costing_rate"] * exchange_rate
+ rate[0]["billing_rate"] = rate[0]["billing_rate"] * exchange_rate
return rate[0] if rate else {}
diff --git a/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json b/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json
index a9b3bfb..ee04c61 100644
--- a/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json
+++ b/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json
@@ -1,979 +1,279 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2013-03-05 09:11:06",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2013-03-05 09:11:06",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "activity_type",
+ "from_time",
+ "description",
+ "section_break_3",
+ "expected_hours",
+ "to_time",
+ "hours",
+ "completed",
+ "section_break_7",
+ "completed_qty",
+ "workstation",
+ "column_break_12",
+ "operation",
+ "operation_id",
+ "project_details",
+ "project",
+ "project_name",
+ "column_break_2",
+ "task",
+ "section_break_6",
+ "is_billable",
+ "sales_invoice",
+ "column_break_8",
+ "billing_hours",
+ "section_break_11",
+ "base_billing_rate",
+ "base_billing_amount",
+ "base_costing_rate",
+ "base_costing_amount",
+ "column_break_14",
+ "billing_rate",
+ "billing_amount",
+ "costing_rate",
+ "costing_amount"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "activity_type",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Activity Type",
- "length": 0,
- "no_copy": 0,
- "options": "Activity Type",
- "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
- },
+ "fieldname": "activity_type",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Activity Type",
+ "options": "Activity Type"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "fieldname": "from_time",
- "fieldtype": "Datetime",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "From Time",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "columns": 2,
+ "fieldname": "from_time",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "From Time"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_3",
- "fieldtype": "Column Break",
- "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,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "expected_hours",
- "fieldtype": "Float",
- "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": "Expected Hrs",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "expected_hours",
+ "fieldtype": "Float",
+ "label": "Expected Hrs"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 1,
- "fieldname": "hours",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Hrs",
- "length": 0,
- "no_copy": 0,
- "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
- },
+ "columns": 1,
+ "fieldname": "hours",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Hrs"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "to_time",
- "fieldtype": "Datetime",
- "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": "To Time",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "to_time",
+ "fieldtype": "Datetime",
+ "label": "To Time"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "fieldname": "completed",
- "fieldtype": "Check",
- "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": "Completed",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "completed",
+ "fieldtype": "Check",
+ "label": "Completed"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_7",
- "fieldtype": "Section Break",
- "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,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_7",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:parent.work_order",
- "fieldname": "completed_qty",
- "fieldtype": "Float",
- "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": "Completed Qty",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "eval:parent.work_order",
+ "fieldname": "completed_qty",
+ "fieldtype": "Float",
+ "label": "Completed Qty"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:parent.work_order",
- "fieldname": "workstation",
- "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": "Workstation",
- "length": 0,
- "no_copy": 0,
- "options": "Workstation",
- "permlevel": 0,
- "precision": "",
- "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
- },
+ "depends_on": "eval:parent.work_order",
+ "fieldname": "workstation",
+ "fieldtype": "Link",
+ "label": "Workstation",
+ "options": "Workstation",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_12",
- "fieldtype": "Column Break",
- "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,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_12",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:parent.work_order",
- "fieldname": "operation",
- "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": "Operation",
- "length": 0,
- "no_copy": 0,
- "options": "Operation",
- "permlevel": 0,
- "precision": "",
- "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
- },
+ "depends_on": "eval:parent.work_order",
+ "fieldname": "operation",
+ "fieldtype": "Link",
+ "label": "Operation",
+ "options": "Operation",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:parent.work_order",
- "fieldname": "operation_id",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Operation Id",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "eval:parent.work_order",
+ "fieldname": "operation_id",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Operation Id"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "project_details",
- "fieldtype": "Section Break",
- "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,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "project_details",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 3,
- "fieldname": "project",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Project",
- "length": 0,
- "no_copy": 0,
- "options": "Project",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "columns": 3,
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Project",
+ "options": "Project",
+ "read_only_depends_on": "eval: parent.parent_project"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_2",
- "fieldtype": "Column Break",
- "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,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fieldname": "task",
- "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": "Task",
- "length": 0,
- "no_copy": 0,
- "options": "Task",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "task",
+ "fieldtype": "Link",
+ "label": "Task",
+ "options": "Task",
+ "remember_last_selected_value": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_6",
- "fieldtype": "Section Break",
- "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,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_6",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 1,
- "depends_on": "",
- "fieldname": "billable",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Bill",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "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
- },
+ "fieldname": "column_break_8",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_8",
- "fieldtype": "Column Break",
- "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,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "depends_on": "is_billable",
+ "fieldname": "billing_hours",
+ "fieldtype": "Float",
+ "label": "Billing Hours",
+ "permlevel": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "billable",
- "fieldname": "billing_hours",
- "fieldtype": "Float",
- "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": "Billing Hours",
- "length": 0,
- "no_copy": 0,
- "permlevel": 1,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "is_billable",
+ "fieldname": "section_break_11",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "billable",
- "fieldname": "section_break_11",
- "fieldtype": "Section Break",
- "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,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "billing_rate",
+ "fieldtype": "Currency",
+ "label": "Billing Rate",
+ "options": "currency",
+ "permlevel": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fieldname": "billing_rate",
- "fieldtype": "Currency",
- "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": "Billing Rate",
- "length": 0,
- "no_copy": 0,
- "permlevel": 1,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "default": "0",
+ "fieldname": "billing_amount",
+ "fieldtype": "Currency",
+ "label": "Billing Amount",
+ "options": "currency",
+ "permlevel": 1,
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "depends_on": "",
- "description": "",
- "fieldname": "billing_amount",
- "fieldtype": "Currency",
- "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": "Billing Amount",
- "length": 0,
- "no_copy": 0,
- "permlevel": 1,
- "precision": "",
- "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
- },
+ "fieldname": "column_break_14",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_14",
- "fieldtype": "Column Break",
- "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,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "costing_rate",
+ "fieldtype": "Currency",
+ "label": "Costing Rate",
+ "options": "currency",
+ "permlevel": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "costing_rate",
- "fieldtype": "Currency",
- "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": "Costing Rate",
- "length": 0,
- "no_copy": 0,
- "permlevel": 1,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "default": "0",
+ "fieldname": "costing_amount",
+ "fieldtype": "Currency",
+ "label": "Costing Amount",
+ "options": "currency",
+ "permlevel": 1,
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "description": "",
- "fieldname": "costing_amount",
- "fieldtype": "Currency",
- "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": "Costing Amount",
- "length": 0,
- "no_copy": 0,
- "permlevel": 1,
- "precision": "",
- "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_on_submit": 1,
+ "fieldname": "sales_invoice",
+ "fieldtype": "Link",
+ "label": "Sales Invoice",
+ "no_copy": 1,
+ "options": "Sales Invoice",
+ "print_hide": 1,
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "reference",
- "fieldtype": "Section Break",
- "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": "Reference",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "columns": 1,
+ "default": "0",
+ "fieldname": "is_billable",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Is Billable",
+ "print_hide": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sales_invoice",
- "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": "Sales Invoice",
- "length": 0,
- "no_copy": 1,
- "options": "Sales Invoice",
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "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
+ "fetch_from": "project.project_name",
+ "fieldname": "project_name",
+ "fieldtype": "Data",
+ "label": "Project Name",
+ "read_only": 1
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Small Text",
+ "label": "Description"
+ },
+ {
+ "fieldname": "base_billing_rate",
+ "fieldtype": "Currency",
+ "label": "Billing Rate",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "base_billing_amount",
+ "fieldtype": "Currency",
+ "label": "Billing Amount",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "base_costing_rate",
+ "fieldtype": "Currency",
+ "label": "Costing Rate",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "base_costing_amount",
+ "fieldtype": "Currency",
+ "label": "Costing Amount",
+ "print_hide": 1,
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2019-02-18 18:55:53.190526",
- "modified_by": "Administrator",
- "module": "Projects",
- "name": "Timesheet Detail",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_order": "ASC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "idx": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-05-18 12:19:33.205940",
+ "modified_by": "Administrator",
+ "module": "Projects",
+ "name": "Timesheet Detail",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "ASC"
}
\ No newline at end of file
diff --git a/erpnext/projects/report/billing_summary.py b/erpnext/projects/report/billing_summary.py
index 6c3c05f..5efde41 100644
--- a/erpnext/projects/report/billing_summary.py
+++ b/erpnext/projects/report/billing_summary.py
@@ -126,7 +126,7 @@
timesheet_details = frappe.get_all(
"Timesheet Detail",
filters = timesheet_details_filter,
- fields=["from_time", "to_time", "hours", "billable", "billing_hours", "billing_rate", "parent"]
+ fields=["from_time", "to_time", "hours", "is_billable", "billing_hours", "billing_rate", "parent"]
)
timesheet_details_map = frappe._dict()
@@ -139,7 +139,7 @@
precision = frappe.get_precision("Timesheet Detail", "hours")
activity_duration = time_diff_in_hours(end_time, start_time)
billing_duration = 0.0
- if activity.billable:
+ if activity.is_billable:
billing_duration = activity.billing_hours
if activity_duration != activity.billing_hours:
billing_duration = activity_duration * activity.billing_hours / activity.hours
diff --git a/erpnext/projects/report/delayed_tasks_summary/__init__.py b/erpnext/projects/report/delayed_tasks_summary/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/projects/report/delayed_tasks_summary/__init__.py
diff --git a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js
new file mode 100644
index 0000000..5aa44c0
--- /dev/null
+++ b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js
@@ -0,0 +1,41 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Delayed Tasks Summary"] = {
+ "filters": [
+ {
+ "fieldname": "from_date",
+ "label": __("From Date"),
+ "fieldtype": "Date"
+ },
+ {
+ "fieldname": "to_date",
+ "label": __("To Date"),
+ "fieldtype": "Date"
+ },
+ {
+ "fieldname": "priority",
+ "label": __("Priority"),
+ "fieldtype": "Select",
+ "options": ["", "Low", "Medium", "High", "Urgent"]
+ },
+ {
+ "fieldname": "status",
+ "label": __("Status"),
+ "fieldtype": "Select",
+ "options": ["", "Open", "Working","Pending Review","Overdue","Completed"]
+ },
+ ],
+ "formatter": function(value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+ if (column.id == "delay") {
+ if (data["delay"] > 0) {
+ value = `<p style="color: red; font-weight: bold">${value}</p>`;
+ } else {
+ value = `<p style="color: green; font-weight: bold">${value}</p>`;
+ }
+ }
+ return value
+ }
+};
diff --git a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.json b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.json
new file mode 100644
index 0000000..100c422
--- /dev/null
+++ b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.json
@@ -0,0 +1,29 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-03-25 15:03:19.857418",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-04-15 15:49:35.432486",
+ "modified_by": "Administrator",
+ "module": "Projects",
+ "name": "Delayed Tasks Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Task",
+ "report_name": "Delayed Tasks Summary",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Projects User"
+ },
+ {
+ "role": "Projects Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py
new file mode 100644
index 0000000..cdabe64
--- /dev/null
+++ b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py
@@ -0,0 +1,133 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.utils import date_diff, nowdate
+
+def execute(filters=None):
+ columns, data = [], []
+ data = get_data(filters)
+ columns = get_columns()
+ charts = get_chart_data(data)
+ return columns, data, None, charts
+
+def get_data(filters):
+ conditions = get_conditions(filters)
+ tasks = frappe.get_all("Task",
+ filters = conditions,
+ fields = ["name", "subject", "exp_start_date", "exp_end_date",
+ "status", "priority", "completed_on", "progress"],
+ order_by="creation"
+ )
+ for task in tasks:
+ if task.exp_end_date:
+ if task.completed_on:
+ task.delay = date_diff(task.completed_on, task.exp_end_date)
+ elif task.status == "Completed":
+ # task is completed but completed on is not set (for older tasks)
+ task.delay = 0
+ else:
+ # task not completed
+ task.delay = date_diff(nowdate(), task.exp_end_date)
+ else:
+ # task has no end date, hence no delay
+ task.delay = 0
+
+ # Sort by descending order of delay
+ tasks.sort(key=lambda x: x["delay"], reverse=True)
+ return tasks
+
+def get_conditions(filters):
+ conditions = frappe._dict()
+ keys = ["priority", "status"]
+ for key in keys:
+ if filters.get(key):
+ conditions[key] = filters.get(key)
+ if filters.get("from_date"):
+ conditions.exp_end_date = [">=", filters.get("from_date")]
+ if filters.get("to_date"):
+ conditions.exp_start_date = ["<=", filters.get("to_date")]
+ return conditions
+
+def get_chart_data(data):
+ delay, on_track = 0, 0
+ for entry in data:
+ if entry.get("delay") > 0:
+ delay = delay + 1
+ else:
+ on_track = on_track + 1
+ charts = {
+ "data": {
+ "labels": ["On Track", "Delayed"],
+ "datasets": [
+ {
+ "name": "Delayed",
+ "values": [on_track, delay]
+ }
+ ]
+ },
+ "type": "percentage",
+ "colors": ["#84D5BA", "#CB4B5F"]
+ }
+ return charts
+
+def get_columns():
+ columns = [
+ {
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "label": "Task",
+ "options": "Task",
+ "width": 150
+ },
+ {
+ "fieldname": "subject",
+ "fieldtype": "Data",
+ "label": "Subject",
+ "width": 200
+ },
+ {
+ "fieldname": "status",
+ "fieldtype": "Data",
+ "label": "Status",
+ "width": 100
+ },
+ {
+ "fieldname": "priority",
+ "fieldtype": "Data",
+ "label": "Priority",
+ "width": 80
+ },
+ {
+ "fieldname": "progress",
+ "fieldtype": "Data",
+ "label": "Progress (%)",
+ "width": 120
+ },
+ {
+ "fieldname": "exp_start_date",
+ "fieldtype": "Date",
+ "label": "Expected Start Date",
+ "width": 150
+ },
+ {
+ "fieldname": "exp_end_date",
+ "fieldtype": "Date",
+ "label": "Expected End Date",
+ "width": 150
+ },
+ {
+ "fieldname": "completed_on",
+ "fieldtype": "Date",
+ "label": "Actual End Date",
+ "width": 130
+ },
+ {
+ "fieldname": "delay",
+ "fieldtype": "Data",
+ "label": "Delay (In Days)",
+ "width": 120
+ }
+ ]
+ return columns
diff --git a/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py b/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py
new file mode 100644
index 0000000..dbeedb4
--- /dev/null
+++ b/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py
@@ -0,0 +1,54 @@
+from __future__ import unicode_literals
+import unittest
+import frappe
+from frappe.utils import nowdate, add_days, add_months
+from erpnext.projects.doctype.task.test_task import create_task
+from erpnext.projects.report.delayed_tasks_summary.delayed_tasks_summary import execute
+
+class TestDelayedTasksSummary(unittest.TestCase):
+ @classmethod
+ def setUp(self):
+ task1 = create_task("_Test Task 98", add_days(nowdate(), -10), nowdate())
+ create_task("_Test Task 99", add_days(nowdate(), -10), add_days(nowdate(), -1))
+
+ task1.status = "Completed"
+ task1.completed_on = add_days(nowdate(), -1)
+ task1.save()
+
+ def test_delayed_tasks_summary(self):
+ filters = frappe._dict({
+ "from_date": add_months(nowdate(), -1),
+ "to_date": nowdate(),
+ "priority": "Low",
+ "status": "Open"
+ })
+ expected_data = [
+ {
+ "subject": "_Test Task 99",
+ "status": "Open",
+ "priority": "Low",
+ "delay": 1
+ },
+ {
+ "subject": "_Test Task 98",
+ "status": "Completed",
+ "priority": "Low",
+ "delay": -1
+ }
+ ]
+ report = execute(filters)
+ data = list(filter(lambda x: x.subject == "_Test Task 99", report[1]))[0]
+
+ for key in ["subject", "status", "priority", "delay"]:
+ self.assertEqual(expected_data[0].get(key), data.get(key))
+
+ filters.status = "Completed"
+ report = execute(filters)
+ data = list(filter(lambda x: x.subject == "_Test Task 98", report[1]))[0]
+
+ for key in ["subject", "status", "priority", "delay"]:
+ self.assertEqual(expected_data[1].get(key), data.get(key))
+
+ def tearDown(self):
+ for task in ["_Test Task 98", "_Test Task 99"]:
+ frappe.get_doc("Task", {"subject": task}).delete()
\ No newline at end of file
diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/__init__.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/__init__.py
diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js
new file mode 100644
index 0000000..9a30b99
--- /dev/null
+++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js
@@ -0,0 +1,48 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Employee Hours Utilization Based On Timesheet"] = {
+ "filters": [
+ {
+ fieldname: "company",
+ label: __("Company"),
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company"),
+ reqd: 1
+ },
+ {
+ fieldname: "from_date",
+ label: __("From Date"),
+ fieldtype: "Date",
+ default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
+ reqd: 1
+ },
+ {
+ fieldname:"to_date",
+ label: __("To Date"),
+ fieldtype: "Date",
+ default: frappe.datetime.now_date(),
+ reqd: 1
+ },
+ {
+ fieldname: "employee",
+ label: __("Employee"),
+ fieldtype: "Link",
+ options: "Employee"
+ },
+ {
+ fieldname: "department",
+ label: __("Department"),
+ fieldtype: "Link",
+ options: "Department"
+ },
+ {
+ fieldname: "project",
+ label: __("Project"),
+ fieldtype: "Link",
+ options: "Project"
+ }
+ ]
+};
diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.json b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.json
new file mode 100644
index 0000000..5ff8186
--- /dev/null
+++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.json
@@ -0,0 +1,22 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-04-05 19:23:43.838623",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-04-05 19:23:43.838623",
+ "modified_by": "Administrator",
+ "module": "Projects",
+ "name": "Employee Hours Utilization Based On Timesheet",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Timesheet",
+ "report_name": "Employee Hours Utilization Based On Timesheet",
+ "report_type": "Script Report",
+ "roles": []
+}
\ No newline at end of file
diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py
new file mode 100644
index 0000000..4d22f46
--- /dev/null
+++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py
@@ -0,0 +1,280 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.utils import flt, getdate
+from six import iteritems
+
+def execute(filters=None):
+ return EmployeeHoursReport(filters).run()
+
+class EmployeeHoursReport:
+ '''Employee Hours Utilization Report Based On Timesheet'''
+ def __init__(self, filters=None):
+ self.filters = frappe._dict(filters or {})
+
+ self.from_date = getdate(self.filters.from_date)
+ self.to_date = getdate(self.filters.to_date)
+
+ self.validate_dates()
+ self.validate_standard_working_hours()
+
+ def validate_dates(self):
+ self.day_span = (self.to_date - self.from_date).days
+
+ if self.day_span <= 0:
+ frappe.throw(_('From Date must come before To Date'))
+
+ def validate_standard_working_hours(self):
+ self.standard_working_hours = frappe.db.get_single_value('HR Settings', 'standard_working_hours')
+ if not self.standard_working_hours:
+ msg = _('The metrics for this report are calculated based on the Standard Working Hours. Please set {0} in {1}.').format(
+ frappe.bold('Standard Working Hours'), frappe.utils.get_link_to_form('HR Settings', 'HR Settings'))
+
+ frappe.throw(msg)
+
+ def run(self):
+ self.generate_columns()
+ self.generate_data()
+ self.generate_report_summary()
+ self.generate_chart_data()
+
+ return self.columns, self.data, None, self.chart, self.report_summary
+
+ def generate_columns(self):
+ self.columns = [
+ {
+ 'label': _('Employee'),
+ 'options': 'Employee',
+ 'fieldname': 'employee',
+ 'fieldtype': 'Link',
+ 'width': 230
+ },
+ {
+ 'label': _('Department'),
+ 'options': 'Department',
+ 'fieldname': 'department',
+ 'fieldtype': 'Link',
+ 'width': 120
+ },
+ {
+ 'label': _('Total Hours (T)'),
+ 'fieldname': 'total_hours',
+ 'fieldtype': 'Float',
+ 'width': 120
+ },
+ {
+ 'label': _('Billed Hours (B)'),
+ 'fieldname': 'billed_hours',
+ 'fieldtype': 'Float',
+ 'width': 170
+ },
+ {
+ 'label': _('Non-Billed Hours (NB)'),
+ 'fieldname': 'non_billed_hours',
+ 'fieldtype': 'Float',
+ 'width': 170
+ },
+ {
+ 'label': _('Untracked Hours (U)'),
+ 'fieldname': 'untracked_hours',
+ 'fieldtype': 'Float',
+ 'width': 170
+ },
+ {
+ 'label': _('% Utilization (B + NB) / T'),
+ 'fieldname': 'per_util',
+ 'fieldtype': 'Percentage',
+ 'width': 200
+ },
+ {
+ 'label': _('% Utilization (B / T)'),
+ 'fieldname': 'per_util_billed_only',
+ 'fieldtype': 'Percentage',
+ 'width': 200
+ }
+ ]
+
+ def generate_data(self):
+ self.generate_filtered_time_logs()
+ self.generate_stats_by_employee()
+ self.set_employee_department_and_name()
+
+ if self.filters.department:
+ self.filter_stats_by_department()
+
+ self.calculate_utilizations()
+
+ self.data = []
+
+ for emp, data in iteritems(self.stats_by_employee):
+ row = frappe._dict()
+ row['employee'] = emp
+ row.update(data)
+ self.data.append(row)
+
+ # Sort by descending order of percentage utilization
+ self.data.sort(key=lambda x: x['per_util'], reverse=True)
+
+ def filter_stats_by_department(self):
+ filtered_data = frappe._dict()
+ for emp, data in self.stats_by_employee.items():
+ if data['department'] == self.filters.department:
+ filtered_data[emp] = data
+
+ # Update stats
+ self.stats_by_employee = filtered_data
+
+ def generate_filtered_time_logs(self):
+ additional_filters = ''
+
+ filter_fields = ['employee', 'project', 'company']
+
+ for field in filter_fields:
+ if self.filters.get(field):
+ if field == 'project':
+ additional_filters += f"AND ttd.{field} = '{self.filters.get(field)}'"
+ else:
+ additional_filters += f"AND tt.{field} = '{self.filters.get(field)}'"
+
+ self.filtered_time_logs = frappe.db.sql('''
+ SELECT tt.employee AS employee, ttd.hours AS hours, ttd.is_billable AS is_billable, ttd.project AS project
+ FROM `tabTimesheet Detail` AS ttd
+ JOIN `tabTimesheet` AS tt
+ ON ttd.parent = tt.name
+ WHERE tt.employee IS NOT NULL
+ AND tt.start_date BETWEEN '{0}' AND '{1}'
+ AND tt.end_date BETWEEN '{0}' AND '{1}'
+ {2}
+ '''.format(self.filters.from_date, self.filters.to_date, additional_filters))
+
+ def generate_stats_by_employee(self):
+ self.stats_by_employee = frappe._dict()
+
+ for emp, hours, is_billable, project in self.filtered_time_logs:
+ self.stats_by_employee.setdefault(
+ emp, frappe._dict()
+ ).setdefault('billed_hours', 0.0)
+
+ self.stats_by_employee[emp].setdefault('non_billed_hours', 0.0)
+
+ if is_billable:
+ self.stats_by_employee[emp]['billed_hours'] += flt(hours, 2)
+ else:
+ self.stats_by_employee[emp]['non_billed_hours'] += flt(hours, 2)
+
+ def set_employee_department_and_name(self):
+ for emp in self.stats_by_employee:
+ emp_name = frappe.db.get_value(
+ 'Employee', emp, 'employee_name'
+ )
+ emp_dept = frappe.db.get_value(
+ 'Employee', emp, 'department'
+ )
+
+ self.stats_by_employee[emp]['department'] = emp_dept
+ self.stats_by_employee[emp]['employee_name'] = emp_name
+
+ def calculate_utilizations(self):
+ TOTAL_HOURS = flt(self.standard_working_hours * self.day_span, 2)
+ for emp, data in iteritems(self.stats_by_employee):
+ data['total_hours'] = TOTAL_HOURS
+ data['untracked_hours'] = flt(TOTAL_HOURS - data['billed_hours'] - data['non_billed_hours'], 2)
+
+ # To handle overtime edge-case
+ if data['untracked_hours'] < 0:
+ data['untracked_hours'] = 0.0
+
+ data['per_util'] = flt(((data['billed_hours'] + data['non_billed_hours']) / TOTAL_HOURS) * 100, 2)
+ data['per_util_billed_only'] = flt((data['billed_hours'] / TOTAL_HOURS) * 100, 2)
+
+ def generate_report_summary(self):
+ self.report_summary = []
+
+ if not self.data:
+ return
+
+ avg_utilization = 0.0
+ avg_utilization_billed_only = 0.0
+ total_billed, total_non_billed = 0.0, 0.0
+ total_untracked = 0.0
+
+ for row in self.data:
+ avg_utilization += row['per_util']
+ avg_utilization_billed_only += row['per_util_billed_only']
+ total_billed += row['billed_hours']
+ total_non_billed += row['non_billed_hours']
+ total_untracked += row['untracked_hours']
+
+ avg_utilization /= len(self.data)
+ avg_utilization = flt(avg_utilization, 2)
+
+ avg_utilization_billed_only /= len(self.data)
+ avg_utilization_billed_only = flt(avg_utilization_billed_only, 2)
+
+ THRESHOLD_PERCENTAGE = 70.0
+ self.report_summary = [
+ {
+ 'value': f'{avg_utilization}%',
+ 'indicator': 'Red' if avg_utilization < THRESHOLD_PERCENTAGE else 'Green',
+ 'label': _('Avg Utilization'),
+ 'datatype': 'Percentage'
+ },
+ {
+ 'value': f'{avg_utilization_billed_only}%',
+ 'indicator': 'Red' if avg_utilization_billed_only < THRESHOLD_PERCENTAGE else 'Green',
+ 'label': _('Avg Utilization (Billed Only)'),
+ 'datatype': 'Percentage'
+ },
+ {
+ 'value': total_billed,
+ 'label': _('Total Billed Hours'),
+ 'datatype': 'Float'
+ },
+ {
+ 'value': total_non_billed,
+ 'label': _('Total Non-Billed Hours'),
+ 'datatype': 'Float'
+ }
+ ]
+
+ def generate_chart_data(self):
+ self.chart = {}
+
+ labels = []
+ billed_hours = []
+ non_billed_hours = []
+ untracked_hours = []
+
+
+ for row in self.data:
+ labels.append(row.get('employee_name'))
+ billed_hours.append(row.get('billed_hours'))
+ non_billed_hours.append(row.get('non_billed_hours'))
+ untracked_hours.append(row.get('untracked_hours'))
+
+ self.chart = {
+ 'data': {
+ 'labels': labels[:30],
+ 'datasets': [
+ {
+ 'name': _('Billed Hours'),
+ 'values': billed_hours[:30]
+ },
+ {
+ 'name': _('Non-Billed Hours'),
+ 'values': non_billed_hours[:30]
+ },
+ {
+ 'name': _('Untracked Hours'),
+ 'values': untracked_hours[:30]
+ }
+ ]
+ },
+ 'type': 'bar',
+ 'barOptions': {
+ 'stacked': True
+ }
+ }
diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py
new file mode 100644
index 0000000..0e5a597
--- /dev/null
+++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py
@@ -0,0 +1,198 @@
+from __future__ import unicode_literals
+import unittest
+import frappe
+
+from frappe.utils.make_random import get_random
+from erpnext.projects.report.employee_hours_utilization_based_on_timesheet.employee_hours_utilization_based_on_timesheet import execute
+from erpnext.hr.doctype.employee.test_employee import make_employee
+from erpnext.projects.doctype.project.test_project import make_project
+
+class TestEmployeeUtilization(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ # Create test employee
+ cls.test_emp1 = make_employee("test1@employeeutil.com", "_Test Company")
+ cls.test_emp2 = make_employee("test2@employeeutil.com", "_Test Company")
+
+ # Create test project
+ cls.test_project = make_project({"project_name": "_Test Project"})
+
+ # Create test timesheets
+ cls.create_test_timesheets()
+
+ frappe.db.set_value("HR Settings", "HR Settings", "standard_working_hours", 9)
+
+ @classmethod
+ def create_test_timesheets(cls):
+ timesheet1 = frappe.new_doc("Timesheet")
+ timesheet1.employee = cls.test_emp1
+ timesheet1.company = '_Test Company'
+
+ timesheet1.append("time_logs", {
+ "activity_type": get_random("Activity Type"),
+ "hours": 5,
+ "is_billable": 1,
+ "from_time": '2021-04-01 13:30:00.000000',
+ "to_time": '2021-04-01 18:30:00.000000'
+ })
+
+ timesheet1.save()
+ timesheet1.submit()
+
+ timesheet2 = frappe.new_doc("Timesheet")
+ timesheet2.employee = cls.test_emp2
+ timesheet2.company = '_Test Company'
+
+ timesheet2.append("time_logs", {
+ "activity_type": get_random("Activity Type"),
+ "hours": 10,
+ "is_billable": 0,
+ "from_time": '2021-04-01 13:30:00.000000',
+ "to_time": '2021-04-01 23:30:00.000000',
+ "project": cls.test_project.name
+ })
+
+ timesheet2.save()
+ timesheet2.submit()
+
+ @classmethod
+ def tearDownClass(cls):
+ # Delete time logs
+ frappe.db.sql("""
+ DELETE FROM `tabTimesheet Detail`
+ WHERE parent IN (
+ SELECT name
+ FROM `tabTimesheet`
+ WHERE company = '_Test Company'
+ )
+ """)
+
+ frappe.db.sql("DELETE FROM `tabTimesheet` WHERE company='_Test Company'")
+ frappe.db.sql(f"DELETE FROM `tabProject` WHERE name='{cls.test_project.name}'")
+
+ def test_utilization_report_with_required_filters_only(self):
+ filters = {
+ "company": "_Test Company",
+ "from_date": "2021-04-01",
+ "to_date": "2021-04-03"
+ }
+
+ report = execute(filters)
+
+ expected_data = self.get_expected_data_for_test_employees()
+ self.assertEqual(report[1], expected_data)
+
+ def test_utilization_report_for_single_employee(self):
+ filters = {
+ "company": "_Test Company",
+ "from_date": "2021-04-01",
+ "to_date": "2021-04-03",
+ "employee": self.test_emp1
+ }
+
+ report = execute(filters)
+
+ emp1_data = frappe.get_doc('Employee', self.test_emp1)
+ expected_data = [
+ {
+ 'employee': self.test_emp1,
+ 'employee_name': 'test1@employeeutil.com',
+ 'billed_hours': 5.0,
+ 'non_billed_hours': 0.0,
+ 'department': emp1_data.department,
+ 'total_hours': 18.0,
+ 'untracked_hours': 13.0,
+ 'per_util': 27.78,
+ 'per_util_billed_only': 27.78
+ }
+ ]
+
+ self.assertEqual(report[1], expected_data)
+
+ def test_utilization_report_for_project(self):
+ filters = {
+ "company": "_Test Company",
+ "from_date": "2021-04-01",
+ "to_date": "2021-04-03",
+ "project": self.test_project.name
+ }
+
+ report = execute(filters)
+
+ emp2_data = frappe.get_doc('Employee', self.test_emp2)
+ expected_data = [
+ {
+ 'employee': self.test_emp2,
+ 'employee_name': 'test2@employeeutil.com',
+ 'billed_hours': 0.0,
+ 'non_billed_hours': 10.0,
+ 'department': emp2_data.department,
+ 'total_hours': 18.0,
+ 'untracked_hours': 8.0,
+ 'per_util': 55.56,
+ 'per_util_billed_only': 0.0
+ }
+ ]
+
+ self.assertEqual(report[1], expected_data)
+
+ def test_utilization_report_for_department(self):
+ emp1_data = frappe.get_doc('Employee', self.test_emp1)
+ filters = {
+ "company": "_Test Company",
+ "from_date": "2021-04-01",
+ "to_date": "2021-04-03",
+ "department": emp1_data.department
+ }
+
+ report = execute(filters)
+
+ expected_data = self.get_expected_data_for_test_employees()
+ self.assertEqual(report[1], expected_data)
+
+ def test_report_summary_data(self):
+ filters = {
+ "company": "_Test Company",
+ "from_date": "2021-04-01",
+ "to_date": "2021-04-03"
+ }
+
+ report = execute(filters)
+ summary = report[4]
+ expected_summary_values = ['41.67%', '13.89%', 5.0, 10.0]
+
+ self.assertEqual(len(summary), 4)
+
+ for i in range(4):
+ self.assertEqual(
+ summary[i]['value'], expected_summary_values[i]
+ )
+
+ def get_expected_data_for_test_employees(self):
+ emp1_data = frappe.get_doc('Employee', self.test_emp1)
+ emp2_data = frappe.get_doc('Employee', self.test_emp2)
+
+ return [
+ {
+ 'employee': self.test_emp2,
+ 'employee_name': 'test2@employeeutil.com',
+ 'billed_hours': 0.0,
+ 'non_billed_hours': 10.0,
+ 'department': emp2_data.department,
+ 'total_hours': 18.0,
+ 'untracked_hours': 8.0,
+ 'per_util': 55.56,
+ 'per_util_billed_only': 0.0
+ },
+ {
+ 'employee': self.test_emp1,
+ 'employee_name': 'test1@employeeutil.com',
+ 'billed_hours': 5.0,
+ 'non_billed_hours': 0.0,
+ 'department': emp1_data.department,
+ 'total_hours': 18.0,
+ 'untracked_hours': 13.0,
+ 'per_util': 27.78,
+ 'per_util_billed_only': 27.78
+ }
+ ]
\ No newline at end of file
diff --git a/erpnext/projects/report/project_profitability/__init__.py b/erpnext/projects/report/project_profitability/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/projects/report/project_profitability/__init__.py
diff --git a/erpnext/projects/report/project_profitability/project_profitability.js b/erpnext/projects/report/project_profitability/project_profitability.js
new file mode 100644
index 0000000..13ae19b
--- /dev/null
+++ b/erpnext/projects/report/project_profitability/project_profitability.js
@@ -0,0 +1,48 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Project Profitability"] = {
+ "filters": [
+ {
+ "fieldname": "company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "default": frappe.defaults.get_user_default("Company"),
+ "reqd": 1
+ },
+ {
+ "fieldname": "start_date",
+ "label": __("Start Date"),
+ "fieldtype": "Date",
+ "reqd": 1,
+ "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1)
+ },
+ {
+ "fieldname": "end_date",
+ "label": __("End Date"),
+ "fieldtype": "Date",
+ "reqd": 1,
+ "default": frappe.datetime.now_date()
+ },
+ {
+ "fieldname": "customer_name",
+ "label": __("Customer"),
+ "fieldtype": "Link",
+ "options": "Customer"
+ },
+ {
+ "fieldname": "employee",
+ "label": __("Employee"),
+ "fieldtype": "Link",
+ "options": "Employee"
+ },
+ {
+ "fieldname": "project",
+ "label": __("Project"),
+ "fieldtype": "Link",
+ "options": "Project"
+ }
+ ]
+};
diff --git a/erpnext/projects/report/project_profitability/project_profitability.json b/erpnext/projects/report/project_profitability/project_profitability.json
new file mode 100644
index 0000000..0b092cd
--- /dev/null
+++ b/erpnext/projects/report/project_profitability/project_profitability.json
@@ -0,0 +1,44 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-04-16 15:50:28.914872",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-04-16 15:50:48.490866",
+ "modified_by": "Administrator",
+ "module": "Projects",
+ "name": "Project Profitability",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Timesheet",
+ "report_name": "Project Profitability",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "HR User"
+ },
+ {
+ "role": "Accounts User"
+ },
+ {
+ "role": "Employee"
+ },
+ {
+ "role": "Projects User"
+ },
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Employee Self Service"
+ },
+ {
+ "role": "HR Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py
new file mode 100644
index 0000000..9139d84
--- /dev/null
+++ b/erpnext/projects/report/project_profitability/project_profitability.py
@@ -0,0 +1,211 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.utils import flt
+
+def execute(filters=None):
+ columns, data = [], []
+ data = get_data(filters)
+ columns = get_columns()
+ charts = get_chart_data(data)
+ return columns, data, None, charts
+
+def get_data(filters):
+ data = get_rows(filters)
+ data = calculate_cost_and_profit(data)
+ return data
+
+def get_rows(filters):
+ conditions = get_conditions(filters)
+ standard_working_hours = frappe.db.get_single_value("HR Settings", "standard_working_hours")
+ if not standard_working_hours:
+ msg = _("The metrics for this report are calculated based on the Standard Working Hours. Please set {0} in {1}.").format(
+ frappe.bold("Standard Working Hours"), frappe.utils.get_link_to_form("HR Settings", "HR Settings"))
+
+ frappe.msgprint(msg)
+ return []
+
+ sql = """
+ SELECT
+ *
+ FROM
+ (SELECT
+ si.customer_name,si.base_grand_total,
+ si.name as voucher_no,tabTimesheet.employee,
+ tabTimesheet.title as employee_name,tabTimesheet.parent_project as project,
+ tabTimesheet.start_date,tabTimesheet.end_date,
+ tabTimesheet.total_billed_hours,tabTimesheet.name as timesheet,
+ ss.base_gross_pay,ss.total_working_days,
+ tabTimesheet.total_billed_hours/(ss.total_working_days * {0}) as utilization
+ FROM
+ `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet
+ join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet.name
+ join `tabSales Invoice` as si on si.name = sit.parent and si.status != "Cancelled"
+ join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != "Cancelled" """.format(standard_working_hours)
+ if conditions:
+ sql += """
+ WHERE
+ {0}) as t""".format(conditions)
+ return frappe.db.sql(sql,filters, as_dict=True)
+
+def calculate_cost_and_profit(data):
+ for row in data:
+ row.fractional_cost = flt(row.base_gross_pay) * flt(row.utilization)
+ row.profit = flt(row.base_grand_total) - flt(row.base_gross_pay) * flt(row.utilization)
+ return data
+
+def get_conditions(filters):
+ conditions = []
+
+ if filters.get("company"):
+ conditions.append("tabTimesheet.company={0}".format(frappe.db.escape(filters.get("company"))))
+
+ if filters.get("start_date"):
+ conditions.append("tabTimesheet.start_date>='{0}'".format(filters.get("start_date")))
+
+ if filters.get("end_date"):
+ conditions.append("tabTimesheet.end_date<='{0}'".format(filters.get("end_date")))
+
+ if filters.get("customer_name"):
+ conditions.append("si.customer_name={0}".format(frappe.db.escape(filters.get("customer_name"))))
+
+ if filters.get("employee"):
+ conditions.append("tabTimesheet.employee={0}".format(frappe.db.escape(filters.get("employee"))))
+
+ if filters.get("project"):
+ conditions.append("tabTimesheet.parent_project={0}".format(frappe.db.escape(filters.get("project"))))
+
+ conditions = " and ".join(conditions)
+ return conditions
+
+def get_chart_data(data):
+ if not data:
+ return None
+
+ labels = []
+ utilization = []
+
+ for entry in data:
+ labels.append(entry.get("employee_name") + " - " + str(entry.get("end_date")))
+ utilization.append(entry.get("utilization"))
+
+ charts = {
+ "data": {
+ "labels": labels,
+ "datasets": [
+ {
+ "name": "Utilization",
+ "values": utilization
+ }
+ ]
+ },
+ "type": "bar",
+ "colors": ["#84BDD5"]
+ }
+ return charts
+
+def get_columns():
+ return [
+ {
+ "fieldname": "customer_name",
+ "label": _("Customer"),
+ "fieldtype": "Link",
+ "options": "Customer",
+ "width": 150
+ },
+ {
+ "fieldname": "employee",
+ "label": _("Employee"),
+ "fieldtype": "Link",
+ "options": "Employee",
+ "width": 130
+ },
+ {
+ "fieldname": "employee_name",
+ "label": _("Employee Name"),
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "voucher_no",
+ "label": _("Sales Invoice"),
+ "fieldtype": "Link",
+ "options": "Sales Invoice",
+ "width": 120
+ },
+ {
+ "fieldname": "timesheet",
+ "label": _("Timesheet"),
+ "fieldtype": "Link",
+ "options": "Timesheet",
+ "width": 120
+ },
+ {
+ "fieldname": "project",
+ "label": _("Project"),
+ "fieldtype": "Link",
+ "options": "Project",
+ "width": 100
+ },
+ {
+ "fieldname": "base_grand_total",
+ "label": _("Bill Amount"),
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 100
+ },
+ {
+ "fieldname": "base_gross_pay",
+ "label": _("Cost"),
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 100
+ },
+ {
+ "fieldname": "profit",
+ "label": _("Profit"),
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 100
+ },
+ {
+ "fieldname": "utilization",
+ "label": _("Utilization"),
+ "fieldtype": "Percentage",
+ "width": 100
+ },
+ {
+ "fieldname": "fractional_cost",
+ "label": _("Fractional Cost"),
+ "fieldtype": "Int",
+ "width": 120
+ },
+ {
+ "fieldname": "total_billed_hours",
+ "label": _("Total Billed Hours"),
+ "fieldtype": "Int",
+ "width": 150
+ },
+ {
+ "fieldname": "start_date",
+ "label": _("Start Date"),
+ "fieldtype": "Date",
+ "width": 100
+ },
+ {
+ "fieldname": "end_date",
+ "label": _("End Date"),
+ "fieldtype": "Date",
+ "width": 100
+ },
+ {
+ "label": _("Currency"),
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "options": "Currency",
+ "width": 80
+ }
+ ]
\ No newline at end of file
diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py
new file mode 100644
index 0000000..ea6bdb5
--- /dev/null
+++ b/erpnext/projects/report/project_profitability/test_project_profitability.py
@@ -0,0 +1,58 @@
+from __future__ import unicode_literals
+import unittest
+import frappe
+from frappe.utils import getdate, nowdate
+from erpnext.hr.doctype.employee.test_employee import make_employee
+from erpnext.projects.doctype.timesheet.test_timesheet import make_salary_structure_for_timesheet, make_timesheet
+from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice
+from erpnext.projects.report.project_profitability.project_profitability import execute
+
+class TestProjectProfitability(unittest.TestCase):
+
+ def setUp(self):
+ emp = make_employee('test_employee_9@salary.com', company='_Test Company')
+ if not frappe.db.exists('Salary Component', 'Timesheet Component'):
+ frappe.get_doc({'doctype': 'Salary Component', 'salary_component': 'Timesheet Component'}).insert()
+ make_salary_structure_for_timesheet(emp, company='_Test Company')
+ self.timesheet = make_timesheet(emp, simulate = True, is_billable=1)
+ self.salary_slip = make_salary_slip(self.timesheet.name)
+ self.salary_slip.submit()
+ self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer')
+ self.sales_invoice.due_date = nowdate()
+ self.sales_invoice.submit()
+
+ frappe.db.set_value('HR Settings', None, 'standard_working_hours', 8)
+
+ def test_project_profitability(self):
+ filters = {
+ 'company': '_Test Company',
+ 'start_date': getdate(),
+ 'end_date': getdate()
+ }
+
+ report = execute(filters)
+
+ row = report[1][0]
+ timesheet = frappe.get_doc("Timesheet", self.timesheet.name)
+
+ self.assertEqual(self.sales_invoice.customer, row.customer_name)
+ self.assertEqual(timesheet.title, row.employee_name)
+ self.assertEqual(self.sales_invoice.base_grand_total, row.base_grand_total)
+ self.assertEqual(self.salary_slip.base_gross_pay, row.base_gross_pay)
+ self.assertEqual(timesheet.total_billed_hours, row.total_billed_hours)
+ self.assertEqual(self.salary_slip.total_working_days, row.total_working_days)
+
+ standard_working_hours = frappe.db.get_single_value("HR Settings", "standard_working_hours")
+ utilization = timesheet.total_billed_hours/(self.salary_slip.total_working_days * standard_working_hours)
+ self.assertEqual(utilization, row.utilization)
+
+ profit = self.sales_invoice.base_grand_total - self.salary_slip.base_gross_pay * utilization
+ self.assertEqual(profit, row.profit)
+
+ fractional_cost = self.salary_slip.base_gross_pay * utilization
+ self.assertEqual(fractional_cost, row.fractional_cost)
+
+ def tearDown(self):
+ frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel()
+ frappe.get_doc("Salary Slip", self.salary_slip.name).cancel()
+ frappe.get_doc("Timesheet", self.timesheet.name).cancel()
diff --git a/erpnext/projects/report/project_summary/project_summary.py b/erpnext/projects/report/project_summary/project_summary.py
index ea7f1ab..2c7bb49 100644
--- a/erpnext/projects/report/project_summary/project_summary.py
+++ b/erpnext/projects/report/project_summary/project_summary.py
@@ -131,25 +131,25 @@
{
"value": avg_completion,
"indicator": "Green" if avg_completion > 50 else "Red",
- "label": "Average Completion",
+ "label": _("Average Completion"),
"datatype": "Percent",
},
{
"value": total,
"indicator": "Blue",
- "label": "Total Tasks",
+ "label": _("Total Tasks"),
"datatype": "Int",
},
{
"value": completed,
"indicator": "Green",
- "label": "Completed Tasks",
+ "label": _("Completed Tasks"),
"datatype": "Int",
},
{
"value": total_overdue,
"indicator": "Green" if total_overdue == 0 else "Red",
- "label": "Overdue Tasks",
+ "label": _("Overdue Tasks"),
"datatype": "Int",
}
]
diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json
index dbbd7e1..c023a73 100644
--- a/erpnext/projects/workspace/projects/projects.json
+++ b/erpnext/projects/workspace/projects/projects.json
@@ -15,6 +15,7 @@
"hide_custom": 0,
"icon": "project",
"idx": 0,
+ "is_default": 0,
"is_standard": 1,
"label": "Projects",
"links": [
@@ -130,6 +131,26 @@
"type": "Link"
},
{
+ "dependencies": "Timesheet",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Employee Hours Utilization",
+ "link_to": "Employee Hours Utilization Based On Timesheet",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Timesheet, Sales Invoice, Salary Slip",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Project Profitability",
+ "link_to": "Project Profitability",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
"dependencies": "Project",
"hidden": 0,
"is_query_report": 1,
@@ -148,9 +169,19 @@
"link_type": "Report",
"onboard": 0,
"type": "Link"
+ },
+ {
+ "dependencies": "Task",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Delayed Tasks Summary",
+ "link_to": "Delayed Tasks Summary",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
}
],
- "modified": "2020-12-01 13:38:37.856224",
+ "modified": "2021-04-25 16:27:16.548780",
"modified_by": "Administrator",
"module": "Projects",
"name": "Projects",
diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js
index 649eb45..ceeecb2 100644
--- a/erpnext/public/js/controllers/accounts.js
+++ b/erpnext/public/js/controllers/accounts.js
@@ -276,74 +276,3 @@
}
}
}
-
-
-// For customizing print
-cur_frm.pformat.total = function(doc) { return ''; }
-cur_frm.pformat.discount_amount = function(doc) { return ''; }
-cur_frm.pformat.grand_total = function(doc) { return ''; }
-cur_frm.pformat.rounded_total = function(doc) { return ''; }
-cur_frm.pformat.in_words = function(doc) { return ''; }
-
-cur_frm.pformat.taxes= function(doc){
- //function to make row of table
- var make_row = function(title, val, bold, is_negative) {
- var bstart = '<b>'; var bend = '</b>';
- return '<tr><td style="width:50%;">' + (bold?bstart:'') + title + (bold?bend:'') + '</td>'
- + '<td style="width:50%;text-align:right;">' + (is_negative ? '- ' : '')
- + format_currency(val, doc.currency) + '</td></tr>';
- }
-
- function print_hide(fieldname) {
- var doc_field = frappe.meta.get_docfield(doc.doctype, fieldname, doc.name);
- return doc_field.print_hide;
- }
-
- out ='';
- if (!doc.print_without_amount) {
- var cl = doc.taxes || [];
-
- // outer table
- var out='<div><table class="noborder" style="width:100%"><tr><td style="width: 60%"></td><td>';
-
- // main table
-
- out +='<table class="noborder" style="width:100%">';
-
- if(!print_hide('total')) {
- out += make_row('Total', doc.total, 1);
- }
-
- // Discount Amount on net total
- if(!print_hide('discount_amount') && doc.apply_discount_on == "Net Total" && doc.discount_amount)
- out += make_row('Discount Amount', doc.discount_amount, 0, 1);
-
- // add rows
- if(cl.length){
- for(var i=0;i<cl.length;i++) {
- if(cl[i].tax_amount!=0 && !cl[i].included_in_print_rate)
- out += make_row(cl[i].description, cl[i].tax_amount, 0);
- }
- }
-
- // Discount Amount on grand total
- if(!print_hide('discount_amount') && doc.apply_discount_on == "Grand Total" && doc.discount_amount)
- out += make_row('Discount Amount', doc.discount_amount, 0, 1);
-
- // grand total
- if(!print_hide('grand_total'))
- out += make_row('Grand Total', doc.grand_total, 1);
-
- if(!print_hide('rounded_total'))
- out += make_row('Rounded Total', doc.rounded_total, 1);
-
- if(doc.in_words && !print_hide('in_words')) {
- out +='</table></td></tr>';
- out += '<tr><td colspan = "2">';
- out += '<table><tr><td style="width:25%;"><b>In Words</b></td>';
- out += '<td style="width:50%;">' + doc.in_words + '</td></tr>';
- }
- out += '</table></td></tr></table></div>';
- }
- return out;
-}
\ No newline at end of file
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index 67b12fb..e7dcd41 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -84,13 +84,13 @@
if (me.frm.doc.is_subcontracted == "Yes") {
return{
query: "erpnext.controllers.queries.item_query",
- filters:{ 'is_sub_contracted_item': 1 }
+ filters:{ 'supplier': me.frm.doc.supplier, 'is_sub_contracted_item': 1 }
}
}
else {
return{
query: "erpnext.controllers.queries.item_query",
- filters: {'is_purchase_item': 1}
+ filters: { 'supplier': me.frm.doc.supplier, 'is_purchase_item': 1 }
}
}
});
@@ -216,7 +216,8 @@
child: item,
args: {
item_code: item.item_code,
- warehouse: item.warehouse
+ warehouse: item.warehouse,
+ company: doc.company
}
});
}
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 3a3ee38..2e133be 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -323,12 +323,15 @@
// set precision in the last item iteration
if (n == me.frm.doc["items"].length - 1) {
me.round_off_totals(tax);
+ me.set_in_company_currency(tax,
+ ["tax_amount", "tax_amount_after_discount_amount"]);
+
+ me.round_off_base_values(tax);
// in tax.total, accumulate grand total for each item
me.set_cumulative_total(i, tax);
- me.set_in_company_currency(tax,
- ["total", "tax_amount", "tax_amount_after_discount_amount"]);
+ me.set_in_company_currency(tax, ["total"]);
// adjust Discount Amount loss in last tax iteration
if ((i == me.frm.doc["taxes"].length - 1) && me.discount_amount_applied
@@ -393,20 +396,11 @@
current_tax_amount = tax_rate * item.qty;
}
- current_tax_amount = this.get_final_tax_amount(tax, current_tax_amount);
this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount);
return current_tax_amount;
},
- get_final_tax_amount: function(tax, current_tax_amount) {
- if (frappe.flags.round_off_applicable_accounts.includes(tax.account_head)) {
- current_tax_amount = Math.round(current_tax_amount);
- }
-
- return current_tax_amount;
- },
-
set_item_wise_tax: function(item, tax, tax_rate, current_tax_amount) {
// store tax breakup for each item
let tax_detail = tax.item_wise_tax_detail;
@@ -420,10 +414,22 @@
},
round_off_totals: function(tax) {
+ if (frappe.flags.round_off_applicable_accounts.includes(tax.account_head)) {
+ tax.tax_amount= Math.round(tax.tax_amount);
+ tax.tax_amount_after_discount_amount = Math.round(tax.tax_amount_after_discount_amount);
+ }
+
tax.tax_amount = flt(tax.tax_amount, precision("tax_amount", tax));
tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount, precision("tax_amount", tax));
},
+ round_off_base_values: function(tax) {
+ if (frappe.flags.round_off_applicable_accounts.includes(tax.account_head)) {
+ tax.base_tax_amount= Math.round(tax.base_tax_amount);
+ tax.base_tax_amount_after_discount_amount = Math.round(tax.base_tax_amount_after_discount_amount);
+ }
+ },
+
manipulate_grand_total_for_inclusive_tax: function() {
var me = this;
// if fully inclusive taxes and diff
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 6c2144d..ad1976d 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -562,7 +562,7 @@
weight_uom: item.weight_uom,
manufacturer: item.manufacturer,
stock_uom: item.stock_uom,
- pos_profile: me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '',
+ pos_profile: cint(me.frm.doc.is_pos) ? me.frm.doc.pos_profile : '',
cost_center: item.cost_center,
tax_category: me.frm.doc.tax_category,
item_tax_template: item.item_tax_template,
@@ -640,6 +640,10 @@
let key = item.name;
me.apply_rule_on_other_items({key: item});
}
+ },
+ () => {
+ var company_currency = me.get_company_currency();
+ me.update_item_grid_labels(company_currency);
}
]);
}
@@ -949,15 +953,15 @@
(this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length)) {
var message1 = "";
var message2 = "";
- var final_message = "Please clear the ";
+ var final_message = __("Please clear the") + " ";
if (this.frm.doc.payment_terms_template) {
- message1 = "selected Payment Terms Template";
+ message1 = __("selected Payment Terms Template");
final_message = final_message + message1;
}
if ((this.frm.doc.payment_schedule || []).length) {
- message2 = "Payment Schedule Table";
+ message2 = __("Payment Schedule Table");
if (message1.length !== 0) message2 = " and " + message2;
final_message = final_message + message2;
}
@@ -1103,6 +1107,8 @@
to_currency: to_currency,
args: args
},
+ freeze: true,
+ freeze_message: __("Fetching exchange rates ..."),
callback: function(r) {
callback(flt(r.message));
}
@@ -1319,13 +1325,11 @@
change_grid_labels: function(company_currency) {
var me = this;
- this.frm.set_currency_labels(["base_rate", "base_net_rate", "base_price_list_rate", "base_amount", "base_net_amount", "base_rate_with_margin"],
- company_currency, "items");
+ this.update_item_grid_labels(company_currency);
- this.frm.set_currency_labels(["rate", "net_rate", "price_list_rate", "amount", "net_amount", "stock_uom_rate", "rate_with_margin"],
- this.frm.doc.currency, "items");
+ this.toggle_item_grid_columns(company_currency);
- if(this.frm.fields_dict["operations"]) {
+ if (this.frm.doc.operations && this.frm.doc.operations.length > 0) {
this.frm.set_currency_labels(["operating_cost", "hour_rate"], this.frm.doc.currency, "operations");
this.frm.set_currency_labels(["base_operating_cost", "base_hour_rate"], company_currency, "operations");
@@ -1336,7 +1340,7 @@
});
}
- if(this.frm.fields_dict["scrap_items"]) {
+ if (this.frm.doc.scrap_items && this.frm.doc.scrap_items.length > 0) {
this.frm.set_currency_labels(["rate", "amount"], this.frm.doc.currency, "scrap_items");
this.frm.set_currency_labels(["base_rate", "base_amount"], company_currency, "scrap_items");
@@ -1347,17 +1351,50 @@
});
}
- if(this.frm.fields_dict["taxes"]) {
+ if (this.frm.doc.taxes && this.frm.doc.taxes.length > 0) {
this.frm.set_currency_labels(["tax_amount", "total", "tax_amount_after_discount"], this.frm.doc.currency, "taxes");
this.frm.set_currency_labels(["base_tax_amount", "base_total", "base_tax_amount_after_discount"], company_currency, "taxes");
}
- if(this.frm.fields_dict["advances"]) {
+ if (this.frm.doc.advances && this.frm.doc.advances.length > 0) {
this.frm.set_currency_labels(["advance_amount", "allocated_amount"],
this.frm.doc.party_account_currency, "advances");
}
+ this.update_payment_schedule_grid_labels(company_currency);
+ },
+
+ update_item_grid_labels: function(company_currency) {
+ this.frm.set_currency_labels([
+ "base_rate", "base_net_rate", "base_price_list_rate",
+ "base_amount", "base_net_amount", "base_rate_with_margin"
+ ], company_currency, "items");
+
+ this.frm.set_currency_labels([
+ "rate", "net_rate", "price_list_rate", "amount",
+ "net_amount", "stock_uom_rate", "rate_with_margin"
+ ], this.frm.doc.currency, "items");
+ },
+
+ update_payment_schedule_grid_labels: function(company_currency) {
+ const me = this;
+ if (this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length > 0) {
+ this.frm.set_currency_labels(["base_payment_amount", "base_outstanding", "base_paid_amount"],
+ company_currency, "payment_schedule");
+ this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"],
+ this.frm.doc.currency, "payment_schedule");
+
+ var schedule_grid = this.frm.fields_dict["payment_schedule"].grid;
+ $.each(["base_payment_amount", "base_outstanding", "base_paid_amount"], function(i, fname) {
+ if (frappe.meta.get_docfield(schedule_grid.doctype, fname))
+ schedule_grid.set_column_disp(fname, me.frm.doc.currency != company_currency);
+ });
+ }
+ },
+
+ toggle_item_grid_columns: function(company_currency) {
+ const me = this;
// toggle columns
var item_grid = this.frm.fields_dict["items"].grid;
$.each(["base_rate", "base_price_list_rate", "base_amount", "base_rate_with_margin"], function(i, fname) {
@@ -1377,9 +1414,6 @@
if(frappe.meta.get_docfield(item_grid.doctype, fname))
item_grid.set_column_disp(fname, (show && (me.frm.doc.currency != company_currency)));
});
-
- // set labels
- var $wrapper = $(this.frm.wrapper);
},
recalculate: function() {
@@ -1993,11 +2027,14 @@
terms_template: doc.payment_terms_template,
posting_date: posting_date,
grand_total: doc.rounded_total || doc.grand_total,
+ base_grand_total: doc.base_rounded_total || doc.base_grand_total,
bill_date: doc.bill_date
},
callback: function(r) {
if(r.message && !r.exc) {
me.frm.set_value("payment_schedule", r.message);
+ const company_currency = me.get_company_currency();
+ me.update_payment_schedule_grid_labels(company_currency);
}
}
})
@@ -2005,6 +2042,7 @@
},
payment_term: function(doc, cdt, cdn) {
+ const me = this;
var row = locals[cdt][cdn];
if(row.payment_term) {
frappe.call({
@@ -2013,12 +2051,15 @@
term: row.payment_term,
bill_date: this.frm.doc.bill_date,
posting_date: this.frm.doc.posting_date || this.frm.doc.transaction_date,
- grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total
+ grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total,
+ base_grand_total: this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total
},
callback: function(r) {
if(r.message && !r.exc) {
for (var d in r.message) {
frappe.model.set_value(cdt, cdn, d, r.message[d]);
+ const company_currency = me.get_company_currency();
+ me.update_payment_schedule_grid_labels(company_currency);
}
}
}
diff --git a/erpnext/public/js/education/lms/quiz.js b/erpnext/public/js/education/lms/quiz.js
index 4a9d1e3..32fa4ab 100644
--- a/erpnext/public/js/education/lms/quiz.js
+++ b/erpnext/public/js/education/lms/quiz.js
@@ -20,6 +20,16 @@
}
make(data) {
+ if (data.duration) {
+ const timer_display = document.createElement("div");
+ timer_display.classList.add("lms-timer", "float-right", "font-weight-bold");
+ document.getElementsByClassName("lms-title")[0].appendChild(timer_display);
+ if (!data.activity || (data.activity && !data.activity.is_complete)) {
+ this.initialiseTimer(data.duration);
+ this.is_time_bound = true;
+ this.time_taken = 0;
+ }
+ }
data.questions.forEach(question_data => {
let question_wrapper = document.createElement('div');
let question = new Question({
@@ -37,12 +47,51 @@
indicator = 'green'
message = 'You have already cleared the quiz.'
}
-
+ if (data.activity.time_taken) {
+ this.calculate_and_display_time(data.activity.time_taken, "Time Taken - ");
+ }
this.set_quiz_footer(message, indicator, data.activity.score)
}
else {
this.make_actions();
}
+ window.addEventListener('beforeunload', (event) => {
+ event.preventDefault();
+ event.returnValue = '';
+ });
+ }
+
+ initialiseTimer(duration) {
+ this.time_left = duration;
+ var self = this;
+ var old_diff;
+ this.calculate_and_display_time(this.time_left, "Time Left - ");
+ this.start_time = new Date().getTime();
+ this.timer = setInterval(function () {
+ var diff = (new Date().getTime() - self.start_time)/1000;
+ var variation = old_diff ? diff - old_diff : diff;
+ old_diff = diff;
+ self.time_left -= variation;
+ self.time_taken += variation;
+ self.calculate_and_display_time(self.time_left, "Time Left - ");
+ if (self.time_left <= 0) {
+ clearInterval(self.timer);
+ self.time_taken -= 1;
+ self.submit();
+ }
+ }, 1000);
+ }
+
+ calculate_and_display_time(second, text) {
+ var timer_display = document.getElementsByClassName("lms-timer")[0];
+ var hours = this.append_zero(Math.floor(second / 3600));
+ var minutes = this.append_zero(Math.floor(second % 3600 / 60));
+ var seconds = this.append_zero(Math.ceil(second % 3600 % 60));
+ timer_display.innerText = text + hours + ":" + minutes + ":" + seconds;
+ }
+
+ append_zero(time) {
+ return time > 9 ? time : "0" + time;
}
make_actions() {
@@ -57,6 +106,10 @@
}
submit() {
+ if (this.is_time_bound) {
+ clearInterval(this.timer);
+ $(".lms-timer").text("");
+ }
this.submit_btn.innerText = 'Evaluating..'
this.submit_btn.disabled = true
this.disable()
@@ -64,7 +117,8 @@
quiz_name: this.name,
quiz_response: this.get_selected(),
course: this.course,
- program: this.program
+ program: this.program,
+ time_taken: this.is_time_bound ? this.time_taken : ""
}).then(res => {
this.submit_btn.remove()
if (!res.message) {
@@ -157,7 +211,7 @@
return input;
}
- let make_label = function(name, value) {
+ let make_label = function (name, value) {
let label = document.createElement('label');
label.classList.add('form-check-label');
label.htmlFor = name;
@@ -166,14 +220,14 @@
}
let make_option = function (wrapper, option) {
- let option_div = document.createElement('div')
- option_div.classList.add('form-check', 'pb-1')
+ let option_div = document.createElement('div');
+ option_div.classList.add('form-check', 'pb-1');
let input = make_input(option.name, option.option);
let label = make_label(option.name, option.option);
- option_div.appendChild(input)
- option_div.appendChild(label)
- wrapper.appendChild(option_div)
- return {input: input, ...option}
+ option_div.appendChild(input);
+ option_div.appendChild(label);
+ wrapper.appendChild(option_div);
+ return { input: input, ...option };
}
let options_wrapper = document.createElement('div')
diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js
index e789923..aa9bba1 100644
--- a/erpnext/public/js/help_links.js
+++ b/erpnext/public/js/help_links.js
@@ -644,14 +644,14 @@
frappe.help.help_links["List/Asset"] = [
{
label: "Managing Fixed Assets",
- url: docsUrl + "user/manual/en/accounts/managing-fixed-assets",
+ url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets",
},
];
frappe.help.help_links["List/Asset Category"] = [
{
label: "Asset Category",
- url: docsUrl + "user/manual/en/accounts/managing-fixed-assets",
+ url: docsUrl + "user/manual/en/asset/asset-category",
},
];
@@ -663,7 +663,7 @@
{ label: "Item", url: docsUrl + "user/manual/en/stock/item" },
{
label: "Item Price",
- url: docsUrl + "user/manual/en/stock/item/item-price",
+ url: docsUrl + "user/manual/en/stock/item-price",
},
{
label: "Barcode",
@@ -672,25 +672,25 @@
},
{
label: "Item Wise Taxation",
- url: docsUrl + "user/manual/en/accounts/item-wise-taxation",
+ url: docsUrl + "user/manual/en/accounts/item-tax-template",
},
{
label: "Managing Fixed Assets",
- url: docsUrl + "user/manual/en/accounts/managing-fixed-assets",
+ url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets",
},
{
label: "Item Codification",
- url: docsUrl + "user/manual/en/stock/item/item-codification",
+ url: docsUrl + "user/manual/en/stock/articles/item-codification",
},
{
label: "Item Variants",
- url: docsUrl + "user/manual/en/stock/item/item-variants",
+ url: docsUrl + "user/manual/en/stock/item-variants",
},
{
label: "Item Valuation",
url:
docsUrl +
- "user/manual/en/stock/item/item-valuation-fifo-and-moving-average",
+ "user/manual/en/stock/articles/item-valuation-fifo-and-moving-average",
},
];
@@ -698,7 +698,7 @@
{ label: "Item", url: docsUrl + "user/manual/en/stock/item" },
{
label: "Item Price",
- url: docsUrl + "user/manual/en/stock/item/item-price",
+ url: docsUrl + "user/manual/en/stock/item-price",
},
{
label: "Barcode",
@@ -707,19 +707,19 @@
},
{
label: "Item Wise Taxation",
- url: docsUrl + "user/manual/en/accounts/item-wise-taxation",
+ url: docsUrl + "user/manual/en/accounts/item-tax-template",
},
{
label: "Managing Fixed Assets",
- url: docsUrl + "user/manual/en/accounts/managing-fixed-assets",
+ url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets",
},
{
label: "Item Codification",
- url: docsUrl + "user/manual/en/stock/item/item-codification",
+ url: docsUrl + "user/manual/en/stock/articles/item-codification",
},
{
label: "Item Variants",
- url: docsUrl + "user/manual/en/stock/item/item-variants",
+ url: docsUrl + "user/manual/en/stock/item-variants",
},
{
label: "Item Valuation",
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index e5b50d8..ce40ced 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -48,31 +48,24 @@
return cint(frappe.boot.sysdefaults.allow_stale);
},
- setup_serial_no: function() {
- var grid_row = cur_frm.open_grid_row();
- if(!grid_row || !grid_row.grid_form.fields_dict.serial_no ||
- grid_row.grid_form.fields_dict.serial_no.get_status()!=="Write") return;
+ setup_serial_or_batch_no: function() {
+ let grid_row = cur_frm.open_grid_row();
+ if (!grid_row || !grid_row.grid_form.fields_dict.serial_no ||
+ grid_row.grid_form.fields_dict.serial_no.get_status() !== "Write") return;
- var $btn = $('<button class="btn btn-sm btn-default">'+__("Add Serial No")+'</button>')
- .appendTo($("<div>")
- .css({"margin-bottom": "10px", "margin-top": "10px"})
- .appendTo(grid_row.grid_form.fields_dict.serial_no.$wrapper));
+ frappe.model.get_value('Item', {'name': grid_row.doc.item_code},
+ ['has_serial_no', 'has_batch_no'], ({has_serial_no, has_batch_no}) => {
+ Object.assign(grid_row.doc, {has_serial_no, has_batch_no});
- var me = this;
- $btn.on("click", function() {
- let callback = '';
- let on_close = '';
-
- frappe.model.get_value('Item', {'name':grid_row.doc.item_code}, 'has_serial_no',
- (data) => {
- if(data) {
- grid_row.doc.has_serial_no = data.has_serial_no;
- me.show_serial_batch_selector(grid_row.frm, grid_row.doc,
- callback, on_close, true);
- }
+ if (has_serial_no) {
+ attach_selector_button(__("Add Serial No"),
+ grid_row.grid_form.fields_dict.serial_no.$wrapper, this, grid_row);
+ } else if (has_batch_no) {
+ attach_selector_button(__("Pick Batch No"),
+ grid_row.grid_form.fields_dict.batch_no.$wrapper, this, grid_row);
}
- );
- });
+ }
+ );
},
route_to_adjustment_jv: (args) => {
@@ -291,17 +284,15 @@
return options[0];
}
},
- copy_parent_value_in_all_row: function(doc, dt, dn, table_fieldname, fieldname, parent_fieldname) {
- var d = locals[dt][dn];
- if(d[fieldname]){
- var cl = doc[table_fieldname] || [];
- for(var i = 0; i < cl.length; i++) {
+ overrides_parent_value_in_all_rows: function(doc, dt, dn, table_fieldname, fieldname, parent_fieldname) {
+ if (doc[parent_fieldname]) {
+ let cl = doc[table_fieldname] || [];
+ for (let i = 0; i < cl.length; i++) {
cl[i][fieldname] = doc[parent_fieldname];
}
+ frappe.refresh_field(table_fieldname);
}
- refresh_field(table_fieldname);
},
-
create_new_doc: function (doctype, update_fields) {
frappe.model.with_doctype(doctype, function() {
var new_doc = frappe.model.get_new_doc(doctype);
@@ -714,7 +705,7 @@
}
frappe.form.link_formatters['Item'] = function(value, doc) {
- if (doc && value && doc.item_name && doc.item_name !== value) {
+ if (doc && value && doc.item_name && doc.item_name !== value && doc.item_code === value) {
return value + ': ' + doc.item_name;
} else if (!value && doc.doctype && doc.item_name) {
// format blank value in child table
@@ -733,6 +724,18 @@
}
}
+frappe.form.link_formatters['Project'] = function(value, doc) {
+ if (doc && value && doc.project_name && doc.project_name !== value && doc.project === value) {
+ return value + ': ' + doc.project_name;
+ } else if (!value && doc.doctype && doc.project_name) {
+ // format blank value in child table
+ return doc.project;
+ } else {
+ // if value is blank in report view or project name and name are the same, return as is
+ return value;
+ }
+};
+
// add description on posting time
$(document).on('app_ready', function() {
if(!frappe.datetime.is_timezone_same()) {
@@ -745,3 +748,14 @@
});
}
});
+
+function attach_selector_button(inner_text, append_loction, context, grid_row) {
+ let $btn_div = $("<div>").css({"margin-bottom": "10px", "margin-top": "10px"})
+ .appendTo(append_loction);
+ let $btn = $(`<button class="btn btn-sm btn-default">${inner_text}</button>`)
+ .appendTo($btn_div);
+
+ $btn.on("click", function() {
+ context.show_serial_batch_selector(grid_row.frm, grid_row.doc, "", "", true);
+ });
+}
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index d49a813..b5d3981 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -74,9 +74,18 @@
fieldname: 'qty',
fieldtype:'Float',
read_only: me.has_batch && !me.has_serial_no,
- label: __(me.has_batch && !me.has_serial_no ? 'Total Qty' : 'Qty'),
+ label: __(me.has_batch && !me.has_serial_no ? 'Selected Qty' : 'Qty'),
default: flt(me.item.stock_qty),
},
+ ...get_pending_qty_fields(me),
+ {
+ fieldname: 'uom',
+ read_only: 1,
+ fieldtype: 'Link',
+ options: 'UOM',
+ label: __('UOM'),
+ default: me.item.uom
+ },
{
fieldname: 'auto_fetch_button',
fieldtype:'Button',
@@ -173,6 +182,7 @@
if (this.has_batch && !this.has_serial_no) {
this.update_total_qty();
+ this.update_pending_qtys();
}
this.dialog.show();
@@ -313,7 +323,21 @@
qty_field.set_input(total_qty);
},
+ update_pending_qtys: function() {
+ const pending_qty_field = this.dialog.fields_dict.pending_qty;
+ const total_selected_qty_field = this.dialog.fields_dict.total_selected_qty;
+ if (!pending_qty_field || !total_selected_qty_field) return;
+
+ const me = this;
+ const required_qty = this.dialog.fields_dict.required_qty.value;
+ const selected_qty = this.dialog.fields_dict.qty.value;
+ const total_selected_qty = selected_qty + calc_total_selected_qty(me);
+ const pending_qty = required_qty - total_selected_qty;
+
+ pending_qty_field.set_input(pending_qty);
+ total_selected_qty_field.set_input(total_selected_qty);
+ },
get_batch_fields: function() {
var me = this;
@@ -353,9 +377,9 @@
return row.on_grid_fields_dict.batch_no.get_value();
}
});
- if (selected_batches.includes(val)) {
+ if (selected_batches.includes(batch_no)) {
this.set_value("");
- frappe.throw(__('Batch {0} already selected.', [val]));
+ frappe.throw(__('Batch {0} already selected.', [batch_no]));
}
if (me.warehouse_details.name) {
@@ -415,6 +439,7 @@
}
me.update_total_qty();
+ me.update_pending_qtys();
}
},
],
@@ -511,3 +536,60 @@
];
}
});
+
+function get_pending_qty_fields(me) {
+ if (!check_can_calculate_pending_qty(me)) return [];
+ const { frm: { doc: { fg_completed_qty }}, item: { item_code, stock_qty }} = me;
+ const { qty_consumed_per_unit } = erpnext.stock.bom.items[item_code];
+
+ const total_selected_qty = calc_total_selected_qty(me);
+ const required_qty = flt(fg_completed_qty) * flt(qty_consumed_per_unit);
+ const pending_qty = required_qty - (flt(stock_qty) + total_selected_qty);
+
+ const pending_qty_fields = [
+ { fieldtype: 'Section Break', label: __('Pending Quantity') },
+ {
+ fieldname: 'required_qty',
+ read_only: 1,
+ fieldtype: 'Float',
+ label: __('Required Qty'),
+ default: required_qty
+ },
+ { fieldtype: 'Column Break' },
+ {
+ fieldname: 'total_selected_qty',
+ read_only: 1,
+ fieldtype: 'Float',
+ label: __('Total Selected Qty'),
+ default: total_selected_qty
+ },
+ { fieldtype: 'Column Break' },
+ {
+ fieldname: 'pending_qty',
+ read_only: 1,
+ fieldtype: 'Float',
+ label: __('Pending Qty'),
+ default: pending_qty
+ },
+ ];
+ return pending_qty_fields;
+}
+
+function calc_total_selected_qty(me) {
+ const { frm: { doc: { items }}, item: { name, item_code }} = me;
+ const totalSelectedQty = items
+ .filter( item => ( item.name !== name ) && ( item.item_code === item_code ) )
+ .map( item => flt(item.qty) )
+ .reduce( (i, j) => i + j, 0);
+ return totalSelectedQty;
+}
+
+function check_can_calculate_pending_qty(me) {
+ const { frm: { doc }, item } = me;
+ const docChecks = doc.bom_no
+ && doc.fg_completed_qty
+ && erpnext.stock.bom
+ && erpnext.stock.bom.name === doc.bom_no;
+ const itemChecks = !!item;
+ return docChecks && itemChecks;
+}
diff --git a/erpnext/public/js/website_theme.js b/erpnext/public/js/website_theme.js
new file mode 100644
index 0000000..0009cac
--- /dev/null
+++ b/erpnext/public/js/website_theme.js
@@ -0,0 +1,14 @@
+// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
+// MIT License. See license.txt
+
+frappe.ui.form.on('Website Theme', {
+ validate(frm) {
+ let theme_scss = frm.doc.theme_scss;
+ if (theme_scss && theme_scss.includes('frappe/public/scss/website')
+ && !theme_scss.includes('erpnext/public/scss/website')
+ ) {
+ frm.set_value('theme_scss',
+ `${frm.doc.theme_scss}\n@import "erpnext/public/scss/website";`);
+ }
+ }
+});
diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss
index 0bb8e68..9bdaa8d 100644
--- a/erpnext/public/scss/point-of-sale.scss
+++ b/erpnext/public/scss/point-of-sale.scss
@@ -129,11 +129,20 @@
@extend .pointer-no-select;
border-radius: var(--border-radius-md);
box-shadow: var(--shadow-base);
+ position: relative;
&:hover {
transform: scale(1.02, 1.02);
}
+ .item-qty-pill {
+ position: absolute;
+ display: flex;
+ margin: var(--margin-sm);
+ justify-content: flex-end;
+ right: 0px;
+ }
+
.item-display {
display: flex;
align-items: center;
@@ -766,9 +775,10 @@
> .payment-modes {
display: flex;
padding-bottom: var(--padding-sm);
- margin-bottom: var(--margin-xs);
+ margin-bottom: var(--margin-sm);
overflow-x: scroll;
overflow-y: hidden;
+ flex-shrink: 0;
> .payment-mode-wrapper {
min-width: 40%;
@@ -825,9 +835,24 @@
> .fields-numpad-container {
display: flex;
flex: 1;
+ height: 100%;
+ position: relative;
+ justify-content: flex-end;
> .fields-section {
flex: 1;
+ position: absolute;
+ display: flex;
+ flex-direction: column;
+ width: 50%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ padding-bottom: var(--margin-md);
+
+ .invoice-fields {
+ overflow-y: scroll;
+ }
}
> .number-pad {
@@ -835,6 +860,7 @@
display: flex;
justify-content: flex-end;
align-items: flex-end;
+ max-width: 50%;
.numpad-container {
display: grid;
@@ -861,6 +887,7 @@
margin-bottom: var(--margin-sm);
justify-content: center;
flex-direction: column;
+ flex-shrink: 0;
> .totals {
display: flex;
diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss
index 159a8a4..9402cf9 100644
--- a/erpnext/public/scss/shopping_cart.scss
+++ b/erpnext/public/scss/shopping_cart.scss
@@ -1,4 +1,3 @@
-@import "frappe/public/scss/desk/variables";
@import "frappe/public/scss/common/mixins";
body.product-page {
@@ -74,15 +73,6 @@
}
}
- // .card-body {
- // text-align: center;
- // }
-
- // .featured-item {
- // .card-body {
- // text-align: left;
- // }
- // }
.card-img-container {
height: 210px;
width: 100%;
@@ -217,12 +207,12 @@
border-color: var(--table-border-color) !important;
padding: 15px;
- @include media-breakpoint-between(xs, md) {
+ @media (max-width: var(--md-width)) {
height: 300px;
width: 300px;
}
- @include media-breakpoint-up(lg) {
+ @media (min-width: var(--lg-width)) {
height: 350px;
width: 350px;
}
@@ -233,11 +223,12 @@
}
.item-slideshow {
- @include media-breakpoint-between(xs, md) {
+
+ @media (max-width: var(--md-width)) {
max-height: 320px;
}
- @include media-breakpoint-up(lg) {
+ @media (min-width: var(--lg-width)) {
max-height: 430px;
}
@@ -254,7 +245,7 @@
cursor: pointer;
&:hover, &.active {
- border-color: $primary;
+ border-color: var(--primary);
}
}
@@ -316,12 +307,9 @@
}
.item-group-slideshow {
- .item-group-description {
- // max-width: 900px;
- }
.carousel-inner.rounded-carousel {
- border-radius: $card-border-radius;
+ border-radius: var(--card-border-radius);
}
}
diff --git a/erpnext/public/scss/website.scss b/erpnext/public/scss/website.scss
index 56b717c..f4325c0 100644
--- a/erpnext/public/scss/website.scss
+++ b/erpnext/public/scss/website.scss
@@ -1,4 +1,3 @@
-@import "frappe/public/scss/website/variables";
.filter-options {
max-height: 300px;
@@ -14,7 +13,7 @@
}
&.active {
- border-color: $primary;
+ border-color: var(--primary);
.check {
display: inline-flex;
@@ -25,7 +24,7 @@
.check {
display: inline-flex;
padding: 0.25rem;
- background: $primary;
+ background: var(--primary);
color: white;
border-radius: 50%;
font-size: 12px;
@@ -38,12 +37,12 @@
}
.result {
- border-bottom: 1px solid $border-color;
+ border-bottom: 1px solid var(--border-color);
}
.transaction-list-item {
padding: 1rem 0;
- border-top: 1px solid $border-color;
+ border-top: 1px solid var(--border-color);
position: relative;
a.transaction-item-link {
diff --git a/erpnext/regional/address_template/templates/united_states.html b/erpnext/regional/address_template/templates/united_states.html
index 089315e..77fce46 100644
--- a/erpnext/regional/address_template/templates/united_states.html
+++ b/erpnext/regional/address_template/templates/united_states.html
@@ -1,4 +1,4 @@
{{ address_line1 }}<br>
{% if address_line2 %}{{ address_line2 }}<br>{% endif -%}
{{ city }}, {% if state %}{{ state }}{% endif -%}{% if pincode %} {{ pincode }}<br>{% endif -%}
-{% if country != "United States" %}{{ country|upper }}{% endif -%}
+{% if country != "United States" %}{{ country }}{% endif -%}
diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json
index db8bda7..68ed339 100644
--- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json
+++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json
@@ -8,6 +8,7 @@
"enable",
"section_break_2",
"sandbox_mode",
+ "applicable_from",
"credentials",
"auth_token",
"token_expiry"
@@ -48,12 +49,19 @@
"fieldname": "sandbox_mode",
"fieldtype": "Check",
"label": "Sandbox Mode"
+ },
+ {
+ "fieldname": "applicable_from",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Applicable From",
+ "reqd": 1
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-01-13 12:04:49.449199",
+ "modified": "2021-03-30 12:26:25.538294",
"modified_by": "Administrator",
"module": "Regional",
"name": "E Invoice Settings",
diff --git a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json
index dd9d997..a65b1ca 100644
--- a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json
+++ b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json
@@ -5,6 +5,7 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
+ "company",
"gstin",
"username",
"password"
@@ -30,12 +31,20 @@
"in_list_view": 1,
"label": "Password",
"reqd": 1
+ },
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2020-12-22 15:10:53.466205",
+ "modified": "2021-03-22 12:16:56.365616",
"modified_by": "Administrator",
"module": "Regional",
"name": "E Invoice User",
diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html
index 369a400..3b6a45a 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html
@@ -172,7 +172,7 @@
</thead>
<tbody>
<tr>
- <td><b>(A) {{__("ITC Available (whether in full op part)")}}</b></td>
+ <td><b>(A) {{__("ITC Available (whether in full or part)")}}</b></td>
<td></td>
<td></td>
<td></td>
diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
index a5dd5a2..6415204 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
@@ -3,148 +3,21 @@
# For license information, please see license.txt
from __future__ import unicode_literals
+import os
+import json
import frappe
+from six import iteritems
from frappe import _
from frappe.model.document import Document
-import json
-from six import iteritems
-from frappe.utils import flt, getdate
+from frappe.utils import flt, cstr
from erpnext.regional.india import state_numbers
class GSTR3BReport(Document):
- def before_save(self):
-
+ def validate(self):
self.get_data()
def get_data(self):
-
- self.report_dict = {
- "gstin": "",
- "ret_period": "",
- "inward_sup": {
- "isup_details": [
- {
- "ty": "GST",
- "intra": 0,
- "inter": 0
- },
- {
- "ty": "NONGST",
- "inter": 0,
- "intra": 0
- }
- ]
- },
- "sup_details": {
- "osup_zero": {
- "csamt": 0,
- "txval": 0,
- "iamt": 0
- },
- "osup_nil_exmp": {
- "txval": 0
- },
- "osup_det": {
- "samt": 0,
- "csamt": 0,
- "txval": 0,
- "camt": 0,
- "iamt": 0
- },
- "isup_rev": {
- "samt": 0,
- "csamt": 0,
- "txval": 0,
- "camt": 0,
- "iamt": 0
- },
- "osup_nongst": {
- "txval": 0,
- }
- },
- "inter_sup": {
- "unreg_details": [],
- "comp_details": [],
- "uin_details": []
- },
- "itc_elg": {
- "itc_avl": [
- {
- "csamt": 0,
- "samt": 0,
- "ty": "IMPG",
- "camt": 0,
- "iamt": 0
- },
- {
- "csamt": 0,
- "samt": 0,
- "ty": "IMPS",
- "camt": 0,
- "iamt": 0
- },
- {
- "samt": 0,
- "csamt": 0,
- "ty": "ISRC",
- "camt": 0,
- "iamt": 0
- },
- {
- "ty": "ISD",
- "iamt": 0,
- "camt": 0,
- "samt": 0,
- "csamt": 0
- },
- {
- "samt": 0,
- "csamt": 0,
- "ty": "OTH",
- "camt": 0,
- "iamt": 0
- }
- ],
- "itc_rev": [
- {
- "ty": "RUL",
- "iamt": 0,
- "camt": 0,
- "samt": 0,
- "csamt": 0
- },
- {
- "ty": "OTH",
- "iamt": 0,
- "camt": 0,
- "samt": 0,
- "csamt": 0
- }
- ],
- "itc_net": {
- "samt": 0,
- "csamt": 0,
- "camt": 0,
- "iamt": 0
- },
- "itc_inelg": [
- {
- "ty": "RUL",
- "iamt": 0,
- "camt": 0,
- "samt": 0,
- "csamt": 0
- },
- {
- "ty": "OTH",
- "iamt": 0,
- "camt": 0,
- "samt": 0,
- "csamt": 0
- }
- ]
- }
- }
+ self.report_dict = json.loads(get_json('gstr_3b_report_template'))
self.gst_details = self.get_company_gst_details()
self.report_dict["gstin"] = self.gst_details.get("gstin")
@@ -152,23 +25,19 @@
self.month_no = get_period(self.month)
self.account_heads = self.get_account_heads()
- outward_supply_tax_amounts = self.get_tax_amounts("Sales Invoice")
- inward_supply_tax_amounts = self.get_tax_amounts("Purchase Invoice", reverse_charge="Y")
+ self.get_outward_supply_details("Sales Invoice")
+ self.set_outward_taxable_supplies()
+
+ self.get_outward_supply_details("Purchase Invoice", reverse_charge=True)
+ self.set_supplies_liable_to_reverse_charge()
+
itc_details = self.get_itc_details()
-
- self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_det", ["Registered Regular"])
- self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_zero", ["SEZ", "Deemed Export", "Overseas"])
- self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Unregistered", "Overseas"], reverse_charge="Y")
- self.report_dict["sup_details"]["osup_nil_exmp"]["txval"] = flt(self.get_nil_rated_supply_value(), 2)
self.set_itc_details(itc_details)
-
- inter_state_supplies = self.get_inter_state_supplies(self.gst_details.get("gst_state_number"))
+ self.get_itc_reversal_entries()
inward_nil_exempt = self.get_inward_nil_exempt(self.gst_details.get("gst_state"))
- self.set_inter_state_supply(inter_state_supplies)
self.set_inward_nil_exempt(inward_nil_exempt)
self.missing_field_invoices = self.get_missing_field_invoices()
-
self.json_output = frappe.as_json(self.report_dict)
def set_inward_nil_exempt(self, inward_nil_exempt):
@@ -178,189 +47,95 @@
self.report_dict["inward_sup"]["isup_details"][1]["intra"] = flt(inward_nil_exempt.get("non_gst").get("intra"), 2)
def set_itc_details(self, itc_details):
-
- itc_type_map = {
+ itc_eligible_type_map = {
'IMPG': 'Import Of Capital Goods',
'IMPS': 'Import Of Service',
+ 'ISRC': 'ITC on Reverse Charge',
'ISD': 'Input Service Distributor',
'OTH': 'All Other ITC'
}
+ itc_ineligible_map = {
+ 'RUL': 'Ineligible As Per Section 17(5)',
+ 'OTH': 'Ineligible Others'
+ }
+
net_itc = self.report_dict["itc_elg"]["itc_net"]
for d in self.report_dict["itc_elg"]["itc_avl"]:
-
- itc_type = itc_type_map.get(d["ty"])
-
- if d["ty"] == 'ISRC':
- reverse_charge = ["Y"]
- itc_type = 'All Other ITC'
- gst_category = ['Unregistered', 'Overseas']
- else:
- gst_category = ['Unregistered', 'Overseas', 'Registered Regular']
- reverse_charge = ["N", "Y"]
-
- for account_head in self.account_heads:
- for category in gst_category:
- for charge_type in reverse_charge:
- for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
- d[key[0]] += flt(itc_details.get((category, itc_type, charge_type, account_head.get(key[1])), {}).get("amount"), 2)
-
+ itc_type = itc_eligible_type_map.get(d["ty"])
for key in ['iamt', 'camt', 'samt', 'csamt']:
+ d[key] = flt(itc_details.get(itc_type, {}).get(key))
net_itc[key] += flt(d[key], 2)
- for account_head in self.account_heads:
- itc_inelg = self.report_dict["itc_elg"]["itc_inelg"][1]
- for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
- itc_inelg[key[0]] = flt(itc_details.get(("Ineligible", "N", account_head.get(key[1])), {}).get("amount"), 2)
+ for d in self.report_dict["itc_elg"]["itc_inelg"]:
+ itc_type = itc_ineligible_map.get(d["ty"])
+ for key in ['iamt', 'camt', 'samt', 'csamt']:
+ d[key] = flt(itc_details.get(itc_type, {}).get(key))
- def prepare_data(self, doctype, tax_details, supply_type, supply_category, gst_category_list, reverse_charge="N"):
+ def get_itc_reversal_entries(self):
+ reversal_entries = frappe.db.sql("""
+ SELECT ja.account, j.reversal_type, sum(credit_in_account_currency) as amount
+ FROM `tabJournal Entry` j, `tabJournal Entry Account` ja
+ where j.docstatus = 1
+ and j.is_opening = 'No'
+ and ja.parent = j.name
+ and j.voucher_type = 'Reversal Of ITC'
+ and month(j.posting_date) = %s and year(j.posting_date) = %s
+ and j.company = %s and j.company_gstin = %s
+ GROUP BY ja.account, j.reversal_type""", (self.month_no, self.year, self.company,
+ self.gst_details.get("gstin")), as_dict=1)
- account_map = {
- 'sgst_account': 'samt',
- 'cess_account': 'csamt',
- 'cgst_account': 'camt',
- 'igst_account': 'iamt'
- }
+ net_itc = self.report_dict["itc_elg"]["itc_net"]
- txval = 0
- total_taxable_value = self.get_total_taxable_value(doctype, reverse_charge)
+ for entry in reversal_entries:
+ if entry.reversal_type == 'As per rules 42 & 43 of CGST Rules':
+ index = 0
+ else:
+ index = 1
- for gst_category in gst_category_list:
- txval += total_taxable_value.get(gst_category,0)
- for account_head in self.account_heads:
- for account_type, account_name in iteritems(account_head):
- if account_map.get(account_type) in self.report_dict.get(supply_type).get(supply_category):
- self.report_dict[supply_type][supply_category][account_map.get(account_type)] += \
- flt(tax_details.get((account_name, gst_category), {}).get("amount"), 2)
-
- self.report_dict[supply_type][supply_category]["txval"] += flt(txval, 2)
-
- def set_inter_state_supply(self, inter_state_supply):
- osup_det = self.report_dict["sup_details"]["osup_det"]
-
- for key, value in iteritems(inter_state_supply):
- if key[0] == "Unregistered":
- self.report_dict["inter_sup"]["unreg_details"].append(value)
-
- if key[0] == "Registered Composition":
- self.report_dict["inter_sup"]["comp_details"].append(value)
-
- if key[0] == "UIN Holders":
- self.report_dict["inter_sup"]["uin_details"].append(value)
-
- def get_total_taxable_value(self, doctype, reverse_charge):
-
- return frappe._dict(frappe.db.sql("""
- select gst_category, sum(net_total) as total
- from `tab{doctype}`
- where docstatus = 1 and month(posting_date) = %s
- and year(posting_date) = %s and reverse_charge = %s
- and company = %s and company_gstin = %s
- group by gst_category
- """ #nosec
- .format(doctype = doctype), (self.month_no, self.year, reverse_charge, self.company, self.gst_details.get("gstin"))))
+ for key in ['camt', 'samt', 'iamt', 'csamt']:
+ if entry.account in self.account_heads.get(key):
+ self.report_dict["itc_elg"]["itc_rev"][index][key] += flt(entry.amount)
+ net_itc[key] -= flt(entry.amount)
def get_itc_details(self):
- itc_amount = frappe.db.sql("""
- select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount,
- t.account_head, s.eligibility_for_itc, s.reverse_charge
- from `tabPurchase Invoice` s , `tabPurchase Taxes and Charges` t
- where s.docstatus = 1 and t.parent = s.name
- and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s
- and s.company_gstin = %s
- group by t.account_head, s.gst_category, s.eligibility_for_itc
- """,
- (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
+ itc_amounts = frappe.db.sql("""
+ SELECT eligibility_for_itc, sum(itc_integrated_tax) as itc_integrated_tax,
+ sum(itc_central_tax) as itc_central_tax,
+ sum(itc_state_tax) as itc_state_tax,
+ sum(itc_cess_amount) as itc_cess_amount
+ FROM `tabPurchase Invoice`
+ WHERE docstatus = 1
+ and is_opening = 'No'
+ and month(posting_date) = %s and year(posting_date) = %s and company = %s
+ and company_gstin = %s
+ GROUP BY eligibility_for_itc
+ """, (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
itc_details = {}
-
- for d in itc_amount:
- itc_details.setdefault((d.gst_category, d.eligibility_for_itc, d.reverse_charge, d.account_head),{
- "amount": d.tax_amount
+ for d in itc_amounts:
+ itc_details.setdefault(d.eligibility_for_itc, {
+ 'iamt': d.itc_integrated_tax,
+ 'camt': d.itc_central_tax,
+ 'samt': d.itc_state_tax,
+ 'csamt': d.itc_cess_amount
})
return itc_details
- def get_nil_rated_supply_value(self):
-
- return frappe.db.sql("""
- select sum(i.base_amount) as total from
- `tabSales Invoice Item` i, `tabSales Invoice` s
- where s.docstatus = 1 and i.parent = s.name and i.is_nil_exempt = 1
- and month(s.posting_date) = %s and year(s.posting_date) = %s
- and s.company = %s and s.company_gstin = %s""",
- (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)[0].total
-
- def get_inter_state_supplies(self, state_number):
- inter_state_supply_tax = frappe.db.sql(""" select t.account_head, t.tax_amount_after_discount_amount as tax_amount,
- s.name, s.net_total, s.place_of_supply, s.gst_category from `tabSales Invoice` s, `tabSales Taxes and Charges` t
- where t.parent = s.name and s.docstatus = 1 and month(s.posting_date) = %s and year(s.posting_date) = %s
- and s.company = %s and s.company_gstin = %s and s.gst_category in ('Unregistered', 'Registered Composition', 'UIN Holders')
- """, (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
-
- inter_state_supply_tax_mapping = {}
- inter_state_supply_details = {}
-
- for d in inter_state_supply_tax:
- inter_state_supply_tax_mapping.setdefault(d.name, {
- 'place_of_supply': d.place_of_supply,
- 'taxable_value': d.net_total,
- 'gst_category': d.gst_category,
- 'camt': 0.0,
- 'samt': 0.0,
- 'iamt': 0.0,
- 'csamt': 0.0
- })
-
- if d.account_head in [a.cgst_account for a in self.account_heads]:
- inter_state_supply_tax_mapping[d.name]['camt'] += d.tax_amount
-
- if d.account_head in [a.sgst_account for a in self.account_heads]:
- inter_state_supply_tax_mapping[d.name]['samt'] += d.tax_amount
-
- if d.account_head in [a.igst_account for a in self.account_heads]:
- inter_state_supply_tax_mapping[d.name]['iamt'] += d.tax_amount
-
- if d.account_head in [a.cess_account for a in self.account_heads]:
- inter_state_supply_tax_mapping[d.name]['csamt'] += d.tax_amount
-
- for key, value in iteritems(inter_state_supply_tax_mapping):
- if value.get('place_of_supply'):
- osup_det = self.report_dict["sup_details"]["osup_det"]
- osup_det["txval"] = flt(osup_det["txval"] + value['taxable_value'], 2)
- osup_det["iamt"] = flt(osup_det["iamt"] + value['iamt'], 2)
- osup_det["camt"] = flt(osup_det["camt"] + value['camt'], 2)
- osup_det["samt"] = flt(osup_det["samt"] + value['samt'], 2)
- osup_det["csamt"] = flt(osup_det["csamt"] + value['csamt'], 2)
-
- if state_number != value.get('place_of_supply').split("-")[0]:
- inter_state_supply_details.setdefault((value.get('gst_category'), value.get('place_of_supply')), {
- "txval": 0.0,
- "pos": value.get('place_of_supply').split("-")[0],
- "iamt": 0.0
- })
-
- inter_state_supply_details[(value.get('gst_category'), value.get('place_of_supply'))]['txval'] += value['taxable_value']
- inter_state_supply_details[(value.get('gst_category'), value.get('place_of_supply'))]['iamt'] += value['iamt']
-
- return inter_state_supply_details
-
def get_inward_nil_exempt(self, state):
- inward_nil_exempt = frappe.db.sql(""" select p.place_of_supply, sum(i.base_amount) as base_amount,
- i.is_nil_exempt, i.is_non_gst from `tabPurchase Invoice` p , `tabPurchase Invoice Item` i
- where p.docstatus = 1 and p.name = i.parent
+ inward_nil_exempt = frappe.db.sql("""
+ SELECT p.place_of_supply, sum(i.base_amount) as base_amount, i.is_nil_exempt, i.is_non_gst
+ FROM `tabPurchase Invoice` p , `tabPurchase Invoice Item` i
+ WHERE p.docstatus = 1 and p.name = i.parent
+ and p.is_opening = 'No'
and p.gst_category != 'Registered Composition'
- and (i.is_nil_exempt = 1 or i.is_non_gst = 1) and
- month(p.posting_date) = %s and year(p.posting_date) = %s and p.company = %s and p.company_gstin = %s
- group by p.place_of_supply, i.is_nil_exempt, i.is_non_gst""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
-
- inward_nil_exempt += frappe.db.sql("""SELECT sum(base_net_total) as base_amount, gst_category, place_of_supply
- FROM `tabPurchase Invoice`
- WHERE docstatus = 1 and gst_category = 'Registered Composition'
- and month(posting_date) = %s and year(posting_date) = %s
- and company = %s and company_gstin = %s
- group by place_of_supply""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
+ and (i.is_nil_exempt = 1 or i.is_non_gst = 1 or p.gst_category = 'Registered Composition') and
+ month(p.posting_date) = %s and year(p.posting_date) = %s
+ and p.company = %s and p.company_gstin = %s
+ GROUP BY p.place_of_supply, i.is_nil_exempt, i.is_non_gst""",
+ (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
inward_nil_exempt_details = {
"gst": {
@@ -388,37 +163,193 @@
return inward_nil_exempt_details
- def get_tax_amounts(self, doctype, reverse_charge="N"):
+ def get_outward_supply_details(self, doctype, reverse_charge=None):
+ self.get_outward_tax_invoices(doctype, reverse_charge=reverse_charge)
+ self.get_outward_items(doctype)
+ self.get_outward_tax_details(doctype)
+ def get_outward_tax_invoices(self, doctype, reverse_charge=None):
+ self.invoices = []
+ self.invoice_detail_map = {}
+ condition = ''
+
+ if reverse_charge:
+ condition += "AND reverse_charge = 'Y'"
+
+ invoice_details = frappe.db.sql("""
+ SELECT
+ name, gst_category, export_type, place_of_supply
+ FROM
+ `tab{doctype}`
+ WHERE
+ docstatus = 1
+ AND month(posting_date) = %s
+ AND year(posting_date) = %s
+ AND company = %s
+ AND company_gstin = %s
+ AND is_opening = 'No'
+ {reverse_charge}
+ ORDER BY name
+ """.format(doctype=doctype, reverse_charge=condition), (self.month_no, self.year,
+ self.company, self.gst_details.get("gstin")), as_dict=1)
+
+ for d in invoice_details:
+ self.invoice_detail_map.setdefault(d.name, d)
+ self.invoices.append(d.name)
+
+ def get_outward_items(self, doctype):
+ self.invoice_items = frappe._dict()
+ self.is_nil_exempt = []
+ self.is_non_gst = []
+
+ if self.get('invoices'):
+ item_details = frappe.db.sql("""
+ SELECT
+ item_code, parent, taxable_value, base_net_amount, item_tax_rate,
+ is_nil_exempt, is_non_gst
+ FROM
+ `tab%s Item`
+ WHERE parent in (%s)
+ """ % (doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1)
+
+ for d in item_details:
+ if d.item_code not in self.invoice_items.get(d.parent, {}):
+ self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code,
+ sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in item_details
+ if i.item_code == d.item_code and i.parent == d.parent))
+
+ if d.is_nil_exempt and d.item_code not in self.is_nil_exempt:
+ self.is_nil_exempt.append(d.item_code)
+
+ if d.is_non_gst and d.item_code not in self.is_non_gst:
+ self.is_non_gst.append(d.item_code)
+
+ def get_outward_tax_details(self, doctype):
if doctype == "Sales Invoice":
tax_template = 'Sales Taxes and Charges'
elif doctype == "Purchase Invoice":
tax_template = 'Purchase Taxes and Charges'
- tax_amounts = frappe.db.sql("""
- select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount, t.account_head
- from `tab{doctype}` s , `tab{template}` t
- where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s
- and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s
- and s.company_gstin = %s
- group by t.account_head, s.gst_category
- """ #nosec
- .format(doctype=doctype, template=tax_template),
- (reverse_charge, self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
+ self.items_based_on_tax_rate = {}
+ self.invoice_cess = frappe._dict()
+ self.cgst_sgst_invoices = []
- tax_details = {}
+ if self.get('invoices'):
+ tax_details = frappe.db.sql("""
+ SELECT
+ parent, account_head, item_wise_tax_detail, base_tax_amount_after_discount_amount
+ FROM `tab%s`
+ WHERE
+ parenttype = %s and docstatus = 1
+ and parent in (%s)
+ ORDER BY account_head
+ """ % (tax_template, '%s', ', '.join(['%s']*len(self.invoices))),
+ tuple([doctype] + list(self.invoices)))
- for d in tax_amounts:
- tax_details.setdefault(
- (d.account_head,d.gst_category),{
- "amount": d.get("tax_amount"),
- }
- )
+ for parent, account, item_wise_tax_detail, tax_amount in tax_details:
+ if account in self.account_heads.get('csamt'):
+ self.invoice_cess.setdefault(parent, tax_amount)
+ else:
+ if item_wise_tax_detail:
+ try:
+ item_wise_tax_detail = json.loads(item_wise_tax_detail)
+ cgst_or_sgst = False
+ if account in self.account_heads.get('camt') \
+ or account in self.account_heads.get('samt'):
+ cgst_or_sgst = True
- return tax_details
+ for item_code, tax_amounts in item_wise_tax_detail.items():
+ if not (cgst_or_sgst or account in self.account_heads.get('iamt') or
+ (item_code in self.is_non_gst + self.is_nil_exempt)):
+ continue
+
+ tax_rate = tax_amounts[0]
+ if tax_rate:
+ if cgst_or_sgst:
+ tax_rate *= 2
+ if parent not in self.cgst_sgst_invoices:
+ self.cgst_sgst_invoices.append(parent)
+
+ rate_based_dict = self.items_based_on_tax_rate\
+ .setdefault(parent, {}).setdefault(tax_rate, [])
+ if item_code not in rate_based_dict:
+ rate_based_dict.append(item_code)
+ except ValueError:
+ continue
+
+
+ if self.get('invoice_items'):
+ # Build itemised tax for export invoices, nil and exempted where tax table is blank
+ for invoice, items in iteritems(self.invoice_items):
+ if invoice not in self.items_based_on_tax_rate and (self.invoice_detail_map.get(invoice, {}).get('export_type')
+ == "Without Payment of Tax"):
+ self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
+
+ def set_outward_taxable_supplies(self):
+ inter_state_supply_details = {}
+
+ for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
+ for rate, items in items_based_on_rate.items():
+ for item_code, taxable_value in self.invoice_items.get(inv).items():
+ if item_code in items:
+ if item_code in self.is_nil_exempt:
+ self.report_dict['sup_details']['osup_nil_exmp']['txval'] += taxable_value
+ elif item_code in self.is_non_gst:
+ self.report_dict['sup_details']['osup_nongst']['txval'] += taxable_value
+ elif rate == 0:
+ self.report_dict['sup_details']['osup_zero']['txval'] += taxable_value
+ #self.report_dict['sup_details']['osup_zero'][key] += tax_amount
+ else:
+ if inv in self.cgst_sgst_invoices:
+ tax_rate = rate/2
+ self.report_dict['sup_details']['osup_det']['camt'] += (taxable_value * tax_rate /100)
+ self.report_dict['sup_details']['osup_det']['samt'] += (taxable_value * tax_rate /100)
+ self.report_dict['sup_details']['osup_det']['txval'] += taxable_value
+ else:
+ self.report_dict['sup_details']['osup_det']['iamt'] += (taxable_value * rate /100)
+ self.report_dict['sup_details']['osup_det']['txval'] += taxable_value
+
+ gst_category = self.invoice_detail_map.get(inv, {}).get('gst_category')
+ place_of_supply = self.invoice_detail_map.get(inv, {}).get('place_of_supply') or '00-Other Territory'
+
+ if gst_category in ['Unregistered', 'Registered Composition', 'UIN Holders'] and \
+ self.gst_details.get("gst_state") != place_of_supply.split("-")[1]:
+ inter_state_supply_details.setdefault((gst_category, place_of_supply), {
+ "txval": 0.0,
+ "pos": place_of_supply.split("-")[0],
+ "iamt": 0.0
+ })
+ inter_state_supply_details[(gst_category, place_of_supply)]['txval'] += taxable_value
+ inter_state_supply_details[(gst_category, place_of_supply)]['iamt'] += (taxable_value * rate /100)
+
+ self.set_inter_state_supply(inter_state_supply_details)
+
+ def set_supplies_liable_to_reverse_charge(self):
+ for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
+ for rate, items in items_based_on_rate.items():
+ for item_code, taxable_value in self.invoice_items.get(inv).items():
+ if item_code in items:
+ if inv in self.cgst_sgst_invoices:
+ tax_rate = rate/2
+ self.report_dict['sup_details']['isup_rev']['camt'] += (taxable_value * tax_rate /100)
+ self.report_dict['sup_details']['isup_rev']['samt'] += (taxable_value * tax_rate /100)
+ self.report_dict['sup_details']['isup_rev']['txval'] += taxable_value
+ else:
+ self.report_dict['sup_details']['isup_rev']['iamt'] += (taxable_value * rate /100)
+ self.report_dict['sup_details']['isup_rev']['txval'] += taxable_value
+
+ def set_inter_state_supply(self, inter_state_supply):
+ for key, value in iteritems(inter_state_supply):
+ if key[0] == "Unregistered":
+ self.report_dict["inter_sup"]["unreg_details"].append(value)
+
+ if key[0] == "Registered Composition":
+ self.report_dict["inter_sup"]["comp_details"].append(value)
+
+ if key[0] == "UIN Holders":
+ self.report_dict["inter_sup"]["uin_details"].append(value)
def get_company_gst_details(self):
-
gst_details = frappe.get_all("Address",
fields=["gstin", "gst_state", "gst_state_number"],
filters={
@@ -431,20 +362,28 @@
frappe.throw(_("Please enter GSTIN and state for the Company Address {0}").format(self.company_address))
def get_account_heads(self):
+ account_map = {
+ 'sgst_account': 'samt',
+ 'cess_account': 'csamt',
+ 'cgst_account': 'camt',
+ 'igst_account': 'iamt'
+ }
- account_heads = frappe.get_all("GST Account",
- fields=["cgst_account", "sgst_account", "igst_account", "cess_account"],
- filters={
- "company":self.company
- })
+ account_heads = {}
+ gst_settings_accounts = frappe.get_all("GST Account",
+ filters={'company': self.company, 'is_reverse_charge_account': 0},
+ fields=["cgst_account", "sgst_account", "igst_account", "cess_account"])
- if account_heads:
- return account_heads
- else:
- frappe.throw(_("Please set account heads in GST Settings for Compnay {0}").format(self.company))
+ if not gst_settings_accounts:
+ frappe.throw(_("Please set GST Accounts in GST Settings"))
+
+ for d in gst_settings_accounts:
+ for acc, val in d.items():
+ account_heads.setdefault(account_map.get(acc), []).append(val)
+
+ return account_heads
def get_missing_field_invoices(self):
-
missing_field_invoices = []
for doctype in ["Sales Invoice", "Purchase Invoice"]:
@@ -456,26 +395,32 @@
party_type = 'Supplier'
party = 'supplier'
- docnames = frappe.db.sql("""
- select t1.name from `tab{doctype}` t1, `tab{party_type}` t2
- where t1.docstatus = 1 and month(t1.posting_date) = %s and year(t1.posting_date) = %s
+ docnames = frappe.db.sql(
+ """
+ SELECT t1.name FROM `tab{doctype}` t1, `tab{party_type}` t2
+ WHERE t1.docstatus = 1 and t1.is_opening = 'No'
+ and month(t1.posting_date) = %s and year(t1.posting_date) = %s
and t1.company = %s and t1.place_of_supply IS NULL and t1.{party} = t2.name and
t2.gst_category != 'Overseas'
- """.format(doctype = doctype, party_type = party_type, party=party), (self.month_no, self.year, self.company), as_dict=1) #nosec
+ """.format(doctype = doctype, party_type = party_type,
+ party=party) ,(self.month_no, self.year, self.company), as_dict=1) #nosec
for d in docnames:
missing_field_invoices.append(d.name)
return ",".join(missing_field_invoices)
-def get_state_code(state):
+def get_json(template):
+ file_path = os.path.join(os.path.dirname(__file__), '{template}.json'.format(template=template))
+ with open(file_path, 'r') as f:
+ return cstr(f.read())
+def get_state_code(state):
state_code = state_numbers.get(state)
return state_code
def get_period(month, year=None):
-
month_no = {
"January": 1,
"February": 2,
@@ -499,13 +444,11 @@
@frappe.whitelist()
def view_report(name):
-
json_data = frappe.get_value("GSTR 3B Report", name, 'json_output')
return json.loads(json_data)
@frappe.whitelist()
def make_json(name):
-
json_data = frappe.get_value("GSTR 3B Report", name, 'json_output')
file_name = "GST3B.json"
frappe.local.response.filename = file_name
diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report_template.json b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report_template.json
new file mode 100644
index 0000000..a68bd6a
--- /dev/null
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report_template.json
@@ -0,0 +1,127 @@
+{
+ "gstin": "",
+ "ret_period": "",
+ "inward_sup": {
+ "isup_details": [
+ {
+ "ty": "GST",
+ "intra": 0,
+ "inter": 0
+ },
+ {
+ "ty": "NONGST",
+ "inter": 0,
+ "intra": 0
+ }
+ ]
+ },
+ "sup_details": {
+ "osup_zero": {
+ "csamt": 0,
+ "txval": 0,
+ "iamt": 0
+ },
+ "osup_nil_exmp": {
+ "txval": 0
+ },
+ "osup_det": {
+ "samt": 0,
+ "csamt": 0,
+ "txval": 0,
+ "camt": 0,
+ "iamt": 0
+ },
+ "isup_rev": {
+ "samt": 0,
+ "csamt": 0,
+ "txval": 0,
+ "camt": 0,
+ "iamt": 0
+ },
+ "osup_nongst": {
+ "txval": 0
+ }
+ },
+ "inter_sup": {
+ "unreg_details": [],
+ "comp_details": [],
+ "uin_details": []
+ },
+ "itc_elg": {
+ "itc_avl": [
+ {
+ "csamt": 0,
+ "samt": 0,
+ "ty": "IMPG",
+ "camt": 0,
+ "iamt": 0
+ },
+ {
+ "csamt": 0,
+ "samt": 0,
+ "ty": "IMPS",
+ "camt": 0,
+ "iamt": 0
+ },
+ {
+ "samt": 0,
+ "csamt": 0,
+ "ty": "ISRC",
+ "camt": 0,
+ "iamt": 0
+ },
+ {
+ "ty": "ISD",
+ "iamt": 0,
+ "camt": 0,
+ "samt": 0,
+ "csamt": 0
+ },
+ {
+ "samt": 0,
+ "csamt": 0,
+ "ty": "OTH",
+ "camt": 0,
+ "iamt": 0
+ }
+ ],
+ "itc_rev": [
+ {
+ "ty": "RUL",
+ "iamt": 0,
+ "camt": 0,
+ "samt": 0,
+ "csamt": 0
+ },
+ {
+ "ty": "OTH",
+ "iamt": 0,
+ "camt": 0,
+ "samt": 0,
+ "csamt": 0
+ }
+ ],
+ "itc_net": {
+ "samt": 0,
+ "csamt": 0,
+ "camt": 0,
+ "iamt": 0
+ },
+ "itc_inelg": [
+ {
+ "ty": "RUL",
+ "iamt": 0,
+ "camt": 0,
+ "samt": 0,
+ "csamt": 0
+ },
+ {
+ "ty": "OTH",
+ "iamt": 0,
+ "camt": 0,
+ "samt": 0,
+ "csamt": 0
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py
index ef8af24..3857ce1 100644
--- a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py
@@ -60,8 +60,7 @@
output = json.loads(report.json_output)
- self.assertEqual(output["sup_details"]["osup_det"]["iamt"], 36),
- self.assertEqual(output["sup_details"]["osup_zero"]["iamt"], 18),
+ self.assertEqual(output["sup_details"]["osup_det"]["iamt"], 54)
self.assertEqual(output["inter_sup"]["unreg_details"][0]["iamt"], 18),
self.assertEqual(output["sup_details"]["osup_nil_exmp"]["txval"], 100),
self.assertEqual(output["inward_sup"]["isup_details"][0]["intra"], 250)
diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json
index c1680c4..afdd54b 100644
--- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json
+++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"creation": "2019-10-15 12:33:21.845329",
"doctype": "DocType",
"editable_grid": 1,
@@ -86,12 +87,14 @@
"reqd": 1
},
{
+ "depends_on": "eval:!doc.__islocal",
"fieldname": "upload_xml_invoices_section",
"fieldtype": "Section Break",
"label": "Upload XML Invoices"
}
],
- "modified": "2020-05-25 21:32:49.064579",
+ "links": [],
+ "modified": "2021-04-24 10:33:12.250687",
"modified_by": "Administrator",
"module": "Regional",
"name": "Import Supplier Invoice",
diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py
index 31a7545..0030053 100644
--- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py
+++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py
@@ -28,14 +28,19 @@
self.name = "Import Invoice on " + format_datetime(self.creation)
def import_xml_data(self):
- import_file = frappe.get_doc("File", {"file_url": self.zip_file})
+ zip_file = frappe.get_doc("File", {
+ "file_url": self.zip_file,
+ "attached_to_doctype": self.doctype,
+ "attached_to_name": self.name
+ })
+
self.publish("File Import", _("Processing XML Files"), 1, 3)
self.file_count = 0
self.purchase_invoices_count = 0
self.default_uom = frappe.db.get_value("Stock Settings", fieldname="stock_uom")
- with zipfile.ZipFile(get_full_path(self.zip_file)) as zf:
+ with zipfile.ZipFile(zip_file.get_full_path()) as zf:
for file_name in zf.namelist():
content = get_file_content(file_name, zf)
file_content = bs(content, "xml")
@@ -124,9 +129,9 @@
if disc_line.find("Percentuale"):
invoices_args["total_discount"] += flt((flt(disc_line.Percentuale.text) / 100) * (rate * qty))
+ @frappe.whitelist()
def process_file_data(self):
- self.status = "Processing File Data"
- self.save()
+ self.db_set("status", "Processing File Data", notify=True, commit=True)
frappe.enqueue_doc(self.doctype, self.name, "import_xml_data", queue="long", timeout=3600)
def publish(self, title, message, count, total):
@@ -380,24 +385,3 @@
new_uom.uom_name = uom
new_uom.save()
return new_uom.uom_name
-
-def get_full_path(file_name):
- """Returns file path from given file name"""
- file_path = file_name
-
- if "/" not in file_path:
- file_path = "/files/" + file_path
-
- if file_path.startswith("/private/files/"):
- file_path = get_files_path(*file_path.split("/private/files/", 1)[1].split("/"), is_private=1)
-
- elif file_path.startswith("/files/"):
- file_path = get_files_path(*file_path.split("/files/", 1)[1].split("/"))
-
- elif file_path.startswith("http"):
- pass
-
- elif not self.file_url:
- frappe.throw(_("There is some problem with the file url: {0}").format(file_path))
-
- return file_path
\ No newline at end of file
diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py
index 346ebbf..c478b0f 100644
--- a/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py
+++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py
@@ -49,11 +49,11 @@
certificate.insert()
# check company details
- self.assertEquals(certificate.company_pan_number, 'BBBTI3374C')
- self.assertEquals(certificate.company_80g_number, 'NQ.CIT(E)I2018-19/DEL-IE28615-27062018/10087')
+ self.assertEqual(certificate.company_pan_number, 'BBBTI3374C')
+ self.assertEqual(certificate.company_80g_number, 'NQ.CIT(E)I2018-19/DEL-IE28615-27062018/10087')
# check donation details
- self.assertEquals(certificate.amount, donation.amount)
+ self.assertEqual(certificate.amount, donation.amount)
duplicate_certificate = create_80g_certificate(args)
# duplicate validation
@@ -83,9 +83,9 @@
certificate.get_payments()
certificate.insert()
- self.assertEquals(len(certificate.payments), 1)
- self.assertEquals(certificate.payments[0].amount, membership.amount)
- self.assertEquals(certificate.payments[0].invoice_id, invoice.name)
+ self.assertEqual(len(certificate.payments), 1)
+ self.assertEqual(certificate.payments[0].amount, membership.amount)
+ self.assertEqual(certificate.payments[0].invoice_id, invoice.name)
def create_80g_certificate(args):
diff --git a/erpnext/regional/germany/setup.py b/erpnext/regional/germany/setup.py
index d6047e8..c1fa6e4 100644
--- a/erpnext/regional/germany/setup.py
+++ b/erpnext/regional/germany/setup.py
@@ -1,6 +1,32 @@
import os
import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def setup(company=None, patch=True):
- pass
+ make_custom_fields()
+ add_custom_roles_for_reports()
+
+
+def make_custom_fields():
+ custom_fields = {
+ 'Party Account': [
+ dict(fieldname='debtor_creditor_number', label='Debtor/Creditor Number',
+ fieldtype='Data', insert_after='account', translatable=0)
+ ]
+ }
+
+ create_custom_fields(custom_fields)
+
+
+def add_custom_roles_for_reports():
+ """Add Access Control to UAE VAT 201."""
+ if not frappe.db.get_value('Custom Role', dict(report='DATEV')):
+ frappe.get_doc(dict(
+ doctype='Custom Role',
+ report='DATEV',
+ roles= [
+ dict(role='Accounts User'),
+ dict(role='Accounts Manager')
+ ]
+ )).insert()
diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py
index f138a80..122c15f 100644
--- a/erpnext/regional/germany/utils/datev/datev_csv.py
+++ b/erpnext/regional/germany/utils/datev/datev_csv.py
@@ -55,11 +55,10 @@
quoting=QUOTE_NONNUMERIC
)
- if not six.PY2:
- data = data.encode('latin_1')
+ data = data.encode('latin_1', errors='replace')
header = get_header(filters, csv_class)
- header = ';'.join(header).encode('latin_1')
+ header = ';'.join(header).encode('latin_1', errors='replace')
# 1st Row: Header with meta data
# 2nd Row: Data heading (Überschrift der Nutzdaten), included in `data` here.
diff --git a/erpnext/regional/india/__init__.py b/erpnext/regional/india/__init__.py
index 378b735..faeb36f 100644
--- a/erpnext/regional/india/__init__.py
+++ b/erpnext/regional/india/__init__.py
@@ -69,7 +69,7 @@
"Mizoram": "15",
"Nagaland": "13",
"Odisha": "21",
- "Other Territory": "98",
+ "Other Territory": "97",
"Pondicherry": "34",
"Punjab": "03",
"Rajasthan": "08",
diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js
index 7cd64f2..23d4fe9 100644
--- a/erpnext/regional/india/e_invoice/einvoice.js
+++ b/erpnext/regional/india/e_invoice/einvoice.js
@@ -1,12 +1,13 @@
erpnext.setup_einvoice_actions = (doctype) => {
frappe.ui.form.on(doctype, {
async refresh(frm) {
- const einvoicing_enabled = await frappe.db.get_single_value("E Invoice Settings", "enable");
- const supply_type = frm.doc.gst_category;
- const valid_supply_type = ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'].includes(supply_type);
- const company_transaction = frm.doc.billing_address_gstin == frm.doc.company_gstin;
+ const res = await frappe.call({
+ method: 'erpnext.regional.india.e_invoice.utils.validate_eligibility',
+ args: { doc: frm.doc }
+ });
+ const invoice_eligible = res.message;
- if (cint(einvoicing_enabled) == 0 || !valid_supply_type || company_transaction) return;
+ if (!invoice_eligible) return;
const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc;
@@ -45,7 +46,7 @@
"default": "1-Duplicate",
"options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
},
- {
+ {
"label": "Remark",
"fieldname": "remark",
"fieldtype": "Data",
@@ -60,7 +61,7 @@
const data = d.get_values();
frappe.call({
method: 'erpnext.regional.india.e_invoice.utils.cancel_irn',
- args: {
+ args: {
doctype,
docname: name,
irn: irn,
@@ -109,45 +110,27 @@
}
if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) {
- const fields = [
- {
- "label": "Reason",
- "fieldname": "reason",
- "fieldtype": "Select",
- "reqd": 1,
- "default": "1-Duplicate",
- "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
- },
- {
- "label": "Remark",
- "fieldname": "remark",
- "fieldtype": "Data",
- "reqd": 1
- }
- ];
const action = () => {
- const d = new frappe.ui.Dialog({
- title: __('Cancel E-Way Bill'),
- fields: fields,
- primary_action: function() {
- const data = d.get_values();
- frappe.call({
- method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill',
- args: {
- doctype,
- docname: name,
- eway_bill: ewaybill,
- reason: data.reason.split('-')[0],
- remark: data.remark
- },
- freeze: true,
- callback: () => frm.reload_doc() || d.hide(),
- error: () => d.hide()
- });
+ let message = __('Cancellation of e-way bill is currently not supported. ');
+ message += '<br><br>';
+ message += __('You must first use the portal to cancel the e-way bill and then update the cancelled status in the ERPNext system.');
+
+ const dialog = frappe.msgprint({
+ title: __('Update E-Way Bill Cancelled Status?'),
+ message: message,
+ indicator: 'orange',
+ primary_action: {
+ action: function() {
+ frappe.call({
+ method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill',
+ args: { doctype, docname: name },
+ freeze: true,
+ callback: () => frm.reload_doc() || dialog.hide()
+ });
+ }
},
- primary_action_label: __('Submit')
+ primary_action_label: __('Yes')
});
- d.show();
};
add_custom_button(__("Cancel E-Way Bill"), action);
}
@@ -254,7 +237,7 @@
title: __("Preview"),
size: "large",
fields: [
- {
+ {
"label": "Preview",
"fieldname": "preview_html",
"fieldtype": "HTML"
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index 3dd1b36..843fb01 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -15,18 +15,45 @@
import io
from frappe import _, bold
from pyqrcode import create as qrcreate
+from frappe.utils.background_jobs import enqueue
+from frappe.utils.scheduler import is_scheduler_inactive
+from frappe.core.page.background_jobs.background_jobs import get_info
from frappe.integrations.utils import make_post_request, make_get_request
from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply
-from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, get_link_to_form
+from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, get_link_to_form, getdate, time_diff_in_hours
-def validate_einvoice_fields(doc):
- einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable'))
- invalid_doctype = doc.doctype != 'Sales Invoice'
+@frappe.whitelist()
+def validate_eligibility(doc):
+ if isinstance(doc, six.string_types):
+ doc = json.loads(doc)
+
+ invalid_doctype = doc.get('doctype') != 'Sales Invoice'
+ if invalid_doctype:
+ return False
+
+ einvoicing_enabled = cint(frappe.db.get_single_value('E Invoice Settings', 'enable'))
+ if not einvoicing_enabled:
+ return False
+
+ einvoicing_eligible_from = frappe.db.get_single_value('E Invoice Settings', 'applicable_from') or '2021-04-01'
+ if getdate(doc.get('posting_date')) < getdate(einvoicing_eligible_from):
+ return False
+
+ invalid_company = not frappe.db.get_value('E Invoice User', { 'company': doc.get('company') })
invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export']
company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin')
no_taxes_applied = not doc.get('taxes')
+ has_non_gst_item = any(d for d in doc.get('items', []) if d.get('is_non_gst'))
- if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction or no_taxes_applied:
+ if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied or has_non_gst_item:
+ return False
+
+ return True
+
+def validate_einvoice_fields(doc):
+ invoice_eligible = validate_eligibility(doc)
+
+ if not invoice_eligible:
return
if doc.docstatus == 0 and doc._action == 'save':
@@ -35,6 +62,8 @@
if len(doc.name) > 16:
raise_document_name_too_long_error()
+ doc.einvoice_status = 'Pending'
+
elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn:
frappe.throw(_('You must generate IRN before submitting the document.'), title=_('Missing IRN'))
@@ -43,13 +72,14 @@
def raise_document_name_too_long_error():
title = _('Document ID Too Long')
- msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice, ')
- msg += _('document id {} exceed 16 letters. ').format(bold(_('should not')))
+ msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice')
+ msg += ', '
+ msg += _('document id {} exceed 16 letters.').format(bold(_('should not')))
msg += '<br><br>'
- msg += _('You must {} your {} in order to have document id of {} length 16. ').format(
+ msg += _('You must {} your {} in order to have document id of {} length 16.').format(
bold(_('modify')), bold(_('naming series')), bold(_('maximum'))
)
- msg += _('Please account for ammended documents too. ')
+ msg += _('Please account for ammended documents too.')
frappe.throw(msg, title=title)
def read_json(name):
@@ -76,6 +106,9 @@
))
def get_doc_details(invoice):
+ if getdate(invoice.posting_date) < getdate('2021-01-01'):
+ frappe.throw(_('IRN generation is not allowed for invoices dated before 1st Jan 2021'), title=_('Not Allowed'))
+
invoice_type = 'CRN' if invoice.is_return else 'INV'
invoice_name = invoice.name
@@ -87,56 +120,39 @@
invoice_date=invoice_date
))
-def get_party_details(address_name, company_address=None, billing_address=None, shipping_address=None):
- d = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0]
-
- if ((not d.gstin and not shipping_address)
- or not d.city
- or not d.pincode
- or not d.address_title
- or not d.address_line1
- or not d.gst_state_number):
+def validate_address_fields(address, is_shipping_address):
+ if ((not address.gstin and not is_shipping_address)
+ or not address.city
+ or not address.pincode
+ or not address.address_title
+ or not address.address_line1
+ or not address.gst_state_number):
frappe.throw(
- msg=_('Address lines, city, pincode, gstin is mandatory for address {}. Please set them and try again.').format(
- get_link_to_form('Address', address_name)
- ),
+ msg=_('Address Lines, City, Pincode, GSTIN are mandatory for address {}. Please set them and try again.').format(address.name),
title=_('Missing Address Fields')
)
- if d.gst_state_number == 97:
+def get_party_details(address_name, is_shipping_address=False):
+ addr = frappe.get_doc('Address', address_name)
+
+ validate_address_fields(addr, is_shipping_address)
+
+ if addr.gst_state_number == 97:
# according to einvoice standard
- pincode = 999999
+ addr.pincode = 999999
party_address_details = frappe._dict(dict(
- legal_name=sanitize_for_json(d.address_title),
- location=sanitize_for_json(d.city),
- pincode=d.pincode,
- state_code=d.gst_state_number,
- address_line1=sanitize_for_json(d.address_line1),
- address_line2=sanitize_for_json(d.address_line2)
+ legal_name=sanitize_for_json(addr.address_title),
+ location=sanitize_for_json(addr.city),
+ pincode=addr.pincode, gstin=addr.gstin,
+ state_code=addr.gst_state_number,
+ address_line1=sanitize_for_json(addr.address_line1),
+ address_line2=sanitize_for_json(addr.address_line2)
))
- if d.gstin:
- party_address_details.gstin = d.gstin
+
return party_address_details
-def get_gstin_details(gstin):
- if not hasattr(frappe.local, 'gstin_cache'):
- frappe.local.gstin_cache = {}
-
- key = gstin
- details = frappe.local.gstin_cache.get(key)
- if details:
- return details
-
- details = frappe.cache().hget('gstin_cache', key)
- if details:
- frappe.local.gstin_cache[key] = details
- return details
-
- if not details:
- return GSPConnector.get_gstin_details(gstin)
-
def get_overseas_address_details(address_name):
address_title, address_line1, address_line2, city = frappe.db.get_value(
'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city']
@@ -171,10 +187,15 @@
item.description = sanitize_for_json(d.item_name)
item.qty = abs(item.qty)
- item.discount_amount = 0
- item.unit_rate = abs(item.base_net_amount / item.qty)
- item.gross_amount = abs(item.base_net_amount)
- item.taxable_value = abs(item.base_net_amount)
+
+ if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount:
+ item.discount_amount = abs(item.base_amount - item.base_net_amount)
+ else:
+ item.discount_amount = 0
+
+ item.unit_rate = abs((abs(item.taxable_value) - item.discount_amount)/ item.qty)
+ item.gross_amount = abs(item.taxable_value) + item.discount_amount
+ item.taxable_value = abs(item.taxable_value)
item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
@@ -207,11 +228,11 @@
is_applicable = t.tax_amount and t.account_head in gst_accounts_list
if is_applicable:
# this contains item wise tax rate & tax amount (incl. discount)
- item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code)
+ item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code or item.item_name)
item_tax_rate = item_tax_detail[0]
# item tax amount excluding discount amount
- item_tax_amount = (item_tax_rate / 100) * item.base_net_amount
+ item_tax_amount = (item_tax_rate / 100) * item.taxable_value
if t.account_head in gst_accounts.cess_account:
item_tax_amount_after_discount = item_tax_detail[1]
@@ -225,6 +246,9 @@
if t.account_head in gst_accounts[f'{tax_type}_account']:
item.tax_rate += item_tax_rate
item[f'{tax_type}_amount'] += abs(item_tax_amount)
+ else:
+ # TODO: other charges per item
+ pass
return item
@@ -232,10 +256,14 @@
invoice_value_details = frappe._dict(dict())
if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount:
- invoice_value_details.base_total = abs(invoice.base_total)
- invoice_value_details.invoice_discount_amt = abs(invoice.base_discount_amount)
+ # Discount already applied on net total which means on items
+ invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')]))
+ invoice_value_details.invoice_discount_amt = 0
+ elif invoice.apply_discount_on == 'Grand Total' and invoice.discount_amount:
+ invoice_value_details.invoice_discount_amt = invoice.base_discount_amount
+ invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')]))
else:
- invoice_value_details.base_total = abs(invoice.base_net_total)
+ invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')]))
# since tax already considers discount amount
invoice_value_details.invoice_discount_amt = 0
@@ -256,7 +284,11 @@
invoice_value_details.total_igst_amt = 0
invoice_value_details.total_cess_amt = 0
invoice_value_details.total_other_charges = 0
+ considered_rows = []
+
for t in invoice.taxes:
+ tax_amount = t.base_tax_amount if (invoice.apply_discount_on == 'Grand Total' and invoice.discount_amount) \
+ else t.base_tax_amount_after_discount_amount
if t.account_head in gst_accounts_list:
if t.account_head in gst_accounts.cess_account:
# using after discount amt since item also uses after discount amt for cess calc
@@ -264,12 +296,26 @@
for tax_type in ['igst', 'cgst', 'sgst']:
if t.account_head in gst_accounts[f'{tax_type}_account']:
- invoice_value_details[f'total_{tax_type}_amt'] += abs(t.base_tax_amount_after_discount_amount)
+
+ invoice_value_details[f'total_{tax_type}_amt'] += abs(tax_amount)
+ update_other_charges(t, invoice_value_details, gst_accounts_list, invoice, considered_rows)
else:
- invoice_value_details.total_other_charges += abs(t.base_tax_amount_after_discount_amount)
+ invoice_value_details.total_other_charges += abs(tax_amount)
return invoice_value_details
+def update_other_charges(tax_row, invoice_value_details, gst_accounts_list, invoice, considered_rows):
+ prev_row_id = cint(tax_row.row_id) - 1
+ if tax_row.account_head in gst_accounts_list and prev_row_id not in considered_rows:
+ if tax_row.charge_type == 'On Previous Row Amount':
+ amount = invoice.get('taxes')[prev_row_id].tax_amount_after_discount_amount
+ invoice_value_details.total_other_charges -= abs(amount)
+ considered_rows.append(prev_row_id)
+ if tax_row.charge_type == 'On Previous Row Total':
+ amount = invoice.get('taxes')[prev_row_id].base_total - invoice.base_net_total
+ invoice_value_details.total_other_charges -= abs(amount)
+ considered_rows.append(prev_row_id)
+
def get_payment_details(invoice):
payee_name = invoice.company
mode_of_payment = ', '.join([d.mode_of_payment for d in invoice.payments])
@@ -282,6 +328,10 @@
))
def get_return_doc_reference(invoice):
+ if not invoice.return_against:
+ frappe.throw(_('For generating IRN, reference to the original invoice is mandatory for a credit note. Please set {} field to generate e-invoice.')
+ .format(frappe.bold('Return Against')), title=_('Missing Field'))
+
invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date')
return frappe._dict(dict(
invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy')
@@ -289,7 +339,9 @@
def get_eway_bill_details(invoice):
if invoice.is_return:
- frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes'), title=_('E Invoice Validation Failed'))
+ frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes. Please clear fields in the Transporter Section of the invoice.'),
+ title=_('Invalid Fields'))
+
mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' }
vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' }
@@ -307,9 +359,15 @@
def validate_mandatory_fields(invoice):
if not invoice.company_address:
- frappe.throw(_('Company Address is mandatory to fetch company GSTIN details.'), title=_('Missing Fields'))
+ frappe.throw(
+ _('Company Address is mandatory to fetch company GSTIN details. Please set Company Address and try again.'),
+ title=_('Missing Fields')
+ )
if not invoice.customer_address:
- frappe.throw(_('Customer Address is mandatory to fetch customer GSTIN details.'), title=_('Missing Fields'))
+ frappe.throw(
+ _('Customer Address is mandatory to fetch customer GSTIN details. Please set Company Address and try again.'),
+ title=_('Missing Fields')
+ )
if not frappe.db.get_value('Address', invoice.company_address, 'gstin'):
frappe.throw(
_('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'),
@@ -321,6 +379,39 @@
title=_('Missing Fields')
)
+def validate_totals(einvoice):
+ item_list = einvoice['ItemList']
+ value_details = einvoice['ValDtls']
+
+ 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 item_list:
+ total_item_ass_value += flt(item['AssAmt'])
+ total_item_cgst_value += flt(item['CgstAmt'])
+ total_item_sgst_value += flt(item['SgstAmt'])
+ total_item_igst_value += flt(item['IgstAmt'])
+ total_item_value += flt(item['TotItemVal'])
+
+ if abs(flt(item['AssAmt']) * flt(item['GstRt']) / 100) - (flt(item['CgstAmt']) + flt(item['SgstAmt']) + flt(item['IgstAmt'])) > 1:
+ frappe.throw(_('Row #{}: GST rate is invalid. Please remove tax rows with zero tax amount from taxes table.').format(item.idx))
+
+ if abs(flt(value_details['AssVal']) - total_item_ass_value) > 1:
+ frappe.throw(_('Total Taxable Value of the items is not equal to the Invoice Net Total. Please check item taxes / discounts for any correction.'))
+
+ if abs(flt(value_details['TotInvVal']) + flt(value_details['Discount']) - flt(value_details['OthChrg']) - total_item_value) > 1:
+ frappe.throw(_('Total Value of the items is not equal to the Invoice Grand Total. Please check item taxes / discounts for any correction.'))
+
+ calculated_invoice_value = \
+ flt(value_details['AssVal']) + flt(value_details['CgstVal']) \
+ + flt(value_details['SgstVal']) + flt(value_details['IgstVal']) \
+ + flt(value_details['OthChrg']) - flt(value_details['Discount'])
+
+ if abs(flt(value_details['TotInvVal']) - calculated_invoice_value) > 1:
+ frappe.throw(_('Total Item Value + Taxes - Discount is not equal to the Invoice Grand Total. Please check taxes / discounts for any correction.'))
+
def make_einvoice(invoice):
validate_mandatory_fields(invoice)
@@ -330,12 +421,12 @@
item_list = get_item_list(invoice)
doc_details = get_doc_details(invoice)
invoice_value_details = get_invoice_value_details(invoice)
- seller_details = get_party_details(invoice.company_address, company_address=1)
+ seller_details = get_party_details(invoice.company_address)
if invoice.gst_category == 'Overseas':
buyer_details = get_overseas_address_details(invoice.customer_address)
else:
- buyer_details = get_party_details(invoice.customer_address, billing_address=1)
+ buyer_details = get_party_details(invoice.customer_address)
place_of_supply = get_place_of_supply(invoice, invoice.doctype)
if place_of_supply:
place_of_supply = place_of_supply.split('-')[0]
@@ -343,20 +434,23 @@
place_of_supply = sanitize_for_json(invoice.billing_address_gstin)[:2]
buyer_details.update(dict(place_of_supply=place_of_supply))
+ seller_details.update(dict(legal_name=invoice.company))
+ buyer_details.update(dict(legal_name=invoice.customer_name or invoice.customer))
+
shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({})
if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name:
if invoice.gst_category == 'Overseas':
shipping_details = get_overseas_address_details(invoice.shipping_address_name)
else:
- shipping_details = get_party_details(invoice.shipping_address_name, shipping_address=1)
+ shipping_details = get_party_details(invoice.shipping_address_name, is_shipping_address=True)
if invoice.is_pos and invoice.base_paid_amount:
payment_details = get_payment_details(invoice)
- if invoice.is_return and invoice.return_against:
+ if invoice.is_return:
prev_doc_details = get_return_doc_reference(invoice)
- if invoice.transporter:
+ if invoice.transporter and not invoice.is_return:
eway_bill_details = get_eway_bill_details(invoice)
# not yet implemented
@@ -369,99 +463,102 @@
period_details=period_details, prev_doc_details=prev_doc_details,
export_details=export_details, eway_bill_details=eway_bill_details
)
- einvoice = safe_json_load(einvoice)
- validations = json.loads(read_json('einv_validation'))
- errors = validate_einvoice(validations, einvoice)
- if errors:
- message = "\n".join([
- "E Invoice: ", json.dumps(einvoice, indent=4),
- "-" * 50,
- "Errors: ", json.dumps(errors, indent=4)
- ])
- frappe.log_error(title="E Invoice Validation Failed", message=message)
- frappe.throw(errors, title=_('E Invoice Validation Failed'), as_list=1)
+ try:
+ einvoice = safe_json_load(einvoice)
+ einvoice = santize_einvoice_fields(einvoice)
+ except Exception:
+ show_link_to_error_log(invoice, einvoice)
+
+ validate_totals(einvoice)
+
+ return einvoice
+
+def show_link_to_error_log(invoice, einvoice):
+ err_log = log_error(einvoice)
+ link_to_error_log = get_link_to_form('Error Log', err_log.name, 'Error Log')
+ frappe.throw(
+ _('An error occurred while creating e-invoice for {}. Please check {} for more information.').format(
+ invoice.name, link_to_error_log),
+ title=_('E Invoice Creation Failed')
+ )
+
+def log_error(data=None):
+ if isinstance(data, six.string_types):
+ data = json.loads(data)
+
+ seperator = "--" * 50
+ err_tb = traceback.format_exc()
+ err_msg = str(sys.exc_info()[1])
+ data = json.dumps(data, indent=4)
+
+ message = "\n".join([
+ "Error", err_msg, seperator,
+ "Data:", data, seperator,
+ "Exception:", err_tb
+ ])
+ frappe.log_error(title=_('E Invoice Request Failed'), message=message)
+
+def santize_einvoice_fields(einvoice):
+ int_fields = ["Pin","Distance","CrDay"]
+ float_fields = ["Qty","FreeQty","UnitPrice","TotAmt","Discount","PreTaxVal","AssAmt","GstRt","IgstAmt","CgstAmt","SgstAmt","CesRt","CesAmt","CesNonAdvlAmt","StateCesRt","StateCesAmt","StateCesNonAdvlAmt","OthChrg","TotItemVal","AssVal","CgstVal","SgstVal","IgstVal","CesVal","StCesVal","Discount","OthChrg","RndOffAmt","TotInvVal","TotInvValFc","PaidAmt","PaymtDue","ExpDuty",]
+ copy = einvoice.copy()
+ for key, value in copy.items():
+ if isinstance(value, list):
+ for idx, d in enumerate(value):
+ santized_dict = santize_einvoice_fields(d)
+ if santized_dict:
+ einvoice[key][idx] = santized_dict
+ else:
+ einvoice[key].pop(idx)
+
+ if not einvoice[key]:
+ einvoice.pop(key, None)
+
+ elif isinstance(value, dict):
+ santized_dict = santize_einvoice_fields(value)
+ if santized_dict:
+ einvoice[key] = santized_dict
+ else:
+ einvoice.pop(key, None)
+
+ elif not value or value == "None":
+ einvoice.pop(key, None)
+
+ elif key in float_fields:
+ einvoice[key] = flt(value, 2)
+
+ elif key in int_fields:
+ einvoice[key] = cint(value)
return einvoice
def safe_json_load(json_string):
- JSONDecodeError = ValueError if six.PY2 else json.JSONDecodeError
-
try:
return json.loads(json_string)
- except JSONDecodeError as e:
+ except json.JSONDecodeError as e:
# print a snippet of 40 characters around the location where error occured
pos = e.pos
start, end = max(0, pos-20), min(len(json_string)-1, pos+20)
snippet = json_string[start:end]
frappe.throw(_("Error in input data. Please check for any special characters near following input: <br> {}").format(snippet))
-def validate_einvoice(validations, einvoice, errors=None):
- if errors is None:
- errors = []
- for fieldname, field_validation in validations.items():
- value = einvoice.get(fieldname, None)
- if not value or value == "None":
- # remove keys with empty values
- einvoice.pop(fieldname, None)
- continue
-
- value_type = field_validation.get("type").lower()
- if value_type in ['object', 'array']:
- child_validations = field_validation.get('properties')
-
- if isinstance(value, list):
- for d in value:
- validate_einvoice(child_validations, d, errors)
- if not d:
- # remove empty dicts
- einvoice.pop(fieldname, None)
- else:
- validate_einvoice(child_validations, value, errors)
- if not value:
- # remove empty dicts
- einvoice.pop(fieldname, None)
- continue
-
- # convert to int or str
- if value_type == 'string':
- einvoice[fieldname] = str(value)
- elif value_type == 'number':
- is_integer = '.' not in str(field_validation.get('maximum'))
- precision = 3 if '.999' in str(field_validation.get('maximum')) else 2
- einvoice[fieldname] = flt(value, precision) if not is_integer else cint(value)
- value = einvoice[fieldname]
-
- max_length = field_validation.get('maxLength')
- minimum = flt(field_validation.get('minimum'))
- maximum = flt(field_validation.get('maximum'))
- pattern_str = field_validation.get('pattern')
- pattern = re.compile(pattern_str or '')
-
- label = field_validation.get('description') or fieldname
-
- if value_type == 'string' and len(value) > max_length:
- errors.append(_('{} should not exceed {} characters').format(label, max_length))
- if value_type == 'number' and (value > maximum or value < minimum):
- errors.append(_('{} {} should be between {} and {}').format(label, value, minimum, maximum))
- if pattern_str and not pattern.match(value):
- errors.append(field_validation.get('validationMsg'))
-
- return errors
-
-class RequestFailed(Exception): pass
+class RequestFailed(Exception):
+ pass
+class CancellationNotAllowed(Exception):
+ pass
class GSPConnector():
def __init__(self, doctype=None, docname=None):
- self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings')
- sandbox_mode = self.e_invoice_settings.sandbox_mode
+ self.doctype = doctype
+ self.docname = docname
- self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None
- self.credentials = self.get_credentials()
+ self.set_invoice()
+ self.set_credentials()
# authenticate url is same for sandbox & live
self.authenticate_url = 'https://gsp.adaequare.com/gsp/authenticate?grant_type=token'
- self.base_url = 'https://gsp.adaequare.com' if not sandbox_mode else 'https://gsp.adaequare.com/test'
+ self.base_url = 'https://gsp.adaequare.com' if not self.e_invoice_settings.sandbox_mode else 'https://gsp.adaequare.com/test'
self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel'
self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn'
@@ -470,18 +567,29 @@
self.cancel_ewaybill_url = self.base_url + '/enriched/ewb/ewayapi?action=CANEWB'
self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill'
- def get_credentials(self):
+ def set_invoice(self):
+ self.invoice = None
+ if self.doctype and self.docname:
+ self.invoice = frappe.get_cached_doc(self.doctype, self.docname)
+
+ def set_credentials(self):
+ self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings')
+
+ if not self.e_invoice_settings.enable:
+ frappe.throw(_("E-Invoicing is disabled. Please enable it from {} to generate e-invoices.").format(get_link_to_form("E Invoice Settings", "E Invoice Settings")))
+
if self.invoice:
gstin = self.get_seller_gstin()
- if not self.e_invoice_settings.enable:
- frappe.throw(_("E-Invoicing is disabled. Please enable it from {} to generate e-invoices.").format(get_link_to_form("E Invoice Settings", "E Invoice Settings")))
- credentials = next(d for d in self.e_invoice_settings.credentials if d.gstin == gstin)
+ credentials_for_gstin = [d for d in self.e_invoice_settings.credentials if d.gstin == gstin]
+ if credentials_for_gstin:
+ self.credentials = credentials_for_gstin[0]
+ else:
+ frappe.throw(_('Cannot find e-invoicing credentials for selected Company GSTIN. Please check E-Invoice Settings'))
else:
- credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None
- return credentials
+ self.credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None
def get_seller_gstin(self):
- gstin = self.invoice.company_gstin or frappe.db.get_value('Address', self.invoice.company_address, 'gstin')
+ gstin = frappe.db.get_value('Address', self.invoice.company_address, 'gstin')
if not gstin:
frappe.throw(_('Cannot retrieve Company GSTIN. Please select company address with valid GSTIN.'))
return gstin
@@ -529,7 +637,7 @@
self.e_invoice_settings.reload()
except Exception:
- self.log_error(res)
+ log_error(res)
self.raise_error(True)
def get_headers(self):
@@ -551,16 +659,15 @@
if res.get('success'):
return res.get('result')
else:
- self.log_error(res)
+ log_error(res)
raise RequestFailed
except RequestFailed:
self.raise_error()
except Exception:
- self.log_error()
+ log_error()
self.raise_error(True)
-
@staticmethod
def get_gstin_details(gstin):
'''fetch and cache GSTIN details'''
@@ -576,12 +683,13 @@
return details
def generate_irn(self):
- headers = self.get_headers()
- einvoice = make_einvoice(self.invoice)
- data = json.dumps(einvoice, indent=4)
-
+ data = {}
try:
+ headers = self.get_headers()
+ einvoice = make_einvoice(self.invoice)
+ data = json.dumps(einvoice, indent=4)
res = self.make_request('post', self.generate_irn_url, headers, data)
+
if res.get('success'):
self.set_einvoice_data(res.get('result'))
@@ -601,12 +709,36 @@
except RequestFailed:
errors = self.sanitize_error_message(res.get('message'))
+ self.set_failed_status(errors=errors)
self.raise_error(errors=errors)
- except Exception:
- self.log_error(data)
+ except Exception as e:
+ self.set_failed_status(errors=str(e))
+ log_error(data)
self.raise_error(True)
+ @staticmethod
+ def bulk_generate_irn(invoices):
+ gsp_connector = GSPConnector()
+ gsp_connector.doctype = 'Sales Invoice'
+
+ failed = []
+
+ for invoice in invoices:
+ try:
+ gsp_connector.docname = invoice
+ gsp_connector.set_invoice()
+ gsp_connector.set_credentials()
+ gsp_connector.generate_irn()
+
+ except Exception as e:
+ failed.append({
+ 'docname': invoice,
+ 'message': str(e)
+ })
+
+ return failed
+
def get_irn_details(self, irn):
headers = self.get_headers()
@@ -623,21 +755,30 @@
self.raise_error(errors=errors)
except Exception:
- self.log_error()
+ log_error()
self.raise_error(True)
def cancel_irn(self, irn, reason, remark):
- headers = self.get_headers()
- data = json.dumps({
- 'Irn': irn,
- 'Cnlrsn': reason,
- 'Cnlrem': remark
- }, indent=4)
-
+ data, res = {}, {}
try:
+ # validate cancellation
+ if time_diff_in_hours(now_datetime(), self.invoice.ack_date) > 24:
+ frappe.throw(_('E-Invoice cannot be cancelled after 24 hours of IRN generation.'), title=_('Not Allowed'), exc=CancellationNotAllowed)
+ if not irn:
+ frappe.throw(_('IRN not found. You must generate IRN before cancelling.'), title=_('Not Allowed'), exc=CancellationNotAllowed)
+
+ headers = self.get_headers()
+ data = json.dumps({
+ 'Irn': irn,
+ 'Cnlrsn': reason,
+ 'Cnlrem': remark
+ }, indent=4)
+
res = self.make_request('post', self.cancel_irn_url, headers, data)
- if res.get('success'):
+ if res.get('success') or '9999' in res.get('message'):
self.invoice.irn_cancelled = 1
+ self.invoice.irn_cancel_date = res.get('result')['CancelDate'] if res.get('result') else ""
+ self.invoice.einvoice_status = 'Cancelled'
self.invoice.flags.updater_reference = {
'doctype': self.invoice.doctype,
'docname': self.invoice.name,
@@ -650,12 +791,41 @@
except RequestFailed:
errors = self.sanitize_error_message(res.get('message'))
+ self.set_failed_status(errors=errors)
self.raise_error(errors=errors)
- except Exception:
- self.log_error(data)
+ except CancellationNotAllowed as e:
+ self.set_failed_status(errors=str(e))
+ self.raise_error(errors=str(e))
+
+ except Exception as e:
+ self.set_failed_status(errors=str(e))
+ log_error(data)
self.raise_error(True)
+ @staticmethod
+ def bulk_cancel_irn(invoices, reason, remark):
+ gsp_connector = GSPConnector()
+ gsp_connector.doctype = 'Sales Invoice'
+
+ failed = []
+
+ for invoice in invoices:
+ try:
+ gsp_connector.docname = invoice
+ gsp_connector.set_invoice()
+ gsp_connector.set_credentials()
+ irn = gsp_connector.invoice.irn
+ gsp_connector.cancel_irn(irn, reason, remark)
+
+ except Exception as e:
+ failed.append({
+ 'docname': invoice,
+ 'message': str(e)
+ })
+
+ return failed
+
def generate_eway_bill(self, **kwargs):
args = frappe._dict(kwargs)
@@ -677,6 +847,7 @@
res = self.make_request('post', self.generate_ewaybill_url, headers, data)
if res.get('success'):
self.invoice.ewaybill = res.get('result').get('EwbNo')
+ self.invoice.eway_bill_validity = res.get('result').get('EwbValidTill')
self.invoice.eway_bill_cancelled = 0
self.invoice.update(args)
self.invoice.flags.updater_reference = {
@@ -694,7 +865,7 @@
self.raise_error(errors=errors)
except Exception:
- self.log_error(data)
+ log_error(data)
self.raise_error(True)
def cancel_eway_bill(self, eway_bill, reason, remark):
@@ -726,7 +897,7 @@
self.raise_error(errors=errors)
except Exception:
- self.log_error(data)
+ log_error(data)
self.raise_error(True)
def sanitize_error_message(self, message):
@@ -741,6 +912,9 @@
]
then we trim down the message by looping over errors
'''
+ if not message:
+ return []
+
errors = re.findall(': [^:]+', message)
for idx, e in enumerate(errors):
# remove colons
@@ -752,22 +926,6 @@
return errors
- def log_error(self, data={}):
- if not isinstance(data, dict):
- data = json.loads(data)
-
- seperator = "--" * 50
- err_tb = traceback.format_exc()
- err_msg = str(sys.exc_info()[1])
- data = json.dumps(data, indent=4)
-
- message = "\n".join([
- "Error", err_msg, seperator,
- "Data:", data, seperator,
- "Exception:", err_tb
- ])
- frappe.log_error(title=_('E Invoice Request Failed'), message=message)
-
def raise_error(self, raise_exception=False, errors=[]):
title = _('E Invoice Request Failed')
if errors:
@@ -787,10 +945,14 @@
self.invoice.irn = res.get('Irn')
self.invoice.ewaybill = res.get('EwbNo')
+ self.invoice.eway_bill_validity = res.get('EwbValidTill')
self.invoice.ack_no = res.get('AckNo')
self.invoice.ack_date = res.get('AckDt')
self.invoice.signed_einvoice = dec_signed_invoice
+ self.invoice.ack_no = res.get('AckNo')
+ self.invoice.ack_date = res.get('AckDt')
self.invoice.signed_qr_code = res.get('SignedQRCode')
+ self.invoice.einvoice_status = 'Generated'
self.attach_qrcode_image()
@@ -827,6 +989,17 @@
self.invoice.flags.ignore_validate = True
self.invoice.save()
+ def set_failed_status(self, errors=None):
+ frappe.db.rollback()
+ self.invoice.einvoice_status = 'Failed'
+ self.invoice.failure_description = self.get_failure_message(errors) if errors else ""
+ self.update_invoice()
+ frappe.db.commit()
+
+ def get_failure_message(self, errors):
+ if isinstance(errors, list):
+ errors = ', '.join(errors)
+ return errors
def sanitize_for_json(string):
"""Escape JSON specific characters from a string."""
@@ -855,6 +1028,115 @@
gsp_connector.generate_eway_bill(**kwargs)
@frappe.whitelist()
-def cancel_eway_bill(doctype, docname, eway_bill, reason, remark):
- gsp_connector = GSPConnector(doctype, docname)
- gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
+def cancel_eway_bill(doctype, docname):
+ # TODO: uncomment when eway_bill api from Adequare is enabled
+ # gsp_connector = GSPConnector(doctype, docname)
+ # gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
+
+ frappe.db.set_value(doctype, docname, 'ewaybill', '')
+ frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1)
+
+@frappe.whitelist()
+def generate_einvoices(docnames):
+ docnames = json.loads(docnames) or []
+
+ if len(docnames) < 10:
+ failures = GSPConnector.bulk_generate_irn(docnames)
+ frappe.local.message_log = []
+
+ if failures:
+ show_bulk_action_failure_message(failures)
+
+ success = len(docnames) - len(failures)
+ frappe.msgprint(
+ _('{} e-invoices generated successfully').format(success),
+ title=_('Bulk E-Invoice Generation Complete')
+ )
+
+ else:
+ enqueue_bulk_action(schedule_bulk_generate_irn, docnames=docnames)
+
+def schedule_bulk_generate_irn(docnames):
+ failures = GSPConnector.bulk_generate_irn(docnames)
+ frappe.local.message_log = []
+
+ frappe.publish_realtime("bulk_einvoice_generation_complete", {
+ "user": frappe.session.user,
+ "failures": failures,
+ "invoices": docnames
+ })
+
+def show_bulk_action_failure_message(failures):
+ for doc in failures:
+ docname = '<a href="sales-invoice/{0}">{0}</a>'.format(doc.get('docname'))
+ message = doc.get('message').replace("'", '"')
+ if message[0] == '[':
+ errors = json.loads(message)
+ error_list = ''.join(['<li>{}</li>'.format(err) for err in errors])
+ message = '''{} has following errors:<br>
+ <ul style="padding-left: 20px; padding-top: 5px">{}</ul>'''.format(docname, error_list)
+ else:
+ message = '{} - {}'.format(docname, message)
+
+ frappe.msgprint(
+ message,
+ title=_('Bulk E-Invoice Generation Complete'),
+ indicator='red'
+ )
+
+@frappe.whitelist()
+def cancel_irns(docnames, reason, remark):
+ docnames = json.loads(docnames) or []
+
+ if len(docnames) < 10:
+ failures = GSPConnector.bulk_cancel_irn(docnames, reason, remark)
+ frappe.local.message_log = []
+
+ if failures:
+ show_bulk_action_failure_message(failures)
+
+ success = len(docnames) - len(failures)
+ frappe.msgprint(
+ _('{} e-invoices cancelled successfully').format(success),
+ title=_('Bulk E-Invoice Cancellation Complete')
+ )
+ else:
+ enqueue_bulk_action(schedule_bulk_cancel_irn, docnames=docnames, reason=reason, remark=remark)
+
+def schedule_bulk_cancel_irn(docnames, reason, remark):
+ failures = GSPConnector.bulk_cancel_irn(docnames, reason, remark)
+ frappe.local.message_log = []
+
+ frappe.publish_realtime("bulk_einvoice_cancellation_complete", {
+ "user": frappe.session.user,
+ "failures": failures,
+ "invoices": docnames
+ })
+
+def enqueue_bulk_action(job, **kwargs):
+ check_scheduler_status()
+
+ enqueue(
+ job,
+ **kwargs,
+ queue="long",
+ timeout=10000,
+ event="processing_bulk_einvoice_action",
+ now=frappe.conf.developer_mode or frappe.flags.in_test,
+ )
+
+ if job == schedule_bulk_generate_irn:
+ msg = _('E-Invoices will be generated in a background process.')
+ else:
+ msg = _('E-Invoices will be cancelled in a background process.')
+
+ frappe.msgprint(msg, alert=1)
+
+def check_scheduler_status():
+ if is_scheduler_inactive() and not frappe.flags.in_test:
+ frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive"))
+
+def job_already_enqueued(job_name):
+ enqueued_jobs = [d.get("job_name") for d in get_info()]
+ if job_name in enqueued_jobs:
+ return True
\ No newline at end of file
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index f7689cf..229e0c0 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -12,14 +12,14 @@
from frappe.utils import today
def setup(company=None, patch=True):
- setup_company_independent_fixtures()
+ setup_company_independent_fixtures(patch=patch)
if not patch:
make_fixtures(company)
# TODO: for all countries
-def setup_company_independent_fixtures():
+def setup_company_independent_fixtures(patch=False):
make_custom_fields()
- make_property_setters()
+ make_property_setters(patch=patch)
add_permissions()
add_custom_roles_for_reports()
frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test)
@@ -51,7 +51,7 @@
def add_custom_roles_for_reports():
for report_name in ('GST Sales Register', 'GST Purchase Register',
- 'GST Itemised Sales Register', 'GST Itemised Purchase Register', 'Eway Bill'):
+ 'GST Itemised Sales Register', 'GST Itemised Purchase Register', 'Eway Bill', 'E-Invoice Summary'):
if not frappe.db.get_value('Custom Role', dict(report=report_name)):
frappe.get_doc(dict(
@@ -112,10 +112,14 @@
frappe.db.set_value("Print Format", "GST Tax Invoice", "disabled", 0)
frappe.db.set_value("Print Format", "GST E-Invoice", "disabled", 0)
-def make_property_setters():
+def make_property_setters(patch=False):
# GST rules do not allow for an invoice no. bigger than 16 characters
- make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '')
- make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '')
+ journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + ['Reversal Of ITC']
+
+ if not patch:
+ make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '')
+ make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '')
+ make_property_setter('Journal Entry', 'voucher_type', 'options', '\n'.join(journal_entry_types), '')
def make_custom_fields(update=True):
hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
@@ -127,6 +131,9 @@
is_non_gst = dict(fieldname='is_non_gst', label='Is Non GST',
fieldtype='Check', fetch_from='item_code.is_non_gst', insert_after='is_nil_exempt',
print_hide=1)
+ taxable_value = dict(fieldname='taxable_value', label='Taxable Value',
+ fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency",
+ print_hide=1)
purchase_invoice_gst_category = [
dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break',
@@ -156,6 +163,13 @@
fetch_if_empty=1),
]
+ delivery_note_gst_category = [
+ dict(fieldname='gst_category', label='GST Category',
+ fieldtype='Select', insert_after='gst_vehicle_type', print_hide=1,
+ options='\nRegistered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders',
+ fetch_from='customer.gst_category', fetch_if_empty=1),
+ ]
+
invoice_gst_fields = [
dict(fieldname='invoice_copy', label='Invoice Copy',
fieldtype='Select', insert_after='export_type', print_hide=1, allow_on_submit=1,
@@ -187,15 +201,20 @@
purchase_invoice_itc_fields = [
dict(fieldname='eligibility_for_itc', label='Eligibility For ITC',
fieldtype='Select', insert_after='reason_for_issuing_document', print_hide=1,
- options='Input Service Distributor\nImport Of Service\nImport Of Capital Goods\nIneligible\nAll Other ITC', default="All Other ITC"),
+ options='Input Service Distributor\nImport Of Service\nImport Of Capital Goods\nITC on Reverse Charge\nIneligible As Per Section 17(5)\nIneligible Others\nAll Other ITC',
+ default="All Other ITC"),
dict(fieldname='itc_integrated_tax', label='Availed ITC Integrated Tax',
- fieldtype='Data', insert_after='eligibility_for_itc', print_hide=1),
+ fieldtype='Currency', insert_after='eligibility_for_itc',
+ options='Company:company:default_currency', print_hide=1),
dict(fieldname='itc_central_tax', label='Availed ITC Central Tax',
- fieldtype='Data', insert_after='itc_integrated_tax', print_hide=1),
+ fieldtype='Currency', insert_after='itc_integrated_tax',
+ options='Company:company:default_currency', print_hide=1),
dict(fieldname='itc_state_tax', label='Availed ITC State/UT Tax',
- fieldtype='Data', insert_after='itc_central_tax', print_hide=1),
+ fieldtype='Currency', insert_after='itc_central_tax',
+ options='Company:company:default_currency', print_hide=1),
dict(fieldname='itc_cess_amount', label='Availed ITC Cess',
- fieldtype='Data', insert_after='itc_state_tax', print_hide=1),
+ fieldtype='Currency', insert_after='itc_state_tax',
+ options='Company:company:default_currency', print_hide=1),
]
sales_invoice_gst_fields = [
@@ -225,6 +244,23 @@
depends_on="eval:doc.gst_category=='Overseas' "),
]
+ journal_entry_fields = [
+ dict(fieldname='reversal_type', label='Reversal Type',
+ fieldtype='Select', insert_after='voucher_type', print_hide=1,
+ options="As per rules 42 & 43 of CGST Rules\nOthers",
+ depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
+ mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"),
+ dict(fieldname='company_address', label='Company Address',
+ fieldtype='Link', options='Address', insert_after='reversal_type',
+ print_hide=1, depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
+ mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"),
+ dict(fieldname='company_gstin', label='Company GSTIN',
+ fieldtype='Data', read_only=1, insert_after='company_address', print_hide=1,
+ fetch_from='company_address.gstin',
+ depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
+ mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'")
+ ]
+
inter_state_gst_field = [
dict(fieldname='is_inter_state', label='Is Inter State',
fieldtype='Check', insert_after='disabled', print_hide=1),
@@ -280,7 +316,7 @@
'allow_on_submit': 1,
'insert_after': 'customer_name_in_arabic',
'translatable': 0,
- }
+ }
]
si_ewaybill_fields = [
@@ -408,21 +444,40 @@
dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1,
depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'),
- dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1),
-
- dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
-
dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+ dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1,
+ depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill'),
+
dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
- dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
+ dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type',
+ print_hide=1, hidden=1),
- dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
+ dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section',
+ no_copy=1, print_hide=1),
- dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=1)
+ dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
+
+ dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date',
+ no_copy=1, print_hide=1),
+
+ dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date',
+ no_copy=1, print_hide=1, read_only=1),
+
+ dict(fieldname='signed_qr_code', label='Signed QRCode', fieldtype='Code', options='JSON', hidden=1, insert_after='signed_einvoice',
+ no_copy=1, print_hide=1, read_only=1),
+
+ dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, insert_after='signed_qr_code',
+ no_copy=1, print_hide=1, read_only=1),
+
+ dict(fieldname='einvoice_status', label='E-Invoice Status', fieldtype='Select', insert_after='qrcode_image',
+ options='\nPending\nGenerated\nCancelled\nFailed', default=None, hidden=1, no_copy=1, print_hide=1, read_only=1),
+
+ dict(fieldname='failure_description', label='E-Invoice Failure Description', fieldtype='Code', options='JSON',
+ hidden=1, insert_after='einvoice_status', no_copy=1, print_hide=1, read_only=1)
]
custom_fields = {
@@ -438,7 +493,8 @@
'Purchase Order': purchase_invoice_gst_fields,
'Purchase Receipt': purchase_invoice_gst_fields,
'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields + si_einvoice_fields,
- 'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields,
+ 'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields + delivery_note_gst_category,
+ 'Journal Entry': journal_entry_fields,
'Sales Order': sales_invoice_gst_fields,
'Tax Category': inter_state_gst_field,
'Item': [
@@ -453,10 +509,10 @@
'Supplier Quotation Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
'Sales Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
'Delivery Note Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
- 'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
+ 'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value],
'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
- 'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
+ 'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value],
'Material Request Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
'Salary Component': [
dict(fieldname= 'component_type',
diff --git a/erpnext/regional/india/test_utils.py b/erpnext/regional/india/test_utils.py
index 7ce27f6..a16f56c 100644
--- a/erpnext/regional/india/test_utils.py
+++ b/erpnext/regional/india/test_utils.py
@@ -12,14 +12,14 @@
mock_get_cached.return_value = "India" # mock country
posting_date = "2021-05-01"
- invalid_names = [ "SI$1231", "012345678901234567", "SI 2020 05",
- "SI.2020.0001", "PI2021 - 001" ]
+ invalid_names = ["SI$1231", "012345678901234567", "SI 2020 05",
+ "SI.2020.0001", "PI2021 - 001"]
for name in invalid_names:
doc = frappe._dict(name=name, posting_date=posting_date)
self.assertRaises(frappe.ValidationError, validate_document_name, doc)
- valid_names = [ "012345678901236", "SI/2020/0001", "SI/2020-0001",
- "2020-PI-0001", "PI2020-0001" ]
+ valid_names = ["012345678901236", "SI/2020/0001", "SI/2020-0001",
+ "2020-PI-0001", "PI2020-0001"]
for name in valid_names:
doc = frappe._dict(name=name, posting_date=posting_date)
try:
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index 3637de4..075c698 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -2,7 +2,7 @@
import frappe, re, json
from frappe import _
import erpnext
-from frappe.utils import cstr, flt, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words, getdate
+from frappe.utils import cstr, flt, cint, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words, getdate
from erpnext.regional.india import states, state_numbers
from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount
from erpnext.controllers.accounts_controller import get_taxes_and_charges
@@ -41,24 +41,25 @@
return
if len(doc.gstin) != 15:
- frappe.throw(_("Invalid GSTIN! A GSTIN must have 15 characters."))
+ frappe.throw(_("A GSTIN must have 15 characters."), title=_("Invalid GSTIN"))
if gst_category and gst_category == 'UIN Holders':
if not GSTIN_UIN_FORMAT.match(doc.gstin):
- frappe.throw(_("Invalid GSTIN! The input you've entered doesn't match the GSTIN format for UIN Holders or Non-Resident OIDAR Service Providers"))
+ frappe.throw(_("The input you've entered doesn't match the GSTIN format for UIN Holders or Non-Resident OIDAR Service Providers"),
+ title=_("Invalid GSTIN"))
else:
if not GSTIN_FORMAT.match(doc.gstin):
- frappe.throw(_("Invalid GSTIN! The input you've entered doesn't match the format of GSTIN."))
+ frappe.throw(_("The input you've entered doesn't match the format of GSTIN."), title=_("Invalid GSTIN"))
validate_gstin_check_digit(doc.gstin)
set_gst_state_and_state_number(doc)
if not doc.gst_state:
- frappe.throw(_("Please Enter GST state"))
+ frappe.throw(_("Please enter GST state"), title=_("Invalid State"))
if doc.gst_state_number != doc.gstin[:2]:
- frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.")
- .format(doc.gst_state_number))
+ frappe.throw(_("First 2 digits of GSTIN should match with State number {0}.")
+ .format(doc.gst_state_number), title=_("Invalid GSTIN"))
def validate_pan_for_india(doc, method):
if doc.get('country') != 'India' or not doc.pan:
@@ -154,6 +155,7 @@
def validate_document_name(doc, method=None):
"""Validate GST invoice number requirements."""
+
country = frappe.get_cached_value("Company", doc.company, "country")
# Date was chosen as start of next FY to avoid irritating current users.
@@ -202,8 +204,6 @@
if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
master_doctype = "Sales Taxes and Charges Template"
-
- get_tax_template_for_sez(party_details, master_doctype, company, 'Customer')
get_tax_template_based_on_category(master_doctype, company, party_details)
if party_details.get('taxes_and_charges'):
@@ -214,7 +214,6 @@
elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"):
master_doctype = "Purchase Taxes and Charges Template"
- get_tax_template_for_sez(party_details, master_doctype, company, 'Supplier')
get_tax_template_based_on_category(master_doctype, company, party_details)
if party_details.get('taxes_and_charges'):
@@ -281,20 +280,6 @@
{'company': company, 'disabled': 0, 'tax_category': tax_category.name}, 'name')
return default_tax
-def get_tax_template_for_sez(party_details, master_doctype, company, party_type):
-
- gst_details = frappe.db.get_value(party_type, {'name': party_details.get(frappe.scrub(party_type))},
- ['gst_category', 'export_type'], as_dict=1)
-
- if gst_details:
- if gst_details.gst_category == 'SEZ' and gst_details.export_type == 'With Payment of Tax':
- default_tax = frappe.db.get_value(master_doctype, {"company": company, "is_inter_state":1, "disabled":0,
- "gst_state": number_state_mapping[party_details.company_gstin[:2]]})
-
- party_details["taxes_and_charges"] = default_tax
- party_details.taxes = get_taxes_and_charges(master_doctype, default_tax)
-
-
def calculate_annual_eligible_hra_exemption(doc):
basic_component, hra_component = frappe.db.get_value('Company', doc.company, ["basic_component", "hra_component"])
if not (basic_component and hra_component):
@@ -515,7 +500,7 @@
if not isinstance(docname, list):
# removes characters not allowed in a filename (https://stackoverflow.com/a/38766141/4767738)
- filename_prefix = re.sub('[^\w_.)( -]', '', docname)
+ filename_prefix = re.sub(r'[^\w_.)( -]', '', docname)
frappe.local.response.filename = '{0}_e-WayBill_Data_{1}.json'.format(filename_prefix, frappe.utils.random_string(5))
@@ -695,13 +680,22 @@
return int(state_code)
@frappe.whitelist()
-def get_gst_accounts(company, account_wise=False):
+def get_gst_accounts(company=None, account_wise=False, only_reverse_charge=0, only_non_reverse_charge=0):
+ filters={"parent": "GST Settings"}
+
+ if company:
+ filters.update({'company': company})
+ if only_reverse_charge:
+ filters.update({'is_reverse_charge_account': 1})
+ elif only_non_reverse_charge:
+ filters.update({'is_reverse_charge_account': 0})
+
gst_accounts = frappe._dict()
gst_settings_accounts = frappe.get_all("GST Account",
- filters={"parent": "GST Settings", "company": company},
+ filters=filters,
fields=["cgst_account", "sgst_account", "igst_account", "cess_account"])
- if not gst_settings_accounts and not frappe.flags.in_test:
+ if not gst_settings_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate:
frappe.throw(_("Please set GST Accounts in GST Settings"))
for d in gst_settings_accounts:
@@ -713,101 +707,63 @@
return gst_accounts
-def update_grand_total_for_rcm(doc, method):
+def validate_reverse_charge_transaction(doc, method):
country = frappe.get_cached_value('Company', doc.company, 'country')
if country != 'India':
return
- gst_tax, base_gst_tax = get_gst_tax_amount(doc)
-
- if not base_gst_tax:
- return
+ base_gst_tax = 0
+ base_reverse_charge_booked = 0
if doc.reverse_charge == 'Y':
- doc.taxes_and_charges_added -= gst_tax
- doc.total_taxes_and_charges -= gst_tax
- doc.base_taxes_and_charges_added -= base_gst_tax
- doc.base_total_taxes_and_charges -= base_gst_tax
+ gst_accounts = get_gst_accounts(doc.company, only_reverse_charge=1)
+ reverse_charge_accounts = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
+ + gst_accounts.get('igst_account')
- update_totals(gst_tax, base_gst_tax, doc)
-
-def update_totals(gst_tax, base_gst_tax, doc):
- doc.base_grand_total -= base_gst_tax
- doc.grand_total -= gst_tax
-
- if doc.meta.get_field("rounded_total"):
- if doc.is_rounded_total_disabled():
- doc.outstanding_amount = doc.grand_total
- else:
- doc.rounded_total = round_based_on_smallest_currency_fraction(doc.grand_total,
- doc.currency, doc.precision("rounded_total"))
-
- doc.rounding_adjustment += flt(doc.rounded_total - doc.grand_total,
- doc.precision("rounding_adjustment"))
-
- doc.outstanding_amount = doc.rounded_total or doc.grand_total
-
- doc.in_words = money_in_words(doc.grand_total, doc.currency)
- doc.base_in_words = money_in_words(doc.base_grand_total, erpnext.get_company_currency(doc.company))
- doc.set_payment_schedule()
-
-def make_regional_gl_entries(gl_entries, doc):
- country = frappe.get_cached_value('Company', doc.company, 'country')
-
- if country != 'India':
- return gl_entries
-
- gst_tax, base_gst_tax = get_gst_tax_amount(doc)
-
- if not base_gst_tax:
- return gl_entries
-
- if doc.reverse_charge == 'Y':
- gst_accounts = get_gst_accounts(doc.company)
- gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
+ gst_accounts = get_gst_accounts(doc.company, only_non_reverse_charge=1)
+ non_reverse_charge_accounts = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
+ gst_accounts.get('igst_account')
for tax in doc.get('taxes'):
- if tax.category not in ("Total", "Valuation and Total"):
- continue
+ if tax.account_head in non_reverse_charge_accounts:
+ if tax.add_deduct_tax == 'Add':
+ base_gst_tax += tax.base_tax_amount_after_discount_amount
+ else:
+ base_gst_tax += tax.base_tax_amount_after_discount_amount
+ elif tax.account_head in reverse_charge_accounts:
+ if tax.add_deduct_tax == 'Add':
+ base_reverse_charge_booked += tax.base_tax_amount_after_discount_amount
+ else:
+ base_reverse_charge_booked += tax.base_tax_amount_after_discount_amount
- dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
- if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list:
- account_currency = get_account_currency(tax.account_head)
+ if base_gst_tax != base_reverse_charge_booked:
+ msg = _("Booked reverse charge is not equal to applied tax amount")
+ msg += "<br>"
+ msg += _("Please refer {gst_document_link} to learn more about how to setup and create reverse charge invoice").format(
+ gst_document_link='<a href="https://docs.erpnext.com/docs/user/manual/en/regional/india/gst-setup">GST Documentation</a>')
- gl_entries.append(doc.get_gl_dict(
- {
- "account": tax.account_head,
- "cost_center": tax.cost_center,
- "posting_date": doc.posting_date,
- "against": doc.supplier,
- dr_or_cr: tax.base_tax_amount_after_discount_amount,
- dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \
- if account_currency==doc.company_currency \
- else tax.tax_amount_after_discount_amount
- }, account_currency, item=tax)
- )
+ frappe.throw(msg)
- return gl_entries
+def update_itc_availed_fields(doc, method):
+ country = frappe.get_cached_value('Company', doc.company, 'country')
-def get_gst_tax_amount(doc):
- gst_accounts = get_gst_accounts(doc.company)
- gst_account_list = gst_accounts.get('cgst_account', []) + gst_accounts.get('sgst_account', []) \
- + gst_accounts.get('igst_account', [])
+ if country != 'India':
+ return
- base_gst_tax = 0
- gst_tax = 0
+ # Initialize values
+ doc.itc_integrated_tax = doc.itc_state_tax = doc.itc_central_tax = doc.itc_cess_amount = 0
+ gst_accounts = get_gst_accounts(doc.company, only_non_reverse_charge=1)
for tax in doc.get('taxes'):
- if tax.category not in ("Total", "Valuation and Total"):
- continue
-
- if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list:
- base_gst_tax += tax.base_tax_amount_after_discount_amount
- gst_tax += tax.tax_amount_after_discount_amount
-
- return gst_tax, base_gst_tax
+ if tax.account_head in gst_accounts.get('igst_account', []):
+ doc.itc_integrated_tax += flt(tax.base_tax_amount_after_discount_amount)
+ if tax.account_head in gst_accounts.get('sgst_account', []):
+ doc.itc_state_tax += flt(tax.base_tax_amount_after_discount_amount)
+ if tax.account_head in gst_accounts.get('cgst_account', []):
+ doc.itc_central_tax += flt(tax.base_tax_amount_after_discount_amount)
+ if tax.account_head in gst_accounts.get('cess_account', []):
+ doc.itc_cess_amount += flt(tax.base_tax_amount_after_discount_amount)
@frappe.whitelist()
def get_regional_round_off_accounts(company, account_list):
@@ -832,3 +788,69 @@
account_list.extend(gst_account_list)
return account_list
+
+def update_taxable_values(doc, method):
+ country = frappe.get_cached_value('Company', doc.company, 'country')
+
+ if country != 'India':
+ return
+
+ gst_accounts = get_gst_accounts(doc.company)
+
+ # Only considering sgst account to avoid inflating taxable value
+ gst_account_list = gst_accounts.get('sgst_account', []) + gst_accounts.get('sgst_account', []) \
+ + gst_accounts.get('igst_account', [])
+
+ additional_taxes = 0
+ total_charges = 0
+ item_count = 0
+ considered_rows = []
+
+ for tax in doc.get('taxes'):
+ prev_row_id = cint(tax.row_id) - 1
+ if tax.account_head in gst_account_list and prev_row_id not in considered_rows:
+ if tax.charge_type == 'On Previous Row Amount':
+ additional_taxes += doc.get('taxes')[prev_row_id].tax_amount_after_discount_amount
+ considered_rows.append(prev_row_id)
+ if tax.charge_type == 'On Previous Row Total':
+ additional_taxes += doc.get('taxes')[prev_row_id].base_total - doc.base_net_total
+ considered_rows.append(prev_row_id)
+
+ for item in doc.get('items'):
+ if doc.apply_discount_on == 'Grand Total' and doc.discount_amount:
+ proportionate_value = item.base_amount if doc.base_total else item.qty
+ total_value = doc.base_total if doc.base_total else doc.total_qty
+ else:
+ proportionate_value = item.base_net_amount if doc.base_net_total else item.qty
+ total_value = doc.base_net_total if doc.base_net_total else doc.total_qty
+
+ applicable_charges = flt(flt(proportionate_value * (flt(additional_taxes) / flt(total_value)),
+ item.precision('taxable_value')))
+ item.taxable_value = applicable_charges + proportionate_value
+ total_charges += applicable_charges
+ item_count += 1
+
+ if total_charges != additional_taxes:
+ diff = additional_taxes - total_charges
+ doc.get('items')[item_count - 1].taxable_value += diff
+
+def get_depreciation_amount(asset, depreciable_value, row):
+ depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked)
+
+ if row.depreciation_method in ("Straight Line", "Manual"):
+ depreciation_amount = (flt(row.value_after_depreciation) -
+ flt(row.expected_value_after_useful_life)) / depreciation_left
+ else:
+ rate_of_depreciation = row.rate_of_depreciation
+ # if its the first depreciation
+ if depreciable_value == asset.gross_purchase_amount:
+ # as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2
+ diff = date_diff(asset.available_for_use_date, row.depreciation_start_date)
+ if diff <= 180:
+ rate_of_depreciation = rate_of_depreciation / 2
+ frappe.msgprint(
+ _('As per IT Act, the rate of depreciation for the first depreciation entry is reduced by 50%.'))
+
+ depreciation_amount = flt(depreciable_value * (flt(rate_of_depreciation) / 100))
+
+ return depreciation_amount
\ No newline at end of file
diff --git a/erpnext/regional/italy/setup.py b/erpnext/regional/italy/setup.py
index a1f5bb9..7db2f6b 100644
--- a/erpnext/regional/italy/setup.py
+++ b/erpnext/regional/italy/setup.py
@@ -139,6 +139,9 @@
dict(fieldname='customer_fiscal_code', label='Customer Fiscal Code',
fieldtype='Data', insert_after='cb_e_invoicing_reference', read_only=1,
fetch_from="customer.fiscal_code"),
+ dict(fieldname='type_of_document', label='Type of Document',
+ fieldtype='Select', insert_after='customer_fiscal_code',
+ options='\nTD01\nTD02\nTD03\nTD04\nTD05\nTD06\nTD16\nTD17\nTD18\nTD19\nTD20\nTD21\nTD22\nTD23\nTD24\nTD25\nTD26\nTD27'),
],
'Purchase Invoice Item': invoice_item_fields,
'Sales Order Item': invoice_item_fields,
diff --git a/erpnext/regional/italy/utils.py b/erpnext/regional/italy/utils.py
index 08573cd..ba1aeaf 100644
--- a/erpnext/regional/italy/utils.py
+++ b/erpnext/regional/italy/utils.py
@@ -57,11 +57,12 @@
invoice.company_address_data = company_address
#Set invoice type
- if invoice.is_return and invoice.return_against:
- invoice.type_of_document = "TD04" #Credit Note (Nota di Credito)
- invoice.return_against_unamended = get_unamended_name(frappe.get_doc("Sales Invoice", invoice.return_against))
- else:
- invoice.type_of_document = "TD01" #Sales Invoice (Fattura)
+ if not invoice.type_of_document:
+ if invoice.is_return and invoice.return_against:
+ invoice.type_of_document = "TD04" #Credit Note (Nota di Credito)
+ invoice.return_against_unamended = get_unamended_name(frappe.get_doc("Sales Invoice", invoice.return_against))
+ else:
+ invoice.type_of_document = "TD01" #Sales Invoice (Fattura)
#set customer information
invoice.customer_data = frappe.get_doc("Customer", invoice.customer)
diff --git a/erpnext/regional/report/datev/datev.json b/erpnext/regional/report/datev/datev.json
index 80a866c..94e3960 100644
--- a/erpnext/regional/report/datev/datev.json
+++ b/erpnext/regional/report/datev/datev.json
@@ -1,29 +1,22 @@
{
- "add_total_row": 0,
- "apply_user_permissions": 0,
- "creation": "2019-04-24 08:45:16.650129",
- "disabled": 0,
- "icon": "octicon octicon-repo-pull",
- "color": "#4CB944",
- "docstatus": 0,
- "doctype": "Report",
- "idx": 0,
- "is_standard": "Yes",
- "module": "Regional",
- "name": "DATEV",
- "owner": "Administrator",
- "ref_doctype": "GL Entry",
- "report_name": "DATEV",
- "report_type": "Script Report",
- "roles": [
- {
- "role": "Accounts User"
- },
- {
- "role": "Accounts Manager"
- },
- {
- "role": "Auditor"
- }
- ]
-}
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2019-04-24 08:45:16.650129",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-04-06 12:23:00.379517",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "DATEV",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "GL Entry",
+ "report_name": "DATEV",
+ "report_type": "Script Report",
+ "roles": []
+}
\ No newline at end of file
diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py
index cbc9478..a5ca7ee 100644
--- a/erpnext/regional/report/datev/datev.py
+++ b/erpnext/regional/report/datev/datev.py
@@ -3,9 +3,9 @@
Provide a report and downloadable CSV according to the German DATEV format.
- Query report showing only the columns that contain data, formatted nicely for
- dispay to the user.
+ dispay to the user.
- CSV download functionality `download_datev_csv` that provides a CSV file with
- all required columns. Used to import the data into the DATEV Software.
+ all required columns. Used to import the data into the DATEV Software.
"""
from __future__ import unicode_literals
@@ -88,6 +88,32 @@
"fieldtype": "Dynamic Link",
"options": "Beleginfo - Art 2",
"width": 150
+ },
+ {
+ "label": "Beleginfo - Art 3",
+ "fieldname": "Beleginfo - Art 3",
+ "fieldtype": "Link",
+ "options": "DocType",
+ "width": 100
+ },
+ {
+ "label": "Beleginfo - Inhalt 3",
+ "fieldname": "Beleginfo - Inhalt 3",
+ "fieldtype": "Dynamic Link",
+ "options": "Beleginfo - Art 3",
+ "width": 150
+ },
+ {
+ "label": "Beleginfo - Art 4",
+ "fieldname": "Beleginfo - Art 4",
+ "fieldtype": "Data",
+ "width": 100
+ },
+ {
+ "label": "Beleginfo - Inhalt 4",
+ "fieldname": "Beleginfo - Inhalt 4",
+ "fieldtype": "Data",
+ "width": 150
}
]
@@ -120,10 +146,8 @@
validate_fiscal_year(from_date, to_date, company)
if not frappe.db.exists('DATEV Settings', filters.get('company')):
- frappe.log_error(_('Please create {} for Company {}.').format(
- '<a href="desk#List/DATEV%20Settings/List">{}</a>'.format(_('DATEV Settings')),
- frappe.bold(filters.get('company'))
- ))
+ msg = 'Please create DATEV Settings for Company {}'.format(filters.get('company'))
+ frappe.log_error(msg, title='DATEV Settings missing')
return False
return True
@@ -169,7 +193,11 @@
gl.voucher_type as 'Beleginfo - Art 1',
gl.voucher_no as 'Beleginfo - Inhalt 1',
gl.against_voucher_type as 'Beleginfo - Art 2',
- gl.against_voucher as 'Beleginfo - Inhalt 2'
+ gl.against_voucher as 'Beleginfo - Inhalt 2',
+ gl.party_type as 'Beleginfo - Art 3',
+ gl.party as 'Beleginfo - Inhalt 3',
+ case gl.party_type when 'Customer' then 'Debitorennummer' when 'Supplier' then 'Kreditorennummer' else NULL end as 'Beleginfo - Art 4',
+ par.debtor_creditor_number as 'Beleginfo - Inhalt 4'
FROM `tabGL Entry` gl
@@ -177,6 +205,19 @@
left join `tabAccount` acc
on gl.account = acc.name
+ left join `tabCustomer` cus
+ on gl.party_type = 'Customer'
+ and gl.party = cus.name
+
+ left join `tabSupplier` sup
+ on gl.party_type = 'Supplier'
+ and gl.party = sup.name
+
+ left join `tabParty Account` par
+ on par.parent = gl.party
+ and par.parenttype = gl.party_type
+ and par.company = %(company)s
+
WHERE gl.company = %(company)s
AND DATE(gl.posting_date) >= %(from_date)s
AND DATE(gl.posting_date) <= %(to_date)s
@@ -196,40 +237,56 @@
return frappe.db.sql("""
SELECT
- acc.account_number as 'Konto',
- CASE cus.customer_type WHEN 'Company' THEN cus.customer_name ELSE null END as 'Name (Adressatentyp Unternehmen)',
- CASE cus.customer_type WHEN 'Individual' THEN con.last_name ELSE null END as 'Name (Adressatentyp natürl. Person)',
- CASE cus.customer_type WHEN 'Individual' THEN con.first_name ELSE null END as 'Vorname (Adressatentyp natürl. Person)',
- CASE cus.customer_type WHEN 'Individual' THEN '1' WHEN 'Company' THEN '2' ELSE '0' end as 'Adressatentyp',
+ par.debtor_creditor_number as 'Konto',
+ CASE cus.customer_type
+ WHEN 'Company' THEN cus.customer_name
+ ELSE null
+ END as 'Name (Adressatentyp Unternehmen)',
+ CASE cus.customer_type
+ WHEN 'Individual' THEN TRIM(SUBSTR(cus.customer_name, LOCATE(' ', cus.customer_name)))
+ ELSE null
+ END as 'Name (Adressatentyp natürl. Person)',
+ CASE cus.customer_type
+ WHEN 'Individual' THEN SUBSTRING_INDEX(SUBSTRING_INDEX(cus.customer_name, ' ', 1), ' ', -1)
+ ELSE null
+ END as 'Vorname (Adressatentyp natürl. Person)',
+ CASE cus.customer_type
+ WHEN 'Individual' THEN '1'
+ WHEN 'Company' THEN '2'
+ ELSE '0'
+ END as 'Adressatentyp',
adr.address_line1 as 'Straße',
adr.pincode as 'Postleitzahl',
adr.city as 'Ort',
UPPER(country.code) as 'Land',
adr.address_line2 as 'Adresszusatz',
- con.email_id as 'E-Mail',
- coalesce(con.mobile_no, con.phone) as 'Telefon',
+ adr.email_id as 'E-Mail',
+ adr.phone as 'Telefon',
+ adr.fax as 'Fax',
cus.website as 'Internet',
cus.tax_id as 'Steuernummer'
- FROM `tabParty Account` par
+ FROM `tabCustomer` cus
- left join `tabAccount` acc
- on acc.name = par.account
+ left join `tabParty Account` par
+ on par.parent = cus.name
+ and par.parenttype = 'Customer'
+ and par.company = %(company)s
- left join `tabCustomer` cus
- on cus.name = par.parent
+ left join `tabDynamic Link` dyn_adr
+ on dyn_adr.link_name = cus.name
+ and dyn_adr.link_doctype = 'Customer'
+ and dyn_adr.parenttype = 'Address'
left join `tabAddress` adr
- on adr.name = cus.customer_primary_address
+ on adr.name = dyn_adr.parent
+ and adr.is_primary_address = '1'
left join `tabCountry` country
on country.name = adr.country
- left join `tabContact` con
- on con.name = cus.customer_primary_contact
-
- WHERE par.company = %(company)s
- AND par.parenttype = 'Customer'""", filters, as_dict=1)
+ WHERE adr.is_primary_address = '1'
+ """, filters, as_dict=1)
def get_suppliers(filters):
@@ -242,35 +299,48 @@
return frappe.db.sql("""
SELECT
- acc.account_number as 'Konto',
- CASE sup.supplier_type WHEN 'Company' THEN sup.supplier_name ELSE null END as 'Name (Adressatentyp Unternehmen)',
- CASE sup.supplier_type WHEN 'Individual' THEN con.last_name ELSE null END as 'Name (Adressatentyp natürl. Person)',
- CASE sup.supplier_type WHEN 'Individual' THEN con.first_name ELSE null END as 'Vorname (Adressatentyp natürl. Person)',
- CASE sup.supplier_type WHEN 'Individual' THEN '1' WHEN 'Company' THEN '2' ELSE '0' end as 'Adressatentyp',
+ par.debtor_creditor_number as 'Konto',
+ CASE sup.supplier_type
+ WHEN 'Company' THEN sup.supplier_name
+ ELSE null
+ END as 'Name (Adressatentyp Unternehmen)',
+ CASE sup.supplier_type
+ WHEN 'Individual' THEN TRIM(SUBSTR(sup.supplier_name, LOCATE(' ', sup.supplier_name)))
+ ELSE null
+ END as 'Name (Adressatentyp natürl. Person)',
+ CASE sup.supplier_type
+ WHEN 'Individual' THEN SUBSTRING_INDEX(SUBSTRING_INDEX(sup.supplier_name, ' ', 1), ' ', -1)
+ ELSE null
+ END as 'Vorname (Adressatentyp natürl. Person)',
+ CASE sup.supplier_type
+ WHEN 'Individual' THEN '1'
+ WHEN 'Company' THEN '2'
+ ELSE '0'
+ END as 'Adressatentyp',
adr.address_line1 as 'Straße',
adr.pincode as 'Postleitzahl',
adr.city as 'Ort',
UPPER(country.code) as 'Land',
adr.address_line2 as 'Adresszusatz',
- con.email_id as 'E-Mail',
- coalesce(con.mobile_no, con.phone) as 'Telefon',
+ adr.email_id as 'E-Mail',
+ adr.phone as 'Telefon',
+ adr.fax as 'Fax',
sup.website as 'Internet',
sup.tax_id as 'Steuernummer',
case sup.on_hold when 1 then sup.release_date else null end as 'Zahlungssperre bis'
- FROM `tabParty Account` par
+ FROM `tabSupplier` sup
- left join `tabAccount` acc
- on acc.name = par.account
-
- left join `tabSupplier` sup
- on sup.name = par.parent
+ left join `tabParty Account` par
+ on par.parent = sup.name
+ and par.parenttype = 'Supplier'
+ and par.company = %(company)s
left join `tabDynamic Link` dyn_adr
on dyn_adr.link_name = sup.name
and dyn_adr.link_doctype = 'Supplier'
and dyn_adr.parenttype = 'Address'
-
+
left join `tabAddress` adr
on adr.name = dyn_adr.parent
and adr.is_primary_address = '1'
@@ -278,17 +348,8 @@
left join `tabCountry` country
on country.name = adr.country
- left join `tabDynamic Link` dyn_con
- on dyn_con.link_name = sup.name
- and dyn_con.link_doctype = 'Supplier'
- and dyn_con.parenttype = 'Contact'
-
- left join `tabContact` con
- on con.name = dyn_con.parent
- and con.is_primary_contact = '1'
-
- WHERE par.company = %(company)s
- AND par.parenttype = 'Supplier'""", filters, as_dict=1)
+ WHERE adr.is_primary_address = '1'
+ """, filters, as_dict=1)
def get_account_names(filters):
diff --git a/erpnext/regional/report/e_invoice_summary/__init__.py b/erpnext/regional/report/e_invoice_summary/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/regional/report/e_invoice_summary/__init__.py
diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js
new file mode 100644
index 0000000..4713217
--- /dev/null
+++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js
@@ -0,0 +1,55 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["E-Invoice Summary"] = {
+ "filters": [
+ {
+ "fieldtype": "Link",
+ "options": "Company",
+ "reqd": 1,
+ "fieldname": "company",
+ "label": __("Company"),
+ "default": frappe.defaults.get_user_default("Company"),
+ },
+ {
+ "fieldtype": "Link",
+ "options": "Customer",
+ "fieldname": "customer",
+ "label": __("Customer")
+ },
+ {
+ "fieldtype": "Date",
+ "reqd": 1,
+ "fieldname": "from_date",
+ "label": __("From Date"),
+ "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
+ },
+ {
+ "fieldtype": "Date",
+ "reqd": 1,
+ "fieldname": "to_date",
+ "label": __("To Date"),
+ "default": frappe.datetime.get_today(),
+ },
+ {
+ "fieldtype": "Select",
+ "fieldname": "status",
+ "label": __("Status"),
+ "options": "\nPending\nGenerated\nCancelled\nFailed"
+ }
+ ],
+
+ "formatter": function (value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+
+ if (column.fieldname == "einvoice_status" && value) {
+ if (value == 'Pending') value = `<span class="bold" style="color: var(--text-on-orange)">${value}</span>`;
+ else if (value == 'Generated') value = `<span class="bold" style="color: var(--text-on-green)">${value}</span>`;
+ else if (value == 'Cancelled') value = `<span class="bold" style="color: var(--text-on-red)">${value}</span>`;
+ else if (value == 'Failed') value = `<span class="bold" style="color: var(--text-on-red)">${value}</span>`;
+ }
+
+ return value;
+ }
+};
diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json
new file mode 100644
index 0000000..4deb073
--- /dev/null
+++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json
@@ -0,0 +1,28 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-03-12 11:23:37.312294",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "json": "{}",
+ "letter_head": "Logo",
+ "modified": "2021-03-12 12:36:48.689413",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "E-Invoice Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Sales Invoice",
+ "report_name": "E-Invoice Summary",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Administrator"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py
new file mode 100644
index 0000000..47acf29
--- /dev/null
+++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py
@@ -0,0 +1,106 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+def execute(filters=None):
+ validate_filters(filters)
+
+ columns = get_columns()
+ data = get_data(filters)
+
+ return columns, data
+
+def validate_filters(filters={}):
+ filters = frappe._dict(filters)
+
+ if not filters.company:
+ frappe.throw(_('{} is mandatory for generating E-Invoice Summary Report').format(_('Company')), title=_('Invalid Filter'))
+ if filters.company:
+ # validate if company has e-invoicing enabled
+ pass
+ if not filters.from_date or not filters.to_date:
+ frappe.throw(_('From Date & To Date is mandatory for generating E-Invoice Summary Report'), title=_('Invalid Filter'))
+ if filters.from_date > filters.to_date:
+ frappe.throw(_('From Date must be before To Date'), title=_('Invalid Filter'))
+
+def get_data(filters={}):
+ query_filters = {
+ 'posting_date': ['between', [filters.from_date, filters.to_date]],
+ 'einvoice_status': ['is', 'set'],
+ 'company': filters.company
+ }
+ if filters.customer:
+ query_filters['customer'] = filters.customer
+ if filters.status:
+ query_filters['einvoice_status'] = filters.status
+
+ data = frappe.get_all(
+ 'Sales Invoice',
+ filters=query_filters,
+ fields=[d.get('fieldname') for d in get_columns()]
+ )
+
+ return data
+
+def get_columns():
+ return [
+ {
+ "fieldtype": "Date",
+ "fieldname": "posting_date",
+ "label": _("Posting Date"),
+ "width": 0
+ },
+ {
+ "fieldtype": "Link",
+ "fieldname": "name",
+ "label": _("Sales Invoice"),
+ "options": "Sales Invoice",
+ "width": 140
+ },
+ {
+ "fieldtype": "Data",
+ "fieldname": "einvoice_status",
+ "label": _("Status"),
+ "width": 100
+ },
+ {
+ "fieldtype": "Link",
+ "fieldname": "customer",
+ "options": "Customer",
+ "label": _("Customer")
+ },
+ {
+ "fieldtype": "Check",
+ "fieldname": "is_return",
+ "label": _("Is Return"),
+ "width": 85
+ },
+ {
+ "fieldtype": "Data",
+ "fieldname": "ack_no",
+ "label": "Ack. No.",
+ "width": 145
+ },
+ {
+ "fieldtype": "Data",
+ "fieldname": "ack_date",
+ "label": "Ack. Date",
+ "width": 165
+ },
+ {
+ "fieldtype": "Data",
+ "fieldname": "irn",
+ "label": _("IRN No."),
+ "width": 250
+ },
+ {
+ "fieldtype": "Currency",
+ "options": "Company:company:default_currency",
+ "fieldname": "base_grand_total",
+ "label": _("Grand Total"),
+ "width": 120
+ }
+ ]
\ No newline at end of file
diff --git a/erpnext/regional/report/gstr_1/gstr_1.js b/erpnext/regional/report/gstr_1/gstr_1.js
index 1a7ff2b..444f5db 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.js
+++ b/erpnext/regional/report/gstr_1/gstr_1.js
@@ -46,7 +46,13 @@
"label": __("Type of Business"),
"fieldtype": "Select",
"reqd": 1,
- "options": ["B2B", "B2C Large", "B2C Small", "CDNR", "EXPORT"],
+ "options": [
+ { "value": "B2B", "label": __("B2B Invoices - 4A, 4B, 4C, 6B, 6C") },
+ { "value": "B2C Large", "label": __("B2C(Large) Invoices - 5A, 5B") },
+ { "value": "B2C Small", "label": __("B2C(Small) Invoices - 7") },
+ { "value": "CDNR-REG", "label": __("Credit/Debit Notes (Registered) - 9B") },
+ { "value": "EXPORT", "label": __("Export Invoice - 6A") }
+ ],
"default": "B2B"
}
],
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 62faa30..b7c0962 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -32,6 +32,7 @@
reverse_charge,
return_against,
is_return,
+ is_debit_note,
gst_category,
export_type,
port_code,
@@ -42,7 +43,7 @@
def run(self):
self.get_columns()
- self.gst_accounts = get_gst_accounts(self.filters.company)
+ self.gst_accounts = get_gst_accounts(self.filters.company, only_non_reverse_charge=1)
self.get_invoice_data()
if self.invoices:
@@ -62,9 +63,9 @@
for rate, items in items_based_on_rate.items():
row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items)
- if self.filters.get("type_of_business") == "CDNR":
+ if self.filters.get("type_of_business") == "CDNR-REG":
row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N")
- row.append("C" if invoice_details.return_against else "R")
+ row.append("C" if invoice_details.is_return else "D")
if taxable_value:
self.data.append(row)
@@ -105,7 +106,7 @@
def get_row_data_for_invoice(self, invoice, invoice_details, tax_rate, items):
row = []
for fieldname in self.invoice_fields:
- if self.filters.get("type_of_business") == "CDNR" and fieldname == "invoice_value":
+ if self.filters.get("type_of_business") == "CDNR-REG" and fieldname == "invoice_value":
row.append(abs(invoice_details.base_rounded_total) or abs(invoice_details.base_grand_total))
elif fieldname == "invoice_value":
row.append(invoice_details.base_rounded_total or invoice_details.base_grand_total)
@@ -171,7 +172,7 @@
if self.filters.get("type_of_business") == "B2B":
- conditions += "and ifnull(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') and is_return != 1"
+ conditions += "AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') AND is_return != 1"
if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"):
b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit')
@@ -179,19 +180,19 @@
frappe.throw(_("Please set B2C Limit in GST Settings."))
if self.filters.get("type_of_business") == "B2C Large":
- conditions += """ and ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'')
- and grand_total > {0} and is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit))
+ conditions += """ AND ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'')
+ AND grand_total > {0} AND is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit))
elif self.filters.get("type_of_business") == "B2C Small":
- conditions += """ and (
+ conditions += """ AND (
SUBSTR(place_of_supply, 1, 2) = SUBSTR(company_gstin, 1, 2)
- or grand_total <= {0}) and is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit))
+ OR grand_total <= {0}) and is_return != 1 AND gst_category ='Unregistered' """.format(flt(b2c_limit))
- elif self.filters.get("type_of_business") == "CDNR":
- conditions += """ and is_return = 1 """
+ elif self.filters.get("type_of_business") == "CDNR-REG":
+ conditions += """ AND (is_return = 1 OR is_debit_note = 1) AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ')"""
elif self.filters.get("type_of_business") == "EXPORT":
- conditions += """ and is_return !=1 and gst_category = 'Overseas' """
+ conditions += """ AND is_return !=1 and gst_category = 'Overseas' """
return conditions
def get_invoice_items(self):
@@ -199,7 +200,7 @@
self.item_tax_rate = frappe._dict()
items = frappe.db.sql("""
- select item_code, parent, base_net_amount, item_tax_rate
+ select item_code, parent, taxable_value, base_net_amount, item_tax_rate
from `tab%s Item`
where parent in (%s)
""" % (self.doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1)
@@ -207,7 +208,7 @@
for d in items:
if d.item_code not in self.invoice_items.get(d.parent, {}):
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code,
- sum(i.get('base_net_amount', 0) for i in items
+ sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in items
if i.item_code == d.item_code and i.parent == d.parent))
item_tax_rate = {}
@@ -403,7 +404,7 @@
"width": 100
}
]
- elif self.filters.get("type_of_business") == "CDNR":
+ elif self.filters.get("type_of_business") == "CDNR-REG":
self.invoice_columns = [
{
"fieldname": "customer_gstin",
@@ -438,6 +439,17 @@
"width":120
},
{
+ "fieldname": "reverse_charge",
+ "label": "Reverse Charge",
+ "fieldtype": "Data"
+ },
+ {
+ "fieldname": "export_type",
+ "label": "Export Type",
+ "fieldtype": "Data",
+ "hidden": 1
+ },
+ {
"fieldname": "reason_for_issuing_document",
"label": "Reason For Issuing document",
"fieldtype": "Data",
@@ -450,6 +462,11 @@
"width": 120
},
{
+ "fieldname": "gst_category",
+ "label": "GST Category",
+ "fieldtype": "Data"
+ },
+ {
"fieldname": "invoice_value",
"label": "Invoice Value",
"fieldtype": "Currency",
@@ -458,10 +475,10 @@
]
self.other_columns = [
{
- "fieldname": "cess_amount",
- "label": "Cess Amount",
- "fieldtype": "Currency",
- "width": 100
+ "fieldname": "cess_amount",
+ "label": "Cess Amount",
+ "fieldtype": "Currency",
+ "width": 100
},
{
"fieldname": "pre_gst",
@@ -557,11 +574,11 @@
def get_json(filters, report_name, data):
filters = json.loads(filters)
report_data = json.loads(data)
- gstin = get_company_gstin_number(filters["company"])
+ gstin = get_company_gstin_number(filters["company"], filters["company_address"])
fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year)
- gst_json = {"gstin": "", "version": "GST2.2.9",
+ gst_json = {"version": "GST2.2.9",
"hash": "hash", "gstin": gstin, "fp": fp}
res = {}
@@ -589,6 +606,12 @@
out = get_export_json(res)
gst_json["exp"] = out
+ elif filters["type_of_business"] == 'CDNR-REG':
+ for item in report_data[:-1]:
+ res.setdefault(item["customer_gstin"], {}).setdefault(item["invoice_number"],[]).append(item)
+
+ out = get_cdnr_reg_json(res, gstin)
+ gst_json["cdnr"] = out
return {
'report_name': report_name,
@@ -628,7 +651,6 @@
return out
def get_b2cs_json(data, gstin):
-
company_state_number = gstin[0:2]
out = []
@@ -713,6 +735,54 @@
return out
+def get_cdnr_reg_json(res, gstin):
+ out = []
+
+ for gst_in in res:
+ cdnr_item, inv = {"ctin": gst_in, "nt": []}, []
+ if not gst_in: continue
+
+ for number, invoice in iteritems(res[gst_in]):
+ if not invoice[0]["place_of_supply"]:
+ frappe.throw(_("""{0} not entered in Invoice {1}.
+ Please update and try again""").format(frappe.bold("Place Of Supply"),
+ frappe.bold(invoice[0]['invoice_number'])))
+
+ inv_item = {
+ "nt_num": invoice[0]["invoice_number"],
+ "nt_dt": getdate(invoice[0]["posting_date"]).strftime('%d-%m-%Y'),
+ "val": abs(flt(invoice[0]["invoice_value"])),
+ "ntty": invoice[0]["document_type"],
+ "pos": "%02d" % int(invoice[0]["place_of_supply"].split('-')[0]),
+ "rchrg": invoice[0]["reverse_charge"],
+ "inv_type": get_invoice_type_for_cdnr(invoice[0])
+ }
+
+ inv_item["itms"] = []
+ for item in invoice:
+ inv_item["itms"].append(get_rate_and_tax_details(item, gstin))
+
+ inv.append(inv_item)
+
+ if not inv: continue
+ cdnr_item["nt"] = inv
+ out.append(cdnr_item)
+
+ return out
+
+def get_invoice_type_for_cdnr(row):
+ if row.get('gst_category') == 'SEZ':
+ if row.get('export_type') == 'WPAY':
+ invoice_type = 'SEWP'
+ else:
+ invoice_type = 'SEWOP'
+ elif row.get('gst_category') == 'Deemed Export':
+ row.invoice_type = 'DE'
+ elif row.get('gst_category') == 'Registered Regular':
+ invoice_type = 'R'
+
+ return invoice_type
+
def get_basic_invoice_detail(row):
return {
"inum": row["invoice_number"],
@@ -740,23 +810,29 @@
return {"num": int(num), "itm_det": itm_det}
-def get_company_gstin_number(company):
- filters = [
- ["is_your_company_address", "=", 1],
- ["Dynamic Link", "link_doctype", "=", "Company"],
- ["Dynamic Link", "link_name", "=", company],
- ["Dynamic Link", "parenttype", "=", "Address"],
- ]
+def get_company_gstin_number(company, address=None):
+ if address:
+ gstin = frappe.db.get_value("Address", address, "gstin")
- gstin = frappe.get_all("Address", filters=filters, fields=["gstin"])
-
- if gstin:
- return gstin[0]["gstin"]
- else:
- frappe.throw(_("Please set valid GSTIN No. in Company Address for company {0}").format(
- frappe.bold(company)
+ if not gstin:
+ filters = [
+ ["is_your_company_address", "=", 1],
+ ["Dynamic Link", "link_doctype", "=", "Company"],
+ ["Dynamic Link", "link_name", "=", company],
+ ["Dynamic Link", "parenttype", "=", "Address"],
+ ]
+ gstin = frappe.get_all("Address", filters=filters, pluck="gstin")
+ if gstin:
+ gstin[0]
+
+ if not gstin:
+ address = frappe.bold(address) if address else ""
+ frappe.throw(_("Please set valid GSTIN No. in Company Address {} for company {}").format(
+ address, frappe.bold(company)
))
+ return gstin
+
@frappe.whitelist()
def download_json_file():
''' download json content in a file '''
diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py
index d9ac6cb..9b3677d 100644
--- a/erpnext/regional/saudi_arabia/setup.py
+++ b/erpnext/regional/saudi_arabia/setup.py
@@ -4,11 +4,8 @@
from __future__ import unicode_literals
from erpnext.regional.united_arab_emirates.setup import make_custom_fields, add_print_formats
-from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax
+
def setup(company=None, patch=True):
make_custom_fields()
add_print_formats()
-
- if company:
- create_sales_tax(company)
\ No newline at end of file
diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py
index 68208ab..bd12d66 100644
--- a/erpnext/regional/united_arab_emirates/setup.py
+++ b/erpnext/regional/united_arab_emirates/setup.py
@@ -6,7 +6,6 @@
import frappe, os, json
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from frappe.permissions import add_permission, update_permission_property
-from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax
from erpnext.payroll.doctype.gratuity_rule.gratuity_rule import get_gratuity_rule
def setup(company=None, patch=True):
@@ -16,9 +15,6 @@
add_permissions()
create_gratuity_rule()
- if company:
- create_sales_tax(company)
-
def make_custom_fields():
is_zero_rated = dict(fieldname='is_zero_rated', label='Is Zero Rated',
fieldtype='Check', fetch_from='item_code.is_zero_rated', insert_after='description',
diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json
index 7d5e84d..cd94ee1 100644
--- a/erpnext/selling/doctype/customer/customer.json
+++ b/erpnext/selling/doctype/customer/customer.json
@@ -212,7 +212,8 @@
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Represents Company",
- "options": "Company"
+ "options": "Company",
+ "unique": 1
},
{
"depends_on": "represents_company",
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 96b3fa4..51d86ff 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -38,11 +38,19 @@
set_name_by_naming_series(self)
def get_customer_name(self):
- if frappe.db.get_value("Customer", self.customer_name):
+
+ if frappe.db.get_value("Customer", self.customer_name) and not frappe.flags.in_import:
count = frappe.db.sql("""select ifnull(MAX(CAST(SUBSTRING_INDEX(name, ' ', -1) AS UNSIGNED)), 0) from tabCustomer
where name like %s""", "%{0} - %".format(self.customer_name), as_list=1)[0][0]
count = cint(count) + 1
- return "{0} - {1}".format(self.customer_name, cstr(count))
+
+ new_customer_name = "{0} - {1}".format(self.customer_name, cstr(count))
+
+ msgprint(_("Changed customer name to '{}' as '{}' already exists.")
+ .format(new_customer_name, self.customer_name),
+ title=_("Note"), indicator="yellow")
+
+ return new_customer_name
return self.customer_name
@@ -482,7 +490,7 @@
outstanding_based_on_gle = flt(outstanding_based_on_gle[0][0]) if outstanding_based_on_gle else 0
# Outstanding based on Sales Order
- outstanding_based_on_so = 0.0
+ outstanding_based_on_so = 0
# if credit limit check is bypassed at sales order level,
# we should not consider outstanding Sales Orders, when customer credit balance report is run
@@ -493,9 +501,11 @@
where customer=%s and docstatus = 1 and company=%s
and per_billed < 100 and status != 'Closed'""", (customer, company))
- outstanding_based_on_so = flt(outstanding_based_on_so[0][0]) if outstanding_based_on_so else 0.0
+ outstanding_based_on_so = flt(outstanding_based_on_so[0][0]) if outstanding_based_on_so else 0
# Outstanding based on Delivery Note, which are not created against Sales Order
+ outstanding_based_on_dn = 0
+
unmarked_delivery_note_items = frappe.db.sql("""select
dn_item.name, dn_item.amount, dn.base_net_total, dn.base_grand_total
from `tabDelivery Note` dn, `tabDelivery Note Item` dn_item
@@ -507,15 +517,29 @@
and ifnull(dn_item.against_sales_invoice, '') = ''
""", (customer, company), as_dict=True)
- outstanding_based_on_dn = 0.0
+ if not unmarked_delivery_note_items:
+ return outstanding_based_on_gle + outstanding_based_on_so
+
+ si_amounts = frappe.db.sql("""
+ SELECT
+ dn_detail, sum(amount) from `tabSales Invoice Item`
+ WHERE
+ docstatus = 1
+ and dn_detail in ({})
+ GROUP BY dn_detail""".format(", ".join(
+ frappe.db.escape(dn_item.name)
+ for dn_item in unmarked_delivery_note_items
+ ))
+ )
+
+ si_amounts = {si_item[0]: si_item[1] for si_item in si_amounts}
for dn_item in unmarked_delivery_note_items:
- si_amount = frappe.db.sql("""select sum(amount)
- from `tabSales Invoice Item`
- where dn_detail = %s and docstatus = 1""", dn_item.name)[0][0]
+ dn_amount = flt(dn_item.amount)
+ si_amount = flt(si_amounts.get(dn_item.name))
- if flt(dn_item.amount) > flt(si_amount) and dn_item.base_net_total:
- outstanding_based_on_dn += ((flt(dn_item.amount) - flt(si_amount)) \
+ if dn_amount > si_amount and dn_item.base_net_total:
+ outstanding_based_on_dn += ((dn_amount - si_amount)
/ dn_item.base_net_total) * dn_item.base_grand_total
return outstanding_based_on_gle + outstanding_based_on_so + outstanding_based_on_dn
diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py
index f0143f3..527a999 100644
--- a/erpnext/selling/doctype/quotation/test_quotation.py
+++ b/erpnext/selling/doctype/quotation/test_quotation.py
@@ -48,7 +48,7 @@
sales_order.transaction_date = nowdate()
sales_order.insert()
- self.assertEquals(sales_order.currency, "USD")
+ self.assertEqual(sales_order.currency, "USD")
self.assertNotEqual(sales_order.currency, quotation.currency)
def test_make_sales_order(self):
diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json
index 0a5c665..762b6f1 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.json
+++ b/erpnext/selling/doctype/sales_order/sales_order.json
@@ -98,6 +98,7 @@
"rounded_total",
"in_words",
"advance_paid",
+ "disable_rounded_total",
"packing_list",
"packed_items",
"payment_schedule_section",
@@ -901,6 +902,7 @@
"width": "150px"
},
{
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "base_rounding_adjustment",
"fieldtype": "Currency",
"hide_days": 1,
@@ -912,6 +914,7 @@
"read_only": 1
},
{
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "base_rounded_total",
"fieldtype": "Currency",
"hide_days": 1,
@@ -961,6 +964,7 @@
"width": "150px"
},
{
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "rounding_adjustment",
"fieldtype": "Currency",
"hide_days": 1,
@@ -973,6 +977,7 @@
},
{
"bold": 1,
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "rounded_total",
"fieldtype": "Currency",
"hide_days": 1,
@@ -1474,13 +1479,20 @@
"label": "Represents Company",
"options": "Company",
"read_only": 1
+ },
+ {
+ "default": "0",
+ "depends_on": "grand_total",
+ "fieldname": "disable_rounded_total",
+ "fieldtype": "Check",
+ "label": "Disable Rounded Total"
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2021-01-20 23:40:39.929296",
+ "modified": "2021-04-15 23:55:13.439068",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 3137621..9873710 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -85,7 +85,7 @@
si1.update_billed_amount_in_sales_order = 1
si1.submit()
so.load_from_db()
- self.assertEquals(so.per_billed, 0)
+ self.assertEqual(so.per_billed, 0)
def test_make_sales_invoice_with_terms(self):
so = make_sales_order(do_not_submit=True)
@@ -996,7 +996,7 @@
# Check if Work Orders were raised
for item in so_item_name:
wo_qty = frappe.db.sql("select sum(qty) from `tabWork Order` where sales_order=%s and sales_order_item=%s", (so.name, item))
- self.assertEquals(wo_qty[0][0], so_item_name.get(item))
+ self.assertEqual(wo_qty[0][0], so_item_name.get(item))
def test_serial_no_based_delivery(self):
frappe.set_value("Stock Settings", None, "automatically_set_serial_nos_based_on_fifo", 1)
diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json
index 2104c01..f01934b 100644
--- a/erpnext/selling/doctype/selling_settings/selling_settings.json
+++ b/erpnext/selling/doctype/selling_settings/selling_settings.json
@@ -18,6 +18,8 @@
"dn_required",
"sales_update_frequency",
"maintain_same_sales_rate",
+ "maintain_same_rate_action",
+ "role_to_override_stop_action",
"editable_price_list_rate",
"allow_multiple_items",
"allow_against_multiple_purchase_orders",
@@ -133,6 +135,23 @@
"fieldname": "hide_tax_id",
"fieldtype": "Check",
"label": "Hide Customer's Tax ID from Sales Transactions"
+ },
+ {
+ "default": "Stop",
+ "depends_on": "maintain_same_sales_rate",
+ "description": "Configure the action to stop the transaction or just warn if the same rate is not maintained.",
+ "fieldname": "maintain_same_rate_action",
+ "fieldtype": "Select",
+ "label": "Action If Same Rate is Not Maintained",
+ "mandatory_depends_on": "maintain_same_sales_rate",
+ "options": "Stop\nWarn"
+ },
+ {
+ "depends_on": "eval: doc.maintain_same_rate_action == 'Stop'",
+ "fieldname": "role_to_override_stop_action",
+ "fieldtype": "Link",
+ "label": "Role Allowed to Override Stop Action",
+ "options": "Role"
}
],
"icon": "fa fa-cog",
@@ -140,7 +159,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-03-02 17:35:53.603607",
+ "modified": "2021-04-04 20:18:12.814624",
"modified_by": "Administrator",
"module": "Selling",
"name": "Selling Settings",
diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.py b/erpnext/selling/doctype/selling_settings/selling_settings.py
index d297883..b219e7e 100644
--- a/erpnext/selling/doctype/selling_settings/selling_settings.py
+++ b/erpnext/selling/doctype/selling_settings/selling_settings.py
@@ -30,8 +30,8 @@
# Make property setters to hide tax_id fields
for doctype in ("Sales Order", "Sales Invoice", "Delivery Note"):
- make_property_setter(doctype, "tax_id", "hidden", self.hide_tax_id, "Check")
- make_property_setter(doctype, "tax_id", "print_hide", self.hide_tax_id, "Check")
+ make_property_setter(doctype, "tax_id", "hidden", self.hide_tax_id, "Check", validate_fields_for_doctype=False)
+ make_property_setter(doctype, "tax_id", "print_hide", self.hide_tax_id, "Check", validate_fields_for_doctype=False)
def set_default_customer_group_and_territory(self):
if not self.customer_group:
diff --git a/erpnext/selling/doctype/sms_center/sms_center.py b/erpnext/selling/doctype/sms_center/sms_center.py
index bb6ba1f..d142d16 100644
--- a/erpnext/selling/doctype/sms_center/sms_center.py
+++ b/erpnext/selling/doctype/sms_center/sms_center.py
@@ -12,6 +12,7 @@
from frappe.core.doctype.sms_settings.sms_settings import send_sms
class SMSCenter(Document):
+ @frappe.whitelist()
def create_receiver_list(self):
rec, where_clause = '', ''
if self.send_to == 'All Customer Contact':
@@ -73,6 +74,7 @@
return receiver_nos
+ @frappe.whitelist()
def send_sms(self):
receiver_list = []
if not self.message:
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index 062cba1..cb811df 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -15,7 +15,6 @@
data = dict()
result = []
- allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')
warehouse, hide_unavailable_items = frappe.db.get_value('POS Profile', pos_profile, ['warehouse', 'hide_unavailable_items'])
if not frappe.db.exists('Item Group', item_group):
@@ -23,7 +22,7 @@
if search_value:
data = search_serial_or_batch_or_barcode_number(search_value)
-
+
item_code = data.get("item_code") if data.get("item_code") else search_value
serial_no = data.get("serial_no") if data.get("serial_no") else ""
batch_no = data.get("batch_no") if data.get("batch_no") else ""
@@ -31,7 +30,7 @@
if data:
item_info = frappe.db.get_value(
- "Item", data.get("item_code"),
+ "Item", data.get("item_code"),
["name as item_code", "item_name", "description", "stock_uom", "image as item_image", "is_stock_item"]
, as_dict=1)
item_info.setdefault('serial_no', serial_no)
@@ -62,7 +61,6 @@
`tabItem` item {bin_join_selection}
WHERE
item.disabled = 0
- AND item.is_stock_item = 1
AND item.has_variants = 0
AND item.is_sales_item = 1
AND item.is_fixed_asset = 0
@@ -84,6 +82,7 @@
), {'warehouse': warehouse}, as_dict=1)
if items_data:
+ items_data = filter_service_items(items_data)
items = [d.item_code for d in items_data]
item_prices_data = frappe.get_all("Item Price",
fields = ["item_code", "price_list_rate", "currency"],
@@ -96,10 +95,7 @@
for item in items_data:
item_code = item.item_code
item_price = item_prices.get(item_code) or {}
- if allow_negative_stock:
- item_stock_qty = frappe.db.sql("""select ifnull(sum(actual_qty), 0) from `tabBin` where item_code = %s""", item_code)[0][0]
- else:
- item_stock_qty = get_stock_availability(item_code, warehouse)
+ item_stock_qty = get_stock_availability(item_code, warehouse)
row = {}
row.update(item)
@@ -135,12 +131,36 @@
return {}
+def filter_service_items(items):
+ for item in items:
+ if not item['is_stock_item']:
+ if not frappe.db.exists('Product Bundle', item['item_code']):
+ items.remove(item)
+
+ return items
+
def get_conditions(item_code, serial_no, batch_no, barcode):
if serial_no or batch_no or barcode:
return "item.name = {0}".format(frappe.db.escape(item_code))
- return """(item.name like {item_code}
- or item.item_name like {item_code})""".format(item_code = frappe.db.escape('%' + item_code + '%'))
+ return make_condition(item_code)
+
+def make_condition(item_code):
+ condition = "("
+ condition += """item.name like {item_code}
+ or item.item_name like {item_code}""".format(item_code = frappe.db.escape('%' + item_code + '%'))
+ condition += add_search_fields_condition(item_code)
+ condition += ")"
+
+ return condition
+
+def add_search_fields_condition(item_code):
+ condition = ''
+ search_fields = frappe.get_all('POS Search Fields', fields = ['fieldname'])
+ if search_fields:
+ for field in search_fields:
+ condition += " or item.{0} like {1}".format(field['fieldname'], frappe.db.escape('%' + item_code + '%'))
+ return condition
def get_item_group_condition(pos_profile):
cond = "and 1=1"
@@ -257,4 +277,4 @@
elif fieldname == 'mobile_no':
contact_doc.set('phone_nos', [{ 'phone': value, 'is_primary_mobile_no': 1}])
frappe.db.set_value('Customer', customer, 'mobile_no', value)
- contact_doc.save()
\ No newline at end of file
+ contact_doc.save()
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index 9e3c9a5..ae3f9e3 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -56,10 +56,6 @@
dialog.fields_dict.balance_details.grid.refresh();
});
}
- const pos_profile_query = {
- query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query',
- filters: { company: frappe.defaults.get_default('company') }
- }
const dialog = new frappe.ui.Dialog({
title: __('Create POS Opening Entry'),
static: true,
@@ -105,6 +101,10 @@
primary_action_label: __('Submit')
});
dialog.show();
+ const pos_profile_query = {
+ query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query',
+ filters: { company: dialog.fields_dict.company.get_value() }
+ };
}
async prepare_app_defaults(data) {
@@ -241,10 +241,8 @@
events: {
get_frm: () => this.frm,
- cart_item_clicked: (item_code, batch_no, uom) => {
- const search_field = batch_no ? 'batch_no' : 'item_code';
- const search_value = batch_no || item_code;
- const item_row = this.frm.doc.items.find(i => i[search_field] === search_value && i.uom === uom);
+ cart_item_clicked: (item_code, batch_no, uom, rate) => {
+ const item_row = this.get_item_from_frm(item_code, batch_no, uom, rate);
this.item_details.toggle_item_details_section(item_row);
},
@@ -275,23 +273,25 @@
this.cart.toggle_numpad(minimize);
},
- form_updated: async (cdt, cdn, fieldname, value) => {
+ form_updated: (cdt, cdn, fieldname, value) => {
const item_row = frappe.model.get_doc(cdt, cdn);
if (item_row && item_row[fieldname] != value) {
- if (fieldname === 'qty' && flt(value) == 0) {
- this.remove_item_from_cart();
- return;
- }
-
- const { item_code, batch_no, uom } = this.item_details.current_item;
+ const { item_code, batch_no, uom, rate } = this.item_details.current_item;
const event = {
field: fieldname,
value,
- item: { item_code, batch_no, uom }
+ item: { item_code, batch_no, uom, rate }
}
return this.on_cart_update(event)
}
+
+ return Promise.resolve();
+ },
+
+ highlight_cart_item: (item) => {
+ const cart_item = this.cart.get_cart_item(item);
+ this.cart.toggle_item_highlight(cart_item);
},
item_field_focused: (fieldname) => {
@@ -506,8 +506,8 @@
let item_row = undefined;
try {
let { field, value, item } = args;
- const { item_code, batch_no, serial_no, uom } = item;
- item_row = this.get_item_from_frm(item_code, batch_no, uom);
+ const { item_code, batch_no, serial_no, uom, rate } = item;
+ item_row = this.get_item_from_frm(item_code, batch_no, uom, rate);
const item_selected_from_selector = field === 'qty' && value === "+1"
@@ -540,7 +540,7 @@
item_selected_from_selector && (value = flt(value))
- const args = { item_code, batch_no, [field]: value };
+ const args = { item_code, batch_no, rate, [field]: value };
if (serial_no) {
await this.check_serial_no_availablilty(item_code, this.frm.doc.set_warehouse, serial_no);
@@ -555,9 +555,11 @@
await this.check_stock_availability(item_row, value, this.frm.doc.set_warehouse);
await this.trigger_new_item_events(item_row);
-
- this.check_serial_batch_selection_needed(item_row) && this.edit_item_details_of(item_row);
+
this.update_cart_html(item_row);
+
+ this.item_details.$component.is(':visible') && this.edit_item_details_of(item_row);
+ this.check_serial_batch_selection_needed(item_row) && this.edit_item_details_of(item_row);
}
} catch (error) {
@@ -568,12 +570,13 @@
}
}
- get_item_from_frm(item_code, batch_no, uom) {
+ get_item_from_frm(item_code, batch_no, uom, rate) {
const has_batch_no = batch_no;
return this.frm.doc.items.find(
i => i.item_code === item_code
&& (!has_batch_no || (has_batch_no && i.batch_no === batch_no))
&& (i.uom === uom)
+ && (i.rate == rate)
);
}
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
index 9ab9eef..f5019f5 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -7,7 +7,6 @@
this.allowed_customer_groups = settings.customer_groups;
this.allow_rate_change = settings.allow_rate_change;
this.allow_discount_change = settings.allow_discount_change;
-
this.init_component();
}
@@ -185,7 +184,8 @@
const item_code = unescape($cart_item.attr('data-item-code'));
const batch_no = unescape($cart_item.attr('data-batch-no'));
const uom = unescape($cart_item.attr('data-uom'));
- me.events.cart_item_clicked(item_code, batch_no, uom);
+ const rate = unescape($cart_item.attr('data-rate'));
+ me.events.cart_item_clicked(item_code, batch_no, uom, rate);
this.numpad_value = '';
});
@@ -521,28 +521,34 @@
}
}
- get_cart_item({ item_code, batch_no, uom }) {
+ get_cart_item({ item_code, batch_no, uom, rate }) {
const batch_attr = `[data-batch-no="${escape(batch_no)}"]`;
const item_code_attr = `[data-item-code="${escape(item_code)}"]`;
const uom_attr = `[data-uom="${escape(uom)}"]`;
+ const rate_attr = `[data-rate="${escape(rate)}"]`;
const item_selector = batch_no ?
- `.cart-item-wrapper${batch_attr}${uom_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}`;
+ `.cart-item-wrapper${batch_attr}${uom_attr}${rate_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}${rate_attr}`;
return this.$cart_items_wrapper.find(item_selector);
}
+ get_item_from_frm(item) {
+ const doc = this.events.get_frm().doc;
+ const { item_code, batch_no, uom, rate } = item;
+ const search_field = batch_no ? 'batch_no' : 'item_code';
+ const search_value = batch_no || item_code;
+
+ return doc.items.find(i => i[search_field] === search_value && i.uom === uom && i.rate === rate);
+ }
+
update_item_html(item, remove_item) {
const $item = this.get_cart_item(item);
if (remove_item) {
$item && $item.next().remove() && $item.remove();
} else {
- const { item_code, batch_no, uom } = item;
- const search_field = batch_no ? 'batch_no' : 'item_code';
- const search_value = batch_no || item_code;
- const item_row = this.events.get_frm().doc.items.find(i => i[search_field] === search_value && i.uom === uom);
-
+ const item_row = this.get_item_from_frm(item);
this.render_cart_item(item_row, $item);
}
@@ -560,7 +566,7 @@
this.$cart_items_wrapper.append(
`<div class="cart-item-wrapper"
data-item-code="${escape(item_data.item_code)}" data-uom="${escape(item_data.uom)}"
- data-batch-no="${escape(item_data.batch_no || '')}">
+ data-batch-no="${escape(item_data.batch_no || '')}" data-rate="${escape(item_data.rate)}">
</div>
<div class="seperator"></div>`
)
@@ -637,13 +643,23 @@
function get_item_image_html() {
const { image, item_name } = item_data;
if (image) {
- return `<div class="item-image"><img src="${image}" alt="${image}""></div>`;
+ return `
+ <div class="item-image">
+ <img
+ onerror="cur_pos.cart.handle_broken_image(this)"
+ src="${image}" alt="${frappe.get_abbr(item_name)}"">
+ </div>`;
} else {
return `<div class="item-image item-abbr">${frappe.get_abbr(item_name)}</div>`;
}
}
}
+ handle_broken_image($img) {
+ const item_abbr = $($img).attr('alt');
+ $($img).parent().replaceWith(`<div class="item-image item-abbr">${item_abbr}</div>`);
+ }
+
scroll_to_item($item) {
if ($item.length === 0) return;
const scrollTop = $item.offset().top - this.$cart_items_wrapper.offset().top + this.$cart_items_wrapper.scrollTop();
diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js
index cb0a010..df62696 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_details.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_details.js
@@ -54,13 +54,24 @@
this.$dicount_section = this.$component.find('.discount-section');
}
- toggle_item_details_section(item) {
- const { item_code, batch_no, uom } = this.current_item;
+ has_item_has_changed(item) {
+ const { item_code, batch_no, uom, rate } = this.current_item;
const item_code_is_same = item && item_code === item.item_code;
const batch_is_same = item && batch_no == item.batch_no;
const uom_is_same = item && uom === item.uom;
+ const rate_is_same = item && rate === item.rate;
+
+ if (!item)
+ return false;
- this.item_has_changed = !item ? false : item_code_is_same && batch_is_same && uom_is_same ? false : true;
+ if (item_code_is_same && batch_is_same && uom_is_same && rate_is_same)
+ return false;
+
+ return true;
+ }
+
+ toggle_item_details_section(item) {
+ this.item_has_changed = this.has_item_has_changed(item);
this.events.toggle_item_selector(this.item_has_changed);
this.toggle_component(this.item_has_changed);
@@ -72,11 +83,12 @@
this.item_row = item;
this.currency = this.events.get_frm().doc.currency;
- this.current_item = { item_code: item.item_code, batch_no: item.batch_no, uom: item.uom };
+ this.current_item = { item_code: item.item_code, batch_no: item.batch_no, uom: item.uom, rate: item.rate };
this.render_dom(item);
this.render_discount_dom(item);
this.render_form(item);
+ this.events.highlight_cart_item(item);
} else {
this.validate_serial_batch_item();
this.current_item = {};
@@ -198,13 +210,14 @@
if (this.allow_rate_change) {
this.rate_control.df.onchange = function() {
if (this.value || flt(this.value) === 0) {
+ me.events.set_value_in_current_cart_item('rate', this.value);
me.events.form_updated(me.doctype, me.name, 'rate', this.value).then(() => {
const item_row = frappe.get_doc(me.doctype, me.name);
const doc = me.events.get_frm().doc;
-
me.$item_price.html(format_currency(item_row.rate, doc.currency));
me.render_discount_dom(item_row);
});
+ me.current_item.rate = this.value;
}
};
} else {
@@ -293,11 +306,7 @@
frappe.model.on("POS Invoice Item", "*", (fieldname, value, item_row) => {
const field_control = this[`${fieldname}_control`];
- const { item_code, batch_no, uom } = this.current_item;
- const item_code_is_same = item_code === item_row.item_code;
- const batch_is_same = batch_no == item_row.batch_no;
- const uom_is_same = uom === item_row.uom;
- const item_is_same = item_code_is_same && batch_is_same && uom_is_same ? true : false;
+ const item_is_same = !this.has_item_has_changed(item_row);
if (item_is_same && field_control && field_control.get_value() !== value) {
field_control.set_value(value);
diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js
index e0d5b73..5b48725 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_selector.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js
@@ -78,16 +78,33 @@
get_item_html(item) {
const me = this;
// eslint-disable-next-line no-unused-vars
- const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item;
+ const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom, price_list_rate } = item;
const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange";
+ let qty_to_display = actual_qty;
+
+ if (Math.round(qty_to_display) > 999) {
+ qty_to_display = Math.round(qty_to_display)/1000;
+ qty_to_display = qty_to_display.toFixed(1) + 'K';
+ }
+
function get_item_image_html() {
if (!me.hide_images && item_image) {
- return `<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
- <img class="h-full" src="${item_image}" alt="${frappe.get_abbr(item.item_name)}" style="object-fit: cover;">
+ return `<div class="item-qty-pill">
+ <span class="indicator-pill whitespace-nowrap ${indicator_color}">${qty_to_display}</span>
+ </div>
+ <div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
+ <img
+ onerror="cur_pos.item_selector.handle_broken_image(this)"
+ class="h-full" src="${item_image}"
+ alt="${frappe.get_abbr(item.item_name)}"
+ style="object-fit: cover;">
</div>`;
} else {
- return `<div class="item-display abbr">${frappe.get_abbr(item.item_name)}</div>`;
+ return `<div class="item-qty-pill">
+ <span class="indicator-pill whitespace-nowrap ${indicator_color}">${qty_to_display}</span>
+ </div>
+ <div class="item-display abbr">${frappe.get_abbr(item.item_name)}</div>`;
}
}
@@ -95,21 +112,26 @@
`<div class="item-wrapper"
data-item-code="${escape(item.item_code)}" data-serial-no="${escape(serial_no)}"
data-batch-no="${escape(batch_no)}" data-uom="${escape(stock_uom)}"
- title="Avaiable Qty: ${actual_qty}">
+ data-rate="${escape(price_list_rate)}"
+ title="${item.item_name}">
${get_item_image_html()}
<div class="item-detail">
<div class="item-name">
- <span class="indicator ${indicator_color}"></span>
${frappe.ellipsis(item.item_name, 18)}
</div>
- <div class="item-rate">${format_currency(item.price_list_rate, item.currency, 0) || 0}</div>
+ <div class="item-rate">${format_currency(price_list_rate, item.currency, 0) || 0}</div>
</div>
</div>`
);
}
+ handle_broken_image($img) {
+ const item_abbr = $($img).attr('alt');
+ $($img).parent().replaceWith(`<div class="item-display abbr">${item_abbr}</div>`);
+ }
+
make_search_bar() {
const me = this;
const doc = me.events.get_frm().doc;
@@ -159,6 +181,32 @@
bind_events() {
const me = this;
window.onScan = onScan;
+
+ onScan.decodeKeyEvent = function (oEvent) {
+ var iCode = this._getNormalizedKeyNum(oEvent);
+ switch (true) {
+ case iCode >= 48 && iCode <= 90: // numbers and letters
+ case iCode >= 106 && iCode <= 111: // operations on numeric keypad (+, -, etc.)
+ case (iCode >= 160 && iCode <= 164) || iCode == 170: // ^ ! # $ *
+ case iCode >= 186 && iCode <= 194: // (; = , - . / `)
+ case iCode >= 219 && iCode <= 222: // ([ \ ] ')
+ case iCode == 32: // spacebar
+ if (oEvent.key !== undefined && oEvent.key !== '') {
+ return oEvent.key;
+ }
+
+ var sDecoded = String.fromCharCode(iCode);
+ switch (oEvent.shiftKey) {
+ case false: sDecoded = sDecoded.toLowerCase(); break;
+ case true: sDecoded = sDecoded.toUpperCase(); break;
+ }
+ return sDecoded;
+ case iCode >= 96 && iCode <= 105: // numbers on numeric keypad
+ return 0 + (iCode - 96);
+ }
+ return '';
+ };
+
onScan.attachTo(document, {
onScan: (sScancode) => {
if (this.search_field && this.$component.is(':visible')) {
@@ -175,13 +223,15 @@
let batch_no = unescape($item.attr('data-batch-no'));
let serial_no = unescape($item.attr('data-serial-no'));
let uom = unescape($item.attr('data-uom'));
+ let rate = unescape($item.attr('data-rate'));
// escape(undefined) returns "undefined" then unescape returns "undefined"
batch_no = batch_no === "undefined" ? undefined : batch_no;
serial_no = serial_no === "undefined" ? undefined : serial_no;
uom = uom === "undefined" ? undefined : uom;
+ rate = rate === "undefined" ? undefined : rate;
- me.events.item_selected({ field: 'qty', value: "+1", item: { item_code, batch_no, serial_no, uom }});
+ me.events.item_selected({ field: 'qty', value: "+1", item: { item_code, batch_no, serial_no, uom, rate }});
me.set_search_value('');
});
@@ -290,4 +340,4 @@
toggle_component(show) {
show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none');
}
-};
\ No newline at end of file
+};
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_list.js b/erpnext/selling/page/point_of_sale/pos_past_order_list.js
index ec39231..70c7dc2 100644
--- a/erpnext/selling/page/point_of_sale/pos_past_order_list.js
+++ b/erpnext/selling/page/point_of_sale/pos_past_order_list.js
@@ -105,7 +105,7 @@
<svg class="mr-2" width="12" height="12" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
</svg>
- ${invoice.customer}
+ ${frappe.ellipsis(invoice.customer, 20)}
</div>
</div>
<div class="invoice-total-status">
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
index b10a9e3..cec831d 100644
--- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
+++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
@@ -176,6 +176,14 @@
this.show_summary_placeholder();
});
+ this.$summary_container.on('click', '.delete-btn', () => {
+ this.events.delete_order(this.doc.name);
+ this.show_summary_placeholder();
+ // this.toggle_component(false);
+ // this.$component.find('.no-summary-placeholder').removeClass('d-none');
+ // this.$summary_wrapper.addClass('d-none');
+ });
+
this.$summary_container.on('click', '.new-btn', () => {
this.events.new_order();
this.toggle_component(false);
@@ -196,11 +204,11 @@
print_receipt() {
const frm = this.events.get_frm();
frappe.utils.print(
- frm.doctype,
- frm.docname,
+ this.doc.doctype,
+ this.doc.name,
frm.pos_print_format,
- frm.doc.letter_head,
- frm.doc.language || frappe.boot.lang
+ this.doc.letter_head,
+ this.doc.language || frappe.boot.lang
);
}
@@ -233,7 +241,7 @@
send_email() {
const frm = this.events.get_frm();
- const recipients = this.email_dialog.get_values().recipients;
+ const recipients = this.email_dialog.get_values().email_id;
const doc = this.doc || frm.doc;
const print_format = frm.pos_print_format;
diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js
index 22a279d..600f160 100644
--- a/erpnext/selling/page/point_of_sale/pos_payment.js
+++ b/erpnext/selling/page/point_of_sale/pos_payment.js
@@ -252,6 +252,41 @@
}
}
+ setup_listener_for_payments() {
+ frappe.realtime.on("process_phone_payment", (data) => {
+ const doc = this.events.get_frm().doc;
+ const { response, amount, success, failure_message } = data;
+ let message, title;
+
+ if (success) {
+ title = __("Payment Received");
+ if (amount >= doc.grand_total) {
+ frappe.dom.unfreeze();
+ message = __("Payment of {0} received successfully.", [format_currency(amount, doc.currency, 0)]);
+ this.events.submit_invoice();
+ cur_frm.reload_doc();
+
+ } else {
+ message = __("Payment of {0} received successfully. Waiting for other requests to complete...", [format_currency(amount, doc.currency, 0)]);
+ }
+ } else if (failure_message) {
+ message = failure_message;
+ title = __("Payment Failed");
+ }
+
+ frappe.msgprint({ "message": message, "title": title });
+ });
+ }
+
+ auto_set_remaining_amount() {
+ const doc = this.events.get_frm().doc;
+ const remaining_amount = doc.grand_total - doc.paid_amount;
+ const current_value = this.selected_mode ? this.selected_mode.get_value() : undefined;
+ if (!current_value && remaining_amount > 0 && this.selected_mode) {
+ this.selected_mode.set_value(remaining_amount);
+ }
+ }
+
attach_shortcuts() {
const ctrl_label = frappe.utils.is_mac() ? '⌘' : 'Ctrl';
this.$component.find('.submit-order-btn').attr("title", `${ctrl_label}+Enter`);
diff --git a/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json b/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json
index 9094a07b..9d1b196 100644
--- a/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json
+++ b/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json
@@ -1,4 +1,5 @@
{
+ "absolute_value": 0,
"align_labels_right": 0,
"creation": "2017-08-08 12:33:04.773099",
"custom_format": 1,
@@ -7,10 +8,10 @@
"docstatus": 0,
"doctype": "Print Format",
"font": "Default",
- "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Tahoma, sans-serif;\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n<p class=\"text-center\">\n\t{{ doc.company }}<br>\n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t<b>{{ _(\"GSTIN\") }}:</b>{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"<br>GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t<br>\n\t{% if doc.docstatus == 0 %}\n\t\t<b>{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}</b><br>\n\t{% else %}\n\t\t<b>{{ doc.select_print_heading or _(\"Invoice\") }}</b><br>\n\t{% endif %}\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t<b>{{ _(\"Customer\") }}:</b><br>\n\t\t{{ doc.customer_name }}<br>\n\t\t{{ customer_address }}\n\t{% endif %}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t<br>{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.gst_hsn_code -%}\n\t\t\t\t\t<br><b>{{ _(\"HSN/SAC\") }}:</b> {{ item.gst_hsn_code }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t<br><b>{{ _(\"SR.No\") }}:</b><br>\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.rate }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if (not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) and row.tax_amount != 0 -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{% if '%' in row.description %}\n\t\t\t\t\t {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- if doc.change_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- endif -%}\n\t</tbody>\n</table>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
+ "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n<p class=\"text-center\">\n\t{{ doc.company }}<br>\n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t<b>{{ _(\"GSTIN\") }}:</b>{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"<br>GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t<br>\n\t{% if doc.docstatus == 0 %}\n\t\t<b>{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}</b><br>\n\t{% else %}\n\t\t<b>{{ doc.select_print_heading or _(\"Invoice\") }}</b><br>\n\t{% endif %}\n</p>\n\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Cashier\") }}:</b> {{ doc.owner }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t<b>{{ _(\"Time\") }}:</b> {{ doc.get_formatted(\"posting_time\") }}<br>\n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t<b>{{ _(\"Customer\") }}:</b><br>\n\t\t{{ doc.customer_name }}<br>\n\t\t{{ customer_address }}\n\t{% endif %}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t<br>{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.gst_hsn_code -%}\n\t\t\t\t\t<br><b>{{ _(\"HSN/SAC\") }}:</b> {{ item.gst_hsn_code }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t<br><b>{{ _(\"SR.No\") }}:</b><br>\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.rate }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if (not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) and row.tax_amount != 0 -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{% if '%' in row.description %}\n\t\t\t\t\t {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t{%- for row in doc.payments -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t {{ row.mode_of_payment }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t{%- endfor -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- if doc.change_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- endif -%}\n\t</tbody>\n</table>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
"idx": 0,
"line_breaks": 0,
- "modified": "2020-04-29 16:47:02.743246",
+ "modified": "2021-04-15 15:26:04.396169",
"modified_by": "Administrator",
"module": "Selling",
"name": "GST POS Invoice",
diff --git a/erpnext/selling/print_format/pos_invoice/pos_invoice.json b/erpnext/selling/print_format/pos_invoice/pos_invoice.json
index 99094ed..6c01e26 100644
--- a/erpnext/selling/print_format/pos_invoice/pos_invoice.json
+++ b/erpnext/selling/print_format/pos_invoice/pos_invoice.json
@@ -1,4 +1,5 @@
{
+ "absolute_value": 0,
"align_labels_right": 0,
"creation": "2011-12-21 11:08:55",
"custom_format": 1,
@@ -6,10 +7,10 @@
"doc_type": "POS Invoice",
"docstatus": 0,
"doctype": "Print Format",
- "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Tahoma, sans-serif;\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n<p class=\"text-center\" style=\"margin-bottom: 1rem\">\n\t{{ doc.company }}<br>\n\t{{ doc.select_print_heading or _(\"Invoice\") }}<br>\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t<b>{{ _(\"Customer\") }}:</b> {{ doc.customer_name }}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t<br>{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t<br><b>{{ _(\"SR.No\") }}:</b><br>\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.get_formatted(\"rate\") }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t {% if '%' in row.description %}\n\t\t\t\t\t {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.change_amount -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t</td>\n\t\t\t</tr>\n\t\t{%- endif -%}\n\t</tbody>\n</table>\n<hr>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
+ "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n<p class=\"text-center\" style=\"margin-bottom: 1rem\">\n\t{{ doc.company }}<br>\n\t<b>{{ doc.select_print_heading or _(\"Invoice\") }}</b><br>\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Cashier\") }}:</b> {{ doc.owner }}<br>\n\t<b>{{ _(\"Customer\") }}:</b> {{ doc.customer_name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t<b>{{ _(\"Time\") }}:</b> {{ doc.get_formatted(\"posting_time\") }}<br>\n</p>\n\n<hr>\n<table class=\"table table-condensed\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t<br>{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t<br><b>{{ _(\"SR.No\") }}:</b><br>\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.get_formatted(\"rate\") }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t {% if '%' in row.description %}\n\t\t\t\t\t {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t{%- for row in doc.payments -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t {{ row.mode_of_payment }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t{%- endfor -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.change_amount -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t</td>\n\t\t\t</tr>\n\t\t{%- endif -%}\n\t</tbody>\n</table>\n<hr>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
"idx": 1,
"line_breaks": 0,
- "modified": "2020-04-29 16:45:58.942375",
+ "modified": "2021-04-15 15:23:28.867135",
"modified_by": "Administrator",
"module": "Selling",
"name": "POS Invoice",
diff --git a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py
index f396705..6fb7666 100644
--- a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py
+++ b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py
@@ -57,18 +57,18 @@
return columns
def get_details(filters):
- conditions = ""
+ sql_query = """SELECT
+ c.name, c.customer_name,
+ ccl.bypass_credit_limit_check,
+ c.is_frozen, c.disabled
+ FROM `tabCustomer` c, `tabCustomer Credit Limit` ccl
+ WHERE
+ c.name = ccl.parent
+ AND ccl.company = %(company)s"""
+
+ # customer filter is optional.
if filters.get("customer"):
- conditions += " AND c.name = '" + filters.get("customer") + "'"
+ sql_query += " AND c.name = %(customer)s"
- return frappe.db.sql("""SELECT
- c.name, c.customer_name,
- ccl.bypass_credit_limit_check,
- c.is_frozen, c.disabled
- FROM `tabCustomer` c, `tabCustomer Credit Limit` ccl
- WHERE
- c.name = ccl.parent
- AND ccl.company = '{0}'
- {1}
- """.format( filters.get("company"),conditions), as_dict=1) #nosec
+ return frappe.db.sql(sql_query, filters, as_dict=1)
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index c2b5e4f..b24048d 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -90,12 +90,6 @@
frm.toggle_enable("default_currency", (frm.doc.__onload &&
!frm.doc.__onload.transactions_exist));
- if (frm.has_perm('write')) {
- frm.add_custom_button(__('Create Tax Template'), function() {
- frm.trigger("make_default_tax_template");
- });
- }
-
if (frappe.perm.has_perm("Cost Center", 0, 'read')) {
frm.add_custom_button(__('Cost Centers'), function() {
frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name});
@@ -121,17 +115,21 @@
}
if (frm.has_perm('write')) {
- frm.add_custom_button(__('Default Tax Template'), function() {
+ frm.add_custom_button(__('Create Tax Template'), function() {
frm.trigger("make_default_tax_template");
- }, __('Create'));
+ }, __('Manage'));
+ }
+
+ if (frappe.user.has_role('System Manager')) {
+ if (frm.has_perm('write')) {
+ frm.add_custom_button(__('Delete Transactions'), function() {
+ frm.trigger("delete_company_transactions");
+ }, __('Manage'));
+ }
}
}
erpnext.company.set_chart_of_accounts_options(frm.doc);
-
- if (!frappe.user.has_role('System Manager')) {
- frm.get_field("delete_company_transactions").hide();
- }
},
make_default_tax_template: function(frm) {
@@ -145,11 +143,6 @@
})
},
- onload_post_render: function(frm) {
- if(frm.get_field("delete_company_transactions").$input)
- frm.get_field("delete_company_transactions").$input.addClass("btn-danger");
- },
-
country: function(frm) {
erpnext.company.set_chart_of_accounts_options(frm.doc);
},
@@ -169,9 +162,9 @@
return;
}
frappe.call({
- method: "erpnext.setup.doctype.company.delete_company_transactions.delete_company_transactions",
+ method: "erpnext.setup.doctype.company.company.create_transaction_deletion_request",
args: {
- company_name: data.company_name
+ company: data.company_name
},
freeze: true,
callback: function(r, rt) {
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index 83cbf47..061986d 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -99,7 +99,6 @@
"company_description",
"registration_info",
"registration_details",
- "delete_company_transactions",
"lft",
"rgt",
"old_parent"
@@ -667,11 +666,6 @@
"oldfieldtype": "Code"
},
{
- "fieldname": "delete_company_transactions",
- "fieldtype": "Button",
- "label": "Delete Company Transactions"
- },
- {
"fieldname": "lft",
"fieldtype": "Int",
"hidden": 1,
@@ -747,7 +741,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
- "modified": "2021-02-16 15:53:37.167589",
+ "modified": "2021-05-07 03:11:28.189740",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 0922171..077538d 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -17,6 +17,7 @@
from past.builtins import cmp
import functools
from erpnext.accounts.doctype.account.account import get_account_currency
+from erpnext.setup.setup_wizard.operations.taxes_setup import setup_taxes_and_charges
class Company(NestedSet):
nsm_parent_field = 'parent_company'
@@ -68,11 +69,7 @@
@frappe.whitelist()
def create_default_tax_template(self):
- from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax
- create_sales_tax({
- 'country': self.country,
- 'company_name': self.name
- })
+ setup_taxes_and_charges(self.name, self.country)
def validate_default_accounts(self):
accounts = [
@@ -616,4 +613,13 @@
if out:
return sorted(out, key = functools.cmp_to_key(lambda x,y: cmp(y[1], x[1])))[0][0]
else:
- return None
\ No newline at end of file
+ return None
+
+@frappe.whitelist()
+def create_transaction_deletion_request(company):
+ tdr = frappe.get_doc({
+ 'doctype': 'Transaction Deletion Record',
+ 'company': company
+ })
+ tdr.insert()
+ tdr.submit()
diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py
deleted file mode 100644
index 0df4c87..0000000
--- a/erpnext/setup/doctype/company/delete_company_transactions.py
+++ /dev/null
@@ -1,117 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-from __future__ import unicode_literals
-import frappe
-
-from frappe.utils import cint
-from frappe import _
-from frappe.desk.notifications import clear_notifications
-
-import functools
-
-@frappe.whitelist()
-def delete_company_transactions(company_name):
- frappe.only_for("System Manager")
- doc = frappe.get_doc("Company", company_name)
-
- if frappe.session.user != doc.owner:
- frappe.throw(_("Transactions can only be deleted by the creator of the Company"),
- frappe.PermissionError)
-
- delete_bins(company_name)
- delete_lead_addresses(company_name)
-
- for doctype in frappe.db.sql_list("""select parent from
- tabDocField where fieldtype='Link' and options='Company'"""):
- if doctype not in ("Account", "Cost Center", "Warehouse", "Budget",
- "Party Account", "Employee", "Sales Taxes and Charges Template",
- "Purchase Taxes and Charges Template", "POS Profile", "BOM",
- "Company", "Bank Account", "Item Tax Template", "Mode Of Payment",
- "Item Default", "Customer", "Supplier", "GST Account"):
- delete_for_doctype(doctype, company_name)
-
- # reset company values
- doc.total_monthly_sales = 0
- doc.sales_monthly_history = None
- doc.save()
- # Clear notification counts
- clear_notifications()
-
-def delete_for_doctype(doctype, company_name):
- meta = frappe.get_meta(doctype)
- company_fieldname = meta.get("fields", {"fieldtype": "Link",
- "options": "Company"})[0].fieldname
-
- if not meta.issingle:
- if not meta.istable:
- # delete communication
- delete_communications(doctype, company_name, company_fieldname)
-
- # delete children
- for df in meta.get_table_fields():
- frappe.db.sql("""delete from `tab{0}` where parent in
- (select name from `tab{1}` where `{2}`=%s)""".format(df.options,
- doctype, company_fieldname), company_name)
-
- #delete version log
- frappe.db.sql("""delete from `tabVersion` where ref_doctype=%s and docname in
- (select name from `tab{0}` where `{1}`=%s)""".format(doctype,
- company_fieldname), (doctype, company_name))
-
- # delete parent
- frappe.db.sql("""delete from `tab{0}`
- where {1}= %s """.format(doctype, company_fieldname), company_name)
-
- # reset series
- naming_series = meta.get_field("naming_series")
- if naming_series and naming_series.options:
- prefixes = sorted(naming_series.options.split("\n"),
- key=functools.cmp_to_key(lambda a, b: len(b) - len(a)))
-
- for prefix in prefixes:
- if prefix:
- last = frappe.db.sql("""select max(name) from `tab{0}`
- where name like %s""".format(doctype), prefix + "%")
- if last and last[0][0]:
- last = cint(last[0][0].replace(prefix, ""))
- else:
- last = 0
-
- frappe.db.sql("""update tabSeries set current = %s
- where name=%s""", (last, prefix))
-
-def delete_bins(company_name):
- frappe.db.sql("""delete from tabBin where warehouse in
- (select name from tabWarehouse where company=%s)""", company_name)
-
-def delete_lead_addresses(company_name):
- """Delete addresses to which leads are linked"""
- leads = frappe.get_all("Lead", filters={"company": company_name})
- leads = [ "'%s'"%row.get("name") for row in leads ]
- addresses = []
- if leads:
- addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name
- in ({leads})""".format(leads=",".join(leads)))
-
- if addresses:
- addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
-
- frappe.db.sql("""delete from tabAddress where name in ({addresses}) and
- name not in (select distinct dl1.parent from `tabDynamic Link` dl1
- inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
- and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses)))
-
- frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead'
- and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads)))
-
- frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)))
-
-def delete_communications(doctype, company_name, company_fieldname):
- reference_docs = frappe.get_all(doctype, filters={company_fieldname:company_name})
- reference_doc_names = [r.name for r in reference_docs]
-
- communications = frappe.get_all("Communication", filters={"reference_doctype":doctype,"reference_name":["in", reference_doc_names]})
- communication_names = [c.name for c in communications]
-
- frappe.delete_doc("Communication", communication_names, ignore_permissions=True)
diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py
index 29f6c37..e1c803a 100644
--- a/erpnext/setup/doctype/company/test_company.py
+++ b/erpnext/setup/doctype/company/test_company.py
@@ -86,15 +86,6 @@
self.delete_mode_of_payment(template)
frappe.delete_doc("Company", template)
- def test_delete_communication(self):
- from erpnext.setup.doctype.company.delete_company_transactions import delete_communications
- company = create_child_company()
- lead = create_test_lead_in_company(company)
- communication = create_company_communication("Lead", lead)
- delete_communications("Lead", "Test Company", "company")
- self.assertFalse(frappe.db.exists("Communcation", communication))
- self.assertFalse(frappe.db.exists({"doctype":"Comunication Link", "link_name": communication}))
-
def delete_mode_of_payment(self, company):
frappe.db.sql(""" delete from `tabMode of Payment Account`
where company =%s """, (company))
diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py
index ac55fdf..340d89b 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.py
+++ b/erpnext/setup/doctype/email_digest/email_digest.py
@@ -50,8 +50,12 @@
recipients = list(filter(lambda r: r in valid_users,
self.recipient_list.split("\n")))
+ original_user = frappe.session.user
+
if recipients:
for user_id in recipients:
+ frappe.set_user(user_id)
+ frappe.set_user_lang(user_id)
msg_for_this_recipient = self.get_msg_html()
if msg_for_this_recipient:
frappe.sendmail(
@@ -62,6 +66,9 @@
reference_name = self.name,
unsubscribe_message = _("Unsubscribe from this Email Digest"))
+ frappe.set_user(original_user)
+ frappe.set_user_lang(original_user)
+
def get_msg_html(self):
"""Build email digest content"""
frappe.flags.ignore_account_permission = True
@@ -242,7 +249,7 @@
card = cache.get(cache_key)
if card:
- card = eval(card)
+ card = frappe.safe_eval(card)
else:
card = frappe._dict(getattr(self, "get_" + key)())
@@ -801,7 +808,6 @@
val = balance_on_to_date - balance_before_from_date
else:
last_year_closing_balance = get_balance_on(account, date=fy_start_date - timedelta(days=1))
- print(fy_start_date - timedelta(days=1), last_year_closing_balance)
val = balance_on_to_date + (last_year_closing_balance - balance_before_from_date)
return val
@@ -830,4 +836,4 @@
elif frequency == "Monthly":
to_date = add_to_date(from_date, months=1)
- return from_date, to_date
\ No newline at end of file
+ return from_date, to_date
diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.py b/erpnext/setup/doctype/global_defaults/global_defaults.py
index 76a8450..a0ba1ef 100644
--- a/erpnext/setup/doctype/global_defaults/global_defaults.py
+++ b/erpnext/setup/doctype/global_defaults/global_defaults.py
@@ -59,12 +59,14 @@
# Make property setters to hide rounded total fields
for doctype in ("Quotation", "Sales Order", "Sales Invoice", "Delivery Note",
- "Supplier Quotation", "Purchase Order", "Purchase Invoice"):
- make_property_setter(doctype, "base_rounded_total", "hidden", self.disable_rounded_total, "Check")
- make_property_setter(doctype, "base_rounded_total", "print_hide", 1, "Check")
+ "Supplier Quotation", "Purchase Order", "Purchase Invoice", "Purchase Receipt"):
+ make_property_setter(doctype, "base_rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False)
+ make_property_setter(doctype, "base_rounded_total", "print_hide", 1, "Check", validate_fields_for_doctype=False)
- make_property_setter(doctype, "rounded_total", "hidden", self.disable_rounded_total, "Check")
- make_property_setter(doctype, "rounded_total", "print_hide", self.disable_rounded_total, "Check")
+ make_property_setter(doctype, "rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False)
+ make_property_setter(doctype, "rounded_total", "print_hide", self.disable_rounded_total, "Check", validate_fields_for_doctype=False)
+
+ make_property_setter(doctype, "disable_rounded_total", "default", cint(self.disable_rounded_total), "Text", validate_fields_for_doctype=False)
def toggle_in_words(self):
self.disable_in_words = cint(self.disable_in_words)
@@ -72,5 +74,5 @@
# Make property setters to hide in words fields
for doctype in ("Quotation", "Sales Order", "Sales Invoice", "Delivery Note",
"Supplier Quotation", "Purchase Order", "Purchase Invoice", "Purchase Receipt"):
- make_property_setter(doctype, "in_words", "hidden", self.disable_in_words, "Check")
- make_property_setter(doctype, "in_words", "print_hide", self.disable_in_words, "Check")
+ make_property_setter(doctype, "in_words", "hidden", self.disable_in_words, "Check", validate_fields_for_doctype=False)
+ make_property_setter(doctype, "in_words", "print_hide", self.disable_in_words, "Check", validate_fields_for_doctype=False)
diff --git a/erpnext/setup/doctype/item_group/item_group.js b/erpnext/setup/doctype/item_group/item_group.js
index 1413cb2..885d874 100644
--- a/erpnext/setup/doctype/item_group/item_group.js
+++ b/erpnext/setup/doctype/item_group/item_group.js
@@ -61,7 +61,7 @@
frappe.set_route("List", "Item", {"item_group": frm.doc.name});
});
}
-
+
frappe.model.with_doctype('Item', () => {
const item_meta = frappe.get_meta('Item');
@@ -69,10 +69,12 @@
df => ['Link', 'Table MultiSelect'].includes(df.fieldtype) && !df.hidden
).map(df => ({ label: df.label, value: df.fieldname }));
- const field = frappe.meta.get_docfield("Website Filter Field", "fieldname", frm.docname);
- field.fieldtype = 'Select';
- field.options = valid_fields;
- frm.fields_dict.filter_fields.grid.refresh();
+ frm.fields_dict.filter_fields.grid.update_docfield_property(
+ 'fieldname', 'fieldtype', 'Select'
+ );
+ frm.fields_dict.filter_fields.grid.update_docfield_property(
+ 'fieldname', 'options', valid_fields
+ );
});
},
diff --git a/erpnext/setup/doctype/naming_series/naming_series.py b/erpnext/setup/doctype/naming_series/naming_series.py
index c4f1de1..c1f9433 100644
--- a/erpnext/setup/doctype/naming_series/naming_series.py
+++ b/erpnext/setup/doctype/naming_series/naming_series.py
@@ -159,6 +159,7 @@
if frappe.db.get_value('Series', series, 'name', order_by="name") == None:
frappe.db.sql("insert into tabSeries (name, current) values (%s, 0)", (series))
+ @frappe.whitelist()
def update_series_start(self):
if self.prefix:
prefix = self.parse_naming_series()
@@ -182,8 +183,8 @@
def set_by_naming_series(doctype, fieldname, naming_series, hide_name_field=True):
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
if naming_series:
- make_property_setter(doctype, "naming_series", "hidden", 0, "Check")
- make_property_setter(doctype, "naming_series", "reqd", 1, "Check")
+ make_property_setter(doctype, "naming_series", "hidden", 0, "Check", validate_fields_for_doctype=False)
+ make_property_setter(doctype, "naming_series", "reqd", 1, "Check", validate_fields_for_doctype=False)
# set values for mandatory
try:
@@ -194,15 +195,15 @@
pass
if hide_name_field:
- make_property_setter(doctype, fieldname, "reqd", 0, "Check")
- make_property_setter(doctype, fieldname, "hidden", 1, "Check")
+ make_property_setter(doctype, fieldname, "reqd", 0, "Check", validate_fields_for_doctype=False)
+ make_property_setter(doctype, fieldname, "hidden", 1, "Check", validate_fields_for_doctype=False)
else:
- make_property_setter(doctype, "naming_series", "reqd", 0, "Check")
- make_property_setter(doctype, "naming_series", "hidden", 1, "Check")
+ make_property_setter(doctype, "naming_series", "reqd", 0, "Check", validate_fields_for_doctype=False)
+ make_property_setter(doctype, "naming_series", "hidden", 1, "Check", validate_fields_for_doctype=False)
if hide_name_field:
- make_property_setter(doctype, fieldname, "hidden", 0, "Check")
- make_property_setter(doctype, fieldname, "reqd", 1, "Check")
+ make_property_setter(doctype, fieldname, "hidden", 0, "Check", validate_fields_for_doctype=False)
+ make_property_setter(doctype, fieldname, "reqd", 1, "Check", validate_fields_for_doctype=False)
# set values for mandatory
frappe.db.sql("""update `tab{doctype}` set `{fieldname}`=`name` where
diff --git a/erpnext/setup/doctype/transaction_deletion_record/__init__.py b/erpnext/setup/doctype/transaction_deletion_record/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/setup/doctype/transaction_deletion_record/__init__.py
diff --git a/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py
new file mode 100644
index 0000000..bbe6836
--- /dev/null
+++ b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+class TestTransactionDeletionRecord(unittest.TestCase):
+ def setUp(self):
+ create_company('Dunder Mifflin Paper Co')
+
+ def tearDown(self):
+ frappe.db.rollback()
+
+ def test_doctypes_contain_company_field(self):
+ tdr = create_transaction_deletion_request('Dunder Mifflin Paper Co')
+ for doctype in tdr.doctypes:
+ contains_company = False
+ doctype_fields = frappe.get_meta(doctype.doctype_name).as_dict()['fields']
+ for doctype_field in doctype_fields:
+ if doctype_field['fieldtype'] == 'Link' and doctype_field['options'] == 'Company':
+ contains_company = True
+ break
+ self.assertTrue(contains_company)
+
+ def test_no_of_docs_is_correct(self):
+ for i in range(5):
+ create_task('Dunder Mifflin Paper Co')
+ tdr = create_transaction_deletion_request('Dunder Mifflin Paper Co')
+ for doctype in tdr.doctypes:
+ if doctype.doctype_name == 'Task':
+ self.assertEqual(doctype.no_of_docs, 5)
+
+ def test_deletion_is_successful(self):
+ create_task('Dunder Mifflin Paper Co')
+ create_transaction_deletion_request('Dunder Mifflin Paper Co')
+ tasks_containing_company = frappe.get_all('Task',
+ filters = {
+ 'company' : 'Dunder Mifflin Paper Co'
+ })
+ self.assertEqual(tasks_containing_company, [])
+
+def create_company(company_name):
+ company = frappe.get_doc({
+ 'doctype': 'Company',
+ 'company_name': company_name,
+ 'default_currency': 'INR'
+ })
+ company.insert(ignore_if_duplicate = True)
+
+def create_transaction_deletion_request(company):
+ tdr = frappe.get_doc({
+ 'doctype': 'Transaction Deletion Record',
+ 'company': company
+ })
+ tdr.insert()
+ tdr.submit()
+ return tdr
+
+
+def create_task(company):
+ task = frappe.get_doc({
+ 'doctype': 'Task',
+ 'company': company,
+ 'subject': 'Delete'
+ })
+ task.insert()
diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js
new file mode 100644
index 0000000..20caa15
--- /dev/null
+++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js
@@ -0,0 +1,40 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Transaction Deletion Record', {
+ onload: function(frm) {
+ if (frm.doc.docstatus == 0) {
+ let doctypes_to_be_ignored_array;
+ frappe.call({
+ method: 'erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.get_doctypes_to_be_ignored',
+ callback: function(r) {
+ doctypes_to_be_ignored_array = r.message;
+ populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm);
+ frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false);
+ frm.refresh_field('doctypes_to_be_ignored');
+ }
+ });
+ }
+
+ frm.get_field('doctypes_to_be_ignored').grid.cannot_add_rows = true;
+ frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false);
+ frm.refresh_field('doctypes_to_be_ignored');
+ },
+
+ refresh: function(frm) {
+ frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false);
+ frm.refresh_field('doctypes_to_be_ignored');
+ }
+
+});
+
+function populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm) {
+ if (!(frm.doc.doctypes_to_be_ignored)) {
+ var i;
+ for (i = 0; i < doctypes_to_be_ignored_array.length; i++) {
+ frm.add_child('doctypes_to_be_ignored', {
+ doctype_name: doctypes_to_be_ignored_array[i]
+ });
+ }
+ }
+}
diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json
new file mode 100644
index 0000000..9313f95
--- /dev/null
+++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json
@@ -0,0 +1,79 @@
+{
+ "actions": [],
+ "autoname": "TDL.####",
+ "creation": "2021-04-06 20:17:18.404716",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "company",
+ "doctypes",
+ "doctypes_to_be_ignored",
+ "amended_from",
+ "status"
+ ],
+ "fields": [
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
+ {
+ "fieldname": "doctypes",
+ "fieldtype": "Table",
+ "label": "Summary",
+ "options": "Transaction Deletion Record Item",
+ "read_only": 1
+ },
+ {
+ "fieldname": "doctypes_to_be_ignored",
+ "fieldtype": "Table",
+ "label": "Excluded DocTypes",
+ "options": "Transaction Deletion Record Item"
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Transaction Deletion Record",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "hidden": 1,
+ "label": "Status",
+ "options": "Draft\nCompleted"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2021-05-08 23:13:48.049879",
+ "modified_by": "Administrator",
+ "module": "Setup",
+ "name": "Transaction Deletion Record",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
new file mode 100644
index 0000000..38f8de7
--- /dev/null
+++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
@@ -0,0 +1,147 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+from frappe.utils import cint
+import frappe
+from frappe.model.document import Document
+from frappe import _
+from frappe.desk.notifications import clear_notifications
+
+class TransactionDeletionRecord(Document):
+ def validate(self):
+ frappe.only_for('System Manager')
+ company_obj = frappe.get_doc('Company', self.company)
+ if frappe.session.user != company_obj.owner and frappe.session.user != 'Administrator':
+ frappe.throw(_('Transactions can only be deleted by the creator of the Company or the Administrator.'),
+ frappe.PermissionError)
+ doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
+ for doctype in self.doctypes_to_be_ignored:
+ if doctype.doctype_name not in doctypes_to_be_ignored_list:
+ frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it. "), title=_("Not Allowed"))
+
+ def before_submit(self):
+ if not self.doctypes_to_be_ignored:
+ self.populate_doctypes_to_be_ignored_table()
+
+ self.delete_bins()
+ self.delete_lead_addresses()
+
+ company_obj = frappe.get_doc('Company', self.company)
+ # reset company values
+ company_obj.total_monthly_sales = 0
+ company_obj.sales_monthly_history = None
+ company_obj.save()
+ # Clear notification counts
+ clear_notifications()
+
+ singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name')
+ tables = frappe.get_all('DocType', filters = {'istable': 1}, pluck = 'name')
+ doctypes_to_be_ignored_list = singles
+ for doctype in self.doctypes_to_be_ignored:
+ doctypes_to_be_ignored_list.append(doctype.doctype_name)
+
+ docfields = frappe.get_all('DocField',
+ filters = {
+ 'fieldtype': 'Link',
+ 'options': 'Company',
+ 'parent': ['not in', doctypes_to_be_ignored_list]},
+ fields=['parent', 'fieldname'])
+
+ for docfield in docfields:
+ if docfield['parent'] != self.doctype:
+ no_of_docs = frappe.db.count(docfield['parent'], {
+ docfield['fieldname'] : self.company
+ })
+
+ if no_of_docs > 0:
+ self.delete_version_log(docfield['parent'], docfield['fieldname'])
+ self.delete_communications(docfield['parent'], docfield['fieldname'])
+
+ # populate DocTypes table
+ if docfield['parent'] not in tables:
+ self.append('doctypes', {
+ 'doctype_name' : docfield['parent'],
+ 'no_of_docs' : no_of_docs
+ })
+
+ # delete the docs linked with the specified company
+ frappe.db.delete(docfield['parent'], {
+ docfield['fieldname'] : self.company
+ })
+
+ naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname')
+ if naming_series:
+ if '#' in naming_series:
+ self.update_naming_series(naming_series, docfield['parent'])
+
+ def populate_doctypes_to_be_ignored_table(self):
+ doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
+ for doctype in doctypes_to_be_ignored_list:
+ self.append('doctypes_to_be_ignored', {
+ 'doctype_name' : doctype
+ })
+
+ def update_naming_series(self, naming_series, doctype_name):
+ if '.' in naming_series:
+ prefix, hashes = naming_series.rsplit('.', 1)
+ else:
+ prefix, hashes = naming_series.rsplit('{', 1)
+ last = frappe.db.sql("""select max(name) from `tab{0}`
+ where name like %s""".format(doctype_name), prefix + '%')
+ if last and last[0][0]:
+ last = cint(last[0][0].replace(prefix, ''))
+ else:
+ last = 0
+
+ frappe.db.sql("""update tabSeries set current = %s where name=%s""", (last, prefix))
+
+ def delete_version_log(self, doctype, company_fieldname):
+ frappe.db.sql("""delete from `tabVersion` where ref_doctype=%s and docname in
+ (select name from `tab{0}` where `{1}`=%s)""".format(doctype,
+ company_fieldname), (doctype, self.company))
+
+ def delete_communications(self, doctype, company_fieldname):
+ reference_docs = frappe.get_all(doctype, filters={company_fieldname:self.company})
+ reference_doc_names = [r.name for r in reference_docs]
+
+ communications = frappe.get_all('Communication', filters={'reference_doctype':doctype,'reference_name':['in', reference_doc_names]})
+ communication_names = [c.name for c in communications]
+
+ frappe.delete_doc('Communication', communication_names, ignore_permissions=True)
+
+ def delete_bins(self):
+ frappe.db.sql("""delete from tabBin where warehouse in
+ (select name from tabWarehouse where company=%s)""", self.company)
+
+ def delete_lead_addresses(self):
+ """Delete addresses to which leads are linked"""
+ leads = frappe.get_all('Lead', filters={'company': self.company})
+ leads = ["'%s'" % row.get("name") for row in leads]
+ addresses = []
+ if leads:
+ addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name
+ in ({leads})""".format(leads=",".join(leads)))
+
+ if addresses:
+ addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
+
+ frappe.db.sql("""delete from tabAddress where name in ({addresses}) and
+ name not in (select distinct dl1.parent from `tabDynamic Link` dl1
+ inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
+ and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses)))
+
+ frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead'
+ and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads)))
+
+ frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)))
+
+@frappe.whitelist()
+def get_doctypes_to_be_ignored():
+ doctypes_to_be_ignored_list = ['Account', 'Cost Center', 'Warehouse', 'Budget',
+ 'Party Account', 'Employee', 'Sales Taxes and Charges Template',
+ 'Purchase Taxes and Charges Template', 'POS Profile', 'BOM',
+ 'Company', 'Bank Account', 'Item Tax Template', 'Mode of Payment',
+ 'Item Default', 'Customer', 'Supplier', 'GST Account']
+ return doctypes_to_be_ignored_list
diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js
new file mode 100644
index 0000000..d7175dd
--- /dev/null
+++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js
@@ -0,0 +1,12 @@
+// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
+// License: GNU General Public License v3. See license.txt
+
+frappe.listview_settings['Transaction Deletion Record'] = {
+ get_indicator: function(doc) {
+ if (doc.docstatus == 0) {
+ return [__("Draft"), "red"];
+ } else {
+ return [__("Completed"), "green"];
+ }
+ }
+};
\ No newline at end of file
diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/__init__.py b/erpnext/setup/doctype/transaction_deletion_record_item/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/setup/doctype/transaction_deletion_record_item/__init__.py
diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json
new file mode 100644
index 0000000..be0be94
--- /dev/null
+++ b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json
@@ -0,0 +1,39 @@
+{
+ "actions": [],
+ "creation": "2021-04-07 07:34:00.124124",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "doctype_name",
+ "no_of_docs"
+ ],
+ "fields": [
+ {
+ "fieldname": "doctype_name",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "DocType",
+ "options": "DocType",
+ "reqd": 1
+ },
+ {
+ "fieldname": "no_of_docs",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Number of Docs"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-05-08 23:10:46.166744",
+ "modified_by": "Administrator",
+ "module": "Setup",
+ "name": "Transaction Deletion Record Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py
new file mode 100644
index 0000000..2176cb1
--- /dev/null
+++ b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class TransactionDeletionRecordItem(Document):
+ pass
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index 82f191d..bbee74c 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -8,9 +8,11 @@
from .default_success_action import get_default_success_action
from frappe import _
from frappe.utils import cint
+from frappe.installer import update_site_config
from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from erpnext.setup.default_energy_point_rules import get_default_energy_point_rules
+from six import iteritems
default_mail_footer = """<div style="padding: 7px; text-align: right; color: #888"><small>Sent via
<a style="color: #888" href="http://erpnext.org">ERPNext</a></div>"""
@@ -29,6 +31,7 @@
add_company_to_session_defaults()
add_standard_navbar_items()
add_app_name()
+ add_non_standard_user_types()
frappe.db.commit()
@@ -36,7 +39,7 @@
if cint(frappe.db.get_single_value('System Settings', 'setup_complete') or 0):
message = """ERPNext can only be installed on a fresh site where the setup wizard is not completed.
You can reinstall this site (after saving your data) using: bench --site [sitename] reinstall"""
- frappe.throw(message)
+ frappe.throw(message) # nosemgrep
def set_single_defaults():
@@ -164,3 +167,81 @@
def add_app_name():
frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext')
+
+def add_non_standard_user_types():
+ user_types = get_user_types_data()
+
+ user_type_limit = {}
+ for user_type, data in iteritems(user_types):
+ user_type_limit.setdefault(frappe.scrub(user_type), 10)
+
+ update_site_config('user_type_doctype_limit', user_type_limit)
+
+ for user_type, data in iteritems(user_types):
+ create_custom_role(data)
+ create_user_type(user_type, data)
+
+def get_user_types_data():
+ return {
+ 'Employee Self Service': {
+ 'role': 'Employee Self Service',
+ 'apply_user_permission_on': 'Employee',
+ 'user_id_field': 'user_id',
+ 'doctypes': {
+ 'Salary Slip': ['read'],
+ 'Employee': ['read', 'write'],
+ 'Expense Claim': ['read', 'write', 'create', 'delete'],
+ 'Leave Application': ['read', 'write', 'create', 'delete'],
+ 'Attendance Request': ['read', 'write', 'create', 'delete'],
+ 'Compensatory Leave Request': ['read', 'write', 'create', 'delete'],
+ 'Employee Tax Exemption Declaration': ['read', 'write', 'create', 'delete'],
+ 'Employee Tax Exemption Proof Submission': ['read', 'write', 'create', 'delete'],
+ 'Timesheet': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend']
+ }
+ }
+ }
+
+def create_custom_role(data):
+ if data.get('role') and not frappe.db.exists('Role', data.get('role')):
+ frappe.get_doc({
+ 'doctype': 'Role',
+ 'role_name': data.get('role'),
+ 'desk_access': 1,
+ 'is_custom': 1
+ }).insert(ignore_permissions=True)
+
+def create_user_type(user_type, data):
+ if frappe.db.exists('User Type', user_type):
+ doc = frappe.get_cached_doc('User Type', user_type)
+ doc.user_doctypes = []
+ else:
+ doc = frappe.new_doc('User Type')
+ doc.update({
+ 'name': user_type,
+ 'role': data.get('role'),
+ 'user_id_field': data.get('user_id_field'),
+ 'apply_user_permission_on': data.get('apply_user_permission_on')
+ })
+
+ create_role_permissions_for_doctype(doc, data)
+ doc.save(ignore_permissions=True)
+
+def create_role_permissions_for_doctype(doc, data):
+ for doctype, perms in iteritems(data.get('doctypes')):
+ args = {'document_type': doctype}
+ for perm in perms:
+ args[perm] = 1
+
+ doc.append('user_doctypes', args)
+
+def update_select_perm_after_install():
+ if not frappe.flags.update_select_perm_after_migrate:
+ return
+
+ frappe.flags.ignore_select_perm = False
+ for row in frappe.get_all('User Type', filters= {'is_standard': 0}):
+ print('Updating user type :- ', row.name)
+ doc = frappe.get_doc('User Type', row.name)
+ doc.save()
+
+ frappe.flags.update_select_perm_after_migrate = False
diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json
index beddaee..5876488 100644
--- a/erpnext/setup/setup_wizard/data/country_wise_tax.json
+++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json
@@ -481,14 +481,250 @@
},
"Germany": {
- "Germany VAT 19%": {
- "account_name": "VAT 19%",
- "tax_rate": 19.00,
- "default": 1
- },
- "Germany VAT 7%": {
- "account_name": "VAT 7%",
- "tax_rate": 7.00
+ "chart_of_accounts": {
+ "SKR04 mit Kontonummern": {
+ "sales_tax_templates": [
+ {
+ "title": "Umsatzsteuer 19%",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "Umsatzsteuer 19%",
+ "account_number": "3806",
+ "tax_rate": 19.00
+ }
+ }
+ ]
+ },
+ {
+ "title": "Umsatzsteuer 7%",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "Umsatzsteuer 7%",
+ "account_number": "3801",
+ "tax_rate": 7.00
+ }
+ }
+ ]
+ }
+ ],
+ "purchase_tax_templates": [
+ {
+ "title": "Abziehbare Vorsteuer 19%",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "Abziehbare Vorsteuer 19%",
+ "account_number": "1406",
+ "root_type": "Asset",
+ "tax_rate": 19.00
+ }
+ }
+ ]
+ },
+ {
+ "title": "Abziehbare Vorsteuer 7%",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "Abziehbare Vorsteuer 7%",
+ "account_number": "1401",
+ "root_type": "Asset",
+ "tax_rate": 7.00
+ }
+ }
+ ]
+ },
+ {
+ "title": "Innergemeinschaftlicher Erwerb 19% Umsatzsteuer und 19% Vorsteuer",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "Abziehbare Vorsteuer nach § 13b UStG 19%",
+ "account_number": "1407",
+ "root_type": "Asset",
+ "tax_rate": 19.00
+ },
+ "add_deduct_tax": "Add"
+ },
+ {
+ "account_head": {
+ "account_name": "Umsatzsteuer nach § 13b UStG 19%",
+ "account_number": "3837",
+ "root_type": "Liability",
+ "tax_rate": 19.00
+ },
+ "add_deduct_tax": "Deduct"
+ }
+ ]
+ }
+ ]
+ },
+ "SKR03 mit Kontonummern": {
+ "sales_tax_templates": [
+ {
+ "title": "Umsatzsteuer 19%",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "Umsatzsteuer 19%",
+ "account_number": "1776",
+ "tax_rate": 19.00
+ }
+ }
+ ]
+ },
+ {
+ "title": "Umsatzsteuer 7%",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "Umsatzsteuer 7%",
+ "account_number": "1771",
+ "tax_rate": 7.00
+ }
+ }
+ ]
+ }
+ ],
+ "purchase_tax_templates": [
+ {
+ "title": "Abziehbare Vorsteuer 19%",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "Abziehbare Vorsteuer 19%",
+ "account_number": "1576",
+ "root_type": "Asset",
+ "tax_rate": 19.00
+ }
+ }
+ ]
+ },
+ {
+ "title": "Abziehbare Vorsteuer 7%",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "Abziehbare Vorsteuer 7%",
+ "account_number": "1571",
+ "root_type": "Asset",
+ "tax_rate": 7.00
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "Standard with Numbers": {
+ "sales_tax_templates": [
+ {
+ "title": "Umsatzsteuer 19%",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "Umsatzsteuer 19%",
+ "account_number": "2301",
+ "tax_rate": 19.00
+ }
+ }
+ ]
+ },
+ {
+ "title": "Umsatzsteuer 7%",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "Umsatzsteuer 7%",
+ "account_number": "2302",
+ "tax_rate": 7.00
+ }
+ }
+ ]
+ }
+ ],
+ "purchase_tax_templates": [
+ {
+ "title": "Abziehbare Vorsteuer 19%",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "Abziehbare Vorsteuer 19%",
+ "account_number": "1501",
+ "root_type": "Asset",
+ "tax_rate": 19.00
+ }
+ }
+ ]
+ },
+ {
+ "title": "Abziehbare Vorsteuer 7%",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "Abziehbare Vorsteuer 7%",
+ "account_number": "1502",
+ "root_type": "Asset",
+ "tax_rate": 7.00
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "*": {
+ "sales_tax_templates": [
+ {
+ "title": "Umsatzsteuer 19%",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "Umsatzsteuer 19%",
+ "tax_rate": 19.00
+ }
+ }
+ ]
+ },
+ {
+ "title": "Umsatzsteuer 7%",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "Umsatzsteuer 7%",
+ "tax_rate": 7.00
+ }
+ }
+ ]
+ }
+ ],
+ "purchase_tax_templates": [
+ {
+ "title": "Abziehbare Vorsteuer 19%",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "Abziehbare Vorsteuer 19%",
+ "tax_rate": 19.00,
+ "root_type": "Asset"
+ }
+ }
+ ]
+ },
+ {
+ "title": "Abziehbare Vorsteuer 7%",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "Abziehbare Vorsteuer 7%",
+ "root_type": "Asset",
+ "tax_rate": 7.00
+ }
+ }
+ ]
+ }
+ ]
+ }
}
},
@@ -580,26 +816,135 @@
},
"India": {
- "In State GST": {
- "account_name": ["SGST", "CGST"],
- "tax_rate": [9.00, 9.00],
- "default": 1
- },
- "Out of State GST": {
- "account_name": "IGST",
- "tax_rate": 18.00
- },
- "VAT 5%": {
- "account_name": "VAT 5%",
- "tax_rate": 5.00
- },
- "VAT 4%": {
- "account_name": "VAT 4%",
- "tax_rate": 4.00
- },
- "VAT 14%": {
- "account_name": "VAT 14%",
- "tax_rate": 14.00
+ "chart_of_accounts": {
+ "*": {
+ "item_tax_templates": [
+ {
+ "title": "In State GST",
+ "taxes": [
+ {
+ "tax_type": {
+ "account_name": "SGST",
+ "tax_rate": 9.00
+ }
+ },
+ {
+ "tax_type": {
+ "account_name": "CGST",
+ "tax_rate": 9.00
+ }
+ }
+ ]
+ },
+ {
+ "title": "Out of State GST",
+ "taxes": [
+ {
+ "tax_type": {
+ "account_name": "IGST",
+ "tax_rate": 18.00
+ }
+ }
+ ]
+ },
+ {
+ "title": "VAT 5%",
+ "taxes": [
+ {
+ "tax_type": {
+ "account_name": "VAT 5%",
+ "tax_rate": 5.00
+ }
+ }
+ ]
+ },
+ {
+ "title": "VAT 4%",
+ "taxes": [
+ {
+ "tax_type": {
+ "account_name": "VAT 4%",
+ "tax_rate": 4.00
+ }
+ }
+ ]
+ },
+ {
+ "title": "VAT 14%",
+ "taxes": [
+ {
+ "tax_type": {
+ "account_name": "VAT 14%",
+ "tax_rate": 14.00
+ }
+ }
+ ]
+ }
+ ],
+ "*": [
+ {
+ "title": "In State GST",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "SGST",
+ "tax_rate": 9.00
+ }
+ },
+ {
+ "account_head": {
+ "account_name": "CGST",
+ "tax_rate": 9.00
+ }
+ }
+ ]
+ },
+ {
+ "title": "Out of State GST",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "IGST",
+ "tax_rate": 18.00
+ }
+ }
+ ]
+ },
+ {
+ "title": "VAT 5%",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "VAT 5%",
+ "tax_rate": 5.00
+ }
+ }
+ ]
+ },
+ {
+ "title": "VAT 4%",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "VAT 4%",
+ "tax_rate": 4.00
+ }
+ }
+ ]
+ },
+ {
+ "title": "VAT 14%",
+ "taxes": [
+ {
+ "account_head": {
+ "account_name": "VAT 14%",
+ "tax_rate": 14.00
+ }
+ }
+ ]
+ }
+ ]
+ }
}
},
diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py
index 5053c6a..5c725d3 100644
--- a/erpnext/setup/setup_wizard/operations/install_fixtures.py
+++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py
@@ -12,6 +12,7 @@
from erpnext.accounts.doctype.account.account import RootNotEditable
from erpnext.regional.address_template.setup import set_up_address_templates
+from frappe.utils.nestedset import rebuild_tree
default_lead_sources = ["Existing Customer", "Reference", "Advertisement",
"Cold Calling", "Exhibition", "Supplier Reference", "Mass Mailing",
@@ -280,13 +281,15 @@
set_more_defaults()
update_global_search_doctypes()
- # path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(country))
- # if os.path.exists(path.encode("utf-8")):
- # frappe.get_attr("erpnext.regional.{0}.setup.setup_company_independent_fixtures".format(frappe.scrub(country)))()
-
-
def set_more_defaults():
# Do more setup stuff that can be done here with no dependencies
+ update_selling_defaults()
+ update_buying_defaults()
+ update_hr_defaults()
+ add_uom_data()
+ update_item_variant_settings()
+
+def update_selling_defaults():
selling_settings = frappe.get_doc("Selling Settings")
selling_settings.set_default_customer_group_and_territory()
selling_settings.cust_master_name = "Customer Name"
@@ -296,13 +299,7 @@
selling_settings.sales_update_frequency = "Each Transaction"
selling_settings.save()
- add_uom_data()
-
- # set no copy fields of an item doctype to item variant settings
- doc = frappe.get_doc('Item Variant Settings')
- doc.set_default_fields()
- doc.save()
-
+def update_buying_defaults():
buying_settings = frappe.get_doc("Buying Settings")
buying_settings.supp_master_name = "Supplier Name"
buying_settings.po_required = "No"
@@ -311,12 +308,19 @@
buying_settings.allow_multiple_items = 1
buying_settings.save()
+def update_hr_defaults():
hr_settings = frappe.get_doc("HR Settings")
hr_settings.emp_created_by = "Naming Series"
hr_settings.leave_approval_notification_template = _("Leave Approval Notification")
hr_settings.leave_status_notification_template = _("Leave Status Notification")
hr_settings.save()
+def update_item_variant_settings():
+ # set no copy fields of an item doctype to item variant settings
+ doc = frappe.get_doc('Item Variant Settings')
+ doc.set_default_fields()
+ doc.save()
+
def add_uom_data():
# add UOMs
uoms = json.loads(open(frappe.get_app_path("erpnext", "setup", "setup_wizard", "data", "uom_data.json")).read())
@@ -327,7 +331,7 @@
"uom_name": _(d.get("uom_name")),
"name": _(d.get("uom_name")),
"must_be_whole_number": d.get("must_be_whole_number")
- }).insert(ignore_permissions=True)
+ }).db_insert()
# bootstrap uom conversion factors
uom_conversions = json.loads(open(frappe.get_app_path("erpnext", "setup", "setup_wizard", "data", "uom_conversion_data.json")).read())
@@ -336,7 +340,7 @@
frappe.get_doc({
"doctype": "UOM Category",
"category_name": _(d.get("category"))
- }).insert(ignore_permissions=True)
+ }).db_insert()
if not frappe.db.exists("UOM Conversion Factor", {"from_uom": _(d.get("from_uom")), "to_uom": _(d.get("to_uom"))}):
uom_conversion = frappe.get_doc({
@@ -369,8 +373,8 @@
{"doctype": "Sales Stage", "stage_name": _("Proposal/Price Quote")},
{"doctype": "Sales Stage", "stage_name": _("Negotiation/Review")}
]
-
- make_records(records)
+ for sales_stage in records:
+ frappe.get_doc(sales_stage).db_insert()
def install_company(args):
records = [
@@ -418,7 +422,14 @@
{'doctype': 'Department', 'department_name': _('Legal'), 'parent_department': _('All Departments'), 'company': args.company_name},
]
- make_records(records)
+ # Make root department with NSM updation
+ make_records(records[:1])
+
+ frappe.local.flags.ignore_update_nsm = True
+ make_records(records[1:])
+ frappe.local.flags.ignore_update_nsm = False
+
+ rebuild_tree("Department", "parent_department")
def install_defaults(args=None):
@@ -432,7 +443,15 @@
# enable default currency
frappe.db.set_value("Currency", args.get("currency"), "enabled", 1)
+ frappe.db.set_value("Stock Settings", None, "email_footer_address", args.get("company_name"))
+ set_global_defaults(args)
+ set_active_domains(args)
+ update_stock_settings()
+ update_shopping_cart_settings(args)
+ create_bank_account(args)
+
+def set_global_defaults(args):
global_defaults = frappe.get_doc("Global Defaults", "Global Defaults")
current_fiscal_year = frappe.get_all("Fiscal Year")[0]
@@ -445,13 +464,10 @@
global_defaults.save()
- system_settings = frappe.get_doc("System Settings")
- system_settings.email_footer_address = args.get("company_name")
- system_settings.save()
+def set_active_domains(args):
+ frappe.get_single('Domain Settings').set_active_domains(args.get('domains'))
- domain_settings = frappe.get_single('Domain Settings')
- domain_settings.set_active_domains(args.get('domains'))
-
+def update_stock_settings():
stock_settings = frappe.get_doc("Stock Settings")
stock_settings.item_naming_by = "Item Code"
stock_settings.valuation_method = "FIFO"
@@ -463,48 +479,44 @@
stock_settings.set_qty_in_transactions_based_on_serial_no_input = 1
stock_settings.save()
- if args.bank_account:
- company_name = args.company_name
- bank_account_group = frappe.db.get_value("Account",
- {"account_type": "Bank", "is_group": 1, "root_type": "Asset",
- "company": company_name})
- if bank_account_group:
- bank_account = frappe.get_doc({
- "doctype": "Account",
- 'account_name': args.bank_account,
- 'parent_account': bank_account_group,
- 'is_group':0,
- 'company': company_name,
- "account_type": "Bank",
- })
- try:
- doc = bank_account.insert()
+def create_bank_account(args):
+ if not args.bank_account:
+ return
- frappe.db.set_value("Company", args.company_name, "default_bank_account", bank_account.name, update_modified=False)
+ company_name = args.company_name
+ bank_account_group = frappe.db.get_value("Account",
+ {"account_type": "Bank", "is_group": 1, "root_type": "Asset",
+ "company": company_name})
+ if bank_account_group:
+ bank_account = frappe.get_doc({
+ "doctype": "Account",
+ 'account_name': args.bank_account,
+ 'parent_account': bank_account_group,
+ 'is_group':0,
+ 'company': company_name,
+ "account_type": "Bank",
+ })
+ try:
+ doc = bank_account.insert()
- except RootNotEditable:
- frappe.throw(_("Bank account cannot be named as {0}").format(args.bank_account))
- except frappe.DuplicateEntryError:
- # bank account same as a CoA entry
- pass
+ frappe.db.set_value("Company", args.company_name, "default_bank_account", bank_account.name, update_modified=False)
- # Now, with fixtures out of the way, onto concrete stuff
- records = [
+ except RootNotEditable:
+ frappe.throw(_("Bank account cannot be named as {0}").format(args.bank_account))
+ except frappe.DuplicateEntryError:
+ # bank account same as a CoA entry
+ pass
- # Shopping cart: needs price lists
- {
- "doctype": "Shopping Cart Settings",
- "enabled": 1,
- 'company': args.company_name,
- # uh oh
- 'price_list': frappe.db.get_value("Price List", {"selling": 1}),
- 'default_customer_group': _("Individual"),
- 'quotation_series': "QTN-",
- },
- ]
-
- make_records(records)
-
+def update_shopping_cart_settings(args):
+ shopping_cart = frappe.get_doc("Shopping Cart Settings")
+ shopping_cart.update({
+ "enabled": 1,
+ 'company': args.company_name,
+ 'price_list': frappe.db.get_value("Price List", {"selling": 1}),
+ 'default_customer_group': _("Individual"),
+ 'quotation_series': "QTN-",
+ })
+ shopping_cart.update_single(shopping_cart.get_valid_dict())
def get_fy_details(fy_start_date, fy_end_date):
start_year = getdate(fy_start_date).year
diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py
index c3c1593..429a558 100644
--- a/erpnext/setup/setup_wizard/operations/taxes_setup.py
+++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py
@@ -1,123 +1,232 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
-import frappe, copy, os, json
-from frappe.utils import flt
-from erpnext.accounts.doctype.account.account import RootNotEditable
-def create_sales_tax(args):
- country_wise_tax = get_country_wise_tax(args.get("country"))
- if country_wise_tax and len(country_wise_tax) > 0:
- for sales_tax, tax_data in country_wise_tax.items():
- make_tax_account_and_template(
- args.get("company_name"),
- tax_data.get('account_name'),
- tax_data.get('tax_rate'), sales_tax)
+import os
+import json
-def make_tax_account_and_template(company, account_name, tax_rate, template_name=None):
- if not isinstance(account_name, (list, tuple)):
- account_name = [account_name]
- tax_rate = [tax_rate]
+import frappe
+from frappe import _
- accounts = []
- for i, name in enumerate(account_name):
- tax_account = make_tax_account(company, account_name[i], tax_rate[i])
- if tax_account:
- accounts.append(tax_account)
- try:
- if accounts:
- make_sales_and_purchase_tax_templates(accounts, template_name)
- make_item_tax_templates(accounts, template_name)
- except frappe.NameError:
- if frappe.message_log: frappe.message_log.pop()
- except RootNotEditable:
- pass
+def setup_taxes_and_charges(company_name: str, country: str):
+ file_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'country_wise_tax.json')
+ with open(file_path, 'r') as json_file:
+ tax_data = json.load(json_file)
-def make_tax_account(company, account_name, tax_rate):
- tax_group = get_tax_account_group(company)
- if tax_group:
- try:
- return frappe.get_doc({
- "doctype":"Account",
- "company": company,
- "parent_account": tax_group,
- "account_name": account_name,
- "is_group": 0,
- "report_type": "Balance Sheet",
- "root_type": "Liability",
- "account_type": "Tax",
- "tax_rate": flt(tax_rate) if tax_rate else None
- }).insert(ignore_permissions=True, ignore_mandatory=True)
- except frappe.NameError:
- if frappe.message_log: frappe.message_log.pop()
- abbr = frappe.get_cached_value('Company', company, 'abbr')
- account = '{0} - {1}'.format(account_name, abbr)
- return frappe.get_doc('Account', account)
+ country_wise_tax = tax_data.get(country)
-def make_sales_and_purchase_tax_templates(accounts, template_name=None):
- if not template_name:
- template_name = accounts[0].name
+ if not country_wise_tax:
+ return
- sales_tax_template = {
- "doctype": "Sales Taxes and Charges Template",
- "title": template_name,
- "company": accounts[0].company,
- 'taxes': []
+ if 'chart_of_accounts' not in country_wise_tax:
+ country_wise_tax = simple_to_detailed(country_wise_tax)
+
+ from_detailed_data(company_name, country_wise_tax.get('chart_of_accounts'))
+
+
+def simple_to_detailed(templates):
+ """
+ Convert a simple taxes object into a more detailed data structure.
+
+ Example input:
+
+ {
+ "France VAT 20%": {
+ "account_name": "VAT 20%",
+ "tax_rate": 20,
+ "default": 1
+ },
+ "France VAT 10%": {
+ "account_name": "VAT 10%",
+ "tax_rate": 10
+ }
}
-
- for account in accounts:
- sales_tax_template['taxes'].append({
- "category": "Total",
- "charge_type": "On Net Total",
- "account_head": account.name,
- "description": "{0} @ {1}".format(account.account_name, account.tax_rate),
- "rate": account.tax_rate
- })
- # Sales
- frappe.get_doc(copy.deepcopy(sales_tax_template)).insert(ignore_permissions=True)
-
- # Purchase
- purchase_tax_template = copy.deepcopy(sales_tax_template)
- purchase_tax_template["doctype"] = "Purchase Taxes and Charges Template"
-
- doc = frappe.get_doc(purchase_tax_template)
- doc.insert(ignore_permissions=True)
-
-def make_item_tax_templates(accounts, template_name=None):
- if not template_name:
- template_name = accounts[0].name
-
- item_tax_template = {
- "doctype": "Item Tax Template",
- "title": template_name,
- "company": accounts[0].company,
- 'taxes': []
+ """
+ return {
+ 'chart_of_accounts': {
+ '*': {
+ 'item_tax_templates': [{
+ 'title': title,
+ 'taxes': [{
+ 'tax_type': {
+ 'account_name': data.get('account_name'),
+ 'tax_rate': data.get('tax_rate')
+ }
+ }]
+ } for title, data in templates.items()],
+ '*': [{
+ 'title': title,
+ 'is_default': data.get('default', 0),
+ 'taxes': [{
+ 'account_head': {
+ 'account_name': data.get('account_name'),
+ 'tax_rate': data.get('tax_rate')
+ }
+ }]
+ } for title, data in templates.items()]
+ }
+ }
}
- for account in accounts:
- item_tax_template['taxes'].append({
- "tax_type": account.name,
- "tax_rate": account.tax_rate
- })
+def from_detailed_data(company_name, data):
+ """Create Taxes and Charges Templates from detailed data."""
+ coa_name = frappe.db.get_value('Company', company_name, 'chart_of_accounts')
+ tax_templates = data.get(coa_name) or data.get('*')
+ sales_tax_templates = tax_templates.get('sales_tax_templates') or tax_templates.get('*')
+ purchase_tax_templates = tax_templates.get('purchase_tax_templates') or tax_templates.get('*')
+ item_tax_templates = tax_templates.get('item_tax_templates') or tax_templates.get('*')
- # Items
- frappe.get_doc(copy.deepcopy(item_tax_template)).insert(ignore_permissions=True)
+ if sales_tax_templates:
+ for template in sales_tax_templates:
+ make_taxes_and_charges_template(company_name, 'Sales Taxes and Charges Template', template)
-def get_tax_account_group(company):
- tax_group = frappe.db.get_value("Account",
- {"account_name": "Duties and Taxes", "is_group": 1, "company": company})
- if not tax_group:
- tax_group = frappe.db.get_value("Account", {"is_group": 1, "root_type": "Liability",
- "account_type": "Tax", "company": company})
+ if purchase_tax_templates:
+ for template in purchase_tax_templates:
+ make_taxes_and_charges_template(company_name, 'Purchase Taxes and Charges Template', template)
- return tax_group
+ if item_tax_templates:
+ for template in item_tax_templates:
+ make_item_tax_template(company_name, template)
-def get_country_wise_tax(country):
- data = {}
- with open (os.path.join(os.path.dirname(__file__), "..", "data", "country_wise_tax.json")) as countrywise_tax:
- data = json.load(countrywise_tax).get(country)
- return data
+def make_taxes_and_charges_template(company_name, doctype, template):
+ template['company'] = company_name
+ template['doctype'] = doctype
+
+ if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}):
+ return
+
+ for tax_row in template.get('taxes'):
+ account_data = tax_row.get('account_head')
+ tax_row_defaults = {
+ 'category': 'Total',
+ 'charge_type': 'On Net Total'
+ }
+
+ # if account_head is a dict, search or create the account and get it's name
+ if isinstance(account_data, dict):
+ tax_row_defaults['description'] = '{0} @ {1}'.format(account_data.get('account_name'), account_data.get('tax_rate'))
+ tax_row_defaults['rate'] = account_data.get('tax_rate')
+ account = get_or_create_account(company_name, account_data)
+ tax_row['account_head'] = account.name
+
+ # use the default value if nothing other is specified
+ for fieldname, default_value in tax_row_defaults.items():
+ if fieldname not in tax_row:
+ tax_row[fieldname] = default_value
+
+ return frappe.get_doc(template).insert(ignore_permissions=True)
+
+
+def make_item_tax_template(company_name, template):
+ """Create an Item Tax Template.
+
+ This requires a separate method because Item Tax Template is structured
+ differently from Sales and Purchase Tax Templates.
+ """
+ doctype = 'Item Tax Template'
+ template['company'] = company_name
+ template['doctype'] = doctype
+
+ if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}):
+ return
+
+ for tax_row in template.get('taxes'):
+ account_data = tax_row.get('tax_type')
+
+ # if tax_type is a dict, search or create the account and get it's name
+ if isinstance(account_data, dict):
+ account = get_or_create_account(company_name, account_data)
+ tax_row['tax_type'] = account.name
+ if 'tax_rate' not in tax_row:
+ tax_row['tax_rate'] = account_data.get('tax_rate')
+
+ return frappe.get_doc(template).insert(ignore_permissions=True)
+
+
+def get_or_create_account(company_name, account):
+ """
+ Check if account already exists. If not, create it.
+ Return a tax account or None.
+ """
+ default_root_type = 'Liability'
+ root_type = account.get('root_type', default_root_type)
+
+ existing_accounts = frappe.get_list('Account',
+ filters={
+ 'company': company_name,
+ 'root_type': root_type
+ },
+ or_filters={
+ 'account_name': account.get('account_name'),
+ 'account_number': account.get('account_number')
+ }
+ )
+
+ if existing_accounts:
+ return frappe.get_doc('Account', existing_accounts[0].name)
+
+ tax_group = get_or_create_tax_group(company_name, root_type)
+
+ account['doctype'] = 'Account'
+ account['company'] = company_name
+ account['parent_account'] = tax_group
+ account['report_type'] = 'Balance Sheet'
+ account['account_type'] = 'Tax'
+ account['root_type'] = root_type
+ account['is_group'] = 0
+
+ return frappe.get_doc(account).insert(ignore_permissions=True, ignore_mandatory=True)
+
+
+def get_or_create_tax_group(company_name, root_type):
+ # Look for a group account of type 'Tax'
+ tax_group_name = frappe.db.get_value('Account', {
+ 'is_group': 1,
+ 'root_type': root_type,
+ 'account_type': 'Tax',
+ 'company': company_name
+ })
+
+ if tax_group_name:
+ return tax_group_name
+
+ # Look for a group account named 'Duties and Taxes' or 'Tax Assets'
+ account_name = _('Duties and Taxes') if root_type == 'Liability' else _('Tax Assets')
+ tax_group_name = frappe.db.get_value('Account', {
+ 'is_group': 1,
+ 'root_type': root_type,
+ 'account_name': account_name,
+ 'company': company_name
+ })
+
+ if tax_group_name:
+ return tax_group_name
+
+ # Create a new group account named 'Duties and Taxes' or 'Tax Assets' just
+ # below the root account
+ root_account = frappe.get_list('Account', {
+ 'is_group': 1,
+ 'root_type': root_type,
+ 'company': company_name,
+ 'report_type': 'Balance Sheet',
+ 'parent_account': ('is', 'not set')
+ }, limit=1)[0]
+
+ tax_group_account = frappe.get_doc({
+ 'doctype': 'Account',
+ 'company': company_name,
+ 'is_group': 1,
+ 'report_type': 'Balance Sheet',
+ 'root_type': root_type,
+ 'account_type': 'Tax',
+ 'account_name': account_name,
+ 'parent_account': root_account.name
+ }).insert(ignore_permissions=True)
+
+ tax_group_name = tax_group_account.name
+
+ return tax_group_name
diff --git a/erpnext/setup/setup_wizard/setup_wizard.py b/erpnext/setup/setup_wizard/setup_wizard.py
index e74d837..f63d269 100644
--- a/erpnext/setup/setup_wizard/setup_wizard.py
+++ b/erpnext/setup/setup_wizard/setup_wizard.py
@@ -52,11 +52,6 @@
'fail_msg': 'Failed to set defaults',
'tasks': [
{
- 'fn': setup_post_company_fixtures,
- 'args': args,
- 'fail_msg': _("Failed to setup post company fixtures")
- },
- {
'fn': setup_defaults,
'args': args,
'fail_msg': _("Failed to setup defaults")
@@ -94,9 +89,6 @@
def setup_company(args):
fixtures.install_company(args)
-def setup_post_company_fixtures(args):
- fixtures.install_post_company_fixtures(args)
-
def setup_defaults(args):
fixtures.install_defaults(frappe._dict(args))
@@ -129,7 +121,6 @@
def setup_complete(args=None):
stage_fixtures(args)
setup_company(args)
- setup_post_company_fixtures(args)
setup_defaults(args)
stage_four(args)
fin(args)
diff --git a/erpnext/setup/setup_wizard/utils.py b/erpnext/setup/setup_wizard/utils.py
index e82bc96..4223f00 100644
--- a/erpnext/setup/setup_wizard/utils.py
+++ b/erpnext/setup/setup_wizard/utils.py
@@ -9,5 +9,4 @@
'data', 'test_mfg.json'), 'r') as f:
data = json.loads(f.read())
- #setup_wizard.create_sales_tax(data)
setup_complete(data)
diff --git a/erpnext/setup/workspace/home/home.json b/erpnext/setup/workspace/home/home.json
index 305456b..1576d5a 100644
--- a/erpnext/setup/workspace/home/home.json
+++ b/erpnext/setup/workspace/home/home.json
@@ -248,177 +248,9 @@
"link_type": "DocType",
"onboard": 1,
"type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
- "label": "Healthcare",
- "onboard": 0,
- "type": "Card Break"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Patient",
- "link_to": "Patient",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Diagnosis",
- "link_to": "Diagnosis",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
- "label": "Education",
- "onboard": 0,
- "type": "Card Break"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Student",
- "link_to": "Student",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Instructor",
- "link_to": "Instructor",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Course",
- "link_to": "Course",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Room",
- "link_to": "Room",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
- "label": "Non Profit",
- "onboard": 0,
- "type": "Card Break"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Donor",
- "link_to": "Donor",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Member",
- "link_to": "Member",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Volunteer",
- "link_to": "Volunteer",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Chapter",
- "link_to": "Chapter",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
- "label": "Agriculture",
- "onboard": 0,
- "type": "Card Break"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Location",
- "link_to": "Location",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Crop",
- "link_to": "Crop",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Crop Cycle",
- "link_to": "Crop Cycle",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Fertilizer",
- "link_to": "Fertilizer",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
}
],
- "modified": "2021-03-16 15:59:58.416154",
+ "modified": "2021-04-19 15:48:44.089927",
"modified_by": "Administrator",
"module": "Setup",
"name": "Home",
diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py
index 681d161..56afe95 100644
--- a/erpnext/shopping_cart/cart.py
+++ b/erpnext/shopping_cart/cart.py
@@ -112,9 +112,7 @@
def request_for_quotation():
quotation = _get_cart_quotation()
quotation.flags.ignore_permissions = True
- quotation.save()
- if not get_shopping_cart_settings().save_quotations_as_draft:
- quotation.submit()
+ quotation.submit()
return quotation.name
@frappe.whitelist()
@@ -232,12 +230,12 @@
if address_type.lower() == "billing":
quotation.customer_address = address_name
quotation.address_display = address_display
- quotation.shipping_address_name == quotation.shipping_address_name or address_name
+ quotation.shipping_address_name = quotation.shipping_address_name or address_name
address_doc = next((doc for doc in get_billing_addresses() if doc["name"] == address_name), None)
elif address_type.lower() == "shipping":
quotation.shipping_address_name = address_name
quotation.shipping_address = address_display
- quotation.customer_address == quotation.customer_address or address_name
+ quotation.customer_address = quotation.customer_address or address_name
address_doc = next((doc for doc in get_shipping_addresses() if doc["name"] == address_name), None)
apply_cart_settings(quotation=quotation)
diff --git a/erpnext/shopping_cart/test_shopping_cart.py b/erpnext/shopping_cart/test_shopping_cart.py
index d857bf5..ac61aeb 100644
--- a/erpnext/shopping_cart/test_shopping_cart.py
+++ b/erpnext/shopping_cart/test_shopping_cart.py
@@ -7,7 +7,7 @@
from frappe.utils import nowdate, add_months
from erpnext.shopping_cart.cart import _get_cart_quotation, update_cart, get_party
from erpnext.tests.utils import create_test_contact_and_address
-
+from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule
# test_dependencies = ['Payment Terms Template']
@@ -125,7 +125,7 @@
tax_rule = frappe.get_test_records("Tax Rule")[0]
try:
frappe.get_doc(tax_rule).insert()
- except frappe.DuplicateEntryError:
+ except (frappe.DuplicateEntryError, ConflictingTaxRule):
pass
def create_quotation(self):
diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js
index 933ca8a..a657ecf 100644
--- a/erpnext/stock/dashboard/item_dashboard.js
+++ b/erpnext/stock/dashboard/item_dashboard.js
@@ -268,7 +268,9 @@
frappe.call({
method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry',
args: values,
+ btn: dialog.get_primary_btn(),
freeze: true,
+ freeze_message: __('Creating Stock Entry'),
callback: function (r) {
frappe.show_alert(__('Stock Entry {0} created',
['<a href="/app/stock-entry/' + r.message.name + '">' + r.message.name + '</a>']));
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index 334bdea..7875b9c 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -273,11 +273,11 @@
},
items_on_form_rendered: function(doc, grid_row) {
- erpnext.setup_serial_no();
+ erpnext.setup_serial_or_batch_no();
},
packed_items_on_form_rendered: function(doc, grid_row) {
- erpnext.setup_serial_no();
+ erpnext.setup_serial_or_batch_no();
},
close_delivery_note: function(doc){
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index f595aad..280fde1 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -99,6 +99,7 @@
"rounding_adjustment",
"rounded_total",
"in_words",
+ "disable_rounded_total",
"terms_section_break",
"tc_name",
"terms",
@@ -768,6 +769,7 @@
"width": "150px"
},
{
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "base_rounding_adjustment",
"fieldtype": "Currency",
"label": "Rounding Adjustment (Company Currency)",
@@ -777,6 +779,7 @@
"read_only": 1
},
{
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "base_rounded_total",
"fieldtype": "Currency",
"label": "Rounded Total (Company Currency)",
@@ -819,6 +822,7 @@
"width": "150px"
},
{
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "rounding_adjustment",
"fieldtype": "Currency",
"label": "Rounding Adjustment",
@@ -829,6 +833,7 @@
},
{
"bold": 1,
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "rounded_total",
"fieldtype": "Currency",
"label": "Rounded Total",
@@ -1271,13 +1276,20 @@
"label": "Represents Company",
"options": "Company",
"read_only": 1
+ },
+ {
+ "default": "0",
+ "depends_on": "grand_total",
+ "fieldname": "disable_rounded_total",
+ "fieldtype": "Check",
+ "label": "Disable Rounded Total"
}
],
"icon": "fa fa-truck",
"idx": 146,
"is_submittable": 1,
"links": [],
- "modified": "2020-12-26 17:07:59.194403",
+ "modified": "2021-04-15 23:55:49.620641",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index d326a04..cce51cb 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -732,7 +732,8 @@
"doctype": target_doctype,
"postprocess": update_details,
"field_no_map": [
- "taxes_and_charges"
+ "taxes_and_charges",
+ "set_warehouse"
]
},
doctype +" Item": {
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index d39b229..0c63df0 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -710,7 +710,7 @@
dn1.submit()
si = make_sales_invoice(dn.name)
- self.assertEquals(si.items[0].qty, 1)
+ self.assertEqual(si.items[0].qty, 1)
def test_make_sales_invoice_from_dn_with_returned_qty_duplicate_items(self):
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
@@ -738,8 +738,8 @@
dn1.submit()
si2 = make_sales_invoice(dn.name)
- self.assertEquals(si2.items[0].qty, 2)
- self.assertEquals(si2.items[1].qty, 1)
+ self.assertEqual(si2.items[0].qty, 2)
+ self.assertEqual(si2.items[1].qty, 1)
def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.js b/erpnext/stock/doctype/delivery_trip/delivery_trip.js
index a6fbb66..68cba29 100755
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.js
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.js
@@ -41,6 +41,15 @@
},
refresh: function (frm) {
+ if (frm.doc.docstatus == 1 && frm.doc.employee) {
+ frm.add_custom_button(__('Expense Claim'), function() {
+ frappe.model.open_mapped_doc({
+ method: 'erpnext.stock.doctype.delivery_trip.delivery_trip.make_expense_claim',
+ frm: cur_frm,
+ });
+ }, __("Create"));
+ }
+
if (frm.doc.docstatus == 1 && frm.doc.delivery_stops.length > 0) {
frm.add_custom_button(__("Notify Customers via Email"), function () {
frm.trigger('notify_customers');
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.json b/erpnext/stock/doctype/delivery_trip/delivery_trip.json
index 879901f..11b71c2 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.json
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.json
@@ -21,6 +21,7 @@
"column_break_4",
"vehicle",
"departure_time",
+ "employee",
"delivery_service_stops",
"delivery_stops",
"calculate_arrival_time",
@@ -176,11 +177,19 @@
"fieldtype": "Data",
"label": "Driver Email",
"read_only": 1
+ },
+ {
+ "fetch_from": "driver.employee",
+ "fieldname": "employee",
+ "fieldtype": "Link",
+ "label": "Employee",
+ "options": "Employee",
+ "read_only": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-01-26 22:37:14.824021",
+ "modified": "2021-04-30 21:21:36.610142",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Trip",
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
index de85bc3..81e7301 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
@@ -11,6 +11,7 @@
from frappe.contacts.doctype.address.address import get_address_display
from frappe.model.document import Document
from frappe.utils import cint, get_datetime, get_link_to_form
+from frappe.model.mapper import get_mapped_doc
class DeliveryTrip(Document):
@@ -394,3 +395,15 @@
employee = frappe.db.get_value("Driver", driver, "employee")
email = frappe.db.get_value("Employee", employee, "prefered_email")
return {"email": email}
+
+@frappe.whitelist()
+def make_expense_claim(source_name, target_doc=None):
+ doc = get_mapped_doc("Delivery Trip", source_name,
+ {"Delivery Trip": {
+ "doctype": "Expense Claim",
+ "field_map": {
+ "name" : "delivery_trip"
+ }
+ }}, target_doc)
+
+ return doc
\ No newline at end of file
diff --git a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py
index eeea6da..1e71603 100644
--- a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py
+++ b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py
@@ -7,7 +7,7 @@
import erpnext
import frappe
-from erpnext.stock.doctype.delivery_trip.delivery_trip import get_contact_and_address, notify_customers
+from erpnext.stock.doctype.delivery_trip.delivery_trip import get_contact_and_address, notify_customers, make_expense_claim
from erpnext.tests.utils import create_test_contact_and_address
from frappe.utils import add_days, flt, now_datetime, nowdate
@@ -28,6 +28,10 @@
frappe.db.sql("delete from `tabEmail Template`")
frappe.db.sql("delete from `tabDelivery Trip`")
+ def test_expense_claim_fields_are_fetched_properly(self):
+ expense_claim = make_expense_claim(self.delivery_trip.name)
+ self.assertEqual(self.delivery_trip.name, expense_claim.delivery_trip)
+
def test_delivery_trip_notify_customers(self):
notify_customers(delivery_trip=self.delivery_trip.name)
self.delivery_trip.load_from_db()
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 2079cf8..8aec893 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -46,9 +46,6 @@
}, __("View"));
}
- if (!frm.doc.is_fixed_asset) {
- erpnext.item.make_dashboard(frm);
- }
if (frm.doc.is_fixed_asset) {
frm.trigger('is_fixed_asset');
@@ -96,6 +93,10 @@
erpnext.item.edit_prices_button(frm);
erpnext.item.toggle_attributes(frm);
+
+ if (!frm.doc.is_fixed_asset) {
+ erpnext.item.make_dashboard(frm);
+ }
frm.add_custom_button(__('Duplicate'), function() {
var new_item = frappe.model.copy_doc(frm.doc);
@@ -473,11 +474,15 @@
me.multiple_variant_dialog.get_primary_btn().html(__('Create Variants'));
me.multiple_variant_dialog.disable_primary_action();
} else {
+
let no_of_combinations = lengths.reduce((a, b) => a * b, 1);
- me.multiple_variant_dialog.get_primary_btn()
- .html(__(
- `Make ${no_of_combinations} Variant${no_of_combinations === 1 ? '' : 's'}`
- ));
+ let msg;
+ if (no_of_combinations === 1) {
+ msg = __("Make {0} Variant", [no_of_combinations]);
+ } else {
+ msg = __("Make {0} Variants", [no_of_combinations]);
+ }
+ me.multiple_variant_dialog.get_primary_btn().html(msg);
me.multiple_variant_dialog.enable_primary_action();
}
}
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 7cb84a6..dbac794 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -63,7 +63,7 @@
if self.variant_of:
if not self.item_code:
template_item_name = frappe.db.get_value("Item", self.variant_of, "item_name")
- self.item_code = make_variant_item_code(self.variant_of, template_item_name, self)
+ make_variant_item_code(self.variant_of, template_item_name, self)
else:
from frappe.model.naming import set_name_by_naming_series
set_name_by_naming_series(self)
@@ -674,10 +674,10 @@
if not records: return
document = _("Stock Reconciliation") if len(records) == 1 else _("Stock Reconciliations")
- msg = _("The items {0} and {1} are present in the following {2} : ").format(
+ msg = _("The items {0} and {1} are present in the following {2} :").format(
frappe.bold(old_name), frappe.bold(new_name), document)
- msg += '<br>'
+ msg += ' <br>'
msg += ', '.join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "<br><br>"
msg += _("Note: To merge the items, create a separate Stock Reconciliation for the old item {0}").format(
diff --git a/erpnext/stock/doctype/item/test_records.json b/erpnext/stock/doctype/item/test_records.json
index c1f20a4..6cec852 100644
--- a/erpnext/stock/doctype/item/test_records.json
+++ b/erpnext/stock/doctype/item/test_records.json
@@ -59,6 +59,8 @@
"show_in_website": 1,
"website_warehouse": "_Test Warehouse - _TC",
"gst_hsn_code": "999800",
+ "opening_stock": 10,
+ "valuation_rate": 100,
"item_defaults": [{
"company": "_Test Company",
"default_warehouse": "_Test Warehouse - _TC",
diff --git a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js
index 24f7e31..e8fb347 100644
--- a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js
+++ b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js
@@ -15,8 +15,9 @@
}
});
- const child = frappe.meta.get_docfield("Variant Field", "field_name", frm.doc.name);
- child.options = allow_fields;
+ frm.fields_dict.fields.grid.update_docfield_property(
+ 'field_name', 'options', allow_fields
+ );
});
}
});
diff --git a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py
index 0422442..78f1131 100644
--- a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py
+++ b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py
@@ -13,10 +13,11 @@
def set_default_fields(self):
self.fields = []
fields = frappe.get_meta('Item').fields
- exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website",
+ exclude_fields = {"naming_series", "item_code", "item_name", "show_in_website",
"show_variant_in_website", "standard_rate", "opening_stock", "image", "description",
"variant_of", "valuation_rate", "description", "barcodes",
- "website_image", "thumbnail", "website_specifiations", "web_long_description"]
+ "website_image", "thumbnail", "website_specifiations", "web_long_description",
+ "has_variants", "attributes"}
for d in fields:
if not d.no_copy and d.fieldname not in exclude_fields and \
diff --git a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json
index 4fcdb4c..9c59c13 100644
--- a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json
+++ b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json
@@ -10,8 +10,8 @@
"exchange_rate",
"description",
"col_break3",
- "base_amount",
- "amount"
+ "amount",
+ "base_amount"
],
"fields": [
{
@@ -59,7 +59,7 @@
{
"fieldname": "base_amount",
"fieldtype": "Currency",
- "label": "Base Amount",
+ "label": "Amount (Company Currency)",
"options": "Company:company:default_currency",
"read_only": 1
}
@@ -67,7 +67,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2020-12-26 01:07:23.233604",
+ "modified": "2021-05-17 13:57:10.807980",
"modified_by": "Administrator",
"module": "Stock",
"name": "Landed Cost Taxes and Charges",
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 7dfc5da..92c8d21 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -433,13 +433,21 @@
if (doc.material_request_type == "Customer Provided") {
return{
query: "erpnext.controllers.queries.item_query",
- filters:{ 'customer': me.frm.doc.customer }
+ filters:{
+ 'customer': me.frm.doc.customer,
+ 'is_stock_item':1
+ }
}
- } else if (doc.material_request_type != "Manufacture") {
+ } else if (doc.material_request_type == "Purchase") {
return{
query: "erpnext.controllers.queries.item_query",
filters: {'is_purchase_item': 1}
}
+ } else {
+ return{
+ query: "erpnext.controllers.queries.item_query",
+ filters: {'is_stock_item': 1}
+ }
}
});
},
diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json
index 8d7b238..4e2d9e6 100644
--- a/erpnext/stock/doctype/material_request/material_request.json
+++ b/erpnext/stock/doctype/material_request/material_request.json
@@ -181,7 +181,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "\nDraft\nSubmitted\nStopped\nCancelled\nPending\nPartially Ordered\nOrdered\nIssued\nTransferred\nReceived",
+ "options": "\nDraft\nSubmitted\nStopped\nCancelled\nPending\nPartially Ordered\nPartially Received\nOrdered\nIssued\nTransferred\nReceived",
"print_hide": 1,
"print_width": "100px",
"read_only": 1,
diff --git a/erpnext/stock/doctype/packing_slip/packing_slip.js b/erpnext/stock/doctype/packing_slip/packing_slip.js
index bd14e5f..40d4685 100644
--- a/erpnext/stock/doctype/packing_slip/packing_slip.js
+++ b/erpnext/stock/doctype/packing_slip/packing_slip.js
@@ -110,19 +110,4 @@
refresh_many(['net_weight_pkg', 'net_weight_uom', 'gross_weight_uom', 'gross_weight_pkg']);
}
-var make_row = function(title,val,bold){
- var bstart = '<b>'; var bend = '</b>';
- return '<tr><td class="datalabelcell">'+(bold?bstart:'')+title+(bold?bend:'')+'</td>'
- +'<td class="datainputcell" style="text-align:left;">'+ val +'</td>'
- +'</tr>'
-}
-
-cur_frm.pformat.net_weight_pkg= function(doc){
- return '<table style="width:100%">' + make_row('Net Weight', doc.net_weight_pkg) + '</table>'
-}
-
-cur_frm.pformat.gross_weight_pkg= function(doc){
- return '<table style="width:100%">' + make_row('Gross Weight', doc.gross_weight_pkg) + '</table>'
-}
-
// TODO: validate gross weight field
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 755fa61..6ab68e2 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -379,7 +379,6 @@
else:
stock_entry = update_stock_entry_items_with_no_reference(pick_list, stock_entry)
- stock_entry.set_incoming_rate()
stock_entry.set_actual_qty()
stock_entry.calculate_rate_and_amount()
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index 57cc350..befdad9 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -73,6 +73,34 @@
})
}, __('Create'));
}
+
+ frm.events.add_custom_buttons(frm);
+ },
+
+ add_custom_buttons: function(frm) {
+ if (frm.doc.docstatus == 0) {
+ frm.add_custom_button(__('Purchase Invoice'), function () {
+ if (!frm.doc.supplier) {
+ frappe.throw({
+ title: __("Mandatory"),
+ message: __("Please Select a Supplier")
+ });
+ }
+ erpnext.utils.map_current_doc({
+ method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt",
+ source_doctype: "Purchase Invoice",
+ target: frm,
+ setters: {
+ supplier: frm.doc.supplier,
+ },
+ get_query_filters: {
+ docstatus: 1,
+ per_received: ["<", 100],
+ company: frm.doc.company
+ }
+ })
+ }, __("Get Items From"));
+ }
},
company: function(frm) {
@@ -248,13 +276,6 @@
}
}
-cur_frm.cscript.select_print_heading = function(doc, cdt, cdn) {
- if(doc.select_print_heading)
- cur_frm.pformat.print_heading = doc.select_print_heading;
- else
- cur_frm.pformat.print_heading = "Purchase Receipt";
-}
-
cur_frm.fields_dict['select_print_heading'].get_query = function(doc, cdt, cdn) {
return {
filters: [
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 5d7597b..f1292d8 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -53,7 +53,20 @@
'target_ref_field': 'stock_qty',
'source_field': 'stock_qty',
'percent_join_field': 'material_request'
+ },
+ {
+ 'source_dt': 'Purchase Receipt Item',
+ 'target_dt': 'Purchase Invoice Item',
+ 'join_field': 'purchase_invoice_item',
+ 'target_field': 'received_qty',
+ 'target_parent_dt': 'Purchase Invoice',
+ 'target_parent_field': 'per_received',
+ 'target_ref_field': 'qty',
+ 'source_field': 'received_qty',
+ 'percent_join_field': 'purchase_invoice',
+ 'overflow_type': 'receipt'
}]
+
if cint(self.is_return):
self.status_updater.extend([
{
@@ -221,6 +234,7 @@
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
self.delete_auto_created_batches()
+ @frappe.whitelist()
def get_current_stock(self):
for d in self.get('supplied_items'):
if self.supplier_warehouse:
@@ -229,16 +243,23 @@
def get_gl_entries(self, warehouse_account=None):
from erpnext.accounts.general_ledger import process_gl_map
+ gl_entries = []
+ self.make_item_gl_entries(gl_entries, warehouse_account=warehouse_account)
+ self.make_tax_gl_entries(gl_entries)
+ self.get_asset_gl_entry(gl_entries)
+
+ return process_gl_map(gl_entries)
+
+ def make_item_gl_entries(self, gl_entries, warehouse_account=None):
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
landed_cost_entries = get_item_account_wise_additional_cost(self.name)
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
auto_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, 'enable_perpetual_inventory_for_non_stock_items'))
- gl_entries = []
warehouse_with_no_account = []
- negative_expense_to_be_booked = 0.0
stock_items = self.get_stock_items()
+
for d in self.get("items"):
if d.item_code in stock_items and flt(d.valuation_rate) and flt(d.qty):
if warehouse_account.get(d.warehouse):
@@ -249,21 +270,22 @@
if not stock_value_diff:
continue
+ warehouse_account_name = warehouse_account[d.warehouse]["account"]
+ warehouse_account_currency = warehouse_account[d.warehouse]["account_currency"]
+ supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get("account")
+ supplier_warehouse_account_currency = warehouse_account.get(self.supplier_warehouse, {}).get("account_currency")
+ remarks = self.get("remarks") or _("Accounting Entry for Stock")
+
# If PR is sub-contracted and fg item rate is zero
- # in that case if account for shource and target warehouse are same,
+ # in that case if account for source and target warehouse are same,
# then GL entries should not be posted
if flt(stock_value_diff) == flt(d.rm_supp_cost) \
and warehouse_account.get(self.supplier_warehouse) \
- and warehouse_account[d.warehouse]["account"] == warehouse_account[self.supplier_warehouse]["account"]:
+ and warehouse_account_name == supplier_warehouse_account:
continue
- gl_entries.append(self.get_gl_dict({
- "account": warehouse_account[d.warehouse]["account"],
- "against": stock_rbnb,
- "cost_center": d.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "debit": stock_value_diff
- }, warehouse_account[d.warehouse]["account_currency"], item=d))
+ self.add_gl_entry(gl_entries, warehouse_account_name, d.cost_center, stock_value_diff, 0.0, remarks,
+ stock_rbnb, account_currency=warehouse_account_currency, item=d)
# GL Entry for from warehouse or Stock Received but not billed
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
@@ -273,43 +295,28 @@
credit_amount = flt(d.base_net_amount, d.precision("base_net_amount")) \
if credit_currency == self.company_currency else flt(d.net_amount, d.precision("net_amount"))
if credit_amount:
- gl_entries.append(self.get_gl_dict({
- "account": warehouse_account[d.from_warehouse]['account'] \
- if d.from_warehouse else stock_rbnb,
- "against": warehouse_account[d.warehouse]["account"],
- "cost_center": d.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "debit": -1 * flt(d.base_net_amount, d.precision("base_net_amount")),
- "debit_in_account_currency": -1 * credit_amount
- }, credit_currency, item=d))
+ account = warehouse_account[d.from_warehouse]['account'] \
+ if d.from_warehouse else stock_rbnb
- negative_expense_to_be_booked += flt(d.item_tax_amount)
+ self.add_gl_entry(gl_entries, account, d.cost_center,
+ -1 * flt(d.base_net_amount, d.precision("base_net_amount")), 0.0, remarks, warehouse_account_name,
+ debit_in_account_currency=-1 * credit_amount, account_currency=credit_currency, item=d)
- # Amount added through landed-cost-voucher
+ # Amount added through landed-cos-voucher
if d.landed_cost_voucher_amount and landed_cost_entries:
for account, amount in iteritems(landed_cost_entries[(d.item_code, d.name)]):
account_currency = get_account_currency(account)
- gl_entries.append(self.get_gl_dict({
- "account": account,
- "account_currency": account_currency,
- "against": warehouse_account[d.warehouse]["account"],
- "cost_center": d.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "credit": (flt(amount["base_amount"]) if (amount["base_amount"] or
- account_currency!=self.company_currency) else flt(amount["amount"])),
- "credit_in_account_currency": flt(amount["amount"]),
- "project": d.project
- }, item=d))
+ credit_amount = (flt(amount["base_amount"]) if (amount["base_amount"] or
+ account_currency!=self.company_currency) else flt(amount["amount"]))
+
+ self.add_gl_entry(gl_entries, account, d.cost_center, 0.0, credit_amount, remarks,
+ warehouse_account_name, credit_in_account_currency=flt(amount["amount"]),
+ account_currency=account_currency, project=d.project, item=d)
# sub-contracting warehouse
if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse):
- gl_entries.append(self.get_gl_dict({
- "account": warehouse_account[self.supplier_warehouse]["account"],
- "against": warehouse_account[d.warehouse]["account"],
- "cost_center": d.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "credit": flt(d.rm_supp_cost)
- }, warehouse_account[self.supplier_warehouse]["account_currency"], item=d))
+ self.add_gl_entry(gl_entries, supplier_warehouse_account, d.cost_center, 0.0, flt(d.rm_supp_cost),
+ remarks, warehouse_account_name, account_currency=supplier_warehouse_account_currency, item=d)
# divisional loss adjustment
valuation_amount_as_per_doc = flt(d.base_net_amount, d.precision("base_net_amount")) + \
@@ -326,46 +333,32 @@
cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center")
- gl_entries.append(self.get_gl_dict({
- "account": loss_account,
- "against": warehouse_account[d.warehouse]["account"],
- "cost_center": cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "debit": divisional_loss,
- "project": d.project
- }, credit_currency, item=d))
+ self.add_gl_entry(gl_entries, loss_account, cost_center, divisional_loss, 0.0, remarks,
+ warehouse_account_name, account_currency=credit_currency, project=d.project, item=d)
elif d.warehouse not in warehouse_with_no_account or \
d.rejected_warehouse not in warehouse_with_no_account:
warehouse_with_no_account.append(d.warehouse)
elif d.item_code not in stock_items and not d.is_fixed_asset and flt(d.qty) and auto_accounting_for_non_stock_items:
-
service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed")
credit_currency = get_account_currency(service_received_but_not_billed_account)
-
- gl_entries.append(self.get_gl_dict({
- "account": service_received_but_not_billed_account,
- "against": d.expense_account,
- "cost_center": d.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Service"),
- "project": d.project,
- "credit": d.amount,
- "voucher_detail_no": d.name
- }, credit_currency, item=d))
-
debit_currency = get_account_currency(d.expense_account)
+ remarks = self.get("remarks") or _("Accounting Entry for Service")
- gl_entries.append(self.get_gl_dict({
- "account": d.expense_account,
- "against": service_received_but_not_billed_account,
- "cost_center": d.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Service"),
- "project": d.project,
- "debit": d.amount,
- "voucher_detail_no": d.name
- }, debit_currency, item=d))
+ self.add_gl_entry(gl_entries, service_received_but_not_billed_account, d.cost_center, 0.0, d.amount,
+ remarks, d.expense_account, account_currency=credit_currency, project=d.project,
+ voucher_detail_no=d.name, item=d)
- self.get_asset_gl_entry(gl_entries)
+ self.add_gl_entry(gl_entries, d.expense_account, d.cost_center, d.amount, 0.0, remarks, service_received_but_not_billed_account,
+ account_currency = debit_currency, project=d.project, voucher_detail_no=d.name, item=d)
+
+ if warehouse_with_no_account:
+ frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" +
+ "\n".join(warehouse_with_no_account))
+
+ def make_tax_gl_entries(self, gl_entries):
+ expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
+ negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get('items')])
# Cost center-wise amount breakup for other charges included for valuation
valuation_tax = {}
for tax in self.get("taxes"):
@@ -406,23 +399,33 @@
applicable_amount = negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount)
amount_including_divisional_loss -= applicable_amount
- gl_entries.append(
- self.get_gl_dict({
- "account": account,
- "cost_center": tax.cost_center,
- "credit": applicable_amount,
- "remarks": self.remarks or _("Accounting Entry for Stock"),
- "against": against_account
- }, item=tax)
- )
+ self.add_gl_entry(gl_entries, account, tax.cost_center, 0.0, applicable_amount, self.remarks or _("Accounting Entry for Stock"),
+ against_account, item=tax)
i += 1
- if warehouse_with_no_account:
- frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" +
- "\n".join(warehouse_with_no_account))
+ def add_gl_entry(self, gl_entries, account, cost_center, debit, credit, remarks, against_account,
+ debit_in_account_currency=None, credit_in_account_currency=None, account_currency=None,
+ project=None, voucher_detail_no=None, item=None):
+ gl_entry = {
+ "account": account,
+ "cost_center": cost_center,
+ "debit": debit,
+ "credit": credit,
+ "against_account": against_account,
+ "remarks": remarks,
+ }
- return process_gl_map(gl_entries)
+ if voucher_detail_no:
+ gl_entry.update({"voucher_detail_no": voucher_detail_no})
+
+ if debit_in_account_currency:
+ gl_entry.update({"debit_in_account_currency": debit_in_account_currency})
+
+ if credit_in_account_currency:
+ gl_entry.update({"credit_in_account_currency": credit_in_account_currency})
+
+ gl_entries.append(self.get_gl_dict(gl_entry, item=item))
def get_asset_gl_entry(self, gl_entries):
for item in self.get("items"):
@@ -444,30 +447,21 @@
asset_amount = flt(item.net_amount) + flt(item.item_tax_amount/self.conversion_rate)
base_asset_amount = flt(item.base_net_amount + item.item_tax_amount)
+ remarks = self.get("remarks") or _("Accounting Entry for Asset")
cwip_account_currency = get_account_currency(cwip_account)
# debit cwip account
- gl_entries.append(self.get_gl_dict({
- "account": cwip_account,
- "against": arbnb_account,
- "cost_center": item.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
- "debit": base_asset_amount,
- "debit_in_account_currency": (base_asset_amount
- if cwip_account_currency == self.company_currency else asset_amount)
- }, item=item))
+ debit_in_account_currency = (base_asset_amount
+ if cwip_account_currency == self.company_currency else asset_amount)
+ self.add_gl_entry(gl_entries, cwip_account, item.cost_center, base_asset_amount, 0.0, remarks,
+ arbnb_account, debit_in_account_currency=debit_in_account_currency, item=item)
asset_rbnb_currency = get_account_currency(arbnb_account)
# credit arbnb account
- gl_entries.append(self.get_gl_dict({
- "account": arbnb_account,
- "against": cwip_account,
- "cost_center": item.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
- "credit": base_asset_amount,
- "credit_in_account_currency": (base_asset_amount
- if asset_rbnb_currency == self.company_currency else asset_amount)
- }, item=item))
+ credit_in_account_currency = (base_asset_amount
+ if asset_rbnb_currency == self.company_currency else asset_amount)
+ self.add_gl_entry(gl_entries, arbnb_account, item.cost_center, 0.0, base_asset_amount, remarks,
+ cwip_account, credit_in_account_currency=credit_in_account_currency, item=item)
def add_lcv_gl_entries(self, item, gl_entries):
expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation")
@@ -478,23 +472,13 @@
# This returns company's default cwip account
asset_account = get_asset_account("capital_work_in_progress_account", company=self.company)
- gl_entries.append(self.get_gl_dict({
- "account": expenses_included_in_asset_valuation,
- "against": asset_account,
- "cost_center": item.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "credit": flt(item.landed_cost_voucher_amount),
- "project": item.project
- }, item=item))
+ remarks = self.get("remarks") or _("Accounting Entry for Stock")
- gl_entries.append(self.get_gl_dict({
- "account": asset_account,
- "against": expenses_included_in_asset_valuation,
- "cost_center": item.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "debit": flt(item.landed_cost_voucher_amount),
- "project": item.project
- }, item=item))
+ self.add_gl_entry(gl_entries, expenses_included_in_asset_valuation, item.cost_center, 0.0, flt(item.landed_cost_voucher_amount),
+ remarks, asset_account, project=item.project, item=item)
+
+ self.add_gl_entry(gl_entries, asset_account, item.cost_center, 0.0, flt(item.landed_cost_voucher_amount),
+ remarks, expenses_included_in_asset_valuation, project=item.project, item=item)
def update_assets(self, item, valuation_rate):
assets = frappe.db.get_all('Asset',
@@ -513,7 +497,9 @@
def update_billing_status(self, update_modified=True):
updated_pr = [self.name]
for d in self.get("items"):
- if d.purchase_order_item:
+ if d.purchase_invoice and d.purchase_invoice_item:
+ d.db_set('billed_amt', d.amount, update_modified=update_modified)
+ elif d.purchase_order_item:
updated_pr += update_billed_amount_based_on_po(d.purchase_order_item, update_modified)
for pr in set(updated_pr):
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 7f0c3fa..5095a80 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -13,8 +13,9 @@
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.stock.doctype.item.test_item import make_item
from six import iteritems
+from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
-
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
class TestPurchaseReceipt(unittest.TestCase):
def setUp(self):
@@ -144,6 +145,62 @@
self.assertFalse(frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name}))
self.assertFalse(frappe.db.get_all('Serial No', {'batch_no': batch_no}))
+ def test_duplicate_serial_nos(self):
+ from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+
+ item = frappe.db.exists("Item", {'item_name': 'Test Serialized Item 123'})
+ if not item:
+ item = create_item("Test Serialized Item 123")
+ item.has_serial_no = 1
+ item.serial_no_series = "TSI123-.####"
+ item.save()
+ else:
+ item = frappe.get_doc("Item", {'item_name': 'Test Serialized Item 123'})
+
+ # First make purchase receipt
+ pr = make_purchase_receipt(item_code=item.name, qty=2, rate=500)
+ pr.load_from_db()
+
+ serial_nos = frappe.db.get_value('Stock Ledger Entry',
+ {'voucher_type': 'Purchase Receipt', 'voucher_no': pr.name, 'item_code': item.name}, 'serial_no')
+
+ serial_nos = get_serial_nos(serial_nos)
+
+ self.assertEquals(get_serial_nos(pr.items[0].serial_no), serial_nos)
+
+ # Then tried to receive same serial nos in difference company
+ pr_different_company = make_purchase_receipt(item_code=item.name, qty=2, rate=500,
+ serial_no='\n'.join(serial_nos), company='_Test Company 1', do_not_submit=True,
+ warehouse = 'Stores - _TC1')
+
+ self.assertRaises(SerialNoDuplicateError, pr_different_company.submit)
+
+ # Then made delivery note to remove the serial nos from stock
+ dn = create_delivery_note(item_code=item.name, qty=2, rate = 1500, serial_no='\n'.join(serial_nos))
+ dn.load_from_db()
+ self.assertEquals(get_serial_nos(dn.items[0].serial_no), serial_nos)
+
+ posting_date = add_days(today(), -3)
+
+ # Try to receive same serial nos again in the same company with backdated.
+ pr1 = make_purchase_receipt(item_code=item.name, qty=2, rate=500,
+ posting_date=posting_date, serial_no='\n'.join(serial_nos), do_not_submit=True)
+
+ self.assertRaises(SerialNoExistsInFutureTransaction, pr1.submit)
+
+ # Try to receive same serial nos with different company with backdated.
+ pr2 = make_purchase_receipt(item_code=item.name, qty=2, rate=500,
+ posting_date=posting_date, serial_no='\n'.join(serial_nos), company='_Test Company 1', do_not_submit=True,
+ warehouse = 'Stores - _TC1')
+
+ self.assertRaises(SerialNoExistsInFutureTransaction, pr2.submit)
+
+ # Receive the same serial nos after the delivery note posting date and time
+ make_purchase_receipt(item_code=item.name, qty=2, rate=500, serial_no='\n'.join(serial_nos))
+
+ # Raise the error for backdated deliver note entry cancel
+ self.assertRaises(SerialNoExistsInFutureTransaction, dn.cancel)
+
def test_purchase_receipt_gl_entry(self):
pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1",
@@ -240,6 +297,8 @@
item_code = "Test Extra Item 1", qty=10, basic_rate=100)
se2 = make_stock_entry(target="_Test Warehouse - _TC",
item_code = "_Test FG Item", qty=1, basic_rate=100)
+ se3 = make_stock_entry(target="_Test Warehouse - _TC",
+ item_code = "Test Extra Item 2", qty=1, basic_rate=100)
rm_items = [
{
"item_code": item_code,
@@ -274,6 +333,7 @@
se.cancel()
se1.cancel()
se2.cancel()
+ se3.cancel()
po.reload()
po.cancel()
@@ -562,29 +622,6 @@
new_pr_doc.cancel()
- def test_not_accept_duplicate_serial_no(self):
- from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
- from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
-
- item_code = frappe.db.get_value('Item', {'has_serial_no': 1, 'is_fixed_asset': 0, "has_batch_no": 0})
- if not item_code:
- item = make_item("Test Serial Item 1", dict(has_serial_no=1, has_batch_no=0))
- item_code = item.name
-
- serial_no = random_string(5)
- pr1 = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no)
- dn = create_delivery_note(item_code=item_code, qty=1, serial_no=serial_no)
-
- pr2 = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no, do_not_submit=True)
- self.assertRaises(SerialNoDuplicateError, pr2.submit)
-
- se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1,
- serial_no=serial_no, basic_rate=100, do_not_submit=True)
- se.submit()
-
- dn.cancel()
- pr1.cancel()
-
def test_auto_asset_creation(self):
asset_item = "Test Asset Item"
@@ -619,10 +656,10 @@
pr = make_purchase_receipt(item_code=asset_item, qty=3)
assets = frappe.db.get_all('Asset', filters={'purchase_receipt': pr.name})
- self.assertEquals(len(assets), 3)
+ self.assertEqual(len(assets), 3)
location = frappe.db.get_value('Asset', assets[0].name, 'location')
- self.assertEquals(location, "Test Location")
+ self.assertEqual(location, "Test Location")
pr.cancel()
@@ -727,7 +764,7 @@
pr1.submit()
pi = make_purchase_invoice(pr.name)
- self.assertEquals(pi.items[0].qty, 3)
+ self.assertEqual(pi.items[0].qty, 3)
pr1.cancel()
pr.reload()
@@ -758,8 +795,8 @@
pr2.submit()
pi2 = make_purchase_invoice(pr1.name)
- self.assertEquals(pi2.items[0].qty, 2)
- self.assertEquals(pi2.items[1].qty, 1)
+ self.assertEqual(pi2.items[0].qty, 2)
+ self.assertEqual(pi2.items[1].qty, 1)
pr2.cancel()
pi1.cancel()
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index efe3642..82cc98e 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -72,16 +72,18 @@
"warehouse",
"rejected_warehouse",
"from_warehouse",
- "purchase_order",
"material_request",
+ "purchase_order",
+ "purchase_invoice",
"column_break_40",
"is_fixed_asset",
"asset_location",
"asset_category",
"schedule_date",
"quality_inspection",
- "purchase_order_item",
"material_request_item",
+ "purchase_order_item",
+ "purchase_invoice_item",
"purchase_receipt_item",
"delivery_note_item",
"putaway_rule",
@@ -937,7 +939,21 @@
"fieldname": "base_rate_with_margin",
"fieldtype": "Currency",
"label": "Rate With Margin (Company Currency)",
- "options": "Company:company:default_currency",
+ "options": "Company:company:default_currency"
+ },
+ {
+ "fieldname": "purchase_invoice",
+ "fieldtype": "Link",
+ "label": "Purchase Invoice",
+ "options": "Purchase Invoice",
+ "read_only": 1
+ },
+ {
+ "fieldname": "purchase_invoice_item",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Purchase Invoice Item",
+ "no_copy": 1,
"print_hide": 1,
"read_only": 1
}
@@ -945,7 +961,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2021-02-23 00:59:14.360847",
+ "modified": "2021-03-29 04:17:00.336298",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
index a7dfc9e..56b046a 100644
--- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
@@ -27,10 +27,11 @@
dn.reload()
self.assertRaises(QualityInspectionRejectedError, dn.submit)
- frappe.db.set_value("Quality Inspection Reading", {"parent": qa.name}, "status", "Accepted")
+ frappe.db.set_value("Quality Inspection", qa.name, "status", "Accepted")
dn.reload()
dn.submit()
+ qa.reload()
qa.cancel()
dn.reload()
dn.cancel()
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
index f8cfdf8..5b626ea 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -4,8 +4,9 @@
from __future__ import unicode_literals
import frappe, erpnext
+from rq.timeouts import JobTimeoutException
from frappe.model.document import Document
-from frappe.utils import cint, get_link_to_form, add_to_date, today
+from frappe.utils import cint, get_link_to_form, add_to_date, now, today, time_diff_in_hours
from erpnext.stock.stock_ledger import repost_future_sle
from erpnext.accounts.utils import update_gl_entries_after, check_if_stock_and_account_balance_synced
from frappe.utils.user import get_users_with_role
@@ -57,7 +58,8 @@
repost_gl_entries(doc)
doc.set_status('Completed')
- except Exception:
+
+ except (Exception, JobTimeoutException):
frappe.db.rollback()
traceback = frappe.get_traceback()
frappe.log_error(traceback)
@@ -113,6 +115,12 @@
frappe.sendmail(recipients=recipients, subject=subject, message=message)
def repost_entries():
+ job_log = frappe.get_all('Scheduled Job Log', fields = ['status', 'creation'],
+ filters = {'scheduled_job_type': 'repost_item_valuation.repost_entries'}, order_by='creation desc', limit=1)
+
+ if job_log and job_log[0]['status'] == 'Start' and time_diff_in_hours(now(), job_log[0]['creation']) < 2:
+ return
+
riv_entries = get_repost_item_valuation_entries()
for row in riv_entries:
@@ -124,12 +132,12 @@
return
for d in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}):
- check_if_stock_and_account_balance_synced(today(), d.company)
+ check_if_stock_and_account_balance_synced(today(), d.name)
def get_repost_item_valuation_entries():
- date = add_to_date(today(), hours=-12)
+ date = add_to_date(now(), hours=-3)
return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation`
WHERE status != 'Completed' and creation <= %s and docstatus = 1
ORDER BY timestamp(posting_date, posting_time) asc, creation asc
- """, date, as_dict=1)
\ No newline at end of file
+ """, date, as_dict=1)
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index c8d8ca9..5ecc9f8 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -14,6 +14,7 @@
from erpnext.controllers.stock_controller import StockController
from six import string_types
from six.moves import map
+
class SerialNoCannotCreateDirectError(ValidationError): pass
class SerialNoCannotCannotChangeError(ValidationError): pass
class SerialNoNotRequiredError(ValidationError): pass
@@ -242,7 +243,7 @@
if frappe.db.exists("Serial No", serial_no):
sr = frappe.db.get_value("Serial No", serial_no, ["name", "item_code", "batch_no", "sales_order",
"delivery_document_no", "delivery_document_type", "warehouse", "purchase_document_type",
- "purchase_document_no", "company"], as_dict=1)
+ "purchase_document_no", "company", "status"], as_dict=1)
if sr.item_code!=sle.item_code:
if not allow_serial_nos_with_different_item(serial_no, sle):
@@ -265,6 +266,9 @@
frappe.throw(_("Serial No {0} does not belong to Warehouse {1}").format(serial_no,
sle.warehouse), SerialNoWarehouseError)
+ if not sr.purchase_document_no:
+ frappe.throw(_("Serial No {0} not in stock").format(serial_no), SerialNoNotExistsError)
+
if sle.voucher_type in ("Delivery Note", "Sales Invoice"):
if sr.batch_no and sr.batch_no != sle.batch_no:
@@ -322,11 +326,35 @@
frappe.throw(_("Serial Nos Required for Serialized Item {0}").format(sle.item_code),
SerialNoRequiredError)
elif serial_nos:
+ # SLE is being cancelled and has serial nos
for serial_no in serial_nos:
- sr = frappe.db.get_value("Serial No", serial_no, ["name", "warehouse"], as_dict=1)
- if sr and cint(sle.actual_qty) < 0 and sr.warehouse != sle.warehouse:
- frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}")
- .format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse))
+ check_serial_no_validity_on_cancel(serial_no, sle)
+
+def check_serial_no_validity_on_cancel(serial_no, sle):
+ sr = frappe.db.get_value("Serial No", serial_no, ["name", "warehouse", "company", "status"], as_dict=1)
+ sr_link = frappe.utils.get_link_to_form("Serial No", serial_no)
+ doc_link = frappe.utils.get_link_to_form(sle.voucher_type, sle.voucher_no)
+ actual_qty = cint(sle.actual_qty)
+ is_stock_reco = sle.voucher_type == "Stock Reconciliation"
+ msg = None
+
+ if sr and (actual_qty < 0 or is_stock_reco) and sr.warehouse != sle.warehouse:
+ # receipt(inward) is being cancelled
+ msg = _("Cannot cancel {0} {1} as Serial No {2} does not belong to the warehouse {3}").format(
+ sle.voucher_type, doc_link, sr_link, frappe.bold(sle.warehouse))
+ elif sr and actual_qty > 0 and not is_stock_reco:
+ # delivery is being cancelled, check for warehouse.
+ if sr.warehouse:
+ # serial no is active in another warehouse/company.
+ msg = _("Cannot cancel {0} {1} as Serial No {2} is active in warehouse {3}").format(
+ sle.voucher_type, doc_link, sr_link, frappe.bold(sr.warehouse))
+ elif sr.company != sle.company and sr.status == "Delivered":
+ # serial no is inactive (allowed) or delivered from another company (block).
+ msg = _("Cannot cancel {0} {1} as Serial No {2} does not belong to the company {3}").format(
+ sle.voucher_type, doc_link, sr_link, frappe.bold(sle.company))
+
+ if msg:
+ frappe.throw(msg, title=_("Cannot cancel"))
def validate_material_transfer_entry(sle_doc):
sle_doc.update({
@@ -357,19 +385,6 @@
if sn.company != sle.company:
return False
- status = False
- if sn.purchase_document_no:
- if (sle.voucher_type in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"] and
- sn.delivery_document_type not in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"]):
- status = True
-
- # If status is receipt then system will allow to in-ward the delivered serial no
- if (status and sle.voucher_type == "Stock Entry" and frappe.db.get_value("Stock Entry",
- sle.voucher_no, "purpose") in ("Material Receipt", "Material Transfer")):
- status = False
-
- return status
-
def allow_serial_nos_with_different_item(sle_serial_no, sle):
"""
Allows same serial nos for raw materials and finished goods
diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py
index ed70790..cde7fe0 100644
--- a/erpnext/stock/doctype/serial_no/test_serial_no.py
+++ b/erpnext/stock/doctype/serial_no/test_serial_no.py
@@ -40,16 +40,139 @@
se = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
- create_delivery_note(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0])
+ dn = create_delivery_note(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0])
+
+ serial_no = frappe.get_doc("Serial No", serial_nos[0])
+
+ # check Serial No details after delivery
+ self.assertEqual(serial_no.status, "Delivered")
+ self.assertEqual(serial_no.warehouse, None)
+ self.assertEqual(serial_no.company, "_Test Company")
+ self.assertEqual(serial_no.delivery_document_type, "Delivery Note")
+ self.assertEqual(serial_no.delivery_document_no, dn.name)
wh = create_warehouse("_Test Warehouse", company="_Test Company 1")
- make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0],
+ pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0],
company="_Test Company 1", warehouse=wh)
- serial_no = frappe.db.get_value("Serial No", serial_nos[0], ["warehouse", "company"], as_dict=1)
+ serial_no.reload()
+ # check Serial No details after purchase in second company
+ self.assertEqual(serial_no.status, "Active")
self.assertEqual(serial_no.warehouse, wh)
self.assertEqual(serial_no.company, "_Test Company 1")
+ self.assertEqual(serial_no.purchase_document_type, "Purchase Receipt")
+ self.assertEqual(serial_no.purchase_document_no, pr.name)
+
+ def test_inter_company_transfer_intermediate_cancellation(self):
+ """
+ Receive into and Deliver Serial No from one company.
+ Then Receive into and Deliver from second company.
+ Try to cancel intermediate receipts/deliveries to test if it is blocked.
+ """
+ se = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
+ serial_nos = get_serial_nos(se.get("items")[0].serial_no)
+
+ sn_doc = frappe.get_doc("Serial No", serial_nos[0])
+
+ # check Serial No details after purchase in first company
+ self.assertEqual(sn_doc.status, "Active")
+ self.assertEqual(sn_doc.company, "_Test Company")
+ self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC")
+ self.assertEqual(sn_doc.purchase_document_no, se.name)
+
+ dn = create_delivery_note(item_code="_Test Serialized Item With Series",
+ qty=1, serial_no=serial_nos[0])
+ sn_doc.reload()
+ # check Serial No details after delivery from **first** company
+ self.assertEqual(sn_doc.status, "Delivered")
+ self.assertEqual(sn_doc.company, "_Test Company")
+ self.assertEqual(sn_doc.warehouse, None)
+ self.assertEqual(sn_doc.delivery_document_no, dn.name)
+
+ # try cancelling the first Serial No Receipt, even though it is delivered
+ # block cancellation is Serial No is out of the warehouse
+ self.assertRaises(frappe.ValidationError, se.cancel)
+
+ # receive serial no in second company
+ wh = create_warehouse("_Test Warehouse", company="_Test Company 1")
+ pr = make_purchase_receipt(item_code="_Test Serialized Item With Series",
+ qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh)
+ sn_doc.reload()
+
+ self.assertEqual(sn_doc.warehouse, wh)
+ # try cancelling the delivery from the first company
+ # block cancellation as Serial No belongs to different company
+ self.assertRaises(frappe.ValidationError, dn.cancel)
+
+ # deliver from second company
+ dn_2 = create_delivery_note(item_code="_Test Serialized Item With Series",
+ qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh)
+ sn_doc.reload()
+
+ # check Serial No details after delivery from **second** company
+ self.assertEqual(sn_doc.status, "Delivered")
+ self.assertEqual(sn_doc.company, "_Test Company 1")
+ self.assertEqual(sn_doc.warehouse, None)
+ self.assertEqual(sn_doc.delivery_document_no, dn_2.name)
+
+ # cannot cancel any intermediate document before last Delivery Note
+ self.assertRaises(frappe.ValidationError, se.cancel)
+ self.assertRaises(frappe.ValidationError, dn.cancel)
+ self.assertRaises(frappe.ValidationError, pr.cancel)
+
+ def test_inter_company_transfer_fallback_on_cancel(self):
+ """
+ Test Serial No state changes on cancellation.
+ If Delivery cancelled, it should fall back on last Receipt in the same company.
+ If Receipt is cancelled, it should be Inactive in the same company.
+ """
+ # Receipt in **first** company
+ se = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
+ serial_nos = get_serial_nos(se.get("items")[0].serial_no)
+ sn_doc = frappe.get_doc("Serial No", serial_nos[0])
+
+ # Delivery from first company
+ dn = create_delivery_note(item_code="_Test Serialized Item With Series",
+ qty=1, serial_no=serial_nos[0])
+
+ # Receipt in **second** company
+ wh = create_warehouse("_Test Warehouse", company="_Test Company 1")
+ pr = make_purchase_receipt(item_code="_Test Serialized Item With Series",
+ qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh)
+
+ # Delivery from second company
+ dn_2 = create_delivery_note(item_code="_Test Serialized Item With Series",
+ qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh)
+ sn_doc.reload()
+
+ self.assertEqual(sn_doc.status, "Delivered")
+ self.assertEqual(sn_doc.company, "_Test Company 1")
+ self.assertEqual(sn_doc.delivery_document_no, dn_2.name)
+
+ dn_2.cancel()
+ sn_doc.reload()
+ # Fallback on Purchase Receipt if Delivery is cancelled
+ self.assertEqual(sn_doc.status, "Active")
+ self.assertEqual(sn_doc.company, "_Test Company 1")
+ self.assertEqual(sn_doc.warehouse, wh)
+ self.assertEqual(sn_doc.purchase_document_no, pr.name)
+
+ pr.cancel()
+ sn_doc.reload()
+ # Inactive in same company if Receipt cancelled
+ self.assertEqual(sn_doc.status, "Inactive")
+ self.assertEqual(sn_doc.company, "_Test Company 1")
+ self.assertEqual(sn_doc.warehouse, None)
+
+ dn.cancel()
+ sn_doc.reload()
+ # Fallback on Purchase Receipt in FIRST company if
+ # Delivery from FIRST company is cancelled
+ self.assertEqual(sn_doc.status, "Active")
+ self.assertEqual(sn_doc.company, "_Test Company")
+ self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC")
+ self.assertEqual(sn_doc.purchase_document_no, se.name)
def tearDown(self):
frappe.db.rollback()
\ No newline at end of file
diff --git a/erpnext/stock/doctype/shipment/shipment.js b/erpnext/stock/doctype/shipment/shipment.js
index 7af16af..ce2906e 100644
--- a/erpnext/stock/doctype/shipment/shipment.js
+++ b/erpnext/stock/doctype/shipment/shipment.js
@@ -363,43 +363,6 @@
if (frm.doc.pickup_date < frappe.datetime.get_today()) {
frappe.throw(__("Pickup Date cannot be before this day"));
}
- if (frm.doc.pickup_date == frappe.datetime.get_today()) {
- var pickup_time = frm.events.get_pickup_time(frm);
- frm.set_value("pickup_from", pickup_time);
- frm.trigger('set_pickup_to_time');
- }
- },
- pickup_from: function(frm) {
- var pickup_time = frm.events.get_pickup_time(frm);
- if (frm.doc.pickup_from && frm.doc.pickup_date == frappe.datetime.get_today()) {
- let current_hour = pickup_time.split(':')[0];
- let current_min = pickup_time.split(':')[1];
- let pickup_hour = frm.doc.pickup_from.split(':')[0];
- let pickup_min = frm.doc.pickup_from.split(':')[1];
- if (pickup_hour < current_hour || (pickup_hour == current_hour && pickup_min < current_min)) {
- frm.set_value("pickup_from", pickup_time);
- frappe.throw(__("Pickup Time cannot be in the past"));
- }
- }
- frm.trigger('set_pickup_to_time');
- },
- get_pickup_time: function() {
- let current_hour = new Date().getHours();
- let current_min = new Date().toLocaleString('en-US', {minute: 'numeric'});
- if (current_min < 30) {
- current_min = '30';
- } else {
- current_min = '00';
- current_hour = Number(current_hour)+1;
- }
- let pickup_time = current_hour +':'+ current_min;
- return pickup_time;
- },
- set_pickup_to_time: function(frm) {
- let pickup_to_hour = Number(frm.doc.pickup_from.split(':')[0])+5;
- let pickup_to_min = frm.doc.pickup_from.split(':')[1];
- let pickup_to = pickup_to_hour +':'+ pickup_to_min;
- frm.set_value("pickup_to", pickup_to);
},
clear_pickup_fields: function(frm) {
let fields = ["pickup_address_name", "pickup_contact_name", "pickup_address", "pickup_contact", "pickup_contact_email", "pickup_contact_person"];
diff --git a/erpnext/stock/doctype/shipment/shipment.json b/erpnext/stock/doctype/shipment/shipment.json
index 76c331c..a33cbc2 100644
--- a/erpnext/stock/doctype/shipment/shipment.json
+++ b/erpnext/stock/doctype/shipment/shipment.json
@@ -275,14 +275,16 @@
"default": "09:00",
"fieldname": "pickup_from",
"fieldtype": "Time",
- "label": "Pickup from"
+ "label": "Pickup from",
+ "reqd": 1
},
{
"allow_on_submit": 1,
"default": "17:00",
"fieldname": "pickup_to",
"fieldtype": "Time",
- "label": "Pickup to"
+ "label": "Pickup to",
+ "reqd": 1
},
{
"fieldname": "column_break_36",
@@ -431,7 +433,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-12-25 15:02:34.891976",
+ "modified": "2021-04-13 17:14:18.181818",
"modified_by": "Administrator",
"module": "Stock",
"name": "Shipment",
@@ -469,4 +471,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/doctype/shipment/shipment.py b/erpnext/stock/doctype/shipment/shipment.py
index 4697a7b..01fcee4 100644
--- a/erpnext/stock/doctype/shipment/shipment.py
+++ b/erpnext/stock/doctype/shipment/shipment.py
@@ -23,10 +23,10 @@
frappe.throw(_('Please enter Shipment Parcel information'))
if self.value_of_goods == 0:
frappe.throw(_('Value of goods cannot be 0'))
- self.status = 'Submitted'
+ self.db_set('status', 'Submitted')
def on_cancel(self):
- self.status = 'Cancelled'
+ self.db_set('status', 'Cancelled')
def validate_weight(self):
for parcel in self.shipment_parcel:
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 98246fb..de23e76 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -107,6 +107,7 @@
frappe.flags.hide_serial_batch_dialog = true;
}
});
+ attach_bom_items(frm.doc.bom_no);
},
setup_quality_inspection: function(frm) {
@@ -311,6 +312,7 @@
}
frm.trigger("setup_quality_inspection");
+ attach_bom_items(frm.doc.bom_no)
},
stock_entry_type: function(frm){
@@ -558,7 +560,6 @@
})
);
}
-
for (let i in frm.doc.items) {
let item = frm.doc.items[i];
@@ -599,7 +600,6 @@
add_to_transit: function(frm) {
if(frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer') {
frm.set_value('to_warehouse', '');
- frm.set_value('stock_entry_type', 'Material Transfer');
frm.fields_dict.to_warehouse.get_query = function() {
return {
filters:{
@@ -609,12 +609,13 @@
}
};
};
- frm.trigger('set_tansit_warehouse');
+ frm.trigger('set_transit_warehouse');
}
},
- set_tansit_warehouse: function(frm) {
- if(frm.doc.add_to_transit && frm.doc.purpose == 'Material Transfer' && !frm.doc.to_warehouse) {
+ set_transit_warehouse: function(frm) {
+ if(frm.doc.add_to_transit && frm.doc.purpose == 'Material Transfer' && !frm.doc.to_warehouse
+ && frm.doc.from_warehouse) {
let dt = frm.doc.from_warehouse ? 'Warehouse' : 'Company';
let dn = frm.doc.from_warehouse ? frm.doc.from_warehouse : frm.doc.company;
frappe.db.get_value(dt, dn, 'default_in_transit_warehouse', (r) => {
@@ -856,7 +857,6 @@
}
erpnext.hide_company();
erpnext.utils.add_item(this.frm);
- this.frm.trigger('add_to_transit');
},
scan_barcode: function() {
@@ -921,6 +921,7 @@
method: "get_items",
callback: function(r) {
if(!r.exc) refresh_field("items");
+ if(me.frm.doc.bom_no) attach_bom_items(me.frm.doc.bom_no)
}
});
}
@@ -984,7 +985,7 @@
},
from_warehouse: function(doc) {
- this.frm.trigger('set_tansit_warehouse');
+ this.frm.trigger('set_transit_warehouse');
this.set_warehouse_in_children(doc.items, "s_warehouse", doc.from_warehouse);
},
@@ -998,7 +999,7 @@
},
items_on_form_rendered: function(doc, grid_row) {
- erpnext.setup_serial_no();
+ erpnext.setup_serial_or_batch_no();
},
toggle_related_fields: function(doc) {
@@ -1066,4 +1067,22 @@
}
+function attach_bom_items(bom_no) {
+ if (check_should_not_attach_bom_items(bom_no)) return
+ frappe.db.get_doc("BOM",bom_no).then(bom => {
+ const {name, items} = bom
+ erpnext.stock.bom = {name, items:{}}
+ items.forEach(item => {
+ erpnext.stock.bom.items[item.item_code] = item;
+ });
+ });
+}
+
+function check_should_not_attach_bom_items(bom_no) {
+ return (
+ bom_no === undefined ||
+ (erpnext.stock.bom && erpnext.stock.bom.name === bom_no)
+ );
+}
+
$.extend(cur_frm.cscript, new erpnext.stock.StockEntry({frm: cur_frm}));
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json
index 98c047a..a0b5457 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.json
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.json
@@ -59,10 +59,6 @@
"supplier_name",
"supplier_address",
"address_display",
- "column_break_39",
- "customer",
- "customer_name",
- "customer_address",
"accounting_dimensions_section",
"project",
"dimension_col_break",
@@ -435,13 +431,13 @@
},
{
"collapsible": 1,
- "depends_on": "eval: in_list([\"Sales Return\", \"Purchase Return\", \"Send to Subcontractor\"], doc.purpose)",
+ "depends_on": "eval:doc.purpose === \"Send to Subcontractor\"",
"fieldname": "contact_section",
"fieldtype": "Section Break",
- "label": "Customer or Supplier Details"
+ "label": "Supplier Details"
},
{
- "depends_on": "eval:doc.purpose==\"Purchase Return\" || doc.purpose==\"Send to Subcontractor\"",
+ "depends_on": "eval:doc.purpose === \"Send to Subcontractor\"",
"fieldname": "supplier",
"fieldtype": "Link",
"label": "Supplier",
@@ -453,7 +449,7 @@
},
{
"bold": 1,
- "depends_on": "eval:doc.purpose==\"Purchase Return\" || doc.purpose==\"Send to Subcontractor\"",
+ "depends_on": "eval:doc.purpose === \"Send to Subcontractor\"",
"fieldname": "supplier_name",
"fieldtype": "Data",
"label": "Supplier Name",
@@ -463,7 +459,7 @@
"read_only": 1
},
{
- "depends_on": "eval:doc.purpose==\"Purchase Return\" || doc.purpose==\"Send to Subcontractor\"",
+ "depends_on": "eval:doc.purpose === \"Send to Subcontractor\"",
"fieldname": "supplier_address",
"fieldtype": "Link",
"label": "Supplier Address",
@@ -478,41 +474,6 @@
"label": "Address"
},
{
- "fieldname": "column_break_39",
- "fieldtype": "Column Break"
- },
- {
- "depends_on": "eval:doc.purpose==\"Sales Return\"",
- "fieldname": "customer",
- "fieldtype": "Link",
- "label": "Customer",
- "no_copy": 1,
- "oldfieldname": "customer",
- "oldfieldtype": "Link",
- "options": "Customer",
- "print_hide": 1
- },
- {
- "bold": 1,
- "depends_on": "eval:doc.purpose==\"Sales Return\"",
- "fieldname": "customer_name",
- "fieldtype": "Data",
- "label": "Customer Name",
- "no_copy": 1,
- "oldfieldname": "customer_name",
- "oldfieldtype": "Data",
- "read_only": 1
- },
- {
- "depends_on": "eval:doc.purpose==\"Sales Return\"",
- "fieldname": "customer_address",
- "fieldtype": "Small Text",
- "label": "Customer Address",
- "no_copy": 1,
- "oldfieldname": "customer_address",
- "oldfieldtype": "Small Text"
- },
- {
"collapsible": 1,
"fieldname": "printing_settings",
"fieldtype": "Section Break",
@@ -637,6 +598,8 @@
{
"default": "0",
"depends_on": "eval: doc.purpose=='Material Transfer' && !doc.outgoing_stock_entry",
+ "fetch_from": "stock_entry_type.add_to_transit",
+ "fetch_if_empty": 1,
"fieldname": "add_to_transit",
"fieldtype": "Check",
"label": "Add to Transit",
@@ -655,7 +618,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-12-09 14:58:13.267321",
+ "modified": "2021-05-24 11:32:23.904307",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry",
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index f8ac400..2f76bc7 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -76,6 +76,7 @@
self.validate_difference_account()
self.set_job_card_data()
self.set_purpose_for_stock_entry()
+ self.validate_duplicate_serial_no()
if not self.from_bom:
self.fg_completed_qty = 0.0
@@ -398,8 +399,12 @@
and item_code = %s
and ifnull(s_warehouse,'')='' """ % (", ".join(["%s" * len(other_ste)]), "%s"), args)[0][0]
if fg_qty_already_entered and fg_qty_already_entered >= qty:
- frappe.throw(_("Stock Entries already created for Work Order ")
- + self.work_order + ":" + ", ".join(other_ste), DuplicateEntryForWorkOrderError)
+ frappe.throw(
+ _("Stock Entries already created for Work Order {0}: {1}").format(
+ self.work_order, ", ".join(other_ste)
+ ),
+ DuplicateEntryForWorkOrderError,
+ )
def set_actual_qty(self):
allow_negative_stock = cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock"))
@@ -435,6 +440,7 @@
if transferred_serial_no:
d.serial_no = transferred_serial_no
+ @frappe.whitelist()
def get_stock_and_rate(self):
"""
Updates rate and availability of all the items.
@@ -582,6 +588,22 @@
self.purpose = frappe.get_cached_value('Stock Entry Type',
self.stock_entry_type, 'purpose')
+ def validate_duplicate_serial_no(self):
+ warehouse_wise_serial_nos = {}
+
+ # In case of repack the source and target serial nos could be same
+ for warehouse in ['s_warehouse', 't_warehouse']:
+ serial_nos = []
+ for row in self.items:
+ if not (row.serial_no and row.get(warehouse)): continue
+
+ for sn in get_serial_nos(row.serial_no):
+ if sn in serial_nos:
+ frappe.throw(_('The serial no {0} has added multiple times in the stock entry {1}')
+ .format(frappe.bold(sn), self.name))
+
+ serial_nos.append(sn)
+
def validate_purchase_order(self):
"""Throw exception if more raw material is transferred against Purchase Order than in
the raw materials supplied table"""
diff --git a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json
index 0f2b55e..eee38be 100644
--- a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json
+++ b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json
@@ -6,7 +6,8 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
- "purpose"
+ "purpose",
+ "add_to_transit"
],
"fields": [
{
@@ -18,10 +19,17 @@
"options": "\nMaterial Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor",
"reqd": 1,
"set_only_once": 1
+ },
+ {
+ "default": "0",
+ "depends_on": "eval: doc.purpose == 'Material Transfer'",
+ "fieldname": "add_to_transit",
+ "fieldtype": "Check",
+ "label": "Add to Transit"
}
],
"links": [],
- "modified": "2020-08-10 23:24:37.160817",
+ "modified": "2021-05-21 11:27:01.144110",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry Type",
diff --git a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py
index a4116ab..1069ec8 100644
--- a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py
+++ b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py
@@ -7,4 +7,6 @@
from frappe.model.document import Document
class StockEntryType(Document):
- pass
+ def validate(self):
+ if self.add_to_transit and self.purpose != 'Material Transfer':
+ self.add_to_transit = 0
diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
index 349d8ae..ba31ad7 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
@@ -15,10 +15,12 @@
from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import create_landed_cost_voucher
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import BackDatedStockTransaction
+from frappe.core.page.permission_manager.permission_manager import reset
class TestStockLedgerEntry(unittest.TestCase):
def setUp(self):
items = create_items()
+ reset('Stock Entry')
# delete SLE and BINs for all items
frappe.db.sql("delete from `tabStock Ledger Entry` where item_code in (%s)" % (', '.join(['%s']*len(items))), items)
@@ -34,7 +36,7 @@
qty=50,
rate=100,
company=company,
- expense_account = "Stock Adjustment - _TC",
+ expense_account = "Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC",
posting_date='2020-04-10',
posting_time='14:00'
)
@@ -46,7 +48,7 @@
qty=10,
rate=200,
company=company,
- expense_account = "Stock Adjustment - _TC",
+ expense_account="Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC",
posting_date='2020-04-20',
posting_time='14:00'
)
@@ -58,7 +60,7 @@
target="Finished Goods - _TC",
company=company,
qty=10,
- expense_account="Stock Adjustment - _TC",
+ expense_account="Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC",
posting_date='2020-04-30',
posting_time='14:00'
)
@@ -90,7 +92,7 @@
qty=50,
rate=150,
company=company,
- expense_account = "Stock Adjustment - _TC",
+ expense_account ="Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC",
posting_date='2020-04-12',
posting_time='14:00'
)
@@ -314,10 +316,11 @@
# Set User with Stock User role but not Stock Manager
try:
user = frappe.get_doc("User", "test@example.com")
- frappe.set_user(user.name)
user.add_roles("Stock User")
user.remove_roles("Stock Manager")
+ frappe.set_user(user.name)
+
stock_entry_on_today = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100)
back_dated_se_1 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100,
posting_date=add_days(today(), -1), do_not_submit=True)
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index b452e96..7e216d6 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -72,7 +72,7 @@
if item_dict.get("serial_nos"):
item.current_serial_no = item_dict.get("serial_nos")
- if self.purpose == "Stock Reconciliation":
+ if self.purpose == "Stock Reconciliation" and not item.serial_no:
item.serial_no = item.current_serial_no
item.current_qty = item_dict.get("qty")
@@ -398,7 +398,7 @@
merge_similar_entries = {}
for d in sl_entries:
- if not d.serial_no or d.actual_qty < 0:
+ if not d.serial_no or flt(d.get("actual_qty")) < 0:
new_sl_entries.append(d)
continue
@@ -469,7 +469,7 @@
def submit(self):
if len(self.items) > 100:
msgprint(_("The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Draft stage"))
- self.queue_action('submit')
+ self.queue_action('submit', timeout=2000)
else:
self._submit()
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 6690c6a..36380b8 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -32,7 +32,7 @@
company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
# [[qty, valuation_rate, posting_date,
# posting_time, expected_stock_value, bin_qty, bin_valuation]]
-
+
input_data = [
[50, 1000, "2012-12-26", "12:00"],
[25, 900, "2012-12-26", "12:00"],
@@ -86,7 +86,7 @@
se1.cancel()
def test_get_items(self):
- create_warehouse("_Test Warehouse Group 1",
+ create_warehouse("_Test Warehouse Group 1",
{"is_group": 1, "company": "_Test Company", "parent_warehouse": "All Warehouses - _TC"})
create_warehouse("_Test Warehouse Ledger 1",
{"is_group": 0, "parent_warehouse": "_Test Warehouse Group 1 - _TC", "company": "_Test Company"})
diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
index 85c7ebe..6bbba05 100644
--- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
+++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"creation": "2015-02-17 01:06:05.072764",
"doctype": "DocType",
"document_type": "Other",
@@ -170,6 +171,7 @@
},
{
"default": "0",
+ "depends_on": "allow_zero_valuation_rate",
"fieldname": "allow_zero_valuation_rate",
"fieldtype": "Check",
"label": "Allow Zero Valuation Rate",
@@ -179,7 +181,7 @@
],
"istable": 1,
"links": [],
- "modified": "2021-03-23 11:09:44.407157",
+ "modified": "2021-05-21 12:13:33.041266",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation Item",
@@ -189,4 +191,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index 84af57b..cf5d98d 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -5,39 +5,44 @@
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
+ "item_defaults_section",
"item_naming_by",
"item_group",
"stock_uom",
"default_warehouse",
- "sample_retention_warehouse",
"column_break_4",
"valuation_method",
+ "sample_retention_warehouse",
+ "use_naming_series",
+ "naming_series_prefix",
+ "section_break_9",
"over_delivery_receipt_allowance",
- "action_if_quality_inspection_is_not_submitted",
- "show_barcode_field",
- "clean_description_html",
- "disable_serial_no_and_batch_selector",
- "section_break_7",
+ "role_allowed_to_over_deliver_receive",
+ "column_break_12",
"auto_insert_price_list_rate_if_missing",
"allow_negative_stock",
- "column_break_10",
+ "show_barcode_field",
+ "clean_description_html",
+ "action_if_quality_inspection_is_not_submitted",
+ "section_break_7",
"automatically_set_serial_nos_based_on_fifo",
"set_qty_in_transactions_based_on_serial_no_input",
+ "column_break_10",
+ "disable_serial_no_and_batch_selector",
"auto_material_request",
"auto_indent",
+ "column_break_27",
"reorder_email_notify",
"inter_warehouse_transfer_settings_section",
"allow_from_dn",
+ "column_break_31",
"allow_from_pr",
"control_historical_stock_transactions_section",
- "role_allowed_to_create_edit_back_dated_transactions",
- "column_break_26",
"stock_frozen_upto",
"stock_frozen_upto_days",
- "stock_auth_role",
- "batch_id_sb",
- "use_naming_series",
- "naming_series_prefix"
+ "column_break_26",
+ "role_allowed_to_create_edit_back_dated_transactions",
+ "stock_auth_role"
],
"fields": [
{
@@ -101,23 +106,24 @@
"default": "1",
"fieldname": "show_barcode_field",
"fieldtype": "Check",
- "label": "Show Barcode Field"
+ "label": "Show Barcode Field in Stock Transactions"
},
{
"default": "1",
"fieldname": "clean_description_html",
"fieldtype": "Check",
- "label": "Convert Item Description to Clean HTML"
+ "label": "Convert Item Description to Clean HTML in Transactions"
},
{
"fieldname": "section_break_7",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "label": "Serialised and Batch Setting"
},
{
"default": "0",
"fieldname": "auto_insert_price_list_rate_if_missing",
"fieldtype": "Check",
- "label": "Auto Insert Price List Rate If Missing"
+ "label": "Auto Insert Item Price If Missing"
},
{
"default": "0",
@@ -179,15 +185,10 @@
"options": "Role"
},
{
- "fieldname": "batch_id_sb",
- "fieldtype": "Section Break",
- "label": "Batch Identification"
- },
- {
"default": "0",
"fieldname": "use_naming_series",
"fieldtype": "Check",
- "label": "Use Naming Series"
+ "label": "Have Default Naming Series for Batch ID?"
},
{
"default": "BATCH-",
@@ -234,6 +235,35 @@
"fieldname": "disable_serial_no_and_batch_selector",
"fieldtype": "Check",
"label": "Disable Serial No And Batch Selector"
+ },
+ {
+ "description": "Users with this role are allowed to over deliver/receive against orders above the allowance percentage",
+ "fieldname": "role_allowed_to_over_deliver_receive",
+ "fieldtype": "Link",
+ "label": "Role Allowed to Over Deliver/Receive",
+ "options": "Role"
+ },
+ {
+ "fieldname": "item_defaults_section",
+ "fieldtype": "Section Break",
+ "label": "Item Defaults"
+ },
+ {
+ "fieldname": "section_break_9",
+ "fieldtype": "Section Break",
+ "label": "Stock Transactions Settings"
+ },
+ {
+ "fieldname": "column_break_12",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_27",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_31",
+ "fieldtype": "Column Break"
}
],
"icon": "icon-cog",
@@ -241,7 +271,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-01-18 13:15:38.352796",
+ "modified": "2021-04-30 17:27:42.709231",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Settings",
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py
index 3b9608b..2dd7c6f 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.py
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.py
@@ -30,7 +30,7 @@
# show/hide barcode field
for name in ["barcode", "barcodes", "scan_barcode"]:
frappe.make_property_setter({'fieldname': name, 'property': 'hidden',
- 'value': 0 if self.show_barcode_field else 1})
+ 'value': 0 if self.show_barcode_field else 1}, validate_fields_for_doctype=False)
self.validate_warehouses()
self.cant_change_valuation_method()
@@ -67,10 +67,10 @@
self.toggle_warehouse_field_for_inter_warehouse_transfer()
def toggle_warehouse_field_for_inter_warehouse_transfer(self):
- make_property_setter("Sales Invoice Item", "target_warehouse", "hidden", 1 - cint(self.allow_from_dn), "Check")
- make_property_setter("Delivery Note Item", "target_warehouse", "hidden", 1 - cint(self.allow_from_dn), "Check")
- make_property_setter("Purchase Invoice Item", "from_warehouse", "hidden", 1 - cint(self.allow_from_pr), "Check")
- make_property_setter("Purchase Receipt Item", "from_warehouse", "hidden", 1 - cint(self.allow_from_pr), "Check")
+ make_property_setter("Sales Invoice Item", "target_warehouse", "hidden", 1 - cint(self.allow_from_dn), "Check", validate_fields_for_doctype=False)
+ make_property_setter("Delivery Note Item", "target_warehouse", "hidden", 1 - cint(self.allow_from_dn), "Check", validate_fields_for_doctype=False)
+ make_property_setter("Purchase Invoice Item", "from_warehouse", "hidden", 1 - cint(self.allow_from_pr), "Check", validate_fields_for_doctype=False)
+ make_property_setter("Purchase Receipt Item", "from_warehouse", "hidden", 1 - cint(self.allow_from_pr), "Check", validate_fields_for_doctype=False)
def clean_all_descriptions():
diff --git a/erpnext/stock/doctype/warehouse/warehouse.json b/erpnext/stock/doctype/warehouse/warehouse.json
index bddb114..9b90932 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.json
+++ b/erpnext/stock/doctype/warehouse/warehouse.json
@@ -70,6 +70,7 @@
"oldfieldname": "company",
"oldfieldtype": "Link",
"options": "Company",
+ "read_only_depends_on": "eval: !doc.__islocal",
"remember_last_selected_value": 1,
"reqd": 1,
"search_index": 1
@@ -244,7 +245,7 @@
"idx": 1,
"is_tree": 1,
"links": [],
- "modified": "2021-02-16 17:21:52.380098",
+ "modified": "2021-04-09 19:54:56.263965",
"modified_by": "Administrator",
"module": "Stock",
"name": "Warehouse",
diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py
index 6c84f16..2062bdd 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.py
+++ b/erpnext/stock/doctype/warehouse/warehouse.py
@@ -3,8 +3,9 @@
from __future__ import unicode_literals
import frappe, erpnext
-from frappe.utils import cint, nowdate
+from frappe.utils import cint, flt
from frappe import throw, _
+from collections import defaultdict
from frappe.utils.nestedset import NestedSet
from erpnext.stock import get_warehouse_account
from frappe.contacts.address_and_contact import load_address_and_contact
@@ -139,8 +140,6 @@
@frappe.whitelist()
def get_children(doctype, parent=None, company=None, is_root=False):
- from erpnext.stock.utils import get_stock_value_from_bin
-
if is_root:
parent = ""
@@ -153,13 +152,48 @@
warehouses = frappe.get_list(doctype, fields=fields, filters=filters, order_by='name')
+ company_currency = ''
+ if company:
+ company_currency = frappe.get_cached_value('Company', company, 'default_currency')
+
+ warehouse_wise_value = get_warehouse_wise_stock_value(company)
+
# return warehouses
for wh in warehouses:
- wh["balance"] = get_stock_value_from_bin(warehouse=wh.value)
- if company:
- wh["company_currency"] = frappe.db.get_value('Company', company, 'default_currency')
+ wh["balance"] = warehouse_wise_value.get(wh.value)
+ if company_currency:
+ wh["company_currency"] = company_currency
return warehouses
+def get_warehouse_wise_stock_value(company):
+ warehouses = frappe.get_all('Warehouse',
+ fields = ['name', 'parent_warehouse'], filters = {'company': company})
+ parent_warehouse = {d.name : d.parent_warehouse for d in warehouses}
+
+ filters = {'warehouse': ('in', [data.name for data in warehouses])}
+ bin_data = frappe.get_all('Bin', fields = ['sum(stock_value) as stock_value', 'warehouse'],
+ filters = filters, group_by = 'warehouse')
+
+ warehouse_wise_stock_value = defaultdict(float)
+ for row in bin_data:
+ if not row.stock_value:
+ continue
+
+ warehouse_wise_stock_value[row.warehouse] = row.stock_value
+ update_value_in_parent_warehouse(warehouse_wise_stock_value,
+ parent_warehouse, row.warehouse, row.stock_value)
+
+ return warehouse_wise_stock_value
+
+def update_value_in_parent_warehouse(warehouse_wise_stock_value, parent_warehouse_dict, warehouse, stock_value):
+ parent_warehouse = parent_warehouse_dict.get(warehouse)
+ if not parent_warehouse:
+ return
+
+ warehouse_wise_stock_value[parent_warehouse] += flt(stock_value)
+ update_value_in_parent_warehouse(warehouse_wise_stock_value, parent_warehouse_dict,
+ parent_warehouse, stock_value)
+
@frappe.whitelist()
def add_node():
from frappe.desk.treeview import make_tree_args
diff --git a/erpnext/stock/doctype/warehouse/warehouse_tree.js b/erpnext/stock/doctype/warehouse/warehouse_tree.js
index 3665c05..407d7d1 100644
--- a/erpnext/stock/doctype/warehouse/warehouse_tree.js
+++ b/erpnext/stock/doctype/warehouse/warehouse_tree.js
@@ -20,7 +20,7 @@
onrender: function(node) {
if (node.data && node.data.balance!==undefined) {
$('<span class="balance-area pull-right">'
- + format_currency(Math.abs(node.data.balance), node.data.company_currency)
+ + format_currency((node.data.balance), node.data.company_currency)
+ '</span>').insertBefore(node.$ul);
}
}
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index e23f7d4..d1dcdc2 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -79,14 +79,14 @@
get_price_list_rate(args, item, out)
if args.customer and cint(args.is_pos):
- out.update(get_pos_profile_item_details(args.company, args))
+ out.update(get_pos_profile_item_details(args.company, args, update_data=True))
if (args.get("doctype") == "Material Request" and
args.get("material_request_type") == "Material Transfer"):
out.update(get_bin_details(args.item_code, args.get("from_warehouse")))
elif out.get("warehouse"):
- out.update(get_bin_details(args.item_code, out.warehouse))
+ out.update(get_bin_details(args.item_code, out.warehouse, args.company))
# update args with out, if key or value not exists
for key, value in iteritems(out):
@@ -309,8 +309,6 @@
"update_stock": args.get("update_stock") if args.get('doctype') in ['Sales Invoice', 'Purchase Invoice'] else 0,
"delivered_by_supplier": item.delivered_by_supplier if args.get("doctype") in ["Sales Order", "Sales Invoice"] else 0,
"is_fixed_asset": item.is_fixed_asset,
- "weight_per_unit":item.weight_per_unit,
- "weight_uom":item.weight_uom,
"last_purchase_rate": item.last_purchase_rate if args.get("doctype") in ["Purchase Order"] else 0,
"transaction_date": args.get("transaction_date"),
"against_blanket_order": args.get("against_blanket_order"),
@@ -472,7 +470,9 @@
item_tax_template = _get_item_tax_template(args, item_group_doc.taxes, out)
item_group = item_group_doc.parent_item_group
-def _get_item_tax_template(args, taxes, out={}, for_validate=False):
+def _get_item_tax_template(args, taxes, out=None, for_validate=False):
+ if out is None:
+ out = {}
taxes_with_validity = []
taxes_with_no_validity = []
@@ -611,8 +611,12 @@
meta = frappe.get_meta(args.parenttype or args.doctype)
if meta.get_field("currency") or args.get('currency'):
- pl_details = get_price_list_currency_and_exchange_rate(args)
- args.update(pl_details)
+ if not args.get("price_list_currency") or not args.get("plc_conversion_rate"):
+ # if currency and plc_conversion_rate exist then
+ # `get_price_list_currency_and_exchange_rate` has already been called
+ pl_details = get_price_list_currency_and_exchange_rate(args)
+ args.update(pl_details)
+
if meta.get_field("currency"):
validate_conversion_rate(args, meta)
@@ -922,10 +926,19 @@
{"item_code": item_code, "warehouse": warehouse}, "projected_qty")}
@frappe.whitelist()
-def get_bin_details(item_code, warehouse):
- return frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse},
+def get_bin_details(item_code, warehouse, company=None):
+ bin_details = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse},
["projected_qty", "actual_qty", "reserved_qty"], as_dict=True, cache=True) \
or {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0}
+ if company:
+ bin_details['company_total_stock'] = get_company_total_stock(item_code, company)
+ return bin_details
+
+def get_company_total_stock(item_code, company):
+ return frappe.db.sql("""SELECT sum(actual_qty) from
+ (`tabBin` INNER JOIN `tabWarehouse` ON `tabBin`.warehouse = `tabWarehouse`.name)
+ WHERE `tabWarehouse`.company = %s and `tabBin`.item_code = %s""",
+ (company, item_code))[0][0]
@frappe.whitelist()
def get_serial_no_details(item_code, warehouse, stock_qty, serial_no):
@@ -993,6 +1006,8 @@
args = process_args(args)
parent = get_price_list_currency_and_exchange_rate(args)
+ args.update(parent)
+
children = []
if "items" in args:
@@ -1057,7 +1072,7 @@
return frappe._dict({
"price_list_currency": price_list_currency,
"price_list_uom_dependant": price_list_uom_dependant,
- "plc_conversion_rate": plc_conversion_rate
+ "plc_conversion_rate": plc_conversion_rate or 1
})
@frappe.whitelist()
diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
index 087c12e..01927c2 100644
--- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
+++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
@@ -70,7 +70,7 @@
return frappe.db.sql("""
select item_code, batch_no, warehouse, posting_date, sum(actual_qty) as actual_qty
from `tabStock Ledger Entry`
- where docstatus < 2 and ifnull(batch_no, '') != '' %s
+ where is_cancelled = 0 and docstatus < 2 and ifnull(batch_no, '') != '' %s
group by voucher_no, batch_no, item_code, warehouse
order by item_code, warehouse""" %
conditions, as_dict=1)
diff --git a/erpnext/stock/report/item_variant_details/item_variant_details.py b/erpnext/stock/report/item_variant_details/item_variant_details.py
index e8449cc..d8563d7 100644
--- a/erpnext/stock/report/item_variant_details/item_variant_details.py
+++ b/erpnext/stock/report/item_variant_details/item_variant_details.py
@@ -14,47 +14,58 @@
if not item:
return []
item_dicts = []
- variants = None
- variant_results = frappe.db.sql("""select name from `tabItem`
- where variant_of = %s""", item, as_dict=1)
+ variant_results = frappe.db.get_all(
+ "Item",
+ fields=["name"],
+ filters={
+ "variant_of": ["=", item],
+ "disabled": 0
+ }
+ )
+
if not variant_results:
- frappe.msgprint(_("There isn't any item variant for the selected item"))
+ frappe.msgprint(_("There aren't any item variants for the selected item"))
return []
else:
- variants = ", ".join([frappe.db.escape(variant['name']) for variant in variant_results])
+ variant_list = [variant['name'] for variant in variant_results]
- order_count_map = get_open_sales_orders_map(variants)
- stock_details_map = get_stock_details_map(variants)
- buying_price_map = get_buying_price_map(variants)
- selling_price_map = get_selling_price_map(variants)
- attr_val_map = get_attribute_values_map(variants)
+ order_count_map = get_open_sales_orders_count(variant_list)
+ stock_details_map = get_stock_details_map(variant_list)
+ buying_price_map = get_buying_price_map(variant_list)
+ selling_price_map = get_selling_price_map(variant_list)
+ attr_val_map = get_attribute_values_map(variant_list)
- attribute_list = [d[0] for d in frappe.db.sql("""select attribute
- from `tabItem Variant Attribute`
- where parent in ({variants}) group by attribute""".format(variants=variants))]
+ attributes = frappe.db.get_all(
+ "Item Variant Attribute",
+ fields=["attribute"],
+ filters={
+ "parent": ["in", variant_list]
+ },
+ group_by="attribute"
+ )
+ attribute_list = [row.get("attribute") for row in attributes]
# Prepare dicts
variant_dicts = [{"variant_name": d['name']} for d in variant_results]
for item_dict in variant_dicts:
- name = item_dict["variant_name"]
+ name = item_dict.get("variant_name")
- for d in attribute_list:
- attr_dict = attr_val_map[name]
- if attr_dict and attr_dict.get(d):
- item_dict[d] = attr_val_map[name][d]
+ for attribute in attribute_list:
+ attr_dict = attr_val_map.get(name)
+ if attr_dict and attr_dict.get(attribute):
+ item_dict[frappe.scrub(attribute)] = attr_val_map.get(name).get(attribute)
- item_dict["Open Orders"] = order_count_map.get(name) or 0
+ item_dict["open_orders"] = order_count_map.get(name) or 0
if stock_details_map.get(name):
- item_dict["Inventory"] = stock_details_map.get(name)["Inventory"] or 0
- item_dict["In Production"] = stock_details_map.get(name)["In Production"] or 0
- item_dict["Available Selling"] = stock_details_map.get(name)["Available Selling"] or 0
+ item_dict["current_stock"] = stock_details_map.get(name)["Inventory"] or 0
+ item_dict["in_production"] = stock_details_map.get(name)["In Production"] or 0
else:
- item_dict["Inventory"] = item_dict["In Production"] = item_dict["Available Selling"] = 0
+ item_dict["current_stock"] = item_dict["in_production"] = 0
- item_dict["Avg. Buying Price List Rate"] = buying_price_map.get(name) or 0
- item_dict["Avg. Selling Price List Rate"] = selling_price_map.get(name) or 0
+ item_dict["avg_buying_price_list_rate"] = buying_price_map.get(name) or 0
+ item_dict["avg_selling_price_list_rate"] = selling_price_map.get(name) or 0
item_dicts.append(item_dict)
@@ -71,117 +82,158 @@
item_doc = frappe.get_doc("Item", item)
- for d in item_doc.attributes:
- columns.append(d.attribute + ":Data:100")
+ for entry in item_doc.attributes:
+ columns.append({
+ "fieldname": frappe.scrub(entry.attribute),
+ "label": entry.attribute,
+ "fieldtype": "Data",
+ "width": 100
+ })
- columns += [_("Avg. Buying Price List Rate") + ":Currency:110", _("Avg. Selling Price List Rate") + ":Currency:110",
- _("Inventory") + ":Float:100", _("In Production") + ":Float:100",
- _("Open Orders") + ":Float:100", _("Available Selling") + ":Float:100"
+ additional_columns = [
+ {
+ "fieldname": "avg_buying_price_list_rate",
+ "label": _("Avg. Buying Price List Rate"),
+ "fieldtype": "Currency",
+ "width": 150
+ },
+ {
+ "fieldname": "avg_selling_price_list_rate",
+ "label": _("Avg. Selling Price List Rate"),
+ "fieldtype": "Currency",
+ "width": 150
+ },
+ {
+ "fieldname": "current_stock",
+ "label": _("Current Stock"),
+ "fieldtype": "Float",
+ "width": 120
+ },
+ {
+ "fieldname": "in_production",
+ "label": _("In Production"),
+ "fieldtype": "Float",
+ "width": 150
+ },
+ {
+ "fieldname": "open_orders",
+ "label": _("Open Sales Orders"),
+ "fieldtype": "Float",
+ "width": 150
+ }
]
+ columns.extend(additional_columns)
return columns
-def get_open_sales_orders_map(variants):
- open_sales_orders = frappe.db.sql("""
- select
- count(*) as count,
- item_code
- from
- `tabSales Order Item`
- where
- docstatus = 1 and
- qty > ifnull(delivered_qty, 0) and
- item_code in ({variants})
- group by
- item_code
- """.format(variants=variants), as_dict=1)
+def get_open_sales_orders_count(variants_list):
+ open_sales_orders = frappe.db.get_list(
+ "Sales Order",
+ fields=[
+ "name",
+ "`tabSales Order Item`.item_code"
+ ],
+ filters=[
+ ["Sales Order", "docstatus", "=", 1],
+ ["Sales Order Item", "item_code", "in", variants_list]
+ ],
+ distinct=1
+ )
order_count_map = {}
- for d in open_sales_orders:
- order_count_map[d["item_code"]] = d["count"]
+ for row in open_sales_orders:
+ item_code = row.get("item_code")
+ if order_count_map.get(item_code) is None:
+ order_count_map[item_code] = 1
+ else:
+ order_count_map[item_code] += 1
return order_count_map
-def get_stock_details_map(variants):
- stock_details = frappe.db.sql("""
- select
- sum(planned_qty) as planned_qty,
- sum(actual_qty) as actual_qty,
- sum(projected_qty) as projected_qty,
- item_code
- from
- `tabBin`
- where
- item_code in ({variants})
- group by
- item_code
- """.format(variants=variants), as_dict=1)
+def get_stock_details_map(variant_list):
+ stock_details = frappe.db.get_all(
+ "Bin",
+ fields=[
+ "sum(planned_qty) as planned_qty",
+ "sum(actual_qty) as actual_qty",
+ "sum(projected_qty) as projected_qty",
+ "item_code",
+ ],
+ filters={
+ "item_code": ["in", variant_list]
+ },
+ group_by="item_code"
+ )
stock_details_map = {}
- for d in stock_details:
- name = d["item_code"]
+ for row in stock_details:
+ name = row.get("item_code")
stock_details_map[name] = {
- "Inventory" :d["actual_qty"],
- "In Production" :d["planned_qty"],
- "Available Selling" :d["projected_qty"]
+ "Inventory": row.get("actual_qty"),
+ "In Production": row.get("planned_qty")
}
return stock_details_map
-def get_buying_price_map(variants):
- buying = frappe.db.sql("""
- select
- avg(price_list_rate) as avg_rate,
- item_code
- from
- `tabItem Price`
- where
- item_code in ({variants}) and buying=1
- group by
- item_code
- """.format(variants=variants), as_dict=1)
+def get_buying_price_map(variant_list):
+ buying = frappe.db.get_all(
+ "Item Price",
+ fields=[
+ "avg(price_list_rate) as avg_rate",
+ "item_code",
+ ],
+ filters={
+ "item_code": ["in", variant_list],
+ "buying": 1
+ },
+ group_by="item_code"
+ )
buying_price_map = {}
- for d in buying:
- buying_price_map[d["item_code"]] = d["avg_rate"]
+ for row in buying:
+ buying_price_map[row.get("item_code")] = row.get("avg_rate")
return buying_price_map
-def get_selling_price_map(variants):
- selling = frappe.db.sql("""
- select
- avg(price_list_rate) as avg_rate,
- item_code
- from
- `tabItem Price`
- where
- item_code in ({variants}) and selling=1
- group by
- item_code
- """.format(variants=variants), as_dict=1)
+def get_selling_price_map(variant_list):
+ selling = frappe.db.get_all(
+ "Item Price",
+ fields=[
+ "avg(price_list_rate) as avg_rate",
+ "item_code",
+ ],
+ filters={
+ "item_code": ["in", variant_list],
+ "selling": 1
+ },
+ group_by="item_code"
+ )
selling_price_map = {}
- for d in selling:
- selling_price_map[d["item_code"]] = d["avg_rate"]
+ for row in selling:
+ selling_price_map[row.get("item_code")] = row.get("avg_rate")
return selling_price_map
-def get_attribute_values_map(variants):
- list_attr = frappe.db.sql("""
- select
- attribute, attribute_value, parent
- from
- `tabItem Variant Attribute`
- where
- parent in ({variants})
- """.format(variants=variants), as_dict=1)
+def get_attribute_values_map(variant_list):
+ attribute_list = frappe.db.get_all(
+ "Item Variant Attribute",
+ fields=[
+ "attribute",
+ "attribute_value",
+ "parent"
+ ],
+ filters={
+ "parent": ["in", variant_list]
+ }
+ )
attr_val_map = {}
- for d in list_attr:
- name = d["parent"]
+ for row in attribute_list:
+ name = row.get("parent")
if not attr_val_map.get(name):
attr_val_map[name] = {}
- attr_val_map[name][d["attribute"]] = d["attribute_value"]
+ attr_val_map[name][row.get("attribute")] = row.get("attribute_value")
return attr_val_map
diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
index 5df3fa8..2f70523 100644
--- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
+++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
@@ -55,19 +55,31 @@
def get_consumed_items(condition):
+ purpose_to_exclude = [
+ "Material Transfer for Manufacture",
+ "Material Transfer",
+ "Send to Subcontractor"
+ ]
+
+ condition += """
+ and (
+ purpose is NULL
+ or purpose not in ({})
+ )
+ """.format(', '.join([f"'{p}'" for p in purpose_to_exclude]))
+ condition = condition.replace("posting_date", "sle.posting_date")
+
consumed_items = frappe.db.sql("""
select item_code, abs(sum(actual_qty)) as consumed_qty
- from `tabStock Ledger Entry`
- where actual_qty < 0
+ from `tabStock Ledger Entry` as sle left join `tabStock Entry` as se
+ on sle.voucher_no = se.name
+ where
+ actual_qty < 0
and voucher_type not in ('Delivery Note', 'Sales Invoice')
%s
- group by item_code
- """ % condition, as_dict=1)
+ group by item_code""" % condition, as_dict=1)
- consumed_items_map = {}
- for item in consumed_items:
- consumed_items_map.setdefault(item.item_code, item.consumed_qty)
-
+ consumed_items_map = {item.item_code : item.consumed_qty for item in consumed_items}
return consumed_items_map
def get_delivered_items(condition):
diff --git a/erpnext/stock/report/serial_no_ledger/__init__.py b/erpnext/stock/report/serial_no_ledger/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/report/serial_no_ledger/__init__.py
diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js
new file mode 100644
index 0000000..616312e
--- /dev/null
+++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js
@@ -0,0 +1,52 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Serial No Ledger"] = {
+ "filters": [
+ {
+ 'label': __('Item Code'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'item_code',
+ 'reqd': 1,
+ 'options': 'Item',
+ get_query: function() {
+ return {
+ filters: {
+ 'has_serial_no': 1
+ }
+ }
+ }
+ },
+ {
+ 'label': __('Serial No'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'serial_no',
+ 'options': 'Serial No',
+ 'reqd': 1
+ },
+ {
+ 'label': __('Warehouse'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'warehouse',
+ 'options': 'Warehouse',
+ get_query: function() {
+ let company = frappe.query_report.get_filter_value('company');
+
+ if (company) {
+ return {
+ filters: {
+ 'company': company
+ }
+ }
+ }
+ }
+ },
+ {
+ 'label': __('As On Date'),
+ 'fieldtype': 'Date',
+ 'fieldname': 'posting_date',
+ 'default': frappe.datetime.get_today()
+ },
+ ]
+};
diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.json b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.json
new file mode 100644
index 0000000..e20e74c
--- /dev/null
+++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.json
@@ -0,0 +1,33 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-04-20 13:32:41.523219",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "json": "{}",
+ "modified": "2021-04-20 13:33:19.015829",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Serial No Ledger",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Stock Ledger Entry",
+ "report_name": "Serial No Ledger",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Purchase User"
+ },
+ {
+ "role": "Sales User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
new file mode 100644
index 0000000..c3339fd
--- /dev/null
+++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
@@ -0,0 +1,53 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+from frappe import _
+from erpnext.stock.stock_ledger import get_stock_ledger_entries
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+def execute(filters=None):
+ columns = get_columns(filters)
+ data = get_data(filters)
+ return columns, data
+
+def get_columns(filters):
+ columns = [{
+ 'label': _('Posting Date'),
+ 'fieldtype': 'Date',
+ 'fieldname': 'posting_date'
+ }, {
+ 'label': _('Posting Time'),
+ 'fieldtype': 'Time',
+ 'fieldname': 'posting_time'
+ }, {
+ 'label': _('Voucher Type'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'voucher_type',
+ 'options': 'DocType',
+ 'width': 220
+ }, {
+ 'label': _('Voucher No'),
+ 'fieldtype': 'Dynamic Link',
+ 'fieldname': 'voucher_no',
+ 'options': 'voucher_type',
+ 'width': 220
+ }, {
+ 'label': _('Company'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'company',
+ 'options': 'Company',
+ 'width': 220
+ }, {
+ 'label': _('Warehouse'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'warehouse',
+ 'options': 'Warehouse',
+ 'width': 220
+ }]
+
+ return columns
+
+def get_data(filters):
+ return get_stock_ledger_entries(filters, '<=', order="asc") or []
+
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py
index ff603fc..623dc2f 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/stock_ageing.py
@@ -49,7 +49,7 @@
for batch in fifo_queue:
batch_age = date_diff(to_date, batch[1])
- if type(batch[0]) in ['int', 'float']:
+ if isinstance(batch[0], (int, float)):
age_qty += batch_age * batch[0]
total_qty += batch[0]
else:
@@ -302,4 +302,4 @@
fieldname=fieldname,
fieldtype=fieldtype,
width=width
- ))
\ No newline at end of file
+ ))
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index 6dfede4..bbd73e9 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -165,7 +165,7 @@
select
sle.item_code, warehouse, sle.posting_date, sle.actual_qty, sle.valuation_rate,
sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference,
- sle.item_code as name, sle.voucher_no, sle.stock_value
+ sle.item_code as name, sle.voucher_no, sle.stock_value, sle.batch_no
from
`tabStock Ledger Entry` sle force index (posting_sort_index)
where sle.docstatus < 2 %s %s
@@ -193,7 +193,7 @@
qty_dict = iwb_map[(d.company, d.item_code, d.warehouse)]
- if d.voucher_type == "Stock Reconciliation":
+ if d.voucher_type == "Stock Reconciliation" and not d.batch_no:
qty_diff = flt(d.qty_after_transaction) - flt(qty_dict.bal_qty)
else:
qty_diff = flt(d.actual_qty)
diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js
index babc6dc..cb109f8 100644
--- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js
+++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js
@@ -14,7 +14,14 @@
"fieldname":"warehouse",
"label": __("Warehouse"),
"fieldtype": "Link",
- "options": "Warehouse"
+ "options": "Warehouse",
+ "get_query": () => {
+ return {
+ filters: {
+ company: frappe.query_report.get_filter_value('company')
+ }
+ }
+ }
},
{
"fieldname":"item_code",
diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
index 1183e41..808d279 100644
--- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
+++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
@@ -6,6 +6,7 @@
from frappe import _
from frappe.utils import flt, today
from erpnext.stock.utils import update_included_uom_in_report, is_reposting_item_valuation_in_progress
+from erpnext.accounts.doctype.pos_invoice.pos_invoice import get_pos_reserved_qty
def execute(filters=None):
is_reposting_item_valuation_in_progress()
@@ -49,9 +50,13 @@
if (re_order_level or re_order_qty) and re_order_level > bin.projected_qty:
shortage_qty = re_order_level - flt(bin.projected_qty)
+ reserved_qty_for_pos = get_pos_reserved_qty(bin.item_code, bin.warehouse)
+ if reserved_qty_for_pos:
+ bin.projected_qty -= reserved_qty_for_pos
+
data.append([item.name, item.item_name, item.description, item.item_group, item.brand, bin.warehouse,
item.stock_uom, bin.actual_qty, bin.planned_qty, bin.indented_qty, bin.ordered_qty,
- bin.reserved_qty, bin.reserved_qty_for_production, bin.reserved_qty_for_sub_contract,
+ bin.reserved_qty, bin.reserved_qty_for_production, bin.reserved_qty_for_sub_contract, reserved_qty_for_pos,
bin.projected_qty, re_order_level, re_order_qty, shortage_qty])
if include_uom:
@@ -74,9 +79,11 @@
{"label": _("Requested Qty"), "fieldname": "indented_qty", "fieldtype": "Float", "width": 110, "convertible": "qty"},
{"label": _("Ordered Qty"), "fieldname": "ordered_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
{"label": _("Reserved Qty"), "fieldname": "reserved_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
- {"label": _("Reserved Qty for Production"), "fieldname": "reserved_qty_for_production", "fieldtype": "Float",
+ {"label": _("Reserved for Production"), "fieldname": "reserved_qty_for_production", "fieldtype": "Float",
"width": 100, "convertible": "qty"},
- {"label": _("Reserved for sub contracting"), "fieldname": "reserved_qty_for_sub_contract", "fieldtype": "Float",
+ {"label": _("Reserved for Sub Contracting"), "fieldname": "reserved_qty_for_sub_contract", "fieldtype": "Float",
+ "width": 100, "convertible": "qty"},
+ {"label": _("Reserved for POS Transactions"), "fieldname": "reserved_qty_for_pos", "fieldtype": "Float",
"width": 100, "convertible": "qty"},
{"label": _("Projected Qty"), "fieldname": "projected_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
{"label": _("Reorder Level"), "fieldname": "re_order_level", "fieldtype": "Float", "width": 100, "convertible": "qty"},
diff --git a/erpnext/stock/report/total_stock_summary/total_stock_summary.py b/erpnext/stock/report/total_stock_summary/total_stock_summary.py
index ed52393..59c253c 100644
--- a/erpnext/stock/report/total_stock_summary/total_stock_summary.py
+++ b/erpnext/stock/report/total_stock_summary/total_stock_summary.py
@@ -51,7 +51,7 @@
INNER JOIN `tabWarehouse` warehouse
ON warehouse.name = ledger.warehouse
WHERE
- actual_qty != 0 %s""" % (columns, conditions))
+ ledger.actual_qty != 0 %s""" % (columns, conditions))
def validate_filters(filters):
if filters.get("group_by") == 'Company' and \
diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py
index 8ba1f1c..8917bfe 100644
--- a/erpnext/stock/stock_balance.py
+++ b/erpnext/stock/stock_balance.py
@@ -194,9 +194,6 @@
serial_nos = frappe.db.sql("""select count(name) from `tabSerial No`
where item_code=%s and warehouse=%s and docstatus < 2""", (d[0], d[1]))
- if serial_nos and flt(serial_nos[0][0]) != flt(d[2]):
- print(d[0], d[1], d[2], serial_nos[0][0])
-
sle = frappe.db.sql("""select valuation_rate, company from `tabStock Ledger Entry`
where item_code = %s and warehouse = %s and is_cancelled = 0
order by posting_date desc limit 1""", (d[0], d[1]))
@@ -230,7 +227,7 @@
})
update_bin(args)
-
+
create_repost_item_valuation_entry({
"item_code": d[0],
"warehouse": d[1],
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 121c51c..b2825fc 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -2,9 +2,11 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
-import frappe, erpnext
+import frappe
+import erpnext
+import copy
from frappe import _
-from frappe.utils import cint, flt, cstr, now, now_datetime
+from frappe.utils import cint, flt, cstr, now, get_link_to_form
from frappe.model.meta import get_field_precision
from erpnext.stock.utils import get_valuation_method, get_incoming_outgoing_rate_for_cancel
from erpnext.stock.utils import get_bin
@@ -13,6 +15,8 @@
# future reposting
class NegativeStockError(frappe.ValidationError): pass
+class SerialNoExistsInFutureTransaction(frappe.ValidationError):
+ pass
_exceptions = frappe.local('stockledger_exceptions')
# _exceptions = []
@@ -27,6 +31,9 @@
set_as_cancel(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no'))
for sle in sl_entries:
+ if sle.serial_no:
+ validate_serial_no(sle)
+
if cancel:
sle['actual_qty'] = -flt(sle.get('actual_qty'))
@@ -46,6 +53,30 @@
args = sle_doc.as_dict()
update_bin(args, allow_negative_stock, via_landed_cost_voucher)
+def validate_serial_no(sle):
+ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+ for sn in get_serial_nos(sle.serial_no):
+ args = copy.deepcopy(sle)
+ args.serial_no = sn
+ args.warehouse = ''
+
+ vouchers = []
+ for row in get_stock_ledger_entries(args, '>'):
+ voucher_type = frappe.bold(row.voucher_type)
+ voucher_no = frappe.bold(get_link_to_form(row.voucher_type, row.voucher_no))
+ vouchers.append(f'{voucher_type} {voucher_no}')
+
+ if vouchers:
+ serial_no = frappe.bold(sn)
+ msg = (f'''The serial no {serial_no} has been used in the future transactions so you need to cancel them first.
+ The list of the transactions are as below.''' + '<br><br><ul><li>')
+
+ msg += '</li><li>'.join(vouchers)
+ msg += '</li></ul>'
+
+ title = 'Cannot Submit' if not sle.get('is_cancelled') else 'Cannot Cancel'
+ frappe.throw(_(msg), title=_(title), exc=SerialNoExistsInFutureTransaction)
+
def validate_cancellation(args):
if args[0].get("is_cancelled"):
repost_entry = frappe.db.get_value("Repost Item Valuation", {
@@ -201,7 +232,8 @@
and is_cancelled = 0
and timestamp(posting_date, time_format(posting_time, %(time_format)s)) < timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s))
order by timestamp(posting_date, posting_time) desc, creation desc
- limit 1""", args, as_dict=1)
+ limit 1
+ for update""", args, as_dict=1)
return sle[0] if sle else frappe._dict()
@@ -372,7 +404,8 @@
elif sle.voucher_type in ("Purchase Receipt", "Purchase Invoice", "Delivery Note", "Sales Invoice"):
if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_return"):
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return # don't move this import to top
- rate = get_rate_for_return(sle.voucher_type, sle.voucher_no, sle.item_code, voucher_detail_no=sle.voucher_detail_no)
+ rate = get_rate_for_return(sle.voucher_type, sle.voucher_no, sle.item_code,
+ voucher_detail_no=sle.voucher_detail_no, sle = sle)
else:
if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"):
rate_field = "valuation_rate"
@@ -415,7 +448,7 @@
frappe.db.set_value("Stock Entry Detail", sle.voucher_detail_no, "basic_rate", outgoing_rate)
# Update outgoing item's rate, recalculate FG Item's rate and total incoming/outgoing amount
- stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no)
+ stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no, for_update=True)
stock_entry.calculate_rate_and_amount(reset_outgoing_rate=False, raise_error_if_no_rate=False)
stock_entry.db_update()
for d in stock_entry.items:
@@ -591,7 +624,7 @@
break
# If no entry found with outgoing rate, collapse stack
- if index == None:
+ if index is None: # nosemgrep
new_stock_value = sum((d[0]*d[1] for d in self.wh_data.stock_queue)) - qty_to_pop*outgoing_rate
new_stock_qty = sum((d[0] for d in self.wh_data.stock_queue)) - qty_to_pop
self.wh_data.stock_queue = [[new_stock_qty, new_stock_value/new_stock_qty if new_stock_qty > 0 else outgoing_rate]]
@@ -603,7 +636,7 @@
batch = self.wh_data.stock_queue[index]
if qty_to_pop >= batch[0]:
# consume current batch
- qty_to_pop = qty_to_pop - batch[0]
+ qty_to_pop = _round_off_if_near_zero(qty_to_pop - batch[0])
self.wh_data.stock_queue.pop(index)
if not self.wh_data.stock_queue and qty_to_pop:
# stock finished, qty still remains to be withdrawn
@@ -617,8 +650,8 @@
batch[0] = batch[0] - qty_to_pop
qty_to_pop = 0
- stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in self.wh_data.stock_queue))
- stock_qty = sum((flt(batch[0]) for batch in self.wh_data.stock_queue))
+ stock_value = _round_off_if_near_zero(sum((flt(batch[0]) * flt(batch[1]) for batch in self.wh_data.stock_queue)))
+ stock_qty = _round_off_if_near_zero(sum((flt(batch[0]) for batch in self.wh_data.stock_queue)))
if stock_qty:
self.wh_data.valuation_rate = stock_value / flt(stock_qty)
@@ -717,7 +750,17 @@
conditions += " and " + previous_sle.get("warehouse_condition")
if check_serial_no and previous_sle.get("serial_no"):
- conditions += " and serial_no like {}".format(frappe.db.escape('%{0}%'.format(previous_sle.get("serial_no"))))
+ # conditions += " and serial_no like {}".format(frappe.db.escape('%{0}%'.format(previous_sle.get("serial_no"))))
+ serial_no = previous_sle.get("serial_no")
+ conditions += (""" and
+ (
+ serial_no = {0}
+ or serial_no like {1}
+ or serial_no like {2}
+ or serial_no like {3}
+ )
+ """).format(frappe.db.escape(serial_no), frappe.db.escape('{}\n%'.format(serial_no)),
+ frappe.db.escape('%\n{}'.format(serial_no)), frappe.db.escape('%\n{}\n%'.format(serial_no)))
if not previous_sle.get("posting_date"):
previous_sle["posting_date"] = "1900-01-01"
@@ -792,12 +835,12 @@
if not allow_zero_rate and not valuation_rate and raise_error_if_no_rate \
and cint(erpnext.is_perpetual_inventory_enabled(company)):
frappe.local.message_log = []
- form_link = frappe.utils.get_link_to_form("Item", item_code)
+ form_link = get_link_to_form("Item", item_code)
message = _("Valuation Rate for the Item {0}, is required to do accounting entries for {1} {2}.").format(form_link, voucher_type, voucher_no)
- message += "<br><br>" + _(" Here are the options to proceed:")
+ message += "<br><br>" + _("Here are the options to proceed:")
solutions = "<li>" + _("If the item is transacting as a Zero Valuation Rate item in this entry, please enable 'Allow Zero Valuation Rate' in the {0} Item table.").format(voucher_type) + "</li>"
- solutions += "<li>" + _("If not, you can Cancel / Submit this entry ") + _("{0}").format(frappe.bold("after")) + _(" performing either one below:") + "</li>"
+ solutions += "<li>" + _("If not, you can Cancel / Submit this entry") + " {0} ".format(frappe.bold("after")) + _("performing either one below:") + "</li>"
sub_solutions = "<ul><li>" + _("Create an incoming stock transaction for the Item.") + "</li>"
sub_solutions += "<li>" + _("Mention Valuation Rate in the Item master.") + "</li></ul>"
msg = message + solutions + sub_solutions + "</li>"
@@ -857,3 +900,12 @@
order by timestamp(posting_date, posting_time) asc
limit 1
""", args, as_dict=1)
+
+def _round_off_if_near_zero(number: float, precision: int = 6) -> float:
+ """ Rounds off the number to zero only if number is close to zero for decimal
+ specified in precision. Precision defaults to 6.
+ """
+ if flt(number) < (1.0 / (10**precision)):
+ return 0
+
+ return flt(number)
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 0af3d90..034d3eb 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -172,7 +172,7 @@
bin_obj.flags.ignore_permissions = 1
bin_obj.insert()
else:
- bin_obj = frappe.get_cached_doc('Bin', bin)
+ bin_obj = frappe.get_doc('Bin', bin, for_update=True)
bin_obj.flags.ignore_permissions = True
return bin_obj
diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js
index 9fe12f9..ecc9fcf 100644
--- a/erpnext/support/doctype/issue/issue.js
+++ b/erpnext/support/doctype/issue/issue.js
@@ -48,44 +48,62 @@
}
},
- refresh: function (frm) {
- if (frm.doc.status !== "Closed") {
- if (frm.doc.service_level_agreement && frm.doc.agreement_status === "Ongoing") {
- frappe.call({
- "method": "frappe.client.get",
- args: {
- doctype: "Service Level Agreement",
- name: frm.doc.service_level_agreement
- },
- callback: function(data) {
- let statuses = data.message.pause_sla_on;
- const hold_statuses = [];
- $.each(statuses, (_i, entry) => {
- hold_statuses.push(entry.status);
- });
- if (hold_statuses.includes(frm.doc.status)) {
- frm.dashboard.clear_headline();
- let message = {"indicator": "orange", "msg": __("SLA is on hold since {0}", [moment(frm.doc.on_hold_since).fromNow(true)])};
- frm.dashboard.set_headline_alert(
- '<div class="row">' +
- '<div class="col-xs-12">' +
- '<span class="indicator whitespace-nowrap '+ message.indicator +'"><span>'+ message.msg +'</span></span> ' +
- '</div>' +
- '</div>'
- );
- } else {
- set_time_to_resolve_and_response(frm);
- }
- }
- });
- }
+ refresh: function(frm) {
- frm.add_custom_button(__("Close"), function () {
+ // alert messages
+ if (frm.doc.status !== "Closed" && frm.doc.service_level_agreement
+ && frm.doc.agreement_status === "Ongoing") {
+ frappe.call({
+ "method": "frappe.client.get",
+ args: {
+ doctype: "Service Level Agreement",
+ name: frm.doc.service_level_agreement
+ },
+ callback: function(data) {
+ let statuses = data.message.pause_sla_on;
+ const hold_statuses = [];
+ $.each(statuses, (_i, entry) => {
+ hold_statuses.push(entry.status);
+ });
+ if (hold_statuses.includes(frm.doc.status)) {
+ frm.dashboard.clear_headline();
+ let message = { "indicator": "orange", "msg": __("SLA is on hold since {0}", [moment(frm.doc.on_hold_since).fromNow(true)]) };
+ frm.dashboard.set_headline_alert(
+ '<div class="row">' +
+ '<div class="col-xs-12">' +
+ '<span class="indicator whitespace-nowrap ' + message.indicator + '"><span>' + message.msg + '</span></span> ' +
+ '</div>' +
+ '</div>'
+ );
+ } else {
+ set_time_to_resolve_and_response(frm);
+ }
+ }
+ });
+ } else if (frm.doc.service_level_agreement) {
+ frm.dashboard.clear_headline();
+
+ let agreement_status = (frm.doc.agreement_status == "Fulfilled") ?
+ { "indicator": "green", "msg": "Service Level Agreement has been fulfilled" } :
+ { "indicator": "red", "msg": "Service Level Agreement Failed" };
+
+ frm.dashboard.set_headline_alert(
+ '<div class="row">' +
+ '<div class="col-xs-12">' +
+ '<span class="indicator whitespace-nowrap ' + agreement_status.indicator + '"><span class="hidden-xs">' + agreement_status.msg + '</span></span> ' +
+ '</div>' +
+ '</div>'
+ );
+ }
+
+ // buttons
+ if (frm.doc.status !== "Closed") {
+ frm.add_custom_button(__("Close"), function() {
frm.set_value("status", "Closed");
frm.save();
});
- frm.add_custom_button(__("Task"), function () {
+ frm.add_custom_button(__("Task"), function() {
frappe.model.open_mapped_doc({
method: "erpnext.support.doctype.issue.issue.make_task",
frm: frm
@@ -93,23 +111,7 @@
}, __("Create"));
} else {
- if (frm.doc.service_level_agreement) {
- frm.dashboard.clear_headline();
-
- let agreement_status = (frm.doc.agreement_status == "Fulfilled") ?
- {"indicator": "green", "msg": "Service Level Agreement has been fulfilled"} :
- {"indicator": "red", "msg": "Service Level Agreement Failed"};
-
- frm.dashboard.set_headline_alert(
- '<div class="row">' +
- '<div class="col-xs-12">' +
- '<span class="indicator whitespace-nowrap '+ agreement_status.indicator +'"><span class="hidden-xs">'+ agreement_status.msg +'</span></span> ' +
- '</div>' +
- '</div>'
- );
- }
-
- frm.add_custom_button(__("Reopen"), function () {
+ frm.add_custom_button(__("Reopen"), function() {
frm.set_value("status", "Open");
frm.save();
});
diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json
index a43381c..bc29821 100644
--- a/erpnext/support/doctype/issue/issue.json
+++ b/erpnext/support/doctype/issue/issue.json
@@ -119,7 +119,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "Open\nReplied\nHold\nResolved\nClosed",
+ "options": "Open\nReplied\nOn Hold\nResolved\nClosed",
"search_index": 1
},
{
@@ -410,7 +410,7 @@
"icon": "fa fa-ticket",
"idx": 7,
"links": [],
- "modified": "2020-08-11 18:49:07.574769",
+ "modified": "2021-05-26 10:49:07.574769",
"modified_by": "Administrator",
"module": "Support",
"name": "Issue",
diff --git a/erpnext/support/doctype/issue/test_issue.py b/erpnext/support/doctype/issue/test_issue.py
index 46d02d8..7da5d7f 100644
--- a/erpnext/support/doctype/issue/test_issue.py
+++ b/erpnext/support/doctype/issue/test_issue.py
@@ -22,50 +22,50 @@
customer = create_customer("_Test Customer", "__Test SLA Customer Group", "__Test SLA Territory")
issue = make_issue(creation, "_Test Customer", 1)
- self.assertEquals(issue.response_by, datetime.datetime(2019, 3, 4, 14, 0))
- self.assertEquals(issue.resolution_by, datetime.datetime(2019, 3, 4, 15, 0))
+ self.assertEqual(issue.response_by, datetime.datetime(2019, 3, 4, 14, 0))
+ self.assertEqual(issue.resolution_by, datetime.datetime(2019, 3, 4, 15, 0))
# make issue with customer_group specific SLA
customer = create_customer("__Test Customer", "_Test SLA Customer Group", "__Test SLA Territory")
issue = make_issue(creation, "__Test Customer", 2)
- self.assertEquals(issue.response_by, datetime.datetime(2019, 3, 4, 14, 0))
- self.assertEquals(issue.resolution_by, datetime.datetime(2019, 3, 4, 15, 0))
+ self.assertEqual(issue.response_by, datetime.datetime(2019, 3, 4, 14, 0))
+ self.assertEqual(issue.resolution_by, datetime.datetime(2019, 3, 4, 15, 0))
# make issue with territory specific SLA
customer = create_customer("___Test Customer", "__Test SLA Customer Group", "_Test SLA Territory")
issue = make_issue(creation, "___Test Customer", 3)
- self.assertEquals(issue.response_by, datetime.datetime(2019, 3, 4, 14, 0))
- self.assertEquals(issue.resolution_by, datetime.datetime(2019, 3, 4, 15, 0))
+ self.assertEqual(issue.response_by, datetime.datetime(2019, 3, 4, 14, 0))
+ self.assertEqual(issue.resolution_by, datetime.datetime(2019, 3, 4, 15, 0))
# make issue with default SLA
issue = make_issue(creation=creation, index=4)
- self.assertEquals(issue.response_by, datetime.datetime(2019, 3, 4, 16, 0))
- self.assertEquals(issue.resolution_by, datetime.datetime(2019, 3, 4, 18, 0))
+ self.assertEqual(issue.response_by, datetime.datetime(2019, 3, 4, 16, 0))
+ self.assertEqual(issue.resolution_by, datetime.datetime(2019, 3, 4, 18, 0))
# make issue with default SLA before working hours
creation = datetime.datetime(2019, 3, 4, 7, 0)
issue = make_issue(creation=creation, index=5)
- self.assertEquals(issue.response_by, datetime.datetime(2019, 3, 4, 14, 0))
- self.assertEquals(issue.resolution_by, datetime.datetime(2019, 3, 4, 16, 0))
+ self.assertEqual(issue.response_by, datetime.datetime(2019, 3, 4, 14, 0))
+ self.assertEqual(issue.resolution_by, datetime.datetime(2019, 3, 4, 16, 0))
# make issue with default SLA after working hours
creation = datetime.datetime(2019, 3, 4, 20, 0)
issue = make_issue(creation, index=6)
- self.assertEquals(issue.response_by, datetime.datetime(2019, 3, 6, 14, 0))
- self.assertEquals(issue.resolution_by, datetime.datetime(2019, 3, 6, 16, 0))
+ self.assertEqual(issue.response_by, datetime.datetime(2019, 3, 6, 14, 0))
+ self.assertEqual(issue.resolution_by, datetime.datetime(2019, 3, 6, 16, 0))
# make issue with default SLA next day
creation = datetime.datetime(2019, 3, 4, 14, 0)
issue = make_issue(creation=creation, index=7)
- self.assertEquals(issue.response_by, datetime.datetime(2019, 3, 4, 18, 0))
- self.assertEquals(issue.resolution_by, datetime.datetime(2019, 3, 6, 12, 0))
+ self.assertEqual(issue.response_by, datetime.datetime(2019, 3, 4, 18, 0))
+ self.assertEqual(issue.resolution_by, datetime.datetime(2019, 3, 6, 12, 0))
frappe.flags.current_time = datetime.datetime(2019, 3, 4, 15, 0)
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.js b/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
index 5346195..00060b9 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
@@ -10,7 +10,9 @@
let statuses = frappe.meta.get_docfield('Issue', 'status', frm.doc.name).options;
statuses = statuses.split('\n');
allow_statuses = statuses.filter((status) => !exclude_statuses.includes(status));
- frappe.meta.get_docfield('Pause SLA On Status', 'status', frm.doc.name).options = [''].concat(allow_statuses);
+ frm.fields_dict.pause_sla_on.grid.update_docfield_property(
+ 'status', 'options', [''].concat(allow_statuses)
+ );
});
}
-});
\ No newline at end of file
+});
diff --git a/erpnext/support/report/issue_analytics/issue_analytics.js b/erpnext/support/report/issue_analytics/issue_analytics.js
index f87b2c2..746eee0 100644
--- a/erpnext/support/report/issue_analytics/issue_analytics.js
+++ b/erpnext/support/report/issue_analytics/issue_analytics.js
@@ -52,6 +52,7 @@
label: __("Status"),
fieldtype: "Select",
options:[
+ "",
{label: __('Open'), value: 'Open'},
{label: __('Replied'), value: 'Replied'},
{label: __('Resolved'), value: 'Resolved'},
@@ -138,4 +139,4 @@
}
});
}
-};
\ No newline at end of file
+};
diff --git a/erpnext/support/report/issue_summary/issue_summary.js b/erpnext/support/report/issue_summary/issue_summary.js
index 684482a..a5122d0 100644
--- a/erpnext/support/report/issue_summary/issue_summary.js
+++ b/erpnext/support/report/issue_summary/issue_summary.js
@@ -39,8 +39,10 @@
label: __("Status"),
fieldtype: "Select",
options:[
+ "",
{label: __('Open'), value: 'Open'},
{label: __('Replied'), value: 'Replied'},
+ {label: __('On Hold'), value: 'On Hold'},
{label: __('Resolved'), value: 'Resolved'},
{label: __('Closed'), value: 'Closed'}
]
@@ -70,4 +72,4 @@
options: "User"
}
]
-};
\ No newline at end of file
+};
diff --git a/erpnext/support/report/issue_summary/issue_summary.py b/erpnext/support/report/issue_summary/issue_summary.py
index 7861e30..bba25b8 100644
--- a/erpnext/support/report/issue_summary/issue_summary.py
+++ b/erpnext/support/report/issue_summary/issue_summary.py
@@ -62,7 +62,7 @@
'width': 200
})
- self.statuses = ['Open', 'Replied', 'Resolved', 'Closed']
+ self.statuses = ['Open', 'Replied', 'On Hold', 'Resolved', 'Closed']
for status in self.statuses:
self.columns.append({
'label': _(status),
@@ -265,6 +265,7 @@
labels = []
open_issues = []
replied_issues = []
+ on_hold_issues = []
resolved_issues = []
closed_issues = []
@@ -277,6 +278,7 @@
labels.append(entry.get(entity_field))
open_issues.append(entry.get('open'))
replied_issues.append(entry.get('replied'))
+ on_hold_issues.append(entry.get('on_hold'))
resolved_issues.append(entry.get('resolved'))
closed_issues.append(entry.get('closed'))
@@ -293,6 +295,10 @@
'values': replied_issues[:30]
},
{
+ 'name': 'On Hold',
+ 'values': on_hold_issues[:30]
+ },
+ {
'name': 'Resolved',
'values': resolved_issues[:30]
},
@@ -313,12 +319,14 @@
open_issues = 0
replied = 0
+ on_hold = 0
resolved = 0
closed = 0
for entry in self.data:
open_issues += entry.get('open')
replied += entry.get('replied')
+ on_hold += entry.get('on_hold')
resolved += entry.get('resolved')
closed += entry.get('closed')
@@ -336,6 +344,12 @@
'datatype': 'Int',
},
{
+ 'value': on_hold,
+ 'indicator': 'Grey',
+ 'label': _('On Hold'),
+ 'datatype': 'Int',
+ },
+ {
'value': resolved,
'indicator': 'Green',
'label': _('Resolved'),
diff --git a/erpnext/templates/generators/item/item_add_to_cart.html b/erpnext/templates/generators/item/item_add_to_cart.html
index f5adbf0..167c848 100644
--- a/erpnext/templates/generators/item/item_add_to_cart.html
+++ b/erpnext/templates/generators/item/item_add_to_cart.html
@@ -11,7 +11,7 @@
<small class="formatted-price">({{ product_info.price.formatted_price }} / {{ product_info.uom }})</small>
</div>
{% else %}
- {{ _("Unit of Measurement") }} : {{ product_info.uom }}
+ {{ _("UOM") }} : {{ product_info.uom }}
{% endif %}
{% if cart_settings.show_stock_availability %}
diff --git a/erpnext/templates/includes/transaction_row.html b/erpnext/templates/includes/transaction_row.html
index 930d0c2..3834131 100644
--- a/erpnext/templates/includes/transaction_row.html
+++ b/erpnext/templates/includes/transaction_row.html
@@ -14,11 +14,7 @@
</div>
</div>
<div class="col-sm-3 text-right bold">
- {% if doc.doctype == "Quotation" and not doc.docstatus %}
- {{ _("Pending") }}
- {% else %}
- {{ doc.get_formatted("grand_total") }}
- {% endif %}
+ {{ doc.get_formatted("grand_total") }}
</div>
</div>
<a class="transaction-item-link" href="/{{ pathname }}/{{ doc.name }}">Link</a>
diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html
index 07dd676..28faea8 100644
--- a/erpnext/templates/pages/order.html
+++ b/erpnext/templates/pages/order.html
@@ -12,21 +12,22 @@
{% endblock %}
{% block header_actions %}
- <div class="dropdown">
- <button class="btn btn-outline-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
- <span>{{ _('Actions') }}</span>
- <b class="caret"></b>
- </button>
- <ul class="dropdown-menu dropdown-menu-right" role="menu">
- {% if doc.doctype == 'Purchase Order' %}
- <a class="dropdown-item" href="/api/method/erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_invoice_from_portal?purchase_order_name={{ doc.name }}" data-action="make_purchase_invoice">{{ _("Make Purchase Invoice") }}</a>
- {% endif %}
- <a class="dropdown-item" href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}'
- target="_blank" rel="noopener noreferrer">
- {{ _("Print") }}
- </a>
- </ul>
- </div>
+<div class="dropdown">
+ <button class="btn btn-outline-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
+ <span>{{ _('Actions') }}</span>
+ <b class="caret"></b>
+ </button>
+ <ul class="dropdown-menu dropdown-menu-right" role="menu">
+ {% if doc.doctype == 'Purchase Order' %}
+ <a class="dropdown-item" href="/api/method/erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_invoice_from_portal?purchase_order_name={{ doc.name }}" data-action="make_purchase_invoice">{{ _("Make Purchase Invoice") }}</a>
+ {% endif %}
+ <a class="dropdown-item" href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}'
+ target="_blank" rel="noopener noreferrer">
+ {{ _("Print") }}
+ </a>
+ </ul>
+</div>
+
{% endblock %}
{% block page_content %}
diff --git a/erpnext/tests/__init__.py b/erpnext/tests/__init__.py
index e69de29..a504340 100644
--- a/erpnext/tests/__init__.py
+++ b/erpnext/tests/__init__.py
@@ -0,0 +1 @@
+global_test_dependencies = ['User', 'Company', 'Item']
diff --git a/erpnext/utilities/__init__.py b/erpnext/utilities/__init__.py
index 618cc98..0a5aa3c 100644
--- a/erpnext/utilities/__init__.py
+++ b/erpnext/utilities/__init__.py
@@ -12,7 +12,6 @@
for f in dt.fields:
if f.fieldname == d.fieldname and f.fieldtype in ("Text", "Small Text"):
- print(f.parent, f.fieldname)
f.fieldtype = "Text Editor"
dt.save()
break
diff --git a/erpnext/utilities/activation.py b/erpnext/utilities/activation.py
index 7b17c8c..50c4b25 100644
--- a/erpnext/utilities/activation.py
+++ b/erpnext/utilities/activation.py
@@ -18,7 +18,6 @@
"Delivery Note": 5,
"Employee": 3,
"Instructor": 5,
- "Instructor": 5,
"Issue": 5,
"Item": 5,
"Journal Entry": 3,
diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py
index c8ae733..f99da58 100644
--- a/erpnext/utilities/transaction_base.py
+++ b/erpnext/utilities/transaction_base.py
@@ -7,7 +7,6 @@
from frappe import _
from frappe.utils import cstr, now_datetime, cint, flt, get_time, get_datetime, get_link_to_form, date_diff, nowdate
from erpnext.controllers.status_updater import StatusUpdater
-from erpnext.accounts.utils import get_fiscal_year
from six import string_types
@@ -121,11 +120,11 @@
buying_doctypes = ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]
if self.doctype in buying_doctypes:
- to_disable = "Maintain same rate throughout Purchase cycle"
- settings_page = "Buying Settings"
+ action = frappe.db.get_single_value("Buying Settings", "maintain_same_rate_action")
+ settings_doc = "Buying Settings"
else:
- to_disable = "Maintain same rate throughout Sales cycle"
- settings_page = "Selling Settings"
+ action = frappe.db.get_single_value("Selling Settings", "maintain_same_rate_action")
+ settings_doc = "Selling Settings"
for ref_dt, ref_dn_field, ref_link_field in ref_details:
for d in self.get("items"):
@@ -133,11 +132,16 @@
ref_rate = frappe.db.get_value(ref_dt + " Item", d.get(ref_link_field), "rate")
if abs(flt(d.rate - ref_rate, d.precision("rate"))) >= .01:
- frappe.msgprint(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4}) ")
- .format(d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate))
- frappe.throw(_("To allow different rates, disable the {0} checkbox in {1}.")
- .format(frappe.bold(_(to_disable)),
- get_link_to_form(settings_page, settings_page, frappe.bold(settings_page))))
+ if action == "Stop":
+ role_allowed_to_override = frappe.db.get_single_value(settings_doc, 'role_to_override_stop_action')
+
+ if role_allowed_to_override not in frappe.get_roles():
+ frappe.throw(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format(
+ d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate))
+ else:
+ frappe.msgprint(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format(
+ d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate), title=_("Warning"), indicator="orange")
+
def get_link_filters(self, for_doctype):
if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype):
diff --git a/erpnext/www/book_appointment/index.js b/erpnext/www/book_appointment/index.js
index 377a3cc..5562cbd 100644
--- a/erpnext/www/book_appointment/index.js
+++ b/erpnext/www/book_appointment/index.js
@@ -48,7 +48,7 @@
function hide_next_button() {
let next_button = document.getElementById('next-button');
next_button.disabled = true;
- next_button.onclick = () => frappe.msgprint("Please select a date and time");
+ next_button.onclick = () => frappe.msgprint(__("Please select a date and time"));
}
function show_next_button() {
@@ -63,7 +63,7 @@
if (date_picker.value === '') {
clear_time_slots();
hide_next_button();
- frappe.throw('Please select a date');
+ frappe.throw(__('Please select a date'));
}
window.selected_date = date_picker.value;
window.selected_timezone = timezone.value;
diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html
index dc9b6d8..15afb09 100644
--- a/erpnext/www/lms/content.html
+++ b/erpnext/www/lms/content.html
@@ -62,7 +62,7 @@
{{_('Back to Course')}}
</a>
</div>
- <div>
+ <div class="lms-title">
<h2>{{ content.name }} <span class="small text-muted">({{ position + 1 }}/{{length}})</span></h2>
</div>
{% endmacro %}
@@ -169,14 +169,51 @@
const next_url = '/lms/course?name={{ course }}&program={{ program }}'
{% endif %}
frappe.ready(() => {
- const quiz = new Quiz(document.getElementById('quiz-wrapper'), {
- name: '{{ content.name }}',
- course: '{{ course }}',
- program: '{{ program }}',
- quiz_exit_button: quiz_exit_button,
- next_url: next_url
- })
- window.quiz = quiz;
+ {% if content.is_time_bound %}
+ var duration = get_duration("{{content.duration}}")
+ var d = frappe.msgprint({
+ title: __('Important Notice'),
+ indicator: "red",
+ message: __(`This is a Time-Bound Quiz. <br><br>
+ A timer for <b>${duration}</b> will start, once you click on <b>Proceed</b>. <br><br>
+ If you fail to submit before the time is up, the Quiz will be submitted automatically.`),
+ primary_action: {
+ label: __("Proceed"),
+ action: () => {
+ create_quiz();
+ d.hide();
+ }
+ },
+ secondary_action: {
+ action: () => {
+ d.hide();
+ window.location.href = "/lms/course?name={{ course }}&program={{ program }}";
+ },
+ label: __("Go Back"),
+ }
+ });
+ {% else %}
+ create_quiz();
+ {% endif %}
+ function create_quiz() {
+ const quiz = new Quiz(document.getElementById('quiz-wrapper'), {
+ name: '{{ content.name }}',
+ course: '{{ course }}',
+ program: '{{ program }}',
+ quiz_exit_button: quiz_exit_button,
+ next_url: next_url
+ })
+ window.quiz = quiz;
+ }
+ function get_duration(seconds){
+ var hours = append_zero(Math.floor(seconds / 3600));
+ var minutes = append_zero(Math.floor(seconds % 3600 / 60));
+ var seconds = append_zero(Math.floor(seconds % 3600 % 60));
+ return `${hours}:${minutes}:${seconds}`;
+ }
+ function append_zero(time) {
+ return time > 9 ? time : "0" + time;
+ }
})
{% endif %}
diff --git a/erpnext/www/lms/index.html b/erpnext/www/lms/index.html
index 7b239ac..c1e9620 100644
--- a/erpnext/www/lms/index.html
+++ b/erpnext/www/lms/index.html
@@ -42,7 +42,9 @@
<section class="top-section" style="padding: 6rem 0rem;">
<div class='container pb-5'>
<h1>{{ education_settings.portal_title }}</h1>
- <p class='lead'>{{ education_settings.description }}</p>
+ {% if education_settings.description %}
+ <p class='lead'>{{ education_settings.description }}</p>
+ {% endif %}
<p class="mt-4">
{% if frappe.session.user == 'Guest' %}
<a class="btn btn-primary btn-lg" href="/login#signup">{{_('Sign Up')}}</a>
@@ -51,13 +53,15 @@
</div>
<div class='container'>
<div class="row mt-5">
- {% for program in featured_programs %}
- {{ program_card(program.program, program.has_access) }}
- {% endfor %}
{% if featured_programs %}
+ {% for program in featured_programs %}
+ {{ program_card(program.program, program.has_access) }}
+ {% endfor %}
{% for n in range( (3 - (featured_programs|length)) %3) %}
{{ null_card() }}
{% endfor %}
+ {% else %}
+ <p class="lead">You have not enrolled in any program. Contact your Instructor.</p>
{% endif %}
</div>
</div>
diff --git a/erpnext/www/lms/topic.py b/erpnext/www/lms/topic.py
index f75ae8e..8abbc72 100644
--- a/erpnext/www/lms/topic.py
+++ b/erpnext/www/lms/topic.py
@@ -35,7 +35,7 @@
progress.append({'content': content, 'content_type': content.doctype, 'completed': status})
elif content.doctype == 'Quiz':
if student:
- status, score, result = utils.check_quiz_completion(content, course_enrollment.name)
+ status, score, result, time_taken = utils.check_quiz_completion(content, course_enrollment.name)
else:
status = False
score = None
diff --git a/package.json b/package.json
index d12661b..c9ee7a6 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
"url": "https://github.com/frappe/erpnext/issues"
},
"devDependencies": {
- "snyk": "^1.290.1"
+ "snyk": "^1.518.0"
},
"dependencies": {
"onscan.js": "^1.5.2"
diff --git a/requirements.txt b/requirements.txt
index 5a35236..32da48e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,15 +1,12 @@
-braintree==3.57.1
-frappe
-gocardless-pro==1.11.0
-googlemaps==3.1.1
-pandas>=1.0.5
-plaid-python>=7.0.0
-pycountry==19.8.18
-PyGithub==1.44.1
-python-stdnum==1.12
-python-youtube==0.6.0
-taxjar==1.9.0
-tweepy==3.8.0
-Unidecode==1.1.1
-WooCommerce==2.1.1
-pycryptodome==3.9.8
+# frappe # https://github.com/frappe/frappe is installed during bench-init
+gocardless-pro~=1.22.0
+googlemaps # used in ERPNext, but dependency is defined in Frappe
+pandas~=1.1.5
+plaid-python~=7.2.1
+pycountry~=20.7.3
+PyGithub~=1.54.1
+python-stdnum~=1.16
+python-youtube~=0.8.0
+taxjar~=1.9.2
+tweepy~=3.10.0
+Unidecode~=1.2.0
diff --git a/yarn.lock b/yarn.lock
index e5a2da1..0a2ac1a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,241 +2,622 @@
# yarn lockfile v1
-"@snyk/cli-interface@1.5.0":
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-1.5.0.tgz#b9dbe6ebfb86e67ffabf29d4e0d28a52670ac456"
- integrity sha512-+Qo+IO3YOXWgazlo+CKxOuWFLQQdaNCJ9cSfhFQd687/FuesaIxWdInaAdfpsLScq0c6M1ieZslXgiZELSzxbg==
+"@arcanis/slice-ansi@^1.0.2":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@arcanis/slice-ansi/-/slice-ansi-1.0.2.tgz#35331e41a1062e3c53c01ad2ec1555c5c1959d8f"
+ integrity sha512-lDL63z0W/L/WTgqrwVOuNyMAsTv+pvjybd21z9SWdStmQoXT59E/iVWwat3gYjcdTNBf6oHAMoyFm8dtjpXEYw==
dependencies:
- tslib "^1.9.3"
+ grapheme-splitter "^1.0.4"
-"@snyk/cli-interface@2.2.0":
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.2.0.tgz#5536bc913917c623d16d727f9f3759521a916026"
- integrity sha512-sA7V2JhgqJB9z5uYotgQc5iNDv//y+Mdm39rANxmFjtZMSYJZHkP80arzPjw1mB5ni/sWec7ieYUUFeySZBfVg==
+"@deepcode/dcignore@^1.0.2":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@deepcode/dcignore/-/dcignore-1.0.2.tgz#39e4a3df7dde8811925330506e4bb3fbf3c288d8"
+ integrity sha512-DPgxtHuJwBORpqRkPXzzOT+uoPRVJmaN7LR+pmeL6DQM90kj6G6GFUH1i/YpRH8NbML8ZGEDwB9f9u4UwD2pzg==
+
+"@nodelib/fs.scandir@2.1.4":
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69"
+ integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==
dependencies:
- tslib "^1.9.3"
+ "@nodelib/fs.stat" "2.0.4"
+ run-parallel "^1.1.9"
-"@snyk/cli-interface@2.3.0":
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.3.0.tgz#9d38f815a5cf2be266006954c2a058463d531e08"
- integrity sha512-ecbylK5Ol2ySb/WbfPj0s0GuLQR+KWKFzUgVaoNHaSoN6371qRWwf2uVr+hPUP4gXqCai21Ug/RDArfOhlPwrQ==
+"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2":
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655"
+ integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==
+
+"@nodelib/fs.walk@^1.2.3":
+ version "1.2.6"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063"
+ integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==
dependencies:
- tslib "^1.9.3"
+ "@nodelib/fs.scandir" "2.1.4"
+ fastq "^1.6.0"
-"@snyk/cli-interface@2.3.1", "@snyk/cli-interface@^2.0.3":
+"@octetstream/promisify@2.0.2":
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/@octetstream/promisify/-/promisify-2.0.2.tgz#29ac3bd7aefba646db670227f895d812c1a19615"
+ integrity sha512-7XHoRB61hxsz8lBQrjC1tq/3OEIgpvGWg6DKAdwi7WRzruwkmsdwmOoUXbU4Dtd4RSOMDwed0SkP3y8UlMt1Bg==
+
+"@open-policy-agent/opa-wasm@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@open-policy-agent/opa-wasm/-/opa-wasm-1.2.0.tgz#481b766093f70b00efefbee1e4192f375fd34ca2"
+ integrity sha512-CtUBTnzvDrT0NASa8IuGQTxFGgt2vxbLnMYuTA+uDFxOcA4uK4mGFgrhHJtxUZnWHiwemOvKKSY3BMCo7qiAsQ==
+ dependencies:
+ sprintf-js "^1.1.2"
+ utf8 "^3.0.0"
+
+"@sindresorhus/is@^0.14.0":
+ version "0.14.0"
+ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
+ integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
+
+"@sindresorhus/is@^2.1.1":
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.1.tgz#ceff6a28a5b4867c2dd4a1ba513de278ccbe8bb1"
+ integrity sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg==
+
+"@sindresorhus/is@^4.0.0":
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.1.tgz#d26729db850fa327b7cacc5522252194404226f5"
+ integrity sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==
+
+"@snyk/cli-interface@2.11.0", "@snyk/cli-interface@^2.11.0", "@snyk/cli-interface@^2.9.1", "@snyk/cli-interface@^2.9.2":
+ version "2.11.0"
+ resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.11.0.tgz#9df68c8cd54de5dff69f0ab797a188541d9c8965"
+ integrity sha512-T3xfDqrEFKclHGdJx4/5+D5F7e76/99f33guE4RTlVITBhy7VVnjz4t/NDr3UYqcC0MgAmiC4bSVYHnlshuwJw==
+ dependencies:
+ "@types/graphlib" "^2"
+
+"@snyk/cli-interface@^2.0.3":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.3.1.tgz#73f2f4bd717b9f03f096ede3ff5830eb8d2f3716"
integrity sha512-JZvsmhDXSyjv1dkc12lPI3tNTNYlIaOiIQMYFg2RgqF3QmWjTyBUgRZcF7LoKyufHtS4dIudM6k1aHBpSaDrhw==
dependencies:
tslib "^1.9.3"
-"@snyk/cocoapods-lockfile-parser@3.0.0":
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/@snyk/cocoapods-lockfile-parser/-/cocoapods-lockfile-parser-3.0.0.tgz#514b744cedd9d3d3efb2a5d06fce1662fec2ff1a"
- integrity sha512-AebCc+v9vtOL9tFkU4/tommgVsXxqdx6t45kCkBW+FC4PaYvfYEg9Eg/9GqlY9+nFrLFo/uTr+E/aR0AF/KqYA==
+"@snyk/cloud-config-parser@^1.9.2":
+ version "1.9.2"
+ resolved "https://registry.yarnpkg.com/@snyk/cloud-config-parser/-/cloud-config-parser-1.9.2.tgz#e6c8e575db8527b33cf1ba766f86e1b3414cf6e1"
+ integrity sha512-m8Y2+3l4fxj96QMrTfiCEaXgCpDkCkJIX/5wv0V0RHuxpUiyh+KxC2yJ8Su4wybBj6v6hB9hB7h5/L+Gy4V4PA==
dependencies:
- "@snyk/dep-graph" "^1.11.0"
- "@snyk/ruby-semver" "^2.0.4"
- "@types/js-yaml" "^3.12.1"
- core-js "^3.2.0"
- js-yaml "^3.13.1"
- source-map-support "^0.5.7"
- tslib "^1.9.3"
-
-"@snyk/composer-lockfile-parser@1.2.0":
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/@snyk/composer-lockfile-parser/-/composer-lockfile-parser-1.2.0.tgz#62c6d88c6a39c55dda591854f5380923a993182f"
- integrity sha512-kZT+HTqgNcQMeoE5NM9M3jj463M8zI7ZxqZXLw9WoyVs5JTt9g0qFWxIG1cNwZdGVI+y7tzZbNWw9BlMD1vCCQ==
- dependencies:
- lodash "^4.17.13"
-
-"@snyk/configstore@3.2.0-rc1", "@snyk/configstore@^3.2.0-rc1":
- version "3.2.0-rc1"
- resolved "https://registry.yarnpkg.com/@snyk/configstore/-/configstore-3.2.0-rc1.tgz#385c050d11926a26d0335a4b3be9e55f90f6e0ac"
- integrity sha512-CV3QggFY8BY3u8PdSSlUGLibqbqCG1zJRmGM2DhnhcxQDRRPTGTP//l7vJphOVsUP1Oe23+UQsj7KRWpRUZiqg==
- dependencies:
- dot-prop "^5.2.0"
- graceful-fs "^4.1.2"
- make-dir "^1.0.0"
- unique-string "^1.0.0"
- write-file-atomic "^2.0.0"
- xdg-basedir "^3.0.0"
-
-"@snyk/dep-graph@1.13.1":
- version "1.13.1"
- resolved "https://registry.yarnpkg.com/@snyk/dep-graph/-/dep-graph-1.13.1.tgz#45721f7e21136b62d1cdd99b3319e717d9071dfb"
- integrity sha512-Ww2xvm5UQgrq9eV0SdTBCh+w/4oI2rCx5vn1IOSeypaR0CO4p+do1vm3IDZ2ugg4jLSfHP8+LiD6ORESZMkQ2w==
- dependencies:
- graphlib "^2.1.5"
- lodash "^4.7.14"
- object-hash "^1.3.1"
- semver "^6.0.0"
- source-map-support "^0.5.11"
- tslib "^1.9.3"
-
-"@snyk/dep-graph@^1.11.0", "@snyk/dep-graph@^1.13.1":
- version "1.15.0"
- resolved "https://registry.yarnpkg.com/@snyk/dep-graph/-/dep-graph-1.15.0.tgz#67bf790bc9f0eee36ad7dad053465cdd928ce223"
- integrity sha512-GdF/dvqfKRVHqQio/tSkR4GRpAqIglLPEDZ+XlV7jT5btq9+Fxq2h25Lmm/a7sw+ODTOOqNhTF9y8ASc9VIhww==
- dependencies:
- graphlib "^2.1.5"
- lodash "^4.7.14"
- object-hash "^1.3.1"
- semver "^6.0.0"
- source-map-support "^0.5.11"
+ esprima "^4.0.1"
tslib "^1.10.0"
+ yaml-js "^0.3.0"
+
+"@snyk/cocoapods-lockfile-parser@3.6.2":
+ version "3.6.2"
+ resolved "https://registry.yarnpkg.com/@snyk/cocoapods-lockfile-parser/-/cocoapods-lockfile-parser-3.6.2.tgz#803ae9466f408c48ba7c5a8ec51b9dbac6f633b3"
+ integrity sha512-ca2JKOnSRzYHJkhOB9gYmdRZHmd02b/uBd/S0D5W+L9nIMS7sUBV5jfhKwVgrYPIpVNIc0XCI9rxK4TfkQRpiA==
+ dependencies:
+ "@snyk/dep-graph" "^1.23.1"
+ "@types/js-yaml" "^3.12.1"
+ js-yaml "^3.13.1"
+ tslib "^1.10.0"
+
+"@snyk/code-client@3.4.1":
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/@snyk/code-client/-/code-client-3.4.1.tgz#b9d025897cd586e0aef903162ac0407d0bffc3cd"
+ integrity sha512-XJ7tUdX1iQyzN/BmHac7p+Oyw1SyTcqSkCNExwBJxyQdlnUAKK6QKIWLXS81tTpZ79FgCdT+0fdS0AjsyS99eA==
+ dependencies:
+ "@deepcode/dcignore" "^1.0.2"
+ "@snyk/fast-glob" "^3.2.6-patch"
+ "@types/flat-cache" "^2.0.0"
+ "@types/lodash.chunk" "^4.2.6"
+ "@types/lodash.omit" "^4.5.6"
+ "@types/lodash.union" "^4.6.6"
+ "@types/micromatch" "^4.0.1"
+ "@types/sarif" "^2.1.3"
+ "@types/uuid" "^8.3.0"
+ axios "^0.21.1"
+ ignore "^5.1.8"
+ lodash.chunk "^4.2.0"
+ lodash.omit "^4.5.0"
+ lodash.union "^4.6.0"
+ micromatch "^4.0.2"
+ queue "^6.0.1"
+ uuid "^8.3.2"
+
+"@snyk/composer-lockfile-parser@^1.4.1":
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/@snyk/composer-lockfile-parser/-/composer-lockfile-parser-1.4.1.tgz#2f7c93ad367520322b16d9490a208fec08445e0e"
+ integrity sha512-wNANv235j95NFsQuODIXCiQZ9kcyg9fz92Kg1zoGvaP3kN/ma7fgCnvQL/dyml6iouQJR5aZovjhrrfEFoKtiQ==
+ dependencies:
+ lodash.findkey "^4.6.0"
+ lodash.get "^4.4.2"
+ lodash.invert "^4.3.0"
+ lodash.isempty "^4.4.0"
+
+"@snyk/dep-graph@^1.19.3", "@snyk/dep-graph@^1.21.0", "@snyk/dep-graph@^1.23.0", "@snyk/dep-graph@^1.23.1", "@snyk/dep-graph@^1.27.1", "@snyk/dep-graph@^1.28.0":
+ version "1.28.0"
+ resolved "https://registry.yarnpkg.com/@snyk/dep-graph/-/dep-graph-1.28.0.tgz#d68c0576cb3562c6e819ca8a8c7ac29ee11d9776"
+ integrity sha512-Oup9nAvb558jdNvbZah/vaBtOtCcizkdeS+OBQeBIqIffyer4mc4juSn4b1SFjCpu7AG7piio8Lj8k1B9ps6Tg==
+ dependencies:
+ event-loop-spinner "^2.1.0"
+ lodash.clone "^4.5.0"
+ lodash.constant "^3.0.0"
+ lodash.filter "^4.6.0"
+ lodash.foreach "^4.5.0"
+ lodash.isempty "^4.4.0"
+ lodash.isequal "^4.5.0"
+ lodash.isfunction "^3.0.9"
+ lodash.isundefined "^3.0.1"
+ lodash.keys "^4.2.0"
+ lodash.map "^4.6.0"
+ lodash.reduce "^4.6.0"
+ lodash.size "^4.2.0"
+ lodash.transform "^4.6.0"
+ lodash.union "^4.6.0"
+ lodash.values "^4.3.0"
+ object-hash "^2.0.3"
+ semver "^7.0.0"
+ tslib "^1.13.0"
+
+"@snyk/docker-registry-v2-client@1.13.9":
+ version "1.13.9"
+ resolved "https://registry.yarnpkg.com/@snyk/docker-registry-v2-client/-/docker-registry-v2-client-1.13.9.tgz#54c2e3071de58fc6fc12c5fef5eaeae174ecda12"
+ integrity sha512-DIFLEhr8m1GrAwsLGInJmpcQMacjuhf3jcbpQTR+LeMvZA9IuKq+B7kqw2O2FzMiHMZmUb5z+tV+BR7+IUHkFQ==
+ dependencies:
+ needle "^2.5.0"
+ parse-link-header "^1.0.1"
+ tslib "^1.10.0"
+
+"@snyk/fast-glob@^3.2.6-patch":
+ version "3.2.6-patch"
+ resolved "https://registry.yarnpkg.com/@snyk/fast-glob/-/fast-glob-3.2.6-patch.tgz#a0866bedb17f95255e4050dad08daeaff0f4caa8"
+ integrity sha512-E/Pfdze/WFfxwyuTFcfhQN1SwyUsc43yuCoW63RVBCaxTD6OzhVD2Pvc/Sy7BjiWUfmelzyKkIBpoow8zZX7Zg==
+ dependencies:
+ "@nodelib/fs.stat" "^2.0.2"
+ "@nodelib/fs.walk" "^1.2.3"
+ "@snyk/glob-parent" "^5.1.2-patch.1"
+ merge2 "^1.3.0"
+ micromatch "^4.0.2"
+ picomatch "^2.2.1"
+
+"@snyk/fix@1.554.0":
+ version "1.554.0"
+ resolved "https://registry.yarnpkg.com/@snyk/fix/-/fix-1.554.0.tgz#7ae786882e0ffea5e7f10d0b41e3d593b65555c4"
+ integrity sha512-q2eRVStgspPeI2wZ2EQGLpiWZMRg7o+4tsCk6m/kHZgQGDN4Bb7L3xslFW3OgF0+ZksYSaHl2cW2HmGiLRaYcA==
+ dependencies:
+ "@snyk/dep-graph" "^1.21.0"
+ chalk "4.1.0"
+ debug "^4.3.1"
+ ora "5.3.0"
+ p-map "^4.0.0"
+ strip-ansi "6.0.0"
"@snyk/gemfile@1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@snyk/gemfile/-/gemfile-1.2.0.tgz#919857944973cce74c650e5428aaf11bcd5c0457"
integrity sha512-nI7ELxukf7pT4/VraL4iabtNNMz8mUo7EXlqCFld8O5z6mIMLX9llps24iPpaIZOwArkY3FWA+4t+ixyvtTSIA==
-"@snyk/ruby-semver@^2.0.4":
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/@snyk/ruby-semver/-/ruby-semver-2.0.4.tgz#457686ea7a4d60b10efddde99587efb3a53ba884"
- integrity sha512-ceMD4CBS3qtAg+O0BUvkKdsheUNCqi+/+Rju243Ul8PsUgZnXmGiqfk/2z7DCprRQnxUTra4+IyeDQT7wAheCQ==
+"@snyk/glob-parent@^5.1.2-patch.1":
+ version "5.1.2-patch.1"
+ resolved "https://registry.yarnpkg.com/@snyk/glob-parent/-/glob-parent-5.1.2-patch.1.tgz#87733b4ab282043fa7915200bc94cb391df6d44f"
+ integrity sha512-OkUPdHgxIWKAAzceG1nraNA0kgI+eS0I9wph8tll9UL0slD2mIWSj4mAqroGovaEXm8nHedoUfuDRGEb6wnzCQ==
dependencies:
- lodash "^4.17.14"
+ is-glob "^4.0.1"
-"@snyk/snyk-cocoapods-plugin@2.0.1":
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/@snyk/snyk-cocoapods-plugin/-/snyk-cocoapods-plugin-2.0.1.tgz#be8660c854d551a56baa9d072bb4ae7f188cc1cd"
- integrity sha512-XVkvaMvMzQ3miJi/YZmsRJSAUfDloYhfg6pXPgzAeAugB4p+cNi01Z68pT62ypB8U/Ugh1Xx2pb9aoOFqBbSjA==
+"@snyk/graphlib@2.1.9-patch.3", "@snyk/graphlib@^2.1.9-patch.3":
+ version "2.1.9-patch.3"
+ resolved "https://registry.yarnpkg.com/@snyk/graphlib/-/graphlib-2.1.9-patch.3.tgz#b8edb2335af1978db7f3cb1f28f5d562960acf23"
+ integrity sha512-bBY9b9ulfLj0v2Eer0yFYa3syVeIxVKl2EpxSrsVeT4mjA0CltZyHsF0JjoaGXP27nItTdJS5uVsj1NA+3aE+Q==
dependencies:
- "@snyk/cli-interface" "1.5.0"
- "@snyk/cocoapods-lockfile-parser" "3.0.0"
- "@snyk/dep-graph" "^1.13.1"
+ lodash.clone "^4.5.0"
+ lodash.constant "^3.0.0"
+ lodash.filter "^4.6.0"
+ lodash.foreach "^4.5.0"
+ lodash.has "^4.5.2"
+ lodash.isempty "^4.4.0"
+ lodash.isfunction "^3.0.9"
+ lodash.isundefined "^3.0.1"
+ lodash.keys "^4.2.0"
+ lodash.map "^4.6.0"
+ lodash.reduce "^4.6.0"
+ lodash.size "^4.2.0"
+ lodash.transform "^4.6.0"
+ lodash.union "^4.6.0"
+ lodash.values "^4.3.0"
+
+"@snyk/inquirer@^7.3.3-patch":
+ version "7.3.3-patch"
+ resolved "https://registry.yarnpkg.com/@snyk/inquirer/-/inquirer-7.3.3-patch.tgz#ef84d531724c53b755e8dd454e1a3c2ccdcfc0bf"
+ integrity sha512-aWiQSOacH2lOpJ1ard9ErABcH4tdJogdr+mg1U67iZJOPO9n2gFgAwz1TQJDyPkv4/A5mh4hT2rg03Uq+KBn2Q==
+ dependencies:
+ ansi-escapes "^4.2.1"
+ chalk "^4.1.0"
+ cli-cursor "^3.1.0"
+ cli-width "^3.0.0"
+ external-editor "^3.0.3"
+ figures "^3.0.0"
+ lodash.assign "^4.2.0"
+ lodash.assignin "^4.2.0"
+ lodash.clone "^4.5.0"
+ lodash.defaults "^4.2.0"
+ lodash.filter "^4.6.0"
+ lodash.find "^4.6.0"
+ lodash.findindex "^4.6.0"
+ lodash.flatten "^4.4.0"
+ lodash.isboolean "^3.0.3"
+ lodash.isfunction "^3.0.9"
+ lodash.isnumber "^3.0.3"
+ lodash.isplainobject "^4.0.6"
+ lodash.isstring "^4.0.1"
+ lodash.last "^3.0.0"
+ lodash.map "^4.6.0"
+ lodash.omit "^4.5.0"
+ lodash.set "^4.3.2"
+ lodash.sum "^4.0.2"
+ lodash.uniq "^4.5.0"
+ mute-stream "0.0.8"
+ run-async "^2.4.0"
+ rxjs "^6.6.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+ through "^2.3.6"
+
+"@snyk/java-call-graph-builder@1.19.1":
+ version "1.19.1"
+ resolved "https://registry.yarnpkg.com/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.19.1.tgz#1d579d782df3bb5f9d5171cc35180596cd90aa8b"
+ integrity sha512-bxjHef5Qm3pNc+BrFlxMudmSSbOjA395ZqBddc+dvsFHoHeyNbiY56Y1JSGUlTgjRM+PKNPBiCuELTSMaROeZg==
+ dependencies:
+ "@snyk/graphlib" "2.1.9-patch.3"
+ ci-info "^2.0.0"
+ debug "^4.1.1"
+ glob "^7.1.6"
+ jszip "^3.2.2"
+ needle "^2.3.3"
+ progress "^2.0.3"
+ snyk-config "^4.0.0-rc.2"
source-map-support "^0.5.7"
+ temp-dir "^2.0.0"
+ tmp "^0.2.1"
tslib "^1.9.3"
+ xml-js "^1.6.11"
-"@snyk/update-notifier@^2.5.1-rc2":
- version "2.5.1-rc2"
- resolved "https://registry.yarnpkg.com/@snyk/update-notifier/-/update-notifier-2.5.1-rc2.tgz#14bf816114b5698a255289d7170157f254202fad"
- integrity sha512-dlled3mfpnAt3cQb5hxkFiqfPCj4Yk0xV8Yl5P8PeVv1pUmO7vI4Ka4Mjs4r6CYM5f9kZhviFPQQcWOIDlMRcw==
+"@snyk/java-call-graph-builder@1.20.0":
+ version "1.20.0"
+ resolved "https://registry.yarnpkg.com/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.20.0.tgz#ffca734cf7ce276a69277963149358190eaac3e5"
+ integrity sha512-NX8bpIu7oG5cuSSm6WvtxqcCuJs2gRjtKhtuSeF1p5TYXyESs3FXQ0nHjfY90LiyTTc+PW/UBq6SKbBA6bCBww==
dependencies:
- "@snyk/configstore" "3.2.0-rc1"
- boxen "^1.3.0"
- chalk "^2.3.2"
- import-lazy "^2.1.0"
- is-ci "^1.0.10"
- is-installed-globally "^0.1.0"
- is-npm "^1.0.0"
- latest-version "^3.1.0"
- semver-diff "^2.0.0"
- xdg-basedir "^3.0.0"
+ "@snyk/graphlib" "2.1.9-patch.3"
+ ci-info "^2.0.0"
+ debug "^4.1.1"
+ glob "^7.1.6"
+ jszip "^3.2.2"
+ needle "^2.3.3"
+ progress "^2.0.3"
+ snyk-config "^4.0.0-rc.2"
+ source-map-support "^0.5.7"
+ temp-dir "^2.0.0"
+ tmp "^0.2.1"
+ tslib "^1.9.3"
+ xml-js "^1.6.11"
-"@types/agent-base@^4.2.0":
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/@types/agent-base/-/agent-base-4.2.0.tgz#00644e8b395b40e1bf50aaf1d22cabc1200d5051"
- integrity sha512-8mrhPstU+ZX0Ugya8tl5DsDZ1I5ZwQzbL/8PA0z8Gj0k9nql7nkaMzmPVLj+l/nixWaliXi+EBiLA8bptw3z7Q==
+"@snyk/mix-parser@^1.1.1":
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/@snyk/mix-parser/-/mix-parser-1.3.2.tgz#930de1d9c3a91e20660751f78c3e6f6a88ac5b2b"
+ integrity sha512-0Aq9vcgmjH0d9Gk5q0k6l4ZOvSHPf6/BCQGDVOpKp0hwOkXWnpDOLLPxL+uBCktuH9zTYQFB0aTk91kQImZqmA==
dependencies:
- "@types/events" "*"
+ "@snyk/dep-graph" "^1.28.0"
+ tslib "^2.0.0"
+
+"@snyk/rpm-parser@^2.0.0":
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/@snyk/rpm-parser/-/rpm-parser-2.2.1.tgz#b61ccf5478684b203576bd2be68de434ccbb0069"
+ integrity sha512-OAON0bPf3c5fgM/GK9DX0aZErB6SnuRyYlPH0rqI1TXGsKrYnVELhaE6ctNbEfPTQuY9r6q0vM+UYDaFM/YliA==
+ dependencies:
+ event-loop-spinner "^2.0.0"
+
+"@snyk/snyk-cocoapods-plugin@2.5.2":
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/@snyk/snyk-cocoapods-plugin/-/snyk-cocoapods-plugin-2.5.2.tgz#cd724fcd637cb3af76187bf7254819b6079489f6"
+ integrity sha512-WHhnwyoGOhjFOjBXqUfszD84SErrtjHjium/4xFbqKpEE+yuwxs8OwV/S29BtxhYiGtjpD1azv5QtH30VUMl0A==
+ dependencies:
+ "@snyk/cli-interface" "^2.11.0"
+ "@snyk/cocoapods-lockfile-parser" "3.6.2"
+ "@snyk/dep-graph" "^1.23.1"
+ source-map-support "^0.5.7"
+ tslib "^2.0.0"
+
+"@snyk/snyk-docker-pull@3.2.3":
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/@snyk/snyk-docker-pull/-/snyk-docker-pull-3.2.3.tgz#9743ea624098c7abd0f95c438c76067530494f4b"
+ integrity sha512-hiFiSmWGLc2tOI7FfgIhVdFzO2f69im8O6p3OV4xEZ/Ss1l58vwtqudItoswsk7wj/azRlgfBW8wGu2MjoudQg==
+ dependencies:
+ "@snyk/docker-registry-v2-client" "1.13.9"
+ child-process "^1.0.2"
+ tar-stream "^2.1.2"
+ tmp "^0.1.0"
+
+"@snyk/snyk-hex-plugin@1.1.4":
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/@snyk/snyk-hex-plugin/-/snyk-hex-plugin-1.1.4.tgz#4a5b1684cecc1a557ec1a9f5f8646683ae89f0da"
+ integrity sha512-kLfFGckSmyKe667UGPyWzR/H7/Trkt4fD8O/ktElOx1zWgmivpLm0Symb4RCfEmz9irWv+N6zIKRrfSNdytcPQ==
+ dependencies:
+ "@snyk/dep-graph" "^1.28.0"
+ "@snyk/mix-parser" "^1.1.1"
+ debug "^4.3.1"
+ tmp "^0.0.33"
+ tslib "^2.0.0"
+ upath "2.0.1"
+
+"@szmarczak/http-timer@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"
+ integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==
+ dependencies:
+ defer-to-connect "^1.0.1"
+
+"@szmarczak/http-timer@^4.0.5":
+ version "4.0.5"
+ resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152"
+ integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==
+ dependencies:
+ defer-to-connect "^2.0.0"
+
+"@types/braces@*":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@types/braces/-/braces-3.0.0.tgz#7da1c0d44ff1c7eb660a36ec078ea61ba7eb42cb"
+ integrity sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw==
+
+"@types/cacheable-request@^6.0.1":
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976"
+ integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==
+ dependencies:
+ "@types/http-cache-semantics" "*"
+ "@types/keyv" "*"
"@types/node" "*"
-
-"@types/bunyan@*":
- version "1.8.6"
- resolved "https://registry.yarnpkg.com/@types/bunyan/-/bunyan-1.8.6.tgz#6527641cca30bedec5feb9ab527b7803b8000582"
- integrity sha512-YiozPOOsS6bIuz31ilYqR5SlLif4TBWsousN2aCWLi5233nZSX19tFbcQUPdR7xJ8ypPyxkCGNxg0CIV5n9qxQ==
- dependencies:
- "@types/node" "*"
+ "@types/responselike" "*"
"@types/debug@^4.1.4":
version "4.1.5"
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd"
integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==
-"@types/events@*":
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
- integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
+"@types/emscripten@^1.38.0":
+ version "1.39.4"
+ resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.39.4.tgz#d61990c0cee72c4e475de737a140b51fe925a2c8"
+ integrity sha512-k3LLVMFrdNA9UCvMDPWMbFrGPNb+GcPyw29ktJTo1RCN7RmxFG5XzPZcPKRlnLuLT/FRm8wp4ohvDwNY7GlROQ==
+
+"@types/flat-cache@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@types/flat-cache/-/flat-cache-2.0.0.tgz#64e5d3b426c392b603a208a55bdcc7d920ce6e57"
+ integrity sha512-fHeEsm9hvmZ+QHpw6Fkvf19KIhuqnYLU6vtWLjd5BsMd/qVi7iTkMioDZl0mQmfNRA1A6NwvhrSRNr9hGYZGww==
+
+"@types/graphlib@^2":
+ version "2.1.7"
+ resolved "https://registry.yarnpkg.com/@types/graphlib/-/graphlib-2.1.7.tgz#e6a47a4f43511f5bad30058a669ce5ce93bfd823"
+ integrity sha512-K7T1n6U2HbTYu+SFHlBjz/RH74OA2D/zF1qlzn8uXbvB4uRg7knOM85ugS2bbXI1TXMh7rLqk4OVRwIwEBaixg==
+
+"@types/http-cache-semantics@*":
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a"
+ integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==
"@types/js-yaml@^3.12.1":
version "3.12.2"
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.2.tgz#a35a1809c33a68200fb6403d1ad708363c56470a"
integrity sha512-0CFu/g4mDSNkodVwWijdlr8jH7RoplRWNgovjFLEZeT+QEbbZXjBmCe3HwaWheAlCbHwomTwzZoSedeOycABug==
+"@types/keyv@*":
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7"
+ integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==
+ dependencies:
+ "@types/node" "*"
+
+"@types/lodash.chunk@^4.2.6":
+ version "4.2.6"
+ resolved "https://registry.yarnpkg.com/@types/lodash.chunk/-/lodash.chunk-4.2.6.tgz#9d35f05360b0298715d7f3d9efb34dd4f77e5d2a"
+ integrity sha512-SPlusB7jxXyGcTXYcUdWr7WmhArO/rmTq54VN88iKMxGUhyg79I4Q8n4riGn3kjaTjOJrVlHhxgX/d7woak5BQ==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash.omit@^4.5.6":
+ version "4.5.6"
+ resolved "https://registry.yarnpkg.com/@types/lodash.omit/-/lodash.omit-4.5.6.tgz#f2a9518259e481a48ff7ec423420fa8fd58933e2"
+ integrity sha512-KXPpOSNX2h0DAG2w7ajpk7TXvWF28ZHs5nJhOJyP0BQHkehgr948RVsToItMme6oi0XJkp19CbuNXkIX8FiBlQ==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash.union@^4.6.6":
+ version "4.6.6"
+ resolved "https://registry.yarnpkg.com/@types/lodash.union/-/lodash.union-4.6.6.tgz#2f77f2088326ed147819e9e384182b99aae8d4b0"
+ integrity sha512-Wu0ZEVNcyCz8eAn6TlUbYWZoGbH9E+iOHxAZbwUoCEXdUiy6qpcz5o44mMXViM4vlPLLCPlkAubEP1gokoSZaw==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash@*":
+ version "4.14.168"
+ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008"
+ integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==
+
+"@types/micromatch@^4.0.1":
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/@types/micromatch/-/micromatch-4.0.1.tgz#9381449dd659fc3823fd2a4190ceacc985083bc7"
+ integrity sha512-my6fLBvpY70KattTNzYOK6KU1oR1+UCz9ug/JbcF5UrEmeCt9P7DV2t7L8+t18mMPINqGQCE4O8PLOPbI84gxw==
+ dependencies:
+ "@types/braces" "*"
+
"@types/node@*":
version "13.5.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.5.3.tgz#37f1f539b7535b9fb4ef77d59db1847a837b7f17"
integrity sha512-ZPnWX9PW992w6DUsz3JIXHaSb5v7qmKCVzC3km6SxcDGxk7zmLfYaCJTbktIa5NeywJkkZDhGldKqDIvC5DRrA==
-"@types/node@^6.14.4":
- version "6.14.9"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-6.14.9.tgz#733583e21ef0eab85a9737dfafbaa66345a92ef0"
- integrity sha512-leP/gxHunuazPdZaCvsCefPQxinqUDsCxCR5xaDUrY2MkYxQRFZZwU5e7GojyYsGB7QVtCi7iVEl/hoFXQYc+w==
+"@types/node@^13.7.0":
+ version "13.13.50"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.50.tgz#bc8ebf70c392a98ffdba7aab9b46989ea96c1c62"
+ integrity sha512-y7kkh+hX/0jZNxMyBR/6asG0QMSaPSzgeVK63dhWHl4QAXCQB8lExXmzLL6SzmOgKHydtawpMnNhlDbv7DXPEA==
-"@types/restify@^4.3.6":
- version "4.3.6"
- resolved "https://registry.yarnpkg.com/@types/restify/-/restify-4.3.6.tgz#5da5889b65c34c33937a67686bab591325dde806"
- integrity sha512-4l4f0EXnleXQttlhRCXtTuJ8UelsKiAKIK2AAEd2epBHu41aEbM0U2z6E5tUrNwlbxz7qaNBISduGMeg+G3PaA==
+"@types/responselike@*", "@types/responselike@^1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29"
+ integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==
dependencies:
- "@types/bunyan" "*"
"@types/node" "*"
-"@types/semver@^5.5.0":
- version "5.5.0"
- resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
- integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==
+"@types/sarif@^2.1.3":
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/@types/sarif/-/sarif-2.1.3.tgz#1f9c16033f1461536ac014284920350109614c02"
+ integrity sha512-zf+EoIplTkQW2TV2mwtJtlI0g540Z3Rs9tX9JqRAtyjnDCqkP+eMTgWCj3PGNbQpi+WXAjvC3Ou/dvvX2sLK4w==
-"@types/xml2js@0.4.3":
- version "0.4.3"
- resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.3.tgz#2f41bfc74d5a4022511721f872ed395a210ad3b7"
- integrity sha512-Pv2HGRE4gWLs31In7nsyXEH4uVVsd0HNV9i2dyASvtDIlOtSTr1eczPLDpdEuyv5LWH5LT20GIXwPjkshKWI1g==
+"@types/semver@^7.1.0":
+ version "7.3.4"
+ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.4.tgz#43d7168fec6fa0988bb1a513a697b29296721afb"
+ integrity sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ==
+
+"@types/treeify@^1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@types/treeify/-/treeify-1.0.0.tgz#f04743cb91fc38254e8585d692bd92503782011c"
+ integrity sha512-ONpcZAEYlbPx4EtJwfTyCDQJGUpKf4sEcuySdCVjK5Fj/3vHp5HII1fqa1/+qrsLnpYELCQTfVW/awsGJePoIg==
+
+"@types/uuid@^8.3.0":
+ version "8.3.0"
+ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f"
+ integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==
+
+"@yarnpkg/core@^2.4.0":
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/@yarnpkg/core/-/core-2.4.0.tgz#b5d8cc7ee2ddb022816c7afa3f83c3ee3d317c80"
+ integrity sha512-FYjcPNTfDfMKLFafQPt49EY28jnYC82Z2S7oMwLPUh144BL8v8YXzb4aCnFyi5nFC5h2kcrJfZh7+Pm/qvCqGw==
dependencies:
- "@types/events" "*"
- "@types/node" "*"
+ "@arcanis/slice-ansi" "^1.0.2"
+ "@types/semver" "^7.1.0"
+ "@types/treeify" "^1.0.0"
+ "@yarnpkg/fslib" "^2.4.0"
+ "@yarnpkg/json-proxy" "^2.1.0"
+ "@yarnpkg/libzip" "^2.2.1"
+ "@yarnpkg/parsers" "^2.3.0"
+ "@yarnpkg/pnp" "^2.3.2"
+ "@yarnpkg/shell" "^2.4.1"
+ binjumper "^0.1.4"
+ camelcase "^5.3.1"
+ chalk "^3.0.0"
+ ci-info "^2.0.0"
+ clipanion "^2.6.2"
+ cross-spawn "7.0.3"
+ diff "^4.0.1"
+ globby "^11.0.1"
+ got "^11.7.0"
+ json-file-plus "^3.3.1"
+ lodash "^4.17.15"
+ micromatch "^4.0.2"
+ mkdirp "^0.5.1"
+ p-limit "^2.2.0"
+ pluralize "^7.0.0"
+ pretty-bytes "^5.1.0"
+ semver "^7.1.2"
+ stream-to-promise "^2.2.0"
+ tar-stream "^2.0.1"
+ treeify "^1.1.0"
+ tslib "^1.13.0"
+ tunnel "^0.0.6"
-"@yarnpkg/lockfile@^1.0.2":
+"@yarnpkg/fslib@^2.1.0", "@yarnpkg/fslib@^2.4.0":
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/@yarnpkg/fslib/-/fslib-2.4.0.tgz#a265b737cd089ef293ad964e06c143f5efd411a9"
+ integrity sha512-CwffYY9owtl3uImNOn1K4jl5iIb/L16a9UZ9Q3lkBARk6tlUsPrNFX00eoUlFcLn49TTfd3zdN6higloGCyncw==
+ dependencies:
+ "@yarnpkg/libzip" "^2.2.1"
+ tslib "^1.13.0"
+
+"@yarnpkg/json-proxy@^2.1.0":
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/@yarnpkg/json-proxy/-/json-proxy-2.1.0.tgz#362a161678cd7dda74b47b4fc848a2f1730d16cd"
+ integrity sha512-rOgCg2DkyviLgr80mUMTt9vzdf5RGOujQB26yPiXjlz4WNePLBshKlTNG9rKSoKQSOYEQcw6cUmosfOKDatrCw==
+ dependencies:
+ "@yarnpkg/fslib" "^2.1.0"
+ tslib "^1.13.0"
+
+"@yarnpkg/libzip@^2.2.1":
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/@yarnpkg/libzip/-/libzip-2.2.1.tgz#61c9b8b2499ee6bd9c4fcbf8248f68e07bd89948"
+ integrity sha512-AYDJXrkzayoDd3ZlVgFJ+LyDX+Zj/cki3vxIpcYxejtgkl3aquVWOxlC0DD9WboBWsJFIP1MjrUbchLyh++/7A==
+ dependencies:
+ "@types/emscripten" "^1.38.0"
+ tslib "^1.13.0"
+
+"@yarnpkg/lockfile@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
+"@yarnpkg/parsers@^2.3.0":
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/@yarnpkg/parsers/-/parsers-2.3.0.tgz#7b9564c6df02f4921d5cfe8287c4b648e93ea84b"
+ integrity sha512-qgz0QUgOvnhtF92kaluIhIIKBUHlYlHUBQxqh5v9+sxEQvUeF6G6PKiFlzo3E6O99XwvNEGpVu1xZPoSGyGscQ==
+ dependencies:
+ js-yaml "^3.10.0"
+ tslib "^1.13.0"
+
+"@yarnpkg/pnp@^2.3.2":
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/@yarnpkg/pnp/-/pnp-2.3.2.tgz#9a052a06bf09c9f0b7c31e0867a7e725cb6401ed"
+ integrity sha512-JdwHu1WBCISqJEhIwx6Hbpe8MYsYbkGMxoxolkDiAeJ9IGEe08mQcbX1YmUDV1ozSWlm9JZE90nMylcDsXRFpA==
+ dependencies:
+ "@types/node" "^13.7.0"
+ "@yarnpkg/fslib" "^2.4.0"
+ tslib "^1.13.0"
+
+"@yarnpkg/shell@^2.4.1":
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/@yarnpkg/shell/-/shell-2.4.1.tgz#abc557f8924987c9c382703e897433a82780265d"
+ integrity sha512-oNNJkH8ZI5uwu0dMkJf737yMSY1WXn9gp55DqSA5wAOhKvV5DJTXFETxkVgBQhO6Bow9tMGSpvowTMD/oAW/9g==
+ dependencies:
+ "@yarnpkg/fslib" "^2.4.0"
+ "@yarnpkg/parsers" "^2.3.0"
+ clipanion "^2.6.2"
+ cross-spawn "7.0.3"
+ fast-glob "^3.2.2"
+ micromatch "^4.0.2"
+ stream-buffers "^3.0.2"
+ tslib "^1.13.0"
+
abbrev@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
-agent-base@4, agent-base@^4.2.0, agent-base@^4.3.0:
- version "4.3.0"
- resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
- integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
+aggregate-error@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
+ integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==
dependencies:
- es6-promisify "^5.0.0"
+ clean-stack "^2.0.0"
+ indent-string "^4.0.0"
-agent-base@~4.2.1:
- version "4.2.1"
- resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
- integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==
+ansi-align@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb"
+ integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==
dependencies:
- es6-promisify "^5.0.0"
+ string-width "^3.0.0"
-ansi-align@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f"
- integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=
- dependencies:
- string-width "^2.0.0"
-
-ansi-escapes@3.2.0, ansi-escapes@^3.2.0:
+ansi-escapes@3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
-ansi-regex@^2.0.0:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
- integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
-
-ansi-regex@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
- integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
+ansi-escapes@^4.2.1:
+ version "4.3.2"
+ resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
+ integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==
+ dependencies:
+ type-fest "^0.21.3"
ansi-regex@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
+ansi-regex@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
+ integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
+
ansi-styles@^3.2.0, ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
@@ -244,11 +625,23 @@
dependencies:
color-convert "^1.9.0"
+ansi-styles@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+ integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+ dependencies:
+ color-convert "^2.0.1"
+
ansicolors@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979"
integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=
+any-promise@^1.1.0, any-promise@~1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
+ integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
+
archy@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40"
@@ -261,26 +654,57 @@
dependencies:
sprintf-js "~1.0.2"
+array-union@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
+ integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
+
asap@~2.0.3:
version "2.0.6"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
-ast-types@0.x.x:
- version "0.13.2"
- resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.2.tgz#df39b677a911a83f3a049644fb74fdded23cea48"
- integrity sha512-uWMHxJxtfj/1oZClOxDEV1sQ1HCDkA4MG8Gr69KKeBjEVH0R84WlejZ0y2DcwyBlpAEMltmVYkVgqfLFb2oyiA==
+asn1@~0.2.0:
+ version "0.2.4"
+ resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
+ integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
+ dependencies:
+ safer-buffer "~2.1.0"
-async@^1.4.0:
- version "1.5.2"
- resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
- integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=
+async@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
+ integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==
+
+axios@^0.21.1:
+ version "0.21.1"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
+ integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
+ dependencies:
+ follow-redirects "^1.10.0"
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+base64-js@^1.3.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
+ integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
+
+bcrypt-pbkdf@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
+ integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
+ dependencies:
+ tweetnacl "^0.14.3"
+
+binjumper@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/binjumper/-/binjumper-0.1.4.tgz#4acc0566832714bd6508af6d666bd9e5e21fc7f8"
+ integrity sha512-Gdxhj+U295tIM6cO4bJO1jsvSjBVHNpj2o/OwW7pqDEtaqF6KdOxjtbo93jMMKAkP7+u09+bV8DhSqjIv4qR3w==
+
bl@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.1.tgz#1cbb439299609e419b5a74d7fce2f8b37d8e5c6f"
@@ -288,18 +712,33 @@
dependencies:
readable-stream "^3.0.1"
-boxen@^1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b"
- integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==
+bl@^4.0.3:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
+ integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
dependencies:
- ansi-align "^2.0.0"
- camelcase "^4.0.0"
- chalk "^2.0.1"
- cli-boxes "^1.0.0"
- string-width "^2.0.0"
- term-size "^1.2.0"
- widest-line "^2.0.0"
+ buffer "^5.5.0"
+ inherits "^2.0.4"
+ readable-stream "^3.4.0"
+
+boolean@^3.0.1:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.3.tgz#0fee0c9813b66bef25a8a6a904bb46736d05f024"
+ integrity sha512-EqrTKXQX6Z3A2nRmMEIlAIfjQOgFnVO2nqZGpbcsPnYGWBwpFqzlrozU1dy+S2iqfYDLh26ef4KrgTxu9xQrxA==
+
+boxen@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64"
+ integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==
+ dependencies:
+ ansi-align "^3.0.0"
+ camelcase "^5.3.1"
+ chalk "^3.0.0"
+ cli-boxes "^2.2.0"
+ string-width "^4.1.0"
+ term-size "^2.1.0"
+ type-fest "^0.8.1"
+ widest-line "^3.1.0"
brace-expansion@^1.1.7:
version "1.1.11"
@@ -309,41 +748,86 @@
balanced-match "^1.0.0"
concat-map "0.0.1"
+braces@^3.0.1:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
+ integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
+ dependencies:
+ fill-range "^7.0.1"
+
+browserify-zlib@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d"
+ integrity sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=
+ dependencies:
+ pako "~0.2.0"
+
buffer-from@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
-bytes@3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
- integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
-
-camelcase@^2.0.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
- integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=
-
-camelcase@^4.0.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
- integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
-
-capture-stack-trace@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d"
- integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==
-
-chalk@^2.0.1:
- version "2.4.1"
- resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e"
- integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==
+buffer@^5.5.0:
+ version "5.7.1"
+ resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
+ integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
dependencies:
- ansi-styles "^3.2.1"
- escape-string-regexp "^1.0.5"
- supports-color "^5.3.0"
+ base64-js "^1.3.1"
+ ieee754 "^1.1.13"
-chalk@^2.3.2, chalk@^2.4.2:
+cacheable-lookup@^5.0.3:
+ version "5.0.4"
+ resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005"
+ integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==
+
+cacheable-request@^6.0.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912"
+ integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==
+ dependencies:
+ clone-response "^1.0.2"
+ get-stream "^5.1.0"
+ http-cache-semantics "^4.0.0"
+ keyv "^3.0.0"
+ lowercase-keys "^2.0.0"
+ normalize-url "^4.1.0"
+ responselike "^1.0.2"
+
+cacheable-request@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58"
+ integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==
+ dependencies:
+ clone-response "^1.0.2"
+ get-stream "^5.1.0"
+ http-cache-semantics "^4.0.0"
+ keyv "^4.0.0"
+ lowercase-keys "^2.0.0"
+ normalize-url "^4.1.0"
+ responselike "^2.0.0"
+
+call-bind@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
+ integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
+ dependencies:
+ function-bind "^1.1.1"
+ get-intrinsic "^1.0.2"
+
+camelcase@^5.3.1:
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
+ integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
+
+chalk@4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
+ integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
+chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -352,56 +836,90 @@
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
+chalk@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
+ integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
+chalk@^4.1.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad"
+ integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
chardet@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
-ci-info@^1.5.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497"
- integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==
+child-process@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/child-process/-/child-process-1.0.2.tgz#98974dc7ed1ee4c6229f8e305fa7313a6885a7f2"
+ integrity sha1-mJdNx+0e5MYin44wX6cxOmiFp/I=
-cli-boxes@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143"
- integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM=
+chownr@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
+ integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
-cli-cursor@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
- integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=
+ci-info@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
+ integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
+
+clean-stack@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
+ integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
+
+cli-boxes@^2.2.0:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f"
+ integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==
+
+cli-cursor@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
+ integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==
dependencies:
- restore-cursor "^2.0.0"
+ restore-cursor "^3.1.0"
cli-spinner@0.2.10:
version "0.2.10"
resolved "https://registry.yarnpkg.com/cli-spinner/-/cli-spinner-0.2.10.tgz#f7d617a36f5c47a7bc6353c697fc9338ff782a47"
integrity sha512-U0sSQ+JJvSLi1pAYuJykwiA8Dsr15uHEy85iCJ6A+0DjVxivr3d+N2Wjvodeg89uP5K6TswFkKBfAD7B3YSn/Q==
-cli-width@^2.0.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
- integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
+cli-spinners@^2.5.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.0.tgz#36c7dc98fb6a9a76bd6238ec3f77e2425627e939"
+ integrity sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q==
-cliui@^3.0.3:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
- integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=
+cli-width@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
+ integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
+
+clipanion@^2.6.2:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/clipanion/-/clipanion-2.6.2.tgz#820e7440812052442455b248f927b187ed732f71"
+ integrity sha512-0tOHJNMF9+4R3qcbBL+4IxLErpaYSYvzs10aXuECDbZdJOuJHdagJMAqvLdeaUQTI/o2uSCDRpet6ywDiKOAYw==
+
+clone-response@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
+ integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=
dependencies:
- string-width "^1.0.1"
- strip-ansi "^3.0.1"
- wrap-ansi "^2.0.0"
+ mimic-response "^1.0.0"
-co@^4.6.0:
- version "4.6.0"
- resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
- integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=
-
-code-point-at@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
- integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
+clone@^1.0.2:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
+ integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
color-convert@^1.9.0:
version "1.9.3"
@@ -410,41 +928,58 @@
dependencies:
color-name "1.1.3"
+color-convert@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
+ integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+ dependencies:
+ color-name "~1.1.4"
+
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
+color-name@~1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
+ integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
-core-js@^3.2.0:
- version "3.6.4"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647"
- integrity sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==
+configstore@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96"
+ integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==
+ dependencies:
+ dot-prop "^5.2.0"
+ graceful-fs "^4.1.2"
+ make-dir "^3.0.0"
+ unique-string "^2.0.0"
+ write-file-atomic "^3.0.0"
+ xdg-basedir "^4.0.0"
+
+core-js@^3.6.5:
+ version "3.11.0"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.11.0.tgz#05dac6aa70c0a4ad842261f8957b961d36eb8926"
+ integrity sha512-bd79DPpx+1Ilh9+30aT5O1sgpQd4Ttg8oqkqi51ZzhedMM1omD2e6IOF48Z/DzDCZ2svp49tN/3vneTK6ZBkXw==
core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
-create-error-class@^3.0.0:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6"
- integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=
+cross-spawn@7.0.3:
+ version "7.0.3"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
+ integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
dependencies:
- capture-stack-trace "^1.0.0"
-
-cross-spawn@^5.0.1:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
- integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=
- dependencies:
- lru-cache "^4.0.1"
- shebang-command "^1.2.0"
- which "^1.2.9"
+ path-key "^3.1.0"
+ shebang-command "^2.0.0"
+ which "^2.0.1"
cross-spawn@^6.0.0:
version "6.0.5"
@@ -457,84 +992,108 @@
shebang-command "^1.2.0"
which "^1.2.9"
-crypto-random-string@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
- integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=
+crypto-random-string@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
+ integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
-data-uri-to-buffer@1:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz#77163ea9c20d8641b4707e8f18abdf9a78f34835"
- integrity sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==
-
-debug@2:
- version "2.6.9"
- resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
- integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
- dependencies:
- ms "2.0.0"
-
-debug@3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
- integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
- dependencies:
- ms "2.0.0"
-
-debug@4, debug@^4.1.1:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
- integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
- dependencies:
- ms "^2.1.1"
-
-debug@^3.1.0, debug@^3.2.5, debug@^3.2.6:
+debug@^3.1.0, debug@^3.2.6:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
dependencies:
ms "^2.1.1"
-decamelize@^1.1.1:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
- integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
+debug@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
+ integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
+ dependencies:
+ ms "^2.1.1"
+
+debug@^4.2.0, debug@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
+ integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
+ dependencies:
+ ms "2.1.2"
+
+decompress-response@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
+ integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=
+ dependencies:
+ mimic-response "^1.0.0"
+
+decompress-response@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
+ integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==
+ dependencies:
+ mimic-response "^3.1.0"
deep-extend@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
-deep-is@~0.1.3:
- version "0.1.3"
- resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
- integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
-
-degenerator@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-1.0.4.tgz#fcf490a37ece266464d9cc431ab98c5819ced095"
- integrity sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU=
+defaults@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"
+ integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=
dependencies:
- ast-types "0.x.x"
- escodegen "1.x.x"
- esprima "3.x.x"
+ clone "^1.0.2"
-depd@~1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
- integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
+defer-to-connect@^1.0.1:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591"
+ integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==
+
+defer-to-connect@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587"
+ integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==
+
+define-properties@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
+ integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
+ dependencies:
+ object-keys "^1.0.12"
+
+detect-node@^2.0.4:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.5.tgz#9d270aa7eaa5af0b72c4c9d9b814e7f4ce738b79"
+ integrity sha512-qi86tE6hRcFHy8jI1m2VG+LaPUR1LhqDa5G8tVjuUXmOrpuAgqsA1pN0+ldgr3aKUH+QLI9hCY/OcRYisERejw==
diff@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
-dockerfile-ast@0.0.18:
- version "0.0.18"
- resolved "https://registry.yarnpkg.com/dockerfile-ast/-/dockerfile-ast-0.0.18.tgz#94a0ba84eb9b3e9fb7bd6beae0ea7eb6dcbca75a"
- integrity sha512-SEp95qCox1KAzf8BBtjHoBDD0a7/eNlZJ6fgDf9RxqeSEDwLuEN9YjdZ/tRlkrYLxXR4i+kqZzS4eDRSqs8VKQ==
+dir-glob@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
+ integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==
dependencies:
- vscode-languageserver-types "^3.5.0"
+ path-type "^4.0.0"
+
+docker-modem@2.1.3:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-2.1.3.tgz#15432225f63db02eb5de4bb9a621b7293e5f264d"
+ integrity sha512-cwaRptBmYZwu/FyhGcqBm2MzXA77W2/E6eVkpOZVDk6PkI9Bjj84xPrXiHMA+OWjzNy+DFjgKh8Q+1hMR7/OHg==
+ dependencies:
+ debug "^4.1.1"
+ readable-stream "^3.5.0"
+ split-ca "^1.0.1"
+ ssh2 "^0.8.7"
+
+dockerfile-ast@0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/dockerfile-ast/-/dockerfile-ast-0.2.0.tgz#13cc4a6fe3aea30a4104622b30f49a0fe3a5c038"
+ integrity sha512-iQyp12k1A4tF3sEfLAq2wfFPKdpoiGTJeuiu2Y1bdEqIZu0DfSSL2zm0fk7a/UHeQkngnYaRRGuON+C+2LO1Fw==
+ dependencies:
+ vscode-languageserver-types "^3.16.0"
dot-prop@^5.2.0:
version "5.2.0"
@@ -543,22 +1102,40 @@
dependencies:
is-obj "^2.0.0"
-dotnet-deps-parser@4.9.0:
- version "4.9.0"
- resolved "https://registry.yarnpkg.com/dotnet-deps-parser/-/dotnet-deps-parser-4.9.0.tgz#d14f9f92ae9a64062cd215c8863d1e77e80236f0"
- integrity sha512-V0O+7pI7Ei+iL5Kgy6nYq1UTwzrpqci5K/zf8cXyP5RWBSQBUl/JOE9I67zLUkKiwOdfPhbMQgcRj/yGA+NL1A==
+dotnet-deps-parser@5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/dotnet-deps-parser/-/dotnet-deps-parser-5.0.0.tgz#5115c442cbefea59e4fb9f9ed8fa4863a0f3186d"
+ integrity sha512-1l9K4UnQQHSfKgeHeLrxnB53AidCZqPyf9dkRL4/fZl8//NPiiDD43zHtgylw8DHlO7gvM8+O5a0UPHesNYZKw==
dependencies:
- "@types/xml2js" "0.4.3"
- lodash "^4.17.11"
+ lodash.isempty "^4.4.0"
+ lodash.set "^4.3.2"
+ lodash.uniq "^4.5.0"
source-map-support "^0.5.7"
tslib "^1.10.0"
- xml2js "0.4.19"
+ xml2js "0.4.23"
duplexer3@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
+duplexify@^3.5.0, duplexify@^3.6.0:
+ version "3.7.1"
+ resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
+ integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==
+ dependencies:
+ end-of-stream "^1.0.0"
+ inherits "^2.0.1"
+ readable-stream "^2.0.0"
+ stream-shift "^1.0.0"
+
+elfy@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/elfy/-/elfy-1.0.0.tgz#7a1c86af7d41e0a568cbb4a3fa5b685648d9efcd"
+ integrity sha512-4Kp3AA94jC085IJox+qnvrZ3PudqTi4gQNvIoTZfJJ9IqkRuCoqP60vCVYlIg00c5aYusi5Wjh2bf0cHYt+6gQ==
+ dependencies:
+ endian-reader "^0.3.0"
+
email-validator@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/email-validator/-/email-validator-2.0.4.tgz#b8dfaa5d0dae28f1b03c95881d904d4e40bfe7ed"
@@ -569,81 +1146,61 @@
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
-end-of-stream@^1.1.0, end-of-stream@^1.4.1:
+emoji-regex@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
+ integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
+end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
dependencies:
once "^1.4.0"
-es6-promise@^4.0.3:
- version "4.2.8"
- resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
- integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
-
-es6-promisify@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
- integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
+end-of-stream@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.1.0.tgz#e9353258baa9108965efc41cb0ef8ade2f3cfb07"
+ integrity sha1-6TUyWLqpEIll78QcsO+K3i88+wc=
dependencies:
- es6-promise "^4.0.3"
+ once "~1.3.0"
+
+endian-reader@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/endian-reader/-/endian-reader-0.3.0.tgz#84eca436b80aed0d0639c47291338b932efe50a0"
+ integrity sha1-hOykNrgK7Q0GOcRykTOLky7+UKA=
+
+es6-error@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
+ integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
+
+escape-goat@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
+ integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
-escodegen@1.x.x:
- version "1.13.0"
- resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.13.0.tgz#c7adf9bd3f3cc675bb752f202f79a720189cab29"
- integrity sha512-eYk2dCkxR07DsHA/X2hRBj0CFAZeri/LyDMc0C8JT1Hqi6JnVpMhJ7XFITbb0+yZS3lVkaPL2oCkZ3AVmeVbMw==
- dependencies:
- esprima "^4.0.1"
- estraverse "^4.2.0"
- esutils "^2.0.2"
- optionator "^0.8.1"
- optionalDependencies:
- source-map "~0.6.1"
-
-esprima@3.x.x:
- version "3.1.3"
- resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
- integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=
+escape-string-regexp@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
+ integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
esprima@^4.0.0, esprima@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
-estraverse@^4.2.0:
- version "4.3.0"
- resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
- integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
-
-esutils@^2.0.2:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
- integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
-
-event-loop-spinner@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/event-loop-spinner/-/event-loop-spinner-1.1.0.tgz#96de9c70e6e2b0b3e257b0901e25e792e3c9c8d0"
- integrity sha512-YVFs6dPpZIgH665kKckDktEVvSBccSYJmoZUfhNUdv5d3Xv+Q+SKF4Xis1jolq9aBzuW1ZZhQh/m/zU/TPdDhw==
+event-loop-spinner@^2.0.0, event-loop-spinner@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/event-loop-spinner/-/event-loop-spinner-2.1.0.tgz#75f501d585105c6d57f174073b39af1b6b3a1567"
+ integrity sha512-RJ10wL8/F9AlfBgRCvYctJIXSb9XkVmSCK3GGUvPD3dJrvTjDeDT0tmhcbEC6I2NEjNM9xD38HQJ4F/f/gb4VQ==
dependencies:
- tslib "^1.10.0"
-
-execa@^0.7.0:
- version "0.7.0"
- resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
- integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=
- dependencies:
- cross-spawn "^5.0.1"
- get-stream "^3.0.0"
- is-stream "^1.1.0"
- npm-run-path "^2.0.0"
- p-finally "^1.0.0"
- signal-exit "^3.0.0"
- strip-eof "^1.0.0"
+ tslib "^2.1.0"
execa@^1.0.0:
version "1.0.0"
@@ -658,11 +1215,6 @@
signal-exit "^3.0.0"
strip-eof "^1.0.0"
-extend@~3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
- integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
-
external-editor@^3.0.3:
version "3.1.0"
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495"
@@ -672,81 +1224,97 @@
iconv-lite "^0.4.24"
tmp "^0.0.33"
-fast-levenshtein@~2.0.6:
- version "2.0.6"
- resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
- integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
+fast-glob@^3.1.1, fast-glob@^3.2.2:
+ version "3.2.5"
+ resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661"
+ integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==
+ dependencies:
+ "@nodelib/fs.stat" "^2.0.2"
+ "@nodelib/fs.walk" "^1.2.3"
+ glob-parent "^5.1.0"
+ merge2 "^1.3.0"
+ micromatch "^4.0.2"
+ picomatch "^2.2.1"
-figures@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
- integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=
+fastq@^1.6.0:
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858"
+ integrity sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==
+ dependencies:
+ reusify "^1.0.4"
+
+figures@^3.0.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
+ integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==
dependencies:
escape-string-regexp "^1.0.5"
-file-uri-to-path@1:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
- integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
+fill-range@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
+ integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
+ dependencies:
+ to-regex-range "^5.0.1"
+
+follow-redirects@^1.10.0:
+ version "1.14.0"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.0.tgz#f5d260f95c5f8c105894491feee5dc8993b402fe"
+ integrity sha512-0vRwd7RKQBTt+mgu87mtYeofLFZpTas2S9zY+jIeuLJMNvudIgF52nr19q40HOwH5RrhWIPuj9puybzSJiRrVg==
fs-constants@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
+fs-minipass@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
+ integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
+ dependencies:
+ minipass "^3.0.0"
+
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
-ftp@~0.3.10:
- version "0.3.10"
- resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d"
- integrity sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=
+function-bind@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+ integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+
+get-intrinsic@^1.0.2:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
+ integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
dependencies:
- readable-stream "1.1.x"
- xregexp "2.0.0"
+ function-bind "^1.1.1"
+ has "^1.0.3"
+ has-symbols "^1.0.1"
-get-stream@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
- integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
-
-get-stream@^4.0.0:
+get-stream@^4.0.0, get-stream@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
dependencies:
pump "^3.0.0"
-get-uri@^2.0.0:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-2.0.4.tgz#d4937ab819e218d4cb5ae18e4f5962bef169cc6a"
- integrity sha512-v7LT/s8kVjs+Tx0ykk1I+H/rbpzkHvuIq87LmeXptcf5sNWm9uQiwjNAt94SJPA1zOlCntmnOlJvVWKmzsxG8Q==
+get-stream@^5.1.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
+ integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
dependencies:
- data-uri-to-buffer "1"
- debug "2"
- extend "~3.0.2"
- file-uri-to-path "1"
- ftp "~0.3.10"
- readable-stream "2"
+ pump "^3.0.0"
-git-up@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/git-up/-/git-up-4.0.1.tgz#cb2ef086653640e721d2042fe3104857d89007c0"
- integrity sha512-LFTZZrBlrCrGCG07/dm1aCjjpL1z9L3+5aEeI9SBhAqSc+kiA9Or1bgZhQFNppJX6h/f5McrvJt1mQXTFm6Qrw==
+glob-parent@^5.1.0:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
+ integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
- is-ssh "^1.3.0"
- parse-url "^5.0.0"
+ is-glob "^4.0.1"
-git-url-parse@11.1.2:
- version "11.1.2"
- resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-11.1.2.tgz#aff1a897c36cc93699270587bea3dbcbbb95de67"
- integrity sha512-gZeLVGY8QVKMIkckncX+iCq2/L8PlwncvDFKiWkBn9EtCfYDbliRTTp6qzyQ1VMdITUfq7293zDzfpjdiGASSQ==
- dependencies:
- git-up "^4.0.0"
-
-glob@^7.1.3:
+glob@^7.1.3, glob@^7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
@@ -758,91 +1326,182 @@
once "^1.3.0"
path-is-absolute "^1.0.0"
-global-dirs@^0.1.0:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445"
- integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=
+global-agent@^2.1.12:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-2.2.0.tgz#566331b0646e6bf79429a16877685c4a1fbf76dc"
+ integrity sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg==
dependencies:
- ini "^1.3.4"
+ boolean "^3.0.1"
+ core-js "^3.6.5"
+ es6-error "^4.1.1"
+ matcher "^3.0.0"
+ roarr "^2.15.3"
+ semver "^7.3.2"
+ serialize-error "^7.0.1"
-got@^6.7.1:
- version "6.7.1"
- resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0"
- integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=
+global-dirs@^2.0.1:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.1.0.tgz#e9046a49c806ff04d6c1825e196c8f0091e8df4d"
+ integrity sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==
dependencies:
- create-error-class "^3.0.0"
+ ini "1.3.7"
+
+globalthis@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.2.tgz#2a235d34f4d8036219f7e34929b5de9e18166b8b"
+ integrity sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ==
+ dependencies:
+ define-properties "^1.1.3"
+
+globby@^11.0.1:
+ version "11.0.3"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb"
+ integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==
+ dependencies:
+ array-union "^2.1.0"
+ dir-glob "^3.0.1"
+ fast-glob "^3.1.1"
+ ignore "^5.1.4"
+ merge2 "^1.3.0"
+ slash "^3.0.0"
+
+got@11.4.0:
+ version "11.4.0"
+ resolved "https://registry.yarnpkg.com/got/-/got-11.4.0.tgz#1f0910310572af4efcc6890e1dacd7affb710b39"
+ integrity sha512-XysJZuZNVpaQ37Oo2LV90MIkPeYITehyy1A0QzO1JwOXm8EWuEf9eeGk2XuHePvLEGnm9AVOI37bHwD6KYyBtg==
+ dependencies:
+ "@sindresorhus/is" "^2.1.1"
+ "@szmarczak/http-timer" "^4.0.5"
+ "@types/cacheable-request" "^6.0.1"
+ "@types/responselike" "^1.0.0"
+ cacheable-lookup "^5.0.3"
+ cacheable-request "^7.0.1"
+ decompress-response "^6.0.0"
+ http2-wrapper "^1.0.0-beta.4.5"
+ lowercase-keys "^2.0.0"
+ p-cancelable "^2.0.0"
+ responselike "^2.0.0"
+
+got@^11.7.0:
+ version "11.8.2"
+ resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599"
+ integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==
+ dependencies:
+ "@sindresorhus/is" "^4.0.0"
+ "@szmarczak/http-timer" "^4.0.5"
+ "@types/cacheable-request" "^6.0.1"
+ "@types/responselike" "^1.0.0"
+ cacheable-lookup "^5.0.3"
+ cacheable-request "^7.0.1"
+ decompress-response "^6.0.0"
+ http2-wrapper "^1.0.0-beta.5.2"
+ lowercase-keys "^2.0.0"
+ p-cancelable "^2.0.0"
+ responselike "^2.0.0"
+
+got@^9.6.0:
+ version "9.6.0"
+ resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
+ integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==
+ dependencies:
+ "@sindresorhus/is" "^0.14.0"
+ "@szmarczak/http-timer" "^1.1.2"
+ cacheable-request "^6.0.0"
+ decompress-response "^3.3.0"
duplexer3 "^0.1.4"
- get-stream "^3.0.0"
- is-redirect "^1.0.0"
- is-retry-allowed "^1.0.0"
- is-stream "^1.0.0"
- lowercase-keys "^1.0.0"
- safe-buffer "^5.0.1"
- timed-out "^4.0.0"
- unzip-response "^2.0.1"
- url-parse-lax "^1.0.0"
-
-graceful-fs@^4.1.11:
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
- integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
+ get-stream "^4.1.0"
+ lowercase-keys "^1.0.1"
+ mimic-response "^1.0.1"
+ p-cancelable "^1.0.0"
+ to-readable-stream "^1.0.0"
+ url-parse-lax "^3.0.0"
graceful-fs@^4.1.2:
version "4.1.15"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
-graphlib@^2.1.1, graphlib@^2.1.5:
- version "2.1.8"
- resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da"
- integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==
+grapheme-splitter@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
+ integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
+
+gunzip-maybe@^1.4.2:
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz#b913564ae3be0eda6f3de36464837a9cd94b98ac"
+ integrity sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==
dependencies:
- lodash "^4.17.15"
+ browserify-zlib "^0.1.4"
+ is-deflate "^1.0.0"
+ is-gzip "^1.0.0"
+ peek-stream "^1.1.0"
+ pumpify "^1.3.3"
+ through2 "^2.0.3"
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
-hosted-git-info@^2.7.1:
- version "2.8.5"
- resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c"
- integrity sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
-http-errors@1.7.3:
- version "1.7.3"
- resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
- integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
- dependencies:
- depd "~1.1.2"
- inherits "2.0.4"
- setprototypeof "1.1.1"
- statuses ">= 1.5.0 < 2"
- toidentifier "1.0.0"
+has-symbols@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
+ integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
-http-proxy-agent@^2.1.0:
+has-yarn@^2.1.0:
version "2.1.0"
- resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
- integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==
- dependencies:
- agent-base "4"
- debug "3.1.0"
+ resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77"
+ integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==
-https-proxy-agent@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz#b8c286433e87602311b01c8ea34413d856a4af81"
- integrity sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==
+has@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+ integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
dependencies:
- agent-base "^4.3.0"
- debug "^3.1.0"
+ function-bind "^1.1.1"
-iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4:
+hosted-git-info@^3.0.4, hosted-git-info@^3.0.7:
+ version "3.0.8"
+ resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.8.tgz#6e35d4cc87af2c5f816e4cb9ce350ba87a3f370d"
+ integrity sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==
+ dependencies:
+ lru-cache "^6.0.0"
+
+http-cache-semantics@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
+ integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
+
+http2-wrapper@^1.0.0-beta.4.5, http2-wrapper@^1.0.0-beta.5.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d"
+ integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==
+ dependencies:
+ quick-lru "^5.1.1"
+ resolve-alpn "^1.0.0"
+
+iconv-lite@^0.4.24, iconv-lite@^0.4.4:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
dependencies:
safer-buffer ">= 2.1.2 < 3"
+ieee754@^1.1.13:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
+ integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
+
+ignore@^5.1.4, ignore@^5.1.8:
+ version "5.1.8"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
+ integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
+
immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
@@ -858,6 +1517,11 @@
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
+indent-string@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
+ integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
+
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@@ -871,125 +1535,134 @@
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
-inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.1:
+inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
-ini@^1.3.0, ini@^1.3.4, ini@~1.3.0:
+ini@1.3.7:
+ version "1.3.7"
+ resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84"
+ integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==
+
+ini@~1.3.0:
version "1.3.5"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
-inquirer@^6.2.2:
- version "6.5.2"
- resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca"
- integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==
- dependencies:
- ansi-escapes "^3.2.0"
- chalk "^2.4.2"
- cli-cursor "^2.1.0"
- cli-width "^2.0.0"
- external-editor "^3.0.3"
- figures "^2.0.0"
- lodash "^4.17.12"
- mute-stream "0.0.7"
- run-async "^2.2.0"
- rxjs "^6.4.0"
- string-width "^2.1.0"
- strip-ansi "^5.1.0"
- through "^2.3.6"
+is-callable@^1.1.5:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e"
+ integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==
-invert-kv@^1.0.0:
+is-ci@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c"
+ integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==
+ dependencies:
+ ci-info "^2.0.0"
+
+is-deflate@^1.0.0:
version "1.0.0"
- resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
- integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY=
+ resolved "https://registry.yarnpkg.com/is-deflate/-/is-deflate-1.0.0.tgz#c862901c3c161fb09dac7cdc7e784f80e98f2f14"
+ integrity sha1-yGKQHDwWH7CdrHzcfnhPgOmPLxQ=
-ip@1.1.5, ip@^1.1.5:
- version "1.1.5"
- resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
- integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=
+is-docker@^2.0.0:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
+ integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
-is-ci@^1.0.10:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c"
- integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==
- dependencies:
- ci-info "^1.5.0"
-
-is-fullwidth-code-point@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
- integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs=
- dependencies:
- number-is-nan "^1.0.0"
+is-extglob@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+ integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
is-fullwidth-code-point@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
-is-installed-globally@^0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80"
- integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=
- dependencies:
- global-dirs "^0.1.0"
- is-path-inside "^1.0.0"
+is-fullwidth-code-point@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
+ integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
-is-npm@^1.0.0:
+is-glob@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
+ integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
+ dependencies:
+ is-extglob "^2.1.1"
+
+is-gzip@^1.0.0:
version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4"
- integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ=
+ resolved "https://registry.yarnpkg.com/is-gzip/-/is-gzip-1.0.0.tgz#6ca8b07b99c77998025900e555ced8ed80879a83"
+ integrity sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM=
+
+is-installed-globally@^0.3.1:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141"
+ integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==
+ dependencies:
+ global-dirs "^2.0.1"
+ is-path-inside "^3.0.1"
+
+is-interactive@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
+ integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==
+
+is-npm@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d"
+ integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==
+
+is-number@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+ integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
is-obj@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982"
integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==
-is-path-inside@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036"
- integrity sha1-jvW33lBDej/cprToZe96pVy0gDY=
- dependencies:
- path-is-inside "^1.0.1"
+is-path-inside@^3.0.1:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
+ integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
-is-promise@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
- integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=
-
-is-redirect@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
- integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=
-
-is-retry-allowed@^1.0.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4"
- integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==
-
-is-ssh@^1.3.0:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.1.tgz#f349a8cadd24e65298037a522cf7520f2e81a0f3"
- integrity sha512-0eRIASHZt1E68/ixClI8bp2YK2wmBPVWEismTs6M+M099jKgrzl/3E976zIbImSIob48N2/XGe9y7ZiYdImSlg==
- dependencies:
- protocols "^1.1.0"
-
-is-stream@^1.0.0, is-stream@^1.1.0:
+is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
-is-wsl@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
- integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
+is-typedarray@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
+ integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
-isarray@0.0.1:
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
- integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
+is-unicode-supported@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
+ integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
+
+is-wsl@^2.1.1:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
+ integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
+ dependencies:
+ is-docker "^2.0.0"
+
+is-yarn-global@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
+ integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==
+
+is@^3.2.1:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/is/-/is-3.3.0.tgz#61cff6dd3c4193db94a3d62582072b44e5645d79"
+ integrity sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==
isarray@~1.0.0:
version "1.0.0"
@@ -1001,6 +1674,14 @@
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
+js-yaml@^3.10.0:
+ version "3.14.1"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
+ integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^4.0.0"
+
js-yaml@^3.13.1:
version "3.13.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
@@ -1009,37 +1690,72 @@
argparse "^1.0.7"
esprima "^4.0.0"
-jszip@^3.1.5:
- version "3.2.2"
- resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.2.2.tgz#b143816df7e106a9597a94c77493385adca5bd1d"
- integrity sha512-NmKajvAFQpbg3taXQXr/ccS2wcucR1AZ+NtyWp2Nq7HHVsXhcJFR8p0Baf32C2yVvBylFWVeKf+WI2AnvlPhpA==
+json-buffer@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
+ integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=
+
+json-buffer@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
+ integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
+
+json-file-plus@^3.3.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/json-file-plus/-/json-file-plus-3.3.1.tgz#f4363806b82819ff8803d83d539d6a9edd2a5258"
+ integrity sha512-wo0q1UuiV5NsDPQDup1Km8IwEeqe+olr8tkWxeJq9Bjtcp7DZ0l+yrg28fSC3DEtrE311mhTZ54QGS6oiqnZEA==
+ dependencies:
+ is "^3.2.1"
+ node.extend "^2.0.0"
+ object.assign "^4.1.0"
+ promiseback "^2.0.2"
+ safer-buffer "^2.0.2"
+
+json-stringify-safe@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
+ integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
+
+jszip@3.4.0:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.4.0.tgz#1a69421fa5f0bb9bc222a46bca88182fba075350"
+ integrity sha512-gZAOYuPl4EhPTXT0GjhI3o+ZAz3su6EhLrKUoAivcKqyqC7laS5JEv4XWZND9BgcDcF83vI85yGbDmDR6UhrIg==
dependencies:
lie "~3.3.0"
pako "~1.0.2"
readable-stream "~2.3.6"
set-immediate-shim "~1.0.1"
-latest-version@^3.1.0:
+jszip@^3.2.2:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.6.0.tgz#839b72812e3f97819cc13ac4134ffced95dd6af9"
+ integrity sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ==
+ dependencies:
+ lie "~3.3.0"
+ pako "~1.0.2"
+ readable-stream "~2.3.6"
+ set-immediate-shim "~1.0.1"
+
+keyv@^3.0.0:
version "3.1.0"
- resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15"
- integrity sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=
+ resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9"
+ integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==
dependencies:
- package-json "^4.0.0"
+ json-buffer "3.0.0"
-lcid@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
- integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=
+keyv@^4.0.0:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254"
+ integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==
dependencies:
- invert-kv "^1.0.0"
+ json-buffer "3.0.1"
-levn@~0.3.0:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
- integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=
+latest-version@^5.0.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face"
+ integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==
dependencies:
- prelude-ls "~1.1.2"
- type-check "~0.3.2"
+ package-json "^6.3.0"
lie@~3.3.0:
version "3.3.0"
@@ -1058,6 +1774,16 @@
resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2"
integrity sha1-uo31+4QesKPoBEIysOJjqNxqKKI=
+lodash.camelcase@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
+ integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
+
+lodash.chunk@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/lodash.chunk/-/lodash.chunk-4.2.0.tgz#66e5ce1f76ed27b4303d8c6512e8d1216e8106bc"
+ integrity sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw=
+
lodash.clone@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6"
@@ -1068,32 +1794,235 @@
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
+lodash.constant@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/lodash.constant/-/lodash.constant-3.0.0.tgz#bfe05cce7e515b3128925d6362138420bd624910"
+ integrity sha1-v+Bczn5RWzEokl1jYhOEIL1iSRA=
+
+lodash.defaults@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
+ integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=
+
+lodash.endswith@^4.2.1:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/lodash.endswith/-/lodash.endswith-4.2.1.tgz#fed59ac1738ed3e236edd7064ec456448b37bc09"
+ integrity sha1-/tWawXOO0+I27dcGTsRWRIs3vAk=
+
+lodash.filter@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace"
+ integrity sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=
+
+lodash.find@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1"
+ integrity sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E=
+
+lodash.findindex@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/lodash.findindex/-/lodash.findindex-4.6.0.tgz#a3245dee61fb9b6e0624b535125624bb69c11106"
+ integrity sha1-oyRd7mH7m24GJLU1ElYku2nBEQY=
+
+lodash.findkey@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/lodash.findkey/-/lodash.findkey-4.6.0.tgz#83058e903b51cbb759d09ccf546dea3ea39c4718"
+ integrity sha1-gwWOkDtRy7dZ0JzPVG3qPqOcRxg=
+
+lodash.flatmap@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz#ef8cbf408f6e48268663345305c6acc0b778702e"
+ integrity sha1-74y/QI9uSCaGYzRTBcaswLd4cC4=
+
lodash.flatten@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
+lodash.flattendeep@^4.4.0:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
+ integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=
+
+lodash.foreach@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53"
+ integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=
+
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
+lodash.groupby@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1"
+ integrity sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E=
+
+lodash.has@^4.5.2:
+ version "4.5.2"
+ resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862"
+ integrity sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=
+
+lodash.invert@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/lodash.invert/-/lodash.invert-4.3.0.tgz#8ffe20d4b616f56bea8f1aa0c6ebd80dcf742aee"
+ integrity sha1-j/4g1LYW9WvqjxqgxuvYDc90Ku4=
+
+lodash.isboolean@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
+ integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
+
+lodash.isempty@^4.4.0:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e"
+ integrity sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=
+
+lodash.isequal@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
+ integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
+
+lodash.isfunction@^3.0.9:
+ version "3.0.9"
+ resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051"
+ integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==
+
+lodash.isnumber@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
+ integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=
+
+lodash.isobject@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d"
+ integrity sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=
+
+lodash.isplainobject@^4.0.6:
+ version "4.0.6"
+ resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
+ integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
+
+lodash.isstring@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
+ integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
+
+lodash.isundefined@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz#23ef3d9535565203a66cefd5b830f848911afb48"
+ integrity sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g=
+
+lodash.keys@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-4.2.0.tgz#a08602ac12e4fb83f91fc1fb7a360a4d9ba35205"
+ integrity sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU=
+
+lodash.last@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/lodash.last/-/lodash.last-3.0.0.tgz#242f663112dd4c6e63728c60a3c909d1bdadbd4c"
+ integrity sha1-JC9mMRLdTG5jcoxgo8kJ0b2tvUw=
+
+lodash.map@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3"
+ integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=
+
+lodash.merge@^4.6.2:
+ version "4.6.2"
+ resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
+ integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
+
+lodash.omit@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60"
+ integrity sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA=
+
+lodash.orderby@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3"
+ integrity sha1-5pfwTOXXhSL1TZM4syuBozk+TrM=
+
+lodash.reduce@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b"
+ integrity sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=
+
lodash.set@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23"
integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=
-lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.7.14:
+lodash.size@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/lodash.size/-/lodash.size-4.2.0.tgz#71fe75ed3eabdb2bcb73a1b0b4f51c392ee27b86"
+ integrity sha1-cf517T6r2yvLc6GwtPUcOS7ie4Y=
+
+lodash.sortby@^4.7.0:
+ version "4.7.0"
+ resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
+ integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
+
+lodash.sum@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/lodash.sum/-/lodash.sum-4.0.2.tgz#ad90e397965d803d4f1ff7aa5b2d0197f3b4637b"
+ integrity sha1-rZDjl5ZdgD1PH/eqWy0Bl/O0Y3s=
+
+lodash.topairs@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/lodash.topairs/-/lodash.topairs-4.3.0.tgz#3b6deaa37d60fb116713c46c5f17ea190ec48d64"
+ integrity sha1-O23qo31g+xFnE8RsXxfqGQ7EjWQ=
+
+lodash.transform@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/lodash.transform/-/lodash.transform-4.6.0.tgz#12306422f63324aed8483d3f38332b5f670547a0"
+ integrity sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A=
+
+lodash.union@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88"
+ integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=
+
+lodash.uniq@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
+ integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
+
+lodash.upperfirst@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce"
+ integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984=
+
+lodash.values@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347"
+ integrity sha1-o6bCsOvsxcLLocF+bmIP6BtT00c=
+
+lodash@^4.17.15:
version "4.17.19"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
-lowercase-keys@^1.0.0:
+log-symbols@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
+ integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==
+ dependencies:
+ chalk "^4.1.0"
+ is-unicode-supported "^0.1.0"
+
+lowercase-keys@^1.0.0, lowercase-keys@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
-lru-cache@^4.0.0, lru-cache@^4.0.1:
+lowercase-keys@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
+ integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
+
+lru-cache@^4.0.0:
version "4.1.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
@@ -1108,22 +2037,67 @@
dependencies:
yallist "^3.0.2"
+lru-cache@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
+ integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
+ dependencies:
+ yallist "^4.0.0"
+
macos-release@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f"
integrity sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA==
-make-dir@^1.0.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
- integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
+make-dir@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
+ integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
dependencies:
- pify "^3.0.0"
+ semver "^6.0.0"
-mimic-fn@^1.0.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
- integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
+matcher@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca"
+ integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==
+ dependencies:
+ escape-string-regexp "^4.0.0"
+
+merge2@^1.3.0:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
+ integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
+
+micromatch@4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"
+ integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==
+ dependencies:
+ braces "^3.0.1"
+ picomatch "^2.0.5"
+
+micromatch@^4.0.2:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9"
+ integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==
+ dependencies:
+ braces "^3.0.1"
+ picomatch "^2.2.3"
+
+mimic-fn@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
+ integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
+
+mimic-response@^1.0.0, mimic-response@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
+ integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
+
+mimic-response@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
+ integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
minimatch@^3.0.4:
version "3.0.4"
@@ -1132,59 +2106,79 @@
dependencies:
brace-expansion "^1.1.7"
-minimist@^1.2.0:
+minimist@^1.2.0, minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
-ms@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
- integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
+minipass@^3.0.0:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd"
+ integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==
+ dependencies:
+ yallist "^4.0.0"
+
+minizlib@^2.1.1:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
+ integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
+ dependencies:
+ minipass "^3.0.0"
+ yallist "^4.0.0"
+
+mkdirp@^0.5.1:
+ version "0.5.5"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
+ integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
+ dependencies:
+ minimist "^1.2.5"
+
+mkdirp@^1.0.3, mkdirp@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
+ integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
+
+ms@2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+ integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
ms@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
-mute-stream@0.0.7:
- version "0.0.7"
- resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
- integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
+mute-stream@0.0.8:
+ version "0.0.8"
+ resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
+ integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
-nconf@^0.10.0:
- version "0.10.0"
- resolved "https://registry.yarnpkg.com/nconf/-/nconf-0.10.0.tgz#da1285ee95d0a922ca6cee75adcf861f48205ad2"
- integrity sha512-fKiXMQrpP7CYWJQzKkPPx9hPgmq+YLDyxcG9N8RpiE9FoCkCbzD0NyW0YhE3xn3Aupe7nnDeIx4PFzYehpHT9Q==
- dependencies:
- async "^1.4.0"
- ini "^1.3.0"
- secure-keys "^1.0.0"
- yargs "^3.19.0"
-
-needle@^2.2.4, needle@^2.4.0:
- version "2.4.0"
- resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c"
- integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==
+needle@2.6.0, needle@^2.3.3, needle@^2.5.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/needle/-/needle-2.6.0.tgz#24dbb55f2509e2324b4a99d61f413982013ccdbe"
+ integrity sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg==
dependencies:
debug "^3.2.6"
iconv-lite "^0.4.4"
sax "^1.2.4"
-netmask@^1.0.6:
- version "1.0.6"
- resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35"
- integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=
-
nice-try@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
-normalize-url@^3.3.0:
- version "3.3.0"
- resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
- integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==
+node.extend@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/node.extend/-/node.extend-2.0.2.tgz#b4404525494acc99740f3703c496b7d5182cc6cc"
+ integrity sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ==
+ dependencies:
+ has "^1.0.3"
+ is "^3.2.1"
+
+normalize-url@^4.1.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129"
+ integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==
npm-run-path@^2.0.0:
version "2.0.2"
@@ -1193,15 +2187,25 @@
dependencies:
path-key "^2.0.0"
-number-is-nan@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
- integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
+object-hash@^2.0.3:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.1.1.tgz#9447d0279b4fcf80cff3259bf66a1dc73afabe09"
+ integrity sha512-VOJmgmS+7wvXf8CjbQmimtCnEx3IAoLxI3fp2fbWehxrWBcAQFbk+vcwb6vzR0VZv/eNCJ/27j151ZTwqW/JeQ==
-object-hash@^1.3.1:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df"
- integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==
+object-keys@^1.0.12, object-keys@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
+ integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
+
+object.assign@^4.1.0:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
+ integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==
+ dependencies:
+ call-bind "^1.0.0"
+ define-properties "^1.1.3"
+ has-symbols "^1.0.1"
+ object-keys "^1.1.1"
once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
@@ -1210,43 +2214,46 @@
dependencies:
wrappy "1"
-onetime@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
- integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=
+once@~1.3.0:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20"
+ integrity sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=
dependencies:
- mimic-fn "^1.0.0"
+ wrappy "1"
+
+onetime@^5.1.0:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
+ integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
+ dependencies:
+ mimic-fn "^2.1.0"
onscan.js@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/onscan.js/-/onscan.js-1.5.2.tgz#14ed636e5f4c3f0a78bacbf9a505dad3140ee341"
integrity sha512-9oGYy2gXYRjvXO9GYqqVca0VuCTAmWhbmX3egBSBP13rXiMNb+dKPJzKFEeECGqPBpf0m40Zoo+GUQ7eCackdw==
-opn@^5.5.0:
- version "5.5.0"
- resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc"
- integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==
+open@^7.0.3:
+ version "7.4.2"
+ resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321"
+ integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==
dependencies:
- is-wsl "^1.1.0"
+ is-docker "^2.0.0"
+ is-wsl "^2.1.1"
-optionator@^0.8.1:
- version "0.8.3"
- resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
- integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==
+ora@5.3.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/ora/-/ora-5.3.0.tgz#fb832899d3a1372fe71c8b2c534bbfe74961bb6f"
+ integrity sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==
dependencies:
- deep-is "~0.1.3"
- fast-levenshtein "~2.0.6"
- levn "~0.3.0"
- prelude-ls "~1.1.2"
- type-check "~0.3.2"
- word-wrap "~1.2.3"
-
-os-locale@^1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9"
- integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=
- dependencies:
- lcid "^1.0.0"
+ bl "^4.0.3"
+ chalk "^4.1.0"
+ cli-cursor "^3.1.0"
+ cli-spinners "^2.5.0"
+ is-interactive "^1.0.0"
+ log-symbols "^4.0.0"
+ strip-ansi "^6.0.0"
+ wcwidth "^1.0.1"
os-name@^3.0.0:
version "3.1.0"
@@ -1261,134 +2268,164 @@
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
+p-cancelable@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
+ integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==
+
+p-cancelable@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.0.tgz#4d51c3b91f483d02a0d300765321fca393d758dd"
+ integrity sha512-HAZyB3ZodPo+BDpb4/Iu7Jv4P6cSazBz9ZM0ChhEXp70scx834aWCEjQRwgt41UzzejUAPdbqqONfRWTPYrPAQ==
+
p-finally@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
+p-limit@^2.2.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
+ integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
+ dependencies:
+ p-try "^2.0.0"
+
p-map@2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==
-pac-proxy-agent@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-3.0.1.tgz#115b1e58f92576cac2eba718593ca7b0e37de2ad"
- integrity sha512-44DUg21G/liUZ48dJpUSjZnFfZro/0K5JTyFYLBcmh9+T6Ooi4/i4efwUiEy0+4oQusCBqWdhv16XohIj1GqnQ==
+p-map@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
+ integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==
dependencies:
- agent-base "^4.2.0"
- debug "^4.1.1"
- get-uri "^2.0.0"
- http-proxy-agent "^2.1.0"
- https-proxy-agent "^3.0.0"
- pac-resolver "^3.0.0"
- raw-body "^2.2.0"
- socks-proxy-agent "^4.0.1"
+ aggregate-error "^3.0.0"
-pac-resolver@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-3.0.0.tgz#6aea30787db0a891704deb7800a722a7615a6f26"
- integrity sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA==
- dependencies:
- co "^4.6.0"
- degenerator "^1.0.4"
- ip "^1.1.5"
- netmask "^1.0.6"
- thunkify "^2.1.2"
+p-try@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
+ integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
-package-json@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed"
- integrity sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=
+package-json@^6.3.0:
+ version "6.5.0"
+ resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0"
+ integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==
dependencies:
- got "^6.7.1"
- registry-auth-token "^3.0.1"
- registry-url "^3.0.3"
- semver "^5.1.0"
+ got "^9.6.0"
+ registry-auth-token "^4.0.0"
+ registry-url "^5.0.0"
+ semver "^6.2.0"
+
+pako@~0.2.0:
+ version "0.2.9"
+ resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
+ integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=
pako@~1.0.2:
version "1.0.11"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
-parse-path@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-4.0.1.tgz#0ec769704949778cb3b8eda5e994c32073a1adff"
- integrity sha512-d7yhga0Oc+PwNXDvQ0Jv1BuWkLVPXcAoQ/WREgd6vNNoKYaW52KI+RdOFjI63wjkmps9yUE8VS4veP+AgpQ/hA==
+parse-link-header@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/parse-link-header/-/parse-link-header-1.0.1.tgz#bedfe0d2118aeb84be75e7b025419ec8a61140a7"
+ integrity sha1-vt/g0hGK64S+deewJUGeyKYRQKc=
dependencies:
- is-ssh "^1.3.0"
- protocols "^1.4.0"
-
-parse-url@^5.0.0:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-5.0.1.tgz#99c4084fc11be14141efa41b3d117a96fcb9527f"
- integrity sha512-flNUPP27r3vJpROi0/R3/2efgKkyXqnXwyP1KQ2U0SfFRgdizOdWfvrrvJg1LuOoxs7GQhmxJlq23IpQ/BkByg==
- dependencies:
- is-ssh "^1.3.0"
- normalize-url "^3.3.0"
- parse-path "^4.0.0"
- protocols "^1.4.0"
+ xtend "~4.0.1"
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
-path-is-inside@^1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
- integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
-
path-key@^2.0.0, path-key@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
-pify@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
- integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
+path-key@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
-prelude-ls@~1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
- integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
+path-type@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
+ integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
-prepend-http@^1.0.1:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
- integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
+peek-stream@^1.1.0:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/peek-stream/-/peek-stream-1.1.3.tgz#3b35d84b7ccbbd262fff31dc10da56856ead6d67"
+ integrity sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==
+ dependencies:
+ buffer-from "^1.0.0"
+ duplexify "^3.5.0"
+ through2 "^2.0.3"
+
+picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.3:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.3.tgz#465547f359ccc206d3c48e46a1bcb89bf7ee619d"
+ integrity sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==
+
+pluralize@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
+ integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==
+
+prepend-http@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
+ integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
+
+pretty-bytes@^5.1.0:
+ version "5.6.0"
+ resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
+ integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==
process-nextick-args@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
-"promise@>=3.2 <8":
+progress@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
+ integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
+
+promise-deferred@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/promise-deferred/-/promise-deferred-2.0.3.tgz#b99c9588820798501862a593d49cece51d06fd7f"
+ integrity sha512-n10XaoznCzLfyPFOlEE8iurezHpxrYzyjgq/1eW9Wk1gJwur/N7BdBmjJYJpqMeMcXK4wEbzo2EvZQcqjYcKUQ==
+ dependencies:
+ promise "^7.3.1"
+
+promise-fs@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/promise-fs/-/promise-fs-2.1.1.tgz#0b725a592c165ff16157d1f13640ba390637e557"
+ integrity sha512-43p7e4QzAQ3w6eyN0+gbBL7jXiZFWLWYITg9wIObqkBySu/a5K1EDcQ/S6UyB/bmiZWDA4NjTbcopKLTaKcGSw==
+ dependencies:
+ "@octetstream/promisify" "2.0.2"
+
+promise-queue@^2.2.5:
+ version "2.2.5"
+ resolved "https://registry.yarnpkg.com/promise-queue/-/promise-queue-2.2.5.tgz#2f6f5f7c0f6d08109e967659c79b88a9ed5e93b4"
+ integrity sha1-L29ffA9tCBCelnZZx5uIqe1ek7Q=
+
+"promise@>=3.2 <8", promise@^7.3.1:
version "7.3.1"
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==
dependencies:
asap "~2.0.3"
-protocols@^1.1.0, protocols@^1.4.0:
- version "1.4.7"
- resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.7.tgz#95f788a4f0e979b291ffefcf5636ad113d037d32"
- integrity sha512-Fx65lf9/YDn3hUX08XUc0J8rSux36rEsyiv21ZGUC1mOyeM3lTRpZLcrm8aAolzS4itwVfm7TAPyxC2E5zd6xg==
-
-proxy-agent@^3.1.1:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-3.1.1.tgz#7e04e06bf36afa624a1540be247b47c970bd3014"
- integrity sha512-WudaR0eTsDx33O3EJE16PjBRZWcX8GqCEeERw1W3hZJgH/F2a46g7jty6UGty6NeJ4CKQy8ds2CJPMiyeqaTvw==
+promiseback@^2.0.2:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/promiseback/-/promiseback-2.0.3.tgz#bd468d86930e8cd44bfc3292de9a6fbafb6378e6"
+ integrity sha512-VZXdCwS0ppVNTIRfNsCvVwJAaP2b+pxQF7lM8DMWfmpNWyTxB6O5YNbzs+8z0ki/KIBHKHk308NTIl4kJUem3w==
dependencies:
- agent-base "^4.2.0"
- debug "4"
- http-proxy-agent "^2.1.0"
- https-proxy-agent "^3.0.0"
- lru-cache "^5.1.1"
- pac-proxy-agent "^3.0.1"
- proxy-from-env "^1.0.0"
- socks-proxy-agent "^4.0.1"
+ is-callable "^1.1.5"
+ promise-deferred "^2.0.3"
proxy-from-env@^1.0.0:
version "1.0.0"
@@ -1400,6 +2437,14 @@
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
+pump@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
+ integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
pump@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
@@ -1408,17 +2453,40 @@
end-of-stream "^1.1.0"
once "^1.3.1"
-raw-body@^2.2.0:
- version "2.4.1"
- resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c"
- integrity sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==
+pumpify@^1.3.3:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce"
+ integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==
dependencies:
- bytes "3.1.0"
- http-errors "1.7.3"
- iconv-lite "0.4.24"
- unpipe "1.0.0"
+ duplexify "^3.6.0"
+ inherits "^2.0.3"
+ pump "^2.0.0"
-rc@^1.0.1, rc@^1.1.6:
+pupa@^2.0.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62"
+ integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==
+ dependencies:
+ escape-goat "^2.0.0"
+
+queue-microtask@^1.2.2:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
+ integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
+
+queue@^6.0.1:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65"
+ integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==
+ dependencies:
+ inherits "~2.0.3"
+
+quick-lru@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
+ integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
+
+rc@^1.2.8:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
@@ -1428,17 +2496,7 @@
minimist "^1.2.0"
strip-json-comments "~2.0.1"
-readable-stream@1.1.x:
- version "1.1.14"
- resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
- integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
- dependencies:
- core-util-is "~1.0.0"
- inherits "~2.0.1"
- isarray "0.0.1"
- string_decoder "~0.10.x"
-
-readable-stream@2, readable-stream@~2.3.6:
+readable-stream@^2.0.0, readable-stream@~2.3.6:
version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
@@ -1451,7 +2509,7 @@
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
-readable-stream@^3.0.1, readable-stream@^3.1.1:
+readable-stream@^3.0.1, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
@@ -1460,29 +2518,52 @@
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
-registry-auth-token@^3.0.1:
- version "3.4.0"
- resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e"
- integrity sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==
+registry-auth-token@^4.0.0:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250"
+ integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==
dependencies:
- rc "^1.1.6"
- safe-buffer "^5.0.1"
+ rc "^1.2.8"
-registry-url@^3.0.3:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942"
- integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI=
+registry-url@^5.0.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009"
+ integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==
dependencies:
- rc "^1.0.1"
+ rc "^1.2.8"
-restore-cursor@^2.0.0:
+resolve-alpn@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.1.2.tgz#30b60cfbb0c0b8dc897940fe13fe255afcdd4d28"
+ integrity sha512-8OyfzhAtA32LVUsJSke3auIyINcwdh5l3cvYKdKO0nvsYSKuiLfTM5i78PJswFPT8y6cPW+L1v6/hE95chcpDA==
+
+responselike@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
+ integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=
+ dependencies:
+ lowercase-keys "^1.0.0"
+
+responselike@^2.0.0:
version "2.0.0"
- resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
- integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368=
+ resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723"
+ integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==
dependencies:
- onetime "^2.0.0"
+ lowercase-keys "^2.0.0"
+
+restore-cursor@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
+ integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==
+ dependencies:
+ onetime "^5.1.0"
signal-exit "^3.0.2"
+reusify@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
+ integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
+
rimraf@^2.6.3:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
@@ -1490,21 +2571,45 @@
dependencies:
glob "^7.1.3"
-run-async@^2.2.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
- integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA=
+rimraf@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
+ integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
dependencies:
- is-promise "^2.1.0"
+ glob "^7.1.3"
-rxjs@^6.4.0:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c"
- integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==
+roarr@^2.15.3:
+ version "2.15.4"
+ resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd"
+ integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==
+ dependencies:
+ boolean "^3.0.1"
+ detect-node "^2.0.4"
+ globalthis "^1.0.1"
+ json-stringify-safe "^5.0.1"
+ semver-compare "^1.0.0"
+ sprintf-js "^1.1.2"
+
+run-async@^2.4.0:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
+ integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==
+
+run-parallel@^1.1.9:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
+ integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
+ dependencies:
+ queue-microtask "^1.2.2"
+
+rxjs@^6.6.0:
+ version "6.6.7"
+ resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
+ integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==
dependencies:
tslib "^1.9.0"
-safe-buffer@^5.0.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
@@ -1514,7 +2619,7 @@
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
-"safer-buffer@>= 2.1.2 < 3":
+"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@@ -1524,43 +2629,52 @@
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
-secure-keys@^1.0.0:
+semver-compare@^1.0.0:
version "1.0.0"
- resolved "https://registry.yarnpkg.com/secure-keys/-/secure-keys-1.0.0.tgz#f0c82d98a3b139a8776a8808050b824431087fca"
- integrity sha1-8MgtmKOxOah3aogIBQuCRDEIf8o=
+ resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
+ integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
-semver-diff@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
- integrity sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=
+semver-diff@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b"
+ integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==
dependencies:
- semver "^5.0.3"
-
-semver@^5.0.3, semver@^5.1.0, semver@^5.5.1:
- version "5.7.1"
- resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
- integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+ semver "^6.3.0"
semver@^5.5.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
-semver@^6.0.0, semver@^6.1.0, semver@^6.1.2:
+semver@^5.5.1:
+ version "5.7.1"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+ integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+
+semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+semver@^7.0.0, semver@^7.1.2, semver@^7.3.2, semver@^7.3.4:
+ version "7.3.5"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
+ integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
+ dependencies:
+ lru-cache "^6.0.0"
+
+serialize-error@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18"
+ integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==
+ dependencies:
+ type-fest "^0.13.1"
+
set-immediate-shim@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=
-setprototypeof@1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
- integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
-
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@@ -1568,167 +2682,244 @@
dependencies:
shebang-regex "^1.0.0"
+shebang-command@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
+ integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+ dependencies:
+ shebang-regex "^3.0.0"
+
shebang-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
+shebang-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
+ integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
-smart-buffer@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba"
- integrity sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==
+slash@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
+ integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
-snyk-config@^2.2.1:
- version "2.2.3"
- resolved "https://registry.yarnpkg.com/snyk-config/-/snyk-config-2.2.3.tgz#8e09bb98602ad044954d30a9fc1695ab5b6042fa"
- integrity sha512-9NjxHVMd1U1LFw66Lya4LXgrsFUiuRiL4opxfTFo0LmMNzUoU5Bk/p0zDdg3FE5Wg61r4fP2D8w+QTl6M8CGiw==
+snyk-config@4.0.0, snyk-config@^4.0.0-rc.2:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/snyk-config/-/snyk-config-4.0.0.tgz#21d459f19087991246cc07a7ffb4501dce6f4159"
+ integrity sha512-E6jNe0oUjjzVASWBOAc/mA23DhbzABDF9MI6UZvl0gylh2NSXSXw2/LjlqMNOKL2c1qkbSkzLOdIX5XACoLCAQ==
dependencies:
- debug "^3.1.0"
- lodash "^4.17.15"
- nconf "^0.10.0"
-
-snyk-docker-plugin@1.38.0:
- version "1.38.0"
- resolved "https://registry.yarnpkg.com/snyk-docker-plugin/-/snyk-docker-plugin-1.38.0.tgz#afe0ac316e461b200bcd0063295a3f8bd3655e93"
- integrity sha512-43HbJj6QatuL2BNG+Uq2Taa73wdfSQSID8FJWW4q5/LYgd9D+RtdiE4lAMwxqYYbvThU9uuza4epuF/B1CAlYw==
- dependencies:
+ async "^3.2.0"
debug "^4.1.1"
- dockerfile-ast "0.0.18"
- event-loop-spinner "^1.1.0"
- semver "^6.1.0"
- tar-stream "^2.1.0"
- tslib "^1"
+ lodash.merge "^4.6.2"
+ minimist "^1.2.5"
-snyk-go-parser@1.3.1:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/snyk-go-parser/-/snyk-go-parser-1.3.1.tgz#427387507578baf008a3e73828e0e53ed8c796f3"
- integrity sha512-jrFRfIk6yGHFeipGD66WV9ei/A/w/lIiGqI80w1ndMbg6D6M5pVNbK7ngDTmo4GdHrZDYqx/VBGBsUm2bol3Rg==
+snyk-cpp-plugin@2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/snyk-cpp-plugin/-/snyk-cpp-plugin-2.2.1.tgz#55891511a43a6448e5a7c836a94f66f70fa705eb"
+ integrity sha512-NFwVLMCqKTocY66gcim0ukF6e31VRDJqDapg5sy3vCHqlD1OCNUXSK/aI4VQEEndDrsnFmQepsL5KpEU0dDRIQ==
dependencies:
- toml "^3.0.0"
- tslib "^1.9.3"
-
-snyk-go-plugin@1.11.1:
- version "1.11.1"
- resolved "https://registry.yarnpkg.com/snyk-go-plugin/-/snyk-go-plugin-1.11.1.tgz#cd7c73c42bd3cf5faa2a90a54cd7c6db926fea5d"
- integrity sha512-IsNi7TmpHoRHzONOWJTT8+VYozQJnaJpKgnYNQjzNm2JlV8bDGbdGQ1a8LcEoChxnJ8v8aMZy7GTiQyGGABtEQ==
- dependencies:
+ "@snyk/dep-graph" "^1.19.3"
+ chalk "^4.1.0"
debug "^4.1.1"
- graphlib "^2.1.1"
- snyk-go-parser "1.3.1"
- tmp "0.0.33"
- tslib "^1.10.0"
+ hosted-git-info "^3.0.7"
+ tslib "^2.0.0"
-snyk-gradle-plugin@3.2.4:
- version "3.2.4"
- resolved "https://registry.yarnpkg.com/snyk-gradle-plugin/-/snyk-gradle-plugin-3.2.4.tgz#c1ff1dfbbe3c1a254d0da54a91c3f59c1b5582ca"
- integrity sha512-XmS1gl7uZNHP9HP5RaPuRXW3VjkbdWe+EgSOlvmspztkubIOIainqc87k7rIJ6u3tLBhqsZK8b5ru0/E9Q69hQ==
+snyk-docker-plugin@4.19.3:
+ version "4.19.3"
+ resolved "https://registry.yarnpkg.com/snyk-docker-plugin/-/snyk-docker-plugin-4.19.3.tgz#14569f25c52a3fc71a20f80f5beac4ccdc326c11"
+ integrity sha512-5WkXyT7uY5NrTOvEqxeMqb6dDcskT3c/gbHUTOyPuvE6tMut+OOYK8RRXbwZFeLzpS8asq4e1R7U7syYG3VXwg==
dependencies:
- "@snyk/cli-interface" "2.3.0"
- "@types/debug" "^4.1.4"
+ "@snyk/dep-graph" "^1.21.0"
+ "@snyk/rpm-parser" "^2.0.0"
+ "@snyk/snyk-docker-pull" "3.2.3"
chalk "^2.4.2"
debug "^4.1.1"
- tmp "0.0.33"
- tslib "^1.9.3"
+ docker-modem "2.1.3"
+ dockerfile-ast "0.2.0"
+ elfy "^1.0.0"
+ event-loop-spinner "^2.0.0"
+ gunzip-maybe "^1.4.2"
+ mkdirp "^1.0.4"
+ semver "^7.3.4"
+ snyk-nodejs-lockfile-parser "1.30.2"
+ tar-stream "^2.1.0"
+ tmp "^0.2.1"
+ tslib "^1"
+ uuid "^8.2.0"
-snyk-module@1.9.1, snyk-module@^1.6.0, snyk-module@^1.9.1:
- version "1.9.1"
- resolved "https://registry.yarnpkg.com/snyk-module/-/snyk-module-1.9.1.tgz#b2a78f736600b0ab680f1703466ed7309c980804"
- integrity sha512-A+CCyBSa4IKok5uEhqT+hV/35RO6APFNLqk9DRRHg7xW2/j//nPX8wTSZUPF8QeRNEk/sX+6df7M1y6PBHGSHA==
+snyk-go-parser@1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/snyk-go-parser/-/snyk-go-parser-1.4.1.tgz#df16a5fbd7a517ee757268ef081abc33506c8857"
+ integrity sha512-StU3uHB85VMEkcgXta63M0Fgd+9cs5sMCjQXTBoYTdE4dxarPn7U67yCuwkRRdZdny1ZXtzfY8LKns9i0+dy9w==
dependencies:
- debug "^3.1.0"
- hosted-git-info "^2.7.1"
+ toml "^3.0.0"
+ tslib "^1.10.0"
-snyk-mvn-plugin@2.8.0:
- version "2.8.0"
- resolved "https://registry.yarnpkg.com/snyk-mvn-plugin/-/snyk-mvn-plugin-2.8.0.tgz#20c4201debd99928ade099fd426d13bd17b2cc85"
- integrity sha512-Jt6lsVOFOYj7rp0H2IWz/BZS9xxaO0jEFTAoafLCocJIWWuGhPpVocCqmh/hrYAdKY9gS4gVOViMJ3EvcC1r1Q==
- dependencies:
- "@snyk/cli-interface" "2.3.1"
- debug "^4.1.1"
- lodash "^4.17.15"
- needle "^2.4.0"
- tmp "^0.1.0"
- tslib "1.9.3"
-
-snyk-nodejs-lockfile-parser@1.17.0:
+snyk-go-plugin@1.17.0:
version "1.17.0"
- resolved "https://registry.yarnpkg.com/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.17.0.tgz#709e1d8c83faccae3bfdac5c10620dcedbf8c4ac"
- integrity sha512-i4GAYFj9TJLOQ8F+FbIJuJWdGymi8w/XcrEX0FzXk7DpYUCY3mWibyKhw8RasfYBx5vLwUzEvRMaQuc2EwlyfA==
+ resolved "https://registry.yarnpkg.com/snyk-go-plugin/-/snyk-go-plugin-1.17.0.tgz#56d0c92d7def29ba4c3c2030c5830093e3b0dd26"
+ integrity sha512-1jAYPRgMapO2BYL+HWsUq5gsAiDGmI0Pn7omc0lk24tcUOMhUB+1hb0u9WBMNzHvXBjevBkjOctjpnt2hMKN6Q==
dependencies:
- "@yarnpkg/lockfile" "^1.0.2"
- graphlib "^2.1.5"
- lodash "^4.17.14"
- p-map "2.1.0"
- source-map-support "^0.5.7"
- tslib "^1.9.3"
- uuid "^3.3.2"
+ "@snyk/dep-graph" "^1.23.1"
+ "@snyk/graphlib" "2.1.9-patch.3"
+ debug "^4.1.1"
+ snyk-go-parser "1.4.1"
+ tmp "0.2.1"
+ tslib "^1.10.0"
-snyk-nuget-plugin@1.16.0:
- version "1.16.0"
- resolved "https://registry.yarnpkg.com/snyk-nuget-plugin/-/snyk-nuget-plugin-1.16.0.tgz#241c6c8a417429c124c3ebf6db314a14eb8eed89"
- integrity sha512-OEusK3JKKpR4Yto5KwuqjQGgb9wAhmDqBWSQomWdtKQVFrzn5B6BMzOFikUzmeMTnUGGON7gurQBLXeZZLhRqg==
+snyk-gradle-plugin@3.14.2:
+ version "3.14.2"
+ resolved "https://registry.yarnpkg.com/snyk-gradle-plugin/-/snyk-gradle-plugin-3.14.2.tgz#898b051f679e681b6d859f0ca84a500ac028af7d"
+ integrity sha512-l/nivKDZz7e2wymrwP6g2WQD8qgaYeE22SnbZrfIpwGolif81U28A9FsRedwkxKyB/shrM0vGEoD3c3zI8NLBw==
dependencies:
- debug "^3.1.0"
- dotnet-deps-parser "4.9.0"
- jszip "^3.1.5"
- lodash "^4.17.14"
- snyk-paket-parser "1.5.0"
+ "@snyk/cli-interface" "2.11.0"
+ "@snyk/dep-graph" "^1.28.0"
+ "@snyk/java-call-graph-builder" "1.20.0"
+ "@types/debug" "^4.1.4"
+ chalk "^3.0.0"
+ debug "^4.1.1"
+ tmp "0.2.1"
+ tslib "^2.0.0"
+
+snyk-module@3.1.0, snyk-module@^3.0.0, snyk-module@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/snyk-module/-/snyk-module-3.1.0.tgz#3e088ff473ddf0d4e253a46ea6749d76d8e6e7ba"
+ integrity sha512-HHuOYEAACpUpkFgU8HT57mmxmonaJ4O3YADoSkVhnhkmJ+AowqZyJOau703dYHNrq2DvQ7qYw81H7yyxS1Nfjw==
+ dependencies:
+ debug "^4.1.1"
+ hosted-git-info "^3.0.4"
+
+snyk-mvn-plugin@2.25.3:
+ version "2.25.3"
+ resolved "https://registry.yarnpkg.com/snyk-mvn-plugin/-/snyk-mvn-plugin-2.25.3.tgz#fb7f6fa1d565b9f07c032e8b34e6308c310b2a27"
+ integrity sha512-JAxOThX51JDbgMMjp3gQDVi07G9VgTYSF06QC7f5LNA0zoXNr743e2rm78RGw5bqE3JRjZxEghiLHPPuvS5DDg==
+ dependencies:
+ "@snyk/cli-interface" "2.11.0"
+ "@snyk/dep-graph" "^1.23.1"
+ "@snyk/java-call-graph-builder" "1.19.1"
+ debug "^4.1.1"
+ glob "^7.1.6"
+ needle "^2.5.0"
+ tmp "^0.1.0"
+ tslib "1.11.1"
+
+snyk-nodejs-lockfile-parser@1.30.2:
+ version "1.30.2"
+ resolved "https://registry.yarnpkg.com/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.30.2.tgz#8dbb64c42382aeaf4488c36e48c1e48eb75a1584"
+ integrity sha512-wI3VXVYO/ok0uaQm5i+Koo4rKBNilYC/QRIQFlyGbZXf+WBdRcTBKVDfTy8uNfUhMRSGzd84lNclMnetU9Y+vw==
+ dependencies:
+ "@snyk/graphlib" "2.1.9-patch.3"
+ "@yarnpkg/lockfile" "^1.1.0"
+ event-loop-spinner "^2.0.0"
+ got "11.4.0"
+ lodash.clonedeep "^4.5.0"
+ lodash.flatmap "^4.5.0"
+ lodash.isempty "^4.4.0"
+ lodash.set "^4.3.2"
+ lodash.topairs "^4.3.0"
+ p-map "2.1.0"
+ snyk-config "^4.0.0-rc.2"
tslib "^1.9.3"
+ uuid "^8.3.0"
+ yaml "^1.9.2"
+
+snyk-nodejs-lockfile-parser@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.yarnpkg.com/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.32.0.tgz#2e25ea8622ef03ae7457a93ae70e156d6c46c2ef"
+ integrity sha512-FdYa/7NibnJPqBfobyw5jgI1/rd0LpMZf2W4WYYLRc2Hz7LZjKAByPjIX6qoA+lB9SC7yk5HYwWj2n4Fbg/DDw==
+ dependencies:
+ "@snyk/graphlib" "2.1.9-patch.3"
+ "@yarnpkg/core" "^2.4.0"
+ "@yarnpkg/lockfile" "^1.1.0"
+ event-loop-spinner "^2.0.0"
+ got "11.4.0"
+ lodash.clonedeep "^4.5.0"
+ lodash.flatmap "^4.5.0"
+ lodash.isempty "^4.4.0"
+ lodash.set "^4.3.2"
+ lodash.topairs "^4.3.0"
+ p-map "2.1.0"
+ snyk-config "^4.0.0-rc.2"
+ tslib "^1.9.3"
+ uuid "^8.3.0"
+ yaml "^1.9.2"
+
+snyk-nuget-plugin@1.21.1:
+ version "1.21.1"
+ resolved "https://registry.yarnpkg.com/snyk-nuget-plugin/-/snyk-nuget-plugin-1.21.1.tgz#a79bbc65456823a1148119873226afb0e4907ec8"
+ integrity sha512-nRtedIvrow5ODqOKkQWolKrxn8ZoNL3iNJGuW0jNhwv+/9K0XE1UORM5F1ENAsd+nzCSO/kiYAXCc5CNK8HWEw==
+ dependencies:
+ debug "^4.1.1"
+ dotnet-deps-parser "5.0.0"
+ jszip "3.4.0"
+ snyk-paket-parser "1.6.0"
+ tslib "^1.11.2"
xml2js "^0.4.17"
-snyk-paket-parser@1.5.0:
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/snyk-paket-parser/-/snyk-paket-parser-1.5.0.tgz#a0e96888d9d304b1ae6203a0369971575f099548"
- integrity sha512-1CYMPChJ9D9LBy3NLqHyv8TY7pR/LMISSr08LhfFw/FpfRZ+gTH8W6bbxCmybAYrOFNCqZkRprqOYDqZQFHipA==
+snyk-paket-parser@1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/snyk-paket-parser/-/snyk-paket-parser-1.6.0.tgz#f70c423b33d31484c8c4cae74bb7f5deb9bbc382"
+ integrity sha512-6htFynjBe/nakclEHUZ1A3j5Eu32/0pNve5Qm4MFn3YQmJgj7UcAO8hdyK3QfzEY29/kAv/rkJQg+SKshn+N9Q==
dependencies:
tslib "^1.9.3"
-snyk-php-plugin@1.7.0:
- version "1.7.0"
- resolved "https://registry.yarnpkg.com/snyk-php-plugin/-/snyk-php-plugin-1.7.0.tgz#cf1906ed8a10db134c803be3d6e4be0cbdc5fe33"
- integrity sha512-mDe90xkqSEVrpx1ZC7ItqCOc6fZCySbE+pHVI+dAPUmf1C1LSWZrZVmAVeo/Dw9sJzJfzmcdAFQl+jZP8/uV0A==
+snyk-php-plugin@1.9.2:
+ version "1.9.2"
+ resolved "https://registry.yarnpkg.com/snyk-php-plugin/-/snyk-php-plugin-1.9.2.tgz#282ef733060aab49da23e1fb2d2dd1af8f71f7cd"
+ integrity sha512-IQcdsQBqqXVRY5DatlI7ASy4flbhtU2V7cr4P2rK9rkFnVHO6LHcitwKXVZa9ocdOmpZDzk7U6iwHJkVFcR6OA==
dependencies:
- "@snyk/cli-interface" "2.2.0"
- "@snyk/composer-lockfile-parser" "1.2.0"
- tslib "1.9.3"
+ "@snyk/cli-interface" "^2.9.1"
+ "@snyk/composer-lockfile-parser" "^1.4.1"
+ tslib "1.11.1"
-snyk-policy@1.13.5:
- version "1.13.5"
- resolved "https://registry.yarnpkg.com/snyk-policy/-/snyk-policy-1.13.5.tgz#c5cf262f759879a65ab0810dd58d59c8ec7e9e47"
- integrity sha512-KI6GHt+Oj4fYKiCp7duhseUj5YhyL/zJOrrJg0u6r59Ux9w8gmkUYT92FHW27ihwuT6IPzdGNEuy06Yv2C9WaQ==
+snyk-poetry-lockfile-parser@^1.1.6:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/snyk-poetry-lockfile-parser/-/snyk-poetry-lockfile-parser-1.1.6.tgz#bab5a279c103cbcca8eb86ab87717b115592881e"
+ integrity sha512-MoekbWOZPj9umfukjk2bd2o3eRj0OyO+58sxq9crMtHmTlze4h0/Uj4+fb0JFPBOtBO3c2zwbA+dvFQmpKoOTA==
dependencies:
- debug "^3.1.0"
+ "@snyk/cli-interface" "^2.9.2"
+ "@snyk/dep-graph" "^1.23.0"
+ debug "^4.2.0"
+ toml "^3.0.0"
+ tslib "^2.0.0"
+
+snyk-policy@1.19.0:
+ version "1.19.0"
+ resolved "https://registry.yarnpkg.com/snyk-policy/-/snyk-policy-1.19.0.tgz#0cbc442d9503970fb3afea938f57d57993a914ad"
+ integrity sha512-XYjhOTRPFA7NfDUsH6uH1fbML2OgSFsqdUPbud7x01urNP9CHXgUgAD4NhKMi3dVQK+7IdYadWt0wrFWw4y+qg==
+ dependencies:
+ debug "^4.1.1"
email-validator "^2.0.4"
js-yaml "^3.13.1"
lodash.clonedeep "^4.5.0"
+ promise-fs "^2.1.1"
semver "^6.0.0"
- snyk-module "^1.9.1"
- snyk-resolve "^1.0.1"
- snyk-try-require "^1.3.1"
- then-fs "^2.0.0"
+ snyk-module "^3.0.0"
+ snyk-resolve "^1.1.0"
+ snyk-try-require "^2.0.0"
-snyk-python-plugin@1.17.0:
- version "1.17.0"
- resolved "https://registry.yarnpkg.com/snyk-python-plugin/-/snyk-python-plugin-1.17.0.tgz#9bc38ba3c799c3cbef7676a1081f52608690d254"
- integrity sha512-EKdVOUlvhiVpXA5TeW8vyxYVqbITAfT+2AbL2ZRiiUNLP5ae+WiNYaPy7aB5HAS9IKBKih+IH8Ag65Xu1IYSYA==
+snyk-python-plugin@1.19.8:
+ version "1.19.8"
+ resolved "https://registry.yarnpkg.com/snyk-python-plugin/-/snyk-python-plugin-1.19.8.tgz#9e4dfa8ed7e16ef2752f934b786d2e033de62ce0"
+ integrity sha512-LMKVnv0J4X/qHMoKB17hMND0abWtm9wdgI4xVzrOcf2Vtzs3J87trRhwLxQA2lMoBW3gcjtTeBUvNKaxikSVeQ==
dependencies:
"@snyk/cli-interface" "^2.0.3"
+ snyk-poetry-lockfile-parser "^1.1.6"
tmp "0.0.33"
-snyk-resolve-deps@4.4.0:
- version "4.4.0"
- resolved "https://registry.yarnpkg.com/snyk-resolve-deps/-/snyk-resolve-deps-4.4.0.tgz#ef20fb578a4c920cc262fb73dd292ff21215f52d"
- integrity sha512-aFPtN8WLqIk4E1ulMyzvV5reY1Iksz+3oPnUVib1jKdyTHymmOIYF7z8QZ4UUr52UsgmrD9EA/dq7jpytwFoOQ==
+snyk-resolve-deps@4.7.2:
+ version "4.7.2"
+ resolved "https://registry.yarnpkg.com/snyk-resolve-deps/-/snyk-resolve-deps-4.7.2.tgz#11e7051110dadd8756819594bb30e6b88777a8b4"
+ integrity sha512-Bmtr7QdRL2b3Js+mPDmvXbkprOpzO8aUFXqR0nJKAOlUVQqZ84yiuT0n/mssEiJJ0vP+k0kZvTeiTwgio4KZRg==
dependencies:
- "@types/node" "^6.14.4"
- "@types/semver" "^5.5.0"
ansicolors "^0.3.2"
- debug "^3.2.5"
+ debug "^4.1.1"
lodash.assign "^4.2.0"
lodash.assignin "^4.2.0"
lodash.clone "^4.5.0"
@@ -1737,13 +2928,21 @@
lodash.set "^4.3.2"
lru-cache "^4.0.0"
semver "^5.5.1"
- snyk-module "^1.6.0"
+ snyk-module "^3.1.0"
snyk-resolve "^1.0.0"
snyk-tree "^1.0.0"
snyk-try-require "^1.1.1"
then-fs "^2.0.0"
-snyk-resolve@1.0.1, snyk-resolve@^1.0.0, snyk-resolve@^1.0.1:
+snyk-resolve@1.1.0, snyk-resolve@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/snyk-resolve/-/snyk-resolve-1.1.0.tgz#52740cb01ba477851086855f9857b3a44296ee0e"
+ integrity sha512-OZMF8I8TOu0S58Z/OS9mr8jkEzGAPByCsAkrWlcmZgPaE0RsxVKVIFPhbMNy/JlYswgGDYYIEsNw+e0j1FnTrw==
+ dependencies:
+ debug "^4.1.1"
+ promise-fs "^2.1.1"
+
+snyk-resolve@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/snyk-resolve/-/snyk-resolve-1.0.1.tgz#eaa4a275cf7e2b579f18da5b188fe601b8eed9ab"
integrity sha512-7+i+LLhtBo1Pkth01xv+RYJU8a67zmJ8WFFPvSxyCjdlKIcsps4hPQFebhz+0gC5rMemlaeIV6cqwqUf9PEDpw==
@@ -1769,7 +2968,7 @@
dependencies:
archy "^1.0.0"
-snyk-try-require@1.3.1, snyk-try-require@^1.1.1, snyk-try-require@^1.3.1:
+snyk-try-require@1.3.1, snyk-try-require@^1.1.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/snyk-try-require/-/snyk-try-require-1.3.1.tgz#6e026f92e64af7fcccea1ee53d524841e418a212"
integrity sha1-bgJvkuZK9/zM6h7lPVJIQeQYohI=
@@ -1779,74 +2978,90 @@
lru-cache "^4.0.0"
then-fs "^2.0.0"
-snyk@^1.290.1:
- version "1.290.2"
- resolved "https://registry.yarnpkg.com/snyk/-/snyk-1.290.2.tgz#a5e36e735a8083464263abdb266b6c9b3d46de7f"
- integrity sha512-siieHkSY/b3Yw1Gf84L07j65m2Bht1PamAbX3cmZ1zAzsUxfXpqZq5W9PlAp5z1d0Tp1vxsQmXw6UGW0K1Tq1Q==
+snyk-try-require@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/snyk-try-require/-/snyk-try-require-2.0.1.tgz#076ae9bc505d64d28389452ce19fcac28f26655a"
+ integrity sha512-VCOfFIvqLMXgCXEdooQgu3A40XYIFBnj0X8Y01RJ5iAbu08b4WKGN/uAKaRVF30dABS4EcjsalmCO+YlKUPEIA==
dependencies:
- "@snyk/cli-interface" "2.3.0"
- "@snyk/configstore" "^3.2.0-rc1"
- "@snyk/dep-graph" "1.13.1"
+ debug "^4.1.1"
+ lodash.clonedeep "^4.3.0"
+ lru-cache "^5.1.1"
+
+snyk@^1.518.0:
+ version "1.564.0"
+ resolved "https://registry.yarnpkg.com/snyk/-/snyk-1.564.0.tgz#c8c511128351f8b8fe239b010b6799f40bb659c5"
+ integrity sha512-Fh4YusvJ9XdQfyz8JH9J8mBbipfgGLiF60MW9DYhQgP6h8z5uckAfd+S/uFMwPOVOIoe00fFo7aCLxFUuPcVJQ==
+ dependencies:
+ "@open-policy-agent/opa-wasm" "^1.2.0"
+ "@snyk/cli-interface" "2.11.0"
+ "@snyk/cloud-config-parser" "^1.9.2"
+ "@snyk/code-client" "3.4.1"
+ "@snyk/dep-graph" "^1.27.1"
+ "@snyk/fix" "1.554.0"
"@snyk/gemfile" "1.2.0"
- "@snyk/snyk-cocoapods-plugin" "2.0.1"
- "@snyk/update-notifier" "^2.5.1-rc2"
- "@types/agent-base" "^4.2.0"
- "@types/restify" "^4.3.6"
+ "@snyk/graphlib" "^2.1.9-patch.3"
+ "@snyk/inquirer" "^7.3.3-patch"
+ "@snyk/snyk-cocoapods-plugin" "2.5.2"
+ "@snyk/snyk-hex-plugin" "1.1.4"
abbrev "^1.1.1"
ansi-escapes "3.2.0"
chalk "^2.4.2"
cli-spinner "0.2.10"
- debug "^3.1.0"
+ configstore "^5.0.1"
+ debug "^4.1.1"
diff "^4.0.1"
- git-url-parse "11.1.2"
- glob "^7.1.3"
- inquirer "^6.2.2"
- lodash "^4.17.14"
- needle "^2.2.4"
- opn "^5.5.0"
+ global-agent "^2.1.12"
+ lodash.assign "^4.2.0"
+ lodash.camelcase "^4.3.0"
+ lodash.clonedeep "^4.5.0"
+ lodash.endswith "^4.2.1"
+ lodash.flatten "^4.4.0"
+ lodash.flattendeep "^4.4.0"
+ lodash.get "^4.4.2"
+ lodash.groupby "^4.6.0"
+ lodash.isempty "^4.4.0"
+ lodash.isobject "^3.0.2"
+ lodash.map "^4.6.0"
+ lodash.omit "^4.5.0"
+ lodash.orderby "^4.6.0"
+ lodash.sortby "^4.7.0"
+ lodash.uniq "^4.5.0"
+ lodash.upperfirst "^4.3.1"
+ lodash.values "^4.3.0"
+ micromatch "4.0.2"
+ needle "2.6.0"
+ open "^7.0.3"
+ ora "5.3.0"
os-name "^3.0.0"
- proxy-agent "^3.1.1"
+ promise-queue "^2.2.5"
proxy-from-env "^1.0.0"
+ rimraf "^2.6.3"
semver "^6.0.0"
- snyk-config "^2.2.1"
- snyk-docker-plugin "1.38.0"
- snyk-go-plugin "1.11.1"
- snyk-gradle-plugin "3.2.4"
- snyk-module "1.9.1"
- snyk-mvn-plugin "2.8.0"
- snyk-nodejs-lockfile-parser "1.17.0"
- snyk-nuget-plugin "1.16.0"
- snyk-php-plugin "1.7.0"
- snyk-policy "1.13.5"
- snyk-python-plugin "1.17.0"
- snyk-resolve "1.0.1"
- snyk-resolve-deps "4.4.0"
+ snyk-config "4.0.0"
+ snyk-cpp-plugin "2.2.1"
+ snyk-docker-plugin "4.19.3"
+ snyk-go-plugin "1.17.0"
+ snyk-gradle-plugin "3.14.2"
+ snyk-module "3.1.0"
+ snyk-mvn-plugin "2.25.3"
+ snyk-nodejs-lockfile-parser "1.32.0"
+ snyk-nuget-plugin "1.21.1"
+ snyk-php-plugin "1.9.2"
+ snyk-policy "1.19.0"
+ snyk-python-plugin "1.19.8"
+ snyk-resolve "1.1.0"
+ snyk-resolve-deps "4.7.2"
snyk-sbt-plugin "2.11.0"
snyk-tree "^1.0.0"
snyk-try-require "1.3.1"
source-map-support "^0.5.11"
strip-ansi "^5.2.0"
+ tar "^6.1.0"
tempfile "^2.0.0"
- then-fs "^2.0.0"
+ update-notifier "^4.1.0"
uuid "^3.3.2"
wrap-ansi "^5.1.0"
-socks-proxy-agent@^4.0.1:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz#3c8991f3145b2799e70e11bd5fbc8b1963116386"
- integrity sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==
- dependencies:
- agent-base "~4.2.1"
- socks "~2.3.2"
-
-socks@~2.3.2:
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/socks/-/socks-2.3.3.tgz#01129f0a5d534d2b897712ed8aceab7ee65d78e3"
- integrity sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==
- dependencies:
- ip "1.1.5"
- smart-buffer "^4.1.0"
-
source-map-support@^0.5.11, source-map-support@^0.5.7:
version "0.5.16"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042"
@@ -1855,37 +3070,72 @@
buffer-from "^1.0.0"
source-map "^0.6.0"
-source-map@^0.6.0, source-map@~0.6.1:
+source-map@^0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+split-ca@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6"
+ integrity sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY=
+
+sprintf-js@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673"
+ integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==
+
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
-"statuses@>= 1.5.0 < 2":
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
- integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
-
-string-width@^1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
- integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=
+ssh2-streams@~0.4.10:
+ version "0.4.10"
+ resolved "https://registry.yarnpkg.com/ssh2-streams/-/ssh2-streams-0.4.10.tgz#48ef7e8a0e39d8f2921c30521d56dacb31d23a34"
+ integrity sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ==
dependencies:
- code-point-at "^1.0.0"
- is-fullwidth-code-point "^1.0.0"
- strip-ansi "^3.0.0"
+ asn1 "~0.2.0"
+ bcrypt-pbkdf "^1.0.2"
+ streamsearch "~0.1.2"
-string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
- integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
+ssh2@^0.8.7:
+ version "0.8.9"
+ resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.9.tgz#54da3a6c4ba3daf0d8477a538a481326091815f3"
+ integrity sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw==
dependencies:
- is-fullwidth-code-point "^2.0.0"
- strip-ansi "^4.0.0"
+ ssh2-streams "~0.4.10"
+
+stream-buffers@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-3.0.2.tgz#5249005a8d5c2d00b3a32e6e0a6ea209dc4f3521"
+ integrity sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==
+
+stream-shift@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d"
+ integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==
+
+stream-to-array@~2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/stream-to-array/-/stream-to-array-2.3.0.tgz#bbf6b39f5f43ec30bc71babcb37557acecf34353"
+ integrity sha1-u/azn19D7DC8cbq8s3VXrOzzQ1M=
+ dependencies:
+ any-promise "^1.1.0"
+
+stream-to-promise@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/stream-to-promise/-/stream-to-promise-2.2.0.tgz#b1edb2e1c8cb11289d1b503c08d3f2aef51e650f"
+ integrity sha1-se2y4cjLESidG1A8CNPyrvUeZQ8=
+ dependencies:
+ any-promise "~1.3.0"
+ end-of-stream "~1.1.0"
+ stream-to-array "~2.3.0"
+
+streamsearch@~0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
+ integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
string-width@^3.0.0:
version "3.1.0"
@@ -1896,6 +3146,15 @@
is-fullwidth-code-point "^2.0.0"
strip-ansi "^5.1.0"
+string-width@^4.0.0, string-width@^4.1.0:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5"
+ integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.0"
+
string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
@@ -1903,11 +3162,6 @@
dependencies:
safe-buffer "~5.2.0"
-string_decoder@~0.10.x:
- version "0.10.31"
- resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
- integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
-
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
@@ -1915,19 +3169,12 @@
dependencies:
safe-buffer "~5.1.0"
-strip-ansi@^3.0.0, strip-ansi@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
- integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=
+strip-ansi@6.0.0, strip-ansi@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
+ integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==
dependencies:
- ansi-regex "^2.0.0"
-
-strip-ansi@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
- integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
- dependencies:
- ansi-regex "^3.0.0"
+ ansi-regex "^5.0.0"
strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
version "5.2.0"
@@ -1953,6 +3200,24 @@
dependencies:
has-flag "^3.0.0"
+supports-color@^7.1.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+ dependencies:
+ has-flag "^4.0.0"
+
+tar-stream@^2.0.1, tar-stream@^2.1.2:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
+ integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
+ dependencies:
+ bl "^4.0.3"
+ end-of-stream "^1.4.1"
+ fs-constants "^1.0.0"
+ inherits "^2.0.3"
+ readable-stream "^3.1.1"
+
tar-stream@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3"
@@ -1964,11 +3229,28 @@
inherits "^2.0.3"
readable-stream "^3.1.1"
+tar@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83"
+ integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==
+ dependencies:
+ chownr "^2.0.0"
+ fs-minipass "^2.0.0"
+ minipass "^3.0.0"
+ minizlib "^2.1.1"
+ mkdirp "^1.0.3"
+ yallist "^4.0.0"
+
temp-dir@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d"
integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=
+temp-dir@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e"
+ integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==
+
tempfile@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/tempfile/-/tempfile-2.0.0.tgz#6b0446856a9b1114d1856ffcbe509cccb0977265"
@@ -1977,12 +3259,10 @@
temp-dir "^1.0.0"
uuid "^3.0.1"
-term-size@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69"
- integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=
- dependencies:
- execa "^0.7.0"
+term-size@^2.1.0:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54"
+ integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==
then-fs@^2.0.0:
version "2.0.0"
@@ -1991,21 +3271,19 @@
dependencies:
promise ">=3.2 <8"
+through2@^2.0.3:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
+ integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
+ dependencies:
+ readable-stream "~2.3.6"
+ xtend "~4.0.1"
+
through@^2.3.6:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
-thunkify@^2.1.2:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/thunkify/-/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d"
- integrity sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0=
-
-timed-out@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
- integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=
-
tmp@0.0.33, tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@@ -2013,6 +3291,13 @@
dependencies:
os-tmpdir "~1.0.2"
+tmp@0.2.1, tmp@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
+ integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==
+ dependencies:
+ rimraf "^3.0.0"
+
tmp@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877"
@@ -2020,10 +3305,17 @@
dependencies:
rimraf "^2.6.3"
-toidentifier@1.0.0:
+to-readable-stream@^1.0.0:
version "1.0.0"
- resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
- integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
+ resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771"
+ integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==
+
+to-regex-range@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+ integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+ dependencies:
+ is-number "^7.0.0"
toml@^3.0.0:
version "3.0.0"
@@ -2035,46 +3327,105 @@
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
-tslib@1.9.3:
- version "1.9.3"
- resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
- integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
+treeify@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8"
+ integrity sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==
+
+tslib@1.11.1:
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
+ integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==
tslib@^1, tslib@^1.10.0, tslib@^1.9.0, tslib@^1.9.3:
version "1.10.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
-type-check@~0.3.2:
- version "0.3.2"
- resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
- integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=
+tslib@^1.11.2, tslib@^1.13.0:
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
+ integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
+
+tslib@^2.0.0, tslib@^2.1.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c"
+ integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==
+
+tunnel@^0.0.6:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
+ integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
+
+tweetnacl@^0.14.3:
+ version "0.14.5"
+ resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
+ integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
+
+type-fest@^0.13.1:
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934"
+ integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==
+
+type-fest@^0.21.3:
+ version "0.21.3"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
+ integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
+
+type-fest@^0.8.1:
+ version "0.8.1"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
+ integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
+
+typedarray-to-buffer@^3.1.5:
+ version "3.1.5"
+ resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
+ integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==
dependencies:
- prelude-ls "~1.1.2"
+ is-typedarray "^1.0.0"
-unique-string@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a"
- integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=
+unique-string@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d"
+ integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==
dependencies:
- crypto-random-string "^1.0.0"
+ crypto-random-string "^2.0.0"
-unpipe@1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
- integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
-
-unzip-response@^2.0.1:
+upath@2.0.1:
version "2.0.1"
- resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97"
- integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=
+ resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b"
+ integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==
-url-parse-lax@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73"
- integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=
+update-notifier@^4.1.0:
+ version "4.1.3"
+ resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.3.tgz#be86ee13e8ce48fb50043ff72057b5bd598e1ea3"
+ integrity sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==
dependencies:
- prepend-http "^1.0.1"
+ boxen "^4.2.0"
+ chalk "^3.0.0"
+ configstore "^5.0.1"
+ has-yarn "^2.1.0"
+ import-lazy "^2.1.0"
+ is-ci "^2.0.0"
+ is-installed-globally "^0.3.1"
+ is-npm "^4.0.0"
+ is-yarn-global "^0.3.0"
+ latest-version "^5.0.0"
+ pupa "^2.0.1"
+ semver-diff "^3.1.1"
+ xdg-basedir "^4.0.0"
+
+url-parse-lax@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c"
+ integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=
+ dependencies:
+ prepend-http "^2.0.0"
+
+utf8@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1"
+ integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
version "1.0.2"
@@ -2086,10 +3437,22 @@
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
-vscode-languageserver-types@^3.5.0:
- version "3.15.1"
- resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de"
- integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==
+uuid@^8.2.0, uuid@^8.3.0, uuid@^8.3.2:
+ version "8.3.2"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
+ integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
+
+vscode-languageserver-types@^3.16.0:
+ version "3.16.0"
+ resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247"
+ integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==
+
+wcwidth@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
+ integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=
+ dependencies:
+ defaults "^1.0.3"
which@^1.2.9:
version "1.3.1"
@@ -2098,17 +3461,19 @@
dependencies:
isexe "^2.0.0"
-widest-line@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc"
- integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==
+which@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
dependencies:
- string-width "^2.1.1"
+ isexe "^2.0.0"
-window-size@^0.1.4:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876"
- integrity sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=
+widest-line@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca"
+ integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==
+ dependencies:
+ string-width "^4.0.0"
windows-release@^3.1.0:
version "3.2.0"
@@ -2117,19 +3482,6 @@
dependencies:
execa "^1.0.0"
-word-wrap@~1.2.3:
- version "1.2.3"
- resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
- integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
-
-wrap-ansi@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
- integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=
- dependencies:
- string-width "^1.0.1"
- strip-ansi "^3.0.1"
-
wrap-ansi@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"
@@ -2144,29 +3496,29 @@
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
-write-file-atomic@^2.0.0:
- version "2.4.3"
- resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481"
- integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==
+write-file-atomic@^3.0.0:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8"
+ integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==
dependencies:
- graceful-fs "^4.1.11"
imurmurhash "^0.1.4"
+ is-typedarray "^1.0.0"
signal-exit "^3.0.2"
+ typedarray-to-buffer "^3.1.5"
-xdg-basedir@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
- integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=
+xdg-basedir@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
+ integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
-xml2js@0.4.19:
- version "0.4.19"
- resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
- integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==
+xml-js@^1.6.11:
+ version "1.6.11"
+ resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9"
+ integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==
dependencies:
- sax ">=0.6.0"
- xmlbuilder "~9.0.1"
+ sax "^1.2.4"
-xml2js@^0.4.17:
+xml2js@0.4.23, xml2js@^0.4.17:
version "0.4.23"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
@@ -2179,20 +3531,10 @@
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
-xmlbuilder@~9.0.1:
- version "9.0.7"
- resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
- integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
-
-xregexp@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"
- integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=
-
-y18n@^3.2.0:
- version "3.2.1"
- resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
- integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
+xtend@~4.0.1:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
+ integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
yallist@^2.1.2:
version "2.1.2"
@@ -2204,15 +3546,17 @@
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
-yargs@^3.19.0:
- version "3.32.0"
- resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995"
- integrity sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=
- dependencies:
- camelcase "^2.0.1"
- cliui "^3.0.3"
- decamelize "^1.1.1"
- os-locale "^1.4.0"
- string-width "^1.0.1"
- window-size "^0.1.4"
- y18n "^3.2.0"
+yallist@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
+ integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+
+yaml-js@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/yaml-js/-/yaml-js-0.3.0.tgz#ad0893d9de881a93fd6bf936e8d89cdde309e848"
+ integrity sha512-JbTUdsPiCkOyz+JOSqAVc19omTnUBnBQglhuclYov5HpWbEOz8y+ftqWjiMa9Pe/eF/dmCUeNgVs/VWg53GlgQ==
+
+yaml@^1.9.2:
+ version "1.10.2"
+ resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
+ integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==