Merge PR #979 into 14.0

Signed-off-by pedrobaeza
This commit is contained in:
OCA-git-bot
2023-01-01 16:46:12 +00:00
37 changed files with 393 additions and 820 deletions

View File

@@ -1,7 +1,7 @@
# Copyright 2014 Compassion CH - Cyril Sester <csester@compassion.ch>
# Copyright 2014 Tecnativa - Pedro M. Baeza
# Copyright 2015-2020 Akretion - Alexis de Lattre <alexis.delattre@akretion.com>
# Copyright 2017 Tecnativa - Carlos Dauden <carlos.dauden@tecnativa.com>
# 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",

View File

@@ -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

View File

@@ -1,23 +0,0 @@
# Copyright 2014 Compassion CH - Cyril Sester <csester@compassion.ch>
# Copyright 2014 Serv. Tecnol. Avanzados - Pedro M. Baeza
# Copyright 2015-16 Akretion - Alexis de Lattre <alexis.delattre@akretion.com>
# 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

View File

@@ -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):

View File

@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
© 2015-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="bank_payment_line_form" model="ir.ui.view">
<field name="name">banking.mandate.bank.payment.line.form</field>
<field name="model">bank.payment.line</field>
<field name="inherit_id" ref="account_payment_order.bank_payment_line_form" />
<field name="arch" type="xml">
<field name="partner_bank_id" position="after">
<field
name="mandate_id"
invisible="context.get('default_payment_type')!='inbound'"
/>
</field>
</field>
</record>
<record id="bank_payment_line_tree" model="ir.ui.view">
<field name="name">banking.mandate.bank.payment.line.tree</field>
<field name="model">bank.payment.line</field>
<field name="inherit_id" ref="account_payment_order.bank_payment_line_tree" />
<field name="arch" type="xml">
<field name="partner_bank_id" position="after">
<field
name="mandate_id"
string="Mandate"
invisible="context.get('default_payment_type')!='inbound'"
/>
</field>
</field>
</record>
</odoo>

View File

@@ -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",

View File

@@ -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

View File

@@ -1,9 +1,9 @@
# Copyright 2013-2016 Akretion - Alexis de Lattre <alexis.delattre@akretion.com>
# 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

View File

@@ -1,7 +1,7 @@
# Copyright 2013-2016 Akretion - Alexis de Lattre <alexis.delattre@akretion.com>
# 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,

View File

@@ -1,24 +0,0 @@
# Copyright 2013-2016 Akretion - Alexis de Lattre <alexis.delattre@akretion.com>
# 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

View File

@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
© 2015-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="bank_payment_line_form" model="ir.ui.view">
<field name="name">pain.base.bank.payment.line.form</field>
<field name="model">bank.payment.line</field>
<field name="inherit_id" ref="account_payment_order.bank_payment_line_form" />
<field name="arch" type="xml">
<field name="partner_bank_id" position="after">
<field name="priority" />
<field name="local_instrument" />
<field name="category_purpose" />
<field name="purpose" />
</field>
</field>
</record>
<record id="bank_payment_line_tree" model="ir.ui.view">
<field name="name">pain.base.bank.payment.line.tree</field>
<field name="model">bank.payment.line</field>
<field name="inherit_id" ref="account_payment_order.bank_payment_line_tree" />
<field name="arch" type="xml">
<field name="communication" position="after">
<field name="priority" optional="hide" />
</field>
</field>
</record>
</odoo>

View File

@@ -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"],

View File

@@ -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
)

View File

@@ -1,6 +1,6 @@
# Copyright 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# 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()

View File

@@ -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"],

View File

@@ -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

View File

@@ -1,5 +1,5 @@
# Copyright 2020 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# 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"})

View File

@@ -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

View File

@@ -1,5 +1,5 @@
# Copyright 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# 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
)

View File

@@ -1,9 +1,9 @@
# © 2009 EduSense BV (<http://www.edusense.nl>)
# © 2011-2013 Therp BV (<https://therp.nl>)
# © 2013-2014 ACSONE SA (<https://acsone.eu>).
# © 2014-2016 Tecnativa - Pedro M. Baeza
# © 2016 Akretion (<https://www.akretion.com>).
# © 2016 Aselcis (<https://www.aselcis.com>).
# © 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",

View File

@@ -1,18 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
© 2015-2016 Akretion (https://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
Copyright 2019 Tecnativa - Pedro M. Baeza
Copyright 2015-2016 Akretion - Alexis de Lattre
Copyright 2019-2022 Tecnativa - Pedro M. Baeza
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-->
<odoo noupdate="1">
<record id="bank_payment_line_seq" model="ir.sequence">
<field name="name">Bank Payment Line</field>
<field name="code">bank.payment.line</field>
<field name="prefix">L</field>
<field name="padding">5</field>
<field name="company_id" eval="False" />
</record>
<record id="account_payment_line_seq" model="ir.sequence">
<field name="name">Payment Line</field>
<field name="code">account.payment.line</field>

View File

@@ -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)

View File

@@ -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

View File

@@ -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",

View File

@@ -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)

View File

@@ -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

View File

@@ -1,7 +1,7 @@
# © 2009 EduSense BV (<http://www.edusense.nl>)
# © 2011-2013 Therp BV (<https://therp.nl>)
# © 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]

View File

@@ -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()

View File

