test_sequence_mixin.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. # -*- coding: utf-8 -*-
  2. from odoo.addons.account.tests.common import AccountTestInvoicingCommon
  3. from odoo.tests import tagged
  4. from odoo.tests.common import Form, TransactionCase
  5. from odoo import fields, api, SUPERUSER_ID, Command
  6. from odoo.exceptions import ValidationError, UserError
  7. from odoo.tools import mute_logger
  8. from dateutil.relativedelta import relativedelta
  9. from freezegun import freeze_time
  10. from functools import reduce
  11. import json
  12. import psycopg2
  13. from unittest.mock import patch
  14. class TestSequenceMixinCommon(AccountTestInvoicingCommon):
  15. @classmethod
  16. def setUpClass(cls, chart_template_ref=None):
  17. super().setUpClass(chart_template_ref=chart_template_ref)
  18. cls.test_move = cls.create_move()
  19. @classmethod
  20. def create_move(cls, date=None, journal=None, name=None, post=False):
  21. move = cls.env['account.move'].create({
  22. 'move_type': 'entry',
  23. 'date': date or '2016-01-01',
  24. 'line_ids': [
  25. (0, None, {
  26. 'name': 'line',
  27. 'account_id': cls.company_data['default_account_revenue'].id,
  28. }),
  29. ]
  30. })
  31. if journal:
  32. move.name = False
  33. move.journal_id = journal
  34. if name:
  35. move.name = name
  36. if post:
  37. move.action_post()
  38. return move
  39. @tagged('post_install', '-at_install')
  40. class TestSequenceMixin(TestSequenceMixinCommon):
  41. def test_sequence_change_date(self):
  42. """Change the sequence when we change the date iff it has never been posted."""
  43. # Check setup
  44. self.assertEqual(self.test_move.state, 'draft')
  45. self.assertEqual(self.test_move.name, 'MISC/2016/01/0001')
  46. self.assertEqual(fields.Date.to_string(self.test_move.date), '2016-01-01')
  47. # Never posetd, the number must change if we change the date
  48. self.test_move.date = '2020-02-02'
  49. self.assertEqual(self.test_move.name, 'MISC/2020/02/0001')
  50. # We don't recompute user's input when posting
  51. self.test_move.name = 'MyMISC/2020/0000001'
  52. self.test_move.action_post()
  53. self.assertEqual(self.test_move.name, 'MyMISC/2020/0000001')
  54. # Has been posted, and it doesn't change anymore
  55. self.test_move.button_draft()
  56. self.test_move.date = '2020-01-02'
  57. self.test_move.action_post()
  58. self.assertEqual(self.test_move.name, 'MyMISC/2020/0000001')
  59. def test_sequence_change_date_with_quick_edit_mode(self):
  60. """
  61. Test the sequence update behavior when changing the date of a move in quick edit mode.
  62. The sequence should only be recalculated if a value (year or month) utilized in the sequence is modified.
  63. """
  64. self.env.company.quick_edit_mode = "out_and_in_invoices"
  65. self.env.company.fiscalyear_last_day = 30
  66. self.env.company.fiscalyear_last_month = '12'
  67. bill = self.env['account.move'].create({
  68. 'partner_id': 1,
  69. 'move_type': 'in_invoice',
  70. 'date': '2016-01-01',
  71. 'line_ids': [
  72. Command.create({
  73. 'name': 'line',
  74. 'account_id': self.company_data['default_account_revenue'].id,
  75. }),
  76. ]
  77. })
  78. bill = bill.copy({'date': '2016-02-01'})
  79. self.assertEqual(bill.name, 'BILL/2016/02/0001')
  80. with Form(bill) as bill_form:
  81. bill_form.date = '2016-02-02'
  82. self.assertEqual(bill_form.name, 'BILL/2016/02/0001')
  83. bill_form.date = '2016-03-01'
  84. self.assertEqual(bill_form.name, 'BILL/2016/03/0001')
  85. bill_form.date = '2017-01-01'
  86. self.assertEqual(bill_form.name, 'BILL/2017/01/0001')
  87. invoice = self.env['account.move'].create({
  88. 'partner_id': 1,
  89. 'move_type': 'out_invoice',
  90. 'date': '2016-01-01',
  91. 'line_ids': [
  92. Command.create({
  93. 'name': 'line',
  94. 'account_id': self.company_data['default_account_revenue'].id,
  95. }),
  96. ]
  97. })
  98. self.assertEqual(invoice.name, 'INV/2016/00001')
  99. with Form(invoice) as invoice_form:
  100. invoice_form.date = '2016-01-02'
  101. self.assertEqual(invoice_form.name, 'INV/2016/00001')
  102. invoice_form.date = '2016-02-02'
  103. self.assertEqual(invoice_form.name, 'INV/2016/00001')
  104. invoice_form.date = '2017-01-01'
  105. self.assertEqual(invoice_form.name, 'INV/2017/00001')
  106. def test_sequence_empty_editable_with_quick_edit_mode(self):
  107. """ Ensure the names of all but the first moves in a period are empty and editable in quick edit mode """
  108. self.env.company.quick_edit_mode = 'in_invoices'
  109. bill_1 = self.env['account.move'].create({
  110. 'partner_id': 1,
  111. 'move_type': 'in_invoice',
  112. 'date': '2016-01-01',
  113. 'line_ids': [
  114. Command.create({
  115. 'name': 'line',
  116. 'account_id': self.company_data['default_account_revenue'].id,
  117. }),
  118. ]
  119. })
  120. # First move in a period gets a name
  121. self.assertEqual(bill_1.name, 'BILL/2016/01/0001')
  122. bill_2 = bill_1.copy({'date': '2016-01-02'})
  123. with Form(bill_2) as bill_2_form:
  124. # Subsequent moves in the same period get an empty editable name in draft mode
  125. self.assertFalse(bill_2_form.name)
  126. bill_2_form.name = 'BILL/2016/01/0002'
  127. self.assertEqual(bill_2_form.name, 'BILL/2016/01/0002')
  128. bill_3 = bill_1.copy({'date': '2016-01-03'})
  129. bill_4 = bill_1.copy({'date': '2016-01-04'})
  130. (bill_3 + bill_4).date = fields.Date.from_string('2016-02-01')
  131. # Same works with updating multiple moves
  132. with Form(bill_3) as bill_3_form:
  133. self.assertEqual(bill_3_form.name, 'BILL/2016/02/0001')
  134. with Form(bill_4) as bill_4_form:
  135. self.assertFalse(bill_4_form.name)
  136. bill_4_form.name = 'BILL/2016/02/0002'
  137. self.assertEqual(bill_4_form.name, 'BILL/2016/02/0002')
  138. def test_sequence_draft_change_date(self):
  139. # When a draft entry is added to an empty period, it should get a name.
  140. # When a draft entry with a name is moved to a period already having entries, its name should be reset to '/'.
  141. new_move = self.test_move.copy({'date': '2016-02-01'})
  142. new_multiple_move_1 = self.test_move.copy({'date': '2016-03-01'})
  143. new_multiple_move_2 = self.test_move.copy({'date': '2016-04-01'})
  144. new_moves = new_multiple_move_1 + new_multiple_move_2
  145. # Empty period, so a name should be set
  146. self.assertEqual(new_move.name, 'MISC/2016/02/0001')
  147. self.assertEqual(new_multiple_move_1.name, 'MISC/2016/03/0001')
  148. self.assertEqual(new_multiple_move_2.name, 'MISC/2016/04/0001')
  149. # Move to an existing period with another move in it
  150. new_move.date = fields.Date.to_date('2016-01-10')
  151. new_moves.date = fields.Date.to_date('2016-01-15')
  152. # Not an empty period, so names should be reset to '/' (draft)
  153. self.assertEqual(new_move.name, '/')
  154. self.assertEqual(new_multiple_move_1.name, '/')
  155. self.assertEqual(new_multiple_move_2.name, '/')
  156. # Move back to a period with no moves in it
  157. new_move.date = fields.Date.to_date('2016-02-01')
  158. new_moves.date = fields.Date.to_date('2016-03-01')
  159. # All moves in the previously empty periods should be given a name instead of `/`
  160. self.assertEqual(new_move.name, 'MISC/2016/02/0001')
  161. self.assertEqual(new_multiple_move_1.name, 'MISC/2016/03/0001')
  162. # Since this is the second one in the same period, it should remain `/`
  163. self.assertEqual(new_multiple_move_2.name, '/')
  164. # Move both moves back to different periods, both with already moves in it.
  165. new_multiple_move_1.date = fields.Date.to_date('2016-01-10')
  166. new_multiple_move_2.date = fields.Date.to_date('2016-02-10')
  167. # Moves are not in empty periods, so names should be set to '/' (draft)
  168. self.assertEqual(new_multiple_move_1.name, '/')
  169. self.assertEqual(new_multiple_move_2.name, '/')
  170. # Change the journal of the last two moves (empty)
  171. journal = self.env['account.journal'].create({
  172. 'name': 'awesome journal',
  173. 'type': 'general',
  174. 'code': 'AJ',
  175. })
  176. new_moves.journal_id = journal
  177. # Both moves should be assigned a name, since no moves are in the journal and they are in different periods.
  178. self.assertEqual(new_multiple_move_1.name, 'AJ/2016/01/0001')
  179. self.assertEqual(new_multiple_move_2.name, 'AJ/2016/02/0001')
  180. # When the date is removed in the form view, the name should not recompute
  181. with Form(new_multiple_move_1) as move_form:
  182. move_form.date = False
  183. self.assertEqual(new_multiple_move_1.name, 'AJ/2016/01/0001')
  184. move_form.date = fields.Date.to_date('2016-01-10')
  185. def test_journal_sequence(self):
  186. self.assertEqual(self.test_move.name, 'MISC/2016/01/0001')
  187. self.test_move.action_post()
  188. self.assertEqual(self.test_move.name, 'MISC/2016/01/0001')
  189. copy1 = self.create_move(date=self.test_move.date)
  190. self.assertEqual(copy1.name, '/')
  191. copy1.action_post()
  192. self.assertEqual(copy1.name, 'MISC/2016/01/0002')
  193. copy2 = self.create_move(date=self.test_move.date)
  194. new_journal = self.test_move.journal_id.copy()
  195. new_journal.code = "MISC2"
  196. copy2.journal_id = new_journal
  197. self.assertEqual(copy2.name, 'MISC2/2016/01/0001')
  198. with Form(copy2) as move_form: # It is editable in the form
  199. with mute_logger('odoo.tests.common.onchange'):
  200. move_form.name = 'MyMISC/2016/0001'
  201. self.assertIn(
  202. 'The sequence will restart at 1 at the start of every year',
  203. move_form._perform_onchange(['name'])['warning']['message'],
  204. )
  205. move_form.journal_id = self.test_move.journal_id
  206. self.assertEqual(move_form.name, '/')
  207. move_form.journal_id = new_journal
  208. self.assertEqual(move_form.name, 'MISC2/2016/01/0001')
  209. with mute_logger('odoo.tests.common.onchange'):
  210. move_form.name = 'MyMISC/2016/0001'
  211. self.assertIn(
  212. 'The sequence will restart at 1 at the start of every year',
  213. move_form._perform_onchange(['name'])['warning']['message'],
  214. )
  215. copy2.action_post()
  216. self.assertEqual(copy2.name, 'MyMISC/2016/0001')
  217. copy3 = self.create_move(date=copy2.date, journal=new_journal)
  218. self.assertEqual(copy3.name, '/')
  219. with self.assertRaises(AssertionError):
  220. with Form(copy2) as move_form: # It is not editable in the form
  221. move_form.name = 'MyMISC/2016/0002'
  222. copy3.action_post()
  223. self.assertEqual(copy3.name, 'MyMISC/2016/0002')
  224. copy3.name = 'MISC2/2016/00002'
  225. copy4 = self.create_move(date=copy2.date, journal=new_journal)
  226. copy4.action_post()
  227. self.assertEqual(copy4.name, 'MISC2/2016/00003')
  228. copy5 = self.create_move(date=copy2.date, journal=new_journal)
  229. copy5.date = '2021-02-02'
  230. copy5.action_post()
  231. self.assertEqual(copy5.name, 'MISC2/2021/00001')
  232. copy5.name = 'N\'importe quoi?'
  233. copy6 = self.create_move(date=copy5.date, journal=new_journal)
  234. copy6.action_post()
  235. self.assertEqual(copy6.name, 'N\'importe quoi?1')
  236. def test_journal_sequence_format(self):
  237. """Test different format of sequences and what it becomes on another period"""
  238. sequences = [
  239. ('JRNL/2016/00001', 'JRNL/2016/00002', 'JRNL/2016/00003', 'JRNL/2017/00001'),
  240. ('1234567', '1234568', '1234569', '1234570'),
  241. ('20190910', '20190911', '20190912', '20190913'),
  242. ('2016-0910', '2016-0911', '2016-0912', '2017-0001'),
  243. ('201603-10', '201603-11', '201604-01', '201703-01'),
  244. ('16-03-10', '16-03-11', '16-04-01', '17-03-01'),
  245. ('2016-10', '2016-11', '2016-12', '2017-01'),
  246. ('045-001-000002', '045-001-000003', '045-001-000004', '045-001-000005'),
  247. ('JRNL/2016/00001suffix', 'JRNL/2016/00002suffix', 'JRNL/2016/00003suffix', 'JRNL/2017/00001suffix'),
  248. ]
  249. init_move = self.create_move(date='2016-03-12')
  250. next_move = self.create_move(date='2016-03-12')
  251. next_move_month = self.create_move(date='2016-04-12')
  252. next_move_year = self.create_move(date='2017-03-12')
  253. next_moves = (next_move + next_move_month + next_move_year)
  254. next_moves.action_post()
  255. for sequence_init, sequence_next, sequence_next_month, sequence_next_year in sequences:
  256. init_move.name = sequence_init
  257. next_moves.name = False
  258. next_moves._compute_name()
  259. self.assertEqual(
  260. [next_move.name, next_move_month.name, next_move_year.name],
  261. [sequence_next, sequence_next_month, sequence_next_year],
  262. )
  263. def test_journal_next_sequence(self):
  264. """Sequences behave correctly even when there is not enough padding."""
  265. prefix = "TEST_ORDER/2016/"
  266. self.test_move.name = f"{prefix}1"
  267. for c in range(2, 25):
  268. copy = self.create_move(date=self.test_move.date)
  269. copy.name = "/"
  270. copy.action_post()
  271. self.assertEqual(copy.name, f"{prefix}{c}")
  272. def test_journal_sequence_multiple_type(self):
  273. """Domain is computed accordingly to different types."""
  274. entry, entry2, invoice, invoice2, refund, refund2 = (
  275. self.create_move(date='2016-01-01')
  276. for i in range(6)
  277. )
  278. (invoice + invoice2 + refund + refund2).write({
  279. 'journal_id': self.company_data['default_journal_sale'].id,
  280. 'partner_id': 1,
  281. 'invoice_date': '2016-01-01',
  282. })
  283. (invoice + invoice2).move_type = 'out_invoice'
  284. (refund + refund2).move_type = 'out_refund'
  285. all_moves = (entry + entry2 + invoice + invoice2 + refund + refund2)
  286. all_moves.name = False
  287. all_moves.action_post()
  288. self.assertEqual(entry.name, 'MISC/2016/01/0002')
  289. self.assertEqual(entry2.name, 'MISC/2016/01/0003')
  290. self.assertEqual(invoice.name, 'INV/2016/00001')
  291. self.assertEqual(invoice2.name, 'INV/2016/00002')
  292. self.assertEqual(refund.name, 'RINV/2016/00001')
  293. self.assertEqual(refund2.name, 'RINV/2016/00002')
  294. def test_journal_sequence_groupby_compute(self):
  295. """The grouping optimization is correctly done."""
  296. # Setup two journals with a sequence that resets yearly
  297. journals = self.env['account.journal'].create([{
  298. 'name': f'Journal{i}',
  299. 'code': f'J{i}',
  300. 'type': 'general',
  301. } for i in range(2)])
  302. account = self.env['account.account'].search([], limit=1)
  303. moves = self.env['account.move'].create([{
  304. 'journal_id': journals[i].id,
  305. 'line_ids': [(0, 0, {'account_id': account.id, 'name': 'line'})],
  306. 'date': '2010-01-01',
  307. } for i in range(2)])._post()
  308. for i in range(2):
  309. moves[i].name = f'J{i}/2010/00001'
  310. # Check that the moves are correctly batched
  311. moves = self.env['account.move'].create([{
  312. 'journal_id': journals[journal_index].id,
  313. 'line_ids': [(0, 0, {'account_id': account.id, 'name': 'line'})],
  314. 'date': f'2010-{month}-01',
  315. } for journal_index, month in [(1, 1), (0, 1), (1, 2), (1, 1)]])._post()
  316. self.assertEqual(
  317. moves.mapped('name'),
  318. ['J1/2010/00002', 'J0/2010/00002', 'J1/2010/00004', 'J1/2010/00003'],
  319. )
  320. journals[0].code = 'OLD'
  321. journals.flush_recordset()
  322. journal_same_code = self.env['account.journal'].create([{
  323. 'name': 'Journal0',
  324. 'code': 'J0',
  325. 'type': 'general',
  326. }])
  327. moves = (
  328. self.create_move(date='2010-01-01', journal=journal_same_code, name='J0/2010/00001')
  329. + self.create_move(date='2010-01-01', journal=journal_same_code)
  330. + self.create_move(date='2010-01-01', journal=journal_same_code)
  331. + self.create_move(date='2010-01-01', journal=journals[0])
  332. )._post()
  333. self.assertEqual(
  334. moves.mapped('name'),
  335. ['J0/2010/00001', 'J0/2010/00002', 'J0/2010/00003', 'J0/2010/00003'],
  336. )
  337. def test_journal_override_sequence_regex(self):
  338. """There is a possibility to override the regex and change the order of the paramters."""
  339. self.create_move(date='2020-01-01', name='00000876-G 0002/2020')
  340. next_move = self.create_move(date='2020-01-01')
  341. next_move.action_post()
  342. self.assertEqual(next_move.name, '00000876-G 0002/2021') # Wait, I didn't want this!
  343. next_move.button_draft()
  344. next_move.name = False
  345. next_move.journal_id.sequence_override_regex = r'^(?P<seq>\d*)(?P<suffix1>.*?)(?P<year>(\d{4})?)(?P<suffix2>)$'
  346. next_move.action_post()
  347. self.assertEqual(next_move.name, '00000877-G 0002/2020') # Pfew, better!
  348. next_move = self.create_move(date='2020-01-01')
  349. next_move.action_post()
  350. self.assertEqual(next_move.name, '00000878-G 0002/2020')
  351. next_move = self.create_move(date='2017-05-02')
  352. next_move.action_post()
  353. self.assertEqual(next_move.name, '00000001-G 0002/2017')
  354. def test_journal_sequence_ordering(self):
  355. """Entries are correctly sorted when posting multiple at once."""
  356. self.test_move.name = 'XMISC/2016/00001'
  357. copies = reduce((lambda x, y: x+y), [
  358. self.create_move(date=self.test_move.date)
  359. for i in range(6)
  360. ])
  361. copies[0].date = '2019-03-05'
  362. copies[1].date = '2019-03-06'
  363. copies[2].date = '2019-03-07'
  364. copies[3].date = '2019-03-04'
  365. copies[4].date = '2019-03-05'
  366. copies[5].date = '2019-03-05'
  367. # that entry is actualy the first one of the period, so it already has a name
  368. # set it to '/' so that it is recomputed at post to be ordered correctly.
  369. copies[0].name = '/'
  370. copies.action_post()
  371. # Ordered by date
  372. self.assertEqual(copies[0].name, 'XMISC/2019/00002')
  373. self.assertEqual(copies[1].name, 'XMISC/2019/00005')
  374. self.assertEqual(copies[2].name, 'XMISC/2019/00006')
  375. self.assertEqual(copies[3].name, 'XMISC/2019/00001')
  376. self.assertEqual(copies[4].name, 'XMISC/2019/00003')
  377. self.assertEqual(copies[5].name, 'XMISC/2019/00004')
  378. # Can't have twice the same name
  379. with self.assertRaises(psycopg2.DatabaseError), mute_logger('odoo.sql_db'), self.env.cr.savepoint():
  380. copies[0].name = 'XMISC/2019/00001'
  381. # Lets remove the order by date
  382. copies[0].name = 'XMISC/2019/10001'
  383. copies[1].name = 'XMISC/2019/10002'
  384. copies[2].name = 'XMISC/2019/10003'
  385. copies[3].name = 'XMISC/2019/10004'
  386. copies[4].name = 'XMISC/2019/10005'
  387. copies[5].name = 'XMISC/2019/10006'
  388. copies[4].button_draft()
  389. copies[4].with_context(force_delete=True).unlink()
  390. copies[5].button_draft()
  391. wizard = Form(self.env['account.resequence.wizard'].with_context(
  392. active_ids=set(copies.ids) - set(copies[4].ids),
  393. active_model='account.move'),
  394. )
  395. new_values = json.loads(wizard.new_values)
  396. self.assertEqual(new_values[str(copies[0].id)]['new_by_date'], 'XMISC/2019/10002')
  397. self.assertEqual(new_values[str(copies[0].id)]['new_by_name'], 'XMISC/2019/10001')
  398. self.assertEqual(new_values[str(copies[1].id)]['new_by_date'], 'XMISC/2019/10004')
  399. self.assertEqual(new_values[str(copies[1].id)]['new_by_name'], 'XMISC/2019/10002')
  400. self.assertEqual(new_values[str(copies[2].id)]['new_by_date'], 'XMISC/2019/10005')
  401. self.assertEqual(new_values[str(copies[2].id)]['new_by_name'], 'XMISC/2019/10003')
  402. self.assertEqual(new_values[str(copies[3].id)]['new_by_date'], 'XMISC/2019/10001')
  403. self.assertEqual(new_values[str(copies[3].id)]['new_by_name'], 'XMISC/2019/10004')
  404. self.assertEqual(new_values[str(copies[5].id)]['new_by_date'], 'XMISC/2019/10003')
  405. self.assertEqual(new_values[str(copies[5].id)]['new_by_name'], 'XMISC/2019/10005')
  406. wizard.save().resequence()
  407. self.assertEqual(copies[3].state, 'posted')
  408. self.assertEqual(copies[5].name, 'XMISC/2019/10005')
  409. self.assertEqual(copies[5].state, 'draft')
  410. def test_sequence_get_more_specific(self):
  411. """There is the ability to change the format (i.e. from yearly to montlhy)."""
  412. def test_date(date, name):
  413. test = self.create_move(date=date)
  414. test.action_post()
  415. self.assertEqual(test.name, name)
  416. def set_sequence(date, name):
  417. return self.create_move(date=date, name=name)._post()
  418. # Start with a continuous sequence
  419. self.test_move.name = 'MISC/00001'
  420. # Change the prefix to reset every year starting in 2017
  421. new_year = set_sequence(self.test_move.date + relativedelta(years=1), 'MISC/2017/00001')
  422. # Change the prefix to reset every month starting in February 2017
  423. new_month = set_sequence(new_year.date + relativedelta(months=1), 'MISC/2017/02/00001')
  424. test_date(self.test_move.date, 'MISC/00002') # Keep the old prefix in 2016
  425. test_date(new_year.date, 'MISC/2017/00002') # Keep the new prefix in 2017
  426. test_date(new_month.date, 'MISC/2017/02/00002') # Keep the new prefix in February 2017
  427. # Change the prefix to never reset (again) year starting in 2018 (Please don't do that)
  428. reset_never = set_sequence(self.test_move.date + relativedelta(years=2), 'MISC/00100')
  429. test_date(reset_never.date, 'MISC/00101') # Keep the new prefix in 2018
  430. def test_resequence_clash(self):
  431. """Resequence doesn't clash when it uses a name set in the same batch
  432. but that will be overriden later."""
  433. moves = self.env['account.move']
  434. for i in range(3):
  435. moves += self.create_move(name=str(i))
  436. moves.action_post()
  437. mistake = moves[1]
  438. mistake.button_draft()
  439. mistake.posted_before = False
  440. mistake.with_context(force_delete=True).unlink()
  441. moves -= mistake
  442. self.env['account.resequence.wizard'].create({
  443. 'move_ids': moves.ids,
  444. 'first_name': '2',
  445. }).resequence()
  446. @freeze_time('2021-10-01 00:00:00')
  447. def test_change_journal_on_first_account_move(self):
  448. """Changing the journal on the first move is allowed"""
  449. journal = self.env['account.journal'].create({
  450. 'name': 'awesome journal',
  451. 'type': 'general',
  452. 'code': 'AJ',
  453. })
  454. move = self.env['account.move'].create({})
  455. self.assertEqual(move.name, 'MISC/2021/10/0001')
  456. with Form(move) as move_form:
  457. move_form.journal_id = journal
  458. self.assertEqual(move.name, 'AJ/2021/10/0001')
  459. def test_sequence_move_name_related_field_well_computed(self):
  460. AccountMove = type(self.env['account.move'])
  461. _compute_name = AccountMove._compute_name
  462. def _flushing_compute_name(self):
  463. self.env['account.move.line'].flush_model(fnames=['move_name'])
  464. _compute_name(self)
  465. payments = self.env['account.payment'].create([{
  466. 'payment_type': 'inbound',
  467. 'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
  468. 'partner_type': 'customer',
  469. 'partner_id': self.partner_a.id,
  470. 'amount': 500,
  471. }] * 2)
  472. with patch.object(AccountMove, '_compute_name', _flushing_compute_name):
  473. payments.action_post()
  474. for move in payments.move_id:
  475. self.assertRecordValues(move.line_ids, [{'move_name': move.name}] * len(move.line_ids))
  476. @tagged('post_install', '-at_install')
  477. class TestSequenceMixinDeletion(TestSequenceMixinCommon):
  478. @classmethod
  479. def setUpClass(cls, chart_template_ref=None):
  480. super().setUpClass(chart_template_ref=chart_template_ref)
  481. journal = cls.env['account.journal'].create({
  482. 'name': 'Test sequences - deletion',
  483. 'code': 'SEQDEL',
  484. 'type': 'general',
  485. })
  486. cls.move_1_1 = cls.create_move('2021-01-01', journal, name='TOTO/2021/01/0001', post=True)
  487. cls.move_1_2 = cls.create_move('2021-01-02', journal, post=True)
  488. cls.move_1_3 = cls.create_move('2021-01-03', journal, post=True)
  489. cls.move_2_1 = cls.create_move('2021-02-01', journal, post=True)
  490. cls.move_draft = cls.create_move('2021-02-02', journal, post=False)
  491. cls.move_2_2 = cls.create_move('2021-02-03', journal, post=True)
  492. cls.move_3_1 = cls.create_move('2021-02-10', journal, name='TURLUTUTU/21/02/001', post=True)
  493. def test_sequence_deletion_1(self):
  494. """The last element of a sequence chain should always be deletable if in draft state.
  495. Trying to delete another part of the chain shouldn't work.
  496. """
  497. # A draft move without any name can always be deleted.
  498. self.move_draft.unlink()
  499. # The last element of each sequence chain should allow deletion.
  500. # Everything should be deletable if we follow this order (a bit randomized on purpose)
  501. for move in (self.move_1_3, self.move_1_2, self.move_3_1, self.move_2_2, self.move_2_1, self.move_1_1):
  502. move.button_draft()
  503. move.unlink()
  504. def test_sequence_deletion_2(self):
  505. """Can delete in batch."""
  506. all_moves = (self.move_1_3 + self.move_1_2 + self.move_3_1 + self.move_2_2 + self.move_2_1 + self.move_1_1)
  507. all_moves.button_draft()
  508. all_moves.unlink()
  509. @tagged('post_install', '-at_install')
  510. class TestSequenceMixinConcurrency(TransactionCase):
  511. def setUp(self):
  512. super().setUp()
  513. with self.env.registry.cursor() as cr:
  514. env = api.Environment(cr, SUPERUSER_ID, {})
  515. journal = env['account.journal'].create({
  516. 'name': 'concurency_test',
  517. 'code': 'CT',
  518. 'type': 'general',
  519. })
  520. account = env['account.account'].create({
  521. 'code': 'CT',
  522. 'name': 'CT',
  523. 'account_type': 'asset_fixed',
  524. })
  525. moves = env['account.move'].create([{
  526. 'journal_id': journal.id,
  527. 'date': fields.Date.from_string('2016-01-01'),
  528. 'line_ids': [(0, 0, {'name': 'name', 'account_id': account.id})]
  529. }] * 3)
  530. moves.name = '/'
  531. moves[0].action_post()
  532. self.assertEqual(moves.mapped('name'), ['CT/2016/01/0001', '/', '/'])
  533. env.cr.commit()
  534. self.data = {
  535. 'move_ids': moves.ids,
  536. 'account_id': account.id,
  537. 'journal_id': journal.id,
  538. 'envs': [
  539. api.Environment(self.env.registry.cursor(), SUPERUSER_ID, {}),
  540. api.Environment(self.env.registry.cursor(), SUPERUSER_ID, {}),
  541. api.Environment(self.env.registry.cursor(), SUPERUSER_ID, {}),
  542. ],
  543. }
  544. self.addCleanup(self.cleanUp)
  545. def cleanUp(self):
  546. with self.env.registry.cursor() as cr:
  547. env = api.Environment(cr, SUPERUSER_ID, {})
  548. moves = env['account.move'].browse(self.data['move_ids'])
  549. moves.button_draft()
  550. moves.posted_before = False
  551. moves.unlink()
  552. journal = env['account.journal'].browse(self.data['journal_id'])
  553. journal.unlink()
  554. account = env['account.account'].browse(self.data['account_id'])
  555. account.unlink()
  556. env.cr.commit()
  557. for env in self.data['envs']:
  558. env.cr.close()
  559. def test_sequence_concurency(self):
  560. """Computing the same name in concurent transactions is not allowed."""
  561. env0, env1, env2 = self.data['envs']
  562. # start the transactions here on cr1 to simulate concurency with cr2
  563. env1.cr.execute('SELECT 1')
  564. # post in cr2
  565. move = env2['account.move'].browse(self.data['move_ids'][1])
  566. move.action_post()
  567. env2.cr.commit()
  568. # try to post in cr1, the retry sould find the right number
  569. move = env1['account.move'].browse(self.data['move_ids'][2])
  570. move.action_post()
  571. env1.cr.commit()
  572. # check the values
  573. moves = env0['account.move'].browse(self.data['move_ids'])
  574. self.assertEqual(moves.mapped('name'), ['CT/2016/01/0001', 'CT/2016/01/0002', 'CT/2016/01/0003'])
  575. def test_sequence_concurency_no_useless_lock(self):
  576. """Do not lock needlessly when the sequence is not computed"""
  577. env0, env1, env2 = self.data['envs']
  578. # start the transactions here on cr1 to simulate concurency with cr2
  579. env1.cr.execute('SELECT 1')
  580. # get the last sequence in cr1 (for instance opening a form view)
  581. move = env2['account.move'].browse(self.data['move_ids'][1])
  582. move.highest_name
  583. env2.cr.commit()
  584. # post in cr1, should work even though cr2 read values
  585. move = env1['account.move'].browse(self.data['move_ids'][2])
  586. move.action_post()
  587. env1.cr.commit()
  588. # check the values
  589. moves = env0['account.move'].browse(self.data['move_ids'])
  590. self.assertEqual(moves.mapped('name'), ['CT/2016/01/0001', '/', 'CT/2016/01/0002'])