Browse Source
[REF] Refactoring of shift module
[REF] Refactoring of shift module
Move counter specific and cooperator status specific to a module call beesdoo_worker_status Remove dependency of beesdoo_base to beesdoo_shift beesdoo_worker_status depends on beesdoo_base Move all attendance sheet related code to a new module attendance sheet Leave empty interface on beesdoo_shift to be implemented in other module to define specific behavior of the counter and the status of the cooperatorpull/143/head
Thibault Francois
5 years ago
44 changed files with 1315 additions and 763 deletions
-
48beesdoo_base/demo/cooperators.xml
-
3beesdoo_base/views/partner.xml
-
10beesdoo_shift/__manifest__.py
-
27beesdoo_shift/data/cron.xml
-
110beesdoo_shift/data/mail_template.xml
-
12beesdoo_shift/data/system_parameter.xml
-
84beesdoo_shift/demo/cooperators.xml
-
6beesdoo_shift/demo/templates.xml
-
89beesdoo_shift/demo/workers.xml
-
2beesdoo_shift/models/__init__.py
-
400beesdoo_shift/models/cooperative_status.py
-
2beesdoo_shift/models/planning.py
-
121beesdoo_shift/models/task.py
-
19beesdoo_shift/security/group.xml
-
15beesdoo_shift/security/ir.model.access.csv
-
56beesdoo_shift/views/cooperative_status.xml
-
2beesdoo_shift/wizard/__init__.py
-
2beesdoo_shift_atttendance/__init__.py
-
36beesdoo_shift_atttendance/__manifest__.py
-
29beesdoo_shift_atttendance/data/cron.xml
-
117beesdoo_shift_atttendance/data/mail_template.xml
-
14beesdoo_shift_atttendance/data/system_parameter.xml
-
2beesdoo_shift_atttendance/models/__init__.py
-
4beesdoo_shift_atttendance/models/attendance_sheet.py
-
61beesdoo_shift_atttendance/models/res_config_settings.py
-
11beesdoo_shift_atttendance/security/group.xml
-
12beesdoo_shift_atttendance/security/ir.model.access.csv
-
0beesdoo_shift_atttendance/tests/__init__.py
-
96beesdoo_shift_atttendance/tests/test_beesdoo_shift.py
-
0beesdoo_shift_atttendance/views/attendance_sheet.xml
-
0beesdoo_shift_atttendance/views/res_config_settings_view.xml
-
1beesdoo_shift_atttendance/wizard/__init__.py
-
0beesdoo_shift_atttendance/wizard/validate_attendance_sheet.py
-
0beesdoo_shift_atttendance/wizard/validate_attendance_sheet.xml
-
3beesdoo_website_shift/controllers/main.py
-
54beesdoo_website_shift/views/my_shift_website_templates.xml
-
8beesdoo_website_shift/views/shift_website_templates.xml
-
0beesdoo_worker_status/__init__.py
-
26beesdoo_worker_status/__manifest__.py
-
1beesdoo_worker_status/models/__init__.py
-
266beesdoo_worker_status/models/cooperative_status.py
-
81beesdoo_worker_status/models/task.py
-
1beesdoo_worker_status/tests/__init__.py
-
247beesdoo_worker_status/tests/test_beesdoo_shift.py
@ -0,0 +1,84 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!-- |
|||
Copyright 2019 Coop IT Easy |
|||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
--> |
|||
<odoo> |
|||
<record id="res_partner_cooperator_1_demo" model="res.partner"> |
|||
<field name="name">Fernand Peso</field> |
|||
<field name="customer" eval="True"/> |
|||
<field name="is_company" eval="False"/> |
|||
<field name="email">fernand_peso@demo.net</field> |
|||
<field name="street">Avenue des Bas-de-Callanques, 15</field> |
|||
<field name="city">Etterbeek</field> |
|||
<field name="zip">1040</field> |
|||
<field name="country_id" ref="base.be"/> |
|||
<field name="working_mode">regular</field> |
|||
<field name="is_worker" eval="True" /> |
|||
</record> |
|||
|
|||
<record id="res_partner_cooperator_2_demo" model="res.partner"> |
|||
<field name="name">Dupont Dupont</field> |
|||
<field name="customer" eval="True"/> |
|||
<field name="is_company" eval="False"/> |
|||
<field name="email">d_dupont@demo.net</field> |
|||
<field name="street">Rue des sables, 20</field> |
|||
<field name="city">Bruxelles</field> |
|||
<field name="zip">10000</field> |
|||
<field name="country_id" ref="base.be"/> |
|||
<field name="working_mode">irregular</field> |
|||
<field name="is_worker" eval="True" /> |
|||
</record> |
|||
|
|||
<record id="res_partner_cooperator_3_demo" model="res.partner"> |
|||
<field name="name">Ronan Le Gall</field> |
|||
<field name="customer" eval="True"/> |
|||
<field name="is_company" eval="False"/> |
|||
<field name="email">ronan_gall@demo.net</field> |
|||
<field name="street">Rue des pecheurs, 23</field> |
|||
<field name="city">Landudec</field> |
|||
<field name="zip">29710</field> |
|||
<field name="country_id" ref="base.fr"/> |
|||
<field name="working_mode">regular</field> |
|||
<field name="is_worker" eval="True" /> |
|||
</record> |
|||
|
|||
<record id="res_partner_cooperator_4_demo" model="res.partner"> |
|||
<field name="name">Elouan Bees</field> |
|||
<field name="customer" eval="True"/> |
|||
<field name="is_company" eval="False"/> |
|||
<field name="email">elouan_bees@demo.net</field> |
|||
<field name="street">Rue Wéry, 15</field> |
|||
<field name="city">Ixelles</field> |
|||
<field name="zip">1050</field> |
|||
<field name="country_id" ref="base.be"/> |
|||
<field name="working_mode">irregular</field> |
|||
<field name="is_worker" eval="True" /> |
|||
</record> |
|||
|
|||
<record id="res_partner_cooperator_5_demo" model="res.partner"> |
|||
<field name="name">Anne de Marchalo</field> |
|||
<field name="customer" eval="True"/> |
|||
<field name="is_company" eval="False"/> |
|||
<field name="email">anne_marchalo@demo.net</field> |
|||
<field name="street">Rue du Wels, 6</field> |
|||
<field name="city">Nantes</field> |
|||
<field name="zip">44000</field> |
|||
<field name="country_id" ref="base.fr"/> |
|||
<field name="working_mode">regular</field> |
|||
<field name="is_worker" eval="True" /> |
|||
</record> |
|||
|
|||
<record id="res_partner_cooperator_6_demo" model="res.partner"> |
|||
<field name="name">Jean Beaumont</field> |
|||
<field name="customer" eval="True"/> |
|||
<field name="is_company" eval="False"/> |
|||
<field name="email">jean_beaumont@demo.net</field> |
|||
<field name="street">Rue de la Jungle, 8</field> |
|||
<field name="city">St-Gilles</field> |
|||
<field name="zip">1060</field> |
|||
<field name="country_id" ref="base.be"/> |
|||
<field name="working_mode">regular</field> |
|||
<field name="is_worker" eval="True" /> |
|||
</record> |
|||
</odoo> |
@ -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 |
@ -0,0 +1,2 @@ |
|||
from . import models |
|||
from . import wizard |
@ -0,0 +1,36 @@ |
|||
{ |
|||
'name': "Beescoop Shift Attendance Sheet", |
|||
|
|||
'summary': """ |
|||
Volonteer Timetable Management |
|||
Attendance Sheet""", |
|||
|
|||
'description': """ |
|||
|
|||
""", |
|||
|
|||
'author': "Elouan Le Bars, Coop It Easy", |
|||
'website': "https://github.com/beescoop/Obeesdoo", |
|||
|
|||
'category': 'Cooperative management', |
|||
'version': '12.0.1.0.0', |
|||
|
|||
'depends': [ |
|||
'beesdoo_shift', |
|||
'beesdoo_worker_status', #TODO move the part that require beesdoo_worker_status in beesdoo_worker status or another module |
|||
'mail', |
|||
'barcodes', |
|||
], |
|||
|
|||
'data': [ |
|||
"data/system_parameter.xml", |
|||
"data/cron.xml", |
|||
"data/mail_template.xml", |
|||
"security/group.xml", |
|||
"security/ir.model.access.csv", |
|||
"views/res_config_settings_view.xml", |
|||
"wizard/validate_attendance_sheet.xml", |
|||
"views/attendance_sheet.xml", |
|||
], |
|||
'demo': [] |
|||
} |
@ -0,0 +1,29 @@ |
|||
<odoo> |
|||
<data noupdate="1"> |
|||
<record id="ir_cron_generate_attendance_sheet" model="ir.cron"> |
|||
<field name="name">Generate Attendance Sheets</field> |
|||
<field name="model_id" ref="model_beesdoo_shift_sheet" /> |
|||
<field name="state">code</field> |
|||
<field name="code">model._generate_attendance_sheet()</field> |
|||
<field name="user_id" ref="base.user_root" /> |
|||
<field name="interval_number">4</field> |
|||
<field name="interval_type">minutes</field> |
|||
<field name="numbercall">-1</field> |
|||
<field name="doall" eval="False" /> |
|||
<field name="active" eval="False" /> |
|||
</record> |
|||
|
|||
<record id="ir_cron_check_non_validated_sheet" model="ir.cron"> |
|||
<field name="name">Check for non-validated sheets</field> |
|||
<field name="model_id" ref="model_beesdoo_shift_sheet" /> |
|||
<field name="state">code</field> |
|||
<field name="code">model._cron_non_validated_sheets()</field> |
|||
<field name="interval_number">1</field> |
|||
<field name="interval_type">days</field> |
|||
<field name="numbercall">-1</field> |
|||
<field name="doall" eval="False" /> |
|||
<field name="nextcall" eval="datetime.now().replace(hour=00, minute=00, second=10)" /> |
|||
<field name="active" eval="False" /> |
|||
</record> |
|||
</data> |
|||
</odoo> |
@ -0,0 +1,117 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<odoo> |
|||
<!-- Mail template are declared in a NOUPDATE block |
|||
so users can freely customize/delete them --> |
|||
<data noupdate="1"> |
|||
<record id="email_template_non_attendance" model="mail.template"> |
|||
<field name="name">Shift Non-attendance</field> |
|||
<field name="subject">Non-attendance to your last shift.</field> |
|||
<field name="partner_to">${object.replaced_id.id or object.worker_id.id|safe}</field> |
|||
<field name="model_id" ref="model_beesdoo_shift_shift"/> |
|||
<field name="auto_delete" eval="True"/> |
|||
<field name="lang">${object.worker_id.lang}</field> |
|||
<field name="body_html"><![CDATA[ |
|||
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: #FFF; "> |
|||
|
|||
% if object.replaced_id: |
|||
<p>Hello ${object.replaced_id.name}, |
|||
|
|||
<br><br>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: |
|||
</p><p>Hello ${object.worker_id.name},</p> |
|||
|
|||
<p>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': |
|||
<br><br>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': |
|||
<br><br>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': |
|||
<br><br>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.<br> |
|||
% 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.<br> |
|||
% 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 |
|||
<br> |
|||
% endif |
|||
|
|||
% if object.replaced_id: |
|||
Your current status is "${object.replaced_id.cooperative_status_ids.get_status_value()}". |
|||
% else: |
|||
<br><br>Your current status is "${object.worker_id.cooperative_status_ids.get_status_value()}". |
|||
% endif |
|||
|
|||
<br>If you have any question regarding this non-attendance, just answer this e-mail. |
|||
</p> |
|||
<br> |
|||
<p>Cooperatively yours,<br> |
|||
The Members' office volunteers</p> |
|||
<p>${object.worker_id.company_id.name}.</p> |
|||
|
|||
% 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}<br> |
|||
% 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}<br> |
|||
% 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 ''}<br> |
|||
% endif |
|||
% if object.worker_id.company_id.phone: |
|||
Phone: ${object.worker_id.company_id.phone} |
|||
% endif |
|||
|
|||
% if object.worker_id.company_id.website: |
|||
<div> |
|||
Web : <a href="${object.worker_id.company_id.website}">${object.worker_id.company_id.website}</a> |
|||
</div> |
|||
%endif |
|||
% if object.worker_id.company_id.logo_url: |
|||
<div> |
|||
<img src="${object.worker_id.company_id.logo_url}"> |
|||
</div> |
|||
%endif |
|||
</div> |
|||
]]></field> |
|||
</record> |
|||
<record id="email_template_non_validated_sheet" model="mail.template"> |
|||
<field name="name">Non-validated sheet</field> |
|||
<field name="subject">[${object.day}] Non-validated sheet ${object.time_slot}</field> |
|||
<field name="model_id" ref="model_beesdoo_shift_sheet"/> |
|||
<field name="auto_delete" eval="True"/> |
|||
<field name="body_html"><![CDATA[ |
|||
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: #FFF; "> |
|||
|
|||
<p>${object.day} |
|||
<br/><br/>The attendance sheet for ${object.time_slot} is not validated. |
|||
<br/><br/>Please, do it as soon as possible so as to update workers' status. |
|||
</p> |
|||
|
|||
</div> |
|||
]]></field> |
|||
</record> |
|||
</data> |
|||
</odoo> |
@ -0,0 +1,14 @@ |
|||
<odoo noupdate="1"> |
|||
<record id="beesdoo_shift.card_support" model="ir.config_parameter"> |
|||
<field name="key">beesdoo_shift.card_support</field> |
|||
<field name="value">False</field> |
|||
</record> |
|||
<record id="beesdoo_shift.attendance_sheet_generation_interval" model="ir.config_parameter"> |
|||
<field name="key">beesdoo_shift.attendance_sheet_generation_interval</field> |
|||
<field name="value">15</field> |
|||
</record> |
|||
<record id="beesdoo_shift.default_task_type_id" model="ir.config_parameter"> |
|||
<field name="key">beesdoo_shift.default_task_type_id</field> |
|||
<field name="value">1</field> |
|||
</record> |
|||
</odoo> |
@ -0,0 +1,2 @@ |
|||
from . import attendance_sheet |
|||
from . import res_config_settings |
@ -0,0 +1,61 @@ |
|||
# Copyright 2019-2020 Elouan Le Bars <elouan@coopiteasy.be> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
import ast |
|||
|
|||
from odoo import fields, models, api |
|||
|
|||
|
|||
class ResConfigSettings(models.TransientModel): |
|||
_inherit = "res.config.settings" |
|||
|
|||
card_support = fields.Boolean( |
|||
string="Scan cooperators cards instead of login for sheets validation", |
|||
config_parameter="beesdoo_shift.card_support", |
|||
) |
|||
task_type_default_id = fields.Many2one( |
|||
"beesdoo.shift.type", |
|||
string="Default Task Type", |
|||
help="Default task type for attendance sheet pre-filling", |
|||
required=True, |
|||
default=False, |
|||
) |
|||
attendance_sheet_generation_interval = fields.Integer( |
|||
string="Time interval for attendance sheet generation", |
|||
help="Time interval expressed in minutes", |
|||
required=True, |
|||
config_parameter="beesdoo_shift.attendance_sheet_generation_interval", |
|||
) |
|||
|
|||
@api.multi |
|||
def set_values(self): |
|||
super(ResConfigSettings, self).set_values() |
|||
parameters = self.env["ir.config_parameter"].sudo() |
|||
parameters.set_param( |
|||
"beesdoo_shift.card_support", str(self.card_support), |
|||
) |
|||
parameters.set_param( |
|||
"beesdoo_shift.task_type_default_id", |
|||
str(self.task_type_default_id.id), |
|||
) |
|||
parameters.set_param( |
|||
"beesdoo_shift.attendance_sheet_generation_interval", |
|||
str(self.attendance_sheet_generation_interval), |
|||
) |
|||
|
|||
@api.multi |
|||
def get_values(self): |
|||
res = super(ResConfigSettings, self).get_values() |
|||
res.update( |
|||
card_support=ast.literal_eval( |
|||
self.env["ir.config_parameter"].get_param( |
|||
"beesdoo_shift.card_support" |
|||
), |
|||
), |
|||
task_type_default_id=int( |
|||
self.env["ir.config_parameter"].get_param( |
|||
"beesdoo_shift.task_type_default_id" |
|||
) |
|||
), |
|||
) |
|||
return res |
@ -0,0 +1,11 @@ |
|||
<odoo> |
|||
<record id="group_shift_attendance_sheet" model="res.groups"> |
|||
<field name="name">Attendance Sheet Generic Access</field> |
|||
<field name="category_id" |
|||
ref="base.module_category_cooperative_management"/> |
|||
</record> |
|||
<record id="beesdoo_shift.group_shift_attendance" model="res.groups"> |
|||
<field name="implied_ids" |
|||
eval="[(4, ref('group_shift_attendance_sheet'))]"/> |
|||
</record> |
|||
</odoo> |
@ -0,0 +1,12 @@ |
|||
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink |
|||
create_beesdoo_shift_shift,create_edit_beesdoo_shift_shift,model_beesdoo_shift_shift,group_shift_attendance_sheet,1,1,1,0 |
|||
read_beesdoo_shift_sheet_shift,read_beesdoo_shift_sheet_shift,model_beesdoo_shift_sheet_shift,group_shift_attendance_sheet,1,0,0,0 |
|||
create_beesdoo_shift_sheet_shift,create_beesdoo_shift_sheet_shift,model_beesdoo_shift_sheet_shift,group_shift_attendance_sheet,1,1,1,0 |
|||
create_beesdoo_shift_sheet_expected,create_beesdoo_shift_sheet_expected,model_beesdoo_shift_sheet_expected,group_shift_attendance_sheet,1,1,1,0 |
|||
manage_beesdoo_shift_sheet_added,manage_beesdoo_shift_sheet_added,model_beesdoo_shift_sheet_added,group_shift_attendance_sheet,1,1,1,1 |
|||
create_beesdoo_shift_sheet,create_beesdoo_shift_sheet,model_beesdoo_shift_sheet,group_shift_attendance_sheet,1,1,1,0 |
|||
sheet_access_beesdoo_shift_template,sheet_access_beesdoo_shift_template,model_beesdoo_shift_template,group_shift_attendance_sheet,1,0,0,0 |
|||
sheet_access_beesdoo_shift_type,sheet_access_beesdoo_shift_type,model_beesdoo_shift_type,group_shift_attendance_sheet,1,0,0,0 |
|||
access_beesdoo_shift_daynumber,access_beesdoo_shift_daynumber,model_beesdoo_shift_daynumber,group_shift_attendance_sheet,1,0,0,0 |
|||
manage_beesdoo_shift_sheet_shift,beesdoo_shift_sheet_shift,model_beesdoo_shift_sheet_shift,group_shift_attendance,1,1,1,1 |
|||
manage_beesdoo_shift_sheet_expected,manage_beesdoo_shift_sheet_expected,model_beesdoo_shift_sheet_expected,group_shift_attendance,1,1,1,1 |
@ -0,0 +1 @@ |
|||
from . import validate_attendance_sheet |
@ -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': [ |
|||
] |
|||
} |
@ -0,0 +1 @@ |
|||
from . import cooperative_status |
@ -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')] |
@ -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 |
@ -0,0 +1 @@ |
|||
from . import test_beesdoo_shift |
@ -0,0 +1,247 @@ |
|||
# Copyright 2019 - Today Coop IT Easy SCRLfs (<http://www.coopiteasy.be>) |
|||
# 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) |
Write
Preview
Loading…
Cancel
Save
Reference in new issue