mirror of
https://github.com/OCA/contract.git
synced 2025-02-13 17:57:24 +02:00
When a contrat had no payment token but the corresponding partner had one, the transaction was created without an acquirer, leading to an integrity error in postgres. This change makes sure the token used to test the ability to pay an invoice is passed along to the transaction creation call. Tests were also added to check the ability to use the contract token if present, but the partner's in the opposite case. This change fixes #165.
174 lines
5.8 KiB
Python
174 lines
5.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2017 LasLabs Inc.
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
|
|
|
import logging
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
from odoo import api, fields, models, _
|
|
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AccountAnalyticAccount(models.Model):
|
|
_inherit = 'account.analytic.account'
|
|
|
|
payment_token_id = fields.Many2one(
|
|
string='Payment Token',
|
|
comodel_name='payment.token',
|
|
domain="[('partner_id', '=', partner_id)]",
|
|
context="{'default_partner_id': partner_id}",
|
|
help='This is the payment token that will be used to automatically '
|
|
'reconcile debts against this account. If none is set, the '
|
|
'bill to partner\'s default token will be used.',
|
|
)
|
|
|
|
@api.multi
|
|
@api.onchange('partner_id')
|
|
def _onchange_partner_id_payment_token(self):
|
|
""" Clear the payment token when the partner is changed. """
|
|
self.payment_token_id = self.env['payment.token']
|
|
|
|
@api.model
|
|
def cron_retry_auto_pay(self):
|
|
""" Retry automatic payments for appropriate invoices. """
|
|
|
|
invoice_lines = self.env['account.invoice.line'].search([
|
|
('invoice_id.state', '=', 'open'),
|
|
('invoice_id.auto_pay_attempts', '>', 0),
|
|
('account_analytic_id.is_auto_pay', '=', True),
|
|
])
|
|
now = datetime.now()
|
|
|
|
for invoice_line in invoice_lines:
|
|
|
|
account = invoice_line.account_analytic_id
|
|
invoice = invoice_line.invoice_id
|
|
fail_time = fields.Datetime.from_string(invoice.auto_pay_failed)
|
|
retry_delta = timedelta(hours=account.auto_pay_retry_hours)
|
|
retry_time = fail_time + retry_delta
|
|
|
|
if retry_time < now:
|
|
account._do_auto_pay(invoice)
|
|
|
|
@api.multi
|
|
def _create_invoice(self):
|
|
""" If automatic payment is enabled, perform auto pay actions. """
|
|
invoice = super(AccountAnalyticAccount, self)._create_invoice()
|
|
if not self.is_auto_pay:
|
|
return invoice
|
|
self._do_auto_pay(invoice)
|
|
return invoice
|
|
|
|
@api.multi
|
|
def _do_auto_pay(self, invoice):
|
|
""" Perform all automatic payment operations on open invoices. """
|
|
self.ensure_one()
|
|
invoice.ensure_one()
|
|
invoice.action_invoice_open()
|
|
self._send_invoice_message(invoice)
|
|
self._pay_invoice(invoice)
|
|
|
|
@api.multi
|
|
def _pay_invoice(self, invoice):
|
|
""" Pay the invoice using the account or partner token. """
|
|
|
|
if invoice.state != 'open':
|
|
_logger.info('Cannot pay an invoice that is not in open state.')
|
|
return
|
|
|
|
if not invoice.residual:
|
|
_logger.debug('Cannot pay an invoice with no balance.')
|
|
return
|
|
|
|
token = self.payment_token_id or self.partner_id.payment_token_id
|
|
if not token:
|
|
_logger.debug(
|
|
'Cannot pay an invoice without defining a payment token',
|
|
)
|
|
return
|
|
|
|
transaction = self.env['payment.transaction'].create(
|
|
self._get_tx_vals(invoice, token),
|
|
)
|
|
valid_states = ['authorized', 'done']
|
|
|
|
try:
|
|
result = transaction.s2s_do_transaction()
|
|
if not result or transaction.state not in valid_states:
|
|
_logger.debug(
|
|
'Payment transaction failed (%s)',
|
|
transaction.state_message,
|
|
)
|
|
else:
|
|
# Success
|
|
return True
|
|
|
|
except Exception:
|
|
_logger.exception(
|
|
'Payment transaction (%s) generated a gateway error.',
|
|
transaction.id,
|
|
)
|
|
|
|
transaction.state = 'error'
|
|
invoice.write({
|
|
'auto_pay_attempts': invoice.auto_pay_attempts + 1,
|
|
'auto_pay_failed': fields.Datetime.now(),
|
|
})
|
|
|
|
if invoice.auto_pay_attempts >= self.auto_pay_retries:
|
|
template = self.pay_fail_mail_template_id
|
|
self.write({
|
|
'is_auto_pay': False,
|
|
'payment_token_id': False,
|
|
})
|
|
if token == self.partner_id.payment_token_id:
|
|
self.partner_id.payment_token_id = False
|
|
|
|
else:
|
|
template = self.pay_retry_mail_template_id
|
|
|
|
if template:
|
|
template.send_mail(invoice.id)
|
|
|
|
return
|
|
|
|
@api.multi
|
|
def _get_tx_vals(self, invoice, token):
|
|
""" Return values for create of payment.transaction for invoice."""
|
|
amount_due = invoice.residual
|
|
partner = token.partner_id
|
|
reference = self.env['payment.transaction'].get_next_reference(
|
|
invoice.number,
|
|
)
|
|
return {
|
|
'reference': '%s' % reference,
|
|
'acquirer_id': token.acquirer_id.id,
|
|
'payment_token_id': token.id,
|
|
'amount': amount_due,
|
|
'state': 'draft',
|
|
'currency_id': invoice.currency_id.id,
|
|
'partner_id': partner.id,
|
|
'partner_country_id': partner.country_id.id,
|
|
'partner_city': partner.city,
|
|
'partner_zip': partner.zip,
|
|
'partner_email': partner.email,
|
|
}
|
|
|
|
@api.multi
|
|
def _send_invoice_message(self, invoice):
|
|
""" Send the appropriate emails for the invoices if needed. """
|
|
if invoice.sent:
|
|
return
|
|
if not self.invoice_mail_template_id:
|
|
return
|
|
_logger.info('Sending invoice %s, %s (template %s)',
|
|
invoice, invoice.number, self.invoice_mail_template_id)
|
|
mail_id = self.invoice_mail_template_id.send_mail(invoice.id)
|
|
invoice.with_context(mail_post_autofollow=True)
|
|
invoice.sent = True
|
|
invoice.message_post(body=_("Invoice sent"))
|
|
return self.env['mail.mail'].browse(mail_id)
|