From 79250ab61ad9c76a2420e8fa3209b4768be9ce23 Mon Sep 17 00:00:00 2001 From: Luc De Meyer Date: Sat, 7 May 2022 18:42:03 +0200 Subject: [PATCH] [14.0]add account_ebics_batch --- .copier-answers.yml | 13 + .editorconfig | 20 + .eslintrc.yml | 180 +++++++ .flake8 | 10 + .isort.cfg | 12 + .pre-commit-config.yaml | 135 +++++ .prettierrc.yml | 8 + .pylintrc | 88 ++++ .pylintrc-mandatory | 64 +++ .travis.yml | 42 ++ account_ebics/__manifest__.py | 52 +- account_ebics/wizards/ebics_xfer.py | 472 ++++++++++-------- account_ebics_batch/README.rst | 50 ++ account_ebics_batch/__init__.py | 1 + account_ebics_batch/__manifest__.py | 20 + account_ebics_batch/data/ir_cron_data.xml | 17 + account_ebics_batch/models/__init__.py | 1 + account_ebics_batch/models/ebics_batch_log.py | 193 +++++++ .../security/ir.model.access.csv | 3 + .../static/description/icon.png | Bin 0 -> 17137 bytes .../views/ebics_batch_log_views.xml | 118 +++++ account_ebics_batch/views/menu.xml | 12 + requirements.txt | 3 + setup/.setuptools-odoo-make-default-ignore | 2 + setup/README | 2 + setup/account_ebics/odoo/addons/account_ebics | 1 + setup/account_ebics/setup.py | 6 + .../odoo/addons/account_ebics_batch | 1 + setup/account_ebics_batch/setup.py | 6 + .../addons/account_ebics_oca_statement_import | 1 + .../setup.py | 6 + .../odoo/addons/account_ebics_oe | 1 + setup/account_ebics_oe/setup.py | 6 + .../addons/account_ebics_oe_statement_import | 1 + .../setup.py | 6 + .../odoo/addons/account_ebics_payment_order | 1 + setup/account_ebics_payment_order/setup.py | 6 + 37 files changed, 1316 insertions(+), 244 deletions(-) create mode 100644 .copier-answers.yml create mode 100644 .editorconfig create mode 100644 .eslintrc.yml create mode 100644 .flake8 create mode 100644 .isort.cfg create mode 100644 .pre-commit-config.yaml create mode 100644 .prettierrc.yml create mode 100644 .pylintrc create mode 100644 .pylintrc-mandatory create mode 100644 .travis.yml create mode 100644 account_ebics_batch/README.rst create mode 100644 account_ebics_batch/__init__.py create mode 100644 account_ebics_batch/__manifest__.py create mode 100644 account_ebics_batch/data/ir_cron_data.xml create mode 100644 account_ebics_batch/models/__init__.py create mode 100644 account_ebics_batch/models/ebics_batch_log.py create mode 100644 account_ebics_batch/security/ir.model.access.csv create mode 100644 account_ebics_batch/static/description/icon.png create mode 100644 account_ebics_batch/views/ebics_batch_log_views.xml create mode 100644 account_ebics_batch/views/menu.xml create mode 100644 requirements.txt create mode 100644 setup/.setuptools-odoo-make-default-ignore create mode 100644 setup/README create mode 120000 setup/account_ebics/odoo/addons/account_ebics create mode 100644 setup/account_ebics/setup.py create mode 120000 setup/account_ebics_batch/odoo/addons/account_ebics_batch create mode 100644 setup/account_ebics_batch/setup.py create mode 120000 setup/account_ebics_oca_statement_import/odoo/addons/account_ebics_oca_statement_import create mode 100644 setup/account_ebics_oca_statement_import/setup.py create mode 120000 setup/account_ebics_oe/odoo/addons/account_ebics_oe create mode 100644 setup/account_ebics_oe/setup.py create mode 120000 setup/account_ebics_oe_statement_import/odoo/addons/account_ebics_oe_statement_import create mode 100644 setup/account_ebics_oe_statement_import/setup.py create mode 120000 setup/account_ebics_payment_order/odoo/addons/account_ebics_payment_order create mode 100644 setup/account_ebics_payment_order/setup.py 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/__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/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_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 0000000000000000000000000000000000000000..889d1294392923a091fba44560342126a11c1aac GIT binary patch literal 17137 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 literal 0 KcmV+b0RR6000031 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/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, +)