- Create journal entries as soon as bank/cash statement lines are created, temporary booked on a suspense account set on the journal.
- Simplify the management of "blue" lines in the reconciliation widget. A "blue" line is now a journal item using a temporary liquidity account (outstanding payment/receipt accounts, set on the journal).
- Adapt and simplify the bank reconciliation report.
- Remove the bank reconciliation threshold date. The reconciliation report will show the not already reconciled journal entries using a liquidity account and the not already reconciled journal entries using a temporary liquidity account. Without accounting, an account.payment will involve directly the liquidity account and then, will be considered as a statement line directly.
- Remove the post_at bank reconciliation feature. The "paid" state will be set on the invoices only if reconciled with a journal entry involving the journal's liquidity account.
With invoicing, the payment will do that so the "in_payment" state should never be shown up.
With accounting, only the statement lines have the power to move an invoice to the "paid" state.
- Fix various corner cases about the management of multi-currency in bank statement lines.
- Fix the conversion dates in multi-currency: Since the bank/cash is always used on the statement lines, it will use always the real "bank" date instead of the fictive payment one.
- Ensure the 'reconcile' method will raise an error if the involved moves are not posted.
related enterprise PR odoo/enterprise#7019
closes odoo/odoo#41301
--task: 2092096
Related: odoo/upgrade#1018
Signed-off-by: Quentin De Paoli (qdp) <qdp@openerp.com>
117 lines
5.0 KiB
Python
117 lines
5.0 KiB
Python
# coding: utf-8
|
|
|
|
import datetime
|
|
|
|
from odoo import _, api, fields, models
|
|
from odoo.exceptions import ValidationError
|
|
|
|
|
|
class AccountPayment(models.Model):
|
|
_inherit = 'account.payment'
|
|
|
|
payment_transaction_id = fields.Many2one('payment.transaction', string='Payment Transaction', readonly=True)
|
|
payment_token_id = fields.Many2one(
|
|
'payment.token', string="Saved payment token",
|
|
domain="""[
|
|
(payment_method_code == 'electronic', '=', 1),
|
|
('company_id', '=', company_id),
|
|
('acquirer_id.capture_manually', '=', False),
|
|
('acquirer_id.journal_id', '=', journal_id),
|
|
('partner_id', 'in', related_partner_ids),
|
|
]""",
|
|
help="Note that tokens from acquirers set to only authorize transactions (instead of capturing the amount) are not available.")
|
|
related_partner_ids = fields.Many2many('res.partner', compute='_compute_related_partners')
|
|
|
|
def _get_payment_chatter_link(self):
|
|
self.ensure_one()
|
|
return '<a href=# data-oe-model=account.payment data-oe-id=%d>%s</a>' % (self.id, self.name)
|
|
|
|
@api.depends('partner_id.commercial_partner_id.child_ids')
|
|
def _compute_related_partners(self):
|
|
for p in self:
|
|
p.related_partner_ids = (
|
|
p.partner_id
|
|
| p.partner_id.commercial_partner_id
|
|
| p.partner_id.commercial_partner_id.child_ids
|
|
)._origin
|
|
|
|
@api.onchange('partner_id', 'payment_method_id', 'journal_id')
|
|
def _onchange_set_payment_token_id(self):
|
|
if not (self.payment_method_code == 'electronic' and self.partner_id and self.journal_id):
|
|
self.payment_token_id = False
|
|
return
|
|
|
|
self.payment_token_id = self.env['payment.token'].search([
|
|
('partner_id', 'in', self.related_partner_ids.ids),
|
|
('acquirer_id.capture_manually', '=', False),
|
|
('acquirer_id.journal_id', '=', self.journal_id.id),
|
|
], limit=1)
|
|
|
|
def _prepare_payment_transaction_vals(self):
|
|
self.ensure_one()
|
|
return {
|
|
'amount': self.amount,
|
|
'currency_id': self.currency_id.id,
|
|
'partner_id': self.partner_id.id,
|
|
'partner_country_id': self.partner_id.country_id.id,
|
|
'invoice_ids': [(6, 0, self.invoice_ids.ids)],
|
|
'payment_token_id': self.payment_token_id.id,
|
|
'acquirer_id': self.payment_token_id.acquirer_id.id,
|
|
'payment_id': self.id,
|
|
'type': 'server2server',
|
|
}
|
|
|
|
def _create_payment_transaction(self, vals=None):
|
|
for pay in self:
|
|
if pay.payment_transaction_id:
|
|
raise ValidationError(_('A payment transaction already exists.'))
|
|
elif not pay.payment_token_id:
|
|
raise ValidationError(_('A token is required to create a new payment transaction.'))
|
|
|
|
transactions = self.env['payment.transaction']
|
|
for pay in self:
|
|
transaction_vals = pay._prepare_payment_transaction_vals()
|
|
|
|
if vals:
|
|
transaction_vals.update(vals)
|
|
|
|
transaction = self.env['payment.transaction'].create(transaction_vals)
|
|
transactions += transaction
|
|
|
|
# Link the transaction to the payment.
|
|
pay.payment_transaction_id = transaction
|
|
|
|
return transactions
|
|
|
|
def action_validate_invoice_payment(self):
|
|
res = super(AccountPayment, self).action_validate_invoice_payment()
|
|
self.mapped('payment_transaction_id').filtered(lambda x: x.state == 'done' and not x.is_processed)._post_process_after_done()
|
|
return res
|
|
|
|
def action_post(self):
|
|
# Post the payments "normally" if no transactions are needed.
|
|
# If not, let the acquirer updates the state.
|
|
# __________ ______________
|
|
# | Payments | | Transactions |
|
|
# |__________| |______________|
|
|
# || | |
|
|
# || | |
|
|
# || | |
|
|
# __________ no s2s required __\/______ s2s required | | s2s_do_transaction()
|
|
# | Posted |<-----------------| post() |---------------- |
|
|
# |__________| |__________|<----- |
|
|
# | |
|
|
# OR---------------
|
|
# __________ __________ |
|
|
# | Cancelled|<-----------------| cancel() |<-----
|
|
# |__________| |__________|
|
|
|
|
payments_need_trans = self.filtered(lambda pay: pay.payment_token_id and not pay.payment_transaction_id)
|
|
transactions = payments_need_trans._create_payment_transaction()
|
|
|
|
res = super(AccountPayment, self - payments_need_trans).action_post()
|
|
|
|
transactions.s2s_do_transaction()
|
|
|
|
return res
|