From ab38d2a34c41414158c80f1a4c61af4286c4d84f Mon Sep 17 00:00:00 2001
From: Benjamin <benjamin@le-filament.com>
Date: Tue, 2 Mar 2021 14:58:23 +0100
Subject: [PATCH] [add] payment schedule functionality

---
 __manifest__.py                |   1 +
 models/__init__.py             |   1 +
 models/account_invoice.py      | 158 +++++++++++++++++++++++++++++----
 models/account_payment_term.py |  53 +++++++++++
 models/scop_cotisation.py      |  13 ++-
 views/account_payment_term.xml |  21 +++++
 6 files changed, 224 insertions(+), 23 deletions(-)
 create mode 100644 models/account_payment_term.py
 create mode 100644 views/account_payment_term.xml

diff --git a/__manifest__.py b/__manifest__.py
index 058b54f..f5f616f 100755
--- a/__manifest__.py
+++ b/__manifest__.py
@@ -18,6 +18,7 @@
         "security/security_rules.xml",
         "security/ir.model.access.csv",
         "views/account_invoice.xml",
+        "views/account_payment_term.xml",
         "views/res_config_settings.xml",
         "views/scop_cotisation_task.xml",
     ],
diff --git a/models/__init__.py b/models/__init__.py
index 048e4e9..4db72cb 100755
--- a/models/__init__.py
+++ b/models/__init__.py
@@ -2,6 +2,7 @@
 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
 
 from . import account_invoice
+from . import account_payment_term
 from . import chart_template
 from . import res_company
 from . import res_config_settings
diff --git a/models/account_invoice.py b/models/account_invoice.py
index 57e14ac..b4f5c8b 100755
--- a/models/account_invoice.py
+++ b/models/account_invoice.py
@@ -1,7 +1,8 @@
 # © 2020 Le Filament (<http://www.le-filament.com>)
 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
 
-from odoo import models, fields, api
+from odoo import models, fields, api, _
+from odoo.exceptions import UserError
 
 
 class ScopAccountInvoice(models.Model):
