19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:30:07 +01:00
parent ba20ce7443
commit 768b70e05e
2357 changed files with 1057103 additions and 712486 deletions

View file

@ -1,8 +1,13 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
from odoo import api, fields, models
from odoo.tools import format_date, str2bool
from odoo.tools.translate import _
from odoo.addons.payment import utils as payment_utils
from odoo.tools.image import image_data_uri
class AccountMove(models.Model):
@ -14,7 +19,11 @@ class AccountMove(models.Model):
readonly=True, copy=False)
authorized_transaction_ids = fields.Many2many(
string="Authorized Transactions", comodel_name='payment.transaction',
compute='_compute_authorized_transaction_ids', readonly=True, copy=False)
compute='_compute_authorized_transaction_ids', readonly=True, copy=False,
compute_sudo=True)
transaction_count = fields.Integer(
string="Transaction Count", compute='_compute_transaction_count'
)
amount_paid = fields.Monetary(
string="Amount paid",
compute='_compute_amount_paid'
@ -27,6 +36,11 @@ class AccountMove(models.Model):
lambda tx: tx.state == 'authorized'
)
@api.depends('transaction_ids')
def _compute_transaction_count(self):
for invoice in self:
invoice.transaction_count = len(invoice.transaction_ids)
@api.depends('transaction_ids')
def _compute_amount_paid(self):
""" Sum all the transaction amount for which state is in 'authorized' or 'done'
@ -41,34 +55,72 @@ class AccountMove(models.Model):
def _has_to_be_paid(self):
self.ensure_one()
transactions = self.transaction_ids.filtered(lambda tx: tx.state in ('pending', 'authorized', 'done'))
pending_manual_txs = transactions.filtered(lambda tx: tx.state == 'pending' and tx.provider_code in ('none', 'custom'))
return bool(
(
self.amount_residual
or not transactions
pending_transactions = transactions.filtered(
lambda tx: tx.state in {'pending', 'authorized'}
and tx.provider_code not in {'none', 'custom'})
enabled_feature = str2bool(
self.env['ir.config_parameter'].sudo().get_param(
'account_payment.enable_portal_payment'
)
)
return enabled_feature and bool(
(self.amount_residual or not transactions)
and self.state == 'posted'
and self.payment_state in ('not_paid', 'partial')
and self.payment_state in ('not_paid', 'in_payment', 'partial')
and not self.currency_id.is_zero(self.amount_residual)
and self.amount_total
and self.move_type == 'out_invoice'
and (pending_manual_txs or not transactions or self.amount_paid < self.amount_total)
and not pending_transactions
)
def _get_online_payment_error(self):
"""
Returns the appropriate error message to be displayed if _has_to_be_paid() method returns False.
"""
self.ensure_one()
transactions = self.transaction_ids.filtered(lambda tx: tx.state in ('pending', 'authorized', 'done'))
pending_transactions = transactions.filtered(
lambda tx: tx.state in {'pending', 'authorized'}
and tx.provider_code not in {'none', 'custom'})
enabled_feature = str2bool(
self.env['ir.config_parameter'].sudo().get_param(
'account_payment.enable_portal_payment'
)
)
errors = []
if not enabled_feature:
errors.append(_("This invoice cannot be paid online."))
if transactions or self.currency_id.is_zero(self.amount_residual):
errors.append(_("There is no amount to be paid."))
if self.state != 'posted':
errors.append(_("This invoice isn't posted."))
if self.currency_id.is_zero(self.amount_residual):
errors.append(_("This invoice has already been paid."))
if self.move_type != 'out_invoice':
errors.append(_("This is not an outgoing invoice."))
if pending_transactions:
errors.append(_("There are pending transactions for this invoice."))
return '\n'.join(errors)
@api.private
def get_portal_last_transaction(self):
self.ensure_one()
return self.with_context(active_test=False).transaction_ids._get_last()
return self.with_context(active_test=False).sudo().transaction_ids._get_last()
def payment_action_capture(self):
""" Capture all transactions linked to this invoice. """
self.ensure_one()
payment_utils.check_rights_on_recordset(self)
# In sudo mode because we need to be able to read on provider fields.
self.authorized_transaction_ids.sudo().action_capture()
# In sudo mode to bypass the checks on the rights on the transactions.
return self.sudo().transaction_ids.action_capture()
def payment_action_void(self):
""" Void all transactions linked to this invoice. """
payment_utils.check_rights_on_recordset(self)
# In sudo mode because we need to be able to read on provider fields.
self.authorized_transaction_ids.sudo().action_void()
# In sudo mode to bypass the checks on the rights on the transactions.
self.sudo().authorized_transaction_ids.action_void()
def action_view_payment_transactions(self):
action = self.env['ir.actions.act_window']._for_xml_id('payment.action_payment_transaction')
@ -83,11 +135,48 @@ class AccountMove(models.Model):
return action
def _get_default_payment_link_values(self):
self.ensure_one()
next_payment_values = self._get_invoice_next_payment_values()
amount_max = next_payment_values.get('amount_due')
additional_info = {}
open_installments = []
installment_state = next_payment_values.get('installment_state')
next_amount_to_pay = next_payment_values.get('next_amount_to_pay')
if installment_state in ('next', 'overdue'):
open_installments = []
for installment in next_payment_values.get('not_reconciled_installments'):
data = {
'type': installment['type'],
'number': installment['number'],
'amount': installment['amount_residual_currency_unsigned'],
'date_maturity': format_date(self.env, installment['date_maturity']),
}
open_installments.append(data)
elif installment_state == 'epd':
amount_max = next_amount_to_pay # with epd, next_amount_to_pay is the invoice amount residual
additional_info.update({
'has_eligible_epd': True,
'discount_date': next_payment_values.get('discount_date')
})
return {
'description': self.payment_reference,
'amount': self.amount_residual,
'currency_id': self.currency_id.id,
'partner_id': self.partner_id.id,
'amount_max': self.amount_residual,
'open_installments': open_installments,
'amount': next_amount_to_pay,
'amount_max': amount_max,
**additional_info
}
def _generate_portal_payment_qr(self):
self.ensure_one()
portal_url = self._get_portal_payment_link()
barcode = self.env['ir.actions.report'].barcode(barcode_type="QR", value=portal_url, width=128, height=128, quiet=False)
return image_data_uri(base64.b64encode(barcode))
def _get_portal_payment_link(self):
self.ensure_one()
payment_link_wizard = self.env['payment.link.wizard'].with_context(
active_id=self.id, active_model=self._name
).create({})
return payment_link_wizard.link

View file

@ -12,7 +12,7 @@ class AccountPayment(models.Model):
string="Payment Transaction",
comodel_name='payment.transaction',
readonly=True,
auto_join=True, # No access rule bypass since access to payments means access to txs too
bypass_search_access=True, # No access rule bypass since access to payments means access to txs too
)
payment_token_id = fields.Many2one(
string="Saved Payment Token", comodel_name='payment.token', domain="""[
@ -49,7 +49,16 @@ class AccountPayment(models.Model):
def _compute_amount_available_for_refund(self):
for payment in self:
tx_sudo = payment.payment_transaction_id.sudo()
if tx_sudo.provider_id.support_refund and tx_sudo.operation != 'refund':
payment_method = (
tx_sudo.payment_method_id.primary_payment_method_id
or tx_sudo.payment_method_id
)
if (
tx_sudo # The payment was created by a transaction.
and tx_sudo.provider_id.support_refund != 'none'
and payment_method.support_refund != 'none'
and tx_sudo.operation != 'refund'
):
# Only consider refund transactions that are confirmed by summing the amounts of
# payments linked to such refund transactions. Indeed, should a refund transaction
# be stuck forever in a transient state (due to webhook failure, for example), the
@ -63,17 +72,11 @@ class AccountPayment(models.Model):
@api.depends('payment_method_line_id')
def _compute_suitable_payment_token_ids(self):
for payment in self:
related_partner_ids = (
payment.partner_id
| payment.partner_id.commercial_partner_id
| payment.partner_id.commercial_partner_id.child_ids
)._origin
if payment.use_electronic_payment_method:
payment.suitable_payment_token_ids = self.env['payment.token'].sudo().search([
('company_id', '=', payment.company_id.id),
*self.env['payment.token']._check_company_domain(payment.company_id),
('provider_id.capture_manually', '=', False),
('partner_id', 'in', related_partner_ids.ids),
('partner_id', '=', payment.partner_id.id),
('provider_id', '=', payment.payment_method_line_id.payment_provider_id.id),
])
else:
@ -93,10 +96,10 @@ class AccountPayment(models.Model):
('source_payment_id', 'in', self.ids),
('payment_transaction_id.operation', '=', 'refund')
],
fields=['source_payment_id'],
groupby=['source_payment_id']
groupby=['source_payment_id'],
aggregates=['__count']
)
data = {x['source_payment_id'][0]: x['source_payment_id_count'] for x in rg_data}
data = {source_payment.id: count for source_payment, count in rg_data}
for payment in self:
payment.refunds_count = data.get(payment.id, 0)
@ -109,18 +112,12 @@ class AccountPayment(models.Model):
self.payment_token_id = False
return
related_partner_ids = (
self.partner_id
| self.partner_id.commercial_partner_id
| self.partner_id.commercial_partner_id.child_ids
)._origin
self.payment_token_id = self.env['payment.token'].sudo().search([
('company_id', '=', self.company_id.id),
('partner_id', 'in', related_partner_ids.ids),
*self.env['payment.token']._check_company_domain(self.company_id),
('partner_id', '=', self.partner_id.id),
('provider_id.capture_manually', '=', False),
('provider_id', '=', self.payment_method_line_id.payment_provider_id.id),
], limit=1)
], limit=1) # In sudo mode to read the provider fields.
#=== ACTION METHODS ===#
@ -138,10 +135,10 @@ class AccountPayment(models.Model):
res = super(AccountPayment, self - payments_need_tx).action_post()
for tx in transactions: # Process the transactions with a payment by token
tx._send_payment_request()
tx._charge_with_token()
# Post payments for issued transactions
transactions._finalize_post_processing()
transactions._post_process()
payments_tx_done = payments_need_tx.filtered(
lambda p: p.payment_transaction_id.state == 'done'
)
@ -177,7 +174,7 @@ class AccountPayment(models.Model):
action['res_id'] = refund_tx.id
action['view_mode'] = 'form'
else:
action['view_mode'] = 'tree,form'
action['view_mode'] = 'list,form'
action['domain'] = [('source_payment_id', '=', self.id)]
return action
@ -203,10 +200,17 @@ class AccountPayment(models.Model):
def _prepare_payment_transaction_vals(self, **extra_create_values):
self.ensure_one()
if self.env.context.get('active_model', '') == 'account.move':
invoice_ids = self.env.context.get('active_ids', [])
elif self.env.context.get('active_model', '') == 'account.move.line':
invoice_ids = self.env['account.move.line'].browse(self.env.context.get('active_ids')).move_id.ids
else:
invoice_ids = []
return {
'provider_id': self.payment_token_id.provider_id.id,
'payment_method_id': self.payment_token_id.payment_method_id.id,
'reference': self.env['payment.transaction']._compute_reference(
self.payment_token_id.provider_id.code, prefix=self.ref
self.payment_token_id.provider_id.code, prefix=self.memo
),
'amount': self.amount,
'currency_id': self.currency_id.id,
@ -214,10 +218,7 @@ class AccountPayment(models.Model):
'token_id': self.payment_token_id.id,
'operation': 'offline',
'payment_id': self.id,
**({'invoice_ids': [Command.set(self._context.get('active_ids', []))]}
if self._context.get('active_model') == 'account.move'
else {}),
**extra_create_values,
'invoice_ids': [Command.set(invoice_ids)],
}
def _get_payment_refund_wizard_values(self):

