diff --git a/beesdoo_base/demo/cooperators.xml b/beesdoo_base/demo/cooperators.xml index 1f144fc..9150e25 100644 --- a/beesdoo_base/demo/cooperators.xml +++ b/beesdoo_base/demo/cooperators.xml @@ -83,4 +83,52 @@ 1060 + + + 421457731741 + Demo data + + + + + + + 429919251493 + Demo data + + + + + + + 421457731742 + Demo data + + + + + + + 421457731743 + Demo data + + + + + + + 421457731744 + Demo data + + + + + + + 421457731745 + Demo data + + + + diff --git a/beesdoo_base/views/partner.xml b/beesdoo_base/views/partner.xml index a903ff7..6b1b745 100644 --- a/beesdoo_base/views/partner.xml +++ b/beesdoo_base/views/partner.xml @@ -20,7 +20,8 @@ - + + weeks -1 - - - - - - Generate Attendance Sheets - - code - model._generate_attendance_sheet() - - 4 - minutes - -1 - - @@ -47,19 +32,7 @@ -1 - - - - Check for non-validated sheets - code - model._cron_non_validated_sheets() - 1 - days - -1 - - - diff --git a/beesdoo_shift/data/mail_template.xml b/beesdoo_shift/data/mail_template.xml index cdd1d49..5e5e7fa 100644 --- a/beesdoo_shift/data/mail_template.xml +++ b/beesdoo_shift/data/mail_template.xml @@ -3,116 +3,6 @@ - - Shift Non-attendance - Non-attendance to your last shift. - ${object.replaced_id.id or object.worker_id.id|safe} - - - ${object.worker_id.lang} - - - % if object.replaced_id: -

Hello ${object.replaced_id.name}, - -

You have been recorded as non-attended during your last shift (${format_tz(object.start_time,object.replaced_id.tz or 'Europe/Brussels','%d.%m.%Y - %H:%M')}), - and you were supposed to replace ${object.worker_id.name}. - % endif - - % if not object.replaced_id: -

Hello ${object.worker_id.name},

- -

You have been recorded as non-attended during your last shift (${format_tz(object.start_time,object.worker_id.tz or 'Europe/Brussels','%d.%m.%Y - %H:%M')}). - % endif - - % if object.worker_id.working_mode == 'regular': - % if object.state == 'absent_0': -

Super-cooperator assigned you 0 compensation, so you won't have any additionnal shift to do before your next regular shift. - % endif - % if object.state == 'absent_1': -

Super-cooperator assigned you 1 compensation, so you have to attend one additionnal shift before your next regular shift. - % endif - % if object.state == 'absent_2': -

Super-cooperator assigned you 2 compensations, so you have to attend two additionnal shifts before your next regular shift. - % endif - - % if object.replaced_id: - You were supposed to replace ${object.worker_id.name}. - You have to do ${(object.replaced_id.cooperative_status_ids.sr + object.replaced_id.cooperative_status_ids.sc) * -1 } shifts before your next regular shift.
- % else: - You have to do ${(object.worker_id.cooperative_status_ids.sr + object.worker_id.cooperative_status_ids.sc) * -1 } shifts before your next regular shift.
- % endif - % endif - - % if object.worker_id.working_mode == 'irregular': - Your shift counter is at ${object.worker_id.cooperative_status_ids.sr}. - - % if object.worker_id.cooperative_status_ids.future_alert_date: - It should be superior or equal to 1 before the - ${object.worker_id.cooperative_status_ids.future_alert_date}. - % endif -
- % endif - - % if object.replaced_id: - Your current status is "${object.replaced_id.cooperative_status_ids.get_status_value()}". - % else: -

Your current status is "${object.worker_id.cooperative_status_ids.get_status_value()}". - % endif - -
If you have any question regarding this non-attendance, just answer this e-mail. -

-
-

Cooperatively yours,
- The Members' office volunteers

-

${object.worker_id.company_id.name}.

- - % if object.worker_id.company_id.street: - ${object.worker_id.company_id.street} - % endif - % if object.worker_id.company_id.street2: - ${object.worker_id.company_id.street2}
- % endif - % if object.worker_id.company_id.city or object.worker_id.company_id.zip: - ${object.worker_id.company_id.zip} ${object.worker_id.company_id.city}
- % endif - % if object.worker_id.company_id.country_id: - ${object.worker_id.company_id.state_id and ('%s, ' % object.worker_id.company_id.state_id.name) or ''} ${object.worker_id.company_id.country_id.name or ''}
- % endif - % if object.worker_id.company_id.phone: - Phone:  ${object.worker_id.company_id.phone} - % endif - - % if object.worker_id.company_id.website: - - %endif - % if object.worker_id.company_id.logo_url: -
- -
- %endif - - ]]>
-
- - Non-validated sheet - [${object.day}] Non-validated sheet ${object.time_slot} - - - - -

${object.day} -

The attendance sheet for ${object.time_slot} is not validated. -

Please, do it as soon as possible so as to update workers' status. -

