Browse Source

[ADD] beesdoo_shift module, first version of the module to manage shifts

pull/105/head
Thibault Francois 7 years ago
committed by Elouan
parent
commit
366e4dc2a1
  1. 3
      beesdoo_shift/__init__.py
  2. 28
      beesdoo_shift/__openerp__.py
  3. 3
      beesdoo_shift/models/__init__.py
  4. 118
      beesdoo_shift/models/planning.py
  5. 36
      beesdoo_shift/models/task.py
  6. 6
      beesdoo_shift/security/ir.model.access.csv
  7. 80
      beesdoo_shift/views/planning.xml
  8. 77
      beesdoo_shift/views/task.xml
  9. 246
      beesdoo_shift/views/task_template.xml
  10. 2
      beesdoo_shift/wizard/__init__.py
  11. 53
      beesdoo_shift/wizard/batch_template.py
  12. 41
      beesdoo_shift/wizard/batch_template.xml
  13. 18
      beesdoo_shift/wizard/instanciate_planning.py
  14. 20
      beesdoo_shift/wizard/instanciate_planning.xml

3
beesdoo_shift/__init__.py

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
import models
import wizard

28
beesdoo_shift/__openerp__.py

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
{
'name': "Beescoop Shift Management",
'summary': """
Volonteer Timetable Management""",
'description': """
""",
'author': "Thibault Francois",
'website': "https://github.com/beescoop/Obeesdoo",
'category': 'Coop',
'version': '0.1',
'depends': ['beesdoo_base'],
'data': [
"security/ir.model.access.csv",
"views/task_template.xml",
"views/task.xml",
"views/planning.xml",
"wizard/instanciate_planning.xml",
"wizard/batch_template.xml",
],
}

3
beesdoo_shift/models/__init__.py

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
import planning
import task

118
beesdoo_shift/models/planning.py

@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
from openerp import models, fields, api, _
from openerp.exceptions import UserError
from pytz import UTC
import math
from datetime import datetime, timedelta
def float_to_time(f):
decimal, integer = math.modf(f)
return "%s:%s" % (str(int(integer)).zfill(2), str(int(round(decimal * 60))).zfill(2))
def floatime_to_hour_minute(f):
decimal, integer = math.modf(f)
return int(integer), int(round(decimal * 60))
def get_first_day_of_week():
today = datetime.now()
return datetime.now() - timedelta(days=today.weekday())
class TaskType(models.Model):
_name = 'beesdoo.shift.type'
name = fields.Char()
description = fields.Text()
active = fields.Boolean(default=True)
class DayNumber(models.Model):
_name = 'beesdoo.shift.daynumber'
_order = 'number asc'
name = fields.Char()
number = fields.Integer("Day Number", help="From 1 to N, When you will instanciate your planning, Day 1 will be the start date of the instance, Day 2 the second, etc...")
active = fields.Boolean(default=True)
class Planning(models.Model):
_name = 'beesdoo.shift.planning'
name = fields.Char()
task_template_ids = fields.One2many('beesdoo.shift.template', 'planning_id')
class TaskTemplate(models.Model):
_name = 'beesdoo.shift.template'
name = fields.Char(required=True)
planning_id = fields.Many2one('beesdoo.shift.planning', required=True)
day_nb_id = fields.Many2one('beesdoo.shift.daynumber', string='Day', required=True)
task_type_id = fields.Many2one('beesdoo.shift.type', string="Type")
start_time = fields.Float(required=True)
end_time = fields.Float(required=True)
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')])
remaining_worker = fields.Integer(compute="_get_remaining", store=True, string="Remaining Place")
active = fields.Boolean(default=True)
#For Kanban View Only
color = fields.Integer('Color Index')
worker_name = fields.Char(compute="_get_worker_name")
#For calendar View
start_date = fields.Datetime(compute="_get_fake_date", search="_dummy_search")
end_date = fields.Datetime(compute="_get_fake_date", search="_dummy_search")
@api.depends('start_time', 'end_time')
def _get_fake_date(self):
today = datetime.strptime(self._context.get('visualize_date'), '%Y-%m-%d') if self._context.get('visualize_date') else get_first_day_of_week()
for rec in self:
day = today + timedelta(days=rec.day_nb_id.number - 1)
h_begin, m_begin = floatime_to_hour_minute(rec.start_time)
h_end, m_end = floatime_to_hour_minute(rec.end_time)
rec.start_date = fields.Datetime.context_timestamp(self, day).replace(hour=h_begin, minute=m_begin, second=0).astimezone(UTC)
rec.end_date = fields.Datetime.context_timestamp(self, day).replace(hour=h_end, minute=m_end, second=0).astimezone(UTC)
def _dummy_search(self, operator, value):
return []
@api.depends('worker_ids', 'worker_nb')
def _get_remaining(self):
for rec in self:
rec.remaining_worker = rec.worker_nb - len(rec.worker_ids)
@api.depends("worker_ids")
def _get_worker_name(self):
for rec in self:
rec.worker_name = ','.join(rec.worker_ids.mapped('display_name'))
@api.constrains('worker_nb', 'worker_ids')
def _nb_worker_max(self):
for rec in self:
if len(rec.worker_ids) > rec.worker_nb:
raise UserError(_('you cannot assign more worker then the number maximal define on the template'))
@api.onchange('start_time', 'end_time')
def _get_duration(self):
if self.start_time and self.end_time:
self.duration = self.end_time - self.start_time
@api.onchange('duration')
def _set_duration(self):
if self.start_time:
self.end_time = self.start_time +self.duration
def _generate_task_day(self):
tasks = self.env['beesdoo.shift.shift']
for rec in self:
for i in xrange(0, rec.worker_nb):
tasks |= tasks.create({
'name' : "%s (%s) - (%s) [%s]" % (rec.name, float_to_time(rec.start_time), float_to_time(rec.end_time), i),
'task_template_id' : rec.id,
'task_type_id' : rec.task_type_id.id,
'worker_id' : rec.worker_ids[i].id if len(rec.worker_ids) > i else False,
'start_time' : rec.start_date,
'end_time' : rec.end_date,
})
return tasks

