diff --git a/beesdoo_shift/__openerp__.py b/beesdoo_shift/__openerp__.py index a87a609..dc23e2e 100644 --- a/beesdoo_shift/__openerp__.py +++ b/beesdoo_shift/__openerp__.py @@ -20,15 +20,18 @@ 'data': [ "data/stage.xml", "data/system_parameter.xml", + "data/cron.xml", "security/group.xml", "security/ir.model.access.csv", "views/task_template.xml", "views/task.xml", "views/planning.xml", "views/cooperative_status.xml", + "views/exempt_reason.xml", "wizard/instanciate_planning.xml", "wizard/batch_template.xml", "wizard/assign_super_coop.xml", "wizard/subscribe.xml", + "wizard/extension.xml", ], } diff --git a/beesdoo_shift/data/cron.xml b/beesdoo_shift/data/cron.xml new file mode 100644 index 0000000..f534dc2 --- /dev/null +++ b/beesdoo_shift/data/cron.xml @@ -0,0 +1,12 @@ + + + Update Cooperatoor status base on the date + 24 + hours + -1 + + cooperative.status + _set_today + () + + \ No newline at end of file diff --git a/beesdoo_shift/data/stage.xml b/beesdoo_shift/data/stage.xml index 6d7f19e..73fcc0c 100644 --- a/beesdoo_shift/data/stage.xml +++ b/beesdoo_shift/data/stage.xml @@ -3,35 +3,42 @@ Unconfirmed 1 0 + draft Confirmed 2 5 + open Attended 3 1 + done Absent 5 2 + absent Excused 6 4 + excused Excused - Absolute Necessity 6 4 + excused_necessity Cancelled 7 8 + cancel diff --git a/beesdoo_shift/data/system_parameter.xml b/beesdoo_shift/data/system_parameter.xml index c106f6d..1aef808 100644 --- a/beesdoo_shift/data/system_parameter.xml +++ b/beesdoo_shift/data/system_parameter.xml @@ -1,10 +1,18 @@ - + alert_delay 28 - + default_grace_delay 10 + + default_extension_delay + 28 + + + always_update + 0 + \ No newline at end of file diff --git a/beesdoo_shift/models/cooperative_status.py b/beesdoo_shift/models/cooperative_status.py index 69ebd85..8256a99 100644 --- a/beesdoo_shift/models/cooperative_status.py +++ b/beesdoo_shift/models/cooperative_status.py @@ -2,40 +2,182 @@ from openerp import models, fields, api, _ from openerp.exceptions import ValidationError +from datetime import timedelta + +def add_days_delta(date_from, days_delta): + if not date_from: + return date_from + next_date = fields.Date.from_string(date_from) + timedelta(days=days_delta) + return fields.Date.to_string(next_date) + +class ExemptReason(models.Model): + _name = 'cooperative.exempt.reason' + + name = fields.Char(required=True) + +class HistoryStatus(models.Model): + _name = 'cooperative.status.history' + + _order= 'create_date desc' + + status_id = fields.Many2one('cooperative.status') + cooperator_id = fields.Many2one('res.partner') + change = fields.Char() + type = fields.Selection([('status', 'Status Change'), ('counter', 'Counter Change')]) + user_id = fields.Many2one('res.users', string="User") + class CooperativeStatus(models.Model): _name = 'cooperative.status' _rec_name = 'cooperator_id' + _order = 'cooperator_id' + + today = fields.Date(help="Field that allow to compute field and store them even if they are based on the current date") cooperator_id = fields.Many2one('res.partner') info_session = fields.Boolean('Information Session ?') info_session_date = fields.Datetime('Information Session Date') super = fields.Boolean("Super Cooperative") - sr = fields.Integer("Compteur shift regulier") - sc = fields.Integer("Compteur shift de compensation") + sr = fields.Integer("Compteur shift regulier", default=0) + sc = fields.Integer("Compteur shift de compensation", default=0) time_holiday = fields.Integer("Holidays Days NB", default=0) - time_extension = fields.Integer("Extension Days NB", default=0) #Durée initial par défault sur ir_config_parameter + 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") holiday_start_time = fields.Date("Holidays Start Day") alert_start_time = fields.Date("Alert Start Day") extension_start_time = fields.Date("Extension Start Day") #Champ compute - status = fields.Selection([('ok', 'Up to Date'), ('holiday', 'Holidays'), ('alert', 'Alerte'), ('unsubscribed', 'Unsubscribed')], compute="_compute_status", string="Cooperative Status") working_mode = fields.Selection( [ ('regular', 'Regular worker'), ('irregular', 'Irregular worker'), ('exempt', 'Exempted'), ], - string="Working mode", + string="Working mode" ) + exempt_reason_id = fields.Many2one('cooperative.exempt.reason', 'Exempt Reason') + status = fields.Selection([('ok', 'Up to Date'), + ('holiday', 'Holidays'), + ('alert', 'Alerte'), + ('extension', 'Extension'), + ('suspended', 'Suspended'), + ('unsubscribed', 'Unsubscribed')], + compute="_compute_status", string="Cooperative Status", store=True) + can_shop = fields.Boolean(compute='_compute_status', store=True) + history_ids = fields.One2many('cooperative.status.history', 'status_id', readonly=True) + unsubscribed = fields.Boolean(default=False, help="Manually unsubscribed") + + + @api.depends('today', 'sr', 'sc', 'time_holiday', 'holiday_start_time', 'time_extension', 'alert_start_time', 'extension_start_time', 'unsubscribed') def _compute_status(self): + alert_delay = int(self.env['ir.config_parameter'].get_param('alert_delay', 28)) + grace_delay = int(self.env['ir.config_parameter'].get_param('default_grace_delay', 10)) + update = int(self.env['ir.config_parameter'].get_param('always_update', False)) + print update for rec in self: - rec.status = 'ok' + if update: + rec.status = 'ok' + rec.can_shop = True + continue + + ok = rec.sr >= 0 and rec.sc >= 0 + grace_delay = grace_delay + rec.time_extension + + if rec.sr < -1 or rec.unsubscribed: + rec.status = 'unsubscribed' + rec.can_shop = False + + #Transition to alert sr < 0 or stay in alert sr < 0 or sc < 0 and thus alert time is defined + elif not ok and rec.alert_start_time and rec.extension_start_time and rec.today <= add_days_delta(rec.extension_start_time, grace_delay): + rec.status = 'extension' + rec.can_shop = True + elif not ok and rec.alert_start_time and rec.extension_start_time and rec.today > add_days_delta(rec.extension_start_time, grace_delay): + rec.status = 'suspended' + rec.can_shop = False + elif not ok and rec.alert_start_time and rec.today > add_days_delta(rec.alert_start_time, alert_delay): + rec.status = 'suspended' + rec.can_shop = False + elif (rec.sr < 0) or (not ok and rec.alert_start_time): + rec.status = 'alert' + rec.can_shop = True + + #Check for holidays; Can be in holidays even in alert or other mode ? + elif rec.today >= rec.holiday_start_time and rec.today < add_days_delta(rec.holiday_start_time, rec.time_holiday): + rec.status = 'holiday' + rec.can_shop = True + elif ok or (not rec.alert_start_time and rec.sr >= 0): + rec.status = 'ok' + rec.can_shop = True + + + @api.multi + def write(self, vals): + """ + Overwrite write to historize the change + """ + for field in ['sr', 'sc', 'time_extension', 'extension_start_time', 'alert_start_time', 'unsubscribed']: + if not field in vals: + continue + for rec in self: + data = { + 'status_id': rec.id, + 'cooperator_id': rec.cooperator_id.id, + 'type': 'counter', + 'user_id': self.env.context.get('real_uid', self.env.uid), + } + if vals.get(field, rec[field]) != rec[field]: + data['change'] = '%s: %s -> %s' % (field.upper(), rec[field], vals.get(field)) + self.env['cooperative.status.history'].sudo().create(data) + return super(CooperativeStatus, self).write(vals) + + def _state_change(self, new_state, old_stage): + self.ensure_one() + if new_state == 'alert': + self.write({'alert_start_time': self.today, 'extension_start_time': False, 'time_extension': 0}) + if new_state == 'ok': #reset alert start time if back to ok + self.write({'alert_start_time': False, 'extension_start_time': False, 'time_extension': 0}) + if new_state == 'unsubscribed': + self.cooperator_id.sudo().write({'subscribed_shift_ids' : [(5,0,0)]}) + self.env['beesdoo.shift.shift'].sudo().unsubscribe_from_today([self.cooperator_id.id], today=self.today) + + @api.multi + def _write(self, vals): + """ + Overwrite write to historize the change of status + and make action on status change + """ + if 'status' in vals: + self._cr.execute('select id, status, sr, sc from "%s" where id in %%s' % self._table, (self._ids,)) + result = self._cr.dictfetchall() + old_status_per_id = {r['id'] : r for r in result} + for rec in self: + if old_status_per_id[rec.id]['status'] != vals['status']: + data = { + 'status_id': rec.id, + 'cooperator_id': rec.cooperator_id.id, + 'type': 'status', + 'change': "STATUS: %s -> %s" % (old_status_per_id[rec.id]['status'], vals['status']), + 'user_id': self.env.context.get('real_uid', self.env.uid), + } + self.env['cooperative.status.history'].sudo().create(data) + rec._state_change(vals['status'], old_status_per_id[rec.id]['status']) + return super(CooperativeStatus, self)._write(vals) _sql_constraints = [ ('cooperator_uniq', 'unique (cooperator_id)', _('You can only set one cooperator status per cooperator')), ] + @api.model + def _set_today(self): + """ + Method call by the cron to update store value base on the date + """ + self.search([]).write({'today': fields.Date.today()}) + + @api.multi + def clear_history(self): + self.ensure_one() + self.history_ids.unlink() + class ResPartner(models.Model): _inherit = 'res.partner' @@ -44,7 +186,11 @@ class ResPartner(models.Model): info_session = fields.Boolean(related='cooperative_status_ids.info_session', string='Information Session ?', readonly=True, store=True) info_session_date = fields.Datetime(related='cooperative_status_ids.info_session_date', string='Information Session Date', readonly=True, store=True) working_mode = fields.Selection(related='cooperative_status_ids.working_mode', readonly=True, store=True) + exempt_reason_id = fields.Many2one(related='cooperative_status_ids.exempt_reason_id', readonly=True, store=True) + state = fields.Selection(related='cooperative_status_ids.status', readonly=True, store=True) + extension_start_time = fields.Date(related='cooperative_status_ids.extension_start_time', string="Extension Start Day", readonly=True, store=True) subscribed_shift_ids = fields.Many2many('beesdoo.shift.template') + @api.multi def coop_subscribe(self): return { @@ -56,4 +202,28 @@ class ResPartner(models.Model): 'target': 'new', } + @api.multi + def coop_unsubscribe(self): + res = self.coop_subscribe() + res['context'] = {'default_unsubscribed': True} + return res + + @api.multi + def manual_extension(self): + return { + 'name': _('Manual Extension'), + 'type': 'ir.actions.act_window', + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'beesdoo.shift.extension', + 'target': 'new', + } + + @api.multi + def auto_extension(self): + res = self.manual_extension() + res['context'] = {'default_auto': True} + res['name'] = _('Trigger Grace Delay') + return res #TODO access right + vue on res.partner + #TODO can_shop : Status can_shop ou extempted ou part C diff --git a/beesdoo_shift/models/task.py b/beesdoo_shift/models/task.py index 5fc7992..4e0331d 100644 --- a/beesdoo_shift/models/task.py +++ b/beesdoo_shift/models/task.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from openerp import models, fields, api +from openerp.exceptions import UserError class TaskStage(models.Model): _name = 'beesdoo.shift.stage' @@ -8,6 +9,11 @@ class TaskStage(models.Model): name = fields.Char() sequence = fields.Integer() color = fields.Integer() + code = fields.Char(readonly=True) + + @api.multi + def unlink(self): + raise UserError(_("You Cannot delete Task Stage")) class Task(models.Model): @@ -49,4 +55,60 @@ class Task(models.Model): 'stage_id': _read_group_stage_id, } - #TODO button to replaced someone \ No newline at end of file + #TODO button to replaced someone + @api.model + def unsubscribe_from_today(self, worker_ids, today=None): + today = today or fields.Date.today() + today = today + ' 00:00:00' + to_unsubscribe = self.search([('worker_id', 'in', worker_ids), ('start_time', '>=', today)]) + to_unsubscribe.write({'worker_id': False, 'is_regular': False}) + #What about replacement ? + #Remove worker, replaced_id and regular + to_unsubscribe_replace = self.search([('replaced_id', 'in', worker_ids), ('start_time', '>=', today)]) + to_unsubscribe_replace.write({'worker_id': False, 'is_regular': False, 'replaced_id': False}) + + @api.multi + def write(self, vals): + """ + Overwrite write to track stage change + """ + if 'stage_id' in vals: + for rec in self: + if vals['stage_id'] != rec.stage_id.id: + rec._update_stage(rec.stage_id.id, vals['stage_id']) + return super(Task, self).write(vals) + + def _update_stage(self, old_stage, new_stage): + self.ensure_one() + update = int(self.env['ir.config_parameter'].get_param('always_update', False)) + if not (self.worker_id or self.replaced_id) or update: + return + new_stage = self.env['beesdoo.shift.stage'].browse(new_stage) + + if not self.replaced_id: #No replacement case + status = self.worker_id.cooperative_status_ids[0] + else: + status = self.replaced_id.cooperative_status_ids[0] + + data = {} + if new_stage == self.env.ref('beesdoo_shift.done') and self.is_regular: + pass + if new_stage == self.env.ref('beesdoo_shift.done') and not self.is_regular: + if status.sr < 0: + data['sr'] = status.sr + 1 + elif status.sc < 0: + data['sc'] = status.sc + 1 + else: + data['sr'] = status.sr + 1 + + if new_stage == self.env.ref('beesdoo_shift.absent') and not self.replaced_id: + data['sr'] = status.sr - 1 + if status.sr <= 0: + data['sc'] = status.sc -1 + if new_stage == self.env.ref('beesdoo_shift.absent') and self.replaced_id: + data['sr'] = status.sr -1 + + if new_stage == self.env.ref('beesdoo_shift.excused'): + data['sr'] = status.sr -1 + + status.sudo().write(data) diff --git a/beesdoo_shift/security/ir.model.access.csv b/beesdoo_shift/security/ir.model.access.csv index f90d443..9376d04 100644 --- a/beesdoo_shift/security/ir.model.access.csv +++ b/beesdoo_shift/security/ir.model.access.csv @@ -14,4 +14,5 @@ all_config_coopplanning_daynumber,Attendance Read Daynumber,model_beesdoo_shift_ all_config_coopplanning_planning,Attendance Read Planning,model_beesdoo_shift_planning,group_planning_management,1,1,1,1 all_config_coopplanning_task_template,Attendance Read Template,model_beesdoo_shift_template,group_planning_management,1,1,1,1 all_config_coopplanning_task,Attendance Edit Shift,model_beesdoo_shift_shift,group_planning_management,1,1,1,1 - +exempt_reason_read_all,Exempt Reason Read all ,beesdoo_shift.model_cooperative_exempt_reason,,1,0,0,0 +exempt_reason,Exempt Reason Admin,beesdoo_shift.model_cooperative_exempt_reason,beesdoo_shift.group_cooperative_admin,1,1,1,1 diff --git a/beesdoo_shift/views/cooperative_status.xml b/beesdoo_shift/views/cooperative_status.xml index b3e16d5..b650073 100644 --- a/beesdoo_shift/views/cooperative_status.xml +++ b/beesdoo_shift/views/cooperative_status.xml @@ -9,6 +9,24 @@