You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
173 lines
5.8 KiB
173 lines
5.8 KiB
# -*- 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)
|