36
beesdoo_shift/models/task.py

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
from openerp import models, fields
STATES = [
('draft', 'Unconfirmed'),
('open', 'Confirmed'),
('done', 'Attended'),
('absent', 'Absent'),
('excused', 'Excused'),
('replaced', 'Replaced'),
('cancel', 'Cancelled'),
]
class Task(models.Model):
_name = 'beesdoo.shift.shift'
#EX01 ADD inheritance
_inherit = ['mail.thread']
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')])
start_time = fields.Datetime(track_visibility='always')
end_time = fields.Datetime(track_visibility='always')
state = fields.Selection(STATES, default='draft', track_visibility='onchange')
def message_auto_subscribe(self, updated_fields, values=None):
self._add_follower(values)
return super(Task, self).message_auto_subscribe(updated_fields, values=values)
def _add_follower(self, vals):
if vals.get('worker_id'):
worker = self.env['res.partner'].browse(vals['worker_id'])
self.message_subscribe(partner_ids=worker.ids)

6
beesdoo_shift/security/ir.model.access.csv

@ -0,0 +1,6 @@
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_coopplanning_task_type,access_coopplanning_task_type,model_beesdoo_shift_type,,1,1,1,1
access_coopplanning_daynumber,access_coopplanning_daynumber,model_beesdoo_shift_daynumber,,1,1,1,1
access_coopplanning_planning,access_coopplanning_planning,model_beesdoo_shift_planning,,1,1,1,1
access_coopplanning_task_template,access_coopplanning_task_template,model_beesdoo_shift_template,,1,1,1,1
access_coopplanning_task,access_coopplanning_task,model_beesdoo_shift_shift,,1,1,1,1

80
beesdoo_shift/views/planning.xml

@ -0,0 +1,80 @@
<odoo>
<record model="ir.ui.view" id="planning_view_tree">
<field name="name">Planning List</field>
<field name="model">beesdoo.shift.planning</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
</tree>
</field>
</record>
<record model="ir.actions.act_window" id="action_shift_template">
<field name="name">Planning Action</field>
<field name="res_model">beesdoo.shift.template</field>
<field name="view_mode">kanban,tree,form,calendar,pivot</field>
<field name="context">{'group_by': 'day_nb_id',
'search_default_planning_id': active_id,
'default_planning_id': active_id}</field>
</record>
<record model="ir.actions.act_window" id="action_generate_shift_wizard">
<field name="name">Instanciate Planning Action</field>
<field name="res_model">beesddoo.shift.generate_planning</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<record model="ir.ui.view" id="planning_view_form">
<field name="name">Planning Form</field>
<field name="model">beesdoo.shift.planning</field>
<field name="arch" type="xml">
<form>
<header>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" type="action"
name="%(action_shift_template)d" icon="fa-book"
string="Shifts">
<!-- <field string="Ventes" name="sale_order_count"
widget="statinfo" modifiers="{'readonly': true}"/> -->
</button>
</div>
<div class="oe_title">
<h1>
<field name="name" placeholder="name" />
</h1>
</div>
<group>
<button type="action"
name="%(action_generate_shift_wizard)d" string="Generate shifts" colspan="2"/>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="planning_view_tree">
<field name="name">Planning Tree</field>
<field name="model">beesdoo.shift.planning</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<button type="action" name="%(action_shift_template)d"
string="Shifts" />
</tree>
</field>
</record>
<!-- Actions -->
<record model="ir.actions.act_window" id="action_planning">
<field name="name">Planning Action</field>
<field name="res_model">beesdoo.shift.planning</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem name="Planning Week" id="menu_planning" parent="menu_template_top"
sequence="10" action="action_planning" />
</odoo>