- - - ]]>
-
Shift Summary Your next shift (${format_tz(object.start_time,object.worker_id.tz or 'Europe/Brussels','%d.%m.%Y - %H:%M')}) diff --git a/beesdoo_shift/data/system_parameter.xml b/beesdoo_shift/data/system_parameter.xml index e1bd967..299b6db 100644 --- a/beesdoo_shift/data/system_parameter.xml +++ b/beesdoo_shift/data/system_parameter.xml @@ -31,16 +31,4 @@ regular_counter_to_unsubscribe -4 - - beesdoo_shift.card_support - False - - - beesdoo_shift.attendance_sheet_generation_interval - 15 - - - beesdoo_shift.pre_filled_task_type_id - 1 - diff --git a/beesdoo_shift/demo/cooperators.xml b/beesdoo_shift/demo/cooperators.xml new file mode 100644 index 0000000..8f1ebfd --- /dev/null +++ b/beesdoo_shift/demo/cooperators.xml @@ -0,0 +1,84 @@ + + + + + Fernand Peso + + + fernand_peso@demo.net + Avenue des Bas-de-Callanques, 15 + Etterbeek + 1040 + + regular + + + + + Dupont Dupont + + + d_dupont@demo.net + Rue des sables, 20 + Bruxelles + 10000 + + irregular + + + + + Ronan Le Gall + + + ronan_gall@demo.net + Rue des pecheurs, 23 + Landudec + 29710 + + regular + + + + + Elouan Bees + + + elouan_bees@demo.net + Rue Wéry, 15 + Ixelles + 1050 + + irregular + + + + + Anne de Marchalo + + + anne_marchalo@demo.net + Rue du Wels, 6 + Nantes + 44000 + + regular + + + + + Jean Beaumont + + + jean_beaumont@demo.net + Rue de la Jungle, 8 + St-Gilles + 1060 + + regular + + + diff --git a/beesdoo_shift/demo/templates.xml b/beesdoo_shift/demo/templates.xml index d635a58..fd88cea 100644 --- a/beesdoo_shift/demo/templates.xml +++ b/beesdoo_shift/demo/templates.xml @@ -75,7 +75,7 @@ 2.5 12 - +
@@ -88,7 +88,7 @@ 2.5 9 - + @@ -101,7 +101,7 @@ 2.5 7 - + diff --git a/beesdoo_shift/demo/workers.xml b/beesdoo_shift/demo/workers.xml index cb145d6..989577b 100644 --- a/beesdoo_shift/demo/workers.xml +++ b/beesdoo_shift/demo/workers.xml @@ -5,7 +5,7 @@ --> - + @@ -14,7 +14,7 @@ - + 2 irregular @@ -22,7 +22,7 @@ - + 2 @@ -30,101 +30,24 @@ - + 2 irregular - + 2 regular - + 2 regular - - regular - share_a - - - - irregular - share_a - - - - regular - share_a - - - - irregular - share_a - - - - regular - share_a - - - - regular - share_a - - - - 421457731741 - Demo data - - - - - - - 429919251493 - Demo data - - - - - - - 421457731742 - Demo data - - - - - - - 421457731743 - Demo data - - - - - - - 421457731744 - Demo data - - - - - - - 421457731745 - Demo data - - - - diff --git a/beesdoo_shift/models/__init__.py b/beesdoo_shift/models/__init__.py index dfc484a..ddc930b 100644 --- a/beesdoo_shift/models/__init__.py +++ b/beesdoo_shift/models/__init__.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- from . import task -from . import attendance_sheet from . import planning -from . import res_config_settings from . import cooperative_status diff --git a/beesdoo_shift/models/cooperative_status.py b/beesdoo_shift/models/cooperative_status.py index ed88b05..25ff584 100644 --- a/beesdoo_shift/models/cooperative_status.py +++ b/beesdoo_shift/models/cooperative_status.py @@ -5,7 +5,6 @@ from datetime import timedelta, datetime import logging _logger = logging.getLogger(__name__) -PERIOD = 28 # TODO: use system parameter def add_days_delta(date_from, days_delta): if not date_from: @@ -33,16 +32,19 @@ class CooperativeStatus(models.Model): _name = 'cooperative.status' _rec_name = 'cooperator_id' _order = 'cooperator_id' - - def get_status_value(self): - """ - Workararound to get translated selection value instead of key in mail template. - """ - - state_list = self.env["cooperative.status"]._fields["status"].selection - state_list = self.env["cooperative.status"]._fields['status']._description_selection(self.env) - - return dict(state_list)[self.status] + _period = 28 + + def _get_status(self): + return [ + ('ok', 'Up to Date'), + ('holiday', 'Holidays'), + ('alert', 'Alerte'), + ('extension', 'Extension'), + ('suspended', 'Suspended'), + ('exempted', 'Exempted'), + ('unsubscribed', 'Unsubscribed'), + ('resigning', 'Resigning') + ] 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) cooperator_id = fields.Many2one('res.partner') @@ -57,7 +59,6 @@ class CooperativeStatus(models.Model): holiday_end_time = fields.Date("Holidays End Day") alert_start_time = fields.Date("Alert Start Day") extension_start_time = fields.Date("Extension Start Day") - #Champ compute working_mode = fields.Selection( [ ('regular', 'Regular worker'), @@ -67,16 +68,9 @@ class CooperativeStatus(models.Model): string="Working mode" ) exempt_reason_id = fields.Many2one('cooperative.exempt.reason', 'Exempt Reason') - status = fields.Selection([('ok', 'Up to Date'), - ('holiday', 'Holidays'), - ('alert', 'Alert'), - ('extension', 'Extension'), - ('suspended', 'Suspended'), - ('exempted', 'Exempted'), - ('unsubscribed', 'Unsubscribed'), - ('resigning', 'Resigning')], + status = fields.Selection(selection=_get_status, compute="_compute_status", string="Cooperative Status", store=True) - can_shop = fields.Boolean(compute='_compute_status', store=True) + can_shop = fields.Boolean(compute='_compute_can_shop', store=True) history_ids = fields.One2many('cooperative.status.history', 'status_id', readonly=True) unsubscribed = fields.Boolean(default=False, help="Manually unsubscribed") resigning = fields.Boolean(default=False, help="Want to leave the beescoop") @@ -92,6 +86,10 @@ class CooperativeStatus(models.Model): temporary_exempt_start_date = fields.Date() temporary_exempt_end_date = fields.Date() + @api.depends('status') + def _compute_can_shop(self): + for rec in self: + rec.can_shop = rec.status in self._can_shop_status() @api.depends('today', 'sr', 'sc', 'holiday_end_time', 'holiday_start_time', 'time_extension', @@ -100,215 +98,32 @@ class CooperativeStatus(models.Model): 'irregular_absence_counter', 'temporary_exempt_start_date', 'temporary_exempt_end_date', 'resigning', 'cooperator_id.subscribed_shift_ids') def _compute_status(self): - alert_delay = int(self.env['ir.config_parameter'].sudo().get_param('alert_delay', 28)) - grace_delay = int(self.env['ir.config_parameter'].sudo().get_param('default_grace_delay', 10)) update = int(self.env['ir.config_parameter'].sudo().get_param('always_update', False)) for rec in self: if update or not rec.today: rec.status = 'ok' - rec.can_shop = True continue if rec.resigning: rec.status = 'resigning' - rec.can_shop = False continue if rec.working_mode == 'regular': - rec._set_regular_status(grace_delay, alert_delay) + rec.status = rec._get_regular_status() elif rec.working_mode == 'irregular': - rec._set_irregular_status(grace_delay, alert_delay) + rec.status = rec._get_irregular_status() elif rec.working_mode == 'exempt': rec.status = 'ok' - rec.can_shop = True - @api.depends('today', 'irregular_start_date', 'sr', 'holiday_start_time', - 'holiday_end_time', 'temporary_exempt_start_date', - 'temporary_exempt_end_date') - def _compute_future_alert_date(self): - """Compute date before which the worker is up to date""" - for rec in self: - # Only for irregular worker - if rec.working_mode != 'irregular' and not rec.irregular_start_date: - rec.future_alert_date = False - # Alert start time already set - elif rec.alert_start_time: - rec.future_alert_date = False - # Holidays are not set properly - elif bool(rec.holiday_start_time) != bool(rec.holiday_end_time): - rec.future_alert_date = False - # Exemption have not a start and end time - elif (bool(rec.temporary_exempt_start_date) - != bool(rec.temporary_exempt_end_date)): - rec.future_alert_date = False - else: - date = rec.today - counter = rec.sr - # Simulate the countdown - while counter > 0: - date = add_days_delta(date, 1) - date = self._next_countdown_date(rec.irregular_start_date, - date) - # Check holidays - if (rec.holiday_start_time and rec.holiday_end_time - and date >= rec.holiday_start_time - and date <= rec.holiday_end_time): - continue - # Check temporary exemption - elif (rec.temporary_exempt_start_date - and rec.temporary_exempt_end_date - and date >= rec.temporary_exempt_start_date - and date <= rec.temporary_exempt_end_date): - continue - else: - counter -= 1 - rec.future_alert_date = self._next_countdown_date( - rec.irregular_start_date, date - ) - - @api.depends('today', 'irregular_start_date', 'holiday_start_time', - 'holiday_end_time', 'temporary_exempt_start_date', - 'temporary_exempt_end_date') - def _compute_next_countdown_date(self): - """ - Compute the following countdown date. This date is the date when - the worker will see his counter changed du to the cron. This - date is like the birthday date of the worker that occurred each - PERIOD. - """ - for rec in self: - # Only for irregular worker - if rec.working_mode != 'irregular' and not rec.irregular_start_date: - rec.next_countdown_date = False - # Holidays are not set properly - elif bool(rec.holiday_start_time) != bool(rec.holiday_end_time): - rec.next_countdown_date = False - # Exemption have not a start and end time - elif (bool(rec.temporary_exempt_start_date) - != bool(rec.temporary_exempt_end_date)): - rec.next_countdown_date = False - else: - date = rec.today - next_countdown_date = False - while not next_countdown_date: - date = self._next_countdown_date(rec.irregular_start_date, date) - # Check holidays - if (rec.holiday_start_time and rec.holiday_end_time - and date >= rec.holiday_start_time - and date <= rec.holiday_end_time): - date = add_days_delta(date, 1) - continue - # Check temporary exemption - elif (rec.temporary_exempt_start_date - and rec.temporary_exempt_end_date - and date >= rec.temporary_exempt_start_date - and date <= rec.temporary_exempt_end_date): - date = add_days_delta(date, 1) - continue - else: - next_countdown_date = date - rec.next_countdown_date = next_countdown_date + + _sql_constraints = [ + ('cooperator_uniq', 'unique (cooperator_id)', _('You can only set one cooperator status per cooperator')), + ] @api.constrains("working_mode", "irregular_start_date") def _constrains_irregular_start_date(self): if self.working_mode == "irregular" and not self.irregular_start_date: raise UserError(_("Irregular workers must have an irregular start date.")) - def _next_countdown_date(self, irregular_start_date, today=False): - """ - Return the next countdown date given irregular_start_date and - today dates. - This does not take holiday and other status into account. - """ - today = today or fields.Date.today() - - delta = (today - irregular_start_date).days - if not delta % PERIOD: - return today - return add_days_delta(today, PERIOD - (delta % PERIOD)) - - def _set_regular_status(self, grace_delay, alert_delay): - self.ensure_one() - counter_unsubscribe = int(self.env['ir.config_parameter'].sudo().get_param('regular_counter_to_unsubscribe', -4)) - ok = self.sr >= 0 and self.sc >= 0 - grace_delay = grace_delay + self.time_extension - - if (self.sr + self.sc) <= counter_unsubscribe or self.unsubscribed: - self.status = 'unsubscribed' - self.can_shop = False - #Check if exempted. Exempt end date is not required. - elif self.temporary_exempt_start_date and self.today >= self.temporary_exempt_start_date: - if not self.temporary_exempt_end_date or self.today <= self.temporary_exempt_end_date: - self.status = 'exempted' - self.can_shop = True - - #Transition to alert sr < 0 or stay in alert sr < 0 or sc < 0 and thus alert time is defined - 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): - self.status = 'extension' - self.can_shop = True - 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): - self.status = 'suspended' - self.can_shop = False - elif not ok and self.alert_start_time and self.today > add_days_delta(self.alert_start_time, alert_delay): - self.status = 'suspended' - self.can_shop = False - elif (self.sr < 0) or (not ok and self.alert_start_time): - self.status = 'alert' - self.can_shop = True - - #Check for holidays; Can be in holidays even in alert or other mode ? - elif ( - self.holiday_start_time - and self.holiday_end_time - and self.today >= self.holiday_start_time - and self.today <= self.holiday_end_time - ): - - self.status = 'holiday' - self.can_shop = False - elif ok or (not self.alert_start_time and self.sr >= 0): - self.status = 'ok' - self.can_shop = True - - def _set_irregular_status(self, grace_delay, alert_delay): - counter_unsubscribe = int(self.env['ir.config_parameter'].sudo().get_param('irregular_counter_to_unsubscribe', -3)) - self.ensure_one() - ok = self.sr >= 0 - grace_delay = grace_delay + self.time_extension - if self.sr <= counter_unsubscribe or self.unsubscribed: - self.status = 'unsubscribed' - self.can_shop = False - #Check if exempted. Exempt end date is not required. - elif self.temporary_exempt_start_date and self.today >= self.temporary_exempt_start_date: - if not self.temporary_exempt_end_date or self.today <= self.temporary_exempt_end_date: - self.status = 'exempted' - self.can_shop = True - #Transition to alert sr < 0 or stay in alert sr < 0 or sc < 0 and thus alert time is defined - 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): - self.status = 'extension' - self.can_shop = True - 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): - self.status = 'suspended' - self.can_shop = False - elif not ok and self.alert_start_time and self.today > add_days_delta(self.alert_start_time, alert_delay): - self.status = 'suspended' - self.can_shop = False - elif (self.sr < 0) or (not ok and self.alert_start_time): - self.status = 'alert' - self.can_shop = True - - #Check for holidays; Can be in holidays even in alert or other mode ? - elif ( - self.holiday_start_time - and self.holiday_end_time - and self.today >= self.holiday_start_time - and self.today <= self.holiday_end_time - ): - self.status = 'holiday' - self.can_shop = False - elif ok or (not self.alert_start_time and self.sr >= 0): - self.status = 'ok' - self.can_shop = True - @api.multi def write(self, vals): """ @@ -329,34 +144,6 @@ class CooperativeStatus(models.Model): self.env['cooperative.status.history'].sudo().create(data) return super(CooperativeStatus, self).write(vals) - def _state_change(self, new_state): - 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': - data = {'extension_start_time': False, 'time_extension': 0} - data['alert_start_time'] = False - self.write(data) - if new_state == 'unsubscribed' or new_state == 'resigning': - # Remove worker from task_templates - self.cooperator_id.sudo().write( - {'subscribed_shift_ids': [(5, 0, 0)]}) - # Remove worker from supercoop in task_templates - task_tpls = self.env['beesdoo.shift.template'].search( - [('super_coop_id', 'in', self.cooperator_id.user_ids.ids)] - ) - task_tpls.write({'super_coop_id': False}) - # Remove worker for future tasks (remove also supercoop) - self.env['beesdoo.shift.shift'].sudo().unsubscribe_from_today( - [self.cooperator_id.id], now=fields.Datetime.now() - ) - - def _change_counter(self, data): - self.sc += data.get('sc', 0) - self.sr += data.get('sr', 0) - self.irregular_absence_counter += data.get('irregular_absence_counter', 0) - self.irregular_absence_date = data.get('irregular_absence_date', False) - @api.multi def _write(self, vals): """ @@ -380,9 +167,12 @@ class CooperativeStatus(models.Model): rec._state_change(vals['status']) return super(CooperativeStatus, self)._write(vals) - _sql_constraints = [ - ('cooperator_uniq', 'unique (cooperator_id)', _('You can only set one cooperator status per cooperator')), - ] + def get_status_value(self): + """ + Workararound to get translated selection value instead of key in mail template. + """ + state_list = self.env["cooperative.status"]._fields['status']._description_selection(self.env) + return dict(state_list)[self.status] @api.model def _set_today(self): @@ -391,40 +181,119 @@ class CooperativeStatus(models.Model): """ self.search([]).write({'today': fields.Date.today()}) - @api.multi - def clear_history(self): - self.ensure_one() - self.history_ids.unlink() - @api.model def _cron_compute_counter_irregular(self, today=False): + """ + Journal ensure that a irregular worker will be only check + once per day + """ today = today or fields.Date.today() journal = self.env['beesdoo.shift.journal'].search([('date', '=', today)]) if not journal: journal = self.env['beesdoo.shift.journal'].create({'date': today}) - domain = ['&', - '&', - '&', ('status', '!=', 'unsubscribed'), - ('working_mode', '=', 'irregular'), - ('irregular_start_date', '!=', False), - '|', - '|', ('holiday_start_time', '=', False), ('holiday_end_time', '=', False), - '|', ('holiday_start_time', '>', today), ('holiday_end_time', '<', today), - ] + + domain = self._get_irregular_worker_domain() irregular = self.search(domain) for status in irregular: - if status.status == 'exempted': - continue delta = (today - status.irregular_start_date).days - if delta and delta % PERIOD == 0 and status not in journal.line_ids: - if status.sr > 0: - status.sr -= 1 - elif status.alert_start_time: - status.sr -= 1 - else: - status.sr -= 2 + if delta and delta % self._period == 0 and status not in journal.line_ids: + status._change_irregular_counter() journal.line_ids |= status + @api.multi + def clear_history(self): + self.ensure_one() + self.history_ids.unlink() + + ######################################################## + # Method to override # + # To define the behavior of the status # + # # + # By default: everyone is always up to date # + ######################################################## + + ############################## + # Computed field section # + ############################## + @api.depends('today') + def _compute_future_alert_date(self): + """ + Compute date until the worker is up to date + for irregular worker + """ + for rec in self: + rec.future_alert_date = False + + @api.depends('today') + def _compute_next_countdown_date(self): + """ + Compute the following countdown date. This date is the date when + the worker will see his counter changed due to the cron. This + date is like the birthday date of the worker that occurred each + _period. + """ + for rec in self: + rec.next_countdown_date = False + + def _can_shop_status(self): + """ + return the list of status that give access + to active cooperator privilege + """ + return ['ok', 'alert', 'extension', 'exempted'] + + ##################################### + # Status Change implementation # + ##################################### + + def _get_regular_status(self): + """ + Return the value of the status + for the regular worker + """ + return 'ok' + + def _get_irregular_status(self): + """ + Return the value of the status + for the irregular worker + """ + return 'ok' + + def _state_change(self, new_state): + """ + Hook to watch change in the state + """ + pass + + def _change_counter(self, data): + """ + Call when a shift state is changed + use data generated by _get_counter_date_state_change + """ + pass + + + ############################################### + ###### Irregular Cron implementation ########## + ############################################### + + def _get_irregular_worker_domain(self): + """ + return the domain the give the list + of valid irregular worker that should + get their counter changed by the cron + """ + return [(0, '=', 1)] + + def _change_irregular_counter(self): + """ + Define how the counter will change + for the irregular worker + where today - start_date is a multiple of the period + by default 28 days + """ + pass class ShiftCronJournal(models.Model): _name = 'beesdoo.shift.journal' @@ -452,6 +321,8 @@ class ResPartner(models.Model): """ _inherit = 'res.partner' + worker_store = fields.Boolean(default=False) + is_worker = fields.Boolean(related="worker_store", string="Worker", readonly=False) cooperative_status_ids = fields.One2many('cooperative.status', 'cooperator_id', readonly=True) super = fields.Boolean(related='cooperative_status_ids.super', string="Super Cooperative", readonly=True, store=True) info_session = fields.Boolean(related='cooperative_status_ids.info_session', string='Information Session ?', readonly=True, store=True) @@ -520,4 +391,3 @@ class ResPartner(models.Model): } #TODO access right + vue on res.partner - #TODO can_shop : Status can_shop ou extempted ou part C diff --git a/beesdoo_shift/models/planning.py b/beesdoo_shift/models/planning.py index 593bd64..45b3635 100644 --- a/beesdoo_shift/models/planning.py +++ b/beesdoo_shift/models/planning.py @@ -89,7 +89,7 @@ class TaskTemplate(models.Model): duration = fields.Float(help="Duration in Hour") worker_nb = fields.Integer(string="Number of worker", help="Max number of worker for this task", default=1) - worker_ids = fields.Many2many('res.partner', string="Recurrent worker assigned", domain=[('eater', '=', 'worker_eater'), ('working_mode', '=', 'regular')]) + worker_ids = fields.Many2many('res.partner', string="Recurrent worker assigned", domain=[('is_worker', '=', True)]) remaining_worker = fields.Integer(compute="_get_remaining", store=True, string="Remaining Place") active = fields.Boolean(default=True) #For Kanban View Only diff --git a/beesdoo_shift/models/task.py b/beesdoo_shift/models/task.py index 98cf4e8..0215fdb 100644 --- a/beesdoo_shift/models/task.py +++ b/beesdoo_shift/models/task.py @@ -13,31 +13,50 @@ class Task(models.Model): _order = "start_time asc" + ################################## + # Method to override # + # to have different state # + # on the shift # + ################################## + def _get_selection_status(self): + return [ + ("open","Confirmed"), + ("done","Attended"), + ("absent","Absent"), + ("excused","Excused"), + ("cancel","Cancelled") + ] + + def _get_color_mapping(state): + return { + "draft": 0, + "open": 1, + "done": 5, + "absent": 2, + "excused": 3, + "cancel": 9, + }[state] + + def _get_final_state(): + return ["done", "absent", "excused"] + name = fields.Char(track_visibility='always') task_template_id = fields.Many2one('beesdoo.shift.template') planning_id = fields.Many2one(related='task_template_id.planning_id', store=True) task_type_id = fields.Many2one('beesdoo.shift.type', string="Task Type") worker_id = fields.Many2one('res.partner', track_visibility='onchange', domain=[ - ('eater', '=', 'worker_eater'), + ('is_worker', '=', True), ('working_mode', 'in', ('regular', 'irregular')), ('state', 'not in', ('unsubscribed', 'resigning')), ]) start_time = fields.Datetime(track_visibility='always', index=True, required=True) end_time = fields.Datetime(track_visibility='always', required=True) - state = fields.Selection(selection=[ - ("draft","Unconfirmed"), - ("open","Confirmed"), - ("done","Attended"), - ("absent_2","Absent - 2 compensations"), - ("absent_1","Absent - 1 compensation"), - ("absent_0","Absent - 0 compensation"), - ("cancel","Cancelled") - ], + state = fields.Selection(selection=_get_selection_status, default="open", required=True, - store=True, track_visibility='onchange', + group_expand='_expand_states' ) color = fields.Integer(compute="_compute_color") super_coop_id = fields.Many2one('res.users', string="Super Cooperative", domain=[('partner_id.super', '=', True)], track_visibility='onchange') @@ -52,19 +71,14 @@ class Task(models.Model): revert_info = fields.Text(copy=False) working_mode = fields.Selection(related='worker_id.working_mode') + def _expand_states(self, states, domain, order): + return [key for key, val in self._fields['state'].selection] + + @api.depends("state") def _compute_color(self): - color_mapping = { - "draft": 0, - "open": 0, - "done": 10, - "absent_2": 1, - "absent_1": 2, - "absent_0": 3, - "cancel": 5, - } for rec in self: - rec.color = color_mapping[rec.state] + rec.color = self._state_color_mapping(rec.state) def _compensation_validation(self, task): """ @@ -81,7 +95,7 @@ class Task(models.Model): @api.constrains("state") def _lock_future_task(self): if datetime.now() < self.start_time: - if self.state in ["done", "absent_2", "absent_1", "absent_0"]: + if self.state in self._get_final_state(): raise UserError(_( "Shift state of a future shift " "can't be set to 'present' or 'absent'." @@ -213,53 +227,19 @@ class Task(models.Model): def _update_state(self, new_state): self.ensure_one() self._revert() - update = int(self.env['ir.config_parameter'].sudo().get_param('always_update', False)) data = {} - if not (self.worker_id or self.replaced_id) and new_state in ("done", "absent_0", "absent_1", "absent_2"): + if not (self.worker_id or self.replaced_id) and new_state in self._get_final_state(): raise UserError(_("You cannot change to the status %s if no worker is defined for the shift") % new_state) + if not (self.worker_id.working_mode in ['regular', 'irregular']): + raise UserError(_("Working mode is not properly defined. Please check if the worker is subscribed")) - if update or not (self.worker_id or self.replaced_id): + always_update = int(self.env['ir.config_parameter'].sudo().get_param('always_update', False)) + if always_update or not (self.worker_id or self.replaced_id): return - if self.worker_id.working_mode == 'regular': - 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] - - if new_state == "done" and not self.is_regular: - # Regular counter is always updated first - if status.sr < 0: - data['sr'] = 1 - elif status.sc < 0: - data['sc'] = 1 - # Bonus shift case - else: - data['sr'] = 1 - - if new_state == "absent_2": - data['sr'] = -1 - data['sc'] = -1 - - if new_state == "absent_1": - data['sr'] = -1 - - elif self.worker_id.working_mode == 'irregular': - status = self.worker_id.cooperative_status_ids[0] - if new_state == "done" or new_state == "absent_0": - data['sr'] = 1 - data['irregular_absence_date'] = False - data['irregular_absence_counter'] = 1 if status.irregular_absence_counter < 0 else 0 - if new_state == "absent_2" or new_state == "absent_1": - if new_state == "absent_2": - data['sr'] = -1 - data['irregular_absence_date'] = self.start_time.date() - data['irregular_absence_counter'] = -1 - - else: - raise UserError(_("Working mode is not properly defined. Please check if the worker is subscribed")) + data = self._get_counter_date_state_change(new_state) status.sudo()._change_counter(data) self._set_revert_info(data, status) @@ -288,3 +268,20 @@ class Task(models.Model): for rec in confirmed_tasks: shift_summary_mail_template.send_mail(rec.id, True) + + ######################################################## + # Method to override # + # To define the behavior of the status # + # # + # By default: everyone is always up to date # + ######################################################## + + def _get_counter_date_state_change(self, new_state): + """ + Return the data to change counter or other things + that change on the cooperator status + see _change_counter + + We have eheck the worker is legitimate + """ + return {} diff --git a/beesdoo_shift/security/group.xml b/beesdoo_shift/security/group.xml index bc64bed..4540e32 100644 --- a/beesdoo_shift/security/group.xml +++ b/beesdoo_shift/security/group.xml @@ -1,14 +1,4 @@ - - Attendance Sheet Generic Access - - - - Attendance Sheet Validation - - - Shift and Worker Read Access @@ -28,11 +18,10 @@ Cooperative Admin - - + + diff --git a/beesdoo_shift/security/ir.model.access.csv b/beesdoo_shift/security/ir.model.access.csv index 76458a3..4f97611 100644 --- a/beesdoo_shift/security/ir.model.access.csv +++ b/beesdoo_shift/security/ir.model.access.csv @@ -1,14 +1,4 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink - -create_beesdoo_shift_shift,create_edit_beesdoo_shift_shift,model_beesdoo_shift_shift,group_shift_attendance_sheet,1,1,1,0 -read_beesdoo_shift_sheet_shift,read_beesdoo_shift_sheet_shift,model_beesdoo_shift_sheet_shift,group_shift_attendance_sheet,1,0,0,0 -create_beesdoo_shift_sheet_shift,create_beesdoo_shift_sheet_shift,model_beesdoo_shift_sheet_shift,group_shift_attendance_sheet,1,1,1,0 -create_beesdoo_shift_sheet_expected,create_beesdoo_shift_sheet_expected,model_beesdoo_shift_sheet_expected,group_shift_attendance_sheet,1,1,1,0 -manage_beesdoo_shift_sheet_added,manage_beesdoo_shift_sheet_added,model_beesdoo_shift_sheet_added,group_shift_attendance_sheet,1,1,1,1 -create_beesdoo_shift_sheet,create_beesdoo_shift_sheet,model_beesdoo_shift_sheet,group_shift_attendance_sheet,1,1,1,0 -sheet_access_beesdoo_shift_template,sheet_access_beesdoo_shift_template,model_beesdoo_shift_template,group_shift_attendance_sheet,1,0,0,0 -sheet_access_beesdoo_shift_type,sheet_access_beesdoo_shift_type,model_beesdoo_shift_type,group_shift_attendance_sheet,1,0,0,0 -access_beesdoo_shift_daynumber,access_beesdoo_shift_daynumber,model_beesdoo_shift_daynumber,group_shift_attendance_sheet,1,0,0,0 read_beesdoo_shift_planning,read_beesdoo_shift_planning,model_beesdoo_shift_planning,,1,0,0,0 access_beesdoo_shift_template,access_beesdoo_shift_template,model_beesdoo_shift_template,group_shift_attendance,1,0,0,0 write_beesdoo_shift_shift,write_beesdoo_shift_shift,model_beesdoo_shift_shift,group_shift_attendance,1,1,0,0 @@ -17,8 +7,6 @@ manage_beesdoo_shift_type,manage_beesdoo_shift_type,model_beesdoo_shift_type,gro manage_beesdoo_shift_daynumber,manage_beesdoo_shift_daynumber,model_beesdoo_shift_daynumber,group_planning_management,1,1,1,1 manage_beesdoo_shift_planning,manage_beesdoo_shift_planning,model_beesdoo_shift_planning,group_planning_management,1,1,1,1 manage_beesdoo_shift_template,manage_beesdoo_shift_template,model_beesdoo_shift_template,group_planning_management,1,1,1,1 -manage_beesdoo_shift_sheet_shift,beesdoo_shift_sheet_shift,model_beesdoo_shift_sheet_shift,group_shift_attendance,1,1,1,1 -manage_beesdoo_shift_sheet_expected,manage_beesdoo_shift_sheet_expected,model_beesdoo_shift_sheet_expected,group_shift_attendance,1,1,1,1 manage_cooperative_status,manage_cooperative_status,model_cooperative_status,group_cooperative_admin,1,1,1,1 manage_cooperative_exempt_reason,manage_cooperative_exempt_reason,model_cooperative_exempt_reason,group_cooperative_admin,1,1,1,1 read_beesdoo_shift_journal,read_beesdoo_shift_journal,model_beesdoo_shift_journal,group_cooperative_admin,1,0,1,1 @@ -26,5 +14,4 @@ access_cooperative_status,access_cooperative_status,model_cooperative_status,,1, read_cooperative_exempt_reason,read_cooperative_exempt_reason,model_cooperative_exempt_reason,,1,0,0,0 read_cooperative_status_history,read_cooperative_status_history,model_cooperative_status_history,,1,0,0,0 access_beesdoo_shift_type,access_beesdoo_shift_type,model_beesdoo_shift_type,group_shift_attendance,1,0,0,0 -access_beesdoo_shift_type,access_beesdoo_shift_type,model_beesdoo_shift_daynumber,group_shift_attendance,1,0,0,0 -access_beesdoo_shift_template,access_beesdoo_shift_template,model_beesdoo_shift_template,group_shift_attendance,1,0,0,0 +access_attendance_beesdoo_shift_daynumber,access_beesdoo_shift_daynumber,model_beesdoo_shift_daynumber,group_shift_attendance,1,0,0,0 diff --git a/beesdoo_shift/views/cooperative_status.xml b/beesdoo_shift/views/cooperative_status.xml index 85cca36..414b62e 100644 --- a/beesdoo_shift/views/cooperative_status.xml +++ b/beesdoo_shift/views/cooperative_status.xml @@ -2,57 +2,65 @@ Partner Super Coop res.partner - + 50
- - - - - - - + + + + + + - - - + + + + + + + + + + - - - - + + + +
@@ -175,7 +183,7 @@ Worker res.partner kanban,tree,form - [('cooperator_type', '=', 'share_a')] + [('is_worker', '=', True)]
+ + + Generate Attendance Sheets + + code + model._generate_attendance_sheet() + + 4 + minutes + -1 + + + + + + Check for non-validated sheets + + code + model._cron_non_validated_sheets() + 1 + days + -1 + + + + + +
diff --git a/beesdoo_shift_atttendance/data/mail_template.xml b/beesdoo_shift_atttendance/data/mail_template.xml new file mode 100644 index 0000000..30a940a --- /dev/null +++ b/beesdoo_shift_atttendance/data/mail_template.xml @@ -0,0 +1,117 @@ + + + + + + Shift Non-attendance + Non-attendance to your last shift. + ${object.replaced_id.id or object.worker_id.id|safe} + + + ${object.worker_id.lang} + + + % if object.replaced_id: +

