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 ` -
${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 '';
-
- // main table
-
- out +='
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {{__("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([' +
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
{% 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 @@
\n\t{{ doc.company }} \n\t{{ _(\"Receipt No\") }}: {{ doc.name }} \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{{ _(\"Receipt No\") }}: {{ doc.name }} \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{{ _(\"Receipt No\") }}: {{ doc.name }} \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{{ _(\"Receipt No\") }}: {{ doc.name }} \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 +' | '
- +'" + _("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( - ' ' +
- ' '
- );
- } 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(
+ '' +
- ' ' +
- ' ' +
- '' +
+ ' '
+ );
+ } 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(
- '' +
+ '' + agreement_status.msg + ' ' +
+ ' ' +
+ '' +
- ' '
- );
- }
-
- 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 }})
' +
- ''+ agreement_status.msg +' ' +
- ' ' +
- '
- {% 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}})+ 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 %} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||