mirror of
https://github.com/OCA/contract.git
synced 2025-02-13 17:57:24 +02:00
[ADD] contract_auto: Automatic Payment For Contracts
This commit is contained in:
174
contract_payment_auto/models/account_analytic_account.py
Normal file
174
contract_payment_auto/models/account_analytic_account.py
Normal file
@@ -0,0 +1,174 @@
|
||||
# -*- 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),
|
||||
)
|
||||
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):
|
||||
""" Return values for create of payment.transaction for invoice."""
|
||||
amount_due = invoice.residual
|
||||
token = self.payment_token_id
|
||||
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)
|
||||
Reference in New Issue
Block a user