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.

294 lines
12 KiB

[ADD] Improve Shift management après avoir généré les shifts pour une semaine, rediriger vers les shifts générés : ok Ajouter un supercoopérateur au niveau du template de shift. Pas de champs related car il faut pouvoir gérer les remplacements entre supercoopérateurs. : Rajouter un boolean sur res.users pour dire qu'ils sont super coopérateur du coup on peut choisir qu'un utilisateur super coopérateur : Ok Sur la vue des shifts, permettre de sélectionner un ensemble de shifts pour pouvoir changer son supercoopérateur en une action : Ok Avoir une vue des shifts par défaut plus lisible que le calendrier : KANBAN avec TODAY par défaut et les colonnes selon le type de shift : ok Dans la vue KANBAN du planning des semaines, faire un tri sur la start date pour que ça ressemble à une vue calendrier :ok Droits d'accès : 3 groupes : modifier un shift existant et confirmer son statut : absence/présence générer et supprimer des shifts à partir du template modifier le template des semaines et gérer la config En réfléchissant à ceux qui vont gérer les présences : il faudrait avoir une vue facile sur les shifts du jour et une manière simple de dire présent/absent/remplacé pour l'ensemble des coopérateurs présents (éviter de devoir chaque fois sélectionner le shift, cliquer sur le statut, aller au shift suivant, cliquer sur le statut...). J'ai fait un nouveau point de menu avec une vue Kanban grouper par status, il suffit de faire du drag and drop pour changer le status, les shifts sont aussi filtré sur aujourd'hui et seulement ceux ayant un worker assigné.
8 years ago
[ADD] Improve Shift management après avoir généré les shifts pour une semaine, rediriger vers les shifts générés : ok Ajouter un supercoopérateur au niveau du template de shift. Pas de champs related car il faut pouvoir gérer les remplacements entre supercoopérateurs. : Rajouter un boolean sur res.users pour dire qu'ils sont super coopérateur du coup on peut choisir qu'un utilisateur super coopérateur : Ok Sur la vue des shifts, permettre de sélectionner un ensemble de shifts pour pouvoir changer son supercoopérateur en une action : Ok Avoir une vue des shifts par défaut plus lisible que le calendrier : KANBAN avec TODAY par défaut et les colonnes selon le type de shift : ok Dans la vue KANBAN du planning des semaines, faire un tri sur la start date pour que ça ressemble à une vue calendrier :ok Droits d'accès : 3 groupes : modifier un shift existant et confirmer son statut : absence/présence générer et supprimer des shifts à partir du template modifier le template des semaines et gérer la config En réfléchissant à ceux qui vont gérer les présences : il faudrait avoir une vue facile sur les shifts du jour et une manière simple de dire présent/absent/remplacé pour l'ensemble des coopérateurs présents (éviter de devoir chaque fois sélectionner le shift, cliquer sur le statut, aller au shift suivant, cliquer sur le statut...). J'ai fait un nouveau point de menu avec une vue Kanban grouper par status, il suffit de faire du drag and drop pour changer le status, les shifts sont aussi filtré sur aujourd'hui et seulement ceux ayant un worker assigné.
8 years ago
[ADD] Improve Shift management après avoir généré les shifts pour une semaine, rediriger vers les shifts générés : ok Ajouter un supercoopérateur au niveau du template de shift. Pas de champs related car il faut pouvoir gérer les remplacements entre supercoopérateurs. : Rajouter un boolean sur res.users pour dire qu'ils sont super coopérateur du coup on peut choisir qu'un utilisateur super coopérateur : Ok Sur la vue des shifts, permettre de sélectionner un ensemble de shifts pour pouvoir changer son supercoopérateur en une action : Ok Avoir une vue des shifts par défaut plus lisible que le calendrier : KANBAN avec TODAY par défaut et les colonnes selon le type de shift : ok Dans la vue KANBAN du planning des semaines, faire un tri sur la start date pour que ça ressemble à une vue calendrier :ok Droits d'accès : 3 groupes : modifier un shift existant et confirmer son statut : absence/présence générer et supprimer des shifts à partir du template modifier le template des semaines et gérer la config En réfléchissant à ceux qui vont gérer les présences : il faudrait avoir une vue facile sur les shifts du jour et une manière simple de dire présent/absent/remplacé pour l'ensemble des coopérateurs présents (éviter de devoir chaque fois sélectionner le shift, cliquer sur le statut, aller au shift suivant, cliquer sur le statut...). J'ai fait un nouveau point de menu avec une vue Kanban grouper par status, il suffit de faire du drag and drop pour changer le status, les shifts sont aussi filtré sur aujourd'hui et seulement ceux ayant un worker assigné.
8 years ago
  1. # -*- coding: utf-8 -*-
  2. import json
  3. from datetime import datetime, timedelta
  4. from openerp import _, api, fields, models
  5. from openerp.exceptions import UserError, ValidationError
  6. class Task(models.Model):
  7. _name = 'beesdoo.shift.shift'
  8. _inherit = ['mail.thread']
  9. _order = "start_time asc"
  10. name = fields.Char(track_visibility='always')
  11. task_template_id = fields.Many2one('beesdoo.shift.template')
  12. planning_id = fields.Many2one(related='task_template_id.planning_id', store=True)
  13. task_type_id = fields.Many2one('beesdoo.shift.type', string="Task Type")
  14. worker_id = fields.Many2one('res.partner', track_visibility='onchange',
  15. domain=[
  16. ('eater', '=', 'worker_eater'),
  17. ('working_mode', 'in', ('regular', 'irregular')),
  18. ('state', 'not in', ('unsubscribed', 'resigning')),
  19. ])
  20. start_time = fields.Datetime(track_visibility='always', index=True, required=True)
  21. end_time = fields.Datetime(track_visibility='always', required=True)
  22. state = fields.Selection(selection=[
  23. ("draft","Unconfirmed"),
  24. ("open","Confirmed"),
  25. ("done","Attended"),
  26. ("absent_2","Absent - 2 compensations"),
  27. ("absent_1","Absent - 1 compensation"),
  28. ("absent_0","Absent - 0 compensation"),
  29. ("cancel","Cancelled")
  30. ],
  31. default="open",
  32. required=True,
  33. store=True,
  34. track_visibility='onchange',
  35. )
  36. color = fields.Integer(compute="_compute_color")
  37. super_coop_id = fields.Many2one('res.users', string="Super Cooperative", domain=[('partner_id.super', '=', True)], track_visibility='onchange')
  38. # TODO: Maybe is_regular and is_compensation must be merged in a
  39. # selection field as they are mutually exclusive.
  40. is_regular = fields.Boolean(default=False, string="Regular shift")
  41. is_compensation = fields.Boolean(default=False, string="Compensation shift")
  42. replaced_id = fields.Many2one('res.partner', track_visibility='onchange',
  43. domain=[
  44. ('eater', '=', 'worker_eater'),
  45. ('working_mode', '=', 'regular'),
  46. ('state', 'not in', ('unsubscribed', 'resigning')),
  47. ])
  48. revert_info = fields.Text(copy=False)
  49. working_mode = fields.Selection(related='worker_id.working_mode')
  50. @api.depends("state")
  51. def _compute_color(self):
  52. color_mapping = {
  53. "draft": 0,
  54. "open": 1,
  55. "done": 5,
  56. "absent_2": 2,
  57. "absent_1": 7,
  58. "absent_0": 3,
  59. "cancel": 9,
  60. }
  61. for rec in self:
  62. rec.color = color_mapping[rec.state]
  63. def _compensation_validation(self, task):
  64. """
  65. Raise a validation error if the fields is_regular and
  66. is_compensation are not properly set.
  67. """
  68. if (task.is_regular == task.is_compensation
  69. or not (task.is_regular or task.is_compensation)):
  70. raise ValidationError(
  71. "You must choose between Regular Shift or "
  72. "Compensation Shift."
  73. )
  74. @api.constrains("state")
  75. def _lock_future_task(self):
  76. start_time_dt = fields.Datetime.from_string(self.start_time)
  77. if datetime.now() < start_time_dt:
  78. if self.state in ["done", "absent_2", "absent_1", "absent_0"]:
  79. raise UserError(_(
  80. "You cannot set shift state to 'present' "
  81. "or 'absent' for a future shift."
  82. ))
  83. @api.constrains('is_regular', 'is_compensation')
  84. def _check_compensation(self):
  85. for task in self:
  86. if task.working_mode == 'regular':
  87. self._compensation_validation(task)
  88. @api.constrains('worker_id')
  89. def _check_worker_id(self):
  90. """
  91. When worker_id changes we need to check whether is_regular
  92. and is_compensation are set correctly.
  93. When worker_id is set to a worker that doesn't need field
  94. is_regular and is_compensation, these two fields are set to
  95. False.
  96. """
  97. for task in self:
  98. if task.working_mode == 'regular':
  99. self._compensation_validation(task)
  100. else:
  101. task.write({
  102. 'is_regular': False,
  103. 'is_compensation': False,
  104. })
  105. if task.worker_id:
  106. if task.worker_id == task.replaced_id:
  107. raise UserError("A worker cannot replace himself.")
  108. def message_auto_subscribe(self, updated_fields, values=None):
  109. self._add_follower(values)
  110. return super(Task, self).message_auto_subscribe(updated_fields, values=values)
  111. def _add_follower(self, vals):
  112. if vals.get('worker_id'):
  113. worker = self.env['res.partner'].browse(vals['worker_id'])
  114. self.message_subscribe(partner_ids=worker.ids)
  115. #TODO button to replaced someone
  116. @api.model
  117. def unsubscribe_from_today(self, worker_ids, today=None, end_date=None, now=None):
  118. """
  119. Unsubscribe workers from *worker_ids* from all shift that start *today* and later.
  120. If *end_date* is given, unsubscribe workers from shift between *today* and *end_date*.
  121. If *now* is given workers are unsubscribed from all shifts starting *now* and later.
  122. If *now* is given, *end_date* is not taken into account.
  123. :type today: fields.Date
  124. :type end_date: fields.Date
  125. :type now: fields.Datetime
  126. """
  127. if now:
  128. if len(now) != 19:
  129. raise UserError (_("'Now' must be a datetime."))
  130. date_domain = [('start_time', '>', now)]
  131. else:
  132. today = today or fields.Date.today()
  133. today += ' 00:00:00'
  134. date_domain = [('start_time', '>', today)]
  135. if end_date:
  136. end_date += ' 23:59:59'
  137. date_domain.append(('end_time', '<=', end_date))
  138. to_unsubscribe = self.search([('worker_id', 'in', worker_ids)] + date_domain)
  139. to_unsubscribe.write({'worker_id': False})
  140. # Remove worker, replaced_id and regular
  141. to_unsubscribe_replace = self.search([('replaced_id', 'in', worker_ids)] + date_domain)
  142. to_unsubscribe_replace.write({'worker_id': False, 'replaced_id': False})
  143. # If worker is Super cooperator, remove it from planning
  144. super_coop_ids = self.env['res.users'].search(
  145. [('partner_id', 'in', worker_ids), ('super', '=', True)]).ids
  146. if super_coop_ids:
  147. to_unsubscribe_super_coop = self.search(
  148. [('super_coop_id', 'in', super_coop_ids)] + date_domain)
  149. to_unsubscribe_super_coop.write({'super_coop_id': False})
  150. @api.multi
  151. def write(self, vals):
  152. """
  153. Overwrite write to track state change
  154. If worker is changer:
  155. Revert for the current worker
  156. Change the worker info
  157. Compute state change for the new worker
  158. """
  159. if 'worker_id' in vals:
  160. for rec in self:
  161. if rec.worker_id.id != vals['worker_id']:
  162. rec._revert()
  163. # To satisfy the constrains on worker_id, it must be
  164. # accompanied by the change in is_regular and
  165. # is_compensation field.
  166. super(Task, rec).write({
  167. 'worker_id': vals['worker_id'],
  168. 'is_regular': vals.get('is_regular', rec.is_regular),
  169. 'is_compensation': vals.get('is_compensation',
  170. rec.is_compensation),
  171. })
  172. rec._update_state(rec.state)
  173. if 'state' in vals:
  174. for rec in self:
  175. if vals['state'] != rec.state:
  176. rec._update_state(vals['state'])
  177. return super(Task, self).write(vals)
  178. def _set_revert_info(self, data, status):
  179. data_new = {
  180. 'status_id': status.id,
  181. 'data' : {k: data.get(k, 0) * -1 for k in ['sr', 'sc', 'irregular_absence_counter']}
  182. }
  183. if data.get('irregular_absence_date'):
  184. data_new['data']['irregular_absence_date'] = False
  185. self.write({'revert_info': json.dumps(data_new)})
  186. def _revert(self):
  187. if not self.revert_info:
  188. return
  189. data = json.loads(self.revert_info)
  190. self.env['cooperative.status'].browse(data['status_id']).sudo()._change_counter(data['data'])
  191. self.revert_info = False
  192. def _update_state(self, new_state):
  193. self.ensure_one()
  194. self._revert()
  195. update = int(self.env['ir.config_parameter'].get_param('always_update', False))
  196. data = {}
  197. if not (self.worker_id or self.replaced_id) and new_state in ("done", "absent_0", "absent_1", "absent_2"):
  198. raise UserError(_("You cannot change to the status %s if no worker is defined for the shift") % new_state)
  199. if update or not (self.worker_id or self.replaced_id):
  200. return
  201. if self.worker_id.working_mode == 'regular':
  202. if not self.replaced_id: #No replacement case
  203. status = self.worker_id.cooperative_status_ids[0]
  204. else:
  205. status = self.replaced_id.cooperative_status_ids[0]
  206. if new_state == "done" and not self.is_regular:
  207. # Regular counter is always updated first
  208. if status.sr < 0:
  209. data['sr'] = 1
  210. elif status.sc < 0:
  211. data['sc'] = 1
  212. # Bonus shift case
  213. else:
  214. data['sr'] = 1
  215. if new_state == "absent_2":
  216. data['sr'] = -1
  217. data['sc'] = -1
  218. if new_state == "absent_1":
  219. data['sr'] = -1
  220. elif self.worker_id.working_mode == 'irregular':
  221. status = self.worker_id.cooperative_status_ids[0]
  222. if new_state == "done" or new_state == "absent_0":
  223. data['sr'] = 1
  224. data['irregular_absence_date'] = False
  225. data['irregular_absence_counter'] = 1 if status.irregular_absence_counter < 0 else 0
  226. if new_state == "absent_2" or new_state == "absent_1":
  227. if new_state == "absent_2":
  228. data['sr'] = -1
  229. data['irregular_absence_date'] = self.start_time[:10]
  230. data['irregular_absence_counter'] = -1
  231. else:
  232. raise UserError(_("Working mode is not properly defined. Please check if the worker is subscribed"))
  233. status.sudo()._change_counter(data)
  234. self._set_revert_info(data, status)
  235. @api.model
  236. def _cron_send_weekly_emails(self):
  237. """
  238. Send a summary email for all workers
  239. if they have a shift planned during the week.
  240. """
  241. tasks = self.env["beesdoo.shift.shift"]
  242. shift_summary_mail_template = self.env.ref(
  243. "beesdoo_shift.email_template_shift_summary", False
  244. )
  245. start_time = datetime.now() + timedelta(days=1)
  246. end_time = datetime.now() + timedelta(days=7)
  247. confirmed_tasks = tasks.search(
  248. [
  249. ("start_time", ">", start_time.strftime("%Y-%m-%d 00:00:01")),
  250. ("start_time", "<", end_time.strftime("%Y-%m-%d 23:59:59")),
  251. ("worker_id", "!=", False),
  252. ("state", "=", "open"),
  253. ]
  254. )
  255. for rec in confirmed_tasks:
  256. shift_summary_mail_template.send_mail(rec.id, True)