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.

333 lines
13 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.other_payment_token = self.env['payment.token'].create({
  43. 'name': 'Test Other Token',
  44. 'partner_id': self.partner.id,
  45. 'active': True,
  46. 'acquirer_id': self.acquirer.id,
  47. 'acquirer_ref': 'OtherTest',
  48. })
  49. self.contract = self.Model.create({
  50. 'name': 'Test Contract',
  51. 'partner_id': self.partner.id,
  52. 'pricelist_id': self.partner.property_product_pricelist.id,
  53. 'recurring_invoices': True,
  54. 'date_start': '2016-02-15',
  55. 'recurring_next_date': fields.Datetime.now(),
  56. 'payment_token_id': self.payment_token.id,
  57. })
  58. self.contract_line = self.env['account.analytic.invoice.line'].create({
  59. 'analytic_account_id': self.contract.id,
  60. 'product_id': self.product.id,
  61. 'name': 'Services from #START# to #END#',
  62. 'quantity': 1,
  63. 'uom_id': self.product.uom_id.id,
  64. 'price_unit': 100,
  65. 'discount': 50,
  66. })
  67. def _validate_invoice(self, invoice):
  68. self.assertEqual(len(invoice), 1)
  69. self.assertEqual(invoice._name, 'account.invoice')
  70. def _create_invoice(self, open=False, sent=False):
  71. self.contract.is_auto_pay = False
  72. invoice = self.contract._create_invoice()
  73. if open or sent:
  74. invoice.action_invoice_open()
  75. if sent:
  76. invoice.sent = True
  77. self.contract.is_auto_pay = True
  78. return invoice
  79. @contextmanager
  80. def _mock_transaction(self, state='authorized', s2s_side_effect=None):
  81. Transactions = self.contract.env['payment.transaction']
  82. TransactionsCreate = Transactions.create
  83. if not callable(s2s_side_effect):
  84. s2s_side_effect = [s2s_side_effect]
  85. s2s = mock.MagicMock()
  86. s2s.side_effect = s2s_side_effect
  87. def create(vals):
  88. record = TransactionsCreate(vals)
  89. record.state = state
  90. return record
  91. model_create = mock.MagicMock()
  92. model_create.side_effect = create
  93. Transactions._patch_method('create', model_create)
  94. Transactions._patch_method('s2s_do_transaction', s2s)
  95. try:
  96. yield
  97. finally:
  98. Transactions._revert_method('create')
  99. Transactions._revert_method('s2s_do_transaction')
  100. def test_onchange_partner_id_payment_token(self):
  101. """ It should clear the payment token. """
  102. self.assertTrue(self.contract.payment_token_id)
  103. self.contract._onchange_partner_id_payment_token()
  104. self.assertFalse(self.contract.payment_token_id)
  105. def test_create_invoice_no_autopay(self):
  106. """ It should return the new invoice without calling autopay. """
  107. self.contract.is_auto_pay = False
  108. with mock.patch.object(self.contract, '_do_auto_pay') as method:
  109. invoice = self.contract._create_invoice()
  110. self._validate_invoice(invoice)
  111. method.assert_not_called()
  112. def test_create_invoice_autopay(self):
  113. """ It should return the new invoice after calling autopay. """
  114. with mock.patch.object(self.contract, '_do_auto_pay') as method:
  115. invoice = self.contract._create_invoice()
  116. self._validate_invoice(invoice)
  117. method.assert_called_once_with(invoice)
  118. def test_do_auto_pay_ensure_one(self):
  119. """ It should ensure_one on self. """
  120. with self.assertRaises(ValueError):
  121. self.env['account.analytic.account']._do_auto_pay(
  122. self._create_invoice(),
  123. )
  124. def test_do_auto_pay_invoice_ensure_one(self):
  125. """ It should ensure_one on the invoice. """
  126. with self.assertRaises(ValueError):
  127. self.contract._do_auto_pay(
  128. self.env['account.invoice'],
  129. )
  130. def test_do_auto_pay_open_invoice(self):
  131. """ It should open the invoice. """
  132. invoice = self._create_invoice()
  133. self.contract._do_auto_pay(invoice)
  134. self.assertEqual(invoice.state, 'open')
  135. def test_do_auto_pay_sends_message(self):
  136. """ It should call the send message method with the invoice. """
  137. with mock.patch.object(self.contract, '_send_invoice_message') as m:
  138. invoice = self._create_invoice()
  139. self.contract._do_auto_pay(invoice)
  140. m.assert_called_once_with(invoice)
  141. def test_do_auto_pay_does_pay(self):
  142. """ It should try to pay the invoice. """
  143. with mock.patch.object(self.contract, '_pay_invoice') as m:
  144. invoice = self._create_invoice()
  145. self.contract._do_auto_pay(invoice)
  146. m.assert_called_once_with(invoice)
  147. def test_pay_invoice_not_open(self):
  148. """ It should return None if the invoice isn't open. """
  149. invoice = self._create_invoice()
  150. res = self.contract._pay_invoice(invoice)
  151. self.assertIs(res, None)
  152. def test_pay_invoice_no_residual(self):
  153. """ It should return None if no residual on the invoice. """
  154. invoice = self._create_invoice()
  155. invoice.state = 'open'
  156. res = self.contract._pay_invoice(invoice)
  157. self.assertIs(res, None)
  158. def test_pay_invoice_no_token(self):
  159. """ It should return None if no payment token. """
  160. self.contract.payment_token_id = False
  161. invoice = self._create_invoice(True)
  162. res = self.contract._pay_invoice(invoice)
  163. self.assertIs(res, None)
  164. def assert_successful_pay_invoice(self, expected_token=None):
  165. with self._mock_transaction(s2s_side_effect=True):
  166. invoice = self._create_invoice(True)
  167. res = self.contract._pay_invoice(invoice)
  168. self.assertTrue(res)
  169. if expected_token is not None:
  170. Transactions = self.contract.env['payment.transaction']
  171. tx_vals = Transactions.create.call_args[0][0]
  172. self.assertEqual(tx_vals.get('payment_token_id'),
  173. expected_token.id)
  174. def test_pay_invoice_success(self):
  175. """ It should return True on success. """
  176. self.assert_successful_pay_invoice()
  177. def test_pay_invoice_with_contract_token(self):
  178. """ When contract and partner have a token, contract's is used. """
  179. self.partner.payment_token_id = self.other_payment_token
  180. self.contract.payment_token_id = self.payment_token
  181. self.assert_successful_pay_invoice(expected_token=self.payment_token)
  182. def test_pay_invoice_with_partner_token_success(self):
  183. """ When contract has no related token, it should use partner's. """
  184. self.contract.payment_token_id = False
  185. self.partner.payment_token_id = self.other_payment_token
  186. self.assert_successful_pay_invoice(
  187. expected_token=self.other_payment_token)
  188. @mute_logger(account_analytic_account.__name__)
  189. def test_pay_invoice_exception(self):
  190. """ It should catch exceptions. """
  191. with self._mock_transaction(s2s_side_effect=Exception):
  192. invoice = self._create_invoice(True)
  193. res = self.contract._pay_invoice(invoice)
  194. self.assertIs(res, None)
  195. def test_pay_invoice_invalid_state(self):
  196. """ It should return None on invalid state. """
  197. with self._mock_transaction(s2s_side_effect=True):
  198. invoice = self._create_invoice(True)
  199. invoice.state = 'draft'
  200. res = self.contract._pay_invoice(invoice)
  201. self.assertIs(res, None)
  202. @mute_logger(account_analytic_account.__name__)
  203. def test_pay_invoice_increments_retries(self):
  204. """ It should increment invoice retries on failure. """
  205. with self._mock_transaction(s2s_side_effect=False):
  206. invoice = self._create_invoice(True)
  207. self.assertFalse(invoice.auto_pay_attempts)
  208. self.contract._pay_invoice(invoice)
  209. self.assertTrue(invoice.auto_pay_attempts)
  210. def test_pay_invoice_updates_fail_date(self):
  211. """ It should update the invoice auto pay fail date on failure. """
  212. with self._mock_transaction(s2s_side_effect=False):
  213. invoice = self._create_invoice(True)
  214. self.assertFalse(invoice.auto_pay_failed)
  215. self.contract._pay_invoice(invoice)
  216. self.assertTrue(invoice.auto_pay_failed)
  217. def test_pay_invoice_too_many_attempts(self):
  218. """ It should clear autopay after too many attempts. """
  219. with self._mock_transaction(s2s_side_effect=False):
  220. invoice = self._create_invoice(True)
  221. invoice.auto_pay_attempts = self.contract.auto_pay_retries - 1
  222. self.contract._pay_invoice(invoice)
  223. self.assertFalse(self.contract.is_auto_pay)
  224. self.assertFalse(self.contract.payment_token_id)
  225. def test_pay_invoice_too_many_attempts_partner_token(self):
  226. """ It should clear the partner token when attempts were on it. """
  227. self.partner.payment_token_id = self.contract.payment_token_id
  228. with self._mock_transaction(s2s_side_effect=False):
  229. invoice = self._create_invoice(True)
  230. invoice.auto_pay_attempts = self.contract.auto_pay_retries
  231. self.contract._pay_invoice(invoice)
  232. self.assertFalse(self.partner.payment_token_id)
  233. def test_get_tx_vals(self):
  234. """ It should return a dict. """
  235. self.assertIsInstance(
  236. self.contract._get_tx_vals(self._create_invoice(),
  237. self.contract.payment_token_id),
  238. dict,
  239. )
  240. def test_send_invoice_message_sent(self):
  241. """ It should return None if the invoice has already been sent. """
  242. invoice = self._create_invoice(sent=True)
  243. res = self.contract._send_invoice_message(invoice)
  244. self.assertIs(res, None)
  245. def test_send_invoice_message_no_template(self):
  246. """ It should return None if the invoice isn't sent. """
  247. invoice = self._create_invoice(True)
  248. self.contract.invoice_mail_template_id = False
  249. res = self.contract._send_invoice_message(invoice)
  250. self.assertIs(res, None)
  251. def test_send_invoice_message_sets_invoice_state(self):
  252. """ It should set the invoice to sent. """
  253. invoice = self._create_invoice(True)
  254. self.assertFalse(invoice.sent)
  255. self.contract._send_invoice_message(invoice)
  256. self.assertTrue(invoice.sent)
  257. def test_send_invoice_message_returns_mail(self):
  258. """ It should create and return the message. """
  259. invoice = self._create_invoice(True)
  260. res = self.contract._send_invoice_message(invoice)
  261. self.assertEqual(res._name, 'mail.mail')
  262. def test_cron_retry_auto_pay_needed(self):
  263. """ It should auto-pay the correct invoice if needed. """
  264. invoice = self._create_invoice(True)
  265. invoice.write({
  266. 'auto_pay_attempts': 1,
  267. 'auto_pay_failed': '2015-01-01 00:00:00',
  268. })
  269. meth = mock.MagicMock()
  270. self.contract._patch_method('_do_auto_pay', meth)
  271. try:
  272. self.contract.cron_retry_auto_pay()
  273. finally:
  274. self.contract._revert_method('_do_auto_pay')
  275. meth.assert_called_once_with(invoice)
  276. def test_cron_retry_auto_pay_skip(self):
  277. """ It should skip invoices that don't need to be paid. """
  278. invoice = self._create_invoice(True)
  279. invoice.write({
  280. 'auto_pay_attempts': 1,
  281. 'auto_pay_failed': fields.Datetime.now(),
  282. })
  283. meth = mock.MagicMock()
  284. self.contract._patch_method('_do_auto_pay', meth)
  285. try:
  286. self.contract.cron_retry_auto_pay()
  287. finally:
  288. self.contract._revert_method('_do_auto_pay')
  289. meth.assert_not_called()