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:
-
The attendance sheet for ${object.time_slot} is not validated.
-
Please, do it as soon as possible so as to update workers' status.
-
-
-
- ]]>
- Shift SummaryYour 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.512
-
+
@@ -88,7 +88,7 @@
2.59
-
+
@@ -101,7 +101,7 @@
2.57
-
+
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 @@
-
+ 2irregular
@@ -22,7 +22,7 @@
-
+ 2
@@ -30,101 +30,24 @@
-
+ 2irregular
-
+ 2regular
-
+ 2regular
-
- 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 Coopres.partner
-
+ 50
+ attrs="{'invisible': [('is_worker', '=', False)]}"/>
+ attrs="{'invisible': [('is_worker', '=', False)]}"/>
+ attrs="{'invisible': ['|', ('is_worker', '=', False), ('state', '!=', 'ok')]}"/>
-
+ attrs="{'invisible': ['|', ('is_worker', '=', False), ('state', '=', 'unsubscribed')]}"/>
+
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
@@ -175,7 +183,7 @@
Workerres.partnerkanban,tree,form
- [('cooperator_type', '=', 'share_a')]
+ [('is_worker', '=', True)]
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:
+
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
+ 50Shifts 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)