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.

303 lines
12 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 mock
  5. from contextlib import contextmanager
  6. from odoo import fields
  7. from odoo.tools import mute_logger
  8. from odoo.tests.common import TransactionCase
  9. from ..models import account_analytic_account
  10. class TestAccountAnalyticAccount(TransactionCase):
  11. def setUp(self):
  12. super(TestAccountAnalyticAccount, self).setUp()
  13. self.Model = self.env['account.analytic.account']
  14. self.partner = self.env.ref('base.res_partner_2')
  15. self.product = self.env.ref('product.product_product_2')
  16. self.product.taxes_id += self.env['account.tax'].search(
  17. [('type_tax_use', '=', 'sale')], limit=1)
  18. self.product.description_sale = 'Test description sale'
  19. self.template_vals = {
  20. 'recurring_rule_type': 'yearly',
  21. 'recurring_interval': 12345,
  22. 'name': 'Test Contract Template',
  23. 'is_auto_pay': True,
  24. }
  25. self.template = self.env['account.analytic.contract'].create(
  26. self.template_vals,
  27. )
  28. self.acquirer = self.env['payment.acquirer'].create({
  29. 'name': 'Test Acquirer',
  30. 'provider': 'manual',
  31. 'view_template_id': self.env['ir.ui.view'].search([], limit=1).id,
  32. })
  33. self.payment_token = self.env['payment.token'].create({
  34. 'name': 'Test Token',
  35. 'partner_id': self.partner.id,
  36. 'active': True,
  37. 'acquirer_id': self.acquirer.id,
  38. 'acquirer_ref': 'Test',
  39. })
  40. self.contract = self.Model.create({
  41. 'name': 'Test Contract',
  42. 'partner_id': self.partner.id,
  43. 'pricelist_id': self.partner.property_product_pricelist.id,
  44. 'recurring_invoices': True,
  45. 'date_start': '2016-02-15',
  46. 'recurring_next_date': fields.Datetime.now(),
  47. 'payment_token_id': self.payment_token.id,
  48. })
  49. self.contract_line = self.env['account.analytic.invoice.line'].create({
  50. 'analytic_account_id': self.contract.id,
  51. 'product_id': self.product.id,
  52. 'name': 'Services from #START# to #END#',
  53. 'quantity': 1,
  54. 'uom_id': self.product.uom_id.id,
  55. 'price_unit': 100,
  56. 'discount': 50,
  57. })
  58. def _validate_invoice(self, invoice):
  59. self.assertEqual(len(invoice), 1)
  60. self.assertEqual(invoice._name, 'account.invoice')
  61. def _create_invoice(self, open=False, sent=False):
  62. self.contract.is_auto_pay = False
  63. invoice = self.contract._create_invoice()
  64. if open or sent:
  65. invoice.action_invoice_open()
  66. if sent:
  67. invoice.sent = True
  68. self.contract.is_auto_pay = True
  69. return invoice
  70. @contextmanager
  71. def _mock_transaction(self, state='authorized', s2s_side_effect=None):
  72. Transactions = self.contract.env['payment.transaction']
  73. TransactionsCreate = Transactions.create
  74. if not callable(s2s_side_effect):
  75. s2s_side_effect = [s2s_side_effect]
  76. s2s = mock.MagicMock()
  77. s2s.side_effect = s2s_side_effect
  78. def create(vals):
  79. record = TransactionsCreate(vals)
  80. record.state = state
  81. return record
  82. model_create = mock.MagicMock()
  83. model_create.side_effect = create
  84. Transactions._patch_method('create', model_create)
  85. Transactions._patch_method('s2s_do_transaction', s2s)
  86. try:
  87. yield
  88. finally:
  89. Transactions._revert_method('create')
  90. Transactions._revert_method('s2s_do_transaction')
  91. def test_onchange_partner_id_payment_token(self):
  92. """ It should clear the payment token. """
  93. self.assertTrue(self.contract.payment_token_id)
  94. self.contract._onchange_partner_id_payment_token()
  95. self.assertFalse(self.contract.payment_token_id)
  96. def test_create_invoice_no_autopay(self):
  97. """ It should return the new invoice without calling autopay. """
  98. self.contract.is_auto_pay = False
  99. with mock.patch.object(self.contract, '_do_auto_pay') as method:
  100. invoice = self.contract._create_invoice()
  101. self._validate_invoice(invoice)
  102. method.assert_not_called()
  103. def test_create_invoice_autopay(self):
  104. """ It should return the new invoice after calling autopay. """
  105. with mock.patch.object(self.contract, '_do_auto_pay') as method:
  106. invoice = self.contract._create_invoice()
  107. self._validate_invoice(invoice)
  108. method.assert_called_once_with(invoice)
  109. def test_do_auto_pay_ensure_one(self):
  110. """ It should ensure_one on self. """
  111. with self.assertRaises(ValueError):
  112. self.env['account.analytic.account']._do_auto_pay(
  113. self._create_invoice(),
  114. )
  115. def test_do_auto_pay_invoice_ensure_one(self):
  116. """ It should ensure_one on the invoice. """
  117. with self.assertRaises(ValueError):
  118. self.contract._do_auto_pay(
  119. self.env['account.invoice'],
  120. )
  121. def test_do_auto_pay_open_invoice(self):
  122. """ It should open the invoice. """
  123. invoice = self._create_invoice()
  124. self.contract._do_auto_pay(invoice)
  125. self.assertEqual(invoice.state, 'open')
  126. def test_do_auto_pay_sends_message(self):
  127. """ It should call the send message method with the invoice. """
  128. with mock.patch.object(self.contract, '_send_invoice_message') as m:
  129. invoice = self._create_invoice()
  130. self.contract._do_auto_pay(invoice)
  131. m.assert_called_once_with(invoice)
  132. def test_do_auto_pay_does_pay(self):
  133. """ It should try to pay the invoice. """
  134. with mock.patch.object(self.contract, '_pay_invoice') as m:
  135. invoice = self._create_invoice()
  136. self.contract._do_auto_pay(invoice)
  137. m.assert_called_once_with(invoice)
  138. def test_pay_invoice_not_open(self):
  139. """ It should return None if the invoice isn't open. """
  140. invoice = self._create_invoice()
  141. res = self.contract._pay_invoice(invoice)
  142. self.assertIs(res, None)
  143. def test_pay_invoice_no_residual(self):
  144. """ It should return None if no residual on the invoice. """
  145. invoice = self._create_invoice()
  146. invoice.state = 'open'
  147. res = self.contract._pay_invoice(invoice)
  148. self.assertIs(res, None)
  149. def test_pay_invoice_no_token(self):
  150. """ It should return None if no payment token. """
  151. self.contract.payment_token_id = False
  152. invoice = self._create_invoice(True)
  153. res = self.contract._pay_invoice(invoice)
  154. self.assertIs(res, None)
  155. def test_pay_invoice_success(self):
  156. """ It should return True on success. """
  157. with self._mock_transaction(s2s_side_effect=True):
  158. invoice = self._create_invoice(True)
  159. res = self.contract._pay_invoice(invoice)
  160. self.assertTrue(res)
  161. @mute_logger(account_analytic_account.__name__)
  162. def test_pay_invoice_exception(self):
  163. """ It should catch exceptions. """
  164. with self._mock_transaction(s2s_side_effect=Exception):
  165. invoice = self._create_invoice(True)
  166. res = self.contract._pay_invoice(invoice)
  167. self.assertIs(res, None)
  168. def test_pay_invoice_invalid_state(self):
  169. """ It should return None on invalid state. """
  170. with self._mock_transaction(s2s_side_effect=True):
  171. invoice = self._create_invoice(True)
  172. invoice.state = 'draft'
  173. res = self.contract._pay_invoice(invoice)
  174. self.assertIs(res, None)
  175. @mute_logger(account_analytic_account.__name__)
  176. def test_pay_invoice_increments_retries(self):
  177. """ It should increment invoice retries on failure. """
  178. with self._mock_transaction(s2s_side_effect=False):
  179. invoice = self._create_invoice(True)
  180. self.assertFalse(invoice.auto_pay_attempts)
  181. self.contract._pay_invoice(invoice)
  182. self.assertTrue(invoice.auto_pay_attempts)
  183. def test_pay_invoice_updates_fail_date(self):
  184. """ It should update the invoice auto pay fail date on failure. """
  185. with self._mock_transaction(s2s_side_effect=False):
  186. invoice = self._create_invoice(True)
  187. self.assertFalse(invoice.auto_pay_failed)
  188. self.contract._pay_invoice(invoice)
  189. self.assertTrue(invoice.auto_pay_failed)
  190. def test_pay_invoice_too_many_attempts(self):
  191. """ It should clear autopay after too many attempts. """
  192. with self._mock_transaction(s2s_side_effect=False):
  193. invoice = self._create_invoice(True)
  194. invoice.auto_pay_attempts = self.contract.auto_pay_retries - 1
  195. self.contract._pay_invoice(invoice)
  196. self.assertFalse(self.contract.is_auto_pay)
  197. self.assertFalse(self.contract.payment_token_id)
  198. def test_pay_invoice_too_many_attempts_partner_token(self):
  199. """ It should clear the partner token when attempts were on it. """
  200. self.partner.payment_token_id = self.contract.payment_token_id
  201. with self._mock_transaction(s2s_side_effect=False):
  202. invoice = self._create_invoice(True)
  203. invoice.auto_pay_attempts = self.contract.auto_pay_retries
  204. self.contract._pay_invoice(invoice)
  205. self.assertFalse(self.partner.payment_token_id)
  206. def test_get_tx_vals(self):
  207. """ It should return a dict. """
  208. self.assertIsInstance(
  209. self.contract._get_tx_vals(self._create_invoice()),
  210. dict,
  211. )
  212. def test_send_invoice_message_sent(self):
  213. """ It should return None if the invoice has already been sent. """
  214. invoice = self._create_invoice(sent=True)
  215. res = self.contract._send_invoice_message(invoice)
  216. self.assertIs(res, None)
  217. def test_send_invoice_message_no_template(self):
  218. """ It should return None if the invoice isn't sent. """
  219. invoice = self._create_invoice(True)
  220. self.contract.invoice_mail_template_id = False
  221. res = self.contract._send_invoice_message(invoice)
  222. self.assertIs(res, None)
  223. def test_send_invoice_message_sets_invoice_state(self):
  224. """ It should set the invoice to sent. """
  225. invoice = self._create_invoice(True)
  226. self.assertFalse(invoice.sent)
  227. self.contract._send_invoice_message(invoice)
  228. self.assertTrue(invoice.sent)
  229. def test_send_invoice_message_returns_mail(self):
  230. """ It should create and return the message. """
  231. invoice = self._create_invoice(True)
  232. res = self.contract._send_invoice_message(invoice)
  233. self.assertEqual(res._name, 'mail.mail')
  234. def test_cron_retry_auto_pay_needed(self):
  235. """ It should auto-pay the correct invoice if needed. """
  236. invoice = self._create_invoice(True)
  237. invoice.write({
  238. 'auto_pay_attempts': 1,
  239. 'auto_pay_failed': '2015-01-01 00:00:00',
  240. })
  241. meth = mock.MagicMock()
  242. self.contract._patch_method('_do_auto_pay', meth)
  243. try:
  244. self.contract.cron_retry_auto_pay()
  245. finally:
  246. self.contract._revert_method('_do_auto_pay')
  247. meth.assert_called_once_with(invoice)
  248. def test_cron_retry_auto_pay_skip(self):
  249. """ It should skip invoices that don't need to be paid. """
  250. invoice = self._create_invoice(True)
  251. invoice.write({
  252. 'auto_pay_attempts': 1,
  253. 'auto_pay_failed': fields.Datetime.now(),
  254. })
  255. meth = mock.MagicMock()
  256. self.contract._patch_method('_do_auto_pay', meth)
  257. try:
  258. self.contract.cron_retry_auto_pay()
  259. finally:
  260. self.contract._revert_method('_do_auto_pay')
  261. meth.assert_not_called()