diff --git a/.github/helper/semgrep_rules/README.md b/.github/helper/semgrep_rules/README.md new file mode 100644 index 00000000000..670d8d280f2 --- /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 00000000000..4798b927f83 --- /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 00000000000..54df0624806 --- /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 00000000000..f477d7c1768 --- /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 00000000000..5a5098bf506 --- /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 00000000000..7b92fe2dffb --- /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 00000000000..bd6cd9126c9 --- /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 00000000000..3737da5a7e2 --- /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 00000000000..4a744574350 --- /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 00000000000..ed06a6a80c9 --- /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 78c2f5a187b..84ecfb14571 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -80,15 +80,29 @@ jobs: 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 00000000000..df082632365 --- /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 bb592ae75c5..0a556f57b41 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,10 @@ ERPNext is built on the [Frappe Framework](https://github.com/frappe/frappe), a --- +### 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 199a183e479..6775398532b 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe 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 c801cfcbba6..1be2fbf5c81 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -13,7 +13,7 @@ class BalanceMismatchError(frappe.ValidationError): pass 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 @@ class Account(NestedSet): if parent_value_changed: doc.save() + @frappe.whitelist() def convert_group_to_ledger(self): if self.check_if_child_exists(): throw(_("Account with child nodes cannot be converted to ledger")) @@ -224,6 +225,7 @@ class Account(NestedSet): self.save() return 1 + @frappe.whitelist() def convert_ledger_to_group(self): if self.check_gle_exists(): throw(_("Account with existing transaction can not be converted to group.")) 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 0e3b24cda3d..927adc7086c 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 @@ def create_charts(company, chart_template=None, existing_company=None, custom_ch # Rebuild NestedSet HSM tree for Account Doctype # after all accounts are already inserted. - frappe.local.flags.ignore_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 da1d10deb97..d60c5596618 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 00000000000..b6673795bea --- /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 df6cedd7cf8..63b5dbbd3e6 100644 --- a/erpnext/accounts/doctype/accounting_period/accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/accounting_period.py @@ -39,6 +39,7 @@ class AccountingPeriod(Document): 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 a3c29b6d640..e1276e7da3d 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 5593466fc2b..4d3388090dc 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -30,5 +30,5 @@ class AccountsSettings(Document): 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 49b2b186c4b..059e1d31588 100644 --- a/erpnext/accounts/doctype/bank/bank.js +++ b/erpnext/accounts/doctype/bank/bank.js @@ -42,10 +42,9 @@ let add_fields_to_mapping_table = function (frm) { }); }); - 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 76d82e73393..79f5596384c 100644 --- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py +++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py @@ -12,6 +12,7 @@ form_grid_templates = { } 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 @@ class BankClearance(Document): 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 f2c3dea1160..78e7ff6ea29 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 @@ frappe.ui.form.on("Bank Reconciliation Tool", { 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 4837db3b867..b643e6e0912 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 2b6aeee1bc6..0d4e56ff14a 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 @@ frappe.ui.form.on("Bank Statement Import", { `); }, - - 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 ` -
${doctype}
- - `; - }) - .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 69ee4971cd5..88aa7ef8b59 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 3b14e4efa02..ce149f96e6f 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 @@ from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profi 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 @@ class TestBankTransaction(unittest.TestCase): # 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 @@ class TestBankTransaction(unittest.TestCase): # 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 @@ class TestBankTransaction(unittest.TestCase): 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 @@ def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"): 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 @@ def add_transactions(): }).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 @@ def add_vouchers(): 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 @@ def add_vouchers(): }) 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 9b64f8100fe..fd86ed4c90e 100644 --- a/erpnext/accounts/doctype/c_form/c_form.py +++ b/erpnext/accounts/doctype/c_form/c_form.py @@ -57,6 +57,7 @@ class CForm(Document): 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 03c3eb0ac0b..f96f59169e8 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 @@ def validate_accounts(file_name): 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 += "

" + 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 12094d4f989..8a5473f3a16 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.py +++ b/erpnext/accounts/doctype/cost_center/cost_center.py @@ -50,6 +50,7 @@ class CostCenter(NestedSet): 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 @@ class CostCenter(NestedSet): 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 1092f4c8f10..b7b6020caa9 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 @@ frappe.ui.form.on('Exchange Rate Revaluation', { 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 9594706d0f6..56193216a22 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 @@ class ExchangeRateRevaluation(Document): 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 @@ class ExchangeRateRevaluation(Document): 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 da6a3fd2ef9..42556269fdd 100644 --- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py +++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py @@ -12,6 +12,7 @@ from frappe.model.document import Document 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 @@ class FiscalYear(Document): 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 ce76d0a39cc..78febf9c2ee 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -290,4 +290,8 @@ def rename_temporarily_named_docs(doctype): 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 af8940cde5b..7b62b617f97 100644 --- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py +++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py @@ -125,6 +125,7 @@ class InvoiceDiscounting(AccountsController): 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 @@ class InvoiceDiscounting(AccountsController): 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 2f8d3e31d95..957a50f0132 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -327,18 +327,16 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro } 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 ? ('') : ""; - return "
" - + ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency)) - + " " + dr_or_cr - + "
"; - } - }) + const formatter = function(value, df, options, doc) { + var currency = frappe.meta.get_field_currency(df, doc); + var dr_or_cr = value ? ('') : ""; + return "
" + + ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency)) + + " " + dr_or_cr + + "
"; + }; + 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.validate = function(doc,cdt,cdn) { 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 @@ $.extend(erpnext.journal_entry, { }; $.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 3419bb6c3e9..fefab82efc5 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -564,6 +564,7 @@ class JournalEntry(AccountsController): 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 @@ class JournalEntry(AccountsController): 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 774159d6919..a89fefde073 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 18f853cadce..88667d72076 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.utils import (flt, add_months) 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 e6449b78316..29dc96e8c6f 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 @@ class OpeningInvoiceCreationTool(Document): 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 b5f6a401df4..b80e8ada38f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -561,7 +561,7 @@ frappe.ui.form.on('Payment Entry', { 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 @@ frappe.ui.form.on('Payment Entry', { } 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 @@ frappe.ui.form.on('Payment Entry', { {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 @@ frappe.ui.form.on('Payment Entry', { 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 @@ frappe.ui.form.on('Payment Entry', { 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 @@ frappe.ui.form.on('Payment Entry', { }); }, - 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 @@ frappe.ui.form.on('Payment Entry', { } 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 @@ frappe.ui.form.on('Payment Entry', { } $.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 8acd92cb6b5..62ab76c3238 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -333,33 +333,50 @@ class PaymentEntry(AccountsController): 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, 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')) - for key, amount in iteritems(invoice_payment_amount_map): 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 @@ def get_outstanding_reference_documents(args): 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 @@ def get_outstanding_reference_documents(args): 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 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= 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 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= 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 @@ def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outsta 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 772fc1a2521..4641d6b5ffa 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 @@ class TestPaymentEntry(unittest.TestCase): 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 @@ def create_payment_terms_template(): }] }).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 8f5e9fbc286..912ad0977a2 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 dd91fec3c17..8dcd1aa8e7f 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -234,8 +234,9 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo }); 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 f7a15c04faa..cf6ec18f3b8 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.accounts.utils import (get_outstanding_invoices, 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 @@ class PaymentReconciliation(Document): 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 @@ class PaymentReconciliation(Document): '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 d363cf161b5..6ed7a3154e5 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 054c2d11917..acd0144c2ea 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 e77c244d3dc..aec4965d79a 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 f5c5bca87a7..84c8d09b164 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 2b2b6afe79f..80e3348d817 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 @@ from frappe import _ 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 @@ class PaymentTermsTemplate(Document): 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 eee32233144..20b3dca6aae 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 a74fa062b6a..fdb4f6fc716 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 @@ class PeriodClosingVoucher(AccountsController): 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 9ea616f8e77..aa0c53e228b 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 @@ frappe.ui.form.on('POS Closing Entry', { }); 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 = 'issue'; + 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 @@ frappe.ui.form.on('POS Closing Entry', { 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 refresh_fields(frm) { } 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 a9b91e02a9d..4d6e4a2ba07 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 f5224a269e1..82528728ddc 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 @@ class POSClosingEntry(StatusUpdater): 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 @@ class POSClosingEntry(StatusUpdater): 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 @@ class POSClosingEntry(StatusUpdater): @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 20fd610899e..cffeb4d5351 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 @@ frappe.listview_settings['POS Closing Entry'] = { "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 40db09ec3be..b596c0cf25a 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 @@ from __future__ import unicode_literals 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 @@ class TestPOSClosingEntry(unittest.TestCase): 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 @@ class TestPOSClosingEntry(unittest.TestCase): 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 @@ def init_user_and_profile(**args): 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 76e00923c49..1e6a3d1b3be 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -57,7 +57,7 @@ class POSInvoice(SalesInvoice): 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 @@ class POSInvoice(SalesInvoice): 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 @@ class POSInvoice(SalesInvoice): 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 @@ class POSInvoice(SalesInvoice): 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 @@ class POSInvoice(SalesInvoice): 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 @@ class POSInvoice(SalesInvoice): return profile + @frappe.whitelist() def set_missing_values(self, for_validate=False): profile = self.set_pos_fields(for_validate) @@ -377,12 +387,20 @@ class POSInvoice(SalesInvoice): "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 @@ class POSInvoice(SalesInvoice): 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 054afe5bbba..6172796129f 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.accounts.doctype.pos_invoice.pos_invoice import make_sales_return 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 @@ class TestPOSInvoice(unittest.TestCase): 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 40f77b4088d..bc7874305c0 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.utils.background_jobs import enqueue 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 @@ class POSInvoiceMergeLog(Document): 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 @@ class POSInvoiceMergeLog(Document): 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 @@ class POSInvoiceMergeLog(Document): 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 @@ class POSInvoiceMergeLog(Document): 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 @@ class POSInvoiceMergeLog(Document): 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 @@ def get_invoice_customer_map(pos_invoices): 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 @@ def unconsolidate_pos_invoices(closing_entry): 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 check_scheduler_status(): 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 db046c98008..040a815fab3 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 @@ from __future__ import unicode_literals 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 @@ class TestPOSInvoiceMergeLog(unittest.TestCase): 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) + + finally: + frappe.set_user("Administrator") + frappe.db.sql("delete from `tabPOS Profile`") + frappe.db.sql("delete from `tabPOS Invoice`") - 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 ee76bba7500..cf7ed26d277 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py @@ -62,14 +62,15 @@ class POSProfile(Document): 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 62dc1fcb209..0033965700e 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 @@ def make_pos_profile(**args): "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% rename from erpnext/selling/doctype/lead_source/__init__.py rename 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 00000000000..a627f5b5be4 --- /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 00000000000..720ea77745f --- /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 8890d594036..9003af56a5d 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.ui.form.on('POS Settings', { } }); - 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 35395889a6a..962eb94a295 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 33771645fe2..428989aa965 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", @@ -458,10 +459,6 @@ "fieldtype": "Float", "label": "Qty" }, - { - "fieldname": "column_break_51", - "fieldtype": "Column Break" - }, { "fieldname": "free_item_uom", "fieldtype": "Link", @@ -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 f0b4e2976d0..aedf1c6f1a6 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -237,6 +237,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa "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 f28cee7c5af..ef9aad562df 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 @@ class TestPricingRule(unittest.TestCase): 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 @@ def make_pricing_rule(**args): "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 d1633359963..d23b952bdc2 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -173,7 +173,7 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True): 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 @@ def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args): 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 @@ def apply_pricing_rule_on_transaction(doc): 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 @@ def apply_pricing_rule_on_transaction(doc): 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 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): 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 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): 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 e1ddeff61f7..f61aacbce27 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 @@ - {% for row in data %} + {% for row in data %} {% if(row.posting_date) %} {{ frappe.format(row.posting_date, 'Date') }} @@ -38,30 +38,30 @@ {% endif %} - {{ frappe.utils.fmt_money(row.debit, filters.presentation_currency) }} + {{ frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }} - {{ frappe.utils.fmt_money(row.credit, filters.presentation_currency) }} + {{ frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }} {% else %} {{ frappe.format(row.account, {fieldtype: "Link"}) or " " }} - {{ 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) }} - {{ 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) }} {% endif %} - {{ frappe.utils.fmt_money(row.balance, filters.presentation_currency) }} + {{ frappe.utils.fmt_money(row.balance, currency=filters.presentation_currency) }} {% endfor %}

-{% if aging %} -

{{ _("Ageing Report Based On ") }} {{ aging.ageing_based_on }}

+{% if ageing %} +

{{ _("Ageing Report Based On ") }} {{ ageing.ageing_based_on }}

{{ _("Up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
@@ -78,10 +78,10 @@ - {{ aging.range1 }} - {{ aging.range2 }} - {{ aging.range3 }} - {{ aging.range4 }} + {{ frappe.utils.fmt_money(ageing.range1, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }} 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 7425132c468..6dc46430e06 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 @@ frappe.ui.form.on('Process Statement Of Accounts', { 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 d50e4a8af95..a0dbff3db43 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 @@ class ProcessStatementOfAccounts(Document): 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 @@ class ProcessStatementOfAccounts(Document): 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 @@ def get_report_pdf(doc, consolidated=True): '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 @@ def get_report_pdf(doc, consolidated=True): 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 @@ def get_customers_based_on_sales_person(sales_person): 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 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory): 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 @@ def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=Tr 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 89f7238a061..7d9302382f1 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.utils import cstr 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 @@ def get_args_for_pricing_rule(doc): 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 224b8de779d..795fb1c6f46 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 72d53bfa016..3eab51510db 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 b31cdce0b2f..617b5b49d4c 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.fields_dict['items'].grid.get_field('project').get_query = function(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 = __("Purchase Invoice"); -} - frappe.ui.form.on("Purchase Invoice", { setup: function(frm) { frm.custom_make_buttons = { @@ -523,8 +514,30 @@ frappe.ui.form.on("Purchase Invoice", { } }, + 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 @@ frappe.ui.form.on("Purchase Invoice", { 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 18b66375e99..24e67febca5 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": [ { @@ -1326,13 +1326,6 @@ "label": "Project", "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", @@ -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 5c4e32e493e..83e9f7583e9 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1207,3 +1207,41 @@ def make_inter_company_sales_invoice(source_name, target_doc=None): 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 ded293b88d5..66be11ff231 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 @@ class TestPurchaseInvoice(unittest.TestCase): 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 @@ class TestPurchaseInvoice(unittest.TestCase): 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 e7166c5a12d..9f9e90d8a75 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 96ad0fd7852..10e1c73ea90 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 3e1c5228ead..ada665a0ca9 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.listview_settings['Sales Invoice'].onload = function (doclist) { 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 @@ frappe.listview_settings['Sales Invoice'].onload = function (doclist) { }); }; - 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: + + `; + 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: + + `; + 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 8e8f85a3e48..95e570b4d9d 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 @@ frappe.ui.form.on('Sales Invoice Timesheet', { }, 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 720a9175e66..c6c67b4ddc1 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 a1bf66b03e5..4461f29fe37 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.deferred_revenue import validate_service_stop_date 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 @@ class SalesInvoice(SellingController): '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', @@ -211,6 +211,9 @@ class SalesInvoice(SellingController): # this sequence because outstanding may get -ve self.make_gl_entries() + if self.update_stock == 1: + self.repost_future_sle_and_gle() + if self.update_stock == 1: self.repost_future_sle_and_gle() @@ -272,7 +275,7 @@ class SalesInvoice(SellingController): 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 @@ class SalesInvoice(SellingController): 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 @@ class SalesInvoice(SellingController): 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 @@ class SalesInvoice(SellingController): else: self.calculate_billing_amount_for_timesheet() + @frappe.whitelist() def add_timesheet_data(self): self.set('timesheets', []) if self.project: @@ -1286,6 +1291,7 @@ class SalesInvoice(SellingController): 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 e00a58f8641..3781f8ccc9b 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 90e21444f54..9059d0b0404 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 @@ class TestSalesInvoice(unittest.TestCase): 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 @@ class TestSalesInvoice(unittest.TestCase): 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 @@ class TestSalesInvoice(unittest.TestCase): 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 @@ def create_sales_invoice(**args): 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 e80df2ab886..c4e4be7f781 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 826044a4075..7c4ff73d908 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -276,7 +276,7 @@ class Subscription(Document): 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 @@ class Subscription(Document): 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 632e30db45d..ac1ffd9e750 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 @@ test_records = frappe.get_test_records('Tax Rule') 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 961bdb147f2..09db7fee2b1 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 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu 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 dd3b49aa04b..0cea7612dd3 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 @@ class TestTaxWithholdingCategory(unittest.TestCase): 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 dac0c216c8a..f1717c50d8d 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -18,7 +18,8 @@ def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, upd 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 @@ def round_off_debit_credit(gl_map): 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 51fc7ec49aa..444b40ed798 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -364,7 +364,7 @@ class ReceivablePayableReport(object): 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 @@ class ReceivablePayableReport(object): "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/accounts/report/billed_items_to_be_received/__init__.py b/erpnext/accounts/report/billed_items_to_be_received/__init__.py new file mode 100644 index 00000000000..e69de29bb2d 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 00000000000..e1fccb6e720 --- /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 00000000000..de09b33c960 --- /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 00000000000..2ce5d50edf4 --- /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 7dfce85629b..14efa1f8fc7 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -51,7 +51,11 @@ def get_period_list(from_fiscal_year, to_fiscal_year, period_start_date, period_ "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 89a05b187d1..5a64e27ccb7 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -406,9 +406,10 @@ def check_if_advance_entry_modified(args): 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 fadb66535f5..9ffa481c1cb 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -443,6 +443,16 @@ "onboard": 0, "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, diff --git a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py b/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py index afbd9b4e6e0..9000dea913b 100644 --- a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py +++ b/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py @@ -71,6 +71,7 @@ class CropCycle(Document): "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 @@ class CropCycle(Document): 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 dc2781cf006..9cb492aff1e 100644 --- a/erpnext/agriculture/doctype/fertilizer/fertilizer.py +++ b/erpnext/agriculture/doctype/fertilizer/fertilizer.py @@ -7,6 +7,7 @@ import frappe 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 304727e04fb..2806cc6523e 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.naming import make_autoname 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 17b96a0ac15..37835f8c7b1 100644 --- a/erpnext/agriculture/doctype/soil_analysis/soil_analysis.py +++ b/erpnext/agriculture/doctype/soil_analysis/soil_analysis.py @@ -7,6 +7,7 @@ import frappe 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 8c1d7ed5ac1..209b2c8598f 100644 --- a/erpnext/agriculture/doctype/soil_texture/soil_texture.py +++ b/erpnext/agriculture/doctype/soil_texture/soil_texture.py @@ -13,6 +13,7 @@ class SoilTexture(Document): 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 @@ class SoilTexture(Document): 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 @@ class SoilTexture(Document): 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 @@ class SoilTexture(Document): 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 88f1fbd9cce..d9f007cea13 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.model.document import Document 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 938daa207e8..235e684e511 100644 --- a/erpnext/agriculture/doctype/weather/weather.py +++ b/erpnext/agriculture/doctype/weather/weather.py @@ -7,6 +7,7 @@ import frappe 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 e8e8ec6cc0b..9aff1440d6a 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -553,6 +553,7 @@ class Asset(AccountsController): 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 248cb9a8a0e..630a1dc8cd5 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 d32e98e8d94..ef9372eeb65 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -133,6 +133,7 @@ class PurchaseOrder(BuyingController): 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 @@ class PurchaseOrder(BuyingController): 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 @@ def make_purchase_receipt(source_name, target_doc=None): "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 604c88682f7..42f4472f29e 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 @@ class TestPurchaseOrder(unittest.TestCase): 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 @@ class TestPurchaseOrder(unittest.TestCase): 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 @@ class TestPurchaseOrder(unittest.TestCase): 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 @@ class TestPurchaseOrder(unittest.TestCase): 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 @@ class TestPurchaseOrder(unittest.TestCase): 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 @@ class TestPurchaseOrder(unittest.TestCase): 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 @@ class TestPurchaseOrder(unittest.TestCase): 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 @@ class TestPurchaseOrder(unittest.TestCase): 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 @@ class TestPurchaseOrder(unittest.TestCase): 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 5baf6939cd3..1dbd7c60c3e 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", @@ -743,6 +745,22 @@ "options": "currency", "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", @@ -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 7cf22f87e4f..b530d1ab241 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 @@ class RequestforQuotation(BuyingController): 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 6e6eaed95d3..25282405492 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 @@ import unittest 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 @@ valid_scorecard = [ { "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 00000000000..2bfbdfcc5db --- /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 00000000000..a6cebabab10 --- /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)) + +
+More + +- 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)) +
\ 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 00000000000..d991034e36e --- /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 00000000000..eb9499d115b --- /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 12a81c78879..c409850734c 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -26,7 +26,8 @@ from erpnext.controllers.print_settings import set_print_templates_for_item_tabl 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 @@ class AccountsController(TransactionBase): 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 @@ class AccountsController(TransactionBase): 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 @@ class AccountsController(TransactionBase): 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 @@ class AccountsController(TransactionBase): 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 @@ class AccountsController(TransactionBase): return {} + @frappe.whitelist() def set_advances(self): """Returns list of advances against Account, Party, Reference""" @@ -656,6 +678,7 @@ class AccountsController(TransactionBase): '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 @@ class AccountsController(TransactionBase): 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 @@ class AccountsController(TransactionBase): 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 @@ class AccountsController(TransactionBase): 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 @@ def update_invoice_status(): @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 @@ def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_ 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 @@ def get_payment_term_details(term, posting_date=None, grand_total=None, bill_dat 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 @@ def get_due_date(term, posting_date=None, bill_date=None): 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 @@ def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child 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 @@ def validate_and_delete_children(parent, data): 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 update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil ) 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 219d5295c38..b686dc026c6 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -6,6 +6,7 @@ import frappe 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 @@ class BuyingController(StockController): 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 @@ def get_transferred_batch_qty_map(purchase_order, fg_item): 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 @@ def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty 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 1f95e004244..051481ff603 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -262,7 +262,8 @@ def copy_attributes_to_variant(item, variant): # 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 81f0ad3fed1..bc1ac5ea069 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -325,7 +325,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, 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 @@ def get_tax_template(doctype, txt, searchfield, start, page_len, filters): 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 de61b35316e..5f759b43bc6 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals 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 @@ def make_return_doc(doctype, source_name, target_doc=None): 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 edc40c430ac..54156f379c5 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -311,14 +311,16 @@ class SellingController(StockController): 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 0987d0985ea..4bb6138e5d7 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -98,6 +98,7 @@ status_map = { ["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 @@ class StatusUpdater(Document): 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 @@ class StatusUpdater(Document): 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 11ac703311b..b14c2745159 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -117,7 +117,6 @@ class StockController(AccountsController): "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 @@ class StockController(AccountsController): 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 @@ class StockController(AccountsController): ) message += "

" 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 @@ class StockController(AccountsController): "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 @@ def is_reposting_pending(): {'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() + + 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) + ) + ) -def get_sle(args): 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 @@ def create_repost_item_valuation_entry(args): 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 c33e556c259..9fae49482dd 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -113,7 +113,12 @@ class calculate_taxes_and_totals(object): 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 @@ class calculate_taxes_and_totals(object): 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 @@ class calculate_taxes_and_totals(object): # 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 @@ class calculate_taxes_and_totals(object): 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 @@ class calculate_taxes_and_totals(object): 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 @@ class calculate_taxes_and_totals(object): 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 @@ class init_landed_taxes_and_totals(object): 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 801c405732c..ecf041efd17 100644 --- a/erpnext/controllers/website_list_for_contact.py +++ b/erpnext/controllers/website_list_for_contact.py @@ -25,7 +25,7 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p if not filters: filters = [] - if doctype in ['Supplier Quotation', 'Purchase Invoice', 'Quotation']: + if doctype in ['Supplier Quotation', 'Purchase Invoice']: filters.append((doctype, 'docstatus', '<', 2)) else: filters.append((doctype, 'docstatus', '=', 1)) diff --git a/erpnext/crm/doctype/lead_source/__init__.py b/erpnext/crm/doctype/lead_source/__init__.py new file mode 100644 index 00000000000..e69de29bb2d 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 00000000000..3cbe6492090 --- /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 00000000000..723c6d993d7 --- /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 d2d75586212..5c64fb8b4ad 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/selling/doctype/lead_source/test_lead_source.py b/erpnext/crm/doctype/lead_source/test_lead_source.py similarity index 52% rename from erpnext/selling/doctype/lead_source/test_lead_source.py rename to erpnext/crm/doctype/lead_source/test_lead_source.py index 42df18f1818..b5bc6490cf9 100644 --- a/erpnext/selling/doctype/lead_source/test_lead_source.py +++ b/erpnext/crm/doctype/lead_source/test_lead_source.py @@ -1,12 +1,10 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt from __future__ import unicode_literals -import frappe +# import frappe import unittest -# test_records = frappe.get_test_records('Lead Source') - 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 377e061fdf4..d8c6fb4f90f 100644 --- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py +++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py @@ -11,7 +11,8 @@ from frappe.utils.file_manager import get_file, get_file_path 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 @@ class LinkedInSettings(Document): 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 47b05f306b7..23ad98a2828 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -85,6 +85,7 @@ class Opportunity(TransactionBase): 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 @@ def make_quotation(source_name, target_doc=None): "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 976a23dfc7d..1e1beab2d25 100644 --- a/erpnext/crm/doctype/twitter_settings/twitter_settings.py +++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.py @@ -11,6 +11,7 @@ from frappe.utils import get_url_to_form, get_link_to_form 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 @@ class TwitterSettings(Document): 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 @@ class TwitterSettings(Document): 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 @@ class TwitterSettings(Document): 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 f7aa6e9fc11..2b3acf1b93b 100644 --- a/erpnext/education/doctype/course_enrollment/course_enrollment.py +++ b/erpnext/education/doctype/course_enrollment/course_enrollment.py @@ -41,7 +41,7 @@ class CourseEnrollment(Document): 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 @@ class CourseEnrollment(Document): "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 97c29ab667d..6a0dcf460a7 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 @@ from erpnext.education.utils import OverlapError 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 a85d3e70f34..658380ea429 100644 --- a/erpnext/education/doctype/education_settings/education_settings.py +++ b/erpnext/education/doctype/education_settings/education_settings.py @@ -31,9 +31,9 @@ class EducationSettings(Document): 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 1543acdca98..0b025c75347 100644 --- a/erpnext/education/doctype/fee_schedule/fee_schedule.py +++ b/erpnext/education/doctype/fee_schedule/fee_schedule.py @@ -52,6 +52,7 @@ class FeeSchedule(Document): 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 d18c0f9625c..b282babd0fc 100644 --- a/erpnext/education/doctype/program_enrollment/program_enrollment.py +++ b/erpnext/education/doctype/program_enrollment/program_enrollment.py @@ -91,6 +91,8 @@ class ProgramEnrollment(Document): (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 8180102c582..5833b67f9bb 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 @@ class ProgramEnrollmentTool(Document): 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 @@ class ProgramEnrollmentTool(Document): 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 569c281f4c2..16d7d7e4bfb 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 e78db42f7d3..742c88754a0 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 81626f19186..2dc0f634f0f 100644 --- a/erpnext/education/doctype/student/student.py +++ b/erpnext/education/doctype/student/student.py @@ -114,7 +114,7 @@ class Student(Document): 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 55384b9e530..e6e46d1c1ba 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 d7645e30cdf..dc8667ec065 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 frappe.model.document import Document 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 @@ class StudentGroupCreationTool(Document): 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 cffc3960a05..8f51fef8476 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -194,7 +194,7 @@ def add_activity(course, content_type, content, program): 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 @@ def evaluate_quiz(quiz_response, quiz_name, course, program): 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 @@ def get_quiz(quiz_name, course): 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 @@ def get_quiz(quiz_name, course): } 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 @@ def check_content_completion(content_name, content_type, enrollment_name): 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 899b7ffe13d..9c598401492 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 @@ class AmazonMWSSettings(Document): 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 @@ def setup_custom_fields(): 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 b5718026c12..fdfaa1b0540 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 @@ class MpesaSettings(Document): 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 @@ def get_completed_integration_requests_info(reference_doctype, reference_docname 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 21f6fee79c8..16c65733f0b 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 @@ from frappe.utils import add_months, formatdate, getdate, today 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 3c906374c42..e2243eabde0 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 @@ class TestPlaidSettings(unittest.TestCase): 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 96a533ee10c..866ea662788 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 @@ class QuickBooksMigrator(Document): 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 5f471ab2e78..6bec301b8e7 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 @@ class ShopifySettings(unittest.TestCase): 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 462685f5e71..907a22333b7 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 @@ class TallyMigration(Document): 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 325c2094fbf..cbf89ee3bde 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py @@ -54,6 +54,7 @@ class ClinicalProcedure(Document): 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 @@ class ClinicalProcedure(Document): 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 @@ class ClinicalProcedure(Document): 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 e7319085e46..3a299eda262 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 @@ class InpatientMedicationEntry(Document): 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 33cbbec8129..b379e98fe13 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 @@ class InpatientMedicationOrder(Document): 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 a21caca8ffa..21776d2380a 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 @@ class TestInpatientMedicationOrder(unittest.TestCase): 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 818f125e773..3fa98b66780 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 5ced845c1b0..0e1c2ba7664 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 88d7f0b2332..f4d1eaf2e3f 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py @@ -53,12 +53,15 @@ class InpatientRecord(Document): + """ {0}""".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 check_out_inpatient(inpatient_record): 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 8603f974c39..789d452c070 100644 --- a/erpnext/healthcare/doctype/patient/patient.py +++ b/erpnext/healthcare/doctype/patient/patient.py @@ -111,6 +111,7 @@ class Patient(Document): 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 1f76cd624cd..cdd4ad39c88 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -113,6 +113,7 @@ class PatientAppointment(Document): 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 c7074e88d5d..f28d32c22c7 100644 --- a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.js +++ b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.js @@ -39,11 +39,13 @@ frappe.ui.form.on('Patient Assessment', { }, 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 @@ frappe.ui.form.on('Patient Assessment Sheet', { 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 2e8c994c3d9..887d58a2e04 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 @@ class PatientHistorySettings(Document): 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 @@ class PatientHistorySettings(Document): 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 d1f72d625b8..42e231dc662 100644 --- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js +++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js @@ -58,8 +58,12 @@ frappe.ui.form.on('Therapy Plan', { } 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 @@ frappe.ui.form.on('Therapy Plan Detail', { 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 ac01c604dda..e2096604344 100644 --- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py +++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py @@ -33,6 +33,7 @@ class TherapyPlan(Document): 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 6c825b8a58e..3f6a36a9686 100644 --- a/erpnext/healthcare/doctype/therapy_type/therapy_type.py +++ b/erpnext/healthcare/doctype/therapy_type/therapy_type.py @@ -50,6 +50,7 @@ class TherapyType(Document): 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 3f145dc958c..5fa419f412e 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -196,6 +196,10 @@ sounds = [ {"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 @@ doc_events = { "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 @@ doc_events = { ('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 @@ auto_cancel_exempted_doctypes= [ "Inpatient Medication Entry" ] +after_migrate = ["erpnext.setup.install.update_select_perm_after_install"] + scheduler_events = { "cron": { "0/30 * * * *": [ @@ -325,6 +332,7 @@ scheduler_events = { "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 18a4fe53c4b..f3b8a799b3c 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -35,7 +35,8 @@ class Attendance(Document): 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 7a9727f18c4..a6fe429be17 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 @@ class CompensatoryLeaveRequest(Document): 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 @@ class CompensatoryLeaveRequest(Document): 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 @@ class CompensatoryLeaveRequest(Document): )) 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 2cef5092767..539a360269f 100644 --- a/erpnext/hr/doctype/department/department.py +++ b/erpnext/hr/doctype/department/department.py @@ -31,7 +31,8 @@ class Department(NestedSet): 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 d0e7d0537b2..ed7d5884347 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -8,7 +8,7 @@ from frappe.utils import getdate, validate_email_address, today, add_years, form 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 @@ class Employee(NestedSet): 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 @@ class Employee(NestedSet): 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 @@ class Employee(NestedSet): 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 @@ def has_user_permission_for_employee(user_name, employee_name): '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 5037ceb489e..fa4b06aad37 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.js +++ b/erpnext/hr/doctype/employee_advance/employee_advance.js @@ -34,7 +34,7 @@ frappe.ui.form.on('Employee Advance', { }; }); - frm.set_query('salary_component', function(doc) { + frm.set_query('salary_component', function() { return { filters: { "type": "Deduction" @@ -44,48 +44,49 @@ frappe.ui.form.on('Employee Advance', { }, 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 @@ frappe.ui.form.on('Employee Advance', { 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 @@ frappe.ui.form.on('Employee Advance', { }); }, - 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 @@ frappe.ui.form.on('Employee Advance', { } 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 @@ frappe.ui.form.on('Employee Advance', { 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 cf6b5404ecf..ea25aa720ad 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 c3b4a3a8894..2f493e2d4e6 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 @@ from frappe import _ def get_data(): return { 'fieldname': 'employee_advance', - 'non_standard_fieldnames': { - 'Payment Entry': 'reference_name', - 'Journal Entry': 'reference_name' - }, + 'non_standard_fieldnames': { + 'Payment Entry': 'reference_name', + 'Journal Entry': 'reference_name' + }, 'transactions': [ { 'items': ['Expense Claim'] diff --git a/erpnext/hr/doctype/employee_referral/__init__.py b/erpnext/hr/doctype/employee_referral/__init__.py new file mode 100644 index 00000000000..e69de29bb2d 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 00000000000..9c99bbbefad --- /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 00000000000..3ae73a9e4d9 --- /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 00000000000..45d68729ce6 --- /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 00000000000..afa2a1ff1fc --- /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 00000000000..7533ab635f5 --- /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 00000000000..a674f390265 --- /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 f44d83060aa..7af209887f0 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 2fa114d3452..713fcf526b5 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 @@ class TestEmployeeSeparation(unittest.TestCase): '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 bf893d5fab3..5010fc3f75c 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -6,7 +6,7 @@ import frappe, erpnext 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 @@ class ExpenseClaim(AccountsController): 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 @@ class ExpenseClaim(AccountsController): 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 f9e3a441bf0..3f22ca21412 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 @@ class TestExpenseClaim(unittest.TestCase): 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 @@ class TestExpenseClaim(unittest.TestCase): 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 @@ def make_expense_claim(payable_account, amount, sanctioned_amount, company, acco 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 6df7bc88c02..8af8cea605d 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list.py +++ b/erpnext/hr/doctype/holiday_list/holiday_list.py @@ -16,6 +16,7 @@ class HolidayList(Document): 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 @@ class HolidayList(Document): 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 09666c5db5b..3db6c239ef0 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 1360fd1890a..bcea5f50d93 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 a6aef049195..0594ba395ba 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.py +++ b/erpnext/hr/doctype/job_applicant/job_applicant.py @@ -28,10 +28,21 @@ class JobApplicant(Document): 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 3a300c0d632..ae02c512c23 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 69d605d0633..11302cad758 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -99,6 +99,7 @@ class LeaveAllocation(Document): .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 350ceadccdb..0bf551e178c 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -6,7 +6,7 @@ import frappe 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 @@ class LeaveApplication(Document): 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 @@ class LeaveApplication(Document): )) 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 b335c485944..b54c9712c89 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 frappe.utils import add_days, nowdate, now_datetime, getdate, add_months 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 @@ class TestLeaveApplication(unittest.TestCase): @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 @@ class TestLeaveApplication(unittest.TestCase): 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 @@ class TestLeaveApplication(unittest.TestCase): 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 @@ class TestLeaveApplication(unittest.TestCase): 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 @@ class TestLeaveApplication(unittest.TestCase): # 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 @@ class TestLeaveApplication(unittest.TestCase): 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 @@ class TestLeaveApplication(unittest.TestCase): to_date = add_days(date, 4), company = "_Test Company", docstatus = 1, - status = "Approved" + status = "Approved" )) self.assertTrue(leave_application.insert()) @@ -393,7 +396,7 @@ class TestLeaveApplication(unittest.TestCase): 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 @@ class TestLeaveApplication(unittest.TestCase): 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 @@ class TestLeaveApplication(unittest.TestCase): description = "_Test Reason", company = "_Test Company", docstatus = 1, - status = "Approved" + status = "Approved" )) leave_application.submit() @@ -565,6 +568,48 @@ class TestLeaveApplication(unittest.TestCase): 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 57e61b5e08a..74014020fc6 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 @@ class LeaveControlPanel(Document): 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 83eeae3adba..1f6c03f7b60 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 4c1a46522f6..e041b7fb8f8 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py @@ -63,6 +63,7 @@ class LeaveEncashment(Document): 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 63559c4f5ae..cf130361810 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 @@ def validate_leave_allocation_against_leave_application(ledger): """, (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 @@ def create_leave_ledger_entry(ref_doc, args, submit=True): 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 4064c56e44c..462b81df1d5 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 @@ class LeavePolicyAssignment(Document): 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 473193d5ac4..177c45edc65 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.py +++ b/erpnext/hr/doctype/shift_request/shift_request.py @@ -7,6 +7,7 @@ import frappe 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 @@ class ShiftRequest(Document): 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 @@ class ShiftRequest(Document): 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 230bb2b0e4e..9c0d8e31985 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 @@ from __future__ import unicode_literals 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 @@ class TestShiftRequest(unittest.TestCase): 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 @@ class TestShiftRequest(unittest.TestCase): 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 054e7e3688e..d5fdda80944 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.attendance.attendance import mark_attendance 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 92715d34453..e86fa2b1c42 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 @@ def get_data(filters, leave_types): 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 @@ def get_data(filters, leave_types): 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 c5929c6bf99..bcb0ee4d0da 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 @@ def execute(filters=None): 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 @@ def execute(filters=None): if (att_map_set & emp_map_set): parameter_row = [""+ parameter + ""] + ['' 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 @@ def get_chart_data(emp_att_map, days): 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 @@ def add_data(employee_map, att_map, filters, holiday_map, conditions, default_ho 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 e961114ac2c..303c829eb6c 100644 --- a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py +++ b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py @@ -31,7 +31,7 @@ def get_columns(): "fieldtype": "Link", "fieldname": "job_opening", "options": "Job Opening", - "width": 100 + "width": 105 }, { "label": _("Job Applicant"), @@ -44,13 +44,13 @@ def get_columns(): "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 @@ def get_job_offer(ja_list): 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 0c4c1cafb07..2540b3db63b 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -32,13 +32,15 @@ class EmployeeBoardingController(Document): 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 @@ def grant_leaves_automatically(): 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 f650b24d861..c5201c22c9b 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": [ @@ -226,42 +227,12 @@ "onboard": 0, "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" }, @@ -383,16 +384,6 @@ "onboard": 0, "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, @@ -420,6 +411,15 @@ "onboard": 0, "type": "Link" }, + { + "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, @@ -464,6 +464,15 @@ "onboard": 0, "type": "Card Break" }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Driver", + "link_to": "Driver", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, { "dependencies": "", "hidden": 0, @@ -511,6 +520,15 @@ "onboard": 1, "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, @@ -541,6 +559,24 @@ "onboard": 0, "type": "Link" }, + { + "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, @@ -625,33 +661,6 @@ "onboard": 0, "type": "Link" }, - { - "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, @@ -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 acf09f5c037..c9f23ca4df3 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 83a813f947b..230475f2d14 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -44,6 +44,7 @@ class Loan(AccountsController): 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 @@ class Loan(AccountsController): 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 @@ class Loan(AccountsController): 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 @@ def request_loan_closure(loan, posting_date=None): 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 @@ def get_shortfall_applicants(): 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 13a209418d1..fae6f860b6d 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -56,25 +56,25 @@ class TestLoan(unittest.TestCase): 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 @@ class TestLoan(unittest.TestCase): 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 @@ class TestLoan(unittest.TestCase): 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 @@ class TestLoan(unittest.TestCase): 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 @@ class TestLoan(unittest.TestCase): 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 a353a7740d3..f91fa072352 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 cd5df4d3cd1..7811d56a758 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 185bf7a6663..30e2328442a 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 2b5df4be243..6479853246f 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 bac06c4e9e6..3d99b1f3040 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 @@ class LoanRepayment(AccountsController): 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 @@ class LoanRepayment(AccountsController): 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 @@ class LoanRepayment(AccountsController): 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 @@ class LoanRepayment(AccountsController): '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 @@ class LoanRepayment(AccountsController): 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 @@ class LoanRepayment(AccountsController): 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 @@ class LoanRepayment(AccountsController): "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 @@ class LoanRepayment(AccountsController): "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 @@ def create_repayment_entry(loan, applicant, company, posting_date, loan_type, 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 @@ def get_accrued_interest_entries(against_loan): `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 @@ def get_amounts(amounts, against_loan, posting_date): 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 @@ def get_amounts(amounts, against_loan, posting_date): # 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 @@ def get_amounts(amounts, against_loan, posting_date): 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 @@ def get_amounts(amounts, against_loan, posting_date): @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 7dd5725e2e7..18bd4aea78b 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 102bc0d71d3..99b5c72b2db 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 64698068842..8233b7b297a 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 @@ from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpled 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 @@ def update_shortfall_status(loan, security_value): 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 @@ def check_for_ltv_shortfall(process_loan_security_shortfall): '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 @@ def check_for_ltv_shortfall(process_loan_security_shortfall): 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 @@ def check_for_ltv_shortfall(process_loan_security_shortfall): 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 @@ def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_ 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 @@ def get_ltv_ratio(loan_security): 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 2e2b2518d2c..92923bbf250 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 c4c2d683780..b24dc2f7c28 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 @@ from __future__ import unicode_literals 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 @@ class LoanSecurityUnpledge(Document): 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 3ef53044c20..c0a5d2cda12 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 4617a62f5b6..4ca9ef174c7 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 2f4fe249456..3d070812152 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 0f72c3cce7c..2a74a1eb858 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 @@ def get_active_loan_details(filters): 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 cba6a2d0149..0aefe19c8d8 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.stock.utils import get_valid_serial_nos 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 0e9a21c0268..7ba43d6471e 100644 --- a/erpnext/manufacturing/dashboard_fixtures.py +++ b/erpnext/manufacturing/dashboard_fixtures.py @@ -43,7 +43,6 @@ def get_charts(): return [{ "doctype": "Dashboard Chart", "based_on": "modified", - "time_interval": "Yearly", "chart_type": "Sum", "chart_name": _("Produced Quantity"), "name": "Produced Quantity", @@ -60,7 +59,6 @@ def get_charts(): }, { "doctype": "Dashboard Chart", "based_on": "creation", - "time_interval": "Yearly", "chart_type": "Sum", "chart_name": _("Completed Operation"), "name": "Completed Operation", @@ -238,4 +236,4 @@ def get_number_cards(): "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 03beedb6635..979f7ca3128 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -113,6 +113,7 @@ class BOM(WebsiteGenerator): return item + @frappe.whitelist() def get_routing(self): if self.routing: self.set("operations", []) @@ -145,6 +146,7 @@ class BOM(WebsiteGenerator): 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 @@ class BOM(WebsiteGenerator): .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 32394788723..7108338dab4 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 @@ class TestBOM(unittest.TestCase): 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 @@ class TestBOM(unittest.TestCase): 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 742d18c4cda..8fbcd4ea1db 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 @@ class BOMUpdateTool(Document): 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 @@ def update_cost(): 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 662a06b1ee2..fb26062566a 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -47,6 +47,8 @@ class JobCard(Document): 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 @@ class JobCard(Document): "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 @@ class JobCard(Document): 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_material_request(source_name, target_doc=None): 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 f93b244a504..6c60bbde86c 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 b723387a091..288c1d0cd66 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -25,6 +25,16 @@ frappe.ui.form.on('Production Plan', { } }); + 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 @@ frappe.ui.form.on('Production Plan', { 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 @@ cur_frm.fields_dict['sales_orders'].grid.get_field("sales_order").get_query = fu ['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 7daf7069f3e..f11470086af 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 8f9dd052175..a3e23a68972 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -29,6 +29,7 @@ class ProductionPlan(Document): 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 @@ class ProductionPlan(Document): '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 @@ class ProductionPlan(Document): 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 @@ class ProductionPlan(Document): '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 @@ class ProductionPlan(Document): 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 @@ class ProductionPlan(Document): return item_dict + @frappe.whitelist() def make_work_order(self): wo_list = [] self.validate_data() @@ -367,6 +372,7 @@ class ProductionPlan(Document): 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 @@ def download_raw_materials(doc): 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 @@ def download_raw_materials(doc): 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 @@ def get_subitems(doc, data, item_details, bom_no, company, include_non_stock_ite 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 @@ def get_subitems(doc, data, item_details, bom_no, company, include_non_stock_ite 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 @@ def get_material_request_items(row, sales_order, 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 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False): """.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 @@ def get_items_for_material_requests(doc, warehouses=None): 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 @@ def get_items_for_material_requests(doc, warehouses=None): 'description' : item_master.description, 'stock_uom' : item_master.stock_uom, 'conversion_factor' : conversion_factor, + 'safety_stock': item_master.safety_stock } ) @@ -732,7 +750,7 @@ def get_items_for_material_requests(doc, warehouses=None): 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 @@ def get_items_for_material_requests(doc, warehouses=None): 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) + "

" - 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 9b1a8ca670e..032c9cd9a21 100644 --- a/erpnext/manufacturing/doctype/routing/routing.js +++ b/erpnext/manufacturing/doctype/routing/routing.js @@ -11,10 +11,9 @@ frappe.ui.form.on('Routing', { }, 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 @@ frappe.ui.form.on('BOM Operation', { 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 73d05a61570..6a38dcfa030 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.workstation.test_workstation import make_work 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 @@ class TestRouting(unittest.TestCase): 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 @@ def setup_bom(**args): }) 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 @@ def setup_bom(**args): 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 00e8c5418a0..6b1fafe5f4c 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 @@ class TestWorkOrder(unittest.TestCase): 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 @@ class TestWorkOrder(unittest.TestCase): 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 @@ class TestWorkOrder(unittest.TestCase): 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 @@ class TestWorkOrder(unittest.TestCase): 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 @@ class TestWorkOrder(unittest.TestCase): 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 allow_overproduction(fieldname, percentage): 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 585a09db2bf..cd9edeeea83 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 3d64ad4318d..8507f5eb34c 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -509,6 +509,7 @@ class WorkOrder(Document): 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 @@ class WorkOrder(Document): 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 7b5747e393f..7317152565a 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 2ca9f1694b3..fc27d355984 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 @@ class ForecastingReport(ExponentialSmoothingForecast): 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 6a2a06dbc88..4fd1a30ab9e 100644 --- a/erpnext/non_profit/doctype/donation/donation.py +++ b/erpnext/non_profit/doctype/donation/donation.py @@ -42,7 +42,7 @@ class Donation(Document): 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 @@ class Donation(Document): 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 3ba2ee71c67..efc072ee971 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -53,6 +53,7 @@ class Member(Document): 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 52447e43860..e8ae6187b7e 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -74,6 +74,7 @@ class Membership(Document): 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 @@ class Membership(Document): 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 cff92b42abb..4c4ca9834b0 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 @@ frappe.ui.form.on("Non Profit Settings", { }; }); - frm.set_query("debit_account", function() { + frm.set_query("membership_debit_account", function() { return { filters: { "account_type": "Receivable", @@ -29,6 +29,16 @@ frappe.ui.form.on("Non Profit Settings", { }; }); + 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 @@ frappe.ui.form.on("Non Profit Settings", { }; }); + 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. ") + `${__('ERPNext Docs')}`, 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 108554c6a08..a84cc2cdb53 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.integrations.utils import get_payment_gateway_controller 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 @@ class NonProfitSettings(Document): _("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 7016ecdd969..9ef949c2c73 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -99,7 +99,7 @@ execute:frappe.delete_doc("DocType", "Purchase Request") 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.update_item_description_based_on_item_master 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_doctype('Dashboard') 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.v13_0.delete_report_requested_items_to_order 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.set_company_in_leave_ledger_entry 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 00000000000..b6bd5fa311c --- /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 00000000000..4d649dd0f0c --- /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 00000000000..387e88588d9 --- /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 00000000000..bf8f566d32a --- /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 00000000000..1208222504c --- /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 00000000000..a0c9fcf4cbe --- /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 00000000000..1a99b3163b6 --- /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 c26cddbe4e5..01a4ae04add 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 9ad48e23b7f..c92d52dcec0 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 @@ from erpnext.regional.india.setup import setup 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 48d5cb4cc8f..59b2e49b26e 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 @@ def execute(): 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 af1f6e7ec17..77a23cfc3f8 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 @@ def execute(): 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 00000000000..61dc824dd4c --- /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 5920bf1f704..9af0a8dbef7 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 @@ def execute(): 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 @@ def execute(): 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 d968e1fb763..021bb72cae6 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 @@ def execute(): 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 @@ def execute(): 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 @@ def execute(): 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 00000000000..a9d7883d40a --- /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 00000000000..53da7006b98 --- /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 00000000000..491dc82f784 --- /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 be5e30f3074..a5b93f63071 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 @@ import frappe 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 00000000000..1c576db1c7e --- /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 de08aa26b3b..d927524a3c0 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 @@ def execute(): 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 00000000000..1830bab02ba --- /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 00000000000..4816b40250e --- /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 00000000000..c425599e26b --- /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 517e66c4bc1..a2a48a62e1a 100644 --- a/erpnext/patches/v7_1/update_lead_source.py +++ b/erpnext/patches/v7_1/update_lead_source.py @@ -5,7 +5,7 @@ from frappe import _ 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 2b29f667fbc..5e17a5cbb7d 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 13b6c05e22d..ebeddf97f9e 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -13,12 +13,19 @@ class AdditionalSalary(Document): 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 @@ class AdditionalSalary(Document): 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 @@ def get_additional_salaries(employee, start_date, end_date, component_type): 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 4c45580bf01..83326975b0a 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 ea9ccd52055..e1f8431ec59 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 @@ frappe.ui.form.on('Employee Benefit Claim', { 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 da24aacda1b..b3bac01818f 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 e5b1052b3a5..0d10b2c19ae 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 0e0c9b5a1ac..fb11875e964 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 @@ frappe.ui.form.on('Employee Tax Exemption Declaration', { }); }).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 83d4ae53df8..b247d266ae4 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 497f35c41e3..4fb0a3771ed 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 @@ frappe.ui.form.on('Employee Tax Exemption Proof Submission', { 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 53f18cb1fe3..77b107ef4a3 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 9fa261dea2d..5a7de37becb 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 395e56fa92e..f2892600d12 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js @@ -133,45 +133,63 @@ frappe.ui.form.on('Payroll Entry', { } }; }); + + 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 @@ let render_employee_attendance = function (frm, data) { }) ); }; - -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 6bcd4e0c006..3953b463f12 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.utils import cint, flt, add_days, getdate, add_to_date, DATE_FORMAT, 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 @@ class PayrollEntry(Document): 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 @@ class PayrollEntry(Document): 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 @@ class PayrollEntry(Document): """ 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 @@ class PayrollEntry(Document): 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 @@ class PayrollEntry(Document): 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 @@ class PayrollEntry(Document): 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 @@ class PayrollEntry(Document): 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 @@ class PayrollEntry(Document): 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 @@ class PayrollEntry(Document): 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 payroll_entry_has_bank_entries(name): 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 @@ def create_salary_slips_for_employees(employees, args, publish_progress=True): 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 @@ def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filte '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 84c381489ca..7528bf7a7f8 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 @@ class TestPayrollEntry(unittest.TestCase): 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 5efa41db1f7..459b7eacb43 100644 --- a/erpnext/payroll/doctype/payroll_settings/payroll_settings.py +++ b/erpnext/payroll/doctype/payroll_settings/payroll_settings.py @@ -28,5 +28,5 @@ class PayrollSettings(Document): 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 66472300788..7ea6210c7ad 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 7460c752278..5258f3aff9e 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.js +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js @@ -39,7 +39,10 @@ frappe.ui.form.on("Salary Slip", { 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 @@ frappe.ui.form.on("Salary Slip", { 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 @@ frappe.ui.form.on("Salary Slip", { 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 @@ frappe.ui.form.on('Salary Slip Timesheet', { }); 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 66883682625..42a0f290cb4 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 a04a6358078..afdf081ac89 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -124,9 +124,12 @@ class SalarySlip(TransactionBase): 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 @@ class SalarySlip(TransactionBase): 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 @@ class SalarySlip(TransactionBase): 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 @@ class SalarySlip(TransactionBase): 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 @@ class SalarySlip(TransactionBase): 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 @@ class SalarySlip(TransactionBase): 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 @@ class SalarySlip(TransactionBase): 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 76726956531..01e4170d311 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 @@ class TestSalarySlip(unittest.TestCase): 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 6aa13873633..d5c20dce6b0 100755 --- a/erpnext/payroll/doctype/salary_structure/salary_structure.js +++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js @@ -16,11 +16,11 @@ frappe.ui.form.on('Salary Structure', { onload: function(frm) { let help_button = $(` - Condition and Formula Help + ${__("Condition and Formula Help")} `).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.ui.form.on('Salary Structure', { 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 @@ frappe.ui.form.on('Salary Structure', { 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 de56fc8457e..5dd1d701f02 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 17120815504..58c445f8a96 100644 --- a/erpnext/payroll/doctype/salary_structure/salary_structure.py +++ b/erpnext/payroll/doctype/salary_structure/salary_structure.py @@ -88,7 +88,7 @@ class SalaryStructure(Document): 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 @@ class SalaryStructure(Document): 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 f2fb558a14b..36387f23df0 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 @@ def create_salary_structure_assignment(employee, salary_structure, from_date=Non 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 92bb347661e..c8b98e5aafc 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 4bae67546c7..b1a7cc27344 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 946b8c8707a..a7cf7bf9884 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 b68b5d7aa85..2f8b0371648 100644 --- a/erpnext/portal/doctype/products_settings/products_settings.js +++ b/erpnext/portal/doctype/products_settings/products_settings.js @@ -10,10 +10,12 @@ frappe.ui.form.on('Products Settings', { 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 97042dba92c..3521e7e8bf0 100644 --- a/erpnext/portal/product_configurator/test_product_configurator.py +++ b/erpnext/portal/product_configurator/test_product_configurator.py @@ -10,8 +10,38 @@ from erpnext.stock.doctype.item.test_item import make_item_variant 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 @@ class TestProductConfigurator(unittest.TestCase): 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 21fd7c28789..d77eb2c3966 100644 --- a/erpnext/portal/product_configurator/utils.py +++ b/erpnext/portal/product_configurator/utils.py @@ -298,7 +298,7 @@ def get_items_by_fields(field_filters): 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 077011ace07..c5265e23c0a 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -18,8 +18,8 @@ frappe.ui.form.on("Project", { }; }, 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 f9e1359b450..55c5149a9cc 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -179,9 +179,6 @@ class Project(Document): 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 15a2873aded..70139c6da84 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -4,12 +4,14 @@ from __future__ import unicode_literals 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 @@ class TestProject(unittest.TestCase): 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 @@ class TestProject(unittest.TestCase): 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 @@ def make_project(args): 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 002ddb2f409..6a9d2d1424a 100644 --- a/erpnext/projects/doctype/task/task.js +++ b/erpnext/projects/doctype/task/task.js @@ -32,7 +32,8 @@ frappe.ui.form.on("Task", { 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 160cc5812f7..ef4740d9eef 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 855ff5f83e8..d1583f1473c 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -36,6 +36,7 @@ class Task(NestedSet): 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 @@ class Task(NestedSet): dependent_task_format = """{0}""".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 f7c764e1bd2..d21ac0f2f02 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -151,11 +151,11 @@ class TestTimesheet(unittest.TestCase): 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 b123af5d188..43a57e5d4a4 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -174,9 +174,10 @@ frappe.ui.form.on("Timesheet Detail", { 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/projects/report/delayed_tasks_summary/__init__.py b/erpnext/projects/report/delayed_tasks_summary/__init__.py new file mode 100644 index 00000000000..e69de29bb2d 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 00000000000..5aa44c0a8c9 --- /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 = `

${value}

`; + } else { + value = `

${value}

`; + } + } + 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 00000000000..100c422433e --- /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 00000000000..cdabe6487ea --- /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 00000000000..dbeedb4be92 --- /dev/null +++ b/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py @@ -0,0 +1,54 @@ +from __future__ import unicode_literals +import unittest +import frappe +from frappe.utils import nowdate, add_days, add_months +from erpnext.projects.doctype.task.test_task import create_task +from erpnext.projects.report.delayed_tasks_summary.delayed_tasks_summary import execute + +class TestDelayedTasksSummary(unittest.TestCase): + @classmethod + def setUp(self): + task1 = create_task("_Test Task 98", add_days(nowdate(), -10), nowdate()) + create_task("_Test Task 99", add_days(nowdate(), -10), add_days(nowdate(), -1)) + + task1.status = "Completed" + task1.completed_on = add_days(nowdate(), -1) + task1.save() + + def test_delayed_tasks_summary(self): + filters = frappe._dict({ + "from_date": add_months(nowdate(), -1), + "to_date": nowdate(), + "priority": "Low", + "status": "Open" + }) + expected_data = [ + { + "subject": "_Test Task 99", + "status": "Open", + "priority": "Low", + "delay": 1 + }, + { + "subject": "_Test Task 98", + "status": "Completed", + "priority": "Low", + "delay": -1 + } + ] + report = execute(filters) + data = list(filter(lambda x: x.subject == "_Test Task 99", report[1]))[0] + + for key in ["subject", "status", "priority", "delay"]: + self.assertEqual(expected_data[0].get(key), data.get(key)) + + filters.status = "Completed" + report = execute(filters) + data = list(filter(lambda x: x.subject == "_Test Task 98", report[1]))[0] + + for key in ["subject", "status", "priority", "delay"]: + self.assertEqual(expected_data[1].get(key), data.get(key)) + + def tearDown(self): + for task in ["_Test Task 98", "_Test Task 99"]: + frappe.get_doc("Task", {"subject": task}).delete() \ No newline at end of file diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/__init__.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/__init__.py new file mode 100644 index 00000000000..e69de29bb2d 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 00000000000..9a30b99f9ba --- /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 00000000000..5ff8186572e --- /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 00000000000..842fd4d3148 --- /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 00000000000..fa8782733fe --- /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/projects/report/project_profitability/__init__.py b/erpnext/projects/report/project_profitability/__init__.py new file mode 100644 index 00000000000..e69de29bb2d 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 00000000000..13ae19bb299 --- /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 00000000000..0b092cd2c09 --- /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 00000000000..5ad2d852326 --- /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 00000000000..7fe28b10746 --- /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 ea7f1ab2e77..2c7bb49cfba 100644 --- a/erpnext/projects/report/project_summary/project_summary.py +++ b/erpnext/projects/report/project_summary/project_summary.py @@ -131,25 +131,25 @@ def get_report_summary(data): { "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 dbbd7e1458e..c023a73ff4e 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": [ @@ -129,6 +130,26 @@ "onboard": 1, "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, @@ -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 649eb454acc..ceeecb28a25 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -276,74 +276,3 @@ erpnext.taxes.set_conditional_mandatory_rate_or_amount = function(grid_row) { } } } - - -// 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 = ''; var bend = ''; - return '' + (bold?bstart:'') + title + (bold?bend:'') + '' - + '' + (is_negative ? '- ' : '') - + format_currency(val, doc.currency) + ''; - } - - 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='
'; - - // main table - - out +=''; - - 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'; - } - out += '
'; - } - 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 8ceae836207..8b4a4976cab 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -216,7 +216,8 @@ erpnext.buying.BuyingController = class BuyingController extends erpnext.Transac 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 448bb653642..31410da6bfc 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 @@ erpnext.taxes_and_totals = class taxes_and_totals extends erpnext.payments { "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 @@ erpnext.taxes_and_totals = class taxes_and_totals extends erpnext.payments { // 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 @@ erpnext.taxes_and_totals = class taxes_and_totals extends erpnext.payments { 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 @@ erpnext.taxes_and_totals = class taxes_and_totals extends erpnext.payments { } 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 a4c165e9ee7..b3c2006ec0f 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -5,7 +5,7 @@ frappe.provide('erpnext.accounts.dimensions'); 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 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe 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 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe 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 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe 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 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe 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 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe to_currency: to_currency, args: args }, + freeze: true, + freeze_message: __("Fetching exchange rates ..."), callback: function(r) { callback(flt(r.message)); } @@ -1173,6 +1179,11 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe 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 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe 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 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe 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 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe 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 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe 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 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } 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 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } } - 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 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe 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 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } payment_term(doc, cdt, cdn) { + const me = this; var row = locals[cdt][cdn]; if(row.payment_term) { frappe.call({ @@ -1994,12 +2051,15 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe 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 4a9d1e34e6f..32fa4ab1ecf 100644 --- a/erpnext/public/js/education/lms/quiz.js +++ b/erpnext/public/js/education/lms/quiz.js @@ -20,6 +20,16 @@ class Quiz { } 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 @@ class Quiz { 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 @@ class Quiz { } 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 @@ class Quiz { 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 @@ class Question { 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 @@ class Question { } 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 472c5374f5b..e78992302f1 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 e5b50d86eda..19c90730908 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -291,17 +291,15 @@ $.extend(erpnext.utils, { 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 @@ erpnext.utils.map_current_doc = function(opts) { } 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 d44c7083563..79b6220c1c3 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 @@ erpnext.SerialNoBatchSelector = class SerialNoBatchSelector { 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 00000000000..0009cacf61e --- /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 bf82cc080a0..5a8ec73cfe1 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 @@ import frappe 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 db8bda75bfd..68ed3391d04 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 dd9d99773a3..a65b1ca7ca8 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 888b2da48eb..369a4001ef6 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 @@ - {{__("Suppliies made to Composition Taxable Persons")}} + {{__("Supplies made to Composition Taxable Persons")}} {% 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 a49996d107e..a5dd5a2e094 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 @@ class GSTR3BReport(Document): 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 @@ class GSTR3BReport(Document): 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 @@ class GSTR3BReport(Document): 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 @@ class GSTR3BReport(Document): 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 023b4ed22bc..ef8af24c42a 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 @@ class TestGSTR3BReport(unittest.TestCase): 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 @@ def create_purchase_invoices(): 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 c1680c4b492..afdd54b4181 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 31a7545a0df..00300539e9a 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 @@ class ImportSupplierInvoice(Document): 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 @@ class ImportSupplierInvoice(Document): 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 @@ def create_uom(uom): 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 5bbd5750f99..41a0f1193bc 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 @@ class TaxExemption80GCertificate(Document): 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 @@ class TaxExemption80GCertificate(Document): 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 @@ class TaxExemption80GCertificate(Document): '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 d6047e863ce..ac1f5434887 100644 --- a/erpnext/regional/germany/setup.py +++ b/erpnext/regional/germany/setup.py @@ -3,4 +3,17 @@ import frappe 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 378b735e078..faeb36fc693 100644 --- a/erpnext/regional/india/__init__.py +++ b/erpnext/regional/india/__init__.py @@ -69,7 +69,7 @@ state_numbers = { "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 86290cfe524..f4a3542a60e 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 7cd64f2fc07..23d4fe9030b 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 @@ erpnext.setup_einvoice_actions = (doctype) => { "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 @@ erpnext.setup_einvoice_actions = (doctype) => { 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 @@ erpnext.setup_einvoice_actions = (doctype) => { } 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 += '

'; + 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 @@ const get_preview_dialog = (frm, action) => { 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 96f7f1b224f..699441be7e6 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -15,18 +15,44 @@ import traceback 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 @@ def validate_einvoice_fields(doc): 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_transaction_details(invoice): )) 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 @@ def get_doc_details(invoice): 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 @@ def get_item_list(invoice): 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 @@ def update_item_taxes(invoice, item): 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 @@ def update_item_taxes(invoice, item): 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 @@ def get_invoice_value_details(invoice): 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 @@ def update_invoice_taxes(invoice, invoice_value_details): 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 @@ def update_invoice_taxes(invoice, invoice_value_details): 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_payment_details(invoice): )) 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_return_doc_reference(invoice): 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 get_eway_bill_details(invoice): 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 @@ def validate_mandatory_fields(invoice): 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 @@ def make_einvoice(invoice): 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 @@ def make_einvoice(invoice): 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 @@ def safe_json_load(json_string): snippet = json_string[start:end] frappe.throw(_("Error in input data. Please check for any special characters near following input:
{}").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 @@ class GSPConnector(): 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 @@ class GSPConnector(): 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 @@ class GSPConnector(): 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 @@ class GSPConnector(): 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 @@ class GSPConnector(): 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 @@ class GSPConnector(): 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 @@ class GSPConnector(): 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 @@ class GSPConnector(): 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 @@ class GSPConnector(): 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 @@ class GSPConnector(): ] 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 @@ class GSPConnector(): 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 @@ class GSPConnector(): 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 @@ class GSPConnector(): '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 @@ class GSPConnector(): 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 @@ def generate_eway_bill(doctype, docname, **kwargs): 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 = '{0}'.format(doc.get('docname')) + message = doc.get('message').replace("'", '"') + if message[0] == '[': + errors = json.loads(message) + error_list = ''.join(['
  • {}
  • '.format(err) for err in errors]) + message = '''{} has following errors:
    + '''.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 ee49aae0501..9ded8dab5bc 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -5,19 +5,21 @@ from __future__ import unicode_literals 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 create_hsn_codes(data, code_field): 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 @@ def add_print_formats(): 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 @@ def make_custom_fields(update=True): 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 @@ def make_custom_fields(update=True): 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 @@ def make_custom_fields(update=True): 'allow_on_submit': 1, 'insert_after': 'customer_name_in_arabic', 'translatable': 0, - } + } ] si_ewaybill_fields = [ @@ -401,21 +419,37 @@ def make_custom_fields(update=True): 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 @@ def make_custom_fields(update=True): '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 @@ def make_custom_fields(update=True): '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 @@ def create_gratuity_rule(): }) 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 7ce27f6cf5a..a16f56c704a 100644 --- a/erpnext/regional/india/test_utils.py +++ b/erpnext/regional/india/test_utils.py @@ -12,14 +12,14 @@ class TestIndiaUtils(unittest.TestCase): 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 e24bd6c3d07..6338056698f 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals 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 @@ def validate_gstin_for_india(doc, method): 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 set_place_of_supply(doc, method=None): 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 @@ def get_regional_round_off_accounts(company, account_list): 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 586a52937b5..b54ac538126 100644 --- a/erpnext/regional/italy/sales_invoice.js +++ b/erpnext/regional/italy/sales_invoice.js @@ -11,15 +11,10 @@ erpnext.setup_e_invoice_button = (doctype) => { 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 95b92e76a69..7db2f6b0f8d 100644 --- a/erpnext/regional/italy/setup.py +++ b/erpnext/regional/italy/setup.py @@ -128,11 +128,8 @@ def make_custom_fields(update=True): 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 @@ def make_custom_fields(update=True): 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 @@ def add_permissions(): 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 6842fb2a619..ba1aeafc3e9 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 @@ def update_itemised_tax_data(doc): @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 @@ def prepare_invoice(invoice, progressive_number): 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 prepare_invoice(invoice, progressive_number): 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 @@ def get_conditions(filters): 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 @@ def prepare_and_attach_invoice(doc, replace=False): @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 sales_invoice_on_cancel(doc, method): 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 80a866cbf5c..94e3960eade 100644 --- a/erpnext/regional/report/datev/datev.json +++ b/erpnext/regional/report/datev/datev.json @@ -1,29 +1,22 @@ { - "add_total_row": 0, - "apply_user_permissions": 0, - "creation": "2019-04-24 08:45:16.650129", - "disabled": 0, - "icon": "octicon octicon-repo-pull", - "color": "#4CB944", - "docstatus": 0, - "doctype": "Report", - "idx": 0, - "is_standard": "Yes", - "module": "Regional", - "name": "DATEV", - "owner": "Administrator", - "ref_doctype": "GL Entry", - "report_name": "DATEV", - "report_type": "Script Report", - "roles": [ - { - "role": "Accounts User" - }, - { - "role": "Accounts Manager" - }, - { - "role": "Auditor" - } - ] -} + "add_total_row": 0, + "columns": [], + "creation": "2019-04-24 08:45:16.650129", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-04-06 12:23:00.379517", + "modified_by": "Administrator", + "module": "Regional", + "name": "DATEV", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "GL Entry", + "report_name": "DATEV", + "report_type": "Script Report", + "roles": [] +} \ No newline at end of file diff --git a/erpnext/regional/report/e_invoice_summary/__init__.py b/erpnext/regional/report/e_invoice_summary/__init__.py new file mode 100644 index 00000000000..e69de29bb2d 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 00000000000..4713217d83c --- /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 = `${value}`; + else if (value == 'Generated') value = `${value}`; + else if (value == 'Cancelled') value = `${value}`; + else if (value == 'Failed') value = `${value}`; + } + + 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 00000000000..4deb073a53d --- /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 00000000000..47acf291a39 --- /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 09b04ff367f..808fd3a2cc9 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -78,7 +78,7 @@ class Gstr1Report(object): 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 @@ class Gstr1Report(object): "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 @@ class Gstr1Report(object): 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 @@ class Gstr1Report(object): 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 @@ def get_json(filters, report_name, data): 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 f899349ccc0..616c2b853df 100644 --- a/erpnext/regional/report/gstr_2/gstr_2.py +++ b/erpnext/regional/report/gstr_2/gstr_2.py @@ -44,7 +44,7 @@ class Gstr2Report(Gstr1Report): 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 @@ class Gstr2Report(Gstr1Report): 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 d9ac6cb0f65..9b3677d2c64 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 68208ab31bf..bd12d661f00 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -6,7 +6,6 @@ from __future__ import unicode_literals 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 @@ def setup(company=None, patch=True): 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 7d5e84df52f..cd94ee101af 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 c4525946088..49ca9423e8d 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -38,11 +38,19 @@ class Customer(TransactionBase): 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 @@ class Customer(TransactionBase): 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 @@ def _set_missing_values(source, target): @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 @@ def get_loyalty_programs(doc): "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 @@ def get_customer_primary_contact(doctype, txt, searchfield, start, page_len, fil """, { '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 6af6a4f6486..00000000000 --- 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 373e83af9cf..00000000000 --- 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/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 5da248c1b52..246f9234a41 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -64,6 +64,7 @@ class Quotation(SellingController): 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 0a5c6651ba3..762b6f1d6c9 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 e56129170c1..d9e52e1d69d 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -150,7 +150,7 @@ class SalesOrder(SellingController): 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 @@ class SalesOrder(SellingController): 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 @@ def get_events(start, end, filters=None): @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 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t 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 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t 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 0fdfb1b889e..3137621fd71 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 @@ class TestSalesOrder(unittest.TestCase): 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 @@ class TestSalesOrder(unittest.TestCase): 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 @@ class TestSalesOrder(unittest.TestCase): 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 @@ class TestSalesOrder(unittest.TestCase): 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 @@ class TestSalesOrder(unittest.TestCase): 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 @@ class TestSalesOrder(unittest.TestCase): 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 @@ class TestSalesOrder(unittest.TestCase): 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 @@ class TestSalesOrder(unittest.TestCase): 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 @@ class TestSalesOrder(unittest.TestCase): 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 @@ class TestSalesOrder(unittest.TestCase): 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 @@ class TestSalesOrder(unittest.TestCase): 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 2104c0131c4..f01934b7e6b 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 d2978838763..b219e7ecce0 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.py +++ b/erpnext/selling/doctype/selling_settings/selling_settings.py @@ -30,8 +30,8 @@ class SellingSettings(Document): # 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 bb6ba1ffce4..d142d16248f 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.model.document import Document 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 @@ class SMSCenter(Document): 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 062cba19e6c..750a1a6071d 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 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va 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 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va 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 @@ def get_conditions(item_code, serial_no, batch_no, barcode): if serial_no or batch_no or barcode: return "item.name = {0}".format(frappe.db.escape(item_code)) - return """(item.name like {item_code} - or item.item_name like {item_code})""".format(item_code = frappe.db.escape('%' + item_code + '%')) + return make_condition(item_code) + +def make_condition(item_code): + condition = "(" + condition += """item.name like {item_code} + or item.item_name like {item_code}""".format(item_code = frappe.db.escape('%' + item_code + '%')) + condition += add_search_fields_condition(item_code) + condition += ")" + + return condition + +def add_search_fields_condition(item_code): + condition = '' + search_fields = frappe.get_all('POS Search Fields', fields = ['fieldname']) + if search_fields: + for field in search_fields: + condition += " or item.{0} like {1}".format(field['fieldname'], frappe.db.escape('%' + item_code + '%')) + return condition def get_item_group_condition(pos_profile): cond = "and 1=1" @@ -257,4 +273,4 @@ def set_customer_info(fieldname, customer, value=""): 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 278821e3928..8adf5bf7473 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 @@ erpnext.PointOfSale.Controller = class { 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 @@ erpnext.PointOfSale.Controller = class { 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 9ab9eefa30d..11a63b3d4a6 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 @@ erpnext.PointOfSale.ItemCart = class { 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 cb0a0103e00..32a4556766a 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 @@ erpnext.PointOfSale.ItemDetails = class { 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 e0d5b731665..709fe577477 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 @@ erpnext.PointOfSale.ItemSelector = class { 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 ec392313f5e..70c7dc2adf2 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 @@ erpnext.PointOfSale.PastOrderList = class { - ${invoice.customer} + ${frappe.ellipsis(invoice.customer, 20)}
    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 be2b769a8a3..acf4eb371f6 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 @@ erpnext.PointOfSale.PastOrderSummary = class { {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 @@ erpnext.PointOfSale.PastOrderSummary = class { 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 @@ erpnext.PointOfSale.PastOrderSummary = class { }); 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 22a279d463f..600f1604900 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 @@ erpnext.PointOfSale.Payment = class { } } + 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 9094a07bccc..9d1b196cf09 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": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n

    \n\t{{ doc.company }}
    \n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"
    \", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t{{ _(\"GSTIN\") }}:{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"
    GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t
    \n\t{% if doc.docstatus == 0 %}\n\t\t{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}
    \n\t{% else %}\n\t\t{{ doc.select_print_heading or _(\"Invoice\") }}
    \n\t{% endif %}\n

    \n

    \n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
    \n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
    \n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"
    \", \" \") %}\n\t\t{{ _(\"Customer\") }}:
    \n\t\t{{ doc.customer_name }}
    \n\t\t{{ customer_address }}\n\t{% endif %}\n

    \n\n
    \n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{%- for item in doc.items -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endfor -%}\n\t\n
    {{ _(\"Item\") }}{{ _(\"Qty\") }}{{ _(\"Amount\") }}
    \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
    {{ 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
    {{ _(\"HSN/SAC\") }}: {{ 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
    {{ _(\"SR.No\") }}:
    \n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t
    {{ item.qty }}
    @ {{ item.rate }}
    {{ item.get_formatted(\"amount\") }}
    \n\n\t\n\t\t\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% endif %}\n\t\t\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\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\t\t{%- if doc.discount_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.rounded_total -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t{%- if doc.change_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t{%- endif -%}\n\t\n
    \n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t
    \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\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t
    \n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t
    \n

    {{ doc.terms or \"\" }}

    \n

    {{ _(\"Thank you, please visit again.\") }}

    ", + "html": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n

    \n\t{{ doc.company }}
    \n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"
    \", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t{{ _(\"GSTIN\") }}:{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"
    GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t
    \n\t{% if doc.docstatus == 0 %}\n\t\t{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}
    \n\t{% else %}\n\t\t{{ doc.select_print_heading or _(\"Invoice\") }}
    \n\t{% endif %}\n

    \n\n

    \n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
    \n\t{{ _(\"Cashier\") }}: {{ doc.owner }}
    \n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
    \n\t{{ _(\"Time\") }}: {{ doc.get_formatted(\"posting_time\") }}
    \n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"
    \", \" \") %}\n\t\t{{ _(\"Customer\") }}:
    \n\t\t{{ doc.customer_name }}
    \n\t\t{{ customer_address }}\n\t{% endif %}\n

    \n\n
    \n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{%- for item in doc.items -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endfor -%}\n\t\n
    {{ _(\"Item\") }}{{ _(\"Qty\") }}{{ _(\"Amount\") }}
    \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
    {{ 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
    {{ _(\"HSN/SAC\") }}: {{ 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
    {{ _(\"SR.No\") }}:
    \n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t
    {{ item.qty }}
    @ {{ item.rate }}
    {{ item.get_formatted(\"amount\") }}
    \n\n\t\n\t\t\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% endif %}\n\t\t\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\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\t\t{%- if doc.discount_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.rounded_total -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t{%- for row in doc.payments -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t{%- endfor -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t{%- if doc.change_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t{%- endif -%}\n\t\n
    \n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t
    \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\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t
    \n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t
    \n\t\t\t\t {{ row.mode_of_payment }}\n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"amount\", doc) }}\n\t\t\t\t
    \n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t
    \n

    {{ doc.terms or \"\" }}

    \n

    {{ _(\"Thank you, please visit again.\") }}

    ", "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 99094ed9b02..6c01e265879 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": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n

    \n\t{{ doc.company }}
    \n\t{{ doc.select_print_heading or _(\"Invoice\") }}
    \n

    \n

    \n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
    \n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
    \n\t{{ _(\"Customer\") }}: {{ doc.customer_name }}\n

    \n\n
    \n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{%- for item in doc.items -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endfor -%}\n\t\n
    {{ _(\"Item\") }}{{ _(\"Qty\") }}{{ _(\"Amount\") }}
    \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
    {{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t
    {{ _(\"SR.No\") }}:
    \n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t
    {{ item.qty }}
    @ {{ item.get_formatted(\"rate\") }}
    {{ item.get_formatted(\"amount\") }}
    \n\n\t\n\t\t\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% endif %}\n\t\t\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\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.rounded_total -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.change_amount -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t{%- endif -%}\n\t\n
    \n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t
    \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\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t
    \n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t
    \n\t\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t
    \n
    \n

    {{ doc.terms or \"\" }}

    \n

    {{ _(\"Thank you, please visit again.\") }}

    ", + "html": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n

    \n\t{{ doc.company }}
    \n\t{{ doc.select_print_heading or _(\"Invoice\") }}
    \n

    \n

    \n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
    \n\t{{ _(\"Cashier\") }}: {{ doc.owner }}
    \n\t{{ _(\"Customer\") }}: {{ doc.customer_name }}
    \n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
    \n\t{{ _(\"Time\") }}: {{ doc.get_formatted(\"posting_time\") }}
    \n

    \n\n
    \n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{%- for item in doc.items -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endfor -%}\n\t\n
    {{ _(\"Item\") }}{{ _(\"Qty\") }}{{ _(\"Amount\") }}
    \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
    {{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t
    {{ _(\"SR.No\") }}:
    \n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t
    {{ item.qty }}
    @ {{ item.get_formatted(\"rate\") }}
    {{ item.get_formatted(\"amount\") }}
    \n\n\t\n\t\t\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% endif %}\n\t\t\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\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.rounded_total -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t{%- for row in doc.payments -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t{%- endfor -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.change_amount -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t{%- endif -%}\n\t\n
    \n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t
    \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\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t
    \n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t
    \n\t\t\t\t {{ row.mode_of_payment }}\n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"amount\", doc) }}\n\t\t\t\t
    \n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t
    \n\t\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t
    \n
    \n

    {{ doc.terms or \"\" }}

    \n

    {{ _(\"Thank you, please visit again.\") }}

    ", "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 f396705460e..6fb7666c2ce 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 @@ def get_columns(customer_naming_type): 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 c041d269a76..c2b5e4f9a90 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -259,6 +259,7 @@ erpnext.company.setup_queries = function(frm) { ["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 @@ erpnext.company.setup_queries = function(frm) { ["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 56f60dfcff0..83cbf475abd 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 433851cde53..64e027dd28b 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -17,6 +17,7 @@ from frappe.utils.nestedset import NestedSet 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 @@ class Company(NestedSet): 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 0df4c87f51f..8367a257ea4 100644 --- a/erpnext/setup/doctype/company/delete_company_transactions.py +++ b/erpnext/setup/doctype/company/delete_company_transactions.py @@ -15,7 +15,7 @@ def delete_company_transactions(company_name): 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 @@ def delete_company_transactions(company_name): 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 cbb4c7c5deb..8c97322a71a 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -24,6 +24,7 @@ class EmailDigest(Document): 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 @@ class EmailDigest(Document): 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 @@ class EmailDigest(Document): 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 @@ class EmailDigest(Document): 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 552331aac89..942dd5989ea 100644 --- a/erpnext/setup/doctype/global_defaults/global_defaults.js +++ b/erpnext/setup/doctype/global_defaults/global_defaults.js @@ -17,7 +17,7 @@ frappe.ui.form.on('Global Defaults', { 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 fa7bc504b68..e5872171815 100644 --- a/erpnext/setup/doctype/global_defaults/global_defaults.py +++ b/erpnext/setup/doctype/global_defaults/global_defaults.py @@ -50,6 +50,7 @@ class GlobalDefaults(Document): # clear cache frappe.clear_cache() + @frappe.whitelist() def get_defaults(self): return frappe.defaults.get_defaults() @@ -59,11 +60,11 @@ class GlobalDefaults(Document): # 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 @@ class GlobalDefaults(Document): # 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 1413cb28622..885d874720d 100644 --- a/erpnext/setup/doctype/item_group/item_group.js +++ b/erpnext/setup/doctype/item_group/item_group.js @@ -61,7 +61,7 @@ frappe.ui.form.on("Item Group", { 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 @@ frappe.ui.form.on("Item Group", { 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 abff97364c0..c1f9433b411 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 import msgprint, throw, _ 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 @@ class NamingSeries(Document): 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 @@ class NamingSeries(Document): 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 @@ class NamingSeries(Document): 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 @@ class NamingSeries(Document): 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 @@ class NamingSeries(Document): 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 @@ def set_by_naming_series(doctype, fieldname, naming_series, hide_name_field=True 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 1e424dd4b35..c7220cbc071 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -8,9 +8,11 @@ from erpnext.accounts.doctype.cash_flow_mapper.default_cash_flow_mapper import D 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 = """
    Sent via ERPNext
    """ @@ -29,6 +31,7 @@ def after_install(): add_company_to_session_defaults() add_standard_navbar_items() add_app_name() + add_non_standard_user_types() frappe.db.commit() @@ -142,13 +145,15 @@ def add_standard_navbar_items(): } ] - 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_standard_navbar_items(): 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 beddaeed793..58764880330 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 5053c6a5124..f21d55fe214 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -12,6 +12,7 @@ from frappe.desk.doctype.global_search_settings.global_search_settings import up 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 @@ def install(country=None): 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 @@ def set_more_defaults(): 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 @@ def set_more_defaults(): 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 @@ def add_uom_data(): "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 @@ def add_uom_data(): 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 @@ def add_sale_stages(): {"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 @@ def install_post_company_fixtures(args=None): {'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 @@ def install_defaults(args=None): # 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 @@ def install_defaults(args=None): global_defaults.save() - system_settings = frappe.get_doc("System Settings") - system_settings.email_footer_address = args.get("company_name") - system_settings.save() - - domain_settings = frappe.get_single('Domain Settings') - domain_settings.set_active_domains(args.get('domains')) +def set_active_domains(args): + frappe.get_single('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 @@ def install_defaults(args=None): 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 = [ - - # 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) + 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 +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 c3c1593c046..429a558c589 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 e74d837ef5c..f63d2695aa3 100644 --- a/erpnext/setup/setup_wizard/setup_wizard.py +++ b/erpnext/setup/setup_wizard/setup_wizard.py @@ -51,11 +51,6 @@ def get_setup_stages(args=None): 'status': _('Setting defaults'), '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, @@ -94,9 +89,6 @@ def stage_fixtures(args): 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 login_as_first_user(args): 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 e82bc96d937..4223f000a6b 100644 --- a/erpnext/setup/setup_wizard/utils.py +++ b/erpnext/setup/setup_wizard/utils.py @@ -9,5 +9,4 @@ def complete(): '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 305456b2666..1576d5a3993 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 681d161edcd..56afe95efd0 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -112,9 +112,7 @@ def place_order(): 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 @@ def update_cart_address(address_type, address_name): 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 cf59a52b5b2..d857bf5f5c1 100644 --- a/erpnext/shopping_cart/test_shopping_cart.py +++ b/erpnext/shopping_cart/test_shopping_cart.py @@ -16,6 +16,11 @@ class TestShoppingCart(unittest.TestCase): 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 @@ class TestShoppingCart(unittest.TestCase): 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 @@ class TestShoppingCart(unittest.TestCase): 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 @@ class TestShoppingCart(unittest.TestCase): 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 @@ class TestShoppingCart(unittest.TestCase): "_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 30e0b762bd5..dbf9901a007 100644 --- a/erpnext/stock/dashboard/item_dashboard.js +++ b/erpnext/stock/dashboard/item_dashboard.js @@ -8,7 +8,7 @@ erpnext.stock.ItemDashboard = class ItemDashboard { 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 @@ erpnext.stock.ItemDashboard = class ItemDashboard { 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 @@ erpnext.stock.ItemDashboard = class ItemDashboard { 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 @@ erpnext.stock.ItemDashboard = class ItemDashboard { 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 @@ erpnext.stock.ItemDashboard = class ItemDashboard { frappe.call({ method: this.method, args: args, - callback: function(r) { + callback: function (r) { me.render(r.message); } }); @@ -115,7 +121,7 @@ erpnext.stock.ItemDashboard = class ItemDashboard { 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 @@ erpnext.stock.ItemDashboard = class ItemDashboard { 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 @@ erpnext.stock.ItemDashboard = class ItemDashboard { return { data: data, max_count: max_count, - can_write:can_write, + can_write: can_write, show_item: show_item || false }; } @@ -169,8 +175,8 @@ erpnext.stock.ItemDashboard = class ItemDashboard { 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.ItemDashboard = class ItemDashboard { } }; -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 @@ erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callb 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', - ['' + r.message.name+ ''])); + ['' + r.message.name + ''])); dialog.hide(); callback(r); }, }); }); - $('

    ' - + __("Add more items or open full form") + '

    ') + $('

    ' + + __("Add more items or open full form") + '

    ') .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 @@ erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callb 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 cafb5c3a0a9..45e662807a0 100644 --- a/erpnext/stock/dashboard/item_dashboard.py +++ b/erpnext/stock/dashboard/item_dashboard.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals 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 @@ def get_data(item_code=None, warehouse=None, item_group=None, 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 04d624ec0b7..8e79f0e5552 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 f595aade917..280fde158f5 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 35443906c86..d326a041730 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -101,7 +101,7 @@ class DeliveryNote(SellingController): 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 28e9533186c..de85bc3922c 100644 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py @@ -90,6 +90,7 @@ class DeliveryTrip(Document): 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 2aa42e66f8e..45e3c21b27d 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -46,9 +46,6 @@ frappe.ui.form.on("Item", { }, __("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 @@ frappe.ui.form.on("Item", { 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 @@ $.extend(erpnext.item, { 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 33a8fe7c8d8..6fed9efa63f 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 7b7d2da969c..dbac79465ee 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -50,6 +50,7 @@ class Item(WebsiteGenerator): 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 @@ class Item(WebsiteGenerator): 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 @@ class Item(WebsiteGenerator): 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 += '
    ' + msg += '
    ' msg += ', '.join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "

    " msg += _("Note: To merge the items, create a separate Stock Reconciliation for the old item {0}").format( @@ -706,6 +707,7 @@ class Item(WebsiteGenerator): 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 36d0de1e5df..e0b89d8e451 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -494,7 +494,8 @@ def make_item_variant(): 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 @@ def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, 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 909c4eeb906..6cec85288fe 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 d346979496f..6aa6ffd6c9b 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 24f7e31a0cc..e8fb34732fc 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 @@ frappe.ui.form.on('Item Variant Settings', { } }); - 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 04224424a5e..78f1131b769 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 @@ class ItemVariantSettings(Document): 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 69a8bf19d34..83109469fc7 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.accounts.doctype.account.account import get_account_currency 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 86936b4db37..f516e061c00 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -354,6 +354,10 @@ frappe.ui.form.on('Material Request', { }, 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 @@ erpnext.buying.MaterialRequestController = class MaterialRequestController exten 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 d73349dd39a..8d7b238c17f 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 bd14e5f6161..40d46852d03 100644 --- a/erpnext/stock/doctype/packing_slip/packing_slip.js +++ b/erpnext/stock/doctype/packing_slip/packing_slip.js @@ -110,19 +110,4 @@ cur_frm.cscript.calc_net_total_pkg = function(doc, ps_detail) { refresh_many(['net_weight_pkg', 'net_weight_uom', 'gross_weight_uom', 'gross_weight_pkg']); } -var make_row = function(title,val,bold){ - var bstart = ''; var bend = ''; - return ''+(bold?bstart:'')+title+(bold?bend:'')+'' - +''+ val +'' - +'' -} - -cur_frm.pformat.net_weight_pkg= function(doc){ - return '' + make_row('Net Weight', doc.net_weight_pkg) + '
    ' -} - -cur_frm.pformat.gross_weight_pkg= function(doc){ - return '' + make_row('Gross Weight', doc.gross_weight_pkg) + '
    ' -} - // 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 a7a29cca7f8..2008bffcd32 100644 --- a/erpnext/stock/doctype/packing_slip/packing_slip.py +++ b/erpnext/stock/doctype/packing_slip/packing_slip.py @@ -152,6 +152,7 @@ class PackingSlip(Document): 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 d723fac11be..6ab68e292ae 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -33,6 +33,7 @@ class PickList(Document): 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 @@ def create_delivery_note(source_name, target_doc=None): 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 @@ def create_stock_entry(pick_list): 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 8ea7f89dc4c..c4da05a6d44 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 @@ test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch'] 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 @@ class TestPickList(unittest.TestCase): '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 @@ class TestPickList(unittest.TestCase): '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 @@ class TestPickList(unittest.TestCase): }) 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 @@ class TestPickList(unittest.TestCase): '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 @@ class TestPickList(unittest.TestCase): '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 @@ class TestPickList(unittest.TestCase): '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 @@ class TestPickList(unittest.TestCase): }) 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 @@ class TestPickList(unittest.TestCase): # 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 f87b273d64c..688ae1d4108 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -73,6 +73,34 @@ frappe.ui.form.on("Purchase Receipt", { }) }, __('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.fields_dict['items'].grid.get_field('project').get_query = function(doc, } } -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 70687bdac26..61e60f39228 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -53,7 +53,20 @@ class PurchaseReceipt(BuyingController): '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 @@ class PurchaseReceipt(BuyingController): 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 @@ class PurchaseReceipt(BuyingController): 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 @@ class PurchaseReceipt(BuyingController): 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 7741ee7f609..16eea24f847 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 @@ class TestPurchaseReceipt(unittest.TestCase): 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 @@ class TestPurchaseReceipt(unittest.TestCase): serial_no=serial_no, basic_rate=100, do_not_submit=True) se.submit() + se.cancel() dn.cancel() pr1.cancel() @@ -912,6 +913,57 @@ class TestPurchaseReceipt(unittest.TestCase): 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 efe3642d23c..82cc98e7f75 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 58b1eca2d33..469511af60b 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -18,6 +18,7 @@ class QualityInspection(Document): 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 @@ class QualityInspection(Document): child.update(d) child.status = "Accepted" + @frappe.whitelist() def get_quality_inspection_template(self): template = '' if self.bom_no: @@ -62,17 +64,21 @@ class QualityInspection(Document): (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 @@ class QualityInspection(Document): 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 8436acbed25..3f837805695 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 @@ class RepostItemValuation(Document): 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 @@ class RepostItemValuation(Document): 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 @@ def repost(doc): 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 @@ def notify_error_to_stock_managers(doc, traceback): recipients = get_users_with_role("Stock Manager") if not recipients: get_users_with_role("System Manager") - + subject = _("Error while reposting item valuation") message = (_("Hi,") + "
    " + _("An error has been appeared while reposting item valuation via {0}") @@ -112,4 +112,24 @@ def notify_error_to_stock_managers(doc, traceback): ) 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 c8d8ca9e17e..c02dd2e518d 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -14,6 +14,7 @@ from frappe import _, ValidationError 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 @@ def validate_serial_no(sle, item_det): 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 ed70790b2ca..cde7fe07c63 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 @@ class TestSerialNo(unittest.TestCase): 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 7af16af8986..ce2906ecbe9 100644 --- a/erpnext/stock/doctype/shipment/shipment.js +++ b/erpnext/stock/doctype/shipment/shipment.js @@ -363,43 +363,6 @@ frappe.ui.form.on('Shipment', { 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 76c331c5c25..a33cbc288c5 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 4697a7b3235..01fcee4cac2 100644 --- a/erpnext/stock/doctype/shipment/shipment.py +++ b/erpnext/stock/doctype/shipment/shipment.py @@ -23,10 +23,10 @@ class Shipment(Document): 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 16e74636cee..8b4bac2b87f 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -100,6 +100,13 @@ frappe.ui.form.on('Stock Entry', { 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 @@ frappe.ui.form.on('Stock Entry', { }) ); } - for (let i in frm.doc.items) { let item = frm.doc.items[i]; @@ -721,7 +727,7 @@ frappe.ui.form.on('Stock Entry Detail', { 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.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle } 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 ea1b3873ea7..48cfa51041d 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -398,8 +398,12 @@ class StockEntry(StockController): 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 @@ class StockEntry(StockController): 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 @@ class StockEntry(StockController): 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 @@ class StockEntry(StockController): 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 @@ class StockEntry(StockController): 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 @@ class StockEntry(StockController): return ret + @frappe.whitelist() def set_items_for_stock_in(self): self.items = [] @@ -937,6 +944,7 @@ class StockEntry(StockController): 'batch_no': d.batch_no }) + @frappe.whitelist() def get_items(self): self.set('items', []) self.validate_work_order() @@ -1010,7 +1018,8 @@ class StockEntry(StockController): 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 123f0c86471..a0e70516d4b 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 @@ class TestStockEntry(unittest.TestCase): 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 59f1f3961b6..3296f5ba4ae 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 @@ class TestStockLedgerEntry(unittest.TestCase): 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 @@ class TestStockLedgerEntry(unittest.TestCase): 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 @@ class TestStockLedgerEntry(unittest.TestCase): 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 @@ class TestStockLedgerEntry(unittest.TestCase): 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 @@ class TestStockLedgerEntry(unittest.TestCase): 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 @@ class TestStockLedgerEntry(unittest.TestCase): 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 @@ class TestStockLedgerEntry(unittest.TestCase): # 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 @@ class TestStockLedgerEntry(unittest.TestCase): # 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 @@ def create_items(): 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 f0a90f9754b..7c5f4ece0b3 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -29,6 +29,8 @@ class StockReconciliation(StockController): 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 @@ class StockReconciliation(StockController): 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 @@ class StockReconciliation(StockController): 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 @@ class StockReconciliation(StockController): 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 @@ class StockReconciliation(StockController): 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 @@ def get_difference_account(purpose, company): 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 088456f8651..36380b838b1 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 @@ class TestStockReconciliation(unittest.TestCase): 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 @@ class TestStockReconciliation(unittest.TestCase): 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 @@ class TestStockReconciliation(unittest.TestCase): 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 e53db0772b4..85c7ebe2634 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 84af57b48dd..f18eabc84bb 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 3b9608b8056..2dd7c6f35b8 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -30,7 +30,7 @@ class StockSettings(Document): # 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 @@ class StockSettings(Document): 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 bddb114c9de..9b9093261c2 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 873cfec85ec..3fc1df76bc3 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -86,7 +86,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru 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 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru 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 @@ def get_basic_details(args, item, overwrite_warehouse=True): "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 @@ def get_basic_details(args, item, overwrite_warehouse=True): 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 @@ def get_price_list_rate(args, item_doc, out): 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 @@ def get_projected_qty(item_code, warehouse): {"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 @@ def apply_price_list(args, as_doc=False): 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 @@ def get_price_list_currency_and_exchange_rate(args): 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 5df3fa8067b..2f70523264a 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_item_info(filters): 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) - - consumed_items_map = {} - for item in consumed_items: - consumed_items_map.setdefault(item.item_code, item.consumed_qty) + group by item_code""" % condition, as_dict=1) + 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 ff603fcfb3a..623dc2ffd97 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -49,7 +49,7 @@ def get_average_age(fifo_queue, to_date): 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 @@ def add_column(range_columns, label, fieldname, fieldtype='Float', width=140): 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 f54b3c1bb20..bbfcb7ad7d1 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -207,11 +207,11 @@ class update_entries_after(object): 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 @@ class update_entries_after(object): 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 @@ class update_entries_after(object): 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 @@ class update_entries_after(object): 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 @@ class update_entries_after(object): 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 @@ def get_future_sle_with_negative_qty(args): 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 0af3d908229..034d3ebbb54 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -172,7 +172,7 @@ def get_bin(item_code, warehouse): 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 9fe12f9490b..ecc9fcfe829 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -48,44 +48,62 @@ frappe.ui.form.on("Issue", { } }, - 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( - '
    ' + - '
    ' + - ''+ message.msg +' ' + - '
    ' + - '
    ' - ); - } 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( + '
    ' + + '
    ' + + '' + message.msg + ' ' + + '
    ' + + '
    ' + ); + } 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( + '
    ' + + '
    ' + + ' ' + + '
    ' + + '
    ' + ); + } + + // 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 @@ frappe.ui.form.on("Issue", { }, __("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( - '
    ' + - '
    ' + - ' ' + - '
    ' + - '
    ' - ); - } - - 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 bbbbc4a5270..b068363f061 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -7,7 +7,7 @@ import json 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 @@ class Issue(Document): 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 @@ class Issue(Document): 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 @@ class Issue(Document): 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 5346195a396..00060b95300 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 @@ frappe.ui.form.on('Service Level Agreement', { 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 f87b2c2dddc..746eee025a5 100644 --- a/erpnext/support/report/issue_analytics/issue_analytics.js +++ b/erpnext/support/report/issue_analytics/issue_analytics.js @@ -52,6 +52,7 @@ frappe.query_reports["Issue Analytics"] = { label: __("Status"), fieldtype: "Select", options:[ + "", {label: __('Open'), value: 'Open'}, {label: __('Replied'), value: 'Replied'}, {label: __('Resolved'), value: 'Resolved'}, @@ -138,4 +139,4 @@ frappe.query_reports["Issue Analytics"] = { } }); } -}; \ 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 684482ac8d2..eb0e06cd08b 100644 --- a/erpnext/support/report/issue_summary/issue_summary.js +++ b/erpnext/support/report/issue_summary/issue_summary.js @@ -39,6 +39,7 @@ frappe.query_reports["Issue Summary"] = { label: __("Status"), fieldtype: "Select", options:[ + "", {label: __('Open'), value: 'Open'}, {label: __('Replied'), value: 'Replied'}, {label: __('Resolved'), value: 'Resolved'}, @@ -70,4 +71,4 @@ frappe.query_reports["Issue Summary"] = { 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 f5adbf01e3d..167c848eff1 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 @@ ({{ product_info.price.formatted_price }} / {{ product_info.uom }})
    {% 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 930d0c2613a..383413103e1 100644 --- a/erpnext/templates/includes/transaction_row.html +++ b/erpnext/templates/includes/transaction_row.html @@ -14,11 +14,7 @@
    - {% if doc.doctype == "Quotation" and not doc.docstatus %} - {{ _("Pending") }} - {% else %} - {{ doc.get_formatted("grand_total") }} - {% endif %} + {{ doc.get_formatted("grand_total") }}
    Link diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html index 07dd676e77a..28faea8f4f3 100644 --- a/erpnext/templates/pages/order.html +++ b/erpnext/templates/pages/order.html @@ -12,21 +12,22 @@ {% endblock %} {% block header_actions %} - + + {% endblock %} {% block page_content %} diff --git a/erpnext/utilities/activation.py b/erpnext/utilities/activation.py index 7b17c8c464b..50c4b255ce1 100644 --- a/erpnext/utilities/activation.py +++ b/erpnext/utilities/activation.py @@ -18,7 +18,6 @@ def get_level(): "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 c8ae73365bb..f99da58e467 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -7,7 +7,6 @@ import frappe.share 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 @@ class TransactionBase(StatusUpdater): 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 @@ class TransactionBase(StatusUpdater): 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 dc9b6d80fb5..15afb097b94 100644 --- a/erpnext/www/lms/content.html +++ b/erpnext/www/lms/content.html @@ -62,7 +62,7 @@ {{_('Back to Course')}} -
    +

    {{ content.name }} ({{ position + 1 }}/{{length}})

    {% 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.

    + A timer for ${duration} will start, once you click on Proceed.

    + 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 7b239acd56a..c1e96205eb8 100644 --- a/erpnext/www/lms/index.html +++ b/erpnext/www/lms/index.html @@ -42,7 +42,9 @@

    {{ education_settings.portal_title }}

    -

    {{ education_settings.description }}

    + {% if education_settings.description %} +

    {{ education_settings.description }}

    + {% endif %}

    {% if frappe.session.user == 'Guest' %} {{_('Sign Up')}} @@ -51,13 +53,15 @@

    - {% 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 %} +

    You have not enrolled in any program. Contact your Instructor.

    {% endif %}
    diff --git a/erpnext/www/lms/topic.py b/erpnext/www/lms/topic.py index f75ae8e9b66..8abbc72e918 100644 --- a/erpnext/www/lms/topic.py +++ b/erpnext/www/lms/topic.py @@ -35,7 +35,7 @@ def get_contents(topic, course, program): 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 d12661b5cc6..c9ee7a622c4 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 5a352364b6e..f1ffeb8f481 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 e5a2da1e401..0a2ac1affc8 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== - dependencies: - "@types/events" "*" - "@types/node" "*" - -"@types/bunyan@*": - version "1.8.6" - resolved "https://registry.yarnpkg.com/@types/bunyan/-/bunyan-1.8.6.tgz#6527641cca30bedec5feb9ab527b7803b8000582" - integrity sha512-YiozPOOsS6bIuz31ilYqR5SlLif4TBWsousN2aCWLi5233nZSX19tFbcQUPdR7xJ8ypPyxkCGNxg0CIV5n9qxQ== +"@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: + "@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/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 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: 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 @@ argparse@^1.0.7: 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 @@ bl@^3.0.0: 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 @@ brace-expansion@^1.1.7: 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 @@ chalk@^2.3.2, chalk@^2.4.2: 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 @@ color-convert@^1.9.0: 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 @@ cross-spawn@^6.0.0: 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 @@ dot-prop@^5.2.0: 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 @@ emoji-regex@^7.0.1: 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 @@ execa@^1.0.0: 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 @@ external-editor@^3.0.3: 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 @@ glob@^7.1.3: 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 @@ imurmurhash@^0.1.4: 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 @@ inherits@2, inherits@~2.0.3: 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 @@ isexe@^2.0.0: 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 @@ js-yaml@^3.13.1: 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 @@ lodash.assignin@^4.2.0: 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 @@ lodash.clonedeep@^4.3.0, lodash.clonedeep@^4.5.0: 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 @@ lru-cache@^5.1.1: 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 @@ minimatch@^3.0.4: 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 @@ npm-run-path@^2.0.0: 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 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: 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 @@ os-tmpdir@~1.0.2: 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 @@ pseudomap@^1.0.2: 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 @@ pump@^3.0.0: 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 @@ rc@^1.0.1, rc@^1.1.6: 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 @@ readable-stream@2, readable-stream@~2.3.6: 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 @@ readable-stream@^3.0.1, readable-stream@^3.1.1: 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 @@ rimraf@^2.6.3: 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 @@ safe-buffer@~5.2.0: 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 @@ sax@>=0.6.0, sax@^1.2.4: 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 @@ shebang-command@^1.2.0: 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== - 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== +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: + 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== - 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== +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: + "@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 @@ snyk-resolve-deps@4.4.0: 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 @@ snyk-tree@^1.0.0: 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 @@ snyk-try-require@1.3.1, snyk-try-require@^1.1.1, snyk-try-require@^1.3.1: 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 @@ source-map-support@^0.5.11, source-map-support@^0.5.7: 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 @@ string-width@^3.0.0: 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 @@ string_decoder@^1.1.1: 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 @@ string_decoder@~1.1.1: 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 @@ supports-color@^5.3.0: 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 @@ tar-stream@^2.1.0: 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 @@ tempfile@^2.0.0: 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 @@ then-fs@^2.0.0: 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 @@ tmp@0.0.33, tmp@^0.0.33: 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 @@ tmp@^0.1.0: 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 @@ tree-kill@^1.2.2: 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 @@ uuid@^3.0.1, uuid@^3.3.2: 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 @@ which@^1.2.9: 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 @@ windows-release@^3.1.0: 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 @@ wrappy@1: 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 @@ xmlbuilder@~11.0.0: 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 @@ yallist@^3.0.2: 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==