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.

405 lines
18 KiB

7 years ago
7 years ago
7 years ago
7 years ago
  1. # -*- coding: utf-8 -*-
  2. from openerp import models, fields, api, _
  3. from openerp.exceptions import ValidationError
  4. from datetime import timedelta, datetime
  5. import logging
  6. from openerp.osv.fields import related
  7. _logger = logging.getLogger(__name__)
  8. PERIOD = 28 # TODO: use system parameter
  9. def add_days_delta(date_from, days_delta):
  10. if not date_from:
  11. return date_from
  12. next_date = fields.Date.from_string(date_from) + timedelta(days=days_delta)
  13. return fields.Date.to_string(next_date)
  14. class ExemptReason(models.Model):
  15. _name = 'cooperative.exempt.reason'
  16. name = fields.Char(required=True)
  17. class HistoryStatus(models.Model):
  18. _name = 'cooperative.status.history'
  19. _order= 'create_date desc'
  20. status_id = fields.Many2one('cooperative.status')
  21. cooperator_id = fields.Many2one('res.partner')
  22. change = fields.Char()
  23. type = fields.Selection([('status', 'Status Change'), ('counter', 'Counter Change')])
  24. user_id = fields.Many2one('res.users', string="User")
  25. class CooperativeStatus(models.Model):
  26. _name = 'cooperative.status'
  27. _rec_name = 'cooperator_id'
  28. _order = 'cooperator_id'
  29. today = fields.Date(help="Field that allow to compute field and store them even if they are based on the current date", default=fields.Date.today)
  30. cooperator_id = fields.Many2one('res.partner')
  31. active = fields.Boolean(related="cooperator_id.active", store=True, index=True)
  32. info_session = fields.Boolean('Information Session ?')
  33. info_session_date = fields.Datetime('Information Session Date')
  34. super = fields.Boolean("Super Cooperative")
  35. sr = fields.Integer("Compteur shift regulier", default=0)
  36. sc = fields.Integer("Compteur shift de compensation", default=0)
  37. time_extension = fields.Integer("Extension Days NB", default=0, help="Addtional days to the automatic extension, 5 mean that you have a total of 15 extension days of default one is set to 10")
  38. holiday_start_time = fields.Date("Holidays Start Day")
  39. holiday_end_time = fields.Date("Holidays End Day")
  40. alert_start_time = fields.Date("Alert Start Day")
  41. extension_start_time = fields.Date("Extension Start Day")
  42. #Champ compute
  43. working_mode = fields.Selection(
  44. [
  45. ('regular', 'Regular worker'),
  46. ('irregular', 'Irregular worker'),
  47. ('exempt', 'Exempted'),
  48. ],
  49. string="Working mode"
  50. )
  51. exempt_reason_id = fields.Many2one('cooperative.exempt.reason', 'Exempt Reason')
  52. status = fields.Selection([('ok', 'Up to Date'),
  53. ('holiday', 'Holidays'),
  54. ('alert', 'Alerte'),
  55. ('extension', 'Extension'),
  56. ('suspended', 'Suspended'),
  57. ('exempted', 'Exempted'),
  58. ('unsubscribed', 'Unsubscribed'),
  59. ('resigning', 'Resigning')],
  60. compute="_compute_status", string="Cooperative Status", store=True)
  61. can_shop = fields.Boolean(compute='_compute_status', store=True)
  62. history_ids = fields.One2many('cooperative.status.history', 'status_id', readonly=True)
  63. unsubscribed = fields.Boolean(default=False, help="Manually unsubscribed")
  64. resigning = fields.Boolean(default=False, help="Want to leave the beescoop")
  65. #Specific to irregular
  66. irregular_start_date = fields.Date() #TODO migration script
  67. irregular_absence_date = fields.Date()
  68. irregular_absence_counter = fields.Integer() #TODO unsubscribe when reach -2
  69. future_alert_date = fields.Date(compute='_compute_future_alert_date')
  70. temporary_exempt_reason_id = fields.Many2one('cooperative.exempt.reason', 'Exempt Reason')
  71. temporary_exempt_start_date = fields.Date()
  72. temporary_exempt_end_date = fields.Date()
  73. @api.depends('today', 'sr', 'sc', 'holiday_end_time',
  74. 'holiday_start_time', 'time_extension',
  75. 'alert_start_time', 'extension_start_time',
  76. 'unsubscribed', 'irregular_absence_date',
  77. 'irregular_absence_counter', 'temporary_exempt_start_date',
  78. 'temporary_exempt_end_date', 'resigning', 'cooperator_id.subscribed_shift_ids')
  79. def _compute_status(self):
  80. alert_delay = int(self.env['ir.config_parameter'].get_param('alert_delay', 28))
  81. grace_delay = int(self.env['ir.config_parameter'].get_param('default_grace_delay', 10))
  82. update = int(self.env['ir.config_parameter'].get_param('always_update', False))
  83. for rec in self:
  84. if update or not rec.today:
  85. rec.status = 'ok'
  86. rec.can_shop = True
  87. continue
  88. if rec.resigning:
  89. rec.status = 'resigning'
  90. rec.can_shop = False
  91. continue
  92. if rec.working_mode == 'regular':
  93. rec._set_regular_status(grace_delay, alert_delay)
  94. elif rec.working_mode == 'irregular':
  95. rec._set_irregular_status(grace_delay, alert_delay)
  96. elif rec.working_mode == 'exempt':
  97. rec.status = 'ok'
  98. rec.can_shop = True
  99. @api.depends('today', 'irregular_start_date', 'sr')
  100. def _compute_future_alert_date(self):
  101. """Compute date before which the worker is up to date"""
  102. for rec in self:
  103. future_alert_date = False
  104. if not rec.alert_start_time and rec.irregular_start_date:
  105. today_date = fields.Date.from_string(rec.today)
  106. delta = (today_date - fields.Date.from_string(rec.irregular_start_date)).days
  107. future_alert_date = today_date + timedelta(days=(rec.sr + 1) * PERIOD - delta % PERIOD)
  108. future_alert_date = future_alert_date.strftime('%Y-%m-%d')
  109. rec.future_alert_date = future_alert_date
  110. def _set_regular_status(self, grace_delay, alert_delay):
  111. self.ensure_one()
  112. ok = self.sr >= 0 and self.sc >= 0
  113. grace_delay = grace_delay + self.time_extension
  114. if self.sr < -1 or self.unsubscribed:
  115. self.status = 'unsubscribed'
  116. self.can_shop = False
  117. elif self.today >= self.temporary_exempt_start_date and self.today <= self.temporary_exempt_end_date:
  118. self.status = 'exempted'
  119. self.can_shop = True
  120. #Transition to alert sr < 0 or stay in alert sr < 0 or sc < 0 and thus alert time is defined
  121. elif not ok and self.alert_start_time and self.extension_start_time and self.today <= add_days_delta(self.extension_start_time, grace_delay):
  122. self.status = 'extension'
  123. self.can_shop = True
  124. elif not ok and self.alert_start_time and self.extension_start_time and self.today > add_days_delta(self.extension_start_time, grace_delay):
  125. self.status = 'suspended'
  126. self.can_shop = False
  127. elif not ok and self.alert_start_time and self.today > add_days_delta(self.alert_start_time, alert_delay):
  128. self.status = 'suspended'
  129. self.can_shop = False
  130. elif (self.sr < 0) or (not ok and self.alert_start_time):
  131. self.status = 'alert'
  132. self.can_shop = True
  133. #Check for holidays; Can be in holidays even in alert or other mode ?
  134. elif self.today >= self.holiday_start_time and self.today <= self.holiday_end_time:
  135. self.status = 'holiday'
  136. self.can_shop = False
  137. elif ok or (not self.alert_start_time and self.sr >= 0):
  138. self.status = 'ok'
  139. self.can_shop = True
  140. def _set_irregular_status(self, grace_delay, alert_delay):
  141. counter_unsubscribe = int(self.env['ir.config_parameter'].get_param('irregular_counter_to_unsubscribe', -3))
  142. self.ensure_one()
  143. ok = self.sr >= 0
  144. grace_delay = grace_delay + self.time_extension
  145. if self.sr <= counter_unsubscribe or self.unsubscribed:
  146. self.status = 'unsubscribed'
  147. self.can_shop = False
  148. elif self.today >= self.temporary_exempt_start_date and self.today <= self.temporary_exempt_end_date:
  149. self.status = 'exempted'
  150. self.can_shop = True
  151. #Transition to alert sr < 0 or stay in alert sr < 0 or sc < 0 and thus alert time is defined
  152. elif not ok and self.alert_start_time and self.extension_start_time and self.today <= add_days_delta(self.extension_start_time, grace_delay):
  153. self.status = 'extension'
  154. self.can_shop = True
  155. elif not ok and self.alert_start_time and self.extension_start_time and self.today > add_days_delta(self.extension_start_time, grace_delay):
  156. self.status = 'suspended'
  157. self.can_shop = False
  158. elif not ok and self.alert_start_time and self.today > add_days_delta(self.alert_start_time, alert_delay):
  159. self.status = 'suspended'
  160. self.can_shop = False
  161. elif (self.sr < 0) or (not ok and self.alert_start_time):
  162. self.status = 'alert'
  163. self.can_shop = True
  164. #Check for holidays; Can be in holidays even in alert or other mode ?
  165. elif self.today >= self.holiday_start_time and self.today <= self.holiday_end_time:
  166. self.status = 'holiday'
  167. self.can_shop = False
  168. elif ok or (not self.alert_start_time and self.sr >= 0):
  169. self.status = 'ok'
  170. self.can_shop = True
  171. @api.multi
  172. def write(self, vals):
  173. """
  174. Overwrite write to historize the change
  175. """
  176. for field in ['sr', 'sc', 'time_extension', 'extension_start_time', 'alert_start_time', 'unsubscribed']:
  177. if not field in vals:
  178. continue
  179. for rec in self:
  180. data = {
  181. 'status_id': rec.id,
  182. 'cooperator_id': rec.cooperator_id.id,
  183. 'type': 'counter',
  184. 'user_id': self.env.context.get('real_uid', self.env.uid),
  185. }
  186. if vals.get(field, rec[field]) != rec[field]:
  187. data['change'] = '%s: %s -> %s' % (field.upper(), rec[field], vals.get(field))
  188. self.env['cooperative.status.history'].sudo().create(data)
  189. return super(CooperativeStatus, self).write(vals)
  190. def _state_change(self, new_state):
  191. self.ensure_one()
  192. if new_state == 'alert':
  193. self.write({'alert_start_time': self.today, 'extension_start_time': False, 'time_extension': 0})
  194. if new_state == 'ok':
  195. data = {'extension_start_time': False, 'time_extension': 0}
  196. if self.working_mode != 'irregular': # Don't reset alert time for irregular
  197. data['alert_start_time'] = False
  198. self.write(data)
  199. if new_state == 'unsubscribed' or new_state == 'resigning':
  200. # Remove worker from task_templates
  201. self.cooperator_id.sudo().write(
  202. {'subscribed_shift_ids': [(5, 0, 0)]})
  203. # Remove worker from supercoop in task_templates
  204. task_tpls = self.env['beesdoo.shift.template'].search(
  205. [('super_coop_id', 'in', self.cooperator_id.user_ids.ids)]
  206. )
  207. task_tpls.write({'super_coop_id': False})
  208. # Remove worker for future task (remove also supercoop)
  209. # TODO: Add one day otherwise unsubscribed from the shift you were absent
  210. self.env['beesdoo.shift.shift'].sudo().unsubscribe_from_today(
  211. [self.cooperator_id.id], today=fields.Date.today())
  212. def _change_counter(self, data):
  213. self.sc += data.get('sc', 0)
  214. self.sr += data.get('sr', 0)
  215. self.irregular_absence_counter += data.get('irregular_absence_counter', 0)
  216. self.irregular_absence_date = data.get('irregular_absence_date', False)
  217. @api.multi
  218. def _write(self, vals):
  219. """
  220. Overwrite write to historize the change of status
  221. and make action on status change
  222. """
  223. if 'status' in vals:
  224. self._cr.execute('select id, status, sr, sc from "%s" where id in %%s' % self._table, (self._ids,))
  225. result = self._cr.dictfetchall()
  226. old_status_per_id = {r['id'] : r for r in result}
  227. for rec in self:
  228. if old_status_per_id[rec.id]['status'] != vals['status']:
  229. data = {
  230. 'status_id': rec.id,
  231. 'cooperator_id': rec.cooperator_id.id,
  232. 'type': 'status',
  233. 'change': "STATUS: %s -> %s" % (old_status_per_id[rec.id]['status'], vals['status']),
  234. 'user_id': self.env.context.get('real_uid', self.env.uid),
  235. }
  236. self.env['cooperative.status.history'].sudo().create(data)
  237. rec._state_change(vals['status'])
  238. return super(CooperativeStatus, self)._write(vals)
  239. _sql_constraints = [
  240. ('cooperator_uniq', 'unique (cooperator_id)', _('You can only set one cooperator status per cooperator')),
  241. ]
  242. @api.model
  243. def _set_today(self):
  244. """
  245. Method call by the cron to update store value base on the date
  246. """
  247. self.search([]).write({'today': fields.Date.today()})
  248. @api.multi
  249. def clear_history(self):
  250. self.ensure_one()
  251. self.history_ids.unlink()
  252. @api.model
  253. def _cron_compute_counter_irregular(self, today=False):
  254. today = today or fields.Date.today()
  255. journal = self.env['beesdoo.shift.journal'].search([('date', '=', today)])
  256. if not journal:
  257. journal = self.env['beesdoo.shift.journal'].create({'date': today})
  258. domain = ['&',
  259. '&',
  260. '&', ('status', '!=', 'unsubscribed'),
  261. ('working_mode', '=', 'irregular'),
  262. ('irregular_start_date', '!=', False),
  263. '|',
  264. '|', ('holiday_start_time', '=', False), ('holiday_end_time', '=', False),
  265. '|', ('holiday_start_time', '>', today), ('holiday_end_time', '<', today),
  266. ]
  267. irregular = self.search(domain)
  268. today_date = fields.Date.from_string(today)
  269. for status in irregular:
  270. if status.status == 'exempted':
  271. continue
  272. delta = (today_date - fields.Date.from_string(status.irregular_start_date)).days
  273. if delta and delta % PERIOD == 0 and status not in journal.line_ids:
  274. if status.sr > 0:
  275. status.sr -= 1
  276. status.alert_start_time = False
  277. elif status.alert_start_time:
  278. status.sr -= 1
  279. else:
  280. status.sr -= 2
  281. journal.line_ids |= status
  282. class ShiftCronJournal(models.Model):
  283. _name = 'beesdoo.shift.journal'
  284. _order = 'date desc'
  285. _rec_name = 'date'
  286. date = fields.Date()
  287. line_ids = fields.Many2many('cooperative.status')
  288. _sql_constraints = [
  289. ('one_entry_per_day', 'unique (date)', _('You can only create one journal per day')),
  290. ]
  291. @api.multi
  292. def run(self):
  293. self.ensure_one()
  294. if not self.user_has_groups('beesdoo_shift.group_cooperative_admin'):
  295. raise ValidationError(_("You don't have the access to perform this action"))
  296. self.sudo().env['cooperative.status']._cron_compute_counter_irregular(today=self.date)
  297. class ResPartner(models.Model):
  298. _inherit = 'res.partner'
  299. cooperative_status_ids = fields.One2many('cooperative.status', 'cooperator_id', readonly=True)
  300. super = fields.Boolean(related='cooperative_status_ids.super', string="Super Cooperative", readonly=True, store=True)
  301. info_session = fields.Boolean(related='cooperative_status_ids.info_session', string='Information Session ?', readonly=True, store=True)
  302. info_session_date = fields.Datetime(related='cooperative_status_ids.info_session_date', string='Information Session Date', readonly=True, store=True)
  303. working_mode = fields.Selection(related='cooperative_status_ids.working_mode', readonly=True, store=True)
  304. exempt_reason_id = fields.Many2one(related='cooperative_status_ids.exempt_reason_id', readonly=True, store=True)
  305. state = fields.Selection(related='cooperative_status_ids.status', readonly=True, store=True)
  306. extension_start_time = fields.Date(related='cooperative_status_ids.extension_start_time', string="Extension Start Day", readonly=True, store=True)
  307. subscribed_shift_ids = fields.Many2many('beesdoo.shift.template')
  308. @api.multi
  309. def coop_subscribe(self):
  310. return {
  311. 'name': _('Subscribe Cooperator'),
  312. 'type': 'ir.actions.act_window',
  313. 'view_type': 'form',
  314. 'view_mode': 'form',
  315. 'res_model': 'beesdoo.shift.subscribe',
  316. 'target': 'new',
  317. }
  318. @api.multi
  319. def coop_unsubscribe(self):
  320. res = self.coop_subscribe()
  321. res['context'] = {'default_unsubscribed': True}
  322. return res
  323. @api.multi
  324. def manual_extension(self):
  325. return {
  326. 'name': _('Manual Extension'),
  327. 'type': 'ir.actions.act_window',
  328. 'view_type': 'form',
  329. 'view_mode': 'form',
  330. 'res_model': 'beesdoo.shift.extension',
  331. 'target': 'new',
  332. }
  333. @api.multi
  334. def auto_extension(self):
  335. res = self.manual_extension()
  336. res['context'] = {'default_auto': True}
  337. res['name'] = _('Trigger Grace Delay')
  338. return res
  339. @api.multi
  340. def register_holiday(self):
  341. return {
  342. 'name': _('Register Holiday'),
  343. 'type': 'ir.actions.act_window',
  344. 'view_type': 'form',
  345. 'view_mode': 'form',
  346. 'res_model': 'beesdoo.shift.holiday',
  347. 'target': 'new',
  348. }
  349. @api.multi
  350. def temporary_exempt(self):
  351. return {
  352. 'name': _('Temporary Exemption'),
  353. 'type': 'ir.actions.act_window',
  354. 'view_type': 'form',
  355. 'view_mode': 'form',
  356. 'res_model': 'beesdoo.shift.temporary_exemption',
  357. 'target': 'new',
  358. }
  359. #TODO access right + vue on res.partner
  360. #TODO can_shop : Status can_shop ou extempted ou part C