diff --git a/account_banking_mandate/__manifest__.py b/account_banking_mandate/__manifest__.py index 86fd7697d..1ee7d628c 100644 --- a/account_banking_mandate/__manifest__.py +++ b/account_banking_mandate/__manifest__.py @@ -1,7 +1,7 @@ # Copyright 2014 Compassion CH - Cyril Sester -# Copyright 2014 Tecnativa - Pedro M. Baeza # Copyright 2015-2020 Akretion - Alexis de Lattre -# Copyright 2017 Tecnativa - Carlos Dauden +# Copyright 2017 Tecnativa - Carlos Dauden +# Copyright 2014-2022 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). { @@ -23,7 +23,6 @@ "views/account_payment_line.xml", "views/res_partner_bank_view.xml", "views/res_partner.xml", - "views/bank_payment_line_view.xml", "data/mandate_reference_sequence.xml", "security/mandate_security.xml", "security/ir.model.access.csv", diff --git a/account_banking_mandate/models/__init__.py b/account_banking_mandate/models/__init__.py index 096b524b8..0de456248 100644 --- a/account_banking_mandate/models/__init__.py +++ b/account_banking_mandate/models/__init__.py @@ -4,5 +4,4 @@ from . import account_move from . import res_partner_bank from . import res_partner from . import account_payment_line -from . import bank_payment_line from . import account_move_line diff --git a/account_banking_mandate/models/bank_payment_line.py b/account_banking_mandate/models/bank_payment_line.py deleted file mode 100644 index 5eb6c80ba..000000000 --- a/account_banking_mandate/models/bank_payment_line.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2014 Compassion CH - Cyril Sester -# Copyright 2014 Serv. Tecnol. Avanzados - Pedro M. Baeza -# Copyright 2015-16 Akretion - Alexis de Lattre -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). - -from odoo import api, fields, models - - -class BankPaymentLine(models.Model): - _inherit = "bank.payment.line" - - mandate_id = fields.Many2one( - comodel_name="account.banking.mandate", - string="Direct Debit Mandate", - related="payment_line_ids.mandate_id", - check_company=True, - ) - - @api.model - def same_fields_payment_line_and_bank_payment_line(self): - res = super().same_fields_payment_line_and_bank_payment_line() - res.append("mandate_id") - return res diff --git a/account_banking_mandate/tests/test_invoice_mandate.py b/account_banking_mandate/tests/test_invoice_mandate.py index f7d302120..517be642d 100644 --- a/account_banking_mandate/tests/test_invoice_mandate.py +++ b/account_banking_mandate/tests/test_invoice_mandate.py @@ -1,4 +1,5 @@ # Copyright 2017 Creu Blanca +# Copyright 2017-2022 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). from odoo import fields @@ -9,27 +10,16 @@ from odoo.tests.common import TransactionCase class TestInvoiceMandate(TransactionCase): def test_post_invoice_01(self): self.invoice._onchange_partner_id() - + prev_orders = self.env["account.payment.order"].search([]) self.assertEqual(self.invoice.mandate_id, self.mandate) - self.invoice.action_post() - - payable_move_lines = self.invoice.line_ids.filtered( - lambda s: s.account_id == self.invoice_account - ) - if payable_move_lines: - self.assertEqual(payable_move_lines[0].move_id.mandate_id, self.mandate) - self.env["account.invoice.payment.line.multi"].with_context( active_model="account.move", active_ids=self.invoice.ids ).create({}).run() - - payment_order = self.env["account.payment.order"].search([]) + payment_order = self.env["account.payment.order"].search([]) - prev_orders self.assertEqual(len(payment_order.ids), 1) payment_order.payment_mode_id_change() payment_order.draft2open() - payment_order.open2generated() - payment_order.generated2uploaded() self.assertEqual(self.mandate.payment_line_ids_count, 1) def test_post_invoice_02(self): diff --git a/account_banking_mandate/views/bank_payment_line_view.xml b/account_banking_mandate/views/bank_payment_line_view.xml deleted file mode 100644 index d4f19a113..000000000 --- a/account_banking_mandate/views/bank_payment_line_view.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - banking.mandate.bank.payment.line.form - bank.payment.line - - - - - - - - - banking.mandate.bank.payment.line.tree - bank.payment.line - - - - - - - - diff --git a/account_banking_pain_base/__manifest__.py b/account_banking_pain_base/__manifest__.py index 4a5fb803a..5004ddbc7 100644 --- a/account_banking_pain_base/__manifest__.py +++ b/account_banking_pain_base/__manifest__.py @@ -1,7 +1,7 @@ # Copyright 2013-2016 Akretion - Alexis de Lattre -# Copyright 2014-2017 Tecnativa - Pedro M. Baeza # Copyright 2016 Tecnativa - Antonio Espinosa # Copyright 2021 Tecnativa - Carlos Roca +# Copyright 2014-2022 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). { @@ -18,7 +18,6 @@ "security/security.xml", "views/account_payment_line.xml", "views/account_payment_order.xml", - "views/bank_payment_line_view.xml", "views/account_payment_mode.xml", "views/res_config_settings.xml", "views/account_payment_method.xml", diff --git a/account_banking_pain_base/models/__init__.py b/account_banking_pain_base/models/__init__.py index af5bbf1f2..4f1503393 100644 --- a/account_banking_pain_base/models/__init__.py +++ b/account_banking_pain_base/models/__init__.py @@ -1,6 +1,5 @@ from . import account_payment_line from . import account_payment_order -from . import bank_payment_line from . import account_payment_mode from . import res_company from . import res_config_settings diff --git a/account_banking_pain_base/models/account_payment_line.py b/account_banking_pain_base/models/account_payment_line.py index e62dc92b4..3aed2d062 100644 --- a/account_banking_pain_base/models/account_payment_line.py +++ b/account_banking_pain_base/models/account_payment_line.py @@ -1,9 +1,9 @@ # Copyright 2013-2016 Akretion - Alexis de Lattre -# Copyright 2014 Serv. Tecnol. Avanzados - Pedro M. Baeza # Copyright 2021 Tecnativa - Carlos Roca +# Copyright 2014-2022 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -from odoo import fields, models +from odoo import api, fields, models class AccountPaymentLine(models.Model): @@ -173,3 +173,10 @@ class AccountPaymentLine(models.Model): communication_type = fields.Selection( selection_add=[("ISO", "ISO")], ondelete={"ISO": "cascade"} ) + + @api.model + def _get_payment_line_grouping_fields(self): + """Add specific PAIN fields to the grouping criteria.""" + res = super()._get_payment_line_grouping_fields() + res += ["priority", "local_instrument", "category_purpose", "purpose"] + return res diff --git a/account_banking_pain_base/models/account_payment_order.py b/account_banking_pain_base/models/account_payment_order.py index 820d2fe7e..1d4e0c9c9 100644 --- a/account_banking_pain_base/models/account_payment_order.py +++ b/account_banking_pain_base/models/account_payment_order.py @@ -1,7 +1,7 @@ # Copyright 2013-2016 Akretion - Alexis de Lattre -# Copyright 2014 Serv. Tecnol. Avanzados - Pedro M. Baeza # Copyright 2016 Antiun Ingenieria S.L. - Antonio Espinosa # Copyright 2021 Tecnativa - Carlos Roca +# Copyright 2014-2022 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). import logging @@ -610,11 +610,12 @@ class AccountPaymentOrder(models.Model): @api.model def generate_remittance_info_block(self, parent_node, line, gen_args): remittance_info = etree.SubElement(parent_node, "RmtInf") - if line.communication_type == "normal": + communication_type = line.payment_line_ids[:1].communication_type + if communication_type == "normal": remittance_info_unstructured = etree.SubElement(remittance_info, "Ustrd") remittance_info_unstructured.text = self._prepare_field( "Remittance Unstructured Information", - "line.communication", + "line.payment_reference", {"line": line}, 140, gen_args=gen_args, @@ -636,7 +637,7 @@ class AccountPaymentOrder(models.Model): creditor_ref_info_type_issuer = etree.SubElement( creditor_ref_info_type, "Issr" ) - creditor_ref_info_type_issuer.text = line.communication_type + creditor_ref_info_type_issuer.text = communication_type creditor_reference = etree.SubElement( creditor_ref_information, "CdtrRef" ) @@ -655,13 +656,13 @@ class AccountPaymentOrder(models.Model): creditor_ref_info_type_issuer = etree.SubElement( creditor_ref_info_type, "Issr" ) - creditor_ref_info_type_issuer.text = line.communication_type + creditor_ref_info_type_issuer.text = communication_type creditor_reference = etree.SubElement(creditor_ref_information, "Ref") creditor_reference.text = self._prepare_field( "Creditor Structured Reference", - "line.communication", + "line.payment_reference", {"line": line}, 35, gen_args=gen_args, diff --git a/account_banking_pain_base/models/bank_payment_line.py b/account_banking_pain_base/models/bank_payment_line.py deleted file mode 100644 index 0079b5fda..000000000 --- a/account_banking_pain_base/models/bank_payment_line.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright 2013-2016 Akretion - Alexis de Lattre -# Copyright 2021 Tecnativa - Carlos Roca -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). - -from odoo import api, fields, models - - -class BankPaymentLine(models.Model): - _inherit = "bank.payment.line" - - priority = fields.Selection(related="payment_line_ids.priority", string="Priority") - local_instrument = fields.Selection( - related="payment_line_ids.local_instrument", string="Local Instrument" - ) - category_purpose = fields.Selection( - related="payment_line_ids.category_purpose", string="Category Purpose" - ) - purpose = fields.Selection(related="payment_line_ids.purpose") - - @api.model - def same_fields_payment_line_and_bank_payment_line(self): - res = super().same_fields_payment_line_and_bank_payment_line() - res += ["priority", "local_instrument", "category_purpose", "purpose"] - return res diff --git a/account_banking_pain_base/views/bank_payment_line_view.xml b/account_banking_pain_base/views/bank_payment_line_view.xml deleted file mode 100644 index 247de1783..000000000 --- a/account_banking_pain_base/views/bank_payment_line_view.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - pain.base.bank.payment.line.form - bank.payment.line - - - - - - - - - - - - pain.base.bank.payment.line.tree - bank.payment.line - - - - - - - - - diff --git a/account_banking_sepa_credit_transfer/__manifest__.py b/account_banking_sepa_credit_transfer/__manifest__.py index 7e7f52037..348a59f2a 100644 --- a/account_banking_sepa_credit_transfer/__manifest__.py +++ b/account_banking_sepa_credit_transfer/__manifest__.py @@ -1,5 +1,6 @@ # Copyright 2010-2020 Akretion (www.akretion.com) -# Copyright 2016-2020 Tecnativa - Antonio Espinosa and Pedro M. Baeza +# Copyright 2016 Tecnativa - Antonio Espinosa +# Copyright 2016-2022 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) { @@ -7,7 +8,7 @@ "summary": "Create SEPA XML files for Credit Transfers", "version": "14.0.1.2.1", "license": "AGPL-3", - "author": "Akretion, " "Tecnativa, " "Odoo Community Association (OCA)", + "author": "Akretion, Tecnativa, Odoo Community Association (OCA)", "website": "https://github.com/OCA/bank-payment", "category": "Banking addons", "conflicts": ["account_sepa"], diff --git a/account_banking_sepa_credit_transfer/models/account_payment_order.py b/account_banking_sepa_credit_transfer/models/account_payment_order.py index 72e226d9b..7d5dc046b 100644 --- a/account_banking_sepa_credit_transfer/models/account_payment_order.py +++ b/account_banking_sepa_credit_transfer/models/account_payment_order.py @@ -1,5 +1,5 @@ # Copyright 2010-2020 Akretion (www.akretion.com) -# Copyright 2014-2020 Tecnativa - Pedro M. Baeza +# Copyright 2014-2022 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). from lxml import etree @@ -78,10 +78,11 @@ class AccountPaymentOrder(models.Model): lines_per_group = {} # key = (requested_date, priority, local_instrument, categ_purpose) # values = list of lines as object - for line in self.bank_line_ids: - priority = line.priority - local_instrument = line.local_instrument - categ_purpose = line.category_purpose + for line in self.payment_ids: + payment_line = line.payment_line_ids[:1] + priority = payment_line.priority + local_instrument = payment_line.local_instrument + categ_purpose = payment_line.category_purpose # The field line.date is the requested payment date # taking into account the 'date_prefered' setting # cf account_banking_payment_export/models/account_payment.py @@ -145,7 +146,7 @@ class AccountPaymentOrder(models.Model): ) instruction_identification.text = self._prepare_field( "Instruction Identification", - "line.name", + "str(line.move_id.id)", {"line": line}, 35, gen_args=gen_args, @@ -155,7 +156,7 @@ class AccountPaymentOrder(models.Model): ) end2end_identification.text = self._prepare_field( "End to End Identification", - "line.name", + "str(line.move_id.id)", {"line": line}, 35, gen_args=gen_args, @@ -171,9 +172,9 @@ class AccountPaymentOrder(models.Model): instructed_amount = etree.SubElement( amount, "InstdAmt", Ccy=currency_name ) - instructed_amount.text = "%.2f" % line.amount_currency - amount_control_sum_a += line.amount_currency - amount_control_sum_b += line.amount_currency + instructed_amount.text = "%.2f" % line.amount + amount_control_sum_a += line.amount + amount_control_sum_b += line.amount if not line.partner_bank_id: raise UserError( _( @@ -190,9 +191,10 @@ class AccountPaymentOrder(models.Model): gen_args, line, ) - if line.purpose: + line_purpose = line.payment_line_ids[:1].purpose + if line_purpose: purpose = etree.SubElement(credit_transfer_transaction_info, "Purp") - etree.SubElement(purpose, "Cd").text = line.purpose + etree.SubElement(purpose, "Cd").text = line_purpose self.generate_remittance_info_block( credit_transfer_transaction_info, line, gen_args ) diff --git a/account_banking_sepa_credit_transfer/tests/test_sct.py b/account_banking_sepa_credit_transfer/tests/test_sct.py index 7593716d9..8434ab498 100644 --- a/account_banking_sepa_credit_transfer/tests/test_sct.py +++ b/account_banking_sepa_credit_transfer/tests/test_sct.py @@ -1,6 +1,6 @@ # Copyright 2016 Akretion (Alexis de Lattre ) -# Copyright 2018 Tecnativa - Pedro M. Baeza # Copyright 2020 Sygel Technology - Valentin Vinagre +# Copyright 2018-2022 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). import base64 @@ -21,7 +21,6 @@ class TestSCT(SavepointCase): cls.journal_model = cls.env["account.journal"] cls.payment_order_model = cls.env["account.payment.order"] cls.payment_line_model = cls.env["account.payment.line"] - cls.bank_line_model = cls.env["bank.payment.line"] cls.partner_bank_model = cls.env["res.partner.bank"] cls.attachment_model = cls.env["ir.attachment"] cls.invoice_model = cls.env["account.move"] @@ -194,20 +193,16 @@ class TestSCT(SavepointCase): self.payment_order.draft2open() self.assertEqual(self.payment_order.state, "open") self.assertEqual(self.payment_order.sepa, True) - bank_lines = self.bank_line_model.search( - [("partner_id", "=", self.partner_agrolait.id)] - ) - self.assertEqual(len(bank_lines), 1) - agrolait_bank_line = bank_lines[0] + self.assertTrue(self.payment_order.payment_ids) + agrolait_bank_line = self.payment_order.payment_ids[0] self.assertEqual(agrolait_bank_line.currency_id, self.eur_currency) self.assertEqual( agrolait_bank_line.currency_id.compare_amounts( - agrolait_bank_line.amount_currency, 49.0 + agrolait_bank_line.amount, 49.0 ), 0, ) - self.assertEqual(agrolait_bank_line.communication_type, "normal") - self.assertEqual(agrolait_bank_line.communication, "F1341-F1342-A1301") + self.assertEqual(agrolait_bank_line.payment_reference, "F1341-F1342-A1301") self.assertEqual(agrolait_bank_line.partner_bank_id, invoice1.partner_bank_id) action = self.payment_order.open2generated() @@ -286,20 +281,14 @@ class TestSCT(SavepointCase): self.payment_order.draft2open() self.assertEqual(self.payment_order.state, "open") self.assertEqual(self.payment_order.sepa, False) - bank_lines = self.bank_line_model.search( - [("partner_id", "=", self.partner_asus.id)] - ) - self.assertEqual(len(bank_lines), 1) - asus_bank_line = bank_lines[0] + self.assertEqual(self.payment_order.payment_count, 1) + asus_bank_line = self.payment_order.payment_ids[0] self.assertEqual(asus_bank_line.currency_id, self.usd_currency) self.assertEqual( - asus_bank_line.currency_id.compare_amounts( - asus_bank_line.amount_currency, 3054.0 - ), + asus_bank_line.currency_id.compare_amounts(asus_bank_line.amount, 3054.0), 0, ) - self.assertEqual(asus_bank_line.communication_type, "normal") - self.assertEqual(asus_bank_line.communication, "Inv9032-Inv9033") + self.assertEqual(asus_bank_line.payment_reference, "Inv9032-Inv9033") self.assertEqual(asus_bank_line.partner_bank_id, invoice1.partner_bank_id) action = self.payment_order.open2generated() diff --git a/account_banking_sepa_direct_debit/__manifest__.py b/account_banking_sepa_direct_debit/__manifest__.py index b24955f6e..19ee398b5 100644 --- a/account_banking_sepa_direct_debit/__manifest__.py +++ b/account_banking_sepa_direct_debit/__manifest__.py @@ -1,5 +1,6 @@ # Copyright 2013-2020 Akretion (www.akretion.com) -# Copyright 2014-2020 Tecnativa - Pedro M. Baeza & Antonio Espinosa +# Copyright 2016 Tecnativa - Antonio Espinosa +# Copyright 2014-2022 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). { @@ -7,7 +8,7 @@ "summary": "Create SEPA files for Direct Debit", "version": "14.0.1.3.3", "license": "AGPL-3", - "author": "Akretion, " "Tecnativa, " "Odoo Community Association (OCA)", + "author": "Akretion, Tecnativa, Odoo Community Association (OCA)", "website": "https://github.com/OCA/bank-payment", "category": "Banking addons", "depends": ["account_banking_pain_base", "account_banking_mandate"], diff --git a/account_banking_sepa_direct_debit/models/__init__.py b/account_banking_sepa_direct_debit/models/__init__.py index 3a13c7d5b..6902b752e 100644 --- a/account_banking_sepa_direct_debit/models/__init__.py +++ b/account_banking_sepa_direct_debit/models/__init__.py @@ -1,6 +1,5 @@ from . import res_company from . import account_banking_mandate -from . import bank_payment_line from . import account_payment_mode from . import account_payment_method from . import account_payment_order diff --git a/account_banking_sepa_direct_debit/models/account_payment_order.py b/account_banking_sepa_direct_debit/models/account_payment_order.py index 0e0704202..8ee79aa7a 100644 --- a/account_banking_sepa_direct_debit/models/account_payment_order.py +++ b/account_banking_sepa_direct_debit/models/account_payment_order.py @@ -1,5 +1,5 @@ # Copyright 2020 Akretion (Alexis de Lattre ) -# Copyright 2018 Tecnativa - Pedro M. Baeza +# Copyright 2018-2022 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from lxml import etree @@ -72,16 +72,17 @@ class AccountPaymentOrder(models.Model): lines_per_group = {} # key = (requested_date, priority, sequence type) # value = list of lines as objects - for line in self.bank_line_ids: + for line in self.payment_ids: transactions_count_a += 1 - priority = line.priority - categ_purpose = line.category_purpose - scheme = line.mandate_id.scheme - if line.mandate_id.type == "oneoff": + payment_line = line.payment_line_ids[:1] + priority = payment_line.priority + categ_purpose = payment_line.category_purpose + scheme = payment_line.mandate_id.scheme + if payment_line.mandate_id.type == "oneoff": seq_type = "OOFF" - elif line.mandate_id.type == "recurrent": + elif payment_line.mandate_id.type == "recurrent": seq_type_map = {"recurring": "RCUR", "first": "FRST", "final": "FNAL"} - seq_type_label = line.mandate_id.recurrent_sequence_type + seq_type_label = payment_line.mandate_id.recurrent_sequence_type assert seq_type_label is not False seq_type = seq_type_map[seq_type_label] else: @@ -90,7 +91,7 @@ class AccountPaymentOrder(models.Model): "Invalid mandate type in '%s'. Valid ones are 'Recurrent' " "or 'One-Off'" ) - % line.mandate_id.unique_mandate_reference + % payment_line.mandate_id.unique_mandate_reference ) # The field line.date is the requested payment date # taking into account the 'date_preferred' setting @@ -165,7 +166,7 @@ class AccountPaymentOrder(models.Model): ) instruction_identification.text = self._prepare_field( "Instruction Identification", - "line.name", + "str(line.move_id.id)", {"line": line}, 35, gen_args=gen_args, @@ -175,7 +176,7 @@ class AccountPaymentOrder(models.Model): ) end2end_identification.text = self._prepare_field( "End to End Identification", - "line.name", + "str(line.move_id.id)", {"line": line}, 35, gen_args=gen_args, @@ -190,18 +191,19 @@ class AccountPaymentOrder(models.Model): instructed_amount = etree.SubElement( dd_transaction_info, "InstdAmt", Ccy=currency_name ) - instructed_amount.text = "%.2f" % line.amount_currency - amount_control_sum_a += line.amount_currency - amount_control_sum_b += line.amount_currency + instructed_amount.text = "%.2f" % line.amount + amount_control_sum_a += line.amount + amount_control_sum_b += line.amount dd_transaction = etree.SubElement(dd_transaction_info, "DrctDbtTx") mandate_related_info = etree.SubElement(dd_transaction, "MndtRltdInf") mandate_identification = etree.SubElement( mandate_related_info, "MndtId" ) + mandate = line.payment_line_ids[:1].mandate_id mandate_identification.text = self._prepare_field( "Unique Mandate Reference", - "line.mandate_id.unique_mandate_reference", - {"line": line}, + "mandate.unique_mandate_reference", + {"mandate": mandate}, 35, gen_args=gen_args, ) @@ -211,16 +213,11 @@ class AccountPaymentOrder(models.Model): mandate_signature_date.text = self._prepare_field( "Mandate Signature Date", "signature_date", - { - "line": line, - "signature_date": fields.Date.to_string( - line.mandate_id.signature_date - ), - }, + {"signature_date": fields.Date.to_string(mandate.signature_date)}, 10, gen_args=gen_args, ) - if sequence_type == "FRST" and line.mandate_id.last_debit_date: + if sequence_type == "FRST" and mandate.last_debit_date: amendment_indicator = etree.SubElement( mandate_related_info, "AmdmntInd" ) @@ -252,10 +249,10 @@ class AccountPaymentOrder(models.Model): gen_args, line, ) - - if line.purpose: + line_purpose = line.payment_line_ids[:1].purpose + if line_purpose: purpose = etree.SubElement(dd_transaction_info, "Purp") - etree.SubElement(purpose, "Cd").text = line.purpose + etree.SubElement(purpose, "Cd").text = line_purpose self.generate_remittance_info_block(dd_transaction_info, line, gen_args) @@ -281,18 +278,19 @@ class AccountPaymentOrder(models.Model): to_expire_mandates = abmo.browse([]) first_mandates = abmo.browse([]) all_mandates = abmo.browse([]) - for bline in order.bank_line_ids: - if bline.mandate_id in all_mandates: + for payment in order.payment_ids: + mandate = payment.payment_line_ids.mandate_id + if mandate in all_mandates: continue - all_mandates += bline.mandate_id - if bline.mandate_id.type == "oneoff": - to_expire_mandates += bline.mandate_id - elif bline.mandate_id.type == "recurrent": - seq_type = bline.mandate_id.recurrent_sequence_type + all_mandates += mandate + if mandate.type == "oneoff": + to_expire_mandates += mandate + elif mandate.type == "recurrent": + seq_type = mandate.recurrent_sequence_type if seq_type == "final": - to_expire_mandates += bline.mandate_id + to_expire_mandates += mandate elif seq_type == "first": - first_mandates += bline.mandate_id + first_mandates += mandate all_mandates.write({"last_debit_date": order.date_generated}) to_expire_mandates.write({"state": "expired"}) first_mandates.write({"recurrent_sequence_type": "recurring"}) diff --git a/account_banking_sepa_direct_debit/models/bank_payment_line.py b/account_banking_sepa_direct_debit/models/bank_payment_line.py deleted file mode 100644 index 5c4e69073..000000000 --- a/account_banking_sepa_direct_debit/models/bank_payment_line.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2020 Akretion - Alexis de Lattre -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). - -from odoo import models - - -class BankPaymentLine(models.Model): - _inherit = "bank.payment.line" - - def move_line_offsetting_account_hashcode(self): - """ - From my experience, even when you ask several direct debits - at the same date with enough delay, you will have several credits - on your bank statement: one for each mandate types. - So we split the transfer move lines by mandate type, so easier - reconciliation of the bank statement. - """ - hashcode = super().move_line_offsetting_account_hashcode() - hashcode += "-" + str(self.mandate_id.recurrent_sequence_type) - return hashcode diff --git a/account_banking_sepa_direct_debit/tests/test_sdd.py b/account_banking_sepa_direct_debit/tests/test_sdd.py index 3800cd9cd..da55422b8 100644 --- a/account_banking_sepa_direct_debit/tests/test_sdd.py +++ b/account_banking_sepa_direct_debit/tests/test_sdd.py @@ -1,5 +1,5 @@ # Copyright 2016 Akretion (Alexis de Lattre ) -# Copyright 2018-2020 Tecnativa - Pedro M. Baeza +# Copyright 2018-2022 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). import base64 @@ -42,7 +42,6 @@ class TestSDDBase(SavepointCase): cls.payment_order_model = cls.env["account.payment.order"] cls.payment_line_model = cls.env["account.payment.line"] cls.mandate_model = cls.env["account.banking.mandate"] - cls.bank_line_model = cls.env["bank.payment.line"] cls.partner_bank_model = cls.env["res.partner.bank"] cls.attachment_model = cls.env["ir.attachment"] cls.invoice_model = cls.env["account.move"] @@ -255,22 +254,14 @@ class TestSDDBase(SavepointCase): payment_order.draft2open() self.assertEqual(payment_order.state, "open") self.assertEqual(payment_order.sepa, True) - # Check bank payment line - bank_lines = self.bank_line_model.search( - [("partner_id", "=", self.partner_agrolait.id)] - ) - self.assertEqual(len(bank_lines), 1) - agrolait_bank_line = bank_lines[0] + # Check account payment + agrolait_bank_line = payment_order.payment_ids[0] self.assertEqual(agrolait_bank_line.currency_id, self.eur_currency) self.assertEqual( - float_compare( - agrolait_bank_line.amount_currency, 42.0, precision_digits=accpre - ), + float_compare(agrolait_bank_line.amount, 42.0, precision_digits=accpre), 0, ) - self.assertEqual(agrolait_bank_line.communication_type, "normal") - self.assertEqual(agrolait_bank_line.communication, invoice1.name) - self.assertEqual(agrolait_bank_line.mandate_id, invoice1.mandate_id) + self.assertEqual(agrolait_bank_line.payment_reference, invoice1.name) self.assertEqual( agrolait_bank_line.partner_bank_id, invoice1.mandate_id.partner_bank_id ) diff --git a/account_payment_order/__manifest__.py b/account_payment_order/__manifest__.py index d7df6f648..8523ce67d 100644 --- a/account_payment_order/__manifest__.py +++ b/account_payment_order/__manifest__.py @@ -1,9 +1,9 @@ # © 2009 EduSense BV () # © 2011-2013 Therp BV () # © 2013-2014 ACSONE SA (). -# © 2014-2016 Tecnativa - Pedro M. Baeza # © 2016 Akretion (). # © 2016 Aselcis (). +# © 2014-2022 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). { @@ -29,7 +29,6 @@ "views/account_payment_mode.xml", "views/account_payment_order.xml", "views/account_payment_line.xml", - "views/bank_payment_line.xml", "views/account_move_line.xml", "views/ir_attachment.xml", "views/account_invoice_view.xml", diff --git a/account_payment_order/data/payment_seq.xml b/account_payment_order/data/payment_seq.xml index 0d80d10ff..7b288105d 100644 --- a/account_payment_order/data/payment_seq.xml +++ b/account_payment_order/data/payment_seq.xml @@ -1,18 +1,10 @@ - - Bank Payment Line - bank.payment.line - L - 5 - - Payment Line account.payment.line diff --git a/account_payment_order/migrations/14.0.2.0.0/post-migration.py b/account_payment_order/migrations/14.0.2.0.0/post-migration.py new file mode 100644 index 000000000..25fdf053e --- /dev/null +++ b/account_payment_order/migrations/14.0.2.0.0/post-migration.py @@ -0,0 +1,174 @@ +# Copyright 2022 Tecnativa - Pedro M. Baeza +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging + +from openupgradelib import openupgrade + +_logger = logging.getLogger(__name__) + + +def _insert_account_payments(env): + openupgrade.logged_query( + env.cr, "ALTER TABLE account_payment ADD old_bank_payment_line_id INT4" + ) + # Create an account.payment record for each bank.payment.line + openupgrade.logged_query( + env.cr, + """ + INSERT INTO account_payment ( + create_date, create_uid, write_date, write_uid, old_bank_payment_line_name, + payment_order_id, partner_id, amount, currency_id, + payment_method_id, old_bank_payment_line_id, payment_type, + partner_type, + destination_account_id, payment_reference, move_id + ) + SELECT + bpl.create_date, bpl.create_uid, bpl.write_date, bpl.write_uid, bpl.name, + bpl.order_id, bpl.partner_id, bpl.amount_currency, 1, + apm.payment_method_id, bpl.id, apo.payment_type, + CASE WHEN apo.payment_type = 'inbound' THEN 'customer' ELSE 'supplier' END, + aml.account_id, bpl.communication, aml.move_id + FROM bank_payment_line bpl + JOIN account_payment_order apo ON apo.id = bpl.order_id + JOIN account_payment_mode apm ON apm.id = apo.payment_mode_id + LEFT JOIN account_move_line aml ON aml.bank_payment_line_id = bpl.id + """, + ) + # As the information is asymmetric: N payment lines > 1 bank payment line, but there + # are some related non-stored fields to payment lines, we need a second query to + # update some of the fields + openupgrade.logged_query( + env.cr, + """ + UPDATE account_payment ap + SET currency_id = apl.currency_id, + partner_bank_id = apl.partner_bank_id + FROM account_payment_line apl + WHERE apl.bank_line_id = ap.old_bank_payment_line_id + """, + ) + + +def _create_hooks(env): + """Avoid errors due to locked dates, overriding involved methods.""" + + def _check_fiscalyear_lock_date(self): + return True + + def _check_tax_lock_date(self): + return True + + def _check_reconciliation(self): + return True + + # create hooks + _check_fiscalyear_lock_date._original_method = type( + env["account.move"] + )._check_fiscalyear_lock_date + type(env["account.move"])._check_fiscalyear_lock_date = _check_fiscalyear_lock_date + _check_tax_lock_date._original_method = type( + env["account.move.line"] + )._check_tax_lock_date + type(env["account.move.line"])._check_tax_lock_date = _check_tax_lock_date + _check_reconciliation._original_method = type( + env["account.move.line"] + )._check_reconciliation + type(env["account.move.line"])._check_reconciliation = _check_reconciliation + + +def create_moves_from_orphan_account_payments(env): + """Recreate missing journal entries on the newly created account payments.""" + env.cr.execute( + """ + SELECT ap.id, MIN(apl.date), MIN(bpl.company_id), MIN(apo.name), + MIN(apo.journal_id), MIN(apl.currency_id), MIN(apo.state), MIN(apo.id) + FROM bank_payment_line bpl + JOIN account_payment ap ON ap.old_bank_payment_line_id = bpl.id + JOIN account_payment_order apo ON apo.id = bpl.order_id + JOIN account_payment_line apl ON apl.bank_line_id = bpl.id + LEFT JOIN account_move_line aml ON aml.bank_payment_line_id = bpl.id + WHERE aml.move_id IS NULL + GROUP BY ap.id + """ + ) + deprecated_acc_by_company = {} + for row in env.cr.fetchall(): + payment = ( + env["account.payment"] + .with_context( + check_move_validity=False, + tracking_disable=True, + ) + .browse(row[0]) + ) + move = env["account.move"].create( + { + "name": "/", + "date": row[1], + "payment_id": payment.id, + "move_type": "entry", + "company_id": row[2], + "ref": row[3], + "journal_id": row[4], + "currency_id": row[5], + "state": "draft" if row[6] in {"open", "generated"} else "cancel", + "payment_order_id": row[7], + } + ) + payment.move_id = move + # Avoid deprecated account warning + if payment.company_id not in deprecated_acc_by_company: + deprecated_accounts = env["account.account"].search( + [("deprecated", "=", True), ("company_id", "=", payment.company_id.id)] + ) + deprecated_acc_by_company[payment.company_id] = deprecated_accounts + deprecated_accounts.deprecated = False + try: + payment._synchronize_to_moves(["date"]) # no more changed fields needed + except Exception as e: + _logger.error("Failed for payment with id %s: %s", payment.id, e) + raise + # Restore deprecated accounts + for deprecated_accounts in deprecated_acc_by_company.values(): + deprecated_accounts.deprecated = True + + +def _delete_hooks(env): + """Restore the locking dates original methods.""" + type(env["account.move"])._check_fiscalyear_lock_date = type( + env["account.move"] + )._check_fiscalyear_lock_date._original_method + type(env["account.move.line"])._check_tax_lock_date = type( + env["account.move.line"] + )._check_tax_lock_date._original_method + type(env["account.move.line"])._check_reconciliation = type( + env["account.move.line"] + )._check_reconciliation._original_method + + +def _insert_payment_line_payment_link(env): + openupgrade.logged_query( + env.cr, + """ + INSERT INTO account_payment_account_payment_line_rel + (account_payment_id, account_payment_line_id) + SELECT ap.id, apl.id + FROM account_payment_line apl + JOIN account_payment ap ON ap.old_bank_payment_line_id = apl.bank_line_id + """, + ) + + +@openupgrade.migrate() +def migrate(env, version): + openupgrade.logged_query( + env.cr, "ALTER TABLE account_payment ALTER move_id DROP NOT NULL" + ) + _insert_account_payments(env) + _create_hooks(env) + create_moves_from_orphan_account_payments(env) + openupgrade.logged_query( + env.cr, "ALTER TABLE account_payment ALTER move_id SET NOT NULL" + ) + _delete_hooks(env) + _insert_payment_line_payment_link(env) diff --git a/account_payment_order/models/__init__.py b/account_payment_order/models/__init__.py index e5537935e..e686e3db3 100644 --- a/account_payment_order/models/__init__.py +++ b/account_payment_order/models/__init__.py @@ -1,7 +1,6 @@ from . import account_payment_mode from . import account_payment_order from . import account_payment_line -from . import bank_payment_line from . import account_move from . import account_move_line from . import res_bank diff --git a/account_payment_order/models/account_move_line.py b/account_payment_order/models/account_move_line.py index f95a17340..d72acd389 100644 --- a/account_payment_order/models/account_move_line.py +++ b/account_payment_order/models/account_move_line.py @@ -18,12 +18,6 @@ class AccountMoveLine(models.Model): help="Bank account on which we should pay the supplier", check_company=True, ) - bank_payment_line_id = fields.Many2one( - comodel_name="bank.payment.line", - readonly=True, - index=True, - check_company=True, - ) payment_line_ids = fields.One2many( comodel_name="account.payment.line", inverse_name="move_line_id", diff --git a/account_payment_order/models/account_payment.py b/account_payment_order/models/account_payment.py index 6decc5e98..7acb4f5c8 100644 --- a/account_payment_order/models/account_payment.py +++ b/account_payment_order/models/account_payment.py @@ -1,12 +1,18 @@ # Copyright 2019 ACSONE SA/NV +# Copyright 2022 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, models +from odoo import api, fields, models class AccountPayment(models.Model): _inherit = "account.payment" + payment_order_id = fields.Many2one(comodel_name="account.payment.order") + payment_line_ids = fields.Many2many(comodel_name="account.payment.line") + # Compatibility with previous approach for returns - To be removed on v16 + old_bank_payment_line_name = fields.Char() + def _get_default_journal(self): res = super()._get_default_journal() return res.filtered(lambda journal: not journal.inbound_payment_order_only) diff --git a/account_payment_order/models/account_payment_line.py b/account_payment_order/models/account_payment_line.py index 4eddb58de..44c61ea03 100644 --- a/account_payment_order/models/account_payment_line.py +++ b/account_payment_order/models/account_payment_line.py @@ -77,19 +77,17 @@ class AccountPaymentLine(models.Model): communication_type = fields.Selection( selection=[("normal", "Free")], required=True, default="normal" ) - bank_line_id = fields.Many2one( - comodel_name="bank.payment.line", - string="Bank Payment Line", + payment_ids = fields.Many2many( + comodel_name="account.payment", + string="Payment transaction", readonly=True, - index=True, - check_company=True, ) _sql_constraints = [ ( "name_company_unique", "unique(name, company_id)", - "A payment line already exists with this reference " "in the same company!", + "A payment line already exists with this reference in the same company!", ) ] @@ -114,11 +112,21 @@ class AccountPaymentLine(models.Model): else: line.amount_company_currency = 0 + @api.model + def _get_payment_line_grouping_fields(self): + """This list of fields is used o compute the grouping hashcode.""" + return [ + "currency_id", + "partner_id", + "partner_bank_id", + "date", + "communication_type", + ] + def payment_line_hashcode(self): self.ensure_one() - bplo = self.env["bank.payment.line"] values = [] - for field in bplo.same_fields_payment_line_and_bank_payment_line(): + for field in self._get_payment_line_grouping_fields(): values.append(str(self[field])) # Don't group the payment lines that are attached to the same supplier # but to move lines with different accounts (very unlikely), @@ -128,8 +136,7 @@ class AccountPaymentLine(models.Model): # otherwise it would break the structured communication system ! if self.communication_type != "normal": values.append(str(self.id)) - hashcode = "-".join(values) - return hashcode + return "-".join(values) @api.onchange("partner_id") def partner_id_change(self): @@ -168,3 +175,45 @@ class AccountPaymentLine(models.Model): ) if not self.communication: raise UserError(_("Communication is empty on payment line %s.") % self.name) + + def _prepare_account_payment_vals(self): + """Prepare the dictionary to create an account payment record from a set of + payment lines. + """ + journal = self.order_id.journal_id + vals = { + "payment_type": self.order_id.payment_type, + "partner_id": self.partner_id.id, + "destination_account_id": self.move_line_id.account_id.id, + "company_id": self.order_id.company_id.id, + "amount": sum(self.mapped("amount_currency")), + "date": self[:1].date, + "currency_id": self.currency_id.id, + "ref": self.order_id.name, + "payment_reference": "-".join([line.communication for line in self]), + "journal_id": journal.id, + "partner_bank_id": self.partner_bank_id.id, + "payment_order_id": self.order_id.id, + "payment_method_id": self.order_id.payment_mode_id.payment_method_id.id, + "payment_line_ids": [(6, 0, self.ids)], + } + # Determine partner_type + move_type = self[:1].move_line_id.move_id.move_type + if move_type in {"out_invoice", "out_refund"}: + vals["partner_type"] = "customer" + elif move_type in {"in_invoice", "in_refund"}: + vals["partner_type"] = "supplier" + else: + p_type = "customer" if vals["payment_type"] == "inbound" else "supplier" + vals["partner_type"] = p_type + # Fill destination account if manual payment line with no linked journal item + if not vals["destination_account_id"]: + if vals["partner_type"] == "customer": + vals[ + "destination_account_id" + ] = self.partner_id.property_account_receivable_id.id + else: + vals[ + "destination_account_id" + ] = self.partner_id.property_account_payable_id.id + return vals diff --git a/account_payment_order/models/account_payment_order.py b/account_payment_order/models/account_payment_order.py index 2846221b9..843568d19 100644 --- a/account_payment_order/models/account_payment_order.py +++ b/account_payment_order/models/account_payment_order.py @@ -1,7 +1,7 @@ # © 2009 EduSense BV () # © 2011-2013 Therp BV () -# © 2016 Serv. Tecnol. Avanzados - Pedro M. Baeza # © 2016 Akretion (Alexis de Lattre - alexis.delattre@akretion.com) +# Copyright 2016-2022 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). import base64 @@ -121,24 +121,19 @@ class AccountPaymentOrder(models.Model): readonly=True, states={"draft": [("readonly", False)]}, ) - bank_line_ids = fields.One2many( - comodel_name="bank.payment.line", - inverse_name="order_id", - string="Bank Transactions", + payment_ids = fields.One2many( + comodel_name="account.payment", + inverse_name="payment_order_id", + string="Payment Transactions", readonly=True, - help="The bank payment lines are used to generate the payment file. " - "They are automatically created from transaction lines upon " - "confirmation of the payment order: one bank payment line can " - "group several transaction lines if the option " - "'Group Transactions in Payment Orders' is active on the payment " - "mode.", + ) + payment_count = fields.Integer( + compute="_compute_payment_count", + string="Number of Payment Transactions", ) total_company_currency = fields.Monetary( compute="_compute_total", store=True, currency_field="company_currency_id" ) - bank_line_count = fields.Integer( - compute="_compute_bank_line_count", string="Number of Bank Transactions" - ) move_ids = fields.One2many( comodel_name="account.move", inverse_name="payment_order_id", @@ -207,10 +202,10 @@ class AccountPaymentOrder(models.Model): rec.mapped("payment_line_ids.amount_company_currency") or [0.0] ) - @api.depends("bank_line_ids") - def _compute_bank_line_count(self): + @api.depends("payment_ids") + def _compute_payment_count(self): for order in self: - order.bank_line_count = len(order.bank_line_ids) + order.payment_count = len(order.payment_ids) @api.depends("move_ids") def _compute_move_count(self): @@ -250,11 +245,6 @@ class AccountPaymentOrder(models.Model): self.date_prefered = self.payment_mode_id.default_date_prefered def action_uploaded_cancel(self): - for move in self.move_ids: - move.button_cancel() - for move_line in move.line_ids: - move_line.remove_move_reconcile() - move.with_context(force_delete=True).unlink() self.action_cancel() return True @@ -263,27 +253,19 @@ class AccountPaymentOrder(models.Model): return True def action_cancel(self): - for order in self: - order.write({"state": "cancel"}) - order.bank_line_ids.unlink() + # Unreconcile and cancel payments + self.payment_ids.action_draft() + self.payment_ids.action_cancel() + self.write({"state": "cancel"}) return True - @api.model - def _prepare_bank_payment_line(self, paylines): - return { - "order_id": paylines[0].order_id.id, - "payment_line_ids": [(6, 0, paylines.ids)], - "communication": "-".join([line.communication for line in paylines]), - } - def draft2open(self): """ Called when you click on the 'Confirm' button Set the 'date' on payment line depending on the 'date_prefered' setting of the payment.order - Re-generate the bank payment lines + Re-generate the account payments. """ - bplo = self.env["bank.payment.line"] today = fields.Date.context_today(self) for order in self: if not order.journal_id: @@ -302,9 +284,11 @@ class AccountPaymentOrder(models.Model): raise UserError( _("There are no transactions on payment order %s.") % order.name ) - # Delete existing bank payment lines - order.bank_line_ids.unlink() - # Create the bank payment lines from the payment lines + # Unreconcile, cancel and delete existing account payments + order.payment_ids.action_draft() + order.payment_ids.action_cancel() + order.payment_ids.unlink() + # Prepare account payments from the payment lines group_paylines = {} # key = hashcode for payline in order.payment_line_ids: payline.draft2open_payment_line_check() @@ -361,7 +345,8 @@ class AccountPaymentOrder(models.Model): "total": payline.amount_currency, } order.recompute() - # Create bank payment lines + # Create account payments + payment_vals = [] for paydict in list(group_paylines.values()): # Block if a bank payment line is <= 0 if paydict["total"] <= 0: @@ -369,8 +354,8 @@ class AccountPaymentOrder(models.Model): _("The amount for Partner '%s' is negative " "or null (%.2f) !") % (paydict["paylines"][0].partner_id.name, paydict["total"]) ) - vals = self._prepare_bank_payment_line(paydict["paylines"]) - bplo.create(vals) + payment_vals.append(paydict["paylines"]._prepare_account_payment_vals()) + self.env["account.payment"].create(payment_vals) self.write({"state": "open"}) return True @@ -422,165 +407,20 @@ class AccountPaymentOrder(models.Model): return action def generated2uploaded(self): - for order in self: - if order.payment_mode_id.generate_move: - order.generate_move() + self.payment_ids.action_post() + # Perform the reconciliation of payments and source journal items + for payment in self.payment_ids: + ( + payment.payment_line_ids.move_line_id + + payment.move_id.line_ids.filtered( + lambda x: x.account_id == payment.destination_account_id + ) + ).reconcile() self.write( {"state": "uploaded", "date_uploaded": fields.Date.context_today(self)} ) return True - def _prepare_move(self, bank_lines=None): - if self.payment_type == "outbound": - ref = _("Payment order %s") % self.name - else: - ref = _("Debit order %s") % self.name - if bank_lines and len(bank_lines) == 1: - ref += " - " + bank_lines.name - vals = { - "date": bank_lines[0].date, - "journal_id": self.journal_id.id, - "ref": ref, - "payment_order_id": self.id, - "line_ids": [], - } - total_company_currency = total_payment_currency = 0 - for bline in bank_lines: - total_company_currency += bline.amount_company_currency - total_payment_currency += bline.amount_currency - partner_ml_vals = self._prepare_move_line_partner_account(bline) - vals["line_ids"].append((0, 0, partner_ml_vals)) - trf_ml_vals = self._prepare_move_line_offsetting_account( - total_company_currency, total_payment_currency, bank_lines - ) - vals["line_ids"].append((0, 0, trf_ml_vals)) - return vals - - def _prepare_move_line_offsetting_account( - self, amount_company_currency, amount_payment_currency, bank_lines - ): - vals = {} - if self.payment_type == "outbound": - name = _("Payment order %s") % self.name - account_id = self.journal_id.payment_credit_account_id.id - else: - name = _("Debit order %s") % self.name - account_id = self.journal_id.payment_debit_account_id.id - - partner_id = False - for index, bank_line in enumerate(bank_lines): - if index == 0: - partner_id = bank_line.payment_line_ids[0].partner_id.id - elif bank_line.payment_line_ids[0].partner_id.id != partner_id: - # we have different partners in the grouped move - partner_id = False - break - vals.update( - { - "name": name, - "partner_id": partner_id, - "account_id": account_id, - "credit": ( - self.payment_type == "outbound" and amount_company_currency or 0.0 - ), - "debit": ( - self.payment_type == "inbound" and amount_company_currency or 0.0 - ), - } - ) - if bank_lines[0].currency_id != bank_lines[0].company_currency_id: - sign = self.payment_type == "outbound" and -1 or 1 - vals.update( - { - "currency_id": bank_lines[0].currency_id.id, - "amount_currency": amount_payment_currency * sign, - } - ) - return vals - - def _prepare_move_line_partner_account(self, bank_line): - if bank_line.payment_line_ids[0].move_line_id: - account_id = bank_line.payment_line_ids[0].move_line_id.account_id.id - else: - if self.payment_type == "inbound": - account_id = bank_line.partner_id.property_account_receivable_id.id - else: - account_id = bank_line.partner_id.property_account_payable_id.id - if self.payment_type == "outbound": - name = _("Payment bank line %s") % bank_line.name - else: - name = _("Debit bank line %s") % bank_line.name - vals = { - "name": name, - "bank_payment_line_id": bank_line.id, - "partner_id": bank_line.partner_id.id, - "account_id": account_id, - "credit": ( - self.payment_type == "inbound" - and bank_line.amount_company_currency - or 0.0 - ), - "debit": ( - self.payment_type == "outbound" - and bank_line.amount_company_currency - or 0.0 - ), - } - - if bank_line.currency_id != bank_line.company_currency_id: - sign = self.payment_type == "inbound" and -1 or 1 - vals.update( - { - "currency_id": bank_line.currency_id.id, - "amount_currency": bank_line.amount_currency * sign, - } - ) - return vals - - def _create_reconcile_move(self, hashcode, blines): - self.ensure_one() - post_move = self.payment_mode_id.post_move - am_obj = self.env["account.move"] - mvals = self._prepare_move(blines) - move = am_obj.create(mvals) - if post_move: - move.action_post() - blines.reconcile_payment_lines() - - def _prepare_trf_moves(self): - """ - prepare a dict "trfmoves" that can be used when - self.payment_mode_id.move_option = date or line - key = unique identifier (date or True or line.id) - value = bank_pay_lines (recordset that can have several entries) - """ - self.ensure_one() - trfmoves = {} - for bline in self.bank_line_ids: - hashcode = bline.move_line_offsetting_account_hashcode() - if hashcode in trfmoves: - trfmoves[hashcode] += bline - else: - trfmoves[hashcode] = bline - return trfmoves - - def generate_move(self): - """ - Create the moves that pay off the move lines from - the payment/debit order. - """ - self.ensure_one() - trfmoves = self._prepare_trf_moves() - for hashcode, blines in trfmoves.items(): - self._create_reconcile_move(hashcode, blines) - - def action_bank_payment_line(self): - self.ensure_one() - action = self.env.ref("account_payment_order.bank_payment_line_action") - action_dict = action.read()[0] - action_dict["domain"] = [("id", "in", self.bank_line_ids.ids)] - return action_dict - def action_move_journal_line(self): self.ensure_one() action = self.env.ref("account.action_move_journal_line").sudo().read()[0] diff --git a/account_payment_order/models/bank_payment_line.py b/account_payment_order/models/bank_payment_line.py deleted file mode 100644 index 27e0ae324..000000000 --- a/account_payment_order/models/bank_payment_line.py +++ /dev/null @@ -1,201 +0,0 @@ -# Copyright 2015-2016 Akretion - Alexis de Lattre -# Copyright 2018 Tecnativa - Pedro M. Baeza -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). - -from odoo import _, api, fields, models -from odoo.exceptions import UserError - - -class BankPaymentLine(models.Model): - _name = "bank.payment.line" - _description = "Bank Payment Lines" - _check_company_auto = True - - name = fields.Char(string="Bank Payment Line Ref", required=True, readonly=True) - order_id = fields.Many2one( - comodel_name="account.payment.order", - ondelete="cascade", - index=True, - readonly=True, - check_company=True, - ) - payment_type = fields.Selection( - related="order_id.payment_type", readonly=True, store=True - ) - state = fields.Selection(related="order_id.state", readonly=True, store=True) - payment_line_ids = fields.One2many( - comodel_name="account.payment.line", - inverse_name="bank_line_id", - string="Payment Lines", - readonly=True, - ) - partner_id = fields.Many2one( - comodel_name="res.partner", - related="payment_line_ids.partner_id", - readonly=True, - store=True, - check_company=True, - ) # store=True for groupby - # Function Float fields are sometimes badly displayed in tree view, - # see bug report https://github.com/odoo/odoo/issues/8632 - # But is it still true in v9 ? - amount_currency = fields.Monetary( - string="Amount", - currency_field="currency_id", - compute="_compute_amount", - store=True, - readonly=True, - ) - amount_company_currency = fields.Monetary( - string="Amount in Company Currency", - currency_field="company_currency_id", - compute="_compute_amount", - store=True, - readonly=True, - ) - currency_id = fields.Many2one( - comodel_name="res.currency", - required=True, - readonly=True, - related="payment_line_ids.currency_id", - ) - partner_bank_id = fields.Many2one( - comodel_name="res.partner.bank", - string="Bank Account", - readonly=True, - related="payment_line_ids.partner_bank_id", - check_company=True, - ) - date = fields.Date(related="payment_line_ids.date", readonly=True) - communication_type = fields.Selection( - related="payment_line_ids.communication_type", readonly=True - ) - communication = fields.Char(string="Communication", required=True, readonly=True) - company_id = fields.Many2one( - comodel_name="res.company", - related="order_id.payment_mode_id.company_id", - store=True, - readonly=True, - ) - company_currency_id = fields.Many2one( - comodel_name="res.currency", - related="order_id.payment_mode_id.company_id.currency_id", - readonly=True, - store=True, - ) - - @api.model - def same_fields_payment_line_and_bank_payment_line(self): - """ - This list of fields is used both to compute the grouping - hashcode and to copy the values from payment line - to bank payment line - The fields must have the same name on the 2 objects - """ - same_fields = [ - "currency_id", - "partner_id", - "partner_bank_id", - "date", - "communication_type", - ] - return same_fields - - @api.depends("payment_line_ids", "payment_line_ids.amount_currency") - def _compute_amount(self): - for bline in self: - amount_currency = sum(bline.mapped("payment_line_ids.amount_currency")) - amount_company_currency = bline.currency_id._convert( - amount_currency, - bline.company_currency_id, - bline.company_id, - bline.date or fields.Date.today(), - ) - bline.amount_currency = amount_currency - bline.amount_company_currency = amount_company_currency - - @api.model - @api.returns("self") - def create(self, vals): - if vals.get("name", "New") == "New": - vals["name"] = ( - self.env["ir.sequence"].next_by_code("bank.payment.line") or "New" - ) - return super(BankPaymentLine, self).create(vals) - - def move_line_offsetting_account_hashcode(self): - """ - This method is inherited in the module - account_banking_sepa_direct_debit - """ - self.ensure_one() - if self.order_id.payment_mode_id.move_option == "date": - hashcode = fields.Date.to_string(self.date) - else: - hashcode = str(self.id) - return hashcode - - def reconcile_payment_lines(self): - for bline in self: - if all([pline.move_line_id for pline in bline.payment_line_ids]): - bline.reconcile() - else: - bline.no_reconcile_hook() - - def no_reconcile_hook(self): - """This method is designed to be inherited if needed""" - return - - def reconcile(self): - self.ensure_one() - amlo = self.env["account.move.line"] - transit_mlines = amlo.search([("bank_payment_line_id", "=", self.id)]) - assert len(transit_mlines) == 1, "We should have only 1 move" - transit_mline = transit_mlines[0] - assert not transit_mline.reconciled, "Transit move should not be reconciled" - lines_to_rec = transit_mline - for payment_line in self.payment_line_ids: - - if not payment_line.move_line_id: - raise UserError( - _( - "Can not reconcile: no move line for " - "payment line %s of partner '%s'." - ) - % (payment_line.name, payment_line.partner_id.name) - ) - if payment_line.move_line_id.reconciled: - raise UserError( - _("Move line '%s' of partner '%s' has already " "been reconciled") - % (payment_line.move_line_id.name, payment_line.partner_id.name) - ) - if payment_line.move_line_id.account_id != transit_mline.account_id: - raise UserError( - _( - "For partner '%s', the account of the account " - "move line to pay (%s) is different from the " - "account of of the transit move line (%s)." - ) - % ( - payment_line.move_line_id.partner_id.name, - payment_line.move_line_id.account_id.code, - transit_mline.account_id.code, - ) - ) - - lines_to_rec += payment_line.move_line_id - - lines_to_rec.reconcile() - - def unlink(self): - for line in self: - order_state = line.order_id.state - if order_state == "uploaded": - raise UserError( - _( - "Cannot delete a payment order line whose payment order is" - " in state '%s'. You need to cancel it first." - ) - % order_state - ) - return super(BankPaymentLine, self).unlink() diff --git a/account_payment_order/security/ir.model.access.csv b/account_payment_order/security/ir.model.access.csv index 11512c93f..d23d69ab9 100644 --- a/account_payment_order/security/ir.model.access.csv +++ b/account_payment_order/security/ir.model.access.csv @@ -1,7 +1,7 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_account_payment_order,Full access on account.payment.order to Payment Manager,model_account_payment_order,group_account_payment,1,1,1,1 access_account_payment_line,Full access on account.payment.line to Payment Manager,model_account_payment_line,group_account_payment,1,1,1,1 -access_bank_payment_line,Full access on bank.payment.line to Payment Manager,model_bank_payment_line,group_account_payment,1,1,1,1 +access_bank_payment_line,Full access on account.payment to Payment Manager,account.model_account_payment,group_account_payment,1,1,1,1 base.access_res_partner_bank_group_partner_manager,Full access on res.partner.bank to Account Payment group,base.model_res_partner_bank,group_account_payment,1,1,1,1 base.access_res_bank_group_partner_manager,Full access on res.bank to Account Payment group,base.model_res_bank,group_account_payment,1,1,1,1 access_account_payment_line_create,access_account_payment_line_create,model_account_payment_line_create,group_account_payment,1,1,1,1 diff --git a/account_payment_order/security/payment_security.xml b/account_payment_order/security/payment_security.xml index 38a7336eb..a3f0a2050 100644 --- a/account_payment_order/security/payment_security.xml +++ b/account_payment_order/security/payment_security.xml @@ -25,12 +25,5 @@ name="domain_force" >['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] - - Bank payment line multi-company rule - - ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] - diff --git a/account_payment_order/tests/test_payment_order_inbound.py b/account_payment_order/tests/test_payment_order_inbound.py index 38be186e2..3903665f5 100644 --- a/account_payment_order/tests/test_payment_order_inbound.py +++ b/account_payment_order/tests/test_payment_order_inbound.py @@ -1,6 +1,6 @@ # Copyright 2017 Camptocamp SA # Copyright 2017 Creu Blanca -# Copyright 2019 Tecnativa - Pedro M. Baeza +# Copyright 2019-2022 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from datetime import date, timedelta @@ -97,12 +97,12 @@ class TestPaymentOrderInbound(TestPaymentOrderInboundBase): payment_order.write({"journal_id": self.journal.id}) self.assertEqual(len(payment_order.payment_line_ids), 1) - self.assertEqual(len(payment_order.bank_line_ids), 0) + self.assertFalse(payment_order.payment_ids) # Open payment order payment_order.draft2open() - self.assertEqual(payment_order.bank_line_count, 1) + self.assertEqual(payment_order.payment_count, 1) # Generate and upload payment_order.open2generated() @@ -112,10 +112,6 @@ class TestPaymentOrderInbound(TestPaymentOrderInboundBase): with self.assertRaises(UserError): payment_order.unlink() - bank_line = payment_order.bank_line_ids - - with self.assertRaises(UserError): - bank_line.unlink() payment_order.action_uploaded_cancel() self.assertEqual(payment_order.state, "cancel") payment_order.cancel2draft() diff --git a/account_payment_order/tests/test_payment_order_outbound.py b/account_payment_order/tests/test_payment_order_outbound.py index 8383ec50b..f80379841 100644 --- a/account_payment_order/tests/test_payment_order_outbound.py +++ b/account_payment_order/tests/test_payment_order_outbound.py @@ -1,5 +1,6 @@ # © 2017 Camptocamp SA # © 2017 Creu Blanca +# Copyright 2022 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from datetime import date, datetime, timedelta @@ -203,7 +204,7 @@ class TestPaymentOrderOutbound(TestPaymentOrderOutboundBase): order.draft2open() order.open2generated() order.generated2uploaded() - self.assertEqual(order.move_ids[0].date, order.bank_line_ids[0].date) + self.assertEqual(order.move_ids[0].date, order.payment_ids[0].date) self.assertEqual(order.state, "uploaded") def test_cancel_payment_order(self): @@ -220,13 +221,11 @@ class TestPaymentOrderOutbound(TestPaymentOrderOutboundBase): payment_order.write({"journal_id": self.bank_journal.id}) self.assertEqual(len(payment_order.payment_line_ids), 1) - self.assertEqual(len(payment_order.bank_line_ids), 0) + self.assertFalse(payment_order.payment_ids) # Open payment order payment_order.draft2open() - - self.assertEqual(payment_order.bank_line_count, 1) - + self.assertEqual(payment_order.payment_count, 1) # Generate and upload payment_order.open2generated() payment_order.generated2uploaded() @@ -235,10 +234,6 @@ class TestPaymentOrderOutbound(TestPaymentOrderOutboundBase): with self.assertRaises(UserError): payment_order.unlink() - bank_line = payment_order.bank_line_ids - - with self.assertRaises(UserError): - bank_line.unlink() payment_order.action_uploaded_cancel() self.assertEqual(payment_order.state, "cancel") payment_order.cancel2draft() @@ -294,19 +289,19 @@ class TestPaymentOrderOutbound(TestPaymentOrderOutboundBase): self.assertEqual(len(outbound_order.payment_line_ids), 2) self.assertEqual(outbound_order.payment_line_ids[1].date, False) # Open payment order - self.assertEqual(len(outbound_order.bank_line_ids), 0) + self.assertFalse(outbound_order.payment_ids) outbound_order.draft2open() - self.assertEqual(outbound_order.bank_line_count, 2) + self.assertEqual(outbound_order.payment_count, 2) self.assertEqual( outbound_order.payment_line_ids[0].date, - outbound_order.payment_line_ids[0].bank_line_id.date, + outbound_order.payment_line_ids[0].payment_ids.date, ) self.assertEqual( outbound_order.payment_line_ids[1].date, fields.Date.context_today(outbound_order), ) self.assertEqual( - outbound_order.payment_line_ids[1].bank_line_id.date, + outbound_order.payment_line_ids[1].payment_ids.date, fields.Date.context_today(outbound_order), ) diff --git a/account_payment_order/views/account_move_line.xml b/account_payment_order/views/account_move_line.xml index b6d9cd1f2..52a7ebf7a 100644 --- a/account_payment_order/views/account_move_line.xml +++ b/account_payment_order/views/account_move_line.xml @@ -15,7 +15,6 @@ name="partner_bank_id" domain="[('partner_id', '=', partner_id), '|', ('company_id', '=', company_id), ('company_id', '=', False)]" /> - diff --git a/account_payment_order/views/account_payment_line.xml b/account_payment_order/views/account_payment_line.xml index 63ada1dc2..d9d308c76 100644 --- a/account_payment_order/views/account_payment_line.xml +++ b/account_payment_order/views/account_payment_line.xml @@ -40,7 +40,7 @@ /> - + diff --git a/account_payment_order/views/account_payment_order.xml b/account_payment_order/views/account_payment_order.xml index 195352028..40a163632 100644 --- a/account_payment_order/views/account_payment_order.xml +++ b/account_payment_order/views/account_payment_order.xml @@ -98,7 +98,7 @@ /> @@ -122,11 +122,11 @@ /> - + @@ -150,9 +150,9 @@ diff --git a/account_payment_order/views/bank_payment_line.xml b/account_payment_order/views/bank_payment_line.xml deleted file mode 100644 index 52d57e8bf..000000000 --- a/account_payment_order/views/bank_payment_line.xml +++ /dev/null @@ -1,106 +0,0 @@ - - - - - bank.payment.line.form - bank.payment.line - -
- - - - - - - - - - - - - - - -
-
-
- - bank.payment.line.tree - bank.payment.line - - - - - - - - - - - - - - - - bank.payment.line.search - bank.payment.line - - - - - - - - - - - - - - Bank Payment Lines - bank.payment.line - tree,form - {'bank_payment_line_main_view': True} - - -
diff --git a/account_payment_order_vendor_email/models/account_payment_mode.py b/account_payment_order_vendor_email/models/account_payment_mode.py index 3ae72e287..eaaaf3a21 100644 --- a/account_payment_order_vendor_email/models/account_payment_mode.py +++ b/account_payment_order_vendor_email/models/account_payment_mode.py @@ -1,4 +1,5 @@ # Copyright (C) 2020 Open Source Integrators +# Copyright 2022 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from datetime import datetime @@ -25,10 +26,10 @@ class PaymentOrder(models.Model): for rec in self: if rec.payment_mode_id.send_email_to_partner: date_generated = rec.date_generated - for bank_line in rec.bank_line_ids: - partner_name = bank_line.partner_id.name - total_amount = bank_line.amount_currency - payment_ref = bank_line.name + for payment in rec.payment_ids: + partner_name = payment.partner_id.name + total_amount = payment.amount + payment_ref = payment.payment_reference line_data = [] header_data = { "inv_no": "Invoice No.", @@ -42,7 +43,7 @@ class PaymentOrder(models.Model): "due_amount": "Due Amount", } line_data.append(header_data) - for payment_line in bank_line.payment_line_ids: + for payment_line in payment.payment_line_ids: invoice_date = ( payment_line.move_line_id.move_id.invoice_date and datetime.strftime( @@ -69,7 +70,7 @@ class PaymentOrder(models.Model): "account_payment_order_vendor_email." "ach_payment_email_template" ) - partner_email_id = bank_line.partner_id.email + partner_email_id = payment.partner_id.email if partner_email_id: template.write({"email_to": partner_email_id}) template.with_context(