@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_payment_order Full access on account.payment.order to Payment Manager model_account_payment_order group_account_payment 1 1 1 1
3 access_account_payment_line Full access on account.payment.line to Payment Manager model_account_payment_line group_account_payment 1 1 1 1
4 access_bank_payment_line Full access on bank.payment.line to Payment Manager Full access on account.payment to Payment Manager model_bank_payment_line account.model_account_payment group_account_payment 1 1 1 1
5 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
6 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
7 access_account_payment_line_create access_account_payment_line_create model_account_payment_line_create group_account_payment 1 1 1 1

View File

@@ -25,12 +25,5 @@
name="domain_force"
>['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
</record>
<record id="bank_payment_line_company_rule" model="ir.rule">
<field name="name">Bank payment line multi-company rule</field>
<field name="model_id" ref="model_bank_payment_line" />
<field
name="domain_force"
>['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
</record>
</data>
</odoo>

View File

@@ -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()

View File

@@ -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),
)

View File

@@ -15,7 +15,6 @@
name="partner_bank_id"
domain="[('partner_id', '=', partner_id), '|', ('company_id', '=', company_id), ('company_id', '=', False)]"
/>
<field name="bank_payment_line_id" />
</group>
</field>
</record>

View File

@@ -40,7 +40,7 @@
/>
<field name="amount_company_currency" />
<field name="company_currency_id" invisible="1" />
<field name="bank_line_id" />
<field name="payment_ids" />
<field name="payment_type" invisible="1" />
</group>
</group>

View File

@@ -98,7 +98,7 @@
/>
<field name="payment_type" invisible="0" />
<field
name="bank_line_count"
name="payment_count"
attrs="{'invisible': [('state', 'in', ('draft', 'cancel'))]}"
/>
</group>
@@ -122,11 +122,11 @@
/>
</page>
<page
name="bank-lines"
string="Bank Transactions"
name="payment-lines"
string="Payment Transactions"
attrs="{'invisible': [('state', 'in', ('draft', 'cancel'))]}"
>
<field name="bank_line_ids" edit="0" create="0" />
<field name="payment_ids" edit="0" create="0" />
</page>
</notebook>
</sheet>
@@ -150,9 +150,9 @@
<field name="date_uploaded" />
<field name="description" optional="show" />
<field
name="bank_line_count"
name="payment_count"
optional="hide"
string="Bank Transactions"
string="Payment Transactions"
/>
<field name="total_company_currency" sum="Total Company Currency" />
<field name="company_currency_id" invisible="1" />

View File

@@ -1,106 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<!--
© 2015-2016 Akretion (https://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-->
<record id="bank_payment_line_form" model="ir.ui.view">
<field name="name">bank.payment.line.form</field>
<field name="model">bank.payment.line</field>
<field name="arch" type="xml">
<form string="Bank Payment Line" create="false">
<group name="main">
<field
name="order_id"
invisible="not context.get('bank_payment_line_main_view')"
/>
<field name="name" />
<field
name="company_id"
groups="base.group_multi_company"
invisible="not context.get('bank_payment_line_main_view')"
/>
<field name="partner_id" />
<field name="date" />
<field name="amount_currency" />
<field name="currency_id" invisible="1" />
<field name="partner_bank_id" />
<field name="communication_type" />
<field name="communication" />
</group>
<group string="Related Payment Lines" name="payment-lines">
<field name="payment_line_ids" nolabel="1" />
</group>
</form>
</field>
</record>
<record id="bank_payment_line_tree" model="ir.ui.view">
<field name="name">bank.payment.line.tree</field>
<field name="model">bank.payment.line</field>
<field name="arch" type="xml">
<tree string="Bank Payment Lines" create="false">
<field
name="order_id"
invisible="not context.get('bank_payment_line_main_view')"
/>
<field name="partner_id" />
<field name="communication" />
<field name="partner_bank_id" />
<field name="date" />
<field name="amount_currency" sum="Total Amount" />
<field name="currency_id" invisible="1" />
<field name="name" optional="show" />
<field
name="company_id"
groups="base.group_multi_company"
invisible="not context.get('bank_payment_line_main_view')"
/>
</tree>
</field>
</record>
<record id="bank_payment_line_search" model="ir.ui.view">
<field name="name">bank.payment.line.search</field>
<field name="model">bank.payment.line</field>
<field name="arch" type="xml">
<search string="Search Bank Payment Lines">
<field name="partner_id" />
<filter
name="inbound"
string="Inbound"
domain="[('payment_type', '=', 'inbound')]"
/>
<filter
name="outbound"
string="Outbound"
domain="[('payment_type', '=', 'outbound')]"
/>
<group string="Group By" name="groupby">
<filter
name="state_groupby"
string="State"
context="{'group_by': 'state'}"
/>
<filter
name="partner_groupby"
string="Partner"
context="{'group_by': 'partner_id'}"
/>
</group>
</search>
</field>
</record>
<record id="bank_payment_line_action" model="ir.actions.act_window">
<field name="name">Bank Payment Lines</field>
<field name="res_model">bank.payment.line</field>
<field name="view_mode">tree,form</field>
<field name="context">{'bank_payment_line_main_view': True}</field>
</record>
<menuitem
id="bank_payment_line_menu"
action="bank_payment_line_action"
parent="account.menu_finance_payables"
sequence="50"
groups="group_account_payment"
/>
</odoo>

View File

@@ -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(