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.

515 lines
18 KiB

  1. # -*- coding: utf-8 -*-
  2. from lxml import etree
  3. from openerp import models, exceptions, fields, api, _
  4. from openerp.exceptions import UserError, ValidationError
  5. from datetime import datetime
  6. from lxml import etree
  7. class AttendanceSheetShift(models.AbstractModel):
  8. _name = "beesdoo.shift.sheet.shift"
  9. _description = "Copy of an actual shift into an attendance sheet"
  10. @api.model
  11. def _default_task_type_id(self):
  12. parameters = self.env["ir.config_parameter"]
  13. id = int(parameters.get_param("beesdoo_shift.default_task_type_id"))
  14. task_types = self.env["beesdoo.shift.type"]
  15. return task_types.browse(id)
  16. # Related actual shift, not required because doesn't exist for added shift before validation
  17. # To update after validation
  18. task_id = fields.Many2one("beesdoo.shift.shift", string="Task")
  19. attendance_sheet_id = fields.Many2one(
  20. "beesdoo.shift.sheet",
  21. string="Attendance Sheet",
  22. required=True,
  23. ondelete="cascade",
  24. )
  25. stage = fields.Selection(
  26. [
  27. ("present", "Present"),
  28. ("absent_0", "Absent / 0 Compensation"),
  29. ("absent_1", "Absent / 1 Compensation"),
  30. ("absent_2", "Absent / 2 Compensations"),
  31. ("cancelled", "Cancelled"),
  32. ],
  33. string="Shift Stage",
  34. )
  35. worker_id = fields.Many2one(
  36. "res.partner",
  37. string="Worker",
  38. domain=[
  39. ("eater", "=", "worker_eater"),
  40. ("working_mode", "in", ("regular", "irregular")),
  41. ("state", "not in", ("unsubscribed", "resigning")),
  42. ],
  43. )
  44. task_type_id = fields.Many2one(
  45. "beesdoo.shift.type", string="Task Type", default=_default_task_type_id
  46. )
  47. working_mode = fields.Selection(
  48. related="worker_id.working_mode", string="Working Mode", store=True
  49. )
  50. def get_actual_stage(self):
  51. """
  52. Mapping function returning the actual id
  53. of corresponding beesdoo.shift.stage
  54. This behavior should be temporary
  55. (increases lack of understanding).
  56. """
  57. if not self.working_mode or not self.stage:
  58. raise UserError(
  59. _("Impossible to map task status, all values are not set.")
  60. )
  61. if self.working_mode == "regular":
  62. if self.stage == "present":
  63. return "done"
  64. if self.stage == "absent_0":
  65. return "excused_necessity"
  66. if self.stage == "absent_1":
  67. return "excused"
  68. if self.stage == "absent_2":
  69. return "absent"
  70. if self.stage == "cancelled":
  71. return "cancel"
  72. if self.working_mode == "irregular":
  73. if self.stage == "present":
  74. return "done"
  75. if self.stage == "cancelled":
  76. return "cancel"
  77. return "absent"
  78. class AttendanceSheetShiftExpected(models.Model):
  79. _name = "beesdoo.shift.sheet.expected"
  80. _description = "Expected Shift"
  81. _inherit = ["beesdoo.shift.sheet.shift"]
  82. replacement_worker_id = fields.Many2one(
  83. "res.partner",
  84. string="Replacement Worker",
  85. domain=[
  86. ("eater", "=", "worker_eater"),
  87. ("working_mode", "=", "regular"),
  88. ("state", "not in", ("unsubscribed", "resigning")),
  89. ],
  90. )
  91. class AttendanceSheetShiftAdded(models.Model):
  92. """The added shifts stage must be Present
  93. (add an SQL constraint ?)
  94. """
  95. _name = "beesdoo.shift.sheet.added"
  96. _description = "Added Shift"
  97. _inherit = ["beesdoo.shift.sheet.shift"]
  98. # Change the previously determined two booleans for a more comprehensive field
  99. regular_task_type = fields.Selection(
  100. [("normal", "Normal"), ("compensation", "Compensation")],
  101. string="Task Mode (if regular)",
  102. help="Shift type for regular workers. ",
  103. )
  104. stage = fields.Selection(default="present")
  105. # WARNING: check the code, readonly fields modified by onchange are not inserted on write
  106. @api.onchange("working_mode")
  107. def on_change_working_mode(self):
  108. self.stage = "present"
  109. if self.working_mode == "regular":
  110. self.regular_task_type = "compensation"
  111. if self.working_mode == "irregular":
  112. self.regular_task_type = False
  113. class AttendanceSheet(models.Model):
  114. _name = "beesdoo.shift.sheet"
  115. _inherit = [
  116. "mail.thread",
  117. "ir.needaction_mixin",
  118. "barcodes.barcode_events_mixin",
  119. ]
  120. _description = "Attendance sheets with all the shifts in one time range."
  121. _order = "start_time"
  122. name = fields.Char(
  123. string="Name", compute="_compute_name", store=True, readonly=True
  124. )
  125. active = fields.Boolean(string="Active", default=1)
  126. state = fields.Selection(
  127. [("not_validated", "Not Validated"), ("validated", "Validated"),],
  128. string="Status",
  129. readonly=True,
  130. index=True,
  131. default="not_validated",
  132. track_visibility="onchange",
  133. )
  134. start_time = fields.Datetime(
  135. string="Start Time", required=True, readonly=True
  136. )
  137. end_time = fields.Datetime(string="End Time", required=True, readonly=True)
  138. expected_shift_ids = fields.One2many(
  139. "beesdoo.shift.sheet.expected",
  140. "attendance_sheet_id",
  141. string="Expected Shifts",
  142. )
  143. added_shift_ids = fields.One2many(
  144. "beesdoo.shift.sheet.added",
  145. "attendance_sheet_id",
  146. string="Added Shifts",
  147. )
  148. max_worker_no = fields.Integer(
  149. string="Maximum number of workers",
  150. default=0,
  151. readonly=True,
  152. help="Indicative maximum number of workers for the shifts.",
  153. )
  154. annotation = fields.Text("Annotation", default="")
  155. is_annotated = fields.Boolean(
  156. compute="_compute_is_annotated",
  157. string="Annotation",
  158. readonly=True,
  159. store=True,
  160. )
  161. is_read = fields.Boolean(
  162. string="Mark as read",
  163. help="Has annotation been read by an administrator ?",
  164. default=False,
  165. track_visibility="onchange",
  166. )
  167. feedback = fields.Text("Feedback")
  168. worker_nb_feedback = fields.Selection(
  169. [
  170. ("not_enough", "Not enough"),
  171. ("enough", "Enough"),
  172. ("too_many", "Too many"),
  173. ],
  174. string="Number of workers.",
  175. )
  176. attended_worker_nb = fields.Integer(
  177. string="Number of attended workers",
  178. default=0,
  179. help="Number of workers who attended the session.",
  180. readonly=True,
  181. )
  182. validated_by = fields.Many2one(
  183. "res.partner",
  184. string="Validated by",
  185. domain=[
  186. ("eater", "=", "worker_eater"),
  187. ("super", "=", True),
  188. ("working_mode", "=", "regular"),
  189. ("state", "not in", ("unsubscribed", "resigning")),
  190. ],
  191. track_visibility="onchange",
  192. readonly=True,
  193. )
  194. _sql_constraints = [
  195. (
  196. "check_no_annotation_mark_read",
  197. "CHECK ((is_annotated=FALSE AND is_read=FALSE) OR is_annotated=TRUE)",
  198. _("Non-annotated sheets can't be marked as read."),
  199. )
  200. ]
  201. @api.constrains(
  202. "expected_shift_ids",
  203. "added_shift_ids",
  204. "annotation",
  205. "feedback",
  206. "worker_nb_feedback",
  207. )
  208. def _lock_after_validation(self):
  209. if self.state == "validated":
  210. raise UserError(
  211. _("The sheet has already been validated and can't be edited.")
  212. )
  213. @api.multi
  214. def button_mark_as_read(self):
  215. if self.is_read:
  216. raise UserError(_("The sheet has already been marked as read."))
  217. self.is_read = True
  218. @api.constrains("expected_shift_ids", "added_shift_ids")
  219. def _constrain_unique_worker(self):
  220. # Warning : map return generator in python3 (for Odoo 12)
  221. added_ids = map(lambda s: s.worker_id.id, self.added_shift_ids)
  222. expected_ids = map(lambda s: s.worker_id.id, self.expected_shift_ids)
  223. replacement_ids = map(
  224. lambda s: s.replacement_worker_id.id, self.expected_shift_ids
  225. )
  226. replacement_ids = filter(bool, replacement_ids)
  227. ids = added_ids + expected_ids + replacement_ids
  228. if (len(ids) - len(set(ids))) > 0:
  229. raise UserError(
  230. _(
  231. "You can't add the same worker more than once to an attendance sheet."
  232. )
  233. )
  234. # Compute name (not hardcorded to prevent incoherence with timezone)
  235. @api.depends("start_time", "end_time")
  236. def _compute_name(self):
  237. start_time_dt = fields.Datetime.from_string(self.start_time)
  238. start_time_dt = fields.Datetime.context_timestamp(self, start_time_dt)
  239. end_time_dt = fields.Datetime.from_string(self.end_time)
  240. end_time_dt = fields.Datetime.context_timestamp(self, end_time_dt)
  241. self.name = (
  242. start_time_dt.strftime("%Y-%m-%d")
  243. + " "
  244. + start_time_dt.strftime("%H:%M")
  245. + "-"
  246. + end_time_dt.strftime("%H:%M")
  247. )
  248. # Is this method necessary ?
  249. @api.depends("annotation")
  250. def _compute_is_annotated(self):
  251. if self.annotation:
  252. self.is_annotated = len(self.annotation) != 0
  253. return
  254. @api.model
  255. def create(self, vals):
  256. new_sheet = super(AttendanceSheet, self).create(vals)
  257. # Creation and addition of the expected shifts corresponding
  258. # to the time range
  259. tasks = self.env["beesdoo.shift.shift"]
  260. tasks = tasks.search(
  261. [
  262. ("start_time", "=", new_sheet.start_time),
  263. ("end_time", "=", new_sheet.end_time),
  264. ]
  265. )
  266. expected_shift = self.env["beesdoo.shift.sheet.expected"]
  267. task_templates = set()
  268. for task in tasks:
  269. if task.working_mode == "irregular":
  270. stage = "absent_1"
  271. else:
  272. stage = "absent_2"
  273. if task.worker_id:
  274. new_expected_shift = expected_shift.create(
  275. {
  276. "attendance_sheet_id": new_sheet.id,
  277. "task_id": task.id,
  278. "worker_id": task.worker_id.id,
  279. "replacement_worker_id": task.replaced_id.id,
  280. "task_type_id": task.task_type_id.id,
  281. "stage": stage,
  282. "working_mode": task.working_mode,
  283. }
  284. )
  285. task_templates.add(task.task_template_id)
  286. # Maximum number of workers calculation
  287. new_sheet.max_worker_no = sum(r.worker_nb for r in task_templates)
  288. return new_sheet
  289. # Workaround to display notifications only for unread and not validated
  290. # sheets, via a check on domain.
  291. @api.model
  292. def _needaction_count(self, domain=None):
  293. if domain == [
  294. ("is_annotated", "=", True),
  295. ("is_read", "=", False),
  296. ] or domain == [("state", "=", "not_validated")]:
  297. return self.search_count(domain)
  298. return
  299. def validate(self):
  300. self.ensure_one()
  301. if self.state == "validated":
  302. raise UserError("The sheet has already been validated.")
  303. shift = self.env["beesdoo.shift.shift"]
  304. stage = self.env["beesdoo.shift.stage"]
  305. # Fields validation
  306. for added_shift in self.added_shift_ids:
  307. if not added_shift.worker_id:
  308. raise UserError(
  309. _("Worker must be set for shift %s") % added_shift.id
  310. )
  311. if not added_shift.stage:
  312. raise UserError(
  313. _("Shift Stage is missing for %s")
  314. % added_shift.worker_id.name
  315. )
  316. if not added_shift.task_type_id:
  317. raise UserError(
  318. _("Task Type is missing for %s")
  319. % added_shift.worker_id.name
  320. )
  321. if not added_shift.working_mode:
  322. raise UserError(
  323. _("Working mode is missing for %s")
  324. % added_shift.worker_id.name
  325. )
  326. if (
  327. added_shift.worker_id.working_mode == "regular"
  328. and not added_shift.regular_task_type
  329. ):
  330. raise UserError(
  331. _("Regular Task Type is missing for %s")
  332. % added_shift.worker_id.name
  333. )
  334. for expected_shift in self.expected_shift_ids:
  335. if not expected_shift.stage:
  336. raise UserError(
  337. _("Shift Stage is missing for %s")
  338. % expected_shift.worker_id.name
  339. )
  340. # Expected shifts status update
  341. for expected_shift in self.expected_shift_ids:
  342. actual_shift = expected_shift.task_id
  343. # We get stage record corresponding to mapped stage id
  344. actual_stage = self.env.ref(
  345. "beesdoo_shift.%s" % expected_shift.get_actual_stage()
  346. )
  347. # If the actual stage has been deleted, the sheet is still validated.
  348. # Raising an exception would stop this but would prevent validation.
  349. # How can we show a message without stopping validation ?
  350. if actual_stage:
  351. actual_shift.stage_id = actual_stage
  352. actual_shift.replaced_id = expected_shift.replacement_worker_id
  353. # Added shifts status update
  354. for added_shift in self.added_shift_ids:
  355. actual_stage = self.env.ref(
  356. "beesdoo_shift.%s" % added_shift.get_actual_stage()
  357. )
  358. is_regular_worker = added_shift.worker_id.working_mode == "regular"
  359. is_regular_shift = added_shift.regular_task_type == "normal"
  360. # Add an annotation if a regular worker is doing its regular shift
  361. if is_regular_shift and is_regular_worker:
  362. self.annotation += (
  363. _(
  364. "\n\nWarning : %s attended its shift as a normal one but was not expected."
  365. " Something may be wrong in his/her personnal informations.\n"
  366. )
  367. % added_shift.worker_id.name
  368. )
  369. # Edit a non-assigned shift or create one if none
  370. non_assigned_shifts = shift.search(
  371. [
  372. ("worker_id", "=", False),
  373. ("start_time", "=", self.start_time),
  374. ("end_time", "=", self.end_time),
  375. ("task_type_id", "=", added_shift.task_type_id.id),
  376. ],
  377. limit=1,
  378. )
  379. if len(non_assigned_shifts):
  380. actual_shift = non_assigned_shifts[0]
  381. actual_shift.write(
  382. {
  383. "stage_id": actual_stage.id,
  384. "worker_id": added_shift.worker_id.id,
  385. "stage_id": actual_stage.id,
  386. "is_regular": is_regular_shift and is_regular_worker,
  387. "is_compensation": not is_regular_shift
  388. and is_regular_worker,
  389. }
  390. )
  391. else:
  392. actual_shift = self.env["beesdoo.shift.shift"].create(
  393. {
  394. "name": _("[Added Shift] %s") % self.start_time,
  395. "task_type_id": added_shift.task_type_id.id,
  396. "worker_id": added_shift.worker_id.id,
  397. "start_time": self.start_time,
  398. "end_time": self.end_time,
  399. "stage_id": actual_stage.id,
  400. "is_regular": is_regular_shift and is_regular_worker,
  401. "is_compensation": not is_regular_shift
  402. and is_regular_worker,
  403. }
  404. )
  405. added_shift.task_id = actual_shift.id
  406. self.state = "validated"
  407. return
  408. # @api.multi is needed to call the wizard, but doesn't match @api.one
  409. # from the validate() method
  410. @api.multi
  411. def validate_via_wizard(self):
  412. if self.env.user.has_group("beesdoo_shift.group_cooperative_admin"):
  413. self.validated_by = self.env.user.partner_id
  414. self.validate()
  415. return
  416. return {
  417. "type": "ir.actions.act_window",
  418. "res_model": "beesdoo.shift.sheet.validate",
  419. "view_type": "form",
  420. "view_mode": "form",
  421. "target": "new",
  422. }
  423. def on_barcode_scanned(self, barcode):
  424. if self.state == "validated":
  425. raise UserError(
  426. _("You cannot modify a validated attendance sheet.")
  427. )
  428. worker = self.env["res.partner"].search([("barcode", "=", barcode)])
  429. if not len(worker):
  430. raise UserError(_("Worker not found (invalid barcode or status)."))
  431. if len(worker) > 1:
  432. raise UserError(
  433. _("Multiple workers are corresponding this barcode.")
  434. )
  435. if worker.state in ("unsubscribed", "resigning"):
  436. raise UserError(_("Worker is %s.") % worker.state)
  437. if worker.working_mode not in ("regular", "irregular"):
  438. raise UserError(
  439. _("Worker is %s and should be regular or irregular.")
  440. % worker.working_mode
  441. )
  442. for id in self.expected_shift_ids.ids:
  443. shift = self.env["beesdoo.shift.sheet.expected"].browse(id)
  444. if (
  445. shift.worker_id == worker
  446. or shift.replacement_worker_id == worker
  447. ):
  448. shift.stage = "present"
  449. return
  450. if worker.working_mode == "regular":
  451. regular_task_type = "normal"
  452. else:
  453. regular_task_type = False
  454. added_ids = map(lambda s: s.worker_id.id, self.added_shift_ids)
  455. if worker.id in added_ids:
  456. raise UserError(_("Worker is already present."))
  457. self.added_shift_ids |= self.added_shift_ids.new(
  458. {
  459. "task_type_id": self.added_shift_ids._default_task_type_id(),
  460. "stage": "present",
  461. "attendance_sheet_id": self._origin.id,
  462. "worker_id": worker.id,
  463. "regular_task_type": regular_task_type,
  464. }
  465. )