Merge remote-tracking branch 'upstream/develop' into esbuild
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..4798b92
--- /dev/null
+++ b/.github/helper/semgrep_rules/frappe_correctness.py
@@ -0,0 +1,28 @@
+import frappe
+from frappe import _, flt
+
+from frappe.model.document import Document
+
+
+def on_submit(self):
+ if self.value_of_goods == 0:
+ frappe.throw(_('Value of goods cannot be 0'))
+ # ruleid: frappe-modifying-after-submit
+ self.status = 'Submitted'
+
+def on_submit(self):
+ if flt(self.per_billed) < 100:
+ self.update_billing_status()
+ else:
+ # todook: frappe-modifying-after-submit
+ self.status = "Completed"
+ self.db_set("status", "Completed")
+
+class TestDoc(Document):
+ pass
+
+ def validate(self):
+ #ruleid: frappe-modifying-child-tables-while-iterating
+ for item in self.child_table:
+ if item.value < 0:
+ self.remove(item)
diff --git a/.github/helper/semgrep_rules/frappe_correctness.yml b/.github/helper/semgrep_rules/frappe_correctness.yml
new file mode 100644
index 0000000..54df062
--- /dev/null
+++ b/.github/helper/semgrep_rules/frappe_correctness.yml
@@ -0,0 +1,74 @@
+# This file specifies rules for correctness according to how frappe doctype data model works.
+
+rules:
+- id: frappe-modifying-after-submit
+ patterns:
+ - pattern: self.$ATTR = ...
+ - pattern-inside: |
+ def on_submit(self, ...):
+ ...
+ - metavariable-regex:
+ metavariable: '$ATTR'
+ # this is negative look-ahead, add more attrs to ignore like (ignore|ignore_this_too|ignore_me)
+ regex: '^(?!status_updater)(.*)$'
+ message: |
+ Doctype modified after submission. Please check if modification of self.$ATTR is commited to database.
+ languages: [python]
+ severity: ERROR
+
+- id: frappe-modifying-after-cancel
+ patterns:
+ - pattern: self.$ATTR = ...
+ - pattern-inside: |
+ def on_cancel(self, ...):
+ ...
+ - metavariable-regex:
+ metavariable: '$ATTR'
+ regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$'
+ message: |
+ Doctype modified after cancellation. Please check if modification of self.$ATTR is 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..7b92fe2
--- /dev/null
+++ b/.github/helper/semgrep_rules/translate.js
@@ -0,0 +1,37 @@
+// 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])
diff --git a/.github/helper/semgrep_rules/translate.py b/.github/helper/semgrep_rules/translate.py
new file mode 100644
index 0000000..bd6cd91
--- /dev/null
+++ b/.github/helper/semgrep_rules/translate.py
@@ -0,0 +1,53 @@
+# 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
+_('')
diff --git a/.github/helper/semgrep_rules/translate.yml b/.github/helper/semgrep_rules/translate.yml
new file mode 100644
index 0000000..3737da5
--- /dev/null
+++ b/.github/helper/semgrep_rules/translate.yml
@@ -0,0 +1,63 @@
+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*'
+ 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.py b/.github/helper/semgrep_rules/ux.py
new file mode 100644
index 0000000..4a74457
--- /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
+throw("Error Occured")
+
+# ruleid: frappe-missing-translate-function
+frappe.throw("Error Occured")
+
+# ruleid: frappe-missing-translate-function
+frappe.msgprint("Useful message")
+
+# ruleid: frappe-missing-translate-function
+msgprint("Useful message")
+
+
+# ok: frappe-missing-translate-function
+translatedmessage = _("Hello")
+
+# ok: frappe-missing-translate-function
+throw(translatedmessage)
+
+# ok: frappe-missing-translate-function
+msgprint(translatedmessage)
+
+# ok: frappe-missing-translate-function
+msgprint(_("Helpful message"))
+
+# ok: frappe-missing-translate-function
+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..ed06a6a
--- /dev/null
+++ b/.github/helper/semgrep_rules/ux.yml
@@ -0,0 +1,15 @@
+rules:
+- id: frappe-missing-translate-function
+ pattern-either:
+ - patterns:
+ - pattern: frappe.msgprint("...", ...)
+ - pattern-not: frappe.msgprint(_("..."), ...)
+ - pattern-not: frappe.msgprint(__("..."), ...)
+ - patterns:
+ - pattern: frappe.throw("...", ...)
+ - pattern-not: 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, javascript, json]
+ severity: ERROR
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
index 78c2f5a..84ecfb1 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/ci-tests.yml
@@ -80,15 +80,29 @@
env:
TYPE: ${{ matrix.TYPE }}
- - name: Coverage
- if: matrix.TYPE == 'server'
+ - name: Coverage - Pull Request
+ if: matrix.TYPE == 'server' && github.event_name == 'pull_request'
run: |
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
cd ${GITHUB_WORKSPACE}
pip install coveralls==2.2.0
pip install coverage==4.5.4
- coveralls
+ coveralls --service=github
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
+ COVERALLS_SERVICE_NAME: github
+
+ - name: Coverage - Push
+ if: matrix.TYPE == 'server' && github.event_name == 'push'
+ run: |
+ cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
+ cd ${GITHUB_WORKSPACE}
+ pip install coveralls==2.2.0
+ pip install coverage==4.5.4
+ coveralls --service=github-actions
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
+ COVERALLS_SERVICE_NAME: github-actions
diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml
new file mode 100644
index 0000000..df08263
--- /dev/null
+++ b/.github/workflows/semgrep.yml
@@ -0,0 +1,24 @@
+name: Semgrep
+
+on:
+ pull_request:
+ branches:
+ - develop
+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: Run semgrep
+ run: |
+ python -m pip install -q semgrep
+ git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q
+ 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
+ [[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files
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..6775398 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.1'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index c801cfc..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()
@@ -214,6 +214,7 @@
if parent_value_changed:
doc.save()
+ @frappe.whitelist()
def convert_group_to_ledger(self):
if self.check_if_child_exists():
throw(_("Account with child nodes cannot be converted to ledger"))
@@ -224,6 +225,7 @@
self.save()
return 1
+ @frappe.whitelist()
def convert_ledger_to_group(self):
if self.check_gle_exists():
throw(_("Account with existing transaction can not be converted to group."))
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/account/chart_of_accounts/verified/fr_plan_comptable_general.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general.json
index da1d10d..d60c559 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general.json
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general.json
@@ -1,1609 +1,1609 @@
{
- "country_code": "fr",
- "name": "France - Plan Comptable General",
+ "country_code": "fr",
+ "name": "France - Plan Comptable General",
"tree": {
"1-Comptes de Capitaux": {
"10-Capital et R\u00e9serves": {
"101-Capital": {
- "1011-Capital souscrit - non appel\u00e9": {},
- "1012-Capital souscrit - appel\u00e9, non vers\u00e9": {},
+ "1011-Capital souscrit - non appel\u00e9": {},
+ "1012-Capital souscrit - appel\u00e9, non vers\u00e9": {},
"1013-Capital souscrit - appel\u00e9, vers\u00e9": {
- "10131-Capital non amorti": {},
+ "10131-Capital non amorti": {},
"10132-Capital amorti": {}
- },
+ },
"1018-Capital souscrit soumis \u00e0 des r\u00e9glementations particuli\u00e8res": {}
- },
- "102-Fonds fiduciaires": {},
+ },
+ "102-Fonds fiduciaires": {},
"104-Primes li\u00e9es au capital social": {
- "1041-Primes d'\u00e9mission": {},
- "1042-Primes de fusion": {},
- "1043-Primes d'apport": {},
- "1044-Primes de conversion d'obligations en actions": {},
+ "1041-Primes d'\u00e9mission": {},
+ "1042-Primes de fusion": {},
+ "1043-Primes d'apport": {},
+ "1044-Primes de conversion d'obligations en actions": {},
"1045-Bons de souscription d'actions": {}
- },
+ },
"105-Ecarts de r\u00e9\u00e9valuation": {
- "1051-R\u00e9serve sp\u00e9ciale de r\u00e9\u00e9valuation": {},
- "1052-Ecart de r\u00e9\u00e9valuation libre": {},
- "1053-R\u00e9serve de r\u00e9\u00e9valuation": {},
- "1055-Ecarts de r\u00e9\u00e9valuation (autres op\u00e9rations l\u00e9gales)": {},
- "1057-Autres \u00e9carts de r\u00e9\u00e9valuation en France": {},
+ "1051-R\u00e9serve sp\u00e9ciale de r\u00e9\u00e9valuation": {},
+ "1052-Ecart de r\u00e9\u00e9valuation libre": {},
+ "1053-R\u00e9serve de r\u00e9\u00e9valuation": {},
+ "1055-Ecarts de r\u00e9\u00e9valuation (autres op\u00e9rations l\u00e9gales)": {},
+ "1057-Autres \u00e9carts de r\u00e9\u00e9valuation en France": {},
"1058-Autres \u00e9carts de r\u00e9\u00e9valuation \u00e0 l'\u00e9tranger": {}
- },
+ },
"106-R\u00e9serves": {
"1061-R\u00e9serve l\u00e9gale": {
- "10611-R\u00e9serve l\u00e9gale proprement dite": {},
+ "10611-R\u00e9serve l\u00e9gale proprement dite": {},
"10612-Plus-values nettes \u00e0 long terme": {}
- },
- "1062-R\u00e9serves indisponibles": {},
- "1063-R\u00e9serves statutaires ou contractuelles": {},
+ },
+ "1062-R\u00e9serves indisponibles": {},
+ "1063-R\u00e9serves statutaires ou contractuelles": {},
"1064-R\u00e9serves r\u00e9glement\u00e9es": {
- "10641-Plus-values nettes \u00e0 long terme": {},
- "10643-R\u00e9serves cons\u00e9cutives \u00e0 l'octroi de subventions d'investissement": {},
+ "10641-Plus-values nettes \u00e0 long terme": {},
+ "10643-R\u00e9serves cons\u00e9cutives \u00e0 l'octroi de subventions d'investissement": {},
"10648-Autres r\u00e9serves r\u00e9glement\u00e9es": {}
- },
+ },
"1068-Autres r\u00e9serves": {
- "10681-R\u00e9serve de propre assureur": {},
+ "10681-R\u00e9serve de propre assureur": {},
"10688-R\u00e9serves diverses": {}
}
- },
- "107-Ecarts d'\u00e9quivalence": {},
- "108-Compte de l'exploitant": {},
+ },
+ "107-Ecarts d'\u00e9quivalence": {},
+ "108-Compte de l'exploitant": {},
"109-Actionnaires: Capital souscrit - non appel\u00e9": {}
- },
+ },
"11-Report \u00e0 Nouveau": {
- "110-Report \u00e0 nouveau (solde cr\u00e9diteur)": {},
+ "110-Report \u00e0 nouveau (solde cr\u00e9diteur)": {},
"119-Report \u00e0 nouveau (solde d\u00e9biteur)": {}
- },
+ },
"12-R\u00e9sultat de l'Exercice": {
- "120-R\u00e9sultat de l'exercice (b\u00e9n\u00e9fice)": {},
+ "120-R\u00e9sultat de l'exercice (b\u00e9n\u00e9fice)": {},
"129-R\u00e9sultat de l'exercice (perte)": {}
- },
+ },
"13-Subventions d'Investissement": {
"131-Subventions d'\u00e9quipement": {
- "1311-Etat": {},
- "1312-R\u00e9gions": {},
- "1313-D\u00e9partements": {},
- "1314-Communes": {},
- "1315-Collectivit\u00e9s publiques": {},
- "1316-Entreprises publiques": {},
- "1317-Entreprises et organismes priv\u00e9s": {},
+ "1311-Etat": {},
+ "1312-R\u00e9gions": {},
+ "1313-D\u00e9partements": {},
+ "1314-Communes": {},
+ "1315-Collectivit\u00e9s publiques": {},
+ "1316-Entreprises publiques": {},
+ "1317-Entreprises et organismes priv\u00e9s": {},
"1318-Autres": {}
- },
- "138-Autres subventions d'investissement (m\u00eame ventilation que celle du compte 131)": {},
+ },
+ "138-Autres subventions d'investissement (m\u00eame ventilation que celle du compte 131)": {},
"139-Subventions d'investissement inscrites au compte de r\u00e9sultat": {
"1391-Subventions d'\u00e9quipement": {
- "13911-Subventions d'\u00e9quipement": {
- "13911-Etat": {},
- "13912-R\u00e9gions": {},
- "13913-D\u00e9partements": {},
- "13914-Communes": {},
- "13915-Collectivit\u00e9s publiques": {},
- "13916-Entreprises publiques": {},
- "13917-Entreprises et organismes priv\u00e9s": {},
- "13918-Autres": {}
- }
- },
+ "13911-Etat": {},
+ "13912-R\u00e9gions": {},
+ "13913-D\u00e9partements": {},
+ "13914-Communes": {},
+ "13915-Collectivit\u00e9s publiques": {},
+ "13916-Entreprises publiques": {},
+ "13917-Entreprises et organismes priv\u00e9s": {},
+ "13918-Autres": {}
+ },
"1398-Autres subventions d'investissement (m\u00eame ventilation que celle du compte 1391)": {}
}
- },
+ },
"14-Provisions R\u00e9glement\u00e9es": {
"142-Provisions r\u00e9glement\u00e9es relative aux immobilisations": {
- "1423-Provisions pour reconstitution des gisements miniers et p\u00e9troliers": {},
+ "1423-Provisions pour reconstitution des gisements miniers et p\u00e9troliers": {},
"1424-Provisions pour investissement (participation des salari\u00e9s)": {}
- },
+ },
"143-Provisions r\u00e9glement\u00e9es relatives aux stocks": {
- "1431-Hausse des prix": {},
+ "1431-Hausse des prix": {},
"1432-Fluctuation des cours": {}
- },
- "144-Provisions r\u00e9glement\u00e9es relatives aux autres \u00e9l\u00e9ments de l'actif": {},
- "145-Amortissements d\u00e9rogatoires": {},
- "146-Provision sp\u00e9ciale de r\u00e9\u00e9valuation": {},
- "147-Plus-values r\u00e9investies": {},
+ },
+ "144-Provisions r\u00e9glement\u00e9es relatives aux autres \u00e9l\u00e9ments de l'actif": {},
+ "145-Amortissements d\u00e9rogatoires": {},
+ "146-Provision sp\u00e9ciale de r\u00e9\u00e9valuation": {},
+ "147-Plus-values r\u00e9investies": {},
"148-Autres provisions r\u00e9glement\u00e9es": {}
- },
+ },
"15-Provisions": {
"151-Provisions pour risques": {
- "1511-Provisions pour litiges": {},
- "1512-Provisions pour garanties donn\u00e9es aux clients": {},
- "1513-Provisions pour pertes sur march\u00e9s \u00e0 terme": {},
- "1514-Provisions pour amendes et p\u00e9nalit\u00e9s": {},
- "1515-Provisions pour pertes de change": {},
- "1516-Provisions pour pertes sur contrats": {},
+ "1511-Provisions pour litiges": {},
+ "1512-Provisions pour garanties donn\u00e9es aux clients": {},
+ "1513-Provisions pour pertes sur march\u00e9s \u00e0 terme": {},
+ "1514-Provisions pour amendes et p\u00e9nalit\u00e9s": {},
+ "1515-Provisions pour pertes de change": {},
+ "1516-Provisions pour pertes sur contrats": {},
"1518-Autres provisions pour risques": {}
- },
- "153-Provisions pour pensions et obligations similaires": {},
- "154-Provisions pour restructurations": {},
- "155-Provisions pour imp\u00f4ts": {},
- "156-Provisions pour renouvellement des immobilisations (entreprises concessionnaires) ": {},
+ },
+ "153-Provisions pour pensions et obligations similaires": {},
+ "154-Provisions pour restructurations": {},
+ "155-Provisions pour imp\u00f4ts": {},
+ "156-Provisions pour renouvellement des immobilisations (entreprises concessionnaires) ": {},
"157-Provisions pour charges \u00e0 r\u00e9partir sur plusieurs exercices": {
"1572-Provisions pour gros entretien ou grandes r\u00e9visions": {}
- },
+ },
"158-Autres provisions pour charges": {
"1581-Provisions pour remises en \u00e9tat": {}
}
- },
+ },
"16-Emprunts et Dettes Assimil\u00e9es": {
- "161-Emprunts obligataires convertibles": {},
- "162-Obligations repr\u00e9sentatives de passifs nets remis en fiducie": {},
- "163-Autres emprunts obligataires": {},
- "164-Emprunts aupr\u00e8s des \u00e9tablissements de cr\u00e9dit": {},
+ "161-Emprunts obligataires convertibles": {},
+ "162-Obligations repr\u00e9sentatives de passifs nets remis en fiducie": {},
+ "163-Autres emprunts obligataires": {},
+ "164-Emprunts aupr\u00e8s des \u00e9tablissements de cr\u00e9dit": {},
"165-D\u00e9p\u00f4ts et cautionnements re\u00e7us": {
- "1651-D\u00e9p\u00f4ts": {},
+ "1651-D\u00e9p\u00f4ts": {},
"1655-Cautionnements": {}
- },
+ },
"166-Participation des salari\u00e9s aux r\u00e9sultats": {
- "1661-Comptes bloqu\u00e9s": {},
+ "1661-Comptes bloqu\u00e9s": {},
"1662-Fonds de participation": {}
- },
+ },
"167-Emprunts et dettes assortis de conditions particuli\u00e8res": {
- "1671-Emissions de titres participatifs": {},
- "1674-Avances conditionn\u00e9es de l'Etat": {},
+ "1671-Emissions de titres participatifs": {},
+ "1674-Avances conditionn\u00e9es de l'Etat": {},
"1675-Emprunts participatifs": {}
- },
+ },
"168-Autres emprunts et dettes assimil\u00e9es": {
- "1681-Autres emprunts": {},
- "1685-Rentes viag\u00e8res capitalis\u00e9es": {},
- "1687-Autres dettes": {},
+ "1681-Autres emprunts": {},
+ "1685-Rentes viag\u00e8res capitalis\u00e9es": {},
+ "1687-Autres dettes": {},
"1688-Int\u00e9r\u00eats courus": {
- "16881-Int\u00e9r\u00eats courus sur emprunts obligataires convertibles": {},
- "16883-Int\u00e9r\u00eats courus sur autres emprunts obligataires": {},
- "16884-Int\u00e9r\u00eats courus sur emprunts aupr\u00e8s des \u00e9tablissements de cr\u00e9dit": {},
- "16885-Int\u00e9r\u00eats courus sur d\u00e9p\u00f4ts et cautionnements re\u00e7us": {},
- "16886-Int\u00e9r\u00eats courus sur participation des salari\u00e9s aux r\u00e9sultats": {},
- "16887-Int\u00e9r\u00eats courus sur emprunts et dettes assortis de conditions particuli\u00e8res": {},
+ "16881-Int\u00e9r\u00eats courus sur emprunts obligataires convertibles": {},
+ "16883-Int\u00e9r\u00eats courus sur autres emprunts obligataires": {},
+ "16884-Int\u00e9r\u00eats courus sur emprunts aupr\u00e8s des \u00e9tablissements de cr\u00e9dit": {},
+ "16885-Int\u00e9r\u00eats courus sur d\u00e9p\u00f4ts et cautionnements re\u00e7us": {},
+ "16886-Int\u00e9r\u00eats courus sur participation des salari\u00e9s aux r\u00e9sultats": {},
+ "16887-Int\u00e9r\u00eats courus sur emprunts et dettes assortis de conditions particuli\u00e8res": {},
"16888-Int\u00e9r\u00eats courus sur autres emprunts et dettes assimil\u00e9es": {}
- },
+ },
"169-Primes de remboursement des obligations": {}
}
- },
+ },
"17-Dettes Rattach\u00e9es \u00e0 des Participations": {
- "171-Dettes rattach\u00e9es \u00e0 des participations (groupe)": {},
- "174-Dettes rattach\u00e9es \u00e0 des participations (hors groupe)": {},
+ "171-Dettes rattach\u00e9es \u00e0 des participations (groupe)": {},
+ "174-Dettes rattach\u00e9es \u00e0 des participations (hors groupe)": {},
"178-Dettes rattach\u00e9es \u00e0 des soci\u00e9t\u00e9s en participation": {
- "1781-Principal": {},
+ "1781-Principal": {},
"1788-Int\u00e9r\u00eats courus": {}
}
- },
+ },
"18-Comptes de liaison des \u00e9tablisssements et soci\u00e9t\u00e9s en participation": {
- "181-Comptes de liaison des \u00e9tablissements": {},
- "186-Biens et prestations de services \u00e9chang\u00e9s entre \u00e9tablissements (charges)": {},
- "187-Biens et prestations de services \u00e9chang\u00e9s entre \u00e9tablissements (produits)": {},
+ "181-Comptes de liaison des \u00e9tablissements": {},
+ "186-Biens et prestations de services \u00e9chang\u00e9s entre \u00e9tablissements (charges)": {},
+ "187-Biens et prestations de services \u00e9chang\u00e9s entre \u00e9tablissements (produits)": {},
"188-Comptes de liaison des soci\u00e9t\u00e9s en participation": {}
- },
+ },
"root_type": "Equity"
- },
+ },
"2-Comptes d'Immobilisations": {
"20-Immobilisations incorporelles": {
"201-Frais \u00e9tablissement": {
- "2011-Frais de constitution": {},
+ "2011-Frais de constitution": {},
"2012-Frais de premier \u00e9tablissement": {
- "20121-Frais de prospection": {},
+ "20121-Frais de prospection": {},
"20122-Frais de publicit\u00e9": {}
- },
+ },
"2013-Frais d'augmentation de capital et d'op\u00e9rations diverses (fusions, scissions, transformations)": {}
- },
- "203-Frais de recherche et de d\u00e9veloppement": {},
- "205-Concessions et droits similaires, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels, droits et valeurs similaires": {},
- "206-Droit au bail": {},
- "207-Fonds commercial": {},
+ },
+ "203-Frais de recherche et de d\u00e9veloppement": {},
+ "205-Concessions et droits similaires, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels, droits et valeurs similaires": {},
+ "206-Droit au bail": {},
+ "207-Fonds commercial": {},
"208-Autres immobilisations incorporelles": {
"2081-Mali de fusion sur actifs incorporels": {}
}
- },
+ },
"21-Immobilisations corporelles": {
"211-Terrains": {
"2111-Terrains nus": {
"account_type": "Fixed Asset"
- },
+ },
"2112-Terrains am\u00e9nag\u00e9s": {
"account_type": "Fixed Asset"
- },
+ },
"2113-Sous-sols et sur-sols": {
"account_type": "Fixed Asset"
- },
+ },
"2114-Terrains de carri\u00e8res (tr\u00e9fonds)": {
"account_type": "Fixed Asset"
- },
+ },
"2115-Terrains b\u00e2tis": {
"21151-Ensembles immobiliers industriels (A, B)": {
"account_type": "Fixed Asset"
- },
+ },
"21155-Ensembles immobiliers administratifs et commerciaux (A, B)": {
"account_type": "Fixed Asset"
- },
+ },
"21158-Autres ensembles immobiliers": {
"211581-Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations professionnelles (A, B)": {
"account_type": "Fixed Asset"
- },
+ },
"211588-Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations non professionnelles (A, B)": {
"account_type": "Fixed Asset"
- },
+ },
"account_type": "Fixed Asset"
- },
+ },
"account_type": "Fixed Asset"
- },
+ },
"account_type": "Fixed Asset"
- },
+ },
"212-Agencements et am\u00e9nagements de terrains (m\u00eame ventilation que celle du compte 211)": {
"account_type": "Fixed Asset"
- },
+ },
"213-Constructions": {
"2131-B\u00e2timents": {
"21311-Ensembles immobiliers industriels (A, B)": {
"account_type": "Fixed Asset"
- },
+ },
"21315-Ensembles immobiliers administratifs et commerciaux (A, B)": {
"account_type": "Fixed Asset"
- },
+ },
"21318-Autres ensembles immobiliers": {
"213181-Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations professionnelles (A, B)": {
"account_type": "Fixed Asset"
- },
+ },
"213188-Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations non professionnelles (A, B)": {
"account_type": "Fixed Asset"
- },
+ },
"account_type": "Fixed Asset"
- },
+ },
"account_type": "Fixed Asset"
- },
+ },
"2135-Installations g\u00e9n\u00e9rales, agencements, am\u00e9nagements des constructions": {
"21351-Ensembles immobiliers industriels (A, B)": {
"account_type": "Fixed Asset"
- },
+ },
"21355-Ensembles immobiliers administratifs et commerciaux (A, B)": {
"account_type": "Fixed Asset"
- },
+ },
"21358-Autres ensembles immobiliers": {
"213581-Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations professionnelles (A, B)": {
"account_type": "Fixed Asset"
- },
+ },
"213588-Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations non professionnelles (A, B)": {
"account_type": "Fixed Asset"
- },
+ },
"account_type": "Fixed Asset"
- },
+ },
"account_type": "Fixed Asset"
- },
+ },
"2138-Ouvrages d'infrastructure": {
"21381-Voies de terre": {
"account_type": "Fixed Asset"
- },
+ },
"21382-Voies de fer": {
"account_type": "Fixed Asset"
- },
+ },
"21383-Voies d'eau": {
"account_type": "Fixed Asset"
- },
+ },
"21384-Barrages": {
"account_type": "Fixed Asset"
- },
+ },
"21385-Pistes d'a\u00e9rodromes": {
"account_type": "Fixed Asset"
- },
+ },
"account_type": "Fixed Asset"
- },
+ },
"account_type": "Fixed Asset"
- },
+ },
"214-Constructions sur sol d'autrui (m\u00eame ventilation que celle du compte 213)": {
"account_type": "Fixed Asset"
- },
+ },
"215-Installations techniques, mat\u00e9riel et outillage industriels": {
"2151-Installations complexes sp\u00e9cialis\u00e9es": {
"21511-Installations complexes sp\u00e9cialis\u00e9es - sur sol propre": {
"account_type": "Fixed Asset"
- },
+ },
"21514-Installations complexes sp\u00e9cialis\u00e9es - sur sol d'autrui": {
"account_type": "Fixed Asset"
- },
+ },
"account_type": "Fixed Asset"
- },
+ },
"2153-Installations \u00e0 caract\u00e8re sp\u00e9cifique": {
"21531-Installations \u00e0 caract\u00e8re sp\u00e9cifique - sur sol propre": {
"account_type": "Fixed Asset"
- },
+ },
"21534-Installations \u00e0 caract\u00e8re sp\u00e9cifique - sur sol d'autrui": {
"account_type": "Fixed Asset"
- },
+ },
"account_type": "Fixed Asset"
- },
+ },
"2154-Mat\u00e9riel industriel": {
"account_type": "Fixed Asset"
- },
+ },
"2155-Outillage industriel": {
"account_type": "Fixed Asset"
- },
+ },
"2157-Agencements et am\u00e9nagements du mat\u00e9riel et outillage industriel": {
"account_type": "Fixed Asset"
- },
+ },
"account_type": "Fixed Asset"
- },
+ },
"218-Autres immobilisations corporelles": {
"2181-Installations g\u00e9n\u00e9rales, agencements, am\u00e9nagements divers": {
"account_type": "Fixed Asset"
- },
+ },
"2182-Mat\u00e9riel de transport": {
"account_type": "Fixed Asset"
- },
+ },
"2183-Mat\u00e9riel de bureau et mat\u00e9riel informatique": {
"account_type": "Fixed Asset"
- },
+ },
"2184-Mobilier": {
"account_type": "Fixed Asset"
- },
+ },
"2185-Cheptel": {
"account_type": "Fixed Asset"
- },
+ },
"2186-Emballages r\u00e9cup\u00e9rables": {
"account_type": "Fixed Asset"
- },
- "2187-Mali de fusion sur actifs corporels": {},
+ },
+ "2187-Mali de fusion sur actifs corporels": {},
"account_type": "Fixed Asset"
- },
+ },
"account_type": "Fixed Asset"
- },
- "22-Immobilisations mises en concession": {},
+ },
+ "22-Immobilisations mises en concession": {},
"23-Immobilisations en cours": {
"231-Immobilisations corporelles en cours": {
- "2312-Terrains": {},
- "2313-Constructions": {},
- "2315-Installations techniques, mat\u00e9riel et outillage industriels": {},
+ "2312-Terrains": {},
+ "2313-Constructions": {},
+ "2315-Installations techniques, mat\u00e9riel et outillage industriels": {},
"2318-Autres immobilisations corporelles": {}
- },
- "232-Immobilisations incorporelles en cours": {},
- "237-Avances et acomptes vers\u00e9s sur commandes d'immobilisations incorporelles": {},
+ },
+ "232-Immobilisations incorporelles en cours": {},
+ "237-Avances et acomptes vers\u00e9s sur commandes d'immobilisations incorporelles": {},
"238-Avances et acomptes vers\u00e9s sur commandes d'immobilisations corporelles": {
- "2382-Terrains": {},
- "2383-Constructions": {},
- "2385-Installations techniques, mat\u00e9riel et outillage industriels": {},
+ "2382-Terrains": {},
+ "2383-Constructions": {},
+ "2385-Installations techniques, mat\u00e9riel et outillage industriels": {},
"2388-Autres immobilisations corporelles": {}
}
- },
+ },
"25-Parts dans des entreprises li\u00e9es et cr\u00e9ances sur des entreprises li\u00e9es": {
"is_group": 1
- },
+ },
"26-Participations et cr\u00e9ances rattach\u00e9es \u00e0 des participations": {
"261-Titres de participation": {
- "2611-Actions": {},
+ "2611-Actions": {},
"2618-Autres titres": {}
- },
+ },
"266-Autres formes de participation": {
"2661-Droit repr\u00e9sentatifs d'actifs nets remis en fiducie": {}
- },
+ },
"267-Cr\u00e9ances rattach\u00e9es \u00e0 des participations": {
- "2671-Cr\u00e9ances rattach\u00e9es \u00e0 des participations (groupe)": {},
- "2674-Cr\u00e9ances rattach\u00e9es \u00e0 des participations (hors groupe)": {},
- "2675-Versements repr\u00e9sentatifs d'apports non capitalis\u00e9s (appel de fonds)": {},
- "2676-Avances consolidables": {},
- "2677-Autres cr\u00e9ances rattach\u00e9es \u00e0 des participations": {},
+ "2671-Cr\u00e9ances rattach\u00e9es \u00e0 des participations (groupe)": {},
+ "2674-Cr\u00e9ances rattach\u00e9es \u00e0 des participations (hors groupe)": {},
+ "2675-Versements repr\u00e9sentatifs d'apports non capitalis\u00e9s (appel de fonds)": {},
+ "2676-Avances consolidables": {},
+ "2677-Autres cr\u00e9ances rattach\u00e9es \u00e0 des participations": {},
"2678-Int\u00e9r\u00eats courus": {}
- },
+ },
"268-Cr\u00e9ances rattach\u00e9es \u00e0 des soci\u00e9t\u00e9s en participation": {
- "2681-Principal": {},
+ "2681-Principal": {},
"2688-Int\u00e9r\u00eats courus": {}
- },
+ },
"269-Versements restant \u00e0 effectuer sur titres de participation non lib\u00e9r\u00e9s": {}
- },
+ },
"27-Autres immobilisations financi\u00e8res": {
"271-Titres immobilis\u00e9s autres que les titres immobilis\u00e9s de l'activit\u00e9 de portefeuille (droit de propri\u00e9t\u00e9)": {
- "2711-Actions": {},
+ "2711-Actions": {},
"2718-Autres titres": {}
- },
+ },
"272-Titres immobilis\u00e9s (droit de cr\u00e9ance)": {
- "2721-Obligations": {},
+ "2721-Obligations": {},
"2722-Bons": {}
- },
- "273-Titres immobilis\u00e9s de l'activit\u00e9 de portefeuille": {},
+ },
+ "273-Titres immobilis\u00e9s de l'activit\u00e9 de portefeuille": {},
"274-Pr\u00eats": {
- "2741-Pr\u00eats participatifs": {},
- "2742-Pr\u00eats aux associ\u00e9s": {},
- "2743-Pr\u00eats au personnel": {},
+ "2741-Pr\u00eats participatifs": {},
+ "2742-Pr\u00eats aux associ\u00e9s": {},
+ "2743-Pr\u00eats au personnel": {},
"2748-Autres pr\u00eats": {}
- },
+ },
"275-D\u00e9p\u00f4ts et cautionnements vers\u00e9s": {
- "2751-D\u00e9p\u00f4ts": {},
+ "2751-D\u00e9p\u00f4ts": {},
"2755-Cautionnements": {}
- },
+ },
"276-Autres cr\u00e9ances immobilis\u00e9es": {
- "2761-Cr\u00e9ances diverses": {},
+ "2761-Cr\u00e9ances diverses": {},
"2768-Int\u00e9r\u00eats courus": {
- "27682-Int\u00e9r\u00eats courus sur titres immobilis\u00e9s (droit de cr\u00e9ance)": {},
- "27684-Int\u00e9r\u00eats courus sur pr\u00eats": {},
- "27685-Int\u00e9r\u00eats courus sur d\u00e9p\u00f4ts et cautionnements": {},
+ "27682-Int\u00e9r\u00eats courus sur titres immobilis\u00e9s (droit de cr\u00e9ance)": {},
+ "27684-Int\u00e9r\u00eats courus sur pr\u00eats": {},
+ "27685-Int\u00e9r\u00eats courus sur d\u00e9p\u00f4ts et cautionnements": {},
"27688-Int\u00e9r\u00eats courus sur cr\u00e9ances diverses": {}
}
- },
+ },
"277-(Actions propres ou parts propres)": {
- "2771-Actions propres ou parts propres": {},
+ "2771-Actions propres ou parts propres": {},
"2772-Actions propres ou parts propres en voie d'annulation": {}
- },
- "278-Mali de fusion sur actifs financiers": {},
+ },
+ "278-Mali de fusion sur actifs financiers": {},
"279-Versements restant \u00e0 effectuer sur titres immobilis\u00e9s non lib\u00e9r\u00e9s": {}
- },
+ },
"28-Amortissements des immobilisations": {
"280-Amortissements des immobilisations incorporelles": {
"2801-Frais d'\u00e9tablissement (m\u00eame ventilation que celle du compte 212)": {
"account_type": "Accumulated Depreciation"
- },
+ },
"2803-Frais de recherche et de d\u00e9veloppement": {
"account_type": "Accumulated Depreciation"
- },
+ },
"2805-Concessions et droits similaires, brevets, licences, logiciels, droits et valeurs similaires": {
"account_type": "Accumulated Depreciation"
- },
+ },
"2807-Fonds commercial": {
"account_type": "Accumulated Depreciation"
- },
+ },
"2808-Autres immobilisations incorporelles": {
"28081-Mali de fusion sur actifs incorporels": {
"account_type": "Accumulated Depreciation"
- },
+ },
"account_type": "Accumulated Depreciation"
- },
+ },
"account_type": "Accumulated Depreciation"
- },
+ },
"281-Amortissements des immobilisations corporelles": {
"2811-Terrains de gisement": {
"account_type": "Accumulated Depreciation"
- },
+ },
"2812-Agencements, am\u00e9nagements de terrains (m\u00eame ventilation que celle du compte 212)": {
"account_type": "Accumulated Depreciation"
- },
+ },
"2813-Constructions (m\u00eame ventilation que celle du compte 213)": {
"account_type": "Accumulated Depreciation"
- },
+ },
"2814-Constructions sur sol d'autrui (m\u00eame ventilation que celle du compte du 214)": {
"account_type": "Accumulated Depreciation"
- },
+ },
"2815-Installations techniques, mat\u00e9riel et outillage industriels (m\u00eame ventilation que celle du compte 218)": {
"account_type": "Accumulated Depreciation"
- },
+ },
"2818-Autres immobilisations corporelles (m\u00eame ventilation que celle du compte 218)": {
"28187-Mali de fusion sur actifs corporels": {
"account_type": "Accumulated Depreciation"
- },
+ },
"account_type": "Accumulated Depreciation"
- },
+ },
"account_type": "Accumulated Depreciation"
- },
- "282-Amortissements des immobilisations mises en concession": {},
+ },
+ "282-Amortissements des immobilisations mises en concession": {},
"account_type": "Accumulated Depreciation"
- },
+ },
"29-D\u00e9pr\u00e9ciations des immobilisations": {
"290-D\u00e9pr\u00e9ciations des immobilisations incorporelles": {
- "2905-Marques, proc\u00e9d\u00e9s, droits et valeurs similaires": {},
- "2906-Droit au bail": {},
- "2907-Fonds commercial": {},
+ "2905-Marques, proc\u00e9d\u00e9s, droits et valeurs similaires": {},
+ "2906-Droit au bail": {},
+ "2907-Fonds commercial": {},
"2908-Autres immobilisations incorporelles": {
"29081-Mali de fusion sur actifs incorporels": {}
}
- },
+ },
"291-D\u00e9pr\u00e9ciations des immobilisations corporelles (m\u00eame ventilation que celle du compte 21)": {
"2911-Terrains (autres que terrains de gisement)": {
"29187-Mali de fusion sur actifs corporels": {}
}
- },
- "292-D\u00e9pr\u00e9ciations des immobilisations mises en concession": {},
+ },
+ "292-D\u00e9pr\u00e9ciations des immobilisations mises en concession": {},
"293-D\u00e9pr\u00e9ciations des immobilisations en cours": {
- "2931-Immobilisations corporelles en cours": {},
+ "2931-Immobilisations corporelles en cours": {},
"2932-Immobilisations incorporelles en cours": {}
- },
+ },
"296-D\u00e9pr\u00e9ciations des participations et cr\u00e9ances rattach\u00e9es \u00e0 des participations": {
- "2961-Titres de participation": {},
- "2966-Autres formes de participation": {},
- "2967-Cr\u00e9ances rattach\u00e9es \u00e0 des participations (m\u00eame ventilation que celle du compte 267)": {},
+ "2961-Titres de participation": {},
+ "2966-Autres formes de participation": {},
+ "2967-Cr\u00e9ances rattach\u00e9es \u00e0 des participations (m\u00eame ventilation que celle du compte 267)": {},
"2968-Cr\u00e9ances rattach\u00e9es \u00e0 des soci\u00e9t\u00e9s en participation (m\u00eame ventilation que celle du compte 268)": {}
- },
+ },
"297-D\u00e9pr\u00e9ciations des autres immobilisations financi\u00e8res": {
- "2971-Titres immobilis\u00e9s autres que les titres immobilis\u00e9s de l'activit\u00e9 de portefeuille - droit de propri\u00e9t\u00e9": {},
- "2972-Titres immobilis\u00e9s - droit de cr\u00e9ance (m\u00eame ventilation que celle du compte 272)": {},
- "2973- Titres immobilis\u00e9s de l'activit\u00e9 de portefuille": {},
- "2974-Pr\u00eats (m\u00eame ventilation que celle du compte 274)": {},
- "2975-D\u00e9p\u00f4ts et cautionnements vers\u00e9s (m\u00eame ventilation que celle du compte 275)": {},
+ "2971-Titres immobilis\u00e9s autres que les titres immobilis\u00e9s de l'activit\u00e9 de portefeuille - droit de propri\u00e9t\u00e9": {},
+ "2972-Titres immobilis\u00e9s - droit de cr\u00e9ance (m\u00eame ventilation que celle du compte 272)": {},
+ "2973- Titres immobilis\u00e9s de l'activit\u00e9 de portefuille": {},
+ "2974-Pr\u00eats (m\u00eame ventilation que celle du compte 274)": {},
+ "2975-D\u00e9p\u00f4ts et cautionnements vers\u00e9s (m\u00eame ventilation que celle du compte 275)": {},
"2976-Autres cr\u00e9ances immobilis\u00e9es (m\u00eame ventilation que celle du compte 276)": {
"29787-Mali de fusion sur actifs financiers": {}
}
}
- },
+ },
"root_type": "Asset"
- },
+ },
"3-Comptes de Stocks et En-Cours": {
"31-Mati\u00e8res premi\u00e8res (et fournitures)": {
- "311-Mati\u00e8res (ou groupe) A": {},
- "312-Mati\u00e8res (ou groupe) B": {},
+ "311-Mati\u00e8res (ou groupe) A": {},
+ "312-Mati\u00e8res (ou groupe) B": {},
"317-Fournitures A, B, C, ...": {}
- },
+ },
"32-Autres approvisionnements": {
"321-Mat\u00e8res consommables": {
- "3211-Mati\u00e8res (ou groupe) C": {},
+ "3211-Mati\u00e8res (ou groupe) C": {},
"3212-Mati\u00e8res (ou groupe) D": {}
- },
+ },
"322-Fournitures consommables": {
- "3221-Combustibles": {},
- "3222-Produits d'entretien": {},
- "3223-Fournitures d'atelier et d'usine": {},
- "3224-Fournitures de magasin": {},
+ "3221-Combustibles": {},
+ "3222-Produits d'entretien": {},
+ "3223-Fournitures d'atelier et d'usine": {},
+ "3224-Fournitures de magasin": {},
"3225-Fournitures de bureau": {}
- },
+ },
"326-Emballages": {
- "3261-Emballages perdus": {},
- "3265-Emballages r\u00e9cup\u00e9rables non identifiables": {},
+ "3261-Emballages perdus": {},
+ "3265-Emballages r\u00e9cup\u00e9rables non identifiables": {},
"3267-Emballages \u00e0 usage mixte": {}
}
- },
+ },
"33-En-cours de production de biens": {
"331-Produits en cours": {
- "3311-Produits en cours P1": {},
+ "3311-Produits en cours P1": {},
"3312-Produits en cours P2": {}
- },
+ },
"335-Travaux en cours": {
- "Travaux en cours T1": {},
- "Travaux en cours T2": {}
+ "3351-Travaux en cours T1": {},
+ "3352-Travaux en cours T2": {}
}
- },
+ },
"34-En-cours de production de services": {
"341-Etudes en cours": {
- "3411-Etudes en cours E1": {},
+ "3411-Etudes en cours E1": {},
"3412-Etudes en cours E2": {}
- },
+ },
"345-Prestations de services en cours": {
- "3451-Prestations de services S1": {},
+ "3451-Prestations de services S1": {},
"3452-Prestations de services S2": {}
}
- },
+ },
"35-Stocks de produits": {
"351-Produits interm\u00e9diaires": {
"3511-Produits interm\u00e9diaires (ou groupe) A": {
- "account_type": "Stock",
+ "account_type": "Stock",
"is_group": 1
- },
+ },
"3512-Produits interm\u00e9diaires (ou groupe) B": {
- "account_type": "Stock",
+ "account_type": "Stock",
"is_group": 1
- },
+ },
"account_type": "Stock"
- },
+ },
"355-Produits finis": {
"3551-Produits finis (ou groupe) A": {
- "account_type": "Stock",
+ "account_type": "Stock",
"is_group": 1
- },
+ },
"3552-Produits finis (ou groupe) B": {
- "account_type": "Stock",
+ "account_type": "Stock",
"is_group": 1
- },
+ },
"account_type": "Stock"
- },
+ },
"358-Produits r\u00e9siduels (ou mati\u00e8res de r\u00e9cup\u00e9ration)": {
"3581-D\u00e9chets": {
- "account_type": "Stock",
+ "account_type": "Stock",
"is_group": 1
- },
+ },
"3585-Rebuts": {
- "account_type": "Stock",
+ "account_type": "Stock",
"is_group": 1
- },
+ },
"3586-Mati\u00e8res de r\u00e9cup\u00e9ration": {
- "account_type": "Stock",
+ "account_type": "Stock",
"is_group": 1
- },
+ },
"account_type": "Stock"
- },
+ },
"account_type": "Stock"
- },
- "36-(Compte \u00e0 ouvrir, le cas \u00e9ch\u00e9ant, sous l'intitul\u00e9 \"stocks provenant d'immobilisations\")": {},
+ },
+ "36-(Compte \u00e0 ouvrir, le cas \u00e9ch\u00e9ant, sous l'intitul\u00e9 \"stocks provenant d'immobilisations\")": {},
"37-Stocks de marchandises": {
- "371-Marchandises (ou groupe) A": {},
+ "371-Marchandises (ou groupe) A": {},
"372-Marchandises (ou groupe) B": {}
- },
- "38-Stocks en voie d'acheminement, mis en d\u00e9p\u00f4t ou donn\u00e9s en consignation (en cas d'inventaire permanent en comptabilit\u00e9 g\u00e9n\u00e9rale)": {},
+ },
+ "38-Stocks en voie d'acheminement, mis en d\u00e9p\u00f4t ou donn\u00e9s en consignation (en cas d'inventaire permanent en comptabilit\u00e9 g\u00e9n\u00e9rale)": {
+ "account_type": "Stock"
+ },
"39-D\u00e9pr\u00e9ciations des stocks et en-cours": {
"391-D\u00e9pr\u00e9ciations des mati\u00e8res premi\u00e8res (et fournitures)": {
- "3911-Mati\u00e8res (ou groupe) A": {},
- "3912-Mati\u00e8res (ou groupe) B": {},
+ "3911-Mati\u00e8res (ou groupe) A": {},
+ "3912-Mati\u00e8res (ou groupe) B": {},
"3917-Fournitures A, B, C, ...": {}
- },
+ },
"392-D\u00e9pr\u00e9ciations des autres approvisionnements": {
- "3921-Mati\u00e8res consommables (m\u00eame ventilation que celle du compte 321)": {},
- "3922-Fournitures consommables (m\u00eame ventilation que celle du compte 322)": {},
+ "3921-Mati\u00e8res consommables (m\u00eame ventilation que celle du compte 321)": {},
+ "3922-Fournitures consommables (m\u00eame ventilation que celle du compte 322)": {},
"3926-Emballages (m\u00eame ventilation que celle du compte 326)": {}
- },
+ },
"393-D\u00e9pr\u00e9ciations des en-cours de production de biens": {
- "3931-Etudes en cours (m\u00eame ventilation que celle du compte 341)": {},
+ "3931-Etudes en cours (m\u00eame ventilation que celle du compte 341)": {},
"3935-Travaux en cours (m\u00eame ventilation que celle du compte 335)": {}
- },
+ },
"394-D\u00e9pr\u00e9ciations des en-cours de production de services": {
- "3941-Etudes en cours (m\u00eame ventilation que celle du compte 341)": {},
+ "3941-Etudes en cours (m\u00eame ventilation que celle du compte 341)": {},
"3945-Prestations de services en cours (m\u00eame ventilation que celle du compte 345)": {}
- },
+ },
"395-D\u00e9pr\u00e9ciations des stocks de produits": {
- "3951-Produits interm\u00e9diaires (m\u00eame ventilation que celle du compte 351)": {},
+ "3951-Produits interm\u00e9diaires (m\u00eame ventilation que celle du compte 351)": {},
"3955-Produits finis (m\u00eame ventilation que celle du compte 355)": {}
- },
+ },
"397-D\u00e9pr\u00e9ciations des stocks de marchandises": {
- "3971-Marchandise (ou groupe) A": {},
+ "3971-Marchandise (ou groupe) A": {},
"3972-Marchandise (ou groupe) B": {}
}
- },
+ },
"root_type": "Asset"
- },
+ },
"4-Comptes de Tiers (ACTIF)": {
"40-Fournisseurs et Comptes Rattach\u00e9s (ACTIF)": {
"409-Fournisseurs d\u00e9biteurs": {
- "4091-Fournisseurs - Avances et acomptes vers\u00e9s sur commandes": {},
- "4096-Fournisseurs - Cr\u00e9ances pour emballages et mat\u00e9riel \u00e0 rendre": {},
+ "4091-Fournisseurs - Avances et acomptes vers\u00e9s sur commandes": {},
+ "4096-Fournisseurs - Cr\u00e9ances pour emballages et mat\u00e9riel \u00e0 rendre": {},
"4097-Fournisseurs - Autres avoirs": {
- "40971-Fournisseurs d'exploitation": {},
+ "40971-Fournisseurs d'exploitation": {},
"40974-Fournisseurs d'immobilisation": {}
- },
+ },
"4098-Rabais, remises, ristournes \u00e0 obtenir et autres avoirs non encore re\u00e7us": {}
}
- },
+ },
"41-Clients et comptes rattach\u00e9s (ACTIF)": {
"410-Clients et Comptes rattach\u00e9s": {
"account_type": "Receivable"
- },
+ },
"411-Clients": {
"4111-Clients - Ventes de biens ou de prestations de services": {
"account_type": "Receivable"
- },
+ },
"4117-Clients - Retenues de garantie": {
"account_type": "Receivable"
- },
+ },
"account_type": "Receivable"
- },
+ },
"413-Clients - Effets \u00e0 recevoir": {
"account_type": "Receivable"
- },
+ },
"416-Clients douteux ou litigieux": {
"account_type": "Receivable"
- },
+ },
"418-Clients - Produits non encore factur\u00e9s": {
"4181-Clients - Factures \u00e0 \u00e9tablir": {
"account_type": "Receivable"
- },
+ },
"4188-Clients - Int\u00e9r\u00eats courus": {
"account_type": "Receivable"
- },
+ },
"account_type": "Receivable"
- },
+ },
"account_type": "Receivable"
- },
+ },
"42-Personnel et comptes rattach\u00e9s (ACTIF)": {
"425-Personnel - Avances et acomptes": {}
- },
+ },
"43-S\u00e9curit\u00e9 sociale et autres organismes sociaux (ACTIF)": {
- "431-S\u00e9curit\u00e9 sociale": {},
- "437-Autres organismes sociaux": {},
+ "431-S\u00e9curit\u00e9 sociale": {},
+ "437-Autres organismes sociaux": {},
"438-Organismes sociaux - Produits \u00e0 recevoir": {
"4387-Produits \u00e0 recevoir": {}
}
- },
+ },
"44-Etat et autres collectivit\u00e9s publiques (ACTIF)": {
"441-Etat - Subventions \u00e0 recevoir": {
- "4411-Subventions d'investissement": {},
- "4417-Subventions d'exploitation": {},
- "4418-Subventions d'\u00e9quilibre": {},
+ "4411-Subventions d'investissement": {},
+ "4417-Subventions d'exploitation": {},
+ "4418-Subventions d'\u00e9quilibre": {},
"4419-Avances sur subventions": {}
- },
+ },
"443-Op\u00e9rations particuli\u00e8res avec l'Etat, les collectivit\u00e9s publiques, les organismes internationaux": {
- "4431-Cr\u00e9ances sur l'Etat r\u00e9sultant de la suppression de la r\u00e8gle du d\u00e9calage d'un mois en mati\u00e8re de TVA": {},
+ "4431-Cr\u00e9ances sur l'Etat r\u00e9sultant de la suppression de la r\u00e8gle du d\u00e9calage d'un mois en mati\u00e8re de TVA": {},
"4438-Int\u00e9r\u00eats courus sur cr\u00e9ances figurant au compte 4431": {}
- },
+ },
"445-Etat - Taxes sur le chiffre d'affaires (ACTIF)": {
- "4452-TVA due intracommunautaire": {},
+ "4452-TVA due intracommunautaire": {},
"4456-Taxes sur le chiffre d'affaires d\u00e9ductibles": {
- "44562-TVA sur immobilisations": {},
- "44563-TVA transf\u00e9r\u00e9e par d'autres entreprises": {},
+ "44562-TVA sur immobilisations": {},
+ "44563-TVA transf\u00e9r\u00e9e par d'autres entreprises": {},
"44566-TVA sur autres biens et services": {
"tax_rate": 20.0
- },
- "44567-Cr\u00e9dit de TVA \u00e0 reporter": {},
+ },
+ "44567-Cr\u00e9dit de TVA \u00e0 reporter": {},
"44568-Taxes assimil\u00e9es \u00e0 la TVA": {}
- },
+ },
"4458-Taxes sur le chiffre d'affaires \u00e0 r\u00e9gulariser ou en attente (ACTIF)": {
- "44581-Acomptes - R\u00e9gime simplifi\u00e9 d'imposition": {},
- "44582-Acomptes - R\u00e9gime du forfait": {},
- "44583-Remboursement de taxes sur le chiffre d'affaires demand\u00e9": {},
+ "44581-Acomptes - R\u00e9gime simplifi\u00e9 d'imposition": {},
+ "44582-Acomptes - R\u00e9gime du forfait": {},
+ "44583-Remboursement de taxes sur le chiffre d'affaires demand\u00e9": {},
"44586-Taxes sur le chiffre d'affaires sur factures non parvenues": {}
}
- },
+ },
"448-Etat - Charges \u00e0 payer et produits \u00e0 recevoir": {
- "4482-Charges fiscales sur cong\u00e9s \u00e0 payer": {},
- "4486-Charges \u00e0 payer": {},
+ "4482-Charges fiscales sur cong\u00e9s \u00e0 payer": {},
+ "4486-Charges \u00e0 payer": {},
"4487-Produits \u00e0 recevoir": {}
}
- },
+ },
"45-Groupe et associ\u00e9s (ACTIF)": {
"456-Associ\u00e9s - Op\u00e9rations sur le capital (ACTIF)": {
"4562-Apporteurs - Capital appel\u00e9, non vers\u00e9": {
- "45621-Actionnaires - Capital souscrit et appel\u00e9, non vers\u00e9": {},
+ "45621-Actionnaires - Capital souscrit et appel\u00e9, non vers\u00e9": {},
"45625-Associ\u00e9s - Capital appel\u00e9, non vers\u00e9": {}
}
}
- },
+ },
"46-D\u00e9biteurs divers et cr\u00e9diteurs divers (ACTIF)": {
- "462-Cr\u00e9ances sur cessions d'immobilisations": {},
- "465-Cr\u00e9ances sur cessions de valeurs mobili\u00e8res de placement": {},
- "467-Autres comptes d\u00e9biteurs ou cr\u00e9diteurs (ACTIF)": {},
+ "462-Cr\u00e9ances sur cessions d'immobilisations": {},
+ "465-Cr\u00e9ances sur cessions de valeurs mobili\u00e8res de placement": {},
+ "467-Autres comptes d\u00e9biteurs ou cr\u00e9diteurs (ACTIF)": {},
"468-Divers - Charges \u00e0 payer et produits \u00e0 recevoir (ACTIF)": {
"4687-Produits \u00e0 recevoir": {}
}
- },
+ },
"47-Comptes transitoires ou d'attente (ACTIF)": {
"471-Comptes d'attente (ACTIF)": {
"account_type": "Temporary"
- },
+ },
"476-Diff\u00e9rences de conversion (ACTIF)": {
- "4761-Diminution des cr\u00e9ances": {},
- "4762-Augmentation des dettes": {},
+ "4761-Diminution des cr\u00e9ances": {},
+ "4762-Augmentation des dettes": {},
"4768-Diff\u00e9rences compens\u00e9es par couverture de change": {}
- },
+ },
"478-Autres comptes transitoires (ACTIF)": {
- "4781-Mali de fusion sur actif circulant": {},
+ "4781-Mali de fusion sur actif circulant": {},
"4786-Diff\u00e9rences d'\u00e9valuation sur instruments de tr\u00e9sorerie (ACTIF)": {}
}
- },
+ },
"48-Comptes de r\u00e9gularisation (ACTIF)": {
"481-Charges \u00e0 r\u00e9partir sur plusieurs exercices": {
"4816-Frais d'\u00e9mission des emprunts": {}
- },
- "486-Charges constat\u00e9es d'avance": {},
+ },
+ "486-Charges constat\u00e9es d'avance": {},
"488-Comptes de r\u00e9partition p\u00e9riodique des charges et des produits (ACTIF)": {
"4886-Charges": {}
}
- },
+ },
"49-D\u00e9pr\u00e9ciation des comptes de tiers (ACTIF)": {
- "491-D\u00e9pr\u00e9ciations des comptes clients": {},
+ "491-D\u00e9pr\u00e9ciations des comptes clients": {},
"495-D\u00e9pr\u00e9ciations des comptes du groupe et des associ\u00e9s": {
- "4951-Comptes du groupe": {},
- "4955-Comptes courants des associ\u00e9s": {},
+ "4951-Comptes du groupe": {},
+ "4955-Comptes courants des associ\u00e9s": {},
"4958-Op\u00e9rations faites en commun et en GIE": {}
- },
+ },
"496-D\u00e9pr\u00e9ciations des comptes de d\u00e9biteurs divers": {
- "4962-Cr\u00e9ances sur cessions d'immobilisations": {},
- "4965-Cr\u00e9ances sur cessions de valeurs mobili\u00e8res de placement": {},
+ "4962-Cr\u00e9ances sur cessions d'immobilisations": {},
+ "4965-Cr\u00e9ances sur cessions de valeurs mobili\u00e8res de placement": {},
"4967-Autres comptes d\u00e9biteurs": {}
}
- },
+ },
"root_type": "Asset"
- },
+ },
"4-Comptes de Tiers (PASSIF)": {
"40-Fournisseurs et Comptes Rattach\u00e9s (PASSIF)": {
"401-Fournisseurs": {
"4011-Fournisseurs - Achats de biens ou de prestations de services": {
"account_type": "Payable"
- },
+ },
"4017-Fournisseurs - Retenues de garantie": {
"account_type": "Payable"
- },
+ },
"account_type": "Payable"
- },
+ },
"403-Fournisseurs - Effets \u00e0 payer": {
"account_type": "Payable"
- },
+ },
"404-Fournisseurs d'immobilisations": {
"4041-Fournisseurs - Achats d'immobilisations": {
"account_type": "Payable"
- },
+ },
"4047-Fournisseurs d'immobilisations - Retenues de garantie": {
"account_type": "Payable"
- },
+ },
"account_type": "Payable"
- },
+ },
"405-Fournisseurs d'immobilisations - Effets \u00e0 payer": {
"account_type": "Payable"
- },
+ },
"408-Fournisseurs - Factures non parvenues": {
"4081-Fournisseurs": {
"account_type": "Stock Received But Not Billed"
- },
+ },
"4084-Fournisseurs d'immobilisations": {
"account_type": "Stock Received But Not Billed"
- },
+ },
"4088-Fournisseurs - Int\u00e9r\u00eats courus": {
"account_type": "Stock Received But Not Billed"
- },
+ },
"account_type": "Stock Received But Not Billed"
- },
+ },
"account_type": "Payable"
- },
+ },
"41-Clients et comptes rattach\u00e9s (PASSIF)": {
"419-Clients cr\u00e9diteurs": {
- "4191-Clients - Avances et acomptes re\u00e7us sur commandes": {},
- "4196-Clients - Dettes pour emballages et mat\u00e9riels consign\u00e9s": {},
- "4197-Clients - Autres avoirs": {},
+ "4191-Clients - Avances et acomptes re\u00e7us sur commandes": {},
+ "4196-Clients - Dettes pour emballages et mat\u00e9riels consign\u00e9s": {},
+ "4197-Clients - Autres avoirs": {},
"4198-Rabais, remises, ristournes \u00e0 accorder et autres avoirs \u00e0 \u00e9tablir": {}
}
- },
+ },
"42-Personnel et comptes rattach\u00e9s (PASSIF)": {
- "421-Personnel - R\u00e9mun\u00e9rations dues": {},
- "422-Comit\u00e9s d'entreprises, d'\u00e9tablissement...": {},
+ "421-Personnel - R\u00e9mun\u00e9rations dues": {},
+ "422-Comit\u00e9s d'entreprises, d'\u00e9tablissement...": {},
"424-Participation des salari\u00e9s aux r\u00e9sultats": {
- "4246-R\u00e9serve sp\u00e9ciale": {},
+ "4246-R\u00e9serve sp\u00e9ciale": {},
"4248-Comptes courants": {}
- },
- "426-Personnel - D\u00e9p\u00f4ts": {},
- "427-Personnel - Oppositions": {},
+ },
+ "426-Personnel - D\u00e9p\u00f4ts": {},
+ "427-Personnel - Oppositions": {},
"428-Personnel - Charges \u00e0 payer et produits \u00e0 recevoir": {
- "4282-Dettes provisionn\u00e9es pour cong\u00e9s \u00e0 payer": {},
- "4284-Dettes provisionn\u00e9es pour participation des salari\u00e9s aux r\u00e9sultats": {},
- "4286-Autres charges \u00e0 payer": {},
+ "4282-Dettes provisionn\u00e9es pour cong\u00e9s \u00e0 payer": {},
+ "4284-Dettes provisionn\u00e9es pour participation des salari\u00e9s aux r\u00e9sultats": {},
+ "4286-Autres charges \u00e0 payer": {},
"4287-Produits \u00e0 recevoir": {}
}
- },
+ },
"43-S\u00e9curit\u00e9 sociale et autres organismes sociaux (PASSIF)": {
"438-Organismes sociaux - Charges \u00e0 payer": {
- "4382-Charges sociales sur cong\u00e9s \u00e0 payer": {},
+ "4382-Charges sociales sur cong\u00e9s \u00e0 payer": {},
"4386-Autres charges \u00e0 payer": {}
}
- },
+ },
"44-Etat et autres collectivit\u00e9s publiques (PASSIF)": {
"442-Etat - Imp\u00f4ts et taxes recouvrables sur des tiers": {
- "4424-Obligataires": {},
+ "4424-Obligataires": {},
"4425-Associ\u00e9s": {}
- },
- "444-Etat - Imp\u00f4ts sur les b\u00e9n\u00e9fices": {},
+ },
+ "444-Etat - Imp\u00f4ts sur les b\u00e9n\u00e9fices": {},
"445-Etat - Taxes sur le chiffre d'affaires (PASSIF)": {
"4455-Taxes sur le chiffre d'affaires \u00e0 d\u00e9caisser": {
- "44551-TVA \u00e0 d\u00e9caisser": {},
+ "44551-TVA \u00e0 d\u00e9caisser": {},
"44558-Taxes assimil\u00e9es \u00e0 la TVA": {}
- },
+ },
"4457-Taxes sur le chiffre d'affaires collect\u00e9es par l'entreprise": {
"44571-TVA collect\u00e9e": {
- "account_type": "Tax",
+ "account_type": "Tax",
"is_group": 1
- },
+ },
"44578-Taxes assimil\u00e9es \u00e0 la TVA": {}
- },
+ },
"4458-Taxes sur le chiffre d'affaires \u00e0 r\u00e9gulariser ou en attente (PASSIF)": {
- "44584-TVA r\u00e9cup\u00e9r\u00e9e d'avance": {},
+ "44584-TVA r\u00e9cup\u00e9r\u00e9e d'avance": {},
"44587-Taxes sur le chiffre d'affaires sur factures \u00e0 \u00e9tablir": {}
}
- },
- "446-Obligations cautionn\u00e9es": {},
- "447-Autres imp\u00f4ts, taxes et versements assimil\u00e9s": {},
+ },
+ "446-Obligations cautionn\u00e9es": {},
+ "447-Autres imp\u00f4ts, taxes et versements assimil\u00e9s": {},
"449-Quotas d'\u00e9mission \u00e0 acqu\u00e9rir": {}
- },
+ },
"45-Groupe et associ\u00e9s (PASSIF)": {
- "451-Groupe (PASSIF)": {},
+ "451-Groupe (PASSIF)": {},
"455-Associ\u00e9s - Comptes courants (PASSIF)": {
- "4551-Principal (PASSIF)": {},
+ "4551-Principal (PASSIF)": {},
"4558-Int\u00e9r\u00eats courus (PASSIF)": {}
- },
+ },
"456-Associ\u00e9s - Op\u00e9rations sur le capital (PASSIF)": {
"4561-Associ\u00e9s - Comptes d'apport en soci\u00e9t\u00e9": {
- "45611-Apports en nature": {},
+ "45611-Apports en nature": {},
"45615-Apports en num\u00e9raire": {}
- },
- "4563-Associ\u00e9s - Versements re\u00e7us sur augmentation de capital": {},
- "4564-Associ\u00e9s - Versements anticip\u00e9s": {},
- "4566-Actionnaires d\u00e9faillants": {},
+ },
+ "4563-Associ\u00e9s - Versements re\u00e7us sur augmentation de capital": {},
+ "4564-Associ\u00e9s - Versements anticip\u00e9s": {},
+ "4566-Actionnaires d\u00e9faillants": {},
"4567-Associ\u00e9s - Capital \u00e0 rembourser": {}
- },
- "457-Associ\u00e9s - Dividendes \u00e0 payer": {},
+ },
+ "457-Associ\u00e9s - Dividendes \u00e0 payer": {},
"458-Associ\u00e9s - Op\u00e9rations faites en commun et en GIE": {
- "4581-Op\u00e9rations courantes": {},
+ "4581-Op\u00e9rations courantes": {},
"4588-Int\u00e9r\u00eats courus": {}
}
- },
+ },
"46-D\u00e9biteurs divers et cr\u00e9diteurs divers (PASSIF)": {
- "464-Dettes sur acquisitions de valeurs mobili\u00e8res de placement": {},
- "467-Autres comptes d\u00e9biteurs ou cr\u00e9diteurs (PASSIF)": {},
+ "464-Dettes sur acquisitions de valeurs mobili\u00e8res de placement": {},
+ "467-Autres comptes d\u00e9biteurs ou cr\u00e9diteurs (PASSIF)": {},
"468-Divers - Charges \u00e0 payer et produits \u00e0 recevoir (PASSIF)": {
"4686-Charges \u00e0 payer": {}
}
- },
+ },
"47-Comptes transitoires ou d'attente (PASSIF)": {
"471-Comptes d'attente (PASSIF)": {
"account_type": "Temporary"
- },
+ },
"477-Diff\u00e9rences de conversion (PASSIF)": {
- "4771-Augmentation des cr\u00e9ances": {},
- "4772-Diminution des dettes": {},
+ "4771-Augmentation des cr\u00e9ances": {},
+ "4772-Diminution des dettes": {},
"4778-Diff\u00e9rences compens\u00e9es par couverture de change": {}
- },
+ },
"478-Autres comptes transitoires (PASSIF)": {
"4787-Diff\u00e9rences d'\u00e9valuation sur instruments de tr\u00e9sorerie (PASSIF)": {}
}
- },
+ },
"48-Comptes de r\u00e9gularisation (PASSIF)": {
- "487-Produits constat\u00e9s d'avance": {},
+ "487-Produits constat\u00e9s d'avance": {},
"488-Comptes de r\u00e9partition p\u00e9riodique des charges et des produits (PASSIF)": {
"4887-Produits": {}
}
- },
+ },
"root_type": "Liability"
- },
+ },
"5-Comptes Financiers": {
"50-Valeurs mobili\u00e8res de placement": {
- "501-Parts dans des entreprises li\u00e9es": {},
+ "501-Parts dans des entreprises li\u00e9es": {},
"502-Actions propres": {
- "5021-Actions destin\u00e9es \u00e0 \u00eatre attribu\u00e9es aux employ\u00e9s et affect\u00e9es \u00e0 des plans d\u00e9termin\u00e9s": {},
+ "5021-Actions destin\u00e9es \u00e0 \u00eatre attribu\u00e9es aux employ\u00e9s et affect\u00e9es \u00e0 des plans d\u00e9termin\u00e9s": {},
"5022-Actions disponibles pour \u00eatre attribu\u00e9es aux employ\u00e9s ou pour la r\u00e9gularisation des cours de bourse": {}
- },
+ },
"503-Actions": {
- "5031-Titres cot\u00e9s": {},
+ "5031-Titres cot\u00e9s": {},
"5035-Titres non cot\u00e9s": {}
- },
- "504-Autres titres conf\u00e9rant un droit de propri\u00e9t\u00e9": {},
- "505-Obligations et bons \u00e9mis par la soci\u00e9t\u00e9 et rachet\u00e9s par elle": {},
+ },
+ "504-Autres titres conf\u00e9rant un droit de propri\u00e9t\u00e9": {},
+ "505-Obligations et bons \u00e9mis par la soci\u00e9t\u00e9 et rachet\u00e9s par elle": {},
"506-Obligations": {
- "5061-Titres cot\u00e9s": {},
+ "5061-Titres cot\u00e9s": {},
"5065-Titres non cot\u00e9s": {}
- },
- "507-Bons du Tr\u00e9sor et bons de caisse \u00e0 court terme": {},
+ },
+ "507-Bons du Tr\u00e9sor et bons de caisse \u00e0 court terme": {},
"508-Autres valeurs mobili\u00e8res de placement et autres cr\u00e9ances assimil\u00e9es": {
- "5081-Autres valeurs mobili\u00e8res": {},
- "5082-Bons de souscription": {},
+ "5081-Autres valeurs mobili\u00e8res": {},
+ "5082-Bons de souscription": {},
"5088-Int\u00e9r\u00eats courus sur obligations, bons et valeurs assimil\u00e9es": {}
- },
+ },
"509-Versements restant \u00e0 effectuer sur valeurs mobili\u00e8res de placement non lib\u00e9r\u00e9es": {}
- },
+ },
"51-Banques, \u00e9tablissements financiers et assimil\u00e9s": {
"511-Valeurs \u00e0 l'encaissement": {
- "5111-Coupons \u00e9chus \u00e0 l'encaissement": {},
- "5112-Ch\u00e8ques \u00e0 encaisser": {},
- "5113-Effets \u00e0 l'encaissement": {},
+ "5111-Coupons \u00e9chus \u00e0 l'encaissement": {},
+ "5112-Ch\u00e8ques \u00e0 encaisser": {},
+ "5113-Effets \u00e0 l'encaissement": {},
"5114-Effets \u00e0 l'escompte": {}
- },
+ },
"512-Banques": {
"5121-Comptes en monnaie nationale": {
"account_type": "Bank"
- },
+ },
"5124-Comptes en devises": {
"account_type": "Bank"
- },
+ },
"account_type": "Bank"
- },
- "514-Ch\u00e8ques postaux": {},
- "515-\"Caisses\" du Tr\u00e9sor et des \u00e9tablissements publics": {},
- "516-Soci\u00e9t\u00e9s de bourse": {},
- "517-Autres organismes financiers": {},
+ },
+ "514-Ch\u00e8ques postaux": {},
+ "515-\"Caisses\" du Tr\u00e9sor et des \u00e9tablissements publics": {},
+ "516-Soci\u00e9t\u00e9s de bourse": {},
+ "517-Autres organismes financiers": {},
"518-Int\u00e9r\u00eats courus": {
- "5181-Int\u00e9r\u00eats courus \u00e0 payer": {},
+ "5181-Int\u00e9r\u00eats courus \u00e0 payer": {},
"5188-Int\u00e9r\u00eats courus \u00e0 recevoir": {}
- },
+ },
"519-Concours bancaires courants": {
- "5191-Cr\u00e9dit de mobilisation des cr\u00e9ances commerciales (CMCC)": {},
- "5193-Mobilisation de cr\u00e9ances n\u00e9es \u00e0 l'\u00e9tranger": {},
+ "5191-Cr\u00e9dit de mobilisation des cr\u00e9ances commerciales (CMCC)": {},
+ "5193-Mobilisation de cr\u00e9ances n\u00e9es \u00e0 l'\u00e9tranger": {},
"5198-Int\u00e9r\u00eats courus sur concours bancaires courants": {}
}
- },
+ },
"52-Instruments de tr\u00e9sorerie": {
"is_group": 1
- },
+ },
"53-Caisse": {
"531-Caisse si\u00e8ge social": {
"5311-Caisse en monnaie nationale": {
"account_type": "Cash"
- },
+ },
"5314-Caisse en devises": {
"account_type": "Cash"
- },
+ },
"account_type": "Cash"
- },
+ },
"532-Caisse succursale (ou usine) A": {
"account_type": "Cash"
- },
+ },
"533-Caisse succursale (ou usine) B": {
"account_type": "Cash"
- },
+ },
"account_type": "Cash"
- },
+ },
"54-R\u00e9gies d'avance et accr\u00e9ditifs": {
"is_group": 1
- },
+ },
"58-Virements internes": {
"is_group": 1
- },
+ },
"59-D\u00e9pr\u00e9ciations des comptes financiers": {
"590-D\u00e9pr\u00e9ciations des valeurs mobili\u00e8res de placement": {
- "5903-Actions": {},
- "5904-Autres titres conf\u00e9rant un droit de propri\u00e9t\u00e9": {},
- "5906-Obligations": {},
+ "5903-Actions": {},
+ "5904-Autres titres conf\u00e9rant un droit de propri\u00e9t\u00e9": {},
+ "5906-Obligations": {},
"5908-Autres valeurs mobili\u00e8res de placement et cr\u00e9ances assimil\u00e9es": {}
}
- },
+ },
"root_type": "Asset"
- },
+ },
"6-Comptes de Charges": {
"60-Achats (sauf 603)": {
"601-Achats stock\u00e9s - Mati\u00e8res premi\u00e8res (et fournitures)": {
"6011-Mati\u00e8res (ou groupe) A": {
"account_type": "Cost of Goods Sold"
- },
+ },
"6012-Mati\u00e8res (ou groupe) B": {
"account_type": "Cost of Goods Sold"
- },
+ },
"6017-Fournitures A, B, C...": {
"account_type": "Cost of Goods Sold"
- },
+ },
"account_type": "Cost of Goods Sold"
- },
+ },
"602-Achats stock\u00e9s - Autres approvisionnements": {
"6021-Mati\u00e8res consommables": {
"60211-Mati\u00e8res (ou groupe) C": {
"account_type": "Cost of Goods Sold"
- },
+ },
"60212-Mati\u00e8res (ou groupe) D": {
"account_type": "Cost of Goods Sold"
- },
+ },
"account_type": "Cost of Goods Sold"
- },
+ },
"6022-Fournitures consommables": {
"60221-Combustibles": {
"account_type": "Cost of Goods Sold"
- },
+ },
"60222-Produits d'entretien": {
"account_type": "Cost of Goods Sold"
- },
+ },
"60223-Fournitures d'atelier et d'usine": {
"account_type": "Cost of Goods Sold"
- },
+ },
"60224-Fournitures de magasin": {
"account_type": "Cost of Goods Sold"
- },
+ },
"60225-Fournitures de bureau": {
"account_type": "Cost of Goods Sold"
- },
+ },
"account_type": "Cost of Goods Sold"
- },
+ },
"6026-Emballages": {
"60261-Emballages perdus": {
"account_type": "Cost of Goods Sold"
- },
+ },
"60265-Emballages r\u00e9cup\u00e9rables non identifiables": {
"account_type": "Cost of Goods Sold"
- },
+ },
"60267-Emballages \u00e0 usage mixte": {
"account_type": "Cost of Goods Sold"
- },
+ },
"account_type": "Cost of Goods Sold"
- },
+ },
"account_type": "Cost of Goods Sold"
- },
+ },
"603-Variations des stocks (approvisionnements et marchandises)": {
"6031-Variation des stocks de mati\u00e8res premi\u00e8res (et fournitures)": {
"account_type": "Stock Adjustment"
- },
+ },
"6032-Variation des stocks des autres approvisionnements": {
"account_type": "Stock Adjustment"
- },
+ },
"6037-Variation des stocks de marchandises": {
"account_type": "Stock Adjustment"
- },
+ },
"account_type": "Stock Adjustment"
- },
+ },
"604-Achats d'\u00e9tudes et prestations de service": {
"account_type": "Cost of Goods Sold"
- },
+ },
"605-Achats de mat\u00e9riel, \u00e9quipements et travaux": {
"account_type": "Cost of Goods Sold"
- },
+ },
"606-Achats non stock\u00e9s de mati\u00e8res et founitures": {
"6061-Fournitures non stockables (eau, \u00e9nergie...)": {
"account_type": "Cost of Goods Sold"
- },
+ },
"6063-Fournitures d'entretien et de petit \u00e9quipement": {
"account_type": "Cost of Goods Sold"
- },
+ },
"6064-Fournitures administratives": {
"account_type": "Cost of Goods Sold"
- },
+ },
"6068-Autres mati\u00e8res et fournitures": {
"account_type": "Cost of Goods Sold"
- },
+ },
"account_type": "Cost of Goods Sold"
- },
+ },
"607-Achats de marchandises": {
"6071-Marchandises (ou groupe) A": {
"account_type": "Cost of Goods Sold"
- },
+ },
"6072-Marchandises (ou groupe) B": {
"account_type": "Cost of Goods Sold"
- },
+ },
"account_type": "Cost of Goods Sold"
- },
+ },
"608-(Compte r\u00e9serv\u00e9, le cas \u00e9ch\u00e9ant, \u00e0 la recapitulation des Frais accessoires incorpor\u00e9s aux achats)": {
"account_type": "Expenses Included In Valuation"
- },
+ },
"609-Rabais, remises et ristournes obtenus sur achats": {
- "6091-Rabais, remises et ristournes obtenus sur achats - de mati\u00e8res premi\u00e8res (et fournitures)": {},
- "6092-Rabais, remises et ristournes obtenus sur achats - d'autres approvisionnements stock\u00e9s": {},
- "6094-Rabais, remises et ristournes obtenus sur achats - d'\u00e9tudes et prestations de services": {},
- "6095-Rabais, remises et ristournes obtenus sur achats - de mat\u00e9riel, \u00e9quipements et travaux": {},
- "6096-Rabais, remises et ristournes obtenus sur achats - d'approvisionnements non stock\u00e9s": {},
- "6097-Rabais, remises et ristournes obtenus sur achats - de marchandises": {},
+ "6091-Rabais, remises et ristournes obtenus sur achats - de mati\u00e8res premi\u00e8res (et fournitures)": {},
+ "6092-Rabais, remises et ristournes obtenus sur achats - d'autres approvisionnements stock\u00e9s": {},
+ "6094-Rabais, remises et ristournes obtenus sur achats - d'\u00e9tudes et prestations de services": {},
+ "6095-Rabais, remises et ristournes obtenus sur achats - de mat\u00e9riel, \u00e9quipements et travaux": {},
+ "6096-Rabais, remises et ristournes obtenus sur achats - d'approvisionnements non stock\u00e9s": {},
+ "6097-Rabais, remises et ristournes obtenus sur achats - de marchandises": {},
"6098-Rabais, remises et ristournes non affect\u00e9s": {}
}
- },
+ },
"61-Services ext\u00e9rieurs": {
- "611-Sous-traitance g\u00e9n\u00e9rale": {},
+ "611-Sous-traitance g\u00e9n\u00e9rale": {},
"612-Redevances de cr\u00e9dit-bail": {
- "6122-Cr\u00e9dit-bail mobilier": {},
+ "6122-Cr\u00e9dit-bail mobilier": {},
"6125-Cr\u00e9dit-bail immobilier": {}
- },
+ },
"613-Locations": {
- "6132-Locations immobili\u00e8res": {},
- "6135-Locations mobili\u00e8res": {},
+ "6132-Locations immobili\u00e8res": {},
+ "6135-Locations mobili\u00e8res": {},
"6136-Malis sur emballages": {}
- },
- "614-Charges locatives et de copropri\u00e9t\u00e9": {},
+ },
+ "614-Charges locatives et de copropri\u00e9t\u00e9": {},
"615-Entretiens et r\u00e9parations": {
- "6152-Entretiens et r\u00e9parations - sur biens immobiliers": {},
- "6155-Entretiens et r\u00e9parations - sur biens mobiliers": {},
+ "6152-Entretiens et r\u00e9parations - sur biens immobiliers": {},
+ "6155-Entretiens et r\u00e9parations - sur biens mobiliers": {},
"6156-Maintenance": {}
- },
+ },
"616-Primes d'assurance": {
- "6161-Multirisques": {},
- "6162-Assurance obligatoire dommage construction": {},
+ "6161-Multirisques": {},
+ "6162-Assurance obligatoire dommage construction": {},
"6163-Assurance-transport": {
- "61636-Assurance-transport - sur achats": {},
- "61637-Assurance-transport - sur ventes": {},
+ "61636-Assurance-transport - sur achats": {},
+ "61637-Assurance-transport - sur ventes": {},
"61638-Assurance-transport - sur autres biens": {}
- },
- "6164-Risques d'exploitation": {},
+ },
+ "6164-Risques d'exploitation": {},
"6165-Insolvabilit\u00e9 clients": {}
- },
- "617-Etudes et recherches": {},
+ },
+ "617-Etudes et recherches": {},
"618-Divers": {
- "6181-Documentation g\u00e9n\u00e9rale": {},
- "6183-Documentation technique": {},
+ "6181-Documentation g\u00e9n\u00e9rale": {},
+ "6183-Documentation technique": {},
"6185-Frais de colloques, s\u00e9minaires, conf\u00e9rences": {}
- },
+ },
"619-Rabais, remises et ristournes obtenus sur services ext\u00e9rieurs": {}
- },
+ },
"62-Autres services ext\u00e9rieurs": {
"621-Personnel ext\u00e9rieur \u00e0 l'entreprise": {
- "6211-Personnel int\u00e9rimaire": {},
+ "6211-Personnel int\u00e9rimaire": {},
"6214-Personnel d\u00e9tach\u00e9 ou pr\u00eat\u00e9 \u00e0 l'entreprise": {}
- },
+ },
"622-R\u00e9mun\u00e9rations d'interm\u00e9diaires et honoraires": {
- "6221-Commissions et courtages sur achats": {},
- "6222-Commissions et courtages sur ventes": {},
- "6224-R\u00e9mun\u00e9rations des transitaires": {},
- "6225-R\u00e9mun\u00e9rations d'affacturage": {},
- "6226-Honoraires": {},
- "6227-Frais d'actes et de contentieux": {},
+ "6221-Commissions et courtages sur achats": {},
+ "6222-Commissions et courtages sur ventes": {},
+ "6224-R\u00e9mun\u00e9rations des transitaires": {},
+ "6225-R\u00e9mun\u00e9rations d'affacturage": {},
+ "6226-Honoraires": {},
+ "6227-Frais d'actes et de contentieux": {},
"6228-Divers": {}
- },
+ },
"623-Publicit\u00e9, publications, relations publiques": {
- "6231-Annonces et insertions": {},
- "6232-Echantillons": {},
- "6233-Foires et expositions": {},
- "6234-Cadeaux \u00e0 la client\u00e8le": {},
- "6235-Primes": {},
- "6236-Catalogues et imprim\u00e9s": {},
- "6237-Publications": {},
+ "6231-Annonces et insertions": {},
+ "6232-Echantillons": {},
+ "6233-Foires et expositions": {},
+ "6234-Cadeaux \u00e0 la client\u00e8le": {},
+ "6235-Primes": {},
+ "6236-Catalogues et imprim\u00e9s": {},
+ "6237-Publications": {},
"6238-Divers (pourboires, dons courants...)": {}
- },
+ },
"624-Transports de biens et transports collectifs du personnel": {
- "6241-Transports sur achats": {},
+ "6241-Transports sur achats": {},
"6242-Transports sur ventes": {
"account_type": "Chargeable"
- },
- "6243-Transports entre \u00e9tablissements ou chantiers": {},
- "6244-Transports administratifs": {},
- "6247-Transports collectifs du personnel": {},
+ },
+ "6243-Transports entre \u00e9tablissements ou chantiers": {},
+ "6244-Transports administratifs": {},
+ "6247-Transports collectifs du personnel": {},
"6248-Divers": {}
- },
+ },
"625-D\u00e9placements, missions et r\u00e9ceptions": {
- "6251-Voyages et d\u00e9placements": {},
- "6255-Frais de d\u00e9m\u00e9nagement": {},
- "6256-Missions": {},
+ "6251-Voyages et d\u00e9placements": {},
+ "6255-Frais de d\u00e9m\u00e9nagement": {},
+ "6256-Missions": {},
"6257-R\u00e9ceptions": {}
- },
- "626-Frais postaux et de t\u00e9l\u00e9communications": {},
+ },
+ "626-Frais postaux et de t\u00e9l\u00e9communications": {},
"627-Services bancaires et assimil\u00e9s": {
- "6271-Frais sur titres (achat, vente, garde)": {},
- "6272-Commissions et frais sur \u00e9mission d'emprunts": {},
- "6275-Frais sur effets": {},
- "6276-Location de coffres": {},
+ "6271-Frais sur titres (achat, vente, garde)": {},
+ "6272-Commissions et frais sur \u00e9mission d'emprunts": {},
+ "6275-Frais sur effets": {},
+ "6276-Location de coffres": {},
"6278-Autres frais et commissions sur prestations de services": {}
- },
+ },
"628-Divers": {
- "6281-Concours divers (cotisations...)": {},
+ "6281-Concours divers (cotisations...)": {},
"6284-Frais de recrutement de personnel": {}
- },
+ },
"629-Rabais, remises et ristournes obtenus sur autres services ext\u00e9rieurs": {}
- },
+ },
"63-Imp\u00f4ts, taxes et versements assimil\u00e9s": {
"631-Imp\u00f4ts, taxes et versements assimil\u00e9s sur r\u00e9mun\u00e9rations (administrations des imp\u00f4ts)": {
- "6311-Taxes sur les salaires": {},
- "6312-Taxe d'apprentissage": {},
- "6313-Participation des employeurs \u00e0 la formation professionnelle continue": {},
- "6314-Cotisation pour d\u00e9faut d'investissement obligatoire dans la construction": {},
+ "6311-Taxes sur les salaires": {},
+ "6312-Taxe d'apprentissage": {},
+ "6313-Participation des employeurs \u00e0 la formation professionnelle continue": {},
+ "6314-Cotisation pour d\u00e9faut d'investissement obligatoire dans la construction": {},
"6318-Autres": {}
- },
+ },
"633-Imp\u00f4ts, taxes et versements assimil\u00e9s sur r\u00e9mun\u00e9rations (autres organismes)": {
- "6331-Versement de transport": {},
- "6332-Allocations logement": {},
- "6333-Participation des employeurs \u00e0 la formation professionnelle continue": {},
- "6334-Participation des employeurs \u00e0 l'effort de construction": {},
- "6335-Versements lib\u00e9ratoires ouvrant droit \u00e0 l'\u00e9xon\u00e9ration de la taxe d'apprentissage": {},
+ "6331-Versement de transport": {},
+ "6332-Allocations logement": {},
+ "6333-Participation des employeurs \u00e0 la formation professionnelle continue": {},
+ "6334-Participation des employeurs \u00e0 l'effort de construction": {},
+ "6335-Versements lib\u00e9ratoires ouvrant droit \u00e0 l'\u00e9xon\u00e9ration de la taxe d'apprentissage": {},
"6338-Autres": {}
- },
+ },
"635-Autres imp\u00f4ts, taxes et versements assimil\u00e9s (administrations des imp\u00f4ts)": {
"6351-Imp\u00f4ts directs (sauf imp\u00f4ts sur les b\u00e9n\u00e9fices)": {
- "63511-Contribution \u00e9conomique territoriale": {},
- "63512-Taxes fonci\u00e8res": {},
- "63513-Autres imp\u00f4ts locaux": {},
+ "63511-Contribution \u00e9conomique territoriale": {},
+ "63512-Taxes fonci\u00e8res": {},
+ "63513-Autres imp\u00f4ts locaux": {},
"63514-Taxe sur les v\u00e9hicules des soci\u00e9t\u00e9s": {}
- },
- "6352-Taxes sur le chiffre d'affaires non r\u00e9cup\u00e9rables": {},
- "6353-Imp\u00f4ts indirects": {},
+ },
+ "6352-Taxes sur le chiffre d'affaires non r\u00e9cup\u00e9rables": {},
+ "6353-Imp\u00f4ts indirects": {},
"6354-Droits d'enregistrement et de timbre": {
"63541-Droits de mutation": {}
- },
+ },
"6358-Autres droits": {}
- },
+ },
"637-Autres imp\u00f4ts, taxes et versements assimil\u00e9s (autres organismes)": {
- "6371-Contribution sociale de solidarit\u00e9 \u00e0 la charge des soci\u00e9t\u00e9s": {},
- "6372-Taxes per\u00e7ues par les organismes publics internationaux": {},
- "6374-Imp\u00f4ts et taxes exigibles \u00e0 l'\u00e9tranger": {},
+ "6371-Contribution sociale de solidarit\u00e9 \u00e0 la charge des soci\u00e9t\u00e9s": {},
+ "6372-Taxes per\u00e7ues par les organismes publics internationaux": {},
+ "6374-Imp\u00f4ts et taxes exigibles \u00e0 l'\u00e9tranger": {},
"6378-Taxes diverses": {}
}
- },
+ },
"64-Charges de personnel": {
"641-R\u00e9mun\u00e9rations du personnel": {
- "6411-Salaires, appointements": {},
- "6412-Cong\u00e9s pay\u00e9s": {},
- "6413-Primes et gratifications": {},
- "6414-Indemnit\u00e9s et avantages divers": {},
+ "6411-Salaires, appointements": {},
+ "6412-Cong\u00e9s pay\u00e9s": {},
+ "6413-Primes et gratifications": {},
+ "6414-Indemnit\u00e9s et avantages divers": {},
"6415-Suppl\u00e9ment familial": {}
- },
- "644-R\u00e9mun\u00e9ration du travail de l'exploitant": {},
+ },
+ "644-R\u00e9mun\u00e9ration du travail de l'exploitant": {},
"645-Charges de s\u00e9curit\u00e9 sociale et de pr\u00e9voyance": {
- "6451-Cotisations \u00e0 l'URSSAF": {},
- "6452-Cotisations aux mutuelles": {},
- "6453-Cotisations aux caisses de retraites": {},
+ "6451-Cotisations \u00e0 l'URSSAF": {},
+ "6452-Cotisations aux mutuelles": {},
+ "6453-Cotisations aux caisses de retraites": {},
"6454-Cotisations aux ASSEDIC": {}
- },
- "646-Cotisations sociales personnelles de l'exploitant": {},
+ },
+ "646-Cotisations sociales personnelles de l'exploitant": {},
"647-Autres charges sociales": {
"is_group": 1
- },
+ },
"648-Autres charges de personnel": {}
- },
+ },
"65-Autres charges de gestion courante": {
"651-Redevances pour concessions, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels, droits et valeurs similaires": {
- "6511-Redevances pour concessions, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels": {},
- "6516-Droits d'auteur et de reproduction": {},
+ "6511-Redevances pour concessions, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels": {},
+ "6516-Droits d'auteur et de reproduction": {},
"6518-Autres droits et valeurs similaires": {}
- },
- "653-Jetons de pr\u00e9sence": {},
+ },
+ "653-Jetons de pr\u00e9sence": {},
"654-Pertes sur cr\u00e9ances irr\u00e9couvrables": {
- "6541-Cr\u00e9ances de l'exercice": {},
+ "6541-Cr\u00e9ances de l'exercice": {},
"6544-Cr\u00e9ances des exercices ant\u00e9rieurs": {}
- },
+ },
"655-Quotes-parts de r\u00e9sultat sur op\u00e9rations faites en commun": {
- "6551-Quote-part de b\u00e9n\u00e9fice transf\u00e9r\u00e9e (comptabilit\u00e9 du g\u00e9rant)": {},
+ "6551-Quote-part de b\u00e9n\u00e9fice transf\u00e9r\u00e9e (comptabilit\u00e9 du g\u00e9rant)": {},
"6555-Quote-part de perte support\u00e9e (comptabilit\u00e9 des associ\u00e9s non g\u00e9rants)": {}
- },
- "656-Pertes de change sur cr\u00e9ances et dettes commerciales": {},
+ },
+ "656-Pertes de change sur cr\u00e9ances et dettes commerciales": {},
"658-Charges diverses de gestion courante": {}
- },
+ },
"66-Charges financi\u00e8res": {
"661-Charges d'int\u00e9r\u00eats": {
"6611-Int\u00e9r\u00eats des emprunts et dettes": {
- "66116-Int\u00e9r\u00eats des emprunts et dettes - des emprunts et dettes assimil\u00e9es": {},
+ "66116-Int\u00e9r\u00eats des emprunts et dettes - des emprunts et dettes assimil\u00e9es": {},
"66117-Int\u00e9r\u00eats des emprunts et dettes - des dettes rattach\u00e9es \u00e0 des participations": {}
- },
- "6612-Charges de la fiducie, r\u00e9sultat de la p\u00e9riode": {},
- "6615-Int\u00e9r\u00eats des comptes courants et des d\u00e9p\u00f4ts cr\u00e9diteurs": {},
- "6616-Int\u00e9r\u00eats bancaires et sur op\u00e9rations de financement (escompte...)": {},
- "6617-Int\u00e9r\u00eats des obligations cautionn\u00e9es": {},
+ },
+ "6612-Charges de la fiducie, r\u00e9sultat de la p\u00e9riode": {},
+ "6615-Int\u00e9r\u00eats des comptes courants et des d\u00e9p\u00f4ts cr\u00e9diteurs": {},
+ "6616-Int\u00e9r\u00eats bancaires et sur op\u00e9rations de financement (escompte...)": {},
+ "6617-Int\u00e9r\u00eats des obligations cautionn\u00e9es": {},
"6618-Int\u00e9r\u00eats des autres dettes": {
- "66181-Int\u00e9r\u00eats des autres dettes - des dettes commerciales": {},
+ "66181-Int\u00e9r\u00eats des autres dettes - des dettes commerciales": {},
"66188-Int\u00e9r\u00eats des autres dettes - des dettes diverses": {}
}
- },
- "664-Pertes sur cr\u00e9ances li\u00e9es \u00e0 des participations": {},
- "665-Escomptes accord\u00e9s": {},
+ },
+ "664-Pertes sur cr\u00e9ances li\u00e9es \u00e0 des participations": {},
+ "665-Escomptes accord\u00e9s": {},
"666-Pertes de change financi\u00e8res": {
"account_type": "Round Off"
- },
- "667-Charges nettes sur cessions de valeurs mobili\u00e8res de placement": {},
+ },
+ "667-Charges nettes sur cessions de valeurs mobili\u00e8res de placement": {},
"668-Autres charges financi\u00e8res": {}
- },
+ },
"67-Charges exceptionnelles": {
"671-Charges exceptionnelles sur op\u00e9rations de gestion": {
- "6711-P\u00e9nalit\u00e9s sur march\u00e9s (et d\u00e9dits pay\u00e9s sur achats et ventes)": {},
- "6712-P\u00e9nalit\u00e9s, amendes fiscales et p\u00e9nales": {},
- "6713-Dons, lib\u00e9ralit\u00e9s": {},
- "6714-Cr\u00e9ances devenues irr\u00e9couvrables dans l'exercice": {},
- "6715-Subventions accord\u00e9es": {},
- "6717-Rappel d'imp\u00f4ts (autres qu'imp\u00f4ts sur les b\u00e9n\u00e9fices)": {},
+ "6711-P\u00e9nalit\u00e9s sur march\u00e9s (et d\u00e9dits pay\u00e9s sur achats et ventes)": {},
+ "6712-P\u00e9nalit\u00e9s, amendes fiscales et p\u00e9nales": {},
+ "6713-Dons, lib\u00e9ralit\u00e9s": {},
+ "6714-Cr\u00e9ances devenues irr\u00e9couvrables dans l'exercice": {},
+ "6715-Subventions accord\u00e9es": {},
+ "6717-Rappel d'imp\u00f4ts (autres qu'imp\u00f4ts sur les b\u00e9n\u00e9fices)": {},
"6718-Autres charges exceptionnelles sur op\u00e9rations de gestion": {}
- },
- "672-(Compte \u00e0 la disposition des entit\u00e9s pour enregistrer, en cours d'exercice, les charges sur exercices ant\u00e9rieurs)": {},
+ },
+ "672-(Compte \u00e0 la disposition des entit\u00e9s pour enregistrer, en cours d'exercice, les charges sur exercices ant\u00e9rieurs)": {},
"674-Op\u00e9rations de constitution ou liquidation des fiducies": {
- "6741-Op\u00e9rations li\u00e9es \u00e0 la constitution de la fiducie - transfert des \u00e9l\u00e9ments": {},
+ "6741-Op\u00e9rations li\u00e9es \u00e0 la constitution de la fiducie - transfert des \u00e9l\u00e9ments": {},
"6742-Op\u00e9rations li\u00e9es \u00e0 la liquidation de la fiducie": {}
- },
+ },
"675-Valeurs comptables des \u00e9l\u00e9ments d'actif c\u00e9d\u00e9s": {
- "6751-Immobilisations incorporelles": {},
- "6752-Immobilisations corporelles": {},
- "6756-Immobilisations financi\u00e8res": {},
+ "6751-Immobilisations incorporelles": {},
+ "6752-Immobilisations corporelles": {},
+ "6756-Immobilisations financi\u00e8res": {},
"6758-Autres \u00e9l\u00e9ments d'actif": {}
- },
+ },
"678-Autres charges exceptionnelles": {
- "6781-Mali provenant de clauses d'indexation": {},
- "6782-Lots": {},
- "6783-Malis provenant du rachat par l'entreprise d'actions et obligations \u00e9mises par elles-m\u00eame": {},
+ "6781-Mali provenant de clauses d'indexation": {},
+ "6782-Lots": {},
+ "6783-Malis provenant du rachat par l'entreprise d'actions et obligations \u00e9mises par elles-m\u00eame": {},
"6788-Charges exceptionnelles diverses": {}
}
- },
+ },
"68-Dotations aux amortissements, d\u00e9pr\u00e9ciations et provisions": {
"681-Dotations aux amortissements, d\u00e9pr\u00e9ciations et provisions - Charges d'exploitation": {
"6811-Dotations aux amortissements sur immobilisations incorporelles et corporelles": {
"68111-Immobilisations incorporelles": {
"account_type": "Depreciation"
- },
+ },
"68112-Immobilisations corporelles": {
"account_type": "Depreciation"
- },
+ },
"account_type": "Depreciation"
- },
+ },
"6812-Dotations aux amortissements des charges d'exploitation \u00e0 r\u00e9partir": {
"account_type": "Depreciation"
- },
+ },
"6815-Dotations aux provisions d'exploitation": {
"account_type": "Depreciation"
- },
+ },
"6816-Dotations aux d\u00e9pr\u00e9ciations des immobilisations incorporelles et corporelles": {
"68161-Immobilisations incorporelles": {
"account_type": "Depreciation"
- },
+ },
"68162-Immobilisations corporelles": {
"account_type": "Depreciation"
- },
+ },
"account_type": "Depreciation"
- },
+ },
"6817-Dotations pour d\u00e9pr\u00e9ciations des actifs circulants": {
"68173-Stocks et en-cours": {
"account_type": "Depreciation"
- },
+ },
"68174-Cr\u00e9ances": {
"account_type": "Depreciation"
- },
+ },
"account_type": "Depreciation"
- },
+ },
"account_type": "Depreciation"
- },
+ },
"686-Dotations aux amortissements, d\u00e9pr\u00e9ciations et provisions - Charges financi\u00e8res": {
"6861-Dotations aux amortissements des primes de remboursement des obligations": {
"account_type": "Depreciation"
- },
+ },
"6865-Dotations aux provisions financi\u00e8res": {
"account_type": "Depreciation"
- },
+ },
"6866-Dotations aux d\u00e9pr\u00e9ciations des \u00e9l\u00e9ments financiers": {
"68662-Immobilisations financi\u00e8res": {
"account_type": "Depreciation"
- },
+ },
"68665-Valeurs mobili\u00e8res de placement": {
"account_type": "Depreciation"
- },
+ },
"account_type": "Depreciation"
- },
+ },
"6868-Autres dotations": {
"account_type": "Depreciation"
- },
+ },
"account_type": "Depreciation"
- },
+ },
"687-Dotations aux amortissements, d\u00e9pr\u00e9ciations et provisions - Charges exceptionnelles": {
"6871-Dotations aux amortissements exceptionnels des immobilisations": {
"account_type": "Depreciation"
- },
+ },
"6872-Dotations aux provisions r\u00e9glement\u00e9es (immobilisations)": {
"68725-Amortissements d\u00e9rogatoires": {
"account_type": "Depreciation"
- },
+ },
"account_type": "Depreciation"
- },
+ },
"6873-Dotations aux provisions r\u00e9glement\u00e9es (stocks)": {
"account_type": "Depreciation"
- },
+ },
"6874-Dotations aux autres provisions r\u00e9glement\u00e9es": {
"account_type": "Depreciation"
- },
+ },
"6875-Dotations aux provisions exceptionnelles": {
"account_type": "Depreciation"
- },
+ },
"6876-Dotations aux d\u00e9pr\u00e9ciations exceptionnelles": {
"account_type": "Depreciation"
- },
+ },
"account_type": "Depreciation"
- },
+ },
"account_type": "Depreciation"
- },
+ },
"69-Participation des salari\u00e9s, imp\u00f4ts sur les b\u00e9n\u00e9fices et assimil\u00e9s": {
- "691-Participation des salari\u00e9s aux r\u00e9sultats": {},
+ "691-Participation des salari\u00e9s aux r\u00e9sultats": {},
"695-Imp\u00f4ts sur les b\u00e9n\u00e9fices": {
- "6951-Imp\u00f4ts dus en France": {},
- "6952-Contribution additionnelle \u00e0 l'imp\u00f4t sur les b\u00e9n\u00e9fices": {},
+ "6951-Imp\u00f4ts dus en France": {},
+ "6952-Contribution additionnelle \u00e0 l'imp\u00f4t sur les b\u00e9n\u00e9fices": {},
"6954-Imp\u00f4ts dus \u00e0 l'\u00e9tranger": {}
- },
- "696-Suppl\u00e9ments d'imp\u00f4ts sur les soci\u00e9t\u00e9s, li\u00e9s aux distributions": {},
+ },
+ "696-Suppl\u00e9ments d'imp\u00f4ts sur les soci\u00e9t\u00e9s, li\u00e9s aux distributions": {},
"698-Int\u00e9gration fiscale": {
- "6981-Int\u00e9gration fiscale - Charges": {},
+ "6981-Int\u00e9gration fiscale - Charges": {},
"6989-Int\u00e9gration fiscale - Produits": {}
- },
+ },
"699-Produits - Report en arri\u00e8re des d\u00e9ficits": {}
- },
+ },
"root_type": "Expense"
- },
+ },
"7-Comptes de Produits": {
"70-Ventes de produits fabriqu\u00e9s, prestations de services, marchandises": {
"701-Ventes de produits finis": {
- "7011-Produits finis (ou groupe) A": {},
+ "7011-Produits finis (ou groupe) A": {},
"7012-Produits (ou groupe) B": {}
- },
- "702-Ventes de produits interm\u00e9diaires": {},
- "703-Ventes de produits r\u00e9siduels": {},
+ },
+ "702-Ventes de produits interm\u00e9diaires": {},
+ "703-Ventes de produits r\u00e9siduels": {},
"704-Travaux": {
- "7041-Travaux de cat\u00e9gorie (ou activit\u00e9) A": {},
+ "7041-Travaux de cat\u00e9gorie (ou activit\u00e9) A": {},
"7042-Travaux de cat\u00e9gorie (ou activit\u00e9) B": {}
- },
- "705-Etudes": {},
- "706-Prestations de services": {},
+ },
+ "705-Etudes": {},
+ "706-Prestations de services": {},
"707-Ventes de marchandises": {
- "7071-Marchandises (ou groupe) A": {},
+ "7071-Marchandises (ou groupe) A": {},
"7072-Marchandises (ou groupe) B": {}
- },
+ },
"708-Produits des activit\u00e9s annexes": {
- "7081-Produits des services exploit\u00e9s dans l'int\u00e9r\u00eat du personnel": {},
- "7082-Commissions et courtages": {},
- "7083-Locations diverses": {},
- "7084-Mise \u00e0 disposition de personnel factur\u00e9e": {},
- "7085-Ports et frais accessoires factur\u00e9s": {},
- "7086-Bonis sur reprises d'emballages consign\u00e9s": {},
- "7087-Bonifications obtenues des clients et primes sur ventes": {},
+ "7081-Produits des services exploit\u00e9s dans l'int\u00e9r\u00eat du personnel": {},
+ "7082-Commissions et courtages": {},
+ "7083-Locations diverses": {},
+ "7084-Mise \u00e0 disposition de personnel factur\u00e9e": {},
+ "7085-Ports et frais accessoires factur\u00e9s": {},
+ "7086-Bonis sur reprises d'emballages consign\u00e9s": {},
+ "7087-Bonifications obtenues des clients et primes sur ventes": {},
"7088-Autres produits d'activit\u00e9s annexes (cessions d'approvisionnements...)": {}
- },
+ },
"709-Rabais, remises et ristournes accord\u00e9s par l'entreprise": {
- "7091-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur ventes de produits finis": {},
- "7092-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur ventes de produits interm\u00e9diaires": {},
- "7094-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur travaux": {},
- "7095-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur \u00e9tudes": {},
- "7096-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur prestations de services": {},
- "7097-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur ventes de marchandises": {},
+ "7091-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur ventes de produits finis": {},
+ "7092-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur ventes de produits interm\u00e9diaires": {},
+ "7094-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur travaux": {},
+ "7095-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur \u00e9tudes": {},
+ "7096-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur prestations de services": {},
+ "7097-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur ventes de marchandises": {},
"7098-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur produits des activit\u00e9s annexes": {}
}
- },
+ },
"71-Production stock\u00e9e (ou d\u00e9stockage)": {
"713-Variation des stocks (en-cours de production, produits)": {
"7133-Variation des en-cours de production de biens": {
- "71331-Produits en cours": {},
+ "71331-Produits en cours": {},
"71335-Travaux en cours": {}
- },
+ },
"7134-Variation des en-cours de production de services": {
- "71341-Etudes en cours": {},
+ "71341-Etudes en cours": {},
"71345-Prestations de services en cours": {}
- },
+ },
"7135-Variation des stocks de produits": {
- "71351-Produits interm\u00e9diaires": {},
- "71355-Produits finis": {},
+ "71351-Produits interm\u00e9diaires": {},
+ "71355-Produits finis": {},
"71358-Produits r\u00e9siduels": {}
}
}
- },
+ },
"72-Production immobilis\u00e9e": {
- "721-Immobilisations incorporelles": {},
+ "721-Immobilisations incorporelles": {},
"722-Immobilisations corporelles": {}
- },
+ },
"74-Subventions d'exploitation": {
"is_group": 1
- },
+ },
"75-Autres produits de gestion courante": {
"751-Redevances pour concessions, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels, droits et valeurs similaires": {
- "7511-Redevances pour concessions, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels": {},
- "7516-Droits d'auteur et de reproduction": {},
+ "7511-Redevances pour concessions, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels": {},
+ "7516-Droits d'auteur et de reproduction": {},
"7518-Autres droits et valeurs similaires": {}
- },
- "752-Revenus des immeubles non affect\u00e9s aux activit\u00e9s professionnelles": {},
- "753-Jetons de pr\u00e9sence et r\u00e9mun\u00e9rations d'administrateurs, g\u00e9rants...": {},
- "754-Ristournes per\u00e7ues des coop\u00e9ratives (provenant des exc\u00e9dents)": {},
+ },
+ "752-Revenus des immeubles non affect\u00e9s aux activit\u00e9s professionnelles": {},
+ "753-Jetons de pr\u00e9sence et r\u00e9mun\u00e9rations d'administrateurs, g\u00e9rants...": {},
+ "754-Ristournes per\u00e7ues des coop\u00e9ratives (provenant des exc\u00e9dents)": {},
"755-Quotes-parts de r\u00e9sultats sur op\u00e9rations faites en commun": {
- "7551-Quote-part de perte transf\u00e9r\u00e9e (comptabilit\u00e9 du g\u00e9rant)": {},
+ "7551-Quote-part de perte transf\u00e9r\u00e9e (comptabilit\u00e9 du g\u00e9rant)": {},
"7555-Quote-part de b\u00e9n\u00e9fice attribu\u00e9 (comptabilit\u00e9 des associ\u00e9s non g\u00e9rants)": {}
- },
- "756-Gains de change sur cr\u00e9ances et dettes commerciales": {},
+ },
+ "756-Gains de change sur cr\u00e9ances et dettes commerciales": {},
"758-Produits divers de gestion courante": {}
- },
+ },
"76-Produits financiers": {
"761-Produits de participations": {
- "7611-Revenus des titres de participation": {},
- "7612-Produits de la fiducie, r\u00e9sultat de la p\u00e9riode": {},
- "7616-Revenus sur autres formes de participation": {},
+ "7611-Revenus des titres de participation": {},
+ "7612-Produits de la fiducie, r\u00e9sultat de la p\u00e9riode": {},
+ "7616-Revenus sur autres formes de participation": {},
"7617-Revenus des cr\u00e9ances rattach\u00e9es \u00e0 des participations": {}
- },
+ },
"762-Produits des autres immobilisations financi\u00e8res": {
- "7621-Revenus des titres immobilis\u00e9s": {},
- "7626-Revenus des pr\u00eats": {},
+ "7621-Revenus des titres immobilis\u00e9s": {},
+ "7626-Revenus des pr\u00eats": {},
"7627-Revenus des cr\u00e9ances immobilis\u00e9es": {}
- },
+ },
"763-Revenus des autres cr\u00e9ances": {
- "7631-Revenus des cr\u00e9ances commerciales": {},
+ "7631-Revenus des cr\u00e9ances commerciales": {},
"7638-Revenus des cr\u00e9ances diverses": {}
- },
- "764-Revenus des valeurs mobili\u00e8res de placement": {},
- "765-Escomptes obtenus": {},
+ },
+ "764-Revenus des valeurs mobili\u00e8res de placement": {},
+ "765-Escomptes obtenus": {},
"766-Gains de change financi\u00e8res": {
"account_type": "Round Off"
- },
- "767-Produits nets sur cessions de valeurs mobili\u00e8res de placement": {},
+ },
+ "767-Produits nets sur cessions de valeurs mobili\u00e8res de placement": {},
"768-Autres produits financiers": {}
- },
+ },
"77-Produits exceptionnels": {
"771-Produits exceptionnels sur op\u00e9rations de gestion": {
- "7711-D\u00e9dits et p\u00e9nalit\u00e9s per\u00e7us sur achats et sur ventes": {},
- "7713-Lib\u00e9ralit\u00e9s re\u00e7ues": {},
- "7714-Rentr\u00e9es sur cr\u00e9ances amorties": {},
- "7715-Subventions d'\u00e9quilibre": {},
- "7717-D\u00e9gr\u00e8vements d'imp\u00f4ts autres qu'imp\u00f4ts sur les b\u00e9n\u00e9fices": {},
+ "7711-D\u00e9dits et p\u00e9nalit\u00e9s per\u00e7us sur achats et sur ventes": {},
+ "7713-Lib\u00e9ralit\u00e9s re\u00e7ues": {},
+ "7714-Rentr\u00e9es sur cr\u00e9ances amorties": {},
+ "7715-Subventions d'\u00e9quilibre": {},
+ "7717-D\u00e9gr\u00e8vements d'imp\u00f4ts autres qu'imp\u00f4ts sur les b\u00e9n\u00e9fices": {},
"7718-Autres produits exceptionnels sur op\u00e9rations de gestion": {}
- },
- "772-(Compte \u00e0 la disposition des entit\u00e9s pour enregistrer, en cours d'exercice, les Produits sur exercices ant\u00e9rieurs)": {},
+ },
+ "772-(Compte \u00e0 la disposition des entit\u00e9s pour enregistrer, en cours d'exercice, les Produits sur exercices ant\u00e9rieurs)": {},
"774-Op\u00e9rations de constitution ou liquidation des fiducies": {
- "7741-Op\u00e9rations li\u00e9es \u00e0 la constitution de la fiducie - transfert des \u00e9l\u00e9ments": {},
+ "7741-Op\u00e9rations li\u00e9es \u00e0 la constitution de la fiducie - transfert des \u00e9l\u00e9ments": {},
"7742-Op\u00e9rations li\u00e9es \u00e0 la liquidation de la fiducie": {}
- },
+ },
"775-Produits des cessions d'\u00e9l\u00e9ments d'actif": {
- "7751-Immobilisations incorporelles": {},
- "7752-Immobilisations corporelles": {},
- "7756-Immobilisations financi\u00e8res": {},
+ "7751-Immobilisations incorporelles": {},
+ "7752-Immobilisations corporelles": {},
+ "7756-Immobilisations financi\u00e8res": {},
"7758-Autres \u00e9l\u00e9ments d'actif": {}
- },
- "777-Quote-part des subventions d'investissement vir\u00e9e au r\u00e9sultat de l'exercice": {},
+ },
+ "777-Quote-part des subventions d'investissement vir\u00e9e au r\u00e9sultat de l'exercice": {},
"778-Autres produits exceptionnels": {
- "7781-Bonis provenant de clauses d'indexation": {},
- "7782-Lots": {},
- "7783-Bonis provenant du rachat par l'entreprise d'actions et d'obligations \u00e9mises par elle-m\u00eame": {},
+ "7781-Bonis provenant de clauses d'indexation": {},
+ "7782-Lots": {},
+ "7783-Bonis provenant du rachat par l'entreprise d'actions et d'obligations \u00e9mises par elle-m\u00eame": {},
"7788-Produits exceptionnels divers": {}
}
- },
+ },
"78-Reprises sur amortissements, d\u00e9pr\u00e9ciations et provisions": {
"781-Reprises sur amortissements, d\u00e9pr\u00e9ciations et provisions (\u00e0 inscrire dans les produits d'exploitation)": {
"7811-Reprises sur amortissements des immobilisations incorporelles et corporelles": {
- "78111-Immobilisations incorporelles": {},
+ "78111-Immobilisations incorporelles": {},
"78112-Immobilisations corporelles": {}
- },
- "7815-Reprises sur provisions d'exploitation": {},
+ },
+ "7815-Reprises sur provisions d'exploitation": {},
"7816-Reprises sur d\u00e9pr\u00e9ciations des immobilisations corporelles et incorporelles": {
- "78161-Immobilisations incorporelles": {},
+ "78161-Immobilisations incorporelles": {},
"78162-Immobilisations corporelles": {}
- },
+ },
"7817-Reprises sur d\u00e9pr\u00e9ciations des actifs circulants": {
- "78173-Stocks et en-cours": {},
+ "78173-Stocks et en-cours": {},
"78174-Cr\u00e9ances": {}
}
- },
+ },
"786-Reprises sur d\u00e9pr\u00e9ciations et provisions (\u00e0 inscrire dans les produits financiers)": {
- "7865-Reprises sur provisions financi\u00e8res": {},
+ "7865-Reprises sur provisions financi\u00e8res": {},
"7866-Reprises sur d\u00e9pr\u00e9ciations des \u00e9l\u00e9ments financiers": {
- "78662-Immobilisations financi\u00e8res": {},
+ "78662-Immobilisations financi\u00e8res": {},
"78665-Valeurs mobili\u00e8res de placement": {}
}
- },
+ },
"787-Reprises sur d\u00e9pr\u00e9ciations et provisions (\u00e0 inscrire dans les produits exceptionnels)": {
"7872-Reprises sur provisions r\u00e9glement\u00e9es (immobilisations)": {
- "78725-Amortissements d\u00e9rogatoires": {},
- "78726-Provision sp\u00e9ciale de r\u00e9\u00e9valuation": {},
+ "78725-Amortissements d\u00e9rogatoires": {},
+ "78726-Provision sp\u00e9ciale de r\u00e9\u00e9valuation": {},
"78727-Plus-values r\u00e9investies": {}
- },
- "7873-Reprises sur provisions r\u00e9glement\u00e9es (stocks)": {},
- "7874-Reprises sur autres provisions r\u00e9glement\u00e9es": {},
- "7875-Reprises sur provisions exceptionnelles": {},
+ },
+ "7873-Reprises sur provisions r\u00e9glement\u00e9es (stocks)": {},
+ "7874-Reprises sur autres provisions r\u00e9glement\u00e9es": {},
+ "7875-Reprises sur provisions exceptionnelles": {},
"7876-Reprises sur d\u00e9pr\u00e9ciations exceptionnelles": {}
}
- },
+ },
"79-Transferts de charges": {
- "791-Transferts de charges d'exploitation": {},
- "796-Transferts de charges financi\u00e8res": {},
+ "791-Transferts de charges d'exploitation": {},
+ "796-Transferts de charges financi\u00e8res": {},
"797-Transferts de charges exceptionnelles": {}
- },
+ },
"root_type": "Income"
}
}
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general_avec_code.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general_avec_code.json
new file mode 100644
index 0000000..b667379
--- /dev/null
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general_avec_code.json
@@ -0,0 +1,3144 @@
+{
+ "country_code": "fr",
+ "name": "France - Plan Comptable General avec code",
+ "tree": {
+ "Comptes de Capitaux": {
+ "root_type": "Equity",
+ "Capital et R\u00e9serves": {
+ "Capital": {
+ "Capital souscrit - non appel\u00e9": {
+ "account_number": "1011"
+ },
+ "Capital souscrit - appel\u00e9, non vers\u00e9": {
+ "account_number": "1012"
+ },
+ "Capital souscrit - appel\u00e9, vers\u00e9": {
+ "Capital non amorti": {
+ "account_number": "10131"
+ },
+ "Capital amorti": {
+ "account_number": "10132"
+ },
+ "account_number": "1013"
+ },
+ "Capital souscrit soumis \u00e0 des r\u00e9glementations particuli\u00e8res": {
+ "account_number": "1018"
+ },
+ "account_number": "101"
+ },
+ "Fonds fiduciaires": {
+ "account_number": "102"
+ },
+ "Primes li\u00e9es au capital social": {
+ "Primes d'\u00e9mission": {
+ "account_number": "1041"
+ },
+ "Primes de fusion": {
+ "account_number": "1042"
+ },
+ "Primes d'apport": {
+ "account_number": "1043"
+ },
+ "Primes de conversion d'obligations en actions": {
+ "account_number": "1044"
+ },
+ "Bons de souscription d'actions": {
+ "account_number": "1045"
+ },
+ "account_number": "104"
+ },
+ "Ecarts de r\u00e9\u00e9valuation": {
+ "R\u00e9serve sp\u00e9ciale de r\u00e9\u00e9valuation": {
+ "account_number": "1051"
+ },
+ "Ecart de r\u00e9\u00e9valuation libre": {
+ "account_number": "1052"
+ },
+ "R\u00e9serve de r\u00e9\u00e9valuation": {
+ "account_number": "1053"
+ },
+ "Ecarts de r\u00e9\u00e9valuation (autres op\u00e9rations l\u00e9gales)": {
+ "account_number": "1055"
+ },
+ "Autres \u00e9carts de r\u00e9\u00e9valuation en France": {
+ "account_number": "1057"
+ },
+ "Autres \u00e9carts de r\u00e9\u00e9valuation \u00e0 l'\u00e9tranger": {
+ "account_number": "1058"
+ },
+ "account_number": "105"
+ },
+ "R\u00e9serves": {
+ "R\u00e9serve l\u00e9gale": {
+ "R\u00e9serve l\u00e9gale proprement dite": {
+ "account_number": "10611"
+ },
+ "Plus-values nettes \u00e0 long terme": {
+ "account_number": "10612"
+ },
+ "account_number": "1061"
+ },
+ "R\u00e9serves indisponibles": {
+ "account_number": "1062"
+ },
+ "R\u00e9serves statutaires ou contractuelles": {
+ "account_number": "1063"
+ },
+ "R\u00e9serves r\u00e9glement\u00e9es": {
+ "Plus-values nettes \u00e0 long terme": {
+ "account_number": "10641"
+ },
+ "R\u00e9serves cons\u00e9cutives \u00e0 l'octroi de subventions d'investissement": {
+ "account_number": "10643"
+ },
+ "Autres r\u00e9serves r\u00e9glement\u00e9es": {
+ "account_number": "10648"
+ },
+ "account_number": "1064"
+ },
+ "Autres r\u00e9serves": {
+ "R\u00e9serve de propre assureur": {
+ "account_number": "10681"
+ },
+ "R\u00e9serves diverses": {
+ "account_number": "10688"
+ },
+ "account_number": "1068"
+ },
+ "account_number": "106"
+ },
+ "Ecarts d'\u00e9quivalence": {
+ "account_number": "107"
+ },
+ "Compte de l'exploitant": {
+ "account_number": "108"
+ },
+ "Actionnaires: Capital souscrit - non appel\u00e9": {
+ "account_number": "109"
+ },
+ "account_number": "10"
+ },
+ "Report \u00e0 Nouveau": {
+ "Report \u00e0 nouveau (solde cr\u00e9diteur)": {
+ "account_number": "110"
+ },
+ "Report \u00e0 nouveau (solde d\u00e9biteur)": {
+ "account_number": "119"
+ },
+ "account_number": "11"
+ },
+ "R\u00e9sultat de l'Exercice": {
+ "R\u00e9sultat de l'exercice (b\u00e9n\u00e9fice)": {
+ "account_number": "120"
+ },
+ "R\u00e9sultat de l'exercice (perte)": {
+ "account_number": "129"
+ },
+ "account_number": "12"
+ },
+ "Subventions d'Investissement": {
+ "Subventions d'\u00e9quipement": {
+ "Etat": {
+ "account_number": "1311"
+ },
+ "R\u00e9gions": {
+ "account_number": "1312"
+ },
+ "D\u00e9partements": {
+ "account_number": "1313"
+ },
+ "Communes": {
+ "account_number": "1314"
+ },
+ "Collectivit\u00e9s publiques": {
+ "account_number": "1315"
+ },
+ "Entreprises publiques": {
+ "account_number": "1316"
+ },
+ "Entreprises et organismes priv\u00e9s": {
+ "account_number": "1317"
+ },
+ "Autres": {
+ "account_number": "1318"
+ },
+ "account_number": "131"
+ },
+ "Autres subventions d'investissement (m\u00eame ventilation que celle du compte 131)": {
+ "account_number": "138"
+ },
+ "Subventions d'investissement inscrites au compte de r\u00e9sultat": {
+ "Subventions d'\u00e9quipement": {
+ "Etat": {
+ "account_number": "13911"
+ },
+ "R\u00e9gions": {
+ "account_number": "13912"
+ },
+ "D\u00e9partements": {
+ "account_number": "13913"
+ },
+ "Communes": {
+ "account_number": "13914"
+ },
+ "Collectivit\u00e9s publiques": {
+ "account_number": "13915"
+ },
+ "Entreprises publiques": {
+ "account_number": "13916"
+ },
+ "Entreprises et organismes priv\u00e9s": {
+ "account_number": "13917"
+ },
+ "Autres": {
+ "account_number": "13918"
+ },
+ "account_number": "1391"
+ },
+ "Autres subventions d'investissement (m\u00eame ventilation que celle du compte 1391)": {
+ "account_number": "1398"
+ },
+ "account_number": "139"
+ },
+ "account_number": "13"
+ },
+ "Provisions R\u00e9glement\u00e9es": {
+ "Provisions r\u00e9glement\u00e9es relative aux immobilisations": {
+ "Provisions pour reconstitution des gisements miniers et p\u00e9troliers": {
+ "account_number": "1423"
+ },
+ "Provisions pour investissement (participation des salari\u00e9s)": {
+ "account_number": "1424"
+ },
+ "account_number": "142"
+ },
+ "Provisions r\u00e9glement\u00e9es relatives aux stocks": {
+ "Hausse des prix": {
+ "account_number": "1431"
+ },
+ "Fluctuation des cours": {
+ "account_number": "1432"
+ },
+ "account_number": "143"
+ },
+ "Provisions r\u00e9glement\u00e9es relatives aux autres \u00e9l\u00e9ments de l'actif": {
+ "account_number": "144"
+ },
+ "Amortissements d\u00e9rogatoires": {
+ "account_number": "145"
+ },
+ "Provision sp\u00e9ciale de r\u00e9\u00e9valuation": {
+ "account_number": "146"
+ },
+ "Plus-values r\u00e9investies": {
+ "account_number": "147"
+ },
+ "Autres provisions r\u00e9glement\u00e9es": {
+ "account_number": "148"
+ },
+ "account_number": "14"
+ },
+ "Provisions": {
+ "Provisions pour risques": {
+ "Provisions pour litiges": {
+ "account_number": "1511"
+ },
+ "Provisions pour garanties donn\u00e9es aux clients": {
+ "account_number": "1512"
+ },
+ "Provisions pour pertes sur march\u00e9s \u00e0 terme": {
+ "account_number": "1513"
+ },
+ "Provisions pour amendes et p\u00e9nalit\u00e9s": {
+ "account_number": "1514"
+ },
+ "Provisions pour pertes de change": {
+ "account_number": "1515"
+ },
+ "Provisions pour pertes sur contrats": {
+ "account_number": "1516"
+ },
+ "Autres provisions pour risques": {
+ "account_number": "1518"
+ },
+ "account_number": "151"
+ },
+ "Provisions pour pensions et obligations similaires": {
+ "account_number": "153"
+ },
+ "Provisions pour restructurations": {
+ "account_number": "154"
+ },
+ "Provisions pour imp\u00f4ts": {
+ "account_number": "155"
+ },
+ "Provisions pour renouvellement des immobilisations (entreprises concessionnaires) ": {
+ "account_number": "156"
+ },
+ "Provisions pour charges \u00e0 r\u00e9partir sur plusieurs exercices": {
+ "Provisions pour gros entretien ou grandes r\u00e9visions": {
+ "account_number": "1572"
+ },
+ "account_number": "157"
+ },
+ "Autres provisions pour charges": {
+ "Provisions pour remises en \u00e9tat": {
+ "account_number": "1581"
+ },
+ "account_number": "158"
+ },
+ "account_number": "15"
+ },
+ "Emprunts et Dettes Assimil\u00e9es": {
+ "Emprunts obligataires convertibles": {
+ "account_number": "161"
+ },
+ "Obligations repr\u00e9sentatives de passifs nets remis en fiducie": {
+ "account_number": "162"
+ },
+ "Autres emprunts obligataires": {
+ "account_number": "163"
+ },
+ "Emprunts aupr\u00e8s des \u00e9tablissements de cr\u00e9dit": {
+ "account_number": "164"
+ },
+ "D\u00e9p\u00f4ts et cautionnements re\u00e7us": {
+ "D\u00e9p\u00f4ts": {
+ "account_number": "1651"
+ },
+ "Cautionnements": {
+ "account_number": "1655"
+ },
+ "account_number": "165"
+ },
+ "Participation des salari\u00e9s aux r\u00e9sultats": {
+ "Comptes bloqu\u00e9s": {
+ "account_number": "1661"
+ },
+ "Fonds de participation": {
+ "account_number": "1662"
+ },
+ "account_number": "166"
+ },
+ "Emprunts et dettes assortis de conditions particuli\u00e8res": {
+ "Emissions de titres participatifs": {
+ "account_number": "1671"
+ },
+ "Avances conditionn\u00e9es de l'Etat": {
+ "account_number": "1674"
+ },
+ "Emprunts participatifs": {
+ "account_number": "1675"
+ },
+ "account_number": "167"
+ },
+ "Autres emprunts et dettes assimil\u00e9es": {
+ "Autres emprunts": {
+ "account_number": "1681"
+ },
+ "Rentes viag\u00e8res capitalis\u00e9es": {
+ "account_number": "1685"
+ },
+ "Autres dettes": {
+ "account_number": "1687"
+ },
+ "Int\u00e9r\u00eats courus": {
+ "Int\u00e9r\u00eats courus sur emprunts obligataires convertibles": {
+ "account_number": "16881"
+ },
+ "Int\u00e9r\u00eats courus sur autres emprunts obligataires": {
+ "account_number": "16883"
+ },
+ "Int\u00e9r\u00eats courus sur emprunts aupr\u00e8s des \u00e9tablissements de cr\u00e9dit": {
+ "account_number": "16884"
+ },
+ "Int\u00e9r\u00eats courus sur d\u00e9p\u00f4ts et cautionnements re\u00e7us": {
+ "account_number": "16885"
+ },
+ "Int\u00e9r\u00eats courus sur participation des salari\u00e9s aux r\u00e9sultats": {
+ "account_number": "16886"
+ },
+ "Int\u00e9r\u00eats courus sur emprunts et dettes assortis de conditions particuli\u00e8res": {
+ "account_number": "16887"
+ },
+ "Int\u00e9r\u00eats courus sur autres emprunts et dettes assimil\u00e9es": {
+ "account_number": "16888"
+ },
+ "account_number": "1688"
+ },
+ "Primes de remboursement des obligations": {
+ "account_number": "169"
+ },
+ "account_number": "168"
+ },
+ "account_number": "16"
+ },
+ "Dettes Rattach\u00e9es \u00e0 des Participations": {
+ "Dettes rattach\u00e9es \u00e0 des participations (groupe)": {
+ "account_number": "171"
+ },
+ "Dettes rattach\u00e9es \u00e0 des participations (hors groupe)": {
+ "account_number": "174"
+ },
+ "Dettes rattach\u00e9es \u00e0 des soci\u00e9t\u00e9s en participation": {
+ "Principal": {
+ "account_number": "1781"
+ },
+ "Int\u00e9r\u00eats courus": {
+ "account_number": "1788"
+ },
+ "account_number": "178"
+ },
+ "account_number": "17"
+ },
+ "Comptes de liaison des \u00e9tablisssements et soci\u00e9t\u00e9s en participation": {
+ "Comptes de liaison des \u00e9tablissements": {
+ "account_number": "181"
+ },
+ "Biens et prestations de services \u00e9chang\u00e9s entre \u00e9tablissements (charges)": {
+ "account_number": "186"
+ },
+ "Biens et prestations de services \u00e9chang\u00e9s entre \u00e9tablissements (produits)": {
+ "account_number": "187"
+ },
+ "Comptes de liaison des soci\u00e9t\u00e9s en participation": {
+ "account_number": "188"
+ },
+ "account_number": "18"
+ },
+ "account_number": "1"
+ },
+ "Comptes d'Immobilisations": {
+ "root_type": "Asset",
+ "Immobilisations incorporelles": {
+ "Frais \u00e9tablissement": {
+ "Frais de constitution": {
+ "account_number": "2011"
+ },
+ "Frais de premier \u00e9tablissement": {
+ "Frais de prospection": {
+ "account_number": "20121"
+ },
+ "Frais de publicit\u00e9": {
+ "account_number": "20122"
+ },
+ "account_number": "2012"
+ },
+ "Frais d'augmentation de capital et d'op\u00e9rations diverses (fusions, scissions, transformations)": {
+ "account_number": "2013"
+ },
+ "account_number": "201"
+ },
+ "Frais de recherche et de d\u00e9veloppement": {
+ "account_number": "203"
+ },
+ "Concessions et droits similaires, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels, droits et valeurs similaires": {
+ "account_number": "205"
+ },
+ "Droit au bail": {
+ "account_number": "206"
+ },
+ "Fonds commercial": {
+ "account_number": "207"
+ },
+ "Autres immobilisations incorporelles": {
+ "Mali de fusion sur actifs incorporels": {
+ "account_number": "2081"
+ },
+ "account_number": "208"
+ },
+ "account_number": "20"
+ },
+ "Immobilisations corporelles": {
+ "account_type": "Fixed Asset",
+ "Terrains": {
+ "account_type": "Fixed Asset",
+ "Terrains nus": {
+ "account_type": "Fixed Asset",
+ "account_number": "2111"
+ },
+ "Terrains am\u00e9nag\u00e9s": {
+ "account_type": "Fixed Asset",
+ "account_number": "2112"
+ },
+ "Sous-sols et sur-sols": {
+ "account_type": "Fixed Asset",
+ "account_number": "2113"
+ },
+ "Terrains de carri\u00e8res (tr\u00e9fonds)": {
+ "account_type": "Fixed Asset",
+ "account_number": "2114"
+ },
+ "Terrains b\u00e2tis": {
+ "account_type": "Fixed Asset",
+ "Ensembles immobiliers industriels (A, B)": {
+ "account_type": "Fixed Asset",
+ "account_number": "21151"
+ },
+ "Ensembles immobiliers administratifs et commerciaux (A, B)": {
+ "account_type": "Fixed Asset",
+ "account_number": "21155"
+ },
+ "Autres ensembles immobiliers": {
+ "account_type": "Fixed Asset",
+ "Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations professionnelles (A, B)": {
+ "account_type": "Fixed Asset",
+ "account_number": "211581"
+ },
+ "Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations non professionnelles (A, B)": {
+ "account_type": "Fixed Asset",
+ "account_number": "211588"
+ },
+ "account_number": "21158"
+ },
+ "account_number": "2115"
+ },
+ "account_number": "211"
+ },
+ "Agencements et am\u00e9nagements de terrains (m\u00eame ventilation que celle du compte 211)": {
+ "account_type": "Fixed Asset",
+ "account_number": "212"
+ },
+ "Constructions": {
+ "account_type": "Fixed Asset",
+ "B\u00e2timents": {
+ "account_type": "Fixed Asset",
+ "Ensembles immobiliers industriels (A, B)": {
+ "account_type": "Fixed Asset",
+ "account_number": "21311"
+ },
+ "Ensembles immobiliers administratifs et commerciaux (A, B)": {
+ "account_type": "Fixed Asset",
+ "account_number": "21315"
+ },
+ "Autres ensembles immobiliers": {
+ "account_type": "Fixed Asset",
+ "Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations professionnelles (A, B)": {
+ "account_type": "Fixed Asset",
+ "account_number": "213181"
+ },
+ "Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations non professionnelles (A, B)": {
+ "account_type": "Fixed Asset",
+ "account_number": "213188"
+ },
+ "account_number": "21318"
+ },
+ "account_number": "2131"
+ },
+ "Installations g\u00e9n\u00e9rales, agencements, am\u00e9nagements des constructions": {
+ "account_type": "Fixed Asset",
+ "Ensembles immobiliers industriels (A, B)": {
+ "account_type": "Fixed Asset",
+ "account_number": "21351"
+ },
+ "Ensembles immobiliers administratifs et commerciaux (A, B)": {
+ "account_type": "Fixed Asset",
+ "account_number": "21355"
+ },
+ "Autres ensembles immobiliers": {
+ "account_type": "Fixed Asset",
+ "Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations professionnelles (A, B)": {
+ "account_type": "Fixed Asset",
+ "account_number": "213581"
+ },
+ "Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations non professionnelles (A, B)": {
+ "account_type": "Fixed Asset",
+ "account_number": "213588"
+ },
+ "account_number": "21358"
+ },
+ "account_number": "2135"
+ },
+ "Ouvrages d'infrastructure": {
+ "account_type": "Fixed Asset",
+ "Voies de terre": {
+ "account_type": "Fixed Asset",
+ "account_number": "21381"
+ },
+ "Voies de fer": {
+ "account_type": "Fixed Asset",
+ "account_number": "21382"
+ },
+ "Voies d'eau": {
+ "account_type": "Fixed Asset",
+ "account_number": "21383"
+ },
+ "Barrages": {
+ "account_type": "Fixed Asset",
+ "account_number": "21384"
+ },
+ "Pistes d'a\u00e9rodromes": {
+ "account_type": "Fixed Asset",
+ "account_number": "21385"
+ },
+ "account_number": "2138"
+ },
+ "account_number": "213"
+ },
+ "Constructions sur sol d'autrui (m\u00eame ventilation que celle du compte 213)": {
+ "account_type": "Fixed Asset",
+ "account_number": "214"
+ },
+ "Installations techniques, mat\u00e9riel et outillage industriels": {
+ "account_type": "Fixed Asset",
+ "Installations complexes sp\u00e9cialis\u00e9es": {
+ "account_type": "Fixed Asset",
+ "Installations complexes sp\u00e9cialis\u00e9es - sur sol propre": {
+ "account_type": "Fixed Asset",
+ "account_number": "21511"
+ },
+ "Installations complexes sp\u00e9cialis\u00e9es - sur sol d'autrui": {
+ "account_type": "Fixed Asset",
+ "account_number": "21514"
+ },
+ "account_number": "2151"
+ },
+ "Installations \u00e0 caract\u00e8re sp\u00e9cifique": {
+ "account_type": "Fixed Asset",
+ "Installations \u00e0 caract\u00e8re sp\u00e9cifique - sur sol propre": {
+ "account_type": "Fixed Asset",
+ "account_number": "21531"
+ },
+ "Installations \u00e0 caract\u00e8re sp\u00e9cifique - sur sol d'autrui": {
+ "account_type": "Fixed Asset",
+ "account_number": "21534"
+ },
+ "account_number": "2153"
+ },
+ "Mat\u00e9riel industriel": {
+ "account_type": "Fixed Asset",
+ "account_number": "2154"
+ },
+ "Outillage industriel": {
+ "account_type": "Fixed Asset",
+ "account_number": "2155"
+ },
+ "Agencements et am\u00e9nagements du mat\u00e9riel et outillage industriel": {
+ "account_type": "Fixed Asset",
+ "account_number": "2157"
+ },
+ "account_number": "215"
+ },
+ "Autres immobilisations corporelles": {
+ "account_type": "Fixed Asset",
+ "Installations g\u00e9n\u00e9rales, agencements, am\u00e9nagements divers": {
+ "account_type": "Fixed Asset",
+ "account_number": "2181"
+ },
+ "Mat\u00e9riel de transport": {
+ "account_type": "Fixed Asset",
+ "account_number": "2182"
+ },
+ "Mat\u00e9riel de bureau et mat\u00e9riel informatique": {
+ "account_type": "Fixed Asset",
+ "account_number": "2183"
+ },
+ "Mobilier": {
+ "account_type": "Fixed Asset",
+ "account_number": "2184"
+ },
+ "Cheptel": {
+ "account_type": "Fixed Asset",
+ "account_number": "2185"
+ },
+ "Emballages r\u00e9cup\u00e9rables": {
+ "account_type": "Fixed Asset",
+ "account_number": "2186"
+ },
+ "Mali de fusion sur actifs corporels": {
+ "account_number": "2187"
+ },
+ "account_number": "218"
+ },
+ "account_number": "21"
+ },
+ "Immobilisations mises en concession": {
+ "account_number": "22"
+ },
+ "Immobilisations en cours": {
+ "Immobilisations corporelles en cours": {
+ "Terrains": {
+ "account_number": "2312"
+ },
+ "Constructions": {
+ "account_number": "2313"
+ },
+ "Installations techniques, mat\u00e9riel et outillage industriels": {
+ "account_number": "2315"
+ },
+ "Autres immobilisations corporelles": {
+ "account_number": "2318"
+ },
+ "account_number": "231"
+ },
+ "Immobilisations incorporelles en cours": {
+ "account_number": "232"
+ },
+ "Avances et acomptes vers\u00e9s sur commandes d'immobilisations incorporelles": {
+ "account_number": "237"
+ },
+ "Avances et acomptes vers\u00e9s sur commandes d'immobilisations corporelles": {
+ "Terrains": {
+ "account_number": "2382"
+ },
+ "Constructions": {
+ "account_number": "2383"
+ },
+ "Installations techniques, mat\u00e9riel et outillage industriels": {
+ "account_number": "2385"
+ },
+ "Autres immobilisations corporelles": {
+ "account_number": "2388"
+ },
+ "account_number": "238"
+ },
+ "account_number": "23"
+ },
+ "Parts dans des entreprises li\u00e9es et cr\u00e9ances sur des entreprises li\u00e9es": {
+ "is_group": 1,
+ "account_number": "25"
+ },
+ "Participations et cr\u00e9ances rattach\u00e9es \u00e0 des participations": {
+ "Titres de participation": {
+ "Actions": {
+ "account_number": "2611"
+ },
+ "Autres titres": {
+ "account_number": "2618"
+ },
+ "account_number": "261"
+ },
+ "Autres formes de participation": {
+ "Droit repr\u00e9sentatifs d'actifs nets remis en fiducie": {
+ "account_number": "2661"
+ },
+ "account_number": "266"
+ },
+ "Cr\u00e9ances rattach\u00e9es \u00e0 des participations": {
+ "Cr\u00e9ances rattach\u00e9es \u00e0 des participations (groupe)": {
+ "account_number": "2671"
+ },
+ "Cr\u00e9ances rattach\u00e9es \u00e0 des participations (hors groupe)": {
+ "account_number": "2674"
+ },
+ "Versements repr\u00e9sentatifs d'apports non capitalis\u00e9s (appel de fonds)": {
+ "account_number": "2675"
+ },
+ "Avances consolidables": {
+ "account_number": "2676"
+ },
+ "Autres cr\u00e9ances rattach\u00e9es \u00e0 des participations": {
+ "account_number": "2677"
+ },
+ "Int\u00e9r\u00eats courus": {
+ "account_number": "2678"
+ },
+ "account_number": "267"
+ },
+ "Cr\u00e9ances rattach\u00e9es \u00e0 des soci\u00e9t\u00e9s en participation": {
+ "Principal": {
+ "account_number": "2681"
+ },
+ "Int\u00e9r\u00eats courus": {
+ "account_number": "2688"
+ },
+ "account_number": "268"
+ },
+ "Versements restant \u00e0 effectuer sur titres de participation non lib\u00e9r\u00e9s": {
+ "account_number": "269"
+ },
+ "account_number": "26"
+ },
+ "Autres immobilisations financi\u00e8res": {
+ "Titres immobilis\u00e9s autres que les titres immobilis\u00e9s de l'activit\u00e9 de portefeuille (droit de propri\u00e9t\u00e9)": {
+ "Actions": {
+ "account_number": "2711"
+ },
+ "Autres titres": {
+ "account_number": "2718"
+ },
+ "account_number": "271"
+ },
+ "Titres immobilis\u00e9s (droit de cr\u00e9ance)": {
+ "Obligations": {
+ "account_number": "2721"
+ },
+ "Bons": {
+ "account_number": "2722"
+ },
+ "account_number": "272"
+ },
+ "Titres immobilis\u00e9s de l'activit\u00e9 de portefeuille": {
+ "account_number": "273"
+ },
+ "Pr\u00eats": {
+ "Pr\u00eats participatifs": {
+ "account_number": "2741"
+ },
+ "Pr\u00eats aux associ\u00e9s": {
+ "account_number": "2742"
+ },
+ "Pr\u00eats au personnel": {
+ "account_number": "2743"
+ },
+ "Autres pr\u00eats": {
+ "account_number": "2748"
+ },
+ "account_number": "274"
+ },
+ "D\u00e9p\u00f4ts et cautionnements vers\u00e9s": {
+ "D\u00e9p\u00f4ts": {
+ "account_number": "2751"
+ },
+ "Cautionnements": {
+ "account_number": "2755"
+ },
+ "account_number": "275"
+ },
+ "Autres cr\u00e9ances immobilis\u00e9es": {
+ "Cr\u00e9ances diverses": {
+ "account_number": "2761"
+ },
+ "Int\u00e9r\u00eats courus": {
+ "Int\u00e9r\u00eats courus sur titres immobilis\u00e9s (droit de cr\u00e9ance)": {
+ "account_number": "27682"
+ },
+ "Int\u00e9r\u00eats courus sur pr\u00eats": {
+ "account_number": "27684"
+ },
+ "Int\u00e9r\u00eats courus sur d\u00e9p\u00f4ts et cautionnements": {
+ "account_number": "27685"
+ },
+ "Int\u00e9r\u00eats courus sur cr\u00e9ances diverses": {
+ "account_number": "27688"
+ },
+ "account_number": "2768"
+ },
+ "account_number": "276"
+ },
+ "(Actions propres ou parts propres)": {
+ "Actions propres ou parts propres": {
+ "account_number": "2771"
+ },
+ "Actions propres ou parts propres en voie d'annulation": {
+ "account_number": "2772"
+ },
+ "account_number": "277"
+ },
+ "Mali de fusion sur actifs financiers": {
+ "account_number": "278"
+ },
+ "Versements restant \u00e0 effectuer sur titres immobilis\u00e9s non lib\u00e9r\u00e9s": {
+ "account_number": "279"
+ },
+ "account_number": "27"
+ },
+ "Amortissements des immobilisations": {
+ "account_type": "Accumulated Depreciation",
+ "Amortissements des immobilisations incorporelles": {
+ "account_type": "Accumulated Depreciation",
+ "Frais d'\u00e9tablissement (m\u00eame ventilation que celle du compte 212)": {
+ "account_type": "Accumulated Depreciation",
+ "account_number": "2801"
+ },
+ "Frais de recherche et de d\u00e9veloppement": {
+ "account_type": "Accumulated Depreciation",
+ "account_number": "2803"
+ },
+ "Concessions et droits similaires, brevets, licences, logiciels, droits et valeurs similaires": {
+ "account_type": "Accumulated Depreciation",
+ "account_number": "2805"
+ },
+ "Fonds commercial": {
+ "account_type": "Accumulated Depreciation",
+ "account_number": "2807"
+ },
+ "Autres immobilisations incorporelles": {
+ "account_type": "Accumulated Depreciation",
+ "Mali de fusion sur actifs incorporels": {
+ "account_type": "Accumulated Depreciation",
+ "account_number": "28081"
+ },
+ "account_number": "2808"
+ },
+ "account_number": "280"
+ },
+ "Amortissements des immobilisations corporelles": {
+ "account_type": "Accumulated Depreciation",
+ "Terrains de gisement": {
+ "account_type": "Accumulated Depreciation",
+ "account_number": "2811"
+ },
+ "Agencements, am\u00e9nagements de terrains (m\u00eame ventilation que celle du compte 212)": {
+ "account_type": "Accumulated Depreciation",
+ "account_number": "2812"
+ },
+ "Constructions (m\u00eame ventilation que celle du compte 213)": {
+ "account_type": "Accumulated Depreciation",
+ "account_number": "2813"
+ },
+ "Constructions sur sol d'autrui (m\u00eame ventilation que celle du compte du 214)": {
+ "account_type": "Accumulated Depreciation",
+ "account_number": "2814"
+ },
+ "Installations techniques, mat\u00e9riel et outillage industriels (m\u00eame ventilation que celle du compte 218)": {
+ "account_type": "Accumulated Depreciation",
+ "account_number": "2815"
+ },
+ "Autres immobilisations corporelles (m\u00eame ventilation que celle du compte 218)": {
+ "account_type": "Accumulated Depreciation",
+ "Mali de fusion sur actifs corporels": {
+ "account_type": "Accumulated Depreciation",
+ "account_number": "28187"
+ },
+ "account_number": "2818"
+ },
+ "account_number": "281"
+ },
+ "Amortissements des immobilisations mises en concession": {
+ "account_number": "282"
+ },
+ "account_number": "28"
+ },
+ "D\u00e9pr\u00e9ciations des immobilisations": {
+ "D\u00e9pr\u00e9ciations des immobilisations incorporelles": {
+ "Marques, proc\u00e9d\u00e9s, droits et valeurs similaires": {
+ "account_number": "2905"
+ },
+ "Droit au bail": {
+ "account_number": "2906"
+ },
+ "Fonds commercial": {
+ "account_number": "2907"
+ },
+ "Autres immobilisations incorporelles": {
+ "Mali de fusion sur actifs incorporels": {
+ "account_number": "29081"
+ },
+ "account_number": "2908"
+ },
+ "account_number": "290"
+ },
+ "D\u00e9pr\u00e9ciations des immobilisations corporelles (m\u00eame ventilation que celle du compte 21)": {
+ "Terrains (autres que terrains de gisement)": {
+ "Mali de fusion sur actifs corporels": {
+ "account_number": "29187"
+ },
+ "account_number": "2911"
+ },
+ "account_number": "291"
+ },
+ "D\u00e9pr\u00e9ciations des immobilisations mises en concession": {
+ "account_number": "292"
+ },
+ "D\u00e9pr\u00e9ciations des immobilisations en cours": {
+ "Immobilisations corporelles en cours": {
+ "account_number": "2931"
+ },
+ "Immobilisations incorporelles en cours": {
+ "account_number": "2932"
+ },
+ "account_number": "293"
+ },
+ "D\u00e9pr\u00e9ciations des participations et cr\u00e9ances rattach\u00e9es \u00e0 des participations": {
+ "Titres de participation": {
+ "account_number": "2961"
+ },
+ "Autres formes de participation": {
+ "account_number": "2966"
+ },
+ "Cr\u00e9ances rattach\u00e9es \u00e0 des participations (m\u00eame ventilation que celle du compte 267)": {
+ "account_number": "2967"
+ },
+ "Cr\u00e9ances rattach\u00e9es \u00e0 des soci\u00e9t\u00e9s en participation (m\u00eame ventilation que celle du compte 268)": {
+ "account_number": "2968"
+ },
+ "account_number": "296"
+ },
+ "D\u00e9pr\u00e9ciations des autres immobilisations financi\u00e8res": {
+ "Titres immobilis\u00e9s autres que les titres immobilis\u00e9s de l'activit\u00e9 de portefeuille - droit de propri\u00e9t\u00e9": {
+ "account_number": "2971"
+ },
+ "Titres immobilis\u00e9s - droit de cr\u00e9ance (m\u00eame ventilation que celle du compte 272)": {
+ "account_number": "2972"
+ },
+ " Titres immobilis\u00e9s de l'activit\u00e9 de portefuille": {
+ "account_number": "2973"
+ },
+ "Pr\u00eats (m\u00eame ventilation que celle du compte 274)": {
+ "account_number": "2974"
+ },
+ "D\u00e9p\u00f4ts et cautionnements vers\u00e9s (m\u00eame ventilation que celle du compte 275)": {
+ "account_number": "2975"
+ },
+ "Autres cr\u00e9ances immobilis\u00e9es (m\u00eame ventilation que celle du compte 276)": {
+ "Mali de fusion sur actifs financiers": {
+ "account_number": "29787"
+ },
+ "account_number": "2976"
+ },
+ "account_number": "297"
+ },
+ "account_number": "29"
+ },
+ "account_number": "2"
+ },
+ "Comptes de Stocks et En-Cours": {
+ "root_type": "Asset",
+ "Mati\u00e8res premi\u00e8res (et fournitures)": {
+ "Mati\u00e8res (ou groupe) A": {
+ "account_number": "311"
+ },
+ "Mati\u00e8res (ou groupe) B": {
+ "account_number": "312"
+ },
+ "Fournitures A, B, C, ...": {
+ "account_number": "317"
+ },
+ "account_number": "31"
+ },
+ "Autres approvisionnements": {
+ "Mat\u00e8res consommables": {
+ "Mati\u00e8res (ou groupe) C": {
+ "account_number": "3211"
+ },
+ "Mati\u00e8res (ou groupe) D": {
+ "account_number": "3212"
+ },
+ "account_number": "321"
+ },
+ "Fournitures consommables": {
+ "Combustibles": {
+ "account_number": "3221"
+ },
+ "Produits d'entretien": {
+ "account_number": "3222"
+ },
+ "Fournitures d'atelier et d'usine": {
+ "account_number": "3223"
+ },
+ "Fournitures de magasin": {
+ "account_number": "3224"
+ },
+ "Fournitures de bureau": {
+ "account_number": "3225"
+ },
+ "account_number": "322"
+ },
+ "Emballages": {
+ "Emballages perdus": {
+ "account_number": "3261"
+ },
+ "Emballages r\u00e9cup\u00e9rables non identifiables": {
+ "account_number": "3265"
+ },
+ "Emballages \u00e0 usage mixte": {
+ "account_number": "3267"
+ },
+ "account_number": "326"
+ },
+ "account_number": "32"
+ },
+ "En-cours de production de biens": {
+ "Produits en cours": {
+ "Produits en cours P1": {
+ "account_number": "3311"
+ },
+ "Produits en cours P2": {
+ "account_number": "3312"
+ },
+ "account_number": "331"
+ },
+ "Travaux en cours": {
+ "Travaux en cours T1": {
+ "account_number": "3351"
+ },
+ "Travaux en cours T2": {
+ "account_number": "3352"
+ },
+ "account_number": "335"
+ },
+ "account_number": "33"
+ },
+ "En-cours de production de services": {
+ "Etudes en cours": {
+ "Etudes en cours E1": {
+ "account_number": "3411"
+ },
+ "Etudes en cours E2": {
+ "account_number": "3412"
+ },
+ "account_number": "341"
+ },
+ "Prestations de services en cours": {
+ "Prestations de services S1": {
+ "account_number": "3451"
+ },
+ "Prestations de services S2": {
+ "account_number": "3452"
+ },
+ "account_number": "345"
+ },
+ "account_number": "34"
+ },
+ "Stocks de produits": {
+ "account_type": "Stock",
+ "Produits interm\u00e9diaires": {
+ "account_type": "Stock",
+ "Produits interm\u00e9diaires (ou groupe) A": {
+ "account_type": "Stock",
+ "is_group": 1,
+ "account_number": "3511"
+ },
+ "Produits interm\u00e9diaires (ou groupe) B": {
+ "account_type": "Stock",
+ "is_group": 1,
+ "account_number": "3512"
+ },
+ "account_number": "351"
+ },
+ "Produits finis": {
+ "account_type": "Stock",
+ "Produits finis (ou groupe) A": {
+ "account_type": "Stock",
+ "is_group": 1,
+ "account_number": "3551"
+ },
+ "Produits finis (ou groupe) B": {
+ "account_type": "Stock",
+ "is_group": 1,
+ "account_number": "3552"
+ },
+ "account_number": "355"
+ },
+ "Produits r\u00e9siduels (ou mati\u00e8res de r\u00e9cup\u00e9ration)": {
+ "account_type": "Stock",
+ "D\u00e9chets": {
+ "account_type": "Stock",
+ "is_group": 1,
+ "account_number": "3581"
+ },
+ "Rebuts": {
+ "account_type": "Stock",
+ "is_group": 1,
+ "account_number": "3585"
+ },
+ "Mati\u00e8res de r\u00e9cup\u00e9ration": {
+ "account_type": "Stock",
+ "is_group": 1,
+ "account_number": "3586"
+ },
+ "account_number": "358"
+ },
+ "account_number": "35"
+ },
+ "(Compte \u00e0 ouvrir, le cas \u00e9ch\u00e9ant, sous l'intitul\u00e9 \"stocks provenant d'immobilisations\")": {
+ "account_number": "36"
+ },
+ "Stocks de marchandises": {
+ "Marchandises (ou groupe) A": {
+ "account_number": "371"
+ },
+ "Marchandises (ou groupe) B": {
+ "account_number": "372"
+ },
+ "account_number": "37"
+ },
+ "Stocks en voie d'acheminement, mis en d\u00e9p\u00f4t ou donn\u00e9s en consignation (en cas d'inventaire permanent en comptabilit\u00e9 g\u00e9n\u00e9rale)": {
+ "account_type": "Stock",
+ "account_number": "38"
+ },
+ "D\u00e9pr\u00e9ciations des stocks et en-cours": {
+ "D\u00e9pr\u00e9ciations des mati\u00e8res premi\u00e8res (et fournitures)": {
+ "Mati\u00e8res (ou groupe) A": {
+ "account_number": "3911"
+ },
+ "Mati\u00e8res (ou groupe) B": {
+ "account_number": "3912"
+ },
+ "Fournitures A, B, C, ...": {
+ "account_number": "3917"
+ },
+ "account_number": "391"
+ },
+ "D\u00e9pr\u00e9ciations des autres approvisionnements": {
+ "Mati\u00e8res consommables (m\u00eame ventilation que celle du compte 321)": {
+ "account_number": "3921"
+ },
+ "Fournitures consommables (m\u00eame ventilation que celle du compte 322)": {
+ "account_number": "3922"
+ },
+ "Emballages (m\u00eame ventilation que celle du compte 326)": {
+ "account_number": "3926"
+ },
+ "account_number": "392"
+ },
+ "D\u00e9pr\u00e9ciations des en-cours de production de biens": {
+ "Etudes en cours (m\u00eame ventilation que celle du compte 341)": {
+ "account_number": "3931"
+ },
+ "Travaux en cours (m\u00eame ventilation que celle du compte 335)": {
+ "account_number": "3935"
+ },
+ "account_number": "393"
+ },
+ "D\u00e9pr\u00e9ciations des en-cours de production de services": {
+ "Etudes en cours (m\u00eame ventilation que celle du compte 341)": {
+ "account_number": "3941"
+ },
+ "Prestations de services en cours (m\u00eame ventilation que celle du compte 345)": {
+ "account_number": "3945"
+ },
+ "account_number": "394"
+ },
+ "D\u00e9pr\u00e9ciations des stocks de produits": {
+ "Produits interm\u00e9diaires (m\u00eame ventilation que celle du compte 351)": {
+ "account_number": "3951"
+ },
+ "Produits finis (m\u00eame ventilation que celle du compte 355)": {
+ "account_number": "3955"
+ },
+ "account_number": "395"
+ },
+ "D\u00e9pr\u00e9ciations des stocks de marchandises": {
+ "Marchandise (ou groupe) A": {
+ "account_number": "3971"
+ },
+ "Marchandise (ou groupe) B": {
+ "account_number": "3972"
+ },
+ "account_number": "397"
+ },
+ "account_number": "39"
+ },
+ "account_number": "3"
+ },
+ "4-Comptes de Tiers (ACTIF)": {
+ "root_type": "Asset",
+ "40-Fournisseurs et Comptes Rattach\u00e9s (ACTIF)": {
+ "Fournisseurs d\u00e9biteurs": {
+ "Fournisseurs - Avances et acomptes vers\u00e9s sur commandes": {
+ "account_number": "4091"
+ },
+ "Fournisseurs - Cr\u00e9ances pour emballages et mat\u00e9riel \u00e0 rendre": {
+ "account_number": "4096"
+ },
+ "Fournisseurs - Autres avoirs": {
+ "Fournisseurs d'exploitation": {
+ "account_number": "40971"
+ },
+ "Fournisseurs d'immobilisation": {
+ "account_number": "40974"
+ },
+ "account_number": "4097"
+ },
+ "Rabais, remises, ristournes \u00e0 obtenir et autres avoirs non encore re\u00e7us": {
+ "account_number": "4098"
+ },
+ "account_number": "409"
+ }
+ },
+ "41-Clients et comptes rattach\u00e9s (ACTIF)": {
+ "account_type": "Receivable",
+ "Clients et Comptes rattach\u00e9s": {
+ "account_type": "Receivable",
+ "account_number": "410"
+ },
+ "Clients": {
+ "account_type": "Receivable",
+ "Clients - Ventes de biens ou de prestations de services": {
+ "account_type": "Receivable",
+ "account_number": "4111"
+ },
+ "Clients - Retenues de garantie": {
+ "account_type": "Receivable",
+ "account_number": "4117"
+ },
+ "account_number": "411"
+ },
+ "Clients - Effets \u00e0 recevoir": {
+ "account_type": "Receivable",
+ "account_number": "413"
+ },
+ "Clients douteux ou litigieux": {
+ "account_type": "Receivable",
+ "account_number": "416"
+ },
+ "Clients - Produits non encore factur\u00e9s": {
+ "account_type": "Receivable",
+ "Clients - Factures \u00e0 \u00e9tablir": {
+ "account_type": "Receivable",
+ "account_number": "4181"
+ },
+ "Clients - Int\u00e9r\u00eats courus": {
+ "account_type": "Receivable",
+ "account_number": "4188"
+ },
+ "account_number": "418"
+ }
+ },
+ "42-Personnel et comptes rattach\u00e9s (ACTIF)": {
+ "Personnel - Avances et acomptes": {
+ "account_number": "425"
+ }
+ },
+ "43-S\u00e9curit\u00e9 sociale et autres organismes sociaux (ACTIF)": {
+ "S\u00e9curit\u00e9 sociale": {
+ "account_number": "431"
+ },
+ "Autres organismes sociaux": {
+ "account_number": "437"
+ },
+ "438-Organismes sociaux - Produits \u00e0 recevoir": {
+ "Produits \u00e0 recevoir": {
+ "account_number": "4387"
+ }
+ }
+ },
+ "44-Etat et autres collectivit\u00e9s publiques (ACTIF)": {
+ "Etat - Subventions \u00e0 recevoir": {
+ "Subventions d'investissement": {
+ "account_number": "4411"
+ },
+ "Subventions d'exploitation": {
+ "account_number": "4417"
+ },
+ "Subventions d'\u00e9quilibre": {
+ "account_number": "4418"
+ },
+ "Avances sur subventions": {
+ "account_number": "4419"
+ },
+ "account_number": "441"
+ },
+ "Op\u00e9rations particuli\u00e8res avec l'Etat, les collectivit\u00e9s publiques, les organismes internationaux": {
+ "Cr\u00e9ances sur l'Etat r\u00e9sultant de la suppression de la r\u00e8gle du d\u00e9calage d'un mois en mati\u00e8re de TVA": {
+ "account_number": "4431"
+ },
+ "Int\u00e9r\u00eats courus sur cr\u00e9ances figurant au compte 4431": {
+ "account_number": "4438"
+ },
+ "account_number": "443"
+ },
+ "Etat - Taxes sur le chiffre d'affaires (ACTIF)": {
+ "TVA due intracommunautaire": {
+ "account_number": "4452"
+ },
+ "Taxes sur le chiffre d'affaires d\u00e9ductibles": {
+ "TVA sur immobilisations": {
+ "account_number": "44562"
+ },
+ "TVA transf\u00e9r\u00e9e par d'autres entreprises": {
+ "account_number": "44563"
+ },
+ "TVA sur autres biens et services": {
+ "tax_rate": 20,
+ "account_number": "44566"
+ },
+ "Cr\u00e9dit de TVA \u00e0 reporter": {
+ "account_number": "44567"
+ },
+ "Taxes assimil\u00e9es \u00e0 la TVA": {
+ "account_number": "44568"
+ },
+ "account_number": "4456"
+ },
+ "4458-Taxes sur le chiffre d'affaires \u00e0 r\u00e9gulariser ou en attente (ACTIF)": {
+ "Acomptes - R\u00e9gime simplifi\u00e9 d'imposition": {
+ "account_number": "44581"
+ },
+ "Acomptes - R\u00e9gime du forfait": {
+ "account_number": "44582"
+ },
+ "Remboursement de taxes sur le chiffre d'affaires demand\u00e9": {
+ "account_number": "44583"
+ },
+ "Taxes sur le chiffre d'affaires sur factures non parvenues": {
+ "account_number": "44586"
+ }
+ }
+ },
+ "Etat - Charges \u00e0 payer et produits \u00e0 recevoir": {
+ "Charges fiscales sur cong\u00e9s \u00e0 payer": {
+ "account_number": "4482"
+ },
+ "Charges \u00e0 payer": {
+ "account_number": "4486"
+ },
+ "Produits \u00e0 recevoir": {
+ "account_number": "4487"
+ },
+ "account_number": "448"
+ }
+ },
+ "45-Groupe et associ\u00e9s (ACTIF)": {
+ "Associ\u00e9s - Op\u00e9rations sur le capital (ACTIF)": {
+ "456-Apporteurs - Capital appel\u00e9, non vers\u00e9": {
+ "Actionnaires - Capital souscrit et appel\u00e9, non vers\u00e9": {
+ "account_number": "45621"
+ },
+ "Associ\u00e9s - Capital appel\u00e9, non vers\u00e9": {
+ "account_number": "45625"
+ },
+ "account_number": "4562"
+ }
+ }
+ },
+ "46-D\u00e9biteurs divers et cr\u00e9diteurs divers (ACTIF)": {
+ "Cr\u00e9ances sur cessions d'immobilisations": {
+ "account_number": "462"
+ },
+ "Cr\u00e9ances sur cessions de valeurs mobili\u00e8res de placement": {
+ "account_number": "465"
+ },
+ "467-Autres comptes d\u00e9biteurs ou cr\u00e9diteurs (ACTIF)": {},
+ "468-Divers - Charges \u00e0 payer et produits \u00e0 recevoir (ACTIF)": {
+ "Produits \u00e0 recevoir": {
+ "account_number": "4687"
+ }
+ }
+ },
+ "47-Comptes transitoires ou d'attente (ACTIF)": {
+ "471-Comptes d'attente (ACTIF)": {
+ "account_type": "Temporary"
+ },
+ "Diff\u00e9rences de conversion (ACTIF)": {
+ "Diminution des cr\u00e9ances": {
+ "account_number": "4761"
+ },
+ "Augmentation des dettes": {
+ "account_number": "4762"
+ },
+ "Diff\u00e9rences compens\u00e9es par couverture de change": {
+ "account_number": "4768"
+ },
+ "account_number": "476"
+ },
+ "Autres comptes transitoires (ACTIF)": {
+ "Mali de fusion sur actif circulant": {
+ "account_number": "4781"
+ },
+ "478-Diff\u00e9rences d'\u00e9valuation sur instruments de tr\u00e9sorerie (ACTIF)": {
+ "account_number": "4786"
+ }
+ }
+ },
+ "48-Comptes de r\u00e9gularisation (ACTIF)": {
+ "Charges \u00e0 r\u00e9partir sur plusieurs exercices": {
+ "Frais d'\u00e9mission des emprunts": {
+ "account_number": "4816"
+ },
+ "account_number": "481"
+ },
+ "Charges constat\u00e9es d'avance": {
+ "account_number": "486"
+ },
+ "488-Comptes de r\u00e9partition p\u00e9riodique des charges et des produits (ACTIF)": {
+ "Charges": {
+ "account_number": "4886"
+ }
+ }
+ },
+ "49-D\u00e9pr\u00e9ciation des comptes de tiers (ACTIF)": {
+ "D\u00e9pr\u00e9ciations des comptes clients": {
+ "account_number": "491"
+ },
+ "D\u00e9pr\u00e9ciations des comptes du groupe et des associ\u00e9s": {
+ "Comptes du groupe": {
+ "account_number": "4951"
+ },
+ "Comptes courants des associ\u00e9s": {
+ "account_number": "4955"
+ },
+ "Op\u00e9rations faites en commun et en GIE": {
+ "account_number": "4958"
+ },
+ "account_number": "495"
+ },
+ "D\u00e9pr\u00e9ciations des comptes de d\u00e9biteurs divers": {
+ "Cr\u00e9ances sur cessions d'immobilisations": {
+ "account_number": "4962"
+ },
+ "Cr\u00e9ances sur cessions de valeurs mobili\u00e8res de placement": {
+ "account_number": "4965"
+ },
+ "Autres comptes d\u00e9biteurs": {
+ "account_number": "4967"
+ },
+ "account_number": "496"
+ }
+ }
+ },
+ "4-Comptes de Tiers (PASSIF)": {
+ "root_type": "Liability",
+ "40-Fournisseurs et Comptes Rattach\u00e9s (PASSIF)": {
+ "account_type": "Payable",
+ "Fournisseurs": {
+ "account_type": "Payable",
+ "Fournisseurs - Achats de biens ou de prestations de services": {
+ "account_type": "Payable",
+ "account_number": "4011"
+ },
+ "Fournisseurs - Retenues de garantie": {
+ "account_type": "Payable",
+ "account_number": "4017"
+ },
+ "account_number": "401"
+ },
+ "Fournisseurs - Effets \u00e0 payer": {
+ "account_type": "Payable",
+ "account_number": "403"
+ },
+ "Fournisseurs d'immobilisations": {
+ "account_type": "Payable",
+ "Fournisseurs - Achats d'immobilisations": {
+ "account_type": "Payable",
+ "account_number": "4041"
+ },
+ "Fournisseurs d'immobilisations - Retenues de garantie": {
+ "account_type": "Payable",
+ "account_number": "4047"
+ },
+ "account_number": "404"
+ },
+ "Fournisseurs d'immobilisations - Effets \u00e0 payer": {
+ "account_type": "Payable",
+ "account_number": "405"
+ },
+ "Fournisseurs - Factures non parvenues": {
+ "account_type": "Stock Received But Not Billed",
+ "Fournisseurs": {
+ "account_type": "Stock Received But Not Billed",
+ "account_number": "4081"
+ },
+ "Fournisseurs d'immobilisations": {
+ "account_type": "Stock Received But Not Billed",
+ "account_number": "4084"
+ },
+ "Fournisseurs - Int\u00e9r\u00eats courus": {
+ "account_type": "Stock Received But Not Billed",
+ "account_number": "4088"
+ },
+ "account_number": "408"
+ }
+ },
+ "41-Clients et comptes rattach\u00e9s (PASSIF)": {
+ "Clients cr\u00e9diteurs": {
+ "Clients - Avances et acomptes re\u00e7us sur commandes": {
+ "account_number": "4191"
+ },
+ "Clients - Dettes pour emballages et mat\u00e9riels consign\u00e9s": {
+ "account_number": "4196"
+ },
+ "Clients - Autres avoirs": {
+ "account_number": "4197"
+ },
+ "Rabais, remises, ristournes \u00e0 accorder et autres avoirs \u00e0 \u00e9tablir": {
+ "account_number": "4198"
+ },
+ "account_number": "419"
+ }
+ },
+ "42-Personnel et comptes rattach\u00e9s (PASSIF)": {
+ "Personnel - R\u00e9mun\u00e9rations dues": {
+ "account_number": "421"
+ },
+ "Comit\u00e9s d'entreprises, d'\u00e9tablissement...": {
+ "account_number": "422"
+ },
+ "Participation des salari\u00e9s aux r\u00e9sultats": {
+ "R\u00e9serve sp\u00e9ciale": {
+ "account_number": "4246"
+ },
+ "Comptes courants": {
+ "account_number": "4248"
+ },
+ "account_number": "424"
+ },
+ "Personnel - D\u00e9p\u00f4ts": {
+ "account_number": "426"
+ },
+ "Personnel - Oppositions": {
+ "account_number": "427"
+ },
+ "Personnel - Charges \u00e0 payer et produits \u00e0 recevoir": {
+ "Dettes provisionn\u00e9es pour cong\u00e9s \u00e0 payer": {
+ "account_number": "4282"
+ },
+ "Dettes provisionn\u00e9es pour participation des salari\u00e9s aux r\u00e9sultats": {
+ "account_number": "4284"
+ },
+ "Autres charges \u00e0 payer": {
+ "account_number": "4286"
+ },
+ "Produits \u00e0 recevoir": {
+ "account_number": "4287"
+ },
+ "account_number": "428"
+ }
+ },
+ "43-S\u00e9curit\u00e9 sociale et autres organismes sociaux (PASSIF)": {
+ "438-Organismes sociaux - Charges \u00e0 payer": {
+ "Charges sociales sur cong\u00e9s \u00e0 payer": {
+ "account_number": "4382"
+ },
+ "Autres charges \u00e0 payer": {
+ "account_number": "4386"
+ }
+ }
+ },
+ "44-Etat et autres collectivit\u00e9s publiques (PASSIF)": {
+ "Etat - Imp\u00f4ts et taxes recouvrables sur des tiers": {
+ "Obligataires": {
+ "account_number": "4424"
+ },
+ "Associ\u00e9s": {
+ "account_number": "4425"
+ },
+ "account_number": "442"
+ },
+ "Etat - Imp\u00f4ts sur les b\u00e9n\u00e9fices": {
+ "account_number": "444"
+ },
+ "Etat - Taxes sur le chiffre d'affaires (PASSIF)": {
+ "Taxes sur le chiffre d'affaires \u00e0 d\u00e9caisser": {
+ "TVA \u00e0 d\u00e9caisser": {
+ "account_number": "44551"
+ },
+ "Taxes assimil\u00e9es \u00e0 la TVA": {
+ "account_number": "44558"
+ },
+ "account_number": "4455"
+ },
+ "Taxes sur le chiffre d'affaires collect\u00e9es par l'entreprise": {
+ "TVA collect\u00e9e": {
+ "account_type": "Tax",
+ "is_group": 1,
+ "account_number": "44571"
+ },
+ "Taxes assimil\u00e9es \u00e0 la TVA": {
+ "account_number": "44578"
+ },
+ "account_number": "4457"
+ },
+ "4458-Taxes sur le chiffre d'affaires \u00e0 r\u00e9gulariser ou en attente (PASSIF)": {
+ "TVA r\u00e9cup\u00e9r\u00e9e d'avance": {
+ "account_number": "44584"
+ },
+ "Taxes sur le chiffre d'affaires sur factures \u00e0 \u00e9tablir": {
+ "account_number": "44587"
+ }
+ }
+ },
+ "Obligations cautionn\u00e9es": {
+ "account_number": "446"
+ },
+ "Autres imp\u00f4ts, taxes et versements assimil\u00e9s": {
+ "account_number": "447"
+ },
+ "Quotas d'\u00e9mission \u00e0 acqu\u00e9rir": {
+ "account_number": "449"
+ }
+ },
+ "45-Groupe et associ\u00e9s (PASSIF)": {
+ "Groupe (PASSIF)": {
+ "account_number": "451"
+ },
+ "Associ\u00e9s - Comptes courants (PASSIF)": {
+ "Principal (PASSIF)": {
+ "account_number": "4551"
+ },
+ "Int\u00e9r\u00eats courus (PASSIF)": {
+ "account_number": "4558"
+ },
+ "account_number": "455"
+ },
+ "Associ\u00e9s - Op\u00e9rations sur le capital (PASSIF)": {
+ "456-Associ\u00e9s - Comptes d'apport en soci\u00e9t\u00e9": {
+ "Apports en nature": {
+ "account_number": "45611"
+ },
+ "Apports en num\u00e9raire": {
+ "account_number": "45615"
+ },
+ "account_number": "4561"
+ },
+ "Associ\u00e9s - Versements re\u00e7us sur augmentation de capital": {
+ "account_number": "4563"
+ },
+ "Associ\u00e9s - Versements anticip\u00e9s": {
+ "account_number": "4564"
+ },
+ "Actionnaires d\u00e9faillants": {
+ "account_number": "4566"
+ },
+ "Associ\u00e9s - Capital \u00e0 rembourser": {
+ "account_number": "4567"
+ }
+ },
+ "Associ\u00e9s - Dividendes \u00e0 payer": {
+ "account_number": "457"
+ },
+ "Associ\u00e9s - Op\u00e9rations faites en commun et en GIE": {
+ "Op\u00e9rations courantes": {
+ "account_number": "4581"
+ },
+ "Int\u00e9r\u00eats courus": {
+ "account_number": "4588"
+ },
+ "account_number": "458"
+ }
+ },
+ "46-D\u00e9biteurs divers et cr\u00e9diteurs divers (PASSIF)": {
+ "Dettes sur acquisitions de valeurs mobili\u00e8res de placement": {
+ "account_number": "464"
+ },
+ "467-Autres comptes d\u00e9biteurs ou cr\u00e9diteurs (PASSIF)": {},
+ "468-Divers - Charges \u00e0 payer et produits \u00e0 recevoir (PASSIF)": {
+ "Charges \u00e0 payer": {
+ "account_number": "4686"
+ }
+ }
+ },
+ "47-Comptes transitoires ou d'attente (PASSIF)": {
+ "471-Comptes d'attente (PASSIF)": {
+ "account_type": "Temporary"
+ },
+ "Diff\u00e9rences de conversion (PASSIF)": {
+ "Augmentation des cr\u00e9ances": {
+ "account_number": "4771"
+ },
+ "Diminution des dettes": {
+ "account_number": "4772"
+ },
+ "Diff\u00e9rences compens\u00e9es par couverture de change": {
+ "account_number": "4778"
+ },
+ "account_number": "477"
+ },
+ "478-Autres comptes transitoires (PASSIF)": {
+ "Diff\u00e9rences d'\u00e9valuation sur instruments de tr\u00e9sorerie (PASSIF)": {
+ "account_number": "4787"
+ }
+ }
+ },
+ "48-Comptes de r\u00e9gularisation (PASSIF)": {
+ "Produits constat\u00e9s d'avance": {
+ "account_number": "487"
+ },
+ "448-Comptes de r\u00e9partition p\u00e9riodique des charges et des produits (PASSIF)": {
+ "Produits": {
+ "account_number": "4887"
+ }
+ }
+ }
+ },
+ "Comptes Financiers": {
+ "root_type": "Asset",
+ "Valeurs mobili\u00e8res de placement": {
+ "Parts dans des entreprises li\u00e9es": {
+ "account_number": "501"
+ },
+ "Actions propres": {
+ "Actions destin\u00e9es \u00e0 \u00eatre attribu\u00e9es aux employ\u00e9s et affect\u00e9es \u00e0 des plans d\u00e9termin\u00e9s": {
+ "account_number": "5021"
+ },
+ "Actions disponibles pour \u00eatre attribu\u00e9es aux employ\u00e9s ou pour la r\u00e9gularisation des cours de bourse": {
+ "account_number": "5022"
+ },
+ "account_number": "502"
+ },
+ "Actions": {
+ "Titres cot\u00e9s": {
+ "account_number": "5031"
+ },
+ "Titres non cot\u00e9s": {
+ "account_number": "5035"
+ },
+ "account_number": "503"
+ },
+ "Autres titres conf\u00e9rant un droit de propri\u00e9t\u00e9": {
+ "account_number": "504"
+ },
+ "Obligations et bons \u00e9mis par la soci\u00e9t\u00e9 et rachet\u00e9s par elle": {
+ "account_number": "505"
+ },
+ "Obligations": {
+ "Titres cot\u00e9s": {
+ "account_number": "5061"
+ },
+ "Titres non cot\u00e9s": {
+ "account_number": "5065"
+ },
+ "account_number": "506"
+ },
+ "Bons du Tr\u00e9sor et bons de caisse \u00e0 court terme": {
+ "account_number": "507"
+ },
+ "Autres valeurs mobili\u00e8res de placement et autres cr\u00e9ances assimil\u00e9es": {
+ "Autres valeurs mobili\u00e8res": {
+ "account_number": "5081"
+ },
+ "Bons de souscription": {
+ "account_number": "5082"
+ },
+ "Int\u00e9r\u00eats courus sur obligations, bons et valeurs assimil\u00e9es": {
+ "account_number": "5088"
+ },
+ "account_number": "508"
+ },
+ "Versements restant \u00e0 effectuer sur valeurs mobili\u00e8res de placement non lib\u00e9r\u00e9es": {
+ "account_number": "509"
+ },
+ "account_number": "50"
+ },
+ "Banques, \u00e9tablissements financiers et assimil\u00e9s": {
+ "Valeurs \u00e0 l'encaissement": {
+ "Coupons \u00e9chus \u00e0 l'encaissement": {
+ "account_number": "5111"
+ },
+ "Ch\u00e8ques \u00e0 encaisser": {
+ "account_number": "5112"
+ },
+ "Effets \u00e0 l'encaissement": {
+ "account_number": "5113"
+ },
+ "Effets \u00e0 l'escompte": {
+ "account_number": "5114"
+ },
+ "account_number": "511"
+ },
+ "Banques": {
+ "account_type": "Bank",
+ "Comptes en monnaie nationale": {
+ "account_type": "Bank",
+ "account_number": "5121"
+ },
+ "Comptes en devises": {
+ "account_type": "Bank",
+ "account_number": "5124"
+ },
+ "account_number": "512"
+ },
+ "Ch\u00e8ques postaux": {
+ "account_number": "514"
+ },
+ "\"Caisses\" du Tr\u00e9sor et des \u00e9tablissements publics": {
+ "account_number": "515"
+ },
+ "Soci\u00e9t\u00e9s de bourse": {
+ "account_number": "516"
+ },
+ "Autres organismes financiers": {
+ "account_number": "517"
+ },
+ "Int\u00e9r\u00eats courus": {
+ "Int\u00e9r\u00eats courus \u00e0 payer": {
+ "account_number": "5181"
+ },
+ "Int\u00e9r\u00eats courus \u00e0 recevoir": {
+ "account_number": "5188"
+ },
+ "account_number": "518"
+ },
+ "Concours bancaires courants": {
+ "Cr\u00e9dit de mobilisation des cr\u00e9ances commerciales (CMCC)": {
+ "account_number": "5191"
+ },
+ "Mobilisation de cr\u00e9ances n\u00e9es \u00e0 l'\u00e9tranger": {
+ "account_number": "5193"
+ },
+ "Int\u00e9r\u00eats courus sur concours bancaires courants": {
+ "account_number": "5198"
+ },
+ "account_number": "519"
+ },
+ "account_number": "51"
+ },
+ "Instruments de tr\u00e9sorerie": {
+ "is_group": 1,
+ "account_number": "52"
+ },
+ "Caisse": {
+ "account_type": "Cash",
+ "Caisse si\u00e8ge social": {
+ "account_type": "Cash",
+ "Caisse en monnaie nationale": {
+ "account_type": "Cash",
+ "account_number": "5311"
+ },
+ "Caisse en devises": {
+ "account_type": "Cash",
+ "account_number": "5314"
+ },
+ "account_number": "531"
+ },
+ "Caisse succursale (ou usine) A": {
+ "account_type": "Cash",
+ "account_number": "532"
+ },
+ "Caisse succursale (ou usine) B": {
+ "account_type": "Cash",
+ "account_number": "533"
+ },
+ "account_number": "53"
+ },
+ "R\u00e9gies d'avance et accr\u00e9ditifs": {
+ "is_group": 1,
+ "account_number": "54"
+ },
+ "Virements internes": {
+ "is_group": 1,
+ "account_number": "58"
+ },
+ "D\u00e9pr\u00e9ciations des comptes financiers": {
+ "D\u00e9pr\u00e9ciations des valeurs mobili\u00e8res de placement": {
+ "Actions": {
+ "account_number": "5903"
+ },
+ "Autres titres conf\u00e9rant un droit de propri\u00e9t\u00e9": {
+ "account_number": "5904"
+ },
+ "Obligations": {
+ "account_number": "5906"
+ },
+ "Autres valeurs mobili\u00e8res de placement et cr\u00e9ances assimil\u00e9es": {
+ "account_number": "5908"
+ },
+ "account_number": "590"
+ },
+ "account_number": "59"
+ },
+ "account_number": "5"
+ },
+ "Comptes de Charges": {
+ "root_type": "Expense",
+ "Achats (sauf 603)": {
+ "Achats stock\u00e9s - Mati\u00e8res premi\u00e8res (et fournitures)": {
+ "account_type": "Cost of Goods Sold",
+ "Mati\u00e8res (ou groupe) A": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "6011"
+ },
+ "Mati\u00e8res (ou groupe) B": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "6012"
+ },
+ "Fournitures A, B, C...": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "6017"
+ },
+ "account_number": "601"
+ },
+ "Achats stock\u00e9s - Autres approvisionnements": {
+ "account_type": "Cost of Goods Sold",
+ "Mati\u00e8res consommables": {
+ "account_type": "Cost of Goods Sold",
+ "Mati\u00e8res (ou groupe) C": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "60211"
+ },
+ "Mati\u00e8res (ou groupe) D": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "60212"
+ },
+ "account_number": "6021"
+ },
+ "Fournitures consommables": {
+ "account_type": "Cost of Goods Sold",
+ "Combustibles": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "60221"
+ },
+ "Produits d'entretien": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "60222"
+ },
+ "Fournitures d'atelier et d'usine": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "60223"
+ },
+ "Fournitures de magasin": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "60224"
+ },
+ "Fournitures de bureau": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "60225"
+ },
+ "account_number": "6022"
+ },
+ "Emballages": {
+ "account_type": "Cost of Goods Sold",
+ "Emballages perdus": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "60261"
+ },
+ "Emballages r\u00e9cup\u00e9rables non identifiables": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "60265"
+ },
+ "Emballages \u00e0 usage mixte": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "60267"
+ },
+ "account_number": "6026"
+ },
+ "account_number": "602"
+ },
+ "Variations des stocks (approvisionnements et marchandises)": {
+ "account_type": "Stock Adjustment",
+ "Variation des stocks de mati\u00e8res premi\u00e8res (et fournitures)": {
+ "account_type": "Stock Adjustment",
+ "account_number": "6031"
+ },
+ "Variation des stocks des autres approvisionnements": {
+ "account_type": "Stock Adjustment",
+ "account_number": "6032"
+ },
+ "Variation des stocks de marchandises": {
+ "account_type": "Stock Adjustment",
+ "account_number": "6037"
+ },
+ "account_number": "603"
+ },
+ "Achats d'\u00e9tudes et prestations de service": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "604"
+ },
+ "Achats de mat\u00e9riel, \u00e9quipements et travaux": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "605"
+ },
+ "Achats non stock\u00e9s de mati\u00e8res et founitures": {
+ "account_type": "Cost of Goods Sold",
+ "Fournitures non stockables (eau, \u00e9nergie...)": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "6061"
+ },
+ "Fournitures d'entretien et de petit \u00e9quipement": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "6063"
+ },
+ "Fournitures administratives": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "6064"
+ },
+ "Autres mati\u00e8res et fournitures": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "6068"
+ },
+ "account_number": "606"
+ },
+ "Achats de marchandises": {
+ "account_type": "Cost of Goods Sold",
+ "Marchandises (ou groupe) A": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "6071"
+ },
+ "Marchandises (ou groupe) B": {
+ "account_type": "Cost of Goods Sold",
+ "account_number": "6072"
+ },
+ "account_number": "607"
+ },
+ "(Compte r\u00e9serv\u00e9, le cas \u00e9ch\u00e9ant, \u00e0 la recapitulation des Frais accessoires incorpor\u00e9s aux achats)": {
+ "account_type": "Expenses Included In Valuation",
+ "account_number": "608"
+ },
+ "Rabais, remises et ristournes obtenus sur achats": {
+ "Rabais, remises et ristournes obtenus sur achats - de mati\u00e8res premi\u00e8res (et fournitures)": {
+ "account_number": "6091"
+ },
+ "Rabais, remises et ristournes obtenus sur achats - d'autres approvisionnements stock\u00e9s": {
+ "account_number": "6092"
+ },
+ "Rabais, remises et ristournes obtenus sur achats - d'\u00e9tudes et prestations de services": {
+ "account_number": "6094"
+ },
+ "Rabais, remises et ristournes obtenus sur achats - de mat\u00e9riel, \u00e9quipements et travaux": {
+ "account_number": "6095"
+ },
+ "Rabais, remises et ristournes obtenus sur achats - d'approvisionnements non stock\u00e9s": {
+ "account_number": "6096"
+ },
+ "Rabais, remises et ristournes obtenus sur achats - de marchandises": {
+ "account_number": "6097"
+ },
+ "Rabais, remises et ristournes non affect\u00e9s": {
+ "account_number": "6098"
+ },
+ "account_number": "609"
+ },
+ "account_number": "60"
+ },
+ "Services ext\u00e9rieurs": {
+ "Sous-traitance g\u00e9n\u00e9rale": {
+ "account_number": "611"
+ },
+ "Redevances de cr\u00e9dit-bail": {
+ "Cr\u00e9dit-bail mobilier": {
+ "account_number": "6122"
+ },
+ "Cr\u00e9dit-bail immobilier": {
+ "account_number": "6125"
+ },
+ "account_number": "612"
+ },
+ "Locations": {
+ "Locations immobili\u00e8res": {
+ "account_number": "6132"
+ },
+ "Locations mobili\u00e8res": {
+ "account_number": "6135"
+ },
+ "Malis sur emballages": {
+ "account_number": "6136"
+ },
+ "account_number": "613"
+ },
+ "Charges locatives et de copropri\u00e9t\u00e9": {
+ "account_number": "614"
+ },
+ "Entretiens et r\u00e9parations": {
+ "Entretiens et r\u00e9parations - sur biens immobiliers": {
+ "account_number": "6152"
+ },
+ "Entretiens et r\u00e9parations - sur biens mobiliers": {
+ "account_number": "6155"
+ },
+ "Maintenance": {
+ "account_number": "6156"
+ },
+ "account_number": "615"
+ },
+ "Primes d'assurance": {
+ "Multirisques": {
+ "account_number": "6161"
+ },
+ "Assurance obligatoire dommage construction": {
+ "account_number": "6162"
+ },
+ "Assurance-transport": {
+ "Assurance-transport - sur achats": {
+ "account_number": "61636"
+ },
+ "Assurance-transport - sur ventes": {
+ "account_number": "61637"
+ },
+ "Assurance-transport - sur autres biens": {
+ "account_number": "61638"
+ },
+ "account_number": "6163"
+ },
+ "Risques d'exploitation": {
+ "account_number": "6164"
+ },
+ "Insolvabilit\u00e9 clients": {
+ "account_number": "6165"
+ },
+ "account_number": "616"
+ },
+ "Etudes et recherches": {
+ "account_number": "617"
+ },
+ "Divers": {
+ "Documentation g\u00e9n\u00e9rale": {
+ "account_number": "6181"
+ },
+ "Documentation technique": {
+ "account_number": "6183"
+ },
+ "Frais de colloques, s\u00e9minaires, conf\u00e9rences": {
+ "account_number": "6185"
+ },
+ "account_number": "618"
+ },
+ "Rabais, remises et ristournes obtenus sur services ext\u00e9rieurs": {
+ "account_number": "619"
+ },
+ "account_number": "61"
+ },
+ "Autres services ext\u00e9rieurs": {
+ "Personnel ext\u00e9rieur \u00e0 l'entreprise": {
+ "Personnel int\u00e9rimaire": {
+ "account_number": "6211"
+ },
+ "Personnel d\u00e9tach\u00e9 ou pr\u00eat\u00e9 \u00e0 l'entreprise": {
+ "account_number": "6214"
+ },
+ "account_number": "621"
+ },
+ "R\u00e9mun\u00e9rations d'interm\u00e9diaires et honoraires": {
+ "Commissions et courtages sur achats": {
+ "account_number": "6221"
+ },
+ "Commissions et courtages sur ventes": {
+ "account_number": "6222"
+ },
+ "R\u00e9mun\u00e9rations des transitaires": {
+ "account_number": "6224"
+ },
+ "R\u00e9mun\u00e9rations d'affacturage": {
+ "account_number": "6225"
+ },
+ "Honoraires": {
+ "account_number": "6226"
+ },
+ "Frais d'actes et de contentieux": {
+ "account_number": "6227"
+ },
+ "Divers": {
+ "account_number": "6228"
+ },
+ "account_number": "622"
+ },
+ "Publicit\u00e9, publications, relations publiques": {
+ "Annonces et insertions": {
+ "account_number": "6231"
+ },
+ "Echantillons": {
+ "account_number": "6232"
+ },
+ "Foires et expositions": {
+ "account_number": "6233"
+ },
+ "Cadeaux \u00e0 la client\u00e8le": {
+ "account_number": "6234"
+ },
+ "Primes": {
+ "account_number": "6235"
+ },
+ "Catalogues et imprim\u00e9s": {
+ "account_number": "6236"
+ },
+ "Publications": {
+ "account_number": "6237"
+ },
+ "Divers (pourboires, dons courants...)": {
+ "account_number": "6238"
+ },
+ "account_number": "623"
+ },
+ "Transports de biens et transports collectifs du personnel": {
+ "Transports sur achats": {
+ "account_number": "6241"
+ },
+ "Transports sur ventes": {
+ "account_type": "Chargeable",
+ "account_number": "6242"
+ },
+ "Transports entre \u00e9tablissements ou chantiers": {
+ "account_number": "6243"
+ },
+ "Transports administratifs": {
+ "account_number": "6244"
+ },
+ "Transports collectifs du personnel": {
+ "account_number": "6247"
+ },
+ "Divers": {
+ "account_number": "6248"
+ },
+ "account_number": "624"
+ },
+ "D\u00e9placements, missions et r\u00e9ceptions": {
+ "Voyages et d\u00e9placements": {
+ "account_number": "6251"
+ },
+ "Frais de d\u00e9m\u00e9nagement": {
+ "account_number": "6255"
+ },
+ "Missions": {
+ "account_number": "6256"
+ },
+ "R\u00e9ceptions": {
+ "account_number": "6257"
+ },
+ "account_number": "625"
+ },
+ "Frais postaux et de t\u00e9l\u00e9communications": {
+ "account_number": "626"
+ },
+ "Services bancaires et assimil\u00e9s": {
+ "Frais sur titres (achat, vente, garde)": {
+ "account_number": "6271"
+ },
+ "Commissions et frais sur \u00e9mission d'emprunts": {
+ "account_number": "6272"
+ },
+ "Frais sur effets": {
+ "account_number": "6275"
+ },
+ "Location de coffres": {
+ "account_number": "6276"
+ },
+ "Autres frais et commissions sur prestations de services": {
+ "account_number": "6278"
+ },
+ "account_number": "627"
+ },
+ "Divers": {
+ "Concours divers (cotisations...)": {
+ "account_number": "6281"
+ },
+ "Frais de recrutement de personnel": {
+ "account_number": "6284"
+ },
+ "account_number": "628"
+ },
+ "Rabais, remises et ristournes obtenus sur autres services ext\u00e9rieurs": {
+ "account_number": "629"
+ },
+ "account_number": "62"
+ },
+ "Imp\u00f4ts, taxes et versements assimil\u00e9s": {
+ "Imp\u00f4ts, taxes et versements assimil\u00e9s sur r\u00e9mun\u00e9rations (administrations des imp\u00f4ts)": {
+ "Taxes sur les salaires": {
+ "account_number": "6311"
+ },
+ "Taxe d'apprentissage": {
+ "account_number": "6312"
+ },
+ "Participation des employeurs \u00e0 la formation professionnelle continue": {
+ "account_number": "6313"
+ },
+ "Cotisation pour d\u00e9faut d'investissement obligatoire dans la construction": {
+ "account_number": "6314"
+ },
+ "Autres": {
+ "account_number": "6318"
+ },
+ "account_number": "631"
+ },
+ "Imp\u00f4ts, taxes et versements assimil\u00e9s sur r\u00e9mun\u00e9rations (autres organismes)": {
+ "Versement de transport": {
+ "account_number": "6331"
+ },
+ "Allocations logement": {
+ "account_number": "6332"
+ },
+ "Participation des employeurs \u00e0 la formation professionnelle continue": {
+ "account_number": "6333"
+ },
+ "Participation des employeurs \u00e0 l'effort de construction": {
+ "account_number": "6334"
+ },
+ "Versements lib\u00e9ratoires ouvrant droit \u00e0 l'\u00e9xon\u00e9ration de la taxe d'apprentissage": {
+ "account_number": "6335"
+ },
+ "Autres": {
+ "account_number": "6338"
+ },
+ "account_number": "633"
+ },
+ "Autres imp\u00f4ts, taxes et versements assimil\u00e9s (administrations des imp\u00f4ts)": {
+ "Imp\u00f4ts directs (sauf imp\u00f4ts sur les b\u00e9n\u00e9fices)": {
+ "Contribution \u00e9conomique territoriale": {
+ "account_number": "63511"
+ },
+ "Taxes fonci\u00e8res": {
+ "account_number": "63512"
+ },
+ "Autres imp\u00f4ts locaux": {
+ "account_number": "63513"
+ },
+ "Taxe sur les v\u00e9hicules des soci\u00e9t\u00e9s": {
+ "account_number": "63514"
+ },
+ "account_number": "6351"
+ },
+ "Taxes sur le chiffre d'affaires non r\u00e9cup\u00e9rables": {
+ "account_number": "6352"
+ },
+ "Imp\u00f4ts indirects": {
+ "account_number": "6353"
+ },
+ "Droits d'enregistrement et de timbre": {
+ "Droits de mutation": {
+ "account_number": "63541"
+ },
+ "account_number": "6354"
+ },
+ "Autres droits": {
+ "account_number": "6358"
+ },
+ "account_number": "635"
+ },
+ "Autres imp\u00f4ts, taxes et versements assimil\u00e9s (autres organismes)": {
+ "Contribution sociale de solidarit\u00e9 \u00e0 la charge des soci\u00e9t\u00e9s": {
+ "account_number": "6371"
+ },
+ "Taxes per\u00e7ues par les organismes publics internationaux": {
+ "account_number": "6372"
+ },
+ "Imp\u00f4ts et taxes exigibles \u00e0 l'\u00e9tranger": {
+ "account_number": "6374"
+ },
+ "Taxes diverses": {
+ "account_number": "6378"
+ },
+ "account_number": "637"
+ },
+ "account_number": "63"
+ },
+ "Charges de personnel": {
+ "R\u00e9mun\u00e9rations du personnel": {
+ "Salaires, appointements": {
+ "account_number": "6411"
+ },
+ "Cong\u00e9s pay\u00e9s": {
+ "account_number": "6412"
+ },
+ "Primes et gratifications": {
+ "account_number": "6413"
+ },
+ "Indemnit\u00e9s et avantages divers": {
+ "account_number": "6414"
+ },
+ "Suppl\u00e9ment familial": {
+ "account_number": "6415"
+ },
+ "account_number": "641"
+ },
+ "R\u00e9mun\u00e9ration du travail de l'exploitant": {
+ "account_number": "644"
+ },
+ "Charges de s\u00e9curit\u00e9 sociale et de pr\u00e9voyance": {
+ "Cotisations \u00e0 l'URSSAF": {
+ "account_number": "6451"
+ },
+ "Cotisations aux mutuelles": {
+ "account_number": "6452"
+ },
+ "Cotisations aux caisses de retraites": {
+ "account_number": "6453"
+ },
+ "Cotisations aux ASSEDIC": {
+ "account_number": "6454"
+ },
+ "account_number": "645"
+ },
+ "Cotisations sociales personnelles de l'exploitant": {
+ "account_number": "646"
+ },
+ "Autres charges sociales": {
+ "is_group": 1,
+ "account_number": "647"
+ },
+ "Autres charges de personnel": {
+ "account_number": "648"
+ },
+ "account_number": "64"
+ },
+ "Autres charges de gestion courante": {
+ "Redevances pour concessions, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels, droits et valeurs similaires": {
+ "Redevances pour concessions, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels": {
+ "account_number": "6511"
+ },
+ "Droits d'auteur et de reproduction": {
+ "account_number": "6516"
+ },
+ "Autres droits et valeurs similaires": {
+ "account_number": "6518"
+ },
+ "account_number": "651"
+ },
+ "Jetons de pr\u00e9sence": {
+ "account_number": "653"
+ },
+ "Pertes sur cr\u00e9ances irr\u00e9couvrables": {
+ "Cr\u00e9ances de l'exercice": {
+ "account_number": "6541"
+ },
+ "Cr\u00e9ances des exercices ant\u00e9rieurs": {
+ "account_number": "6544"
+ },
+ "account_number": "654"
+ },
+ "Quotes-parts de r\u00e9sultat sur op\u00e9rations faites en commun": {
+ "Quote-part de b\u00e9n\u00e9fice transf\u00e9r\u00e9e (comptabilit\u00e9 du g\u00e9rant)": {
+ "account_number": "6551"
+ },
+ "Quote-part de perte support\u00e9e (comptabilit\u00e9 des associ\u00e9s non g\u00e9rants)": {
+ "account_number": "6555"
+ },
+ "account_number": "655"
+ },
+ "Pertes de change sur cr\u00e9ances et dettes commerciales": {
+ "account_number": "656"
+ },
+ "Charges diverses de gestion courante": {
+ "account_number": "658"
+ },
+ "account_number": "65"
+ },
+ "Charges financi\u00e8res": {
+ "Charges d'int\u00e9r\u00eats": {
+ "Int\u00e9r\u00eats des emprunts et dettes": {
+ "Int\u00e9r\u00eats des emprunts et dettes - des emprunts et dettes assimil\u00e9es": {
+ "account_number": "66116"
+ },
+ "Int\u00e9r\u00eats des emprunts et dettes - des dettes rattach\u00e9es \u00e0 des participations": {
+ "account_number": "66117"
+ },
+ "account_number": "6611"
+ },
+ "Charges de la fiducie, r\u00e9sultat de la p\u00e9riode": {
+ "account_number": "6612"
+ },
+ "Int\u00e9r\u00eats des comptes courants et des d\u00e9p\u00f4ts cr\u00e9diteurs": {
+ "account_number": "6615"
+ },
+ "Int\u00e9r\u00eats bancaires et sur op\u00e9rations de financement (escompte...)": {
+ "account_number": "6616"
+ },
+ "Int\u00e9r\u00eats des obligations cautionn\u00e9es": {
+ "account_number": "6617"
+ },
+ "Int\u00e9r\u00eats des autres dettes": {
+ "Int\u00e9r\u00eats des autres dettes - des dettes commerciales": {
+ "account_number": "66181"
+ },
+ "Int\u00e9r\u00eats des autres dettes - des dettes diverses": {
+ "account_number": "66188"
+ },
+ "account_number": "6618"
+ },
+ "account_number": "661"
+ },
+ "Pertes sur cr\u00e9ances li\u00e9es \u00e0 des participations": {
+ "account_number": "664"
+ },
+ "Escomptes accord\u00e9s": {
+ "account_number": "665"
+ },
+ "Pertes de change financi\u00e8res": {
+ "account_type": "Round Off",
+ "account_number": "666"
+ },
+ "Charges nettes sur cessions de valeurs mobili\u00e8res de placement": {
+ "account_number": "667"
+ },
+ "Autres charges financi\u00e8res": {
+ "account_number": "668"
+ },
+ "account_number": "66"
+ },
+ "Charges exceptionnelles": {
+ "Charges exceptionnelles sur op\u00e9rations de gestion": {
+ "P\u00e9nalit\u00e9s sur march\u00e9s (et d\u00e9dits pay\u00e9s sur achats et ventes)": {
+ "account_number": "6711"
+ },
+ "P\u00e9nalit\u00e9s, amendes fiscales et p\u00e9nales": {
+ "account_number": "6712"
+ },
+ "Dons, lib\u00e9ralit\u00e9s": {
+ "account_number": "6713"
+ },
+ "Cr\u00e9ances devenues irr\u00e9couvrables dans l'exercice": {
+ "account_number": "6714"
+ },
+ "Subventions accord\u00e9es": {
+ "account_number": "6715"
+ },
+ "Rappel d'imp\u00f4ts (autres qu'imp\u00f4ts sur les b\u00e9n\u00e9fices)": {
+ "account_number": "6717"
+ },
+ "Autres charges exceptionnelles sur op\u00e9rations de gestion": {
+ "account_number": "6718"
+ },
+ "account_number": "671"
+ },
+ "(Compte \u00e0 la disposition des entit\u00e9s pour enregistrer, en cours d'exercice, les charges sur exercices ant\u00e9rieurs)": {
+ "account_number": "672"
+ },
+ "Op\u00e9rations de constitution ou liquidation des fiducies": {
+ "Op\u00e9rations li\u00e9es \u00e0 la constitution de la fiducie - transfert des \u00e9l\u00e9ments": {
+ "account_number": "6741"
+ },
+ "Op\u00e9rations li\u00e9es \u00e0 la liquidation de la fiducie": {
+ "account_number": "6742"
+ },
+ "account_number": "674"
+ },
+ "Valeurs comptables des \u00e9l\u00e9ments d'actif c\u00e9d\u00e9s": {
+ "Immobilisations incorporelles": {
+ "account_number": "6751"
+ },
+ "Immobilisations corporelles": {
+ "account_number": "6752"
+ },
+ "Immobilisations financi\u00e8res": {
+ "account_number": "6756"
+ },
+ "Autres \u00e9l\u00e9ments d'actif": {
+ "account_number": "6758"
+ },
+ "account_number": "675"
+ },
+ "Autres charges exceptionnelles": {
+ "Mali provenant de clauses d'indexation": {
+ "account_number": "6781"
+ },
+ "Lots": {
+ "account_number": "6782"
+ },
+ "Malis provenant du rachat par l'entreprise d'actions et obligations \u00e9mises par elles-m\u00eame": {
+ "account_number": "6783"
+ },
+ "Charges exceptionnelles diverses": {
+ "account_number": "6788"
+ },
+ "account_number": "678"
+ },
+ "account_number": "67"
+ },
+ "Dotations aux amortissements, d\u00e9pr\u00e9ciations et provisions": {
+ "account_type": "Depreciation",
+ "Dotations aux amortissements, d\u00e9pr\u00e9ciations et provisions - Charges d'exploitation": {
+ "account_type": "Depreciation",
+ "Dotations aux amortissements sur immobilisations incorporelles et corporelles": {
+ "account_type": "Depreciation",
+ "Immobilisations incorporelles": {
+ "account_type": "Depreciation",
+ "account_number": "68111"
+ },
+ "Immobilisations corporelles": {
+ "account_type": "Depreciation",
+ "account_number": "68112"
+ },
+ "account_number": "6811"
+ },
+ "Dotations aux amortissements des charges d'exploitation \u00e0 r\u00e9partir": {
+ "account_type": "Depreciation",
+ "account_number": "6812"
+ },
+ "Dotations aux provisions d'exploitation": {
+ "account_type": "Depreciation",
+ "account_number": "6815"
+ },
+ "Dotations aux d\u00e9pr\u00e9ciations des immobilisations incorporelles et corporelles": {
+ "account_type": "Depreciation",
+ "Immobilisations incorporelles": {
+ "account_type": "Depreciation",
+ "account_number": "68161"
+ },
+ "Immobilisations corporelles": {
+ "account_type": "Depreciation",
+ "account_number": "68162"
+ },
+ "account_number": "6816"
+ },
+ "Dotations pour d\u00e9pr\u00e9ciations des actifs circulants": {
+ "account_type": "Depreciation",
+ "Stocks et en-cours": {
+ "account_type": "Depreciation",
+ "account_number": "68173"
+ },
+ "Cr\u00e9ances": {
+ "account_type": "Depreciation",
+ "account_number": "68174"
+ },
+ "account_number": "6817"
+ },
+ "account_number": "681"
+ },
+ "Dotations aux amortissements, d\u00e9pr\u00e9ciations et provisions - Charges financi\u00e8res": {
+ "account_type": "Depreciation",
+ "Dotations aux amortissements des primes de remboursement des obligations": {
+ "account_type": "Depreciation",
+ "account_number": "6861"
+ },
+ "Dotations aux provisions financi\u00e8res": {
+ "account_type": "Depreciation",
+ "account_number": "6865"
+ },
+ "Dotations aux d\u00e9pr\u00e9ciations des \u00e9l\u00e9ments financiers": {
+ "account_type": "Depreciation",
+ "Immobilisations financi\u00e8res": {
+ "account_type": "Depreciation",
+ "account_number": "68662"
+ },
+ "Valeurs mobili\u00e8res de placement": {
+ "account_type": "Depreciation",
+ "account_number": "68665"
+ },
+ "account_number": "6866"
+ },
+ "Autres dotations": {
+ "account_type": "Depreciation",
+ "account_number": "6868"
+ },
+ "account_number": "686"
+ },
+ "Dotations aux amortissements, d\u00e9pr\u00e9ciations et provisions - Charges exceptionnelles": {
+ "account_type": "Depreciation",
+ "Dotations aux amortissements exceptionnels des immobilisations": {
+ "account_type": "Depreciation",
+ "account_number": "6871"
+ },
+ "Dotations aux provisions r\u00e9glement\u00e9es (immobilisations)": {
+ "account_type": "Depreciation",
+ "Amortissements d\u00e9rogatoires": {
+ "account_type": "Depreciation",
+ "account_number": "68725"
+ },
+ "account_number": "6872"
+ },
+ "Dotations aux provisions r\u00e9glement\u00e9es (stocks)": {
+ "account_type": "Depreciation",
+ "account_number": "6873"
+ },
+ "Dotations aux autres provisions r\u00e9glement\u00e9es": {
+ "account_type": "Depreciation",
+ "account_number": "6874"
+ },
+ "Dotations aux provisions exceptionnelles": {
+ "account_type": "Depreciation",
+ "account_number": "6875"
+ },
+ "Dotations aux d\u00e9pr\u00e9ciations exceptionnelles": {
+ "account_type": "Depreciation",
+ "account_number": "6876"
+ },
+ "account_number": "687"
+ },
+ "account_number": "68"
+ },
+ "Participation des salari\u00e9s, imp\u00f4ts sur les b\u00e9n\u00e9fices et assimil\u00e9s": {
+ "Participation des salari\u00e9s aux r\u00e9sultats": {
+ "account_number": "691"
+ },
+ "Imp\u00f4ts sur les b\u00e9n\u00e9fices": {
+ "Imp\u00f4ts dus en France": {
+ "account_number": "6951"
+ },
+ "Contribution additionnelle \u00e0 l'imp\u00f4t sur les b\u00e9n\u00e9fices": {
+ "account_number": "6952"
+ },
+ "Imp\u00f4ts dus \u00e0 l'\u00e9tranger": {
+ "account_number": "6954"
+ },
+ "account_number": "695"
+ },
+ "Suppl\u00e9ments d'imp\u00f4ts sur les soci\u00e9t\u00e9s, li\u00e9s aux distributions": {
+ "account_number": "696"
+ },
+ "Int\u00e9gration fiscale": {
+ "Int\u00e9gration fiscale - Charges": {
+ "account_number": "6981"
+ },
+ "Int\u00e9gration fiscale - Produits": {
+ "account_number": "6989"
+ },
+ "account_number": "698"
+ },
+ "Produits - Report en arri\u00e8re des d\u00e9ficits": {
+ "account_number": "699"
+ },
+ "account_number": "69"
+ },
+ "account_number": "6"
+ },
+ "Comptes de Produits": {
+ "root_type": "Income",
+ "Ventes de produits fabriqu\u00e9s, prestations de services, marchandises": {
+ "Ventes de produits finis": {
+ "Produits finis (ou groupe) A": {
+ "account_number": "7011"
+ },
+ "Produits (ou groupe) B": {
+ "account_number": "7012"
+ },
+ "account_number": "701"
+ },
+ "Ventes de produits interm\u00e9diaires": {
+ "account_number": "702"
+ },
+ "Ventes de produits r\u00e9siduels": {
+ "account_number": "703"
+ },
+ "Travaux": {
+ "Travaux de cat\u00e9gorie (ou activit\u00e9) A": {
+ "account_number": "7041"
+ },
+ "Travaux de cat\u00e9gorie (ou activit\u00e9) B": {
+ "account_number": "7042"
+ },
+ "account_number": "704"
+ },
+ "Etudes": {
+ "account_number": "705"
+ },
+ "Prestations de services": {
+ "account_number": "706"
+ },
+ "Ventes de marchandises": {
+ "Marchandises (ou groupe) A": {
+ "account_number": "7071"
+ },
+ "Marchandises (ou groupe) B": {
+ "account_number": "7072"
+ },
+ "account_number": "707"
+ },
+ "Produits des activit\u00e9s annexes": {
+ "Produits des services exploit\u00e9s dans l'int\u00e9r\u00eat du personnel": {
+ "account_number": "7081"
+ },
+ "Commissions et courtages": {
+ "account_number": "7082"
+ },
+ "Locations diverses": {
+ "account_number": "7083"
+ },
+ "Mise \u00e0 disposition de personnel factur\u00e9e": {
+ "account_number": "7084"
+ },
+ "Ports et frais accessoires factur\u00e9s": {
+ "account_number": "7085"
+ },
+ "Bonis sur reprises d'emballages consign\u00e9s": {
+ "account_number": "7086"
+ },
+ "Bonifications obtenues des clients et primes sur ventes": {
+ "account_number": "7087"
+ },
+ "Autres produits d'activit\u00e9s annexes (cessions d'approvisionnements...)": {
+ "account_number": "7088"
+ },
+ "account_number": "708"
+ },
+ "Rabais, remises et ristournes accord\u00e9s par l'entreprise": {
+ "Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur ventes de produits finis": {
+ "account_number": "7091"
+ },
+ "Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur ventes de produits interm\u00e9diaires": {
+ "account_number": "7092"
+ },
+ "Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur travaux": {
+ "account_number": "7094"
+ },
+ "Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur \u00e9tudes": {
+ "account_number": "7095"
+ },
+ "Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur prestations de services": {
+ "account_number": "7096"
+ },
+ "Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur ventes de marchandises": {
+ "account_number": "7097"
+ },
+ "Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur produits des activit\u00e9s annexes": {
+ "account_number": "7098"
+ },
+ "account_number": "709"
+ },
+ "account_number": "70"
+ },
+ "Production stock\u00e9e (ou d\u00e9stockage)": {
+ "Variation des stocks (en-cours de production, produits)": {
+ "Variation des en-cours de production de biens": {
+ "Produits en cours": {
+ "account_number": "71331"
+ },
+ "Travaux en cours": {
+ "account_number": "71335"
+ },
+ "account_number": "7133"
+ },
+ "Variation des en-cours de production de services": {
+ "Etudes en cours": {
+ "account_number": "71341"
+ },
+ "Prestations de services en cours": {
+ "account_number": "71345"
+ },
+ "account_number": "7134"
+ },
+ "Variation des stocks de produits": {
+ "Produits interm\u00e9diaires": {
+ "account_number": "71351"
+ },
+ "Produits finis": {
+ "account_number": "71355"
+ },
+ "Produits r\u00e9siduels": {
+ "account_number": "71358"
+ },
+ "account_number": "7135"
+ },
+ "account_number": "713"
+ },
+ "account_number": "71"
+ },
+ "Production immobilis\u00e9e": {
+ "Immobilisations incorporelles": {
+ "account_number": "721"
+ },
+ "Immobilisations corporelles": {
+ "account_number": "722"
+ },
+ "account_number": "72"
+ },
+ "Subventions d'exploitation": {
+ "is_group": 1,
+ "account_number": "74"
+ },
+ "Autres produits de gestion courante": {
+ "Redevances pour concessions, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels, droits et valeurs similaires": {
+ "Redevances pour concessions, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels": {
+ "account_number": "7511"
+ },
+ "Droits d'auteur et de reproduction": {
+ "account_number": "7516"
+ },
+ "Autres droits et valeurs similaires": {
+ "account_number": "7518"
+ },
+ "account_number": "751"
+ },
+ "Revenus des immeubles non affect\u00e9s aux activit\u00e9s professionnelles": {
+ "account_number": "752"
+ },
+ "Jetons de pr\u00e9sence et r\u00e9mun\u00e9rations d'administrateurs, g\u00e9rants...": {
+ "account_number": "753"
+ },
+ "Ristournes per\u00e7ues des coop\u00e9ratives (provenant des exc\u00e9dents)": {
+ "account_number": "754"
+ },
+ "Quotes-parts de r\u00e9sultats sur op\u00e9rations faites en commun": {
+ "Quote-part de perte transf\u00e9r\u00e9e (comptabilit\u00e9 du g\u00e9rant)": {
+ "account_number": "7551"
+ },
+ "Quote-part de b\u00e9n\u00e9fice attribu\u00e9 (comptabilit\u00e9 des associ\u00e9s non g\u00e9rants)": {
+ "account_number": "7555"
+ },
+ "account_number": "755"
+ },
+ "Gains de change sur cr\u00e9ances et dettes commerciales": {
+ "account_number": "756"
+ },
+ "Produits divers de gestion courante": {
+ "account_number": "758"
+ },
+ "account_number": "75"
+ },
+ "Produits financiers": {
+ "Produits de participations": {
+ "Revenus des titres de participation": {
+ "account_number": "7611"
+ },
+ "Produits de la fiducie, r\u00e9sultat de la p\u00e9riode": {
+ "account_number": "7612"
+ },
+ "Revenus sur autres formes de participation": {
+ "account_number": "7616"
+ },
+ "Revenus des cr\u00e9ances rattach\u00e9es \u00e0 des participations": {
+ "account_number": "7617"
+ },
+ "account_number": "761"
+ },
+ "Produits des autres immobilisations financi\u00e8res": {
+ "Revenus des titres immobilis\u00e9s": {
+ "account_number": "7621"
+ },
+ "Revenus des pr\u00eats": {
+ "account_number": "7626"
+ },
+ "Revenus des cr\u00e9ances immobilis\u00e9es": {
+ "account_number": "7627"
+ },
+ "account_number": "762"
+ },
+ "Revenus des autres cr\u00e9ances": {
+ "Revenus des cr\u00e9ances commerciales": {
+ "account_number": "7631"
+ },
+ "Revenus des cr\u00e9ances diverses": {
+ "account_number": "7638"
+ },
+ "account_number": "763"
+ },
+ "Revenus des valeurs mobili\u00e8res de placement": {
+ "account_number": "764"
+ },
+ "Escomptes obtenus": {
+ "account_number": "765"
+ },
+ "Gains de change financi\u00e8res": {
+ "account_type": "Round Off",
+ "account_number": "766"
+ },
+ "Produits nets sur cessions de valeurs mobili\u00e8res de placement": {
+ "account_number": "767"
+ },
+ "Autres produits financiers": {
+ "account_number": "768"
+ },
+ "account_number": "76"
+ },
+ "Produits exceptionnels": {
+ "Produits exceptionnels sur op\u00e9rations de gestion": {
+ "D\u00e9dits et p\u00e9nalit\u00e9s per\u00e7us sur achats et sur ventes": {
+ "account_number": "7711"
+ },
+ "Lib\u00e9ralit\u00e9s re\u00e7ues": {
+ "account_number": "7713"
+ },
+ "Rentr\u00e9es sur cr\u00e9ances amorties": {
+ "account_number": "7714"
+ },
+ "Subventions d'\u00e9quilibre": {
+ "account_number": "7715"
+ },
+ "D\u00e9gr\u00e8vements d'imp\u00f4ts autres qu'imp\u00f4ts sur les b\u00e9n\u00e9fices": {
+ "account_number": "7717"
+ },
+ "Autres produits exceptionnels sur op\u00e9rations de gestion": {
+ "account_number": "7718"
+ },
+ "account_number": "771"
+ },
+ "(Compte \u00e0 la disposition des entit\u00e9s pour enregistrer, en cours d'exercice, les Produits sur exercices ant\u00e9rieurs)": {
+ "account_number": "772"
+ },
+ "Op\u00e9rations de constitution ou liquidation des fiducies": {
+ "Op\u00e9rations li\u00e9es \u00e0 la constitution de la fiducie - transfert des \u00e9l\u00e9ments": {
+ "account_number": "7741"
+ },
+ "Op\u00e9rations li\u00e9es \u00e0 la liquidation de la fiducie": {
+ "account_number": "7742"
+ },
+ "account_number": "774"
+ },
+ "Produits des cessions d'\u00e9l\u00e9ments d'actif": {
+ "Immobilisations incorporelles": {
+ "account_number": "7751"
+ },
+ "Immobilisations corporelles": {
+ "account_number": "7752"
+ },
+ "Immobilisations financi\u00e8res": {
+ "account_number": "7756"
+ },
+ "Autres \u00e9l\u00e9ments d'actif": {
+ "account_number": "7758"
+ },
+ "account_number": "775"
+ },
+ "Quote-part des subventions d'investissement vir\u00e9e au r\u00e9sultat de l'exercice": {
+ "account_number": "777"
+ },
+ "Autres produits exceptionnels": {
+ "Bonis provenant de clauses d'indexation": {
+ "account_number": "7781"
+ },
+ "Lots": {
+ "account_number": "7782"
+ },
+ "Bonis provenant du rachat par l'entreprise d'actions et d'obligations \u00e9mises par elle-m\u00eame": {
+ "account_number": "7783"
+ },
+ "Produits exceptionnels divers": {
+ "account_number": "7788"
+ },
+ "account_number": "778"
+ },
+ "account_number": "77"
+ },
+ "Reprises sur amortissements, d\u00e9pr\u00e9ciations et provisions": {
+ "Reprises sur amortissements, d\u00e9pr\u00e9ciations et provisions (\u00e0 inscrire dans les produits d'exploitation)": {
+ "Reprises sur amortissements des immobilisations incorporelles et corporelles": {
+ "Immobilisations incorporelles": {
+ "account_number": "78111"
+ },
+ "Immobilisations corporelles": {
+ "account_number": "78112"
+ },
+ "account_number": "7811"
+ },
+ "Reprises sur provisions d'exploitation": {
+ "account_number": "7815"
+ },
+ "Reprises sur d\u00e9pr\u00e9ciations des immobilisations corporelles et incorporelles": {
+ "Immobilisations incorporelles": {
+ "account_number": "78161"
+ },
+ "Immobilisations corporelles": {
+ "account_number": "78162"
+ },
+ "account_number": "7816"
+ },
+ "Reprises sur d\u00e9pr\u00e9ciations des actifs circulants": {
+ "Stocks et en-cours": {
+ "account_number": "78173"
+ },
+ "Cr\u00e9ances": {
+ "account_number": "78174"
+ },
+ "account_number": "7817"
+ },
+ "account_number": "781"
+ },
+ "Reprises sur d\u00e9pr\u00e9ciations et provisions (\u00e0 inscrire dans les produits financiers)": {
+ "Reprises sur provisions financi\u00e8res": {
+ "account_number": "7865"
+ },
+ "Reprises sur d\u00e9pr\u00e9ciations des \u00e9l\u00e9ments financiers": {
+ "Immobilisations financi\u00e8res": {
+ "account_number": "78662"
+ },
+ "Valeurs mobili\u00e8res de placement": {
+ "account_number": "78665"
+ },
+ "account_number": "7866"
+ },
+ "account_number": "786"
+ },
+ "Reprises sur d\u00e9pr\u00e9ciations et provisions (\u00e0 inscrire dans les produits exceptionnels)": {
+ "Reprises sur provisions r\u00e9glement\u00e9es (immobilisations)": {
+ "Amortissements d\u00e9rogatoires": {
+ "account_number": "78725"
+ },
+ "Provision sp\u00e9ciale de r\u00e9\u00e9valuation": {
+ "account_number": "78726"
+ },
+ "Plus-values r\u00e9investies": {
+ "account_number": "78727"
+ },
+ "account_number": "7872"
+ },
+ "Reprises sur provisions r\u00e9glement\u00e9es (stocks)": {
+ "account_number": "7873"
+ },
+ "Reprises sur autres provisions r\u00e9glement\u00e9es": {
+ "account_number": "7874"
+ },
+ "Reprises sur provisions exceptionnelles": {
+ "account_number": "7875"
+ },
+ "Reprises sur d\u00e9pr\u00e9ciations exceptionnelles": {
+ "account_number": "7876"
+ },
+ "account_number": "787"
+ },
+ "account_number": "78"
+ },
+ "Transferts de charges": {
+ "Transferts de charges d'exploitation": {
+ "account_number": "791"
+ },
+ "Transferts de charges financi\u00e8res": {
+ "account_number": "796"
+ },
+ "Transferts de charges exceptionnelles": {
+ "account_number": "797"
+ },
+ "account_number": "79"
+ },
+ "account_number": "7"
+ }
+ }
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.py b/erpnext/accounts/doctype/accounting_period/accounting_period.py
index df6cedd..63b5dbb 100644
--- a/erpnext/accounts/doctype/accounting_period/accounting_period.py
+++ b/erpnext/accounts/doctype/accounting_period/accounting_period.py
@@ -39,6 +39,7 @@
frappe.throw(_("Accounting Period overlaps with {0}")
.format(existing_accounting_period[0].get("name")), OverlapError)
+ @frappe.whitelist()
def get_doctypes_for_closing(self):
docs_for_closing = []
doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", \
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index a3c29b6..e1276e7 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -12,6 +12,7 @@
"frozen_accounts_modifier",
"determine_address_tax_category_from",
"over_billing_allowance",
+ "role_allowed_to_over_bill",
"column_break_4",
"credit_controller",
"check_supplier_invoice_uniqueness",
@@ -226,6 +227,13 @@
"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"
}
],
"icon": "icon-cog",
@@ -233,7 +241,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-01-05 13:04:00.118892",
+ "modified": "2021-03-11 18:52:05.601996",
"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..4d33880 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
@@ -30,5 +30,5 @@
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..059e1d3 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 {
diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
index 76d82e7..79f5596 100644
--- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
+++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
@@ -12,6 +12,7 @@
}
class BankClearance(Document):
+ @frappe.whitelist()
def get_payment_entries(self):
if not (self.from_date and self.to_date):
frappe.throw(_("From Date and To Date are Mandatory"))
@@ -108,6 +109,7 @@
row.update(d)
self.total_amount += flt(amount)
+ @frappe.whitelist()
def update_clearance_date(self):
clearance_date_updated = False
for d in self.get('payment_entries'):
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 f2c3dea..78e7ff6 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 2b6aeee..0d4e56f 100644
--- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js
+++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js
@@ -532,43 +532,4 @@
</table>
`);
},
-
- show_missing_link_values(frm, missing_link_values) {
- let can_be_created_automatically = missing_link_values.every(
- (d) => d.has_one_mandatory_field
- );
-
- let html = missing_link_values
- .map((d) => {
- let doctype = d.doctype;
- let values = d.missing_values;
- return `
- <h5>${doctype}</h5>
- <ul>${values.map((v) => `<li>${v}</li>`).join("")}</ul>
- `;
- })
- .join("");
-
- if (can_be_created_automatically) {
- // prettier-ignore
- let message = __('There are some linked records which needs to be created before we can import your file. Do you want to create the following missing records automatically?');
- frappe.confirm(message + html, () => {
- frm.call("create_missing_link_values", {
- missing_link_values,
- }).then((r) => {
- let records = r.message;
- frappe.msgprint(__(
- "Created {0} records successfully.", [
- records.length,
- ]
- ));
- });
- });
- } else {
- frappe.msgprint(
- // prettier-ignore
- __('The following records needs to be created before we can import your file.') + html
- );
- }
- },
});
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/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
index 3b14e4e..ce149f9 100644
--- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
@@ -15,12 +15,14 @@
test_dependencies = ["Item", "Cost Center"]
class TestBankTransaction(unittest.TestCase):
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
make_pos_profile()
add_transactions()
add_vouchers()
- def tearDown(self):
+ @classmethod
+ def tearDownClass(cls):
for bt in frappe.get_all("Bank Transaction"):
doc = frappe.get_doc("Bank Transaction", bt.name)
doc.cancel()
@@ -33,9 +35,6 @@
# Delete POS Profile
frappe.db.sql("delete from `tabPOS Profile`")
- frappe.flags.test_bank_transactions_created = False
- frappe.flags.test_payments_created = False
-
# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
def test_linked_payments(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic"))
@@ -44,8 +43,8 @@
# This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment
def test_reconcile(self):
- bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"))
- payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200))
+ bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G"))
+ payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1700))
vouchers = json.dumps([{
"payment_doctype":"Payment Entry",
"payment_name":payment.name,
@@ -62,7 +61,6 @@
def test_debit_credit_output(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"))
linked_payments = get_linked_payments(bank_transaction.name, ['payment_entry', 'exact_match'])
- print(linked_payments)
self.assertTrue(linked_payments[0][3])
# Check error if already reconciled
@@ -116,10 +114,6 @@
pass
def add_transactions():
- if frappe.flags.test_bank_transactions_created:
- return
-
- frappe.set_user("Administrator")
create_bank_account()
doc = frappe.get_doc({
@@ -172,14 +166,8 @@
}).insert()
doc.submit()
- frappe.flags.test_bank_transactions_created = True
def add_vouchers():
- if frappe.flags.test_payments_created:
- return
-
- frappe.set_user("Administrator")
-
try:
frappe.get_doc({
"doctype": "Supplier",
@@ -272,13 +260,6 @@
except frappe.DuplicateEntryError:
pass
- si = create_sales_invoice(customer="Fayva", qty=1, rate=109080)
- pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
- pe.reference_no = "Fayva Oct 18"
- pe.reference_date = "2018-10-29"
- pe.insert()
- pe.submit()
-
mode_of_payment = frappe.get_doc({
"doctype": "Mode of Payment",
"name": "Cash"
@@ -291,14 +272,12 @@
})
mode_of_payment.save()
- si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_submit=1)
+ si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1)
si.is_pos = 1
si.append("payments", {
"mode_of_payment": "Cash",
"account": "_Test Bank - _TC",
"amount": 109080
})
- si.save()
+ si.insert()
si.submit()
-
- frappe.flags.test_payments_created = True
diff --git a/erpnext/accounts/doctype/c_form/c_form.py b/erpnext/accounts/doctype/c_form/c_form.py
index 9b64f81..fd86ed4 100644
--- a/erpnext/accounts/doctype/c_form/c_form.py
+++ b/erpnext/accounts/doctype/c_form/c_form.py
@@ -57,6 +57,7 @@
total = sum([flt(d.grand_total) for d in self.get('invoices')])
frappe.db.set(self, 'total_invoiced_amount', total)
+ @frappe.whitelist()
def get_invoice_details(self, invoice_no):
""" Pull details from invoices for referrence """
if invoice_no:
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 03c3eb0..f96f591 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
@@ -293,6 +293,11 @@
accounts_dict = {}
for account in accounts:
accounts_dict.setdefault(account["account_name"], account)
+ if not hasattr(account, "parent_account"):
+ msg = _("Please make sure the file you are using has 'Parent Account' column present in the header.")
+ msg += "<br><br>"
+ msg += _("Alternatively, you can download the template and fill your data in.")
+ frappe.throw(msg, title=_("Parent Account Missing"))
if account["parent_account"] and accounts_dict.get(account["parent_account"]):
accounts_dict[account["parent_account"]]["is_group"] = 1
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.py b/erpnext/accounts/doctype/cost_center/cost_center.py
index 12094d4..8a5473f 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.py
+++ b/erpnext/accounts/doctype/cost_center/cost_center.py
@@ -50,6 +50,7 @@
frappe.throw(_("{0} is not a group node. Please select a group node as parent cost center").format(
frappe.bold(self.parent_cost_center)))
+ @frappe.whitelist()
def convert_group_to_ledger(self):
if self.check_if_child_exists():
frappe.throw(_("Cannot convert Cost Center to ledger as it has child nodes"))
@@ -60,6 +61,7 @@
self.save()
return 1
+ @frappe.whitelist()
def convert_ledger_to_group(self):
if cint(self.enable_distributed_cost_center):
frappe.throw(_("Cost Center with enabled distributed cost center can not be converted to group"))
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 9594706..5619321 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
@@ -27,6 +27,24 @@
if not (self.company and self.posting_date):
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()
@@ -95,6 +113,7 @@
message = _("No outstanding invoices found")
frappe.msgprint(message)
+ @frappe.whitelist()
def make_jv_entry(self):
if self.total_gain_loss == 0:
return
diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
index da6a3fd..4255626 100644
--- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
+++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
@@ -12,6 +12,7 @@
class FiscalYearIncorrectDate(frappe.ValidationError): pass
class FiscalYear(Document):
+ @frappe.whitelist()
def set_as_default(self):
frappe.db.set_value("Global Defaults", None, "current_fiscal_year", self.name)
global_defaults = frappe.get_doc("Global Defaults")
@@ -54,7 +55,7 @@
def on_update(self):
check_duplicate_fiscal_year(self)
frappe.cache().delete_value("fiscal_years")
-
+
def on_trash(self):
global_defaults = frappe.get_doc("Global Defaults")
if global_defaults.current_fiscal_year == self.name:
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index ce76d0a..78febf9 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -290,4 +290,8 @@
oldname = doc.name
set_name_from_naming_options(frappe.get_meta(doctype).autoname, doc)
newname = doc.name
- frappe.db.sql("""UPDATE `tab{}` SET name = %s, to_rename = 0 where name = %s""".format(doctype), (newname, oldname))
+ frappe.db.sql(
+ "UPDATE `tab{}` SET name = %s, to_rename = 0 where name = %s".format(doctype),
+ (newname, oldname),
+ auto_commit=True
+ )
diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
index af8940c..7b62b61 100644
--- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
+++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
@@ -125,6 +125,7 @@
make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding='No')
+ @frappe.whitelist()
def create_disbursement_entry(self):
je = frappe.new_doc("Journal Entry")
je.voucher_type = 'Journal Entry'
@@ -174,6 +175,7 @@
return je
+ @frappe.whitelist()
def close_loan(self):
je = frappe.new_doc("Journal Entry")
je.voucher_type = 'Journal Entry'
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 2f8d3e3..957a50f 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() {
- 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(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 3419bb6..fefab82 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -564,6 +564,7 @@
if gl_map:
make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj, update_outstanding=update_outstanding)
+ @frappe.whitelist()
def get_balance(self):
if not self.get('accounts'):
msgprint(_("'Entries' cannot be empty"), raise_exception=True)
@@ -591,6 +592,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_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/monthly_distribution/monthly_distribution.py b/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py
index 18f853c..88667d7 100644
--- a/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py
+++ b/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py
@@ -8,6 +8,7 @@
from frappe.model.document import Document
class MonthlyDistribution(Document):
+ @frappe.whitelist()
def get_months(self):
month_list = ['January','February','March','April','May','June','July','August','September',
'October','November','December']
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
index e6449b7..29dc96e 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
@@ -167,6 +167,7 @@
return invoice
+ @frappe.whitelist()
def make_invoices(self):
self.validate_company()
invoices = self.get_invoices()
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index b5f6a40..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}
}
}
},
@@ -637,13 +637,13 @@
let to_field = fields[key][1];
if (filters[from_field] && !filters[to_field]) {
- frappe.throw(__("Error: {0} is mandatory field",
- [to_field.replace(/_/g, " ")]
- ));
+ frappe.throw(
+ __("Error: {0} is mandatory field", [to_field.replace(/_/g, " ")])
+ );
} else if (filters[from_field] && filters[from_field] > filters[to_field]) {
- frappe.throw(__("{0}: {1} must be less than {2}",
- [key, from_field.replace(/_/g, " "), to_field.replace(/_/g, " ")]
- ));
+ frappe.throw(
+ __("{0}: {1} must be less than {2}", [key, from_field.replace(/_/g, " "), to_field.replace(/_/g, " ")])
+ );
}
}
},
@@ -692,6 +692,8 @@
c.total_amount = d.invoice_amount;
c.outstanding_amount = d.outstanding_amount;
c.bill_no = d.bill_no;
+ c.payment_term = d.payment_term;
+ c.allocated_amount = d.allocated_amount;
if(!in_list(["Sales Order", "Purchase Order", "Expense Claim", "Fees"], d.voucher_type)) {
if(flt(d.outstanding_amount) > 0)
@@ -741,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 || [],
@@ -774,12 +776,15 @@
} else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) {
if(paid_amount > total_negative_outstanding) {
if(total_negative_outstanding == 0) {
- frappe.msgprint(__("Cannot {0} {1} {2} without any negative outstanding invoice",
- [frm.doc.payment_type,
- (frm.doc.party_type=="Customer" ? "to" : "from"), frm.doc.party_type]));
+ frappe.msgprint(
+ __("Cannot {0} {1} {2} without any negative outstanding invoice", [frm.doc.payment_type,
+ (frm.doc.party_type=="Customer" ? "to" : "from"), frm.doc.party_type])
+ );
return false
} else {
- frappe.msgprint(__("Paid Amount cannot be greater than total negative outstanding amount {0}", [total_negative_outstanding]));
+ frappe.msgprint(
+ __("Paid Amount cannot be greater than total negative outstanding amount {0}", [total_negative_outstanding])
+ );
return false;
}
} else {
@@ -791,21 +796,19 @@
}
$.each(frm.doc.references || [], function(i, row) {
- row.allocated_amount = 0 //If allocate payment amount checkbox is unchecked, set zero to allocate amount
- if(frappe.flags.allocate_payment_amount != 0){
- 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;
- }
+ if (frappe.flags.allocate_payment_amount == 0) {
+ //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 || 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_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 8acd92c..62ab76c 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -333,33 +333,50 @@
invoice_payment_amount_map = {}
invoice_paid_amount_map = {}
- for reference in self.get('references'):
- if reference.payment_term and reference.reference_name:
- key = (reference.payment_term, reference.reference_name)
+ for ref in self.get('references'):
+ if ref.payment_term and ref.reference_name:
+ key = (ref.payment_term, ref.reference_name)
invoice_payment_amount_map.setdefault(key, 0.0)
- invoice_payment_amount_map[key] += reference.allocated_amount
+ invoice_payment_amount_map[key] += ref.allocated_amount
if not invoice_paid_amount_map.get(key):
- payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': reference.reference_name},
- fields=['paid_amount', 'payment_amount', 'payment_term'])
+ payment_schedule = frappe.get_all(
+ 'Payment Schedule',
+ filters={'parent': ref.reference_name},
+ fields=['paid_amount', 'payment_amount', 'payment_term', 'discount', 'outstanding']
+ )
for term in payment_schedule:
- invoice_key = (term.payment_term, reference.reference_name)
+ invoice_key = (term.payment_term, ref.reference_name)
invoice_paid_amount_map.setdefault(invoice_key, {})
- invoice_paid_amount_map[invoice_key]['outstanding'] = term.payment_amount - term.paid_amount
+ invoice_paid_amount_map[invoice_key]['outstanding'] = term.outstanding
+ invoice_paid_amount_map[invoice_key]['discounted_amt'] = ref.total_amount * (term.discount / 100)
- for key, amount in iteritems(invoice_payment_amount_map):
+ for key, allocated_amount in iteritems(invoice_payment_amount_map):
+ outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding'))
+ discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get('discounted_amt'))
+
if cancel:
- frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` - %s
- WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
+ frappe.db.sql("""
+ UPDATE `tabPayment Schedule`
+ SET
+ paid_amount = `paid_amount` - %s,
+ discounted_amount = `discounted_amount` - %s,
+ outstanding = `outstanding` + %s
+ WHERE parent = %s and payment_term = %s""",
+ (allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]))
else:
- outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding'))
-
- if amount > outstanding:
+ if allocated_amount > outstanding:
frappe.throw(_('Cannot allocate more than {0} against payment term {1}').format(outstanding, key[0]))
- if amount and outstanding:
- frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` + %s
- WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
+ if allocated_amount and outstanding:
+ frappe.db.sql("""
+ UPDATE `tabPayment Schedule`
+ SET
+ paid_amount = `paid_amount` + %s,
+ discounted_amount = `discounted_amount` + %s,
+ outstanding = `outstanding` - %s
+ WHERE parent = %s and payment_term = %s""",
+ (allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]))
def set_status(self):
if self.docstatus == 2:
@@ -708,6 +725,8 @@
outstanding_invoices = get_outstanding_invoices(args.get("party_type"), args.get("party"),
args.get("party_account"), filters=args, condition=condition)
+ outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
+
for d in outstanding_invoices:
d["exchange_rate"] = 1
if party_account_currency != company_currency:
@@ -735,6 +754,46 @@
return data
+def split_invoices_based_on_payment_terms(outstanding_invoices):
+ invoice_ref_based_on_payment_terms = {}
+ for idx, d in enumerate(outstanding_invoices):
+ if d.voucher_type in ['Sales Invoice', 'Purchase Invoice']:
+ payment_term_template = frappe.db.get_value(d.voucher_type, d.voucher_no, 'payment_terms_template')
+ if payment_term_template:
+ allocate_payment_based_on_payment_terms = frappe.db.get_value(
+ 'Payment Terms Template', payment_term_template, 'allocate_payment_based_on_payment_terms')
+ if allocate_payment_based_on_payment_terms:
+ payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': d.voucher_no}, fields=["*"])
+
+ for payment_term in payment_schedule:
+ if payment_term.outstanding > 0.1:
+ invoice_ref_based_on_payment_terms.setdefault(idx, [])
+ invoice_ref_based_on_payment_terms[idx].append(frappe._dict({
+ 'due_date': d.due_date,
+ 'currency': d.currency,
+ 'voucher_no': d.voucher_no,
+ 'voucher_type': d.voucher_type,
+ 'posting_date': d.posting_date,
+ 'invoice_amount': flt(d.invoice_amount),
+ 'outstanding_amount': flt(d.outstanding_amount),
+ 'payment_amount': payment_term.payment_amount,
+ 'payment_term': payment_term.payment_term,
+ 'allocated_amount': payment_term.outstanding
+ }))
+
+ if invoice_ref_based_on_payment_terms:
+ for idx, ref in invoice_ref_based_on_payment_terms.items():
+ voucher_no = outstanding_invoices[idx]['voucher_no']
+ voucher_type = outstanding_invoices[idx]['voucher_type']
+
+ frappe.msgprint(_("Spliting {} {} into {} rows as per payment terms").format(
+ voucher_type, voucher_no, len(ref)), alert=True)
+
+ outstanding_invoices.pop(idx - 1)
+ outstanding_invoices += invoice_ref_based_on_payment_terms[idx]
+
+ return outstanding_invoices
+
def get_orders_to_be_billed(posting_date, party_type, party,
company, party_account_currency, company_currency, cost_center=None, filters=None):
if party_type == "Customer":
@@ -1091,6 +1150,8 @@
paid_amount, received_amount = set_paid_amount_and_received_amount(
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc)
+ paid_amount, received_amount, discount_amount = apply_early_payment_discount(paid_amount, received_amount, doc)
+
pe = frappe.new_doc("Payment Entry")
pe.payment_type = payment_type
pe.company = doc.company
@@ -1160,11 +1221,20 @@
pe.setup_party_account_field()
pe.set_missing_values()
+
if party_account and bank:
if dt == "Employee Advance":
reference_doc = doc
pe.set_exchange_rate(ref_doc=reference_doc)
pe.set_amounts()
+ if discount_amount:
+ pe.set_gain_or_loss(account_details={
+ 'account': frappe.get_cached_value('Company', pe.company, "default_discount_account"),
+ 'cost_center': pe.cost_center or frappe.get_cached_value('Company', pe.company, "cost_center"),
+ 'amount': discount_amount * (-1 if payment_type == "Pay" else 1)
+ })
+ pe.set_difference_amount()
+
return pe
def get_bank_cash_account(doc, bank_account):
@@ -1285,6 +1355,33 @@
paid_amount = received_amount * doc.get('exchange_rate', 1)
return paid_amount, received_amount
+def apply_early_payment_discount(paid_amount, received_amount, doc):
+ total_discount = 0
+ if doc.doctype in ['Sales Invoice', 'Purchase Invoice'] and doc.payment_schedule:
+ for term in doc.payment_schedule:
+ if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date:
+ if term.discount_type == 'Percentage':
+ discount_amount = flt(doc.get('grand_total')) * (term.discount / 100)
+ else:
+ discount_amount = term.discount
+
+ discount_amount_in_foreign_currency = discount_amount * doc.get('conversion_rate', 1)
+
+ if doc.doctype == 'Sales Invoice':
+ paid_amount -= discount_amount
+ received_amount -= discount_amount_in_foreign_currency
+ else:
+ received_amount -= discount_amount
+ paid_amount -= discount_amount_in_foreign_currency
+
+ total_discount += discount_amount
+
+ if total_discount:
+ money = frappe.utils.fmt_money(total_discount, currency=doc.get('currency'))
+ frappe.msgprint(_("Discount of {} applied as per Payment Term").format(money), alert=1)
+
+ return paid_amount, received_amount, total_discount
+
def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
references = []
for payment_term in payment_schedule:
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index 772fc1a..4641d6b 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -193,6 +193,34 @@
self.assertEqual(si.payment_schedule[0].paid_amount, 200.0)
self.assertEqual(si.payment_schedule[1].paid_amount, 36.0)
+ def test_payment_entry_against_payment_terms_with_discount(self):
+ si = create_sales_invoice(do_not_save=1, qty=1, rate=200)
+ create_payment_terms_template_with_discount()
+ si.payment_terms_template = 'Test Discount Template'
+
+ frappe.db.set_value('Company', si.company, 'default_discount_account', 'Write Off - _TC')
+
+ si.append('taxes', {
+ "charge_type": "On Net Total",
+ "account_head": "_Test Account Service Tax - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "Service Tax",
+ "rate": 18
+ })
+ si.save()
+
+ si.submit()
+
+ pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
+ pe.submit()
+ si.load_from_db()
+
+ self.assertEqual(pe.references[0].payment_term, '30 Credit Days with 10% Discount')
+ self.assertEqual(si.payment_schedule[0].payment_amount, 236.0)
+ self.assertEqual(si.payment_schedule[0].paid_amount, 212.40)
+ self.assertEqual(si.payment_schedule[0].outstanding, 0)
+ self.assertEqual(si.payment_schedule[0].discounted_amount, 23.6)
+
def test_payment_against_purchase_invoice_to_check_status(self):
pi = make_purchase_invoice(supplier="_Test Supplier USD", debit_to="_Test Payable USD - _TC",
@@ -591,6 +619,26 @@
}]
}).insert()
+def create_payment_terms_template_with_discount():
+
+ create_payment_term('30 Credit Days with 10% Discount')
+
+ if not frappe.db.exists('Payment Terms Template', 'Test Discount Template'):
+ payment_term_template = frappe.get_doc({
+ 'doctype': 'Payment Terms Template',
+ 'template_name': 'Test Discount Template',
+ 'allocate_payment_based_on_payment_terms': 1,
+ 'terms': [{
+ 'doctype': 'Payment Terms Template Detail',
+ 'payment_term': '30 Credit Days with 10% Discount',
+ 'invoice_portion': 100,
+ 'credit_days_based_on': 'Day(s) after invoice date',
+ 'credit_days': 2,
+ 'discount': 10,
+ 'discount_validity_based_on': 'Day(s) after invoice date',
+ 'discount_validity': 1
+ }]
+ }).insert()
def create_payment_term(name):
if not frappe.db.exists('Payment Term', name):
diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
index 8f5e9fb..912ad09 100644
--- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
+++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
@@ -58,7 +58,7 @@
"fieldname": "total_amount",
"fieldtype": "Float",
"in_list_view": 1,
- "label": "Total Amount",
+ "label": "Grand Total",
"print_hide": 1,
"read_only": 1
},
@@ -92,9 +92,10 @@
"options": "Payment Term"
}
],
+ "index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2020-03-13 12:07:19.362539",
+ "modified": "2021-02-10 11:25:47.144392",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
index dd91fec..8dcd1aa 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 f7a15c0..cf6ec18 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -11,6 +11,7 @@
from erpnext.controllers.accounts_controller import get_advance_payment_entries
class PaymentReconciliation(Document):
+ @frappe.whitelist()
def get_unreconciled_entries(self):
self.get_nonreconciled_payment_entries()
self.get_invoice_entries()
@@ -147,6 +148,7 @@
ent.currency = e.get('currency')
ent.outstanding_amount = e.get('outstanding_amount')
+ @frappe.whitelist()
def reconcile(self, args):
for e in self.get('payments'):
e.invoice_type = None
@@ -197,6 +199,7 @@
'difference_account': row.difference_account
})
+ @frappe.whitelist()
def get_difference_amount(self, child_row):
if child_row.get("reference_type") != 'Payment Entry': return
diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json
index d363cf1..6ed7a31 100644
--- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json
+++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json
@@ -6,12 +6,25 @@
"engine": "InnoDB",
"field_order": [
"payment_term",
+ "section_break_15",
"description",
+ "section_break_4",
"due_date",
- "invoice_portion",
- "payment_amount",
"mode_of_payment",
- "paid_amount"
+ "column_break_5",
+ "invoice_portion",
+ "section_break_6",
+ "discount_type",
+ "discount_date",
+ "column_break_9",
+ "discount",
+ "section_break_9",
+ "payment_amount",
+ "outstanding",
+ "paid_amount",
+ "discounted_amount",
+ "column_break_3",
+ "base_payment_amount"
],
"fields": [
{
@@ -25,6 +38,7 @@
},
{
"columns": 2,
+ "fetch_from": "payment_term.description",
"fieldname": "description",
"fieldtype": "Small Text",
"in_list_view": 1,
@@ -62,14 +76,90 @@
"options": "Mode of Payment"
},
{
+ "depends_on": "paid_amount",
"fieldname": "paid_amount",
"fieldtype": "Currency",
- "label": "Paid Amount"
+ "label": "Paid Amount",
+ "options": "currency"
+ },
+ {
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "depends_on": "discounted_amount",
+ "fieldname": "discounted_amount",
+ "fieldtype": "Currency",
+ "label": "Discounted Amount",
+ "read_only": 1
+ },
+ {
+ "fetch_from": "payment_amount",
+ "fieldname": "outstanding",
+ "fieldtype": "Currency",
+ "label": "Outstanding",
+ "options": "currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "discount",
+ "fieldname": "discount_date",
+ "fieldtype": "Date",
+ "label": "Discount Date",
+ "mandatory_depends_on": "discount"
+ },
+ {
+ "default": "Percentage",
+ "fetch_from": "payment_term.discount_type",
+ "fieldname": "discount_type",
+ "fieldtype": "Select",
+ "label": "Discount Type",
+ "options": "Percentage\nAmount"
+ },
+ {
+ "fetch_from": "payment_term.discount",
+ "fieldname": "discount",
+ "fieldtype": "Float",
+ "label": "Discount"
+ },
+ {
+ "fieldname": "section_break_9",
+ "fieldtype": "Section Break"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "section_break_15",
+ "fieldtype": "Section Break",
+ "label": "Description"
+ },
+ {
+ "fieldname": "section_break_6",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_9",
+ "fieldtype": "Column Break"
+ },
+ {
+ "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": "2020-03-13 17:58:24.729526",
+ "modified": "2021-04-28 05:41:35.084233",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Schedule",
diff --git a/erpnext/accounts/doctype/payment_term/payment_term.js b/erpnext/accounts/doctype/payment_term/payment_term.js
index 054c2d1..acd0144 100644
--- a/erpnext/accounts/doctype/payment_term/payment_term.js
+++ b/erpnext/accounts/doctype/payment_term/payment_term.js
@@ -1,2 +1,22 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
+frappe.ui.form.on('Payment Term', {
+ onload(frm) {
+ frm.trigger('set_dynamic_description');
+ },
+ discount(frm) {
+ frm.trigger('set_dynamic_description');
+ },
+ discount_type(frm) {
+ frm.trigger('set_dynamic_description');
+ },
+ set_dynamic_description(frm) {
+ if (frm.doc.discount) {
+ let description = __("{0}% of total invoice value will be given as discount.", [frm.doc.discount]);
+ if (frm.doc.discount_type == 'Amount') {
+ description = __("{0} will be given as discount.", [fmt_money(frm.doc.discount)]);
+ }
+ frm.set_df_property("discount", "description", description);
+ }
+ }
+});
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_term/payment_term.json b/erpnext/accounts/doctype/payment_term/payment_term.json
index e77c244..aec4965 100644
--- a/erpnext/accounts/doctype/payment_term/payment_term.json
+++ b/erpnext/accounts/doctype/payment_term/payment_term.json
@@ -1,386 +1,166 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "field:payment_term_name",
- "beta": 0,
- "creation": "2017-08-10 15:24:54.876365",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "field:payment_term_name",
+ "creation": "2017-08-10 15:24:54.876365",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "payment_term_name",
+ "invoice_portion",
+ "mode_of_payment",
+ "column_break_3",
+ "due_date_based_on",
+ "credit_days",
+ "credit_months",
+ "section_break_8",
+ "discount_type",
+ "discount",
+ "column_break_11",
+ "discount_validity_based_on",
+ "discount_validity",
+ "section_break_6",
+ "description"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 1,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "payment_term_name",
- "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": "Payment Term Name",
- "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
- },
+ "bold": 1,
+ "fieldname": "payment_term_name",
+ "fieldtype": "Data",
+ "label": "Payment Term Name",
+ "unique": 1
+ },
{
- "description": "Provide the invoice portion in percent",
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 1,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "invoice_portion",
- "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": "Invoice Portion",
- "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
- },
+ "bold": 1,
+ "fieldname": "invoice_portion",
+ "fieldtype": "Float",
+ "label": "Invoice Portion (%)"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "mode_of_payment",
- "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": "Mode of Payment",
- "length": 0,
- "no_copy": 0,
- "options": "Mode of Payment",
- "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": "mode_of_payment",
+ "fieldtype": "Link",
+ "label": "Mode of Payment",
+ "options": "Mode of Payment"
+ },
{
- "allow_bulk_edit": 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_on_submit": 0,
- "bold": 1,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "due_date_based_on",
- "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": "Due Date Based On",
- "length": 0,
- "no_copy": 0,
- "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month",
- "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
- },
+ "bold": 1,
+ "fieldname": "due_date_based_on",
+ "fieldtype": "Select",
+ "label": "Due Date Based On",
+ "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month"
+ },
{
- "description": "Give number of days according to prior selection",
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 1,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",
- "fieldname": "credit_days",
- "fieldtype": "Int",
- "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": "Credit Days",
- "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
- },
+ "bold": 1,
+ "depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",
+ "fieldname": "credit_days",
+ "fieldtype": "Int",
+ "label": "Credit Days"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",
- "fieldname": "credit_months",
- "fieldtype": "Int",
- "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": "Credit Months",
- "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:doc.due_date_based_on=='Month(s) after the end of the invoice month'",
+ "fieldname": "credit_months",
+ "fieldtype": "Int",
+ "label": "Credit Months"
+ },
{
- "allow_bulk_edit": 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_on_submit": 0,
- "bold": 1,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "description",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Description",
- "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
+ "bold": 1,
+ "fieldname": "description",
+ "fieldtype": "Small Text",
+ "label": "Description"
+ },
+ {
+ "fieldname": "section_break_8",
+ "fieldtype": "Section Break",
+ "label": "Discount Settings"
+ },
+ {
+ "default": "Percentage",
+ "fieldname": "discount_type",
+ "fieldtype": "Select",
+ "label": "Discount Type",
+ "options": "Percentage\nAmount"
+ },
+ {
+ "fieldname": "discount",
+ "fieldtype": "Float",
+ "label": "Discount"
+ },
+ {
+ "default": "Day(s) after invoice date",
+ "depends_on": "discount",
+ "fieldname": "discount_validity_based_on",
+ "fieldtype": "Select",
+ "label": "Discount Validity Based On",
+ "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month"
+ },
+ {
+ "depends_on": "discount",
+ "fieldname": "discount_validity",
+ "fieldtype": "Int",
+ "label": "Discount Validity",
+ "mandatory_depends_on": "discount"
+ },
+ {
+ "fieldname": "column_break_11",
+ "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": "2020-10-14 10:47:32.830478",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Payment Term",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "links": [],
+ "modified": "2021-02-15 20:30:56.256403",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Payment Term",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Accounts Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Accounts User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User",
+ "share": 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",
- "track_changes": 1,
- "track_seen": 0
-}
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js
index f5c5bca..84c8d09 100644
--- a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js
+++ b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js
@@ -3,11 +3,6 @@
frappe.ui.form.on('Payment Terms Template', {
setup: function(frm) {
- frm.add_fetch("payment_term", "description", "description");
- frm.add_fetch("payment_term", "invoice_portion", "invoice_portion");
- frm.add_fetch("payment_term", "due_date_based_on", "due_date_based_on");
- frm.add_fetch("payment_term", "credit_days", "credit_days");
- frm.add_fetch("payment_term", "credit_months", "credit_months");
- frm.add_fetch("payment_term", "mode_of_payment", "mode_of_payment");
+
}
});
diff --git a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py
index 2b2b6af..80e3348 100644
--- a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py
+++ b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py
@@ -13,7 +13,6 @@
class PaymentTermsTemplate(Document):
def validate(self):
self.validate_invoice_portion()
- self.validate_credit_days()
self.check_duplicate_terms()
def validate_invoice_portion(self):
@@ -24,11 +23,6 @@
if flt(total_portion, 2) != 100.00:
frappe.msgprint(_('Combined invoice portion must equal 100%'), raise_exception=1, indicator='red')
- def validate_credit_days(self):
- for term in self.terms:
- if cint(term.credit_days) < 0:
- frappe.msgprint(_('Credit Days cannot be a negative number'), raise_exception=1, indicator='red')
-
def check_duplicate_terms(self):
terms = []
for term in self.terms:
diff --git a/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json b/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json
index eee3223..20b3dca 100644
--- a/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json
+++ b/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json
@@ -1,278 +1,164 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "",
- "beta": 0,
- "creation": "2017-08-10 15:34:09.409562",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2017-08-10 15:34:09.409562",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "payment_term",
+ "section_break_13",
+ "description",
+ "section_break_4",
+ "invoice_portion",
+ "mode_of_payment",
+ "column_break_3",
+ "due_date_based_on",
+ "credit_days",
+ "credit_months",
+ "section_break_8",
+ "discount_type",
+ "discount",
+ "column_break_11",
+ "discount_validity_based_on",
+ "discount_validity"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "fieldname": "payment_term",
- "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": "Payment Term",
- "length": 0,
- "no_copy": 0,
- "options": "Payment Term",
- "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": "payment_term",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Payment Term",
+ "options": "Payment Term"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "fieldname": "description",
- "fieldtype": "Small Text",
- "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": "Description",
- "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
- },
+ "columns": 2,
+ "fetch_from": "payment_term.description",
+ "fieldname": "description",
+ "fieldtype": "Small Text",
+ "in_list_view": 1,
+ "label": "Description"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "default": "0",
- "fieldname": "invoice_portion",
- "fieldtype": "Percent",
- "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": "Invoice Portion",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "columns": 2,
+ "fetch_from": "payment_term.invoice_portion",
+ "fetch_if_empty": 1,
+ "fieldname": "invoice_portion",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Invoice Portion (%)",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "fieldname": "due_date_based_on",
- "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": "Due Date Based On",
- "length": 0,
- "no_copy": 0,
- "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month",
- "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,
- "translatable": 0,
- "unique": 0
- },
+ "columns": 2,
+ "fetch_from": "payment_term.due_date_based_on",
+ "fetch_if_empty": 1,
+ "fieldname": "due_date_based_on",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Due Date Based On",
+ "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "default": "0",
- "depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",
- "fieldname": "credit_days",
- "fieldtype": "Int",
- "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": "Credit Days",
- "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
- },
+ "columns": 2,
+ "default": "0",
+ "depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",
+ "fetch_from": "payment_term.credit_days",
+ "fetch_if_empty": 1,
+ "fieldname": "credit_days",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Credit Days",
+ "non_negative": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",
- "fieldname": "credit_months",
- "fieldtype": "Int",
- "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": "Credit Months",
- "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
- },
+ "default": "0",
+ "depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",
+ "fetch_from": "payment_term.credit_months",
+ "fetch_if_empty": 1,
+ "fieldname": "credit_months",
+ "fieldtype": "Int",
+ "label": "Credit Months",
+ "non_negative": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "mode_of_payment",
- "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": "Mode of Payment",
- "length": 0,
- "no_copy": 0,
- "options": "Mode of Payment",
- "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": "payment_term.mode_of_payment",
+ "fieldname": "mode_of_payment",
+ "fieldtype": "Link",
+ "label": "Mode of Payment",
+ "options": "Mode of Payment"
+ },
+ {
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_8",
+ "fieldtype": "Section Break",
+ "label": "Discount Settings"
+ },
+ {
+ "default": "Percentage",
+ "fetch_from": "payment_term.discount_type",
+ "fetch_if_empty": 1,
+ "fieldname": "discount_type",
+ "fieldtype": "Select",
+ "label": "Discount Type",
+ "options": "Percentage\nAmount"
+ },
+ {
+ "fetch_from": "payment_term.discount",
+ "fetch_if_empty": 1,
+ "fieldname": "discount",
+ "fieldtype": "Float",
+ "label": "Discount"
+ },
+ {
+ "fieldname": "column_break_11",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "Day(s) after invoice date",
+ "depends_on": "discount",
+ "fetch_from": "payment_term.discount_validity_based_on",
+ "fetch_if_empty": 1,
+ "fieldname": "discount_validity_based_on",
+ "fieldtype": "Select",
+ "label": "Discount Validity Based On",
+ "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "section_break_13",
+ "fieldtype": "Section Break",
+ "label": "Description"
+ },
+ {
+ "depends_on": "discount",
+ "fetch_from": "payment_term.discount_validity",
+ "fetch_if_empty": 1,
+ "fieldname": "discount_validity",
+ "fieldtype": "Int",
+ "label": "Discount Validity",
+ "mandatory_depends_on": "discount"
+ },
+ {
+ "fieldname": "section_break_4",
+ "fieldtype": "Section 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": 1,
- "max_attachments": 0,
- "modified": "2018-08-21 16:15:55.143025",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Payment Terms Template Detail",
- "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
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-02-24 11:56:12.410807",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Payment Terms Template Detail",
+ "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/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
index a74fa06..fdb4f6f 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
@@ -102,14 +102,14 @@
make_gl_entries(gl_entries)
def get_pl_balances(self, dimension_fields):
- """Get balance for pl accounts"""
+ """Get balance for Profit and Loss accounts, only including valid transactions (not cancelled)"""
return frappe.db.sql("""
select
t1.account, t2.account_currency, {dimension_fields},
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as balance_in_account_currency,
sum(t1.debit) - sum(t1.credit) as balance_in_company_currency
from `tabGL Entry` t1, `tabAccount` t2
- where t1.account = t2.name and t2.report_type = 'Profit and Loss'
+ where t1.is_cancelled = 0 and t1.account = t2.name and t2.report_type = 'Profit and Loss'
and t2.docstatus < 2 and t2.company = %s
and t1.posting_date between %s and %s
group by t1.account, {dimension_fields}
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..aa0c53e 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,44 +97,24 @@
refresh_fields(frm);
set_html_data(frm);
}
- })
+ });
+ },
+
+ before_save: function(frm) {
+ for (let row of frm.doc.pos_transactions) {
+ frappe.db.get_doc("POS Invoice", 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);
+ });
+ }
}
});
-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];
@@ -177,11 +193,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 f5224a2..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,33 +16,13 @@
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:
invalid_row = {'idx': d.idx}
- pos_invoice = frappe.db.get_values("POS Invoice", d.pos_invoice,
+ pos_invoice = frappe.db.get_values("POS Invoice", d.pos_invoice,
["consolidated_invoice", "pos_profile", "docstatus", "owner"], as_dict=1)[0]
if pos_invoice.consolidated_invoice:
invalid_row.setdefault('msg', []).append(_('POS Invoice is {}').format(frappe.bold("already consolidated")))
@@ -68,17 +48,22 @@
frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
+ @frappe.whitelist()
def get_payment_reconciliation_details(self):
currency = frappe.get_cached_value('Company', self.company, "default_currency")
return frappe.render_template("erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html",
{"data": self, "currency": currency})
-
+
def on_submit(self):
consolidate_pos_invoices(closing_entry=self)
-
+
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
@@ -88,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/test_pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py
index 40db09e..b596c0c 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py
+++ b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py
@@ -5,12 +5,21 @@
import frappe
import unittest
from frappe.utils import nowdate
+from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import make_closing_entry_from_opening
from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
class TestPOSClosingEntry(unittest.TestCase):
+ def setUp(self):
+ # Make stock available for POS Sales
+ make_stock_entry(target="_Test Warehouse - _TC", qty=2, basic_rate=100)
+
+ def tearDown(self):
+ frappe.set_user("Administrator")
+ frappe.db.sql("delete from `tabPOS Profile`")
+
def test_pos_closing_entry(self):
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
@@ -41,9 +50,6 @@
self.assertEqual(pcv_doc.total_quantity, 2)
self.assertEqual(pcv_doc.net_total, 6700)
- frappe.set_user("Administrator")
- frappe.db.sql("delete from `tabPOS Profile`")
-
def test_cancelling_of_pos_closing_entry(self):
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
@@ -84,8 +90,6 @@
self.assertEqual(si_doc.docstatus, 2)
self.assertEqual(pos_inv1.status, 'Paid')
- frappe.set_user("Administrator")
- frappe.db.sql("delete from `tabPOS Profile`")
def init_user_and_profile(**args):
user = 'test@example.com'
@@ -103,4 +107,4 @@
pos_profile.save()
- return test_user, pos_profile
\ No newline at end of file
+ return test_user, pos_profile
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index 76e0092..1e6a3d1 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -57,7 +57,7 @@
self.apply_loyalty_points()
self.check_phone_payments()
self.set_status(update=True)
-
+
def before_cancel(self):
if self.consolidated_invoice and frappe.db.get_value('Sales Invoice', self.consolidated_invoice, 'docstatus') == 1:
pos_closing_entry = frappe.get_all(
@@ -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:
@@ -221,7 +230,7 @@
base_grand_total = flt(self.base_rounded_total) or flt(self.base_grand_total)
if not flt(self.change_amount) and grand_total < flt(self.paid_amount):
self.change_amount = flt(self.paid_amount - grand_total + flt(self.write_off_amount))
- self.base_change_amount = flt(self.base_paid_amount - base_grand_total + flt(self.base_write_off_amount))
+ self.base_change_amount = flt(self.base_paid_amount) - base_grand_total + flt(self.base_write_off_amount)
if flt(self.change_amount) and not self.account_for_change_amount:
frappe.msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
@@ -355,6 +364,7 @@
return profile
+ @frappe.whitelist()
def set_missing_values(self, for_validate=False):
profile = self.set_pos_fields(for_validate)
@@ -377,12 +387,20 @@
"allow_print_before_pay": profile.get("allow_print_before_pay")
}
+ @frappe.whitelist()
+ def reset_mode_of_payments(self):
+ if self.pos_profile:
+ pos_profile = frappe.get_cached_doc('POS Profile', self.pos_profile)
+ update_multi_mode_option(self, pos_profile)
+ self.paid_amount = 0
+
def set_account_for_mode_of_payment(self):
self.payments = [d for d in self.payments if d.amount or d.base_amount or d.default]
for pay in self.payments:
if not pay.account:
pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account")
+ @frappe.whitelist()
def create_payment_request(self):
for pay in self.payments:
if pay.type == "Phone":
@@ -400,7 +418,7 @@
pay_req.request_phone_payment()
return pay_req
-
+
def get_new_payment_request(self, mop):
payment_gateway_account = frappe.db.get_value("Payment Gateway Account", {
"payment_account": mop.account,
diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
index 054afe5..6172796 100644
--- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
@@ -10,8 +10,14 @@
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):
if frappe.session.user != "Administrator":
frappe.set_user("Administrator")
@@ -316,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..bc78743 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):
@@ -78,8 +78,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 +94,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 +137,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 +176,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 +193,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 +234,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 +251,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:
+ frappe.db.rollback()
+ message_log = frappe.message_log.pop()
+ 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:
+ frappe.db.rollback()
+ message_log = frappe.message_log.pop()
+ 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 +345,14 @@
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):
+ JSONDecodeError = ValueError if six.PY2 else json.JSONDecodeError
+
+ try:
+ json_message = json.loads(message).get('message')
+ except JSONDecodeError:
+ 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 db046c9..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
@@ -14,85 +15,136 @@
def test_consolidated_invoice_creation(self):
frappe.db.sql("delete from `tabPOS Invoice`")
- test_user, pos_profile = init_user_and_profile()
+ try:
+ test_user, pos_profile = init_user_and_profile()
- pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
- pos_inv.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
- })
- pos_inv.submit()
+ pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
+ pos_inv.append('payments', {
+ 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
+ })
+ pos_inv.submit()
- pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
- pos_inv2.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
- })
- pos_inv2.submit()
+ pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
+ pos_inv2.append('payments', {
+ 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
+ })
+ pos_inv2.submit()
- pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
- pos_inv3.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
- })
- pos_inv3.submit()
+ pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
+ pos_inv3.append('payments', {
+ 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
+ })
+ pos_inv3.submit()
- consolidate_pos_invoices()
+ consolidate_pos_invoices()
- pos_inv.load_from_db()
- self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
+ pos_inv.load_from_db()
+ self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
- pos_inv3.load_from_db()
- self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
+ pos_inv3.load_from_db()
+ self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
- self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
+ self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
- frappe.set_user("Administrator")
- frappe.db.sql("delete from `tabPOS Profile`")
- frappe.db.sql("delete from `tabPOS Invoice`")
-
+ finally:
+ frappe.set_user("Administrator")
+ frappe.db.sql("delete from `tabPOS Profile`")
+ frappe.db.sql("delete from `tabPOS Invoice`")
+
def test_consolidated_credit_note_creation(self):
frappe.db.sql("delete from `tabPOS Invoice`")
- test_user, pos_profile = init_user_and_profile()
+ try:
+ test_user, pos_profile = init_user_and_profile()
- pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
- pos_inv.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
- })
- pos_inv.submit()
+ pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
+ pos_inv.append('payments', {
+ 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
+ })
+ pos_inv.submit()
- pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
- pos_inv2.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
- })
- pos_inv2.submit()
+ pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
+ pos_inv2.append('payments', {
+ 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
+ })
+ pos_inv2.submit()
- pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
- pos_inv3.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
- })
- pos_inv3.submit()
+ pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
+ pos_inv3.append('payments', {
+ 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
+ })
+ pos_inv3.submit()
- pos_inv_cn = make_sales_return(pos_inv.name)
- pos_inv_cn.set("payments", [])
- pos_inv_cn.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -300
- })
- pos_inv_cn.paid_amount = -300
- pos_inv_cn.submit()
+ pos_inv_cn = make_sales_return(pos_inv.name)
+ pos_inv_cn.set("payments", [])
+ pos_inv_cn.append('payments', {
+ 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -300
+ })
+ pos_inv_cn.paid_amount = -300
+ pos_inv_cn.submit()
- consolidate_pos_invoices()
+ consolidate_pos_invoices()
- pos_inv.load_from_db()
- self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
+ pos_inv.load_from_db()
+ self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
- pos_inv3.load_from_db()
- self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
+ pos_inv3.load_from_db()
+ self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
- pos_inv_cn.load_from_db()
- self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice))
- self.assertTrue(frappe.db.get_value("Sales Invoice", pos_inv_cn.consolidated_invoice, "is_return"))
+ pos_inv_cn.load_from_db()
+ self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice))
+ self.assertTrue(frappe.db.get_value("Sales Invoice", pos_inv_cn.consolidated_invoice, "is_return"))
- frappe.set_user("Administrator")
- frappe.db.sql("delete from `tabPOS Profile`")
+ finally:
+ frappe.set_user("Administrator")
+ 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/selling/doctype/lead_source/__init__.py b/erpnext/accounts/doctype/pos_search_fields/__init__.py
similarity index 100%
copy from erpnext/selling/doctype/lead_source/__init__.py
copy to 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.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
index 3377164..428989a 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
@@ -44,6 +44,14 @@
"column_break_21",
"min_amt",
"max_amt",
+ "product_discount_scheme_section",
+ "same_item",
+ "free_item",
+ "free_qty",
+ "free_item_rate",
+ "column_break_42",
+ "free_item_uom",
+ "is_recursive",
"section_break_23",
"valid_from",
"valid_upto",
@@ -62,13 +70,6 @@
"discount_amount",
"discount_percentage",
"for_price_list",
- "product_discount_scheme_section",
- "same_item",
- "free_item",
- "free_qty",
- "column_break_51",
- "free_item_uom",
- "free_item_rate",
"section_break_13",
"threshold_percentage",
"priority",
@@ -459,10 +460,6 @@
"label": "Qty"
},
{
- "fieldname": "column_break_51",
- "fieldtype": "Column Break"
- },
- {
"fieldname": "free_item_uom",
"fieldtype": "Link",
"label": "UOM",
@@ -552,19 +549,33 @@
"fieldname": "promotional_scheme",
"fieldtype": "Link",
"label": "Promotional Scheme",
- "options": "Promotional Scheme"
+ "no_copy": 1,
+ "options": "Promotional Scheme",
+ "print_hide": 1,
+ "read_only": 1
},
{
"description": "Simple Python Expression, Example: territory != 'All Territories'",
"fieldname": "condition",
"fieldtype": "Code",
"label": "Condition"
+ },
+ {
+ "fieldname": "column_break_42",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "description": "Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on",
+ "fieldname": "is_recursive",
+ "fieldtype": "Check",
+ "label": "Is Recursive"
}
],
"icon": "fa fa-gift",
"idx": 1,
"links": [],
- "modified": "2021-03-01 23:18:38.717613",
+ "modified": "2021-03-06 22:01:24.840422",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index f0b4e29..aedf1c6 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -237,6 +237,7 @@
"doctype": args.doctype,
"has_margin": False,
"name": args.name,
+ "free_item_data": [],
"parent": args.parent,
"parenttype": args.parenttype,
"child_docname": args.get('child_docname')
diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
index f28cee7..ef9aad5 100644
--- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
@@ -328,6 +328,21 @@
self.assertEquals(item.discount_amount, 110)
self.assertEquals(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.assertEquals(item.margin_rate_or_amount, 10)
+ self.assertEquals(item.rate_with_margin, 1100)
+ self.assertEquals(item.discount_amount, 110)
+ self.assertEquals(item.rate, 990)
+
def test_pricing_rule_for_product_discount_on_same_item(self):
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
test_record = {
@@ -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 d163335..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])
@@ -367,7 +367,7 @@
if items and doc.get("items"):
for row in doc.get('items'):
- if row.get(apply_on) not in items: continue
+ if (row.get(apply_on) or args.get(apply_on)) not in items: continue
if pr_doc.mixed_conditions:
amt = args.get('qty') * args.get("price_list_rate")
@@ -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:
@@ -479,7 +479,7 @@
doc.calculate_taxes_and_totals()
elif d.price_or_product_discount == 'Product':
- item_details = frappe._dict({'parenttype': doc.doctype})
+ item_details = frappe._dict({'parenttype': doc.doctype, 'free_item_data': []})
get_product_discount_rule(d, item_details, doc=doc)
apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
doc.set_missing_values()
@@ -508,9 +508,16 @@
frappe.throw(_("Free item not set in the pricing rule {0}")
.format(get_link_to_form("Pricing Rule", pricing_rule.name)))
- item_details.free_item_data = {
+ qty = pricing_rule.free_qty or 1
+ if pricing_rule.is_recursive:
+ transaction_qty = args.get('qty') if args else doc.total_qty
+ if transaction_qty:
+ qty = flt(transaction_qty) * qty
+
+ free_item_data_args = {
'item_code': free_item,
- 'qty': pricing_rule.free_qty or 1,
+ 'qty': qty,
+ 'pricing_rules': pricing_rule.name,
'rate': pricing_rule.free_item_rate or 0,
'price_list_rate': pricing_rule.free_item_rate or 0,
'is_free_item': 1
@@ -519,24 +526,26 @@
item_data = frappe.get_cached_value('Item', free_item, ['item_name',
'description', 'stock_uom'], as_dict=1)
- item_details.free_item_data.update(item_data)
- item_details.free_item_data['uom'] = pricing_rule.free_item_uom or item_data.stock_uom
- item_details.free_item_data['conversion_factor'] = get_conversion_factor(free_item,
- item_details.free_item_data['uom']).get("conversion_factor", 1)
+ free_item_data_args.update(item_data)
+ free_item_data_args['uom'] = pricing_rule.free_item_uom or item_data.stock_uom
+ free_item_data_args['conversion_factor'] = get_conversion_factor(free_item,
+ free_item_data_args['uom']).get("conversion_factor", 1)
if item_details.get("parenttype") == 'Purchase Order':
- item_details.free_item_data['schedule_date'] = doc.schedule_date if doc else today()
+ free_item_data_args['schedule_date'] = doc.schedule_date if doc else today()
if item_details.get("parenttype") == 'Sales Order':
- item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today()
+ free_item_data_args['delivery_date'] = doc.delivery_date if doc else today()
+
+ item_details.free_item_data.append(free_item_data_args)
def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
- if pricing_rule_args.get('item_code'):
- items = [d.item_code for d in doc.items
- if d.item_code == (pricing_rule_args.get("item_code")) and d.is_free_item]
+ if pricing_rule_args:
+ items = tuple([(d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item])
- if not items:
- doc.append('items', pricing_rule_args)
+ for args in pricing_rule_args:
+ if not items or (args.get('item_code'), args.get('pricing_rules')) not in items:
+ doc.append('items', args)
def get_pricing_rule_items(pr_doc):
apply_on_data = []
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..f61aacb 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
@@ -19,7 +19,7 @@
</tr>
</thead>
<tbody>
- {% for row in data %}
+ {% for row in data %}
<tr>
{% if(row.posting_date) %}
<td>{{ frappe.format(row.posting_date, 'Date') }}</td>
@@ -38,30 +38,30 @@
{% 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>
+{% if ageing %}
+<h3 class="text-center">{{ _("Ageing Report Based On ") }} {{ ageing.ageing_based_on }}</h3>
<h5 class="text-center">
{{ _("Up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
</h5>
@@ -78,10 +78,10 @@
</thead>
<tbody>
<tr>
- <td>{{ aging.range1 }}</td>
- <td>{{ aging.range2 }}</td>
- <td>{{ aging.range3 }}</td>
- <td>{{ aging.range4 }}</td>
+ <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>
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..6dc4643 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
@@ -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.');
}
}
}
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..a0dbff3 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,30 @@
'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)
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 +89,14 @@
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})
+
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 +135,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 +176,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 +208,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 89f7238..7d93023 100644
--- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
+++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
@@ -9,19 +9,19 @@
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']
+ 'supplier_group', 'company', 'currency', 'apply_multiple_pricing_rules']
other_fields = ['min_qty', 'max_qty', 'min_amt',
'max_amt', 'priority','warehouse', 'threshold_percentage', 'rule_description']
price_discount_fields = ['rate_or_discount', 'apply_discount_on', 'apply_discount_on_rate',
- 'rate', 'discount_amount', 'discount_percentage', 'validate_applied_rule']
+ 'rate', 'discount_amount', 'discount_percentage', 'validate_applied_rule', 'apply_multiple_pricing_rules']
product_discount_fields = ['free_item', 'free_qty', 'free_item_uom',
- 'free_item_rate', 'same_item']
+ 'free_item_rate', 'same_item', 'is_recursive', 'apply_multiple_pricing_rules']
class PromotionalScheme(Document):
def validate(self):
@@ -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/promotional_scheme_price_discount/promotional_scheme_price_discount.json b/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json
index 224b8de..795fb1c 100644
--- a/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json
+++ b/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json
@@ -1,792 +1,181 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
+ "actions": [],
"creation": "2019-03-24 14:48:59.649168",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
- "document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
+ "field_order": [
+ "disable",
+ "apply_multiple_pricing_rules",
+ "column_break_2",
+ "rule_description",
+ "section_break_2",
+ "min_qty",
+ "max_qty",
+ "column_break_3",
+ "min_amount",
+ "max_amount",
+ "section_break_6",
+ "rate_or_discount",
+ "column_break_10",
+ "rate",
+ "discount_amount",
+ "discount_percentage",
+ "section_break_11",
+ "warehouse",
+ "threshold_percentage",
+ "validate_applied_rule",
+ "column_break_14",
+ "priority",
+ "apply_discount_on_rate"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
+ "default": "0",
"fieldname": "disable",
"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": "Disable",
- "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
+ "label": "Disable"
},
{
- "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
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "rule_description",
"fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Rule Description",
- "length": 0,
"no_copy": 1,
- "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,
- "translatable": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "section_break_2",
- "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": 1,
"default": "0",
"fieldname": "min_qty",
"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": "Min 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
+ "label": "Min Qty"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
"columns": 1,
"default": "0",
"fieldname": "max_qty",
"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": "Max 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
+ "label": "Max Qty"
},
{
- "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,
"default": "0",
"fieldname": "min_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": "Min Amount",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "label": "Min Amount"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"default": "0",
- "depends_on": "",
"fieldname": "max_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": "Max Amount",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "label": "Max Amount"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
"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,
- "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
+ "fieldtype": "Section Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"default": "Discount Percentage",
- "depends_on": "",
"fieldname": "rate_or_discount",
"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": "Discount Type",
- "length": 0,
- "no_copy": 0,
- "options": "\nRate\nDiscount Percentage\nDiscount Amount",
- "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
+ "options": "\nRate\nDiscount Percentage\nDiscount Amount"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
"fieldname": "column_break_10",
- "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": 2,
"depends_on": "eval:doc.rate_or_discount==\"Rate\"",
"fieldname": "rate",
"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": "Rate",
- "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
+ "label": "Rate"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"depends_on": "eval:doc.rate_or_discount==\"Discount Amount\"",
"fieldname": "discount_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": "Discount Amount",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "label": "Discount Amount"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"depends_on": "eval:doc.rate_or_discount==\"Discount Percentage\"",
"fieldname": "discount_percentage",
"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": "Discount Percentage",
- "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
+ "label": "Discount Percentage"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"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
+ "fieldtype": "Section Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "warehouse",
"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": "Warehouse",
- "length": 0,
- "no_copy": 0,
- "options": "Warehouse",
- "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
+ "options": "Warehouse"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "threshold_percentage",
"fieldtype": "Percent",
- "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": "Threshold for Suggestion",
- "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
+ "label": "Threshold for Suggestion"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"default": "1",
"fieldname": "validate_applied_rule",
"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": "Validate Applied Rule",
- "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
+ "label": "Validate Applied Rule"
},
{
- "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
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "priority",
"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": "Priority",
- "length": 0,
- "no_copy": 0,
- "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20",
- "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
+ "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
+ "default": "0",
"depends_on": "priority",
"fieldname": "apply_multiple_pricing_rules",
"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": "Apply Multiple Pricing Rules",
- "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
+ "label": "Apply Multiple Pricing Rules"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"default": "0",
"depends_on": "eval:in_list(['Discount Percentage', 'Discount Amount'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules",
"fieldname": "apply_discount_on_rate",
"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": "Apply Discount on Rate",
- "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
+ "label": "Apply Discount on Rate"
}
],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
+ "index_web_pages_for_search": 1,
"istable": 1,
- "max_attachments": 0,
- "modified": "2019-03-24 14:48:59.649168",
+ "links": [],
+ "modified": "2021-03-07 11:56:23.424137",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Promotional Scheme Price Discount",
- "name_case": "",
"owner": "Administrator",
"permissions": [],
- "quick_entry": 0,
- "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
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json b/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json
index 72d53bf..3eab515 100644
--- a/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json
+++ b/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json
@@ -1,10 +1,12 @@
{
+ "actions": [],
"creation": "2019-03-24 14:48:59.649168",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"disable",
+ "apply_multiple_pricing_rules",
"column_break_2",
"rule_description",
"section_break_1",
@@ -25,7 +27,7 @@
"threshold_percentage",
"column_break_15",
"priority",
- "apply_multiple_pricing_rules"
+ "is_recursive"
],
"fields": [
{
@@ -152,10 +154,19 @@
"fieldname": "apply_multiple_pricing_rules",
"fieldtype": "Check",
"label": "Apply Multiple Pricing Rules"
+ },
+ {
+ "default": "0",
+ "description": "Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on",
+ "fieldname": "is_recursive",
+ "fieldtype": "Check",
+ "label": "Is Recursive"
}
],
+ "index_web_pages_for_search": 1,
"istable": 1,
- "modified": "2019-07-21 00:00:56.674284",
+ "links": [],
+ "modified": "2021-03-06 21:58:18.162346",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Promotional Scheme Product Discount",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index b31cdce..617b5b4 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,8 +514,30 @@
}
},
+ 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) {
+ if(frm.doc.__onload && frm.is_new()) {
if(frm.doc.supplier) {
frm.doc.apply_tds = frm.doc.__onload.supplier_tds ? 1 : 0;
}
@@ -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 18b6637..24e67fe 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -127,7 +127,6 @@
"write_off_cost_center",
"advances_section",
"allocate_advances_automatically",
- "adjust_advance_taxes",
"get_advances",
"advances",
"payment_schedule_section",
@@ -164,7 +163,8 @@
"to_date",
"column_break_114",
"auto_repeat",
- "update_auto_repeat_reference"
+ "update_auto_repeat_reference",
+ "per_received"
],
"fields": [
{
@@ -1327,13 +1327,6 @@
"options": "Project"
},
{
- "default": "0",
- "description": "Taxes paid while advance payment will be adjusted against this invoice",
- "fieldname": "adjust_advance_taxes",
- "fieldtype": "Check",
- "label": "Adjust Advance Taxes"
- },
- {
"depends_on": "eval:doc.is_internal_supplier",
"description": "Unrealized Profit / Loss account for intra-company transfers",
"fieldname": "unrealized_profit_loss_account",
@@ -1372,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-09 21:12:30.422084",
+ "modified": "2021-03-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 ded293b..66be11f 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()
@@ -898,7 +898,7 @@
acc_settings.submit_journal_entries = 1
acc_settings.save()
- item = create_item("_Test Item for Deferred Accounting")
+ item = create_item("_Test Item for Deferred Accounting", is_purchase_item=True)
item.enable_deferred_expense = 1
item.deferred_expense_account = deferred_account
item.save()
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_records.json b/erpnext/accounts/doctype/purchase_invoice/test_records.json
index e7166c5..9f9e90d 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_records.json
+++ b/erpnext/accounts/doctype/purchase_invoice/test_records.json
@@ -43,7 +43,7 @@
}
],
"grand_total": 0,
- "naming_series": "_T-BILL",
+ "naming_series": "T-PINV-",
"taxes": [
{
"account_head": "_Test Account Shipping Charges - _TC",
@@ -167,7 +167,7 @@
}
],
"grand_total": 0,
- "naming_series": "_T-Purchase Invoice-",
+ "naming_series": "T-PINV-",
"taxes": [
{
"account_head": "_Test Account Shipping Charges - _TC",
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 8e8f85a..95e570b 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -1,10 +1,6 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-// print heading
-frappe.provide('cur_frm.pformat')
-cur_frm.pformat.print_heading = 'Invoice';
-
{% include 'erpnext/selling/sales_common.js' %};
frappe.provide("erpnext.accounts");
@@ -917,7 +913,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 720a917..c6c67b4 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -118,6 +118,7 @@
"in_words",
"total_advance",
"outstanding_amount",
+ "disable_rounded_total",
"advances_section",
"allocate_advances_automatically",
"get_advances",
@@ -1109,6 +1110,7 @@
"reqd": 1
},
{
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "base_rounding_adjustment",
"fieldtype": "Currency",
"hide_days": 1,
@@ -1120,6 +1122,7 @@
"read_only": 1
},
{
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "base_rounded_total",
"fieldtype": "Currency",
"hide_days": 1,
@@ -1168,6 +1171,7 @@
"reqd": 1
},
{
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "rounding_adjustment",
"fieldtype": "Currency",
"hide_days": 1,
@@ -1180,6 +1184,7 @@
},
{
"bold": 1,
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "rounded_total",
"fieldtype": "Currency",
"hide_days": 1,
@@ -1945,6 +1950,13 @@
"fieldtype": "Link",
"label": "Set Target Warehouse",
"options": "Warehouse"
+ },
+ {
+ "default": "0",
+ "depends_on": "grand_total",
+ "fieldname": "disable_rounded_total",
+ "fieldtype": "Check",
+ "label": "Disable Rounded Total"
}
],
"icon": "fa fa-file-text",
@@ -1952,13 +1964,12 @@
"is_submittable": 1,
"links": [
{
- "custom": 1,
"group": "Reference",
"link_doctype": "POS Invoice",
"link_fieldname": "consolidated_invoice"
}
],
- "modified": "2021-02-01 15:42:26.261540",
+ "modified": "2021-04-15 23:57:58.766651",
"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 a1bf66b..4461f29 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',
@@ -214,6 +214,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 +275,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])
)
@@ -390,6 +393,7 @@
if validate_against_credit_limit:
check_credit_limit(self.customer, self.company, bypass_credit_limit_check_at_sales_order)
+ @frappe.whitelist()
def set_missing_values(self, for_validate=False):
pos = self.set_pos_fields(for_validate)
@@ -544,12 +548,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"))
@@ -729,6 +733,7 @@
else:
self.calculate_billing_amount_for_timesheet()
+ @frappe.whitelist()
def add_timesheet_data(self):
self.set('timesheets', [])
if self.project:
@@ -1286,6 +1291,7 @@
break
# Healthcare
+ @frappe.whitelist()
def set_healthcare_services(self, checked_values):
self.set("items", [])
from erpnext.stock.get_item_details import get_item_details
diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json
index e00a58f..3781f8c 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_records.json
+++ b/erpnext/accounts/doctype/sales_invoice/test_records.json
@@ -31,7 +31,7 @@
"base_grand_total": 561.8,
"grand_total": 561.8,
"is_pos": 0,
- "naming_series": "_T-Sales Invoice-",
+ "naming_series": "T-SINV-",
"base_net_total": 500.0,
"taxes": [
{
@@ -104,7 +104,7 @@
"base_grand_total": 630.0,
"grand_total": 630.0,
"is_pos": 0,
- "naming_series": "_T-Sales Invoice-",
+ "naming_series": "T-SINV-",
"base_net_total": 500.0,
"taxes": [
{
@@ -175,7 +175,7 @@
],
"grand_total": 0,
"is_pos": 0,
- "naming_series": "_T-Sales Invoice-",
+ "naming_series": "T-SINV-",
"taxes": [
{
"account_head": "_Test Account Shipping Charges - _TC",
@@ -301,7 +301,7 @@
],
"grand_total": 0,
"is_pos": 0,
- "naming_series": "_T-Sales Invoice-",
+ "naming_series": "T-SINV-",
"taxes": [
{
"account_head": "_Test Account Excise Duty - _TC",
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 90e2144..9059d0b 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1166,10 +1166,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 +1879,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 +1900,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):
@@ -2115,6 +2128,7 @@
si.return_against = args.return_against
si.currency=args.currency or "INR"
si.conversion_rate = args.conversion_rate or 1
+ si.naming_series = args.naming_series or "T-SINV-"
si.append("items", {
"item_code": args.item or args.item_code or "_Test Item",
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_rule/test_tax_rule.py b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py
index 632e30d..ac1ffd9 100644
--- a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py
+++ b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py
@@ -14,10 +14,15 @@
from six import iteritems
class TestTaxRule(unittest.TestCase):
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
+ frappe.db.set_value("Shopping Cart Settings", None, "enabled", 0)
+
+ @classmethod
+ def tearDownClass(cls):
frappe.db.sql("delete from `tabTax Rule`")
- def tearDown(self):
+ def setUp(self):
frappe.db.sql("delete from `tabTax Rule`")
def test_conflict(self):
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..09db7fe 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -251,7 +251,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,
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/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 51fc7ec..444b40e 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.due_date, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount
from `tab{0}` si, `tabPayment Schedule` ps
where
si.name = ps.parent and
@@ -395,13 +395,13 @@
"invoiced": invoiced,
"invoice_grand_total": row.invoiced,
"payment_term": d.description,
- "paid": d.paid_amount,
+ "paid": d.paid_amount + d.discounted_amount,
"credit_note": 0.0,
- "outstanding": invoiced - d.paid_amount
+ "outstanding": invoiced - d.paid_amount - d.discounted_amount
}))
if d.paid_amount:
- row['paid'] -= d.paid_amount
+ row['paid'] -= d.paid_amount + d.discounted_amount
def allocate_closing_to_term(self, row, term, key):
if row[key]:
diff --git a/erpnext/selling/doctype/lead_source/__init__.py b/erpnext/accounts/report/billed_items_to_be_received/__init__.py
similarity index 100%
copy from erpnext/selling/doctype/lead_source/__init__.py
copy to 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/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 7dfce85..14efa1f 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -51,7 +51,11 @@
"from_date": start_date
})
- to_date = add_months(start_date, months_to_add)
+ if i==0 and filter_based_on == 'Date Range':
+ to_date = add_months(get_first_day(start_date), months_to_add)
+ else:
+ to_date = add_months(start_date, months_to_add)
+
start_date = to_date
# Subtract one day from to_date, as it may be first day in next fiscal year or month
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 89a05b1..5a64e27 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -406,9 +406,10 @@
throw(_("""Payment Entry has been modified after you pulled it. Please pull it again."""))
def validate_allocated_amount(args):
+ precision = args.get('precision') or frappe.db.get_single_value("System Settings", "currency_precision")
if args.get("allocated_amount") < 0:
throw(_("Allocated amount cannot be negative"))
- elif args.get("allocated_amount") > args.get("unadjusted_amount"):
+ elif flt(args.get("allocated_amount"), precision) > flt(args.get("unadjusted_amount"), precision):
throw(_("Allocated amount cannot be greater than unadjusted amount"))
def update_reference_in_journal_entry(d, jv_obj):
diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json
index fadb665..9ffa481 100644
--- a/erpnext/accounts/workspace/accounting/accounting.json
+++ b/erpnext/accounts/workspace/accounting/accounting.json
@@ -444,6 +444,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",
diff --git a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py b/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py
index afbd9b4..9000dea 100644
--- a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py
+++ b/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py
@@ -71,6 +71,7 @@
"exp_end_date": add_days(start_date, crop_task.get("end_day") - 1)
}).insert()
+ @frappe.whitelist()
def reload_linked_analysis(self):
linked_doctypes = ['Soil Texture', 'Soil Analysis', 'Plant Analysis']
required_fields = ['location', 'name', 'collection_datetime']
@@ -87,6 +88,7 @@
frappe.publish_realtime("List of Linked Docs",
output, user=frappe.session.user)
+ @frappe.whitelist()
def append_to_child(self, obj_to_append):
for doctype in obj_to_append:
for doc_name in set(obj_to_append[doctype]):
diff --git a/erpnext/agriculture/doctype/fertilizer/fertilizer.py b/erpnext/agriculture/doctype/fertilizer/fertilizer.py
index dc2781c..9cb492a 100644
--- a/erpnext/agriculture/doctype/fertilizer/fertilizer.py
+++ b/erpnext/agriculture/doctype/fertilizer/fertilizer.py
@@ -7,6 +7,7 @@
from frappe.model.document import Document
class Fertilizer(Document):
+ @frappe.whitelist()
def load_contents(self):
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Fertilizer'})
for doc in docs:
diff --git a/erpnext/agriculture/doctype/plant_analysis/plant_analysis.py b/erpnext/agriculture/doctype/plant_analysis/plant_analysis.py
index 304727e..2806cc6 100644
--- a/erpnext/agriculture/doctype/plant_analysis/plant_analysis.py
+++ b/erpnext/agriculture/doctype/plant_analysis/plant_analysis.py
@@ -8,6 +8,7 @@
from frappe.model.document import Document
class PlantAnalysis(Document):
+ @frappe.whitelist()
def load_contents(self):
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Plant Analysis'})
for doc in docs:
diff --git a/erpnext/agriculture/doctype/soil_analysis/soil_analysis.py b/erpnext/agriculture/doctype/soil_analysis/soil_analysis.py
index 17b96a0..37835f8 100644
--- a/erpnext/agriculture/doctype/soil_analysis/soil_analysis.py
+++ b/erpnext/agriculture/doctype/soil_analysis/soil_analysis.py
@@ -7,6 +7,7 @@
from frappe.model.document import Document
class SoilAnalysis(Document):
+ @frappe.whitelist()
def load_contents(self):
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Soil Analysis'})
for doc in docs:
diff --git a/erpnext/agriculture/doctype/soil_texture/soil_texture.py b/erpnext/agriculture/doctype/soil_texture/soil_texture.py
index 8c1d7ed..209b2c8 100644
--- a/erpnext/agriculture/doctype/soil_texture/soil_texture.py
+++ b/erpnext/agriculture/doctype/soil_texture/soil_texture.py
@@ -13,6 +13,7 @@
soil_edit_order = [2, 1, 0]
soil_types = ['clay_composition', 'sand_composition', 'silt_composition']
+ @frappe.whitelist()
def load_contents(self):
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Soil Texture'})
for doc in docs:
@@ -26,6 +27,7 @@
if sum(self.get(soil_type) for soil_type in self.soil_types) != 100:
frappe.throw(_('Soil compositions do not add up to 100'))
+ @frappe.whitelist()
def update_soil_edit(self, soil_type):
self.soil_edit_order[self.soil_types.index(soil_type)] = max(self.soil_edit_order)+1
self.soil_type = self.get_soil_type()
@@ -35,8 +37,8 @@
if sum(self.soil_edit_order) < 5: return
last_edit_index = self.soil_edit_order.index(min(self.soil_edit_order))
- # set composition of the last edited soil
- self.set( self.soil_types[last_edit_index],
+ # set composition of the last edited soil
+ self.set(self.soil_types[last_edit_index],
100 - sum(cint(self.get(soil_type)) for soil_type in self.soil_types) + cint(self.get(self.soil_types[last_edit_index])))
# calculate soil type
@@ -67,4 +69,4 @@
elif (c >= 40 and sa <= 45 and si < 40):
return 'Clay'
else:
- return 'Select'
\ No newline at end of file
+ return 'Select'
diff --git a/erpnext/agriculture/doctype/water_analysis/water_analysis.py b/erpnext/agriculture/doctype/water_analysis/water_analysis.py
index 88f1fbd..d9f007c 100644
--- a/erpnext/agriculture/doctype/water_analysis/water_analysis.py
+++ b/erpnext/agriculture/doctype/water_analysis/water_analysis.py
@@ -9,11 +9,13 @@
from frappe import _
class WaterAnalysis(Document):
+ @frappe.whitelist()
def load_contents(self):
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Water Analysis'})
for doc in docs:
self.append('water_analysis_criteria', {'title': str(doc.name)})
+ @frappe.whitelist()
def update_lab_result_date(self):
if not self.result_datetime:
self.result_datetime = self.laboratory_testing_datetime
diff --git a/erpnext/agriculture/doctype/weather/weather.py b/erpnext/agriculture/doctype/weather/weather.py
index 938daa2..235e684 100644
--- a/erpnext/agriculture/doctype/weather/weather.py
+++ b/erpnext/agriculture/doctype/weather/weather.py
@@ -7,6 +7,7 @@
from frappe.model.document import Document
class Weather(Document):
+ @frappe.whitelist()
def load_contents(self):
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Weather'})
for doc in docs:
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index e8e8ec6..9aff144 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -553,6 +553,7 @@
make_gl_entries(gl_entries)
self.db_set('booked_fixed_asset', 1)
+ @frappe.whitelist()
def get_depreciation_rate(self, args, on_validate=False):
if isinstance(args, string_types):
args = json.loads(args)
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/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index d32e98e..ef9372e 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -133,6 +133,7 @@
d.material_request_item, "schedule_date")
+ @frappe.whitelist()
def get_last_purchase_rate(self):
"""get last purchase rates for all items"""
@@ -252,6 +253,7 @@
self.update_prevdoc_status()
# Must be called after updating ordered qty in Material Request
+ # bin uses Material Request Items to recalculate & update
self.update_requested_qty()
self.update_ordered_qty()
@@ -366,7 +368,6 @@
"Purchase Order": {
"doctype": "Purchase Receipt",
"field_map": {
- "per_billed": "per_billed",
"supplier_warehouse":"supplier_warehouse"
},
"validation": {
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 604c886..42f4472 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -90,6 +90,50 @@
frappe.db.set_value('Item', '_Test Item', 'over_billing_allowance', 0)
frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0)
+ def test_update_remove_child_linked_to_mr(self):
+ """Test impact on linked PO and MR on deleting/updating row."""
+ mr = make_material_request(qty=10)
+ po = make_purchase_order(mr.name)
+ po.supplier = "_Test Supplier"
+ po.save()
+ po.submit()
+
+ first_item_of_po = po.get("items")[0]
+ existing_ordered_qty = get_ordered_qty() # 10
+ existing_requested_qty = get_requested_qty() # 0
+
+ # decrease ordered qty by 3 (10 -> 7) and add item
+ trans_item = json.dumps([
+ {
+ 'item_code': first_item_of_po.item_code,
+ 'rate': first_item_of_po.rate,
+ 'qty': 7,
+ 'docname': first_item_of_po.name
+ },
+ {'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 2}
+ ])
+ update_child_qty_rate('Purchase Order', trans_item, po.name)
+ mr.reload()
+
+ # requested qty increases as ordered qty decreases
+ self.assertEqual(get_requested_qty(), existing_requested_qty + 3) # 3
+ self.assertEqual(mr.items[0].ordered_qty, 7)
+
+ self.assertEqual(get_ordered_qty(), existing_ordered_qty - 3) # 7
+
+ # delete first item linked to Material Request
+ trans_item = json.dumps([
+ {'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 2}
+ ])
+ update_child_qty_rate('Purchase Order', trans_item, po.name)
+ mr.reload()
+
+ # requested qty increases as ordered qty is 0 (deleted row)
+ self.assertEqual(get_requested_qty(), existing_requested_qty + 10) # 10
+ self.assertEqual(mr.items[0].ordered_qty, 0)
+
+ # ordered qty decreases as ordered qty is 0 (deleted row)
+ self.assertEqual(get_ordered_qty(), existing_ordered_qty - 10) # 0
def test_update_child(self):
mr = make_material_request(qty=10)
@@ -120,7 +164,6 @@
self.assertEqual(po.get("items")[0].amount, 1400)
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3)
-
def test_update_child_adding_new_item(self):
po = create_purchase_order(do_not_save=1)
po.items[0].qty = 4
@@ -129,6 +172,7 @@
pr = make_pr_against_po(po.name, 2)
po.load_from_db()
+ existing_ordered_qty = get_ordered_qty()
first_item_of_po = po.get("items")[0]
trans_item = json.dumps([
@@ -145,7 +189,8 @@
po.reload()
self.assertEquals(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)
def test_update_child_removing_item(self):
po = create_purchase_order(do_not_save=1)
@@ -156,6 +201,7 @@
po.reload()
first_item_of_po = po.get("items")[0]
+ existing_ordered_qty = get_ordered_qty()
# add an item
trans_item = json.dumps([
{
@@ -168,6 +214,10 @@
update_child_qty_rate('Purchase Order', trans_item, po.name)
po.reload()
+
+ # ordered qty should increase on row addition
+ self.assertEqual(get_ordered_qty(), existing_ordered_qty + 7)
+
# check if can remove received item
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': po.get("items")[1].name}])
self.assertRaises(frappe.ValidationError, update_child_qty_rate, 'Purchase Order', trans_item, po.name)
@@ -187,6 +237,9 @@
self.assertEquals(len(po.get('items')), 1)
self.assertEqual(po.status, 'To Receive and Bill')
+ # ordered qty should decrease (back to initial) on row deletion
+ self.assertEqual(get_ordered_qty(), existing_ordered_qty)
+
def test_update_child_perm(self):
po = create_purchase_order(item_code= "_Test Item", qty=4)
@@ -230,11 +283,13 @@
new_item_with_tax = frappe.get_doc("Item", "Test Item with Tax")
- new_item_with_tax.append("taxes", {
- "item_tax_template": "Test Update Items Template - _TC",
- "valid_from": nowdate()
- })
- new_item_with_tax.save()
+ if not frappe.db.exists("Item Tax",
+ {"item_tax_template": "Test Update Items Template - _TC", "parent": "Test Item with Tax"}):
+ new_item_with_tax.append("taxes", {
+ "item_tax_template": "Test Update Items Template - _TC",
+ "valid_from": nowdate()
+ })
+ new_item_with_tax.save()
tax_template = "_Test Account Excise Duty @ 10 - _TC"
item = "_Test Item Home Desktop 100"
@@ -380,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.assertEquals(pi.per_received, 100.00)
+ self.assertEquals(pi.items[0].qty, pi.items[0].received_qty)
+
+ po.load_from_db()
+
+ self.assertEquals(po.per_received, 100.00)
+ self.assertEquals(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)
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 7cf22f8..b530d1a 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -66,6 +66,7 @@
def on_cancel(self):
frappe.db.set(self, 'status', 'Cancelled')
+ @frappe.whitelist()
def get_supplier_email_preview(self, supplier):
"""Returns formatted email preview as string."""
rfq_suppliers = list(filter(lambda row: row.supplier == supplier, self.suppliers))
diff --git a/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
index 6e6eaed..2528240 100644
--- a/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
+++ b/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
@@ -9,9 +9,7 @@
class TestSupplierScorecard(unittest.TestCase):
def test_create_scorecard(self):
- delete_test_scorecards()
- my_doc = make_supplier_scorecard()
- doc = my_doc.insert()
+ doc = make_supplier_scorecard().insert()
self.assertEqual(doc.name, valid_scorecard[0].get("supplier"))
def test_criteria_weight(self):
@@ -121,7 +119,8 @@
{
"weight":100.0,
"doctype":"Supplier Scorecard Scoring Criteria",
- "criteria_name":"Delivery"
+ "criteria_name":"Delivery",
+ "formula": "100"
}
],
"supplier":"_Test Supplier",
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 12a81c7..c409850 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -26,7 +26,8 @@
class AccountMissingError(frappe.ValidationError): pass
-force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules")
+force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate",
+ "pricing_rules", "weight_per_unit", "weight_uom", "total_weight")
class AccountsController(TransactionBase):
def __init__(self, *args, **kwargs):
@@ -89,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()
@@ -232,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
@@ -239,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"))
@@ -516,6 +536,7 @@
frappe.db.sql("""delete from `tab%s` where parentfield=%s and parent = %s
and allocated_amount = 0""" % (childtype, '%s', '%s'), (parentfield, self.name))
+ @frappe.whitelist()
def apply_shipping_rule(self):
if self.shipping_rule:
shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule)
@@ -536,6 +557,7 @@
return {}
+ @frappe.whitelist()
def set_advances(self):
"""Returns list of advances against Account, Party, Reference"""
@@ -656,6 +678,7 @@
'dr_or_cr': dr_or_cr,
'unadjusted_amount': flt(d.advance_amount),
'allocated_amount': flt(d.allocated_amount),
+ 'precision': d.precision('advance_amount'),
'exchange_rate': (self.conversion_rate
if self.party_account_currency != self.company_currency else 1),
'grand_total': (self.base_grand_total
@@ -713,7 +736,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))
@@ -898,29 +923,35 @@
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.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):
due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date]
@@ -956,22 +987,28 @@
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"))
+ print(grand_total, 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):
@@ -1211,7 +1248,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
@@ -1219,14 +1256,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)
@@ -1235,18 +1272,24 @@
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.outstanding = term_details.payment_amount
+ term_details.mode_of_payment = term.mode_of_payment
+
if bill_date:
term_details.due_date = get_due_date(term, bill_date)
+ term_details.discount_date = get_discount_date(term, bill_date)
elif posting_date:
term_details.due_date = get_due_date(term, posting_date)
+ term_details.discount_date = get_discount_date(term, posting_date)
if getdate(term_details.due_date) < getdate(posting_date):
term_details.due_date = posting_date
- term_details.mode_of_payment = term.mode_of_payment
return term_details
-
def get_due_date(term, posting_date=None, bill_date=None):
due_date = None
date = bill_date or posting_date
@@ -1258,6 +1301,16 @@
due_date = add_months(get_last_day(date), term.credit_months)
return due_date
+def get_discount_date(term, posting_date=None, bill_date=None):
+ discount_validity = None
+ date = bill_date or posting_date
+ if term.discount_validity_based_on == "Day(s) after invoice date":
+ discount_validity = add_days(date, term.discount_validity)
+ elif term.discount_validity_based_on == "Day(s) after the end of the invoice month":
+ discount_validity = add_days(get_last_day(date), term.discount_validity)
+ elif term.discount_validity_based_on == "Month(s) after the end of the invoice month":
+ discount_validity = add_months(get_last_day(date), term.discount_validity)
+ return discount_validity
def get_supplier_block_status(party_name):
"""
@@ -1316,25 +1369,63 @@
p_doc = frappe.get_doc(parent_doctype, parent_doctype_name)
child_item = frappe.new_doc(child_doctype, p_doc, child_docname)
item = frappe.get_doc("Item", trans_item.get('item_code'))
+
for field in ("item_code", "item_name", "description", "item_group"):
- child_item.update({field: item.get(field)})
+ child_item.update({field: item.get(field)})
+
date_fieldname = "delivery_date" if child_doctype == "Sales Order Item" else "schedule_date"
child_item.update({date_fieldname: trans_item.get(date_fieldname) or p_doc.get(date_fieldname)})
+ child_item.stock_uom = item.stock_uom
child_item.uom = trans_item.get("uom") or item.stock_uom
+ child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
+
if child_doctype == "Purchase Order Item":
- child_item.base_rate = 1 # Initiallize value will update in parent validation
- child_item.base_amount = 1 # Initiallize value will update in parent validation
+ # Initialized value will update in parent validation
+ child_item.base_rate = 1
+ child_item.base_amount = 1
if child_doctype == "Sales Order Item":
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
if not child_item.warehouse:
frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
.format(frappe.bold("default warehouse"), frappe.bold(item.item_code)))
+
set_child_tax_template_and_map(item, child_item, p_doc)
add_taxes_from_tax_template(child_item, p_doc)
return child_item
+def validate_child_on_delete(row, parent):
+ """Check if partially transacted item (row) is being deleted."""
+ if parent.doctype == "Sales Order":
+ if flt(row.delivered_qty):
+ frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been delivered").format(row.idx, row.item_code))
+ if flt(row.work_order_qty):
+ frappe.throw(_("Row #{0}: Cannot delete item {1} which has work order assigned to it.").format(row.idx, row.item_code))
+ if flt(row.ordered_qty):
+ frappe.throw(_("Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.").format(row.idx, row.item_code))
+
+ if parent.doctype == "Purchase Order" and flt(row.received_qty):
+ frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(row.idx, row.item_code))
+
+ if flt(row.billed_amt):
+ frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(row.idx, row.item_code))
+
+def update_bin_on_delete(row, doctype):
+ """Update bin for deleted item (row)."""
+ from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty, get_ordered_qty, get_indented_qty
+ qty_dict = {}
+
+ if doctype == "Sales Order":
+ qty_dict["reserved_qty"] = get_reserved_qty(row.item_code, row.warehouse)
+ else:
+ if row.material_request_item:
+ qty_dict["indented_qty"] = get_indented_qty(row.item_code, row.warehouse)
+
+ qty_dict["ordered_qty"] = get_ordered_qty(row.item_code, row.warehouse)
+
+ update_bin_qty(row.item_code, row.warehouse, qty_dict)
+
def validate_and_delete_children(parent, data):
deleted_children = []
updated_item_names = [d.get("docname") for d in data]
@@ -1343,23 +1434,17 @@
deleted_children.append(item)
for d in deleted_children:
- if parent.doctype == "Sales Order":
- if flt(d.delivered_qty):
- frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been delivered").format(d.idx, d.item_code))
- if flt(d.work_order_qty):
- frappe.throw(_("Row #{0}: Cannot delete item {1} which has work order assigned to it.").format(d.idx, d.item_code))
- if flt(d.ordered_qty):
- frappe.throw(_("Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.").format(d.idx, d.item_code))
-
- if parent.doctype == "Purchase Order" and flt(d.received_qty):
- frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(d.idx, d.item_code))
-
- if flt(d.billed_amt):
- frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(d.idx, d.item_code))
-
+ validate_child_on_delete(d, parent)
d.cancel()
d.delete()
+ # need to update ordered qty in Material Request first
+ # bin uses Material Request Items to recalculate & update
+ parent.update_prevdoc_status()
+
+ for d in deleted_children:
+ update_bin_on_delete(d, parent.doctype)
+
@frappe.whitelist()
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
def check_doc_permissions(doc, perm_type='create'):
@@ -1394,7 +1479,7 @@
)
def get_new_child_item(item_row):
- child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item"
+ child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item"
return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row)
def validate_quantity(child_item, d):
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 219d529..b686dc0 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)
@@ -1056,7 +1059,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 +1112,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..bc1ac5e 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -325,7 +325,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 +713,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..4bb6138 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -98,6 +98,7 @@
["Draft", None],
["Submitted", "eval:self.docstatus == 1"],
["Queued", "eval:self.status == 'Queued'"],
+ ["Failed", "eval:self.status == 'Failed'"],
["Cancelled", "eval:self.docstatus == 2"],
]
}
@@ -201,10 +202,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 +376,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 11ac703..b14c274 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"),
@@ -406,8 +405,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 +482,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):
@@ -495,7 +493,7 @@
"voucher_no": self.name,
"company": self.company
})
- if check_if_future_sle_exists(args):
+ if future_sle_exists(args):
create_repost_item_valuation_entry(args)
elif not is_reposting_pending():
check_if_stock_and_account_balance_synced(self.posting_date,
@@ -506,37 +504,42 @@
{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
-def check_if_future_sle_exists(args):
- sl_entries = frappe.db.get_all("Stock Ledger Entry",
+def future_sle_exists(args):
+ sl_entries = frappe.get_all("Stock Ledger Entry",
filters={"voucher_type": args.voucher_type, "voucher_no": args.voucher_no},
fields=["item_code", "warehouse"],
order_by="creation asc")
- distinct_item_warehouses = list(set([(d.item_code, d.warehouse) for d in sl_entries]))
+ if not sl_entries:
+ return
- sle_exists = False
- for item_code, warehouse in distinct_item_warehouses:
- args.update({
- "item_code": item_code,
- "warehouse": warehouse
- })
- if get_sle(args):
- sle_exists = True
- break
- return sle_exists
+ warehouse_items_map = {}
+ for entry in sl_entries:
+ if entry.warehouse not in warehouse_items_map:
+ warehouse_items_map[entry.warehouse] = set()
-def get_sle(args):
+ warehouse_items_map[entry.warehouse].add(entry.item_code)
+
+ or_conditions = []
+ for warehouse, items in warehouse_items_map.items():
+ or_conditions.append(
+ "warehouse = '{}' and item_code in ({})".format(
+ warehouse,
+ ", ".join(frappe.db.escape(item) for item in items)
+ )
+ )
+
return frappe.db.sql("""
select name
from `tabStock Ledger Entry`
where
- item_code=%(item_code)s
- and warehouse=%(warehouse)s
- and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s)
+ ({})
+ and timestamp(posting_date, posting_time)
+ >= timestamp(%(posting_date)s, %(posting_time)s)
and voucher_no != %(voucher_no)s
and is_cancelled = 0
limit 1
- """, args)
+ """.format(" or ".join(or_conditions)), args)
def create_repost_item_valuation_entry(args):
args = frappe._dict(args)
@@ -554,4 +557,4 @@
repost_entry.allow_zero_rate = args.allow_zero_rate
repost_entry.flags.ignore_links = True
repost_entry.save()
- repost_entry.submit()
\ No newline at end of file
+ repost_entry.submit()
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index c33e556..9fae494 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -113,7 +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"))
- item.discount_amount = item.rate_with_margin - item.rate
+
+ if item.discount_amount and not item.discount_percentage:
+ 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:
@@ -144,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"]
@@ -284,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 \
@@ -334,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
@@ -356,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")]):
@@ -437,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:
@@ -805,4 +819,4 @@
def set_amounts_in_company_currency(self):
for d in self.doc.get(self.tax_field):
d.amount = flt(d.amount, d.precision("amount"))
- d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))
\ No newline at end of file
+ d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))
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/selling/doctype/lead_source/__init__.py b/erpnext/crm/doctype/lead_source/__init__.py
similarity index 100%
rename from erpnext/selling/doctype/lead_source/__init__.py
rename to erpnext/crm/doctype/lead_source/__init__.py
diff --git a/erpnext/crm/doctype/lead_source/lead_source.js b/erpnext/crm/doctype/lead_source/lead_source.js
new file mode 100644
index 0000000..3cbe649
--- /dev/null
+++ b/erpnext/crm/doctype/lead_source/lead_source.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('Lead Source', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/erpnext/crm/doctype/lead_source/lead_source.json b/erpnext/crm/doctype/lead_source/lead_source.json
new file mode 100644
index 0000000..723c6d9
--- /dev/null
+++ b/erpnext/crm/doctype/lead_source/lead_source.json
@@ -0,0 +1,62 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "field:source_name",
+ "creation": "2016-09-16 01:47:47.382372",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "source_name",
+ "details"
+ ],
+ "fields": [
+ {
+ "fieldname": "source_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Source Name",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "fieldname": "details",
+ "fieldtype": "Text Editor",
+ "label": "Details"
+ }
+ ],
+ "links": [],
+ "modified": "2021-02-08 12:51:48.971517",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Lead Source",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales User",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/lead_source/lead_source.py b/erpnext/crm/doctype/lead_source/lead_source.py
similarity index 71%
rename from erpnext/selling/doctype/lead_source/lead_source.py
rename to erpnext/crm/doctype/lead_source/lead_source.py
index d2d7558..5c64fb8 100644
--- a/erpnext/selling/doctype/lead_source/lead_source.py
+++ b/erpnext/crm/doctype/lead_source/lead_source.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
-import frappe
+# import frappe
from frappe.model.document import Document
class LeadSource(Document):
diff --git a/erpnext/crm/doctype/lead_source/test_lead_source.py b/erpnext/crm/doctype/lead_source/test_lead_source.py
new file mode 100644
index 0000000..b5bc649
--- /dev/null
+++ b/erpnext/crm/doctype/lead_source/test_lead_source.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 TestLeadSource(unittest.TestCase):
+ pass
diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
index 377e061..d8c6fb4 100644
--- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
+++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
@@ -11,7 +11,8 @@
from six.moves.urllib.parse import urlencode
class LinkedInSettings(Document):
- def get_authorization_url(self):
+ @frappe.whitelist()
+ def get_authorization_url(self):
params = urlencode({
"response_type":"code",
"client_id": self.consumer_key,
@@ -35,7 +36,7 @@
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
-
+
response = self.http_post(url=url, data=body, headers=headers)
response = frappe.parse_json(response.content.decode())
self.db_set("access_token", response["access_token"])
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index 47b05f3..23ad98a 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -85,6 +85,7 @@
self.opportunity_from = "Lead"
self.party_name = lead_name
+ @frappe.whitelist()
def declare_enquiry_lost(self, lost_reasons_list, detailed_reason=None):
if not self.has_active_quotation():
frappe.db.set(self, 'status', 'Lost')
@@ -248,7 +249,6 @@
"doctype": "Quotation",
"field_map": {
"opportunity_from": "quotation_to",
- "opportunity_type": "order_type",
"name": "enq_no",
}
},
diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.py b/erpnext/crm/doctype/twitter_settings/twitter_settings.py
index 976a23d..1e1beab 100644
--- a/erpnext/crm/doctype/twitter_settings/twitter_settings.py
+++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.py
@@ -11,6 +11,7 @@
from tweepy.error import TweepError
class TwitterSettings(Document):
+ @frappe.whitelist()
def get_authorize_url(self):
callback_url = "{0}/api/method/erpnext.crm.doctype.twitter_settings.twitter_settings.callback?".format(frappe.utils.get_url())
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"), callback_url)
@@ -21,12 +22,12 @@
frappe.msgprint(_("Error! Failed to get request token."))
frappe.throw(_('Invalid {0} or {1}').format(frappe.bold("Consumer Key"), frappe.bold("Consumer Secret Key")))
-
+
def get_access_token(self, oauth_token, oauth_verifier):
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))
- auth.request_token = {
+ auth.request_token = {
'oauth_token' : oauth_token,
- 'oauth_token_secret' : oauth_verifier
+ 'oauth_token_secret' : oauth_verifier
}
try:
@@ -50,10 +51,10 @@
frappe.throw(_('Invalid Consumer Key or Consumer Secret Key'))
def get_api(self, access_token, access_token_secret):
- # authentication of consumer key and secret
- auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))
- # authentication of access token and secret
- auth.set_access_token(access_token, access_token_secret)
+ # authentication of consumer key and secret
+ auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))
+ # authentication of access token and secret
+ auth.set_access_token(access_token, access_token_secret)
return tweepy.API(auth)
@@ -64,7 +65,7 @@
if media:
media_id = self.upload_image(media)
return self.send_tweet(text, media_id)
-
+
def upload_image(self, media):
media = get_file_path(media)
api = self.get_api(self.access_token, self.access_token_secret)
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 97c29ab..6a0dcf4 100644
--- a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py
+++ b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py
@@ -13,6 +13,7 @@
class CourseSchedulingTool(Document):
+ @frappe.whitelist()
def schedule_course(self):
"""Creates course schedules as per specified parameters"""
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/fee_schedule/fee_schedule.py b/erpnext/education/doctype/fee_schedule/fee_schedule.py
index 1543acd..0b025c7 100644
--- a/erpnext/education/doctype/fee_schedule/fee_schedule.py
+++ b/erpnext/education/doctype/fee_schedule/fee_schedule.py
@@ -52,6 +52,7 @@
self.grand_total = no_of_students*self.total_amount
self.grand_total_in_words = money_in_words(self.grand_total)
+ @frappe.whitelist()
def create_fees(self):
self.db_set("fee_creation_status", "In Process")
frappe.publish_realtime("fee_schedule_progress",
diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment.py b/erpnext/education/doctype/program_enrollment/program_enrollment.py
index d18c0f9..b282bab 100644
--- a/erpnext/education/doctype/program_enrollment/program_enrollment.py
+++ b/erpnext/education/doctype/program_enrollment/program_enrollment.py
@@ -91,6 +91,8 @@
(fee, fee) for fee in fee_list]
msgprint(_("Fee Records Created - {0}").format(comma_and(fee_list)))
+
+ @frappe.whitelist()
def get_courses(self):
return frappe.db.sql('''select course from `tabProgram Course` where parent = %s and required = 1''', (self.program), as_dict=1)
diff --git a/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py b/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py
index 8180102..5833b67 100644
--- a/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py
+++ b/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py
@@ -14,6 +14,7 @@
academic_term_reqd = cint(frappe.db.get_single_value('Education Settings', 'academic_term_reqd'))
self.set_onload("academic_term_reqd", academic_term_reqd)
+ @frappe.whitelist()
def get_students(self):
students = []
if not self.get_students_from:
@@ -49,6 +50,7 @@
else:
frappe.throw(_("No students Found"))
+ @frappe.whitelist()
def enroll_students(self):
total = len(self.students)
for i, stud in enumerate(self.students):
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..2dc0f63 100644
--- a/erpnext/education/doctype/student/student.py
+++ b/erpnext/education/doctype/student/student.py
@@ -114,7 +114,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/doctype/student_attendance/student_attendance.json b/erpnext/education/doctype/student_attendance/student_attendance.json
index 55384b9..e6e46d1 100644
--- a/erpnext/education/doctype/student_attendance/student_attendance.json
+++ b/erpnext/education/doctype/student_attendance/student_attendance.json
@@ -10,6 +10,7 @@
"naming_series",
"student",
"student_name",
+ "student_mobile_number",
"course_schedule",
"student_group",
"column_break_3",
@@ -93,11 +94,19 @@
"options": "Student Attendance",
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "fetch_from": "student.student_mobile_number",
+ "fieldname": "student_mobile_number",
+ "fieldtype": "Read Only",
+ "label": "Student Mobile Number",
+ "options": "Phone"
}
],
+ "index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-07-08 13:55:42.580181",
+ "modified": "2021-03-24 00:02:11.005895",
"modified_by": "Administrator",
"module": "Education",
"name": "Student Attendance",
diff --git a/erpnext/education/doctype/student_group_creation_tool/student_group_creation_tool.py b/erpnext/education/doctype/student_group_creation_tool/student_group_creation_tool.py
index d7645e3..dc8667e 100644
--- a/erpnext/education/doctype/student_group_creation_tool/student_group_creation_tool.py
+++ b/erpnext/education/doctype/student_group_creation_tool/student_group_creation_tool.py
@@ -9,6 +9,7 @@
from erpnext.education.doctype.student_group.student_group import get_students
class StudentGroupCreationTool(Document):
+ @frappe.whitelist()
def get_courses(self):
group_list = []
@@ -42,6 +43,7 @@
return group_list
+ @frappe.whitelist()
def create_student_groups(self):
if not self.courses:
frappe.throw(_("""No Student Groups created."""))
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/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/mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py
index b571802..fdfaa1b 100644
--- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py
@@ -59,9 +59,10 @@
request_amounts.append(amount)
else:
request_amounts = [request_amount]
-
+
return request_amounts
+ @frappe.whitelist()
def get_account_balance_info(self):
payload = dict(
reference_doctype="Mpesa Settings",
@@ -198,7 +199,7 @@
completed_mpesa_receipt = fetch_param_value(item_response, "MpesaReceiptNumber", "Name")
completed_payments.append(completed_amount)
mpesa_receipts.append(completed_mpesa_receipt)
-
+
return mpesa_receipts, completed_payments
def get_account_balance(request_payload):
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
index 21f6fee..16c6573 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
@@ -15,6 +15,7 @@
class PlaidSettings(Document):
@staticmethod
+ @frappe.whitelist()
def get_link_token():
plaid = PlaidConnector()
return plaid.get_link_token()
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py
index 3c90637..e2243ea 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py
@@ -23,14 +23,9 @@
doc.cancel()
doc.delete()
- for ba in frappe.get_all("Bank Account"):
- frappe.get_doc("Bank Account", ba.name).delete()
-
- for at in frappe.get_all("Bank Account Type"):
- frappe.get_doc("Bank Account Type", at.name).delete()
-
- for ast in frappe.get_all("Bank Account Subtype"):
- frappe.get_doc("Bank Account Subtype", ast.name).delete()
+ for doctype in ("Bank Account", "Bank Account Type", "Bank Account Subtype"):
+ for d in frappe.get_all(doctype):
+ frappe.delete_doc(doctype, d.name, force=True)
def test_plaid_disabled(self):
frappe.db.set_value("Plaid Settings", None, "enabled", 0)
diff --git a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py
index 96a533e..866ea66 100644
--- a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py
+++ b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py
@@ -54,6 +54,7 @@
self.authorization_url = self.oauth.authorization_url(self.authorization_endpoint)[0]
+ @frappe.whitelist()
def migrate(self):
frappe.enqueue_doc("QuickBooks Migrator", "QuickBooks Migrator", "_migrate", queue="long")
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/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
index 462685f..907a223 100644
--- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
+++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
@@ -594,18 +594,22 @@
frappe.db.set_value("Price List", "Tally Price List", "enabled", 0)
frappe.flags.in_migrate = False
+ @frappe.whitelist()
def process_master_data(self):
self.set_status("Processing Master Data")
frappe.enqueue_doc(self.doctype, self.name, "_process_master_data", queue="long", timeout=3600)
+ @frappe.whitelist()
def import_master_data(self):
self.set_status("Importing Master Data")
frappe.enqueue_doc(self.doctype, self.name, "_import_master_data", queue="long", timeout=3600)
+ @frappe.whitelist()
def process_day_book_data(self):
self.set_status("Processing Day Book Data")
frappe.enqueue_doc(self.doctype, self.name, "_process_day_book_data", queue="long", timeout=3600)
+ @frappe.whitelist()
def import_day_book_data(self):
self.set_status("Importing Day Book Data")
frappe.enqueue_doc(self.doctype, self.name, "_import_day_book_data", queue="long", timeout=3600)
diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py
index 325c209..cbf89ee 100644
--- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py
+++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py
@@ -54,6 +54,7 @@
def set_title(self):
self.title = _('{0} - {1}').format(self.patient_name or self.patient, self.procedure_template)[:100]
+ @frappe.whitelist()
def complete_procedure(self):
if self.consume_stock and self.items:
stock_entry = make_stock_entry(self)
@@ -96,6 +97,7 @@
if self.consume_stock and self.items:
return stock_entry
+ @frappe.whitelist()
def start_procedure(self):
allow_start = self.set_actual_qty()
if allow_start:
@@ -116,6 +118,7 @@
return allow_start
+ @frappe.whitelist()
def make_material_receipt(self, submit=False):
stock_entry = frappe.new_doc('Stock Entry')
diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py
index e731908..3a299ed 100644
--- a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py
+++ b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py
@@ -14,6 +14,7 @@
def validate(self):
self.validate_medication_orders()
+ @frappe.whitelist()
def get_medication_orders(self):
# pull inpatient medication orders based on selected filters
orders = get_pending_medication_orders(self)
diff --git a/erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.py b/erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.py
index 33cbbec..b379e98 100644
--- a/erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.py
+++ b/erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.py
@@ -57,6 +57,7 @@
self.db_set('status', status)
+ @frappe.whitelist()
def add_order_entries(self, order):
if order.get('drug_code'):
dosage = frappe.get_doc('Prescription Dosage', order.get('dosage'))
diff --git a/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py b/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py
index a21caca..21776d2 100644
--- a/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py
+++ b/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py
@@ -81,15 +81,8 @@
self.ip_record.reload()
discharge_patient(self.ip_record)
- for entry in frappe.get_all('Inpatient Medication Entry'):
- doc = frappe.get_doc('Inpatient Medication Entry', entry.name)
- doc.cancel()
- doc.delete()
-
- for entry in frappe.get_all('Inpatient Medication Order'):
- doc = frappe.get_doc('Inpatient Medication Order', entry.name)
- doc.cancel()
- doc.delete()
+ for doctype in ["Inpatient Medication Entry", "Inpatient Medication Order"]:
+ frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype))
def create_dosage_form():
if not frappe.db.exists('Dosage Form', 'Tablet'):
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 5ced845..0e1c2ba 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
@@ -53,7 +53,7 @@
"discharge_ordered_date",
"discharge_practitioner",
"discharge_encounter",
- "discharge_date",
+ "discharge_datetime",
"cb_discharge",
"discharge_instructions",
"followup_date",
@@ -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",
@@ -404,14 +404,15 @@
"permlevel": 1
},
{
- "fieldname": "discharge_date",
- "fieldtype": "Date",
+ "fieldname": "discharge_datetime",
+ "fieldtype": "Datetime",
"label": "Discharge Date",
- "read_only": 1
+ "permlevel": 2
}
],
+ "index_web_pages_for_search": 1,
"links": [],
- "modified": "2020-05-21 02:26:22.144575",
+ "modified": "2021-03-18 15:59:17.318988",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Inpatient Record",
@@ -464,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/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
index 88d7f0b..f4d1eaf 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
@@ -53,12 +53,15 @@
+ """ <b><a href="/app/Form/Inpatient Record/{0}">{0}</a></b>""".format(ip_record[0].name))
frappe.throw(msg)
+ @frappe.whitelist()
def admit(self, service_unit, check_in, expected_discharge=None):
admit_patient(self, service_unit, check_in, expected_discharge)
+ @frappe.whitelist()
def discharge(self):
discharge_patient(self)
+ @frappe.whitelist()
def transfer(self, service_unit, check_in, leave_from):
if leave_from:
patient_leave_service_unit(self, check_in, leave_from)
@@ -151,7 +154,7 @@
def discharge_patient(inpatient_record):
validate_inpatient_invoicing(inpatient_record)
- inpatient_record.discharge_date = today()
+ inpatient_record.discharge_datetime = now_datetime()
inpatient_record.status = "Discharged"
inpatient_record.save(ignore_permissions = True)
diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py
index 8603f97..789d452 100644
--- a/erpnext/healthcare/doctype/patient/patient.py
+++ b/erpnext/healthcare/doctype/patient/patient.py
@@ -111,6 +111,7 @@
age_str = str(age.years) + ' ' + _("Years(s)") + ' ' + str(age.months) + ' ' + _("Month(s)") + ' ' + str(age.days) + ' ' + _("Day(s)")
return age_str
+ @frappe.whitelist()
def invoice_patient_registration(self):
if frappe.db.get_single_value('Healthcare Settings', 'registration_fee'):
company = frappe.defaults.get_user_default('company')
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
index 1f76cd6..cdd4ad3 100755
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
@@ -113,6 +113,7 @@
if fee_validity:
frappe.msgprint(_('{0} has fee validity till {1}').format(self.patient, fee_validity.valid_till))
+ @frappe.whitelist()
def get_therapy_types(self):
if not self.therapy_plan:
return
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/patient_history_settings/patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py
index 2e8c994..887d58a 100644
--- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py
+++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py
@@ -34,6 +34,7 @@
frappe.throw(_('Row #{0}: Field {1} in Document Type {2} is not a Date / Datetime field.').format(
entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type)))
+ @frappe.whitelist()
def get_doctype_fields(self, document_type, fields):
multicheck_fields = []
doc_fields = frappe.get_meta(document_type).fields
@@ -49,6 +50,7 @@
return multicheck_fields
+ @frappe.whitelist()
def get_date_field_for_dt(self, document_type):
meta = frappe.get_meta(document_type)
date_fields = meta.get('fields', {
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_plan/therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
index ac01c60..e209660 100644
--- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
+++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
@@ -33,6 +33,7 @@
self.db_set('total_sessions', total_sessions)
self.db_set('total_sessions_completed', total_sessions_completed)
+ @frappe.whitelist()
def set_therapy_details_from_template(self):
# Add therapy types in the child table
self.set('therapy_plan_details', [])
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 3f145dc..5fa419f 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -196,6 +196,10 @@
{"name": "call-disconnect", "src": "/assets/erpnext/sounds/call-disconnect.mp3", "volume": 0.2},
]
+has_upload_permission = {
+ "Employee": "erpnext.hr.doctype.employee.employee.has_upload_permission"
+}
+
has_website_permission = {
"Sales Order": "erpnext.controllers.website_list_for_contact.has_website_permission",
"Quotation": "erpnext.controllers.website_list_for_contact.has_website_permission",
@@ -257,7 +261,11 @@
"erpnext.regional.italy.utils.sales_invoice_on_cancel",
"erpnext.erpnext_integrations.taxjar_integration.delete_transaction"
],
- "on_trash": "erpnext.regional.check_deletion_permission"
+ "on_trash": "erpnext.regional.check_deletion_permission",
+ "validate": [
+ "erpnext.regional.india.utils.validate_document_name",
+ "erpnext.regional.india.utils.update_taxable_values"
+ ]
},
"Purchase Invoice": {
"validate": [
@@ -279,9 +287,6 @@
('Sales Invoice', 'Sales Order', 'Delivery Note', 'Purchase Invoice', 'Purchase Order', 'Purchase Receipt'): {
'validate': ['erpnext.regional.india.utils.set_place_of_supply']
},
- ('Sales Invoice', 'Purchase Invoice'): {
- 'validate': ['erpnext.regional.india.utils.validate_document_name']
- },
"Contact": {
"on_trash": "erpnext.support.doctype.issue.issue.update_issue",
"after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations",
@@ -303,6 +308,8 @@
"Inpatient Medication Entry"
]
+after_migrate = ["erpnext.setup.install.update_select_perm_after_install"]
+
scheduler_events = {
"cron": {
"0/30 * * * *": [
@@ -325,6 +332,7 @@
"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
"erpnext.support.doctype.issue.issue.set_service_level_agreement_variance",
"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders",
+ "erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries"
],
"daily": [
"erpnext.stock.reorder_item.reorder_item",
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 7a9727f..a6fe429 100644
--- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
+++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
@@ -5,7 +5,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import date_diff, add_days, getdate, cint
+from frappe.utils import date_diff, add_days, getdate, cint, format_date
from frappe.model.document import Document
from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, \
get_holidays_for_employee, create_additional_leave_ledger_entry
@@ -40,7 +40,12 @@
def validate_holidays(self):
holidays = get_holidays_for_employee(self.employee, self.work_from_date, self.work_end_date)
if len(holidays) < date_diff(self.work_end_date, self.work_from_date) + 1:
- frappe.throw(_("Compensatory leave request days not in valid holidays"))
+ if date_diff(self.work_end_date, self.work_from_date):
+ msg = _("The days between {0} to {1} are not valid holidays.").format(frappe.bold(format_date(self.work_from_date)), frappe.bold(format_date(self.work_end_date)))
+ else:
+ msg = _("{0} is not a holiday.").format(frappe.bold(format_date(self.work_from_date)))
+
+ frappe.throw(msg)
def on_submit(self):
company = frappe.db.get_value("Employee", self.employee, "company")
@@ -61,9 +66,9 @@
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(self.work_from_date, self.work_end_date))
+ 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)))
def on_cancel(self):
if self.leave_allocation:
@@ -119,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/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/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index d0e7d05..ed7d588 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -8,7 +8,7 @@
from frappe.model.naming import set_name_by_naming_series
from frappe import throw, _, scrub
from frappe.permissions import add_user_permission, remove_user_permission, \
- set_user_permission_if_allowed, has_permission
+ set_user_permission_if_allowed, has_permission, get_doc_permissions
from frappe.model.document import Document
from erpnext.utilities.transaction_base import delete_events
from frappe.utils.nestedset import NestedSet
@@ -66,7 +66,7 @@
def validate_user_details(self):
data = frappe.db.get_value('User',
self.user_id, ['enabled', 'user_image'], as_dict=1)
- if data.get("user_image"):
+ if data.get("user_image") and self.image == '':
self.image = data.get("user_image")
self.validate_for_enabled_user_id(data.get("enabled", 0))
self.validate_duplicate_user_id()
@@ -80,6 +80,7 @@
self.update_user()
self.update_user_permissions()
self.reset_employee_emails_cache()
+ self.update_approver_role()
def update_user_permissions(self):
if not self.create_user_permission: return
@@ -145,6 +146,17 @@
user.save()
+ def update_approver_role(self):
+ if self.leave_approver:
+ user = frappe.get_doc("User", self.leave_approver)
+ user.flags.ignore_permissions = True
+ user.add_roles("Leave Approver")
+
+ if self.expense_approver:
+ user = frappe.get_doc("User", self.expense_approver)
+ user.flags.ignore_permissions = True
+ user.add_roles("Expense Approver")
+
def validate_date(self):
if self.date_of_birth and getdate(self.date_of_birth) > getdate(today()):
throw(_("Date of Birth cannot be greater than today."))
@@ -501,3 +513,10 @@
'allow': 'Employee',
'for_value': employee_name
})
+
+def has_upload_permission(doc, ptype='read', user=None):
+ if not user:
+ user = frappe.session.user
+ if get_doc_permissions(doc, user=user, ptype=ptype).get(ptype):
+ return True
+ return doc.user_id == user
\ No newline at end of file
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 cf6b540..ea25aa7 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.json
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.json
@@ -181,7 +181,6 @@
"read_only": 1
},
{
- "default": "Company:company:default_currency",
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
"fieldname": "currency",
"fieldtype": "Link",
@@ -201,7 +200,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-11-25 12:01:55.980721",
+ "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/selling/doctype/lead_source/__init__.py b/erpnext/hr/doctype/employee_referral/__init__.py
similarity index 100%
copy from erpnext/selling/doctype/lead_source/__init__.py
copy to 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..3ae73a9
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/employee_referral.json
@@ -0,0 +1,305 @@
+{
+ "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",
+ "column_break_6",
+ "date",
+ "status",
+ "for_designation",
+ "referral_details_section",
+ "email",
+ "contact_no",
+ "resume_link",
+ "column_break_12",
+ "current_employer",
+ "current_job_title",
+ "resume",
+ "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
+ },
+ {
+ "fieldname": "referral_details_section",
+ "fieldtype": "Section Break",
+ "label": "Referral Details"
+ },
+ {
+ "fieldname": "column_break_12",
+ "fieldtype": "Column Break"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2021-05-04 17:03:26.134560",
+ "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.py b/erpnext/hr/doctype/expense_claim/expense_claim.py
index bf893d5..5010fc3 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.py
@@ -6,7 +6,7 @@
from frappe import _
from frappe.utils import get_fullname, flt, cstr, get_link_to_form
from frappe.model.document import Document
-from erpnext.hr.utils import set_employee_name
+from erpnext.hr.utils import set_employee_name, share_doc_with_approver
from erpnext.accounts.party import get_party_account
from erpnext.accounts.general_ledger import make_gl_entries
from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
@@ -53,6 +53,9 @@
elif self.docstatus == 1 and self.approval_status == 'Rejected':
self.status = 'Rejected'
+ def on_update(self):
+ share_doc_with_approver(self, self.expense_approver)
+
def set_payable_account(self):
if not self.payable_account and not self.is_paid:
self.payable_account = frappe.get_cached_value('Company', self.company, 'default_expense_claim_payable_account')
@@ -211,6 +214,7 @@
self.total_claimed_amount += flt(d.amount)
self.total_sanctioned_amount += flt(d.sanctioned_amount)
+ @frappe.whitelist()
def calculate_taxes(self):
self.total_taxes_and_charges = 0
for tax in self.taxes:
diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
index f9e3a44..3f22ca2 100644
--- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
@@ -95,12 +95,12 @@
def test_rejected_expense_claim(self):
payable_account = get_payable_account(company_name)
expense_claim = frappe.get_doc({
- "doctype": "Expense Claim",
- "employee": "_T-Employee-00001",
- "payable_account": payable_account,
- "approval_status": "Rejected",
- "expenses":
- [{ "expense_type": "Travel", "default_account": "Travel Expenses - _TC4", "amount": 300, "sanctioned_amount": 200 }]
+ "doctype": "Expense Claim",
+ "employee": "_T-Employee-00001",
+ "payable_account": payable_account,
+ "approval_status": "Rejected",
+ "expenses":
+ [{ "expense_type": "Travel", "default_account": "Travel Expenses - _TC4", "amount": 300, "sanctioned_amount": 200 }]
})
expense_claim.submit()
@@ -110,6 +110,34 @@
gl_entry = frappe.get_all('GL Entry', {'voucher_type': 'Expense Claim', 'voucher_no': expense_claim.name})
self.assertEquals(len(gl_entry), 0)
+ def test_expense_approver_perms(self):
+ user = "test_approver_perm_emp@example.com"
+ make_employee(user, "_Test Company")
+
+ # check doc shared
+ payable_account = get_payable_account("_Test Company")
+ expense_claim = make_expense_claim(payable_account, 300, 200, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
+ expense_claim.expense_approver = user
+ expense_claim.save()
+ self.assertTrue(expense_claim.name in frappe.share.get_shared("Expense Claim", user))
+
+ # check shared doc revoked
+ expense_claim.reload()
+ expense_claim.expense_approver = "test@example.com"
+ expense_claim.save()
+ self.assertTrue(expense_claim.name not in frappe.share.get_shared("Expense Claim", user))
+
+ expense_claim.reload()
+ expense_claim.expense_approver = user
+ expense_claim.save()
+
+ frappe.set_user(user)
+ expense_claim.reload()
+ expense_claim.status = "Approved"
+ expense_claim.submit()
+ frappe.set_user("Administrator")
+
+
def get_payable_account(company):
return frappe.get_cached_value('Company', company, 'default_payable_account')
@@ -133,21 +161,21 @@
currency, cost_center = frappe.db.get_value('Company', company, ['default_currency', 'cost_center'])
expense_claim = {
- "doctype": "Expense Claim",
- "employee": employee,
- "payable_account": payable_account,
- "approval_status": "Approved",
- "company": company,
- 'currency': currency,
- "expenses": [{
+ "doctype": "Expense Claim",
+ "employee": employee,
+ "payable_account": payable_account,
+ "approval_status": "Approved",
+ "company": company,
+ "currency": currency,
+ "expenses": [{
"expense_type": "Travel",
"default_account": account,
"currency": currency,
"amount": amount,
"sanctioned_amount": sanctioned_amount,
"cost_center": cost_center
- }]
- }
+ }]
+ }
if taxes:
expense_claim.update(taxes)
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..3db6c23 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",
@@ -143,13 +144,18 @@
"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-04-26 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/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/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
index 69d605d..11302ca 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
@@ -99,6 +99,7 @@
.format(formatdate(future_allocation[0].from_date), future_allocation[0].name),
BackDatedAllocationError)
+ @frappe.whitelist()
def set_total_leaves_allocated(self):
self.unused_leaves = get_carry_forwarded_leaves(self.employee,
self.leave_type, self.from_date, self.carry_forward)
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 350cead..0bf551e 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -6,7 +6,7 @@
from frappe import _
from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, \
comma_or, get_fullname, add_days, nowdate, get_datetime_str
-from erpnext.hr.utils import set_employee_name, get_leave_period
+from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver
from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import daterange
@@ -43,6 +43,8 @@
if frappe.db.get_single_value("HR Settings", "send_leave_notification"):
self.notify_leave_approver()
+ share_doc_with_approver(self, self.leave_approver)
+
def on_submit(self):
if self.status == "Open":
frappe.throw(_("Only Leave Applications with status 'Approved' and 'Rejected' can be submitted"))
@@ -417,6 +419,7 @@
))
create_leave_ledger_entry(self, args, submit)
+
def get_allocation_expiry(employee, leave_type, to_date, from_date):
''' Returns expiry of carry forward allocation in leave ledger entry '''
expiry = frappe.get_all("Leave Ledger Entry",
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index b335c48..b54c971 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -11,6 +11,7 @@
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees
+from erpnext.hr.doctype.employee.test_employee import make_employee
test_dependencies = ["Leave Allocation", "Leave Block List", "Employee"]
@@ -56,6 +57,7 @@
@classmethod
def setUpClass(cls):
set_leave_approver()
+ frappe.db.sql("delete from tabAttendance where employee='_T-Employee-00001'")
def tearDown(self):
frappe.set_user("Administrator")
@@ -230,8 +232,9 @@
def test_optional_leave(self):
leave_period = get_leave_period()
today = nowdate()
- from datetime import date
holiday_list = 'Test Holiday List for Optional Holiday'
+ optional_leave_date = add_days(today, 7)
+
if not frappe.db.exists('Holiday List', holiday_list):
frappe.get_doc(dict(
doctype = 'Holiday List',
@@ -239,7 +242,7 @@
from_date = add_months(today, -6),
to_date = add_months(today, 6),
holidays = [
- dict(holiday_date = today, description = 'Test')
+ dict(holiday_date = optional_leave_date, description = 'Test')
]
)).insert()
employee = get_employee()
@@ -255,7 +258,7 @@
allocate_leaves(employee, leave_period, leave_type, 10)
- date = add_days(today, - 1)
+ date = add_days(today, 6)
leave_application = frappe.get_doc(dict(
doctype = 'Leave Application',
@@ -270,14 +273,14 @@
# can only apply on optional holidays
self.assertRaises(NotAnOptionalHoliday, leave_application.insert)
- leave_application.from_date = today
- leave_application.to_date = today
+ leave_application.from_date = optional_leave_date
+ leave_application.to_date = optional_leave_date
leave_application.status = "Approved"
leave_application.insert()
leave_application.submit()
# check leave balance is reduced
- self.assertEqual(get_leave_balance_on(employee.name, leave_type, today), 9)
+ self.assertEqual(get_leave_balance_on(employee.name, leave_type, optional_leave_date), 9)
def test_leaves_allowed(self):
employee = get_employee()
@@ -341,7 +344,7 @@
to_date = add_days(date, 4),
company = "_Test Company",
docstatus = 1,
- status = "Approved"
+ status = "Approved"
))
self.assertRaises(frappe.ValidationError, leave_application.insert)
@@ -363,7 +366,7 @@
to_date = add_days(date, 4),
company = "_Test Company",
docstatus = 1,
- status = "Approved"
+ status = "Approved"
))
self.assertTrue(leave_application.insert())
@@ -393,7 +396,7 @@
to_date = add_days(date, 4),
company = "_Test Company",
docstatus = 1,
- status = "Approved"
+ status = "Approved"
))
self.assertRaises(frappe.ValidationError, leave_application.insert)
@@ -508,7 +511,7 @@
description = "_Test Reason",
company = "_Test Company",
docstatus = 1,
- status = "Approved"
+ status = "Approved"
))
leave_application.submit()
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_application.name))
@@ -540,7 +543,7 @@
description = "_Test Reason",
company = "_Test Company",
docstatus = 1,
- status = "Approved"
+ status = "Approved"
))
leave_application.submit()
@@ -565,6 +568,48 @@
self.assertEquals(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()
+ user = "test_approver_perm_emp@example.com"
+ make_employee(user, "_Test Company")
+
+ # set approver for employee
+ employee.reload()
+ employee.leave_approver = user
+ employee.save()
+ self.assertTrue("Leave Approver" in frappe.get_roles(user))
+
+ make_allocation_record(employee.name)
+
+ application = self.get_application(_test_records[0])
+ application.from_date = '2018-01-01'
+ application.to_date = '2018-01-03'
+ application.leave_approver = user
+ application.insert()
+ self.assertTrue(application.name in frappe.share.get_shared("Leave Application", user))
+
+ # check shared doc revoked
+ application.reload()
+ application.leave_approver = "test@example.com"
+ application.save()
+ self.assertTrue(application.name not in frappe.share.get_shared("Leave Application", user))
+
+ application.reload()
+ application.leave_approver = user
+ application.save()
+
+ frappe.set_user(user)
+ application.reload()
+ application.status = "Approved"
+ application.submit()
+
+ # unset leave approver
+ frappe.set_user("Administrator")
+ employee.reload()
+ employee.leave_approver = ""
+ employee.save()
+
+
def create_carry_forwarded_allocation(employee, leave_type):
# initial leave allocation
leave_allocation = create_leave_allocation(
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 83eeae3..1f6c03f 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.json
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.json
@@ -130,7 +130,6 @@
"read_only": 1
},
{
- "default": "Company:company:default_currency",
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
"fieldname": "currency",
"fieldtype": "Link",
@@ -155,7 +154,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-11-25 11:56:06.777241",
+ "modified": "2021-03-31 22:32:55.492327",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Encashment",
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
index 4c1a465..e041b7f 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
@@ -63,6 +63,7 @@
frappe.db.get_value('Leave Allocation', self.leave_allocation, 'total_leaves_encashed') - self.encashable_days)
self.create_leave_ledger_entry(submit=False)
+ @frappe.whitelist()
def get_leave_details_for_encashment(self):
salary_structure = get_assigned_salary_structure(self.employee, self.encashment_date or getdate(nowdate()))
if not salary_structure:
diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
index 63559c4..cf13036 100644
--- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
+++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
@@ -34,8 +34,8 @@
""", (ledger.employee, ledger.leave_type, ledger.from_date, ledger.to_date))
if leave_application_records:
- frappe.throw(_("Leave allocation %s is linked with leave application %s"
- % (ledger.transaction_name, ', '.join(leave_application_records))))
+ frappe.throw(_("Leave allocation {0} is linked with the Leave Application {1}").format(
+ ledger.transaction_name, ', '.join(leave_application_records)))
def create_leave_ledger_entry(ref_doc, args, submit=True):
ledger = frappe._dict(
@@ -52,7 +52,9 @@
ledger.update(args)
if submit:
- frappe.get_doc(ledger).submit()
+ doc = frappe.get_doc(ledger)
+ doc.flags.ignore_permissions = 1
+ doc.submit()
else:
delete_ledger_entry(ledger)
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 4064c56..462b81d 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
@@ -36,6 +36,7 @@
frappe.throw(_("Leave Policy: {0} already assigned for Employee {1} for period {2} to {3}")
.format(bold(self.leave_policy), bold(self.employee), bold(formatdate(self.effective_from)), bold(formatdate(self.effective_to))))
+ @frappe.whitelist()
def grant_leave_alloc_for_employee(self):
if self.leaves_allocated:
frappe.throw(_("Leave already have been assigned for this Leave Policy Assignment"))
diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py
index 473193d..177c45e 100644
--- a/erpnext/hr/doctype/shift_request/shift_request.py
+++ b/erpnext/hr/doctype/shift_request/shift_request.py
@@ -7,6 +7,7 @@
from frappe import _
from frappe.model.document import Document
from frappe.utils import formatdate, getdate
+from erpnext.hr.utils import share_doc_with_approver
class OverlapError(frappe.ValidationError): pass
@@ -17,6 +18,9 @@
self.validate_approver()
self.validate_default_shift()
+ def on_update(self):
+ share_doc_with_approver(self, self.approver)
+
def on_submit(self):
if self.status not in ["Approved", "Rejected"]:
frappe.throw(_("Only Shift Request with status 'Approved' and 'Rejected' can be submitted"))
@@ -29,6 +33,7 @@
if self.to_date:
assignment_doc.end_date = self.to_date
assignment_doc.shift_request = self.name
+ assignment_doc.flags.ignore_permissions = 1
assignment_doc.insert()
assignment_doc.submit()
diff --git a/erpnext/hr/doctype/shift_request/test_shift_request.py b/erpnext/hr/doctype/shift_request/test_shift_request.py
index 230bb2b..9c0d8e3 100644
--- a/erpnext/hr/doctype/shift_request/test_shift_request.py
+++ b/erpnext/hr/doctype/shift_request/test_shift_request.py
@@ -6,6 +6,7 @@
import frappe
import unittest
from frappe.utils import nowdate, add_days
+from erpnext.hr.doctype.employee.test_employee import make_employee
test_dependencies = ["Shift Type"]
@@ -19,19 +20,8 @@
set_shift_approver(department)
approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0]
- shift_request = frappe.get_doc({
- "doctype": "Shift Request",
- "shift_type": "Day Shift",
- "company": "_Test Company",
- "employee": "_T-Employee-00001",
- "employee_name": "_Test Employee",
- "from_date": nowdate(),
- "to_date": add_days(nowdate(), 10),
- "approver": approver,
- "status": "Approved"
- })
- shift_request.insert()
- shift_request.submit()
+ shift_request = make_shift_request(approver)
+
shift_assignments = frappe.db.sql('''
SELECT shift_request, employee
FROM `tabShift Assignment`
@@ -44,8 +34,65 @@
shift_assignment_doc = frappe.get_doc("Shift Assignment", {"shift_request": d.get('shift_request')})
self.assertEqual(shift_assignment_doc.docstatus, 2)
+ def test_shift_request_approver_perms(self):
+ employee = frappe.get_doc("Employee", "_T-Employee-00001")
+ user = "test_approver_perm_emp@example.com"
+ make_employee(user, "_Test Company")
+
+ # set approver for employee
+ employee.reload()
+ employee.shift_request_approver = user
+ employee.save()
+
+ shift_request = make_shift_request(user, do_not_submit=True)
+ self.assertTrue(shift_request.name in frappe.share.get_shared("Shift Request", user))
+
+ # check shared doc revoked
+ shift_request.reload()
+ department = frappe.get_value("Employee", "_T-Employee-00001", "department")
+ set_shift_approver(department)
+ department_approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0]
+ shift_request.approver = department_approver
+ shift_request.save()
+ self.assertTrue(shift_request.name not in frappe.share.get_shared("Shift Request", user))
+
+ shift_request.reload()
+ shift_request.approver = user
+ shift_request.save()
+
+ frappe.set_user(user)
+ shift_request.reload()
+ shift_request.status = "Approved"
+ shift_request.submit()
+
+ # unset approver
+ frappe.set_user("Administrator")
+ employee.reload()
+ employee.shift_request_approver = ""
+ employee.save()
+
+
def set_shift_approver(department):
department_doc = frappe.get_doc("Department", department)
department_doc.append('shift_request_approver',{'approver': "test1@example.com"})
department_doc.save()
department_doc.reload()
+
+def make_shift_request(approver, do_not_submit=0):
+ shift_request = frappe.get_doc({
+ "doctype": "Shift Request",
+ "shift_type": "Day Shift",
+ "company": "_Test Company",
+ "employee": "_T-Employee-00001",
+ "employee_name": "_Test Employee",
+ "from_date": nowdate(),
+ "to_date": add_days(nowdate(), 10),
+ "approver": approver,
+ "status": "Approved"
+ }).insert()
+
+ if do_not_submit:
+ return shift_request
+
+ shift_request.submit()
+ return shift_request
\ No newline at end of file
diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py
index 054e7e3..d5fdda8 100644
--- a/erpnext/hr/doctype/shift_type/shift_type.py
+++ b/erpnext/hr/doctype/shift_type/shift_type.py
@@ -15,6 +15,7 @@
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
class ShiftType(Document):
+ @frappe.whitelist()
def process_auto_attendance(self):
if not cint(self.enable_auto_attendance) or not self.process_attendance_after or not self.last_sync_of_checkin:
return
diff --git a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py
index 92715d3..e86fa2b 100644
--- a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py
+++ b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py
@@ -47,7 +47,7 @@
user = frappe.session.user
conditions = get_conditions(filters)
- active_employees = frappe.get_all("Employee",
+ active_employees = frappe.get_list("Employee",
filters=conditions,
fields=["name", "employee_name", "department", "user_id", "leave_approver"])
@@ -72,4 +72,4 @@
data.append(row)
- return data
\ No newline at end of file
+ return data
diff --git a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
index c5929c6..bcb0ee4 100644
--- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
+++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
@@ -57,10 +57,10 @@
data = []
+ leave_types = frappe.db.get_list("Leave Type")
leave_list = None
if filters.summarized_view:
- leave_types = frappe.db.sql("""select name from `tabLeave Type`""", as_list=True)
- leave_list = [d[0] + ":Float:120" for d in leave_types]
+ leave_list = [d.name + ":Float:120" for d in leave_types]
columns.extend(leave_list)
columns.extend([_("Total Late Entries") + ":Float:120", _("Total Early Exits") + ":Float:120"])
@@ -72,11 +72,11 @@
if (att_map_set & emp_map_set):
parameter_row = ["<b>"+ parameter + "</b>"] + ['' for day in range(filters["total_days_in_month"] + 2)]
data.append(parameter_row)
- record, emp_att_data = add_data(emp_map[parameter], att_map, filters, holiday_map, conditions, default_holiday_list, leave_list=leave_list)
+ record, emp_att_data = add_data(emp_map[parameter], att_map, filters, holiday_map, conditions, default_holiday_list, leave_types=leave_types)
emp_att_map.update(emp_att_data)
data += record
else:
- record, emp_att_map = add_data(emp_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_list=leave_list)
+ record, emp_att_map = add_data(emp_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_types=leave_types)
data += record
chart_data = get_chart_data(emp_att_map, days)
@@ -126,7 +126,7 @@
return chart
-def add_data(employee_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_list=None):
+def add_data(employee_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_types=None):
record = []
emp_att_map = {}
@@ -204,9 +204,9 @@
else:
leaves[d.leave_type] = d.count
- for d in leave_list:
- if d in leaves:
- row.append(leaves[d])
+ for d in leave_types:
+ if d.name in leaves:
+ row.append(leaves[d.name])
else:
row.append("0.0")
diff --git a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py
index e961114..303c829 100644
--- a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py
+++ b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py
@@ -31,7 +31,7 @@
"fieldtype": "Link",
"fieldname": "job_opening",
"options": "Job Opening",
- "width": 100
+ "width": 105
},
{
"label": _("Job Applicant"),
@@ -44,13 +44,13 @@
"label": _("Applicant name"),
"fieldtype": "data",
"fieldname": "applicant_name",
- "width": 120
+ "width": 130
},
{
"label": _("Application Status"),
"fieldtype": "Data",
"fieldname": "application_status",
- "width": 100
+ "width": 150
},
{
"label": _("Job Offer"),
@@ -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 0c4c1ca..2540b3d 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()
@@ -504,3 +506,25 @@
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):
+ frappe.share.add(doc.doctype, doc.name, user, submit=1,
+ flags={"ignore_share_permission": True})
+
+ frappe.msgprint(_("Shared with the user {0} with {1} access").format(
+ user, frappe.bold("submit"), alert=True))
+
+ # remove shared doc if approver changes
+ doc_before_save = doc.get_doc_before_save()
+ if doc_before_save:
+ approvers = {
+ "Leave Application": "leave_approver",
+ "Expense Claim": "expense_approver",
+ "Shift Request": "approver"
+ }
+
+ approver = approvers.get(doc.doctype)
+ if doc_before_save.get(approver) != doc.get(approver):
+ frappe.share.remove(doc.doctype, doc.name, doc_before_save.get(approver))
diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json
index f650b24..c5201c2 100644
--- a/erpnext/hr/workspace/hr/hr.json
+++ b/erpnext/hr/workspace/hr/hr.json
@@ -15,6 +15,7 @@
"hide_custom": 0,
"icon": "hr",
"idx": 0,
+ "is_default": 0,
"is_standard": 1,
"label": "HR",
"links": [
@@ -227,41 +228,11 @@
"type": "Card Break"
},
{
- "dependencies": "Employee",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Leave Application",
- "link_to": "Leave Application",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "Employee",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Leave Allocation",
- "link_to": "Leave Allocation",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "Leave Type",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Leave Policy",
- "link_to": "Leave Policy",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
- "label": "Leave Period",
- "link_to": "Leave Period",
+ "label": "Holiday List",
+ "link_to": "Holiday List",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
@@ -280,8 +251,28 @@
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
- "label": "Holiday List",
- "link_to": "Holiday List",
+ "label": "Leave Period",
+ "link_to": "Leave Period",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Leave Type",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Leave Policy",
+ "link_to": "Leave Policy",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Leave Policy",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Leave Policy Assignment",
+ "link_to": "Leave Policy Assignment",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
@@ -290,8 +281,18 @@
"dependencies": "Employee",
"hidden": 0,
"is_query_report": 0,
- "label": "Compensatory Leave Request",
- "link_to": "Compensatory Leave Request",
+ "label": "Leave Application",
+ "link_to": "Leave Application",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Leave Allocation",
+ "link_to": "Leave Allocation",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
@@ -317,12 +318,12 @@
"type": "Link"
},
{
- "dependencies": "Leave Application",
+ "dependencies": "Employee",
"hidden": 0,
- "is_query_report": 1,
- "label": "Employee Leave Balance",
- "link_to": "Employee Leave Balance",
- "link_type": "Report",
+ "is_query_report": 0,
+ "label": "Compensatory Leave Request",
+ "link_to": "Compensatory Leave Request",
+ "link_type": "DocType",
"onboard": 0,
"type": "Link"
},
@@ -384,16 +385,6 @@
"type": "Link"
},
{
- "dependencies": "Attendance",
- "hidden": 0,
- "is_query_report": 1,
- "label": "Monthly Attendance Sheet",
- "link_to": "Monthly Attendance Sheet",
- "link_type": "Report",
- "onboard": 0,
- "type": "Link"
- },
- {
"hidden": 0,
"is_query_report": 0,
"label": "Expense Claims",
@@ -423,6 +414,15 @@
{
"hidden": 0,
"is_query_report": 0,
+ "label": "Travel Request",
+ "link_to": "Travel Request",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
"label": "Settings",
"onboard": 0,
"type": "Card Break"
@@ -465,6 +465,15 @@
"type": "Card Break"
},
{
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Driver",
+ "link_to": "Driver",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
@@ -512,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,
@@ -544,6 +562,24 @@
{
"hidden": 0,
"is_query_report": 0,
+ "label": "Appointment Letter",
+ "link_to": "Appointment Letter",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Appointment Letter Template",
+ "link_to": "Appointment Letter Template",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
"label": "Loans",
"onboard": 0,
"type": "Card Break"
@@ -628,33 +664,6 @@
{
"hidden": 0,
"is_query_report": 0,
- "label": "Reports",
- "onboard": 0,
- "type": "Card Break"
- },
- {
- "dependencies": "Employee",
- "hidden": 0,
- "is_query_report": 1,
- "label": "Employee Birthday",
- "link_to": "Employee Birthday",
- "link_type": "Report",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "Employee",
- "hidden": 0,
- "is_query_report": 1,
- "label": "Employees working on a holiday",
- "link_to": "Employees working on a holiday",
- "link_type": "Report",
- "onboard": 0,
- "type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
"label": "Performance",
"onboard": 0,
"type": "Card Break"
@@ -702,7 +711,74 @@
{
"hidden": 0,
"is_query_report": 0,
- "label": "Employee Tax and Benefits",
+ "label": "Key Reports",
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "Attendance",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Monthly Attendance Sheet",
+ "link_to": "Monthly Attendance Sheet",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Staffing Plan",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Recruitment Analytics",
+ "link_to": "Recruitment Analytics",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Employee Analytics",
+ "link_to": "Employee Analytics",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Employee Leave Balance",
+ "link_to": "Employee Leave Balance",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Employee Leave Balance Summary",
+ "link_to": "Employee Leave Balance Summary",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee Advance",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Employee Advance Summary",
+ "link_to": "Employee Advance Summary",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Other Reports",
"onboard": 0,
"type": "Card Break"
},
@@ -710,74 +786,44 @@
"dependencies": "Employee",
"hidden": 0,
"is_query_report": 0,
- "label": "Employee Tax Exemption Declaration",
- "link_to": "Employee Tax Exemption Declaration",
- "link_type": "DocType",
+ "label": "Employee Information",
+ "link_to": "Employee Information",
+ "link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Employee",
"hidden": 0,
- "is_query_report": 0,
- "label": "Employee Tax Exemption Proof Submission",
- "link_to": "Employee Tax Exemption Proof Submission",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "Employee, Payroll Period",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Employee Other Income",
- "link_to": "Employee Other Income",
- "link_type": "DocType",
+ "is_query_report": 1,
+ "label": "Employee Birthday",
+ "link_to": "Employee Birthday",
+ "link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Employee",
"hidden": 0,
- "is_query_report": 0,
- "label": "Employee Benefit Application",
- "link_to": "Employee Benefit Application",
- "link_type": "DocType",
+ "is_query_report": 1,
+ "label": "Employees Working on a Holiday",
+ "link_to": "Employees working on a holiday",
+ "link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
- "dependencies": "Employee",
+ "dependencies": "Daily Work Summary",
"hidden": 0,
- "is_query_report": 0,
- "label": "Employee Benefit Claim",
- "link_to": "Employee Benefit Claim",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "Employee",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Employee Tax Exemption Category",
- "link_to": "Employee Tax Exemption Category",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "Employee",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Employee Tax Exemption Sub Category",
- "link_to": "Employee Tax Exemption Sub Category",
- "link_type": "DocType",
+ "is_query_report": 1,
+ "label": "Daily Work Summary Replies",
+ "link_to": "Daily Work Summary Replies",
+ "link_type": "Report",
"onboard": 0,
"type": "Link"
}
],
- "modified": "2021-01-21 13:38:38.941001",
+ "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..230475f 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')
@@ -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 13a2094..fae6f86 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -56,25 +56,25 @@
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.assertEquals(flt(loan.total_interest_payable, 0), 21034)
+ self.assertEquals(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.assertEquals(flt(loan.total_interest_payable, 0), 22712)
+ self.assertEquals(flt(loan.total_payment, 0), 302712)
def test_loan_with_security(self):
@@ -275,6 +275,11 @@
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)
+
def test_loan_security_unpledge(self):
pledge = [{
"loan_security": "Test Security 1",
@@ -518,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)
@@ -554,8 +533,28 @@
calculated_penalty_amount = frappe.db.get_value('Loan Interest Accrual',
{'process_loan_interest_accrual': process, 'loan': loan.name}, 'penalty_amount')
+ self.assertEquals(loan.loan_amount, 1000000)
self.assertEquals(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.assertEquals(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.assertEquals(amounts['penalty_amount'], 0)
+
def test_loan_write_off_limit(self):
pledge = [{
"loan_security": "Test Security 1",
@@ -646,6 +645,32 @@
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
self.assertEquals(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_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_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
index 2b5df4b..6479853 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
@@ -21,6 +21,7 @@
"interest_payable",
"payable_amount",
"column_break_9",
+ "shortfall_amount",
"payable_principal_amount",
"penalty_amount",
"amount_paid",
@@ -31,6 +32,7 @@
"column_break_21",
"reference_date",
"principal_amount_paid",
+ "total_penalty_paid",
"total_interest_paid",
"repayment_details",
"amended_from"
@@ -226,18 +228,34 @@
"fieldtype": "Percent",
"label": "Rate Of Interest",
"read_only": 1
+ },
+ {
+ "fieldname": "shortfall_amount",
+ "fieldtype": "Currency",
+ "label": "Shortfall Amount",
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "total_penalty_paid",
+ "fieldtype": "Currency",
+ "hidden": 1,
+ "label": "Total Penalty Paid",
+ "options": "Company:company:default_currency",
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-11-05 10:06:58.792841",
+ "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,
@@ -252,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 bac06c4..3d99b1f 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -21,6 +21,7 @@
def validate(self):
amounts = calculate_amounts(self.against_loan, self.posting_date)
self.set_missing_values(amounts)
+ self.check_future_entries()
self.validate_amount()
self.allocate_amounts(amounts)
@@ -60,19 +61,28 @@
if not self.payable_amount:
self.payable_amount = flt(amounts['payable_amount'], precision)
+ shortfall_amount = flt(frappe.db.get_value('Loan Security Shortfall', {'loan': self.against_loan, 'status': 'Pending'},
+ 'shortfall_amount'))
+
+ if shortfall_amount:
+ self.shortfall_amount = shortfall_amount
+
if amounts.get('due_date'):
self.due_date = amounts.get('due_date')
+ def check_future_entries(self):
+ future_repayment_date = frappe.db.get_value("Loan Repayment", {"posting_date": (">", self.posting_date),
+ "docstatus": 1, "against_loan": self.against_loan}, 'posting_date')
+
+ if 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
if not self.amount_paid:
frappe.throw(_("Amount paid cannot be zero"))
- if 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:
@@ -148,11 +158,28 @@
def allocate_amounts(self, repayment_details):
self.set('repayment_details', [])
self.principal_amount_paid = 0
- total_interest_paid = 0
- interest_paid = self.amount_paid - self.penalty_amount
+ self.total_penalty_paid = 0
+ interest_paid = self.amount_paid
- if self.amount_paid - self.penalty_amount > 0:
- interest_paid = self.amount_paid - self.penalty_amount
+ if self.shortfall_amount and self.amount_paid > self.shortfall_amount:
+ self.principal_amount_paid = self.shortfall_amount
+ elif self.shortfall_amount:
+ self.principal_amount_paid = self.amount_paid
+
+ interest_paid -= self.principal_amount_paid
+
+ if interest_paid > 0:
+ if self.penalty_amount and interest_paid > self.penalty_amount:
+ self.total_penalty_paid = self.penalty_amount
+ elif self.penalty_amount:
+ self.total_penalty_paid = interest_paid
+
+ interest_paid -= self.total_penalty_paid
+
+ total_interest_paid = 0
+ # interest_paid = self.amount_paid - self.principal_amount_paid - self.penalty_amount
+
+ if interest_paid > 0:
for lia, amounts in iteritems(repayment_details.get('pending_accrual_entries', [])):
if amounts['interest_amount'] + amounts['payable_principal_amount'] <= interest_paid:
interest_amount = amounts['interest_amount']
@@ -177,7 +204,7 @@
'paid_principal_amount': paid_principal
})
- if repayment_details['unaccrued_interest'] and interest_paid:
+ if repayment_details['unaccrued_interest'] and interest_paid > 0:
# no of days for which to accrue interest
# Interest can only be accrued for an entire day and not partial
if interest_paid > repayment_details['unaccrued_interest']:
@@ -193,20 +220,28 @@
interest_paid -= no_of_days * per_day_interest
self.total_interest_paid = total_interest_paid
- if interest_paid:
+ if interest_paid > 0:
self.principal_amount_paid += interest_paid
def make_gl_entries(self, cancel=0, adv_adj=0):
gle_map = []
loan_details = frappe.get_doc("Loan", self.against_loan)
- if self.penalty_amount:
+ 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({
"account": loan_details.loan_account,
"against": loan_details.payment_account,
- "debit": self.penalty_amount,
- "debit_in_account_currency": self.penalty_amount,
+ "debit": self.total_penalty_paid,
+ "debit_in_account_currency": self.total_penalty_paid,
"against_voucher_type": "Loan",
"against_voucher": self.against_loan,
"remarks": _("Penalty against loan:") + self.against_loan,
@@ -221,8 +256,8 @@
self.get_gl_dict({
"account": loan_details.penalty_income_account,
"against": loan_details.payment_account,
- "credit": self.penalty_amount,
- "credit_in_account_currency": self.penalty_amount,
+ "credit": self.total_penalty_paid,
+ "credit_in_account_currency": self.total_penalty_paid,
"against_voucher_type": "Loan",
"against_voucher": self.against_loan,
"remarks": _("Penalty against loan:") + self.against_loan,
@@ -240,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)
})
@@ -256,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)
})
@@ -284,7 +319,9 @@
return lr
-def get_accrued_interest_entries(against_loan):
+def get_accrued_interest_entries(against_loan, posting_date=None):
+ if not posting_date:
+ posting_date = getdate()
unpaid_accrued_entries = frappe.db.sql(
"""
@@ -295,15 +332,28 @@
`tabLoan Interest Accrual`
WHERE
loan = %s
+ AND posting_date <= %s
AND (interest_amount - paid_interest_amount > 0 OR
payable_principal_amount - paid_principal_amount > 0)
AND
docstatus = 1
ORDER BY posting_date
- """, (against_loan), as_dict=1)
+ """, (against_loan, posting_date), as_dict=1)
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
@@ -312,8 +362,9 @@
against_loan_doc = frappe.get_doc("Loan", against_loan)
loan_type_details = frappe.get_doc("Loan Type", against_loan_doc.loan_type)
- accrued_interest_entries = get_accrued_interest_entries(against_loan_doc.name)
+ 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
@@ -328,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)
@@ -367,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)
@@ -379,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_shortfall/loan_security_shortfall.json b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.json
index 102bc0d..99b5c72 100644
--- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.json
+++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "LM-LSS-.#####",
"creation": "2019-09-06 11:33:34.709540",
"doctype": "DocType",
@@ -14,6 +15,7 @@
"shortfall_amount",
"column_break_8",
"security_value",
+ "shortfall_percentage",
"section_break_8",
"process_loan_security_shortfall"
],
@@ -85,10 +87,18 @@
{
"fieldname": "column_break_8",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "shortfall_percentage",
+ "fieldtype": "Percent",
+ "label": "Shortfall Percentage",
+ "read_only": 1
}
],
"in_create": 1,
- "modified": "2019-10-24 06:24:26.128997",
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-04-01 08:13:43.263772",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Security Shortfall",
diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
index 6469806..8233b7b 100644
--- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
+++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
@@ -12,7 +12,7 @@
class LoanSecurityShortfall(Document):
pass
-def update_shortfall_status(loan, security_value):
+def update_shortfall_status(loan, security_value, on_cancel=0):
loan_security_shortfall = frappe.db.get_value("Loan Security Shortfall",
{"loan": loan, "status": "Pending"}, ['name', 'shortfall_amount'], as_dict=1)
@@ -22,7 +22,9 @@
if security_value >= loan_security_shortfall.shortfall_amount:
frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, {
"status": "Completed",
- "shortfall_amount": loan_security_shortfall.shortfall_amount})
+ "shortfall_amount": loan_security_shortfall.shortfall_amount,
+ "shortfall_percentage": 0
+ })
else:
frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name,
"shortfall_amount", loan_security_shortfall.shortfall_amount - security_value)
@@ -55,6 +57,9 @@
'total_interest_payable', 'disbursed_amount', 'status'],
filters={'status': ('in',['Disbursed','Partially Disbursed']), 'is_secured_loan': 1})
+ loan_shortfall_map = frappe._dict(frappe.get_all("Loan Security Shortfall",
+ fields=["loan", "name"], filters={"status": "Pending"}, as_list=1))
+
loan_security_map = {}
for loan in loans:
@@ -62,7 +67,8 @@
outstanding_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
- flt(loan.total_principal_paid)
else:
- outstanding_amount = loan.disbursed_amount
+ outstanding_amount = flt(loan.disbursed_amount) - flt(loan.total_interest_payable) \
+ - flt(loan.total_principal_paid)
pledged_securities = get_pledged_security_qty(loan.name)
ltv_ratio = ''
@@ -71,16 +77,22 @@
for security, qty in pledged_securities.items():
if not ltv_ratio:
ltv_ratio = get_ltv_ratio(security)
- security_value += loan_security_price_map.get(security) * qty
+ security_value += flt(loan_security_price_map.get(security)) * flt(qty)
- current_ratio = (outstanding_amount/security_value) * 100
+ current_ratio = (outstanding_amount/security_value) * 100 if security_value else 0
if current_ratio > ltv_ratio:
shortfall_amount = outstanding_amount - ((security_value * ltv_ratio) / 100)
create_loan_security_shortfall(loan.name, outstanding_amount, security_value, shortfall_amount,
- process_loan_security_shortfall)
+ current_ratio, process_loan_security_shortfall)
+ elif loan_shortfall_map.get(loan.name):
+ shortfall_amount = outstanding_amount - ((security_value * ltv_ratio) / 100)
+ if shortfall_amount <= 0:
+ shortfall = loan_shortfall_map.get(loan.name)
+ update_pending_shortfall(shortfall)
-def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, process_loan_security_shortfall):
+def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, shortfall_ratio,
+ process_loan_security_shortfall):
existing_shortfall = frappe.db.get_value("Loan Security Shortfall", {"loan": loan, "status": "Pending"}, "name")
if existing_shortfall:
@@ -93,6 +105,7 @@
ltv_shortfall.loan_amount = loan_amount
ltv_shortfall.security_value = security_value
ltv_shortfall.shortfall_amount = shortfall_amount
+ ltv_shortfall.shortfall_percentage = shortfall_ratio
ltv_shortfall.process_loan_security_shortfall = process_loan_security_shortfall
ltv_shortfall.save()
@@ -101,3 +114,12 @@
ltv_ratio = frappe.db.get_value('Loan Security Type', loan_security_type, 'loan_to_value_ratio')
return ltv_ratio
+def update_pending_shortfall(shortfall):
+ # Get all pending loan security shortfall
+ frappe.db.set_value("Loan Security Shortfall", shortfall,
+ {
+ "status": "Completed",
+ "shortfall_amount": 0,
+ "shortfall_percentage": 0
+ })
+
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/loan_management/doctype/salary_slip_loan/salary_slip_loan.json b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json
index 2f4fe24..3d07081 100644
--- a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json
+++ b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json
@@ -70,7 +70,9 @@
{
"fieldname": "loan_repayment_entry",
"fieldtype": "Link",
+ "hidden": 1,
"label": "Loan Repayment Entry",
+ "no_copy": 1,
"options": "Loan Repayment",
"read_only": 1
},
@@ -83,9 +85,10 @@
"read_only": 1
}
],
+ "index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2020-04-16 13:17:04.798335",
+ "modified": "2021-03-14 20:47:11.725818",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Salary Slip Loan",
diff --git a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
index 0f72c3c..2a74a1e 100644
--- a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
+++ b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
@@ -63,9 +63,11 @@
currency = erpnext.get_company_currency(filters.get('company'))
for loan in loan_details:
+ total_payment = loan.total_payment if loan.status == 'Disbursed' else loan.disbursed_amount
+
loan.update({
"sanctioned_amount": flt(sanctioned_amount_map.get(loan.applicant_name)),
- "principal_outstanding": flt(loan.total_payment) - flt(loan.total_principal_paid) \
+ "principal_outstanding": flt(total_payment) - flt(loan.total_principal_paid) \
- flt(loan.total_interest_payable) - flt(loan.written_off_amount),
"total_repayment": flt(payments.get(loan.loan)),
"accrued_interest": flt(accrual_map.get(loan.loan, {}).get("accrued_interest")),
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
index cba6a2d..0aefe19 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
@@ -12,6 +12,7 @@
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
class MaintenanceSchedule(TransactionBase):
+ @frappe.whitelist()
def generate_schedule(self):
self.set('schedules', [])
frappe.db.sql("""delete from `tabMaintenance Schedule Detail`
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.py b/erpnext/manufacturing/doctype/bom/bom.py
index 03beedb..979f7ca 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -113,6 +113,7 @@
return item
+ @frappe.whitelist()
def get_routing(self):
if self.routing:
self.set("operations", [])
@@ -145,6 +146,7 @@
if not item.get(r):
item.set(r, ret[r])
+ @frappe.whitelist()
def get_bom_material_detail(self, args=None):
""" Get raw material details like uom, desc and rate"""
if not args:
@@ -210,6 +212,7 @@
.format(self.rm_cost_as_per, arg["item_code"]), alert=True)
return flt(rate) * flt(self.plc_conversion_rate or 1) / (self.conversion_rate or 1)
+ @frappe.whitelist()
def update_cost(self, update_parent=True, from_child_bom=False, save=True):
if self.docstatus == 2:
return
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 3239478..7108338 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -5,7 +5,7 @@
from __future__ import unicode_literals
import unittest
import frappe
-from frappe.utils import cstr
+from frappe.utils import cstr, flt
from frappe.test_runner import make_test_records
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation
from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost
@@ -81,15 +81,27 @@
bom = frappe.copy_doc(test_records[2])
bom.insert()
- # test amounts in selected currency
- self.assertEqual(bom.operating_cost, 100)
- self.assertEqual(bom.raw_material_cost, 351.68)
- self.assertEqual(bom.total_cost, 451.68)
+ raw_material_cost = 0.0
+ op_cost = 0.0
+
+ for op_row in bom.operations:
+ op_cost += op_row.operating_cost
+
+ for row in bom.items:
+ raw_material_cost += row.amount
+
+ 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, 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, 6000)
- self.assertEqual(bom.base_raw_material_cost, 21100.80)
- self.assertEqual(bom.base_total_cost, 27100.80)
+ 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)
@@ -134,7 +146,13 @@
bom.items[0].conversion_factor = 6
bom.insert()
- reset_item_valuation_rate(item_code='_Test Item', qty=200, rate=200)
+ reset_item_valuation_rate(
+ item_code='_Test Item',
+ warehouse_list=frappe.get_all("Warehouse",
+ {"is_group":0, "company": bom.company}, pluck="name"),
+ qty=200,
+ rate=200
+ )
bom.update_cost()
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/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 662a06b..fb26062 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -47,6 +47,8 @@
if d.completed_qty:
self.total_completed_qty += d.completed_qty
+ self.total_completed_qty = flt(self.total_completed_qty, self.precision("total_completed_qty"))
+
def get_overlap_for(self, args, check_next_available_slot=False):
production_capacity = 1
@@ -164,6 +166,7 @@
"time_in_mins": time_diff_in_minutes(row.planned_end_time, row.planned_start_time),
})
+ @frappe.whitelist()
def get_required_items(self):
if not self.get('work_order'):
return
@@ -255,6 +258,9 @@
data.actual_operation_time = time_in_mins
data.actual_start_time = time_data[0].start_time if time_data else None
data.actual_end_time = time_data[0].end_time if time_data else None
+ if data.get("workstation") != self.workstation:
+ # workstations can change in a job card
+ data.workstation = self.workstation
wo.flags.ignore_validate_update_after_submit = True
wo.update_operation_status()
@@ -427,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/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json
index f93b244..6c60bbd 100644
--- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json
+++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json
@@ -11,10 +11,14 @@
"from_warehouse",
"warehouse",
"column_break_4",
+ "required_bom_qty",
"quantity",
"uom",
"projected_qty",
"actual_qty",
+ "ordered_qty",
+ "reserved_qty_for_production",
+ "safety_stock",
"item_details",
"description",
"min_order_qty",
@@ -129,11 +133,40 @@
"fieldtype": "Link",
"label": "From Warehouse",
"options": "Warehouse"
+ },
+ {
+ "fetch_from": "item_code.safety_stock",
+ "fieldname": "safety_stock",
+ "fieldtype": "Float",
+ "label": "Safety Stock",
+ "no_copy": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "ordered_qty",
+ "fieldtype": "Float",
+ "label": "Ordered Qty",
+ "no_copy": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "reserved_qty_for_production",
+ "fieldtype": "Float",
+ "label": "Reserved Qty for Production",
+ "no_copy": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "required_bom_qty",
+ "fieldtype": "Float",
+ "label": "Required Qty as per BOM",
+ "no_copy": 1,
+ "read_only": 1
}
],
"istable": 1,
"links": [],
- "modified": "2020-02-03 12:22:29.913302",
+ "modified": "2021-03-26 12:41:13.013149",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Material Request Plan Item",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index b723387..288c1d0 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -25,6 +25,16 @@
}
});
+ frm.set_query('material_request', 'material_requests', function() {
+ return {
+ filters: {
+ material_request_type: "Manufacture",
+ docstatus: 1,
+ status: ["!=", "Stopped"],
+ }
+ };
+ });
+
frm.fields_dict['po_items'].grid.get_field('item_code').get_query = function(doc) {
return {
query: "erpnext.controllers.queries.item_query",
@@ -251,7 +261,8 @@
get_items_for_material_requests: function(frm, warehouses) {
const set_fields = ['actual_qty', 'item_code','item_name', 'description', 'uom', 'from_warehouse',
- 'min_order_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'material_request_type'];
+ 'min_order_qty', 'required_bom_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'ordered_qty',
+ 'reserved_qty_for_production', 'material_request_type'];
frappe.call({
method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_items_for_material_requests",
@@ -369,4 +380,4 @@
['Sales Order','docstatus', '=' ,1]
]
}
-};
\ No newline at end of file
+};
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json
index 7daf706..f114700 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.json
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json
@@ -32,6 +32,7 @@
"material_request_planning",
"include_non_stock_items",
"include_subcontracted_items",
+ "include_safety_stock",
"ignore_existing_ordered_qty",
"column_break_25",
"for_warehouse",
@@ -309,13 +310,19 @@
"fieldtype": "Select",
"label": "Sales Order Status",
"options": "\nTo Deliver and Bill\nTo Bill\nTo Deliver"
+ },
+ {
+ "default": "0",
+ "fieldname": "include_safety_stock",
+ "fieldtype": "Check",
+ "label": "Include Safety Stock in Required Qty Calculation"
}
],
"icon": "fa fa-calendar",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-11-10 18:01:54.991970",
+ "modified": "2021-03-08 11:17:25.470147",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 8f9dd05..a3e23a6 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -29,6 +29,7 @@
if not flt(d.planned_qty):
frappe.throw(_("Please enter Planned Qty for Item {0} at row {1}").format(d.item_code, d.idx))
+ @frappe.whitelist()
def get_open_sales_orders(self):
""" Pull sales orders which are pending to deliver based on criteria selected"""
open_so = get_sales_orders(self)
@@ -50,6 +51,7 @@
'grand_total': data.base_grand_total
})
+ @frappe.whitelist()
def get_pending_material_requests(self):
""" Pull Material Requests that are pending based on criteria selected"""
mr_filter = item_filter = ""
@@ -68,7 +70,7 @@
from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
where mr_item.parent = mr.name
and mr.material_request_type = "Manufacture"
- and mr.docstatus = 1 and mr.company = %(company)s
+ and mr.docstatus = 1 and mr.status != "Stopped" and mr.company = %(company)s
and mr_item.qty > ifnull(mr_item.ordered_qty,0) {0} {1}
and (exists (select name from `tabBOM` bom where bom.item=mr_item.item_code
and bom.is_active = 1))
@@ -92,6 +94,7 @@
'material_request_date': data.transaction_date
})
+ @frappe.whitelist()
def get_items(self):
if self.get_items_from == "Sales Order":
self.get_so_items()
@@ -219,6 +222,7 @@
filters = {'docstatus': 0, 'production_plan': ("=", self.name)}):
frappe.delete_doc('Work Order', d.name)
+ @frappe.whitelist()
def set_status(self, close=None):
self.status = {
0: 'Draft',
@@ -302,6 +306,7 @@
return item_dict
+ @frappe.whitelist()
def make_work_order(self):
wo_list = []
self.validate_data()
@@ -367,6 +372,7 @@
except OverProductionError:
pass
+ @frappe.whitelist()
def make_material_request(self):
'''Create Material Requests grouped by Sales Order and Material Request Type'''
material_request_list = []
@@ -434,12 +440,14 @@
if isinstance(doc, string_types):
doc = frappe._dict(json.loads(doc))
- item_list = [['Item Code', 'Description', 'Stock UOM', 'Required Qty', 'Warehouse',
- 'projected Qty', 'Actual Qty']]
+ item_list = [['Item Code', 'Description', 'Stock UOM', 'Warehouse', 'Required Qty as per BOM',
+ 'Projected Qty', 'Actual Qty', 'Ordered Qty', 'Reserved Qty for Production',
+ 'Safety Stock', 'Required Qty']]
for d in get_items_for_material_requests(doc):
- item_list.append([d.get('item_code'), d.get('description'), d.get('stock_uom'), d.get('quantity'),
- d.get('warehouse'), d.get('projected_qty'), d.get('actual_qty')])
+ item_list.append([d.get('item_code'), d.get('description'), d.get('stock_uom'), d.get('warehouse'),
+ d.get('required_bom_qty'), d.get('projected_qty'), d.get('actual_qty'), d.get('ordered_qty'),
+ d.get('reserved_qty_for_production'), d.get('safety_stock'), d.get('quantity')])
if not doc.get('for_warehouse'):
row = {'item_code': d.get('item_code')}
@@ -447,8 +455,9 @@
if d.get("warehouse") == bin_dict.get('warehouse'):
continue
- item_list.append(['', '', '', '', bin_dict.get('warehouse'),
- bin_dict.get('projected_qty', 0), bin_dict.get('actual_qty', 0)])
+ item_list.append(['', '', '', bin_dict.get('warehouse'), '',
+ bin_dict.get('projected_qty', 0), bin_dict.get('actual_qty', 0),
+ bin_dict.get('ordered_qty', 0), bin_dict.get('reserved_qty_for_production', 0)])
build_csv_response(item_list, doc.name)
@@ -482,7 +491,7 @@
ifnull(%(parent_qty)s * sum(bom_item.stock_qty/ifnull(bom.quantity, 1)) * %(planned_qty)s, 0) as qty,
item.is_sub_contracted_item as is_sub_contracted, bom_item.source_warehouse,
item.default_bom as default_bom, bom_item.description as description,
- bom_item.stock_uom as stock_uom, item.min_order_qty as min_order_qty,
+ bom_item.stock_uom as stock_uom, item.min_order_qty as min_order_qty, item.safety_stock as safety_stock,
item_default.default_warehouse, item.purchase_uom, item_uom.conversion_factor
FROM
`tabBOM Item` bom_item
@@ -518,8 +527,8 @@
include_non_stock_items, include_subcontracted_items, d.qty)
return item_details
-def get_material_request_items(row, sales_order,
- company, ignore_existing_ordered_qty, warehouse, bin_dict):
+def get_material_request_items(row, sales_order, company,
+ ignore_existing_ordered_qty, include_safety_stock, warehouse, bin_dict):
total_qty = row['qty']
required_qty = 0
@@ -543,17 +552,23 @@
if frappe.db.get_value("UOM", row['purchase_uom'], "must_be_whole_number"):
required_qty = ceil(required_qty)
+ if include_safety_stock:
+ required_qty += flt(row['safety_stock'])
+
if required_qty > 0:
return {
'item_code': row.item_code,
'item_name': row.item_name,
'quantity': required_qty,
- 'description': row.description,
+ 'required_bom_qty': total_qty,
'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"),
+ 'safety_stock': row.safety_stock,
'actual_qty': bin_dict.get("actual_qty", 0),
'projected_qty': bin_dict.get("projected_qty", 0),
+ 'ordered_qty': bin_dict.get("ordered_qty", 0),
+ 'reserved_qty_for_production': bin_dict.get("reserved_qty_for_production", 0),
'min_order_qty': row['min_order_qty'],
'material_request_type': row.get("default_material_request_type"),
'sales_order': sales_order,
@@ -620,7 +635,8 @@
""".format(lft, rgt, company)
return frappe.db.sql(""" select ifnull(sum(projected_qty),0) as projected_qty,
- ifnull(sum(actual_qty),0) as actual_qty, warehouse from `tabBin`
+ ifnull(sum(actual_qty),0) as actual_qty, ifnull(sum(ordered_qty),0) as ordered_qty,
+ ifnull(sum(reserved_qty_for_production),0) as reserved_qty_for_production, warehouse from `tabBin`
where item_code = %(item_code)s {conditions}
group by item_code, warehouse
""".format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1)
@@ -660,6 +676,7 @@
company = doc.get('company')
ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty')
+ include_safety_stock = doc.get('include_safety_stock')
so_item_details = frappe._dict()
for data in po_items:
@@ -711,6 +728,7 @@
'description' : item_master.description,
'stock_uom' : item_master.stock_uom,
'conversion_factor' : conversion_factor,
+ 'safety_stock': item_master.safety_stock
}
)
@@ -732,7 +750,7 @@
if details.qty > 0:
items = get_material_request_items(details, sales_order, company,
- ignore_existing_ordered_qty, warehouse, bin_dict)
+ ignore_existing_ordered_qty, include_safety_stock, warehouse, bin_dict)
if items:
mr_items.append(items)
@@ -747,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/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py
index 73d05a6..6a38dcf 100644
--- a/erpnext/manufacturing/doctype/routing/test_routing.py
+++ b/erpnext/manufacturing/doctype/routing/test_routing.py
@@ -13,8 +13,15 @@
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
class TestRouting(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls.item_code = "Test Routing Item - A"
+
+ @classmethod
+ def tearDownClass(cls):
+ frappe.db.sql('delete from tabBOM where item=%s', cls.item_code)
+
def test_sequence_id(self):
- item_code = "Test Routing Item - A"
operations = [{"operation": "Test Operation A", "workstation": "Test Workstation A", "time_in_mins": 30},
{"operation": "Test Operation B", "workstation": "Test Workstation A", "time_in_mins": 20}]
@@ -22,8 +29,8 @@
setup_operations(operations)
routing_doc = create_routing(routing_name="Testing Route", operations=operations)
- bom_doc = setup_bom(item_code=item_code, routing=routing_doc.name)
- wo_doc = make_wo_order_test_record(production_item = item_code, bom_no=bom_doc.name)
+ bom_doc = setup_bom(item_code=self.item_code, routing=routing_doc.name)
+ wo_doc = make_wo_order_test_record(production_item = self.item_code, bom_no=bom_doc.name)
for row in routing_doc.operations:
self.assertEqual(row.sequence_id, row.idx)
@@ -74,7 +81,7 @@
})
if not args.raw_materials:
- if not frappe.db.exists('Item', "Test Extra Item 1"):
+ if not frappe.db.exists('Item', "Test Extra Item N-1"):
make_item("Test Extra Item N-1", {
'is_stock_item': 1,
})
@@ -88,4 +95,4 @@
else:
bom_doc = frappe.get_doc("BOM", name)
- return bom_doc
\ No newline at end of file
+ return bom_doc
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 00e8c54..6b1fafe 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -82,7 +82,7 @@
wo_order.set_work_order_operations()
self.assertEqual(wo_order.planned_operating_cost, cost*2)
- def test_resered_qty_for_partial_completion(self):
+ def test_reserved_qty_for_partial_completion(self):
item = "_Test Item"
warehouse = create_warehouse("Test Warehouse for reserved_qty - _TC")
@@ -109,7 +109,7 @@
s.submit()
bin1_at_completion = get_bin(item, warehouse)
-
+
self.assertEqual(cint(bin1_at_completion.reserved_qty_for_production),
reserved_qty_on_submission - 1)
@@ -371,14 +371,14 @@
def test_job_card(self):
stock_entries = []
- data = frappe.get_cached_value('BOM',
- {'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
+ bom = frappe.get_doc('BOM', {
+ 'docstatus': 1,
+ 'with_operations': 1,
+ 'company': '_Test Company'
+ })
- bom, bom_item = data
-
- bom_doc = frappe.get_doc('BOM', bom)
- work_order = make_wo_order_test_record(item=bom_item, qty=1,
- bom_no=bom, source_warehouse="_Test Warehouse - _TC")
+ work_order = make_wo_order_test_record(item=bom.item, qty=1,
+ bom_no=bom.name, source_warehouse="_Test Warehouse - _TC")
for row in work_order.required_items:
stock_entry_doc = test_stock_entry.make_stock_entry(item_code=row.item_code,
@@ -390,14 +390,14 @@
stock_entries.append(ste)
job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name})
- self.assertEqual(len(job_cards), len(bom_doc.operations))
+ self.assertEqual(len(job_cards), len(bom.operations))
for i, job_card in enumerate(job_cards):
doc = frappe.get_doc("Job Card", job_card)
doc.append("time_logs", {
- "from_time": now(),
- "hours": i,
- "to_time": add_to_date(now(), i),
+ "from_time": add_to_date(None, i),
+ "hours": 1,
+ "to_time": add_to_date(None, i + 1),
"completed_qty": doc.for_quantity
})
doc.submit()
@@ -592,6 +592,55 @@
frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM")
+ def test_make_stock_entry_for_customer_provided_item(self):
+ finished_item = 'Test Item for Make Stock Entry 1'
+ make_item(finished_item, {
+ "include_item_in_manufacturing": 1,
+ "is_stock_item": 1
+ })
+
+ customer_provided_item = 'CUST-0987'
+ make_item(customer_provided_item, {
+ 'is_purchase_item': 0,
+ 'is_customer_provided_item': 1,
+ "is_stock_item": 1,
+ "include_item_in_manufacturing": 1,
+ 'customer': '_Test Customer'
+ })
+
+ if not frappe.db.exists('BOM', {'item': finished_item}):
+ make_bom(item=finished_item, raw_materials=[customer_provided_item], rm_qty=1)
+
+ company = "_Test Company with perpetual inventory"
+ customer_warehouse = create_warehouse("Test Customer Provided Warehouse", company=company)
+ wo = make_wo_order_test_record(item=finished_item, qty=1, source_warehouse=customer_warehouse,
+ company=company)
+
+ ste = frappe.get_doc(make_stock_entry(wo.name, purpose='Material Transfer for Manufacture'))
+ ste.insert()
+
+ self.assertEqual(len(ste.items), 1)
+ for item in ste.items:
+ self.assertEqual(item.allow_zero_valuation_rate, 1)
+ self.assertEqual(item.valuation_rate, 0)
+
+ def test_valuation_rate_missing_on_make_stock_entry(self):
+ item_name = 'Test Valuation Rate Missing'
+ make_item(item_name, {
+ "is_stock_item": 1,
+ "include_item_in_manufacturing": 1,
+ })
+
+ if not frappe.db.get_value('BOM', {'item': item_name}):
+ make_bom(item=item_name, raw_materials=[item_name], rm_qty=1)
+
+ company = "_Test Company with perpetual inventory"
+ source_warehouse = create_warehouse("Test Valuation Rate Missing Warehouse", company=company)
+ wo = make_wo_order_test_record(item=item_name, qty=1, source_warehouse=source_warehouse,
+ company=company)
+
+ self.assertRaises(frappe.ValidationError, make_stock_entry, wo.name, 'Material Transfer for Manufacture')
+
def get_scrap_item_details(bom_no):
scrap_items = {}
for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`
@@ -609,6 +658,15 @@
def make_wo_order_test_record(**args):
args = frappe._dict(args)
+ if args.company and args.company != "_Test Company":
+ warehouse_map = {
+ "fg_warehouse": "_Test FG Warehouse",
+ "wip_warehouse": "_Test WIP Warehouse"
+ }
+
+ for attr, wh_name in warehouse_map.items():
+ if not args.get(attr):
+ args[attr] = create_warehouse(wh_name, company=args.company)
wo_order = frappe.new_doc("Work Order")
wo_order.production_item = args.production_item or args.item or args.item_code or "_Test FG Item"
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index 585a09d..cd9edee 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -333,8 +333,7 @@
"fieldname": "operations",
"fieldtype": "Table",
"label": "Operations",
- "options": "Work Order Operation",
- "read_only": 1
+ "options": "Work Order Operation"
},
{
"depends_on": "operations",
@@ -496,7 +495,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2020-05-05 19:32:43.323054",
+ "modified": "2021-03-16 13:27:51.116484",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 3d64ad4..8507f5e 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -509,6 +509,7 @@
stock_bin = get_bin(d.item_code, d.source_warehouse)
stock_bin.update_reserved_qty_for_production()
+ @frappe.whitelist()
def get_items_and_operations_from_bom(self):
self.set_required_items()
self.set_work_order_operations()
@@ -613,6 +614,7 @@
item.db_set('consumed_qty', flt(consumed_qty), update_modified=False)
+ @frappe.whitelist()
def make_bom(self):
data = frappe.db.sql(""" select sed.item_code, sed.qty, sed.s_warehouse
from `tabStock Entry Detail` sed, `tabStock Entry` se
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/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
index 2ca9f16..fc27d35 100644
--- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
+++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
@@ -61,7 +61,7 @@
from_date = add_years(self.filters.from_date, cint(self.filters.no_of_years) * -1)
self.period_list = get_period_list(from_date, self.filters.to_date,
- from_date, self.filters.to_date, None, self.filters.periodicity, ignore_fiscal_year=True)
+ from_date, self.filters.to_date, "Date Range", self.filters.periodicity, ignore_fiscal_year=True)
order_data = self.get_data_for_forecast() or []
diff --git a/erpnext/non_profit/doctype/donation/donation.py b/erpnext/non_profit/doctype/donation/donation.py
index 6a2a06d..4fd1a30 100644
--- a/erpnext/non_profit/doctype/donation/donation.py
+++ b/erpnext/non_profit/doctype/donation/donation.py
@@ -42,7 +42,7 @@
self.load_from_db()
self.create_payment_entry()
- def create_payment_entry(self):
+ def create_payment_entry(self, date=None):
settings = frappe.get_doc('Non Profit Settings')
if not settings.automate_donation_payment_entries:
return
@@ -58,8 +58,9 @@
frappe.flags.ignore_account_permission = False
pe.paid_from = settings.donation_debit_account
pe.paid_to = settings.donation_payment_account
+ pe.posting_date = date or getdate()
pe.reference_no = self.name
- pe.reference_date = getdate()
+ pe.reference_date = date or getdate()
pe.flags.ignore_mandatory = True
pe.insert()
pe.submit()
diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py
index 3ba2ee7..efc072e 100644
--- a/erpnext/non_profit/doctype/member/member.py
+++ b/erpnext/non_profit/doctype/member/member.py
@@ -53,6 +53,7 @@
return subscription
+ @frappe.whitelist()
def make_customer_and_link(self):
if self.customer:
frappe.msgprint(_("A customer is already linked to this Member"))
diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py
index 52447e4..e8ae618 100644
--- a/erpnext/non_profit/doctype/membership/membership.py
+++ b/erpnext/non_profit/doctype/membership/membership.py
@@ -74,6 +74,7 @@
self.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True)
+ @frappe.whitelist()
def generate_invoice(self, save=True, with_payment_entry=False):
if not (self.paid or self.currency or self.amount):
frappe.throw(_("The payment for this membership is not paid. To generate invoice fill the payment details"))
@@ -130,6 +131,7 @@
pe.save()
pe.submit()
+ @frappe.whitelist()
def send_acknowlement(self):
settings = frappe.get_doc("Non Profit Settings")
if not settings.send_email:
diff --git a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js
index cff92b4..4c4ca98 100644
--- a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js
+++ b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js
@@ -19,7 +19,7 @@
};
});
- frm.set_query("debit_account", function() {
+ frm.set_query("membership_debit_account", function() {
return {
filters: {
"account_type": "Receivable",
@@ -29,6 +29,16 @@
};
});
+ frm.set_query("donation_debit_account", function() {
+ return {
+ filters: {
+ "account_type": "Receivable",
+ "is_group": 0,
+ "company": frm.doc.donation_company
+ }
+ };
+ });
+
frm.set_query("membership_payment_account", function () {
var account_types = ["Bank", "Cash"];
return {
@@ -40,6 +50,17 @@
};
});
+ frm.set_query("donation_payment_account", function () {
+ var account_types = ["Bank", "Cash"];
+ return {
+ filters: {
+ "account_type": ["in", account_types],
+ "is_group": 0,
+ "company": frm.doc.donation_company
+ }
+ };
+ });
+
let docs_url = "https://docs.erpnext.com/docs/user/manual/en/non_profit/membership";
frm.set_intro(__("You can learn more about memberships in the manual. ") + `<a href='${docs_url}'>${__('ERPNext Docs')}</a>`, true);
diff --git a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py
index 108554c..a84cc2c 100644
--- a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py
+++ b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py
@@ -9,6 +9,7 @@
from frappe.model.document import Document
class NonProfitSettings(Document):
+ @frappe.whitelist()
def generate_webhook_secret(self, field="membership_webhook_secret"):
key = frappe.generate_hash(length=20)
self.set(field, key)
@@ -21,6 +22,7 @@
_("Webhook Secret")
)
+ @frappe.whitelist()
def revoke_key(self, key):
self.set(key, None)
self.save()
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 7016ecd..9ef949c 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -99,7 +99,7 @@
execute:frappe.delete_doc("DocType", "Purchase Request Item")
erpnext.patches.v4_2.recalculate_bom_cost
erpnext.patches.v4_2.fix_gl_entries_for_stock_transactions
-erpnext.patches.v4_2.update_requested_and_ordered_qty
+erpnext.patches.v4_2.update_requested_and_ordered_qty #2021-03-31
execute:frappe.rename_doc("DocType", "Support Ticket", "Issue", force=True)
erpnext.patches.v4_4.make_email_accounts
execute:frappe.delete_doc("DocType", "Contact Control")
@@ -208,7 +208,7 @@
erpnext.patches.v5_7.item_template_attributes
execute:frappe.delete_doc_if_exists("DocType", "Manage Variants")
execute:frappe.delete_doc_if_exists("DocType", "Manage Variants Item")
-erpnext.patches.v4_2.repost_reserved_qty #2016-04-15
+erpnext.patches.v4_2.repost_reserved_qty #2021-03-31
erpnext.patches.v5_4.update_purchase_cost_against_project
erpnext.patches.v5_8.update_order_reference_in_return_entries
erpnext.patches.v5_8.add_credit_note_print_heading
@@ -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)
@@ -720,7 +720,7 @@
erpnext.patches.v12_0.update_item_tax_template_company
erpnext.patches.v13_0.move_branch_code_to_bank_account
erpnext.patches.v13_0.healthcare_lab_module_rename_doctypes
-erpnext.patches.v13_0.add_standard_navbar_items #4
+erpnext.patches.v13_0.add_standard_navbar_items #2021-03-24
erpnext.patches.v13_0.stock_entry_enhancements
erpnext.patches.v12_0.update_state_code_for_daman_and_diu
erpnext.patches.v12_0.rename_lost_reason_detail
@@ -752,11 +752,26 @@
erpnext.patches.v13_0.convert_qi_parameter_to_link_field
erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes
erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021
+erpnext.patches.v13_0.update_payment_terms_outstanding
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.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.v13_0.set_pos_closing_as_failed
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_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_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/purchase_receipt_status.py b/erpnext/patches/v12_0/purchase_receipt_status.py
new file mode 100644
index 0000000..1a99b31
--- /dev/null
+++ b/erpnext/patches/v12_0/purchase_receipt_status.py
@@ -0,0 +1,30 @@
+""" This patch fixes old purchase receipts (PR) where even after submitting
+ the PR, the `status` remains "Draft". `per_billed` field was copied over from previous
+ doc (PO), hence it is recalculated for setting new correct status of PR.
+"""
+
+import frappe
+
+logger = frappe.logger("patch", allow_site=True, file_count=50)
+
+def execute():
+ affected_purchase_receipts = frappe.db.sql(
+ """select name from `tabPurchase Receipt`
+ where status = 'Draft' and per_billed = 100 and docstatus = 1"""
+ )
+
+ if not affected_purchase_receipts:
+ return
+
+ logger.info("purchase_receipt_status: begin patch, PR count: {}"
+ .format(len(affected_purchase_receipts)))
+
+
+ for pr in affected_purchase_receipts:
+ pr_name = pr[0]
+ logger.info("purchase_receipt_status: patching PR - {}".format(pr_name))
+
+ pr_doc = frappe.get_doc("Purchase Receipt", pr_name)
+
+ pr_doc.update_billing_status(update_modified=False)
+ pr_doc.set_status(update=True, update_modified=False)
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/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_discharge_date_in_ip_record.py b/erpnext/patches/v13_0/rename_discharge_date_in_ip_record.py
new file mode 100644
index 0000000..491dc82
--- /dev/null
+++ b/erpnext/patches/v13_0/rename_discharge_date_in_ip_record.py
@@ -0,0 +1,8 @@
+from __future__ import unicode_literals
+import frappe
+from frappe.model.utils.rename_field import rename_field
+
+def execute():
+ frappe.reload_doc("Healthcare", "doctype", "Inpatient Record")
+ if frappe.db.has_column("Inpatient Record", "discharge_date"):
+ rename_field("Inpatient Record", "discharge_date", "discharge_datetime")
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/setup_patient_history_settings_for_standard_doctypes.py b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py
index de08aa2..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,6 +6,9 @@
if "Healthcare" not in frappe.get_active_domains():
return
+ 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
new file mode 100644
index 0000000..1830bab
--- /dev/null
+++ b/erpnext/patches/v13_0/setup_uae_vat_fields.py
@@ -0,0 +1,16 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+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
+
+ 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_payment_terms_outstanding.py b/erpnext/patches/v13_0/update_payment_terms_outstanding.py
new file mode 100644
index 0000000..4816b40
--- /dev/null
+++ b/erpnext/patches/v13_0/update_payment_terms_outstanding.py
@@ -0,0 +1,15 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# MIT License. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ frappe.reload_doc("accounts", "doctype", "Payment Schedule")
+ if frappe.db.count('Payment Schedule'):
+ frappe.db.sql('''
+ UPDATE
+ `tabPayment Schedule` ps
+ SET
+ ps.outstanding = (ps.payment_amount - ps.paid_amount)
+ ''')
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/v7_1/update_lead_source.py b/erpnext/patches/v7_1/update_lead_source.py
index 517e66c..a2a48a6 100644
--- a/erpnext/patches/v7_1/update_lead_source.py
+++ b/erpnext/patches/v7_1/update_lead_source.py
@@ -5,7 +5,7 @@
def execute():
from erpnext.setup.setup_wizard.operations.install_fixtures import default_lead_sources
- frappe.reload_doc('selling', 'doctype', 'lead_source')
+ frappe.reload_doc('crm', 'doctype', 'lead_source')
frappe.local.lang = frappe.db.get_default("lang") or 'en'
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.json b/erpnext/payroll/doctype/additional_salary/additional_salary.json
index 2b29f66..5e17a5c 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.json
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.json
@@ -163,7 +163,6 @@
"read_only": 1
},
{
- "default": "Company:company:default_currency",
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
"fieldname": "currency",
"fieldtype": "Link",
@@ -176,7 +175,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-10-20 17:51:13.419716",
+ "modified": "2021-03-31 22:33:59.098532",
"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 4c45580..8332697 100644
--- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
+++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
@@ -124,7 +124,6 @@
"read_only": 1
},
{
- "default": "Company:company:default_currency",
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
"fieldname": "currency",
"fieldtype": "Link",
@@ -148,7 +147,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-12-14 15:52:08.566418",
+ "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.js b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.js
index ea9ccd5..e1f8431 100644
--- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.js
+++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.js
@@ -21,7 +21,6 @@
callback: function(r) {
if (r.message) {
frm.set_value('currency', r.message);
- frm.set_df_property('currency', 'hidden', 0);
}
}
});
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 da24aac..b3bac01 100644
--- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json
+++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json
@@ -125,10 +125,9 @@
"label": "Attachments"
},
{
- "default": "Company:company:default_currency",
+ "depends_on": "eval: doc.employee",
"fieldname": "currency",
"fieldtype": "Link",
- "hidden": 1,
"label": "Currency",
"options": "Currency",
"read_only": 1,
@@ -145,7 +144,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-11-25 11:49:56.097352",
+ "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 e5b1052..0d10b2c 100644
--- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.json
+++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.json
@@ -75,7 +75,6 @@
"reqd": 1
},
{
- "default": "Company:company:default_currency",
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
"fieldname": "currency",
"fieldtype": "Link",
@@ -95,7 +94,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-10-20 17:22:16.468042",
+ "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.js b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.js
index 0e0c9b5..fb11875 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.js
+++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.js
@@ -47,5 +47,26 @@
});
}).addClass("btn-primary");
}
+ },
+
+ employee: function(frm) {
+ if (frm.doc.employee) {
+ frm.trigger('get_employee_currency');
+ }
+ },
+
+ get_employee_currency: function(frm) {
+ frappe.call({
+ method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
+ args: {
+ employee: frm.doc.employee,
+ },
+ callback: function(r) {
+ if (r.message) {
+ frm.set_value('currency', r.message);
+ frm.refresh_fields();
+ }
+ }
+ });
}
});
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 83d4ae5..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
@@ -108,7 +108,7 @@
"read_only": 1
},
{
- "default": "Company:company:default_currency",
+ "depends_on": "eval: doc.employee",
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
@@ -119,7 +119,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-10-20 16:42:24.493761",
+ "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.js b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js
index 497f35c..4fb0a37 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js
+++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js
@@ -58,5 +58,26 @@
currency: function(frm) {
frm.refresh_fields();
- }
+ },
+
+ employee: function(frm) {
+ if (frm.doc.employee) {
+ frm.trigger('get_employee_currency');
+ }
+ },
+
+ get_employee_currency: function(frm) {
+ frappe.call({
+ method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
+ args: {
+ employee: frm.doc.employee,
+ },
+ callback: function(r) {
+ if (r.message) {
+ frm.set_value('currency', r.message);
+ frm.refresh_fields();
+ }
+ }
+ });
+ },
});
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 53f18cb..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
@@ -131,7 +131,7 @@
"read_only": 1
},
{
- "default": "Company:company:default_currency",
+ "depends_on": "eval: doc.employee",
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
@@ -142,7 +142,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-10-20 16:47:03.410020",
+ "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 9fa261d..5a7de37 100644
--- a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json
+++ b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json
@@ -93,7 +93,7 @@
"options": "Income Tax Slab Other Charges"
},
{
- "default": "Company:company:default_currency",
+ "fetch_from": "company.default_currency",
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
@@ -104,7 +104,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-10-19 13:54:24.728075",
+ "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 395e56f..f289260 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
@@ -133,45 +133,63 @@
}
};
});
+
+ frm.set_query('employee', 'employees', () => {
+ if (!frm.doc.company) {
+ frappe.msgprint(__("Please set a Company"));
+ return [];
+ }
+ return {
+ query: "erpnext.payroll.doctype.payroll_entry.payroll_entry.employee_query",
+ filters: frm.events.get_employee_filters(frm)
+ };
+ });
+ },
+
+ get_employee_filters: function (frm) {
+ let filters = {};
+ 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;
+ }
+ if (frm.doc.branch) {
+ filters['branch'] = frm.doc.branch;
+ }
+ if (frm.doc.designation) {
+ filters['designation'] = frm.doc.designation;
+ }
+ if (frm.doc.employees) {
+ filters['employees'] = frm.doc.employees.filter(d => d.employee).map(d => d.employee);
+ }
+ return filters;
},
payroll_frequency: function (frm) {
frm.trigger("set_start_end_dates").then( ()=> {
frm.events.clear_employee_table(frm);
- frm.events.get_employee_with_salary_slip_and_set_query(frm);
- });
- },
-
- employee_filters: function (frm, emp_list) {
- frm.set_query('employee', 'employees', () => {
- return {
- filters: {
- name: ["not in", emp_list]
- }
- };
- });
- },
-
- get_employee_with_salary_slip_and_set_query: function (frm) {
- frappe.db.get_list('Salary Slip', {
- filters: {
- start_date: frm.doc.start_date,
- end_date: frm.doc.end_date,
- docstatus: 1,
- },
- fields: ['employee']
- }).then((emp) => {
- var emp_list = [];
- emp.forEach((employee_data) => {
- emp_list.push(Object.values(employee_data)[0]);
- });
- frm.events.employee_filters(frm, emp_list);
});
},
company: function (frm) {
frm.events.clear_employee_table(frm);
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
+ frm.trigger("set_payable_account_and_currency");
+ },
+
+ set_payable_account_and_currency: function (frm) {
+ frappe.db.get_value("Company", {"name": frm.doc.company}, "default_currency", (r) => {
+ frm.set_value('currency', r.default_currency);
+ });
+ frappe.db.get_value("Company", {"name": frm.doc.company}, "default_payroll_payable_account", (r) => {
+ frm.set_value('payroll_payable_account', r.default_payroll_payable_account);
+ });
},
currency: function (frm) {
@@ -345,11 +363,3 @@
})
);
};
-
-frappe.ui.form.on('Payroll Employee Detail', {
- employee: function(frm) {
- if (!frm.doc.payroll_frequency) {
- frappe.throw(__("Please set a Payroll Frequency"));
- }
- }
-});
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index 6bcd4e0..3953b46 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -10,16 +10,17 @@
from frappe import _
from erpnext.accounts.utils import get_fiscal_year
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
+from frappe.desk.reportview import get_match_cond, get_filters_cond
class PayrollEntry(Document):
def onload(self):
if not self.docstatus==1 or self.salary_slips_submitted:
- return
+ return
# check if salary slips were manually submitted
entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name'])
if cint(entries) == len(self.employees):
- self.set_onload("submitted_ss", True)
+ self.set_onload("submitted_ss", True)
def validate(self):
self.number_of_employees = len(self.employees)
@@ -51,50 +52,34 @@
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):
self.set('employees', [])
employees = self.get_emp_list()
@@ -120,28 +105,12 @@
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):
frappe.throw(_("Please set {0}").format(self.meta.get_label(fieldname)))
+ @frappe.whitelist()
def create_salary_slips(self):
"""
Creates salary slip for selected employees if already not created
@@ -174,15 +143,15 @@
"""
Returns list of salary slips based on selected criteria
"""
- cond = self.get_filter_condition()
ss_list = frappe.db.sql("""
select t1.name, t1.salary_structure, t1.payroll_cost_center from `tabSalary Slip` t1
- where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s
- and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s %s
- """ % ('%s', '%s', '%s','%s', cond), (ss_status, self.start_date, self.end_date, self.salary_slip_based_on_timesheet), as_dict=as_dict)
+ where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s and t1.payroll_entry = %s
+ and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s
+ """, (ss_status, self.start_date, self.end_date, self.name, self.salary_slip_based_on_timesheet), as_dict=as_dict)
return ss_list
+ @frappe.whitelist()
def submit_salary_slips(self):
self.check_permission('write')
ss_list = self.get_sal_slip_list(ss_status=0)
@@ -268,26 +237,26 @@
exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
payable_amount += flt(amount, precision)
accounts.append({
- "account": acc_cc[0],
- "debit_in_account_currency": flt(amt, precision),
- "exchange_rate": flt(exchange_rate),
- "party_type": '',
- "cost_center": acc_cc[1] or self.cost_center,
- "project": self.project
- })
+ "account": acc_cc[0],
+ "debit_in_account_currency": flt(amt, precision),
+ "exchange_rate": flt(exchange_rate),
+ "party_type": '',
+ "cost_center": acc_cc[1] or self.cost_center,
+ "project": self.project
+ })
# Deductions
for acc_cc, amount in deductions.items():
exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
payable_amount -= flt(amount, precision)
accounts.append({
- "account": acc_cc[0],
- "credit_in_account_currency": flt(amt, precision),
- "exchange_rate": flt(exchange_rate),
- "cost_center": acc_cc[1] or self.cost_center,
- "party_type": '',
- "project": self.project
- })
+ "account": acc_cc[0],
+ "credit_in_account_currency": flt(amt, precision),
+ "exchange_rate": flt(exchange_rate),
+ "cost_center": acc_cc[1] or self.cost_center,
+ "party_type": '',
+ "project": self.project
+ })
# Payable amount
exchange_rate, payable_amt = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, payable_amount, company_currency, currencies)
@@ -329,13 +298,13 @@
amount = flt(amount) * flt(conversion_rate)
return exchange_rate, amount
+ @frappe.whitelist()
def make_payment_entry(self):
self.check_permission('write')
- cond = self.get_filter_condition()
salary_slip_name_list = frappe.db.sql(""" select t1.name from `tabSalary Slip` t1
- where t1.docstatus = 1 and start_date >= %s and end_date <= %s %s
- """ % ('%s', '%s', cond), (self.start_date, self.end_date), as_list = True)
+ where t1.docstatus = 1 and start_date >= %s and end_date <= %s and t1.payroll_entry = %s
+ """, (self.start_date, self.end_date, self.name), as_list = True)
if salary_slip_name_list and len(salary_slip_name_list) > 0:
salary_slip_total = 0
@@ -367,20 +336,20 @@
exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(self.payment_account, je_payment_amount, company_currency, currencies)
accounts.append({
- "account": self.payment_account,
- "bank_account": self.bank_account,
- "credit_in_account_currency": flt(amount, precision),
- "exchange_rate": flt(exchange_rate),
- })
+ "account": self.payment_account,
+ "bank_account": self.bank_account,
+ "credit_in_account_currency": flt(amount, precision),
+ "exchange_rate": flt(exchange_rate),
+ })
exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, je_payment_amount, company_currency, currencies)
accounts.append({
- "account": payroll_payable_account,
- "debit_in_account_currency": flt(amount, precision),
- "exchange_rate": flt(exchange_rate),
- "reference_type": self.doctype,
- "reference_name": self.name
- })
+ "account": payroll_payable_account,
+ "debit_in_account_currency": flt(amount, precision),
+ "exchange_rate": flt(exchange_rate),
+ "reference_type": self.doctype,
+ "reference_name": self.name
+ })
if len(currencies) > 1:
multi_currency = 1
@@ -406,6 +375,7 @@
self.update(get_start_end_dates(self.payroll_frequency,
self.start_date or self.posting_date, self.company))
+ @frappe.whitelist()
def validate_employee_attendance(self):
employees_to_mark_attendance = []
days_in_payroll, days_holiday, days_attendance_marked = 0, 0, 0
@@ -421,7 +391,7 @@
employees_to_mark_attendance.append({
"employee": employee_detail.employee,
"employee_name": employee_detail.employee_name
- })
+ })
return employees_to_mark_attendance
def get_count_holidays_of_employee(self, employee, start_date):
@@ -438,15 +408,62 @@
def get_count_employee_attendance(self, employee, start_date):
marked_days = 0
attendances = frappe.get_all("Attendance",
- fields = ["count(*)"],
- filters = {
- "employee": employee,
- "attendance_date": ('between', [start_date, self.end_date])
- }, as_list=1)
+ fields = ["count(*)"],
+ filters = {
+ "employee": employee,
+ "attendance_date": ('between', [start_date, self.end_date])
+ }, as_list=1)
if attendances and attendances[0][0]:
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'''
@@ -550,6 +567,7 @@
def create_salary_slips_for_employees(employees, args, publish_progress=True):
salary_slips_exists_for = get_existing_salary_slips(employees, args)
count=0
+ salary_slips_not_created = []
for emp in employees:
if emp not in salary_slips_exists_for:
args.update({
@@ -562,34 +580,26 @@
if publish_progress:
frappe.publish_progress(count*100/len(set(employees) - set(salary_slips_exists_for)),
title = _("Creating Salary Slips..."))
- else:
- salary_slip_name = frappe.db.sql(
- '''SELECT
- name
- FROM `tabSalary Slip`
- WHERE company=%s
- AND start_date >= %s
- AND end_date <= %s
- AND employee = %s
- ''', (args.company, args.start_date, args.end_date, emp), as_dict=True)
- salary_slip_doc = frappe.get_doc('Salary Slip', salary_slip_name[0].name)
- salary_slip_doc.exchange_rate = args.exchange_rate
- salary_slip_doc.set_totals()
- salary_slip_doc.db_update()
+ else:
+ salary_slips_not_created.append(emp)
payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry)
payroll_entry.db_set("salary_slips_created", 1)
payroll_entry.notify_update()
+ if salary_slips_not_created:
+ frappe.msgprint(_("Salary Slips already exists for employees {}, and will not be processed by this payroll.")
+ .format(frappe.bold(", ".join([emp for emp in salary_slips_not_created]))) , title=_("Message"), indicator="orange")
+
def get_existing_salary_slips(employees, args):
return frappe.db.sql_list("""
select distinct employee from `tabSalary Slip`
- where docstatus!= 2 and company = %s
+ where docstatus!= 2 and company = %s and payroll_entry = %s
and start_date >= %s and end_date <= %s
and employee in (%s)
- """ % ('%s', '%s', '%s', ', '.join(['%s']*len(employees))),
- [args.company, args.start_date, args.end_date] + employees)
+ """ % ('%s', '%s', '%s', '%s', ', '.join(['%s']*len(employees))),
+ [args.company, args.payroll_entry, args.start_date, args.end_date] + employees)
def submit_salary_slips_for_employees(payroll_entry, salary_slips, publish_progress=True):
submitted_ss = []
@@ -641,3 +651,63 @@
'txt': "%%%s%%" % frappe.db.escape(txt),
'start': start, 'page_len': page_len
})
+
+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 = []
+ include_employees = []
+ emp_cond = ''
+ if filters.start_date and filters.end_date:
+ 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 include_employees:
+ emp_cond += 'and employee in %(include_employees)s'
+
+ return frappe.db.sql("""select name, employee_name from `tabEmployee`
+ where status = 'Active'
+ and docstatus < 2
+ and ({key} like %(txt)s
+ or employee_name like %(txt)s)
+ {emp_cond}
+ {fcond} {mcond}
+ order by
+ if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
+ if(locate(%(_txt)s, employee_name), locate(%(_txt)s, employee_name), 99999),
+ idx desc,
+ name, employee_name
+ limit %(start)s, %(page_len)s""".format(**{
+ 'key': searchfield,
+ 'fcond': get_filters_cond(doctype, filters, conditions),
+ 'mcond': get_match_cond(doctype),
+ 'emp_cond': emp_cond
+ }), {
+ 'txt': "%%%s%%" % txt,
+ '_txt': txt.replace("%", ""),
+ 'start': start,
+ 'page_len': page_len,
+ '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 84c3814..7528bf7 100644
--- a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
@@ -51,21 +51,22 @@
company_doc = frappe.get_doc('Company', company)
salary_structure = make_salary_structure("_Test Multi Currency Salary Structure", "Monthly", company=company, currency='USD')
- create_salary_structure_assignment(employee, salary_structure.name, company=company)
+ create_salary_structure_assignment(employee, salary_structure.name, company=company, currency='USD')
frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""",(frappe.db.get_value("Employee", {"user_id": "test_muti_currency_employee@payroll.com"})))
salary_slip = get_salary_slip("test_muti_currency_employee@payroll.com", "Monthly", "_Test Multi Currency Salary Structure")
dates = get_start_end_dates('Monthly', nowdate())
- payroll_entry = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date,
+ payroll_entry = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date,
payable_account=company_doc.default_payroll_payable_account, currency='USD', exchange_rate=70)
payroll_entry.make_payment_entry()
salary_slip.load_from_db()
payroll_je = salary_slip.journal_entry
- payroll_je_doc = frappe.get_doc('Journal Entry', payroll_je)
+ if payroll_je:
+ payroll_je_doc = frappe.get_doc('Journal Entry', payroll_je)
- self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_debit)
- self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit)
+ self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_debit)
+ self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit)
payment_entry = frappe.db.sql('''
Select ifnull(sum(je.total_debit),0) as total_debit, ifnull(sum(je.total_credit),0) as total_credit from `tabJournal Entry` je, `tabJournal Entry Account` jea
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 6647230..7ea6210 100644
--- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.json
+++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.json
@@ -93,7 +93,6 @@
"reqd": 1
},
{
- "default": "Company:company:default_currency",
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
"fieldname": "currency",
"fieldtype": "Link",
@@ -106,7 +105,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-10-20 17:27:47.003134",
+ "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 7460c75..5258f3a 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.js
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js
@@ -39,7 +39,10 @@
frm.set_query("employee", function() {
return {
- query: "erpnext.controllers.queries.employee_query"
+ query: "erpnext.controllers.queries.employee_query",
+ filters: {
+ company: frm.doc.company
+ }
};
});
},
@@ -74,17 +77,22 @@
if (!frm.doc.letter_head && company.default_letter_head) {
frm.set_value('letter_head', company.default_letter_head);
}
+ },
+
+ currency: function(frm) {
frm.trigger("set_dynamic_labels");
},
set_dynamic_labels: function(frm) {
var company_currency = frm.doc.company? erpnext.get_currency(frm.doc.company): frappe.defaults.get_default("currency");
- frappe.run_serially([
- () => frm.events.set_exchange_rate(frm, company_currency),
- () => frm.events.change_form_labels(frm, company_currency),
- () => frm.events.change_grid_labels(frm),
- () => frm.refresh_fields()
- ]);
+ if (frm.doc.employee && frm.doc.currency) {
+ frappe.run_serially([
+ () => frm.events.set_exchange_rate(frm, company_currency),
+ () => frm.events.change_form_labels(frm, company_currency),
+ () => frm.events.change_grid_labels(frm),
+ () => frm.refresh_fields()
+ ]);
+ }
},
set_exchange_rate: function(frm, company_currency) {
@@ -100,10 +108,12 @@
to_currency: company_currency,
},
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);
+ if (r.message) {
+ 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);
+ }
}
});
} else {
@@ -213,7 +223,7 @@
});
var set_totals = function(frm) {
- if (frm.doc.docstatus === 0) {
+ if (frm.doc.docstatus === 0 && frm.doc.doctype === "Salary Slip") {
if (frm.doc.earnings || frm.doc.deductions) {
frappe.call({
method: "set_totals",
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json
index 6688368..42a0f29 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.json
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json
@@ -500,7 +500,6 @@
"fieldtype": "Column Break"
},
{
- "default": "Company:company:default_currency",
"depends_on": "eval:(doc.docstatus==1 || doc.salary_structure)",
"fetch_from": "salary_structure.currency",
"fieldname": "currency",
@@ -632,7 +631,7 @@
"idx": 9,
"is_submittable": 1,
"links": [],
- "modified": "2021-02-19 11:48:05.383945",
+ "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 a04a635..afdf081 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -124,9 +124,12 @@
def check_existing(self):
if not self.salary_slip_based_on_timesheet:
+ cond = ""
+ if self.payroll_entry:
+ cond += "and payroll_entry = '{0}'".format(self.payroll_entry)
ret_exist = frappe.db.sql("""select name from `tabSalary Slip`
where start_date = %s and end_date = %s and docstatus != 2
- and employee = %s and name != %s""",
+ and employee = %s and name != %s {0}""".format(cond),
(self.start_date, self.end_date, self.employee, self.name))
if ret_exist:
self.employee = ''
@@ -142,6 +145,7 @@
self.start_date = date_details.start_date
self.end_date = date_details.end_date
+ @frappe.whitelist()
def get_emp_and_working_day_details(self):
'''First time, load all the components from salary structure'''
if self.employee:
@@ -594,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
@@ -607,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
])
@@ -617,13 +621,16 @@
component_row = self.append(component_type)
for attr in (
- 'depends_on_payment_days', 'salary_component', 'abbr'
+ 'depends_on_payment_days', 'salary_component',
'do_not_include_in_total', 'is_tax_applicable',
'is_flexible_benefit', 'variable_based_on_taxable_salary',
'exempted_from_income_tax'
):
component_row.set(attr, component_data.get(attr))
+ abbr = component_data.get('abbr') or component_data.get('salary_component_abbr')
+ component_row.set('abbr', abbr)
+
if additional_salary:
component_row.default_amount = 0
component_row.additional_amount = amount
@@ -1049,7 +1056,7 @@
repayment_entry.save()
repayment_entry.submit()
- loan.loan_repayment_entry = repayment_entry.name
+ frappe.db.set_value("Salary Slip Loan", loan.name, "loan_repayment_entry", repayment_entry.name)
def cancel_loan_repayment_entry(self):
for loan in self.loans:
@@ -1114,10 +1121,12 @@
self.bank_name = emp.bank_name
self.bank_account_no = emp.bank_ac_no
+ @frappe.whitelist()
def process_salary_based_on_working_days(self):
self.get_working_days_details(lwp=self.leave_without_pay)
self.calculate_net_pay()
+ @frappe.whitelist()
def set_totals(self):
self.gross_pay = 0.0
if self.salary_slip_based_on_timesheet == 1:
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.json b/erpnext/payroll/doctype/salary_structure/salary_structure.json
index de56fc8..5dd1d70 100644
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.json
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.json
@@ -232,7 +232,7 @@
"idx": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-09-30 11:30:32.190798",
+ "modified": "2021-03-31 15:41:12.342380",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Structure",
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.py b/erpnext/payroll/doctype/salary_structure/salary_structure.py
index 1712081..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)
@@ -100,7 +100,7 @@
from_date=from_date, base=base, variable=variable, income_tax_slab=income_tax_slab)
else:
assign_salary_structure_for_employees(employees, self,
- payroll_payable_account=payroll_payable_account,
+ payroll_payable_account=payroll_payable_account,
from_date=from_date, base=base, variable=variable, income_tax_slab=income_tax_slab)
else:
frappe.msgprint(_("No Employee Found"))
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 92bb347..c8b98e5 100644
--- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
+++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
@@ -125,7 +125,6 @@
"options": "Income Tax Slab"
},
{
- "default": "Company:company:default_currency",
"depends_on": "eval:(doc.docstatus==1 || doc.salary_structure)",
"fetch_from": "salary_structure.currency",
"fieldname": "currency",
@@ -146,7 +145,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-11-30 18:07:48.251311",
+ "modified": "2021-03-31 22:44:46.267974",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Structure Assignment",
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/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/portal/product_configurator/test_product_configurator.py b/erpnext/portal/product_configurator/test_product_configurator.py
index 97042db..3521e7e 100644
--- a/erpnext/portal/product_configurator/test_product_configurator.py
+++ b/erpnext/portal/product_configurator/test_product_configurator.py
@@ -10,8 +10,38 @@
test_dependencies = ["Item"]
class TestProductConfigurator(unittest.TestCase):
- def setUp(self):
- self.create_variant_item()
+ @classmethod
+ def setUpClass(cls):
+ cls.create_variant_item()
+
+ @classmethod
+ def create_variant_item(cls):
+ if not frappe.db.exists('Item', '_Test Variant Item - 2XL'):
+ frappe.get_doc({
+ "description": "_Test Variant Item - 2XL",
+ "item_code": "_Test Variant Item - 2XL",
+ "item_name": "_Test Variant Item - 2XL",
+ "doctype": "Item",
+ "is_stock_item": 1,
+ "variant_of": "_Test Variant Item",
+ "item_group": "_Test Item Group",
+ "stock_uom": "_Test UOM",
+ "item_defaults": [{
+ "company": "_Test Company",
+ "default_warehouse": "_Test Warehouse - _TC",
+ "expense_account": "_Test Account Cost for Goods Sold - _TC",
+ "buying_cost_center": "_Test Cost Center - _TC",
+ "selling_cost_center": "_Test Cost Center - _TC",
+ "income_account": "Sales - _TC"
+ }],
+ "attributes": [
+ {
+ "attribute": "Test Size",
+ "attribute_value": "2XL"
+ }
+ ],
+ "show_variant_in_website": 1
+ }).insert()
def test_product_list(self):
template_items = frappe.get_all('Item', {'show_in_website': 1})
@@ -46,39 +76,6 @@
def test_get_products_for_website(self):
items = get_products_for_website(attribute_filters={
- 'Test Size': ['Medium']
+ 'Test Size': ['2XL']
})
self.assertEqual(len(items), 1)
-
-
- def create_variant_item(self):
- if not frappe.db.exists('Item', '_Test Variant Item 1'):
- frappe.get_doc({
- "description": "_Test Variant Item 12",
- "doctype": "Item",
- "is_stock_item": 1,
- "variant_of": "_Test Variant Item",
- "item_code": "_Test Variant Item 1",
- "item_group": "_Test Item Group",
- "item_name": "_Test Variant Item 1",
- "stock_uom": "_Test UOM",
- "item_defaults": [{
- "company": "_Test Company",
- "default_warehouse": "_Test Warehouse - _TC",
- "expense_account": "_Test Account Cost for Goods Sold - _TC",
- "buying_cost_center": "_Test Cost Center - _TC",
- "selling_cost_center": "_Test Cost Center - _TC",
- "income_account": "Sales - _TC"
- }],
- "attributes": [
- {
- "attribute": "Test Size",
- "attribute_value": "Medium"
- }
- ],
- "show_variant_in_website": 1
- }).insert()
-
-
- def tearDown(self):
- frappe.db.rollback()
\ No newline at end of file
diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py
index 21fd7c2..d77eb2c 100644
--- a/erpnext/portal/product_configurator/utils.py
+++ b/erpnext/portal/product_configurator/utils.py
@@ -298,7 +298,7 @@
def get_items(filters=None, search=None):
- start = frappe.form_dict.start or 0
+ start = frappe.form_dict.get('start', 0)
products_settings = get_product_settings()
page_length = products_settings.products_per_page
diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js
index 077011a..c5265e2 100644
--- a/erpnext/projects/doctype/project/project.js
+++ b/erpnext/projects/doctype/project/project.js
@@ -18,8 +18,8 @@
};
},
onload: function (frm) {
- var so = frappe.meta.get_docfield("Project", "sales_order");
- so.get_route_options_for_new_doc = function (field) {
+ const so = frm.get_docfield("sales_order");
+ so.get_route_options_for_new_doc = () => {
if (frm.is_new()) return;
return {
"customer": frm.doc.customer,
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index f9e1359..55c5149 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,
diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py
index 15a2873..70139c6 100644
--- a/erpnext/projects/doctype/project/test_project.py
+++ b/erpnext/projects/doctype/project/test_project.py
@@ -4,12 +4,14 @@
import frappe, unittest
-test_records = frappe.get_test_records('Project')
-test_ignore = ["Sales Order"]
+from frappe.utils import getdate, nowdate, add_days
from erpnext.projects.doctype.project_template.test_project_template import make_project_template
from erpnext.projects.doctype.task.test_task import create_task
-from frappe.utils import getdate, nowdate, add_days
+
+test_records = frappe.get_test_records('Project')
+test_ignore = ["Sales Order"]
+
class TestProject(unittest.TestCase):
def test_project_with_template_having_no_parent_and_depend_tasks(self):
@@ -31,12 +33,16 @@
def test_project_template_having_parent_child_tasks(self):
project_name = "Test Project with Template - Tasks with Parent-Child Relation"
+
+ if frappe.db.get_value('Project', {'project_name': project_name}, 'name'):
+ project_name = frappe.db.get_value('Project', {'project_name': project_name}, 'name')
+
frappe.db.sql(""" delete from tabTask where project = %s """, project_name)
frappe.delete_doc('Project', project_name)
task1 = task_exists("Test Template Task Parent")
if not task1:
- task1 = create_task(subject="Test Template Task Parent", is_group=1, is_template=1, begin=1, duration=4)
+ task1 = create_task(subject="Test Template Task Parent", is_group=1, is_template=1, begin=1, duration=10)
task2 = task_exists("Test Template Task Child 1")
if not task2:
@@ -51,7 +57,7 @@
tasks = frappe.get_all('Task', ['subject','exp_end_date','depends_on_tasks', 'name', 'parent_task'], dict(project=project.name), order_by='creation asc')
self.assertEqual(tasks[0].subject, 'Test Template Task Parent')
- self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 1, 4))
+ self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 1, 10))
self.assertEqual(tasks[1].subject, 'Test Template Task Child 1')
self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, 1, 3))
@@ -112,7 +118,8 @@
doctype = 'Project',
project_name = args.project_name,
status = 'Open',
- expected_start_date = args.start_date
+ expected_start_date = args.start_date,
+ company= args.company or '_Test Company'
))
if args.project_template_name:
diff --git a/erpnext/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js
index 002ddb2..6a9d2d1 100644
--- a/erpnext/projects/doctype/task/task.js
+++ b/erpnext/projects/doctype/task/task.js
@@ -32,7 +32,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..d21ac0f 100644
--- a/erpnext/projects/doctype/timesheet/test_timesheet.py
+++ b/erpnext/projects/doctype/timesheet/test_timesheet.py
@@ -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
diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js
index b123af5..43a57e5 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.js
+++ b/erpnext/projects/doctype/timesheet/timesheet.js
@@ -174,9 +174,10 @@
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"));
+ let $timer = $('.form-grid').find('[data-fieldname="timer"]');
+ if ($timer.get(0)) {
+ $timer.append(frappe.render_template("timesheet"));
+ }
frm.trigger("control_timer");
});
},
diff --git a/erpnext/selling/doctype/lead_source/__init__.py b/erpnext/projects/report/delayed_tasks_summary/__init__.py
similarity index 100%
copy from erpnext/selling/doctype/lead_source/__init__.py
copy to 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/selling/doctype/lead_source/__init__.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/__init__.py
similarity index 100%
copy from erpnext/selling/doctype/lead_source/__init__.py
copy to 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..842fd4d
--- /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.billable AS 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, 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 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..fa87827
--- /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,
+ "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,
+ "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/selling/doctype/lead_source/__init__.py b/erpnext/projects/report/project_profitability/__init__.py
similarity index 100%
copy from erpnext/selling/doctype/lead_source/__init__.py
copy to 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..5ad2d85
--- /dev/null
+++ b/erpnext/projects/report/project_profitability/project_profitability.py
@@ -0,0 +1,210 @@
+# 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):
+ 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 = row.base_gross_pay * row.utilization
+ row.profit = row.base_grand_total - row.base_gross_pay * 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..7fe28b1
--- /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):
+ @classmethod
+ 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, 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", "HR Settings", "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()
\ No newline at end of file
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 8ceae83..8b4a497 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -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 448bb65..31410da 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -1,7 +1,7 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-erpnext.taxes_and_totals = class taxes_and_totals extends erpnext.payments {
+erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
setup() {
this.fetch_round_off_accounts();
}
@@ -165,7 +165,7 @@
"company": me.frm.doc.company,
"account_list": frappe.flags.round_off_applicable_accounts
},
- callback: function(r) {
+ callback(r) {
frappe.flags.round_off_applicable_accounts.push(...r.message);
}
});
@@ -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(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(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(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(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() {
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 a4c165e..b3c2006 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -5,7 +5,7 @@
erpnext.TransactionController = class TransactionController extends erpnext.taxes_and_totals {
setup() {
- super.setup();
+ this._super();
frappe.flags.hide_serial_batch_dialog = true;
frappe.ui.form.on(this.frm.doctype + " Item", "rate", function(frm, cdt, cdn) {
var item = frappe.get_doc(cdt, cdn);
@@ -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,
@@ -577,7 +577,7 @@
var d = locals[cdt][cdn];
me.add_taxes_from_item_tax_template(d.item_tax_rate);
if (d.free_item_data) {
- me.apply_product_discount(d.free_item_data);
+ me.apply_product_discount(d);
}
},
() => {
@@ -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);
}
]);
}
@@ -737,34 +741,34 @@
this.frm.trigger("item_code", cdt, cdn);
}
else {
- var valid_serial_nos = [];
-
// Replacing all occurences of comma with carriage return
- var serial_nos = item.serial_no.trim().replace(/,/g, '\n');
-
- serial_nos = serial_nos.trim().split('\n');
-
- // Trim each string and push unique string to new list
- for (var x=0; x<=serial_nos.length - 1; x++) {
- if (serial_nos[x].trim() != "" && valid_serial_nos.indexOf(serial_nos[x].trim()) == -1) {
- valid_serial_nos.push(serial_nos[x].trim());
- }
- }
-
- // Add the new list to the serial no. field in grid with each in new line
- item.serial_no = valid_serial_nos.join('\n');
+ item.serial_no = item.serial_no.replace(/,/g, '\n');
item.conversion_factor = item.conversion_factor || 1;
-
refresh_field("serial_no", item.name, item.parentfield);
- if(!doc.is_return && cint(user_defaults.set_qty_in_transactions_based_on_serial_no_input)) {
- frappe.model.set_value(item.doctype, item.name,
- "qty", valid_serial_nos.length / item.conversion_factor);
- frappe.model.set_value(item.doctype, item.name, "stock_qty", valid_serial_nos.length);
+ if (!doc.is_return && cint(frappe.user_defaults.set_qty_in_transactions_based_on_serial_no_input)) {
+ setTimeout(() => {
+ me.update_qty(cdt, cdn);
+ }, 10000);
}
}
}
}
+ update_qty(cdt, cdn) {
+ var valid_serial_nos = [];
+ var serialnos = [];
+ var item = frappe.get_doc(cdt, cdn);
+ serialnos = item.serial_no.split("\n");
+ for (var i = 0; i < serialnos.length; i++) {
+ if (serialnos[i] != "") {
+ valid_serial_nos.push(serialnos[i]);
+ }
+ }
+ frappe.model.set_value(item.doctype, item.name,
+ "qty", valid_serial_nos.length / item.conversion_factor);
+ frappe.model.set_value(item.doctype, item.name, "stock_qty", valid_serial_nos.length);
+ }
+
validate() {
this.calculate_taxes_and_totals(false);
}
@@ -1103,6 +1107,8 @@
to_currency: to_currency,
args: args
},
+ freeze: true,
+ freeze_message: __("Fetching exchange rates ..."),
callback: function(r) {
callback(flt(r.message));
}
@@ -1173,6 +1179,11 @@
this.calculate_net_weight();
}
+ // for handling customization not to fetch price list rate
+ if(frappe.flags.dont_fetch_price_list_rate) {
+ return
+ }
+
if (!dont_fetch_price_list_rate &&
frappe.meta.has_field(doc.doctype, "price_list_currency")) {
this.apply_price_list(item, true);
@@ -1314,11 +1325,9 @@
change_grid_labels(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"]) {
this.frm.set_currency_labels(["operating_cost", "hour_rate"], this.frm.doc.currency, "operations");
@@ -1353,6 +1362,39 @@
this.frm.doc.party_account_currency, "advances");
}
+ this.update_payment_schedule_grid_labels(company_currency);
+ }
+
+ update_item_grid_labels(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(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(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) {
@@ -1372,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() {
@@ -1533,7 +1572,10 @@
if(k=="price_list_rate") {
if(flt(v) != flt(d.price_list_rate)) price_list_rate_changed = true;
}
- frappe.model.set_value(d.doctype, d.name, k, v);
+
+ if (k !== 'free_item_data') {
+ frappe.model.set_value(d.doctype, d.name, k, v);
+ }
}
}
@@ -1545,7 +1587,7 @@
}
if (d.free_item_data) {
- me.apply_product_discount(d.free_item_data);
+ me.apply_product_discount(d);
}
if (d.apply_rule_on_other_items) {
@@ -1579,20 +1621,31 @@
}
}
- apply_product_discount(free_item_data) {
- const items = this.frm.doc.items.filter(d => (d.item_code == free_item_data.item_code
- && d.is_free_item)) || [];
+ apply_product_discount(args) {
+ const items = this.frm.doc.items.filter(d => (d.is_free_item)) || [];
- if (!items.length) {
- let row_to_modify = frappe.model.add_child(this.frm.doc,
- this.frm.doc.doctype + ' Item', 'items');
+ const exist_items = items.map(row => (row.item_code, row.pricing_rules));
- for (let key in free_item_data) {
- row_to_modify[key] = free_item_data[key];
+ args.free_item_data.forEach(pr_row => {
+ let row_to_modify = {};
+ if (!items || !in_list(exist_items, (pr_row.item_code, pr_row.pricing_rules))) {
+
+ row_to_modify = frappe.model.add_child(this.frm.doc,
+ this.frm.doc.doctype + ' Item', 'items');
+
+ } else if(items) {
+ row_to_modify = items.filter(d => (d.item_code === pr_row.item_code
+ && d.pricing_rules === pr_row.pricing_rules))[0];
}
- } if (items && items.length && free_item_data) {
- items[0].qty = free_item_data.qty
- }
+
+ for (let key in pr_row) {
+ row_to_modify[key] = pr_row[key];
+ }
+ });
+
+ // free_item_data is a temporary variable
+ args.free_item_data = '';
+ refresh_field('items');
}
apply_price_list(item, reset_plc_conversion) {
@@ -1974,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);
}
}
})
@@ -1986,6 +2042,7 @@
}
payment_term(doc, cdt, cdn) {
+ const me = this;
var row = locals[cdt][cdn];
if(row.payment_term) {
frappe.call({
@@ -1994,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 472c537..e789923 100644
--- a/erpnext/public/js/help_links.js
+++ b/erpnext/public/js/help_links.js
@@ -1,466 +1,1051 @@
-frappe.provide('frappe.help.help_links');
+frappe.provide("frappe.help.help_links");
-const docsUrl = 'https://erpnext.com/docs/';
+const docsUrl = "https://erpnext.com/docs/";
-frappe.help.help_links['rename tool'] = [
- { label: 'Bulk Rename', url: docsUrl + 'user/manual/en/setting-up/data/bulk-rename' },
-]
+frappe.help.help_links["Form/Rename Tool"] = [
+ {
+ label: "Bulk Rename",
+ url: docsUrl + "user/manual/en/setting-up/data/bulk-rename",
+ },
+];
//Setup
-frappe.help.help_links['user'] = [
- { label: 'New User', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/adding-users' },
- { label: 'Rename User', url: docsUrl + 'user/manual/en/setting-up/articles/rename-user' },
-]
+frappe.help.help_links["List/User"] = [
+ {
+ label: "New User",
+ url:
+ docsUrl +
+ "user/manual/en/setting-up/users-and-permissions/adding-users",
+ },
+ {
+ label: "Rename User",
+ url: docsUrl + "user/manual/en/setting-up/articles/rename-user",
+ },
+];
-frappe.help.help_links['permission-manager'] = [
- { label: 'Role Permissions Manager', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/role-based-permissions' },
- { label: 'Managing Perm Level in Permissions Manager', url: docsUrl + 'user/manual/en/setting-up/articles/managing-perm-level' },
- { label: 'User Permissions', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/user-permissions' },
- { label: 'Sharing', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/sharing' },
- { label: 'Password', url: docsUrl + 'user/manual/en/setting-up/articles/change-password' },
-]
+frappe.help.help_links["permission-manager"] = [
+ {
+ label: "Role Permissions Manager",
+ url:
+ docsUrl +
+ "user/manual/en/setting-up/users-and-permissions/role-based-permissions",
+ },
+ {
+ label: "Managing Perm Level in Permissions Manager",
+ url: docsUrl + "user/manual/en/setting-up/articles/managing-perm-level",
+ },
+ {
+ label: "User Permissions",
+ url:
+ docsUrl +
+ "user/manual/en/setting-up/users-and-permissions/user-permissions",
+ },
+ {
+ label: "Sharing",
+ url:
+ docsUrl + "user/manual/en/setting-up/users-and-permissions/sharing",
+ },
+ {
+ label: "Password",
+ url: docsUrl + "user/manual/en/setting-up/articles/change-password",
+ },
+];
-frappe.help.help_links['system-settings'] = [
- { label: 'Naming Series', url: docsUrl + 'user/manual/en/setting-up/settings/system-settings' },
-]
+frappe.help.help_links["Form/System Settings"] = [
+ {
+ label: "Naming Series",
+ url: docsUrl + "user/manual/en/setting-up/settings/system-settings",
+ },
+];
-frappe.help.help_links['data-import-tool'] = [
- { label: 'Importing and Exporting Data', url: docsUrl + 'user/manual/en/setting-up/data/data-import-tool' },
- { label: 'Overwriting Data from Data Import Tool', url: docsUrl + 'user/manual/en/setting-up/articles/overwriting-data-from-data-import-tool' },
-]
+frappe.help.help_links["data-import-tool"] = [
+ {
+ label: "Importing and Exporting Data",
+ url: docsUrl + "user/manual/en/setting-up/data/data-import-tool",
+ },
+ {
+ label: "Overwriting Data from Data Import Tool",
+ url:
+ docsUrl +
+ "user/manual/en/setting-up/articles/overwriting-data-from-data-import-tool",
+ },
+];
-frappe.help.help_links['naming-series'] = [
- { label: 'Naming Series', url: docsUrl + 'user/manual/en/setting-up/settings/naming-series' },
- { label: 'Setting the Current Value for Naming Series', url: docsUrl + 'user/manual/en/setting-up/articles/naming-series-current-value' },
-]
+frappe.help.help_links["module_setup"] = [
+ {
+ label: "Role Permissions Manager",
+ url:
+ docsUrl +
+ "user/manual/en/setting-up/users-and-permissions/role-based-permissions",
+ },
+];
-frappe.help.help_links['global-defaults'] = [
- { label: 'Global Settings', url: docsUrl + 'user/manual/en/setting-up/settings/global-defaults' },
-]
+frappe.help.help_links["Form/Naming Series"] = [
+ {
+ label: "Naming Series",
+ url: docsUrl + "user/manual/en/setting-up/settings/naming-series",
+ },
+ {
+ label: "Setting the Current Value for Naming Series",
+ url:
+ docsUrl +
+ "user/manual/en/setting-up/articles/naming-series-current-value",
+ },
+];
-frappe.help.help_links['email-digest'] = [
- { label: 'Email Digest', url: docsUrl + 'user/manual/en/setting-up/email/email-digest' },
-]
+frappe.help.help_links["Form/Global Defaults"] = [
+ {
+ label: "Global Settings",
+ url: docsUrl + "user/manual/en/setting-up/settings/global-defaults",
+ },
+];
-frappe.help.help_links['print-heading'] = [
- { label: 'Print Heading', url: docsUrl + 'user/manual/en/setting-up/print/print-headings' },
-]
+frappe.help.help_links["Form/Email Digest"] = [
+ {
+ label: "Email Digest",
+ url: docsUrl + "user/manual/en/setting-up/email/email-digest",
+ },
+];
-frappe.help.help_links['letter-head'] = [
- { label: 'Letter Head', url: docsUrl + 'user/manual/en/setting-up/print/letter-head' },
-]
+frappe.help.help_links["List/Print Heading"] = [
+ {
+ label: "Print Heading",
+ url: docsUrl + "user/manual/en/setting-up/print/print-headings",
+ },
+];
-frappe.help.help_links['address-template'] = [
- { label: 'Address Template', url: docsUrl + 'user/manual/en/setting-up/print/address-template' },
-]
+frappe.help.help_links["List/Letter Head"] = [
+ {
+ label: "Letter Head",
+ url: docsUrl + "user/manual/en/setting-up/print/letter-head",
+ },
+];
-frappe.help.help_links['terms-and-conditions'] = [
- { label: 'Terms and Conditions', url: docsUrl + 'user/manual/en/setting-up/print/terms-and-conditions' },
-]
+frappe.help.help_links["List/Address Template"] = [
+ {
+ label: "Address Template",
+ url: docsUrl + "user/manual/en/setting-up/print/address-template",
+ },
+];
-frappe.help.help_links['cheque-print-template'] = [
- { label: 'Cheque Print Template', url: docsUrl + 'user/manual/en/setting-up/print/cheque-print-template' },
-]
+frappe.help.help_links["List/Terms and Conditions"] = [
+ {
+ label: "Terms and Conditions",
+ url: docsUrl + "user/manual/en/setting-up/print/terms-and-conditions",
+ },
+];
-frappe.help.help_links['email-account'] = [
- { label: 'Email Account', url: docsUrl + 'user/manual/en/setting-up/email/email-account' },
-]
+frappe.help.help_links["List/Cheque Print Template"] = [
+ {
+ label: "Cheque Print Template",
+ url: docsUrl + "user/manual/en/setting-up/print/cheque-print-template",
+ },
+];
-frappe.help.help_links['notification'] = [
- { label: 'Notification', url: docsUrl + 'user/manual/en/setting-up/email/notifications' },
-]
+frappe.help.help_links["List/Email Account"] = [
+ {
+ label: "Email Account",
+ url: docsUrl + "user/manual/en/setting-up/email/email-account",
+ },
+];
-frappe.help.help_links['notification'] = [
- { label: 'Notification', url: docsUrl + 'user/manual/en/setting-up/email/notifications' },
-]
+frappe.help.help_links["List/Notification"] = [
+ {
+ label: "Notification",
+ url: docsUrl + "user/manual/en/setting-up/email/notifications",
+ },
+];
-frappe.help.help_links['email-digest'] = [
- { label: 'Email Digest', url: docsUrl + 'user/manual/en/setting-up/email/email-digest' },
-]
+frappe.help.help_links["Form/Notification"] = [
+ {
+ label: "Notification",
+ url: docsUrl + "user/manual/en/setting-up/email/notifications",
+ },
+];
-frappe.help.help_links['auto-email-report'] = [
- { label: 'Auto Email Reports', url: docsUrl + 'user/manual/en/setting-up/email/email-reports' },
-]
+frappe.help.help_links["List/Email Digest"] = [
+ {
+ label: "Email Digest",
+ url: docsUrl + "user/manual/en/setting-up/email/email-digest",
+ },
+];
-frappe.help.help_links['print-settings'] = [
- { label: 'Print Settings', url: docsUrl + 'user/manual/en/setting-up/print/print-settings' },
-]
+frappe.help.help_links["List/Auto Email Report"] = [
+ {
+ label: "Auto Email Reports",
+ url: docsUrl + "user/manual/en/setting-up/email/email-reports",
+ },
+];
-frappe.help.help_links['print-format-builder'] = [
- { label: 'Print Format Builder', url: docsUrl + 'user/manual/en/setting-up/print/print-settings' },
-]
+frappe.help.help_links["Form/Print Settings"] = [
+ {
+ label: "Print Settings",
+ url: docsUrl + "user/manual/en/setting-up/print/print-settings",
+ },
+];
-frappe.help.help_links['print-heading'] = [
- { label: 'Print Heading', url: docsUrl + 'user/manual/en/setting-up/print/print-headings' },
-]
+frappe.help.help_links["print-format-builder"] = [
+ {
+ label: "Print Format Builder",
+ url: docsUrl + "user/manual/en/setting-up/print/print-settings",
+ },
+];
+
+frappe.help.help_links["List/Print Heading"] = [
+ {
+ label: "Print Heading",
+ url: docsUrl + "user/manual/en/setting-up/print/print-headings",
+ },
+];
//setup-integrations
-frappe.help.help_links['paypal-settings'] = [
- { label: 'PayPal Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/paypal-integration' },
-]
+frappe.help.help_links["Form/PayPal Settings"] = [
+ {
+ label: "PayPal Settings",
+ url:
+ docsUrl +
+ "user/manual/en/setting-up/integrations/paypal-integration",
+ },
+];
-frappe.help.help_links['razorpay-settings'] = [
- { label: 'Razorpay Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/razorpay-integration' },
-]
+frappe.help.help_links["Form/Razorpay Settings"] = [
+ {
+ label: "Razorpay Settings",
+ url:
+ docsUrl +
+ "user/manual/en/setting-up/integrations/razorpay-integration",
+ },
+];
-frappe.help.help_links['dropbox-settings'] = [
- { label: 'Dropbox Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/dropbox-backup' },
-]
+frappe.help.help_links["Form/Dropbox Settings"] = [
+ {
+ label: "Dropbox Settings",
+ url: docsUrl + "user/manual/en/setting-up/integrations/dropbox-backup",
+ },
+];
-frappe.help.help_links['ldap-settings'] = [
- { label: 'LDAP Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/ldap-integration' },
-]
+frappe.help.help_links["Form/LDAP Settings"] = [
+ {
+ label: "LDAP Settings",
+ url:
+ docsUrl + "user/manual/en/setting-up/integrations/ldap-integration",
+ },
+];
-frappe.help.help_links['stripe-settings'] = [
- { label: 'Stripe Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/stripe-integration' },
-]
+frappe.help.help_links["Form/Stripe Settings"] = [
+ {
+ label: "Stripe Settings",
+ url:
+ docsUrl +
+ "user/manual/en/setting-up/integrations/stripe-integration",
+ },
+];
//Sales
-frappe.help.help_links['quotation'] = [
- { label: 'Quotation', url: docsUrl + 'user/manual/en/selling/quotation' },
- { label: 'Applying Discount', url: docsUrl + 'user/manual/en/selling/articles/applying-discount' },
- { label: 'Sales Person', url: docsUrl + 'user/manual/en/selling/articles/sales-persons-in-the-sales-transactions' },
- { label: 'Applying Margin', url: docsUrl + 'user/manual/en/selling/articles/adding-margin' },
-]
+frappe.help.help_links["Form/Quotation"] = [
+ { label: "Quotation", url: docsUrl + "user/manual/en/selling/quotation" },
+ {
+ label: "Applying Discount",
+ url: docsUrl + "user/manual/en/selling/articles/applying-discount",
+ },
+ {
+ label: "Sales Person",
+ url:
+ docsUrl +
+ "user/manual/en/selling/articles/sales-persons-in-the-sales-transactions",
+ },
+ {
+ label: "Applying Margin",
+ url: docsUrl + "user/manual/en/selling/articles/adding-margin",
+ },
+];
-frappe.help.help_links['customer'] = [
- { label: 'Customer', url: docsUrl + 'user/manual/en/CRM/customer' },
- { label: 'Credit Limit', url: docsUrl + 'user/manual/en/accounts/credit-limit' },
-]
+frappe.help.help_links["List/Customer"] = [
+ { label: "Customer", url: docsUrl + "user/manual/en/CRM/customer" },
+ {
+ label: "Credit Limit",
+ url: docsUrl + "user/manual/en/accounts/credit-limit",
+ },
+];
-frappe.help.help_links['customer'] = [
- { label: 'Customer', url: docsUrl + 'user/manual/en/CRM/customer' },
- { label: 'Credit Limit', url: docsUrl + 'user/manual/en/accounts/credit-limit' },
-]
+frappe.help.help_links["Form/Customer"] = [
+ { label: "Customer", url: docsUrl + "user/manual/en/CRM/customer" },
+ {
+ label: "Credit Limit",
+ url: docsUrl + "user/manual/en/accounts/credit-limit",
+ },
+];
-frappe.help.help_links['sales-taxes-and-charges-template'] = [
- { label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' },
-]
+frappe.help.help_links["List/Sales Taxes and Charges Template"] = [
+ {
+ label: "Setting Up Taxes",
+ url: docsUrl + "user/manual/en/setting-up/setting-up-taxes",
+ },
+];
-frappe.help.help_links['sales-taxes-and-charges-template'] = [
- { label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' },
-]
+frappe.help.help_links["Form/Sales Taxes and Charges Template"] = [
+ {
+ label: "Setting Up Taxes",
+ url: docsUrl + "user/manual/en/setting-up/setting-up-taxes",
+ },
+];
-frappe.help.help_links['sales-order'] = [
- { label: 'Sales Order', url: docsUrl + 'user/manual/en/selling/sales-order' },
- { label: 'Recurring Sales Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' },
- { label: 'Applying Discount', url: docsUrl + 'user/manual/en/selling/articles/applying-discount' },
- { label: 'Drop Shipping', url: docsUrl + 'user/manual/en/selling/articles/drop-shipping' },
- { label: 'Sales Person', url: docsUrl + 'user/manual/en/selling/articles/sales-persons-in-the-sales-transactions' },
- { label: 'Close Sales Order', url: docsUrl + 'user/manual/en/selling/articles/close-sales-order' },
- { label: 'Applying Margin', url: docsUrl + 'user/manual/en/selling/articles/adding-margin' },
-]
+frappe.help.help_links["List/Sales Order"] = [
+ {
+ label: "Sales Order",
+ url: docsUrl + "user/manual/en/selling/sales-order",
+ },
+ {
+ label: "Recurring Sales Order",
+ url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+ },
+ {
+ label: "Applying Discount",
+ url: docsUrl + "user/manual/en/selling/articles/applying-discount",
+ },
+];
-frappe.help.help_links['product-bundle'] = [
- { label: 'Product Bundle', url: docsUrl + 'user/manual/en/selling/setup/product-bundle' },
-]
+frappe.help.help_links["Form/Sales Order"] = [
+ {
+ label: "Sales Order",
+ url: docsUrl + "user/manual/en/selling/sales-order",
+ },
+ {
+ label: "Recurring Sales Order",
+ url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+ },
+ {
+ label: "Applying Discount",
+ url: docsUrl + "user/manual/en/selling/articles/applying-discount",
+ },
+ {
+ label: "Drop Shipping",
+ url: docsUrl + "user/manual/en/selling/articles/drop-shipping",
+ },
+ {
+ label: "Sales Person",
+ url:
+ docsUrl +
+ "user/manual/en/selling/articles/sales-persons-in-the-sales-transactions",
+ },
+ {
+ label: "Close Sales Order",
+ url: docsUrl + "user/manual/en/selling/articles/close-sales-order",
+ },
+ {
+ label: "Applying Margin",
+ url: docsUrl + "user/manual/en/selling/articles/adding-margin",
+ },
+];
-frappe.help.help_links['selling-settings'] = [
- { label: 'Selling Settings', url: docsUrl + 'user/manual/en/selling/setup/selling-settings' },
-]
+frappe.help.help_links["Form/Product Bundle"] = [
+ {
+ label: "Product Bundle",
+ url: docsUrl + "user/manual/en/selling/setup/product-bundle",
+ },
+];
+
+frappe.help.help_links["Form/Selling Settings"] = [
+ {
+ label: "Selling Settings",
+ url: docsUrl + "user/manual/en/selling/setup/selling-settings",
+ },
+];
//Buying
-frappe.help.help_links['supplier'] = [
- { label: 'Supplier', url: docsUrl + 'user/manual/en/buying/supplier' },
-]
+frappe.help.help_links["List/Supplier"] = [
+ { label: "Supplier", url: docsUrl + "user/manual/en/buying/supplier" },
+];
-frappe.help.help_links['request-for-quotation'] = [
- { label: 'Request for Quotation', url: docsUrl + 'user/manual/en/buying/request-for-quotation' },
- { label: 'RFQ Video', url: docsUrl + 'user/videos/learn/request-for-quotation.html' },
-]
+frappe.help.help_links["Form/Supplier"] = [
+ { label: "Supplier", url: docsUrl + "user/manual/en/buying/supplier" },
+];
-frappe.help.help_links['supplier-quotation'] = [
- { label: 'Supplier Quotation', url: docsUrl + 'user/manual/en/buying/supplier-quotation' },
-]
+frappe.help.help_links["Form/Request for Quotation"] = [
+ {
+ label: "Request for Quotation",
+ url: docsUrl + "user/manual/en/buying/request-for-quotation",
+ },
+ {
+ label: "RFQ Video",
+ url: docsUrl + "user/videos/learn/request-for-quotation.html",
+ },
+];
-frappe.help.help_links['buying-settings'] = [
- { label: 'Buying Settings', url: docsUrl + 'user/manual/en/buying/setup/buying-settings' },
-]
+frappe.help.help_links["Form/Supplier Quotation"] = [
+ {
+ label: "Supplier Quotation",
+ url: docsUrl + "user/manual/en/buying/supplier-quotation",
+ },
+];
-frappe.help.help_links['purchase-order'] = [
- { label: 'Purchase Order', url: docsUrl + 'user/manual/en/buying/purchase-order' },
- { label: 'Item UoM', url: docsUrl + 'user/manual/en/buying/articles/purchasing-in-different-unit' },
- { label: 'Supplier Item Code', url: docsUrl + 'user/manual/en/buying/articles/maintaining-suppliers-part-no-in-item' },
- { label: 'Recurring Purchase Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' },
- { label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' },
-]
+frappe.help.help_links["Form/Buying Settings"] = [
+ {
+ label: "Buying Settings",
+ url: docsUrl + "user/manual/en/buying/setup/buying-settings",
+ },
+];
-frappe.help.help_links['purchase-taxes-and-charges-template'] = [
- { label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' },
-]
+frappe.help.help_links["List/Purchase Order"] = [
+ {
+ label: "Purchase Order",
+ url: docsUrl + "user/manual/en/buying/purchase-order",
+ },
+ {
+ label: "Recurring Purchase Order",
+ url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+ },
+];
-frappe.help.help_links['pos-profile'] = [
- { label: 'POS Profile', url: docsUrl + 'user/manual/en/setting-up/pos-setting' },
-]
+frappe.help.help_links["Form/Purchase Order"] = [
+ {
+ label: "Purchase Order",
+ url: docsUrl + "user/manual/en/buying/purchase-order",
+ },
+ {
+ label: "Item UoM",
+ url:
+ docsUrl +
+ "user/manual/en/buying/articles/purchasing-in-different-unit",
+ },
+ {
+ label: "Supplier Item Code",
+ url:
+ docsUrl +
+ "user/manual/en/buying/articles/maintaining-suppliers-part-no-in-item",
+ },
+ {
+ label: "Recurring Purchase Order",
+ url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+ },
+ {
+ label: "Subcontracting",
+ url: docsUrl + "user/manual/en/manufacturing/subcontracting",
+ },
+];
-frappe.help.help_links['price-list'] = [
- { label: 'Price List', url: docsUrl + 'user/manual/en/setting-up/price-lists' },
-]
+frappe.help.help_links["List/Purchase Taxes and Charges Template"] = [
+ {
+ label: "Setting Up Taxes",
+ url: docsUrl + "user/manual/en/setting-up/setting-up-taxes",
+ },
+];
-frappe.help.help_links['authorization-rule'] = [
- { label: 'Authorization Rule', url: docsUrl + 'user/manual/en/setting-up/authorization-rule' },
-]
+frappe.help.help_links["List/POS Profile"] = [
+ {
+ label: "POS Profile",
+ url: docsUrl + "user/manual/en/setting-up/pos-setting",
+ },
+];
-frappe.help.help_links['sms-settings'] = [
- { label: 'SMS Settings', url: docsUrl + 'user/manual/en/setting-up/sms-setting' },
-]
+frappe.help.help_links["List/Price List"] = [
+ {
+ label: "Price List",
+ url: docsUrl + "user/manual/en/setting-up/price-lists",
+ },
+];
-frappe.help.help_links['stock-reconciliation'] = [
- { label: 'Stock Reconciliation', url: docsUrl + 'user/manual/en/setting-up/stock-reconciliation-for-non-serialized-item' },
-]
+frappe.help.help_links["List/Authorization Rule"] = [
+ {
+ label: "Authorization Rule",
+ url: docsUrl + "user/manual/en/setting-up/authorization-rule",
+ },
+];
-frappe.help.help_links['territory/view/tree'] = [
- { label: 'Territory', url: docsUrl + 'user/manual/en/setting-up/territory' },
-]
+frappe.help.help_links["Form/SMS Settings"] = [
+ {
+ label: "SMS Settings",
+ url: docsUrl + "user/manual/en/setting-up/sms-setting",
+ },
+];
-frappe.help.help_links['dropbox-backup'] = [
- { label: 'Dropbox Backup', url: docsUrl + 'user/manual/en/setting-up/third-party-backups' },
- { label: 'Setting Up Dropbox Backup', url: docsUrl + 'user/manual/en/setting-up/articles/setting-up-dropbox-backups' },
-]
+frappe.help.help_links["List/Stock Reconciliation"] = [
+ {
+ label: "Stock Reconciliation",
+ url:
+ docsUrl +
+ "user/manual/en/setting-up/stock-reconciliation-for-non-serialized-item",
+ },
+];
-frappe.help.help_links['workflow'] = [
- { label: 'Workflow', url: docsUrl + 'user/manual/en/setting-up/workflows' },
-]
+frappe.help.help_links["Tree/Territory"] = [
+ {
+ label: "Territory",
+ url: docsUrl + "user/manual/en/setting-up/territory",
+ },
+];
-frappe.help.help_links['company'] = [
- { label: 'Company', url: docsUrl + 'user/manual/en/setting-up/company-setup' },
- { label: 'Managing Multiple Companies', url: docsUrl + 'user/manual/en/setting-up/articles/managing-multiple-companies' },
- { label: 'Delete All Related Transactions for a Company', url: docsUrl + 'user/manual/en/setting-up/articles/delete-a-company-and-all-related-transactions' },
-]
+frappe.help.help_links["Form/Dropbox Backup"] = [
+ {
+ label: "Dropbox Backup",
+ url: docsUrl + "user/manual/en/setting-up/third-party-backups",
+ },
+ {
+ label: "Setting Up Dropbox Backup",
+ url:
+ docsUrl +
+ "user/manual/en/setting-up/articles/setting-up-dropbox-backups",
+ },
+];
+
+frappe.help.help_links["List/Workflow"] = [
+ { label: "Workflow", url: docsUrl + "user/manual/en/setting-up/workflows" },
+];
+
+frappe.help.help_links["List/Company"] = [
+ {
+ label: "Company",
+ url: docsUrl + "user/manual/en/setting-up/company-setup",
+ },
+ {
+ label: "Managing Multiple Companies",
+ url:
+ docsUrl +
+ "user/manual/en/setting-up/articles/managing-multiple-companies",
+ },
+ {
+ label: "Delete All Related Transactions for a Company",
+ url:
+ docsUrl +
+ "user/manual/en/setting-up/articles/delete-a-company-and-all-related-transactions",
+ },
+];
//Accounts
-frappe.help.help_links['accounts'] = [
- { label: 'Introduction to Accounts', url: docsUrl + 'user/manual/en/accounts/' },
- { label: 'Chart of Accounts', url: docsUrl + 'user/manual/en/accounts/chart-of-accounts.html' },
- { label: 'Multi Currency Accounting', url: docsUrl + 'user/manual/en/accounts/multi-currency-accounting' },
-]
+frappe.help.help_links["modules/Accounts"] = [
+ {
+ label: "Introduction to Accounts",
+ url: docsUrl + "user/manual/en/accounts/",
+ },
+ {
+ label: "Chart of Accounts",
+ url: docsUrl + "user/manual/en/accounts/chart-of-accounts.html",
+ },
+ {
+ label: "Multi Currency Accounting",
+ url: docsUrl + "user/manual/en/accounts/multi-currency-accounting",
+ },
+];
-frappe.help.help_links['account/view/tree'] = [
- { label: 'Chart of Accounts', url: docsUrl + 'user/manual/en/accounts/chart-of-accounts' },
- { label: 'Managing Tree Mastes', url: docsUrl + 'user/manual/en/setting-up/articles/managing-tree-structure-masters' },
-]
+frappe.help.help_links["Tree/Account"] = [
+ {
+ label: "Chart of Accounts",
+ url: docsUrl + "user/manual/en/accounts/chart-of-accounts",
+ },
+ {
+ label: "Managing Tree Mastes",
+ url:
+ docsUrl +
+ "user/manual/en/setting-up/articles/managing-tree-structure-masters",
+ },
+];
-frappe.help.help_links['sales-invoice'] = [
- { label: 'Sales Invoice', url: docsUrl + 'user/manual/en/accounts/sales-invoice' },
- { label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' },
- { label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' },
- { label: 'Recurring Sales Invoice', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' },
-]
+frappe.help.help_links["Form/Sales Invoice"] = [
+ {
+ label: "Sales Invoice",
+ url: docsUrl + "user/manual/en/accounts/sales-invoice",
+ },
+ {
+ label: "Accounts Opening Balance",
+ url: docsUrl + "user/manual/en/accounts/opening-accounts",
+ },
+ {
+ label: "Sales Return",
+ url: docsUrl + "user/manual/en/stock/sales-return",
+ },
+ {
+ label: "Recurring Sales Invoice",
+ url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+ },
+];
-frappe.help.help_links['sales-invoice'] = [
- { label: 'Sales Invoice', url: docsUrl + 'user/manual/en/accounts/sales-invoice' },
- { label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' },
- { label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' },
- { label: 'Recurring Sales Invoice', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' },
-]
+frappe.help.help_links["List/Sales Invoice"] = [
+ {
+ label: "Sales Invoice",
+ url: docsUrl + "user/manual/en/accounts/sales-invoice",
+ },
+ {
+ label: "Accounts Opening Balance",
+ url: docsUrl + "user/manual/en/accounts/opening-accounts",
+ },
+ {
+ label: "Sales Return",
+ url: docsUrl + "user/manual/en/stock/sales-return",
+ },
+ {
+ label: "Recurring Sales Invoice",
+ url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+ },
+];
-frappe.help.help_links['pos'] = [
- { label: 'Point of Sale Invoice', url: docsUrl + 'user/manual/en/accounts/point-of-sale-pos-invoice' },
-]
+frappe.help.help_links["pos"] = [
+ {
+ label: "Point of Sale Invoice",
+ url: docsUrl + "user/manual/en/accounts/point-of-sale-pos-invoice",
+ },
+];
-frappe.help.help_links['pos-profile'] = [
- { label: 'Point of Sale Profile', url: docsUrl + 'user/manual/en/setting-up/pos-setting' },
-]
+frappe.help.help_links["List/POS Profile"] = [
+ {
+ label: "Point of Sale Profile",
+ url: docsUrl + "user/manual/en/setting-up/pos-setting",
+ },
+];
-frappe.help.help_links['purchase-invoice'] = [
- { label: 'Purchase Invoice', url: docsUrl + 'user/manual/en/accounts/purchase-invoice' },
- { label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' },
- { label: 'Recurring Purchase Invoice', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' },
-]
+frappe.help.help_links["List/Purchase Invoice"] = [
+ {
+ label: "Purchase Invoice",
+ url: docsUrl + "user/manual/en/accounts/purchase-invoice",
+ },
+ {
+ label: "Accounts Opening Balance",
+ url: docsUrl + "user/manual/en/accounts/opening-accounts",
+ },
+ {
+ label: "Recurring Purchase Invoice",
+ url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+ },
+];
-frappe.help.help_links['journal-entry'] = [
- { label: 'Journal Entry', url: docsUrl + 'user/manual/en/accounts/journal-entry' },
- { label: 'Advance Payment Entry', url: docsUrl + 'user/manual/en/accounts/advance-payment-entry' },
- { label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' },
-]
+frappe.help.help_links["List/Journal Entry"] = [
+ {
+ label: "Journal Entry",
+ url: docsUrl + "user/manual/en/accounts/journal-entry",
+ },
+ {
+ label: "Advance Payment Entry",
+ url: docsUrl + "user/manual/en/accounts/advance-payment-entry",
+ },
+ {
+ label: "Accounts Opening Balance",
+ url: docsUrl + "user/manual/en/accounts/opening-accounts",
+ },
+];
-frappe.help.help_links['payment-entry'] = [
- { label: 'Payment Entry', url: docsUrl + 'user/manual/en/accounts/payment-entry' },
-]
+frappe.help.help_links["List/Payment Entry"] = [
+ {
+ label: "Payment Entry",
+ url: docsUrl + "user/manual/en/accounts/payment-entry",
+ },
+];
-frappe.help.help_links['payment-request'] = [
- { label: 'Payment Request', url: docsUrl + 'user/manual/en/accounts/payment-request' },
-]
+frappe.help.help_links["List/Payment Request"] = [
+ {
+ label: "Payment Request",
+ url: docsUrl + "user/manual/en/accounts/payment-request",
+ },
+];
-frappe.help.help_links['asset'] = [
- { label: 'Managing Fixed Assets', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' },
-]
+frappe.help.help_links["List/Asset"] = [
+ {
+ label: "Managing Fixed Assets",
+ url: docsUrl + "user/manual/en/accounts/managing-fixed-assets",
+ },
+];
-frappe.help.help_links['asset-category'] = [
- { label: 'Asset Category', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' },
-]
+frappe.help.help_links["List/Asset Category"] = [
+ {
+ label: "Asset Category",
+ url: docsUrl + "user/manual/en/accounts/managing-fixed-assets",
+ },
+];
-frappe.help.help_links['cost-center/view/tree'] = [
- { label: 'Budgeting', url: docsUrl + 'user/manual/en/accounts/budgeting' },
-]
+frappe.help.help_links["Tree/Cost Center"] = [
+ { label: "Budgeting", url: docsUrl + "user/manual/en/accounts/budgeting" },
+];
-frappe.help.help_links['item'] = [
- { label: 'Item', url: docsUrl + 'user/manual/en/stock/item' },
- { label: 'Item Price', url: docsUrl + 'user/manual/en/stock/item/item-price' },
- { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' },
- { label: 'Item Wise Taxation', url: docsUrl + 'user/manual/en/accounts/item-wise-taxation' },
- { label: 'Managing Fixed Assets', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' },
- { label: 'Item Codification', url: docsUrl + 'user/manual/en/stock/item/item-codification' },
- { label: 'Item Variants', url: docsUrl + 'user/manual/en/stock/item/item-variants' },
- { label: 'Item Valuation', url: docsUrl + 'user/manual/en/stock/item/item-valuation-fifo-and-moving-average' },
-]
+frappe.help.help_links["List/Item"] = [
+ { label: "Item", url: docsUrl + "user/manual/en/stock/item" },
+ {
+ label: "Item Price",
+ url: docsUrl + "user/manual/en/stock/item/item-price",
+ },
+ {
+ label: "Barcode",
+ url:
+ docsUrl + "user/manual/en/stock/articles/track-items-using-barcode",
+ },
+ {
+ label: "Item Wise Taxation",
+ url: docsUrl + "user/manual/en/accounts/item-wise-taxation",
+ },
+ {
+ label: "Managing Fixed Assets",
+ url: docsUrl + "user/manual/en/accounts/managing-fixed-assets",
+ },
+ {
+ label: "Item Codification",
+ url: docsUrl + "user/manual/en/stock/item/item-codification",
+ },
+ {
+ label: "Item Variants",
+ url: docsUrl + "user/manual/en/stock/item/item-variants",
+ },
+ {
+ label: "Item Valuation",
+ url:
+ docsUrl +
+ "user/manual/en/stock/item/item-valuation-fifo-and-moving-average",
+ },
+];
-frappe.help.help_links['purchase-receipt'] = [
- { label: 'Purchase Receipt', url: docsUrl + 'user/manual/en/stock/purchase-receipt' },
- { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' },
-]
+frappe.help.help_links["Form/Item"] = [
+ { label: "Item", url: docsUrl + "user/manual/en/stock/item" },
+ {
+ label: "Item Price",
+ url: docsUrl + "user/manual/en/stock/item/item-price",
+ },
+ {
+ label: "Barcode",
+ url:
+ docsUrl + "user/manual/en/stock/articles/track-items-using-barcode",
+ },
+ {
+ label: "Item Wise Taxation",
+ url: docsUrl + "user/manual/en/accounts/item-wise-taxation",
+ },
+ {
+ label: "Managing Fixed Assets",
+ url: docsUrl + "user/manual/en/accounts/managing-fixed-assets",
+ },
+ {
+ label: "Item Codification",
+ url: docsUrl + "user/manual/en/stock/item/item-codification",
+ },
+ {
+ label: "Item Variants",
+ url: docsUrl + "user/manual/en/stock/item/item-variants",
+ },
+ {
+ label: "Item Valuation",
+ url:
+ docsUrl +
+ "user/manual/en/stock/item/item-valuation-fifo-and-moving-average",
+ },
+];
-frappe.help.help_links['delivery-note'] = [
- { label: 'Delivery Note', url: docsUrl + 'user/manual/en/stock/delivery-note' },
- { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' },
- { label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' },
-]
+frappe.help.help_links["List/Purchase Receipt"] = [
+ {
+ label: "Purchase Receipt",
+ url: docsUrl + "user/manual/en/stock/purchase-receipt",
+ },
+ {
+ label: "Barcode",
+ url:
+ docsUrl + "user/manual/en/stock/articles/track-items-using-barcode",
+ },
+];
-frappe.help.help_links['delivery-note'] = [
- { label: 'Delivery Note', url: docsUrl + 'user/manual/en/stock/delivery-note' },
- { label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' },
- { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' },
- { label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' },
-]
+frappe.help.help_links["List/Delivery Note"] = [
+ {
+ label: "Delivery Note",
+ url: docsUrl + "user/manual/en/stock/delivery-note",
+ },
+ {
+ label: "Barcode",
+ url:
+ docsUrl + "user/manual/en/stock/articles/track-items-using-barcode",
+ },
+ {
+ label: "Sales Return",
+ url: docsUrl + "user/manual/en/stock/sales-return",
+ },
+];
-frappe.help.help_links['installation-note'] = [
- { label: 'Installation Note', url: docsUrl + 'user/manual/en/stock/installation-note' },
-]
+frappe.help.help_links["Form/Delivery Note"] = [
+ {
+ label: "Delivery Note",
+ url: docsUrl + "user/manual/en/stock/delivery-note",
+ },
+ {
+ label: "Sales Return",
+ url: docsUrl + "user/manual/en/stock/sales-return",
+ },
+ {
+ label: "Barcode",
+ url:
+ docsUrl + "user/manual/en/stock/articles/track-items-using-barcode",
+ },
+ {
+ label: "Subcontracting",
+ url: docsUrl + "user/manual/en/manufacturing/subcontracting",
+ },
+];
+frappe.help.help_links["List/Installation Note"] = [
+ {
+ label: "Installation Note",
+ url: docsUrl + "user/manual/en/stock/installation-note",
+ },
+];
+frappe.help.help_links["Tree"] = [
+ {
+ label: "Managing Tree Structure Masters",
+ url:
+ docsUrl +
+ "user/manual/en/setting-up/articles/managing-tree-structure-masters",
+ },
+];
-frappe.help.help_links['budget'] = [
- { label: 'Budgeting', url: docsUrl + 'user/manual/en/accounts/budgeting' },
-]
+frappe.help.help_links["List/Budget"] = [
+ { label: "Budgeting", url: docsUrl + "user/manual/en/accounts/budgeting" },
+];
//Stock
-frappe.help.help_links['material-request'] = [
- { label: 'Material Request', url: docsUrl + 'user/manual/en/stock/material-request' },
- { label: 'Auto-creation of Material Request', url: docsUrl + 'user/manual/en/stock/articles/auto-creation-of-material-request' },
-]
+frappe.help.help_links["List/Material Request"] = [
+ {
+ label: "Material Request",
+ url: docsUrl + "user/manual/en/stock/material-request",
+ },
+ {
+ label: "Auto-creation of Material Request",
+ url:
+ docsUrl +
+ "user/manual/en/stock/articles/auto-creation-of-material-request",
+ },
+];
-frappe.help.help_links['stock-entry'] = [
- { label: 'Stock Entry', url: docsUrl + 'user/manual/en/stock/stock-entry' },
- { label: 'Stock Entry Types', url: docsUrl + 'user/manual/en/stock/articles/stock-entry-purpose' },
- { label: 'Repack Entry', url: docsUrl + 'user/manual/en/stock/articles/repack-entry' },
- { label: 'Opening Stock', url: docsUrl + 'user/manual/en/stock/opening-stock' },
- { label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' },
-]
+frappe.help.help_links["Form/Material Request"] = [
+ {
+ label: "Material Request",
+ url: docsUrl + "user/manual/en/stock/material-request",
+ },
+ {
+ label: "Auto-creation of Material Request",
+ url:
+ docsUrl +
+ "user/manual/en/stock/articles/auto-creation-of-material-request",
+ },
+];
-frappe.help.help_links['warehouse/view/tree'] = [
- { label: 'Warehouse', url: docsUrl + 'user/manual/en/stock/warehouse' },
-]
+frappe.help.help_links["Form/Stock Entry"] = [
+ { label: "Stock Entry", url: docsUrl + "user/manual/en/stock/stock-entry" },
+ {
+ label: "Stock Entry Types",
+ url: docsUrl + "user/manual/en/stock/articles/stock-entry-purpose",
+ },
+ {
+ label: "Repack Entry",
+ url: docsUrl + "user/manual/en/stock/articles/repack-entry",
+ },
+ {
+ label: "Opening Stock",
+ url: docsUrl + "user/manual/en/stock/opening-stock",
+ },
+ {
+ label: "Subcontracting",
+ url: docsUrl + "user/manual/en/manufacturing/subcontracting",
+ },
+];
-frappe.help.help_links['serial-no'] = [
- { label: 'Serial No', url: docsUrl + 'user/manual/en/stock/serial-no' },
-]
+frappe.help.help_links["List/Stock Entry"] = [
+ { label: "Stock Entry", url: docsUrl + "user/manual/en/stock/stock-entry" },
+];
-frappe.help.help_links['batch'] = [
- { label: 'Batch', url: docsUrl + 'user/manual/en/stock/batch' },
-]
+frappe.help.help_links["Tree/Warehouse"] = [
+ { label: "Warehouse", url: docsUrl + "user/manual/en/stock/warehouse" },
+];
-frappe.help.help_links['packing-slip'] = [
- { label: 'Packing Slip', url: docsUrl + 'user/manual/en/stock/tools/packing-slip' },
-]
+frappe.help.help_links["List/Serial No"] = [
+ { label: "Serial No", url: docsUrl + "user/manual/en/stock/serial-no" },
+];
-frappe.help.help_links['quality-inspection'] = [
- { label: 'Quality Inspection', url: docsUrl + 'user/manual/en/stock/tools/quality-inspection' },
-]
+frappe.help.help_links["Form/Serial No"] = [
+ { label: "Serial No", url: docsUrl + "user/manual/en/stock/serial-no" },
+];
-frappe.help.help_links['landed-cost-voucher'] = [
- { label: 'Landed Cost Voucher', url: docsUrl + 'user/manual/en/stock/tools/landed-cost-voucher' },
-]
+frappe.help.help_links["Form/Batch"] = [
+ { label: "Batch", url: docsUrl + "user/manual/en/stock/batch" },
+];
-frappe.help.help_links['item-group/view/tree'] = [
- { label: 'Item Group', url: docsUrl + 'user/manual/en/stock/setup/item-group' },
-]
+frappe.help.help_links["Form/Packing Slip"] = [
+ {
+ label: "Packing Slip",
+ url: docsUrl + "user/manual/en/stock/tools/packing-slip",
+ },
+];
-frappe.help.help_links['item-attribute'] = [
- { label: 'Item Attribute', url: docsUrl + 'user/manual/en/stock/setup/item-attribute' },
-]
+frappe.help.help_links["Form/Quality Inspection"] = [
+ {
+ label: "Quality Inspection",
+ url: docsUrl + "user/manual/en/stock/tools/quality-inspection",
+ },
+];
-frappe.help.help_links['uom'] = [
- { label: 'Fractions in UOM', url: docsUrl + 'user/manual/en/stock/articles/managing-fractions-in-uom' },
-]
+frappe.help.help_links["Form/Landed Cost Voucher"] = [
+ {
+ label: "Landed Cost Voucher",
+ url: docsUrl + "user/manual/en/stock/tools/landed-cost-voucher",
+ },
+];
-frappe.help.help_links['stock-reconciliation'] = [
- { label: 'Opening Stock Entry', url: docsUrl + 'user/manual/en/stock/opening-stock' },
-]
+frappe.help.help_links["Tree/Item Group"] = [
+ {
+ label: "Item Group",
+ url: docsUrl + "user/manual/en/stock/setup/item-group",
+ },
+];
+
+frappe.help.help_links["Form/Item Attribute"] = [
+ {
+ label: "Item Attribute",
+ url: docsUrl + "user/manual/en/stock/setup/item-attribute",
+ },
+];
+
+frappe.help.help_links["Form/UOM"] = [
+ {
+ label: "Fractions in UOM",
+ url:
+ docsUrl + "user/manual/en/stock/articles/managing-fractions-in-uom",
+ },
+];
+
+frappe.help.help_links["Form/Stock Reconciliation"] = [
+ {
+ label: "Opening Stock Entry",
+ url: docsUrl + "user/manual/en/stock/opening-stock",
+ },
+];
//CRM
-frappe.help.help_links['lead'] = [
- { label: 'Lead', url: docsUrl + 'user/manual/en/CRM/lead' },
-]
+frappe.help.help_links["Form/Lead"] = [
+ { label: "Lead", url: docsUrl + "user/manual/en/CRM/lead" },
+];
-frappe.help.help_links['opportunity'] = [
- { label: 'Opportunity', url: docsUrl + 'user/manual/en/CRM/opportunity' },
-]
+frappe.help.help_links["Form/Opportunity"] = [
+ { label: "Opportunity", url: docsUrl + "user/manual/en/CRM/opportunity" },
+];
-frappe.help.help_links['address'] = [
- { label: 'Address', url: docsUrl + 'user/manual/en/CRM/address' },
-]
+frappe.help.help_links["Form/Address"] = [
+ { label: "Address", url: docsUrl + "user/manual/en/CRM/address" },
+];
-frappe.help.help_links['contact'] = [
- { label: 'Contact', url: docsUrl + 'user/manual/en/CRM/contact' },
-]
+frappe.help.help_links["Form/Contact"] = [
+ { label: "Contact", url: docsUrl + "user/manual/en/CRM/contact" },
+];
-frappe.help.help_links['newsletter'] = [
- { label: 'Newsletter', url: docsUrl + 'user/manual/en/CRM/newsletter' },
-]
+frappe.help.help_links["Form/Newsletter"] = [
+ { label: "Newsletter", url: docsUrl + "user/manual/en/CRM/newsletter" },
+];
-frappe.help.help_links['campaign'] = [
- { label: 'Campaign', url: docsUrl + 'user/manual/en/CRM/setup/campaign' },
-]
+frappe.help.help_links["Form/Campaign"] = [
+ { label: "Campaign", url: docsUrl + "user/manual/en/CRM/setup/campaign" },
+];
-frappe.help.help_links['sales-person/view/tree'] = [
- { label: 'Sales Person', url: docsUrl + 'user/manual/en/CRM/setup/sales-person' },
-]
+frappe.help.help_links["Tree/Sales Person"] = [
+ {
+ label: "Sales Person",
+ url: docsUrl + "user/manual/en/CRM/setup/sales-person",
+ },
+];
-frappe.help.help_links['sales-person'] = [
- { label: 'Sales Person Target', url: docsUrl + 'user/manual/en/selling/setup/sales-person-target-allocation' },
-]
+frappe.help.help_links["Form/Sales Person"] = [
+ {
+ label: "Sales Person Target",
+ url:
+ docsUrl +
+ "user/manual/en/selling/setup/sales-person-target-allocation",
+ },
+];
+
+//Support
+
+frappe.help.help_links["List/Feedback Trigger"] = [
+ {
+ label: "Feedback Trigger",
+ url: docsUrl + "user/manual/en/setting-up/feedback/setting-up-feedback",
+ },
+];
+
+frappe.help.help_links["List/Feedback Request"] = [
+ {
+ label: "Feedback Request",
+ url: docsUrl + "user/manual/en/setting-up/feedback/submit-feedback",
+ },
+];
+
+frappe.help.help_links["List/Feedback Request"] = [
+ {
+ label: "Feedback Request",
+ url: docsUrl + "user/manual/en/setting-up/feedback/submit-feedback",
+ },
+];
//Manufacturing
-frappe.help.help_links['bom'] = [
- { label: 'Bill of Material', url: docsUrl + 'user/manual/en/manufacturing/bill-of-materials' },
- { label: 'Nested BOM Structure', url: docsUrl + 'user/manual/en/manufacturing/articles/nested-bom-structure' },
-]
+frappe.help.help_links["Form/BOM"] = [
+ {
+ label: "Bill of Material",
+ url: docsUrl + "user/manual/en/manufacturing/bill-of-materials",
+ },
+ {
+ label: "Nested BOM Structure",
+ url:
+ docsUrl +
+ "user/manual/en/manufacturing/articles/nested-bom-structure",
+ },
+];
-frappe.help.help_links['work-order'] = [
- { label: 'Work Order', url: docsUrl + 'user/manual/en/manufacturing/work-order' },
-]
+frappe.help.help_links["Form/Work Order"] = [
+ {
+ label: "Work Order",
+ url: docsUrl + "user/manual/en/manufacturing/work-order",
+ },
+];
-frappe.help.help_links['workstation'] = [
- { label: 'Workstation', url: docsUrl + 'user/manual/en/manufacturing/workstation' },
-]
+frappe.help.help_links["Form/Workstation"] = [
+ {
+ label: "Workstation",
+ url: docsUrl + "user/manual/en/manufacturing/workstation",
+ },
+];
-frappe.help.help_links['operation'] = [
- { label: 'Operation', url: docsUrl + 'user/manual/en/manufacturing/operation' },
-]
+frappe.help.help_links["Form/Operation"] = [
+ {
+ label: "Operation",
+ url: docsUrl + "user/manual/en/manufacturing/operation",
+ },
+];
-frappe.help.help_links['bom-update-tool'] = [
- { label: 'BOM Update Tool', url: docsUrl + 'user/manual/en/manufacturing/tools/bom-update-tool' },
-]
+frappe.help.help_links["Form/BOM Update Tool"] = [
+ {
+ label: "BOM Update Tool",
+ url: docsUrl + "user/manual/en/manufacturing/tools/bom-update-tool",
+ },
+];
//Customize
-frappe.help.help_links['customize-form'] = [
- { label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' },
- { label: 'Customize Field', url: docsUrl + 'user/manual/en/customize-erpnext/customize-form' },
-]
+frappe.help.help_links["Form/Customize Form"] = [
+ {
+ label: "Custom Field",
+ url: docsUrl + "user/manual/en/customize-erpnext/custom-field",
+ },
+ {
+ label: "Customize Field",
+ url: docsUrl + "user/manual/en/customize-erpnext/customize-form",
+ },
+];
-frappe.help.help_links['custom-field'] = [
- { label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' },
-]
+frappe.help.help_links["Form/Custom Field"] = [
+ {
+ label: "Custom Field",
+ url: docsUrl + "user/manual/en/customize-erpnext/custom-field",
+ },
+];
-frappe.help.help_links['custom-field'] = [
- { label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' },
-]
+frappe.help.help_links["Form/Custom Field"] = [
+ {
+ label: "Custom Field",
+ url: docsUrl + "user/manual/en/customize-erpnext/custom-field",
+ },
+];
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index e5b50d8..19c9073 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -291,17 +291,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 +712,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
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index d44c708..79b6220 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -353,9 +353,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) {
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/quality_management/doctype/quality_feedback/quality_feedback.py b/erpnext/quality_management/doctype/quality_feedback/quality_feedback.py
index bf82cc0..5a8ec73 100644
--- a/erpnext/quality_management/doctype/quality_feedback/quality_feedback.py
+++ b/erpnext/quality_management/doctype/quality_feedback/quality_feedback.py
@@ -7,6 +7,7 @@
from frappe.model.document import Document
class QualityFeedback(Document):
+ @frappe.whitelist()
def set_parameters(self):
if self.template and not getattr(self, 'parameters', []):
for d in frappe.get_doc('Quality Feedback Template', self.template).parameters:
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 888b2da..369a400 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html
@@ -109,7 +109,7 @@
</td>
</tr>
<tr>
- <td>{{__("Suppliies made to Composition Taxable Persons")}}</td>
+ <td>{{__("Supplies made to Composition Taxable Persons")}}</td>
<td class="right">
{% for row in data.inter_sup.comp_details %}
{% if row %}
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 a49996d..a5dd5a2 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
@@ -172,7 +172,6 @@
self.json_output = frappe.as_json(self.report_dict)
def set_inward_nil_exempt(self, inward_nil_exempt):
-
self.report_dict["inward_sup"]["isup_details"][0]["inter"] = flt(inward_nil_exempt.get("gst").get("inter"), 2)
self.report_dict["inward_sup"]["isup_details"][0]["intra"] = flt(inward_nil_exempt.get("gst").get("intra"), 2)
self.report_dict["inward_sup"]["isup_details"][1]["inter"] = flt(inward_nil_exempt.get("non_gst").get("inter"), 2)
@@ -238,7 +237,6 @@
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):
@@ -352,10 +350,18 @@
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.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)
+
inward_nil_exempt_details = {
"gst": {
"intra": 0.0,
@@ -369,9 +375,11 @@
for d in inward_nil_exempt:
if d.place_of_supply:
- if d.is_nil_exempt == 1 and state == d.place_of_supply.split("-")[1]:
+ if (d.is_nil_exempt == 1 or d.get('gst_category') == 'Registered Composition') \
+ and state == d.place_of_supply.split("-")[1]:
inward_nil_exempt_details["gst"]["intra"] += d.base_amount
- elif d.is_nil_exempt == 1 and state != d.place_of_supply.split("-")[1]:
+ elif (d.is_nil_exempt == 1 or d.get('gst_category') == 'Registered Composition') \
+ and state != d.place_of_supply.split("-")[1]:
inward_nil_exempt_details["gst"]["inter"] += d.base_amount
elif d.is_non_gst == 1 and state == d.place_of_supply.split("-")[1]:
inward_nil_exempt_details["non_gst"]["intra"] += d.base_amount
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 023b4ed..ef8af24 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
@@ -64,7 +64,7 @@
self.assertEqual(output["sup_details"]["osup_zero"]["iamt"], 18),
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]["inter"], 250)
+ self.assertEqual(output["inward_sup"]["isup_details"][0]["intra"], 250)
self.assertEqual(output["itc_elg"]["itc_avl"][4]["samt"], 22.50)
self.assertEqual(output["itc_elg"]["itc_avl"][4]["camt"], 22.50)
@@ -228,6 +228,19 @@
pi1.submit()
+ pi2 = make_purchase_invoice(company="_Test Company GST",
+ customer = '_Test Registered Supplier',
+ currency = 'INR',
+ item = 'Milk',
+ warehouse = 'Finished Goods - _GST',
+ expense_account = 'Cost of Goods Sold - _GST',
+ cost_center = 'Main - _GST',
+ rate=250,
+ qty=1,
+ do_not_save=1
+ )
+ pi2.submit()
+
def make_suppliers():
if not frappe.db.exists("Supplier", "_Test Registered Supplier"):
frappe.get_doc({
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/tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py
index 5bbd575..41a0f11 100644
--- a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py
+++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py
@@ -50,6 +50,7 @@
frappe.throw(_('Please set the {0} for company {1}').format(frappe.bold('PAN Number'),
get_link_to_form('Company', self.company)))
+ @frappe.whitelist()
def set_company_address(self):
address = get_company_address(self.company)
self.company_address = address.company_address
@@ -70,6 +71,7 @@
else:
self.title = self.donor_name
+ @frappe.whitelist()
def get_payments(self):
if not self.member:
frappe.throw(_('Please select a Member first.'))
@@ -81,7 +83,7 @@
'from_date': ['between', (fiscal_year.year_start_date, fiscal_year.year_end_date)],
'to_date': ['between', (fiscal_year.year_start_date, fiscal_year.year_end_date)],
'membership_status': ('!=', 'Cancelled')
- }, ['from_date', 'amount', 'name', 'invoice', 'payment_id'])
+ }, ['from_date', 'amount', 'name', 'invoice', 'payment_id'], order_by='from_date')
if not memberships:
frappe.msgprint(_('No Membership Payments found against the Member {0}').format(self.member))
diff --git a/erpnext/regional/germany/setup.py b/erpnext/regional/germany/setup.py
index d6047e8..ac1f543 100644
--- a/erpnext/regional/germany/setup.py
+++ b/erpnext/regional/germany/setup.py
@@ -3,4 +3,17 @@
def setup(company=None, patch=True):
- pass
+ add_custom_roles_for_reports()
+
+
+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()
\ No newline at end of file
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/einv_validation.json b/erpnext/regional/india/e_invoice/einv_validation.json
index 86290cf..f4a3542 100644
--- a/erpnext/regional/india/e_invoice/einv_validation.json
+++ b/erpnext/regional/india/e_invoice/einv_validation.json
@@ -919,7 +919,8 @@
"minLength": 1,
"maxLength": 15,
"pattern": "^([0-9A-Z/-]){1,15}$",
- "description": "Tranport Document Number"
+ "description": "Tranport Document Number",
+ "validationMsg": "Transport Receipt No is invalid"
},
"TransDocDt": {
"type": "string",
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 96f7f1b..699441b 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -15,18 +15,44 @@
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')
- 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:
+ 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 +61,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'))
@@ -76,6 +104,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,53 +118,38 @@
invoice_date=invoice_date
))
-def get_party_details(address_name):
- d = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0]
-
- if (not d.gstin
- 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:
- # according to einvoice standard
- pincode = 999999
+def get_party_details(address_name, is_shipping_address=False):
+ addr = frappe.get_doc('Address', address_name)
+
+ validate_address_fields(addr, is_shipping_address)
- return frappe._dict(dict(
- gstin=d.gstin,
- 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)
+ if addr.gst_state_number == 97:
+ # according to einvoice standard
+ addr.pincode = 999999
+
+ party_address_details = frappe._dict(dict(
+ 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)
))
-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)
+ return party_address_details
def get_overseas_address_details(address_name):
address_title, address_line1, address_line2, city = frappe.db.get_value(
@@ -169,10 +185,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
@@ -205,11 +226,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]
@@ -223,6 +244,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
@@ -230,10 +254,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
@@ -254,7 +282,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
@@ -262,12 +294,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])
@@ -280,6 +326,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')
@@ -287,7 +337,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' }
@@ -305,9 +357,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.'),
@@ -319,6 +377,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)
@@ -334,24 +425,30 @@
buyer_details = get_overseas_address_details(invoice.customer_address)
else:
buyer_details = get_party_details(invoice.customer_address)
- place_of_supply = get_place_of_supply(invoice, invoice.doctype) or sanitize_for_json(invoice.billing_address_gstin)
- place_of_supply = place_of_supply[:2]
+ place_of_supply = get_place_of_supply(invoice, invoice.doctype)
+ if place_of_supply:
+ place_of_supply = place_of_supply.split('-')[0]
+ else:
+ 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_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
@@ -364,18 +461,73 @@
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
@@ -391,70 +543,22 @@
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=[]):
- 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'
@@ -463,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
@@ -522,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):
@@ -544,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'''
@@ -569,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'))
@@ -594,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()
@@ -616,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,
@@ -643,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)
@@ -687,7 +864,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):
@@ -719,7 +896,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):
@@ -734,6 +911,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
@@ -745,22 +925,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:
@@ -780,8 +944,13 @@
self.invoice.irn = res.get('Irn')
self.invoice.ewaybill = res.get('EwbNo')
+ 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()
@@ -791,7 +960,6 @@
'label': _('IRN Generated')
}
self.update_invoice()
-
def attach_qrcode_image(self):
qrcode = self.invoice.signed_qr_code
doctype = self.invoice.doctype
@@ -818,6 +986,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."""
@@ -846,6 +1025,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 ee49aae..9ded8da 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -5,19 +5,21 @@
import frappe, os, json
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 frappe.permissions import add_permission, update_permission_property
from erpnext.regional.india import states
from erpnext.accounts.utils import get_fiscal_year, FiscalYearError
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(patch=patch)
add_permissions()
add_custom_roles_for_reports()
frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test)
@@ -49,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(
@@ -110,6 +112,12 @@
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(patch=False):
+ # GST rules do not allow for an invoice no. bigger than 16 characters
+ 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.-', '')
+
def make_custom_fields(update=True):
hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
fieldtype='Data', fetch_from='item_code.gst_hsn_code', insert_after='description',
@@ -120,6 +128,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',
@@ -149,6 +160,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,
@@ -273,7 +291,7 @@
'allow_on_submit': 1,
'insert_after': 'customer_name_in_arabic',
'translatable': 0,
- }
+ }
]
si_ewaybill_fields = [
@@ -401,21 +419,37 @@
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_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='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='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=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='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=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 = {
@@ -431,7 +465,7 @@
'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,
'Sales Order': sales_invoice_gst_fields,
'Tax Category': inter_state_gst_field,
'Item': [
@@ -446,7 +480,7 @@
'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],
@@ -860,4 +894,4 @@
})
rule.flags.ignore_mandatory = True
- rule.save()
\ No newline at end of file
+ rule.save()
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 e24bd6c..6338056 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.
@@ -823,9 +825,57 @@
return
gst_accounts = get_gst_accounts(company)
- gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
- + gst_accounts.get('igst_account')
+
+ gst_account_list = []
+ for account in ['cgst_account', 'sgst_account', 'igst_account']:
+ if account in gst_accounts:
+ gst_account_list += gst_accounts.get(account)
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
diff --git a/erpnext/regional/italy/sales_invoice.js b/erpnext/regional/italy/sales_invoice.js
index 586a529..b54ac53 100644
--- a/erpnext/regional/italy/sales_invoice.js
+++ b/erpnext/regional/italy/sales_invoice.js
@@ -11,15 +11,10 @@
callback: function(r) {
frm.reload_doc();
if(r.message) {
- var w = window.open(
- frappe.urllib.get_full_url(
- "/api/method/erpnext.regional.italy.utils.download_e_invoice_file?"
- + "file_name=" + r.message
- )
- )
- if (!w) {
- frappe.msgprint(__("Please enable pop-ups")); return;
- }
+ open_url_post(frappe.request.url, {
+ cmd: 'frappe.core.doctype.file.file.download_file',
+ file_url: r.message
+ });
}
}
});
diff --git a/erpnext/regional/italy/setup.py b/erpnext/regional/italy/setup.py
index 95b92e7..7db2f6b 100644
--- a/erpnext/regional/italy/setup.py
+++ b/erpnext/regional/italy/setup.py
@@ -128,11 +128,8 @@
fetch_from="company.vat_collectability"),
dict(fieldname='sb_e_invoicing_reference', label='E-Invoicing',
fieldtype='Section Break', insert_after='against_income_account', print_hide=1),
- dict(fieldname='company_tax_id', label='Company Tax ID',
- fieldtype='Data', insert_after='sb_e_invoicing_reference', print_hide=1, read_only=1,
- fetch_from="company.tax_id"),
dict(fieldname='company_fiscal_code', label='Company Fiscal Code',
- fieldtype='Data', insert_after='company_tax_id', print_hide=1, read_only=1,
+ fieldtype='Data', insert_after='sb_e_invoicing_reference', print_hide=1, read_only=1,
fetch_from="company.fiscal_code"),
dict(fieldname='company_fiscal_regime', label='Company Fiscal Regime',
fieldtype='Data', insert_after='company_fiscal_code', print_hide=1, read_only=1,
@@ -142,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,
@@ -217,4 +217,4 @@
update_permission_property(doctype, 'Accounts Manager', 0, 'delete', 1)
add_permission(doctype, 'Accounts Manager', 1)
update_permission_property(doctype, 'Accounts Manager', 1, 'write', 1)
- update_permission_property(doctype, 'Accounts Manager', 1, 'create', 1)
\ No newline at end of file
+ update_permission_property(doctype, 'Accounts Manager', 1, 'create', 1)
diff --git a/erpnext/regional/italy/utils.py b/erpnext/regional/italy/utils.py
index 6842fb2..ba1aeaf 100644
--- a/erpnext/regional/italy/utils.py
+++ b/erpnext/regional/italy/utils.py
@@ -1,6 +1,8 @@
from __future__ import unicode_literals
-import frappe, json, os
+import io
+import json
+import frappe
from frappe.utils import flt, cstr
from erpnext.controllers.taxes_and_totals import get_itemised_tax
from frappe import _
@@ -28,20 +30,22 @@
@frappe.whitelist()
def export_invoices(filters=None):
- saved_xmls = []
+ frappe.has_permission('Sales Invoice', throw=True)
- invoices = frappe.get_all("Sales Invoice", filters=get_conditions(filters), fields=["*"])
+ invoices = frappe.get_all(
+ "Sales Invoice",
+ filters=get_conditions(filters),
+ fields=["name", "company_tax_id"]
+ )
- for invoice in invoices:
- attachments = get_e_invoice_attachments(invoice)
- saved_xmls += [attachment.file_name for attachment in attachments]
+ attachments = get_e_invoice_attachments(invoices)
- zip_filename = "{0}-einvoices.zip".format(frappe.utils.get_datetime().strftime("%Y%m%d_%H%M%S"))
+ zip_filename = "{0}-einvoices.zip".format(
+ frappe.utils.get_datetime().strftime("%Y%m%d_%H%M%S"))
- download_zip(saved_xmls, zip_filename)
+ download_zip(attachments, zip_filename)
-@frappe.whitelist()
def prepare_invoice(invoice, progressive_number):
#set company information
company = frappe.get_doc("Company", invoice.company)
@@ -53,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)
@@ -98,7 +103,7 @@
def get_conditions(filters):
filters = json.loads(filters)
- conditions = {"docstatus": 1}
+ conditions = {"docstatus": 1, "company_tax_id": ("!=", "")}
if filters.get("company"): conditions["company"] = filters["company"]
if filters.get("customer"): conditions["customer"] = filters["customer"]
@@ -111,23 +116,22 @@
return conditions
-#TODO: Use function from frappe once PR #6853 is merged.
+
def download_zip(files, output_filename):
- from zipfile import ZipFile
+ import zipfile
- input_files = [frappe.get_site_path('private', 'files', filename) for filename in files]
- output_path = frappe.get_site_path('private', 'files', output_filename)
+ zip_stream = io.BytesIO()
+ with zipfile.ZipFile(zip_stream, 'w', zipfile.ZIP_DEFLATED) as zip_file:
+ for file in files:
+ file_path = frappe.utils.get_files_path(
+ file.file_name, is_private=file.is_private)
- with ZipFile(output_path, 'w') as output_zip:
- for input_file in input_files:
- output_zip.write(input_file, arcname=os.path.basename(input_file))
-
- with open(output_path, 'rb') as fileobj:
- filedata = fileobj.read()
+ zip_file.write(file_path, arcname=file.file_name)
frappe.local.response.filename = output_filename
- frappe.local.response.filecontent = filedata
+ frappe.local.response.filecontent = zip_stream.getvalue()
frappe.local.response.type = "download"
+ zip_stream.close()
def get_invoice_summary(items, taxes):
summary_data = frappe._dict()
@@ -307,23 +311,12 @@
@frappe.whitelist()
def generate_single_invoice(docname):
doc = frappe.get_doc("Sales Invoice", docname)
-
+ frappe.has_permission("Sales Invoice", doc=doc, throw=True)
e_invoice = prepare_and_attach_invoice(doc, True)
+ return e_invoice.file_url
- return e_invoice.file_name
-
-@frappe.whitelist()
-def download_e_invoice_file(file_name):
- content = None
- with open(frappe.get_site_path('private', 'files', file_name), "r") as f:
- content = f.read()
-
- frappe.local.response.filename = file_name
- frappe.local.response.filecontent = content
- frappe.local.response.type = "download"
-
-#Delete e-invoice attachment on cancel.
+# Delete e-invoice attachment on cancel.
def sales_invoice_on_cancel(doc, method):
if get_company_country(doc.company) not in ['Italy',
'Italia', 'Italian Republic', 'Repubblica Italiana']:
@@ -335,16 +328,38 @@
def get_company_country(company):
return frappe.get_cached_value('Company', company, 'country')
-def get_e_invoice_attachments(invoice):
- if not invoice.company_tax_id:
- return []
+def get_e_invoice_attachments(invoices):
+ if not isinstance(invoices, list):
+ if not invoices.company_tax_id:
+ return
+
+ invoices = [invoices]
+
+ tax_id_map = {
+ invoice.name: (
+ invoice.company_tax_id
+ if invoice.company_tax_id.startswith("IT")
+ else "IT" + invoice.company_tax_id
+ ) for invoice in invoices
+ }
+
+ attachments = frappe.get_all(
+ "File",
+ fields=("name", "file_name", "attached_to_name", "is_private"),
+ filters= {
+ "attached_to_name": ('in', tax_id_map),
+ "attached_to_doctype": 'Sales Invoice'
+ }
+ )
out = []
- attachments = get_attachments(invoice.doctype, invoice.name)
- company_tax_id = invoice.company_tax_id if invoice.company_tax_id.startswith("IT") else "IT" + invoice.company_tax_id
-
for attachment in attachments:
- if attachment.file_name and attachment.file_name.startswith(company_tax_id) and attachment.file_name.endswith(".xml"):
+ if (
+ attachment.file_name
+ and attachment.file_name.endswith(".xml")
+ and attachment.file_name.startswith(
+ tax_id_map.get(attachment.attached_to_name))
+ ):
out.append(attachment)
return out
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/selling/doctype/lead_source/__init__.py b/erpnext/regional/report/e_invoice_summary/__init__.py
similarity index 100%
copy from erpnext/selling/doctype/lead_source/__init__.py
copy to 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.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 09b04ff..808fd3a 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -78,7 +78,7 @@
place_of_supply = invoice_details.get("place_of_supply")
ecommerce_gstin = invoice_details.get("ecommerce_gstin")
- b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin, inv),{
+ b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin),{
"place_of_supply": "",
"ecommerce_gstin": "",
"rate": "",
@@ -90,7 +90,7 @@
"invoice_value": invoice_details.get("base_grand_total"),
})
- row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin, inv))
+ row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin))
row["place_of_supply"] = place_of_supply
row["ecommerce_gstin"] = ecommerce_gstin
row["rate"] = rate
@@ -199,7 +199,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 +207,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 = {}
@@ -561,7 +561,7 @@
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 = {}
diff --git a/erpnext/regional/report/gstr_2/gstr_2.py b/erpnext/regional/report/gstr_2/gstr_2.py
index f899349..616c2b8 100644
--- a/erpnext/regional/report/gstr_2/gstr_2.py
+++ b/erpnext/regional/report/gstr_2/gstr_2.py
@@ -44,7 +44,7 @@
for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
invoice_details = self.invoices.get(inv)
for rate, items in items_based_on_rate.items():
- if rate:
+ if rate or invoice_details.get('gst_category') == 'Registered Composition':
if inv not in self.igst_invoices:
rate = rate / 2
row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items)
@@ -86,7 +86,7 @@
conditions += opts[1]
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', 'Registered Composition') and is_return != 1 "
elif self.filters.get("type_of_business") == "CDNR":
conditions += """ and is_return = 1 """
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 c452594..49ca942 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
@@ -230,13 +238,20 @@
frappe.db.set(self, "customer_name", newdn)
def set_loyalty_program(self):
- if self.loyalty_program: return
+ if self.loyalty_program:
+ return
+
loyalty_program = get_loyalty_programs(self)
- if not loyalty_program: return
+ if not loyalty_program:
+ return
+
if len(loyalty_program) == 1:
self.loyalty_program = loyalty_program[0]
else:
- frappe.msgprint(_("Multiple Loyalty Program found for the Customer. Please select manually."))
+ frappe.msgprint(
+ _("Multiple Loyalty Programs found for Customer {}. Please select manually.")
+ .format(frappe.bold(self.customer_name))
+ )
def create_onboarding_docs(self, args):
defaults = frappe.defaults.get_defaults()
@@ -340,7 +355,6 @@
@frappe.whitelist()
def get_loyalty_programs(doc):
''' returns applicable loyalty programs for a customer '''
- from frappe.desk.treeview import get_children
lp_details = []
loyalty_programs = frappe.get_all("Loyalty Program",
@@ -349,15 +363,33 @@
"ifnull(to_date, '2500-01-01')": [">=", today()]})
for loyalty_program in loyalty_programs:
- customer_groups = [d.value for d in get_children("Customer Group", loyalty_program.customer_group)] + [loyalty_program.customer_group]
- customer_territories = [d.value for d in get_children("Territory", loyalty_program.customer_territory)] + [loyalty_program.customer_territory]
-
- if (not loyalty_program.customer_group or doc.customer_group in customer_groups)\
- and (not loyalty_program.customer_territory or doc.territory in customer_territories):
+ if (
+ (not loyalty_program.customer_group
+ or doc.customer_group in get_nested_links(
+ "Customer Group",
+ loyalty_program.customer_group,
+ doc.flags.ignore_permissions
+ ))
+ and (not loyalty_program.customer_territory
+ or doc.territory in get_nested_links(
+ "Territory",
+ loyalty_program.customer_territory,
+ doc.flags.ignore_permissions
+ ))
+ ):
lp_details.append(loyalty_program.name)
return lp_details
+def get_nested_links(link_doctype, link_name, ignore_permissions=False):
+ from frappe.desk.treeview import _get_children
+
+ links = [link_name]
+ for d in _get_children(link_doctype, link_name, ignore_permissions):
+ links.append(d.value)
+
+ return links
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None):
@@ -572,4 +604,4 @@
""", {
'customer': customer,
'txt': '%%%s%%' % txt
- })
\ No newline at end of file
+ })
diff --git a/erpnext/selling/doctype/lead_source/lead_source.js b/erpnext/selling/doctype/lead_source/lead_source.js
deleted file mode 100644
index 6af6a4f..0000000
--- a/erpnext/selling/doctype/lead_source/lead_source.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Lead Source', {
- refresh: function(frm) {
-
- }
-});
diff --git a/erpnext/selling/doctype/lead_source/lead_source.json b/erpnext/selling/doctype/lead_source/lead_source.json
deleted file mode 100644
index 373e83a..0000000
--- a/erpnext/selling/doctype/lead_source/lead_source.json
+++ /dev/null
@@ -1,131 +0,0 @@
-{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 1,
- "autoname": "field:source_name",
- "beta": 0,
- "creation": "2016-09-16 01:47:47.382372",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "fields": [
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "source_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "label": "Source Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "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
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "details",
- "fieldtype": "Text Editor",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "label": "Details",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 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": "2020-09-16 02:03:01.441622",
- "modified_by": "Administrator",
- "module": "Selling",
- "name": "Lead Source",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "apply_user_permissions": 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": "Sales Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- },
- {
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 0,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- }
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_seen": 0
-}
diff --git a/erpnext/selling/doctype/lead_source/test_lead_source.py b/erpnext/selling/doctype/lead_source/test_lead_source.py
deleted file mode 100644
index 42df18f..0000000
--- a/erpnext/selling/doctype/lead_source/test_lead_source.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-from __future__ import unicode_literals
-
-import frappe
-import unittest
-
-# test_records = frappe.get_test_records('Lead Source')
-
-class TestLeadSource(unittest.TestCase):
- pass
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 5da248c..246f923 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -64,6 +64,7 @@
opp = frappe.get_doc("Opportunity", opportunity)
opp.set_status(status=status, update=True)
+ @frappe.whitelist()
def declare_enquiry_lost(self, lost_reasons_list, detailed_reason=None):
if not self.has_sales_order():
get_lost_reasons = frappe.get_list('Quotation Lost Reason',
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/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index e561291..d9e52e1 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -150,7 +150,7 @@
if enq:
frappe.db.sql("update `tabOpportunity` set status = %s where name=%s",(flag,enq[0][0]))
- def update_prevdoc_status(self, flag):
+ def update_prevdoc_status(self, flag=None):
for quotation in list(set([d.prevdoc_docname for d in self.get("items")])):
if quotation:
doc = frappe.get_doc("Quotation", quotation)
@@ -372,6 +372,7 @@
self.indicator_color = "green"
self.indicator_title = _("Paid")
+ @frappe.whitelist()
def get_work_order_items(self, for_raw_material_request=0):
'''Returns items with BOM that already do not have a linked work order'''
items = []
@@ -778,6 +779,7 @@
@frappe.whitelist()
def make_purchase_order_for_default_supplier(source_name, selected_items=None, target_doc=None):
+ """Creates Purchase Order for each Supplier. Returns a list of doc objects."""
if not selected_items: return
if isinstance(selected_items, string_types):
@@ -820,15 +822,16 @@
target.stock_qty = (flt(source.stock_qty) - flt(source.ordered_qty))
target.project = source_parent.project
- suppliers = [item.get('supplier') for item in selected_items if item.get('supplier') and item.get('supplier')]
- suppliers = list(set(suppliers))
+ suppliers = [item.get('supplier') for item in selected_items if item.get('supplier')]
+ suppliers = list(dict.fromkeys(suppliers)) # remove duplicates while preserving order
- items_to_map = [item.get('item_code') for item in selected_items if item.get('item_code') and item.get('item_code')]
+ items_to_map = [item.get('item_code') for item in selected_items if item.get('item_code')]
items_to_map = list(set(items_to_map))
if not suppliers:
frappe.throw(_("Please set a Supplier against the Items to be considered in the Purchase Order."))
+ purchase_orders = []
for supplier in suppliers:
doc = get_mapped_doc("Sales Order", source_name, {
"Sales Order": {
@@ -872,7 +875,9 @@
doc.insert()
frappe.db.commit()
- return doc
+ purchase_orders.append(doc)
+
+ return purchase_orders
@frappe.whitelist()
def make_purchase_order(source_name, selected_items=None, target_doc=None):
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 0fdfb1b..3137621 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -341,6 +341,9 @@
prev_total = so.get("base_total")
prev_total_in_words = so.get("base_in_words")
+ # get reserved qty before update items
+ reserved_qty_for_second_item = get_reserved_qty("_Test Item 2")
+
first_item_of_so = so.get("items")[0]
trans_item = json.dumps([
{'item_code' : first_item_of_so.item_code, 'rate' : first_item_of_so.rate, \
@@ -354,6 +357,10 @@
self.assertEqual(so.get("items")[-1].rate, 200)
self.assertEqual(so.get("items")[-1].qty, 7)
self.assertEqual(so.get("items")[-1].amount, 1400)
+
+ # reserved qty should increase after adding row
+ self.assertEqual(get_reserved_qty('_Test Item 2'), reserved_qty_for_second_item + 7)
+
self.assertEqual(so.status, 'To Deliver and Bill')
updated_total = so.get("base_total")
@@ -373,6 +380,9 @@
create_dn_against_so(so.name, 2)
make_sales_invoice(so.name)
+ # get reserved qty before update items
+ reserved_qty_for_second_item = get_reserved_qty("_Test Item 2")
+
# add an item so as to try removing items
trans_item = json.dumps([
{"item_code": '_Test Item', "qty": 5, "rate":1000, "docname": so.get("items")[0].name},
@@ -382,6 +392,9 @@
so.reload()
self.assertEqual(len(so.get("items")), 2)
+ # reserved qty should increase after adding row
+ self.assertEqual(get_reserved_qty('_Test Item 2'), reserved_qty_for_second_item + 2)
+
# check if delivered items can be removed
trans_item = json.dumps([{
"item_code": '_Test Item 2',
@@ -402,6 +415,10 @@
so.reload()
self.assertEqual(len(so.get("items")), 1)
+
+ # reserved qty should decrease (back to initial) after deleting row
+ self.assertEqual(get_reserved_qty('_Test Item 2'), reserved_qty_for_second_item)
+
self.assertEqual(so.status, 'To Deliver and Bill')
@@ -503,12 +520,18 @@
so = make_sales_order(item_code = "_Test Item", warehouse=None)
+ # get reserved qty of packed item
+ existing_reserved_qty = get_reserved_qty("_Packed Item")
+
added_item = json.dumps([{"item_code" : "_Product Bundle Item", "rate" : 200, 'qty' : 2}])
update_child_qty_rate('Sales Order', added_item, so.name)
so.reload()
self.assertEqual(so.packed_items[0].qty, 4)
+ # reserved qty in packed item should increase after adding bundle item
+ self.assertEqual(get_reserved_qty("_Packed Item"), existing_reserved_qty + 4)
+
# test uom and conversion factor change
update_uom_conv_factor = json.dumps([{
'item_code': so.get("items")[0].item_code,
@@ -523,6 +546,9 @@
so.reload()
self.assertEqual(so.packed_items[0].qty, 8)
+ # reserved qty in packed item should increase after changing bundle item uom
+ self.assertEqual(get_reserved_qty("_Packed Item"), existing_reserved_qty + 8)
+
def test_update_child_with_tax_template(self):
"""
Test Action: Create a SO with one item having its tax account head already in the SO.
@@ -736,7 +762,7 @@
so = make_sales_order(item_list=so_items, do_not_submit=True)
so.submit()
- po = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])
+ po = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])[0]
po.submit()
dn = create_dn_against_so(so.name, delivered_qty=2)
@@ -818,7 +844,7 @@
so.submit()
# create po for only one item
- po1 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])
+ po1 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])[0]
po1.submit()
self.assertEqual(so.customer, po1.customer)
@@ -828,7 +854,7 @@
self.assertEqual(len(po1.items), 1)
# create po for remaining item
- po2 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[1]])
+ po2 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[1]])[0]
po2.submit()
# teardown
@@ -839,6 +865,45 @@
so.load_from_db()
so.cancel()
+ def test_drop_shipping_full_for_default_suppliers(self):
+ """Test if multiple POs are generated in one go against different default suppliers."""
+ from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order_for_default_supplier
+
+ if not frappe.db.exists("Item", "_Test Item for Drop Shipping 1"):
+ make_item("_Test Item for Drop Shipping 1", {"is_stock_item": 1, "delivered_by_supplier": 1})
+
+ if not frappe.db.exists("Item", "_Test Item for Drop Shipping 2"):
+ make_item("_Test Item for Drop Shipping 2", {"is_stock_item": 1, "delivered_by_supplier": 1})
+
+ so_items = [
+ {
+ "item_code": "_Test Item for Drop Shipping 1",
+ "warehouse": "",
+ "qty": 2,
+ "rate": 400,
+ "delivered_by_supplier": 1,
+ "supplier": '_Test Supplier'
+ },
+ {
+ "item_code": "_Test Item for Drop Shipping 2",
+ "warehouse": "",
+ "qty": 2,
+ "rate": 400,
+ "delivered_by_supplier": 1,
+ "supplier": '_Test Supplier 1'
+ }
+ ]
+
+ # create so and po
+ so = make_sales_order(item_list=so_items, do_not_submit=True)
+ so.submit()
+
+ purchase_orders = make_purchase_order_for_default_supplier(so.name, selected_items=so_items)
+
+ self.assertEqual(len(purchase_orders), 2)
+ self.assertEqual(purchase_orders[0].supplier, '_Test Supplier')
+ self.assertEqual(purchase_orders[1].supplier, '_Test Supplier 1')
+
def test_reserved_qty_for_closing_so(self):
bin = frappe.get_all("Bin", filters={"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"},
fields=["reserved_qty"])
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..750a1a6 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -23,7 +23,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 +31,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)
@@ -139,8 +139,24 @@
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 +273,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 278821e..8adf5bf 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -279,11 +279,6 @@
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 event = {
field: fieldname,
@@ -397,6 +392,7 @@
this.recent_order_list.toggle_component(false);
frappe.run_serially([
() => this.frm.refresh(name),
+ () => this.frm.call('reset_mode_of_payments'),
() => this.cart.load_invoice(),
() => this.item_selector.toggle_component(true)
]);
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..11a63b3 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();
}
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..32a4556 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_details.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_details.js
@@ -201,7 +201,6 @@
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);
});
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..709fe57 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_selector.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js
@@ -159,6 +159,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')) {
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 be2b769..acf4eb3 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
@@ -64,10 +64,7 @@
{fieldname: 'print', fieldtype: 'Data', label: 'Print Preview'}
],
primary_action: () => {
- const frm = this.events.get_frm();
- frm.doc = this.doc;
- frm.print_preview.lang_code = frm.doc.language;
- frm.print_preview.printit(true);
+ this.print_receipt();
},
primary_action_label: __('Print'),
});
@@ -179,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);
@@ -192,13 +197,21 @@
});
this.$summary_container.on('click', '.print-btn', () => {
- const frm = this.events.get_frm();
- frm.doc = this.doc;
- frm.print_preview.lang_code = frm.doc.language;
- frm.print_preview.printit(true);
+ this.print_receipt();
});
}
+ print_receipt() {
+ const frm = this.events.get_frm();
+ frappe.utils.print(
+ this.doc.doctype,
+ this.doc.name,
+ frm.pos_print_format,
+ this.doc.letter_head,
+ this.doc.language || frappe.boot.lang
+ );
+ }
+
attach_shortcuts() {
const ctrl_label = frappe.utils.is_mac() ? '⌘' : 'Ctrl';
this.$summary_container.find('.print-btn').attr("title", `${ctrl_label}+P`);
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 c041d26..c2b5e4f 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -259,6 +259,7 @@
["default_payroll_payable_account", {"root_type": "Liability"}],
["round_off_account", {"root_type": "Expense"}],
["write_off_account", {"root_type": "Expense"}],
+ ["default_discount_account", {}],
["discount_allowed_account", {"root_type": "Expense"}],
["discount_received_account", {"root_type": "Income"}],
["exchange_gain_loss_account", {"root_type": "Expense"}],
@@ -275,7 +276,7 @@
["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}],
["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}],
["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}],
- ["unrealized_profit_loss_account", {"root_type": "Liability"}]
+ ["unrealized_profit_loss_account", {"root_type": "Liability"},]
], function(i, v) {
erpnext.company.set_custom_query(frm, v);
});
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index 56f60df..83cbf47 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -59,6 +59,7 @@
"default_deferred_expense_account",
"default_payroll_payable_account",
"default_expense_claim_payable_account",
+ "default_discount_account",
"section_break_22",
"cost_center",
"column_break_26",
@@ -733,6 +734,12 @@
"fieldtype": "Link",
"label": "Unrealized Profit / Loss Account",
"options": "Account"
+ },
+ {
+ "fieldname": "default_discount_account",
+ "fieldtype": "Link",
+ "label": "Default Payment Discount Account",
+ "options": "Account"
}
],
"icon": "fa fa-building",
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 433851c..64e027d 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'
@@ -66,12 +67,9 @@
if frappe.db.sql("select abbr from tabCompany where name!=%s and abbr=%s", (self.name, self.abbr)):
frappe.throw(_("Abbreviation already used for another company"))
+ @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 = [
diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py
index 0df4c87..8367a25 100644
--- a/erpnext/setup/doctype/company/delete_company_transactions.py
+++ b/erpnext/setup/doctype/company/delete_company_transactions.py
@@ -15,7 +15,7 @@
frappe.only_for("System Manager")
doc = frappe.get_doc("Company", company_name)
- if frappe.session.user != doc.owner:
+ if frappe.session.user != doc.owner and frappe.session.user != 'Administrator':
frappe.throw(_("Transactions can only be deleted by the creator of the Company"),
frappe.PermissionError)
@@ -27,7 +27,7 @@
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",
+ "Company", "Bank Account", "Item Tax Template", "Mode Of Payment", "Mode of Payment Account",
"Item Default", "Customer", "Supplier", "GST Account"):
delete_for_doctype(doctype, company_name)
diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py
index cbb4c7c..8c97322 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.py
+++ b/erpnext/setup/doctype/email_digest/email_digest.py
@@ -24,6 +24,7 @@
self._accounts = {}
self.currency = frappe.db.get_value('Company', self.company, "default_currency")
+ @frappe.whitelist()
def get_users(self):
"""get list of users"""
user_list = frappe.db.sql("""
@@ -41,6 +42,7 @@
frappe.response['user_list'] = user_list
+ @frappe.whitelist()
def send(self):
# send email only to enabled users
valid_users = [p[0] for p in frappe.db.sql("""select name from `tabUser`
@@ -48,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(
@@ -60,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
diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.js b/erpnext/setup/doctype/global_defaults/global_defaults.js
index 552331a..942dd59 100644
--- a/erpnext/setup/doctype/global_defaults/global_defaults.js
+++ b/erpnext/setup/doctype/global_defaults/global_defaults.js
@@ -17,7 +17,7 @@
method: "frappe.client.get_list",
args: {
doctype: "UOM Conversion Factor",
- filters: { "category": "Length" },
+ filters: { "category": __("Length") },
fields: ["to_uom"],
limit_page_length: 500
},
diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.py b/erpnext/setup/doctype/global_defaults/global_defaults.py
index fa7bc50..e587217 100644
--- a/erpnext/setup/doctype/global_defaults/global_defaults.py
+++ b/erpnext/setup/doctype/global_defaults/global_defaults.py
@@ -50,6 +50,7 @@
# clear cache
frappe.clear_cache()
+ @frappe.whitelist()
def get_defaults(self):
return frappe.defaults.get_defaults()
@@ -59,11 +60,11 @@
# 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")
+ 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)
def toggle_in_words(self):
self.disable_in_words = cint(self.disable_in_words)
@@ -71,5 +72,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 abff973..c1f9433 100644
--- a/erpnext/setup/doctype/naming_series/naming_series.py
+++ b/erpnext/setup/doctype/naming_series/naming_series.py
@@ -10,10 +10,12 @@
from frappe.model.document import Document
from frappe.model.naming import parse_naming_series
from frappe.permissions import get_doctypes_with_read
+from frappe.core.doctype.doctype.doctype import validate_series
class NamingSeriesNotSetError(frappe.ValidationError): pass
class NamingSeries(Document):
+ @frappe.whitelist()
def get_transactions(self, arg=None):
doctypes = list(set(frappe.db.sql_list("""select parent
from `tabDocField` df where fieldname='naming_series'""")
@@ -52,6 +54,7 @@
options = list(filter(lambda x: x, [cstr(n).strip() for n in ol]))
return options
+ @frappe.whitelist()
def update_series(self, arg=None):
"""update series list"""
self.validate_series_set()
@@ -126,7 +129,7 @@
dt = frappe.get_doc("DocType", self.select_doc_for_series)
options = self.scrub_options_list(self.set_options.split("\n"))
for series in options:
- dt.validate_series(series)
+ validate_series(dt, series)
for i in sr:
if i[0]:
existing_series = [d.split('.')[0] for d in i[0].split("\n")]
@@ -138,10 +141,12 @@
if not re.match("^[\w\- /.#{}]*$", n, re.UNICODE):
throw(_('Special Characters except "-", "#", ".", "/", "{" and "}" not allowed in naming series'))
+ @frappe.whitelist()
def get_options(self, arg=None):
if frappe.get_meta(arg or self.select_doc_for_series).get_field("naming_series"):
return frappe.get_meta(arg or self.select_doc_for_series).get_field("naming_series").options
+ @frappe.whitelist()
def get_current(self, arg=None):
"""get series current"""
if self.prefix:
@@ -154,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()
@@ -177,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:
@@ -189,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/install.py b/erpnext/setup/install.py
index 1e424dd..c7220cb 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()
@@ -142,13 +145,15 @@
}
]
- current_nabvar_items = navbar_settings.help_dropdown
+ current_navbar_items = navbar_settings.help_dropdown
navbar_settings.set('help_dropdown', [])
for item in erpnext_navbar_items:
- navbar_settings.append('help_dropdown', item)
+ current_labels = [item.get('item_label') for item in current_navbar_items]
+ if not item.get('item_label') in current_labels:
+ navbar_settings.append('help_dropdown', item)
- for item in current_nabvar_items:
+ for item in current_navbar_items:
navbar_settings.append('help_dropdown', {
'item_label': item.item_label,
'item_type': item.item_type,
@@ -162,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..f21d55f 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 cf59a52..d857bf5 100644
--- a/erpnext/shopping_cart/test_shopping_cart.py
+++ b/erpnext/shopping_cart/test_shopping_cart.py
@@ -16,6 +16,11 @@
Note:
Shopping Cart == Quotation
"""
+
+ @classmethod
+ def tearDownClass(cls):
+ frappe.db.sql("delete from `tabTax Rule`")
+
def setUp(self):
frappe.set_user("Administrator")
create_test_contact_and_address()
@@ -51,8 +56,8 @@
def test_add_to_cart(self):
self.login_as_customer()
- # remove from cart
- self.remove_all_items_from_cart()
+ # clear existing quotations
+ self.clear_existing_quotations()
# add first item
update_cart("_Test Item", 1)
@@ -100,6 +105,7 @@
self.assertEqual(len(quotation.get("items")), 1)
def test_tax_rule(self):
+ self.create_tax_rule()
self.login_as_customer()
quotation = self.create_quotation()
@@ -115,6 +121,13 @@
self.remove_test_quotation(quotation)
+ def create_tax_rule(self):
+ tax_rule = frappe.get_test_records("Tax Rule")[0]
+ try:
+ frappe.get_doc(tax_rule).insert()
+ except frappe.DuplicateEntryError:
+ pass
+
def create_quotation(self):
quotation = frappe.new_doc("Quotation")
@@ -195,10 +208,15 @@
"_Test Contact For _Test Customer")
frappe.set_user("test_contact_customer@example.com")
- def remove_all_items_from_cart(self):
- quotation = _get_cart_quotation()
- quotation.flags.ignore_permissions=True
- quotation.delete()
+ def clear_existing_quotations(self):
+ quotations = frappe.get_all("Quotation", filters={
+ "party_name": get_party().name,
+ "order_type": "Shopping Cart",
+ "docstatus": 0
+ }, order_by="modified desc", pluck="name")
+
+ for quotation in quotations:
+ frappe.delete_doc("Quotation", quotation, ignore_permissions=True, force=True)
def create_user_if_not_exists(self, email, first_name = None):
if frappe.db.exists("User", email):
diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js
index 30e0b76..dbf9901 100644
--- a/erpnext/stock/dashboard/item_dashboard.js
+++ b/erpnext/stock/dashboard/item_dashboard.js
@@ -8,7 +8,7 @@
make() {
var me = this;
this.start = 0;
- if(!this.sort_by) {
+ if (!this.sort_by) {
this.sort_by = 'projected_qty';
this.sort_order = 'asc';
}
@@ -16,22 +16,25 @@
this.content = $(frappe.render_template('item_dashboard')).appendTo(this.parent);
this.result = this.content.find('.result');
- this.content.on('click', '.btn-move', function() {
- handle_move_add($(this), "Move")
+ this.content.on('click', '.btn-move', function () {
+ handle_move_add($(this), "Move");
});
- this.content.on('click', '.btn-add', function() {
- handle_move_add($(this), "Add")
+ this.content.on('click', '.btn-add', function () {
+ handle_move_add($(this), "Add");
});
- this.content.on('click', '.btn-edit', function() {
+ this.content.on('click', '.btn-edit', function () {
let item = unescape($(this).attr('data-item'));
let warehouse = unescape($(this).attr('data-warehouse'));
let company = unescape($(this).attr('data-company'));
- frappe.db.get_value('Putaway Rule',
- {'item_code': item, 'warehouse': warehouse, 'company': company}, 'name', (r) => {
- frappe.set_route("Form", "Putaway Rule", r.name);
- });
+ frappe.db.get_value('Putaway Rule', {
+ 'item_code': item,
+ 'warehouse': warehouse,
+ 'company': company
+ }, 'name', (r) => {
+ frappe.set_route("Form", "Putaway Rule", r.name);
+ });
});
function handle_move_add(element, action) {
@@ -39,23 +42,26 @@
let warehouse = unescape(element.attr('data-warehouse'));
let actual_qty = unescape(element.attr('data-actual_qty'));
let disable_quick_entry = Number(unescape(element.attr('data-disable_quick_entry')));
- let entry_type = action === "Move" ? "Material Transfer": null;
+ let entry_type = action === "Move" ? "Material Transfer" : null;
if (disable_quick_entry) {
open_stock_entry(item, warehouse, entry_type);
} else {
if (action === "Add") {
let rate = unescape($(this).attr('data-rate'));
- erpnext.stock.move_item(item, null, warehouse, actual_qty, rate, function() { me.refresh(); });
- }
- else {
- erpnext.stock.move_item(item, warehouse, null, actual_qty, null, function() { me.refresh(); });
+ erpnext.stock.move_item(item, null, warehouse, actual_qty, rate, function () {
+ me.refresh();
+ });
+ } else {
+ erpnext.stock.move_item(item, warehouse, null, actual_qty, null, function () {
+ me.refresh();
+ });
}
}
}
function open_stock_entry(item, warehouse, entry_type) {
- frappe.model.with_doctype('Stock Entry', function() {
+ frappe.model.with_doctype('Stock Entry', function () {
var doc = frappe.model.get_new_doc('Stock Entry');
if (entry_type) doc.stock_entry_type = entry_type;
@@ -64,11 +70,11 @@
row.s_warehouse = warehouse;
frappe.set_route('Form', doc.doctype, doc.name);
- })
+ });
}
// more
- this.content.find('.btn-more').on('click', function() {
+ this.content.find('.btn-more').on('click', function () {
me.start += me.page_length;
me.refresh();
});
@@ -94,7 +100,7 @@
frappe.call({
method: this.method,
args: args,
- callback: function(r) {
+ callback: function (r) {
me.render(r.message);
}
});
@@ -115,7 +121,7 @@
this.max_count = this.max_count;
// show more button
- if (data && data.length===(this.page_length + 1)) {
+ if (data && data.length === (this.page_length + 1)) {
this.content.find('.more').removeClass('hidden');
// remove the last element
@@ -141,11 +147,11 @@
if(!max_count) max_count = 0;
if(!data) data = [];
- data.forEach(function(d) {
+ data.forEach(function (d) {
d.actual_or_pending = d.projected_qty + d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract;
d.pending_qty = 0;
d.total_reserved = d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract;
- if(d.actual_or_pending > d.actual_qty) {
+ if (d.actual_or_pending > d.actual_qty) {
d.pending_qty = d.actual_or_pending - d.actual_qty;
}
@@ -161,7 +167,7 @@
return {
data: data,
max_count: max_count,
- can_write:can_write,
+ can_write: can_write,
show_item: show_item || false
};
}
@@ -169,8 +175,8 @@
get_capacity_dashboard_data(data) {
if (!data) data = [];
- data.forEach(function(d) {
- d.color = d.percent_occupied >=80 ? "#f8814f" : "#2490ef";
+ data.forEach(function (d) {
+ d.color = d.percent_occupied >= 80 ? "#f8814f" : "#2490ef";
});
let can_write = 0;
@@ -185,53 +191,77 @@
}
};
-erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callback) {
+erpnext.stock.move_item = function (item, source, target, actual_qty, rate, callback) {
var dialog = new frappe.ui.Dialog({
title: target ? __('Add Item') : __('Move Item'),
- fields: [
- {fieldname: 'item_code', label: __('Item'),
- fieldtype: 'Link', options: 'Item', read_only: 1},
- {fieldname: 'source', label: __('Source Warehouse'),
- fieldtype: 'Link', options: 'Warehouse', read_only: 1},
- {fieldname: 'target', label: __('Target Warehouse'),
- fieldtype: 'Link', options: 'Warehouse', reqd: 1},
- {fieldname: 'qty', label: __('Quantity'), reqd: 1,
- fieldtype: 'Float', description: __('Available {0}', [actual_qty]) },
- {fieldname: 'rate', label: __('Rate'), fieldtype: 'Currency', hidden: 1 },
+ fields: [{
+ fieldname: 'item_code',
+ label: __('Item'),
+ fieldtype: 'Link',
+ options: 'Item',
+ read_only: 1
+ },
+ {
+ fieldname: 'source',
+ label: __('Source Warehouse'),
+ fieldtype: 'Link',
+ options: 'Warehouse',
+ read_only: 1
+ },
+ {
+ fieldname: 'target',
+ label: __('Target Warehouse'),
+ fieldtype: 'Link',
+ options: 'Warehouse',
+ reqd: 1
+ },
+ {
+ fieldname: 'qty',
+ label: __('Quantity'),
+ reqd: 1,
+ fieldtype: 'Float',
+ description: __('Available {0}', [actual_qty])
+ },
+ {
+ fieldname: 'rate',
+ label: __('Rate'),
+ fieldtype: 'Currency',
+ hidden: 1
+ },
],
- })
+ });
dialog.show();
dialog.get_field('item_code').set_input(item);
- if(source) {
+ if (source) {
dialog.get_field('source').set_input(source);
} else {
dialog.get_field('source').df.hidden = 1;
dialog.get_field('source').refresh();
}
- if(rate) {
+ if (rate) {
dialog.get_field('rate').set_value(rate);
dialog.get_field('rate').df.hidden = 0;
dialog.get_field('rate').refresh();
}
- if(target) {
+ if (target) {
dialog.get_field('target').df.read_only = 1;
dialog.get_field('target').value = target;
dialog.get_field('target').refresh();
}
- dialog.set_primary_action(__('Submit'), function() {
+ dialog.set_primary_action(__('Submit'), function () {
var values = dialog.get_values();
- if(!values) {
+ if (!values) {
return;
}
- if(source && values.qty > actual_qty) {
+ if (source && values.qty > actual_qty) {
frappe.msgprint(__('Quantity must be less than or equal to {0}', [actual_qty]));
return;
}
- if(values.source === values.target) {
+ if (values.source === values.target) {
frappe.msgprint(__('Source and target warehouse must be different'));
}
@@ -239,21 +269,21 @@
method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry',
args: values,
freeze: true,
- callback: function(r) {
+ callback: function (r) {
frappe.show_alert(__('Stock Entry {0} created',
- ['<a href="/app/stock-entry/'+r.message.name+'">' + r.message.name+ '</a>']));
+ ['<a href="/app/stock-entry/' + r.message.name + '">' + r.message.name + '</a>']));
dialog.hide();
callback(r);
},
});
});
- $('<p style="margin-left: 10px;"><a class="link-open text-muted small">'
- + __("Add more items or open full form") + '</a></p>')
+ $('<p style="margin-left: 10px;"><a class="link-open text-muted small">' +
+ __("Add more items or open full form") + '</a></p>')
.appendTo(dialog.body)
.find('.link-open')
- .on('click', function() {
- frappe.model.with_doctype('Stock Entry', function() {
+ .on('click', function () {
+ frappe.model.with_doctype('Stock Entry', function () {
var doc = frappe.model.get_new_doc('Stock Entry');
doc.from_warehouse = dialog.get_value('source');
doc.to_warehouse = dialog.get_value('target');
@@ -266,6 +296,6 @@
row.transfer_qty = dialog.get_value('qty');
row.basic_rate = dialog.get_value('rate');
frappe.set_route('Form', doc.doctype, doc.name);
- })
+ });
});
-}
+};
diff --git a/erpnext/stock/dashboard/item_dashboard.py b/erpnext/stock/dashboard/item_dashboard.py
index cafb5c3..45e6628 100644
--- a/erpnext/stock/dashboard/item_dashboard.py
+++ b/erpnext/stock/dashboard/item_dashboard.py
@@ -2,6 +2,7 @@
import frappe
from frappe.model.db_query import DatabaseQuery
+from frappe.utils import flt, cint
@frappe.whitelist()
def get_data(item_code=None, warehouse=None, item_group=None,
@@ -42,11 +43,20 @@
limit_start=start,
limit_page_length='21')
+ precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
+
for item in items:
item.update({
- 'item_name': frappe.get_cached_value("Item", item.item_code, 'item_name'),
- 'disable_quick_entry': frappe.get_cached_value("Item", item.item_code, 'has_batch_no')
- or frappe.get_cached_value("Item", item.item_code, 'has_serial_no'),
+ 'item_name': frappe.get_cached_value(
+ "Item", item.item_code, 'item_name'),
+ 'disable_quick_entry': frappe.get_cached_value(
+ "Item", item.item_code, 'has_batch_no')
+ or frappe.get_cached_value(
+ "Item", item.item_code, 'has_serial_no'),
+ 'projected_qty': flt(item.projected_qty, precision),
+ 'reserved_qty': flt(item.reserved_qty, precision),
+ 'reserved_qty_for_production': flt(item.reserved_qty_for_production, precision),
+ 'reserved_qty_for_sub_contract': flt(item.reserved_qty_for_sub_contract, precision),
+ 'actual_qty': flt(item.actual_qty, precision),
})
-
return items
diff --git a/erpnext/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json
index 04d624e..8e79f0e 100644
--- a/erpnext/stock/doctype/bin/bin.json
+++ b/erpnext/stock/doctype/bin/bin.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "MAT-BIN-.YYYY.-.#####",
"creation": "2013-01-10 16:34:25",
"doctype": "DocType",
@@ -112,7 +113,8 @@
{
"fieldname": "reserved_qty_for_sub_contract",
"fieldtype": "Float",
- "label": "Reserved Qty for sub contract"
+ "label": "Reserved Qty for sub contract",
+ "read_only": 1
},
{
"fieldname": "ma_rate",
@@ -166,7 +168,8 @@
"hide_toolbar": 1,
"idx": 1,
"in_create": 1,
- "modified": "2019-11-18 18:34:59.456882",
+ "links": [],
+ "modified": "2021-03-30 23:09:39.572776",
"modified_by": "Administrator",
"module": "Stock",
"name": "Bin",
@@ -196,5 +199,6 @@
],
"quick_entry": 1,
"search_fields": "item_code,warehouse",
+ "sort_field": "modified",
"sort_order": "ASC"
}
\ No newline at end of file
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 3544390..d326a04 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -101,7 +101,7 @@
for f in fieldname:
toggle_print_hide(self.meta if key == "parent" else item_meta, f)
- super(DeliveryNote, self).before_print()
+ super(DeliveryNote, self).before_print(settings)
def set_actual_qty(self):
for d in self.get('items'):
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
index 28e9533..de85bc3 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
@@ -90,6 +90,7 @@
delivery_notes = [get_link_to_form("Delivery Note", note) for note in delivery_notes]
frappe.msgprint(_("Delivery Notes {0} updated").format(", ".join(delivery_notes)))
+ @frappe.whitelist()
def process_route(self, optimize):
"""
Estimate the arrival times for each stop in the Delivery Trip.
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 2aa42e6..45e3c21 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.json b/erpnext/stock/doctype/item/item.json
index 33a8fe7..6fed9ef 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -1054,6 +1054,7 @@
"read_only": 1
},
{
+ "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
"fieldname": "website_image_alt",
"fieldtype": "Data",
"label": "Image Description"
@@ -1066,7 +1067,7 @@
"index_web_pages_for_search": 1,
"links": [],
"max_attachments": 1,
- "modified": "2021-03-15 13:41:04.108932",
+ "modified": "2021-03-18 14:04:38.575519",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
@@ -1137,4 +1138,4 @@
"sort_order": "DESC",
"title_field": "item_name",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 7b7d2da..dbac794 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -50,6 +50,7 @@
self.set_onload('stock_exists', self.stock_ledger_created())
self.set_asset_naming_series()
+ @frappe.whitelist()
def set_asset_naming_series(self):
if not hasattr(self, '_asset_naming_series'):
from erpnext.assets.doctype.asset.asset import get_asset_naming_series
@@ -62,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)
@@ -673,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(
@@ -706,6 +707,7 @@
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock)
frappe.db.auto_commit_on_many_writes = 0
+ @frappe.whitelist()
def copy_specification_from_item_group(self):
self.set("website_specifications", [])
if self.item_group:
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index 36d0de1..e0b89d8 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -494,7 +494,8 @@
test_records = frappe.get_test_records('Item')
-def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, is_customer_provided_item=None, customer=None, is_purchase_item=None, opening_stock=None):
+def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, is_customer_provided_item=None,
+ customer=None, is_purchase_item=None, opening_stock=None, company=None):
if not frappe.db.exists("Item", item_code):
item = frappe.new_doc("Item")
item.item_code = item_code
@@ -509,7 +510,7 @@
item.customer = customer or ''
item.append("item_defaults", {
"default_warehouse": warehouse or '_Test Warehouse - _TC',
- "company": "_Test Company"
+ "company": company or "_Test Company"
})
item.save()
else:
diff --git a/erpnext/stock/doctype/item/test_records.json b/erpnext/stock/doctype/item/test_records.json
index 909c4ee..6cec852 100644
--- a/erpnext/stock/doctype/item/test_records.json
+++ b/erpnext/stock/doctype/item/test_records.json
@@ -12,6 +12,7 @@
"item_name": "_Test Item",
"apply_warehouse_wise_reorder_level": 1,
"gst_hsn_code": "999800",
+ "opening_stock": 10,
"valuation_rate": 100,
"item_defaults": [{
"company": "_Test Company",
@@ -58,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_attribute/test_records.json b/erpnext/stock/doctype/item_attribute/test_records.json
index d346979..6aa6ffd 100644
--- a/erpnext/stock/doctype/item_attribute/test_records.json
+++ b/erpnext/stock/doctype/item_attribute/test_records.json
@@ -4,10 +4,12 @@
"attribute_name": "Test Size",
"priority": 1,
"item_attribute_values": [
+ {"attribute_value": "Extra Small", "abbr": "XSL"},
{"attribute_value": "Small", "abbr": "S"},
{"attribute_value": "Medium", "abbr": "M"},
{"attribute_value": "Large", "abbr": "L"},
- {"attribute_value": "Extra Small", "abbr": "XSL"}
+ {"attribute_value": "Extra Large", "abbr": "XL"},
+ {"attribute_value": "2XL", "abbr": "2XL"}
]
},
{
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_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
index 69a8bf1..8310946 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
@@ -12,6 +12,7 @@
from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals
class LandedCostVoucher(Document):
+ @frappe.whitelist()
def get_items_from_purchase_receipts(self):
self.set("items", [])
for pr in self.get("purchase_receipts"):
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 86936b4..f516e06 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -354,6 +354,10 @@
},
material_request_type: function(frm) {
frm.toggle_reqd('customer', frm.doc.material_request_type=="Customer Provided");
+
+ if (frm.doc.material_request_type !== 'Material Transfer' && frm.doc.set_from_warehouse) {
+ frm.set_value('set_from_warehouse', '');
+ }
},
});
@@ -429,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 d73349d..8d7b238 100644
--- a/erpnext/stock/doctype/material_request/material_request.json
+++ b/erpnext/stock/doctype/material_request/material_request.json
@@ -20,9 +20,9 @@
"company",
"amended_from",
"warehouse_section",
- "set_warehouse",
- "column_break5",
"set_from_warehouse",
+ "column_break5",
+ "set_warehouse",
"items_section",
"scan_barcode",
"items",
@@ -314,7 +314,7 @@
"idx": 70,
"is_submittable": 1,
"links": [],
- "modified": "2020-09-19 01:04:09.285862",
+ "modified": "2021-03-31 23:52:55.392512",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request",
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/packing_slip/packing_slip.py b/erpnext/stock/doctype/packing_slip/packing_slip.py
index a7a29cc..2008bff 100644
--- a/erpnext/stock/doctype/packing_slip/packing_slip.py
+++ b/erpnext/stock/doctype/packing_slip/packing_slip.py
@@ -152,6 +152,7 @@
return cint(recommended_case_no[0][0]) + 1
+ @frappe.whitelist()
def get_items(self):
self.set("items", [])
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index d723fac..6ab68e2 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -33,6 +33,7 @@
frappe.throw(_('For item {0} at row {1}, count of serial numbers does not match with the picked quantity')
.format(frappe.bold(item.item_code), frappe.bold(item.idx)), title=_("Quantity Mismatch"))
+ @frappe.whitelist()
def set_item_locations(self, save=False):
items = self.aggregate_item_qty()
self.item_location_map = frappe._dict()
@@ -345,7 +346,7 @@
if dn_item:
dn_item.warehouse = location.warehouse
- dn_item.qty = location.picked_qty
+ dn_item.qty = flt(location.picked_qty) / (flt(location.conversion_factor) or 1)
dn_item.batch_no = location.batch_no
dn_item.serial_no = location.serial_no
@@ -378,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/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py
index 8ea7f89d..c4da05a 100644
--- a/erpnext/stock/doctype/pick_list/test_pick_list.py
+++ b/erpnext/stock/doctype/pick_list/test_pick_list.py
@@ -9,6 +9,7 @@
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.item.test_item import create_item
+from erpnext.stock.doctype.pick_list.pick_list import create_delivery_note
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation \
import EmptyStockReconciliationItemsError
@@ -22,7 +23,7 @@
'purpose': 'Opening Stock',
'expense_account': 'Temporary Opening - _TC',
'items': [{
- 'item_code': '_Test Item Home Desktop 100',
+ 'item_code': '_Test Item',
'warehouse': '_Test Warehouse - _TC',
'valuation_rate': 100,
'qty': 5
@@ -37,7 +38,7 @@
'customer': '_Test Customer',
'items_based_on': 'Sales Order',
'locations': [{
- 'item_code': '_Test Item Home Desktop 100',
+ 'item_code': '_Test Item',
'qty': 5,
'stock_qty': 5,
'conversion_factor': 1,
@@ -47,7 +48,7 @@
})
pick_list.set_item_locations()
- self.assertEqual(pick_list.locations[0].item_code, '_Test Item Home Desktop 100')
+ self.assertEqual(pick_list.locations[0].item_code, '_Test Item')
self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC')
self.assertEqual(pick_list.locations[0].qty, 5)
@@ -237,7 +238,7 @@
'purpose': 'Opening Stock',
'expense_account': 'Temporary Opening - _TC',
'items': [{
- 'item_code': '_Test Item Home Desktop 100',
+ 'item_code': '_Test Item',
'warehouse': '_Test Warehouse - _TC',
'valuation_rate': 100,
'qty': 10
@@ -251,7 +252,7 @@
'customer': '_Test Customer',
'company': '_Test Company',
'items': [{
- 'item_code': '_Test Item Home Desktop 100',
+ 'item_code': '_Test Item',
'qty': 10,
'delivery_date': frappe.utils.today()
}],
@@ -264,14 +265,14 @@
'customer': '_Test Customer',
'items_based_on': 'Sales Order',
'locations': [{
- 'item_code': '_Test Item Home Desktop 100',
+ 'item_code': '_Test Item',
'qty': 5,
'stock_qty': 5,
'conversion_factor': 1,
'sales_order': '_T-Sales Order-1',
'sales_order_item': '_T-Sales Order-1_item',
}, {
- 'item_code': '_Test Item Home Desktop 100',
+ 'item_code': '_Test Item',
'qty': 5,
'stock_qty': 5,
'conversion_factor': 1,
@@ -281,16 +282,71 @@
})
pick_list.set_item_locations()
- self.assertEqual(pick_list.locations[0].item_code, '_Test Item Home Desktop 100')
+ self.assertEqual(pick_list.locations[0].item_code, '_Test Item')
self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC')
self.assertEqual(pick_list.locations[0].qty, 5)
self.assertEqual(pick_list.locations[0].sales_order_item, '_T-Sales Order-1_item')
- self.assertEqual(pick_list.locations[1].item_code, '_Test Item Home Desktop 100')
+ self.assertEqual(pick_list.locations[1].item_code, '_Test Item')
self.assertEqual(pick_list.locations[1].warehouse, '_Test Warehouse - _TC')
self.assertEqual(pick_list.locations[1].qty, 5)
self.assertEqual(pick_list.locations[1].sales_order_item, sales_order.items[0].name)
+ def test_pick_list_for_items_with_multiple_UOM(self):
+ purchase_receipt = make_purchase_receipt(item_code="_Test Item", qty=10)
+ purchase_receipt.submit()
+
+ sales_order = frappe.get_doc({
+ 'doctype': 'Sales Order',
+ 'customer': '_Test Customer',
+ 'company': '_Test Company',
+ 'items': [{
+ 'item_code': '_Test Item',
+ 'qty': 1,
+ 'conversion_factor': 5,
+ 'delivery_date': frappe.utils.today()
+ }, {
+ 'item_code': '_Test Item',
+ 'qty': 1,
+ 'conversion_factor': 1,
+ 'delivery_date': frappe.utils.today()
+ }],
+ }).insert()
+ sales_order.submit()
+
+ pick_list = frappe.get_doc({
+ 'doctype': 'Pick List',
+ 'company': '_Test Company',
+ 'customer': '_Test Customer',
+ 'items_based_on': 'Sales Order',
+ 'locations': [{
+ 'item_code': '_Test Item',
+ 'qty': 1,
+ 'stock_qty': 5,
+ 'conversion_factor': 5,
+ 'sales_order': sales_order.name,
+ 'sales_order_item': sales_order.items[0].name ,
+ }, {
+ 'item_code': '_Test Item',
+ 'qty': 1,
+ 'stock_qty': 1,
+ 'conversion_factor': 1,
+ 'sales_order': sales_order.name,
+ 'sales_order_item': sales_order.items[1].name ,
+ }]
+ })
+ pick_list.set_item_locations()
+ pick_list.submit()
+
+ delivery_note = create_delivery_note(pick_list.name)
+
+ self.assertEqual(pick_list.locations[0].qty, delivery_note.items[0].qty)
+ self.assertEqual(pick_list.locations[1].qty, delivery_note.items[1].qty)
+ self.assertEqual(sales_order.items[0].conversion_factor, delivery_note.items[0].conversion_factor)
+
+ pick_list.cancel()
+ sales_order.cancel()
+ purchase_receipt.cancel()
# def test_pick_list_skips_items_in_expired_batch(self):
# pass
@@ -302,4 +358,4 @@
# pass
# def test_pick_list_from_material_request(self):
- # pass
\ No newline at end of file
+ # pass
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index f87b273..688ae1d 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 70687bda..61e60f3 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([
{
@@ -176,7 +189,7 @@
if flt(self.per_billed) < 100:
self.update_billing_status()
else:
- self.status = "Completed"
+ self.db_set("status", "Completed")
# Updating stock ledger should always be called after updating prevdoc status,
@@ -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:
@@ -513,7 +527,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 7741ee7..16eea24 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -191,7 +191,7 @@
rm_supp_cost = sum([d.amount for d in pr.get("supplied_items")])
self.assertEqual(pr.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2))
-
+
pr.cancel()
def test_subcontracting_gle_fg_item_rate_zero(self):
@@ -582,6 +582,7 @@
serial_no=serial_no, basic_rate=100, do_not_submit=True)
se.submit()
+ se.cancel()
dn.cancel()
pr1.cancel()
@@ -912,6 +913,57 @@
ste1.cancel()
po.cancel()
+
+ def test_po_to_pi_and_po_to_pr_worflow_full(self):
+ """Test following behaviour:
+ - Create PO
+ - Create PI from PO and submit
+ - Create PR from PO and submit
+ """
+ from erpnext.buying.doctype.purchase_order import test_purchase_order
+ from erpnext.buying.doctype.purchase_order import purchase_order
+
+ po = test_purchase_order.create_purchase_order()
+
+ pi = purchase_order.make_purchase_invoice(po.name)
+ pi.submit()
+
+ pr = purchase_order.make_purchase_receipt(po.name)
+ pr.submit()
+
+ pr.load_from_db()
+
+ self.assertEqual(pr.status, "Completed")
+ self.assertEqual(pr.per_billed, 100)
+
+ def test_po_to_pi_and_po_to_pr_worflow_partial(self):
+ """Test following behaviour:
+ - Create PO
+ - Create partial PI from PO and submit
+ - Create PR from PO and submit
+ """
+ from erpnext.buying.doctype.purchase_order import test_purchase_order
+ from erpnext.buying.doctype.purchase_order import purchase_order
+
+ po = test_purchase_order.create_purchase_order()
+
+ pi = purchase_order.make_purchase_invoice(po.name)
+ pi.items[0].qty /= 2 # roughly 50%, ^ this function only creates PI with 1 item.
+ pi.submit()
+
+ pr = purchase_order.make_purchase_receipt(po.name)
+ pr.save()
+ # per_billed is only updated after submission.
+ self.assertEqual(flt(pr.per_billed), 0)
+
+ pr.submit()
+
+ pi.load_from_db()
+ pr.load_from_db()
+
+ self.assertEqual(pr.status, "To Bill")
+ self.assertAlmostEqual(pr.per_billed, 50.0, places=2)
+
def get_sl_entries(voucher_type, voucher_no):
return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference
from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s
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/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
index 58b1eca..469511a 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
@@ -18,6 +18,7 @@
if self.readings:
self.inspect_and_set_status()
+ @frappe.whitelist()
def get_item_specification_details(self):
if not self.quality_inspection_template:
self.quality_inspection_template = frappe.db.get_value('Item',
@@ -32,6 +33,7 @@
child.update(d)
child.status = "Accepted"
+ @frappe.whitelist()
def get_quality_inspection_template(self):
template = ''
if self.bom_no:
@@ -62,17 +64,21 @@
(quality_inspection, self.modified, self.reference_name, self.item_code))
else:
+ args = [quality_inspection, self.modified, self.reference_name, self.item_code]
doctype = self.reference_type + ' Item'
+
if self.reference_type == 'Stock Entry':
doctype = 'Stock Entry Detail'
if self.reference_type and self.reference_name:
conditions = ""
if self.batch_no and self.docstatus == 1:
- conditions += " and t1.batch_no = '%s'"%(self.batch_no)
+ conditions += " and t1.batch_no = %s"
+ args.append(self.batch_no)
if self.docstatus == 2: # if cancel, then remove qi link wherever same name
- conditions += " and t1.quality_inspection = '%s'"%(self.name)
+ conditions += " and t1.quality_inspection = %s"
+ args.append(self.name)
frappe.db.sql("""
UPDATE
@@ -85,7 +91,7 @@
and t1.parent = t2.name
{conditions}
""".format(parent_doc=self.reference_type, child_doc=doctype, conditions=conditions),
- (quality_inspection, self.modified, self.reference_name, self.item_code))
+ args)
def inspect_and_set_status(self):
for reading in self.readings:
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 8436acb..3f83780 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -5,7 +5,7 @@
from __future__ import unicode_literals
import frappe, erpnext
from frappe.model.document import Document
-from frappe.utils import cint, get_link_to_form
+from frappe.utils import cint, get_link_to_form, add_to_date, today
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
@@ -29,7 +29,7 @@
self.company = frappe.get_cached_value(self.voucher_type, self.voucher_no, "company")
elif self.warehouse:
self.company = frappe.get_cached_value("Warehouse", self.warehouse, "company")
-
+
def set_status(self, status=None):
if not status:
status = 'Queued'
@@ -39,6 +39,7 @@
frappe.enqueue(repost, timeout=1800, queue='long',
job_name='repost_sle', now=frappe.flags.in_test, doc=self)
+ @frappe.whitelist()
def restart_reposting(self):
self.set_status('Queued')
frappe.enqueue(repost, timeout=1800, queue='long',
@@ -54,7 +55,6 @@
repost_sl_entries(doc)
repost_gl_entries(doc)
- check_if_stock_and_account_balance_synced(doc.posting_date, doc.company)
doc.set_status('Completed')
except Exception:
@@ -103,7 +103,7 @@
recipients = get_users_with_role("Stock Manager")
if not recipients:
get_users_with_role("System Manager")
-
+
subject = _("Error while reposting item valuation")
message = (_("Hi,") + "<br>"
+ _("An error has been appeared while reposting item valuation via {0}")
@@ -112,4 +112,24 @@
)
frappe.sendmail(recipients=recipients, subject=subject, message=message)
+def repost_entries():
+ riv_entries = get_repost_item_valuation_entries()
+ for row in riv_entries:
+ doc = frappe.get_cached_doc('Repost Item Valuation', row.name)
+ repost(doc)
+
+ riv_entries = get_repost_item_valuation_entries()
+ if riv_entries:
+ return
+
+ for d in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}):
+ check_if_stock_and_account_balance_synced(today(), d.name)
+
+def get_repost_item_valuation_entries():
+ date = add_to_date(today(), 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
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index c8d8ca9..c02dd2e 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
@@ -322,11 +323,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({
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 16e7463..8b4bac2 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -100,6 +100,13 @@
frm.add_fetch("bom_no", "inspection_required", "inspection_required");
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
+
+ frappe.db.get_single_value('Stock Settings', 'disable_serial_no_and_batch_selector')
+ .then((value) => {
+ if (value) {
+ frappe.flags.hide_serial_batch_dialog = true;
+ }
+ });
},
setup_quality_inspection: function(frm) {
@@ -551,7 +558,6 @@
})
);
}
-
for (let i in frm.doc.items) {
let item = frm.doc.items[i];
@@ -721,7 +727,7 @@
no_batch_serial_number_value = !d.batch_no;
}
- if (no_batch_serial_number_value) {
+ if (no_batch_serial_number_value && !frappe.flags.hide_serial_batch_dialog) {
erpnext.stock.select_batch_and_serial_no(frm, d);
}
}
@@ -849,7 +855,6 @@
}
erpnext.hide_company();
erpnext.utils.add_item(this.frm);
- this.frm.trigger('add_to_transit');
}
scan_barcode() {
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index ea1b387..48cfa51 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -398,8 +398,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 +439,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.
@@ -458,7 +463,7 @@
Set rate for outgoing, scrapped and finished items
"""
# Set rate for outgoing items
- outgoing_items_cost = self.set_rate_for_outgoing_items(reset_outgoing_rate)
+ outgoing_items_cost = self.set_rate_for_outgoing_items(reset_outgoing_rate, raise_error_if_no_rate)
finished_item_qty = sum([d.transfer_qty for d in self.items if d.is_finished_item])
# Set basic rate for incoming items
@@ -482,13 +487,13 @@
d.basic_rate = flt(d.basic_rate, d.precision("basic_rate"))
d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
- def set_rate_for_outgoing_items(self, reset_outgoing_rate=True):
+ def set_rate_for_outgoing_items(self, reset_outgoing_rate=True, raise_error_if_no_rate=True):
outgoing_items_cost = 0.0
for d in self.get('items'):
if d.s_warehouse:
if reset_outgoing_rate:
args = self.get_args_for_incoming_rate(d)
- rate = get_incoming_rate(args)
+ rate = get_incoming_rate(args, raise_error_if_no_rate)
if rate > 0:
d.basic_rate = rate
@@ -839,6 +844,7 @@
if not pro_doc.operations:
pro_doc.set_actual_dates()
+ @frappe.whitelist()
def get_item_details(self, args=None, for_update=False):
item = frappe.db.sql("""select i.name, i.stock_uom, i.description, i.image, i.item_name, i.item_group,
i.has_batch_no, i.sample_quantity, i.has_serial_no, i.allow_alternative_item,
@@ -913,6 +919,7 @@
return ret
+ @frappe.whitelist()
def set_items_for_stock_in(self):
self.items = []
@@ -937,6 +944,7 @@
'batch_no': d.batch_no
})
+ @frappe.whitelist()
def get_items(self):
self.set('items', [])
self.validate_work_order()
@@ -1010,7 +1018,8 @@
self.set_scrap_items()
self.set_actual_qty()
- self.calculate_rate_and_amount(raise_error_if_no_rate=False)
+ self.validate_customer_provided_item()
+ self.calculate_rate_and_amount()
def set_scrap_items(self):
if self.purpose != "Send to Subcontractor" and self.purpose in ["Manufacture", "Repack"]:
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 123f0c8..a0e7051 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -179,11 +179,15 @@
def test_material_transfer_gl_entry(self):
company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
- mtn = make_stock_entry(item_code="_Test Item", source="Stores - TCP1",
+ item_code = 'Hand Sanitizer - 001'
+ create_item(item_code =item_code, is_stock_item = 1,
+ is_purchase_item=1, opening_stock=1000, valuation_rate=10, company=company, warehouse="Stores - TCP1")
+
+ mtn = make_stock_entry(item_code=item_code, source="Stores - TCP1",
target="Finished Goods - TCP1", qty=45, company=company)
self.check_stock_ledger_entries("Stock Entry", mtn.name,
- [["_Test Item", "Stores - TCP1", -45.0], ["_Test Item", "Finished Goods - TCP1", 45.0]])
+ [[item_code, "Stores - TCP1", -45.0], [item_code, "Finished Goods - TCP1", 45.0]])
source_warehouse_account = get_inventory_account(mtn.company, mtn.get("items")[0].s_warehouse)
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 59f1f39..3296f5b 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
@@ -34,7 +34,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 +46,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 +58,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 +90,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'
)
@@ -125,7 +125,7 @@
pr = make_purchase_receipt(company="_Test Company", posting_date='2020-04-10',
warehouse="Stores - _TC", item_code="_Test Item for Reposting", qty=5, rate=100)
- return_pr = make_purchase_receipt(company="_Test Company", posting_date='2020-04-15',
+ return_pr = make_purchase_receipt(company="_Test Company", posting_date='2020-04-15',
warehouse="Stores - _TC", item_code="_Test Item for Reposting", is_return=1, return_against=pr.name, qty=-2)
# check sle
@@ -278,7 +278,7 @@
frappe.db.set_value("Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM")
make_bom(item = subcontracted_item, raw_materials =[rm_item_code], currency="INR")
-
+
# Purchase raw materials on supplier warehouse: Qty = 50, Rate = 100
pr = make_purchase_receipt(company=company, posting_date='2020-04-10',
warehouse="Stores - _TC", item_code=rm_item_code, qty=10, rate=100)
@@ -292,7 +292,7 @@
# Update raw material's valuation via LCV, Additional cost = 50
lcv = create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company)
-
+
pr1.reload()
self.assertEqual(pr1.items[0].valuation_rate, 125)
@@ -310,31 +310,36 @@
# Back dated stock transactions are only allowed to stock managers
frappe.db.set_value("Stock Settings", None,
"role_allowed_to_create_edit_back_dated_transactions", "Stock Manager")
-
+
# Set User with Stock User role but not Stock Manager
- frappe.set_user("test@example.com")
- user = frappe.get_doc("User", "test@example.com")
- user.add_roles("Stock User")
- user.remove_roles("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")
- 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)
+ 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)
- # Block back-dated entry
- self.assertRaises(BackDatedStockTransaction, back_dated_se_1.submit)
+ # Block back-dated entry
+ self.assertRaises(BackDatedStockTransaction, back_dated_se_1.submit)
- user.add_roles("Stock Manager")
+ frappe.set_user("Administrator")
+ user.add_roles("Stock Manager")
+ frappe.set_user(user.name)
- # Back dated entry allowed to Stock Manager
- back_dated_se_2 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100,
- posting_date=add_days(today(), -1))
+ # Back dated entry allowed to Stock Manager
+ back_dated_se_2 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100,
+ posting_date=add_days(today(), -1))
- back_dated_se_2.cancel()
- stock_entry_on_today.cancel()
+ back_dated_se_2.cancel()
+ stock_entry_on_today.cancel()
- frappe.db.set_value("Stock Settings", None, "role_allowed_to_create_edit_back_dated_transactions", None)
- frappe.set_user("Administrator")
+ finally:
+ frappe.db.set_value("Stock Settings", None, "role_allowed_to_create_edit_back_dated_transactions", None)
+ frappe.set_user("Administrator")
+ user.remove_roles("Stock Manager")
def create_repack_entry(**args):
@@ -398,4 +403,4 @@
make_item(d, properties=properties)
- return items
\ No newline at end of file
+ return items
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index f0a90f9..7c5f4ec 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -29,6 +29,8 @@
self.remove_items_with_no_change()
self.validate_data()
self.validate_expense_account()
+ self.validate_customer_provided_item()
+ self.set_zero_value_for_customer_provided_items()
self.set_total_qty_and_amount()
self.validate_putaway_capacity()
@@ -217,7 +219,7 @@
if row.valuation_rate in ("", None):
row.valuation_rate = previous_sle.get("valuation_rate", 0)
- if row.qty and not row.valuation_rate:
+ if row.qty and not row.valuation_rate and not row.allow_zero_valuation_rate:
frappe.throw(_("Valuation Rate required for Item {0} at row {1}").format(row.item_code, row.idx))
if ((previous_sle and row.qty == previous_sle.get("qty_after_transaction")
@@ -396,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
@@ -436,6 +438,20 @@
if frappe.db.get_value("Account", self.expense_account, "report_type") == "Profit and Loss":
frappe.throw(_("Difference Account must be a Asset/Liability type account, since this Stock Reconciliation is an Opening Entry"), OpeningEntryAccountError)
+ def set_zero_value_for_customer_provided_items(self):
+ changed_any_values = False
+
+ for d in self.get('items'):
+ is_customer_item = frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item')
+ if is_customer_item and d.valuation_rate:
+ d.valuation_rate = 0.0
+ changed_any_values = True
+
+ if changed_any_values:
+ msgprint(_("Valuation rate for customer provided items has been set to zero."),
+ title=_("Note"), indicator="blue")
+
+
def set_total_qty_and_amount(self):
for d in self.get("items"):
d.amount = flt(d.qty, d.precision("qty")) * flt(d.valuation_rate, d.precision("valuation_rate"))
@@ -453,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=4600)
else:
self._submit()
@@ -531,4 +547,4 @@
account = frappe.db.get_value('Account', {'is_group': 0,
'company': company, 'account_type': 'Temporary'}, 'name')
- return account
\ No newline at end of file
+ return account
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 088456f..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"})
@@ -193,6 +193,16 @@
stock_doc = frappe.get_doc("Stock Reconciliation", d)
stock_doc.cancel()
+ def test_customer_provided_items(self):
+ item_code = 'Stock-Reco-customer-Item-100'
+ create_item(item_code, is_customer_provided_item = 1,
+ customer = '_Test Customer', is_purchase_item = 0)
+
+ sr = create_stock_reconciliation(item_code = item_code, qty = 10, rate = 420)
+
+ self.assertEqual(sr.get("items")[0].allow_zero_valuation_rate, 1)
+ self.assertEqual(sr.get("items")[0].valuation_rate, 0)
+ self.assertEqual(sr.get("items")[0].amount, 0)
def insert_existing_sle(warehouse):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
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 e53db07..85c7ebe 100644
--- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
+++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
@@ -13,6 +13,7 @@
"qty",
"valuation_rate",
"amount",
+ "allow_zero_valuation_rate",
"serial_no_and_batch_section",
"serial_no",
"column_break_11",
@@ -166,10 +167,19 @@
"fieldtype": "Link",
"label": "Batch No",
"options": "Batch"
+ },
+ {
+ "default": "0",
+ "fieldname": "allow_zero_valuation_rate",
+ "fieldtype": "Check",
+ "label": "Allow Zero Valuation Rate",
+ "print_hide": 1,
+ "read_only": 1
}
],
"istable": 1,
- "modified": "2019-06-14 17:10:53.188305",
+ "links": [],
+ "modified": "2021-03-23 11:09:44.407157",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation Item",
@@ -179,4 +189,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..f18eabc 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -13,6 +13,7 @@
"column_break_4",
"valuation_method",
"over_delivery_receipt_allowance",
+ "role_allowed_to_over_deliver_receive",
"action_if_quality_inspection_is_not_submitted",
"show_barcode_field",
"clean_description_html",
@@ -234,6 +235,13 @@
"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"
}
],
"icon": "icon-cog",
@@ -241,7 +249,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-01-18 13:15:38.352796",
+ "modified": "2021-03-11 18:48:14.513055",
"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/get_item_details.py b/erpnext/stock/get_item_details.py
index 873cfec..3fc1df7 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -86,7 +86,7 @@
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):
@@ -110,7 +110,7 @@
get_gross_profit(out)
if args.doctype == 'Material Request':
out.rate = args.rate or out.price_list_rate
- out.amount = flt(args.qty * out.rate)
+ out.amount = flt(args.qty) * flt(out.rate)
return out
@@ -309,12 +309,12 @@
"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"),
- "bom_no": item.get("default_bom")
+ "bom_no": item.get("default_bom"),
+ "weight_per_unit": args.get("weight_per_unit") or item.get("weight_per_unit"),
+ "weight_uom": args.get("weight_uom") or item.get("weight_uom")
})
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
@@ -369,6 +369,9 @@
if meta.get_field("barcode"):
update_barcode_value(out)
+ if out.get("weight_per_unit"):
+ out['total_weight'] = out.weight_per_unit * out.stock_qty
+
return out
def get_item_warehouse(item, args, overwrite_warehouse, defaults={}):
@@ -606,8 +609,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)
@@ -917,10 +924,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 = '{0}' and `tabBin`.item_code = '{1}'"""
+ .format(company, item_code))[0][0]
@frappe.whitelist()
def get_serial_no_details(item_code, warehouse, stock_qty, serial_no):
@@ -988,6 +1004,8 @@
args = process_args(args)
parent = get_price_list_currency_and_exchange_rate(args)
+ args.update(parent)
+
children = []
if "items" in args:
@@ -1052,7 +1070,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/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/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/stock_ledger.py b/erpnext/stock/stock_ledger.py
index f54b3c1..bbfcb7a 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -207,11 +207,11 @@
def build(self):
- from erpnext.controllers.stock_controller import check_if_future_sle_exists
+ from erpnext.controllers.stock_controller import future_sle_exists
if self.args.get("sle_id"):
self.process_sle_against_current_timestamp()
- if not check_if_future_sle_exists(self.args):
+ if not future_sle_exists(self.args):
self.update_bin()
else:
entries_to_fix = self.get_future_entries_to_fix()
@@ -372,7 +372,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 +416,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:
@@ -603,7 +604,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 +618,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)
@@ -856,4 +857,13 @@
and qty_after_transaction < 0
order by timestamp(posting_date, posting_time) asc
limit 1
- """, args, as_dict=1)
\ No newline at end of file
+ """, 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.py b/erpnext/support/doctype/issue/issue.py
index bbbbc4a..b068363 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -7,7 +7,7 @@
from frappe import _
from frappe import utils
from frappe.model.document import Document
-from frappe.utils import now_datetime, getdate, get_weekdays, add_to_date, get_time, get_datetime, time_diff_in_seconds
+from frappe.utils import cint, now_datetime, getdate, get_weekdays, add_to_date, get_time, get_datetime, time_diff_in_seconds
from datetime import datetime, timedelta
from frappe.model.mapper import get_mapped_doc
from frappe.utils.user import is_website_user
@@ -128,8 +128,8 @@
def update_agreement_status(self):
if self.service_level_agreement and self.agreement_status == "Ongoing":
- if frappe.db.get_value("Issue", self.name, "response_by_variance") < 0 or \
- frappe.db.get_value("Issue", self.name, "resolution_by_variance") < 0:
+ if cint(frappe.db.get_value("Issue", self.name, "response_by_variance")) < 0 or \
+ cint(frappe.db.get_value("Issue", self.name, "resolution_by_variance")) < 0:
self.agreement_status = "Failed"
else:
@@ -165,6 +165,7 @@
communication.ignore_mandatory = True
communication.save()
+ @frappe.whitelist()
def split_issue(self, subject, communication_id):
# Bug: Pressing enter doesn't send subject
from copy import deepcopy
@@ -259,6 +260,7 @@
self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
frappe.msgprint(_("Service Level Agreement has been changed to {0}.").format(self.service_level_agreement))
+ @frappe.whitelist()
def reset_service_level_agreement(self, reason, user):
if not frappe.db.get_single_value("Support Settings", "allow_resetting_service_level_agreement"):
frappe.throw(_("Allow Resetting Service Level Agreement from Support Settings."))
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..eb0e06c 100644
--- a/erpnext/support/report/issue_summary/issue_summary.js
+++ b/erpnext/support/report/issue_summary/issue_summary.js
@@ -39,6 +39,7 @@
label: __("Status"),
fieldtype: "Select",
options:[
+ "",
{label: __('Open'), value: 'Open'},
{label: __('Replied'), value: 'Replied'},
{label: __('Resolved'), value: 'Resolved'},
@@ -70,4 +71,4 @@
options: "User"
}
]
-};
\ No newline at end of file
+};
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/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/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..f1ffeb8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,15 +1,13 @@
-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
+WooCommerce~=3.0.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==