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.

643 lines
21 KiB

  1. # Copyright 2019-2020 Brainbean Apps (https://brainbeanapps.com)
  2. # Copyright 2019-2020 Dataplug (https://dataplug.io)
  3. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
  4. from datetime import date, datetime
  5. from dateutil.relativedelta import relativedelta
  6. from psycopg2 import IntegrityError
  7. from urllib.error import HTTPError
  8. from odoo.tests import common
  9. from odoo.tools import mute_logger
  10. from odoo import fields
  11. class TestAccountBankAccountStatementImportOnline(common.TransactionCase):
  12. def setUp(self):
  13. super().setUp()
  14. self.now = fields.Datetime.now()
  15. self.AccountJournal = self.env['account.journal']
  16. self.OnlineBankStatementProvider = self.env[
  17. 'online.bank.statement.provider'
  18. ]
  19. self.OnlineBankStatementPullWizard = self.env[
  20. 'online.bank.statement.pull.wizard'
  21. ]
  22. self.AccountBankStatement = self.env['account.bank.statement']
  23. self.AccountBankStatementLine = self.env['account.bank.statement.line']
  24. def test_provider_unlink_restricted(self):
  25. journal = self.AccountJournal.create({
  26. 'name': 'Bank',
  27. 'type': 'bank',
  28. 'code': 'BANK',
  29. })
  30. with common.Form(journal) as journal_form:
  31. journal_form.bank_statements_source = 'online'
  32. journal_form.online_bank_statement_provider = 'dummy'
  33. journal_form.save()
  34. with self.assertRaises(IntegrityError), mute_logger('odoo.sql_db'):
  35. journal.online_bank_statement_provider_id.unlink()
  36. def test_cascade_unlink(self):
  37. journal = self.AccountJournal.create({
  38. 'name': 'Bank',
  39. 'type': 'bank',
  40. 'code': 'BANK',
  41. })
  42. with common.Form(journal) as journal_form:
  43. journal_form.bank_statements_source = 'online'
  44. journal_form.online_bank_statement_provider = 'dummy'
  45. journal_form.save()
  46. self.assertTrue(journal.online_bank_statement_provider_id)
  47. journal.unlink()
  48. self.assertFalse(self.OnlineBankStatementProvider.search([]))
  49. def test_source_change_cleanup(self):
  50. journal = self.AccountJournal.create({
  51. 'name': 'Bank',
  52. 'type': 'bank',
  53. 'code': 'BANK',
  54. })
  55. with common.Form(journal) as journal_form:
  56. journal_form.bank_statements_source = 'online'
  57. journal_form.online_bank_statement_provider = 'dummy'
  58. journal_form.save()
  59. self.assertTrue(journal.online_bank_statement_provider_id)
  60. with common.Form(journal) as journal_form:
  61. journal_form.bank_statements_source = 'undefined'
  62. journal_form.save()
  63. self.assertFalse(journal.online_bank_statement_provider_id)
  64. self.assertFalse(self.OnlineBankStatementProvider.search([]))
  65. def test_pull_boundary(self):
  66. journal = self.AccountJournal.create({
  67. 'name': 'Bank',
  68. 'type': 'bank',
  69. 'code': 'BANK',
  70. 'bank_statements_source': 'online',
  71. 'online_bank_statement_provider': 'dummy',
  72. })
  73. provider = journal.online_bank_statement_provider_id
  74. provider.active = True
  75. provider.with_context(
  76. expand_by=1,
  77. )._pull(
  78. self.now - relativedelta(hours=1),
  79. self.now,
  80. )
  81. statement = self.AccountBankStatement.search(
  82. [('journal_id', '=', journal.id)],
  83. )
  84. self.assertEqual(len(statement), 1)
  85. self.assertEqual(len(statement.line_ids), 12)
  86. def test_pull_mode_daily(self):
  87. journal = self.AccountJournal.create({
  88. 'name': 'Bank',
  89. 'type': 'bank',
  90. 'code': 'BANK',
  91. 'bank_statements_source': 'online',
  92. 'online_bank_statement_provider': 'dummy',
  93. })
  94. provider = journal.online_bank_statement_provider_id
  95. provider.active = True
  96. provider.statement_creation_mode = 'daily'
  97. provider.with_context(step={'hours': 2})._pull(
  98. self.now - relativedelta(days=1),
  99. self.now,
  100. )
  101. self.assertEqual(
  102. len(self.AccountBankStatement.search(
  103. [('journal_id', '=', journal.id)]
  104. )),
  105. 2
  106. )
  107. def test_pull_mode_weekly(self):
  108. journal = self.AccountJournal.create({
  109. 'name': 'Bank',
  110. 'type': 'bank',
  111. 'code': 'BANK',
  112. 'bank_statements_source': 'online',
  113. 'online_bank_statement_provider': 'dummy',
  114. })
  115. provider = journal.online_bank_statement_provider_id
  116. provider.active = True
  117. provider.statement_creation_mode = 'weekly'
  118. provider.with_context(step={'hours': 8})._pull(
  119. self.now - relativedelta(weeks=1),
  120. self.now,
  121. )
  122. self.assertEqual(
  123. len(self.AccountBankStatement.search(
  124. [('journal_id', '=', journal.id)]
  125. )),
  126. 2
  127. )
  128. def test_pull_mode_monthly(self):
  129. journal = self.AccountJournal.create({
  130. 'name': 'Bank',
  131. 'type': 'bank',
  132. 'code': 'BANK',
  133. 'bank_statements_source': 'online',
  134. 'online_bank_statement_provider': 'dummy',
  135. })
  136. provider = journal.online_bank_statement_provider_id
  137. provider.active = True
  138. provider.statement_creation_mode = 'monthly'
  139. provider.with_context(step={'hours': 8})._pull(
  140. self.now - relativedelta(months=1),
  141. self.now,
  142. )
  143. self.assertEqual(
  144. len(self.AccountBankStatement.search(
  145. [('journal_id', '=', journal.id)]
  146. )),
  147. 2
  148. )
  149. def test_pull_scheduled(self):
  150. journal = self.AccountJournal.create({
  151. 'name': 'Bank',
  152. 'type': 'bank',
  153. 'code': 'BANK',
  154. 'bank_statements_source': 'online',
  155. 'online_bank_statement_provider': 'dummy',
  156. })
  157. provider = journal.online_bank_statement_provider_id
  158. provider.active = True
  159. provider.next_run = (
  160. self.now - relativedelta(days=15)
  161. )
  162. self.assertFalse(self.AccountBankStatement.search(
  163. [('journal_id', '=', journal.id)],
  164. ))
  165. provider.with_context(step={'hours': 8})._scheduled_pull()
  166. statement = self.AccountBankStatement.search(
  167. [('journal_id', '=', journal.id)],
  168. )
  169. self.assertEqual(len(statement), 1)
  170. def test_pull_skip_duplicates_by_unique_import_id(self):
  171. journal = self.AccountJournal.create({
  172. 'name': 'Bank',
  173. 'type': 'bank',
  174. 'code': 'BANK',
  175. 'bank_statements_source': 'online',
  176. 'online_bank_statement_provider': 'dummy',
  177. })
  178. provider = journal.online_bank_statement_provider_id
  179. provider.active = True
  180. provider.statement_creation_mode = 'weekly'
  181. provider.with_context(step={'hours': 8})._pull(
  182. self.now - relativedelta(weeks=2),
  183. self.now,
  184. )
  185. self.assertEqual(
  186. len(self.AccountBankStatementLine.search(
  187. [('journal_id', '=', journal.id)]
  188. )),
  189. 14 * (24 / 8)
  190. )
  191. provider.with_context(step={'hours': 8})._pull(
  192. self.now - relativedelta(weeks=3),
  193. self.now - relativedelta(weeks=1),
  194. )
  195. self.assertEqual(
  196. len(self.AccountBankStatementLine.search(
  197. [('journal_id', '=', journal.id)]
  198. )),
  199. 21 * (24 / 8)
  200. )
  201. provider.with_context(step={'hours': 8})._pull(
  202. self.now - relativedelta(weeks=1),
  203. self.now,
  204. )
  205. self.assertEqual(
  206. len(self.AccountBankStatementLine.search(
  207. [('journal_id', '=', journal.id)]
  208. )),
  209. 21 * (24 / 8)
  210. )
  211. def test_interval_type_minutes(self):
  212. journal = self.AccountJournal.create({
  213. 'name': 'Bank',
  214. 'type': 'bank',
  215. 'code': 'BANK',
  216. 'bank_statements_source': 'online',
  217. 'online_bank_statement_provider': 'dummy',
  218. })
  219. provider = journal.online_bank_statement_provider_id
  220. provider.active = True
  221. provider.interval_type = 'minutes'
  222. provider._compute_update_schedule()
  223. def test_interval_type_hours(self):
  224. journal = self.AccountJournal.create({
  225. 'name': 'Bank',
  226. 'type': 'bank',
  227. 'code': 'BANK',
  228. 'bank_statements_source': 'online',
  229. 'online_bank_statement_provider': 'dummy',
  230. })
  231. provider = journal.online_bank_statement_provider_id
  232. provider.active = True
  233. provider.interval_type = 'hours'
  234. provider._compute_update_schedule()
  235. def test_interval_type_days(self):
  236. journal = self.AccountJournal.create({
  237. 'name': 'Bank',
  238. 'type': 'bank',
  239. 'code': 'BANK',
  240. 'bank_statements_source': 'online',
  241. 'online_bank_statement_provider': 'dummy',
  242. })
  243. provider = journal.online_bank_statement_provider_id
  244. provider.active = True
  245. provider.interval_type = 'days'
  246. provider._compute_update_schedule()
  247. def test_interval_type_weeks(self):
  248. journal = self.AccountJournal.create({
  249. 'name': 'Bank',
  250. 'type': 'bank',
  251. 'code': 'BANK',
  252. 'bank_statements_source': 'online',
  253. 'online_bank_statement_provider': 'dummy',
  254. })
  255. provider = journal.online_bank_statement_provider_id
  256. provider.active = True
  257. provider.interval_type = 'weeks'
  258. provider._compute_update_schedule()
  259. def test_pull_no_crash(self):
  260. journal = self.AccountJournal.create({
  261. 'name': 'Bank',
  262. 'type': 'bank',
  263. 'code': 'BANK',
  264. 'bank_statements_source': 'online',
  265. 'online_bank_statement_provider': 'dummy',
  266. })
  267. provider = journal.online_bank_statement_provider_id
  268. provider.active = True
  269. provider.statement_creation_mode = 'weekly'
  270. provider.with_context(
  271. crash=True,
  272. scheduled=True,
  273. )._pull(
  274. self.now - relativedelta(hours=1),
  275. self.now,
  276. )
  277. self.assertFalse(self.AccountBankStatement.search(
  278. [('journal_id', '=', journal.id)],
  279. ))
  280. def test_pull_crash(self):
  281. journal = self.AccountJournal.create({
  282. 'name': 'Bank',
  283. 'type': 'bank',
  284. 'code': 'BANK',
  285. 'bank_statements_source': 'online',
  286. 'online_bank_statement_provider': 'dummy',
  287. })
  288. provider = journal.online_bank_statement_provider_id
  289. provider.active = True
  290. provider.statement_creation_mode = 'weekly'
  291. with self.assertRaises(Exception):
  292. provider.with_context(
  293. crash=True,
  294. )._pull(
  295. self.now - relativedelta(hours=1),
  296. self.now,
  297. )
  298. def test_pull_httperror(self):
  299. journal = self.AccountJournal.create({
  300. 'name': 'Bank',
  301. 'type': 'bank',
  302. 'code': 'BANK',
  303. 'bank_statements_source': 'online',
  304. 'online_bank_statement_provider': 'dummy',
  305. })
  306. provider = journal.online_bank_statement_provider_id
  307. provider.active = True
  308. provider.statement_creation_mode = 'weekly'
  309. with self.assertRaises(HTTPError):
  310. provider.with_context(
  311. crash=True,
  312. exception=HTTPError(None, 500, 'Error', None, None),
  313. )._pull(
  314. self.now - relativedelta(hours=1),
  315. self.now,
  316. )
  317. def test_pull_no_balance(self):
  318. journal = self.AccountJournal.create({
  319. 'name': 'Bank',
  320. 'type': 'bank',
  321. 'code': 'BANK',
  322. 'bank_statements_source': 'online',
  323. 'online_bank_statement_provider': 'dummy',
  324. })
  325. provider = journal.online_bank_statement_provider_id
  326. provider.active = True
  327. provider.statement_creation_mode = 'daily'
  328. provider.with_context(
  329. step={'hours': 2},
  330. balance_start=0,
  331. amount=100.0,
  332. balance=False,
  333. )._pull(
  334. self.now - relativedelta(days=1),
  335. self.now,
  336. )
  337. statements = self.AccountBankStatement.search(
  338. [('journal_id', '=', journal.id)],
  339. order='date asc',
  340. )
  341. self.assertFalse(statements[0].balance_start)
  342. self.assertFalse(statements[0].balance_end_real)
  343. self.assertTrue(statements[0].balance_end)
  344. self.assertTrue(statements[1].balance_start)
  345. self.assertFalse(statements[1].balance_end_real)
  346. def test_wizard(self):
  347. journal = self.AccountJournal.create({
  348. 'name': 'Bank',
  349. 'type': 'bank',
  350. 'code': 'BANK',
  351. 'bank_statements_source': 'online',
  352. 'online_bank_statement_provider': 'dummy',
  353. })
  354. action = journal.action_online_bank_statements_pull_wizard()
  355. self.assertTrue(action['context']['default_provider_ids'][0][2])
  356. wizard = self.OnlineBankStatementPullWizard.with_context(
  357. action['context']
  358. ).create({
  359. 'date_since': self.now - relativedelta(hours=1),
  360. 'date_until': self.now,
  361. })
  362. self.assertTrue(wizard.provider_ids)
  363. wizard.action_pull()
  364. self.assertTrue(self.AccountBankStatement.search(
  365. [('journal_id', '=', journal.id)],
  366. ))
  367. def test_pull_statement_partially(self):
  368. journal = self.AccountJournal.create({
  369. 'name': 'Bank',
  370. 'type': 'bank',
  371. 'code': 'BANK',
  372. 'bank_statements_source': 'online',
  373. 'online_bank_statement_provider': 'dummy',
  374. })
  375. provider = journal.online_bank_statement_provider_id
  376. provider.active = True
  377. provider.statement_creation_mode = 'monthly'
  378. provider_context = {
  379. 'step': {'hours': 24},
  380. 'data_since': datetime(2020, 1, 1),
  381. 'data_until': datetime(2020, 2, 29),
  382. 'amount': 1.0,
  383. 'balance_start': 0,
  384. }
  385. provider.with_context(**provider_context)._pull(
  386. datetime(2020, 1, 1),
  387. datetime(2020, 1, 31),
  388. )
  389. statements = self.AccountBankStatement.search(
  390. [('journal_id', '=', journal.id)],
  391. order='date asc',
  392. )
  393. self.assertEqual(len(statements), 1)
  394. self.assertEqual(statements[0].balance_start, 0.0)
  395. self.assertEqual(statements[0].balance_end_real, 30.0)
  396. provider.with_context(**provider_context)._pull(
  397. datetime(2020, 1, 1),
  398. datetime(2020, 2, 15),
  399. )
  400. statements = self.AccountBankStatement.search(
  401. [('journal_id', '=', journal.id)],
  402. order='date asc',
  403. )
  404. self.assertEqual(len(statements), 2)
  405. self.assertEqual(statements[0].balance_start, 0.0)
  406. self.assertEqual(statements[0].balance_end_real, 31.0)
  407. self.assertEqual(statements[1].balance_start, 31.0)
  408. self.assertEqual(statements[1].balance_end_real, 45.0)
  409. provider.with_context(**provider_context)._pull(
  410. datetime(2020, 1, 1),
  411. datetime(2020, 2, 29),
  412. )
  413. statements = self.AccountBankStatement.search(
  414. [('journal_id', '=', journal.id)],
  415. order='date asc',
  416. )
  417. self.assertEqual(len(statements), 2)
  418. self.assertEqual(statements[0].balance_start, 0.0)
  419. self.assertEqual(statements[0].balance_end_real, 31.0)
  420. self.assertEqual(statements[1].balance_start, 31.0)
  421. self.assertEqual(statements[1].balance_end_real, 59.0)
  422. def test_tz_utc(self):
  423. journal = self.AccountJournal.create({
  424. 'name': 'Bank',
  425. 'type': 'bank',
  426. 'code': 'BANK',
  427. 'bank_statements_source': 'online',
  428. 'online_bank_statement_provider': 'dummy',
  429. })
  430. provider = journal.online_bank_statement_provider_id
  431. provider.active = True
  432. provider.tz = 'UTC'
  433. provider.with_context(
  434. step={'hours': 1},
  435. tz='UTC',
  436. )._pull(
  437. datetime(2020, 4, 17, 22, 0),
  438. datetime(2020, 4, 18, 2, 0),
  439. )
  440. statement = self.AccountBankStatement.search(
  441. [('journal_id', '=', journal.id)],
  442. )
  443. self.assertEqual(len(statement), 2)
  444. lines = statement.mapped('line_ids').sorted()
  445. self.assertEqual(len(lines), 4)
  446. self.assertEqual(lines[0].date, date(2020, 4, 17))
  447. self.assertEqual(lines[1].date, date(2020, 4, 17))
  448. self.assertEqual(lines[2].date, date(2020, 4, 18))
  449. self.assertEqual(lines[3].date, date(2020, 4, 18))
  450. def test_tz_non_utc(self):
  451. journal = self.AccountJournal.create({
  452. 'name': 'Bank',
  453. 'type': 'bank',
  454. 'code': 'BANK',
  455. 'bank_statements_source': 'online',
  456. 'online_bank_statement_provider': 'dummy',
  457. })
  458. provider = journal.online_bank_statement_provider_id
  459. provider.active = True
  460. provider.tz = 'Etc/GMT-2'
  461. provider.with_context(
  462. step={'hours': 1},
  463. tz='UTC',
  464. )._pull(
  465. datetime(2020, 4, 17, 22, 0),
  466. datetime(2020, 4, 18, 2, 0),
  467. )
  468. statement = self.AccountBankStatement.search(
  469. [('journal_id', '=', journal.id)],
  470. )
  471. self.assertEqual(len(statement), 2)
  472. lines = statement.mapped('line_ids').sorted()
  473. self.assertEqual(len(lines), 4)
  474. self.assertEqual(lines[0].date, date(2020, 4, 18))
  475. self.assertEqual(lines[1].date, date(2020, 4, 18))
  476. self.assertEqual(lines[2].date, date(2020, 4, 18))
  477. self.assertEqual(lines[3].date, date(2020, 4, 18))
  478. def test_other_tz_to_utc(self):
  479. journal = self.AccountJournal.create({
  480. 'name': 'Bank',
  481. 'type': 'bank',
  482. 'code': 'BANK',
  483. 'bank_statements_source': 'online',
  484. 'online_bank_statement_provider': 'dummy',
  485. })
  486. provider = journal.online_bank_statement_provider_id
  487. provider.active = True
  488. provider.with_context(
  489. step={'hours': 1},
  490. tz='Etc/GMT-2',
  491. data_since=datetime(2020, 4, 18, 0, 0),
  492. data_until=datetime(2020, 4, 18, 4, 0),
  493. )._pull(
  494. datetime(2020, 4, 17, 22, 0),
  495. datetime(2020, 4, 18, 2, 0),
  496. )
  497. statement = self.AccountBankStatement.search(
  498. [('journal_id', '=', journal.id)],
  499. )
  500. self.assertEqual(len(statement), 2)
  501. lines = statement.mapped('line_ids').sorted()
  502. self.assertEqual(len(lines), 4)
  503. self.assertEqual(lines[0].date, date(2020, 4, 17))
  504. self.assertEqual(lines[1].date, date(2020, 4, 17))
  505. self.assertEqual(lines[2].date, date(2020, 4, 18))
  506. self.assertEqual(lines[3].date, date(2020, 4, 18))
  507. def test_timestamp_date_only_date(self):
  508. journal = self.AccountJournal.create({
  509. 'name': 'Bank',
  510. 'type': 'bank',
  511. 'code': 'BANK',
  512. 'bank_statements_source': 'online',
  513. 'online_bank_statement_provider': 'dummy',
  514. })
  515. provider = journal.online_bank_statement_provider_id
  516. provider.active = True
  517. provider.with_context(
  518. step={'hours': 1},
  519. timestamp_mode='date',
  520. )._pull(
  521. datetime(2020, 4, 18, 0, 0),
  522. datetime(2020, 4, 18, 4, 0),
  523. )
  524. statement = self.AccountBankStatement.search(
  525. [('journal_id', '=', journal.id)],
  526. )
  527. self.assertEqual(len(statement), 1)
  528. lines = statement.line_ids
  529. self.assertEqual(len(lines), 24)
  530. for line in lines:
  531. self.assertEqual(line.date, date(2020, 4, 18))
  532. def test_timestamp_date_only_str(self):
  533. journal = self.AccountJournal.create({
  534. 'name': 'Bank',
  535. 'type': 'bank',
  536. 'code': 'BANK',
  537. 'bank_statements_source': 'online',
  538. 'online_bank_statement_provider': 'dummy',
  539. })
  540. provider = journal.online_bank_statement_provider_id
  541. provider.active = True
  542. provider.with_context(
  543. step={'hours': 1},
  544. timestamp_mode='str',
  545. )._pull(
  546. datetime(2020, 4, 18, 0, 0),
  547. datetime(2020, 4, 18, 4, 0),
  548. )
  549. statement = self.AccountBankStatement.search(
  550. [('journal_id', '=', journal.id)],
  551. )
  552. self.assertEqual(len(statement), 1)
  553. lines = statement.line_ids
  554. self.assertEqual(len(lines), 4)
  555. self.assertEqual(lines[0].date, date(2020, 4, 18))
  556. self.assertEqual(lines[1].date, date(2020, 4, 18))
  557. self.assertEqual(lines[2].date, date(2020, 4, 18))
  558. self.assertEqual(lines[3].date, date(2020, 4, 18))