mirror of
https://github.com/OCA/bank-payment.git
synced 2025-02-02 10:37:31 +02:00
[REF+IMP] account_payment_order: Use native payments
The previous approach creates manually the journal entries and does all the hard work, plus not being 100% compatible with the bank statement reconciliation widget (requiring a patch on OCB to see blue lines). That decision made sense on the moment it was done (v9), where the native payment model (account.payment) was very limited, and wasn't able to store all the needed information for the bank transaction. Now that the limitations are gone, we can get rid off this extra model, and generate instead `account.payment` records, using both the native model + methods to perform the same operations. This serves also to workaround the problem found in #966. All the code, views and tests of main module have been adapted to this new approach in this commit. Later commits will adapt the rest of the modules of the suite, and add migration scripts to transit from the previous approach to this new one. TT39832
This commit is contained in:
@@ -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-2023 Tecnativa - Pedro M. Baeza
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
|
||||
@@ -30,7 +30,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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
# 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")
|
||||
|
||||
def _get_default_journal(self):
|
||||
res = super()._get_default_journal()
|
||||
return res.filtered(lambda journal: not journal.inbound_payment_order_only)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
# © 2016 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
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class AccountPaymentMode(models.Model):
|
||||
@@ -72,29 +71,6 @@ class AccountPaymentMode(models.Model):
|
||||
"(other modules can set additional fields to restrict the "
|
||||
"grouping.)",
|
||||
)
|
||||
generate_move = fields.Boolean(
|
||||
string="Generate Accounting Entries On File Upload", default=True
|
||||
)
|
||||
move_option = fields.Selection(
|
||||
selection=[
|
||||
("date", "One move per payment date"),
|
||||
("line", "One move per payment line"),
|
||||
],
|
||||
default="date",
|
||||
)
|
||||
post_move = fields.Boolean(default=True)
|
||||
|
||||
@api.constrains("generate_move", "move_option")
|
||||
def transfer_move_constrains(self):
|
||||
for mode in self:
|
||||
if mode.generate_move and not mode.move_option:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"On the payment mode '%s', you must "
|
||||
"choose an option for the 'Move Option' parameter."
|
||||
)
|
||||
% mode.name
|
||||
)
|
||||
|
||||
@api.onchange("payment_method_id")
|
||||
def payment_method_id_change(self):
|
||||
@@ -116,11 +92,3 @@ class AccountPaymentMode(models.Model):
|
||||
]
|
||||
).ids
|
||||
self.default_journal_ids = [(6, 0, aj_ids)]
|
||||
|
||||
@api.onchange("generate_move")
|
||||
def generate_move_change(self):
|
||||
if self.generate_move:
|
||||
# default values
|
||||
self.move_option = "date"
|
||||
else:
|
||||
self.move_option = False
|
||||
|
||||
@@ -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
|
||||
@@ -120,24 +120,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",
|
||||
@@ -208,10 +203,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):
|
||||
@@ -251,11 +246,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
|
||||
|
||||
@@ -264,27 +254,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:
|
||||
@@ -303,9 +285,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()
|
||||
@@ -360,7 +344,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:
|
||||
@@ -372,8 +357,8 @@ class AccountPaymentOrder(models.Model):
|
||||
amount=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
|
||||
|
||||
@@ -425,177 +410,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 = {}
|
||||
payment_method = self.payment_mode_id.payment_method_id
|
||||
account_id = False
|
||||
if self.payment_type == "inbound":
|
||||
name = _("Debit order %s") % self.name
|
||||
account_id = (
|
||||
self.journal_id.inbound_payment_method_line_ids.filtered(
|
||||
lambda x: x.payment_method_id == payment_method
|
||||
).payment_account_id.id
|
||||
or self.journal_id.company_id.account_journal_payment_debit_account_id.id
|
||||
)
|
||||
elif self.payment_type == "outbound":
|
||||
name = _("Payment order %s") % self.name
|
||||
account_id = (
|
||||
self.journal_id.outbound_payment_method_line_ids.filtered(
|
||||
lambda x: x.payment_method_id == payment_method
|
||||
).payment_account_id.id
|
||||
or self.journal_id.company_id.account_journal_payment_credit_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]
|
||||
|
||||
@@ -1,204 +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(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 %(line)s of partner '%(partner)s'.",
|
||||
line=payment_line.name,
|
||||
partner=payment_line.partner_id.name,
|
||||
)
|
||||
)
|
||||
if payment_line.move_line_id.reconciled:
|
||||
raise UserError(
|
||||
_(
|
||||
"Move line '%(line)s' of partner '%(partner)s' has already "
|
||||
"been reconciled",
|
||||
line=payment_line.move_line_id.name,
|
||||
partner=payment_line.partner_id.name,
|
||||
)
|
||||
)
|
||||
if payment_line.move_line_id.account_id != transit_mline.account_id:
|
||||
raise UserError(
|
||||
_(
|
||||
"For partner '%(partner)s', the account of the account "
|
||||
"move line to pay (%(line1)s) is different from the "
|
||||
"account of of the transit move line (%(line2)s).",
|
||||
partner=payment_line.move_line_id.partner_id.name,
|
||||
line1=payment_line.move_line_id.account_id.code,
|
||||
line2=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 '%(state)s'. You need to cancel it first.",
|
||||
state=order_state,
|
||||
)
|
||||
)
|
||||
return super(BankPaymentLine, self).unlink()
|
||||
@@ -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
|
||||
|
||||
|
@@ -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>
|
||||
|
||||
@@ -68,14 +68,6 @@ class TestPaymentMode(TransactionCase):
|
||||
}
|
||||
)
|
||||
|
||||
def test_onchange_generate_move(self):
|
||||
self.payment_mode_c1.generate_move = True
|
||||
self.payment_mode_c1.generate_move_change()
|
||||
self.assertEqual(self.payment_mode_c1.move_option, "date")
|
||||
self.payment_mode_c1.generate_move = False
|
||||
self.payment_mode_c1.generate_move_change()
|
||||
self.assertFalse(self.payment_mode_c1.move_option)
|
||||
|
||||
def test_onchange_payment_type(self):
|
||||
self.payment_mode_c1.payment_method_id = self.manual_in
|
||||
self.payment_mode_c1.payment_method_id_change()
|
||||
|
||||
@@ -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
|
||||
@@ -99,12 +99,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()
|
||||
@@ -114,10 +114,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()
|
||||
|
||||
@@ -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
|
||||
@@ -205,7 +206,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):
|
||||
@@ -222,13 +223,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()
|
||||
@@ -237,10 +236,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()
|
||||
@@ -296,16 +291,21 @@ 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, date.today())
|
||||
self.assertEqual(
|
||||
outbound_order.payment_line_ids[1].bank_line_id.date, date.today()
|
||||
outbound_order.payment_line_ids[1].date,
|
||||
fields.Date.context_today(outbound_order),
|
||||
)
|
||||
self.assertEqual(
|
||||
outbound_order.payment_line_ids[1].payment_ids.date,
|
||||
fields.Date.context_today(outbound_order),
|
||||
)
|
||||
|
||||
def test_supplier_refund(self):
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -32,21 +32,6 @@
|
||||
<field name="default_invoice" />
|
||||
<field name="default_date_type" />
|
||||
</group>
|
||||
<group
|
||||
name="accounting-config"
|
||||
string="Accounting Entries Options"
|
||||
attrs="{'invisible': [('payment_order_ok', '=', False)]}"
|
||||
>
|
||||
<field name="generate_move" />
|
||||
<field
|
||||
name="move_option"
|
||||
attrs="{'invisible': [('generate_move', '=', False)], 'required': [('generate_move', '=', True)]}"
|
||||
/>
|
||||
<field
|
||||
name="post_move"
|
||||
attrs="{'invisible': [('generate_move', '=', False)]}"
|
||||
/>
|
||||
</group>
|
||||
</group>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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 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>
|
||||
Reference in New Issue
Block a user