Hello ${object.replaced_id.name}, + +

You have been recorded as non-attended during your last shift (${format_tz(object.start_time,object.replaced_id.tz or 'Europe/Brussels','%d.%m.%Y - %H:%M')}), + and you were supposed to replace ${object.worker_id.name}. + % endif + + % if not object.replaced_id: +

Hello ${object.worker_id.name},

+ +

You have been recorded as non-attended during your last shift (${format_tz(object.start_time,object.worker_id.tz or 'Europe/Brussels','%d.%m.%Y - %H:%M')}). + % endif + + % if object.worker_id.working_mode == 'regular': + % if object.state == 'absent_0': +

Super-cooperator assigned you 0 compensation, so you won't have any additionnal shift to do before your next regular shift. + % endif + % if object.state == 'absent_1': +

Super-cooperator assigned you 1 compensation, so you have to attend one additionnal shift before your next regular shift. + % endif + % if object.state == 'absent_2': +

Super-cooperator assigned you 2 compensations, so you have to attend two additionnal shifts before your next regular shift. + % endif + + % if object.replaced_id: + You were supposed to replace ${object.worker_id.name}. + You have to do ${(object.replaced_id.cooperative_status_ids.sr + object.replaced_id.cooperative_status_ids.sc) * -1 } shifts before your next regular shift.
+ % else: + You have to do ${(object.worker_id.cooperative_status_ids.sr + object.worker_id.cooperative_status_ids.sc) * -1 } shifts before your next regular shift.
+ % endif + % endif + + % if object.worker_id.working_mode == 'irregular': + Your shift counter is at ${object.worker_id.cooperative_status_ids.sr}. + + % if object.worker_id.cooperative_status_ids.future_alert_date: + It should be superior or equal to 1 before the + ${object.worker_id.cooperative_status_ids.future_alert_date}. + % endif +
+ % endif + + % if object.replaced_id: + Your current status is "${object.replaced_id.cooperative_status_ids.get_status_value()}". + % else: +