View file

@ -15,6 +15,6 @@ class AccountPaymentMethod(models.Model):
continue
res[code] = {
'mode': 'electronic',
'domain': [('type', '=', 'bank')],
'type': ('bank',),
}
return res

View file

@ -1,9 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.osv import expression
class AccountPaymentMethodLine(models.Model):
@ -14,6 +12,7 @@ class AccountPaymentMethodLine(models.Model):
compute='_compute_payment_provider_id',
store=True,
readonly=False,
domain="[('code', '=', code)]",
)
payment_provider_state = fields.Selection(
related='payment_provider_id.state'

View file

@ -1,9 +1,10 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class Paymentprovider(models.Model):
class PaymentProvider(models.Model):
_inherit = 'payment.provider'
journal_id = fields.Many2one(
@ -12,7 +13,8 @@ class Paymentprovider(models.Model):
comodel_name='account.journal',
compute='_compute_journal_id',
inverse='_inverse_journal_id',
domain='[("type", "=", "bank"), ("company_id", "=", company_id)]',
check_company=True,
domain='[("type", "=", "bank")]',
copy=False,
)
@ -23,10 +25,14 @@ class Paymentprovider(models.Model):
if not self.id:
return
pay_method_line = self.env['account.payment.method.line'].search(
[('code', '=', self.code), ('payment_provider_id', '=', self.id)],
limit=1,
)
default_payment_method = self._get_provider_payment_method(self._get_code())
if not default_payment_method:
return
pay_method_line = self.env['account.payment.method.line'].search([
('payment_provider_id', '=', self.id),
('journal_id', '!=', False),
], limit=1)
if not self.journal_id:
if pay_method_line:
@ -36,9 +42,10 @@ class Paymentprovider(models.Model):
if not pay_method_line:
pay_method_line = self.env['account.payment.method.line'].search(
[
('company_id', '=', self.company_id.id),
('code', '=', self.code),
*self.env['account.payment.method.line']._check_company_domain(self.company_id),
('code', '=', self._get_code()),
('payment_provider_id', '=', False),
('journal_id', '!=', False),
],
limit=1,
)
@ -47,34 +54,44 @@ class Paymentprovider(models.Model):
pay_method_line.journal_id = self.journal_id
pay_method_line.name = self.name
elif allow_create:
default_payment_method_id = self._get_default_payment_method_id(self.code)
if not default_payment_method_id:
return
create_values = {
'name': self.name,
'payment_method_id': default_payment_method_id,
'payment_method_id': default_payment_method.id,
'journal_id': self.journal_id.id,
'payment_provider_id': self.id,
'payment_account_id': self._get_payment_method_outstanding_account_id(default_payment_method)
}
pay_method_line_same_code = self.env['account.payment.method.line'].search(
[
('company_id', '=', self.company_id.id),
('code', '=', self.code),
*self.env['account.payment.method.line']._check_company_domain(self.company_id),
('code', '=', self._get_code()),
],
limit=1,
)
if pay_method_line_same_code:
create_values['payment_account_id'] = pay_method_line_same_code.payment_account_id.id
if self._get_code() == 'sepa_direct_debit':
create_values['name'] = "Online SEPA"
self.env['account.payment.method.line'].create(create_values)
def _get_payment_method_outstanding_account_id(self, payment_method_id):
if self.code == 'custom':
return False
account_ref = 'account_journal_payment_debit_account_id' if payment_method_id.payment_type == 'inbound' else 'account_journal_payment_credit_account_id'
chart_template = self.with_context(allowed_company_ids=self.company_id.root_id.ids).env['account.chart.template']
outstanding_account_id = (
chart_template.ref(account_ref, raise_if_not_found=False)
or self.company_id.transfer_account_id
).id
return outstanding_account_id
@api.depends('code', 'state', 'company_id')
def _compute_journal_id(self):
for provider in self:
pay_method_line = self.env['account.payment.method.line'].search(
[('code', '=', provider.code), ('payment_provider_id', '=', provider._origin.id)],
limit=1,
)
pay_method_line = self.env['account.payment.method.line'].search([
('payment_provider_id', '=', provider._origin.id),
('journal_id', '!=', False),
], limit=1)
if pay_method_line:
provider.journal_id = pay_method_line.journal_id
@ -93,13 +110,6 @@ class Paymentprovider(models.Model):
for provider in self:
provider._ensure_payment_method_line()
@api.model
def _get_default_payment_method_id(self, code):
provider_payment_method = self._get_provider_payment_method(code)
if provider_payment_method:
return provider_payment_method.id
return None
@api.model
def _get_provider_payment_method(self, code):
return self.env['account.payment.method'].search([('code', '=', code)], limit=1)
@ -107,9 +117,9 @@ class Paymentprovider(models.Model):
#=== BUSINESS METHODS ===#
@api.model
def _setup_provider(self, code):
def _setup_provider(self, code, **kwargs):
""" Override of `payment` to create the payment method of the provider. """
super()._setup_provider(code)
super()._setup_provider(code, **kwargs)
self._setup_payment_method(code)
@api.model
@ -122,17 +132,16 @@ class Paymentprovider(models.Model):
'payment_type': 'inbound',
})
def _check_existing_payment_method_lines(self, payment_method):
existing_payment_method_lines_count = \
self.env['account.payment.method.line'].search_count([('payment_method_id', '=', \
payment_method.id)], limit=1)
return bool(existing_payment_method_lines_count)
def _check_existing_payment(self, payment_method):
existing_payment_count = self.env['account.payment'].search_count([('payment_method_id', '=', payment_method.id)], limit=1)
return bool(existing_payment_count)
@api.model
def _remove_provider(self, code):
def _remove_provider(self, code, **kwargs):
""" Override of `payment` to delete the payment method of the provider. """
payment_method = self._get_provider_payment_method(code)
if self._check_existing_payment_method_lines(payment_method):
raise UserError(_("To uninstall this module, please remove first the corresponding payment method line in the incoming payments tab defined on the bank journal."))
super()._remove_provider(code)
# If the payment method is used by any payments, we block the uninstallation of the module.
if self._check_existing_payment(payment_method):
raise UserError(_("You cannot uninstall this module as payments using this payment method already exist."))
super()._remove_provider(code, **kwargs)
payment_method.unlink()

