diff --git a/beesdoo_website_shift/__init__.py b/beesdoo_website_shift/__init__.py index e046e49..91c5580 100644 --- a/beesdoo_website_shift/__init__.py +++ b/beesdoo_website_shift/__init__.py @@ -1 +1,2 @@ from . import controllers +from . import models diff --git a/beesdoo_website_shift/__openerp__.py b/beesdoo_website_shift/__openerp__.py index bbe19a3..b07357f 100644 --- a/beesdoo_website_shift/__openerp__.py +++ b/beesdoo_website_shift/__openerp__.py @@ -1,6 +1,10 @@ # -*- coding: utf-8 -*- + +# Copyright 2017-2018 Rémy Taymans +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + { - 'name': 'Beescoop Shift Website', + 'name': 'BEES coop Shift Website', 'summary': """ Show available shifts for regular and irregular workers in @@ -10,14 +14,18 @@ """, 'author': 'Rémy Taymans', + 'license': 'AGPL-3', + 'version': '9.0.1.0', 'website': "https://github.com/beescoop/Obeesdoo", 'category': 'Cooperative management', - 'version': '0.1', 'depends': ['website', 'beesdoo_shift'], 'data': [ + 'data/res_config_data.xml', 'views/shift_website_templates.xml', + 'views/my_shift_website_templates.xml', + 'views/res_config_views.xml', ] } diff --git a/beesdoo_website_shift/controllers/main.py b/beesdoo_website_shift/controllers/main.py index 3b12d54..a18a609 100644 --- a/beesdoo_website_shift/controllers/main.py +++ b/beesdoo_website_shift/controllers/main.py @@ -1,56 +1,437 @@ # -*- coding: utf8 -*- -from datetime import datetime + +# Copyright 2017-2018 Rémy Taymans +# Copyright 2017-2018 Thibault François +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from ast import literal_eval +from copy import copy +from datetime import datetime, timedelta from itertools import groupby -from openerp import http + +from openerp import http, fields from openerp.http import request +from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT from openerp.addons.beesdoo_shift.models.planning import float_to_time -class ShiftPortalController(http.Controller): +PERIOD = 28 # TODO: use the same constant as in 'beesdoo_shift' + + +class WebsiteShiftController(http.Controller): + + def is_user_irregular(self): + user = request.env['res.users'].browse(request.uid) + working_mode = user.partner_id.working_mode + return working_mode == 'irregular' + + def is_user_regular(self): + user = request.env['res.users'].browse(request.uid) + working_mode = user.partner_id.working_mode + return working_mode == 'regular' + + def is_user_exempted(self): + user = request.env['res.users'].browse(request.uid) + working_mode = user.partner_id.working_mode + return working_mode == 'exempt' + + @http.route('/my/shift', auth='user', website=True) + def my_shift(self, **kw): + """ + Personal page for managing your shifts + """ + if self.is_user_irregular(): + return request.render( + 'beesdoo_website_shift.my_shift_irregular_worker', + self.my_shift_irregular_worker(nexturl='/my/shift') + ) + if self.is_user_regular(): + return request.render( + 'beesdoo_website_shift.my_shift_regular_worker', + self.my_shift_regular_worker() + ) + if self.is_user_exempted(): + return request.render( + 'beesdoo_website_shift.my_shift_exempted_worker', + self.my_shift_exempted_worker() + ) + + return request.render( + 'beesdoo_website_shift.my_shift_non_worker', + {} + ) + + @http.route('/shift//subscribe', auth='user', website=True) + def subscribe_to_shift(self, shift_id=-1, **kw): + """ + Subscribe the current connected user into the given shift + This is done only if : + * shift sign up is authorised via configuration panel + * the current connected user is an irregular worker + * the given shift exist + * the shift is free for subscription + """ + # Get current user + cur_user = request.env['res.users'].browse(request.uid) + # Get the shift + shift = request.env['beesdoo.shift.shift'].sudo().browse(shift_id) + # Get config + irregular_enable_sign_up = literal_eval(request.env['ir.config_parameter'].get_param( + 'beesdoo_website_shift.irregular_enable_sign_up')) + + if (irregular_enable_sign_up + and cur_user.partner_id.working_mode == 'irregular' + and shift + and not shift.worker_id): + shift.worker_id = cur_user.partner_id + return request.redirect(kw['nexturl']) @http.route('/shift_irregular_worker', auth='public', website=True) - def shift_irregular_worker(self, **kwargs): - # Get all the shifts in the future with no worker - now = datetime.now() - shifts = request.env['beesdoo.shift.shift'].sudo().search( - [('start_time', '>', now.strftime("%Y-%m-%d %H:%M:%S")), - ('worker_id', '=', False)], - order="start_time, task_template_id, task_type_id", + def public_shift_irregular_worker(self, **kw): + """ + Show a public access page that show all the available shifts for irregular worker. + """ + nexturl = '/shift_irregular_worker' + irregular_enable_sign_up = False + + # Create template context + template_context = {} + template_context.update(self.available_shift_irregular_worker( + irregular_enable_sign_up, nexturl + )) + + return request.render( + 'beesdoo_website_shift.public_shift_irregular_worker', + template_context ) - # Grouby task_template_id, if no task_template_id is specified - # then group by start_time - groupby_func = lambda s: (s.task_template_id, s.start_time, s.task_type_id) - groupby_iter = groupby(shifts, groupby_func) + @http.route('/shift_template_regular_worker', auth='public', website=True) + def public_shift_template_regular_worker(self, **kw): + """ + Show a public access page that show all the available shift templates for regular worker. + """ + # Get all the task template + template = request.env['beesdoo.shift.template'] + task_templates = template.sudo().search([], order="planning_id, day_nb_id, start_time") - shifts_and_count = [] - for (keys, grouped_shifts) in groupby_iter: - (task_template, _, _) = keys - s = list(grouped_shifts) - free_space = len(s) - # Among shifts with at least 5 worker max, shows only shifts - # where there is at least two free spaces - if task_template.worker_nb > 5 and free_space >= 2: - shifts_and_count.append([free_space, s[0]]) - # Show available shifts if there is less than 5 worker max - if task_template.worker_nb <= 5: - shifts_and_count.append([free_space, s[0]]) - - return request.render('beesdoo_website_shift.shift_template', + return request.render( + 'beesdoo_website_shift.public_shift_template_regular_worker', { - 'shift_templates': shifts_and_count + 'task_templates': task_templates, + 'float_to_time': float_to_time, } ) - @http.route('/shift_template_regular_worker', auth='public', website=True) - def shift_template_regular_worker(self, **kwargs): + def my_shift_irregular_worker(self, nexturl=""): + """ + Return template variables for 'beesdoo_website_shift.my_shift_irregular_worker' template + """ + # Get current user + cur_user = request.env['res.users'].browse(request.uid) + cur_cooperative_status = cur_user.partner_id.cooperative_status_ids + + # Get config + irregular_enable_sign_up = literal_eval(request.env['ir.config_parameter'].get_param( + 'beesdoo_website_shift.irregular_enable_sign_up')) + + # Create template context + template_context = {} + + template_context.update(self.my_shift_worker_status()) + template_context.update(self.my_shift_next_shifts()) + template_context.update(self.my_shift_past_shifts()) + template_context.update(self.available_shift_irregular_worker( + irregular_enable_sign_up, nexturl + )) + + future_alert_date = False + if not cur_cooperative_status.alert_start_time: + # Compute date before which the worker is up to date + today_date = fields.Date.from_string(cur_cooperative_status.today) + delta = (today_date - fields.Date.from_string(cur_cooperative_status.irregular_start_date)).days + future_alert_date = today_date + timedelta(days=(cur_cooperative_status.sr + 1) * PERIOD - delta % PERIOD) + future_alert_date = future_alert_date.strftime('%Y-%m-%d') + + template_context.update( + { + 'future_alert_date': future_alert_date, + } + ) + return template_context + + def my_shift_regular_worker(self): + """ + Return template variables for 'beesdoo_website_shift.my_shift_regular_worker' template + """ + # Create template context + template_context = {} + # Get all the task template template = request.env['beesdoo.shift.template'] task_templates = template.sudo().search([], order="planning_id, day_nb_id, start_time") - return request.render('beesdoo_website_shift.task_template', + template_context.update(self.my_shift_worker_status()) + template_context.update(self.my_shift_next_shifts()) + template_context.update(self.my_shift_past_shifts()) + template_context.update( { 'task_templates': task_templates, - 'float_to_time': float_to_time + 'float_to_time': float_to_time, } ) + return template_context + + def my_shift_exempted_worker(self): + """ + Return template variables for 'beesdoo_website_shift.my_shift_exempted_worker' template + """ + return self.my_shift_worker_status() + + def available_shift_irregular_worker(self, irregular_enable_sign_up=False, nexturl=""): + """ + Return template variables for 'beesdoo_website_shift.available_shift_irregular_worker' template + """ + # Get current user + cur_user = request.env['res.users'].browse(request.uid) + + # Get all the shifts in the future with no worker + now = datetime.now() + shifts = request.env['beesdoo.shift.shift'].sudo().search( + [('start_time', '>', now.strftime("%Y-%m-%d %H:%M:%S")), + ('worker_id', '=', False)], + order="start_time, task_template_id, task_type_id", + ) + + # Get shifts where user is subscribed + subscribed_shifts = request.env['beesdoo.shift.shift'].sudo().search( + [('start_time', '>', now.strftime("%Y-%m-%d %H:%M:%S")), + ('worker_id', '=', cur_user.partner_id.id)], + order="start_time, task_template_id, task_type_id", + ) + + # Get config + irregular_shift_limit = int(request.env['ir.config_parameter'].get_param( + 'beesdoo_website_shift.irregular_shift_limit')) + highlight_rule = int(request.env['ir.config_parameter'].get_param( + 'beesdoo_website_shift.highlight_rule')) + hide_rule = int(request.env['ir.config_parameter'].get_param( + 'beesdoo_website_shift.hide_rule')) / 100.0 + + # Grouby task_template_id, if no task_template_id is specified + # then group by start_time, if no start_time specified sort by + # task_type + groupby_iter = groupby( + shifts, + lambda s: (s.task_template_id, s.start_time, s.task_type_id) + ) + + shifts_count_subscribed = [] + nb_displayed_shift = 0 # Number of shift displayed + for (keys, grouped_shifts) in groupby_iter: + (task_template, start_time, task_type) = keys + nb_displayed_shift = nb_displayed_shift + 1 + shift_list = list(grouped_shifts) + # Compute available space + free_space = len(shift_list) + # Is the current user subscribed to this task_template + is_subscribed = any( + (sub_shift.task_template_id == task_template and + sub_shift.start_time == start_time and + sub_shift.task_type_id == task_type) + for sub_shift in subscribed_shifts) + if free_space >= task_template.worker_nb * hide_rule: + shifts_count_subscribed.append([shift_list[0], free_space, is_subscribed]) + # Stop showing shifts if the limit is reached + if irregular_shift_limit > 0 and nb_displayed_shift >= irregular_shift_limit: + break + + return { + 'shift_templates': shifts_count_subscribed, + 'highlight_rule': highlight_rule, + 'nexturl': nexturl, + 'irregular_enable_sign_up': irregular_enable_sign_up, + } + + def my_shift_next_shifts(self): + """ + Return template variables for 'beesdoo_website_shift.my_shift_next_shifts' template + """ + # Get current user + cur_user = request.env['res.users'].browse(request.uid) + # Get shifts where user is subscribed + now = datetime.now() + subscribed_shifts_rec = request.env['beesdoo.shift.shift'].sudo().search( + [('start_time', '>', now.strftime("%Y-%m-%d %H:%M:%S")), + ('worker_id', '=', cur_user.partner_id.id)], + order="start_time, task_template_id, task_type_id", + ) + # We don't use record to show the next shift as we need to add + # fictive one for regular worker. I say 'fictive' one because + # the next shifts for the regular worker are generated on a + # PERIOD basis and database doesn't contain more than a PERIOD + # of shift. So a regular worker will always see only one + # next shift, the one that is generated and stored in the + # database. Meaning that if we want to show the next shifts + # for an entire year, we need to compute the dates for the next + # shifts and create it. But we want to keep it 'fictive', + # meaning that we don't want to write them in the database. + # So here we convert recordset into Shift object. + subscribed_shifts = [] + for shift_rec in subscribed_shifts_rec: + shift = Shift(shift_rec) + subscribed_shifts.append(shift) + # We want to keep a copy of the shift that will serve as a + # master to create the fictive shifts. + if shift_rec.worker_id in shift_rec.task_template_id.worker_ids: + main_shift = shift + main_shift_rec = shift_rec + + # In case of regular worker, we compute his fictive next shifts + # according to the regular_next_shift_limit + if self.is_user_regular() and subscribed_shifts and main_shift: + # Get config + regular_next_shift_limit = int(request.env['ir.config_parameter'].get_param( + 'beesdoo_website_shift.regular_next_shift_limit')) + for i in range(1, regular_next_shift_limit): + # Compute the new date for the created shift + start_time = fields.Datetime.from_string(main_shift_rec.start_time) + start_time = (start_time + timedelta(days=i*PERIOD)).strftime(DATETIME_FORMAT) + # Create the fictive shift + shift = copy(main_shift) + shift.id = -i # We give negative id 'caus this shift doesn't exist in database + shift.start_day = start_time + shift.start_date = start_time + subscribed_shifts.append(shift) + + return { + 'is_regular': self.is_user_regular(), + 'subscribed_shifts': subscribed_shifts, + } + + def my_shift_past_shifts(self): + """ + Return template variables for 'beesdoo_website_shift.my_shift_past_shifts' template + """ + # Get current user + cur_user = request.env['res.users'].browse(request.uid) + # Get config + past_shift_limit = 0 + if self.is_user_irregular(): + past_shift_limit = int(request.env['ir.config_parameter'].get_param( + 'beesdoo_website_shift.irregular_past_shift_limit')) + if self.is_user_regular(): + past_shift_limit = int(request.env['ir.config_parameter'].get_param( + 'beesdoo_website_shift.regular_past_shift_limit')) + # Get shifts where user was subscribed + now = datetime.now() + if past_shift_limit > 0: + past_shifts = request.env['beesdoo.shift.shift'].sudo().search( + [('start_time', '<=', now.strftime("%Y-%m-%d %H:%M:%S")), + ('worker_id', '=', cur_user.partner_id.id)], + order="start_time desc, task_template_id, task_type_id", + limit=past_shift_limit, + ) + else: + past_shifts = request.env['beesdoo.shift.shift'].sudo().search( + [('start_time', '<=', now.strftime("%Y-%m-%d %H:%M:%S")), + ('worker_id', '=', cur_user.partner_id.id)], + order="start_time desc, task_template_id, task_type_id", + ) + + return { + 'past_shifts': past_shifts, + } + + def my_shift_worker_status(self): + """ + Return template variables for 'beesdoo_website_shift.my_shift_worker_status_*' template + """ + cur_user = request.env['res.users'].browse(request.uid) + return { + 'status': cur_user.partner_id.cooperative_status_ids, + } + + +class Shift(object): + """ + Represent a shift with all useful information in a format that is directly printable in a template + """ + + def __init__(self, shift_rec=None): + self.id = 0 + self._start_day = '' + self._start_date = '' + self._start_time = '' + self._end_time = '' + self.task_type_name = '' + self.super_coop_name = '' + self.super_coop_phone = '' + self.super_coop_email = '' + if shift_rec: + self.update(shift_rec) + + def update(self, shift_rec=None): + """ Fill in self with data in the given record""" + if shift_rec: + self.id = shift_rec.id + self.start_day = shift_rec.start_time + self.start_date = shift_rec.start_time + self.start_time = shift_rec.start_time + self.end_time = shift_rec.end_time + if shift_rec.task_type_id: + self.task_type_name = shift_rec.task_type_id.name + if shift_rec.super_coop_id: + self.super_coop_name = shift_rec.super_coop_id.name + self.super_coop_phone = shift_rec.super_coop_id.phone + self.super_coop_email = shift_rec.super_coop_id.email + + # Properties + @property + def start_day(self): + return self._start_day + + @property + def start_date(self): + return self._start_date + + @property + def start_time(self): + return self._start_time + + @property + def end_time(self): + return self._end_time + + # Setters + @start_day.setter + def start_day(self, datetime_str): + self._start_day = datetime.strptime(datetime_str, DATETIME_FORMAT).strftime('%A') + + @start_date.setter + def start_date(self, datetime_str): + self._start_date = datetime.strptime(datetime_str, DATETIME_FORMAT).strftime('%d %B %Y') + + @start_time.setter + def start_time(self, datetime_str): + self._start_time = datetime.strptime(datetime_str, DATETIME_FORMAT).strftime('%H:%M') + + @end_time.setter + def end_time(self, datetime_str): + self._end_time = datetime.strptime(datetime_str, DATETIME_FORMAT).strftime('%H:%M') + + # Deleters + @start_day.deleter + def start_day(self): + del self._start_day + + @start_date.deleter + def start_date(self): + del self._start_date + + @start_time.deleter + def start_time(self): + del self._start_time + + @end_time.deleter + def end_time(self): + del self._end_time diff --git a/beesdoo_website_shift/data/res_config_data.xml b/beesdoo_website_shift/data/res_config_data.xml new file mode 100644 index 0000000..55f661d --- /dev/null +++ b/beesdoo_website_shift/data/res_config_data.xml @@ -0,0 +1,37 @@ + + + + + + beesdoo_website_shift.irregular_shift_limit + 0 + + + beesdoo_website_shift.highlight_rule + 3 + + + beesdoo_website_shift.hide_rule + 20 + + + beesdoo_website_shift.irregular_enable_sign_up + True + + + beesdoo_website_shift.irregular_past_shift_limit + 10 + + + beesdoo_website_shift.regular_past_shift_limit + 10 + + + beesdoo_website_shift.regular_next_shift_limit + 13 + + + diff --git a/beesdoo_website_shift/i18n/fr_BE.po b/beesdoo_website_shift/i18n/fr_BE.po index 605ffd5..2d4e825 100644 --- a/beesdoo_website_shift/i18n/fr_BE.po +++ b/beesdoo_website_shift/i18n/fr_BE.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * beesdoo_portal_shift -# +# # Tanslators: # Rémy Taymans , 2017 msgid "" @@ -77,7 +77,7 @@ msgstr "Créneaux" #. module: beesdoo_portal_shift #: model:ir.ui.view,arch_db:beesdoo_portal_shift.task_template -msgid "Super Co-operator" +msgid "Super Cooperator" msgstr "Super-coopérateur" #. module: beesdoo_portal_shift diff --git a/beesdoo_website_shift/models/__init__.py b/beesdoo_website_shift/models/__init__.py new file mode 100644 index 0000000..ab480f8 --- /dev/null +++ b/beesdoo_website_shift/models/__init__.py @@ -0,0 +1 @@ +from . import res_config diff --git a/beesdoo_website_shift/models/res_config.py b/beesdoo_website_shift/models/res_config.py new file mode 100644 index 0000000..e7e4910 --- /dev/null +++ b/beesdoo_website_shift/models/res_config.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- + +# Copyright 2017-2018 Rémy Taymans +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from ast import literal_eval +from openerp import fields, models, api + +PARAMS = [ + ('irregular_shift_limit', 'beesdoo_website_shift.irregular_shift_limit'), + ('highlight_rule', 'beesdoo_website_shift.highlight_rule'), + ('hide_rule', 'beesdoo_website_shift.hide_rule'), + ('irregular_enable_sign_up', 'beesdoo_website_shift.irregular_enable_sign_up'), + ('irregular_past_shift_limit', 'beesdoo_website_shift.irregular_past_shift_limit'), + ('regular_past_shift_limit', 'beesdoo_website_shift.regular_past_shift_limit'), + ('regular_next_shift_limit', 'beesdoo_website_shift.regular_next_shift_limit'), +] + + +class WebsiteShiftConfigSettings(models.TransientModel): + + _name = 'beesdoo.website.shift.config.settings' + _inherit = 'res.config.settings' + + # Irregular worker settings + irregular_shift_limit = fields.Integer( + help="Maximum shift that will be shown" + ) + highlight_rule = fields.Integer( + help="Treshold of available space in a shift that trigger the highlight of the shift" + ) + hide_rule = fields.Integer( + help="Treshold ((available space)/(max space)) in percentage of available space under wich the shift is hidden" + ) + irregular_enable_sign_up = fields.Boolean( + help="Enable shift sign up for irregular worker" + ) + irregular_past_shift_limit = fields.Integer( + help="Maximum past shift that will be shown for irregular worker" + ) + + # Regular worker settings + regular_past_shift_limit = fields.Integer( + help="Maximum past shift that will be shown for regular worker" + ) + regular_next_shift_limit = fields.Integer( + help="Maximun number of next shift that will be shown" + ) + + @api.multi + def set_params(self): + self.ensure_one() + + for field_name, key_name in PARAMS: + value = getattr(self, field_name) + self.env['ir.config_parameter'].set_param(key_name, str(value)) + + @api.multi + def get_default_irregular_shift_limit(self): + return { + 'irregular_shift_limit': int(self.env['ir.config_parameter'].get_param( + 'beesdoo_website_shift.irregular_shift_limit')) + } + + @api.multi + def get_default_highlight_rule(self): + return { + 'highlight_rule': int(self.env['ir.config_parameter'].get_param('beesdoo_website_shift.highlight_rule')) + } + + @api.multi + def get_default_hide_rule(self): + return { + 'hide_rule': int(self.env['ir.config_parameter'].get_param('beesdoo_website_shift.hide_rule')) + } + + @api.multi + def get_default_irregular_shift_sign_up(self): + return { + 'irregular_enable_sign_up': literal_eval(self.env['ir.config_parameter'].get_param( + 'beesdoo_website_shift.irregular_enable_sign_up')) + } + + @api.multi + def get_default_irregular_past_shift_limit(self): + return { + 'irregular_past_shift_limit': int(self.env['ir.config_parameter'].get_param( + 'beesdoo_website_shift.irregular_past_shift_limit')) + } + + @api.multi + def get_default_regular_past_shift_limit(self): + return { + 'regular_past_shift_limit': int(self.env['ir.config_parameter'].get_param( + 'beesdoo_website_shift.regular_past_shift_limit')) + } + + @api.multi + def get_default_regular_next_shift_limit(self): + return { + 'regular_next_shift_limit': int(self.env['ir.config_parameter'].get_param( + 'beesdoo_website_shift.regular_next_shift_limit')) + } diff --git a/beesdoo_website_shift/views/my_shift_website_templates.xml b/beesdoo_website_shift/views/my_shift_website_templates.xml new file mode 100644 index 0000000..d520122 --- /dev/null +++ b/beesdoo_website_shift/views/my_shift_website_templates.xml @@ -0,0 +1,685 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/beesdoo_website_shift/views/res_config_views.xml b/beesdoo_website_shift/views/res_config_views.xml new file mode 100644 index 0000000..960c712 --- /dev/null +++ b/beesdoo_website_shift/views/res_config_views.xml @@ -0,0 +1,100 @@ + + + + + + + Website Shift Settings Irregular Worker + beesdoo.website.shift.config.settings + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + Website Shift Settings Regular Worker + beesdoo.website.shift.config.settings + +
+
+
+
+
+
+
+
+
+
+ + + Website Shift Settings Irregular Worker + beesdoo.website.shift.config.settings + + form + inline + + + + Website Shift Settings Regular Worker + beesdoo.website.shift.config.settings + + form + inline + + + + + + + + +
+
diff --git a/beesdoo_website_shift/views/shift_website_templates.xml b/beesdoo_website_shift/views/shift_website_templates.xml index e7dba77..c9f11b1 100644 --- a/beesdoo_website_shift/views/shift_website_templates.xml +++ b/beesdoo_website_shift/views/shift_website_templates.xml @@ -1,3 +1,8 @@ + + @@ -16,9 +21,9 @@ - -