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.

645 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_mode_daily(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.statement_creation_mode = 'daily'
  76. provider.with_context(step={'hours': 2})._pull(
  77. self.now - relativedelta(days=1),
  78. self.now,
  79. )
  80. self.assertEqual(
  81. len(self.AccountBankStatement.search(
  82. [('journal_id', '=', journal.id)]
  83. )),
  84. 2
  85. )
  86. def test_pull_mode_weekly(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 = 'weekly'
  97. provider.with_context(step={'hours': 8})._pull(
  98. self.now - relativedelta(weeks=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_monthly(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 = 'monthly'
  118. provider.with_context(step={'hours': 8})._pull(
  119. self.now - relativedelta(months=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_scheduled(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.next_run = (
  139. self.now - relativedelta(days=15)
  140. )
  141. self.assertFalse(self.AccountBankStatement.search(
  142. [('journal_id', '=', journal.id)],
  143. ))
  144. provider.with_context(step={'hours': 8})._scheduled_pull()
  145. statement = self.AccountBankStatement.search(
  146. [('journal_id', '=', journal.id)],
  147. )
  148. self.assertEqual(len(statement), 1)
  149. def test_pull_skip_duplicates_by_unique_import_id(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.statement_creation_mode = 'weekly'
  160. provider.with_context(
  161. step={'hours': 8},
  162. data_since=self.now - relativedelta(weeks=2),
  163. data_until=self.now,
  164. )._pull(
  165. self.now - relativedelta(weeks=2),
  166. self.now,
  167. )
  168. self.assertEqual(
  169. len(self.AccountBankStatementLine.search(
  170. [('journal_id', '=', journal.id)]
  171. )),
  172. 14 * (24 / 8)
  173. )
  174. provider.with_context(
  175. step={'hours': 8},
  176. data_since=self.now - relativedelta(weeks=3),
  177. data_until=self.now - relativedelta(weeks=1),
  178. )._pull(
  179. self.now - relativedelta(weeks=3),
  180. self.now - relativedelta(weeks=1),
  181. )
  182. self.assertEqual(
  183. len(self.AccountBankStatementLine.search(
  184. [('journal_id', '=', journal.id)]
  185. )),
  186. 21 * (24 / 8)
  187. )
  188. provider.with_context(
  189. step={'hours': 8},
  190. data_since=self.now - relativedelta(weeks=1),
  191. data_until=self.now,
  192. )._pull(
  193. self.now - relativedelta(weeks=1),
  194. self.now,
  195. )
  196. self.assertEqual(
  197. len(self.AccountBankStatementLine.search(
  198. [('journal_id', '=', journal.id)]
  199. )),
  200. 21 * (24 / 8)
  201. )
  202. def test_interval_type_minutes(self):
  203. journal = self.AccountJournal.create({
  204. 'name': 'Bank',
  205. 'type': 'bank',
  206. 'code': 'BANK',
  207. 'bank_statements_source': 'online',
  208. 'online_bank_statement_provider': 'dummy',
  209. })
  210. provider = journal.online_bank_statement_provider_id
  211. provider.active = True
  212. provider.interval_type = 'minutes'
  213. provider._compute_update_schedule()
  214. def test_interval_type_hours(self):
  215. journal = self.AccountJournal.create({
  216. 'name': 'Bank',
  217. 'type': 'bank',
  218. 'code': 'BANK',
  219. 'bank_statements_source': 'online',
  220. 'online_bank_statement_provider': 'dummy',
  221. })
  222. provider = journal.online_bank_statement_provider_id
  223. provider.active = True
  224. provider.interval_type = 'hours'
  225. provider._compute_update_schedule()
  226. def test_interval_type_days(self):
  227. journal = self.AccountJournal.create({
  228. 'name': 'Bank',
  229. 'type': 'bank',
  230. 'code': 'BANK',
  231. 'bank_statements_source': 'online',
  232. 'online_bank_statement_provider': 'dummy',
  233. })
  234. provider = journal.online_bank_statement_provider_id
  235. provider.active = True
  236. provider.interval_type = 'days'
  237. provider._compute_update_schedule()
  238. def test_interval_type_weeks(self):
  239. journal = self.AccountJournal.create({
  240. 'name': 'Bank',
  241. 'type': 'bank',
  242. 'code': 'BANK',
  243. 'bank_statements_source': 'online',
  244. 'online_bank_statement_provider': 'dummy',
  245. })
  246. provider = journal.online_bank_statement_provider_id
  247. provider.active = True
  248. provider.interval_type = 'weeks'
  249. provider._compute_update_schedule()
  250. def test_pull_no_crash(self):
  251. journal = self.AccountJournal.create({
  252. 'name': 'Bank',
  253. 'type': 'bank',
  254. 'code': 'BANK',
  255. 'bank_statements_source': 'online',
  256. 'online_bank_statement_provider': 'dummy',
  257. })
  258. provider = journal.online_bank_statement_provider_id
  259. provider.active = True
  260. provider.statement_creation_mode = 'weekly'
  261. provider.with_context(
  262. crash=True,
  263. scheduled=True,
  264. )._pull(
  265. self.now - relativedelta(hours=1),
  266. self.now,
  267. )
  268. self.assertFalse(self.AccountBankStatement.search(
  269. [('journal_id', '=', journal.id)],
  270. ))
  271. def test_pull_crash(self):
  272. journal = self.AccountJournal.create({
  273. 'name': 'Bank',
  274. 'type': 'bank',
  275. 'code': 'BANK',
  276. 'bank_statements_source': 'online',
  277. 'online_bank_statement_provider': 'dummy',
  278. })
  279. provider = journal.online_bank_statement_provider_id
  280. provider.active = True
  281. provider.statement_creation_mode = 'weekly'
  282. with self.assertRaises(Exception):
  283. provider.with_context(
  284. crash=True,
  285. )._pull(
  286. self.now - relativedelta(hours=1),
  287. self.now,
  288. )
  289. def test_pull_httperror(self):
  290. journal = self.AccountJournal.create({
  291. 'name': 'Bank',
  292. 'type': 'bank',
  293. 'code': 'BANK',
  294. 'bank_statements_source': 'online',
  295. 'online_bank_statement_provider': 'dummy',
  296. })
  297. provider = journal.online_bank_statement_provider_id
  298. provider.active = True
  299. provider.statement_creation_mode = 'weekly'
  300. with self.assertRaises(HTTPError):
  301. provider.with_context(
  302. crash=True,
  303. exception=HTTPError(None, 500, 'Error', None, None),
  304. )._pull(
  305. self.now - relativedelta(hours=1),
  306. self.now,
  307. )
  308. def test_pull_no_balance(self):
  309. journal = self.AccountJournal.create({
  310. 'name': 'Bank',
  311. 'type': 'bank',
  312. 'code': 'BANK',
  313. 'bank_statements_source': 'online',
  314. 'online_bank_statement_provider': 'dummy',
  315. })
  316. provider = journal.online_bank_statement_provider_id
  317. provider.active = True
  318. provider.statement_creation_mode = 'daily'
  319. provider.with_context(
  320. step={'hours': 2},
  321. balance_start=0,
  322. amount=100.0,
  323. balance=False,
  324. )._pull(
  325. self.now - relativedelta(days=1),
  326. self.now,
  327. )
  328. statements = self.AccountBankStatement.search(
  329. [('journal_id', '=', journal.id)],
  330. order='date asc',
  331. )
  332. self.assertFalse(statements[0].balance_start)
  333. self.assertFalse(statements[0].balance_end_real)
  334. self.assertTrue(statements[0].balance_end)
  335. self.assertTrue(statements[1].balance_start)
  336. self.assertFalse(statements[1].balance_end_real)
  337. def test_wizard(self):
  338. journal = self.AccountJournal.create({
  339. 'name': 'Bank',
  340. 'type': 'bank',
  341. 'code': 'BANK',
  342. 'bank_statements_source': 'online',
  343. 'online_bank_statement_provider': 'dummy',
  344. })
  345. action = journal.action_online_bank_statements_pull_wizard()
  346. self.assertTrue(action['context']['default_provider_ids'][0][2])
  347. wizard = self.OnlineBankStatementPullWizard.with_context(
  348. action['context']
  349. ).create({
  350. 'date_since': self.now - relativedelta(hours=1),
  351. 'date_until': self.now,
  352. })
  353. self.assertTrue(wizard.provider_ids)
  354. wizard.action_pull()
  355. self.assertTrue(self.AccountBankStatement.search(
  356. [('journal_id', '=', journal.id)],
  357. ))
  358. def test_pull_statement_partially(self):
  359. journal = self.AccountJournal.create({
  360. 'name': 'Bank',
  361. 'type': 'bank',
  362. 'code': 'BANK',
  363. 'bank_statements_source': 'online',
  364. 'online_bank_statement_provider': 'dummy',
  365. })
  366. provider = journal.online_bank_statement_provider_id
  367. provider.active = True
  368. provider.statement_creation_mode = 'monthly'
  369. provider_context = {
  370. 'step': {'hours': 24},
  371. 'data_since': datetime(2020, 1, 1),
  372. 'amount': 1.0,
  373. 'balance_start': 0,
  374. }
  375. provider.with_context(
  376. **provider_context,
  377. data_until=datetime(2020, 1, 31),
  378. )._pull(
  379. datetime(2020, 1, 1),
  380. datetime(2020, 1, 31),
  381. )
  382. statements = self.AccountBankStatement.search(
  383. [('journal_id', '=', journal.id)],
  384. order='date asc',
  385. )
  386. self.assertEqual(len(statements), 1)
  387. self.assertEqual(statements[0].balance_start, 0.0)
  388. self.assertEqual(statements[0].balance_end_real, 30.0)
  389. provider.with_context(
  390. **provider_context,
  391. data_until=datetime(2020, 2, 15),
  392. )._pull(
  393. datetime(2020, 1, 1),
  394. datetime(2020, 2, 29),
  395. )
  396. statements = self.AccountBankStatement.search(
  397. [('journal_id', '=', journal.id)],
  398. order='date asc',
  399. )
  400. self.assertEqual(len(statements), 2)
  401. self.assertEqual(statements[0].balance_start, 0.0)
  402. self.assertEqual(statements[0].balance_end_real, 31.0)
  403. self.assertEqual(statements[1].balance_start, 31.0)
  404. self.assertEqual(statements[1].balance_end_real, 45.0)
  405. provider.with_context(
  406. **provider_context,
  407. data_until=datetime(2020, 2, 29),
  408. )._pull(
  409. datetime(2020, 1, 1),
  410. datetime(2020, 2, 29),
  411. )
  412. statements = self.AccountBankStatement.search(
  413. [('journal_id', '=', journal.id)],
  414. order='date asc',
  415. )
  416. self.assertEqual(len(statements), 2)
  417. self.assertEqual(statements[0].balance_start, 0.0)
  418. self.assertEqual(statements[0].balance_end_real, 31.0)
  419. self.assertEqual(statements[1].balance_start, 31.0)
  420. self.assertEqual(statements[1].balance_end_real, 59.0)
  421. def test_tz_utc(self):
  422. journal = self.AccountJournal.create({
  423. 'name': 'Bank',
  424. 'type': 'bank',
  425. 'code': 'BANK',
  426. 'bank_statements_source': 'online',
  427. 'online_bank_statement_provider': 'dummy',
  428. })
  429. provider = journal.online_bank_statement_provider_id
  430. provider.active = True
  431. provider.tz = 'UTC'
  432. provider.with_context(
  433. step={'hours': 1},
  434. data_since=datetime(2020, 4, 17, 22, 0),
  435. data_until=datetime(2020, 4, 18, 2, 0),
  436. tz='UTC',
  437. )._pull(
  438. datetime(2020, 4, 17, 22, 0),
  439. datetime(2020, 4, 18, 2, 0),
  440. )
  441. statement = self.AccountBankStatement.search(
  442. [('journal_id', '=', journal.id)],
  443. )
  444. self.assertEqual(len(statement), 2)
  445. lines = statement.mapped('line_ids').sorted()
  446. self.assertEqual(len(lines), 4)
  447. self.assertEqual(lines[0].date, date(2020, 4, 17))
  448. self.assertEqual(lines[1].date, date(2020, 4, 17))
  449. self.assertEqual(lines[2].date, date(2020, 4, 18))
  450. self.assertEqual(lines[3].date, date(2020, 4, 18))
  451. def test_tz_non_utc(self):
  452. journal = self.AccountJournal.create({
  453. 'name': 'Bank',
  454. 'type': 'bank',
  455. 'code': 'BANK',
  456. 'bank_statements_source': 'online',
  457. 'online_bank_statement_provider': 'dummy',
  458. })
  459. provider = journal.online_bank_statement_provider_id
  460. provider.active = True
  461. provider.tz = 'Etc/GMT-2'
  462. provider.with_context(
  463. step={'hours': 1},
  464. data_since=datetime(2020, 4, 17, 22, 0),
  465. data_until=datetime(2020, 4, 18, 2, 0),
  466. tz='UTC',
  467. )._pull(
  468. datetime(2020, 4, 17, 22, 0),
  469. datetime(2020, 4, 18, 2, 0),
  470. )
  471. statement = self.AccountBankStatement.search(
  472. [('journal_id', '=', journal.id)],
  473. )
  474. self.assertEqual(len(statement), 2)
  475. lines = statement.mapped('line_ids').sorted()
  476. self.assertEqual(len(lines), 4)
  477. self.assertEqual(lines[0].date, date(2020, 4, 18))
  478. self.assertEqual(lines[1].date, date(2020, 4, 18))
  479. self.assertEqual(lines[2].date, date(2020, 4, 18))
  480. self.assertEqual(lines[3].date, date(2020, 4, 18))
  481. def test_other_tz_to_utc(self):
  482. journal = self.AccountJournal.create({
  483. 'name': 'Bank',
  484. 'type': 'bank',
  485. 'code': 'BANK',
  486. 'bank_statements_source': 'online',
  487. 'online_bank_statement_provider': 'dummy',
  488. })
  489. provider = journal.online_bank_statement_provider_id
  490. provider.active = True
  491. provider.with_context(
  492. step={'hours': 1},
  493. tz='Etc/GMT-2',
  494. data_since=datetime(2020, 4, 18, 0, 0),
  495. data_until=datetime(2020, 4, 18, 4, 0),
  496. )._pull(
  497. datetime(2020, 4, 17, 22, 0),
  498. datetime(2020, 4, 18, 2, 0),
  499. )
  500. statement = self.AccountBankStatement.search(
  501. [('journal_id', '=', journal.id)],
  502. )
  503. self.assertEqual(len(statement), 2)
  504. lines = statement.mapped('line_ids').sorted()
  505. self.assertEqual(len(lines), 4)
  506. self.assertEqual(lines[0].date, date(2020, 4, 17))
  507. self.assertEqual(lines[1].date, date(2020, 4, 17))
  508. self.assertEqual(lines[2].date, date(2020, 4, 18))
  509. self.assertEqual(lines[3].date, date(2020, 4, 18))
  510. def test_timestamp_date_only_date(self):
  511. journal = self.AccountJournal.create({
  512. 'name': 'Bank',
  513. 'type': 'bank',
  514. 'code': 'BANK',
  515. 'bank_statements_source': 'online',
  516. 'online_bank_statement_provider': 'dummy',
  517. })
  518. provider = journal.online_bank_statement_provider_id
  519. provider.active = True
  520. provider.with_context(
  521. step={'hours': 1},
  522. timestamp_mode='date',
  523. )._pull(
  524. datetime(2020, 4, 18, 0, 0),
  525. datetime(2020, 4, 18, 4, 0),
  526. )
  527. statement = self.AccountBankStatement.search(
  528. [('journal_id', '=', journal.id)],
  529. )
  530. self.assertEqual(len(statement), 1)
  531. lines = statement.line_ids
  532. self.assertEqual(len(lines), 24)
  533. for line in lines:
  534. self.assertEqual(line.date, date(2020, 4, 18))
  535. def test_timestamp_date_only_str(self):
  536. journal = self.AccountJournal.create({
  537. 'name': 'Bank',
  538. 'type': 'bank',
  539. 'code': 'BANK',
  540. 'bank_statements_source': 'online',
  541. 'online_bank_statement_provider': 'dummy',
  542. })
  543. provider = journal.online_bank_statement_provider_id
  544. provider.active = True
  545. provider.with_context(
  546. step={'hours': 1},
  547. data_since=datetime(2020, 4, 18, 0, 0),
  548. data_until=datetime(2020, 4, 18, 4, 0),
  549. timestamp_mode='str',
  550. )._pull(
  551. datetime(2020, 4, 18, 0, 0),
  552. datetime(2020, 4, 18, 4, 0),
  553. )
  554. statement = self.AccountBankStatement.search(
  555. [('journal_id', '=', journal.id)],
  556. )
  557. self.assertEqual(len(statement), 1)
  558. lines = statement.line_ids
  559. self.assertEqual(len(lines), 4)
  560. self.assertEqual(lines[0].date, date(2020, 4, 18))
  561. self.assertEqual(lines[1].date, date(2020, 4, 18))
  562. self.assertEqual(lines[2].date, date(2020, 4, 18))
  563. self.assertEqual(lines[3].date, date(2020, 4, 18))