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.

506 lines
19 KiB

  1. # Copyright 2019 - Today Coop IT Easy SCRLfs (<http://www.coopiteasy.be>)
  2. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
  3. import time
  4. from datetime import datetime, timedelta
  5. from odoo import exceptions, fields
  6. from odoo.exceptions import UserError, ValidationError
  7. from odoo.tests.common import TransactionCase
  8. class TestBeesdooShift(TransactionCase):
  9. def setUp(self):
  10. super(TestBeesdooShift, self).setUp()
  11. self.shift_model = self.env["beesdoo.shift.shift"]
  12. self.shift_template_model = self.env["beesdoo.shift.template"]
  13. self.attendance_sheet_model = self.env["beesdoo.shift.sheet"]
  14. self.attendance_sheet_shift_model = self.env[
  15. "beesdoo.shift.sheet.shift"
  16. ]
  17. self.shift_expected_model = self.env["beesdoo.shift.sheet.expected"]
  18. self.shift_added_model = self.env["beesdoo.shift.sheet.added"]
  19. self.default_task_type_id = self.env["ir.config_parameter"].get_param(
  20. "beesdoo_shift.default_task_type_id"
  21. )
  22. self.current_time = datetime.now()
  23. self.user_admin = self.env.ref("base.user_root")
  24. self.user_generic = self.env.ref(
  25. "beesdoo_base.beesdoo_shift_user_1_demo"
  26. )
  27. self.user_permanent = self.env.ref(
  28. "beesdoo_base.beesdoo_shift_user_2_demo"
  29. )
  30. self.setting_wizard = self.env["beesdoo.shift.config.settings"].sudo(
  31. self.user_admin
  32. )
  33. self.worker_regular_1 = self.env.ref(
  34. "beesdoo_base.res_partner_cooperator_6_demo"
  35. )
  36. self.worker_regular_2 = self.env.ref(
  37. "beesdoo_base.res_partner_cooperator_5_demo"
  38. )
  39. self.worker_regular_3 = self.env.ref(
  40. "beesdoo_base.res_partner_cooperator_3_demo"
  41. )
  42. self.worker_regular_super_1 = self.env.ref(
  43. "beesdoo_base.res_partner_cooperator_1_demo"
  44. )
  45. self.worker_irregular_1 = self.env.ref(
  46. "beesdoo_base.res_partner_cooperator_2_demo"
  47. )
  48. self.worker_irregular_2 = self.env.ref(
  49. "beesdoo_base.res_partner_cooperator_4_demo"
  50. )
  51. self.task_type_1 = self.env.ref(
  52. "beesdoo_shift.beesdoo_shift_task_type_1_demo"
  53. )
  54. self.task_type_2 = self.env.ref(
  55. "beesdoo_shift.beesdoo_shift_task_type_2_demo"
  56. )
  57. self.task_type_3 = self.env.ref(
  58. "beesdoo_shift.beesdoo_shift_task_type_3_demo"
  59. )
  60. self.task_template_1 = self.env.ref(
  61. "beesdoo_shift.beesdoo_shift_task_template_1_demo"
  62. )
  63. self.task_template_2 = self.env.ref(
  64. "beesdoo_shift.beesdoo_shift_task_template_2_demo"
  65. )
  66. # Set time in and out of generation interval parameter
  67. self.start_in_1 = self.current_time + timedelta(seconds=2)
  68. self.end_in_1 = self.current_time + timedelta(minutes=10)
  69. self.start_in_2 = self.current_time + timedelta(minutes=9)
  70. self.end_in_2 = self.current_time + timedelta(minutes=21)
  71. self.start_out_1 = self.current_time - timedelta(minutes=50)
  72. self.end_out_1 = self.current_time - timedelta(minutes=20)
  73. self.start_out_2 = self.current_time + timedelta(minutes=40)
  74. self.end_out_2 = self.current_time + timedelta(minutes=50)
  75. self.shift_regular_regular_1 = self.shift_model.create(
  76. {
  77. "task_template_id": self.task_template_1.id,
  78. "task_type_id": self.task_type_1.id,
  79. "worker_id": self.worker_regular_1.id,
  80. "start_time": self.start_in_1,
  81. "end_time": self.end_in_1,
  82. "is_regular": True,
  83. "is_compensation": False,
  84. }
  85. )
  86. self.shift_regular_regular_2 = self.shift_model.create(
  87. {
  88. "task_type_id": self.task_type_2.id,
  89. "worker_id": self.worker_regular_2.id,
  90. "start_time": self.start_out_1,
  91. "end_time": self.end_out_1,
  92. "is_regular": True,
  93. "is_compensation": False,
  94. }
  95. )
  96. self.shift_regular_regular_replaced_1 = self.shift_model.create(
  97. {
  98. "task_template_id": self.task_template_1.id,
  99. "task_type_id": self.task_type_3.id,
  100. "worker_id": self.worker_regular_3.id,
  101. "start_time": self.start_in_1,
  102. "end_time": self.end_in_1,
  103. "is_regular": True,
  104. "is_compensation": False,
  105. "replaced_id": self.worker_regular_2.id,
  106. }
  107. )
  108. future_shift_regular = self.shift_model.create(
  109. {
  110. "task_template_id": self.task_template_2.id,
  111. "task_type_id": self.task_type_1.id,
  112. "worker_id": self.worker_regular_super_1.id,
  113. "start_time": self.start_in_2,
  114. "end_time": self.end_in_2,
  115. "is_regular": False,
  116. "is_compensation": True,
  117. }
  118. )
  119. self.shift_irregular_1 = self.shift_model.create(
  120. {
  121. "task_template_id": self.task_template_1.id,
  122. "task_type_id": self.task_type_2.id,
  123. "worker_id": self.worker_irregular_1.id,
  124. "start_time": self.start_in_1,
  125. "end_time": self.end_in_1,
  126. }
  127. )
  128. self.shift_irregular_2 = self.shift_model.create(
  129. {
  130. "task_type_id": self.task_type_3.id,
  131. "worker_id": self.worker_irregular_2.id,
  132. "start_time": self.start_out_2,
  133. "end_time": self.end_out_2,
  134. }
  135. )
  136. self.shift_empty_1 = self.shift_model.create(
  137. {
  138. "task_template_id": self.task_template_1.id,
  139. "task_type_id": self.task_type_1.id,
  140. "start_time": self.start_in_1,
  141. "end_time": self.end_in_1,
  142. }
  143. )
  144. def search_sheets(self, start_time, end_time):
  145. if (type(start_time) and type(end_time)) == datetime:
  146. start_time = fields.Datetime.to_string(start_time)
  147. end_time = fields.Datetime.to_string(end_time)
  148. return self.attendance_sheet_model.search(
  149. [("start_time", "=", start_time), ("end_time", "=", end_time)]
  150. )
  151. def test_default_task_type_setting(self):
  152. "Test default task type setting"
  153. for task_type in (self.task_type_1, self.task_type_2):
  154. # Setting default value
  155. setting_wizard_1 = self.setting_wizard.create(
  156. {"default_task_type_id": task_type.id}
  157. )
  158. setting_wizard_1.execute()
  159. param_id = self.env["ir.config_parameter"].get_param(
  160. "beesdoo_shift.default_task_type_id"
  161. )
  162. self.assertEqual(int(param_id), task_type.id)
  163. # Check propagation on attendance sheet shifts
  164. self.assertEqual(
  165. self.attendance_sheet_shift_model.default_task_type_id(),
  166. task_type,
  167. )
  168. def test_attendance_sheet_creation(self):
  169. "Test creation of an attendance sheet with all its expected shifts"
  170. # Set generation interval setting
  171. setting_wizard_1 = self.setting_wizard.create(
  172. {
  173. "default_task_type_id": self.task_type_1.id,
  174. "attendance_sheet_generation_interval": 15,
  175. }
  176. )
  177. setting_wizard_1.execute()
  178. # Test attendance sheets creation
  179. self.attendance_sheet_model._generate_attendance_sheet()
  180. self.assertEqual(
  181. len(self.search_sheets(self.start_in_1, self.end_in_1)), 1
  182. )
  183. self.assertEqual(
  184. len(self.search_sheets(self.start_in_2, self.end_in_2)), 1
  185. )
  186. self.assertEqual(
  187. len(self.search_sheets(self.start_out_1, self.end_out_1)), 0
  188. )
  189. self.assertEqual(
  190. len(self.search_sheets(self.start_out_2, self.end_out_2)), 0
  191. )
  192. # Test expected shifts creation
  193. # Sheet 1 starts at current time + 2 secs, ends at current time + 10 min
  194. # Sheet 2 starts at current time + 9 min, ends at current time + 21 min
  195. sheet_1 = self.search_sheets(self.start_in_1, self.end_in_1)
  196. sheet_2 = self.search_sheets(self.start_in_2, self.end_in_2)
  197. self.assertTrue(sheet_1.start_time)
  198. self.assertTrue(sheet_1.end_time)
  199. # Empty shift should not be added
  200. self.assertEqual(len(sheet_1.expected_shift_ids), 3)
  201. self.assertEqual(len(sheet_1.added_shift_ids), 0)
  202. self.assertEqual(len(sheet_2.expected_shift_ids), 1)
  203. # Test consistency with actual shift for sheet 1
  204. for shift in sheet_1.expected_shift_ids:
  205. self.assertEqual(shift.worker_id, shift.task_id.worker_id)
  206. self.assertEqual(
  207. shift.replacement_worker_id, shift.task_id.replaced_id
  208. )
  209. self.assertEqual(shift.task_type_id, shift.task_id.task_type_id)
  210. self.assertEqual(shift.super_coop_id, shift.task_id.super_coop_id)
  211. self.assertEqual(shift.working_mode, shift.task_id.working_mode)
  212. # Status should be "absent" for all shifts
  213. self.assertEqual(shift.state, "absent")
  214. self.assertEqual(shift.compensation_no, "2")
  215. # Empty shift should be considered in max worker number calculation
  216. self.assertEqual(sheet_1.max_worker_no, 4)
  217. # Test default values creation
  218. self.assertTrue(sheet_1.time_slot)
  219. self.assertEqual(sheet_1.day, fields.Date.to_string(self.start_in_1))
  220. self.assertEqual(sheet_1.day_abbrevation, "Lundi")
  221. self.assertEqual(sheet_1.week, "Semaine A")
  222. self.assertTrue(sheet_1.name)
  223. self.assertFalse(sheet_1.notes)
  224. self.assertFalse(sheet_1.is_annotated)
  225. def test_attendance_sheet_barcode_scan(self):
  226. """
  227. Edition of an attendance sheet
  228. with barcode scanner, as a generic user"
  229. """
  230. # Attendance sheet generation
  231. self.attendance_sheet_model.sudo(
  232. self.user_generic
  233. )._generate_attendance_sheet()
  234. sheet_1 = self.search_sheets(self.start_in_1, self.end_in_1,)
  235. """
  236. Expected workers are :
  237. worker_regular_1 (barcode : 421457731745)
  238. worker_regular_3 replaced by worker_regular_2 (barcode : 421457731744))
  239. worker_irregular_1 (barcode : 429919251493)
  240. """
  241. # Scan barcode for expected workers
  242. for barcode in [421457731745, 421457731744, 429919251493]:
  243. sheet_1.on_barcode_scanned(barcode)
  244. # Check expected shifts update
  245. for id in sheet_1.expected_shift_ids.ids:
  246. shift = sheet_1.expected_shift_ids.browse(id)
  247. self.assertEqual(shift.state, "done")
  248. """
  249. Added workers are :
  250. worker_regular_super_1 (barcode : 421457731741)
  251. worker_irregular_2 (barcode : 421457731743)
  252. """
  253. # Workararound for _onchange method
  254. # (not applying on temporary object in tests)
  255. sheet_1._origin = sheet_1
  256. # Scan barcode for added workers
  257. sheet_1.on_barcode_scanned(421457731741)
  258. self.assertEqual(len(sheet_1.added_shift_ids), 1)
  259. sheet_1.on_barcode_scanned(421457731743)
  260. # Scan an already added worker should not change anything
  261. sheet_1.on_barcode_scanned(421457731743)
  262. self.assertEqual(len(sheet_1.added_shift_ids), 2)
  263. # Check added shifts fields
  264. for id in sheet_1.added_shift_ids.ids:
  265. shift = sheet_1.added_shift_ids.browse(id)
  266. self.assertEqual(sheet_1, shift.attendance_sheet_id)
  267. self.assertEqual(shift.state, "done")
  268. self.assertEqual(
  269. shift.task_type_id,
  270. self.attendance_sheet_shift_model.default_task_type_id(),
  271. )
  272. if shift.working_mode == "regular":
  273. self.assertTrue(shift.is_compensation)
  274. else:
  275. self.assertFalse(shift.is_compensation)
  276. # Add a worker that should be replaced
  277. with self.assertRaises(UserError) as e:
  278. sheet_1.on_barcode_scanned(421457731742)
  279. # Wrong barcode
  280. with self.assertRaises(UserError) as e:
  281. sheet_1.on_barcode_scanned(101010)
  282. # Add an unsubscribed worker
  283. self.worker_regular_1.cooperative_status_ids.sr = -2
  284. self.worker_regular_1.cooperative_status_ids.sc = -2
  285. with self.assertRaises(UserError) as e:
  286. sheet_1.on_barcode_scanned(421457731745)
  287. def test_attendance_sheet_edition(self):
  288. # Attendance sheet generation
  289. self.attendance_sheet_model.sudo(
  290. self.user_generic
  291. )._generate_attendance_sheet()
  292. sheet_1 = self.search_sheets(self.start_in_1, self.end_in_1)
  293. # Expected shifts edition
  294. sheet_1.expected_shift_ids[1].state = "done"
  295. sheet_1.expected_shift_ids[1].compensation_no = False
  296. sheet_1.expected_shift_ids[2].compensation_no = "1"
  297. # Added shits addition
  298. sheet_1.added_shift_ids |= sheet_1.added_shift_ids.new(
  299. {
  300. "task_type_id": self.task_type_2.id,
  301. "state": "done",
  302. "attendance_sheet_id": sheet_1.id,
  303. "worker_id": self.worker_irregular_2.id,
  304. "is_compensation": False,
  305. "is_regular": False,
  306. }
  307. )
  308. # Same task type as empty shift (should edit it on validation)
  309. sheet_1.added_shift_ids |= sheet_1.added_shift_ids.new(
  310. {
  311. "task_type_id": self.task_type_1.id,
  312. "state": "done",
  313. "attendance_sheet_id": sheet_1.id,
  314. "worker_id": self.worker_regular_super_1.id,
  315. "is_compensation": True,
  316. "is_regular": False,
  317. }
  318. )
  319. # Wait necessary time for shifts to begin
  320. waiting_time = (self.start_in_1 - datetime.now()).total_seconds()
  321. if waiting_time > 0:
  322. with self.assertRaises(UserError) as e:
  323. sheet_1.validate_with_checks()
  324. self.assertIn("wait", str(e.exception))
  325. time.sleep(waiting_time)
  326. # TODO: test validation with wizard (as generic user)
  327. # class odoo.tests.common.Form(recordp, view=None)
  328. # is only available from version 12
  329. # sheet_1 = sheet_1.sudo(self.user_generic)
  330. # Validation without wizard (as admin user)
  331. sheet_1 = sheet_1.sudo(self.user_admin)
  332. sheet_1.worker_nb_feedback = "enough"
  333. sheet_1.feedback = "Great session."
  334. sheet_1.notes = "Important information."
  335. sheet_1.validate_with_checks()
  336. with self.assertRaises(UserError) as e:
  337. sheet_1.validate_with_checks()
  338. self.assertIn("already been validated", str(e.exception))
  339. self.assertEqual(sheet_1.state, "validated")
  340. self.assertEqual(sheet_1.validated_by, self.user_admin.partner_id)
  341. self.assertTrue(sheet_1.is_annotated)
  342. self.assertFalse(sheet_1.is_read)
  343. # Check actual shifts update
  344. workers = sheet_1.expected_shift_ids.mapped(
  345. "worker_id"
  346. ) | sheet_1.added_shift_ids.mapped("worker_id")
  347. self.assertEqual(len(workers), 5)
  348. self.assertEqual(
  349. sheet_1.expected_shift_ids[0].task_id.state, "absent_2"
  350. )
  351. self.assertEqual(sheet_1.expected_shift_ids[1].task_id.state, "done")
  352. self.assertEqual(
  353. sheet_1.expected_shift_ids[2].task_id.state, "absent_1"
  354. )
  355. self.assertEqual(sheet_1.added_shift_ids[0].task_id.state, "done")
  356. self.assertEqual(sheet_1.added_shift_ids[1].task_id.state, "done")
  357. # Empty shift should have been updated
  358. self.assertEqual(
  359. sheet_1.added_shift_ids[0].task_id, self.shift_empty_1
  360. )
  361. # sheet_1.expected_shift_ids[0].worker_id
  362. # sheet_1.expected_shift_ids[2].replacement_worker_id
  363. def test_shift_counters(self):
  364. "Test shift counters calculation and cooperative status update"
  365. status_1 = self.worker_regular_1.cooperative_status_ids
  366. status_2 = self.worker_regular_3.cooperative_status_ids
  367. status_3 = self.worker_irregular_1.cooperative_status_ids
  368. shift_regular = self.shift_model.create(
  369. {
  370. "task_template_id": self.task_template_1.id,
  371. "task_type_id": self.task_type_1.id,
  372. "worker_id": self.worker_regular_1.id,
  373. "start_time": datetime.now() - timedelta(minutes=50),
  374. "end_time": datetime.now() - timedelta(minutes=40),
  375. "is_regular": True,
  376. "is_compensation": False,
  377. }
  378. )
  379. future_shift_regular = self.shift_model.create(
  380. {
  381. "task_template_id": self.task_template_2.id,
  382. "task_type_id": self.task_type_2.id,
  383. "worker_id": self.worker_regular_1.id,
  384. "start_time": datetime.now() + timedelta(minutes=20),
  385. "end_time": datetime.now() + timedelta(minutes=30),
  386. "is_regular": True,
  387. "is_compensation": False,
  388. }
  389. )
  390. shift_irregular = self.shift_model.create(
  391. {
  392. "task_template_id": self.task_template_2.id,
  393. "task_type_id": self.task_type_3.id,
  394. "worker_id": self.worker_irregular_1.id,
  395. "start_time": datetime.now() - timedelta(minutes=15),
  396. "end_time": datetime.now() - timedelta(minutes=10),
  397. }
  398. )
  399. # For a regular worker
  400. status_1.sr = 0
  401. status_1.sc = 0
  402. self.assertEqual(status_1.status, "ok")
  403. shift_regular.state = "absent_1"
  404. self.assertEqual(status_1.sr, -1)
  405. self.assertEqual(status_1.status, "alert")
  406. shift_regular.state = "done"
  407. self.assertEquals(status_1.sr, 0)
  408. self.assertEquals(status_1.sc, 0)
  409. shift_regular.state = "open"
  410. shift_regular.write({"is_regular": False, "is_compensation": True})
  411. shift_regular.state = "done"
  412. self.assertEquals(status_1.sr, 1)
  413. self.assertEquals(status_1.sc, 0)
  414. # Check unsubscribed status
  415. status_1.sr = -1
  416. status_1.sc = -1
  417. # Subscribe him to another future shift
  418. future_shift_regular.worker_id = self.worker_regular_1
  419. with self.assertRaises(ValidationError) as e:
  420. future_shift_regular.state = "absent_2"
  421. self.assertIn("future", str(e.exception))
  422. status_1.sr = -2
  423. status_1.sc = -2
  424. self.assertEquals(status_1.status, "unsubscribed")
  425. # Should be unsubscribed from future shift
  426. self.assertFalse(future_shift_regular.worker_id)
  427. # With replacement worker (self.worker_regular_3)
  428. shift_regular.state = "open"
  429. status_1.sr = 0
  430. status_1.sc = 0
  431. status_2.sr = 0
  432. status_2.sc = 0
  433. shift_regular.replaced_id = self.worker_regular_3
  434. shift_regular.state = "absent_2"
  435. self.assertEqual(status_1.sr, 0)
  436. self.assertEqual(status_1.sc, 0)
  437. self.assertEqual(status_2.sr, -1)
  438. self.assertEqual(status_2.sc, -1)
  439. # For an irregular worker
  440. status_3.sr = 0
  441. status_3.sc = 0
  442. self.assertEqual(status_3.status, "ok")
  443. shift_irregular.state = "done"
  444. self.assertEqual(status_3.sr, 1)
  445. shift_irregular.state = "absent_2"
  446. self.assertEqual(status_3.sr, -1)