@@ -44,11 +45,11 @@ class ScopAccountInvoice(models.Model):
     )
     nb_quarter = fields.Selection(
         string='Nombre de trimestres de cotisation',
-        selection=[('1', '1'),
-                   ('2', '2'),
-                   ('3', '3'),
-                   ('4', '4')],
-        default='4',
+        selection=[(1, '1'),
+                   (2, '2'),
+                   (3, '3'),
+                   (4, '4')],
+        default=4,
         required=True)
     is_sdd = fields.Boolean(
         'Au prélèvement',
@@ -77,6 +78,18 @@ class ScopAccountInvoice(models.Model):
     # ------------------------------------------------------
     # Override Parent
     # ------------------------------------------------------
+    def get_last_maturity_date(self, months, account_move_line_ids):
+        """
+        Get the last maturity date from account_move_line
+        for a certain period (months = [])
+        :param months:
+        :param account_move_line_ids:
+        :return: last date_maturity
+        """
+        line_ids = account_move_line_ids.filtered(
+            lambda l: l.date_maturity.month in months)
+        return line_ids[-1].date_maturity if line_ids else None
+
     @api.multi
     def action_invoice_open(self):
         """
@@ -84,6 +97,125 @@ class ScopAccountInvoice(models.Model):
         quand une facture cotisation devient valide
         """
         results = super(ScopAccountInvoice, self).action_invoice_open()
+        for inv in self:
+            if inv.is_contribution:
+                inv.set_scop_contribution()
+        return results
+
+    @api.multi
+    def action_move_create(self):
+        """
+        Complete override parent
+        Pass invoice in payment_term.compute function to generate payment
+        schedule
+        :return: True
+        """
+        account_move = self.env['account.move']
+
+        for inv in self:
+            if not inv.journal_id.sequence_id:
+                raise UserError(_('Please define sequence on the journal related to this invoice.'))
+            if not inv.invoice_line_ids.filtered(lambda line: line.account_id):
+                raise UserError(_('Please add at least one invoice line.'))
+            if inv.move_id:
+                continue
+
+            if not inv.date_invoice:
+                inv.write({'date_invoice': fields.Date.context_today(self)})
+            if not inv.date_due:
+                inv.write({'date_due': inv.date_invoice})
+            company_currency = inv.company_id.currency_id
+
+            # create move lines
+            # (one per invoice line + eventual taxes and analytic lines)
+            iml = inv.invoice_line_move_line_get()
+            iml += inv.tax_line_move_line_get()
+
+            diff_currency = inv.currency_id != company_currency
+            # create one move line for the total and possibly adjust
+            # the other lines amount
+            total, total_currency, iml = inv.compute_invoice_totals(
+                company_currency, iml)
+
+            name = inv.name or ''
+            if inv.payment_term_id:
+                totlines = inv.payment_term_id.with_context(
+                    currency_id=company_currency.id).compute(total, inv.date_invoice, inv)[0]
+                res_amount_currency = total_currency
+                print(totlines)
+                for i, t in enumerate(totlines):
+                    if inv.currency_id != company_currency:
+                        amount_currency = company_currency._convert(
+                            t[1],
+                            inv.currency_id,
+                            inv.company_id,
+                            inv._get_currency_rate_date() or fields.Date.today())
+                    else:
+                        amount_currency = False
+
+                    # last line: add the diff
+                    res_amount_currency -= amount_currency or 0
+                    if i + 1 == len(totlines):
+                        amount_currency += res_amount_currency
+
+                    iml.append({
+                        'type': 'dest',
+                        'name': name,
+                        'price': t[1],
+                        'account_id': inv.account_id.id,
+                        'date_maturity': t[0],
+                        'amount_currency': diff_currency and amount_currency,
+                        'currency_id': diff_currency and inv.currency_id.id,
+                        'invoice_id': inv.id
+                    })
+            else:
+                iml.append({
+                    'type': 'dest',
+                    'name': name,
+                    'price': total,
+                    'account_id': inv.account_id.id,
+                    'date_maturity': inv.date_due,
+                    'amount_currency': diff_currency and total_currency,
+                    'currency_id': diff_currency and inv.currency_id.id,
+                    'invoice_id': inv.id
+                })
+            part = self.env['res.partner']._find_accounting_partner(
+                inv.partner_id)
+            line = [(0, 0, self.line_get_convert(l, part.id)) for l in iml]
+            line = inv.group_lines(iml, line)
+
+            line = inv.finalize_invoice_move_lines(line)
+
+            date = inv.date or inv.date_invoice
+            move_vals = {
+                'ref': inv.reference,
+                'line_ids': line,
+                'journal_id': inv.journal_id.id,
+                'date': date,
+                'narration': inv.comment,
+            }
+            move = account_move.create(move_vals)
+            # Pass invoice in method post: used if you want to get the same
+            # account move reference when creating the same invoice
+            # after a cancelled one:
+            move.post(invoice = inv)
+            # make the invoice point to that move
+            vals = {
+                'move_id': move.id,
+                'date': date,
+                'move_name': move.name,
+            }
+            inv.write(vals)
+        return True
+
+    # ------------------------------------------------------
+    # Common Function
+    # ------------------------------------------------------
+    @api.multi
+    def set_scop_contribution(self):
+        """
+        Création d'une ligne dans scop.contribution
+        """
         for inv in self:
             if inv.is_contribution:
                 year = inv.year
@@ -116,16 +248,4 @@ class ScopAccountInvoice(models.Model):
                         [10, 11, 12], account_move_line_ids),
                     'invoice_id': inv.id,
                 })