Your current status is "${object.worker_id.cooperative_status_ids.get_status_value()}". + % endif + +
If you have any question regarding this non-attendance, just answer this e-mail. +

+
+

Cooperatively yours,
+ The Members' office volunteers

+

${object.worker_id.company_id.name}.

+ + % if object.worker_id.company_id.street: + ${object.worker_id.company_id.street} + % endif + % if object.worker_id.company_id.street2: + ${object.worker_id.company_id.street2}
+ % endif + % if object.worker_id.company_id.city or object.worker_id.company_id.zip: + ${object.worker_id.company_id.zip} ${object.worker_id.company_id.city}
+ % endif + % if object.worker_id.company_id.country_id: + ${object.worker_id.company_id.state_id and ('%s, ' % object.worker_id.company_id.state_id.name) or ''} ${object.worker_id.company_id.country_id.name or ''}
+ % endif + % if object.worker_id.company_id.phone: + Phone:  ${object.worker_id.company_id.phone} + % endif + + % if object.worker_id.company_id.website: + + %endif + % if object.worker_id.company_id.logo_url: +
+ +
+ %endif + + ]]>
+
+ + Non-validated sheet + [${object.day}] Non-validated sheet ${object.time_slot} + + + + +

${object.day} +

The attendance sheet for ${object.time_slot} is not validated. +