77
beesdoo_shift/views/task.xml

@ -0,0 +1,77 @@
<odoo>
<record model="ir.ui.view" id="task_view_tree">
<field name="name">Task Template List</field>
<field name="model">beesdoo.shift.shift</field>
<field name="arch" type="xml">
<tree>
<field name="start_time" />
<field name="planning_id" />
<field name="task_type_id" />
<field name="name" />
<field name="worker_id" />
<field name="end_time" />
</tree>
</field>
</record>
<record model="ir.ui.view" id="task_view_calendar">
<field name="name">Task Template List</field>
<field name="model">beesdoo.shift.shift</field>
<field name="arch" type="xml">
<calendar string="Calendar View" date_start="start_time"
date_stop="end_time" color="task_type_id">
<field name="name" />
<field name="worker_id" />
</calendar>
</field>
</record>
<record model="ir.ui.view" id="task_view_form">
<field name="name">Task Template Form</field>
<field name="model">beesdoo.shift.shift</field>
<field name="arch" type="xml">
<form>
<header>
<field name="state" widget="statusbar" clickable="1" />
</header>
<sheet>
<div class="oe_title">
<h1>
<field name="name" />
</h1>
</div>
<group>
<group>
<field name="task_template_id" />
<field name="task_type_id" />
<field name="worker_id" />
</group>
<group>
<field name="start_time" />
<field name="end_time" />
</group>
</group>
</sheet>
<!-- Solution Ex1 -->
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" />
<field name="message_ids" widget="mail_thread" />
</div>
</form>
</field>
</record>
<!-- Actions -->
<record model="ir.actions.act_window" id="action_task">
<field name="name">Task Action</field>
<field name="res_model">beesdoo.shift.shift</field>
<field name="view_mode">calendar,tree,form,pivot</field>
</record>
<!-- Top menu item -->
<menuitem name="Planning" id="menu_task_top" parent="menu_root"
sequence="1" />
<!-- actions -->
<menuitem name="Shift" id="menu_task" parent="menu_task_top"
action="action_task" />
</odoo>

246
beesdoo_shift/views/task_template.xml

