Initial commit: Accounting packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:47 +02:00
commit 4ef34c2317
2661 changed files with 1709616 additions and 0 deletions

View file

@ -0,0 +1,9 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import account_journal
from . import account_move
from . import account_payment
from . import account_payment_method
from . import account_payment_method_line
from . import payment_provider
from . import payment_transaction

View file

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, api, models
from odoo.exceptions import UserError
class AccountJournal(models.Model):
_inherit = "account.journal"
def _get_available_payment_method_lines(self, payment_type):
lines = super()._get_available_payment_method_lines(payment_type)
return lines.filtered(lambda l: l.payment_provider_state != 'disabled')
@api.ondelete(at_uninstall=False)
def _unlink_except_linked_to_payment_provider(self):
linked_providers = self.env['payment.provider'].sudo().search([]).filtered(
lambda p: p.journal_id.id in self.ids and p.state != 'disabled'
)
if linked_providers:
raise UserError(_(
"You must first deactivate a payment provider before deleting its journal.\n"
"Linked providers: %s", ', '.join(p.display_name for p in linked_providers)
))

View file

@ -0,0 +1,93 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo.addons.payment import utils as payment_utils
class AccountMove(models.Model):
_inherit = 'account.move'
transaction_ids = fields.Many2many(
string="Transactions", comodel_name='payment.transaction',
relation='account_invoice_transaction_rel', column1='invoice_id', column2='transaction_id',
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)
amount_paid = fields.Monetary(
string="Amount paid",
compute='_compute_amount_paid'
)
@api.depends('transaction_ids')
def _compute_authorized_transaction_ids(self):
for invoice in self:
invoice.authorized_transaction_ids = invoice.transaction_ids.filtered(
lambda tx: tx.state == 'authorized'
)
@api.depends('transaction_ids')
def _compute_amount_paid(self):
""" Sum all the transaction amount for which state is in 'authorized' or 'done'
"""
for invoice in self:
invoice.amount_paid = sum(
invoice.transaction_ids.filtered(
lambda tx: tx.state in ('authorized', 'done')
).mapped('amount')
)
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
)
and self.state == 'posted'
and self.payment_state in ('not_paid', 'partial')
and self.amount_total
and self.move_type == 'out_invoice'
and (pending_manual_txs or not transactions or self.amount_paid < self.amount_total)
)
def get_portal_last_transaction(self):
self.ensure_one()
return self.with_context(active_test=False).transaction_ids._get_last()
def payment_action_capture(self):
""" Capture 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_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()
def action_view_payment_transactions(self):
action = self.env['ir.actions.act_window']._for_xml_id('payment.action_payment_transaction')
if len(self.transaction_ids) == 1:
action['view_mode'] = 'form'
action['res_id'] = self.transaction_ids.id
action['views'] = []
else:
action['domain'] = [('id', 'in', self.transaction_ids.ids)]
return action
def _get_default_payment_link_values(self):
self.ensure_one()
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,
}

View file

@ -0,0 +1,229 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, api, Command, fields, models
from odoo.exceptions import ValidationError
class AccountPayment(models.Model):
_inherit = 'account.payment'
# == Business fields ==
payment_transaction_id = fields.Many2one(
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
)
payment_token_id = fields.Many2one(
string="Saved Payment Token", comodel_name='payment.token', domain="""[
('id', 'in', suitable_payment_token_ids),
]""",
help="Note that only tokens from providers allowing to capture the amount are available.")
amount_available_for_refund = fields.Monetary(compute='_compute_amount_available_for_refund')
# == Display purpose fields ==
suitable_payment_token_ids = fields.Many2many(
comodel_name='payment.token',
compute='_compute_suitable_payment_token_ids',
compute_sudo=True,
)
# Technical field used to hide or show the payment_token_id if needed
use_electronic_payment_method = fields.Boolean(
compute='_compute_use_electronic_payment_method',
)
# == Fields used for traceability ==
source_payment_id = fields.Many2one(
string="Source Payment",
comodel_name='account.payment',
help="The source payment of related refund payments",
related='payment_transaction_id.source_transaction_id.payment_id',
readonly=True,
store=True, # Stored for the group by in `_compute_refunds_count`
index='btree_not_null',
)
refunds_count = fields.Integer(string="Refunds Count", compute='_compute_refunds_count')
#=== COMPUTE METHODS ===#
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':
# 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
# user would never be allowed to refund the source transaction again.
refund_payments = self.search([('source_payment_id', '=', payment.id)])
refunded_amount = abs(sum(refund_payments.mapped('amount')))
payment.amount_available_for_refund = payment.amount - refunded_amount
else:
payment.amount_available_for_refund = 0
@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),
('provider_id.capture_manually', '=', False),
('partner_id', 'in', related_partner_ids.ids),
('provider_id', '=', payment.payment_method_line_id.payment_provider_id.id),
])
else:
payment.suitable_payment_token_ids = [Command.clear()]
@api.depends('payment_method_line_id')
def _compute_use_electronic_payment_method(self):
for payment in self:
# Get a list of all electronic payment method codes.
# These codes are comprised of 'electronic' and the providers of each payment provider.
codes = [key for key in dict(self.env['payment.provider']._fields['code']._description_selection(self.env))]
payment.use_electronic_payment_method = payment.payment_method_code in codes
def _compute_refunds_count(self):
rg_data = self.env['account.payment']._read_group(
domain=[
('source_payment_id', 'in', self.ids),
('payment_transaction_id.operation', '=', 'refund')
],
fields=['source_payment_id'],
groupby=['source_payment_id']
)
data = {x['source_payment_id'][0]: x['source_payment_id_count'] for x in rg_data}
for payment in self:
payment.refunds_count = data.get(payment.id, 0)
#=== ONCHANGE METHODS ===#
@api.onchange('partner_id', 'payment_method_line_id', 'journal_id')
def _onchange_set_payment_token_id(self):
codes = [key for key in dict(self.env['payment.provider']._fields['code']._description_selection(self.env))]
if not (self.payment_method_code in codes and self.partner_id and self.journal_id):
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),
('provider_id.capture_manually', '=', False),
('provider_id', '=', self.payment_method_line_id.payment_provider_id.id),
], limit=1)
#=== ACTION METHODS ===#
def action_post(self):
# Post the payments "normally" if no transactions are needed.
# If not, let the provider update the state.
payments_need_tx = self.filtered(
lambda p: p.payment_token_id and not p.payment_transaction_id
)
# creating the transaction require to access data on payment providers, not always accessible to users
# able to create payments
transactions = payments_need_tx.sudo()._create_payment_transaction()
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()
# Post payments for issued transactions
transactions._finalize_post_processing()
payments_tx_done = payments_need_tx.filtered(
lambda p: p.payment_transaction_id.state == 'done'
)
super(AccountPayment, payments_tx_done).action_post()
payments_tx_not_done = payments_need_tx.filtered(
lambda p: p.payment_transaction_id.state != 'done'
)
payments_tx_not_done.action_cancel()
return res
def action_refund_wizard(self):
self.ensure_one()
return {
'name': _("Refund"),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'payment.refund.wizard',
'target': 'new',
}
def action_view_refunds(self):
self.ensure_one()
action = {
'name': _("Refund"),
'res_model': 'account.payment',
'type': 'ir.actions.act_window',
}
if self.refunds_count == 1:
refund_tx = self.env['account.payment'].search([
('source_payment_id', '=', self.id)
], limit=1)
action['res_id'] = refund_tx.id
action['view_mode'] = 'form'
else:
action['view_mode'] = 'tree,form'
action['domain'] = [('source_payment_id', '=', self.id)]
return action
#=== BUSINESS METHODS - PAYMENT FLOW ===#
def _create_payment_transaction(self, **extra_create_values):
for payment in self:
if payment.payment_transaction_id:
raise ValidationError(_(
"A payment transaction with reference %s already exists.",
payment.payment_transaction_id.reference
))
elif not payment.payment_token_id:
raise ValidationError(_("A token is required to create a new payment transaction."))
transactions = self.env['payment.transaction']
for payment in self:
transaction_vals = payment._prepare_payment_transaction_vals(**extra_create_values)
transaction = self.env['payment.transaction'].create(transaction_vals)
transactions += transaction
payment.payment_transaction_id = transaction # Link the transaction to the payment
return transactions
def _prepare_payment_transaction_vals(self, **extra_create_values):
self.ensure_one()
return {
'provider_id': self.payment_token_id.provider_id.id,
'reference': self.env['payment.transaction']._compute_reference(
self.payment_token_id.provider_id.code, prefix=self.ref
),
'amount': self.amount,
'currency_id': self.currency_id.id,
'partner_id': self.partner_id.id,
'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,
}
def _get_payment_refund_wizard_values(self):
self.ensure_one()
return {
'transaction_id': self.payment_transaction_id.id,
'payment_amount': self.amount,
'amount_available_for_refund': self.amount_available_for_refund,
}

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
class AccountPaymentMethod(models.Model):
_inherit = 'account.payment.method'
@api.model
def _get_payment_method_information(self):
res = super()._get_payment_method_information()
for code, _desc in self.env['payment.provider']._fields['code'].selection:
if code in ('none', 'custom'):
continue
res[code] = {
'mode': 'electronic',
'domain': [('type', '=', 'bank')],
}
return res

View file

@ -0,0 +1,86 @@
# -*- 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):
_inherit = "account.payment.method.line"
payment_provider_id = fields.Many2one(
comodel_name='payment.provider',
compute='_compute_payment_provider_id',
store=True,
readonly=False,
)
payment_provider_state = fields.Selection(
related='payment_provider_id.state'
)
@api.depends('payment_provider_id.name')
def _compute_name(self):
super()._compute_name()
for line in self:
if line.payment_provider_id and not line.name:
line.name = line.payment_provider_id.name
@api.depends('payment_method_id')
def _compute_payment_provider_id(self):
results = self.journal_id._get_journals_payment_method_information()
manage_providers = results['manage_providers']
method_information_mapping = results['method_information_mapping']
providers_per_code = results['providers_per_code']
for line in self:
journal = line.journal_id
company = journal.company_id
if (
company
and line.payment_method_id
and not line.payment_provider_id
and manage_providers
and method_information_mapping.get(line.payment_method_id.id, {}).get('mode') == 'electronic'
):
provider_ids = providers_per_code.get(company.id, {}).get(line.code, set())
# Exclude the 'unique' / 'electronic' values that are already set on the journal.
protected_provider_ids = set()
for payment_type in ('inbound', 'outbound'):
lines = journal[f'{payment_type}_payment_method_line_ids']
for journal_line in lines:
if journal_line.payment_method_id:
if (
manage_providers
and method_information_mapping.get(journal_line.payment_method_id.id, {}).get('mode') == 'electronic'
):
protected_provider_ids.add(journal_line.payment_provider_id.id)
candidates_provider_ids = provider_ids - protected_provider_ids
if candidates_provider_ids:
line.payment_provider_id = next(iter(candidates_provider_ids))
@api.ondelete(at_uninstall=False)
def _unlink_except_active_provider(self):
""" Ensure we don't remove an account.payment.method.line that is linked to a provider
in the test or enabled state.
"""
active_provider = self.payment_provider_id.filtered(lambda provider: provider.state in ['enabled', 'test'])
if active_provider:
raise UserError(_(
"You can't delete a payment method that is linked to a provider in the enabled "
"or test state.\n""Linked providers(s): %s",
', '.join(a.display_name for a in active_provider),
))
def action_open_provider_form(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': _('Provider'),
'view_mode': 'form',
'res_model': 'payment.provider',
'target': 'current',
'res_id': self.payment_provider_id.id
}

View file

@ -0,0 +1,138 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class Paymentprovider(models.Model):
_inherit = 'payment.provider'
journal_id = fields.Many2one(
string="Payment Journal",
help="The journal in which the successful transactions are posted.",
comodel_name='account.journal',
compute='_compute_journal_id',
inverse='_inverse_journal_id',
domain='[("type", "=", "bank"), ("company_id", "=", company_id)]',
copy=False,
)
#=== COMPUTE METHODS ===#
def _ensure_payment_method_line(self, allow_create=True):
self.ensure_one()
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,
)
if not self.journal_id:
if pay_method_line:
pay_method_line.unlink()
return
if not pay_method_line:
pay_method_line = self.env['account.payment.method.line'].search(
[
('company_id', '=', self.company_id.id),
('code', '=', self.code),
('payment_provider_id', '=', False),
],
limit=1,
)
if pay_method_line:
pay_method_line.payment_provider_id = self
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,
'journal_id': self.journal_id.id,
'payment_provider_id': self.id,
}
pay_method_line_same_code = self.env['account.payment.method.line'].search(
[
('company_id', '=', self.company_id.id),
('code', '=', self.code),
],
limit=1,
)
if pay_method_line_same_code:
create_values['payment_account_id'] = pay_method_line_same_code.payment_account_id.id
self.env['account.payment.method.line'].create(create_values)
@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,
)
if pay_method_line:
provider.journal_id = pay_method_line.journal_id
elif provider.state in ('enabled', 'test'):
provider.journal_id = self.env['account.journal'].search(
[
('company_id', '=', provider.company_id.id),
('type', '=', 'bank'),
],
limit=1,
)
if provider.id:
provider._ensure_payment_method_line()
def _inverse_journal_id(self):
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)
#=== BUSINESS METHODS ===#
@api.model
def _setup_provider(self, code):
""" Override of `payment` to create the payment method of the provider. """
super()._setup_provider(code)
self._setup_payment_method(code)
@api.model
def _setup_payment_method(self, code):
if code not in ('none', 'custom') and not self._get_provider_payment_method(code):
providers_description = dict(self._fields['code']._description_selection(self.env))
self.env['account.payment.method'].sudo().create({
'name': providers_description[code],
'code': code,
'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)
@api.model
def _remove_provider(self, code):
""" 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)
payment_method.unlink()