Please, do it as soon as possible so as to update workers' status. +

+ + + ]]>
+
+
+
diff --git a/beesdoo_shift_atttendance/data/system_parameter.xml b/beesdoo_shift_atttendance/data/system_parameter.xml new file mode 100644 index 0000000..342f54c --- /dev/null +++ b/beesdoo_shift_atttendance/data/system_parameter.xml @@ -0,0 +1,14 @@ + + + beesdoo_shift.card_support + False + + + beesdoo_shift.attendance_sheet_generation_interval + 15 + + + beesdoo_shift.default_task_type_id + 1 + + diff --git a/beesdoo_shift_atttendance/models/__init__.py b/beesdoo_shift_atttendance/models/__init__.py new file mode 100644 index 0000000..800e01c --- /dev/null +++ b/beesdoo_shift_atttendance/models/__init__.py @@ -0,0 +1,2 @@ +from . import attendance_sheet +from . import res_config_settings diff --git a/beesdoo_shift/models/attendance_sheet.py b/beesdoo_shift_atttendance/models/attendance_sheet.py similarity index 99% rename from beesdoo_shift/models/attendance_sheet.py rename to beesdoo_shift_atttendance/models/attendance_sheet.py index 4b39e78..6530501 100644 --- a/beesdoo_shift/models/attendance_sheet.py +++ b/beesdoo_shift_atttendance/models/attendance_sheet.py @@ -30,7 +30,7 @@ class AttendanceSheetShift(models.Model): ) ) task_types = self.env["beesdoo.shift.type"] - return task_types.browse(id) + return task_types.browse(default_task_type_id) # Related actual shift task_id = fields.Many2one("beesdoo.shift.shift", string="Task") @@ -54,7 +54,7 @@ class AttendanceSheetShift(models.Model): "res.partner", string="Worker", domain=[ - ("eater", "=", "worker_eater"), + ("is_worker", "=", True), ("working_mode", "in", ("regular", "irregular")), ("state", "not in", ("unsubscribed", "resigning")), ], diff --git a/beesdoo_shift_atttendance/models/res_config_settings.py b/beesdoo_shift_atttendance/models/res_config_settings.py new file mode 100644 index 0000000..9e6090e --- /dev/null +++ b/beesdoo_shift_atttendance/models/res_config_settings.py @@ -0,0 +1,61 @@ +# Copyright 2019-2020 Elouan Le Bars +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import ast + +from odoo import fields, models, api + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + card_support = fields.Boolean( + string="Scan cooperators cards instead of login for sheets validation", + config_parameter="beesdoo_shift.card_support", + ) + task_type_default_id = fields.Many2one( + "beesdoo.shift.type", + string="Default Task Type", + help="Default task type for attendance sheet pre-filling", + required=True, + default=False, + ) + attendance_sheet_generation_interval = fields.Integer( + string="Time interval for attendance sheet generation", + help="Time interval expressed in minutes", + required=True, + config_parameter="beesdoo_shift.attendance_sheet_generation_interval", + ) + + @api.multi + def set_values(self): + super(ResConfigSettings, self).set_values() + parameters = self.env["ir.config_parameter"].sudo() + parameters.set_param( + "beesdoo_shift.card_support", str(self.card_support), + ) + parameters.set_param( + "beesdoo_shift.task_type_default_id", + str(self.task_type_default_id.id), + ) + parameters.set_param( + "beesdoo_shift.attendance_sheet_generation_interval", + str(self.attendance_sheet_generation_interval), + ) + + @api.multi + def get_values(self): + res = super(ResConfigSettings, self).get_values() + res.update( + card_support=ast.literal_eval( + self.env["ir.config_parameter"].get_param( + "beesdoo_shift.card_support" + ), + ), + task_type_default_id=int( + self.env["ir.config_parameter"].get_param( + "beesdoo_shift.task_type_default_id" + ) + ), + ) + return res diff --git a/beesdoo_shift_atttendance/security/group.xml b/beesdoo_shift_atttendance/security/group.xml new file mode 100644 index 0000000..9063849 --- /dev/null +++ b/beesdoo_shift_atttendance/security/group.xml @@ -0,0 +1,11 @@ + + + Attendance Sheet Generic Access + + + + + + diff --git a/beesdoo_shift_atttendance/security/ir.model.access.csv b/beesdoo_shift_atttendance/security/ir.model.access.csv new file mode 100644 index 0000000..077ce09 --- /dev/null +++ b/beesdoo_shift_atttendance/security/ir.model.access.csv @@ -0,0 +1,12 @@ +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +create_beesdoo_shift_shift,create_edit_beesdoo_shift_shift,model_beesdoo_shift_shift,group_shift_attendance_sheet,1,1,1,0 +read_beesdoo_shift_sheet_shift,read_beesdoo_shift_sheet_shift,model_beesdoo_shift_sheet_shift,group_shift_attendance_sheet,1,0,0,0 +create_beesdoo_shift_sheet_shift,create_beesdoo_shift_sheet_shift,model_beesdoo_shift_sheet_shift,group_shift_attendance_sheet,1,1,1,0 +create_beesdoo_shift_sheet_expected,create_beesdoo_shift_sheet_expected,model_beesdoo_shift_sheet_expected,group_shift_attendance_sheet,1,1,1,0 +manage_beesdoo_shift_sheet_added,manage_beesdoo_shift_sheet_added,model_beesdoo_shift_sheet_added,group_shift_attendance_sheet,1,1,1,1 +create_beesdoo_shift_sheet,create_beesdoo_shift_sheet,model_beesdoo_shift_sheet,group_shift_attendance_sheet,1,1,1,0 +sheet_access_beesdoo_shift_template,sheet_access_beesdoo_shift_template,model_beesdoo_shift_template,group_shift_attendance_sheet,1,0,0,0 +sheet_access_beesdoo_shift_type,sheet_access_beesdoo_shift_type,model_beesdoo_shift_type,group_shift_attendance_sheet,1,0,0,0 +access_beesdoo_shift_daynumber,access_beesdoo_shift_daynumber,model_beesdoo_shift_daynumber,group_shift_attendance_sheet,1,0,0,0 +manage_beesdoo_shift_sheet_shift,beesdoo_shift_sheet_shift,model_beesdoo_shift_sheet_shift,group_shift_attendance,1,1,1,1 +manage_beesdoo_shift_sheet_expected,manage_beesdoo_shift_sheet_expected,model_beesdoo_shift_sheet_expected,group_shift_attendance,1,1,1,1 \ No newline at end of file diff --git a/beesdoo_shift/tests/__init__.py b/beesdoo_shift_atttendance/tests/__init__.py similarity index 100% rename from beesdoo_shift/tests/__init__.py rename to beesdoo_shift_atttendance/tests/__init__.py diff --git a/beesdoo_shift/tests/test_beesdoo_shift.py b/beesdoo_shift_atttendance/tests/test_beesdoo_shift.py similarity index 80% rename from beesdoo_shift/tests/test_beesdoo_shift.py rename to beesdoo_shift_atttendance/tests/test_beesdoo_shift.py index 44144d0..58ce822 100644 --- a/beesdoo_shift/tests/test_beesdoo_shift.py +++ b/beesdoo_shift_atttendance/tests/test_beesdoo_shift.py @@ -388,98 +388,4 @@ class TestBeesdooShift(TransactionCase): sheet_1.added_shift_ids[0].task_id, self.shift_empty_1 ) - # sheet_1.expected_shift_ids[0].worker_id - # sheet_1.expected_shift_ids[2].replaced_id - - def test_shift_counters(self): - "Test shift counters calculation and cooperative status update" - - status_1 = self.worker_regular_1.cooperative_status_ids - status_2 = self.worker_regular_3.cooperative_status_ids - status_3 = self.worker_irregular_1.cooperative_status_ids - - shift_regular = self.shift_model.create( - { - "task_template_id": self.task_template_1.id, - "task_type_id": self.task_type_1.id, - "worker_id": self.worker_regular_1.id, - "start_time": datetime.now() - timedelta(minutes=50), - "end_time": datetime.now() - timedelta(minutes=40), - "is_regular": True, - "is_compensation": False, - } - ) - future_shift_regular = self.shift_model.create( - { - "task_template_id": self.task_template_2.id, - "task_type_id": self.task_type_2.id, - "worker_id": self.worker_regular_1.id, - "start_time": datetime.now() + timedelta(minutes=20), - "end_time": datetime.now() + timedelta(minutes=30), - "is_regular": True, - "is_compensation": False, - } - ) - shift_irregular = self.shift_model.create( - { - "task_template_id": self.task_template_2.id, - "task_type_id": self.task_type_3.id, - "worker_id": self.worker_irregular_1.id, - "start_time": datetime.now() - timedelta(minutes=15), - "end_time": datetime.now() - timedelta(minutes=10), - } - ) - - # For a regular worker - status_1.sr = 0 - status_1.sc = 0 - self.assertEqual(status_1.status, "ok") - shift_regular.state = "absent_1" - self.assertEqual(status_1.sr, -1) - self.assertEqual(status_1.status, "alert") - shift_regular.state = "done" - self.assertEquals(status_1.sr, 0) - self.assertEquals(status_1.sc, 0) - shift_regular.state = "open" - shift_regular.write({"is_regular": False, "is_compensation": True}) - shift_regular.state = "done" - self.assertEquals(status_1.sr, 1) - self.assertEquals(status_1.sc, 0) - - # Check unsubscribed status - status_1.sr = -1 - status_1.sc = -1 - - # Subscribe him to another future shift - future_shift_regular.worker_id = self.worker_regular_1 - with self.assertRaises(ValidationError) as e: - future_shift_regular.state = "absent_2" - self.assertIn("future", str(e.exception)) - status_1.sr = -2 - status_1.sc = -2 - self.assertEquals(status_1.status, "unsubscribed") - - # Should be unsubscribed from future shift - self.assertFalse(future_shift_regular.worker_id) - - # With replacement worker (self.worker_regular_3) - shift_regular.state = "open" - status_1.sr = 0 - status_1.sc = 0 - status_2.sr = 0 - status_2.sc = 0 - shift_regular.replaced_id = self.worker_regular_3 - shift_regular.state = "absent_2" - self.assertEqual(status_1.sr, 0) - self.assertEqual(status_1.sc, 0) - self.assertEqual(status_2.sr, -1) - self.assertEqual(status_2.sc, -1) - - # For an irregular worker - status_3.sr = 0 - status_3.sc = 0 - self.assertEqual(status_3.status, "ok") - shift_irregular.state = "done" - self.assertEqual(status_3.sr, 1) - shift_irregular.state = "absent_2" - self.assertEqual(status_3.sr, -1) + # sheet_1.expected_shift_ids[0].worker_id \ No newline at end of file diff --git a/beesdoo_shift/views/attendance_sheet.xml b/beesdoo_shift_atttendance/views/attendance_sheet.xml similarity index 100% rename from beesdoo_shift/views/attendance_sheet.xml rename to beesdoo_shift_atttendance/views/attendance_sheet.xml diff --git a/beesdoo_shift/views/res_config_settings_view.xml b/beesdoo_shift_atttendance/views/res_config_settings_view.xml similarity index 100% rename from beesdoo_shift/views/res_config_settings_view.xml rename to beesdoo_shift_atttendance/views/res_config_settings_view.xml diff --git a/beesdoo_shift_atttendance/wizard/__init__.py b/beesdoo_shift_atttendance/wizard/__init__.py new file mode 100644 index 0000000..1a045d3 --- /dev/null +++ b/beesdoo_shift_atttendance/wizard/__init__.py @@ -0,0 +1 @@ +from . import validate_attendance_sheet diff --git a/beesdoo_shift/wizard/validate_attendance_sheet.py b/beesdoo_shift_atttendance/wizard/validate_attendance_sheet.py similarity index 100% rename from beesdoo_shift/wizard/validate_attendance_sheet.py rename to beesdoo_shift_atttendance/wizard/validate_attendance_sheet.py diff --git a/beesdoo_shift/wizard/validate_attendance_sheet.xml b/beesdoo_shift_atttendance/wizard/validate_attendance_sheet.xml similarity index 100% rename from beesdoo_shift/wizard/validate_attendance_sheet.xml rename to beesdoo_shift_atttendance/wizard/validate_attendance_sheet.xml diff --git a/beesdoo_website_shift/controllers/main.py b/beesdoo_website_shift/controllers/main.py index 53c536f..4123f02 100644 --- a/beesdoo_website_shift/controllers/main.py +++ b/beesdoo_website_shift/controllers/main.py @@ -20,8 +20,7 @@ class WebsiteShiftController(http.Controller): def is_user_worker(self): user = request.env['res.users'].browse(request.uid) - share_type = user.partner_id.cooperator_type - return share_type == 'share_a' + return user.partner_id.is_worker def is_user_irregular(self): user = request.env['res.users'].browse(request.uid) diff --git a/beesdoo_website_shift/views/my_shift_website_templates.xml b/beesdoo_website_shift/views/my_shift_website_templates.xml index 0a91fc1..33fa0f3 100644 --- a/beesdoo_website_shift/views/my_shift_website_templates.xml +++ b/beesdoo_website_shift/views/my_shift_website_templates.xml @@ -798,17 +798,22 @@

