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