From 66be8575405ebeeb4c2ef54e7bda01d2b4a7f1a8 Mon Sep 17 00:00:00 2001 From: benjamin <benjamin@le-filament.com> Date: Thu, 4 Jul 2024 12:09:26 +0200 Subject: [PATCH] [UPD] report template & rmovee cancel state & add validation date --- .pre-commit-config.yaml | 35 +++--- models/scop_cotisation_cg_followup.py | 16 ++- models/scop_cotisation_cg_followup_line.py | 41 ++++++- templates/report_scop_followup.xml | 132 ++++++++++++++++----- views/scop_cotisation_cg_followup.xml | 9 +- views/scop_cotisation_cg_followup_line.xml | 22 +++- 6 files changed, 197 insertions(+), 58 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 732d0c4..4acca68 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,6 +6,8 @@ exclude: | ^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 @@ -25,8 +27,13 @@ repos: entry: found forbidden files; remove them language: fail files: "\\.rej$" + - id: en-po-files + name: en.po files cannot exist + entry: found a en.po file + language: fail + files: '[a-zA-Z0-9_]*/i18n/en\.po$' - repo: https://github.com/oca/maintainer-tools - rev: 7d8a9f9ad73db0976fb03cbee43d953bc29b89e9 + rev: ab1d7f6 hooks: # update the NOT INSTALLABLE ADDONS section above - id: oca-update-pre-commit-excluded-addons @@ -48,7 +55,7 @@ repos: hooks: - id: black - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.6.2 + rev: v2.1.2 hooks: - id: prettier name: prettier (with plugin-xml) @@ -59,7 +66,7 @@ repos: - --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: v8.15.0 + rev: v7.8.1 hooks: - id: eslint verbose: true @@ -67,7 +74,7 @@ repos: - --color - --fix - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v3.2.0 hooks: - id: trailing-whitespace # exclude autogenerated files @@ -89,37 +96,33 @@ repos: - id: mixed-line-ending args: ["--fix=lf"] - repo: https://github.com/asottile/pyupgrade - rev: v2.32.1 + rev: v2.7.2 hooks: - id: pyupgrade args: ["--keep-percent-format"] - repo: https://github.com/PyCQA/isort - rev: 5.10.1 + rev: 5.12.0 hooks: - id: isort name: isort except __init__.py args: - --settings=. exclude: /__init__\.py$ - - repo: https://gitlab.com/PyCQA/flake8 - rev: 3.9.2 + - repo: https://github.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: v2.11.1 + - repo: https://github.com/OCA/pylint-odoo + rev: 7.0.2 hooks: - - id: pylint + - id: pylint_odoo name: pylint with optional checks args: - --rcfile=.pylintrc - --exit-zero verbose: true - additional_dependencies: &pylint_deps - - pylint-odoo==5.0.5 - - id: pylint - name: pylint with mandatory checks + - id: pylint_odoo args: - --rcfile=.pylintrc-mandatory - additional_dependencies: *pylint_deps diff --git a/models/scop_cotisation_cg_followup.py b/models/scop_cotisation_cg_followup.py index ccf2e7f..f100779 100644 --- a/models/scop_cotisation_cg_followup.py +++ b/models/scop_cotisation_cg_followup.py @@ -17,6 +17,7 @@ class ScopCotisationCgFollowup(models.Model): date = fields.Date( "Date Limite de la Relance", states={"done": [("readonly", True)]} ) + date_validation = fields.Date("Date de validation de la Relance") threshold_amount = fields.Monetary( "Seuil de relance", default=0, @@ -34,7 +35,7 @@ class ScopCotisationCgFollowup(models.Model): string="Année minimale", ) state = fields.Selection( - [("draft", "Brouillon"), ("done", "Validé"), ("cancel", "Annulé")], + [("draft", "Brouillon"), ("done", "Validé")], string="Statut", default="draft", tracking=2, @@ -49,7 +50,7 @@ class ScopCotisationCgFollowup(models.Model): string="Nombre de coopératives", compute="_compute_partner_count" ) total_amount = fields.Monetary( - string="Montant à recouvrir", + string="Montant recouvré", compute="_compute_total_amount", currency_field="company_currency_id", ) @@ -135,10 +136,6 @@ class ScopCotisationCgFollowup(models.Model): for line in followup_line_ids: # update partner followup_level line.partner_id.followup_level = line.followup_level - # TODO: - # - création courrier + chargement sur Alfresco - # - envouer un mail (contact COTI ?) - # - autres actions à définir en fonction du niveau # Store invoices history in JSON amount_history = {} @@ -162,10 +159,11 @@ class ScopCotisationCgFollowup(models.Model): ) line.amount_history = amount_history - self.state = "done" + self.update({"date_validation": fields.Date.today(), "state": "done"}) - def cancel_followup(self): - self.state = "cancel" + def reset_to_draft(self): + self.ensure_one() + self.update({"date_validation": False, "state": "draft"}) def create_followup(self): """ diff --git a/models/scop_cotisation_cg_followup_line.py b/models/scop_cotisation_cg_followup_line.py index 99c7375..66d3fa5 100644 --- a/models/scop_cotisation_cg_followup_line.py +++ b/models/scop_cotisation_cg_followup_line.py @@ -2,6 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import ast +from datetime import datetime from odoo import _, api, fields, models from odoo.exceptions import UserError @@ -16,6 +17,7 @@ class ScopCotisationCgFollowupLine(models.Model): comodel_name="scop.cotisation.cg.followup", string="Relance", required=True, + ondelete="cascade", index=True, ) followup_date = fields.Date(related="followup_id.date", string="Date") @@ -157,11 +159,16 @@ class ScopCotisationCgFollowupLine(models.Model): # ------------------------------------------------------ def print_followup(self): return self.env.ref( - "cgscop_cotisation_cg_followup.cgscop_followup_report").report_action(self) + "cgscop_cotisation_cg_followup.cgscop_followup_report" + ).report_action(self) def delete_followup_line(self): self.unlink() + def action_exclude_line(self): + for line in self.browse(self.env.context["active_ids"]): + line.update({"is_excluded": True}) + # ------------------------------------------------------ # Override ORM # ------------------------------------------------------ @@ -195,3 +202,35 @@ class ScopCotisationCgFollowupLine(models.Model): ) ) return super(ScopCotisationCgFollowupLine, self).unlink() + + # ------------------------------------------------------ + # Report functions + # ------------------------------------------------------ + def get_report_invoices(self): + """ + Retournes les impayés depuis la date de validation + """ + self.ensure_one() + domain_odoo = [ + ("invoice_date_due", "<=", self.followup_id.date_validation), + ("is_contribution", "=", True), + ("state", "=", "posted"), + ("move_type", "=", "out_invoice"), + ("bordereau_id", "!=", False), + ("bordereau_id.state", "!=", "cancelled"), + ("partner_id", "=", self.partner_id.id), + ("amount_residual_signed", ">", 0), + ("id", "not in", self.invoice_ids.ids), + ] + if self.followup_id.threshold_year: + domain_odoo.append( + ( + "invoice_date_due", + ">", + datetime(int(self.followup_id.threshold_year), 1, 1), + ) + ) + + # Get Odoo move line & partners + move_ids = self.env["account.move"].search(domain_odoo) + return move_ids + self.invoice_ids diff --git a/templates/report_scop_followup.xml b/templates/report_scop_followup.xml index 79cae31..e507983 100644 --- a/templates/report_scop_followup.xml +++ b/templates/report_scop_followup.xml @@ -5,6 +5,12 @@ <template id="report_scop_followup_document"> <t t-call="web.external_layout"> <t t-set="o" t-value="o.with_context(lang='fr')" /> + <t t-set="all_invoices" t-value="o.get_report_invoices()" /> + <t + t-set="total_amount" + t-value="sum(all_invoices.mapped('amount_residual_signed')) + sum(o.riga_contribution_ids.mapped('amount_due'))" + /> + <div class="page" style="font-size: 16px;"> <!-- Adresse --> <div class="row"> @@ -43,8 +49,7 @@ <div class="row pt32"> <div class="col-12" style="text-align: justify;"> <p> - Objet : Relance pour cotisation impayée<br - /> + Objet : Relance pour cotisation impayée<br /> N° adhérent : <t t-esc="str(o.partner_id.member_number_int)" /><br /> @@ -61,15 +66,20 @@ </p> <p> Sauf erreur ou omission de notre part, votre relevé de compte cotisant à la date du - <span t-field="o.followup_id.date" /> présente un solde débiteur de - <span t-field="o.amount_due" t-options='{"widget": "monetary","display_currency": company.currency_id}' /> + <span + t-field="o.followup_id.date" + /> présente un solde débiteur de + <span + t-esc="total_amount" + t-options='{"widget": "monetary","display_currency": company.currency_id}' + />. </p> <table class="table table-sm table-stripped"> <thead> <tr> <th>Année</th> - <th>Trimestre</th> + <th>Trimestre(s)</th> <th>Type de cotisation</th> <th>Montant total</th> <th>Montant réglé</th> @@ -77,41 +87,109 @@ </tr> </thead> <tbody> - <tr t-foreach="o.invoice_ids" t-as="line"> - <td><span t-esc="line.year" /></td> - <td><span t-field="line.cotiz_quarter" /></td> - <td><span t-field="line.type_contribution_id.name" /></td> - <td class="text-right"><span t-field="line.amount_called" /></td> - <td class="text-right"><span t-esc="line.amount_called - line.amount_residual_signed" t-options='{"widget": "monetary","display_currency": company.currency_id}' /></td> - <td class="text-right"><span t-field="line.amount_residual_signed" /></td> - </tr> - <tr t-foreach="o.riga_contribution_ids.sorted(key='year', reverse=True)" t-as="line"> - <td><span t-field="line.year" /></td> - <td><span t-field="line.quarter" /></td> - <td><span t-field="line.type_contribution_id.name" /></td> - <td class="text-right"><span t-field="line.amount" t-options='{"widget": "monetary","display_currency": company.currency_id}' /></td> - <td class="text-right"><span t-esc="line.amount_paid" t-options='{"widget": "monetary","display_currency": company.currency_id}' /></td> - <td class="text-right"><span t-field="line.amount_due" t-options='{"widget": "monetary","display_currency": company.currency_id}' /></td> - </tr> + <t + t-set="year_invoices" + t-value="sorted(list(set(all_invoices.mapped('year'))), reverse=True)" + /> + + <t + t-set="type_contrib_invoices" + t-value="list(set(all_invoices.mapped('type_contribution_id')))" + /> + <t t-foreach="year_invoices" t-as="year"> + <tr + t-foreach="type_contrib_invoices" + t-as="contrib" + > + <t + t-set="line" + t-value="all_invoices.filtered(lambda c: c.year == year and c.type_contribution_id == contrib)" + /> + <t t-if="line"> + <td><span t-esc="year" /></td> + <td><span + t-esc="', '.join(sorted(line.mapped(lambda c: c.cotiz_quarter or '')))" + /></td> + <td><span t-esc="contrib.name" /></td> + <td class="text-right"><span + t-esc="sum(line.mapped('amount_called'))" + t-options='{"widget": "monetary","display_currency": company.currency_id}' + /></td> + <td class="text-right"><span + t-esc="sum(line.mapped(lambda c: c.amount_called - c.amount_residual_signed))" + t-options='{"widget": "monetary","display_currency": company.currency_id}' + /></td> + <td class="text-right"><span + t-esc="sum(line.mapped('amount_residual_signed'))" + t-options='{"widget": "monetary","display_currency": company.currency_id}' + /></td> + </t> + </tr> + </t> + + <t + t-set="year_riga" + t-value="list(set(o.riga_contribution_ids.sorted(key='year', reverse=True).mapped('year')))" + /> + <t + t-set="type_contrib_riga" + t-value="list(set(o.riga_contribution_ids.mapped('type_contribution_id')))" + /> + <t t-foreach="year_riga" t-as="year"> + <tr + t-foreach="type_contrib_riga" + t-as="contrib" + > + <t + t-set="line" + t-value="o.riga_contribution_ids.filtered(lambda c: c.year == year and c.type_contribution_id == contrib)" + /> + <t t-if="line"> + <td><span t-esc="year" /></td> + <td><span + t-esc="', '.join(sorted(line.mapped('quarter')))" + /></td> + <td><span t-esc="contrib.name" /></td> + <td class="text-right"><span + t-esc="sum(line.mapped('amount'))" + t-options='{"widget": "monetary","display_currency": company.currency_id}' + /></td> + <td class="text-right"><span + t-esc="sum(line.mapped('amount_paid'))" + t-options='{"widget": "monetary","display_currency": company.currency_id}' + /></td> + <td class="text-right"><span + t-esc="sum(line.mapped('amount_due'))" + t-options='{"widget": "monetary","display_currency": company.currency_id}' + /></td> + + </t> + </tr> + </t> </tbody> <tfoot> <th colspan="5" class="text-right">Total impayé</th> - <td class="text-right"><span t-field="o.amount_due" t-options='{"widget": "monetary","display_currency": company.currency_id}' /></td> + <td class="text-right"><span + t-esc="total_amount" + t-options='{"widget": "monetary","display_currency": company.currency_id}' + /></td> </tfoot> </table> - <p class="pt32 font-italic"> + <p class="pt32"> Ces cotisations sont à régler par virement à l’ordre de la CGSCOP (IBAN : FR76 4255 9100 0008 0024 8837 710). Il convient de préciser dans le libellé votre numéro d’adhérent et/ou le nom de votre coopérative ainsi que la période réglée. </p> - <p class="font-italic"> + <p class=""> Pour une gestion simplifiée de vos cotisations, nous pouvons mettre en place le prélèvement automatique trimestriel. Vous êtes informés avant chaque prélèvement par courriel. <br /> - Vous pouvez faire une demande de mandat SEPA à <a href="mailto:administratif.cg@scop.coop">administratif.cg@scop.coop</a>. + Vous pouvez faire une demande de mandat SEPA à <a + href="mailto:administratif.cg@scop.coop" + >administratif.cg@scop.coop</a>. </p> - <p class="font-italic"> + <p class=""> Si vous rencontrez des difficultés ou si vous souhaitez des renseignements complémentaires sur le calcul de ces cotisations, nous vous invitons à contacter votre délégué.e au sein de votre Union Régionale. </p> - <p class="font-italic"> + <p class=""> Nous vous rappelons que les cotisations constituent les principales ressources de notre mouvement, et lui donnent les moyens d’agir en toute indépendance au service de ses adhérents, notamment au travers des outils financiers et des équipes de délégués de votre Union Régionale. </p> <p> diff --git a/views/scop_cotisation_cg_followup.xml b/views/scop_cotisation_cg_followup.xml index fedf9e6..e268c91 100644 --- a/views/scop_cotisation_cg_followup.xml +++ b/views/scop_cotisation_cg_followup.xml @@ -17,11 +17,11 @@ attrs="{'invisible': [('state', '!=', 'draft')]}" /> <button - name="cancel_followup" + name="reset_to_draft" type="object" - string="Annuler la relance" - confirm="Êtes-vous certain(e) de vouloir annuler cette relance ?" - attrs="{'invisible': [('state', '!=', 'draft')]}" + string="Remettre en brouillon" + confirm="Êtes-vous certain(e) de vouloir remettre en brouillon cette relance ?" + attrs="{'invisible': [('state', '!=', 'done')]}" /> <button name="action_report_followup_per_ur" @@ -77,6 +77,7 @@ <group string="Critères de la relance"> <group> <field name="date" required="1" /> + <field name="date_validation" readonly="1" /> <field name="threshold_year" /> <field name="threshold_amount" widget="monetary" /> <div class="text-muted" colspan="2"> diff --git a/views/scop_cotisation_cg_followup_line.xml b/views/scop_cotisation_cg_followup_line.xml index eccce18..aa7d775 100644 --- a/views/scop_cotisation_cg_followup_line.xml +++ b/views/scop_cotisation_cg_followup_line.xml @@ -174,13 +174,18 @@ <field name="model">scop.cotisation.cg.followup.line</field> <field name="arch" type="xml"> <tree string="Détail Relances"> + <field name="followup_state" invisible="1" /> <field name="followup_id" /> <field name="member_number" /> <field name="partner_id" /> <field name="ur_id" /> <field name="followup_level" /> <field name="amount_due" /> - <field name="is_excluded" widget="boolean_toggle" /> + <field + name="is_excluded" + widget="boolean_toggle" + attrs="{'readonly': [('followup_state', '=', 'done')]}" + /> <button name="print_followup" type="object" @@ -225,6 +230,21 @@ <field name="view_mode">tree,pivot,graph,form</field> </record> + <!-- Action server --> + <record id="action_exclude_followup_line" model="ir.actions.server"> + <field name="name">Exclure les relances</field> + <field name="model_id" ref="model_scop_cotisation_cg_followup_line" /> + <field + name="binding_model_id" + ref="cgscop_cotisation_cg_followup.model_scop_cotisation_cg_followup_line" + /> + <field name="binding_view_types">list</field> + <field name="state">code</field> + <field name="code"> + action = model.action_exclude_line() + </field> + </record> + <!-- Menu --> <menuitem name="Détail des Relances" -- GitLab