@ -0,0 +1,246 @@
<odoo>
<record model="ir.ui.view" id="task_template_view_tree">
<field name="name">Shift Template List</field>
<field name="model">beesdoo.shift.template</field>
<field name="arch" type="xml">
<tree>
<field name="planning_id" />
<field name="task_type_id" />
<field name="name" />
<field name="day_nb_id" />
<field name="start_time" />
<field name="end_time" />
<field name="duration" />
<field name="worker_nb" />
<field name="remaining_worker" />
</tree>
</field>
</record>
<record model="ir.ui.view" id="task_template_view_search">
<field name="name">Shift Template Search</field>
<field name="model">beesdoo.shift.template</field>
<field name="arch" type="xml">
<search>
<field name="name" />
<field name="planning_id" />
<field name="task_type_id" />
<field name="day_nb_id" />
<field name="worker_ids" />
<filter string="Planning" context="{'group_by':'planning_id'}" />
<filter string="Week Day" context="{'group_by':'day_nb_id'}" />
<filter string="Place Available" name="available"
domain="[('remaining_worker', '>', 0)]" />
</search>
</field>
</record>
<record model="ir.ui.view" id="task_template_view_form">
<field name="name">Task Template Form</field>
<field name="model">beesdoo.shift.template</field>
<field name="arch" type="xml">
<form>
<sheet>
<div class="oe_title">
<h1>
<field name="name" placeholder="name" />
</h1>
</div>
<group>
<group>
<field name="day_nb_id" />
<field name="planning_id" />
<field name="task_type_id" />
<field name="worker_nb" />
<field name="remaining_worker" />
<field name="active" />
</group>
<group>
<field name="start_time" widget="float_time" />
<field name="duration" widget="float_time" />
<field name="end_time" widget="float_time" />
</group>
</group>
<separator string="Recurring Workers" />
<field name="worker_ids" nolabel="1" />
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="task_template_view_calendar">
<field name="name">Shift Template Calendar</field>
<field name="model">beesdoo.shift.template</field>
<field name="arch" type="xml">
<calendar string="Calendar View" date_start="start_date"
date_stop="end_date" color="task_type_id">
<field name="name" />
<field name="worker_ids" />
</calendar>
</field>
</record>
<record model="ir.ui.view" id="task_template_view_kanban">
<field name="name">Task Template Kanban</field>
<field name="model">beesdoo.shift.template</field>
<field name="arch" type="xml">
<kanban>
<field name="planning_id" />
<field name="color" />
<field name="task_type_id" />
<field name="name" />
<field name="day_nb_id" />
<field name="worker_nb" />
<field name="worker_ids" />
<field name="worker_name" />
<templates>
<t t-name="kanban-box">
<div
t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click">
<div class="o_dropdown_kanban dropdown"
groups="base.group_user">
<a class="dropdown-toggle btn"
data-toggle="dropdown" href="#">
<span class="fa fa-bars fa-lg" />
</a>
<ul class="dropdown-menu" role="menu"
aria-labelledby="dLabel">
<t t-if="widget.editable">
<li>
<a type="edit">Edit Shift
Template</a>
</li>
</t>
<t t-if="widget.deletable">
<li>
<a type="delete">Delete</a>
</li>
</t>
<li>
<ul class="oe_kanban_colorpicker"
data-field="color" />
</li>
</ul>
</div>
<div class="oe_kanban_content">
<strong>
<field name="name" />
</strong>
<div>
<field name="planning_id" />
</div>
<div t-if="record.task_type_id.raw_value">
Type:
<field name="task_type_id" />
</div>
<div>
Worker Number:
<field name="worker_nb" />
</div>
<div>
<field name="start_time"
widget="float_time" />
-
<field name="end_time" widget="float_time" />
</div>
</div>
<div>
<br />
<strong>Recurring Workers</strong>
<t
t-foreach="record.worker_name.raw_value.split(',')"
t-as="worker">
<div>
<t t-esc="worker" />
</div>
</t>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<menuitem name="Shift Management" id="menu_root" />
<menuitem name="Templates" id="menu_template_top" parent="menu_root" />
<!-- Configuration -->
<record model="ir.ui.view" id="daynumber_view_tree">
<field name="name">Day Number List</field>
<field name="model">beesdoo.shift.daynumber</field>
<field name="arch" type="xml">
<tree editable="top">
<field name="name" />
<field name="number" />
<field name="active" />
</tree>
</field>
</record>
<record model="ir.ui.view" id="type_view_tree">
<field name="name">Shift Type List</field>
<field name="model">beesdoo.shift.type</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<field name="description" />
<field name="active" />
</tree>
</field>
</record>
<record model="ir.actions.act_window" id="action_generate_shift_template_wizard">
<field name="name">Generate Shift Template</field>
<field name="res_model">beesddoo.shift.generate_shift_template</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<record model="ir.ui.view" id="type_view_form">
<field name="name">Shift Type Form</field>
<field name="model">beesdoo.shift.type</field>
<field name="arch" type="xml">
<form>
<header>
<button type="action"
name="%(action_generate_shift_template_wizard)d"
string="Generate shift Templates" />
</header>
<sheet>
<group>
<group>
<field name="name" />
</group>
<group>
<field name="active" />
</group>
</group>
<separator string="Description" />
<field name="description" />
</sheet>
</form>
</field>
</record>
<menuitem name="Configuration" id="menu_configuration_top"
parent="menu_root" />
<record model="ir.actions.act_window" id="action_day_number">
<field name="name">Day Number</field>
<field name="res_model">beesdoo.shift.daynumber</field>
<field name="view_mode">tree</field>
</record>
<menuitem name="Shift Day" id="menu_configuration_day"
parent="menu_configuration_top" action="action_day_number" />
<record model="ir.actions.act_window" id="action_type">
<field name="name">Shift Type</field>
<field name="res_model">beesdoo.shift.type</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem name="Shift Type" id="menu_configuration_type"
parent="menu_configuration_top" action="action_type" />
</odoo>