View file

@ -1,6 +1,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, SUPERUSER_ID, _
from odoo import SUPERUSER_ID, _, api, fields, models
class PaymentTransaction(models.Model):
@ -59,14 +59,14 @@ class PaymentTransaction(models.Model):
action['view_mode'] = 'form'
action['views'] = [(self.env.ref('account.view_move_form').id, 'form')]
else:
action['view_mode'] = 'tree,form'
action['view_mode'] = 'list,form'
action['domain'] = [('id', 'in', invoice_ids)]
return action
#=== BUSINESS METHODS - PAYMENT FLOW ===#
@api.model
def _compute_reference_prefix(self, provider_code, separator, **values):
def _compute_reference_prefix(self, separator, **values):
""" Compute the reference prefix from the transaction values.
If the `values` parameter has an entry with 'invoice_ids' as key and a list of (4, id, O) or
@ -75,7 +75,6 @@ class PaymentTransaction(models.Model):
Note: This method should be called in sudo mode to give access to documents (INV, SO, ...).
:param str provider_code: The code of the provider handling the transaction
:param str separator: The custom separator used to separate data references
:param dict values: The transaction values used to compute the reference prefix. It should
have the structure {'invoice_ids': [(X2M command), ...], ...}.
@ -88,40 +87,48 @@ class PaymentTransaction(models.Model):
invoice_ids = self._fields['invoice_ids'].convert_to_cache(command_list, self)
invoices = self.env['account.move'].browse(invoice_ids).exists()
if len(invoices) == len(invoice_ids): # All ids are valid
return separator.join(invoices.mapped('name'))
return super()._compute_reference_prefix(provider_code, separator, **values)
def _set_canceled(self, state_message=None):
""" Update the transactions' state to 'cancel'.
:param str state_message: The reason for which the transaction is set in 'cancel' state
:return: updated transactions
:rtype: `payment.transaction` recordset
"""
processed_txs = super()._set_canceled(state_message)
# Cancel the existing payments
processed_txs.payment_id.action_cancel()
return processed_txs
prefix = separator.join(invoices.filtered(lambda inv: inv.name).mapped('name'))
if name := values.get('name_next_installment'):
prefix = name
return prefix
return super()._compute_reference_prefix(separator, **values)
#=== BUSINESS METHODS - POST-PROCESSING ===#
def _reconcile_after_done(self):
""" Post relevant fiscal documents and create missing payments.
def _post_process(self):
""" Override of `payment` to add account-specific logic to the post-processing.
As there is nothing to reconcile for validation transactions, no payment is created for
them. This is also true for validations with a validity check (transfer of a small amount
with immediate refund) because validation amounts are not included in payouts.
:return: None
In particular, for confirmed transactions we write a message in the chatter with the payment
and transaction references, post relevant fiscal documents, and create missing payments. For
cancelled transactions, we cancel the payment.
"""
super()._reconcile_after_done()
super()._post_process()
for tx in self.filtered(lambda t: t.state == 'done'):
# Validate invoices automatically once the transaction is confirmed.
self.invoice_ids.filtered(lambda inv: inv.state == 'draft').action_post()
# Validate invoices automatically once the transaction is confirmed
self.invoice_ids.filtered(lambda inv: inv.state == 'draft').action_post()
# Create and post missing payments.
# As there is nothing to reconcile for validation transactions, no payment is created
# for them. This is also true for validations with or without a validity check (transfer
# of a small amount with immediate refund) because validation amounts are not included
# in payouts. As the reconciliation is done in the child transactions for partial voids
# and captures, no payment is created for their source transactions either.
if (
tx.operation != 'validation'
and not tx.payment_id
and not any(child.state in ['done', 'cancel'] for child in tx.child_transaction_ids)
):
tx.with_company(tx.company_id)._create_payment()
# Create and post missing payments for transactions requiring reconciliation
for tx in self.filtered(lambda t: t.operation != 'validation' and not t.payment_id):
tx._create_payment()
if tx.payment_id:
message = _(
"The payment related to transaction %(ref)s has been posted: %(link)s",
ref=tx._get_html_link(),
link=tx.payment_id._get_html_link(),
)
tx._log_message_on_linked_documents(message)
for tx in self.filtered(lambda t: t.state == 'cancel'):
tx.payment_id.action_cancel()
def _create_payment(self, **extra_create_values):
"""Create an `account.payment` record for the current transaction.
@ -136,6 +143,8 @@ class PaymentTransaction(models.Model):
"""
self.ensure_one()
reference = f'{self.reference} - {self.provider_reference or ""}'
payment_method_line = self.provider_id.journal_id.inbound_payment_method_line_ids\
.filtered(lambda l: l.payment_provider_id == self.provider_id)
payment_values = {
@ -149,19 +158,52 @@ class PaymentTransaction(models.Model):
'payment_method_line_id': payment_method_line.id,
'payment_token_id': self.token_id.id,
'payment_transaction_id': self.id,
'ref': f'{self.reference} - {self.partner_id.name} - {self.provider_reference or ""}',
'memo': reference,
'write_off_line_vals': [],
'invoice_ids': self.invoice_ids,
**extra_create_values,
}
for invoice in self.invoice_ids:
if invoice.state != 'posted':
continue
next_payment_values = invoice._get_invoice_next_payment_values()
if next_payment_values['installment_state'] == 'epd' and self.amount == next_payment_values['amount_due']:
aml = next_payment_values['epd_line']
epd_aml_values_list = [({
'aml': aml,
'amount_currency': -aml.amount_residual_currency,
'balance': -aml.balance,
})]
open_balance = next_payment_values['epd_discount_amount']
early_payment_values = self.env['account.move']._get_invoice_counterpart_amls_for_early_payment_discount(epd_aml_values_list, open_balance)
for aml_values_list in early_payment_values.values():
if (aml_values_list):
aml_vl = aml_values_list[0]
aml_vl['partner_id'] = invoice.partner_id.id
payment_values['write_off_line_vals'] += [aml_vl]
break
payment_term_lines = self.invoice_ids.line_ids.filtered(lambda line: line.display_type == 'payment_term')
if payment_term_lines:
payment_values['destination_account_id'] = payment_term_lines[0].account_id.id
payment = self.env['account.payment'].create(payment_values)
payment.action_post()
# Track the payment to make a one2one.
self.payment_id = payment
if self.invoice_ids:
self.invoice_ids.filtered(lambda inv: inv.state == 'draft').action_post()
# Reconcile the payment with the source transaction's invoices in case of a partial capture.
if self.operation == self.source_transaction_id.operation:
invoices = self.source_transaction_id.invoice_ids
else:
invoices = self.invoice_ids
invoices = invoices.filtered(lambda inv: inv.state != 'cancel')
if invoices:
invoices.filtered(lambda inv: inv.state == 'draft').action_post()
(payment.line_ids + self.invoice_ids.line_ids).filtered(
(payment.move_id.line_ids + invoices.line_ids).filtered(
lambda line: line.account_id == payment.destination_account_id
and not line.reconciled
).reconcile()
@ -182,26 +224,19 @@ class PaymentTransaction(models.Model):
:return: None
"""
self.ensure_one()
self = self.with_user(SUPERUSER_ID) # Log messages as 'OdooBot'
if self.source_transaction_id.payment_id:
self.source_transaction_id.payment_id.message_post(body=message)
if self.env.uid == SUPERUSER_ID or self.env.context.get('payment_backend_action'):
author = self.env.user.partner_id
else:
author = self.partner_id
if self.source_transaction_id:
for invoice in self.source_transaction_id.invoice_ids:
invoice.message_post(body=message)
for invoice in self.invoice_ids:
invoice.message_post(body=message)
invoice.message_post(body=message, author_id=author.id)
payment_id = self.source_transaction_id.payment_id
if payment_id:
payment_id.message_post(body=message, author_id=author.id)
for invoice in self._get_invoices_to_notify():
invoice.message_post(body=message, author_id=author.id)
#=== BUSINESS METHODS - POST-PROCESSING ===#
def _finalize_post_processing(self):
""" Override of `payment` to write a message in the chatter with the payment and transaction
references.
:return: None
"""
super()._finalize_post_processing()
for tx in self.filtered('payment_id'):
message = _(
"The payment related to the transaction with reference %(ref)s has been posted: "
"%(link)s", ref=tx.reference, link=tx.payment_id._get_html_link()
)
tx._log_message_on_linked_documents(message)
def _get_invoices_to_notify(self):
""" Return the invoices on which to log payment-related messages. """
return self.invoice_ids