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.

174 lines
5.8 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2017 LasLabs Inc.
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  4. import logging
  5. from datetime import datetime, timedelta
  6. from odoo import api, fields, models, _
  7. _logger = logging.getLogger(__name__)
  8. class AccountAnalyticAccount(models.Model):
  9. _inherit = 'account.analytic.account'
  10. payment_token_id = fields.Many2one(
  11. string='Payment Token',
  12. comodel_name='payment.token',
  13. domain="[('partner_id', '=', partner_id)]",
  14. context="{'default_partner_id': partner_id}",
  15. help='This is the payment token that will be used to automatically '
  16. 'reconcile debts against this account. If none is set, the '
  17. 'bill to partner\'s default token will be used.',
  18. )
  19. @api.multi
  20. @api.onchange('partner_id')
  21. def _onchange_partner_id_payment_token(self):
  22. """ Clear the payment token when the partner is changed. """
  23. self.payment_token_id = self.env['payment.token']
  24. @api.model
  25. def cron_retry_auto_pay(self):
  26. """ Retry automatic payments for appropriate invoices. """
  27. invoice_lines = self.env['account.invoice.line'].search([
  28. ('invoice_id.state', '=', 'open'),
  29. ('invoice_id.auto_pay_attempts', '>', 0),
  30. ('account_analytic_id.is_auto_pay', '=', True),
  31. ])
  32. now = datetime.now()
  33. for invoice_line in invoice_lines:
  34. account = invoice_line.account_analytic_id
  35. invoice = invoice_line.invoice_id
  36. fail_time = fields.Datetime.from_string(invoice.auto_pay_failed)
  37. retry_delta = timedelta(hours=account.auto_pay_retry_hours)
  38. retry_time = fail_time + retry_delta
  39. if retry_time < now:
  40. account._do_auto_pay(invoice)
  41. @api.multi
  42. def _create_invoice(self):
  43. """ If automatic payment is enabled, perform auto pay actions. """
  44. invoice = super(AccountAnalyticAccount, self)._create_invoice()
  45. if not self.is_auto_pay:
  46. return invoice
  47. self._do_auto_pay(invoice)
  48. return invoice
  49. @api.multi
  50. def _do_auto_pay(self, invoice):
  51. """ Perform all automatic payment operations on open invoices. """
  52. self.ensure_one()
  53. invoice.ensure_one()
  54. invoice.action_invoice_open()
  55. self._send_invoice_message(invoice)
  56. self._pay_invoice(invoice)
  57. @api.multi
  58. def _pay_invoice(self, invoice):
  59. """ Pay the invoice using the account or partner token. """
  60. if invoice.state != 'open':
  61. _logger.info('Cannot pay an invoice that is not in open state.')
  62. return
  63. if not invoice.residual:
  64. _logger.debug('Cannot pay an invoice with no balance.')
  65. return
  66. token = self.payment_token_id or self.partner_id.payment_token_id
  67. if not token:
  68. _logger.debug(
  69. 'Cannot pay an invoice without defining a payment token',
  70. )
  71. return
  72. transaction = self.env['payment.transaction'].create(
  73. self._get_tx_vals(invoice),
  74. )
  75. valid_states = ['authorized', 'done']
  76. try:
  77. result = transaction.s2s_do_transaction()
  78. if not result or transaction.state not in valid_states:
  79. _logger.debug(
  80. 'Payment transaction failed (%s)',
  81. transaction.state_message,
  82. )
  83. else:
  84. # Success
  85. return True
  86. except Exception:
  87. _logger.exception(
  88. 'Payment transaction (%s) generated a gateway error.',
  89. transaction.id,
  90. )
  91. transaction.state = 'error'
  92. invoice.write({
  93. 'auto_pay_attempts': invoice.auto_pay_attempts + 1,
  94. 'auto_pay_failed': fields.Datetime.now(),
  95. })
  96. if invoice.auto_pay_attempts >= self.auto_pay_retries:
  97. template = self.pay_fail_mail_template_id
  98. self.write({
  99. 'is_auto_pay': False,
  100. 'payment_token_id': False,
  101. })
  102. if token == self.partner_id.payment_token_id:
  103. self.partner_id.payment_token_id = False
  104. else:
  105. template = self.pay_retry_mail_template_id
  106. if template:
  107. template.send_mail(invoice.id)
  108. return
  109. @api.multi
  110. def _get_tx_vals(self, invoice):
  111. """ Return values for create of payment.transaction for invoice."""
  112. amount_due = invoice.residual
  113. token = self.payment_token_id
  114. partner = token.partner_id
  115. reference = self.env['payment.transaction'].get_next_reference(
  116. invoice.number,
  117. )
  118. return {
  119. 'reference': '%s' % reference,
  120. 'acquirer_id': token.acquirer_id.id,
  121. 'payment_token_id': token.id,
  122. 'amount': amount_due,
  123. 'state': 'draft',
  124. 'currency_id': invoice.currency_id.id,
  125. 'partner_id': partner.id,
  126. 'partner_country_id': partner.country_id.id,
  127. 'partner_city': partner.city,
  128. 'partner_zip': partner.zip,
  129. 'partner_email': partner.email,
  130. }
  131. @api.multi
  132. def _send_invoice_message(self, invoice):
  133. """ Send the appropriate emails for the invoices if needed. """
  134. if invoice.sent:
  135. return
  136. if not self.invoice_mail_template_id:
  137. return
  138. _logger.info('Sending invoice %s, %s (template %s)',
  139. invoice, invoice.number, self.invoice_mail_template_id)
  140. mail_id = self.invoice_mail_template_id.send_mail(invoice.id)
  141. invoice.with_context(mail_post_autofollow=True)
  142. invoice.sent = True
  143. invoice.message_post(body=_("Invoice sent"))
  144. return self.env['mail.mail'].browse(mail_id)