View file

@ -0,0 +1,207 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, SUPERUSER_ID, _
class PaymentTransaction(models.Model):
_inherit = 'payment.transaction'
payment_id = fields.Many2one(
string="Payment", comodel_name='account.payment', readonly=True)
invoice_ids = fields.Many2many(
string="Invoices", comodel_name='account.move', relation='account_invoice_transaction_rel',
column1='transaction_id', column2='invoice_id', readonly=True, copy=False,
domain=[('move_type', 'in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund'))])
invoices_count = fields.Integer(string="Invoices Count", compute='_compute_invoices_count')
#=== COMPUTE METHODS ===#
@api.depends('invoice_ids')
def _compute_invoices_count(self):
tx_data = {}
if self.ids:
self.env.cr.execute(
'''
SELECT transaction_id, count(invoice_id)
FROM account_invoice_transaction_rel
WHERE transaction_id IN %s
GROUP BY transaction_id
''',
[tuple(self.ids)]
)
tx_data = dict(self.env.cr.fetchall()) # {id: count}
for tx in self:
tx.invoices_count = tx_data.get(tx.id, 0)
#=== ACTION METHODS ===#
def action_view_invoices(self):
""" Return the action for the views of the invoices linked to the transaction.
Note: self.ensure_one()
:return: The action
:rtype: dict
"""
self.ensure_one()
action = {
'name': _("Invoices"),
'type': 'ir.actions.act_window',
'res_model': 'account.move',
'target': 'current',
}
invoice_ids = self.invoice_ids.ids
if len(invoice_ids) == 1:
invoice = invoice_ids[0]
action['res_id'] = invoice
action['view_mode'] = 'form'
action['views'] = [(self.env.ref('account.view_move_form').id, 'form')]
else:
action['view_mode'] = 'tree,form'
action['domain'] = [('id', 'in', invoice_ids)]
return action
#=== BUSINESS METHODS - PAYMENT FLOW ===#
@api.model
def _compute_reference_prefix(self, provider_code, 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
(6, 0, ids) X2M command as value, the prefix is computed based on the invoice name(s).
Otherwise, an empty string is returned.
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), ...], ...}.
:return: The computed reference prefix if invoice ids are found, an empty string otherwise
:rtype: str
"""
command_list = values.get('invoice_ids')
if command_list:
# Extract invoice id(s) from the X2M commands
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
#=== BUSINESS METHODS - POST-PROCESSING ===#
def _reconcile_after_done(self):
""" Post relevant fiscal documents and create missing payments.
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
"""
super()._reconcile_after_done()
# Validate invoices automatically once the transaction is confirmed
self.invoice_ids.filtered(lambda inv: inv.state == 'draft').action_post()
# 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()
def _create_payment(self, **extra_create_values):
"""Create an `account.payment` record for the current transaction.
If the transaction is linked to some invoices, their reconciliation is done automatically.
Note: self.ensure_one()
:param dict extra_create_values: Optional extra create values
:return: The created payment
:rtype: recordset of `account.payment`
"""
self.ensure_one()
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 = {
'amount': abs(self.amount), # A tx may have a negative amount, but a payment must >= 0
'payment_type': 'inbound' if self.amount > 0 else 'outbound',
'currency_id': self.currency_id.id,
'partner_id': self.partner_id.commercial_partner_id.id,
'partner_type': 'customer',
'journal_id': self.provider_id.journal_id.id,
'company_id': self.provider_id.company_id.id,
'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 ""}',
**extra_create_values,
}
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()
(payment.line_ids + self.invoice_ids.line_ids).filtered(
lambda line: line.account_id == payment.destination_account_id
and not line.reconciled
).reconcile()
return payment
#=== BUSINESS METHODS - LOGGING ===#
def _log_message_on_linked_documents(self, message):
""" Log a message on the payment and the invoices linked to the transaction.
For a module to implement payments and link documents to a transaction, it must override
this method and call super, then log the message on documents linked to the transaction.
Note: self.ensure_one()
:param str message: The message to be logged
: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)
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)
#=== 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)