2
beesdoo_shift/wizard/__init__.py

@ -0,0 +1,2 @@
import instanciate_planning
import batch_template

53
beesdoo_shift/wizard/batch_template.py

@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
'''
Created on 2 janv. 2017
@author: Thibault Francois
'''
from openerp import models, fields, api, _
class GenerateShiftTemplate(models.TransientModel):
_name = 'beesddoo.shift.generate_shift_template'
day_ids = fields.Many2many('beesdoo.shift.daynumber', relation='template_gen_day_number_rel', column1='wizard_id', column2='day_id')
planning_id = fields.Many2one('beesdoo.shift.planning', required=True)
type_id = fields.Many2one('beesdoo.shift.type', default=lambda self: self._context.get('active_id'))
line_ids = fields.One2many('beesddoo.shift.generate_shift_template.line', 'wizard_id')
@api.multi
def generate(self):
self.ensure_one()
ids = []
for day in self.day_ids:
for line in self.line_ids:
shift_template_data = {
'name': '%s' % self.type_id.name,
'planning_id': self.planning_id.id,
'task_type_id': self.type_id.id,
'day_nb_id': day.id,
'start_time': line.start_time,
'end_time': line.end_time,
'duration': line.end_time - line.start_time,
'worker_nb': line.worker_nb,
}
new_rec = self.env['beesdoo.shift.template'].create(shift_template_data)
ids.append(new_rec.id)
return {
'name': _('Generated Shift Template'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'kanban,tree,form',
'res_model': 'beesdoo.shift.template',
'target': 'current',
'domain': [('id', 'in', ids)],
'context': {'group_by': 'day_nb_id'},
}
class GenerateShiftTemplateLine(models.TransientModel):
_name = 'beesddoo.shift.generate_shift_template.line'
wizard_id = fields.Many2one('beesddoo.shift.generate_shift_template')
start_time = fields.Float(required=True)
end_time = fields.Float(required=True)
worker_nb = fields.Integer(default=1)

41
beesdoo_shift/wizard/batch_template.xml

@ -0,0 +1,41 @@
<odoo>
<record model="ir.ui.view" id="task_template_generation_view_form">
<field name="name">Planning Form</field>
<field name="model">beesddoo.shift.generate_shift_template</field>
<field name="arch" type="xml">
<form>
<group>
<field name="planning_id" />
<field name="type_id" invisible="1" />
</group>
<group>
<group>
<separator string="Daily Schedule" colspan="2"/>
<field name="line_ids" nolabel="1" >
<tree editable="bottom">
<field name="start_time" widget="float_time" />
<field name="end_time" widget="float_time" />
<field name="worker_nb" />
</tree>
</field>
</group>
<group>
<separator string="Apply for Days" colspan="2"/>
<field name="day_ids" nolabel="1" >
<tree>
<field name="name" />
<field name="number" />
</tree>
</field>
</group>
</group>
<footer>
<button type="object" name="generate" string="Confirm" class="oe_highlight"/>
or
<button special="cancel" string="Cancel"/>
</footer>
</form>
</field>
</record>
</odoo>

18
beesdoo_shift/wizard/instanciate_planning.py

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from openerp import models, fields, api
class InstanciatePlanning(models.TransientModel):
_name = 'beesddoo.shift.generate_planning'
def _get_planning(self):
return self._context.get('active_id')
date_start = fields.Date("First Day of planning", required=True)
planning_id = fields.Many2one('beesdoo.shift.planning', readonly=True, default=_get_planning)
@api.multi
def generate_task(self):
self.ensure_one()
self = self.with_context(visualize_date=self.date_start)
self.planning_id.task_template_ids._generate_task_day()

20
beesdoo_shift/wizard/instanciate_planning.xml

@ -0,0 +1,20 @@
<odoo>
<record model="ir.ui.view" id="planning_instanciate_view_form">
<field name="name">Planning Form</field>
<field name="model">beesddoo.shift.generate_planning</field>
<field name="arch" type="xml">
<form>
<group>
<field name="date_start" />
<field name="planning_id" invisible="1" />
</group>
<footer>
<button type="object" name="generate_task" string="Confirm" class="oe_highlight"/>
or
<button special="cancel" string="Cancel"/>
</footer>
</form>
</field>
</record>
</odoo>
Loading…
Cancel
Save