From e3943c7d3137c3499a0ecb47f9e99eb26dbf3b3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20-=20Le=20Filament?= <remi@le-filament.com> Date: Thu, 28 Apr 2022 16:27:51 +0200 Subject: [PATCH] [IMP] pre-commit --- .editorconfig | 20 + .eslintrc.yml | 187 +++++++++ .flake8 | 12 + .gitignore | 75 ++++ .isort.cfg | 13 + .pre-commit-config.yaml | 127 +++++++ .prettierrc.yml | 8 + .pylintrc | 87 +++++ .pylintrc-mandatory | 64 ++++ LICENSE | 0 README.rst | 1 - __init__.py | 2 +- __manifest__.py | 26 +- models/__init__.py | 2 +- models/account_invoice.py | 74 ++-- models/lefilament_alfresco.py | 74 ++-- models/project.py | 651 +++++++++++++++++++------------- models/project_task.py | 111 +++--- security/ir.model.access.csv | 2 +- static/description/icon.png | Bin views/account_invoice_views.xml | 8 +- views/project_views.xml | 219 +++++++---- 22 files changed, 1309 insertions(+), 454 deletions(-) create mode 100644 .editorconfig create mode 100644 .eslintrc.yml create mode 100644 .flake8 create mode 100644 .gitignore 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 mode change 100755 => 100644 LICENSE mode change 100755 => 100644 README.rst mode change 100755 => 100644 __init__.py mode change 100755 => 100644 __manifest__.py mode change 100755 => 100644 models/__init__.py mode change 100755 => 100644 models/project.py mode change 100755 => 100644 models/project_task.py mode change 100755 => 100644 security/ir.model.access.csv mode change 100755 => 100644 static/description/icon.png mode change 100755 => 100644 views/project_views.xml 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..d4cc423 --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,187 @@ +env: + browser: true + es6: true + +# See https://github.com/OCA/odoo-community.org/issues/37#issuecomment-470686449 +parserOptions: + ecmaVersion: 2017 + +overrides: + - files: + - "**/*.esm.js" + parserOptions: + sourceType: module + +# Globals available in Odoo that shouldn't produce errorings +globals: + _: readonly + $: readonly + fuzzy: readonly + jQuery: readonly + moment: readonly + odoo: readonly + openerp: readonly + owl: 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..e397e8e --- /dev/null +++ b/.flake8 @@ -0,0 +1,12 @@ +[flake8] +max-line-length = 88 +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 +per-file-ignores= + __init__.py:F401 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..818770f --- /dev/null +++ b/.gitignore @@ -0,0 +1,75 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +/.venv +/.pytest_cache + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg +*.eggs + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Pycharm +.idea + +# Eclipse +.settings + +# Visual Studio cache/options directory +.vs/ +.vscode + +# OSX Files +.DS_Store + +# Django stuff: +*.log + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Sphinx documentation +docs/_build/ + +# Backup files +*~ +*.swp + +# OCA rules +!static/lib/ diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000..0ec187e --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,13 @@ +[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 +ensure_newline_before_comments = True diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..1c6434b --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,127 @@ +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$|/tests/([^/]+/)?cassettes/|^.copier-answers.yml$|^.github/| + # 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://le-filament.com"] + - repo: https://github.com/myint/autoflake + rev: v1.4 + hooks: + - id: autoflake + args: + - --expand-star-imports + - --ignore-init-module-imports + - --in-place + - --remove-all-unused-imports + - --remove-duplicate-keys + - --remove-unused-variables + - 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 (with plugin-xml) + additional_dependencies: + - "prettier@2.1.2" + - "@prettier/plugin-xml@0.12.0" + args: + - --plugin=@prettier/plugin-xml + files: \.(css|htm|html|js|json|jsx|less|md|scss|toml|ts|xml|yaml|yml)$ + - 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 + args: ["--keep-percent-format"] + - 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://gitlab.com/PyCQA/flake8 + rev: 3.8.3 + hooks: + - id: flake8 + name: flake8 + 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..30c91fa --- /dev/null +++ b/.pylintrc @@ -0,0 +1,87 @@ +[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=Le Filament +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=12.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, + 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..e37fa65 --- /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=Le Filament +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=12.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/LICENSE b/LICENSE old mode 100755 new mode 100644 diff --git a/README.rst b/README.rst old mode 100755 new mode 100644 index 81e27ee..550be66 --- a/README.rst +++ b/README.rst @@ -33,4 +33,3 @@ Maintainer :target: https://le-filament.com This module is maintained by Le Filament - diff --git a/__init__.py b/__init__.py old mode 100755 new mode 100644 index 59fb482..06b0d57 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,4 @@ # Copyright 2020 Le Filament (<http://www.le-filament.com>) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from . import models \ No newline at end of file +from . import models diff --git a/__manifest__.py b/__manifest__.py old mode 100755 new mode 100644 index 66da64d..620d246 --- a/__manifest__.py +++ b/__manifest__.py @@ -1,16 +1,14 @@ { - 'name': "ADEFPAT - Gestion des avenants", - 'summary': """Gestion des avenants""", - 'author': "Le Filament", - 'website': "https://www.le-filament.com", - 'version': '12.0.1.0.1', - 'license': "AGPL-3", - 'depends': [ - 'adefpat_project', 'adefpat_alfresco_generation', 'adefpat_account'], - 'data': [ - 'security/ir.model.access.csv', - 'views/account_invoice_views.xml', - 'views/project_views.xml', + "name": "ADEFPAT - Gestion des avenants", + "summary": """Gestion des avenants""", + "author": "Le Filament", + "website": "https://www.le-filament.com", + "version": "12.0.1.0.1", + "license": "AGPL-3", + "depends": ["adefpat_project", "adefpat_alfresco_generation", "adefpat_account"], + "data": [ + "security/ir.model.access.csv", + "views/account_invoice_views.xml", + "views/project_views.xml", ], - -} \ No newline at end of file +} diff --git a/models/__init__.py b/models/__init__.py old mode 100755 new mode 100644 index 683bf62..d611a8c --- a/models/__init__.py +++ b/models/__init__.py @@ -3,4 +3,4 @@ from . import project from . import account_invoice -from . import lefilament_alfresco \ No newline at end of file +from . import lefilament_alfresco diff --git a/models/account_invoice.py b/models/account_invoice.py index 1fb667a..47e9943 100644 --- a/models/account_invoice.py +++ b/models/account_invoice.py @@ -2,12 +2,13 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import base64 -from odoo import fields, models, api + +from odoo import _, api, fields, models from odoo.exceptions import UserError class AccountInvoice(models.Model): - _inherit = 'account.invoice' + _inherit = "account.invoice" # ------------------------------------------------------ # Fields declaration @@ -43,46 +44,60 @@ class AccountInvoice(models.Model): # ------------------------------------------------------ @api.multi def generate_alfresco_file(self): + """ + Ajoute un fichier sur la GED Alfresco + @return: fonction get_partner_files() de res.partner + """ if self.is_avenant: - """ - Ajoute un fichier sur la GED Alfresco - @return: fonction get_partner_files() de res.partner - """ # Get proof folder nodeRef - backend = self.env['cmis.backend'].search([], limit=1) + backend = self.env["cmis.backend"].search([], limit=1) # Si la facture a déjà été créée, ouvrir dans Alfresco if self.alfresco_file: - prop = backend.get_cmis_repository().getFolder( - self.alfresco_file).getProperties() + prop = ( + backend.get_cmis_repository() + .getFolder(self.alfresco_file) + .getProperties() + ) url = backend.get_content_details_url_from_props(prop) - # Sinon, la créer, puis la stocker ainsi que les documents liées à la facture dans Alfresco + # Sinon, la créer, puis la stocker ainsi que les documents liées à la + # facture dans Alfresco else: project_folder = self.project_id.cmis_folder self.cmis_folder = project_folder if not project_folder: - raise UserError("Le dossier du projet n'est pas configuré") + raise UserError(_("Le dossier du projet n'est pas configuré")) # Get Mimetype - attachment = self.env.ref('account.account_invoices').retrieve_attachment(self) + attachment = self.env.ref( + "account.account_invoices" + ).retrieve_attachment(self) if not attachment: raise UserError( - "La facture n'a pas encore été générée. Imprimer la facture pour pouvoir la visualiser dans Alfresco") + _( + "La facture n'a pas encore été générée. " + "Imprimer la facture pour pouvoir la visualiser dans Alfresco" + ) + ) content_type = attachment.mimetype - path_proj = backend.get_cmis_repository().getFolder( - project_folder).getPaths() + path_proj = ( + backend.get_cmis_repository().getFolder(project_folder).getPaths() + ) # Get template doc Facture - template_doc = self.env['adefpat.template.doc'].search([ - ('type_temp', '=', 'facture_av'), - ('noderef_document', '=', False)]) + template_doc = self.env["adefpat.template.doc"].search( + [("type_temp", "=", "facture_av"), ("noderef_document", "=", False)] + ) # Get doc linked to Facture - template_doc_ids = self.env['adefpat.template.doc'].search([ - ('type_temp', '=', 'facture_av'), - ('noderef_document', '!=', False)]) + template_doc_ids = self.env["adefpat.template.doc"].search( + [ + ("type_temp", "=", "facture_av"), + ("noderef_document", "!=", False), + ] + ) # Strore template in Alfresco for template in template_doc_ids: @@ -90,24 +105,23 @@ class AccountInvoice(models.Model): self._publipostage_documents(template, keys, False) path_n0 = path_proj + [template_doc.dossier.name] - path = '/'.join(path_n0) + path = "/".join(path_n0) cmis_obj = backend.get_folder_by_path(path) file = cmis_obj.createDocument( name=attachment.name, properties={}, contentFile=base64.b64decode(attachment.datas), - contentType=content_type + contentType=content_type, ) - file_id = file.getProperties().get('cmis:objectId') + file_id = file.getProperties().get("cmis:objectId") self.alfresco_file = file.getObjectId() - prop = backend.get_cmis_repository().getFolder( - file_id).getProperties() + prop = backend.get_cmis_repository().getFolder(file_id).getProperties() url = backend.get_content_details_url_from_props(prop) return { - 'type': 'ir.actions.act_url', - 'url': url, - 'target': 'new', + "type": "ir.actions.act_url", + "url": url, + "target": "new", } else: - return super(AccountInvoice, self).generate_alfresco_file() \ No newline at end of file + return super(AccountInvoice, self).generate_alfresco_file() diff --git a/models/lefilament_alfresco.py b/models/lefilament_alfresco.py index f0d2a46..06e8a8a 100644 --- a/models/lefilament_alfresco.py +++ b/models/lefilament_alfresco.py @@ -1,19 +1,21 @@ # Copyright 2021 Le Filament (<http://www.le-filament.com>) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import fields, models, api +from odoo import models + class LeFilamentAlfresco(models.AbstractModel): - """ Appelle l'API alfresco et implémente les fonctions suivantes : + """Appelle l'API alfresco et implémente les fonctions suivantes :""" - """ - _inherit = 'lefilament.alfresco' + _inherit = "lefilament.alfresco" - def _publipostage_documents_malette_avenant(self, template_doc, keys_obj=None, versionable=None): - url = '/alfresco/s/publipostage' + def _publipostage_documents_malette_avenant( + self, template_doc, keys_obj=None, versionable=None + ): + url = "/alfresco/s/publipostage" # Récupération du backend - backend_name = self._fields['cmis_folder'] + backend_name = self._fields["cmis_folder"] backend = backend_name.get_backend(self.env) # Path du dossier projet @@ -21,43 +23,61 @@ class LeFilamentAlfresco(models.AbstractModel): # If not parent_id, path = dossier.name path_all, dossier_id = template_doc.get_path_all() - if dossier_id.model_id.model == 'res.partner.porteur.project': - object_ids = self.env['res.partner.porteur.project'].search([ - ('project_id', '=', self.id), - ('is_malette_ok', '=', False) - ]) + if dossier_id.model_id.model == "res.partner.porteur.project": + object_ids = self.env["res.partner.porteur.project"].search( + [("project_id", "=", self.id), ("is_malette_ok", "=", False)] + ) for obj in object_ids: if obj.cmis_folder: - parent_path = backend.get_cmis_repository().getFolder( - obj.cmis_folder).getPaths() + parent_path = ( + backend.get_cmis_repository() + .getFolder(obj.cmis_folder) + .getPaths() + ) if path_all: path_n0 = parent_path + [path_all] else: path_n0 = parent_path - path = '/'.join(path_n0) + path = "/".join(path_n0) keys = obj.fill_data() keys_obj.update(keys) # Document PDF - cmis_obj_nodeRef = backend.get_folder_by_path(path).getProperties()['alfcmis:nodeRef'] - mimetype = template_doc.name.split('.')[1] - if mimetype == 'pdf': + cmis_obj_nodeRef = backend.get_folder_by_path(path).getProperties()[ + "alfcmis:nodeRef" + ] + mimetype = template_doc.name.split(".")[1] + if mimetype == "pdf": # get ID document to copy - id_doc = template_doc.noderef_document.replace('workspace://SpacesStore/', '') + id_doc = template_doc.noderef_document.replace( + "workspace://SpacesStore/", "" + ) # Get ID to noderef Folder parent - id_parent = cmis_obj_nodeRef.replace('workspace://SpacesStore/', '') - uri = '/alfresco/s/slingshot/doclib/action/copy-to/node/workspace/SpacesStore/' + id_parent - workspace = 'workspace://SpacesStore/' + id_doc - workspace_parent = 'workspace://SpacesStore/' + id_parent + id_parent = cmis_obj_nodeRef.replace( + "workspace://SpacesStore/", "" + ) + uri = ( + "/alfresco/s/slingshot/doclib/action/copy-to/" + "node/workspace/SpacesStore/" + id_parent + ) + workspace = "workspace://SpacesStore/" + id_doc + workspace_parent = "workspace://SpacesStore/" + id_parent # Copy the document and retur the objectId Created nodeDoc = self.copy_document( url=uri, workspace=workspace, workspace_parent=workspace_parent, - backend=backend) + backend=backend, + ) self.update_doc_properties(template_doc, nodeDoc) else: - document = self.publipostage_documents(url, path, template_doc, backend, keys_obj, versionable) - nodeDoc = document.json().get('nodeRef', '').replace('workspace://SpacesStore/', '') - self.update_doc_properties(template_doc, nodeDoc) \ No newline at end of file + document = self.publipostage_documents( + url, path, template_doc, backend, keys_obj, versionable + ) + nodeDoc = ( + document.json() + .get("nodeRef", "") + .replace("workspace://SpacesStore/", "") + ) + self.update_doc_properties(template_doc, nodeDoc) diff --git a/models/project.py b/models/project.py old mode 100755 new mode 100644 index f09d4fb..38809c6 --- a/models/project.py +++ b/models/project.py @@ -1,9 +1,11 @@ # Copyright 2021 Le Filament (<http://www.le-filament.com>) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from datetime import datetime, timedelta +from datetime import timedelta + import babel.dates -from odoo import fields, api, models + +from odoo import _, api, fields, models from odoo.exceptions import UserError @@ -12,11 +14,15 @@ class Project(models.Model): is_avenant = fields.Boolean("Avenant lancé", default=False) date_ca_av = fields.Date("Date de CA Avenant") - date_ca_av_next = fields.Date("Lendemain date de CA Avenant", compute='_compute_date_ca_av_next') + date_ca_av_next = fields.Date( + "Lendemain date de CA Avenant", compute="_compute_date_ca_av_next" + ) # Champs étape "Instruction" => Onglet Dossier Avenants contexte_av = fields.Text("Rappel du contexte projet") caract_beneficiaire_av = fields.Text("Caractéristiques du bénéficiaire") - resultats_accompagnement = fields.Text("Résultats de l'accompagnement et besoins subsistants") + resultats_accompagnement = fields.Text( + "Résultats de l'accompagnement et besoins subsistants" + ) objectifs_accompagnement_av = fields.Text("Objectifs d’accompagnement") besoins_beneficiaires_av = fields.Text("Besoins des bénéficiaires") @@ -27,68 +33,75 @@ class Project(models.Model): lieu_av = fields.Text("Lieu") periode_realisation_av = fields.Text("Période de réalisation") - modalite_gap_av = fields.Text( - "Avenant - Modalités GAP") + modalite_gap_av = fields.Text("Avenant - Modalités GAP") # Info Budget nb_jours_adefpat_av = fields.Float("Nombre de jours CFD") cout_jour_adefpat_av = fields.Float("Coût jour CFD") nb_jour_theorique_tot_av = fields.Float( - "Nombre de jours théoriques total", - compute='_compute_nb_jour_theorique_tot_av') + "Nombre de jours théoriques total", compute="_compute_nb_jour_theorique_tot_av" + ) nb_jour_theorique_tot_global = fields.Float( "Nombre de jours théoriques au global", - compute='_compute_nb_jour_theorique_tot_global') + compute="_compute_nb_jour_theorique_tot_global", + ) nb_jour_pratique_tot_av = fields.Float( - "Nombre de jours pratiques total", - compute='_compute_nb_jour_pratique_tot_av') + "Nombre de jours pratiques total", compute="_compute_nb_jour_pratique_tot_av" + ) nb_jour_tot_av = fields.Float( - "Nombre de jours total", - compute='_compute_nb_jour_tot_av', - store=True) + "Nombre de jours total", compute="_compute_nb_jour_tot_av", store=True + ) nb_heure_tot_av = fields.Float( - "Nombre d'heures' total", - compute='_compute_nb_jour_tot_av', - store=True) + "Nombre d'heures' total", compute="_compute_nb_jour_tot_av", store=True + ) total_cout_adefpat_av = fields.Float( - "Total coûts CFD", - compute='_compute_total_cout_adefpat_av') + "Total coûts CFD", compute="_compute_total_cout_adefpat_av" + ) financement_adefpat_av = fields.Float( - "Financement Adefpat", - compute='_compute_financement_adefpat_av') + "Financement Adefpat", compute="_compute_financement_adefpat_av" + ) financement_ids = fields.One2many( - 'adefpat.project.financement', - 'project_id', - domain=[('type_avenant', '=', False)], - string="Financements") + "adefpat.project.financement", + "project_id", + domain=[("type_avenant", "=", False)], + string="Financements", + ) cout_ids = fields.One2many( - 'adefpat.project.cout', - 'project_id', - domain=[('type_avenant', '=', False)], - string="Coûts") + "adefpat.project.cout", + "project_id", + domain=[("type_avenant", "=", False)], + string="Coûts", + ) financement_av_ids = fields.One2many( - 'adefpat.project.financement', - 'project_id', - domain=[('type_avenant', '=', True)], - string="Financements Avenants") - total_financement_av = fields.Float("Total autres Avenants", compute='_compute_total_financment_av') + "adefpat.project.financement", + "project_id", + domain=[("type_avenant", "=", True)], + string="Financements Avenants", + ) + total_financement_av = fields.Float( + "Total autres Avenants", compute="_compute_total_financment_av" + ) cout_av_ids = fields.One2many( - 'adefpat.project.cout.av', - 'project_id', - string="Coûts Avenants") + "adefpat.project.cout.av", "project_id", string="Coûts Avenants" + ) - total_cout_av = fields.Float("Total coûts Avenants", compute='_compute_total_cout_av') + total_cout_av = fields.Float( + "Total coûts Avenants", compute="_compute_total_cout_av" + ) # honoraire_intervenant = fields.Float( # "Honoraires d'intervenants") total_budget_cout_av = fields.Float( - "Total budget coûts", - compute='_compute_total_budget_cout_av') - total_budget_financement_av = fields.Float("Total budget financements", compute='_compute_total_budget_financement_av') + "Total budget coûts", compute="_compute_total_budget_cout_av" + ) + total_budget_financement_av = fields.Float( + "Total budget financements", compute="_compute_total_budget_financement_av" + ) explication_financement_av = fields.Text( - "Explication financement porteur de projet",) + "Explication financement porteur de projet", + ) # ------------------------------------------------------ # Actions @@ -96,112 +109,144 @@ class Project(models.Model): @api.multi def fill_avenant(self): for project in self: - project.write({ - 'is_avenant': True, - 'contexte_av': project.contexte, - 'caract_beneficiaire_av': project.caract_beneficiaire, - 'objectifs_accompagnement_av': project.objectifs_accompagnement, - 'besoins_beneficiaires_av': project.besoins_beneficiaires, - 'contenu_formation_av': project.contenu_formation, - 'methode_savoir_av': project.methode_savoir, - 'travaux_intersessions_av': project.travaux_intersessions, - 'lieu_av': project.lieu, - 'periode_realisation_av': project.periode_realisation, - 'modalite_gap_av': project.modalite_gap, - 'explication_financement_av': project.explication_financement, - 'nb_jours_adefpat_av': project.nb_jours_adefpat, - 'cout_jour_adefpat_av': project.cout_jour_adefpat - }) + project.write( + { + "is_avenant": True, + "contexte_av": project.contexte, + "caract_beneficiaire_av": project.caract_beneficiaire, + "objectifs_accompagnement_av": project.objectifs_accompagnement, + "besoins_beneficiaires_av": project.besoins_beneficiaires, + "contenu_formation_av": project.contenu_formation, + "methode_savoir_av": project.methode_savoir, + "travaux_intersessions_av": project.travaux_intersessions, + "lieu_av": project.lieu, + "periode_realisation_av": project.periode_realisation, + "modalite_gap_av": project.modalite_gap, + "explication_financement_av": project.explication_financement, + "nb_jours_adefpat_av": project.nb_jours_adefpat, + "cout_jour_adefpat_av": project.cout_jour_adefpat, + } + ) def fill_data(self): res = super(Project, self).fill_data() if self.is_avenant and self.date_ca_av_next: - res['##date_ca_next##'] = babel.dates.format_date(date=self.date_ca_av_next, format='dd MMMM Y', - locale=self._context.get('lang') or 'en_US') + res["##date_ca_next##"] = babel.dates.format_date( + date=self.date_ca_av_next, + format="dd MMMM Y", + locale=self._context.get("lang") or "en_US", + ) return res def fill_data_av(self): parser = [ - 'name', - 'name:NAME', - 'date_ca_av', - + "name", + "name:NAME", + "date_ca_av", # Entête Dossier CA - 'name_subtitle', - 'num_dossier', + "name_subtitle", + "num_dossier", # Critères dossier CA - 'nb_activité', - 'nb_emplois', - 'type_beneficiaire', - + "nb_activité", + "nb_emplois", + "type_beneficiaire", # Blocs CA - 'contenu_formation_av', - 'methode_savoir_av', - 'travaux_intersessions_av', - 'lieu_av', - 'periode_realisation_av', - 'explication_financement_av', - 'modalite_gap_av', - 'resultats_accompagnement', - + "contenu_formation_av", + "methode_savoir_av", + "travaux_intersessions_av", + "lieu_av", + "periode_realisation_av", + "explication_financement_av", + "modalite_gap_av", + "resultats_accompagnement", # CDC - 'contexte_av', - 'caract_beneficiaire_av', - 'historique', - 'besoins_beneficiaires_av', - 'objectifs_accompagnement_av', - 'competences_requises', - 'secteurs_requis', - 'modalites_intervention', - 'modalites_facturation', - 'modalites_reponse', - 'modalites_modif_marche', - 'financement_adefpat_av', - 'total_budget_financement_av', - 'total_financement_av', - 'nb_jours_adefpat_av', - 'cout_jour_adefpat_av', - 'total_cout_adefpat_av', - 'total_budget_cout_av', - 'total_cout_av', - + "contexte_av", + "caract_beneficiaire_av", + "historique", + "besoins_beneficiaires_av", + "objectifs_accompagnement_av", + "competences_requises", + "secteurs_requis", + "modalites_intervention", + "modalites_facturation", + "modalites_reponse", + "modalites_modif_marche", + "financement_adefpat_av", + "total_budget_financement_av", + "total_financement_av", + "nb_jours_adefpat_av", + "cout_jour_adefpat_av", + "total_cout_adefpat_av", + "total_budget_cout_av", + "total_cout_av", # # Selection field - 'departement', - 'objectif_projet', - 'secteur_crit', - 'taille', - 'objectif_formation', - 'type_formation', - 'encadrement', - + "departement", + "objectif_projet", + "secteur_crit", + "taille", + "objectif_formation", + "type_formation", + "encadrement", # Many2one - ('territoire_id', ['display_name', 'street']), - ('elu_referent_id', ['name']), - ('user_id:cfd_name', ['name']), - ('user_id:cfd_email', ['email']), - ('type_convention_id', ['name']), - + ("territoire_id", ["display_name", "street"]), + ("elu_referent_id", ["name"]), + ("user_id:cfd_name", ["name"]), + ("user_id:cfd_email", ["email"]), + ("type_convention_id", ["name"]), # One2many List ( - 'membre_ids:LIST_membres', {'type_data': 'LIST', 'fields': [('partner_id', ['display_name', 'function'])]}), - ('porteurs_projets_ids:LIST_porteurs_project', - {'type_data': 'LIST', 'fields': [('porteur_id', ['display_name'])]}), - ('financement_av_ids:LIST_financement', {'type_data': 'LIST', 'fields': [('partner_id', ['display_name'])]}), - - ('porteurs_projets_ids:LIST_porteurs_adress', - {'type_data': 'LIST', 'fields': [('porteur_id', ['display_name', 'street', 'zip', 'city'])]}), - ('financement_av_ids:LIST_financement_adress', - {'type_data': 'LIST', 'fields': [('partner_id', ['display_name', 'street', 'zip', 'city'])]}), - ('od_ids:LIST_ods_adress', {'type_data': 'MULTI', 'fields': ['display_name', 'street', 'zip', 'city']}), - + "membre_ids:LIST_membres", + { + "type_data": "LIST", + "fields": [("partner_id", ["display_name", "function"])], + }, + ), + ( + "porteurs_projets_ids:LIST_porteurs_project", + {"type_data": "LIST", "fields": [("porteur_id", ["display_name"])]}, + ), + ( + "financement_av_ids:LIST_financement", + {"type_data": "LIST", "fields": [("partner_id", ["display_name"])]}, + ), + ( + "porteurs_projets_ids:LIST_porteurs_adress", + { + "type_data": "LIST", + "fields": [ + ("porteur_id", ["display_name", "street", "zip", "city"]) + ], + }, + ), + ( + "financement_av_ids:LIST_financement_adress", + { + "type_data": "LIST", + "fields": [ + ("partner_id", ["display_name", "street", "zip", "city"]) + ], + }, + ), + ( + "od_ids:LIST_ods_adress", + { + "type_data": "MULTI", + "fields": ["display_name", "street", "zip", "city"], + }, + ), # One2many Enum - ('petr_ids:petr_list', {'type_data': 'ENUM', 'fields': ['name']}), - ('porteurs_projets_ids:commune_list', {'type_data': 'ENUM', 'fields': ['commune']}), - ('cout_ids:consultants', {'type_data': 'ENUM', 'fields': [('partner_id', ['name'])]}), - ('od_ids:ods', {'type_data': 'ENUM', 'fields': ['name']}), - ('animateur_ids:animateurs', {'type_data': 'ENUM', 'fields': ['name']}), - + ("petr_ids:petr_list", {"type_data": "ENUM", "fields": ["name"]}), + ( + "porteurs_projets_ids:commune_list", + {"type_data": "ENUM", "fields": ["commune"]}, + ), + ( + "cout_ids:consultants", + {"type_data": "ENUM", "fields": [("partner_id", ["name"])]}, + ), + ("od_ids:ods", {"type_data": "ENUM", "fields": ["name"]}), + ("animateur_ids:animateurs", {"type_data": "ENUM", "fields": ["name"]}), ] keys = self.json_build(parser)[0] @@ -210,19 +255,27 @@ class Project(models.Model): # Nombre de PP eligibles nb_stagiaires = 0 if self.porteurs_projets_ids: - nb_stagiaires = len(self.porteurs_projets_ids.filtered(lambda r: r.eligible == True)) - keys['##nb_porteurs##'] = nb_stagiaires + nb_stagiaires = len(self.porteurs_projets_ids.filtered("eligible")) + keys["##nb_porteurs##"] = nb_stagiaires # Tableau des modules tab_mod = [] for modul in self.cout_av_ids: - tab_par_mod = [modul.module, nb_stagiaires, modul.nb_jour_theorique, modul.nb_jour_pratiques, - (7 * nb_stagiaires * modul.nb_jour_theorique), (7 * nb_stagiaires * modul.nb_jour_pratiques), - ((7 * nb_stagiaires * modul.nb_jour_theorique) + ( - 7 * nb_stagiaires * modul.nb_jour_pratiques))] + tab_par_mod = [ + modul.module, + nb_stagiaires, + modul.nb_jour_theorique, + modul.nb_jour_pratiques, + (7 * nb_stagiaires * modul.nb_jour_theorique), + (7 * nb_stagiaires * modul.nb_jour_pratiques), + ( + (7 * nb_stagiaires * modul.nb_jour_theorique) + + (7 * nb_stagiaires * modul.nb_jour_pratiques) + ), + ] tab_mod.append(tab_par_mod) - keys['##TAB_MODULE_AV##'] = tab_mod + keys["##TAB_MODULE_AV##"] = tab_mod # Tableau des porteurs de projets tab_pp = [] @@ -236,20 +289,23 @@ class Project(models.Model): if cout.partner_id: if res: if cout.partner_id.name not in res: - res += ', ' + cout.partner_id.name + res += ", " + cout.partner_id.name if cout.partner_id.email: - res += ' (' + cout.partner_id.email + ')' + res += " (" + cout.partner_id.email + ")" else: res += cout.partner_id.name if cout.partner_id.email: - res += ' (' + cout.partner_id.email + ')' - keys['##consultants_email##'] = res + res += " (" + cout.partner_id.email + ")" + keys["##consultants_email##"] = res - keys['##TAB_PORTEURS##'] = tab_pp + keys["##TAB_PORTEURS##"] = tab_pp if self.date_ca_av_next: - keys['##date_ca_av_next##'] = babel.dates.format_date(date=self.date_ca_av_next, format='dd MMMM Y', - locale=self._context.get('lang') or 'en_US') + keys["##date_ca_av_next##"] = babel.dates.format_date( + date=self.date_ca_av_next, + format="dd MMMM Y", + locale=self._context.get("lang") or "en_US", + ) return keys @api.multi @@ -257,20 +313,26 @@ class Project(models.Model): for project in self: if not project.cmis_folder: raise UserError( - "Le répertoire Projet d'Alfresco n'est pas configuré") + _("Le répertoire Projet d'Alfresco n'est pas configuré") + ) # Récupération du template de Dossier CA - template_docs = self.env['adefpat.template.doc'].search([ - '&', '|', ('convention_ids', '=', False), - ('convention_ids', '=', project.type_convention_id.id), - ('type_temp', '=', 'dossier_ca_av')]) + template_docs = self.env["adefpat.template.doc"].search( + [ + "&", + "|", + ("convention_ids", "=", False), + ("convention_ids", "=", project.type_convention_id.id), + ("type_temp", "=", "dossier_ca_av"), + ] + ) keys = project.fill_data_av() for template_doc in template_docs: url = project._publipostage_documents(template_doc, keys, True) return { - 'type': 'ir.actions.act_url', - 'url': url, - 'target': 'new', + "type": "ir.actions.act_url", + "url": url, + "target": "new", } @api.multi @@ -278,47 +340,81 @@ class Project(models.Model): for project in self: if not project.cmis_folder: raise UserError( - "Le répertoire Projet d'Alfresco n'est pas configuré") + _("Le répertoire Projet d'Alfresco n'est pas configuré") + ) # Fill data project keys = project.fill_data_av() # Récupération des conventions d'accompagnement # 1 par formateur - template_docs = self.env['adefpat.template.doc'].search([ - '&', '|', ('convention_ids', '=', False), - ('convention_ids', '=', project.type_convention_id.id), - ('type_temp', '=', 'convention_accompagnement_av')]) + template_docs = self.env["adefpat.template.doc"].search( + [ + "&", + "|", + ("convention_ids", "=", False), + ("convention_ids", "=", project.type_convention_id.id), + ("type_temp", "=", "convention_accompagnement_av"), + ] + ) for template_doc in template_docs: #################################### # Group by consultants #################################### - consulant_ok_ids = self.consulant_ids.filtered(lambda r: r.is_selected == True) + consulant_ok_ids = self.consulant_ids.filtered("is_selected") for consult in consulant_ok_ids: keys_obj = consult.fill_data() keys.update(keys_obj) - cout_ids = self.env['adefpat.project.cout.av'].sudo().read_group( - [('project_id', '=', project.id), - ('consultant_id', '=', consult.id)], - ['partner_id', 'montant', 'nb_jour_theorique', 'nb_jour_pratiques'], - ['partner_id'], lazy=False) - - keys['##montant##'] = cout_ids[0]['montant'] - keys['##nb_jour_theorique_tot_av##'] = cout_ids[0]['nb_jour_theorique'] - keys['##nb_jour_pratique_tot_av##'] = cout_ids[0]['nb_jour_pratiques'] - keys['##nb_jour_tot_av##'] = cout_ids[0]['nb_jour_pratiques'] + cout_ids[0]['nb_jour_theorique'] - keys['##nb_heure_tot_av##'] = (cout_ids[0]['nb_jour_pratiques'] + cout_ids[0]['nb_jour_theorique']) * 7 + cout_ids = ( + self.env["adefpat.project.cout.av"] + .sudo() + .read_group( + [ + ("project_id", "=", project.id), + ("consultant_id", "=", consult.id), + ], + [ + "partner_id", + "montant", + "nb_jour_theorique", + "nb_jour_pratiques", + ], + ["partner_id"], + lazy=False, + ) + ) + + keys["##montant##"] = cout_ids[0]["montant"] + keys["##nb_jour_theorique_tot_av##"] = cout_ids[0][ + "nb_jour_theorique" + ] + keys["##nb_jour_pratique_tot_av##"] = cout_ids[0][ + "nb_jour_pratiques" + ] + keys["##nb_jour_tot_av##"] = ( + cout_ids[0]["nb_jour_pratiques"] + + cout_ids[0]["nb_jour_theorique"] + ) + keys["##nb_heure_tot_av##"] = ( + cout_ids[0]["nb_jour_pratiques"] + + cout_ids[0]["nb_jour_theorique"] + ) * 7 project._publipostage_documents(template_doc, keys, False) # Récupération des annexes de conventions d'accompagnement # 1 par bénéficiaire - template_docs = self.env['adefpat.template.doc'].search([ - '&', '|', ('convention_ids', '=', False), - ('convention_ids', '=', project.type_convention_id.id), - ('type_temp', '=', 'annexe_convention_av')]) + template_docs = self.env["adefpat.template.doc"].search( + [ + "&", + "|", + ("convention_ids", "=", False), + ("convention_ids", "=", project.type_convention_id.id), + ("type_temp", "=", "annexe_convention_av"), + ] + ) for template_doc in template_docs: for financ in project.financement_av_ids: @@ -327,10 +423,15 @@ class Project(models.Model): project._publipostage_documents(template_doc, keys, False) # Récupération des conventions d'objectif - template_docs = self.env['adefpat.template.doc'].search([ - '&', '|', ('convention_ids', '=', False), - ('convention_ids', '=', project.type_convention_id.id), - ('type_temp', '=', 'convention_objectif_av')]) + template_docs = self.env["adefpat.template.doc"].search( + [ + "&", + "|", + ("convention_ids", "=", False), + ("convention_ids", "=", project.type_convention_id.id), + ("type_temp", "=", "convention_objectif_av"), + ] + ) for template_doc in template_docs: # Get the display name of first Animateur if project.animateur_ids: @@ -347,20 +448,22 @@ class Project(models.Model): project._publipostage_documents(template_doc, keys, True) # Open URL from path in alfresco - backend_name = self._fields['cmis_folder'] + backend_name = self._fields["cmis_folder"] backend = backend_name.get_backend(project.env) - parent_path = backend.get_cmis_repository().getFolder( - project.cmis_folder).getPaths() + parent_path = ( + backend.get_cmis_repository().getFolder(project.cmis_folder).getPaths() + ) path_proj = parent_path + [template_doc.dossier.name] - path = '/'.join(path_proj) + path = "/".join(path_proj) cmis_folder = backend.get_folder_by_path(path) - properties = backend.get_cmis_repository().getFolder( - cmis_folder).getProperties() + properties = ( + backend.get_cmis_repository().getFolder(cmis_folder).getProperties() + ) url = backend.get_content_details_url_from_props(properties) return { - 'type': 'ir.actions.act_url', - 'url': url, - 'target': 'new', + "type": "ir.actions.act_url", + "url": url, + "target": "new", } @api.multi @@ -369,17 +472,26 @@ class Project(models.Model): if project.is_avenant: if not project.cmis_folder: raise UserError( - "Le répertoire Projet d'Alfresco n'est pas configuré") + _("Le répertoire Projet d'Alfresco n'est pas configuré") + ) project.is_send_malette = True # Récupération des template pour les malettes - template_docs = self.env['adefpat.template.doc'].search([ - '&', '&', '|', ('convention_ids', '=', False), - ('convention_ids', '=', project.type_convention_id.id), - ('type_temp', '=', False), - ('noderef_document', '!=', False)]) + template_docs = self.env["adefpat.template.doc"].search( + [ + "&", + "&", + "|", + ("convention_ids", "=", False), + ("convention_ids", "=", project.type_convention_id.id), + ("type_temp", "=", False), + ("noderef_document", "!=", False), + ] + ) keys = project.fill_data() for template_doc in template_docs: - project._publipostage_documents_malette_avenant(template_doc, keys, True) + project._publipostage_documents_malette_avenant( + template_doc, keys, True + ) # Mise a jour de l'envoi malette sur les porteurs de projet for porteur in project.porteurs_projets_ids: @@ -387,15 +499,18 @@ class Project(models.Model): porteur.is_malette_ok = True # Open URL Project in Alfresco - backend_name = self._fields['cmis_folder'] + backend_name = self._fields["cmis_folder"] backend = backend_name.get_backend(project.env) - properties = backend.get_cmis_repository().getFolder( - project.cmis_folder).getProperties() + properties = ( + backend.get_cmis_repository() + .getFolder(project.cmis_folder) + .getProperties() + ) url = backend.get_content_details_url_from_props(properties) return { - 'type': 'ir.actions.act_url', - 'url': url, - 'target': 'new', + "type": "ir.actions.act_url", + "url": url, + "target": "new", } else: return super(Project, self).generate_dossier_global() @@ -411,30 +526,35 @@ class Project(models.Model): if partner.type_structure_id: # search the product corresponding to the company associated - product_id = self.env['product.product'].search([('type_product', '=', 'collectivite')]) + product_id = self.env["product.product"].search( + [("type_product", "=", "collectivite")] + ) else: # search the product corresponding to the company associated - product_id = self.env['product.product'].search([('type_product', '=', 'particular')]) + product_id = self.env["product.product"].search( + [("type_product", "=", "particular")] + ) # If doesn't exist, create the invoice if not benef.invoice_id: account_invoice_id = self.create_invoice( - product_id, partner, benef.montant) - account_invoice_id.write({ - 'is_avenant': True, - 'name': partner.name + " - avenant"}) - benef.write({'invoice_id': account_invoice_id.id}) + product_id, partner, benef.montant + ) + account_invoice_id.write( + {"is_avenant": True, "name": partner.name + " - avenant"} + ) + benef.write({"invoice_id": account_invoice_id.id}) # ------------------------------------------------------ # Compute # ------------------------------------------------------ - @api.depends('date_ca_av') + @api.depends("date_ca_av") @api.multi def _compute_date_ca_av_next(self): for project in self: if project.date_ca_av: project.date_ca_av_next = project.date_ca_av + timedelta(days=1) - @api.depends('financement_av_ids', 'financement_av_ids.montant') + @api.depends("financement_av_ids", "financement_av_ids.montant") @api.multi def _compute_total_financment_av(self): for project in self: @@ -443,7 +563,7 @@ class Project(models.Model): total_financement += financement.montant project.total_financement_av = total_financement - @api.depends('cout_av_ids', 'cout_av_ids.montant') + @api.depends("cout_av_ids", "cout_av_ids.montant") @api.multi def _compute_total_cout_av(self): for project in self: @@ -452,7 +572,7 @@ class Project(models.Model): total_cout += cout.montant project.total_cout_av = total_cout - @api.depends('cout_av_ids', 'cout_av_ids.nb_jour_theorique') + @api.depends("cout_av_ids", "cout_av_ids.nb_jour_theorique") @api.multi def _compute_nb_jour_theorique_tot_av(self): for project in self: @@ -461,19 +581,23 @@ class Project(models.Model): total_jour += cout.nb_jour_theorique project.nb_jour_theorique_tot_av = total_jour - @api.depends('nb_jour_theorique_tot_av', 'nb_jour_theorique_tot') + @api.depends("nb_jour_theorique_tot_av", "nb_jour_theorique_tot") @api.multi def _compute_nb_jour_theorique_tot_global(self): for project in self: - project.nb_jour_theorique_tot_global = project.nb_jour_theorique_tot_av + project.nb_jour_theorique_tot + project.nb_jour_theorique_tot_global = ( + project.nb_jour_theorique_tot_av + project.nb_jour_theorique_tot + ) - @api.depends('nb_jour_plann', 'nb_jour_theorique_tot_global') + @api.depends("nb_jour_plann", "nb_jour_theorique_tot_global") @api.multi def _compute_nb_jour_rest(self): for project in self: - project.nb_jour_rest = project.nb_jour_theorique_tot_global - project.nb_jour_plann + project.nb_jour_rest = ( + project.nb_jour_theorique_tot_global - project.nb_jour_plann + ) - @api.depends('cout_av_ids', 'cout_av_ids.nb_jour_pratiques') + @api.depends("cout_av_ids", "cout_av_ids.nb_jour_pratiques") @api.multi def _compute_nb_jour_pratique_tot_av(self): for project in self: @@ -482,7 +606,9 @@ class Project(models.Model): total_jour += cout.nb_jour_pratiques project.nb_jour_pratique_tot_av = total_jour - @api.depends('cout_av_ids', 'cout_av_ids.nb_jour_theorique', 'cout_av_ids.nb_jour_pratiques') + @api.depends( + "cout_av_ids", "cout_av_ids.nb_jour_theorique", "cout_av_ids.nb_jour_pratiques" + ) @api.multi def _compute_nb_jour_tot_av(self): for project in self: @@ -492,89 +618,104 @@ class Project(models.Model): project.nb_jour_tot_av = total_jour project.nb_heure_tot_av = total_jour * 7 - @api.depends('nb_jours_adefpat_av', 'cout_jour_adefpat_av') + @api.depends("nb_jours_adefpat_av", "cout_jour_adefpat_av") @api.multi def _compute_total_cout_adefpat_av(self): for project in self: - project.total_cout_adefpat_av = project.nb_jours_adefpat_av * project.cout_jour_adefpat_av + project.total_cout_adefpat_av = ( + project.nb_jours_adefpat_av * project.cout_jour_adefpat_av + ) - @api.depends('total_cout_adefpat_av', 'total_cout_av') + @api.depends("total_cout_adefpat_av", "total_cout_av") @api.multi def _compute_total_budget_cout_av(self): for project in self: - project.total_budget_cout_av = project.total_cout_adefpat_av + project.total_cout_av + project.total_budget_cout_av = ( + project.total_cout_adefpat_av + project.total_cout_av + ) - @api.depends('total_budget_cout_av', 'total_financement_av') + @api.depends("total_budget_cout_av", "total_financement_av") @api.multi def _compute_financement_adefpat_av(self): for project in self: - project.financement_adefpat_av = project.total_budget_cout_av - project.total_financement_av + project.financement_adefpat_av = ( + project.total_budget_cout_av - project.total_financement_av + ) - @api.depends('financement_adefpat_av', 'total_financement_av') + @api.depends("financement_adefpat_av", "total_financement_av") @api.multi def _compute_total_budget_financement_av(self): for project in self: - project.total_budget_financement_av = project.financement_adefpat_av + project.total_financement_av + project.total_budget_financement_av = ( + project.financement_adefpat_av + project.total_financement_av + ) class AdefpatFinancement(models.Model): - _inherit = 'adefpat.project.financement' + _inherit = "adefpat.project.financement" type_avenant = fields.Boolean("Type avenant", default=False) class AdefpatCout(models.Model): - _inherit = 'adefpat.project.cout' + _inherit = "adefpat.project.cout" type_avenant = fields.Boolean("Type avenant", default=False) class AdefpatCoutAV(models.Model): - _name = 'adefpat.project.cout.av' - _description = 'Coûts Avenants' + _name = "adefpat.project.cout.av" + _description = "Coûts Avenants" @api.model def _default_consultant_id(self): - return self.env['res.partner.consultants.project'].search([ - ('project_id', '=', self.env.context.get('default_project_id')), - ('is_selected', '=', True) - ], limit=1) + return self.env["res.partner.consultants.project"].search( + [ + ("project_id", "=", self.env.context.get("default_project_id")), + ("is_selected", "=", True), + ], + limit=1, + ) module = fields.Char("Nom du module") project_id = fields.Many2one( - 'project.project', - string='Projet', - default=lambda self: self.env.context.get('default_project_id')) + "project.project", + string="Projet", + default=lambda self: self.env.context.get("default_project_id"), + ) consultant_id = fields.Many2one( - 'res.partner.consultants.project', + "res.partner.consultants.project", string="Consultant", domain="[('is_selected', '=', True)]", - on_delete='restrict', - default=_default_consultant_id + on_delete="restrict", + default=_default_consultant_id, ) partner_id = fields.Many2one( - 'res.partner', - string='Consultant', - related='consultant_id.partner_id', - store=True) + "res.partner", + string="Consultant", + related="consultant_id.partner_id", + store=True, + ) nb_jour_theorique = fields.Float("Nombre de jours théoriques") nb_jour_pratiques = fields.Float("Nombre de jours pratiques") - cout_jour = fields.Float("Coût jour", related='consultant_id.cout_journée') - montant = fields.Float("Montant", compute='_compute_montant', store=True) + cout_jour = fields.Float("Coût jour", related="consultant_id.cout_journée") + montant = fields.Float("Montant", compute="_compute_montant", store=True) - @api.depends('cout_jour', 'nb_jour_theorique') + @api.depends("cout_jour", "nb_jour_theorique") def _compute_montant(self): for r in self: - r.montant = r.cout_jour * r.nb_jour_theorique\ + r.montant = r.cout_jour * r.nb_jour_theorique class AdefpatTemplateDoc(models.Model): - _inherit = 'adefpat.template.doc' - - type_temp = fields.Selection(selection_add=[ - ('convention_accompagnement_av', "Avenant Conventions d'accompagnement"), - ('convention_objectif_av', "Avenant Conventions d'objectif"), - ('annexe_convention_av', "Avenant Annexes Conventions"), - ('dossier_ca_av', "Avenant Dossier CA"), - ('facture_av', "Avenant Facture"), - ]) \ No newline at end of file + _inherit = "adefpat.template.doc" + + type_temp = fields.Selection( + selection_add=[ + ("convention_accompagnement_av", "Avenant Conventions d'accompagnement"), + ("convention_objectif_av", "Avenant Conventions d'objectif"), + ("annexe_convention_av", "Avenant Annexes Conventions"), + ("dossier_ca_av", "Avenant Dossier CA"), + ("facture_av", "Avenant Facture"), + ] + ) diff --git a/models/project_task.py b/models/project_task.py old mode 100755 new mode 100644 index cd7379b..553bafd --- a/models/project_task.py +++ b/models/project_task.py @@ -1,90 +1,99 @@ # Copyright 2020 Le Filament (<http://www.le-filament.com>) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import fields, models, api +from odoo import api, fields, models class ProjectTask(models.Model): - _inherit = 'project.task' + _inherit = "project.task" @api.model def default_get(self, fields): res = super(ProjectTask, self).default_get(fields) - if 'default_project_id' in self.env.context: - default_project_id = self.env['project.project'].browse(self.env.context['default_project_id']) + if "default_project_id" in self.env.context: + default_project_id = self.env["project.project"].browse( + self.env.context["default_project_id"] + ) if default_project_id.exists().porteurs_projets_ids: participant_ids = [] for porteur_project in default_project_id.porteurs_projets_ids: vals = { - 'porteur_id': porteur_project.porteur_id.id, - 'lastname': porteur_project.lastname, - 'firstname': porteur_project.firstname, - 'commune': porteur_project.commune, - 'mobile': porteur_project.mobile, - 'fixe': porteur_project.fixe, - 'email': porteur_project.email, - 'eligible': porteur_project.eligible, + "porteur_id": porteur_project.porteur_id.id, + "lastname": porteur_project.lastname, + "firstname": porteur_project.firstname, + "commune": porteur_project.commune, + "mobile": porteur_project.mobile, + "fixe": porteur_project.fixe, + "email": porteur_project.email, + "eligible": porteur_project.eligible, } participant_ids.append((0, 0, vals)) - res.update({'participant_ids': participant_ids}) - cout_id = self.env['adefpat.project.cout'].search([ - ('project_id', '=', default_project_id.id), - ('partner_id', '!=', False) - ], limit=1) + res.update({"participant_ids": participant_ids}) + cout_id = self.env["adefpat.project.cout"].search( + [ + ("project_id", "=", default_project_id.id), + ("partner_id", "!=", False), + ], + limit=1, + ) if cout_id: - res.update({'formateur_id': cout_id.partner_id.id}) + res.update({"formateur_id": cout_id.partner_id.id}) return res user_id = fields.Many2one( - 'res.users', - string='CFD', + "res.users", + string="CFD", default=lambda self: self.env.uid, index=True, - track_visibility='always') + track_visibility="always", + ) formateur_id = fields.Many2one( - 'res.partner', - string='Formateur', + "res.partner", + string="Formateur", domain=[ - ('active', '=', True), - ('is_company', '=', False), - ('is_consultant_form', '=', True), - '|', ('reference', '=', 'reference'), - ('reference', '=', 'prereference')], - on_delete='restrict') - duree_jr = fields.Selection([ - ('demi_journee', "Demi journée"), - ('journee', "Journée"), + ("active", "=", True), + ("is_company", "=", False), + ("is_consultant_form", "=", True), + "|", + ("reference", "=", "reference"), + ("reference", "=", "prereference"), + ], + on_delete="restrict", + ) + duree_jr = fields.Selection( + [ + ("demi_journee", "Demi journée"), + ("journee", "Journée"), ], - "Durée (en jours)") - duree_hr = fields.Float( - "Durée en jours", - compute='_compute_duree_hr', - store=True + "Durée (en jours)", ) + duree_hr = fields.Float("Durée en jours", compute="_compute_duree_hr", store=True) participant_ids = fields.One2many( - 'res.partner.porteur.project', - 'task_id', - string="Participants") - cout_seance = fields.Float("Coût de la séance", compute='_compute_cout_seance') + "res.partner.porteur.project", "task_id", string="Participants" + ) + cout_seance = fields.Float("Coût de la séance", compute="_compute_cout_seance") date_account = fields.Date("Date de la facture") - @api.depends('duree_jr') + @api.depends("duree_jr") @api.multi def _compute_duree_hr(self): for task in self: - if task.duree_jr == 'journee': + if task.duree_jr == "journee": task.duree_hr = 1 - if task.duree_jr == 'demi_journee': - task.duree_hr = 0.5\ + if task.duree_jr == "demi_journee": + task.duree_hr = 0.5 - @api.depends('formateur_id', 'duree_hr') + @api.depends("formateur_id", "duree_hr") @api.multi def _compute_cout_seance(self): for task in self: if task.formateur_id: - formateur_proj = self.env['adefpat.project.cout'].search([ - ('project_id', '=', task.project_id.id), - ('partner_id', '=', task.formateur_id.id) - ], limit=1) + formateur_proj = self.env["adefpat.project.cout"].search( + [ + ("project_id", "=", task.project_id.id), + ("partner_id", "=", task.formateur_id.id), + ], + limit=1, + ) if formateur_proj: - task.cout_seance = formateur_proj.cout_jour * task.duree_hr \ No newline at end of file + task.cout_seance = formateur_proj.cout_jour * task.duree_hr diff --git a/security/ir.model.access.csv b/security/ir.model.access.csv old mode 100755 new mode 100644 index ee38fb4..a185d4f --- a/security/ir.model.access.csv +++ b/security/ir.model.access.csv @@ -1,2 +1,2 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_adefpat_project_cout_av_group_user,access_adefpat_project_cout_av_group_user,model_adefpat_project_cout_av,base.group_user,1,1,1,1 \ No newline at end of file +access_adefpat_project_cout_av_group_user,access_adefpat_project_cout_av_group_user,model_adefpat_project_cout_av,base.group_user,1,1,1,1 diff --git a/static/description/icon.png b/static/description/icon.png old mode 100755 new mode 100644 diff --git a/views/account_invoice_views.xml b/views/account_invoice_views.xml index 418f27d..169d9e6 100644 --- a/views/account_invoice_views.xml +++ b/views/account_invoice_views.xml @@ -1,16 +1,16 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8" ?> <odoo> <data> <record id="invoice_form" model="ir.ui.view"> <field name="name">adefpat.invoice_form.form</field> <field name="model">account.invoice</field> - <field name="inherit_id" ref="adefpat_account.invoice_form"/> + <field name="inherit_id" ref="adefpat_account.invoice_form" /> <field name="arch" type="xml"> <xpath expr="//field[@name='project_id']" position="after"> - <field name="is_avenant"/> + <field name="is_avenant" /> </xpath> </field> </record> </data> -</odoo> \ No newline at end of file +</odoo> diff --git a/views/project_views.xml b/views/project_views.xml old mode 100755 new mode 100644 index 106c14a..9df244d --- a/views/project_views.xml +++ b/views/project_views.xml @@ -1,95 +1,134 @@ -<?xml version="1.0"?> +<?xml version="1.0" ?> <!-- Copyright 2020 Le Filament License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> - <odoo> <data> <record id="edit_project" model="ir.ui.view"> <field name="name">av.project.project.form</field> <field name="model">project.project</field> - <field name="inherit_id" ref="adefpat_project.edit_project"/> + <field name="inherit_id" ref="adefpat_project.edit_project" /> <field name="arch" type="xml"> <field name="territoire_id" position="before"> - <field name="is_avenant" invisible="1"/> + <field name="is_avenant" invisible="1" /> </field> <field name="nb_jour_plann" position="before"> - <field name="nb_jour_theorique_tot_global"/> + <field name="nb_jour_theorique_tot_global" /> </field> <header position="inside"> <!-- Ajout du bouton pour récupérer les infos avenants --> - <button string="Avenant" - type="object" class="btn btn-primary" - name="fill_avenant" attrs="{'invisible': [('is_avenant', '=', True)]}"/> + <button + string="Avenant" + type="object" + class="btn btn-primary" + name="fill_avenant" + attrs="{'invisible': [('is_avenant', '=', True)]}" + /> </header> <page name="gap" position="after"> - <page name="dossier_av" string="Dossier Avenant" attrs="{'invisible': [('is_avenant', '=', False)]}"> + <page + name="dossier_av" + string="Dossier Avenant" + attrs="{'invisible': [('is_avenant', '=', False)]}" + > <header> - <button string="Générer Avenant Dossier CA" - type="object" class="btn btn-primary" - name="generate_dossier_ca_av"/> - <button string="Générer Avenant Conventions" - type="object" class="btn btn-primary" - name="generate_dossier_conventions_av"/> + <button + string="Générer Avenant Dossier CA" + type="object" + class="btn btn-primary" + name="generate_dossier_ca_av" + /> + <button + string="Générer Avenant Conventions" + type="object" + class="btn btn-primary" + name="generate_dossier_conventions_av" + /> </header> <group> - <field name="date_ca_av"/> + <field name="date_ca_av" /> </group> <group string="Dossier Consultation Avenant"> <group> - <field name="contexte_av"/> - <field name="caract_beneficiaire_av"/> - <field name="besoins_beneficiaires_av"/> - <field name="resultats_accompagnement"/> - <field name="objectifs_accompagnement_av"/> + <field name="contexte_av" /> + <field name="caract_beneficiaire_av" /> + <field name="besoins_beneficiaires_av" /> + <field name="resultats_accompagnement" /> + <field name="objectifs_accompagnement_av" /> </group> <group string="Infos Dossier CA"> - <field name="lieu_av"/> - <field name="periode_realisation_av"/> - <field name="contenu_formation_av" placeholder="Contenu des séances / Evaluation - capitalisation / La dernière séance permettra de dégager les acquis pédagogiques en s’appuyant sur les objectifs initiaux d’accompagnement : retour d’expérience / pointage des actions restant à conduire"/> - <field name="methode_savoir_av" placeholder="Méthodes et savoir transmis / Evaluation - capitalisation / La dernière séance permettra de dégager les acquis pédagogiques en s’appuyant sur les objectifs initiaux d’accompagnement : retour d’expérience / pointage des actions restant à conduire"/> - <field name="travaux_intersessions_av" placeholder="Travaux intersessions / Evaluation - capitalisation / La dernière séance permettra de dégager les acquis pédagogiques en s’appuyant sur les objectifs initiaux d’accompagnement : retour d’expérience / pointage des actions restant à conduire"/> + <field name="lieu_av" /> + <field name="periode_realisation_av" /> + <field + name="contenu_formation_av" + placeholder="Contenu des séances / Evaluation - capitalisation / La dernière séance permettra de dégager les acquis pédagogiques en s’appuyant sur les objectifs initiaux d’accompagnement : retour d’expérience / pointage des actions restant à conduire" + /> + <field + name="methode_savoir_av" + placeholder="Méthodes et savoir transmis / Evaluation - capitalisation / La dernière séance permettra de dégager les acquis pédagogiques en s’appuyant sur les objectifs initiaux d’accompagnement : retour d’expérience / pointage des actions restant à conduire" + /> + <field + name="travaux_intersessions_av" + placeholder="Travaux intersessions / Evaluation - capitalisation / La dernière séance permettra de dégager les acquis pédagogiques en s’appuyant sur les objectifs initiaux d’accompagnement : retour d’expérience / pointage des actions restant à conduire" + /> </group> </group> <group> - <field name="modalite_gap_av"/> + <field name="modalite_gap_av" /> </group> </page> - <page string="Budget Avenant" attrs="{'invisible': [('is_avenant', '=', False)]}"> - <button string="Générer Factures Avenant" - type="object" class="btn btn-primary" - name="action_generate_invoices_av"/> + <page + string="Budget Avenant" + attrs="{'invisible': [('is_avenant', '=', False)]}" + > + <button + string="Générer Factures Avenant" + type="object" + class="btn btn-primary" + name="action_generate_invoices_av" + /> <group> <group string="Adefpat"> - <field name="nb_jours_adefpat_av"/> - <field name="cout_jour_adefpat_av"/> + <field name="nb_jours_adefpat_av" /> + <field name="cout_jour_adefpat_av" /> </group> - <group > - <field name="nb_jour_tot_av" invisible="1"/> - <field name="nb_heure_tot_av" invisible="1"/> - <field name="nb_jour_theorique_tot_av"/> + <group> + <field name="nb_jour_tot_av" invisible="1" /> + <field name="nb_heure_tot_av" invisible="1" /> + <field name="nb_jour_theorique_tot_av" /> </group> </group> - <field name="cout_av_ids" widget="one2many" context="{'default_project_id': active_id}"> + <field + name="cout_av_ids" + widget="one2many" + context="{'default_project_id': active_id}" + > <tree string="Coûts Avenants" editable="top"> - <field name="module"/> - <field name="consultant_id"/> - <field name="partner_id" invisible="1"/> - <field name="nb_jour_theorique"/> - <field name="nb_jour_pratiques"/> - <field name="cout_jour"/> - <field name="montant" sum="Montant"/> - <field name="project_id" invisible="1"/> + <field name="module" /> + <field name="consultant_id" /> + <field name="partner_id" invisible="1" /> + <field name="nb_jour_theorique" /> + <field name="nb_jour_pratiques" /> + <field name="cout_jour" /> + <field name="montant" sum="Montant" /> + <field name="project_id" invisible="1" /> </tree> </field> - <field name="financement_av_ids" widget="one2many" context="{'default_type_avenant': True}"> + <field + name="financement_av_ids" + widget="one2many" + context="{'default_type_avenant': True}" + > <tree string="Financements Avenant" editable="top"> <field name="partner_id" /> - <field name="montant" sum="Montant"/> - <field name="project_id" invisible="1"/> - <field name="invoice_id" domain="[('project_id', '=', project_id)]"/> - <field name="type_avenant" invisible="1"/> + <field name="montant" sum="Montant" /> + <field name="project_id" invisible="1" /> + <field + name="invoice_id" + domain="[('project_id', '=', project_id)]" + /> + <field name="type_avenant" invisible="1" /> </tree> </field> <group> @@ -102,30 +141,56 @@ </thead> <tbody> <tr> - <td > + <td> <tr> <td> Honoraire d'intervenant : - <field name="cout_av_ids" widget="one2many" context="{'default_project_id': active_id}"> - <tree string="Coûts" editable="top"> - <field name="module"/> - <field name="consultant_id"/> - <field name="partner_id" invisible="1"/> - <field name="nb_jour_theorique"/> - <field name="nb_jour_pratiques"/> - <field name="cout_jour"/> - <field name="montant" sum="Montant"/> + <field + name="cout_av_ids" + widget="one2many" + context="{'default_project_id': active_id}" + > + <tree + string="Coûts" + editable="top" + > + <field name="module" /> + <field + name="consultant_id" + /> + <field + name="partner_id" + invisible="1" + /> + <field + name="nb_jour_theorique" + /> + <field + name="nb_jour_pratiques" + /> + <field name="cout_jour" /> + <field + name="montant" + sum="Montant" + /> </tree> </field> </td> - <td><field name="total_cout_av" widget="monetary" readonly="True"/> €</td> + <td><field + name="total_cout_av" + widget="monetary" + readonly="True" + /> €</td> </tr> <tr> <td> - Temps Adefpat: <br/> + Temps Adefpat: <br /> </td> <td> - <field name="total_cout_adefpat_av" readonly="True"/> € + <field + name="total_cout_adefpat_av" + readonly="True" + /> € </td> </tr> </td> @@ -136,7 +201,11 @@ Adefpat: </td> <td> - <field name="financement_adefpat_av" widget="monetary" readonly="True"/> € + <field + name="financement_adefpat_av" + widget="monetary" + readonly="True" + /> € </td> </tr> <tr> @@ -144,7 +213,11 @@ Autres: </td> <td> - <field name="total_financement_av" widget="monetary" readonly="True"/> € + <field + name="total_financement_av" + widget="monetary" + readonly="True" + /> € </td> </tr> </td> @@ -153,7 +226,11 @@ <td> <tr> <td>Budget Total: </td> - <td><field name="total_budget_cout_av" widget="monetary" readonly="True"/> € + <td><field + name="total_budget_cout_av" + widget="monetary" + readonly="True" + /> € </td> </tr> </td> @@ -161,7 +238,11 @@ <td> <tr> <td>Budget Total:</td> - <td><field name="total_budget_financement_av" widget="monetary" readonly="True"/> €</td> + <td><field + name="total_budget_financement_av" + widget="monetary" + readonly="True" + /> €</td> </tr> </td> </tr> @@ -169,7 +250,7 @@ </table> </group> <group> - <field name="explication_financement_av"/> + <field name="explication_financement_av" /> </group> </page> </page> -- GitLab