-        return results
-
-    def get_last_maturity_date(self, months, account_move_line_ids):
-        """
-        Get the last maturity date from account_move_line
-        for a certain period (months = [])
-        :param months:
-        :param account_move_line_ids:
-        :return: last date_maturity
-        """
-        line_ids = account_move_line_ids.filtered(
-            lambda l: l.date_maturity.month in months)
-        return line_ids[-1].date_maturity if line_ids else None
+        return True
diff --git a/models/account_payment_term.py b/models/account_payment_term.py
new file mode 100644
index 0000000..7f21e60
--- /dev/null
+++ b/models/account_payment_term.py
@@ -0,0 +1,53 @@
+# Copyright 2020 Le Filament
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from odoo import fields, models, api
+
+
+class AccountPaymentTerm(models.Model):
+    _inherit = 'account.payment.term'
+
+    is_contribution = fields.Boolean('Conditions de paiement des cotisations')
+
+    def compute(self, value, date_ref=False, invoice=False):
+        """
+            Override la fonction compute du modèle account.payment.term
+            La fonction initiale checke les conditions de paiement et crée
+            les lignes de paiement associées
+
+            L'héritage permet de créer un échéancier en fonction de celui
+            défini sur la base de cotisations
+        """
+        date_ref = date_ref or fields.Date.today()
+        amount = value
+        sign = value < 0 and -1 or 1
+        if self.env.context.get('currency_id'):
+            currency = self.env['res.currency'].browse(
+                self.env.context['currency_id'])
+        else:
+            currency = self.env.user.company_id.currency_id
+
+        # si base de cotisation
+        if self.is_contribution and invoice and (invoice.cotisation_cg_id or invoice.cotisation_aura_id):
+            result = []
+            if invoice.cotisation_cg_id:
+                base_contrib_field = 'cotisation_cg_id'
+            elif invoice.cotisation_aura_id:
+                base_contrib_field = 'cotisation_aura_id'
+            trimesters = {
+                4: invoice[base_contrib_field].trimester_1,
+                3: invoice[base_contrib_field].trimester_2,
+                2: invoice[base_contrib_field].trimester_3,
+                1: invoice[base_contrib_field].trimester_4,
+            }
+            for i in range(invoice.nb_quarter, 0, -1):
+                # Gestion de l'arrondi de la division
+                if i == 1:
+                    amt = currency.round(amount)
+                else:
+                    amt = currency.round(value / invoice.nb_quarter)
+                result.append((fields.Date.to_string(trimesters.get(i)), amt))
+                amount -= amt
+            return [result]
+        else:
+            return super(AccountPaymentTerm, self).compute(value, date_ref)
diff --git a/models/scop_cotisation.py b/models/scop_cotisation.py
index bfc64d7..a3ca278 100644
--- a/models/scop_cotisation.py
+++ b/models/scop_cotisation.py
@@ -16,7 +16,6 @@ class ScopCotisation(models.AbstractModel):
             fields.Datetime.now().year - 1, fields.Datetime.now().year + 2)],
         string='Année de cotisation',
         required=True)
-
     company_id = fields.Many2one(
         comodel_name='res.company',
         string='Company', change_default=True,
@@ -29,7 +28,8 @@ class ScopCotisation(models.AbstractModel):
     payment_term_id = fields.Many2one(
         comodel_name='account.payment.term',
         string="Conditions de paiement",
-        required=True
+        domain=[('is_contribution', '=', True)],
+        required=True,
     )
     date_cotisation = fields.Date("Date de cotisation", required=True)
 
@@ -42,6 +42,10 @@ class ScopCotisation(models.AbstractModel):
     invoiced_member_count = fields.Integer(
         "Cotisations créées",
         compute='_compute_invoiced_member_count')
+    trimester_1 = fields.Date('1er Trimestre')
+    trimester_2 = fields.Date('2ème Trimestre')
+    trimester_3 = fields.Date('3ème Trimestre')
+    trimester_4 = fields.Date('4ème Trimestre')
 
     # ------------------------------------------------------
     # Compute fields
@@ -66,7 +70,7 @@ class ScopCotisation(models.AbstractModel):
     # Global functions
     # ------------------------------------------------------
     def create_contribution(self, product, partner, type_contribution,
-                            liasse=None, amount=0):
+                            liasse=None, amount=0, date=False):
         Invoice = self.env['account.invoice']
         InvoiceLine = self.env['account.invoice.line']
 
@@ -75,6 +79,7 @@ class ScopCotisation(models.AbstractModel):
             ('year', '=', self.year),
             ('type_contribution_id', '=', type_contribution)
         ])
+        date_invoice = date if date else self.date_cotisation
         if not exisiting_invoice:
             member_invoice = Invoice.create({
                 'partner_id': partner.id,
@@ -88,7 +93,7 @@ class ScopCotisation(models.AbstractModel):
                 'account_id': partner.property_account_receivable_id.id,
                 'payment_term_id': self.payment_term_id.id,
                 'payment_mode_id': partner.customer_payment_mode_id.id,
-                'date_invoice': self.date_cotisation,
+                'date_invoice': date_invoice,
             })
         else:
             member_invoice = exisiting_invoice
diff --git a/views/account_payment_term.xml b/views/account_payment_term.xml
new file mode 100644
index 0000000..926975d
--- /dev/null
+++ b/views/account_payment_term.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+    <!--  Copyright 2020 Le Filament
+          License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).  -->
+    <data>
+        
+        <record id="view_payment_term_form_inherited" model="ir.ui.view">
+            <field name="name">account.payment.term.form</field>
+            <field name="model">account.payment.term</field>
+            <field name="inherit_id" ref="account.view_payment_term_form"/>
+            <field name="arch" type="xml">
+                <xpath expr="//group" position="after">
+                    <group name="schedule">
+                        <field name="is_contribution" widget="boolean_toggle"/>
+                    </group>
+                </xpath>
+            </field>
+        </record>
+    
+    </data>
+</odoo>
\ No newline at end of file
-- 
GitLab