- - + +

- - + +

+ + + + +
@@ -819,17 +824,6 @@
-
- -
- - - - - -
- -
@@ -863,18 +857,18 @@

- - + +

- - + +

- - + +

@@ -883,7 +877,10 @@

- + + + +
@@ -925,7 +922,7 @@
-
+

Please, subscribe, in priority, to the highlighted shifts. To sign up to a shift click on the subscribe button. Notice that you can not unsubscribe online. To unsubscribe to a shift, please, @@ -943,17 +940,6 @@

-
- -
- - - - - -
- -
diff --git a/beesdoo_website_shift/views/shift_website_templates.xml b/beesdoo_website_shift/views/shift_website_templates.xml index bc261f7..96894e3 100644 --- a/beesdoo_website_shift/views/shift_website_templates.xml +++ b/beesdoo_website_shift/views/shift_website_templates.xml @@ -3,7 +3,7 @@ Copyright 2017-2018 Rémy Taymans License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> - + @@ -11,13 +11,13 @@ Shifts Irregular /shift_irregular_worker - 50 + 50 Shifts Regular /shift_template_regular_worker - 51 + 51 @@ -206,4 +206,4 @@ - + diff --git a/beesdoo_worker_status/__init__.py b/beesdoo_worker_status/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/beesdoo_worker_status/__manifest__.py b/beesdoo_worker_status/__manifest__.py new file mode 100644 index 0000000..13c8ca7 --- /dev/null +++ b/beesdoo_worker_status/__manifest__.py @@ -0,0 +1,26 @@ +{ + 'name': "Beescoop Worker Status manager", + + 'summary': """ + Worker status management specific to beescoop""", + + 'description': """ + + """, + + 'author': "Thibault Francois, Elouan Le Bars, Coop It Easy", + 'website': "https://github.com/beescoop/Obeesdoo", + + 'category': 'Cooperative management', + 'version': '12.0.1.0.0', + + 'depends': [ + 'beesdoo_base', + 'beesdoo_shift', + ], + + 'data': [ + ], + 'demo': [ + ] +} diff --git a/beesdoo_worker_status/models/__init__.py b/beesdoo_worker_status/models/__init__.py new file mode 100644 index 0000000..bf0149d --- /dev/null +++ b/beesdoo_worker_status/models/__init__.py @@ -0,0 +1 @@ +from . import cooperative_status \ No newline at end of file diff --git a/beesdoo_worker_status/models/cooperative_status.py b/beesdoo_worker_status/models/cooperative_status.py new file mode 100644 index 0000000..0a05e88 --- /dev/null +++ b/beesdoo_worker_status/models/cooperative_status.py @@ -0,0 +1,266 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import ValidationError, UserError + +from datetime import timedelta, datetime +import logging + +class CooperativeStatus(models.Model): + _inherit = 'cooperative.status' + _period = 28 + + ###################################################### + # # + # Override of method to define status behavior # + # # + ###################################################### + + future_alert_date = fields.Date(compute='_compute_future_alert_date') + next_countdown_date = fields.Date(compute='_compute_next_countdown_date') + + + @api.depends('today', 'irregular_start_date', 'sr', 'holiday_start_time', + 'holiday_end_time', 'temporary_exempt_start_date', + 'temporary_exempt_end_date') + def _compute_future_alert_date(self): + """Compute date before which the worker is up to date""" + for rec in self: + # Only for irregular worker + if rec.working_mode != 'irregular' and not rec.irregular_start_date: + rec.future_alert_date = False + # Alert start time already set + elif rec.alert_start_time: + rec.future_alert_date = False + # Holidays are not set properly + elif bool(rec.holiday_start_time) != bool(rec.holiday_end_time): + rec.future_alert_date = False + # Exemption have not a start and end time + elif (bool(rec.temporary_exempt_start_date) + != bool(rec.temporary_exempt_end_date)): + rec.future_alert_date = False + else: + date = rec.today + counter = rec.sr + # Simulate the countdown + while counter > 0: + date = add_days_delta(date, 1) + date = self._next_countdown_date(rec.irregular_start_date, + date) + # Check holidays + if (rec.holiday_start_time and rec.holiday_end_time + and date >= rec.holiday_start_time + and date <= rec.holiday_end_time): + continue + # Check temporary exemption + elif (rec.temporary_exempt_start_date + and rec.temporary_exempt_end_date + and date >= rec.temporary_exempt_start_date + and date <= rec.temporary_exempt_end_date): + continue + else: + counter -= 1 + rec.future_alert_date = self._next_countdown_date( + rec.irregular_start_date, date + ) + + @api.depends('today', 'irregular_start_date', 'holiday_start_time', + 'holiday_end_time', 'temporary_exempt_start_date', + 'temporary_exempt_end_date') + def _compute_next_countdown_date(self): + """ + Compute the following countdown date. This date is the date when + the worker will see his counter changed du to the cron. This + date is like the birthday date of the worker that occurred each + PERIOD. + """ + for rec in self: + # Only for irregular worker + if rec.working_mode != 'irregular' and not rec.irregular_start_date: + rec.next_countdown_date = False + # Holidays are not set properly + elif bool(rec.holiday_start_time) != bool(rec.holiday_end_time): + rec.next_countdown_date = False + # Exemption have not a start and end time + elif (bool(rec.temporary_exempt_start_date) + != bool(rec.temporary_exempt_end_date)): + rec.next_countdown_date = False + else: + date = rec.today + next_countdown_date = False + while not next_countdown_date: + date = self._next_countdown_date(rec.irregular_start_date, date) + # Check holidays + if (rec.holiday_start_time and rec.holiday_end_time + and date >= rec.holiday_start_time + and date <= rec.holiday_end_time): + date = add_days_delta(date, 1) + continue + # Check temporary exemption + elif (rec.temporary_exempt_start_date + and rec.temporary_exempt_end_date + and date >= rec.temporary_exempt_start_date + and date <= rec.temporary_exempt_end_date): + date = add_days_delta(date, 1) + continue + else: + next_countdown_date = date + rec.next_countdown_date = next_countdown_date + + + ##################################### + # Status Change implementation # + ##################################### + def _set_regular_status(self, grace_delay, alert_delay): + self.ensure_one() + counter_unsubscribe = int(self.env['ir.config_parameter'].get_param('regular_counter_to_unsubscribe', -4)) + ok = self.sr >= 0 and self.sc >= 0 + grace_delay = grace_delay + self.time_extension + + if (self.sr + self.sc) <= counter_unsubscribe or self.unsubscribed: + return 'unsubscribed' + #Check if exempted. Exempt end date is not required. + if self.temporary_exempt_start_date and self.today >= self.temporary_exempt_start_date: + if not self.temporary_exempt_end_date or self.today <= self.temporary_exempt_end_date: + return 'exempted' + + #Transition to alert sr < 0 or stay in alert sr < 0 or sc < 0 and thus alert time is defined + if not ok and self.alert_start_time and self.extension_start_time and self.today <= add_days_delta(self.extension_start_time, grace_delay): + return 'extension' + if not ok and self.alert_start_time and self.extension_start_time and self.today > add_days_delta(self.extension_start_time, grace_delay): + return 'suspended' + if not ok and self.alert_start_time and self.today > add_days_delta(self.alert_start_time, alert_delay): + return 'suspended' + if (self.sr < 0) or (not ok and self.alert_start_time): + return 'alert' + + if ( + self.holiday_start_time + and self.holiday_end_time + and self.today >= self.holiday_start_time + and self.today <= self.holiday_end_time + ): + + return 'holiday' + elif ok or (not self.alert_start_time and self.sr >= 0): + return 'ok' + + def _set_irregular_status(self, grace_delay, alert_delay): + counter_unsubscribe = int(self.env['ir.config_parameter'].get_param('irregular_counter_to_unsubscribe', -3)) + self.ensure_one() + ok = self.sr >= 0 + grace_delay = grace_delay + self.time_extension + if self.sr <= counter_unsubscribe or self.unsubscribed: + return 'unsubscribed' + #Check if exempted. Exempt end date is not required. + elif self.temporary_exempt_start_date and self.today >= self.temporary_exempt_start_date: + if not self.temporary_exempt_end_date or self.today <= self.temporary_exempt_end_date: + return 'exempted' + #Transition to alert sr < 0 or stay in alert sr < 0 or sc < 0 and thus alert time is defined + 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): + return 'extension' + 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): + return 'suspended' + elif not ok and self.alert_start_time and self.today > add_days_delta(self.alert_start_time, alert_delay): + return 'suspended' + elif (self.sr < 0) or (not ok and self.alert_start_time): + return 'alert' + + elif ( + self.holiday_start_time + and self.holiday_end_time + and self.today >= self.holiday_start_time + and self.today <= self.holiday_end_time + ): + return 'holiday' + elif ok or (not self.alert_start_time and self.sr >= 0): + return 'ok' + + def _state_change(self, new_state): + 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': + data = {'extension_start_time': False, 'time_extension': 0} + data['alert_start_time'] = False + self.write(data) + if new_state == 'unsubscribed' or new_state == 'resigning': + # Remove worker from task_templates + self.cooperator_id.sudo().write( + {'subscribed_shift_ids': [(5, 0, 0)]}) + # Remove worker from supercoop in task_templates + task_tpls = self.env['beesdoo.shift.template'].search( + [('super_coop_id', 'in', self.cooperator_id.user_ids.ids)] + ) + task_tpls.write({'super_coop_id': False}) + # Remove worker for future tasks (remove also supercoop) + self.env['beesdoo.shift.shift'].sudo().unsubscribe_from_today( + [self.cooperator_id.id], now=fields.Datetime.now() + ) + + def _change_counter(self, data): + """ + Call when a shift state is changed + use data generated by _get_counter_date_state_change + """ + self.sc += data.get('sc', 0) + self.sr += data.get('sr', 0) + self.irregular_absence_counter += data.get('irregular_absence_counter', 0) + self.irregular_absence_date = data.get('irregular_absence_date', False) + + ############################################### + ###### Irregular Cron implementation ########## + ############################################### + + def _get_irregular_worker_domain(self): + return ['&', + '&', + '&', + ('status', 'not in', ['unsubscribed', 'exempted']), + ('working_mode', '=', 'irregular'), + ('irregular_start_date', '!=', False), + '|', + '|', ('holiday_start_time', '=', False), ('holiday_end_time', '=', False), + '|', ('holiday_start_time', '>', today), ('holiday_end_time', '<', today), + ] + + def _change_irregular_counter(self): + if self.sr > 0: + self.sr -= 1 + elif self.alert_start_time: + self.sr -= 1 + else: + self.sr -= 2 + + ################################## + # Internal Implementation # + ################################## + def _next_countdown_date(self, irregular_start_date, today=False): + """ + Return the next countdown date given irregular_start_date and + today dates. + This does not take holiday and other status into account. + """ + today = today or fields.Date.today() + + delta = (today - irregular_start_date).days + if not delta % PERIOD: + return today + return add_days_delta(today, PERIOD - (delta % PERIOD)) + + +class ResPartner(models.Model): + _inherit = 'res.partner' + """ + Override is_worker definition + You need have subscribe to a A Share + """ + is_worker = fields.Boolean(compute="_is_worker", search="_search_worker") + + def _is_worker(self): + for rec in self: + rec.is_worker = rec.cooperator_type == 'share_a' + + def _search_worker(self, operator, value): + if (operator == '=' and value) or (operator == '!=' and not value): + return [('cooperator_type', '=', 'share_a')] + else: + return [('cooperator_type', '!=', 'share_a')] diff --git a/beesdoo_worker_status/models/task.py b/beesdoo_worker_status/models/task.py new file mode 100644 index 0000000..0720b70 --- /dev/null +++ b/beesdoo_worker_status/models/task.py @@ -0,0 +1,81 @@ +import json +from datetime import datetime, timedelta + +from odoo import _, api, fields, models +from odoo.exceptions import UserError, ValidationError + + + +class Task(models.Model): + _inherit = 'beesdoo.shift.shift' + + ################################# + # State Definition # + ################################# + + def _get_selection_status(self): + return [ + ("open","Confirmed"), + ("done","Attended"), + ("absent_2","Absent - 2 compensations"), + ("absent_1","Absent - 1 compensation"), + ("absent_0","Absent - 0 compensation"), + ("cancel","Cancelled") + ] + + def _get_color_mapping(state): + return { + "draft": 0, + "open": 1, + "done": 5, + "absent_2": 2, + "absent_1": 7, + "absent_0": 3, + "cancel": 9, + }[state] + + def _get_final_state(): + return ["done", "absent_2", "absent_1", "absent_0"] + + + ############################################## + # Change counter when state change # + ############################################## + def _get_counter_date_state_change(self, new_state): + data = {} + if self.worker_id.working_mode == 'regular': + + 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] + + if new_state == "done" and not self.is_regular: + # Regular counter is always updated first + if status.sr < 0: + data['sr'] = 1 + elif status.sc < 0: + data['sc'] = 1 + # Bonus shift case + else: + data['sr'] = 1 + + if new_state == "absent_2": + data['sr'] = -1 + data['sc'] = -1 + + if new_state == "absent_1": + data['sr'] = -1 + + elif self.worker_id.working_mode == 'irregular': + status = self.worker_id.cooperative_status_ids[0] + if new_state == "done" or new_state == "absent_0": + data['sr'] = 1 + data['irregular_absence_date'] = False + data['irregular_absence_counter'] = 1 if status.irregular_absence_counter < 0 else 0 + if new_state == "absent_2" or new_state == "absent_1": + if new_state == "absent_2": + data['sr'] = -1 + data['irregular_absence_date'] = self.start_time.date() + data['irregular_absence_counter'] = -1 + return data \ No newline at end of file diff --git a/beesdoo_worker_status/tests/__init__.py b/beesdoo_worker_status/tests/__init__.py new file mode 100644 index 0000000..1505d70 --- /dev/null +++ b/beesdoo_worker_status/tests/__init__.py @@ -0,0 +1 @@ +from . import test_beesdoo_shift diff --git a/beesdoo_worker_status/tests/test_beesdoo_shift.py b/beesdoo_worker_status/tests/test_beesdoo_shift.py new file mode 100644 index 0000000..08af349 --- /dev/null +++ b/beesdoo_worker_status/tests/test_beesdoo_shift.py @@ -0,0 +1,247 @@ +# Copyright 2019 - Today Coop IT Easy SCRLfs () +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +import time +from datetime import datetime, timedelta + +from odoo import exceptions, fields +from odoo.exceptions import UserError, ValidationError +from odoo.tests.common import TransactionCase + + +class TestBeesdooShift(TransactionCase): + def setUp(self): + super(TestBeesdooShift, self).setUp() + self.shift_model = self.env["beesdoo.shift.shift"] + self.shift_template_model = self.env["beesdoo.shift.template"] + self.attendance_sheet_model = self.env["beesdoo.shift.sheet"] + self.attendance_sheet_shift_model = self.env[ + "beesdoo.shift.sheet.shift" + ] + self.shift_expected_model = self.env["beesdoo.shift.sheet.expected"] + self.shift_added_model = self.env["beesdoo.shift.sheet.added"] + self.task_type_default_id = self.env["ir.config_parameter"].sudo().get_param( + "beesdoo_shift.task_type_default_id" + ) + + self.current_time = datetime.now() + self.user_admin = self.env.ref("base.user_root") + self.user_generic = self.env.ref( + "beesdoo_base.beesdoo_shift_user_1_demo" + ) + self.user_permanent = self.env.ref( + "beesdoo_base.beesdoo_shift_user_2_demo" + ) + + self.setting_wizard = self.env["beesdoo.shift.config.settings"].sudo( + self.user_admin + ) + + self.worker_regular_1 = self.env.ref( + "beesdoo_base.res_partner_cooperator_6_demo" + ) + self.worker_regular_2 = self.env.ref( + "beesdoo_base.res_partner_cooperator_5_demo" + ) + self.worker_regular_3 = self.env.ref( + "beesdoo_base.res_partner_cooperator_3_demo" + ) + self.worker_regular_super_1 = self.env.ref( + "beesdoo_base.res_partner_cooperator_1_demo" + ) + self.worker_irregular_1 = self.env.ref( + "beesdoo_base.res_partner_cooperator_2_demo" + ) + self.worker_irregular_2 = self.env.ref( + "beesdoo_base.res_partner_cooperator_4_demo" + ) + + self.task_type_1 = self.env.ref( + "beesdoo_shift.beesdoo_shift_task_type_1_demo" + ) + self.task_type_2 = self.env.ref( + "beesdoo_shift.beesdoo_shift_task_type_2_demo" + ) + self.task_type_3 = self.env.ref( + "beesdoo_shift.beesdoo_shift_task_type_3_demo" + ) + + self.task_template_1 = self.env.ref( + "beesdoo_shift.beesdoo_shift_task_template_1_demo" + ) + self.task_template_2 = self.env.ref( + "beesdoo_shift.beesdoo_shift_task_template_2_demo" + ) + + # Set time in and out of generation interval parameter + self.start_in_1 = self.current_time + timedelta(seconds=2) + self.end_in_1 = self.current_time + timedelta(minutes=10) + self.start_in_2 = self.current_time + timedelta(minutes=9) + self.end_in_2 = self.current_time + timedelta(minutes=21) + self.start_out_1 = self.current_time - timedelta(minutes=50) + self.end_out_1 = self.current_time - timedelta(minutes=20) + self.start_out_2 = self.current_time + timedelta(minutes=40) + self.end_out_2 = self.current_time + timedelta(minutes=50) + + self.shift_regular_regular_1 = self.shift_model.create( + { + "task_template_id": self.task_template_1.id, + "task_type_id": self.task_type_1.id, + "worker_id": self.worker_regular_1.id, + "start_time": self.start_in_1, + "end_time": self.end_in_1, + "is_regular": True, + "is_compensation": False, + } + ) + self.shift_regular_regular_2 = self.shift_model.create( + { + "task_type_id": self.task_type_2.id, + "worker_id": self.worker_regular_2.id, + "start_time": self.start_out_1, + "end_time": self.end_out_1, + "is_regular": True, + "is_compensation": False, + } + ) + self.shift_regular_regular_replaced_1 = self.shift_model.create( + { + "task_template_id": self.task_template_1.id, + "task_type_id": self.task_type_3.id, + "worker_id": self.worker_regular_3.id, + "start_time": self.start_in_1, + "end_time": self.end_in_1, + "is_regular": True, + "is_compensation": False, + "replaced_id": self.worker_regular_2.id, + } + ) + future_shift_regular = self.shift_model.create( + { + "task_template_id": self.task_template_2.id, + "task_type_id": self.task_type_1.id, + "worker_id": self.worker_regular_super_1.id, + "start_time": self.start_in_2, + "end_time": self.end_in_2, + "is_regular": False, + "is_compensation": True, + } + ) + self.shift_irregular_1 = self.shift_model.create( + { + "task_template_id": self.task_template_1.id, + "task_type_id": self.task_type_2.id, + "worker_id": self.worker_irregular_1.id, + "start_time": self.start_in_1, + "end_time": self.end_in_1, + } + ) + self.shift_irregular_2 = self.shift_model.create( + { + "task_type_id": self.task_type_3.id, + "worker_id": self.worker_irregular_2.id, + "start_time": self.start_out_2, + "end_time": self.end_out_2, + } + ) + self.shift_empty_1 = self.shift_model.create( + { + "task_template_id": self.task_template_1.id, + "task_type_id": self.task_type_1.id, + "start_time": self.start_in_1, + "end_time": self.end_in_1, + } + ) + + def test_shift_counters(self): + "Test shift counters calculation and cooperative status update" + + status_1 = self.worker_regular_1.cooperative_status_ids + status_2 = self.worker_regular_3.cooperative_status_ids + status_3 = self.worker_irregular_1.cooperative_status_ids + + shift_regular = self.shift_model.create( + { + "task_template_id": self.task_template_1.id, + "task_type_id": self.task_type_1.id, + "worker_id": self.worker_regular_1.id, + "start_time": datetime.now() - timedelta(minutes=50), + "end_time": datetime.now() - timedelta(minutes=40), + "is_regular": True, + "is_compensation": False, + } + ) + future_shift_regular = self.shift_model.create( + { + "task_template_id": self.task_template_2.id, + "task_type_id": self.task_type_2.id, + "worker_id": self.worker_regular_1.id, + "start_time": datetime.now() + timedelta(minutes=20), + "end_time": datetime.now() + timedelta(minutes=30), + "is_regular": True, + "is_compensation": False, + } + ) + shift_irregular = self.shift_model.create( + { + "task_template_id": self.task_template_2.id, + "task_type_id": self.task_type_3.id, + "worker_id": self.worker_irregular_1.id, + "start_time": datetime.now() - timedelta(minutes=15), + "end_time": datetime.now() - timedelta(minutes=10), + } + ) + + # For a regular worker + status_1.sr = 0 + status_1.sc = 0 + self.assertEqual(status_1.status, "ok") + shift_regular.state = "absent_1" + self.assertEqual(status_1.sr, -1) + self.assertEqual(status_1.status, "alert") + shift_regular.state = "done" + self.assertEquals(status_1.sr, 0) + self.assertEquals(status_1.sc, 0) + shift_regular.state = "open" + shift_regular.write({"is_regular": False, "is_compensation": True}) + shift_regular.state = "done" + self.assertEquals(status_1.sr, 1) + self.assertEquals(status_1.sc, 0) + + # Check unsubscribed status + status_1.sr = -1 + status_1.sc = -1 + + # Subscribe him to another future shift + future_shift_regular.worker_id = self.worker_regular_1 + with self.assertRaises(ValidationError) as e: + future_shift_regular.state = "absent_2" + self.assertIn("future", str(e.exception)) + status_1.sr = -2 + status_1.sc = -2 + self.assertEquals(status_1.status, "unsubscribed") + + # Should be unsubscribed from future shift + self.assertFalse(future_shift_regular.worker_id) + + # With replacement worker (self.worker_regular_3) + shift_regular.state = "open" + status_1.sr = 0 + status_1.sc = 0 + status_2.sr = 0 + status_2.sc = 0 + shift_regular.replaced_id = self.worker_regular_3 + shift_regular.state = "absent_2" + self.assertEqual(status_1.sr, 0) + self.assertEqual(status_1.sc, 0) + self.assertEqual(status_2.sr, -1) + self.assertEqual(status_2.sc, -1) + + # For an irregular worker + status_3.sr = 0 + status_3.sc = 0 + self.assertEqual(status_3.status, "ok") + shift_irregular.state = "done" + self.assertEqual(status_3.sr, 1) + shift_irregular.state = "absent_2" + self.assertEqual(status_3.sr, -1)