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.

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