diff --git a/.copier-answers.yml b/.copier-answers.yml new file mode 100644 index 0000000..36ee1cf --- /dev/null +++ b/.copier-answers.yml @@ -0,0 +1,13 @@ +# Do NOT update manually; changes here will be overwritten by Copier +_commit: v1.1.1 +_src_path: gh:oca/oca-addons-repo-template +dependency_installation_mode: PIP +generate_requirements_txt: true +include_wkhtmltopdf: false +odoo_version: 14.0 +rebel_module_groups: [] +repo_description: "TODO: add repo description." +repo_name: web +repo_slug: web +travis_apt_packages: [] +travis_apt_sources: [] diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..bfd7ac5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# Configuration for known file extensions +[*.{css,js,json,less,md,py,rst,sass,scss,xml,yaml,yml}] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{json,yml,yaml,rst,md}] +indent_size = 2 + +# Do not configure editor for libs and autogenerated content +[{*/static/{lib,src/lib}/**,*/static/description/index.html,*/readme/../README.rst}] +charset = unset +end_of_line = unset +indent_size = unset +indent_style = unset +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 0000000..88f2881 --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,180 @@ +env: + browser: true + +# See https://github.com/OCA/odoo-community.org/issues/37#issuecomment-470686449 +parserOptions: + ecmaVersion: 2017 + +# Globals available in Odoo that shouldn't produce errorings +globals: + _: readonly + $: readonly + fuzzy: readonly + jQuery: readonly + moment: readonly + odoo: readonly + openerp: readonly + Promise: readonly + +# Styling is handled by Prettier, so we only need to enable AST rules; +# see https://github.com/OCA/maintainer-quality-tools/pull/618#issuecomment-558576890 +rules: + accessor-pairs: warn + array-callback-return: warn + callback-return: warn + capitalized-comments: + - warn + - always + - ignoreConsecutiveComments: true + ignoreInlineComments: true + complexity: + - warn + - 15 + constructor-super: warn + dot-notation: warn + eqeqeq: warn + global-require: warn + handle-callback-err: warn + id-blacklist: warn + id-match: warn + init-declarations: error + max-depth: warn + max-nested-callbacks: warn + max-statements-per-line: warn + no-alert: warn + no-array-constructor: warn + no-caller: warn + no-case-declarations: warn + no-class-assign: warn + no-cond-assign: error + no-const-assign: error + no-constant-condition: warn + no-control-regex: warn + no-debugger: error + no-delete-var: warn + no-div-regex: warn + no-dupe-args: error + no-dupe-class-members: error + no-dupe-keys: error + no-duplicate-case: error + no-duplicate-imports: error + no-else-return: warn + no-empty-character-class: warn + no-empty-function: error + no-empty-pattern: error + no-empty: warn + no-eq-null: error + no-eval: error + no-ex-assign: error + no-extend-native: warn + no-extra-bind: warn + no-extra-boolean-cast: warn + no-extra-label: warn + no-fallthrough: warn + no-func-assign: error + no-global-assign: error + no-implicit-coercion: + - warn + - allow: ["~"] + no-implicit-globals: warn + no-implied-eval: warn + no-inline-comments: warn + no-inner-declarations: warn + no-invalid-regexp: warn + no-irregular-whitespace: warn + no-iterator: warn + no-label-var: warn + no-labels: warn + no-lone-blocks: warn + no-lonely-if: error + no-mixed-requires: error + no-multi-str: warn + no-native-reassign: error + no-negated-condition: warn + no-negated-in-lhs: error + no-new-func: warn + no-new-object: warn + no-new-require: warn + no-new-symbol: warn + no-new-wrappers: warn + no-new: warn + no-obj-calls: warn + no-octal-escape: warn + no-octal: warn + no-param-reassign: warn + no-path-concat: warn + no-process-env: warn + no-process-exit: warn + no-proto: warn + no-prototype-builtins: warn + no-redeclare: warn + no-regex-spaces: warn + no-restricted-globals: warn + no-restricted-imports: warn + no-restricted-modules: warn + no-restricted-syntax: warn + no-return-assign: error + no-script-url: warn + no-self-assign: warn + no-self-compare: warn + no-sequences: warn + no-shadow-restricted-names: warn + no-shadow: warn + no-sparse-arrays: warn + no-sync: warn + no-this-before-super: warn + no-throw-literal: warn + no-undef-init: warn + no-undef: error + no-unmodified-loop-condition: warn + no-unneeded-ternary: error + no-unreachable: error + no-unsafe-finally: error + no-unused-expressions: error + no-unused-labels: error + no-unused-vars: error + no-use-before-define: error + no-useless-call: warn + no-useless-computed-key: warn + no-useless-concat: warn + no-useless-constructor: warn + no-useless-escape: warn + no-useless-rename: warn + no-void: warn + no-with: warn + operator-assignment: [error, always] + prefer-const: warn + radix: warn + require-yield: warn + sort-imports: warn + spaced-comment: [error, always] + strict: [error, function] + use-isnan: error + valid-jsdoc: + - warn + - prefer: + arg: param + argument: param + augments: extends + constructor: class + exception: throws + func: function + method: function + prop: property + return: returns + virtual: abstract + yield: yields + preferType: + array: Array + bool: Boolean + boolean: Boolean + number: Number + object: Object + str: String + string: String + requireParamDescription: false + requireReturn: false + requireReturnDescription: false + requireReturnType: false + valid-typeof: warn + yoda: warn diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..44ed868 --- /dev/null +++ b/.flake8 @@ -0,0 +1,10 @@ +[flake8] +max-line-length = 80 +max-complexity = 16 +# B = bugbear +# B9 = bugbear opinionated (incl line length) +select = C,E,F,W,B,B9 +# E203: whitespace before ':' (black behaviour) +# E501: flake8 line length (covered by bugbear B950) +# W503: line break before binary operator (black behaviour) +ignore = E203,E501,W503 diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000..7683bad --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,12 @@ +[settings] +; see https://github.com/psf/black +multi_line_output=3 +include_trailing_comma=True +force_grid_wrap=0 +combine_as_imports=True +use_parentheses=True +line_length=88 +known_odoo=odoo +known_odoo_addons=odoo.addons +sections=FUTURE,STDLIB,THIRDPARTY,ODOO,ODOO_ADDONS,FIRSTPARTY,LOCALFOLDER +default_section=THIRDPARTY diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..6a8a1aa --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,135 @@ +exclude: | + (?x) + # NOT INSTALLABLE ADDONS + # END NOT INSTALLABLE ADDONS + # Files and folders generated by bots, to avoid loops + ^setup/|/static/description/index\.html$| + # We don't want to mess with tool-generated files + .svg$| + # Maybe reactivate this when all README files include prettier ignore tags? + ^README\.md$| + # Library files can have extraneous formatting (even minimized) + /static/(src/)?lib/| + # Repos using Sphinx to generate docs don't need prettying + ^docs/_templates/.*\.html$| + # You don't usually want a bot to modify your legal texts + (LICENSE.*|COPYING.*) +default_language_version: + python: python3 + node: "14.13.0" +repos: + - repo: local + hooks: + # These files are most likely copier diff rejection junks; if found, + # review them manually, fix the problem (if needed) and remove them + - id: forbidden-files + name: forbidden files + entry: found forbidden files; remove them + language: fail + files: "\\.rej$" + - repo: https://github.com/oca/maintainer-tools + rev: ab1d7f6 + hooks: + # update the NOT INSTALLABLE ADDONS section above + - id: oca-update-pre-commit-excluded-addons + # - id: oca-fix-manifest-website + # args: ["https://github.com/OCA/web"] + - repo: https://github.com/myint/autoflake + rev: v1.4 + hooks: + - id: autoflake + args: ["-i", "--ignore-init-module-imports"] + - repo: https://github.com/psf/black + rev: 20.8b1 + hooks: + - id: black + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v2.1.2 + hooks: + - id: prettier + name: prettier + plugin-xml + additional_dependencies: + - "prettier@2.1.2" + - "@prettier/plugin-xml@0.12.0" + args: + - --plugin=@prettier/plugin-xml + - repo: https://github.com/pre-commit/mirrors-eslint + rev: v7.8.1 + hooks: + - id: eslint + verbose: true + args: + - --color + - --fix + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + # exclude autogenerated files + exclude: /README\.rst$|\.pot?$ + - id: end-of-file-fixer + # exclude autogenerated files + exclude: /README\.rst$|\.pot?$ + - id: debug-statements + - id: fix-encoding-pragma + args: ["--remove"] + - id: check-case-conflict + - id: check-docstring-first + - id: check-executables-have-shebangs + - id: check-merge-conflict + # exclude files where underlines are not distinguishable from merge conflicts + exclude: /README\.rst$|^docs/.*\.rst$ + - id: check-symlinks + - id: check-xml + - id: mixed-line-ending + args: ["--fix=lf"] + - repo: https://github.com/asottile/pyupgrade + rev: v2.7.2 + hooks: + - id: pyupgrade + - repo: https://github.com/PyCQA/isort + rev: 5.5.1 + hooks: + - id: isort + name: isort except __init__.py + args: + - --settings=. + exclude: /__init__\.py$ + - repo: https://github.com/acsone/setuptools-odoo + rev: 2.6.0 + hooks: + - id: setuptools-odoo-make-default + - id: setuptools-odoo-get-requirements + args: + - --output + - requirements.txt + - --header + - "# generated from manifests external_dependencies" + - repo: https://gitlab.com/PyCQA/flake8 + rev: 3.8.3 + hooks: + - id: flake8 + name: flake8 except __init__.py + exclude: /__init__\.py$ + additional_dependencies: ["flake8-bugbear==20.1.4"] + - id: flake8 + name: flake8 only __init__.py + args: ["--extend-ignore=F401"] # ignore unused imports in __init__.py + files: /__init__\.py$ + additional_dependencies: ["flake8-bugbear==20.1.4"] + - repo: https://github.com/PyCQA/pylint + rev: pylint-2.5.3 + hooks: + - id: pylint + name: pylint with optional checks + args: + - --rcfile=.pylintrc + - --exit-zero + verbose: true + additional_dependencies: &pylint_deps + - pylint-odoo==3.5.0 + - id: pylint + name: pylint with mandatory checks + args: + - --rcfile=.pylintrc-mandatory + additional_dependencies: *pylint_deps diff --git a/.prettierrc.yml b/.prettierrc.yml new file mode 100644 index 0000000..5b6d4b3 --- /dev/null +++ b/.prettierrc.yml @@ -0,0 +1,8 @@ +# Defaults for all prettier-supported languages. +# Prettier will complete this with settings from .editorconfig file. +bracketSpacing: false +printWidth: 88 +proseWrap: always +semi: true +trailingComma: "es5" +xmlWhitespaceSensitivity: "strict" diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..cdc64ae --- /dev/null +++ b/.pylintrc @@ -0,0 +1,88 @@ +[MASTER] +load-plugins=pylint_odoo +score=n + +[ODOOLINT] +# readme_template_url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst" +manifest_required_authors=Noviat +manifest_required_keys=license +manifest_deprecated_keys=description,active +license_allowed=AGPL-3,GPL-2,GPL-2 or any later version,GPL-3,GPL-3 or any later version,LGPL-3 +valid_odoo_versions=14.0 + +[MESSAGES CONTROL] +disable=all + +# This .pylintrc contains optional AND mandatory checks and is meant to be +# loaded in an IDE to have it check everything, in the hope this will make +# optional checks more visible to contributors who otherwise never look at a +# green travis to see optional checks that failed. +# .pylintrc-mandatory containing only mandatory checks is used the pre-commit +# config as a blocking check. + +enable=anomalous-backslash-in-string, + api-one-deprecated, + api-one-multi-together, + assignment-from-none, + attribute-deprecated, + class-camelcase, + dangerous-default-value, + dangerous-view-replace-wo-priority, + development-status-allowed, + duplicate-id-csv, + duplicate-key, + duplicate-xml-fields, + duplicate-xml-record-id, + eval-referenced, + eval-used, + incoherent-interpreter-exec-perm, + license-allowed, + manifest-author-string, + manifest-deprecated-key, + manifest-required-author, + manifest-required-key, + manifest-version-format, + method-compute, + method-inverse, + method-required-super, + method-search, + openerp-exception-warning, + pointless-statement, + pointless-string-statement, + print-used, + redundant-keyword-arg, + redundant-modulename-xml, + reimported, + relative-import, + return-in-init, + rst-syntax-error, + sql-injection, + too-few-format-args, + translation-field, + translation-required, + unreachable, + use-vim-comment, + wrong-tabs-instead-of-spaces, + xml-syntax-error, + # messages that do not cause the lint step to fail + consider-merging-classes-inherited, + create-user-wo-reset-password, + dangerous-filter-wo-user, + deprecated-module, + file-not-used, + invalid-commit, + missing-manifest-dependency, + missing-newline-extrafiles, + # missing-readme, + no-utf8-coding-comment, + odoo-addons-relative-import, + old-api7-method-defined, + redefined-builtin, + too-complex, + unnecessary-utf8-coding-comment + + +[REPORTS] +msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg} +output-format=colorized +reports=no diff --git a/.pylintrc-mandatory b/.pylintrc-mandatory new file mode 100644 index 0000000..3c61cdc --- /dev/null +++ b/.pylintrc-mandatory @@ -0,0 +1,64 @@ +[MASTER] +load-plugins=pylint_odoo +score=n + +[ODOOLINT] +readme_template_url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst" +manifest_required_authors=Noviat +manifest_required_keys=license +manifest_deprecated_keys=description,active +license_allowed=AGPL-3,GPL-2,GPL-2 or any later version,GPL-3,GPL-3 or any later version,LGPL-3 +valid_odoo_versions=14.0 + +[MESSAGES CONTROL] +disable=all + +enable=anomalous-backslash-in-string, + api-one-deprecated, + api-one-multi-together, + assignment-from-none, + attribute-deprecated, + class-camelcase, + dangerous-default-value, + dangerous-view-replace-wo-priority, + development-status-allowed, + duplicate-id-csv, + duplicate-key, + duplicate-xml-fields, + duplicate-xml-record-id, + eval-referenced, + eval-used, + incoherent-interpreter-exec-perm, + license-allowed, + manifest-author-string, + manifest-deprecated-key, + manifest-required-author, + manifest-required-key, + manifest-version-format, + method-compute, + method-inverse, + method-required-super, + method-search, + openerp-exception-warning, + pointless-statement, + pointless-string-statement, + print-used, + redundant-keyword-arg, + redundant-modulename-xml, + reimported, + relative-import, + return-in-init, + rst-syntax-error, + sql-injection, + too-few-format-args, + translation-field, + translation-required, + unreachable, + use-vim-comment, + wrong-tabs-instead-of-spaces, + xml-syntax-error + +[REPORTS] +msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg} +output-format=colorized +reports=no diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5e8e3a6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,42 @@ +language: python +cache: + directories: + - $HOME/.cache/pip + - $HOME/.cache/pre-commit + +python: + - "3.6" + +addons: + postgresql: "9.6" + apt: + packages: + - expect-dev # provides unbuffer utility + +stages: + - test + +jobs: + include: + - stage: test + env: + - TESTS=1 ODOO_REPO="odoo/odoo" MAKEPOT="1" + - stage: test + env: + - TESTS=1 ODOO_REPO="OCA/OCB" +env: + global: + - VERSION="14.0" TESTS="0" LINT_CHECK="0" MAKEPOT="0" + - MQT_DEP=PIP + +install: + - git clone --depth=1 https://github.com/OCA/maintainer-quality-tools.git + ${HOME}/maintainer-quality-tools + - export PATH=${HOME}/maintainer-quality-tools/travis:${PATH} + - travis_install_nightly + +script: + - travis_run_tests + +after_success: + - travis_after_tests_success diff --git a/account_ebics/README.rst b/account_ebics/README.rst index 634a617..a7337f2 100644 --- a/account_ebics/README.rst +++ b/account_ebics/README.rst @@ -25,6 +25,7 @@ Remark: The EBICS 'Test Mode' for uploading orders requires Fintech 4.3.4 or higher. SWIFT 3SKey support requires Fintech 6.4 or higher. + | We also recommend to consider the installation of the following modules: diff --git a/account_ebics/__manifest__.py b/account_ebics/__manifest__.py index b3daf19..2f0ad2f 100644 --- a/account_ebics/__manifest__.py +++ b/account_ebics/__manifest__.py @@ -2,31 +2,31 @@ # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). { - 'name': 'EBICS banking protocol', - 'version': '14.0.1.0.2', - 'license': 'LGPL-3', - 'author': 'Noviat', - 'website': 'www.noviat.com', - 'category': 'Accounting & Finance', - 'depends': ['account'], - 'data': [ - 'security/ebics_security.xml', - 'security/ir.model.access.csv', - 'data/ebics_file_format.xml', - 'views/ebics_config_views.xml', - 'views/ebics_file_views.xml', - 'views/ebics_userid_views.xml', - 'views/ebics_file_format_views.xml', - 'wizards/ebics_change_passphrase.xml', - 'wizards/ebics_xfer.xml', - 'views/menu.xml', + "name": "EBICS banking protocol", + "version": "14.0.1.0.3", + "license": "LGPL-3", + "author": "Noviat", + "website": "www.noviat.com", + "category": "Accounting & Finance", + "depends": ["account"], + "data": [ + "security/ebics_security.xml", + "security/ir.model.access.csv", + "data/ebics_file_format.xml", + "views/ebics_config_views.xml", + "views/ebics_file_views.xml", + "views/ebics_userid_views.xml", + "views/ebics_file_format_views.xml", + "wizards/ebics_change_passphrase.xml", + "wizards/ebics_xfer.xml", + "views/menu.xml", ], - 'installable': True, - 'application': True, - 'external_dependencies': { - 'python': [ - 'fintech', - 'cryptography', - ] - }, + "installable": True, + "application": True, + "external_dependencies": { + "python": [ + "fintech", + "cryptography", + ] + }, } diff --git a/account_ebics/data/ebics_file_format.xml b/account_ebics/data/ebics_file_format.xml index e720900..42119b9 100644 --- a/account_ebics/data/ebics_file_format.xml +++ b/account_ebics/data/ebics_file_format.xml @@ -1,4 +1,4 @@ - + @@ -9,7 +9,9 @@ down C52 camt.052 - bank to customer account report in format camt.052 + bank to customer account report in format camt.052 c52.xml @@ -18,16 +20,20 @@ down Z52 camt.052 - bank to customer account report in format camt.052 + bank to customer account report in format camt.052 c52.xml - + camt.053 down C53 camt.053 - Bank to customer statement report in format camt.053 + Bank to customer statement report in format camt.053 c53.xml @@ -36,16 +42,20 @@ down Z53 camt.053 - Bank to customer statement report in format camt.053 + Bank to customer statement report in format camt.053 c53.xml - + camt.054 down C54 camt.054 - Bank to customer debit credit notification in format camt.054 + Bank to customer debit credit notification in format camt.054 c52.xml @@ -54,7 +64,9 @@ down Z54 camt.054 - Bank to customer debit credit notification in format camt.054 + Bank to customer debit credit notification in format camt.054 c52.xml @@ -63,7 +75,9 @@ down FDL cfonb120 - Bank to customer statement report in format cfonb120 + Bank to customer statement report in format cfonb120 cfonb120.dat @@ -71,19 +85,23 @@ pain.002 down CDZ - Payment status report for direct debit in format pain.002 + Payment status report for direct debit in format pain.002 psr.xml - + pain.002 down Z01 pain.002 - Payment status report for direct debit in format pain.002 + Payment status report for direct debit in format pain.002 psr.xml - + @@ -114,7 +132,9 @@ pain.008.001.02.sdd up CDD - Sepa Core Direct Debit Order in format pain.008.001.02 + Sepa Core Direct Debit Order in format pain.008.001.02 xml @@ -122,15 +142,19 @@ pain.008.001.02.sdd up XE3 - Sepa Core Direct Debit Order in format pain.008.001.02 + Sepa Core Direct Debit Order in format pain.008.001.02 xml - + pain.008.001.02.sbb up CDB - Sepa Direct Debit (B2B) Order in format pain.008.001.02 + Sepa Direct Debit (B2B) Order in format pain.008.001.02 xml @@ -138,7 +162,9 @@ pain.008.001.02.sbb up XE4 - Sepa Direct Debit (B2B) Order in format pain.008.001.02 + Sepa Direct Debit (B2B) Order in format pain.008.001.02 xml diff --git a/account_ebics/migrations/13.0.1.1/noupdate_changes.xml b/account_ebics/migrations/13.0.1.1/noupdate_changes.xml index ae3cbe1..0c91541 100644 --- a/account_ebics/migrations/13.0.1.1/noupdate_changes.xml +++ b/account_ebics/migrations/13.0.1.1/noupdate_changes.xml @@ -3,16 +3,20 @@ EBICS Configuration model company rule - - - ['|', ('company_ids', '=', False), ('company_ids', 'in', user.company_ids.ids)] + + + ['|', ('company_ids', '=', False), ('company_ids', 'in', user.company_ids.ids)] EBICS File model company rule - - - ['|', ('company_ids', '=', False), ('company_ids', 'in', user.company_ids.ids)] + + + ['|', ('company_ids', '=', False), ('company_ids', 'in', user.company_ids.ids)] diff --git a/account_ebics/migrations/13.0.1.1/post-migration.py b/account_ebics/migrations/13.0.1.1/post-migration.py index a51a3ac..c1ef5d4 100644 --- a/account_ebics/migrations/13.0.1.1/post-migration.py +++ b/account_ebics/migrations/13.0.1.1/post-migration.py @@ -1,9 +1,10 @@ # Copyright 2009-2020 Noviat. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openupgradelib import openupgrade # pylint: disable=W7936 import os +from openupgradelib import openupgrade # pylint: disable=W7936 + @openupgrade.migrate() def migrate(env, version): @@ -16,58 +17,67 @@ def _ebics_config_upgrade(env, version): env.cr.execute("SELECT * FROM ebics_config") cfg_datas = env.cr.dictfetchall() for cfg_data in cfg_datas: - cfg = env['ebics.config'].browse(cfg_data['id']) - journal = env['account.journal'].search( - [('bank_account_id', '=', cfg_data['bank_id'])]) - keys_fn_old = cfg_data['ebics_keys'] + cfg = env["ebics.config"].browse(cfg_data["id"]) + journal = env["account.journal"].search( + [("bank_account_id", "=", cfg_data["bank_id"])] + ) + keys_fn_old = cfg_data["ebics_keys"] ebics_keys_root = os.path.dirname(keys_fn_old) if os.path.isfile(keys_fn_old): - keys_fn = ebics_keys_root + '/' + cfg_data['ebics_user'] + '_keys' + keys_fn = ebics_keys_root + "/" + cfg_data["ebics_user"] + "_keys" os.rename(keys_fn_old, keys_fn) - state = cfg_data['state'] == 'active' and 'confirm' or 'draft' - cfg.write({ - 'company_ids': [(6, 0, [cfg_data['company_id']])], - 'journal_ids': [(6, 0, journal.ids)], - 'ebics_keys': ebics_keys_root, - 'state': state, - }) + state = cfg_data["state"] == "active" and "confirm" or "draft" + cfg.write( + { + "company_ids": [(6, 0, [cfg_data["company_id"]])], + "journal_ids": [(6, 0, journal.ids)], + "ebics_keys": ebics_keys_root, + "state": state, + } + ) user_vals = { - 'ebics_config_id': cfg_data['id'], - 'name': cfg_data['ebics_user'], + "ebics_config_id": cfg_data["id"], + "name": cfg_data["ebics_user"], } for fld in [ - 'signature_class', 'ebics_passphrase', - 'ebics_ini_letter_fn', 'ebics_public_bank_keys_fn', - 'ebics_key_x509', 'ebics_key_x509_dn_cn', - 'ebics_key_x509_dn_o', 'ebics_key_x509_dn_ou', - 'ebics_key_x509_dn_c', 'ebics_key_x509_dn_st', - 'ebics_key_x509_dn_l', 'ebics_key_x509_dn_e', - 'ebics_file_format_ids', 'state']: + "signature_class", + "ebics_passphrase", + "ebics_ini_letter_fn", + "ebics_public_bank_keys_fn", + "ebics_key_x509", + "ebics_key_x509_dn_cn", + "ebics_key_x509_dn_o", + "ebics_key_x509_dn_ou", + "ebics_key_x509_dn_c", + "ebics_key_x509_dn_st", + "ebics_key_x509_dn_l", + "ebics_key_x509_dn_e", + "ebics_file_format_ids", + "state", + ]: if cfg_data.get(fld): - if fld == 'ebics_file_format_ids': + if fld == "ebics_file_format_ids": user_vals[fld] = [(6, 0, cfg_data[fld])] - elif fld == 'state' and cfg_data['state'] == 'active': - user_vals['state'] = 'active_keys' + elif fld == "state" and cfg_data["state"] == "active": + user_vals["state"] = "active_keys" else: user_vals[fld] = cfg_data[fld] - ebics_userid = env['ebics.userid'].create(user_vals) + ebics_userid = env["ebics.userid"].create(user_vals) env.cr.execute( """ UPDATE ir_attachment SET res_model = 'ebics.userid', res_id = %s WHERE name in ('ebics_ini_letter', 'ebics_public_bank_keys'); """ - % ebics_userid.id) + % ebics_userid.id + ) if len(cfg_datas) == 1: - env.cr.execute( - "UPDATE ebics_file SET ebics_userid_id = %s" % ebics_userid.id) + env.cr.execute("UPDATE ebics_file SET ebics_userid_id = %s" % ebics_userid.id) def _noupdate_changes(env, version): openupgrade.load_data( - env.cr, - 'account_ebics', - 'migrations/13.0.1.1/noupdate_changes.xml' + env.cr, "account_ebics", "migrations/13.0.1.1/noupdate_changes.xml" ) diff --git a/account_ebics/migrations/13.0.1.3/post-migration.py b/account_ebics/migrations/13.0.1.3/post-migration.py index a082120..6c0dd75 100644 --- a/account_ebics/migrations/13.0.1.3/post-migration.py +++ b/account_ebics/migrations/13.0.1.3/post-migration.py @@ -2,16 +2,18 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). _FILE_FORMATS = [ - {'xml_id_name': 'ebics_ff_C52', - 'download_process_method': 'camt.052', - }, - {'xml_id_name': 'ebics_ff_C53', - 'download_process_method': 'camt.053', - }, - {'xml_id_name': 'ebics_ff_FDL_camt_xxx_cfonb120_stm', - 'download_process_method': 'cfonb120', - }, - + { + "xml_id_name": "ebics_ff_C52", + "download_process_method": "camt.052", + }, + { + "xml_id_name": "ebics_ff_C53", + "download_process_method": "camt.053", + }, + { + "xml_id_name": "ebics_ff_FDL_camt_xxx_cfonb120_stm", + "download_process_method": "cfonb120", + }, ] @@ -21,21 +23,22 @@ def migrate(cr, version): def _update_file_format(cr, ff): - cr.execute( + cr.execute( # pylint: disable=E8103 """ SELECT res_id FROM ir_model_data WHERE module='account_ebics' AND name='{}' - """.format(ff['xml_id_name']) + """.format( + ff["xml_id_name"] + ) ) res = cr.fetchone() if res: - cr.execute( + cr.execute( # pylint: disable=E8103 """ UPDATE ebics_file_format SET download_process_method='{download_process_method}' WHERE id={ff_id}; """.format( - download_process_method=ff['download_process_method'], - ff_id=res[0] - ) + download_process_method=ff["download_process_method"], ff_id=res[0] + ) ) diff --git a/account_ebics/migrations/13.0.1.3/pre-migration.py b/account_ebics/migrations/13.0.1.3/pre-migration.py index 7799b26..04212cd 100644 --- a/account_ebics/migrations/13.0.1.3/pre-migration.py +++ b/account_ebics/migrations/13.0.1.3/pre-migration.py @@ -2,36 +2,45 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). _FILE_FORMATS = [ - {'old_xml_id_name': 'ebics_ff_camt_052_001_02_stm', - 'new_xml_id_name': 'ebics_ff_C52', - 'new_name': 'camt.052', - }, - {'old_xml_id_name': 'ebics_ff_camt_053_001_02_stm', - 'new_xml_id_name': 'ebics_ff_C53', - 'new_name': 'camt.053', - }, - {'old_xml_id_name': 'ebics_ff_camt_xxx_cfonb120_stm', - 'new_xml_id_name': 'ebics_ff_FDL_camt_xxx_cfonb120_stm', - }, - {'old_xml_id_name': 'ebics_ff_pain_001_001_03_sct', - 'new_xml_id_name': 'ebics_ff_CCT', - }, - {'old_xml_id_name': 'ebics_ff_pain_001', - 'new_xml_id_name': 'ebics_ff_XE2', - 'new_name': 'pain.001.001.03', - }, - {'old_xml_id_name': 'ebics_ff_pain_008_001_02_sdd', - 'new_xml_id_name': 'ebics_ff_CDD', - }, - {'old_xml_id_name': 'ebics_ff_pain_008', - 'new_xml_id_name': 'ebics_ff_XE3', - }, - {'old_xml_id_name': 'ebics_ff_pain_008_001_02_sbb', - 'new_xml_id_name': 'ebics_ff_CDB', - }, - {'old_xml_id_name': 'ebics_ff_pain_001_001_02_sct', - 'new_xml_id_name': 'ebics_ff_FUL_pain_001_001_02_sct', - }, + { + "old_xml_id_name": "ebics_ff_camt_052_001_02_stm", + "new_xml_id_name": "ebics_ff_C52", + "new_name": "camt.052", + }, + { + "old_xml_id_name": "ebics_ff_camt_053_001_02_stm", + "new_xml_id_name": "ebics_ff_C53", + "new_name": "camt.053", + }, + { + "old_xml_id_name": "ebics_ff_camt_xxx_cfonb120_stm", + "new_xml_id_name": "ebics_ff_FDL_camt_xxx_cfonb120_stm", + }, + { + "old_xml_id_name": "ebics_ff_pain_001_001_03_sct", + "new_xml_id_name": "ebics_ff_CCT", + }, + { + "old_xml_id_name": "ebics_ff_pain_001", + "new_xml_id_name": "ebics_ff_XE2", + "new_name": "pain.001.001.03", + }, + { + "old_xml_id_name": "ebics_ff_pain_008_001_02_sdd", + "new_xml_id_name": "ebics_ff_CDD", + }, + { + "old_xml_id_name": "ebics_ff_pain_008", + "new_xml_id_name": "ebics_ff_XE3", + }, + { + "old_xml_id_name": "ebics_ff_pain_008_001_02_sbb", + "new_xml_id_name": "ebics_ff_CDB", + }, + { + "old_xml_id_name": "ebics_ff_pain_001_001_02_sct", + "new_xml_id_name": "ebics_ff_FUL_pain_001_001_02_sct", + }, ] @@ -44,11 +53,13 @@ def migrate(cr, version): def _update_file_format(cr, ff): - cr.execute( + cr.execute( # pylint: disable=E8103 """ SELECT id, res_id FROM ir_model_data WHERE module='account_ebics' AND name='{}' - """.format(ff['old_xml_id_name']) + """.format( + ff["old_xml_id_name"] + ) ) res = cr.fetchone() if res: @@ -59,7 +70,7 @@ def _update_file_format(cr, ff): """.format( new_xml_id_name=ff["new_xml_id_name"], xml_id=res[0] ) - if ff.get('new_name'): + if ff.get("new_name"): query += """ UPDATE ebics_file_format SET name='{new_name}' @@ -67,4 +78,4 @@ def _update_file_format(cr, ff): """.format( new_name=ff["new_name"], ff_id=res[1] ) - cr.execute(query) + cr.execute(query) # pylint: disable=E8103 diff --git a/account_ebics/models/account_bank_statement.py b/account_ebics/models/account_bank_statement.py index dde134d..e1e1c2b 100644 --- a/account_ebics/models/account_bank_statement.py +++ b/account_ebics/models/account_bank_statement.py @@ -5,7 +5,6 @@ from odoo import fields, models class AccountBankStatement(models.Model): - _inherit = 'account.bank.statement' + _inherit = "account.bank.statement" - ebics_file_id = fields.Many2one( - comodel_name='ebics.file', string='EBICS Data File') + ebics_file_id = fields.Many2one(comodel_name="ebics.file", string="EBICS Data File") diff --git a/account_ebics/models/ebics_config.py b/account_ebics/models/ebics_config.py index 8c616c7..1dcb4fb 100644 --- a/account_ebics/models/ebics_config.py +++ b/account_ebics/models/ebics_config.py @@ -1,9 +1,9 @@ -# Copyright 2009-2020 Noviat. +# Copyright 2009-2022 Noviat. # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). import logging -import re import os +import re from odoo import _, api, fields, models from odoo.exceptions import UserError @@ -16,118 +16,151 @@ class EbicsConfig(models.Model): EBICS configuration is stored in a separate object in order to allow extra security policies on this object. """ - _name = 'ebics.config' - _description = 'EBICS Configuration' - _order = 'name' + + _name = "ebics.config" + _description = "EBICS Configuration" + _order = "name" name = fields.Char( - string='Name', - readonly=True, states={'draft': [('readonly', False)]}, - required=True) + string="Name", + readonly=True, + states={"draft": [("readonly", False)]}, + required=True, + ) journal_ids = fields.Many2many( - comodel_name='account.journal', - readonly=True, states={'draft': [('readonly', False)]}, - string='Bank Accounts', + comodel_name="account.journal", + readonly=True, + states={"draft": [("readonly", False)]}, + string="Bank Accounts", domain="[('type', '=', 'bank')]", - required=True) + required=True, + ) ebics_host = fields.Char( - string='EBICS HostID', required=True, - readonly=True, states={'draft': [('readonly', False)]}, + string="EBICS HostID", + required=True, + readonly=True, + states={"draft": [("readonly", False)]}, help="Contact your bank to get the EBICS HostID." - "\nIn France the BIC is usually allocated to the HostID " - "whereas in Germany it tends to be an institute specific string " - "of 8 characters.") + "\nIn France the BIC is usually allocated to the HostID " + "whereas in Germany it tends to be an institute specific string " + "of 8 characters.", + ) ebics_url = fields.Char( - string='EBICS URL', required=True, - readonly=True, states={'draft': [('readonly', False)]}, - help="Contact your bank to get the EBICS URL.") + string="EBICS URL", + required=True, + readonly=True, + states={"draft": [("readonly", False)]}, + help="Contact your bank to get the EBICS URL.", + ) ebics_version = fields.Selection( - selection=[('H003', 'H003 (2.4)'), - ('H004', 'H004 (2.5)')], - string='EBICS protocol version', - readonly=True, states={'draft': [('readonly', False)]}, - required=True, default='H004') + selection=[("H003", "H003 (2.4)"), ("H004", "H004 (2.5)")], + string="EBICS protocol version", + readonly=True, + states={"draft": [("readonly", False)]}, + required=True, + default="H004", + ) ebics_partner = fields.Char( - string='EBICS PartnerID', required=True, - readonly=True, states={'draft': [('readonly', False)]}, + string="EBICS PartnerID", + required=True, + readonly=True, + states={"draft": [("readonly", False)]}, help="Organizational unit (company or individual) " - "that concludes a contract with the bank. " - "\nIn this contract it will be agreed which order types " - "(file formats) are used, which accounts are concerned, " - "which of the customer's users (subscribers) " - "communicate with the EBICS bank server and the authorisations " - "that these users will possess. " - "\nIt is identified by the PartnerID.") + "that concludes a contract with the bank. " + "\nIn this contract it will be agreed which order types " + "(file formats) are used, which accounts are concerned, " + "which of the customer's users (subscribers) " + "communicate with the EBICS bank server and the authorisations " + "that these users will possess. " + "\nIt is identified by the PartnerID.", + ) ebics_userid_ids = fields.One2many( - comodel_name='ebics.userid', - inverse_name='ebics_config_id', - readonly=True, states={'draft': [('readonly', False)]}, + comodel_name="ebics.userid", + inverse_name="ebics_config_id", + readonly=True, + states={"draft": [("readonly", False)]}, help="Human users or a technical system that is/are " - "assigned to a customer. " - "\nOn the EBICS bank server it is identified " - "by the combination of UserID and PartnerID. " - "The technical subscriber serves only for the data exchange " - "between customer and financial institution. " - "The human user also can authorise orders.") + "assigned to a customer. " + "\nOn the EBICS bank server it is identified " + "by the combination of UserID and PartnerID. " + "The technical subscriber serves only for the data exchange " + "between customer and financial institution. " + "The human user also can authorise orders.", + ) ebics_files = fields.Char( - string='EBICS Files Root', required=True, - readonly=True, states={'draft': [('readonly', False)]}, + string="EBICS Files Root", + required=True, + readonly=True, + states={"draft": [("readonly", False)]}, default=lambda self: self._default_ebics_files(), - help="Root Directory for EBICS File Transfer Folders.") + help="Root Directory for EBICS File Transfer Folders.", + ) # We store the EBICS keys in a separate directory in the file system. # This directory requires special protection to reduce fraude. ebics_keys = fields.Char( - string='EBICS Keys Root', required=True, - readonly=True, states={'draft': [('readonly', False)]}, + string="EBICS Keys Root", + required=True, + readonly=True, + states={"draft": [("readonly", False)]}, default=lambda self: self._default_ebics_keys(), - help="Root Directory for storing the EBICS Keys.") + help="Root Directory for storing the EBICS Keys.", + ) ebics_key_version = fields.Selection( - selection=[('A005', 'A005 (RSASSA-PKCS1-v1_5)'), - ('A006', 'A006 (RSASSA-PSS)')], - string='EBICS key version', - default='A006', - readonly=True, states={'draft': [('readonly', False)]}, - help="The key version of the electronic signature.") + selection=[("A005", "A005 (RSASSA-PKCS1-v1_5)"), ("A006", "A006 (RSASSA-PSS)")], + string="EBICS key version", + default="A006", + readonly=True, + states={"draft": [("readonly", False)]}, + help="The key version of the electronic signature.", + ) ebics_key_bitlength = fields.Integer( - string='EBICS key bitlength', + string="EBICS key bitlength", default=2048, - readonly=True, states={'draft': [('readonly', False)]}, + readonly=True, + states={"draft": [("readonly", False)]}, help="The bit length of the generated keys. " - "\nThe value must be between 1536 and 4096.") + "\nThe value must be between 1536 and 4096.", + ) ebics_file_format_ids = fields.Many2many( - comodel_name='ebics.file.format', - column1='config_id', column2='format_id', - string='EBICS File Formats', - readonly=True, states={'draft': [('readonly', False)]}, + comodel_name="ebics.file.format", + column1="config_id", + column2="format_id", + string="EBICS File Formats", + readonly=True, + states={"draft": [("readonly", False)]}, ) state = fields.Selection( - [('draft', 'Draft'), - ('confirm', 'Confirmed')], - string='State', - default='draft', - required=True, readonly=True) - order_number = fields.Char( - size=4, readonly=True, states={'draft': [('readonly', False)]}, - help="Specify the number for the next order." - "\nThis number should match the following pattern : " - "[A-Z]{1}[A-Z0-9]{3}") - active = fields.Boolean( - string='Active', default=True) - company_ids = fields.Many2many( - comodel_name='res.company', - string='Companies', + [("draft", "Draft"), ("confirm", "Confirmed")], + string="State", + default="draft", required=True, - help="Companies sharing this EBICS contract.") + readonly=True, + ) + order_number = fields.Char( + size=4, + readonly=True, + states={"draft": [("readonly", False)]}, + help="Specify the number for the next order." + "\nThis number should match the following pattern : " + "[A-Z]{1}[A-Z0-9]{3}", + ) + active = fields.Boolean(string="Active", default=True) + company_ids = fields.Many2many( + comodel_name="res.company", + string="Companies", + required=True, + help="Companies sharing this EBICS contract.", + ) @api.model def _default_ebics_files(self): - return '/'.join(['/home/odoo/ebics_files', self._cr.dbname]) + return "/".join(["/home/odoo/ebics_files", self._cr.dbname]) @api.model def _default_ebics_keys(self): - return '/'.join(['/etc/odoo/ebics_keys', self._cr.dbname]) + return "/".join(["/etc/odoo/ebics_keys", self._cr.dbname]) - @api.constrains('order_number') + @api.constrains("order_number") def _check_order_number(self): for cfg in self: nbr = cfg.order_number @@ -140,26 +173,28 @@ class EbicsConfig(models.Model): if not pattern.match(nbr): ok = False if not ok: - raise UserError(_( - "Order Number should comply with the following pattern:" - "\n[A-Z]{1}[A-Z0-9]{3}")) + raise UserError( + _( + "Order Number should comply with the following pattern:" + "\n[A-Z]{1}[A-Z0-9]{3}" + ) + ) - @api.onchange('journal_ids') + @api.onchange("journal_ids") def _onchange_journal_ids(self): - self.company_ids = self.journal_ids.mapped('company_id') + self.company_ids = self.journal_ids.mapped("company_id") def unlink(self): for ebics_config in self: - if ebics_config.state == 'active': - raise UserError(_( - "You cannot remove active EBICS configurations.")) + if ebics_config.state == "active": + raise UserError(_("You cannot remove active EBICS configurations.")) return super(EbicsConfig, self).unlink() def set_to_draft(self): - return self.write({'state': 'draft'}) + return self.write({"state": "draft"}) def set_to_confirm(self): - return self.write({'state': 'confirm'}) + return self.write({"state": "confirm"}) def _get_order_number(self): return self.order_number @@ -167,31 +202,37 @@ class EbicsConfig(models.Model): def _update_order_number(self, OrderID): o_list = list(OrderID) for i, c in enumerate(reversed(o_list), start=1): - if c == '9': - o_list[-i] = 'A' + if c == "9": + o_list[-i] = "A" break - if c == 'Z': + if c == "Z": continue else: o_list[-i] = chr(ord(c) + 1) break - next = ''.join(o_list) - if next == 'ZZZZ': - next = 'A000' - self.order_number = next + next_order_number = "".join(o_list) + if next_order_number == "ZZZZ": + next_order_number = "A000" + self.order_number = next_order_number def _check_ebics_keys(self): - dirname = self.ebics_keys or '' + dirname = self.ebics_keys or "" if not os.path.exists(dirname): - raise UserError(_( - "EBICS Keys Root Directory %s is not available." - "\nPlease contact your system administrator.") - % dirname) + raise UserError( + _( + "EBICS Keys Root Directory %s is not available." + "\nPlease contact your system administrator." + ) + % dirname + ) def _check_ebics_files(self): - dirname = self.ebics_files or '' + dirname = self.ebics_files or "" if not os.path.exists(dirname): - raise UserError(_( - "EBICS Files Root Directory %s is not available." - "\nPlease contact your system administrator.") - % dirname) + raise UserError( + _( + "EBICS Files Root Directory %s is not available." + "\nPlease contact your system administrator." + ) + % dirname + ) diff --git a/account_ebics/models/ebics_file.py b/account_ebics/models/ebics_file.py index ea3e4e8..43baece 100644 --- a/account_ebics/models/ebics_file.py +++ b/account_ebics/models/ebics_file.py @@ -1,4 +1,4 @@ -# Copyright 2009-2021 Noviat. +# Copyright 2009-2022 Noviat. # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). import base64 @@ -6,113 +6,119 @@ import logging from odoo import _, fields, models from odoo.exceptions import UserError +from odoo.tools.safe_eval import safe_eval _logger = logging.getLogger(__name__) class EbicsFile(models.Model): - _name = 'ebics.file' - _description = 'Object to store EBICS Data Files' - _order = 'date desc' + _name = "ebics.file" + _description = "Object to store EBICS Data Files" + _order = "date desc" _sql_constraints = [ - ('name_uniq', 'unique (name, format_id)', - 'This File has already been down- or uploaded !') + ( + "name_uniq", + "unique (name, format_id)", + "This File has already been down- or uploaded !", + ) ] - name = fields.Char(string='Filename') - data = fields.Binary(string='File', readonly=True) + name = fields.Char(string="Filename") + data = fields.Binary(string="File", readonly=True) format_id = fields.Many2one( - comodel_name='ebics.file.format', - string='EBICS File Formats', - readonly=True) - type = fields.Selection( - related='format_id.type', - readonly=True) + comodel_name="ebics.file.format", string="EBICS File Formats", readonly=True + ) + type = fields.Selection(related="format_id.type", readonly=True) date_from = fields.Date( - readonly=True, - help="'Date From' as entered in the download wizard.") + readonly=True, help="'Date From' as entered in the download wizard." + ) date_to = fields.Date( - readonly=True, - help="'Date To' as entered in the download wizard.") + readonly=True, help="'Date To' as entered in the download wizard." + ) date = fields.Datetime( - required=True, readonly=True, - help='File Upload/Download date') + required=True, readonly=True, help="File Upload/Download date" + ) bank_statement_ids = fields.One2many( - comodel_name='account.bank.statement', - inverse_name='ebics_file_id', - string='Generated Bank Statements', readonly=True) + comodel_name="account.bank.statement", + inverse_name="ebics_file_id", + string="Generated Bank Statements", + readonly=True, + ) state = fields.Selection( - [('draft', 'Draft'), - ('done', 'Done')], - string='State', - default='draft', - required=True, readonly=True) + [("draft", "Draft"), ("done", "Done")], + string="State", + default="draft", + required=True, + readonly=True, + ) user_id = fields.Many2one( - comodel_name='res.users', string='User', + comodel_name="res.users", + string="User", default=lambda self: self.env.user, - readonly=True) + readonly=True, + ) ebics_userid_id = fields.Many2one( - comodel_name='ebics.userid', - string='EBICS UserID', - ondelete='restrict', - readonly=True) - note = fields.Text(string='Notes') - note_process = fields.Text(string='Notes') + comodel_name="ebics.userid", + string="EBICS UserID", + ondelete="restrict", + readonly=True, + ) + note = fields.Text(string="Notes") + note_process = fields.Text(string="Notes") company_ids = fields.Many2many( - comodel_name='res.company', - string='Companies', - help="Companies sharing this EBICS file.") + comodel_name="res.company", + string="Companies", + help="Companies sharing this EBICS file.", + ) def unlink(self): ff_methods = self._file_format_methods() for ebics_file in self: - if ebics_file.state == 'done': - raise UserError(_( - "You can only remove EBICS files in state 'Draft'.")) + if ebics_file.state == "done": + raise UserError(_("You can only remove EBICS files in state 'Draft'.")) # execute format specific actions ff = ebics_file.format_id.download_process_method if ff in ff_methods: - if ff_methods[ff].get('unlink'): - ff_methods[ff]['unlink'](ebics_file) + if ff_methods[ff].get("unlink"): + ff_methods[ff]["unlink"](ebics_file) # remove bank statements ebics_file.bank_statement_ids.unlink() return super(EbicsFile, self).unlink() def set_to_draft(self): - return self.write({'state': 'draft'}) + return self.write({"state": "draft"}) def set_to_done(self): - return self.write({'state': 'done'}) + return self.write({"state": "done"}) def process(self): self.ensure_one() - ctx = dict( - self.env.context, - allowed_company_ids=self.env.user.company_ids.ids) + ctx = dict(self.env.context, allowed_company_ids=self.env.user.company_ids.ids) self = self.with_context(ctx) - self.note_process = '' + self.note_process = "" ff_methods = self._file_format_methods() ff = self.format_id.download_process_method if ff in ff_methods: - if ff_methods[ff].get('process'): - res = ff_methods[ff]['process'](self) - self.state = 'done' + if ff_methods[ff].get("process"): + res = ff_methods[ff]["process"](self) + self.state = "done" return res else: return self._process_undefined_format() def action_open_bank_statements(self): self.ensure_one() - action = self.env['ir.actions.act_window']._for_xml_id( - 'account.action_bank_statement_tree') - domain = eval(action.get('domain') or '[]') - domain += [('id', 'in', self._context.get('statement_ids'))] - action.update({'domain': domain}) + action = self.env["ir.actions.act_window"]._for_xml_id( + "account.action_bank_statement_tree" + ) + domain = safe_eval(action.get("domain") or "[]") + domain += [("id", "in", self._context.get("statement_ids"))] + action.update({"domain": domain}) return action def button_close(self): self.ensure_one() - return {'type': 'ir.actions.act_window_close'} + return {"type": "ir.actions.act_window_close"} def _file_format_methods(self): """ @@ -120,35 +126,45 @@ class EbicsFile(models.Model): for extra file formats. """ res = { - 'cfonb120': - {'process': self._process_cfonb120, - 'unlink': self._unlink_cfonb120}, - 'camt.052': - {'process': self._process_camt052, - 'unlink': self._unlink_camt052}, - 'camt.053': - {'process': self._process_camt053, - 'unlink': self._unlink_camt053}, - 'camt.054': - {'process': self._process_camt054, - 'unlink': self._unlink_camt054}, - 'pain.002': - {'process': self._process_pain002, - 'unlink': self._unlink_pain002}, + "cfonb120": { + "process": self._process_cfonb120, + "unlink": self._unlink_cfonb120, + }, + "camt.052": { + "process": self._process_camt052, + "unlink": self._unlink_camt052, + }, + "camt.053": { + "process": self._process_camt053, + "unlink": self._unlink_camt053, + }, + "camt.054": { + "process": self._process_camt054, + "unlink": self._unlink_camt054, + }, + "pain.002": { + "process": self._process_pain002, + "unlink": self._unlink_pain002, + }, } return res def _check_import_module(self, module, raise_if_not_found=True): - mod = self.env['ir.module.module'].sudo().search( - [('name', '=like', module), - ('state', '=', 'installed')]) + mod = ( + self.env["ir.module.module"] + .sudo() + .search([("name", "=like", module), ("state", "=", "installed")]) + ) if not mod: if raise_if_not_found: - raise UserError(_( - "The module to process the '%s' format is not installed " - "on your system. " - "\nPlease install module '%s'") - % (self.format_id.name, module)) + raise UserError( + _( + "The module to process the '%s' format is not installed " + "on your system. " + "\nPlease install module '%s'" + ) + % (self.format_id.name, module) + ) return False return True @@ -156,19 +172,19 @@ class EbicsFile(models.Model): notifications = [] st_line_ids = [] statement_ids = [] - if res.get('context'): - notifications = res['context'].get('notifications', []) - st_line_ids = res['context'].get('statement_line_ids', []) + if res.get("context"): + notifications = res["context"].get("notifications", []) + st_line_ids = res["context"].get("statement_line_ids", []) if notifications: for notif in notifications: parts = [] - for k in ['type', 'message', 'details']: + for k in ["type", "message", "details"]: if notif.get(k): - msg = '%s: %s' % (k, notif[k]) + msg = "{}: {}".format(k, notif[k]) parts.append(msg) - self.note_process += '\n'.join(parts) - self.note_process += '\n' - self.note_process += '\n' + self.note_process += "\n".join(parts) + self.note_process += "\n" + self.note_process += "\n" if st_line_ids: self.flush() self.env.cr.execute( @@ -185,36 +201,37 @@ class EbicsFile(models.Model): WHERE absl.id IN %s ORDER BY date, company_id """, - (tuple(st_line_ids),) + (tuple(st_line_ids),), ) sts_data = self.env.cr.dictfetchall() else: sts_data = [] st_cnt = len(sts_data) if st_cnt: - self.note_process += _( - "%s bank statements have been imported: " - ) % st_cnt - self.note_process += '\n' + self.note_process += _("%s bank statements have been imported: ") % st_cnt + self.note_process += "\n" for st_data in sts_data: self.note_process += ("\n%s, %s (%s)") % ( - st_data['date'], st_data['name'], st_data['company_name']) - statement_ids = [x['statement_id'] for x in sts_data] + st_data["date"], + st_data["name"], + st_data["company_name"], + ) + statement_ids = [x["statement_id"] for x in sts_data] if statement_ids: self.sudo().bank_statement_ids = [(6, 0, statement_ids)] ctx = dict(self.env.context, statement_ids=statement_ids) - module = __name__.split('addons.')[1].split('.')[0] - result_view = self.env.ref('%s.ebics_file_view_form_result' % module) + module = __name__.split("addons.")[1].split(".")[0] + result_view = self.env.ref("%s.ebics_file_view_form_result" % module) return { - 'name': _('Import EBICS File'), - 'res_id': self.id, - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': self._name, - 'view_id': result_view.id, - 'target': 'new', - 'context': ctx, - 'type': 'ir.actions.act_window', + "name": _("Import EBICS File"), + "res_id": self.id, + "view_type": "form", + "view_mode": "form", + "res_model": self._name, + "view_id": result_view.id, + "target": "new", + "context": ctx, + "type": "ir.actions.act_window", } @staticmethod @@ -223,60 +240,63 @@ class EbicsFile(models.Model): We do not support the standard _journal_creation_wizard since a single cfonb120 file may contain statements from different legal entities. """ - import_module = 'account_statement_import_fr_cfonb' + import_module = "account_statement_import_fr_cfonb" self._check_import_module(import_module) - wiz_model = 'account.statement.import' + wiz_model = "account.statement.import" data_file = base64.b64decode(self.data) - lines = data_file.split(b'\n') + lines = data_file.split(b"\n") wiz_vals_list = [] - st_lines = b'' + st_lines = b"" transactions = False for line in lines: rec_type = line[0:2] acc_number = line[21:32] - st_lines += line + b'\n' - if rec_type == b'04': + st_lines += line + b"\n" + if rec_type == b"04": transactions = True - if rec_type == b'07': + if rec_type == b"07": if transactions: - fn = '_'.join([acc_number.decode(), self.name]) - wiz_vals_list.append({ - 'statement_filename': fn, - 'statement_file': base64.b64encode(st_lines) - }) - st_lines = b'' + fn = "_".join([acc_number.decode(), self.name]) + wiz_vals_list.append( + { + "statement_filename": fn, + "statement_file": base64.b64encode(st_lines), + } + ) + st_lines = b"" transactions = False result = { - 'type': 'ir.actions.client', - 'tag': 'bank_statement_reconciliation_view', - 'context': {'statement_line_ids': [], - 'company_ids': self.env.user.company_ids.ids, - 'notifications': []}, + "type": "ir.actions.client", + "tag": "bank_statement_reconciliation_view", + "context": { + "statement_line_ids": [], + "company_ids": self.env.user.company_ids.ids, + "notifications": [], + }, } - wiz_ctx = dict(self.env.context, active_model='ebics.file') + wiz_ctx = dict(self.env.context, active_model="ebics.file") for i, wiz_vals in enumerate(wiz_vals_list, start=1): wiz = self.env[wiz_model].with_context(wiz_ctx).create(wiz_vals) res = wiz.import_file_button() - ctx = res.get('context') - if (res.get('res_model') - == 'account.bank.statement.import.journal.creation'): - message = _( - "Error detected while importing statement number %s.\n" - ) % i + ctx = res.get("context") + if res.get("res_model") == "account.bank.statement.import.journal.creation": + message = _("Error detected while importing statement number %s.\n") % i message += _("No financial journal found.") - details = _( - 'Bank account number: %s' - ) % ctx.get('default_bank_acc_number') - result['context']['notifications'].extend([{ - 'type': 'warning', - 'message': message, - 'details': details, - }]) + details = _("Bank account number: %s") % ctx.get( + "default_bank_acc_number" + ) + result["context"]["notifications"].extend( + [ + { + "type": "warning", + "message": message, + "details": details, + } + ] + ) continue - result['context']['statement_line_ids'].extend( - ctx['statement_line_ids']) - result['context']['notifications'].extend( - ctx['notifications']) + result["context"]["statement_line_ids"].extend(ctx["statement_line_ids"]) + result["context"]["notifications"].extend(ctx["notifications"]) return self._process_result_action(result) @staticmethod @@ -285,11 +305,10 @@ class EbicsFile(models.Model): Placeholder for cfonb120 specific actions before removing the EBICS data file and its related bank statements. """ - pass @staticmethod def _process_camt052(self): - import_module = 'account_statement_import_camt' + import_module = "account_statement_import_camt" self._check_import_module(import_module) return self._process_camt053(self) @@ -299,11 +318,10 @@ class EbicsFile(models.Model): Placeholder for camt052 specific actions before removing the EBICS data file and its related bank statements. """ - pass @staticmethod def _process_camt054(self): - import_module = 'account_statement_import_camt' + import_module = "account_statement_import_camt" self._check_import_module(import_module) return self._process_camt053(self) @@ -313,84 +331,89 @@ class EbicsFile(models.Model): Placeholder for camt054 specific actions before removing the EBICS data file and its related bank statements. """ - pass @staticmethod def _process_camt053(self): modules = [ - ('oca', 'account_statement_import_camt'), - ('oe', 'account_bank_statement_import_camt'), + ("oca", "account_statement_import_camt"), + ("oe", "account_bank_statement_import_camt"), ] found = False - for src, mod in modules: + for _src, mod in modules: if self._check_import_module(mod, raise_if_not_found=False): found = True break if not found: - raise UserError(_( - "The module to process the '%s' format is not installed " - "on your system. " - "\nPlease install one of the following modules: \n%s." - ) % (self.format_id.name, ', '.join([x[1] for x in modules])) + raise UserError( + _( + "The module to process the '%s' format is not installed " + "on your system. " + "\nPlease install one of the following modules: \n%s." + ) + % (self.format_id.name, ", ".join([x[1] for x in modules])) ) - if src == 'oca': + if _src == "oca": self._process_camt053_oca() else: self._process_camt053_oe() def _process_camt053_oca(self): - wiz_model = 'account.statement.import' + wiz_model = "account.statement.import" wiz_vals = { - 'statement_filename': self.name, - 'statement_file': self.data, + "statement_filename": self.name, + "statement_file": self.data, } result = { - 'type': 'ir.actions.client', - 'tag': 'bank_statement_reconciliation_view', - 'context': {'statement_line_ids': [], - 'company_ids': self.env.user.company_ids.ids, - 'notifications': []}, + "type": "ir.actions.client", + "tag": "bank_statement_reconciliation_view", + "context": { + "statement_line_ids": [], + "company_ids": self.env.user.company_ids.ids, + "notifications": [], + }, } - wiz_ctx = dict(self.env.context, active_model='ebics.file') + wiz_ctx = dict(self.env.context, active_model="ebics.file") wiz = self.env[wiz_model].with_context(wiz_ctx).create(wiz_vals) res = wiz.import_file_button() - ctx = res.get('context') - if (res.get('res_model') - == 'account.bank.statement.import.journal.creation'): - message = _( - "Error detected while importing statement %s.\n" - ) % self.name + ctx = res.get("context") + if res.get("res_model") == "account.bank.statement.import.journal.creation": + message = _("Error detected while importing statement %s.\n") % self.name message += _("No financial journal found.") - details = _( - 'Bank account number: %s' - ) % ctx.get('default_bank_acc_number') - result['context']['notifications'].extend([{ - 'type': 'warning', - 'message': message, - 'details': details, - }]) - result['context']['statement_line_ids'].extend( - ctx['statement_line_ids']) - result['context']['notifications'].extend( - ctx['notifications']) + details = _("Bank account number: %s") % ctx.get("default_bank_acc_number") + result["context"]["notifications"].extend( + [ + { + "type": "warning", + "message": message, + "details": details, + } + ] + ) + result["context"]["statement_line_ids"].extend(ctx["statement_line_ids"]) + result["context"]["notifications"].extend(ctx["notifications"]) return self._process_result_action(result) def _process_camt053_oe(self): - wiz_model = 'account.bank.statement.import' + wiz_model = "account.bank.statement.import" wiz_vals = { - 'attachment_ids': [(0, 0, {'name': self.name, - 'datas': self.data, - 'store_fname': self.name})]} - ctx = dict(self.env.context, active_model='ebics.file') + "attachment_ids": [ + ( + 0, + 0, + {"name": self.name, "datas": self.data, "store_fname": self.name}, + ) + ] + } + ctx = dict(self.env.context, active_model="ebics.file") wiz = self.env[wiz_model].with_context(ctx).create(wiz_vals) res = wiz.import_file() - if res.get('res_model') \ - == 'account.bank.statement.import.journal.creation': - if res.get('context'): - bank_account = res['context'].get('default_bank_acc_number') - raise UserError(_( - "No financial journal found for Company Bank Account %s" - ) % bank_account) + if res.get("res_model") == "account.bank.statement.import.journal.creation": + if res.get("context"): + bank_account = res["context"].get("default_bank_acc_number") + raise UserError( + _("No financial journal found for Company Bank Account %s") + % bank_account + ) return self._process_result_action(res) @staticmethod @@ -399,7 +422,6 @@ class EbicsFile(models.Model): Placeholder for camt053 specific actions before removing the EBICS data file and its related bank statements. """ - pass @staticmethod def _process_pain002(self): @@ -408,7 +430,6 @@ class EbicsFile(models.Model): TODO: add import logic based upon OCA 'account_payment_return_import' """ - pass @staticmethod def _unlink_pain002(self): @@ -419,8 +440,11 @@ class EbicsFile(models.Model): raise NotImplementedError def _process_undefined_format(self): - raise UserError(_( - "The current version of the 'account_ebics' module " - "has no support to automatically process EBICS files " - "with format %s." - ) % self.format_id.name) + raise UserError( + _( + "The current version of the 'account_ebics' module " + "has no support to automatically process EBICS files " + "with format %s." + ) + % self.format_id.name + ) diff --git a/account_ebics/models/ebics_file_format.py b/account_ebics/models/ebics_file_format.py index 5864644..8dee0a9 100644 --- a/account_ebics/models/ebics_file_format.py +++ b/account_ebics/models/ebics_file_format.py @@ -5,58 +5,60 @@ from odoo import api, fields, models class EbicsFileFormat(models.Model): - _name = 'ebics.file.format' - _description = 'EBICS File Formats' - _order = 'type,name,order_type' + _name = "ebics.file.format" + _description = "EBICS File Formats" + _order = "type,name,order_type" name = fields.Char( - string='Request Type', + string="Request Type", required=True, help="E.g. camt.xxx.cfonb120.stm, pain.001.001.03.sct.\n" - "Specify camt.052, camt.053, camt.054 for camt " - "Order Types such as C53, Z53, C54, Z54.\n" - "This name has to match the 'Request Type' in your " - "EBICS contract for Order Type 'FDL' or 'FUL'.\n") + "Specify camt.052, camt.053, camt.054 for camt " + "Order Types such as C53, Z53, C54, Z54.\n" + "This name has to match the 'Request Type' in your " + "EBICS contract for Order Type 'FDL' or 'FUL'.\n", + ) type = fields.Selection( - selection=[('down', 'Download'), - ('up', 'Upload')], - required=True) + selection=[("down", "Download"), ("up", "Upload")], required=True + ) order_type = fields.Char( - string='Order Type', + string="Order Type", required=True, help="E.g. C53 (check your EBICS contract).\n" - "For most banks in France you should use the " - "format neutral Order Types 'FUL' for upload " - "and 'FDL' for download.") + "For most banks in France you should use the " + "format neutral Order Types 'FUL' for upload " + "and 'FDL' for download.", + ) download_process_method = fields.Selection( - selection='_selection_download_process_method', + selection="_selection_download_process_method", help="Enable processing within Odoo of the downloaded file " - "via the 'Process' button." - "E.g. specify camt.053 to import a camt.053 file and create " - "a bank statement.") + "via the 'Process' button." + "E.g. specify camt.053 to import a camt.053 file and create " + "a bank statement.", + ) # TODO: # move signature_class parameter so that it can be set per EBICS config signature_class = fields.Selection( - selection=[('E', 'Single signature'), - ('T', 'Transport signature')], - string='Signature Class', + selection=[("E", "Single signature"), ("T", "Transport signature")], + string="Signature Class", help="Please doublecheck the security of your Odoo " - "ERP system when using class 'E' to prevent unauthorised " - "users to make supplier payments." - "\nLeave this field empty to use the default " - "defined for your EBICS UserID.") + "ERP system when using class 'E' to prevent unauthorised " + "users to make supplier payments." + "\nLeave this field empty to use the default " + "defined for your EBICS UserID.", + ) description = fields.Char() suffix = fields.Char( required=True, - help="Specify the filename suffix for this File Format." - "\nE.g. c53.xml") + help="Specify the filename suffix for this File Format." "\nE.g. c53.xml", + ) @api.model def _selection_download_process_method(self): - methods = self.env['ebics.file']._file_format_methods().keys() + methods = self.env["ebics.file"]._file_format_methods().keys() return [(x, x) for x in methods] - @api.onchange('type') + @api.onchange("type") def _onchange_type(self): - if self.type == 'up': + if self.type == "up": self.download_process_method = False diff --git a/account_ebics/models/ebics_userid.py b/account_ebics/models/ebics_userid.py index d3e8818..f7fc99f 100644 --- a/account_ebics/models/ebics_userid.py +++ b/account_ebics/models/ebics_userid.py @@ -13,224 +13,240 @@ from odoo.exceptions import UserError _logger = logging.getLogger(__name__) - -""" -logging.basicConfig( - level=logging.DEBUG, - format='[%(asctime)s] %(levelname)s - %(name)s: %(message)s') -""" - +# logging.basicConfig( +# level=logging.DEBUG, +# format='[%(asctime)s] %(levelname)s - %(name)s: %(message)s') try: import fintech - from fintech.ebics import EbicsKeyRing, EbicsBank, EbicsUser,\ - EbicsClient, EbicsFunctionalError, EbicsTechnicalError - fintech.cryptolib = 'cryptography' + from fintech.ebics import ( + EbicsBank, + EbicsClient, + EbicsFunctionalError, + EbicsKeyRing, + EbicsTechnicalError, + EbicsUser, + ) + + fintech.cryptolib = "cryptography" except ImportError: - _logger.warning('Failed to import fintech') + _logger.warning("Failed to import fintech") class EbicsBank(EbicsBank): - def _next_order_id(self, partnerid): """ EBICS protocol version H003 requires generation of the OrderID. The OrderID must be a string between 'A000' and 'ZZZZ' and unique for each partner id. """ - return hasattr(self, '_order_number') and self._order_number or 'A000' + return hasattr(self, "_order_number") and self._order_number or "A000" class EbicsUserID(models.Model): - _name = 'ebics.userid' - _description = 'EBICS UserID' - _order = 'name' + _name = "ebics.userid" + _description = "EBICS UserID" + _order = "name" name = fields.Char( - string='EBICS UserID', required=True, - readonly=True, states={'draft': [('readonly', False)]}, + string="EBICS UserID", + required=True, + readonly=True, + states={"draft": [("readonly", False)]}, help="Human users or a technical system that is/are " - "assigned to a customer. " - "\nOn the EBICS bank server it is identified " - "by the combination of UserID and PartnerID. " - "The technical subscriber serves only for the data exchange " - "between customer and financial institution. " - "The human user also can authorise orders.") + "assigned to a customer. " + "\nOn the EBICS bank server it is identified " + "by the combination of UserID and PartnerID. " + "The technical subscriber serves only for the data exchange " + "between customer and financial institution. " + "The human user also can authorise orders.", + ) ebics_config_id = fields.Many2one( - comodel_name='ebics.config', - string='EBICS Configuration', - ondelete='cascade') + comodel_name="ebics.config", string="EBICS Configuration", ondelete="cascade" + ) user_ids = fields.Many2many( - comodel_name='res.users', - string='Users', + comodel_name="res.users", + string="Users", required=True, help="Users who are allowed to use this EBICS UserID for " - " bank transactions.") + " bank transactions.", + ) # Currently only a singe signature class per user is supported # Classes A and B are not yet supported. signature_class = fields.Selection( - selection=[('E', 'Single signature'), - ('T', 'Transport signature')], - string='Signature Class', - required=True, default='T', - readonly=True, states={'draft': [('readonly', False)]}, + selection=[("E", "Single signature"), ("T", "Transport signature")], + string="Signature Class", + required=True, + default="T", + readonly=True, + states={"draft": [("readonly", False)]}, help="Default signature class." - "This default can be overriden for specific " - "EBICS transactions (cf. File Formats).") - ebics_keys_fn = fields.Char( - compute='_compute_ebics_keys_fn') - ebics_keys_found = fields.Boolean( - compute='_compute_ebics_keys_found') - ebics_passphrase = fields.Char( - string='EBICS Passphrase') + "This default can be overriden for specific " + "EBICS transactions (cf. File Formats).", + ) + ebics_keys_fn = fields.Char(compute="_compute_ebics_keys_fn") + ebics_keys_found = fields.Boolean(compute="_compute_ebics_keys_found") + ebics_passphrase = fields.Char(string="EBICS Passphrase") ebics_ini_letter = fields.Binary( - string='EBICS INI Letter', readonly=True, - help="INI-letter PDF document to be sent to your bank.") - ebics_ini_letter_fn = fields.Char( - string='INI-letter Filename', readonly=True) + string="EBICS INI Letter", + readonly=True, + help="INI-letter PDF document to be sent to your bank.", + ) + ebics_ini_letter_fn = fields.Char(string="INI-letter Filename", readonly=True) ebics_public_bank_keys = fields.Binary( - string='EBICS Public Bank Keys', readonly=True, - help="EBICS Public Bank Keys to be checked for consistency.") + string="EBICS Public Bank Keys", + readonly=True, + help="EBICS Public Bank Keys to be checked for consistency.", + ) ebics_public_bank_keys_fn = fields.Char( - string='EBICS Public Bank Keys Filename', readonly=True) + string="EBICS Public Bank Keys Filename", readonly=True + ) swift_3skey = fields.Boolean( - string='Enable 3SKey support', + string="Enable 3SKey support", help="Transactions for this user will be signed " - "by means of the SWIFT 3SKey token.") - swift_3skey_certificate = fields.Binary( - string='3SKey Certficate') - swift_3skey_certificate_fn = fields.Char( - string='EBICS Public Bank Keys Filename') + "by means of the SWIFT 3SKey token.", + ) + swift_3skey_certificate = fields.Binary(string="3SKey Certficate") + swift_3skey_certificate_fn = fields.Char(string="EBICS Public Bank Keys Filename") # X.509 Distinguished Name attributes used to # create self-signed X.509 certificates ebics_key_x509 = fields.Boolean( - string='X509 support', - help="Set this flag in order to work with " - "self-signed X.509 certificates") + string="X509 support", + help="Set this flag in order to work with " "self-signed X.509 certificates", + ) ebics_key_x509_dn_cn = fields.Char( - string='Common Name [CN]', - readonly=True, states={'draft': [('readonly', False)]}, + string="Common Name [CN]", + readonly=True, + states={"draft": [("readonly", False)]}, ) ebics_key_x509_dn_o = fields.Char( - string='Organization Name [O]', - readonly=True, states={'draft': [('readonly', False)]}, + string="Organization Name [O]", + readonly=True, + states={"draft": [("readonly", False)]}, ) ebics_key_x509_dn_ou = fields.Char( - string='Organizational Unit Name [OU]', - readonly=True, states={'draft': [('readonly', False)]}, + string="Organizational Unit Name [OU]", + readonly=True, + states={"draft": [("readonly", False)]}, ) ebics_key_x509_dn_c = fields.Char( - string='Country Name [C]', - readonly=True, states={'draft': [('readonly', False)]}, + string="Country Name [C]", + readonly=True, + states={"draft": [("readonly", False)]}, ) ebics_key_x509_dn_st = fields.Char( - string='State Or Province Name [ST]', - readonly=True, states={'draft': [('readonly', False)]}, + string="State Or Province Name [ST]", + readonly=True, + states={"draft": [("readonly", False)]}, ) ebics_key_x509_dn_l = fields.Char( - string='Locality Name [L]', - readonly=True, states={'draft': [('readonly', False)]}, + string="Locality Name [L]", + readonly=True, + states={"draft": [("readonly", False)]}, ) ebics_key_x509_dn_e = fields.Char( - string='Email Address', - readonly=True, states={'draft': [('readonly', False)]}, + string="Email Address", + readonly=True, + states={"draft": [("readonly", False)]}, ) state = fields.Selection( - [('draft', 'Draft'), - ('init', 'Initialisation'), - ('get_bank_keys', 'Get Keys from Bank'), - ('to_verify', 'Verification'), - ('active_keys', 'Active Keys')], - string='State', - default='draft', - required=True, readonly=True) - active = fields.Boolean( - string='Active', default=True) - company_ids = fields.Many2many( - comodel_name='res.company', - string='Companies', + [ + ("draft", "Draft"), + ("init", "Initialisation"), + ("get_bank_keys", "Get Keys from Bank"), + ("to_verify", "Verification"), + ("active_keys", "Active Keys"), + ], + string="State", + default="draft", required=True, - help="Companies sharing this EBICS contract.") + readonly=True, + ) + active = fields.Boolean(string="Active", default=True) + company_ids = fields.Many2many( + comodel_name="res.company", + string="Companies", + required=True, + help="Companies sharing this EBICS contract.", + ) - @api.depends('name') + @api.depends("name") def _compute_ebics_keys_fn(self): for rec in self: keys_dir = rec.ebics_config_id.ebics_keys rec.ebics_keys_fn = ( - rec.name - and keys_dir - and (keys_dir + '/' + rec.name + '_keys')) - - @api.depends('ebics_keys_fn') - def _compute_ebics_keys_found(self): - for rec in self: - rec.ebics_keys_found = ( - rec.ebics_keys_fn - and os.path.isfile(rec.ebics_keys_fn) + rec.name and keys_dir and (keys_dir + "/" + rec.name + "_keys") ) - @api.constrains('ebics_passphrase') + @api.depends("ebics_keys_fn") + def _compute_ebics_keys_found(self): + for rec in self: + rec.ebics_keys_found = rec.ebics_keys_fn and os.path.isfile( + rec.ebics_keys_fn + ) + + @api.constrains("ebics_passphrase") def _check_ebics_passphrase(self): for rec in self: if not rec.ebics_passphrase or len(rec.ebics_passphrase) < 8: - raise UserError(_( - "The passphrase must be at least 8 characters long")) + raise UserError(_("The passphrase must be at least 8 characters long")) - @api.onchange('signature_class') + @api.onchange("signature_class") def _onchange_signature_class(self): - if self.signature_class == 'T': + if self.signature_class == "T": self.swift_3skey = False - @api.onchange('swift_3skey') + @api.onchange("swift_3skey") def _onchange_swift_3skey(self): if self.swift_3skey: self.ebics_key_x509 = True def set_to_draft(self): - return self.write({'state': 'draft'}) + return self.write({"state": "draft"}) def set_to_active_keys(self): - return self.write({'state': 'active_keys'}) + return self.write({"state": "active_keys"}) def set_to_get_bank_keys(self): - return self.write({'state': 'get_bank_keys'}) + return self.write({"state": "get_bank_keys"}) - def ebics_init_1(self): + def ebics_init_1(self): # noqa: C901 """ Initialization of bank keys - Step 1: Create new keys and certificates for this user """ self.ensure_one() self.ebics_config_id._check_ebics_files() - if self.state != 'draft': + if self.state != "draft": raise UserError( - _("Set state to 'draft' before Bank Key (re)initialisation.")) + _("Set state to 'draft' before Bank Key (re)initialisation.") + ) if not self.ebics_passphrase: - raise UserError( - _("Set a passphrase.")) + raise UserError(_("Set a passphrase.")) if self.swift_3skey and not self.swift_3skey_certificate: - raise UserError( - _("3SKey certificate missing.")) + raise UserError(_("3SKey certificate missing.")) ebics_version = self.ebics_config_id.ebics_version try: keyring = EbicsKeyRing( - keys=self.ebics_keys_fn, - passphrase=self.ebics_passphrase) + keys=self.ebics_keys_fn, passphrase=self.ebics_passphrase + ) bank = EbicsBank( keyring=keyring, hostid=self.ebics_config_id.ebics_host, - url=self.ebics_config_id.ebics_url) + url=self.ebics_config_id.ebics_url, + ) user = EbicsUser( keyring=keyring, partnerid=self.ebics_config_id.ebics_partner, - userid=self.name) + userid=self.name, + ) except Exception: exctype, value = exc_info()[:2] error = _("EBICS Initialisation Error:") - error += '\n' + str(exctype) + '\n' + str(value) + error += "\n" + str(exctype) + "\n" + str(value) raise UserError(error) self.ebics_config_id._check_ebics_keys() @@ -240,60 +256,63 @@ class EbicsUserID(models.Model): # enable import of all type of certicates: A00x, X002, E002 if self.swift_3skey: kwargs = { - self.ebics_config_id.ebics_key_version: - base64.decodestring(self.swift_3skey_certificate), + self.ebics_config_id.ebics_key_version: base64.decodestring( + self.swift_3skey_certificate + ), } user.import_certificates(**kwargs) user.create_keys( keyversion=self.ebics_config_id.ebics_key_version, - bitlength=self.ebics_config_id.ebics_key_bitlength) + bitlength=self.ebics_config_id.ebics_key_bitlength, + ) except Exception: exctype, value = exc_info()[:2] error = _("EBICS Initialisation Error:") - error += '\n' + str(exctype) + '\n' + str(value) + error += "\n" + str(exctype) + "\n" + str(value) raise UserError(error) if self.swift_3skey and not self.ebics_key_x509: - raise UserError(_( - "The current version of this module " - "requires to X509 support when enabling 3SKey")) + raise UserError( + _( + "The current version of this module " + "requires to X509 support when enabling 3SKey" + ) + ) if self.ebics_key_x509: dn_attrs = { - 'commonName': self.ebics_key_x509_dn_cn, - 'organizationName': self.ebics_key_x509_dn_o, - 'organizationalUnitName': self.ebics_key_x509_dn_ou, - 'countryName': self.ebics_key_x509_dn_c, - 'stateOrProvinceName': self.ebics_key_x509_dn_st, - 'localityName': self.ebics_key_x509_dn_l, - 'emailAddress': self.ebics_key_x509_dn_e, + "commonName": self.ebics_key_x509_dn_cn, + "organizationName": self.ebics_key_x509_dn_o, + "organizationalUnitName": self.ebics_key_x509_dn_ou, + "countryName": self.ebics_key_x509_dn_c, + "stateOrProvinceName": self.ebics_key_x509_dn_st, + "localityName": self.ebics_key_x509_dn_l, + "emailAddress": self.ebics_key_x509_dn_e, } kwargs = {k: v for k, v in dn_attrs.items() if v} user.create_certificates(**kwargs) - client = EbicsClient( - bank, user, version=ebics_version) + client = EbicsClient(bank, user, version=ebics_version) # Send the public electronic signature key to the bank. ebics_config_bank = self.ebics_config_id.journal_ids[0].bank_id if not ebics_config_bank: - raise UserError(_( - "No bank defined for the financial journal " - "of the EBICS Config")) + raise UserError( + _("No bank defined for the financial journal " "of the EBICS Config") + ) try: supported_versions = client.HEV() if ebics_version not in supported_versions: err_msg = _("EBICS version mismatch.") + "\n" err_msg += _("Versions supported by your bank:") for k in supported_versions: - err_msg += "\n%s: %s " % (k, supported_versions[k]) + err_msg += "\n{}: {} ".format(k, supported_versions[k]) raise UserError(err_msg) - if ebics_version == 'H003': + if ebics_version == "H003": bank._order_number = self.ebics_config_id._get_order_number() OrderID = client.INI() - _logger.info( - '%s, EBICS INI command, OrderID=%s', self._name, OrderID) - if ebics_version == 'H003': + _logger.info("%s, EBICS INI command, OrderID=%s", self._name, OrderID) + if ebics_version == "H003": self.ebics_config_id._update_order_number(OrderID) except URLError: exctype, value = exc_info()[:2] @@ -303,69 +322,68 @@ class EbicsUserID(models.Model): self.name, tb, ) - raise UserError(_( - "urlopen error:\n url '%s' - %s") - % (self.ebics_config_id.ebics_url, str(value))) + raise UserError( + _("urlopen error:\n url '%s' - %s") + % (self.ebics_config_id.ebics_url, str(value)) + ) except EbicsFunctionalError: e = exc_info() error = _("EBICS Functional Error:") - error += '\n' - error += '%s (code: %s)' % (e[1].message, e[1].code) + error += "\n" + error += "{} (code: {})".format(e[1].message, e[1].code) raise UserError(error) except EbicsTechnicalError: e = exc_info() error = _("EBICS Technical Error:") - error += '\n' - error += '%s (code: %s)' % (e[1].message, e[1].code) + error += "\n" + error += "{} (code: {})".format(e[1].message, e[1].code) raise UserError(error) # Send the public authentication and encryption keys to the bank. - if ebics_version == 'H003': + if ebics_version == "H003": bank._order_number = self.ebics_config_id._get_order_number() OrderID = client.HIA() - _logger.info('%s, EBICS HIA command, OrderID=%s', self._name, OrderID) - if ebics_version == 'H003': + _logger.info("%s, EBICS HIA command, OrderID=%s", self._name, OrderID) + if ebics_version == "H003": self.ebics_config_id._update_order_number(OrderID) # Create an INI-letter which must be printed and sent to the bank. ebics_config_bank = self.ebics_config_id.journal_ids[0].bank_id cc = ebics_config_bank.country.code - if cc in ['FR', 'DE']: + if cc in ["FR", "DE"]: lang = cc else: - lang = self.env.user.lang or \ - self.env['res.lang'].search([])[0].code + lang = self.env.user.lang or self.env["res.lang"].search([])[0].code lang = lang[:2] - tmp_dir = os.path.normpath(self.ebics_config_id.ebics_files + '/tmp') + tmp_dir = os.path.normpath(self.ebics_config_id.ebics_files + "/tmp") if not os.path.isdir(tmp_dir): os.makedirs(tmp_dir, mode=0o700) fn_date = fields.Date.today().isoformat() - fn = '_'.join( - [self.ebics_config_id.ebics_host, 'ini_letter', fn_date]) + '.pdf' - full_tmp_fn = os.path.normpath(tmp_dir + '/' + fn) + fn = "_".join([self.ebics_config_id.ebics_host, "ini_letter", fn_date]) + ".pdf" + full_tmp_fn = os.path.normpath(tmp_dir + "/" + fn) user.create_ini_letter( - bankname=ebics_config_bank.name, - path=full_tmp_fn, - lang=lang) - with open(full_tmp_fn, 'rb') as f: + bankname=ebics_config_bank.name, path=full_tmp_fn, lang=lang + ) + with open(full_tmp_fn, "rb") as f: letter = f.read() - self.write({ - 'ebics_ini_letter': base64.encodebytes(letter), - 'ebics_ini_letter_fn': fn, - }) + self.write( + { + "ebics_ini_letter": base64.encodebytes(letter), + "ebics_ini_letter_fn": fn, + } + ) - return self.write({'state': 'init'}) + return self.write({"state": "init"}) def ebics_init_2(self): """ Initialization of bank keys - Step 2: Activation of the account by the bank. """ - if self.state != 'init': - raise UserError( - _("Set state to 'Initialisation'.")) + if self.state != "init": + raise UserError(_("Set state to 'Initialisation'.")) self.ensure_one() - return self.write({'state': 'get_bank_keys'}) + return self.write({"state": "get_bank_keys"}) def ebics_init_3(self): """ @@ -376,26 +394,27 @@ class EbicsUserID(models.Model): """ self.ensure_one() self.ebics_config_id._check_ebics_files() - if self.state != 'get_bank_keys': - raise UserError( - _("Set state to 'Get Keys from Bank'.")) + if self.state != "get_bank_keys": + raise UserError(_("Set state to 'Get Keys from Bank'.")) try: keyring = EbicsKeyRing( - keys=self.ebics_keys_fn, passphrase=self.ebics_passphrase) + keys=self.ebics_keys_fn, passphrase=self.ebics_passphrase + ) bank = EbicsBank( keyring=keyring, hostid=self.ebics_config_id.ebics_host, - url=self.ebics_config_id.ebics_url) + url=self.ebics_config_id.ebics_url, + ) user = EbicsUser( keyring=keyring, partnerid=self.ebics_config_id.ebics_partner, - userid=self.name) - client = EbicsClient( - bank, user, version=self.ebics_config_id.ebics_version) + userid=self.name, + ) + client = EbicsClient(bank, user, version=self.ebics_config_id.ebics_version) except Exception: exctype, value = exc_info()[:2] error = _("EBICS Initialisation Error:") - error += '\n' + str(exctype) + '\n' + str(value) + error += "\n" + str(exctype) + "\n" + str(value) raise UserError(error) try: @@ -403,28 +422,31 @@ class EbicsUserID(models.Model): except EbicsFunctionalError: e = exc_info() error = _("EBICS Functional Error:") - error += '\n' - error += '%s (code: %s)' % (e[1].message, e[1].code) + error += "\n" + error += "{} (code: {})".format(e[1].message, e[1].code) raise UserError(error) except Exception: exctype, value = exc_info()[:2] error = _("EBICS Initialisation Error:") - error += '\n' + str(exctype) + '\n' + str(value) + error += "\n" + str(exctype) + "\n" + str(value) raise UserError(error) public_bank_keys = public_bank_keys.encode() - tmp_dir = os.path.normpath(self.ebics_config_id.ebics_files + '/tmp') + tmp_dir = os.path.normpath(self.ebics_config_id.ebics_files + "/tmp") if not os.path.isdir(tmp_dir): os.makedirs(tmp_dir, mode=0o700) fn_date = fields.Date.today().isoformat() - fn = '_'.join( - [self.ebics_config_id.ebics_host, 'public_bank_keys', fn_date] - ) + '.txt' - self.write({ - 'ebics_public_bank_keys': base64.encodestring(public_bank_keys), - 'ebics_public_bank_keys_fn': fn, - 'state': 'to_verify', - }) + fn = ( + "_".join([self.ebics_config_id.ebics_host, "public_bank_keys", fn_date]) + + ".txt" + ) + self.write( + { + "ebics_public_bank_keys": base64.encodestring(public_bank_keys), + "ebics_public_bank_keys_fn": fn, + "state": "to_verify", + } + ) return True @@ -435,32 +457,32 @@ class EbicsUserID(models.Model): and activate the bank keyu. """ self.ensure_one() - if self.state != 'to_verify': - raise UserError( - _("Set state to 'Verification'.")) + if self.state != "to_verify": + raise UserError(_("Set state to 'Verification'.")) keyring = EbicsKeyRing( - keys=self.ebics_keys_fn, passphrase=self.ebics_passphrase) + keys=self.ebics_keys_fn, passphrase=self.ebics_passphrase + ) bank = EbicsBank( keyring=keyring, hostid=self.ebics_config_id.ebics_host, - url=self.ebics_config_id.ebics_url) + url=self.ebics_config_id.ebics_url, + ) bank.activate_keys() - return self.write({'state': 'active_keys'}) + return self.write({"state": "active_keys"}) def change_passphrase(self): self.ensure_one() ctx = dict(self._context, default_ebics_userid_id=self.id) - module = __name__.split('addons.')[1].split('.')[0] - view = self.env.ref( - '%s.ebics_change_passphrase_view_form' % module) + module = __name__.split("addons.")[1].split(".")[0] + view = self.env.ref("%s.ebics_change_passphrase_view_form" % module) return { - 'name': _('EBICS keys change passphrase'), - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'ebics.change.passphrase', - 'view_id': view.id, - 'target': 'new', - 'context': ctx, - 'type': 'ir.actions.act_window', + "name": _("EBICS keys change passphrase"), + "view_type": "form", + "view_mode": "form", + "res_model": "ebics.change.passphrase", + "view_id": view.id, + "target": "new", + "context": ctx, + "type": "ir.actions.act_window", } diff --git a/account_ebics/models/fintech_ebics_register.py b/account_ebics/models/fintech_ebics_register.py index f908637..613e5be 100644 --- a/account_ebics/models/fintech_ebics_register.py +++ b/account_ebics/models/fintech_ebics_register.py @@ -13,24 +13,25 @@ try: import fintech except ImportError: fintech = None - _logger.warning('Failed to import fintech') + _logger.warning("Failed to import fintech") -fintech_register_name = config.get('fintech_register_name') -fintech_register_keycode = config.get('fintech_register_keycode') -fintech_register_users = config.get('fintech_register_users') +fintech_register_name = config.get("fintech_register_name") +fintech_register_keycode = config.get("fintech_register_keycode") +fintech_register_users = config.get("fintech_register_users") try: if fintech: fintech_register_users = ( fintech_register_users - and [x.strip() for x in fintech_register_users.split(',')] + and [x.strip() for x in fintech_register_users.split(",")] or None ) - fintech.cryptolib = 'cryptography' + fintech.cryptolib = "cryptography" fintech.register( name=fintech_register_name, keycode=fintech_register_keycode, - users=fintech_register_users) + users=fintech_register_users, + ) except RuntimeError as e: if str(e) == "'register' can be called only once": pass @@ -39,7 +40,7 @@ except RuntimeError as e: fintech.register() except Exception: msg = "fintech.register error" - tb = ''.join(format_exception(*exc_info())) - msg += '\n%s' % tb + tb = "".join(format_exception(*exc_info())) + msg += "\n%s" % tb _logger.error(msg) fintech.register() diff --git a/account_ebics/security/ebics_security.xml b/account_ebics/security/ebics_security.xml index fc939a2..7b11eac 100644 --- a/account_ebics/security/ebics_security.xml +++ b/account_ebics/security/ebics_security.xml @@ -1,32 +1,38 @@ - + EBICS Manager - + EBICS Configuration model company rule - - - ['|', ('company_ids', '=', False), ('company_ids', 'in', user.company_ids.ids)] + + + ['|', ('company_ids', '=', False), ('company_ids', 'in', user.company_ids.ids)] EBICS UserID model company rule - - - ['|', ('company_ids', '=', False), ('company_ids', 'in', user.company_ids.ids)] + + + ['|', ('company_ids', '=', False), ('company_ids', 'in', user.company_ids.ids)] EBICS File model company rule - - - ['|', ('company_ids', '=', False), ('company_ids', 'in', user.company_ids.ids)] + + + ['|', ('company_ids', '=', False), ('company_ids', 'in', user.company_ids.ids)] diff --git a/account_ebics/views/ebics_config_views.xml b/account_ebics/views/ebics_config_views.xml index f76c619..d407af5 100644 --- a/account_ebics/views/ebics_config_views.xml +++ b/account_ebics/views/ebics_config_views.xml @@ -6,10 +6,10 @@ ebics.config - - - - + + + + @@ -20,44 +20,63 @@
-
- + - - - - - - + + + + + + - - - - - + + + + + - + - + - + diff --git a/account_ebics/views/ebics_file_format_views.xml b/account_ebics/views/ebics_file_format_views.xml index 79ed9e2..03dd598 100644 --- a/account_ebics/views/ebics_file_format_views.xml +++ b/account_ebics/views/ebics_file_format_views.xml @@ -6,11 +6,11 @@ ebics.file.format - - - - - + + + + + @@ -22,20 +22,22 @@
- - - - + + + + - - + + - +
diff --git a/account_ebics/views/ebics_file_views.xml b/account_ebics/views/ebics_file_views.xml index a7376ae..5961602 100644 --- a/account_ebics/views/ebics_file_views.xml +++ b/account_ebics/views/ebics_file_views.xml @@ -7,18 +7,26 @@ - - - - - - + + + + + + - + - - - + + + @@ -31,14 +39,18 @@ ebics.file - - - - - - - - + + + + + + + + @@ -50,34 +62,55 @@
-
- - - - - - - - - + + + + + + + + + - + - - + +
@@ -91,12 +124,16 @@
- +
-
@@ -107,23 +144,23 @@ ir.actions.act_window ebics.file tree,form - + [('type','=','down')] - + - + tree - - + + - + form - - + + @@ -133,12 +170,16 @@ ebics.file - - - - - - + + + + + + @@ -150,22 +191,38 @@
-
- - - - - - - + + + + + + + - +
@@ -177,23 +234,23 @@ ir.actions.act_window ebics.file tree,form - + [('type','=','up')] - + - + tree - - + + - + form - - + +
diff --git a/account_ebics/views/ebics_userid_views.xml b/account_ebics/views/ebics_userid_views.xml index aa32002..8626d68 100644 --- a/account_ebics/views/ebics_userid_views.xml +++ b/account_ebics/views/ebics_userid_views.xml @@ -6,10 +6,10 @@ ebics.userid - - - - + + + + @@ -20,67 +20,136 @@
-
- - + + - - - - - - + + + + + + - - - + + + - + - Distinguished Name attributes used to create self-signed X.509 certificates: + Distinguished Name attributes used to create self-signed X.509 certificates: - - - - + + + + - - - + + + - - - + + + - - - + + + diff --git a/account_ebics/views/menu.xml b/account_ebics/views/menu.xml index a2a2c6a..b3e3b23 100644 --- a/account_ebics/views/menu.xml +++ b/account_ebics/views/menu.xml @@ -1,58 +1,76 @@ - + - + - + - + - + - + - + - + - + diff --git a/account_ebics/wizards/ebics_change_passphrase.py b/account_ebics/wizards/ebics_change_passphrase.py index 5321453..1ac046c 100644 --- a/account_ebics/wizards/ebics_change_passphrase.py +++ b/account_ebics/wizards/ebics_change_passphrase.py @@ -11,65 +11,58 @@ _logger = logging.getLogger(__name__) try: import fintech from fintech.ebics import EbicsKeyRing - fintech.cryptolib = 'cryptography' + + fintech.cryptolib = "cryptography" except ImportError: - _logger.warning('Failed to import fintech') + _logger.warning("Failed to import fintech") class EbicsChangePassphrase(models.TransientModel): - _name = 'ebics.change.passphrase' - _description = 'Change EBICS keys passphrase' + _name = "ebics.change.passphrase" + _description = "Change EBICS keys passphrase" ebics_userid_id = fields.Many2one( - comodel_name='ebics.userid', - string='EBICS UserID', - readonly=True) - old_pass = fields.Char( - string='Old Passphrase', - required=True) - new_pass = fields.Char( - string='New Passphrase', - required=True) - new_pass_check = fields.Char( - string='New Passphrase (verification)', - required=True) - note = fields.Text(string='Notes', readonly=True) + comodel_name="ebics.userid", string="EBICS UserID", readonly=True + ) + old_pass = fields.Char(string="Old Passphrase", required=True) + new_pass = fields.Char(string="New Passphrase", required=True) + new_pass_check = fields.Char(string="New Passphrase (verification)", required=True) + note = fields.Text(string="Notes", readonly=True) def change_passphrase(self): self.ensure_one() if self.old_pass != self.ebics_userid_id.ebics_passphrase: - raise UserError(_( - "Incorrect old passphrase.")) + raise UserError(_("Incorrect old passphrase.")) if self.new_pass != self.new_pass_check: - raise UserError(_( - "New passphrase verification error.")) + raise UserError(_("New passphrase verification error.")) if self.new_pass == self.ebics_userid_id.ebics_passphrase: - raise UserError(_( - "New passphrase equal to old passphrase.")) + raise UserError(_("New passphrase equal to old passphrase.")) try: keyring = EbicsKeyRing( keys=self.ebics_userid_id.ebics_keys_fn, - passphrase=self.ebics_userid_id.ebics_passphrase) + passphrase=self.ebics_userid_id.ebics_passphrase, + ) keyring.change_passphrase(self.new_pass) except ValueError as e: raise UserError(str(e)) self.ebics_userid.ebics_passphrase = self.new_pass self.note = "The EBICS Passphrase has been changed." - module = __name__.split('addons.')[1].split('.')[0] + module = __name__.split("addons.")[1].split(".")[0] result_view = self.env.ref( - '%s.ebics_change_passphrase_view_form_result' % module) + "%s.ebics_change_passphrase_view_form_result" % module + ) return { - 'name': _('EBICS Keys Change Passphrase'), - 'res_id': self.id, - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'ebics.change.passphrase', - 'view_id': result_view.id, - 'target': 'new', - 'type': 'ir.actions.act_window', + "name": _("EBICS Keys Change Passphrase"), + "res_id": self.id, + "view_type": "form", + "view_mode": "form", + "res_model": "ebics.change.passphrase", + "view_id": result_view.id, + "target": "new", + "type": "ir.actions.act_window", } def button_close(self): self.ensure_one() - return {'type': 'ir.actions.act_window_close'} + return {"type": "ir.actions.act_window_close"} diff --git a/account_ebics/wizards/ebics_change_passphrase.xml b/account_ebics/wizards/ebics_change_passphrase.xml index f0a33f5..bb24f0a 100644 --- a/account_ebics/wizards/ebics_change_passphrase.xml +++ b/account_ebics/wizards/ebics_change_passphrase.xml @@ -1,4 +1,4 @@ - + @@ -8,14 +8,19 @@
- - - + + +
-
@@ -28,9 +33,9 @@
- +
-
diff --git a/account_ebics/wizards/ebics_xfer.py b/account_ebics/wizards/ebics_xfer.py index 0ed09f0..5493056 100644 --- a/account_ebics/wizards/ebics_xfer.py +++ b/account_ebics/wizards/ebics_xfer.py @@ -14,125 +14,146 @@ import os from sys import exc_info from traceback import format_exception -from odoo import api, fields, models, _ +from odoo import _, api, fields, models from odoo.exceptions import UserError _logger = logging.getLogger(__name__) try: import fintech - from fintech.ebics import EbicsKeyRing, EbicsBank, EbicsUser, EbicsClient,\ - EbicsFunctionalError, EbicsTechnicalError, EbicsVerificationError - fintech.cryptolib = 'cryptography' + from fintech.ebics import ( + EbicsBank, + EbicsClient, + EbicsFunctionalError, + EbicsKeyRing, + EbicsTechnicalError, + EbicsUser, + EbicsVerificationError, + ) + + fintech.cryptolib = "cryptography" except ImportError: EbicsBank = object - _logger.warning('Failed to import fintech') + _logger.warning("Failed to import fintech") class EbicsBank(EbicsBank): - def _next_order_id(self, partnerid): """ EBICS protocol version H003 requires generation of the OrderID. The OrderID must be a string between 'A000' and 'ZZZZ' and unique for each partner id. """ - return hasattr(self, '_order_number') and self._order_number or 'A000' + return hasattr(self, "_order_number") and self._order_number or "A000" class EbicsXfer(models.TransientModel): - _name = 'ebics.xfer' - _description = 'EBICS file transfer' + _name = "ebics.xfer" + _description = "EBICS file transfer" ebics_config_id = fields.Many2one( - comodel_name='ebics.config', - string='EBICS Configuration', - domain=[('state', '=', 'confirm')], - default=lambda self: self._default_ebics_config_id()) + comodel_name="ebics.config", + string="EBICS Configuration", + domain=[("state", "=", "confirm")], + default=lambda self: self._default_ebics_config_id(), + ) ebics_userid_id = fields.Many2one( - comodel_name='ebics.userid', - string='EBICS UserID') - ebics_passphrase = fields.Char( - string='EBICS Passphrase') + comodel_name="ebics.userid", string="EBICS UserID" + ) + ebics_passphrase = fields.Char(string="EBICS Passphrase") date_from = fields.Date() date_to = fields.Date() - upload_data = fields.Binary(string='File to Upload') - upload_fname = fields.Char( - string='Upload Filename', default='') + upload_data = fields.Binary(string="File to Upload") + upload_fname = fields.Char(string="Upload Filename", default="") upload_fname_dummy = fields.Char( - related='upload_fname', string='Upload Filename', readonly=True) + related="upload_fname", string="Upload Filename", readonly=True + ) format_id = fields.Many2one( - comodel_name='ebics.file.format', - string='EBICS File Format', + comodel_name="ebics.file.format", + string="EBICS File Format", help="Select EBICS File Format to upload/download." - "\nLeave blank to download all available files.") + "\nLeave blank to download all available files.", + ) allowed_format_ids = fields.Many2many( - related='ebics_config_id.ebics_file_format_ids', - string='Allowed EBICS File Formats') + related="ebics_config_id.ebics_file_format_ids", + string="Allowed EBICS File Formats", + ) order_type = fields.Char( - related='format_id.order_type', - string='Order Type', + related="format_id.order_type", + string="Order Type", help="For most banks in France you should use the " - "format neutral Order Types 'FUL' for upload " - "and 'FDL' for download.") + "format neutral Order Types 'FUL' for upload " + "and 'FDL' for download.", + ) test_mode = fields.Boolean( - string='Test Mode', + string="Test Mode", help="Select this option to test if the syntax of " - "the upload file is correct." - "\nThis option is only available for " - "Order Type 'FUL'.") - note = fields.Text(string='EBICS file transfer Log', readonly=True) + "the upload file is correct." + "\nThis option is only available for " + "Order Type 'FUL'.", + ) + note = fields.Text(string="EBICS file transfer Log", readonly=True) @api.model def _default_ebics_config_id(self): - cfg_mod = self.env['ebics.config'] + cfg_mod = self.env["ebics.config"] cfg = cfg_mod.search( - [('company_ids', 'in', self.env.user.company_ids.ids), - ('state', '=', 'confirm')]) + [ + ("company_ids", "in", self.env.user.company_ids.ids), + ("state", "=", "confirm"), + ] + ) if cfg and len(cfg) == 1: return cfg else: return cfg_mod - @api.onchange('ebics_config_id') + @api.onchange("ebics_config_id") def _onchange_ebics_config_id(self): ebics_userids = self.ebics_config_id.ebics_userid_ids - if self._context.get('ebics_download'): - download_formats = self.ebics_config_id.ebics_file_format_ids\ - .filtered(lambda r: r.type == 'down') + if self._context.get("ebics_download"): + download_formats = self.ebics_config_id.ebics_file_format_ids.filtered( + lambda r: r.type == "down" + ) if len(download_formats) == 1: self.format_id = download_formats if len(ebics_userids) == 1: self.ebics_userid_id = ebics_userids else: transport_users = ebics_userids.filtered( - lambda r: r.signature_class == 'T') + lambda r: r.signature_class == "T" + ) if len(transport_users) == 1: self.ebics_userid_id = transport_users else: - upload_formats = self.ebics_config_id.ebics_file_format_ids\ - .filtered(lambda r: r.type == 'up') + upload_formats = self.ebics_config_id.ebics_file_format_ids.filtered( + lambda r: r.type == "up" + ) if len(upload_formats) == 1: self.format_id = upload_formats if len(ebics_userids) == 1: self.ebics_userid_id = ebics_userids - @api.onchange('upload_data') + @api.onchange("upload_data") def _onchange_upload_data(self): self.upload_fname_dummy = self.upload_fname self.format_id = False self._detect_upload_format() if not self.format_id: - upload_formats = self.format_id \ + upload_formats = ( + self.format_id or self.ebics_config_id.ebics_file_format_ids.filtered( - lambda r: r.type == 'up') + lambda r: r.type == "up" + ) + ) if len(upload_formats) > 1: upload_formats = upload_formats.filtered( - lambda r: self.upload_fname.endswith(r.suffix)) + lambda r: self.upload_fname.endswith(r.suffix) + ) if len(upload_formats) == 1: self.format_id = upload_formats - @api.onchange('format_id') + @api.onchange("format_id") def _onchange_format_id(self): self.order_type = self.format_id.order_type @@ -141,138 +162,152 @@ class EbicsXfer(models.TransientModel): ctx = self._context.copy() ebics_file = self._ebics_upload() if ebics_file: - ctx['ebics_file_id'] = ebics_file.id - module = __name__.split('addons.')[1].split('.')[0] - result_view = self.env.ref( - '%s.ebics_xfer_view_form_result' % module) + ctx["ebics_file_id"] = ebics_file.id + module = __name__.split("addons.")[1].split(".")[0] + result_view = self.env.ref("%s.ebics_xfer_view_form_result" % module) return { - 'name': _('EBICS file transfer result'), - 'res_id': self.id, - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'ebics.xfer', - 'view_id': result_view.id, - 'target': 'new', - 'context': ctx, - 'type': 'ir.actions.act_window', + "name": _("EBICS file transfer result"), + "res_id": self.id, + "view_type": "form", + "view_mode": "form", + "res_model": "ebics.xfer", + "view_id": result_view.id, + "target": "new", + "context": ctx, + "type": "ir.actions.act_window", } def ebics_download(self): self.ensure_one() self.ebics_config_id._check_ebics_files() - ctx = self._context.copy() - self.note = '' + ctx = self.env.context.copy() + self.note = "" + err_cnt = 0 client = self._setup_client() - if client: + if not client: + err_cnt += 1 + self.note += ( + _("EBICS client setup failed for connection '%s'") + % self.ebics_config_id.name + ) + else: download_formats = ( self.format_id or self.ebics_config_id.ebics_file_format_ids.filtered( - lambda r: r.type == 'down' + lambda r: r.type == "down" ) ) - ebics_files = self.env['ebics.file'] + ebics_files = self.env["ebics.file"] date_from = self.date_from and self.date_from.isoformat() or None date_to = self.date_to and self.date_to.isoformat() or None for df in download_formats: try: success = False - if df.order_type == 'FDL': + if df.order_type == "FDL": data = client.FDL(df.name, date_from, date_to) else: params = None if date_from and date_to: - params = {'DateRange': { - 'Start': date_from, - 'End': date_to, - }} + params = { + "DateRange": { + "Start": date_from, + "End": date_to, + } + } data = client.download(df.order_type, params=params) ebics_files += self._handle_download_data(data, df) success = True except EbicsFunctionalError: + err_cnt += 1 e = exc_info() - self.note += '\n' + self.note += "\n" self.note += _( "EBICS Functional Error during download of File Format %s (%s):" ) % (df.name, df.order_type) - self.note += '\n' - self.note += '%s (code: %s)' % (e[1].message, e[1].code) + self.note += "\n" + self.note += "{} (code: {})".format(e[1].message, e[1].code) except EbicsTechnicalError: + err_cnt += 1 e = exc_info() - self.note += '\n' + self.note += "\n" self.note += _( "EBICS Technical Error during download of File Format %s (%s):" ) % (df.name, df.order_type) - self.note += '\n' - self.note += '%s (code: %s)' % (e[1].message, e[1].code) + self.note += "\n" + self.note += "{} (code: {})".format(e[1].message, e[1].code) except EbicsVerificationError: - self.note += '\n' + err_cnt += 1 + self.note += "\n" self.note += _( "EBICS Verification Error during download of " "File Format %s (%s):" ) % (df.name, df.order_type) - self.note += '\n' + self.note += "\n" self.note += _("The EBICS response could not be verified.") except UserError as e: - self.note += '\n' + self.note += "\n" self.note += _( "Warning during download of File Format %s (%s):" ) % (df.name, df.order_type) - self.note += '\n' + self.note += "\n" self.note += e.name except Exception: - self.note += '\n' + err_cnt += 1 + self.note += "\n" self.note += _( "Unknown Error during download of File Format %s (%s):" ) % (df.name, df.order_type) - tb = ''.join(format_exception(*exc_info())) - self.note += '\n%s' % tb + tb = "".join(format_exception(*exc_info())) + self.note += "\n%s" % tb else: # mark received data so that it is not included in further # downloads trans_id = client.last_trans_id client.confirm_download(trans_id=trans_id, success=success) - ctx['ebics_file_ids'] = ebics_files._ids + ctx["ebics_file_ids"] = ebics_files.ids if ebics_files: - self.note += '\n' + self.note += "\n" for f in ebics_files: - self.note += _( - "EBICS File '%s' is available for further processing." - ) % f.name - self.note += '\n' + self.note += ( + _("EBICS File '%s' is available for further processing.") + % f.name + ) + self.note += "\n" - module = __name__.split('addons.')[1].split('.')[0] - result_view = self.env.ref( - '%s.ebics_xfer_view_form_result' % module) + ctx["err_cnt"] = err_cnt + module = __name__.split("addons.")[1].split(".")[0] + result_view = self.env.ref("%s.ebics_xfer_view_form_result" % module) return { - 'name': _('EBICS file transfer result'), - 'res_id': self.id, - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'ebics.xfer', - 'view_id': result_view.id, - 'target': 'new', - 'context': ctx, - 'type': 'ir.actions.act_window', + "name": _("EBICS file transfer result"), + "res_id": self.id, + "view_type": "form", + "view_mode": "form", + "res_model": "ebics.xfer", + "view_id": result_view.id, + "target": "new", + "context": ctx, + "type": "ir.actions.act_window", } def button_close(self): self.ensure_one() - return {'type': 'ir.actions.act_window_close'} + return {"type": "ir.actions.act_window_close"} def view_ebics_file(self): self.ensure_one() - module = __name__.split('addons.')[1].split('.')[0] - act = self.env['ir.actions.act_window']._for_xml_id( - '{}.ebics_file_action_download'.format(module)) - act['domain'] = [('id', 'in', self._context['ebics_file_ids'])] + module = __name__.split("addons.")[1].split(".")[0] + act = self.env["ir.actions.act_window"]._for_xml_id( + "{}.ebics_file_action_download".format(module) + ) + act["domain"] = [("id", "in", self._context["ebics_file_ids"])] return act def _ebics_upload(self): self.ensure_one() - ebics_file = self.env['ebics.file'] - self.note = '' + ebics_file = self.env["ebics.file"] + self.note = "" client = self._setup_client() if client: upload_data = base64.decodestring(self.upload_data) @@ -280,70 +315,69 @@ class EbicsXfer(models.TransientModel): OrderID = False try: order_type = self.order_type - if order_type == 'FUL': + if order_type == "FUL": kwargs = {} bank = self.ebics_config_id.journal_ids[0].bank_id cc = bank.country.code if cc: - kwargs['country'] = cc + kwargs["country"] = cc if self.test_mode: - kwargs['TEST'] = 'TRUE' + kwargs["TEST"] = "TRUE" OrderID = client.FUL(ef_format.name, upload_data, **kwargs) else: OrderID = client.upload(order_type, upload_data) if OrderID: - self.note += '\n' - self.note += _( - "EBICS File has been uploaded (OrderID %s)." - ) % OrderID + self.note += "\n" + self.note += ( + _("EBICS File has been uploaded (OrderID %s).") % OrderID + ) ef_note = _("EBICS OrderID: %s") % OrderID - if self.env.context.get('origin'): - ef_note += '\n' + _( - "Origin: %s") % self._context['origin'] + if self.env.context.get("origin"): + ef_note += "\n" + _("Origin: %s") % self._context["origin"] suffix = self.format_id.suffix fn = self.upload_fname if not fn.endswith(suffix): - fn = '.'.join([fn, suffix]) + fn = ".".join([fn, suffix]) ef_vals = { - 'name': self.upload_fname, - 'data': self.upload_data, - 'date': fields.Datetime.now(), - 'format_id': self.format_id.id, - 'state': 'done', - 'user_id': self._uid, - 'ebics_userid_id': self.ebics_userid_id.id, - 'note': ef_note, + "name": self.upload_fname, + "data": self.upload_data, + "date": fields.Datetime.now(), + "format_id": self.format_id.id, + "state": "done", + "user_id": self._uid, + "ebics_userid_id": self.ebics_userid_id.id, + "note": ef_note, "company_ids": [ self.env.context.get("force_company", self.env.company.id) ], } self._update_ef_vals(ef_vals) - ebics_file = self.env['ebics.file'].create(ef_vals) + ebics_file = self.env["ebics.file"].create(ef_vals) except EbicsFunctionalError: e = exc_info() - self.note += '\n' + self.note += "\n" self.note += _("EBICS Functional Error:") - self.note += '\n' - self.note += '%s (code: %s)' % (e[1].message, e[1].code) + self.note += "\n" + self.note += "{} (code: {})".format(e[1].message, e[1].code) except EbicsTechnicalError: e = exc_info() - self.note += '\n' + self.note += "\n" self.note += _("EBICS Technical Error:") - self.note += '\n' - self.note += '%s (code: %s)' % (e[1].message, e[1].code) + self.note += "\n" + self.note += "{} (code: {})".format(e[1].message, e[1].code) except EbicsVerificationError: - self.note += '\n' + self.note += "\n" self.note += _("EBICS Verification Error:") - self.note += '\n' + self.note += "\n" self.note += _("The EBICS response could not be verified.") except Exception: - self.note += '\n' + self.note += "\n" self.note += _("Unknown Error") - tb = ''.join(format_exception(*exc_info())) - self.note += '\n%s' % tb + tb = "".join(format_exception(*exc_info())) + self.note += "\n%s" % tb - if self.ebics_config_id.ebics_version == 'H003': + if self.ebics_config_id.ebics_version == "H003": OrderID = self.ebics_config_id._get_order_number() self.ebics_config_id.sudo()._update_order_number(OrderID) @@ -353,33 +387,35 @@ class EbicsXfer(models.TransientModel): self.ebics_config_id._check_ebics_keys() passphrase = self._get_passphrase() keyring = EbicsKeyRing( - keys=self.ebics_userid_id.ebics_keys_fn, - passphrase=passphrase) + keys=self.ebics_userid_id.ebics_keys_fn, passphrase=passphrase + ) bank = EbicsBank( keyring=keyring, hostid=self.ebics_config_id.ebics_host, - url=self.ebics_config_id.ebics_url) - if self.ebics_config_id.ebics_version == 'H003': + url=self.ebics_config_id.ebics_url, + ) + if self.ebics_config_id.ebics_version == "H003": bank._order_number = self.ebics_config_id._get_order_number() user = EbicsUser( keyring=keyring, partnerid=self.ebics_config_id.ebics_partner, - userid=self.ebics_userid_id.name) - signature_class = self.format_id.signature_class \ - or self.ebics_userid_id.signature_class - if signature_class == 'T': + userid=self.ebics_userid_id.name, + ) + signature_class = ( + self.format_id.signature_class or self.ebics_userid_id.signature_class + ) + if signature_class == "T": user.manual_approval = True try: - client = EbicsClient( - bank, user, version=self.ebics_config_id.ebics_version) + client = EbicsClient(bank, user, version=self.ebics_config_id.ebics_version) except Exception: - self.note += '\n' + self.note += "\n" self.note += _("Unknown Error") - tb = ''.join(format_exception(*exc_info())) - self.note += '\n%s' % tb + tb = "".join(format_exception(*exc_info())) + self.note += "\n%s" % tb client = False return client @@ -390,19 +426,18 @@ class EbicsXfer(models.TransientModel): if passphrase: return passphrase - module = __name__.split('addons.')[1].split('.')[0] - passphrase_view = self.env.ref( - '%s.ebics_xfer_view_form_passphrase' % module) + module = __name__.split("addons.")[1].split(".")[0] + passphrase_view = self.env.ref("%s.ebics_xfer_view_form_passphrase" % module) return { - 'name': _('EBICS file transfer'), - 'res_id': self.id, - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'ebics.xfer', - 'view_id': passphrase_view.id, - 'target': 'new', - 'context': self._context, - 'type': 'ir.actions.act_window', + "name": _("EBICS file transfer"), + "res_id": self.id, + "view_type": "form", + "view_mode": "form", + "res_model": "ebics.xfer", + "view_id": passphrase_view.id, + "target": "new", + "context": self._context, + "type": "ir.actions.act_window", } def _file_format_methods(self): @@ -411,10 +446,10 @@ class EbicsXfer(models.TransientModel): for extra file formats. """ res = { - 'camt.xxx.cfonb120.stm': self._handle_cfonb120, - 'camt.xxx.cfonb120.stm.rfi': self._handle_cfonb120, - 'camt.052.001.02.stm': self._handle_camt052, - 'camt.053.001.02.stm': self._handle_camt053, + "camt.xxx.cfonb120.stm": self._handle_cfonb120, + "camt.xxx.cfonb120.stm.rfi": self._handle_cfonb120, + "camt.052.001.02.stm": self._handle_camt052, + "camt.053.001.02.stm": self._handle_camt053, } return res @@ -422,24 +457,24 @@ class EbicsXfer(models.TransientModel): """ Adapt this method to customize the EBICS File values. """ - if self.format_id and self.format_id.type == 'up': - fn = ef_vals['name'] - dups = self._check_duplicate_ebics_file( - fn, self.format_id) + if self.format_id and self.format_id.type == "up": + fn = ef_vals["name"] + dups = self._check_duplicate_ebics_file(fn, self.format_id) if dups: n = 1 - fn = '_'.join([fn, str(n)]) + fn = "_".join([fn, str(n)]) while self._check_duplicate_ebics_file(fn, self.format_id): n += 1 - fn = '_'.join([fn, str(n)]) - ef_vals['name'] = fn + fn = "_".join([fn, str(n)]) + ef_vals["name"] = fn def _handle_download_data(self, data, file_format): - ebics_files = self.env['ebics.file'] + ebics_files = self.env["ebics.file"] if isinstance(data, dict): for doc in data: ebics_files += self._create_ebics_file( - data[doc], file_format, docname=doc) + data[doc], file_format, docname=doc + ) else: ebics_files += self._create_ebics_file(data, file_format) return ebics_files @@ -457,59 +492,61 @@ class EbicsXfer(models.TransientModel): file format specific processing. """ ebics_files_root = self.ebics_config_id.ebics_files - tmp_dir = os.path.normpath(ebics_files_root + '/tmp') + tmp_dir = os.path.normpath(ebics_files_root + "/tmp") if not os.path.isdir(tmp_dir): os.makedirs(tmp_dir, mode=0o700) - fn_parts = [self.ebics_config_id.ebics_host, - self.ebics_config_id.ebics_partner] + fn_parts = [self.ebics_config_id.ebics_host, self.ebics_config_id.ebics_partner] if docname: fn_parts.append(docname) else: fn_date = self.date_to or fields.Date.today() fn_parts.append(fn_date.isoformat()) - base_fn = '_'.join(fn_parts) + base_fn = "_".join(fn_parts) n = 1 - full_tmp_fn = os.path.normpath(tmp_dir + '/' + base_fn) + full_tmp_fn = os.path.normpath(tmp_dir + "/" + base_fn) while os.path.exists(full_tmp_fn): n += 1 - tmp_fn = base_fn + '_' + str(n).rjust(3, '0') - full_tmp_fn = os.path.normpath(tmp_dir + '/' + tmp_fn) + tmp_fn = base_fn + "_" + str(n).rjust(3, "0") + full_tmp_fn = os.path.normpath(tmp_dir + "/" + tmp_fn) - with open(full_tmp_fn, 'wb') as f: + with open(full_tmp_fn, "wb") as f: f.write(data) ff_methods = self._file_format_methods() if file_format.name in ff_methods: data = ff_methods[file_format.name](data) - fn = '.'.join([base_fn, file_format.suffix]) + fn = ".".join([base_fn, file_format.suffix]) dups = self._check_duplicate_ebics_file(fn, file_format) if dups: - raise UserError(_( - "EBICS File with name '%s' has already been downloaded." - "\nPlease check this file and rename in case there is " - "no risk on duplicate transactions.") - % fn) + raise UserError( + _( + "EBICS File with name '%s' has already been downloaded." + "\nPlease check this file and rename in case there is " + "no risk on duplicate transactions." + ) + % fn + ) data = base64.encodebytes(data) ef_vals = { - 'name': fn, - 'data': data, - 'date': fields.Datetime.now(), - 'date_from': self.date_from, - 'date_to': self.date_to, - 'format_id': file_format.id, - 'user_id': self._uid, - 'ebics_userid_id': self.ebics_userid_id.id, - 'company_ids': self.ebics_config_id.company_ids.ids, + "name": fn, + "data": data, + "date": fields.Datetime.now(), + "date_from": self.date_from, + "date_to": self.date_to, + "format_id": file_format.id, + "user_id": self._uid, + "ebics_userid_id": self.ebics_userid_id.id, + "company_ids": self.ebics_config_id.company_ids.ids, } self._update_ef_vals(ef_vals) - ebics_file = self.env['ebics.file'].create(ef_vals) + ebics_file = self.env["ebics.file"].create(ef_vals) return ebics_file def _check_duplicate_ebics_file(self, fn, file_format): - dups = self.env['ebics.file'].search( - [('name', '=', fn), - ('format_id', '=', file_format.id)]) + dups = self.env["ebics.file"].search( + [("name", "=", fn), ("format_id", "=", file_format.id)] + ) return dups def _detect_upload_format(self): @@ -517,31 +554,30 @@ class EbicsXfer(models.TransientModel): Use this method in order to automatically detect and set the EBICS upload file format. """ - pass def _update_order_number(self, OrderID): o_list = list(OrderID) for i, c in enumerate(reversed(o_list), start=1): - if c == '9': - o_list[-i] = 'A' + if c == "9": + o_list[-i] = "A" break - if c == 'Z': + if c == "Z": continue else: o_list[-i] = chr(ord(c) + 1) break - next_nr = ''.join(o_list) - if next_nr == 'ZZZZ': - next_nr = 'A000' + next_nr = "".join(o_list) + if next_nr == "ZZZZ": + next_nr = "A000" self.ebics_config_id.order_number = next_nr def _insert_line_terminator(self, data_in, line_len): - data_in = data_in.replace(b'\n', b'').replace(b'\r', b'') - data_out = b'' + data_in = data_in.replace(b"\n", b"").replace(b"\r", b"") + data_out = b"" max_len = len(data_in) i = 0 while i + line_len <= max_len: - data_out += data_in[i:i + line_len] + b'\n' + data_out += data_in[i : i + line_len] + b"\n" i += line_len return data_out diff --git a/account_ebics/wizards/ebics_xfer.xml b/account_ebics/wizards/ebics_xfer.xml index 091c08b..3dfd4d9 100644 --- a/account_ebics/wizards/ebics_xfer.xml +++ b/account_ebics/wizards/ebics_xfer.xml @@ -1,4 +1,4 @@ - + @@ -8,23 +8,36 @@
- - - - - - - - + + + + + + + +
-
@@ -37,26 +50,43 @@
- - - - - - - - - - - + + + + + + + + + + +
-
@@ -69,11 +99,16 @@
- +
-
@@ -86,7 +121,7 @@ form new {'ebics_download': 1} - +
@@ -96,7 +131,7 @@ form new {'ebics_upload': 1} - +
diff --git a/account_ebics_batch/README.rst b/account_ebics_batch/README.rst new file mode 100644 index 0000000..80f2d09 --- /dev/null +++ b/account_ebics_batch/README.rst @@ -0,0 +1,50 @@ +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: https://www.gnu.org/licenses/agpl + :alt: License: AGPL-3 + +============================================ +Module to enable batch import of EBICS files +============================================ + +This module adds a cron job for the automated import of EBICS files. + +| + +A Log is created during the import in order to document import errors. +If errors have been detected, the Batch Import Log state is set to 'error'. + +When all EBICS Files have been imported correctly, the Batch Import Log state is set to 'done'. + +| + +The user can reprocess the imported EBICS files in status 'draft' via the Log object 'REPROCESS' button until all errors have been cleared. + +As an alternative, the user can force the Batch Import Log state to 'done' +(e.g. when the errors have been circumvented via manual encoding or the reprocessing of a single EBICS file). + +| + +Configuration +============= + +Adapt the 'EBICS Batch Import' ir.cron job created during the module installation. + +The cron job calls the following python method: + +| + +.. code-block:: python + + _batch_import() + + +The EBICS download will be performed on all confirmed EBICS connections. + +You can limit the automated operation to a subset of your EBICS connections via the ebics_config_ids parameter, e.g. + +| + +.. code-block:: python + + _batch_import(ebics_config_ids=[1,3]) + diff --git a/account_ebics_batch/__init__.py b/account_ebics_batch/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/account_ebics_batch/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/account_ebics_batch/__manifest__.py b/account_ebics_batch/__manifest__.py new file mode 100644 index 0000000..b84f781 --- /dev/null +++ b/account_ebics_batch/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2009-2022 Noviat. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "EBICS Files batch import", + "version": "14.0.1.0.0", + "license": "AGPL-3", + "author": "Noviat", + "website": "http://www.noviat.com", + "category": "Accounting & Finance", + "summary": "EBICS Files automated import and processing", + "depends": ["account_ebics"], + "data": [ + "security/ir.model.access.csv", + "data/ir_cron_data.xml", + "views/ebics_batch_log_views.xml", + "views/menu.xml", + ], + "installable": True, +} diff --git a/account_ebics_batch/data/ir_cron_data.xml b/account_ebics_batch/data/ir_cron_data.xml new file mode 100644 index 0000000..d33d59d --- /dev/null +++ b/account_ebics_batch/data/ir_cron_data.xml @@ -0,0 +1,17 @@ + + + + + EBICS Batch Import + + code + model._batch_import() + + 1 + days + -1 + + + + + diff --git a/account_ebics_batch/models/__init__.py b/account_ebics_batch/models/__init__.py new file mode 100644 index 0000000..740bafa --- /dev/null +++ b/account_ebics_batch/models/__init__.py @@ -0,0 +1 @@ +from . import ebics_batch_log diff --git a/account_ebics_batch/models/ebics_batch_log.py b/account_ebics_batch/models/ebics_batch_log.py new file mode 100644 index 0000000..9dfb064 --- /dev/null +++ b/account_ebics_batch/models/ebics_batch_log.py @@ -0,0 +1,193 @@ +# Copyright 2009-2022 Noviat. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from sys import exc_info +from traceback import format_exception + +from odoo import _, api, fields, models +from odoo.exceptions import UserError + + +class EbicsBatchLog(models.Model): + _name = "ebics.batch.log" + _description = "Object to store EBICS Batch Import Logs" + _order = "create_date desc" + + date_from = fields.Date() + date_to = fields.Date() + ebics_config_ids = fields.Many2many( + comodel_name="ebics.config", string="EBICS Configurations" + ) + log_ids = fields.One2many( + comodel_name="ebics.batch.log.item", + inverse_name="log_id", + string="Batch Import Log Items", + readonly=True, + ) + file_ids = fields.Many2many( + comodel_name="ebics.file", + string="Batch Import EBICS Files", + readonly=True, + ) + file_count = fields.Integer( + string="EBICS Files Count", compute="_compute_ebics_files_fields", readonly=True + ) + has_draft_files = fields.Boolean(compute="_compute_ebics_files_fields") + state = fields.Selection( + selection=[("draft", "Draft"), ("error", "Error"), ("done", "Done")], + string="State", + required=True, + readonly=True, + default="draft", + ) + + @api.depends("file_ids") + def _compute_ebics_files_fields(self): + for rec in self: + rec.has_draft_files = "draft" in rec.file_ids.mapped("state") + rec.file_count = len(rec.file_ids) + + def unlink(self): + for log in self: + if log.state != "draft": + raise UserError(_("Only log objects in state 'draft' can be deleted !")) + return super().unlink() + + def button_draft(self): + self.state = "draft" + + def button_done(self): + self.state = "done" + + def reprocess(self): + import_dict = {"errors": []} + self._ebics_process(import_dict) + self._finalise_processing(import_dict) + + def view_ebics_files(self): + action = self.env["ir.actions.actions"]._for_xml_id( + "account_ebics.ebics_file_action_download" + ) + action["domain"] = [("id", "in", self.file_ids.ids)] + return action + + def _batch_import(self, ebics_config_ids=None, date_from=None, date_to=None): + """ + Call this method from a cron job to automate the EBICS import. + """ + log_model = self.env["ebics.batch.log"] + import_dict = {"errors": []} + configs = self.env["ebics.config"].browse(ebics_config_ids) or self.env[ + "ebics.config" + ].search( + [ + ("company_ids", "in", self.env.user.company_ids.ids), + ("state", "=", "confirm"), + ] + ) + log = log_model.create( + { + "ebics_config_ids": [(6, 0, configs.ids)], + "date_from": date_from, + "date_to": date_to, + } + ) + ebics_file_ids = [] + for config in configs: + err_msg = ( + _("Error while processing EBICS connection '%s' :\n") % config.name + ) + if config.state == "draft": + import_dict["errors"].append( + err_msg + + _( + "Please set state to 'Confirm' and " + "Reprocess this EBICS Import Log." + ) + ) + continue + try: + with self.env.cr.savepoint(): + ebics_file_ids += self._ebics_import( + config, date_from, date_to, import_dict + ) + except UserError as e: + import_dict["errors"].append(err_msg + " ".join(e.args)) + except Exception: + tb = "".join(format_exception(*exc_info())) + import_dict["errors"].append(err_msg + tb) + log.file_ids = [(6, 0, ebics_file_ids)] + try: + with self.env.cr.savepoint(): + log._ebics_process(import_dict) + except UserError as e: + import_dict["errors"].append(err_msg + " ".join(e.args)) + except Exception: + tb = "".join(format_exception(*exc_info())) + import_dict["errors"].append(err_msg + tb) + log._finalise_processing(import_dict) + + def _finalise_processing(self, import_dict): + log_item_model = self.env["ebics.batch.log.item"] + state = self.has_draft_files and "draft" or "done" + note = "" + error_count = 0 + if import_dict["errors"]: + state = "error" + note = "\n\n".join(import_dict["errors"]) + error_count = len(import_dict["errors"]) + log_item_model.create( + { + "log_id": self.id, + "state": state, + "note": note, + "error_count": error_count, + } + ) + self.state = state + + def _ebics_import(self, config, date_from, date_to, import_dict): + ctx = dict(self.env.context, ebics_download=True) + xfer_wiz = ( + self.env["ebics.xfer"] + .with_context(ctx) + .create( + { + "ebics_config_id": config.id, + "date_from": date_from, + "date_to": date_to, + } + ) + ) + xfer_wiz._onchange_ebics_config_id() + res = xfer_wiz.ebics_download() + file_ids = res["context"].get("ebics_file_ids") + if res["context"]["err_cnt"]: + import_dict["errors"].append(xfer_wiz.note) + return file_ids + + def _ebics_process(self, import_dict): + to_process = self.file_ids.filtered(lambda r: r.state == "draft") + for ebics_file in to_process: + ebics_file.process() + + +class EbicsBatchLogItem(models.Model): + _name = "ebics.batch.log.item" + _description = "Object to store EBICS Batch Import Log Items" + _order = "create_date desc" + + log_id = fields.Many2one( + comodel_name="ebics.batch.log", + string="Batch Object", + ondelete="cascade", + readonly=True, + ) + state = fields.Selection( + selection=[("draft", "Draft"), ("error", "Error"), ("done", "Done")], + string="State", + required=True, + readonly=True, + ) + note = fields.Text(string="Batch Import Log", readonly=True) + error_count = fields.Integer(string="Number of Errors", required=True, default=0) diff --git a/account_ebics_batch/security/ir.model.access.csv b/account_ebics_batch/security/ir.model.access.csv new file mode 100644 index 0000000..af76670 --- /dev/null +++ b/account_ebics_batch/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_ebics_batch_log,ebics.batch.log,model_ebics_batch_log,account.group_account_invoice,1,1,1,1 +access_ebics_batch_log_item,ebics.batch.log.item,model_ebics_batch_log_item,account.group_account_invoice,1,1,1,1 diff --git a/account_ebics_batch/static/description/icon.png b/account_ebics_batch/static/description/icon.png new file mode 100644 index 0000000..889d129 Binary files /dev/null and b/account_ebics_batch/static/description/icon.png differ diff --git a/account_ebics_batch/views/ebics_batch_log_views.xml b/account_ebics_batch/views/ebics_batch_log_views.xml new file mode 100644 index 0000000..716882f --- /dev/null +++ b/account_ebics_batch/views/ebics_batch_log_views.xml @@ -0,0 +1,118 @@ + + + + + ebics.batch.log.search + ebics.batch.log + + + + + + + + + + + + + + + + ebics.batch.log.tree + ebics.batch.log + + + + + + + + + + + ebics.batch.log.form + ebics.batch.log + +
+
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + EBICS Batch Import Logs + ir.actions.act_window + ebics.batch.log + tree,form + + + + +
diff --git a/account_ebics_batch/views/menu.xml b/account_ebics_batch/views/menu.xml new file mode 100644 index 0000000..21343b8 --- /dev/null +++ b/account_ebics_batch/views/menu.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/account_ebics_oca_statement_import/__manifest__.py b/account_ebics_oca_statement_import/__manifest__.py index d9f3fef..a141b7a 100644 --- a/account_ebics_oca_statement_import/__manifest__.py +++ b/account_ebics_oca_statement_import/__manifest__.py @@ -2,16 +2,16 @@ # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). { - 'name': 'account_ebics with OCA Bank Statement Imoort', - 'summary': "Use OCA Bank Statement Import with account_ebics", - 'version': '14.0.1.0.0', - 'author': 'Noviat', - 'category': 'Hidden', - 'license': 'LGPL-3', - 'depends': [ - 'account_ebics', - 'account_statement_import', + "name": "account_ebics with OCA Bank Statement Imoort", + "summary": "Use OCA Bank Statement Import with account_ebics", + "version": "14.0.1.0.0", + "author": "Noviat", + "category": "Hidden", + "license": "LGPL-3", + "depends": [ + "account_ebics", + "account_statement_import", ], - 'installable': True, - 'auto_install': True, + "installable": True, + "auto_install": True, } diff --git a/account_ebics_oca_statement_import/wizards/account_statement_import.py b/account_ebics_oca_statement_import/wizards/account_statement_import.py index 430d1fc..cc49e00 100644 --- a/account_ebics_oca_statement_import/wizards/account_statement_import.py +++ b/account_ebics_oca_statement_import/wizards/account_statement_import.py @@ -3,32 +3,34 @@ import logging -from odoo import models, _ +from odoo import _, models _logger = logging.getLogger(__name__) class AccountStatementImport(models.TransientModel): - _inherit = 'account.statement.import' + _inherit = "account.statement.import" def _check_parsed_data(self, stmts_vals): """ Basic and structural verifications """ - if self.env.context.get('active_model') == 'ebics.file': + if self.env.context.get("active_model") == "ebics.file": message = False if len(stmts_vals) == 0: message = _("This file doesn't contain any statement.") if not message: no_st_line = True for vals in stmts_vals: - if vals['transactions'] and len(vals['transactions']) > 0: + if vals["transactions"] and len(vals["transactions"]) > 0: no_st_line = False break if no_st_line: - message = _('This file doesn\'t contain any transaction.') + message = _("This file doesn't contain any transaction.") if message: - log_msg = _( - "Error detected while processing and EBICS File" - ) + ':\n' + message + log_msg = ( + _("Error detected while processing and EBICS File") + + ":\n" + + message + ) _logger.warn(log_msg) return return super()._check_parsed_data(stmts_vals) @@ -43,20 +45,16 @@ class AccountStatementImport(models.TransientModel): We could also create empty bank statement (in state done) to clearly show days without transactions via the bank statement list view. """ - if self.env.context.get('active_model') == 'ebics.file': + if self.env.context.get("active_model") == "ebics.file": transactions = False for st_vals in stmts_vals: - if st_vals.get('transactions'): + if st_vals.get("transactions"): transactions = True break if not transactions: - message = _('This file doesn\'t contain any transaction.') + message = _("This file doesn't contain any transaction.") st_line_ids = [] - notifications = { - 'type': 'warning', - 'message': message, - 'details': '' - } + notifications = {"type": "warning", "message": message, "details": ""} return st_line_ids, [notifications] return super()._create_bank_statements(stmts_vals, result) diff --git a/account_ebics_oe/__manifest__.py b/account_ebics_oe/__manifest__.py index cdaa6fe..43a8872 100644 --- a/account_ebics_oe/__manifest__.py +++ b/account_ebics_oe/__manifest__.py @@ -2,19 +2,17 @@ # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). { - 'name': 'account_ebics on Odoo Enterprise', - 'summary': "Deploy account_ebics module on Odoo Enterprise", - 'version': '14.0.1.0.0', - 'author': 'Noviat', - 'category': 'Hidden', - 'license': 'LGPL-3', - 'depends': [ - 'account_ebics', - 'account_accountant', + "name": "account_ebics on Odoo Enterprise", + "summary": "Deploy account_ebics module on Odoo Enterprise", + "version": "14.0.1.0.0", + "author": "Noviat", + "category": "Hidden", + "license": "LGPL-3", + "depends": [ + "account_ebics", + "account_accountant", ], - 'data': [ - 'views/account_ebics_menu.xml' - ], - 'installable': True, - 'auto_install': True, + "data": ["views/account_ebics_menu.xml"], + "installable": True, + "auto_install": True, } diff --git a/account_ebics_oe/views/account_ebics_menu.xml b/account_ebics_oe/views/account_ebics_menu.xml index 0e68143..4f9f379 100644 --- a/account_ebics_oe/views/account_ebics_menu.xml +++ b/account_ebics_oe/views/account_ebics_menu.xml @@ -1,8 +1,8 @@ - + - + diff --git a/account_ebics_oe_statement_import/__manifest__.py b/account_ebics_oe_statement_import/__manifest__.py index 794f45a..c0b0337 100644 --- a/account_ebics_oe_statement_import/__manifest__.py +++ b/account_ebics_oe_statement_import/__manifest__.py @@ -2,16 +2,16 @@ # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). { - 'name': 'account_ebics with Odoo Enterprise Bank Statement Import', - 'summary': "Use Odoo Enterprise Bank Statement Import with account_ebics", - 'version': '14.0.1.0.0', - 'author': 'Noviat', - 'category': 'Hidden', - 'license': 'LGPL-3', - 'depends': [ - 'account_ebics_oe', - 'account_bank_statement_import', + "name": "account_ebics with Odoo Enterprise Bank Statement Import", + "summary": "Use Odoo Enterprise Bank Statement Import with account_ebics", + "version": "14.0.1.0.0", + "author": "Noviat", + "category": "Hidden", + "license": "LGPL-3", + "depends": [ + "account_ebics_oe", + "account_bank_statement_import", ], - 'installable': True, - 'auto_install': True, + "installable": True, + "auto_install": True, } diff --git a/account_ebics_oe_statement_import/wizards/account_bank_statement_import.py b/account_ebics_oe_statement_import/wizards/account_bank_statement_import.py index 937f807..3748d31 100644 --- a/account_ebics_oe_statement_import/wizards/account_bank_statement_import.py +++ b/account_ebics_oe_statement_import/wizards/account_bank_statement_import.py @@ -3,32 +3,34 @@ import logging -from odoo import models, _ +from odoo import _, models _logger = logging.getLogger(__name__) class AccountBankStatementImport(models.TransientModel): - _inherit = 'account.bank.statement.import' + _inherit = "account.bank.statement.import" def _check_parsed_data(self, stmts_vals, account_number): """ Basic and structural verifications """ - if self.env.context.get('active_model') == 'ebics.file': + if self.env.context.get("active_model") == "ebics.file": message = False if len(stmts_vals) == 0: message = _("This file doesn't contain any statement.") if not message: no_st_line = True for vals in stmts_vals: - if vals['transactions'] and len(vals['transactions']) > 0: + if vals["transactions"] and len(vals["transactions"]) > 0: no_st_line = False break if no_st_line: - message = _('This file doesn\'t contain any transaction.') + message = _("This file doesn't contain any transaction.") if message: - log_msg = _( - "Error detected while processing and EBICS File" - ) + ':\n' + message + log_msg = ( + _("Error detected while processing and EBICS File") + + ":\n" + + message + ) _logger.warn(log_msg) return super()._check_parsed_data(stmts_vals, account_number) @@ -43,20 +45,16 @@ class AccountBankStatementImport(models.TransientModel): We could also create empty bank statement (in state done) to clearly show days without transactions via the bank statement list view. """ - if self.env.context.get('active_model') == 'ebics.file': + if self.env.context.get("active_model") == "ebics.file": transactions = False for st_vals in stmts_vals: - if st_vals.get('transactions'): + if st_vals.get("transactions"): transactions = True break if not transactions: - message = _('This file doesn\'t contain any transaction.') + message = _("This file doesn't contain any transaction.") st_line_ids = [] - notifications = { - 'type': 'warning', - 'message': message, - 'details': '' - } + notifications = {"type": "warning", "message": message, "details": ""} return st_line_ids, [notifications] return super()._create_bank_statements(stmts_vals) diff --git a/account_ebics_payment_order/__manifest__.py b/account_ebics_payment_order/__manifest__.py index 5836ab7..35ad8a2 100644 --- a/account_ebics_payment_order/__manifest__.py +++ b/account_ebics_payment_order/__manifest__.py @@ -2,16 +2,14 @@ # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). { - 'name': 'Upload Payment Order via EBICS', - 'version': '14.0.1.0.0', - 'license': 'LGPL-3', - 'author': 'Noviat', - 'category': 'Accounting & Finance', - 'depends': [ - 'account_ebics', - 'account_payment_order'], - 'data': [ - 'views/account_payment_order.xml', + "name": "Upload Payment Order via EBICS", + "version": "14.0.1.0.0", + "license": "LGPL-3", + "author": "Noviat", + "category": "Accounting & Finance", + "depends": ["account_ebics", "account_payment_order"], + "data": [ + "views/account_payment_order.xml", ], - 'installable': True, + "installable": True, } diff --git a/account_ebics_payment_order/models/account_payment_order.py b/account_ebics_payment_order/models/account_payment_order.py index 64e9442..081e927 100644 --- a/account_ebics_payment_order/models/account_payment_order.py +++ b/account_ebics_payment_order/models/account_payment_order.py @@ -6,57 +6,69 @@ from odoo.exceptions import UserError class AccountPaymentOrder(models.Model): - _inherit = 'account.payment.order' + _inherit = "account.payment.order" def ebics_upload(self): self.ensure_one() ctx = self._context.copy() - attach = self.env['ir.attachment'].search( - [('res_model', '=', self._name), - ('res_id', '=', self.id)]) + attach = self.env["ir.attachment"].search( + [("res_model", "=", self._name), ("res_id", "=", self.id)] + ) if not attach: - raise UserError(_( - "This payment order doesn't contains attachements." - "\nPlease generate first the Payment Order file first.")) + raise UserError( + _( + "This payment order doesn't contains attachements." + "\nPlease generate first the Payment Order file first." + ) + ) elif len(attach) > 1: - raise UserError(_( - "This payment order contains multiple attachments." - "\nPlease remove the obsolete attachments or upload " - "the payment order file via the " - "EBICS Processing > EBICS Upload menu")) + raise UserError( + _( + "This payment order contains multiple attachments." + "\nPlease remove the obsolete attachments or upload " + "the payment order file via the " + "EBICS Processing > EBICS Upload menu" + ) + ) else: - origin = _("Payment Order") + ': ' + self.name - ebics_config = self.env['ebics.config'].search([ - ('journal_ids', '=', self.journal_id.id), - ('state', '=', 'confirm'), - ]) + origin = _("Payment Order") + ": " + self.name + ebics_config = self.env["ebics.config"].search( + [ + ("journal_ids", "=", self.journal_id.id), + ("state", "=", "confirm"), + ] + ) if not ebics_config: - raise UserError(_( - "No active EBICS configuration available " - "for the selected bank." - )) + raise UserError( + _( + "No active EBICS configuration available " + "for the selected bank." + ) + ) if len(ebics_config) == 1: ctx["default_ebics_config_id"] = ebics_config.id - ctx.update({ - 'default_upload_data': attach.datas, - 'default_upload_fname': attach.name, - 'origin': origin, - 'force_comany': self.company_id.id, - }) - ebics_xfer = self.env['ebics.xfer'].with_context(ctx).create({}) + ctx.update( + { + "default_upload_data": attach.datas, + "default_upload_fname": attach.name, + "origin": origin, + "force_comany": self.company_id.id, + } + ) + ebics_xfer = self.env["ebics.xfer"].with_context(ctx).create({}) ebics_xfer._onchange_ebics_config_id() ebics_xfer._onchange_upload_data() ebics_xfer._onchange_format_id() - view = self.env.ref('account_ebics.ebics_xfer_view_form_upload') + view = self.env.ref("account_ebics.ebics_xfer_view_form_upload") act = { - 'name': _('EBICS Upload'), - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'ebics.xfer', - 'view_id': view.id, - 'res_id': ebics_xfer.id, - 'type': 'ir.actions.act_window', - 'target': 'new', - 'context': ctx, + "name": _("EBICS Upload"), + "view_type": "form", + "view_mode": "form", + "res_model": "ebics.xfer", + "view_id": view.id, + "res_id": ebics_xfer.id, + "type": "ir.actions.act_window", + "target": "new", + "context": ctx, } return act diff --git a/account_ebics_payment_order/views/account_payment_order.xml b/account_ebics_payment_order/views/account_payment_order.xml index 9bd43c8..8f81e20 100644 --- a/account_ebics_payment_order/views/account_payment_order.xml +++ b/account_ebics_payment_order/views/account_payment_order.xml @@ -1,15 +1,20 @@ - + account.payment.order.form account.payment.order - + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6c420f9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +# generated from manifests external_dependencies +cryptography +fintech diff --git a/setup/.setuptools-odoo-make-default-ignore b/setup/.setuptools-odoo-make-default-ignore new file mode 100644 index 0000000..207e615 --- /dev/null +++ b/setup/.setuptools-odoo-make-default-ignore @@ -0,0 +1,2 @@ +# addons listed in this file are ignored by +# setuptools-odoo-make-default (one addon per line) diff --git a/setup/README b/setup/README new file mode 100644 index 0000000..a63d633 --- /dev/null +++ b/setup/README @@ -0,0 +1,2 @@ +To learn more about this directory, please visit +https://pypi.python.org/pypi/setuptools-odoo diff --git a/setup/account_ebics/odoo/addons/account_ebics b/setup/account_ebics/odoo/addons/account_ebics new file mode 120000 index 0000000..16c1742 --- /dev/null +++ b/setup/account_ebics/odoo/addons/account_ebics @@ -0,0 +1 @@ +../../../../account_ebics \ No newline at end of file diff --git a/setup/account_ebics/setup.py b/setup/account_ebics/setup.py new file mode 100644 index 0000000..28c57bb --- /dev/null +++ b/setup/account_ebics/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/setup/account_ebics_batch/odoo/addons/account_ebics_batch b/setup/account_ebics_batch/odoo/addons/account_ebics_batch new file mode 120000 index 0000000..d1e7e96 --- /dev/null +++ b/setup/account_ebics_batch/odoo/addons/account_ebics_batch @@ -0,0 +1 @@ +../../../../account_ebics_batch \ No newline at end of file diff --git a/setup/account_ebics_batch/setup.py b/setup/account_ebics_batch/setup.py new file mode 100644 index 0000000..28c57bb --- /dev/null +++ b/setup/account_ebics_batch/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/setup/account_ebics_oca_statement_import/odoo/addons/account_ebics_oca_statement_import b/setup/account_ebics_oca_statement_import/odoo/addons/account_ebics_oca_statement_import new file mode 120000 index 0000000..766f43e --- /dev/null +++ b/setup/account_ebics_oca_statement_import/odoo/addons/account_ebics_oca_statement_import @@ -0,0 +1 @@ +../../../../account_ebics_oca_statement_import \ No newline at end of file diff --git a/setup/account_ebics_oca_statement_import/setup.py b/setup/account_ebics_oca_statement_import/setup.py new file mode 100644 index 0000000..28c57bb --- /dev/null +++ b/setup/account_ebics_oca_statement_import/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/setup/account_ebics_oe/odoo/addons/account_ebics_oe b/setup/account_ebics_oe/odoo/addons/account_ebics_oe new file mode 120000 index 0000000..bcdee8e --- /dev/null +++ b/setup/account_ebics_oe/odoo/addons/account_ebics_oe @@ -0,0 +1 @@ +../../../../account_ebics_oe \ No newline at end of file diff --git a/setup/account_ebics_oe/setup.py b/setup/account_ebics_oe/setup.py new file mode 100644 index 0000000..28c57bb --- /dev/null +++ b/setup/account_ebics_oe/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/setup/account_ebics_oe_statement_import/odoo/addons/account_ebics_oe_statement_import b/setup/account_ebics_oe_statement_import/odoo/addons/account_ebics_oe_statement_import new file mode 120000 index 0000000..75aca1f --- /dev/null +++ b/setup/account_ebics_oe_statement_import/odoo/addons/account_ebics_oe_statement_import @@ -0,0 +1 @@ +../../../../account_ebics_oe_statement_import \ No newline at end of file diff --git a/setup/account_ebics_oe_statement_import/setup.py b/setup/account_ebics_oe_statement_import/setup.py new file mode 100644 index 0000000..28c57bb --- /dev/null +++ b/setup/account_ebics_oe_statement_import/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/setup/account_ebics_payment_order/odoo/addons/account_ebics_payment_order b/setup/account_ebics_payment_order/odoo/addons/account_ebics_payment_order new file mode 120000 index 0000000..5ff2d5c --- /dev/null +++ b/setup/account_ebics_payment_order/odoo/addons/account_ebics_payment_order @@ -0,0 +1 @@ +../../../../account_ebics_payment_order \ No newline at end of file diff --git a/setup/account_ebics_payment_order/setup.py b/setup/account_ebics_payment_order/setup.py new file mode 100644 index 0000000..28c57bb --- /dev/null +++ b/setup/account_ebics_payment_order/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)