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.

392 lines
15 KiB

7 years ago
7 years ago
7 years ago
7 years ago
  1. from odoo import models, fields, api, _
  2. from odoo.exceptions import ValidationError, UserError
  3. from datetime import timedelta, datetime
  4. import logging
  5. _logger = logging.getLogger(__name__)
  6. def add_days_delta(date_from, days_delta):
  7. if not date_from:
  8. return date_from
  9. next_date = date_from + timedelta(days=days_delta)
  10. return next_date
  11. class ExemptReason(models.Model):
  12. _name = 'cooperative.exempt.reason'
  13. name = fields.Char(required=True)
  14. class HistoryStatus(models.Model):
  15. _name = 'cooperative.status.history'
  16. _order= 'create_date desc'
  17. status_id = fields.Many2one('cooperative.status')
  18. cooperator_id = fields.Many2one('res.partner')
  19. change = fields.Char()
  20. type = fields.Selection([('status', 'Status Change'), ('counter', 'Counter Change')])
  21. user_id = fields.Many2one('res.users', string="User")
  22. class CooperativeStatus(models.Model):
  23. _name = 'cooperative.status'
  24. _rec_name = 'cooperator_id'
  25. _order = 'cooperator_id'
  26. _period = 28
  27. def _get_status(self):
  28. return [
  29. ('ok', 'Up to Date'),
  30. ('holiday', 'Holidays'),
  31. ('alert', 'Alerte'),
  32. ('extension', 'Extension'),
  33. ('suspended', 'Suspended'),
  34. ('exempted', 'Exempted'),
  35. ('unsubscribed', 'Unsubscribed'),
  36. ('resigning', 'Resigning')
  37. ]
  38. 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)
  39. cooperator_id = fields.Many2one('res.partner')
  40. active = fields.Boolean(related="cooperator_id.active", store=True, index=True)
  41. info_session = fields.Boolean('Information Session ?')
  42. info_session_date = fields.Date('Information Session Date')
  43. super = fields.Boolean("Super Cooperative")
  44. sr = fields.Integer("Regular shifts counter", default=0)
  45. sc = fields.Integer("Compensation shifts counter", default=0)
  46. 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")
  47. holiday_start_time = fields.Date("Holidays Start Day")
  48. holiday_end_time = fields.Date("Holidays End Day")
  49. alert_start_time = fields.Date("Alert Start Day")
  50. extension_start_time = fields.Date("Extension Start Day")
  51. working_mode = fields.Selection(
  52. [
  53. ('regular', 'Regular worker'),
  54. ('irregular', 'Irregular worker'),
  55. ('exempt', 'Exempted'),
  56. ],
  57. string="Working mode"
  58. )
  59. exempt_reason_id = fields.Many2one('cooperative.exempt.reason', 'Exempt Reason')
  60. status = fields.Selection(selection=_get_status,
  61. compute="_compute_status", string="Cooperative Status", store=True)
  62. can_shop = fields.Boolean(compute='_compute_can_shop', store=True)
  63. history_ids = fields.One2many('cooperative.status.history', 'status_id', readonly=True)
  64. unsubscribed = fields.Boolean(default=False, help="Manually unsubscribed")
  65. resigning = fields.Boolean(default=False, help="Want to leave the beescoop")
  66. #Specific to irregular
  67. irregular_start_date = fields.Date() #TODO migration script
  68. irregular_absence_date = fields.Date()
  69. irregular_absence_counter = fields.Integer() #TODO unsubscribe when reach -2
  70. future_alert_date = fields.Date(compute='_compute_future_alert_date')
  71. next_countdown_date = fields.Date(compute='_compute_next_countdown_date')
  72. temporary_exempt_reason_id = fields.Many2one('cooperative.exempt.reason', 'Exempt Reason')
  73. temporary_exempt_start_date = fields.Date()
  74. temporary_exempt_end_date = fields.Date()
  75. @api.depends('status')
  76. def _compute_can_shop(self):
  77. for rec in self:
  78. rec.can_shop = rec.status in self._can_shop_status()
  79. @api.depends('today', 'sr', 'sc', 'holiday_end_time',
  80. 'holiday_start_time', 'time_extension',
  81. 'alert_start_time', 'extension_start_time',
  82. 'unsubscribed', 'irregular_absence_date',
  83. 'irregular_absence_counter', 'temporary_exempt_start_date',
  84. 'temporary_exempt_end_date', 'resigning', 'cooperator_id.subscribed_shift_ids')
  85. def _compute_status(self):
  86. update = int(self.env['ir.config_parameter'].sudo().get_param('always_update', False))
  87. for rec in self:
  88. if update or not rec.today:
  89. rec.status = 'ok'
  90. continue
  91. if rec.resigning:
  92. rec.status = 'resigning'
  93. continue
  94. if rec.working_mode == 'regular':
  95. rec.status = rec._get_regular_status()
  96. elif rec.working_mode == 'irregular':
  97. rec.status = rec._get_irregular_status()
  98. elif rec.working_mode == 'exempt':
  99. rec.status = 'ok'
  100. _sql_constraints = [
  101. ('cooperator_uniq', 'unique (cooperator_id)', _('You can only set one cooperator status per cooperator')),
  102. ]
  103. @api.constrains("working_mode", "irregular_start_date")
  104. def _constrains_irregular_start_date(self):
  105. if self.working_mode == "irregular" and not self.irregular_start_date:
  106. raise UserError(_("Irregular workers must have an irregular start date."))
  107. @api.multi
  108. def write(self, vals):
  109. """
  110. Overwrite write to historize the change
  111. """
  112. for field in ['sr', 'sc', 'time_extension', 'extension_start_time', 'alert_start_time', 'unsubscribed']:
  113. if not field in vals:
  114. continue
  115. for rec in self:
  116. data = {
  117. 'status_id': rec.id,
  118. 'cooperator_id': rec.cooperator_id.id,
  119. 'type': 'counter',
  120. 'user_id': self.env.context.get('real_uid', self.env.uid),
  121. }
  122. if vals.get(field, rec[field]) != rec[field]:
  123. data['change'] = '%s: %s -> %s' % (field.upper(), rec[field], vals.get(field))
  124. self.env['cooperative.status.history'].sudo().create(data)
  125. return super(CooperativeStatus, self).write(vals)
  126. @api.multi
  127. def _write(self, vals):
  128. """
  129. Overwrite write to historize the change of status
  130. and make action on status change
  131. """
  132. if 'status' in vals:
  133. self._cr.execute('select id, status, sr, sc from "%s" where id in %%s' % self._table, (self._ids,))
  134. result = self._cr.dictfetchall()
  135. old_status_per_id = {r['id'] : r for r in result}
  136. for rec in self:
  137. if old_status_per_id[rec.id]['status'] != vals['status']:
  138. data = {
  139. 'status_id': rec.id,
  140. 'cooperator_id': rec.cooperator_id.id,
  141. 'type': 'status',
  142. 'change': "STATUS: %s -> %s" % (old_status_per_id[rec.id]['status'], vals['status']),
  143. 'user_id': self.env.context.get('real_uid', self.env.uid),
  144. }
  145. self.env['cooperative.status.history'].sudo().create(data)
  146. rec._state_change(vals['status'])
  147. return super(CooperativeStatus, self)._write(vals)
  148. def get_status_value(self):
  149. """
  150. Workararound to get translated selection value instead of key in mail template.
  151. """
  152. state_list = self.env["cooperative.status"]._fields['status']._description_selection(self.env)
  153. return dict(state_list)[self.status]
  154. @api.model
  155. def _set_today(self):
  156. """
  157. Method call by the cron to update store value base on the date
  158. """
  159. self.search([]).write({'today': fields.Date.today()})
  160. @api.model
  161. def _cron_compute_counter_irregular(self, today=False):
  162. """
  163. Journal ensure that a irregular worker will be only check
  164. once per day
  165. """
  166. today = today or fields.Date.today()
  167. journal = self.env['beesdoo.shift.journal'].search([('date', '=', today)])
  168. if not journal:
  169. journal = self.env['beesdoo.shift.journal'].create({'date': today})
  170. domain = self._get_irregular_worker_domain(today=today)
  171. irregular = self.search(domain)
  172. for status in irregular:
  173. delta = (today - status.irregular_start_date).days
  174. if delta and delta % self._period == 0 and status not in journal.line_ids:
  175. status._change_irregular_counter()
  176. journal.line_ids |= status
  177. @api.multi
  178. def clear_history(self):
  179. self.ensure_one()
  180. self.history_ids.unlink()
  181. ########################################################
  182. # Method to override #
  183. # To define the behavior of the status #
  184. # #
  185. # By default: everyone is always up to date #
  186. ########################################################
  187. ##############################
  188. # Computed field section #
  189. ##############################
  190. @api.depends('today')
  191. def _compute_future_alert_date(self):
  192. """
  193. Compute date until the worker is up to date
  194. for irregular worker
  195. """
  196. for rec in self:
  197. rec.future_alert_date = False
  198. @api.depends('today')
  199. def _compute_next_countdown_date(self):
  200. """
  201. Compute the following countdown date. This date is the date when
  202. the worker will see his counter changed due to the cron. This
  203. date is like the birthday date of the worker that occurred each
  204. _period.
  205. """
  206. for rec in self:
  207. rec.next_countdown_date = False
  208. def _can_shop_status(self):
  209. """
  210. return the list of status that give access
  211. to active cooperator privilege
  212. """
  213. return ['ok', 'alert', 'extension', 'exempted']
  214. #####################################
  215. # Status Change implementation #
  216. #####################################
  217. def _get_regular_status(self):
  218. """
  219. Return the value of the status
  220. for the regular worker
  221. """
  222. return 'ok'
  223. def _get_irregular_status(self):
  224. """
  225. Return the value of the status
  226. for the irregular worker
  227. """
  228. return 'ok'
  229. def _state_change(self, new_state):
  230. """
  231. Hook to watch change in the state
  232. """
  233. pass
  234. def _change_counter(self, data):
  235. """
  236. Call when a shift state is changed
  237. use data generated by _get_counter_date_state_change
  238. """
  239. pass
  240. ###############################################
  241. ###### Irregular Cron implementation ##########
  242. ###############################################
  243. def _get_irregular_worker_domain(self):
  244. """
  245. return the domain the give the list
  246. of valid irregular worker that should
  247. get their counter changed by the cron
  248. """
  249. return [(0, '=', 1)]
  250. def _change_irregular_counter(self):
  251. """
  252. Define how the counter will change
  253. for the irregular worker
  254. where today - start_date is a multiple of the period
  255. by default 28 days
  256. """
  257. pass
  258. class ShiftCronJournal(models.Model):
  259. _name = 'beesdoo.shift.journal'
  260. _order = 'date desc'
  261. _rec_name = 'date'
  262. date = fields.Date()
  263. line_ids = fields.Many2many('cooperative.status')
  264. _sql_constraints = [
  265. ('one_entry_per_day', 'unique (date)', _('You can only create one journal per day')),
  266. ]
  267. @api.multi
  268. def run(self):
  269. self.ensure_one()
  270. if not self.user_has_groups('beesdoo_shift.group_cooperative_admin'):
  271. raise ValidationError(_("You don't have the access to perform this action"))
  272. self.sudo().env['cooperative.status']._cron_compute_counter_irregular(today=self.date)
  273. class ResPartner(models.Model):
  274. """
  275. One2many relationship with CooperativeStatus should
  276. be replaced by inheritance.
  277. """
  278. _inherit = 'res.partner'
  279. worker_store = fields.Boolean(default=False)
  280. is_worker = fields.Boolean(related="worker_store", string="Worker", readonly=False)
  281. cooperative_status_ids = fields.One2many('cooperative.status', 'cooperator_id', readonly=True)
  282. super = fields.Boolean(related='cooperative_status_ids.super', string="Super Cooperative", readonly=True, store=True)
  283. info_session = fields.Boolean(related='cooperative_status_ids.info_session', string='Information Session ?', readonly=True, store=True)
  284. info_session_date = fields.Date(related='cooperative_status_ids.info_session_date', string='Information Session Date', readonly=True, store=True)
  285. working_mode = fields.Selection(related='cooperative_status_ids.working_mode', readonly=True, store=True)
  286. exempt_reason_id = fields.Many2one(related='cooperative_status_ids.exempt_reason_id', readonly=True, store=True)
  287. state = fields.Selection(related='cooperative_status_ids.status', readonly=True, store=True)
  288. extension_start_time = fields.Date(related='cooperative_status_ids.extension_start_time', string="Extension Start Day", readonly=True, store=True)
  289. subscribed_shift_ids = fields.Many2many('beesdoo.shift.template')
  290. @api.multi
  291. def coop_subscribe(self):
  292. return {
  293. 'name': _('Subscribe Cooperator'),
  294. 'type': 'ir.actions.act_window',
  295. 'view_type': 'form',
  296. 'view_mode': 'form',
  297. 'res_model': 'beesdoo.shift.subscribe',
  298. 'target': 'new',
  299. }
  300. @api.multi
  301. def coop_unsubscribe(self):
  302. res = self.coop_subscribe()
  303. res['context'] = {'default_unsubscribed': True}
  304. return res
  305. @api.multi
  306. def manual_extension(self):
  307. return {
  308. 'name': _('Manual Extension'),
  309. 'type': 'ir.actions.act_window',
  310. 'view_type': 'form',
  311. 'view_mode': 'form',
  312. 'res_model': 'beesdoo.shift.extension',
  313. 'target': 'new',
  314. }
  315. @api.multi
  316. def auto_extension(self):
  317. res = self.manual_extension()
  318. res['context'] = {'default_auto': True}
  319. res['name'] = _('Trigger Grace Delay')
  320. return res
  321. @api.multi
  322. def register_holiday(self):
  323. return {
  324. 'name': _('Register Holiday'),
  325. 'type': 'ir.actions.act_window',
  326. 'view_type': 'form',
  327. 'view_mode': 'form',
  328. 'res_model': 'beesdoo.shift.holiday',
  329. 'target': 'new',
  330. }
  331. @api.multi
  332. def temporary_exempt(self):
  333. return {
  334. 'name': _('Temporary Exemption'),
  335. 'type': 'ir.actions.act_window',
  336. 'view_type': 'form',
  337. 'view_mode': 'form',
  338. 'res_model': 'beesdoo.shift.temporary_exemption',
  339. 'target': 'new',
  340. }
  341. #TODO access right + vue on res.partner