Browse Source
Merge pull request #133 from beescoop/12.0-mig-beesdoo_shift
Merge pull request #133 from beescoop/12.0-mig-beesdoo_shift
[12.0][MIG] beesdoo_shift, beesdoo_website_shift, beesdoo_worker_statuspull/149/head
Rémy Taymans
5 years ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
82 changed files with 9968 additions and 2135 deletions
-
2beesdoo_base/__manifest__.py
-
10beesdoo_base/data/default_contact.xml
-
48beesdoo_base/demo/cooperators.xml
-
37beesdoo_base/demo/users.xml
-
1beesdoo_base/security/groups.xml
-
7beesdoo_base/views/partner.xml
-
5beesdoo_shift/__init__.py
-
15beesdoo_shift/__manifest__.py
-
37beesdoo_shift/data/cron.xml
-
58beesdoo_shift/data/mail_template.xml
-
44beesdoo_shift/data/stage.xml
-
68beesdoo_shift/demo/templates.xml
-
2654beesdoo_shift/i18n/fr_BE.po
-
2633beesdoo_shift/i18n/nl_BE.po
-
6beesdoo_shift/models/__init__.py
-
401beesdoo_shift/models/cooperative_status.py
-
53beesdoo_shift/models/planning.py
-
253beesdoo_shift/models/task.py
-
21beesdoo_shift/security/group.xml
-
35beesdoo_shift/security/ir.model.access.csv
-
77beesdoo_shift/views/cooperative_status.xml
-
6beesdoo_shift/views/exempt_reason.xml
-
62beesdoo_shift/views/menu.xml
-
39beesdoo_shift/views/planning.xml
-
122beesdoo_shift/views/task.xml
-
93beesdoo_shift/views/task_template.xml
-
14beesdoo_shift/wizard/__init__.py
-
3beesdoo_shift/wizard/assign_super_coop.py
-
3beesdoo_shift/wizard/batch_template.py
-
13beesdoo_shift/wizard/extension.py
-
9beesdoo_shift/wizard/holiday.py
-
5beesdoo_shift/wizard/instanciate_planning.py
-
7beesdoo_shift/wizard/subscribe.py
-
2beesdoo_shift/wizard/subscribe.xml
-
9beesdoo_shift/wizard/temporary_exemption.py
-
2beesdoo_shift_attendance/__init__.py
-
45beesdoo_shift_attendance/__manifest__.py
-
29beesdoo_shift_attendance/data/cron.xml
-
118beesdoo_shift_attendance/data/mail_template.xml
-
14beesdoo_shift_attendance/data/system_parameter.xml
-
40beesdoo_shift_attendance/demo/users.xml
-
2beesdoo_shift_attendance/models/__init__.py
-
654beesdoo_shift_attendance/models/attendance_sheet.py
-
63beesdoo_shift_attendance/models/res_config_settings.py
-
15beesdoo_shift_attendance/security/group.xml
-
12beesdoo_shift_attendance/security/ir.model.access.csv
-
1beesdoo_shift_attendance/tests/__init__.py
-
393beesdoo_shift_attendance/tests/test_beesdoo_shift.py
-
263beesdoo_shift_attendance/views/attendance_sheet.xml
-
93beesdoo_shift_attendance/views/res_config_settings_view.xml
-
2beesdoo_shift_attendance/wizard/__init__.py
-
57beesdoo_shift_attendance/wizard/generate_missing_attendance_sheets.py
-
35beesdoo_shift_attendance/wizard/generate_missing_attendance_sheets.xml
-
138beesdoo_shift_attendance/wizard/validate_attendance_sheet.py
-
48beesdoo_shift_attendance/wizard/validate_attendance_sheet.xml
-
26beesdoo_website_shift/__manifest__.py
-
32beesdoo_website_shift/__openerp__.py
-
90beesdoo_website_shift/controllers/main.py
-
38beesdoo_website_shift/data/res_config_data.xml
-
2beesdoo_website_shift/i18n/fr_BE.po
-
11beesdoo_website_shift/migrations/9.0.2.1.1/post-migrate.py
-
1beesdoo_website_shift/models/__init__.py
-
127beesdoo_website_shift/models/res_config.py
-
47beesdoo_website_shift/models/website.py
-
1417beesdoo_website_shift/views/my_shift_website_templates.xml
-
184beesdoo_website_shift/views/res_config_views.xml
-
336beesdoo_website_shift/views/shift_website_templates.xml
-
1beesdoo_worker_status/__init__.py
-
29beesdoo_worker_status/__manifest__.py
-
30beesdoo_worker_status/demo/cooperators.xml
-
46beesdoo_worker_status/demo/tasks.xml
-
77beesdoo_worker_status/demo/workers.xml
-
2beesdoo_worker_status/models/__init__.py
-
284beesdoo_worker_status/models/cooperative_status.py
-
81beesdoo_worker_status/models/task.py
-
1beesdoo_worker_status/tests/__init__.py
-
228beesdoo_worker_status/tests/test_beesdoo_shift.py
-
1macavrac_base/__init__.py
-
23macavrac_base/__manifest__.py
-
1macavrac_base/models/__init__.py
-
49macavrac_base/models/res_partner.py
-
35macavrac_base/views/res_partner.xml
@ -1,10 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<odoo> |
|
||||
<data noupdate="1"> |
|
||||
<record model="res.partner" id="commande_beescoop"> |
|
||||
<field name="name">commande@bees-coop.be</field> |
|
||||
<field name="email">commande@bees-coop.be</field> |
|
||||
<field name="active">True</field> |
|
||||
</record> |
|
||||
</data> |
|
||||
</odoo> |
|
@ -1,37 +0,0 @@ |
|||||
<?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> |
|
||||
<!-- Generic and permanent account --> |
|
||||
|
|
||||
<record id="beesdoo_shift_partner_1_demo" model="res.partner"> |
|
||||
<field name="firstname">Generic Account</field> |
|
||||
<field name="lastname">Demo</field> |
|
||||
<field name="email">generic@demo.net</field> |
|
||||
</record> |
|
||||
|
|
||||
<record id="beesdoo_shift_partner_2_demo" model="res.partner"> |
|
||||
<field name="firstname">Permanent Member</field> |
|
||||
<field name="lastname">Demo</field> |
|
||||
<field name="is_company" eval="False"/> |
|
||||
<field name="email">permanent@demo.net</field> |
|
||||
<field name="city">Ixelles</field> |
|
||||
<field name="zip">1050</field> |
|
||||
<field name="country_id" ref="base.be"/> |
|
||||
</record> |
|
||||
|
|
||||
<record id="beesdoo_shift_user_1_demo" model="res.users"> |
|
||||
<field name="partner_id" ref="beesdoo_shift_partner_1_demo"/> |
|
||||
<field name="login">generic</field> |
|
||||
<field name="password">demo</field> |
|
||||
</record> |
|
||||
|
|
||||
<record id="beesdoo_shift_user_2_demo" model="res.users"> |
|
||||
<field name="partner_id" ref="beesdoo_shift_partner_2_demo"/> |
|
||||
<field name="login">permanent</field> |
|
||||
<field name="password">demo</field> |
|
||||
</record> |
|
||||
|
|
||||
</odoo> |
|
@ -1,3 +1,2 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
import models |
|
||||
import wizard |
|
||||
|
from . import models |
||||
|
from . import wizard |
@ -0,0 +1,58 @@ |
|||||
|
<?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_shift_summary" model="mail.template"> |
||||
|
<field name="name">Shift Summary</field> |
||||
|
<field name="subject">Your next shift (${format_tz(object.start_time,object.worker_id.tz or 'Europe/Brussels','%d.%m.%Y - %H:%M')})</field> |
||||
|
<field name="email_from">${object.worker_id.company_id.email}</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; "> |
||||
|
|
||||
|
<p>Hello ${object.worker_id.name},</p> |
||||
|
|
||||
|
<p>You are awaited the ${format_tz(object.start_time,object.worker_id.tz or 'Europe/Brussels','%d.%m.%Y')} |
||||
|
for the shift starting at ${format_tz(object.start_time,object.worker_id.tz or 'Europe/Brussels','%H:%M')}. |
||||
|
|
||||
|
<br/><br/>Please contact us at ${object.worker_id.company_id.email} if you have any trouble attending the shift. |
||||
|
</p> |
||||
|
<br/> |
||||
|
<p>Sustainably yours,</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> |
||||
|
</data> |
||||
|
</odoo> |
@ -1,44 +0,0 @@ |
|||||
<odoo> |
|
||||
<record model="beesdoo.shift.stage" id="draft"> |
|
||||
<field name="name">Unconfirmed</field> |
|
||||
<field name="sequence">1</field> |
|
||||
<field name="color">0</field> |
|
||||
<field name="code">draft</field> |
|
||||
</record> |
|
||||
<record model="beesdoo.shift.stage" id="open"> |
|
||||
<field name="name">Confirmed</field> |
|
||||
<field name="sequence">2</field> |
|
||||
<field name="color">5</field> |
|
||||
<field name="code">open</field> |
|
||||
</record> |
|
||||
<record model="beesdoo.shift.stage" id="done"> |
|
||||
<field name="name">Attended</field> |
|
||||
<field name="sequence">3</field> |
|
||||
<field name="color">1</field> |
|
||||
<field name="code">done</field> |
|
||||
</record> |
|
||||
<record model="beesdoo.shift.stage" id="absent"> |
|
||||
<field name="name">Absent</field> |
|
||||
<field name="sequence">5</field> |
|
||||
<field name="color">2</field> |
|
||||
<field name="code">absent</field> |
|
||||
</record> |
|
||||
<record model="beesdoo.shift.stage" id="excused"> |
|
||||
<field name="name">Excused</field> |
|
||||
<field name="sequence">6</field> |
|
||||
<field name="color">4</field> |
|
||||
<field name="code">excused</field> |
|
||||
</record> |
|
||||
<record model="beesdoo.shift.stage" id="excused_necessity"> |
|
||||
<field name="name">Excused - Absolute Necessity</field> |
|
||||
<field name="sequence">7</field> |
|
||||
<field name="color">4</field> |
|
||||
<field name="code">excused_necessity</field> |
|
||||
</record> |
|
||||
<record model="beesdoo.shift.stage" id="cancel"> |
|
||||
<field name="name">Cancelled</field> |
|
||||
<field name="sequence">8</field> |
|
||||
<field name="color">8</field> |
|
||||
<field name="code">cancel</field> |
|
||||
</record> |
|
||||
</odoo> |
|
@ -0,0 +1,68 @@ |
|||||
|
<?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="beesdoo_shift_task_type_1_demo" model="beesdoo.shift.type"> |
||||
|
<field name="name">Inventaire</field> |
||||
|
<field name="description">Inventaire frais et vrac.</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_task_type_2_demo" model="beesdoo.shift.type"> |
||||
|
<field name="name">Bureau des Membres</field> |
||||
|
<field name="description">Gestion admin (encodage liste présences, etc.)</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_task_type_3_demo" model="beesdoo.shift.type"> |
||||
|
<field name="name">Magasin</field> |
||||
|
<field name="description">Gestion du magasin.</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_task_type_4_demo" model="beesdoo.shift.type"> |
||||
|
<field name="name">Découpe fromage</field> |
||||
|
<field name="description">Caisse, remplissage des rayons, accueil.</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_daynumber_1_demo" model="beesdoo.shift.daynumber"> |
||||
|
<field name="name">Lundi</field> |
||||
|
<field name="number">1</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_daynumber_2_demo" model="beesdoo.shift.daynumber"> |
||||
|
<field name="name">Mardi</field> |
||||
|
<field name="number">2</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_daynumber_3_demo" model="beesdoo.shift.daynumber"> |
||||
|
<field name="name">Mercredi</field> |
||||
|
<field name="number">3</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_daynumber_4_demo" model="beesdoo.shift.daynumber"> |
||||
|
<field name="name">Jeudi</field> |
||||
|
<field name="number">4</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_daynumber_5_demo" model="beesdoo.shift.daynumber"> |
||||
|
<field name="name">Vendredi</field> |
||||
|
<field name="number">5</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_daynumber_6_demo" model="beesdoo.shift.daynumber"> |
||||
|
<field name="name">Samedi</field> |
||||
|
<field name="number">6</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_daynumber_7_demo" model="beesdoo.shift.daynumber"> |
||||
|
<field name="name">Dimanche</field> |
||||
|
<field name="number">7</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_planning_1_demo" model="beesdoo.shift.planning"> |
||||
|
<field name="sequence">1</field> |
||||
|
<field name="name">Semaine A</field> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
2654
beesdoo_shift/i18n/fr_BE.po
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
2633
beesdoo_shift/i18n/nl_BE.po
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,4 +1,4 @@ |
|||||
# -*- coding: utf-8 -*- |
# -*- coding: utf-8 -*- |
||||
import planning |
|
||||
import task |
|
||||
import cooperative_status |
|
||||
|
from . import task |
||||
|
from . import planning |
||||
|
from . import cooperative_status |
@ -1,23 +1,28 @@ |
|||||
<odoo> |
<odoo> |
||||
<data noupdate="0"> |
|
||||
<record id="group_shift_attendance" model="res.groups"> |
<record id="group_shift_attendance" model="res.groups"> |
||||
<field name="name">Shift Attendance</field> |
|
||||
|
<field name="name">Shift and Worker Read Access</field> |
||||
<field name="category_id" ref="base.module_category_cooperative_management"/> |
<field name="category_id" ref="base.module_category_cooperative_management"/> |
||||
</record> |
</record> |
||||
<record id="group_shift_management" model="res.groups"> |
<record id="group_shift_management" model="res.groups"> |
||||
<field name="name">Shift Management</field> |
|
||||
|
<field name="name">Shifts and Attendance Sheets Management</field> |
||||
<field name="category_id" ref="base.module_category_cooperative_management"/> |
<field name="category_id" ref="base.module_category_cooperative_management"/> |
||||
<field name="implied_ids" eval="[(4, ref('group_shift_attendance'))]"/> |
<field name="implied_ids" eval="[(4, ref('group_shift_attendance'))]"/> |
||||
</record> |
</record> |
||||
<record id="group_planning_management" model="res.groups"> |
<record id="group_planning_management" model="res.groups"> |
||||
<field name="name">Planning Management</field> |
<field name="name">Planning Management</field> |
||||
<field name="category_id" ref="base.module_category_cooperative_management"/> |
|
||||
<field name="implied_ids" eval="[(4, ref('group_shift_management'))]"/> |
|
||||
|
<field name="category_id" |
||||
|
ref="base.module_category_cooperative_management"/> |
||||
|
<field name="implied_ids" |
||||
|
eval="[(4, ref('group_shift_management'))]"/> |
||||
</record> |
</record> |
||||
<record id="group_cooperative_admin" model="res.groups"> |
<record id="group_cooperative_admin" model="res.groups"> |
||||
<field name="name">Cooperative Admin</field> |
<field name="name">Cooperative Admin</field> |
||||
<field name="category_id" ref="base.module_category_cooperative_management"/> |
|
||||
<field name="implied_ids" eval="[(4, ref('group_planning_management'))]"/> |
|
||||
|
<field name="category_id" |
||||
|
ref="base.module_category_cooperative_management"/> |
||||
|
<field name="implied_ids" |
||||
|
eval="[(4, ref('group_planning_management'))]" /> |
||||
|
<field name="users" |
||||
|
eval="[(4, ref('base.user_root')), |
||||
|
(4, ref('base.user_admin'))]"/> |
||||
</record> |
</record> |
||||
</data> |
|
||||
</odoo> |
</odoo> |
@ -1,20 +1,17 @@ |
|||||
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink |
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink |
||||
access_coopplanning_task_stage,Attendance Read Stage,model_beesdoo_shift_stage,group_shift_attendance,1,0,0,0 |
|
||||
access_coopplanning_task_type,Attendance Read Type,model_beesdoo_shift_type,group_shift_attendance,1,0,0,0 |
|
||||
access_coopplanning_daynumber,Attendance Read Daynumber,model_beesdoo_shift_daynumber,group_shift_attendance,1,0,0,0 |
|
||||
access_coopplanning_planning,Attendance Read Planning,model_beesdoo_shift_planning,group_shift_attendance,1,0,0,0 |
|
||||
access_coopplanning_task_template,Attendance Read Template,model_beesdoo_shift_template,group_shift_attendance,1,0,0,0 |
|
||||
access_coopplanning_task,Attendance Edit Shift,model_beesdoo_shift_shift,group_shift_attendance,1,1,0,0 |
|
||||
access_coopplanning_task_full,Shift Management all Shift,model_beesdoo_shift_shift,group_shift_management,1,1,1,1 |
|
||||
access_coop_status,Coop Status Read for all,model_cooperative_status,,1,0,0,0 |
|
||||
access_coop_status_all,Coop Status Admin,model_cooperative_status,group_cooperative_admin,1,1,1,1 |
|
||||
all_config_coopplanning_task_stage,Attendance Read Stage,model_beesdoo_shift_stage,group_planning_management,1,1,1,1 |
|
||||
all_config_coopplanning_task_type,Attendance Read Type,model_beesdoo_shift_type,group_planning_management,1,1,1,1 |
|
||||
all_config_coopplanning_daynumber,Attendance Read Daynumber,model_beesdoo_shift_daynumber,group_planning_management,1,1,1,1 |
|
||||
all_config_coopplanning_planning,Attendance Read Planning,model_beesdoo_shift_planning,group_planning_management,1,1,1,1 |
|
||||
all_config_coopplanning_task_template,Attendance Read Template,model_beesdoo_shift_template,group_planning_management,1,1,1,1 |
|
||||
all_config_coopplanning_task,Attendance Edit Shift,model_beesdoo_shift_shift,group_planning_management,1,1,1,1 |
|
||||
exempt_reason_read_all,Exempt Reason Read all ,beesdoo_shift.model_cooperative_exempt_reason,,1,0,0,0 |
|
||||
exempt_reason,Exempt Reason Admin,beesdoo_shift.model_cooperative_exempt_reason,beesdoo_shift.group_cooperative_admin,1,1,1,1 |
|
||||
history_read_all,History Read All,beesdoo_shift.model_cooperative_status_history,,1,0,0,0 |
|
||||
access_beesdoo_shift_journal,access_beesdoo_shift_journal,model_beesdoo_shift_journal,beesdoo_shift.group_cooperative_admin,1,0,1,1 |
|
||||
|
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 |
||||
|
manage_beesdoo_shift_shift,manage_beesdoo_shift_shift,model_beesdoo_shift_shift,group_shift_attendance,1,1,1,1 |
||||
|
manage_beesdoo_shift_type,manage_beesdoo_shift_type,model_beesdoo_shift_type,group_planning_management,1,1,1,1 |
||||
|
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_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 |
||||
|
access_cooperative_status,access_cooperative_status,model_cooperative_status,,1,0,0,0 |
||||
|
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_attendance_beesdoo_shift_daynumber,access_beesdoo_shift_daynumber,model_beesdoo_shift_daynumber,group_shift_attendance,1,0,0,0 |
@ -0,0 +1,62 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
|
||||
|
<!-- Root menu --> |
||||
|
<menuitem name="Shift Management" id="menu_root" |
||||
|
groups="beesdoo_shift.group_shift_attendance" /> |
||||
|
|
||||
|
|
||||
|
<!-- Planning --> |
||||
|
<menuitem name="Planning" id="menu_task_top" parent="menu_root" |
||||
|
sequence="1" groups="beesdoo_shift.group_shift_attendance" /> |
||||
|
|
||||
|
<menuitem name="Shifts" id="menu_task" parent="menu_task_top" |
||||
|
action="action_task" groups="beesdoo_shift.group_shift_attendance" /> |
||||
|
|
||||
|
|
||||
|
<!-- Worker --> |
||||
|
<menuitem name="Worker" id="menu_worker_top" parent="menu_root" |
||||
|
groups="beesdoo_shift.group_shift_attendance" sequence="1" /> |
||||
|
|
||||
|
<menuitem name="Worker" id="menu_worker" parent="menu_worker_top" |
||||
|
action="action_worker" /> |
||||
|
|
||||
|
|
||||
|
<!-- Templates --> |
||||
|
<menuitem name="Templates" id="menu_template_top" parent="menu_root" |
||||
|
groups="beesdoo_shift.group_shift_management" |
||||
|
sequence="20" /> |
||||
|
|
||||
|
<menuitem name="Planning Week" id="menu_planning" parent="menu_template_top" |
||||
|
sequence="20" action="action_planning" /> |
||||
|
|
||||
|
|
||||
|
<!-- Status --> |
||||
|
<menuitem name="Status" id="menu_status_top" parent="menu_root" |
||||
|
groups="beesdoo_shift.group_shift_management" sequence="20" /> |
||||
|
|
||||
|
<menuitem name="Cooperative Status" id="menu_status" parent="menu_status_top" |
||||
|
action="action_coop_status" groups="beesdoo_shift.group_cooperative_admin" /> |
||||
|
|
||||
|
<menuitem name="Exempt Reason" id="menu_exempt_reason" parent="menu_status_top" |
||||
|
action="action_exempt_reason" groups="beesdoo_shift.group_cooperative_admin" /> |
||||
|
|
||||
|
<menuitem name="Counter Update Journal" id="menu_journal" parent="menu_status_top" |
||||
|
action="action_journal" groups="beesdoo_shift.group_cooperative_admin" /> |
||||
|
|
||||
|
|
||||
|
<!-- Configuration / Settings --> |
||||
|
<menuitem name="Configuration" |
||||
|
id="menu_configuration_top" |
||||
|
parent="beesdoo_shift.menu_root" |
||||
|
groups="beesdoo_shift.group_planning_management" |
||||
|
sequence="21" |
||||
|
/> |
||||
|
|
||||
|
<menuitem name="Shift Day" id="menu_configuration_day" |
||||
|
parent="menu_configuration_top" action="action_day_number" /> |
||||
|
|
||||
|
<menuitem name="Shift Type" id="menu_configuration_type" |
||||
|
parent="menu_configuration_top" action="action_type" /> |
||||
|
|
||||
|
</odoo> |
@ -1,7 +1,7 @@ |
|||||
import instanciate_planning |
|
||||
import batch_template |
|
||||
import assign_super_coop |
|
||||
import subscribe |
|
||||
import extension |
|
||||
import holiday |
|
||||
import temporary_exemption |
|
||||
|
from . import instanciate_planning |
||||
|
from . import batch_template |
||||
|
from . import assign_super_coop |
||||
|
from . import subscribe |
||||
|
from . import extension |
||||
|
from . import holiday |
||||
|
from . import temporary_exemption |
@ -0,0 +1,2 @@ |
|||||
|
from . import models |
||||
|
from . import wizard |
@ -0,0 +1,45 @@ |
|||||
|
# This module is, for now, specific to the BEES coop. |
||||
|
# Therefore, this module depends on `beesdo_worker_status`. |
||||
|
# If someone needs this module but has another worker_status rules |
||||
|
# this module can be splitted into a generic part, and a specific part |
||||
|
# that implement the worker_status rules. |
||||
|
{ |
||||
|
'name': "Beescoop Shift Attendance Sheet", |
||||
|
|
||||
|
'summary': """ |
||||
|
Volonteer Timetable Management |
||||
|
Attendance Sheet for BEES coop""", |
||||
|
|
||||
|
'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_base', |
||||
|
'beesdoo_shift', |
||||
|
'beesdoo_worker_status', |
||||
|
'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", |
||||
|
"wizard/generate_missing_attendance_sheets.xml", |
||||
|
"views/attendance_sheet.xml", |
||||
|
], |
||||
|
'demo': [ |
||||
|
"demo/users.xml", |
||||
|
] |
||||
|
} |
@ -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,118 @@ |
|||||
|
<?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="beesdoo_shift.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="card_support" model="ir.config_parameter"> |
||||
|
<field name="key">beesdoo_shift_attendance.card_support</field> |
||||
|
<field name="value">False</field> |
||||
|
</record> |
||||
|
<record id="attendance_sheet_generation_interval" model="ir.config_parameter"> |
||||
|
<field name="key">beesdoo_shift_attendance.attendance_sheet_generation_interval</field> |
||||
|
<field name="value">15</field> |
||||
|
</record> |
||||
|
<record id="default_task_type_id" model="ir.config_parameter"> |
||||
|
<field name="key">beesdoo_shift_attendance.default_task_type_id</field> |
||||
|
<field name="value">1</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,40 @@ |
|||||
|
<?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> |
||||
|
<!-- Generic and permanent account --> |
||||
|
|
||||
|
<record id="beesdoo_shift_partner_1_demo" model="res.partner"> |
||||
|
<field name="firstname">Generic Account</field> |
||||
|
<field name="lastname">Demo</field> |
||||
|
<field name="email">generic@demo.net</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_partner_2_demo" model="res.partner"> |
||||
|
<field name="firstname">Permanent Member</field> |
||||
|
<field name="lastname">Demo</field> |
||||
|
<field name="is_company" eval="False"/> |
||||
|
<field name="email">permanent@demo.net</field> |
||||
|
<field name="city">Ixelles</field> |
||||
|
<field name="zip">1050</field> |
||||
|
<field name="country_id" ref="base.be"/> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_user_1_demo" model="res.users"> |
||||
|
<field name="partner_id" ref="beesdoo_shift_partner_1_demo"/> |
||||
|
<field name="login">generic</field> |
||||
|
<field name="password">demo</field> |
||||
|
<field name="groups_id" |
||||
|
eval="[(4,ref('base.group_user')), (4,ref('beesdoo_shift_attendance.group_shift_attendance_sheet'))]"/> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_user_2_demo" model="res.users"> |
||||
|
<field name="partner_id" ref="beesdoo_shift_partner_2_demo"/> |
||||
|
<field name="login">permanent</field> |
||||
|
<field name="password">demo</field> |
||||
|
<field name="groups_id" eval="[(4,ref('beesdoo_shift.group_shift_management'))]"/> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,2 @@ |
|||||
|
from . import attendance_sheet |
||||
|
from . import res_config_settings |
@ -0,0 +1,654 @@ |
|||||
|
from datetime import date, datetime, timedelta |
||||
|
|
||||
|
from lxml import etree |
||||
|
|
||||
|
from odoo import _, api, exceptions, fields, models |
||||
|
from odoo.exceptions import UserError, ValidationError |
||||
|
|
||||
|
|
||||
|
class AttendanceSheetShift(models.Model): |
||||
|
""" |
||||
|
Partial copy of Task class to use in AttendanceSheet, |
||||
|
actual Task is updated at validation. |
||||
|
|
||||
|
Should be Abstract and not used alone (common code for |
||||
|
AttendanceSheetShiftAdded and AttendanceSheetShiftExpected), |
||||
|
but create() method from res.partner raise error |
||||
|
when class is Abstract. |
||||
|
""" |
||||
|
_name = "beesdoo.shift.sheet.shift" |
||||
|
_description = "Copy of an actual shift into an attendance sheet" |
||||
|
_order = "task_type_id, worker_name" |
||||
|
|
||||
|
@api.model |
||||
|
def pre_filled_task_type_id(self): |
||||
|
parameters = self.env["ir.config_parameter"].sudo() |
||||
|
tasktype_id = int( |
||||
|
parameters.get_param( |
||||
|
"beesdoo_shift_attendance.pre_filled_task_type_id", default=1 |
||||
|
) |
||||
|
) |
||||
|
task_types = self.env["beesdoo.shift.type"] |
||||
|
return task_types.browse(tasktype_id) |
||||
|
|
||||
|
# Related actual shift |
||||
|
task_id = fields.Many2one("beesdoo.shift.shift", string="Task") |
||||
|
attendance_sheet_id = fields.Many2one( |
||||
|
"beesdoo.shift.sheet", |
||||
|
string="Attendance Sheet", |
||||
|
required=True, |
||||
|
ondelete="cascade", |
||||
|
) |
||||
|
state = fields.Selection( |
||||
|
[ |
||||
|
("done", "Present"), |
||||
|
("absent_0", "Absent - 0 Compensation"), |
||||
|
("absent_1", "Absent - 1 Compensation"), |
||||
|
("absent_2", "Absent - 2 Compensations"), |
||||
|
], |
||||
|
string="Shift State", |
||||
|
required=True, |
||||
|
) |
||||
|
worker_id = fields.Many2one( |
||||
|
"res.partner", |
||||
|
string="Worker", |
||||
|
domain=[ |
||||
|
("is_worker", "=", True), |
||||
|
("working_mode", "in", ("regular", "irregular")), |
||||
|
("state", "not in", ("unsubscribed", "resigning")), |
||||
|
], |
||||
|
required=True, |
||||
|
) |
||||
|
worker_name = fields.Char(related="worker_id.name", store=True) |
||||
|
task_type_id = fields.Many2one( |
||||
|
"beesdoo.shift.type", string="Task Type", default=pre_filled_task_type_id |
||||
|
) |
||||
|
working_mode = fields.Selection( |
||||
|
related="worker_id.working_mode", string="Working Mode" |
||||
|
) |
||||
|
# The two exclusive booleans are gathered in a simple one |
||||
|
is_compensation = fields.Boolean( |
||||
|
string="Compensation shift ?", help="Only for regular workers" |
||||
|
) |
||||
|
|
||||
|
|
||||
|
class AttendanceSheetShiftExpected(models.Model): |
||||
|
""" |
||||
|
Shifts already expected. |
||||
|
""" |
||||
|
|
||||
|
_name = "beesdoo.shift.sheet.expected" |
||||
|
_description = "Expected Shift" |
||||
|
_inherit = ["beesdoo.shift.sheet.shift"] |
||||
|
|
||||
|
super_coop_id = fields.Many2one( |
||||
|
related="task_id.super_coop_id", store=True |
||||
|
) |
||||
|
replaced_id = fields.Many2one( |
||||
|
"res.partner", |
||||
|
string="Replacement Worker", |
||||
|
help="Replacement Worker (must be regular)", |
||||
|
domain=[ |
||||
|
("eater", "=", "worker_eater"), |
||||
|
("working_mode", "=", "regular"), |
||||
|
("state", "not in", ("unsubscribed", "resigning")), |
||||
|
], |
||||
|
) |
||||
|
|
||||
|
@api.onchange("replaced_id") |
||||
|
def on_change_replacement_worker(self): |
||||
|
if self.replaced_id: |
||||
|
self.state = "done" |
||||
|
|
||||
|
|
||||
|
class AttendanceSheetShiftAdded(models.Model): |
||||
|
""" |
||||
|
Shifts added during time slot. |
||||
|
""" |
||||
|
|
||||
|
_name = "beesdoo.shift.sheet.added" |
||||
|
_description = "Added Shift" |
||||
|
_inherit = ["beesdoo.shift.sheet.shift"] |
||||
|
|
||||
|
state = fields.Selection(default="done") |
||||
|
|
||||
|
@api.onchange("working_mode") |
||||
|
def on_change_working_mode(self): |
||||
|
self.state = "done" |
||||
|
self.is_compensation = self.working_mode == "regular" |
||||
|
|
||||
|
|
||||
|
class AttendanceSheet(models.Model): |
||||
|
_name = "beesdoo.shift.sheet" |
||||
|
_inherit = [ |
||||
|
"mail.thread", |
||||
|
"barcodes.barcode_events_mixin", |
||||
|
] |
||||
|
_description = "Attendance sheet" |
||||
|
_order = "start_time" |
||||
|
|
||||
|
name = fields.Char(string="Name", compute="_compute_name") |
||||
|
time_slot = fields.Char( |
||||
|
string="Time Slot", |
||||
|
compute="_compute_time_slot", |
||||
|
store=True, |
||||
|
readonly=True, |
||||
|
) |
||||
|
active = fields.Boolean(string="Active", default=1) |
||||
|
state = fields.Selection( |
||||
|
[("not_validated", "Not Validated"), ("validated", "Validated"),], |
||||
|
string="State", |
||||
|
readonly=True, |
||||
|
index=True, |
||||
|
default="not_validated", |
||||
|
track_visibility="onchange", |
||||
|
) |
||||
|
start_time = fields.Datetime( |
||||
|
string="Start Time", required=True, readonly=True |
||||
|
) |
||||
|
end_time = fields.Datetime(string="End Time", required=True, readonly=True) |
||||
|
day = fields.Date(string="Day", compute="_compute_day", store=True) |
||||
|
day_abbrevation = fields.Char( |
||||
|
string="Day Abbrevation", compute="_compute_day_abbrevation" |
||||
|
) |
||||
|
week = fields.Char( |
||||
|
string="Week", |
||||
|
help="Computed from planning name", |
||||
|
compute="_compute_week", |
||||
|
) |
||||
|
|
||||
|
expected_shift_ids = fields.One2many( |
||||
|
"beesdoo.shift.sheet.expected", |
||||
|
"attendance_sheet_id", |
||||
|
string="Expected Shifts", |
||||
|
) |
||||
|
added_shift_ids = fields.One2many( |
||||
|
"beesdoo.shift.sheet.added", |
||||
|
"attendance_sheet_id", |
||||
|
string="Added Shifts", |
||||
|
) |
||||
|
|
||||
|
max_worker_no = fields.Integer( |
||||
|
string="Maximum number of workers", |
||||
|
default=0, |
||||
|
readonly=True, |
||||
|
help="Indicative maximum number of workers.", |
||||
|
) |
||||
|
attended_worker_no = fields.Integer( |
||||
|
string="Number of workers present", |
||||
|
default=0, |
||||
|
readonly=True, |
||||
|
) |
||||
|
notes = fields.Text( |
||||
|
"Notes", |
||||
|
default="", |
||||
|
help="Notes about the attendance for the Members Office", |
||||
|
) |
||||
|
is_annotated = fields.Boolean( |
||||
|
compute="_compute_is_annotated", |
||||
|
string="Is annotated", |
||||
|
readonly=True, |
||||
|
store=True, |
||||
|
) |
||||
|
is_read = fields.Boolean( |
||||
|
string="Mark as read", |
||||
|
help="Has notes been read by an administrator ?", |
||||
|
default=False, |
||||
|
track_visibility="onchange", |
||||
|
) |
||||
|
feedback = fields.Text("Comments about the shift") |
||||
|
worker_nb_feedback = fields.Selection( |
||||
|
[ |
||||
|
("not_enough", "Not enough workers"), |
||||
|
("enough", "Enough workers"), |
||||
|
("too_many", "Too many workers"), |
||||
|
("empty", "I was not there during the shift"), |
||||
|
], |
||||
|
string="Was your team big enough ? *", |
||||
|
) |
||||
|
validated_by = fields.Many2one( |
||||
|
"res.partner", |
||||
|
string="Validated by", |
||||
|
domain=[ |
||||
|
("eater", "=", "worker_eater"), |
||||
|
("super", "=", True), |
||||
|
("working_mode", "=", "regular"), |
||||
|
("state", "not in", ("unsubscribed", "resigning")), |
||||
|
], |
||||
|
track_visibility="onchange", |
||||
|
readonly=True, |
||||
|
) |
||||
|
|
||||
|
_sql_constraints = [ |
||||
|
( |
||||
|
"check_not_annotated_mark_as_read", |
||||
|
"CHECK ((is_annotated=FALSE AND is_read=FALSE) OR is_annotated=TRUE)", |
||||
|
_("Non-annotated sheets can't be marked as read."), |
||||
|
) |
||||
|
] |
||||
|
|
||||
|
@api.depends("start_time", "end_time") |
||||
|
def _compute_time_slot(self): |
||||
|
for rec in self: |
||||
|
start_time = fields.Datetime.context_timestamp(rec, rec.start_time) |
||||
|
end_time = fields.Datetime.context_timestamp(rec, rec.end_time) |
||||
|
rec.time_slot = ( |
||||
|
start_time.strftime("%H:%M") |
||||
|
+ "-" |
||||
|
+ end_time.strftime("%H:%M") |
||||
|
) |
||||
|
|
||||
|
@api.depends("start_time", "end_time", "week", "day_abbrevation") |
||||
|
def _compute_name(self): |
||||
|
for rec in self: |
||||
|
start_time = fields.Datetime.context_timestamp(rec, rec.start_time) |
||||
|
name = "[%s] " % fields.Date.to_string(start_time) |
||||
|
if rec.week: |
||||
|
name += rec.week + " " |
||||
|
if rec.day_abbrevation: |
||||
|
name += rec.day_abbrevation + " " |
||||
|
if rec.time_slot: |
||||
|
name += "(%s)" % rec.time_slot |
||||
|
rec.name = name |
||||
|
|
||||
|
@api.depends("start_time") |
||||
|
def _compute_day(self): |
||||
|
for rec in self: |
||||
|
rec.day = rec.start_time.date() |
||||
|
|
||||
|
@api.depends("expected_shift_ids") |
||||
|
def _compute_day_abbrevation(self): |
||||
|
""" |
||||
|
Compute Day Abbrevation from Planning Name |
||||
|
of first expected shift with one. |
||||
|
""" |
||||
|
for rec in self: |
||||
|
for shift in rec.expected_shift_ids: |
||||
|
if shift.task_id.task_template_id.day_nb_id.name: |
||||
|
rec.day_abbrevation = ( |
||||
|
shift.task_id.task_template_id.day_nb_id.name |
||||
|
) |
||||
|
|
||||
|
@api.depends("expected_shift_ids") |
||||
|
def _compute_week(self): |
||||
|
""" |
||||
|
Compute Week Name from Planning Name |
||||
|
of first expected shift with one. |
||||
|
""" |
||||
|
for rec in self: |
||||
|
for shift in rec.expected_shift_ids: |
||||
|
if shift.task_id.planning_id.name: |
||||
|
rec.week = shift.task_id.planning_id.name |
||||
|
|
||||
|
@api.depends("notes") |
||||
|
def _compute_is_annotated(self): |
||||
|
for rec in self: |
||||
|
if rec.notes: |
||||
|
rec.is_annotated = bool(rec.notes.strip()) |
||||
|
|
||||
|
@api.constrains("expected_shift_ids", "added_shift_ids") |
||||
|
def _constrain_unique_worker(self): |
||||
|
# Warning : map return generator in python3 (for Odoo 12) |
||||
|
added_ids = [s.worker_id.id for s in self.added_shift_ids] |
||||
|
expected_ids = [s.worker_id.id for s in self.expected_shift_ids] |
||||
|
replacement_ids = [ |
||||
|
s.replaced_id.id |
||||
|
for s in self.expected_shift_ids |
||||
|
if s.replaced_id.id |
||||
|
] |
||||
|
ids = added_ids + expected_ids + replacement_ids |
||||
|
|
||||
|
if (len(ids) - len(set(ids))) > 0: |
||||
|
raise UserError( |
||||
|
_( |
||||
|
"You can't add the same worker more than once to an attendance sheet." |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
@api.constrains( |
||||
|
"expected_shift_ids", |
||||
|
"added_shift_ids", |
||||
|
"notes", |
||||
|
"feedback", |
||||
|
"worker_nb_feedback", |
||||
|
) |
||||
|
def _lock_after_validation(self): |
||||
|
if self.state == "validated": |
||||
|
raise UserError( |
||||
|
_("The sheet has already been validated and can't be edited.") |
||||
|
) |
||||
|
|
||||
|
def on_barcode_scanned(self, barcode): |
||||
|
if self.env.user.has_group("beesdoo_shift_attendance.group_shift_attendance"): |
||||
|
raise UserError( |
||||
|
_("You must be logged as 'Attendance Sheet Generic Access' " |
||||
|
" if you want to scan cards.") |
||||
|
) |
||||
|
|
||||
|
if self.state == "validated": |
||||
|
raise UserError( |
||||
|
_("A validated attendance sheet can't be modified") |
||||
|
) |
||||
|
|
||||
|
worker = self.env["res.partner"].search([("barcode", "=", barcode)]) |
||||
|
|
||||
|
if not len(worker): |
||||
|
raise UserError( |
||||
|
_( |
||||
|
"Worker not found (invalid barcode or status). \nBarcode : %s" |
||||
|
) |
||||
|
% barcode |
||||
|
) |
||||
|
if len(worker) > 1: |
||||
|
raise UserError( |
||||
|
_( |
||||
|
"Multiple workers are corresponding this barcode. \nBarcode : %s" |
||||
|
) |
||||
|
% barcode |
||||
|
) |
||||
|
|
||||
|
if worker.state == "unsubscribed": |
||||
|
shift_counter = ( |
||||
|
worker.cooperative_status_ids.sc |
||||
|
+ worker.cooperative_status_ids.sr |
||||
|
) |
||||
|
raise UserError( |
||||
|
_( |
||||
|
"Beware, your account is frozen because your shift counter " |
||||
|
"is at %s. Please contact Members Office to unfreeze it. " |
||||
|
"If you want to attend this shift, your supercoop " |
||||
|
"can write your name in the notes field during validation." |
||||
|
) |
||||
|
% shift_counter |
||||
|
) |
||||
|
if worker.state == "resigning": |
||||
|
raise UserError( |
||||
|
_( |
||||
|
"Beware, you are recorded as resigning. " |
||||
|
"Please contact member's office if this is incorrect. Thank you." |
||||
|
) |
||||
|
) |
||||
|
if worker.working_mode not in ("regular", "irregular"): |
||||
|
raise UserError( |
||||
|
_("%s's working mode is %s and should be regular or irregular.") |
||||
|
% (worker.name, worker.working_mode) |
||||
|
) |
||||
|
|
||||
|
# Expected shifts status update |
||||
|
for id in self.expected_shift_ids.ids: |
||||
|
shift = self.env["beesdoo.shift.sheet.expected"].browse(id) |
||||
|
if ( |
||||
|
shift.worker_id == worker and not shift.replaced_id |
||||
|
) or shift.replaced_id == worker: |
||||
|
shift.state = "done" |
||||
|
return |
||||
|
if shift.worker_id == worker and shift.replaced_id: |
||||
|
raise UserError( |
||||
|
_("%s is registered as replaced.") % worker.name |
||||
|
) |
||||
|
|
||||
|
is_compensation = worker.working_mode == "regular" |
||||
|
|
||||
|
added_ids = [s.worker_id.id for s in self.added_shift_ids] |
||||
|
|
||||
|
if worker.id not in added_ids: |
||||
|
# Added shift creation |
||||
|
self.added_shift_ids |= self.added_shift_ids.new( |
||||
|
{ |
||||
|
"task_type_id": self.added_shift_ids.pre_filled_task_type_id(), |
||||
|
"state": "done", |
||||
|
"attendance_sheet_id": self._origin.id, |
||||
|
"worker_id": worker.id, |
||||
|
"is_compensation": is_compensation, |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
@api.model |
||||
|
def create(self, vals): |
||||
|
new_sheet = super(AttendanceSheet, self).create(vals) |
||||
|
|
||||
|
# Creation and addition of the expected shifts corresponding |
||||
|
# to the time range |
||||
|
tasks = self.env["beesdoo.shift.shift"] |
||||
|
expected_shift = self.env["beesdoo.shift.sheet.expected"] |
||||
|
# Fix issues with equality check on datetime |
||||
|
# by searching on a small intervall instead |
||||
|
delta = timedelta(minutes=1) |
||||
|
|
||||
|
tasks = tasks.search( |
||||
|
[ |
||||
|
("start_time", ">", new_sheet.start_time - delta), |
||||
|
("start_time", "<", new_sheet.start_time + delta), |
||||
|
("end_time", ">", new_sheet.end_time - delta), |
||||
|
("end_time", "<", new_sheet.end_time + delta), |
||||
|
] |
||||
|
) |
||||
|
|
||||
|
workers = [] |
||||
|
|
||||
|
for task in tasks: |
||||
|
# Only one shift is added if multiple similar exist |
||||
|
if task.worker_id and task.worker_id not in workers and (task.state != "cancel") : |
||||
|
expected_shift.create( |
||||
|
{ |
||||
|
"attendance_sheet_id": new_sheet.id, |
||||
|
"task_id": task.id, |
||||
|
"worker_id": task.worker_id.id, |
||||
|
"replaced_id": task.replaced_id.id, |
||||
|
"task_type_id": task.task_type_id.id, |
||||
|
"state": "absent_2", |
||||
|
"working_mode": task.working_mode, |
||||
|
"is_compensation": task.is_compensation, |
||||
|
} |
||||
|
) |
||||
|
workers.append(task.worker_id) |
||||
|
# Maximum number of workers calculation (count empty shifts) |
||||
|
new_sheet.max_worker_no = len(tasks) |
||||
|
return new_sheet |
||||
|
|
||||
|
@api.multi |
||||
|
def button_mark_as_read(self): |
||||
|
if self.is_read: |
||||
|
raise UserError(_("The sheet has already been marked as read.")) |
||||
|
self.is_read = True |
||||
|
|
||||
|
def _validate(self, user): |
||||
|
self.ensure_one() |
||||
|
if self.state == "validated": |
||||
|
raise UserError("The sheet has already been validated.") |
||||
|
|
||||
|
# Expected shifts status update |
||||
|
for expected_shift in self.expected_shift_ids: |
||||
|
actual_shift = expected_shift.task_id |
||||
|
actual_shift.replaced_id = expected_shift.replaced_id |
||||
|
actual_shift.state = expected_shift.state |
||||
|
|
||||
|
if expected_shift.state == "done": |
||||
|
self.attended_worker_no += 1 |
||||
|
|
||||
|
if expected_shift.state != "done": |
||||
|
mail_template = self.env.ref( |
||||
|
"beesdoo_shift_attendance.email_template_non_attendance", False |
||||
|
) |
||||
|
mail_template.send_mail(expected_shift.task_id.id, True) |
||||
|
|
||||
|
# Added shifts status update |
||||
|
for added_shift in self.added_shift_ids: |
||||
|
is_regular_worker = added_shift.worker_id.working_mode == "regular" |
||||
|
is_compensation = added_shift.is_compensation |
||||
|
|
||||
|
# Edit a non-assigned shift or create one if none |
||||
|
|
||||
|
# Fix issues with equality check on datetime |
||||
|
# by searching on a small intervall instead |
||||
|
delta = timedelta(minutes=1) |
||||
|
|
||||
|
non_assigned_shifts = self.env["beesdoo.shift.shift"].search( |
||||
|
[ |
||||
|
("worker_id", "=", False), |
||||
|
("start_time", ">", self.start_time - delta), |
||||
|
("start_time", "<", self.start_time + delta), |
||||
|
("end_time", ">", self.end_time - delta), |
||||
|
("end_time", "<", self.end_time + delta), |
||||
|
("task_type_id", "=", added_shift.task_type_id.id), |
||||
|
], |
||||
|
limit=1, |
||||
|
) |
||||
|
|
||||
|
if len(non_assigned_shifts): |
||||
|
actual_shift = non_assigned_shifts[0] |
||||
|
else: |
||||
|
actual_shift = self.env["beesdoo.shift.shift"].create( |
||||
|
{ |
||||
|
"name": _("%s (added)" % self.name), |
||||
|
"task_type_id": added_shift.task_type_id.id, |
||||
|
"start_time": self.start_time, |
||||
|
"end_time": self.end_time, |
||||
|
} |
||||
|
) |
||||
|
actual_shift.write( |
||||
|
{ |
||||
|
"state": added_shift.state, |
||||
|
"worker_id": added_shift.worker_id.id, |
||||
|
"is_regular": not is_compensation and is_regular_worker, |
||||
|
"is_compensation": is_compensation and is_regular_worker, |
||||
|
} |
||||
|
) |
||||
|
added_shift.task_id = actual_shift.id |
||||
|
|
||||
|
if actual_shift.state == "done": |
||||
|
self.attended_worker_no += 1 |
||||
|
|
||||
|
self.validated_by = user |
||||
|
self.state = "validated" |
||||
|
return |
||||
|
|
||||
|
@api.multi |
||||
|
def validate_with_checks(self): |
||||
|
self.ensure_one() |
||||
|
|
||||
|
if self.state == "validated": |
||||
|
raise UserError(_("The sheet has already been validated.")) |
||||
|
if self.start_time > datetime.now(): |
||||
|
raise UserError( |
||||
|
_( |
||||
|
"Attendance sheet can only be validated once the shifts have started." |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
# Fields validation |
||||
|
for added_shift in self.added_shift_ids: |
||||
|
if not added_shift.worker_id: |
||||
|
raise UserError( |
||||
|
_("Worker name is missing for an added shift.") |
||||
|
) |
||||
|
if added_shift.state != "done": |
||||
|
raise UserError( |
||||
|
_("Shift State is missing or wrong for %s") |
||||
|
% added_shift.worker_id.name |
||||
|
) |
||||
|
if not added_shift.task_type_id: |
||||
|
raise UserError( |
||||
|
_("Task Type is missing for %s") |
||||
|
% added_shift.worker_id.name |
||||
|
) |
||||
|
if not added_shift.working_mode: |
||||
|
raise UserError( |
||||
|
_("Working mode is missing for %s") |
||||
|
% added_shift.worker_id.name |
||||
|
) |
||||
|
if added_shift.working_mode not in ["regular", "irregular"]: |
||||
|
raise UserError( |
||||
|
_("Warning : Working mode for %s is %s") |
||||
|
% ( |
||||
|
added_shift.worker_id.name, |
||||
|
added_shift.worker_id.working_mode, |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
for expected_shift in self.expected_shift_ids: |
||||
|
if not expected_shift.state: |
||||
|
raise UserError( |
||||
|
_("Shift State is missing for %s") |
||||
|
% expected_shift.worker_id.name |
||||
|
) |
||||
|
if ( |
||||
|
expected_shift.state == "absent" |
||||
|
and not expected_shift.compensation_no |
||||
|
): |
||||
|
raise UserError( |
||||
|
_("Compensation number is missing for %s") |
||||
|
% expected_shift.worker_id.name |
||||
|
) |
||||
|
|
||||
|
# Open a validation wizard only if not admin |
||||
|
if self.env.user.has_group( |
||||
|
"beesdoo_shift_attendance.group_shift_attendance_sheet_validation" |
||||
|
): |
||||
|
if not self.worker_nb_feedback: |
||||
|
raise UserError( |
||||
|
_("Please give your feedback about the number of workers.") |
||||
|
) |
||||
|
self._validate(self.env.user.partner_id) |
||||
|
return |
||||
|
return { |
||||
|
"type": "ir.actions.act_window", |
||||
|
"res_model": "beesdoo.shift.sheet.validate", |
||||
|
"view_type": "form", |
||||
|
"view_mode": "form", |
||||
|
"target": "new", |
||||
|
} |
||||
|
|
||||
|
@api.model |
||||
|
def _generate_attendance_sheet(self): |
||||
|
""" |
||||
|
Generate sheets with shifts in the time interval |
||||
|
defined from corresponding CRON time interval. |
||||
|
""" |
||||
|
tasks = self.env["beesdoo.shift.shift"] |
||||
|
sheets = self.env["beesdoo.shift.sheet"] |
||||
|
current_time = datetime.now() |
||||
|
generation_interval_setting = int( |
||||
|
self.env["ir.config_parameter"].sudo().get_param( |
||||
|
"beesdoo_shift_attendance.attendance_sheet_generation_interval" |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
allowed_time_range = timedelta(minutes=generation_interval_setting) |
||||
|
|
||||
|
tasks = tasks.search( |
||||
|
[ |
||||
|
("start_time", ">", str(current_time)), |
||||
|
("start_time", "<", str(current_time + allowed_time_range)), |
||||
|
] |
||||
|
) |
||||
|
|
||||
|
for task in tasks: |
||||
|
start_time = task.start_time |
||||
|
end_time = task.end_time |
||||
|
sheets = sheets.search( |
||||
|
[("start_time", "=", start_time), ("end_time", "=", end_time)] |
||||
|
) |
||||
|
|
||||
|
if not sheets: |
||||
|
sheet = sheets.create( |
||||
|
{"start_time": start_time, "end_time": end_time} |
||||
|
) |
||||
|
|
||||
|
@api.model |
||||
|
def _cron_non_validated_sheets(self): |
||||
|
sheets = self.env["beesdoo.shift.sheet"] |
||||
|
non_validated_sheets = sheets.search( |
||||
|
[ |
||||
|
("day", "=", date.today() - timedelta(days=1)), |
||||
|
("state", "=", "not_validated"), |
||||
|
] |
||||
|
) |
||||
|
|
||||
|
if non_validated_sheets: |
||||
|
mail_template = self.env.ref( |
||||
|
"beesdoo_shift_attendance.email_template_non_validated_sheet", |
||||
|
False, |
||||
|
) |
||||
|
for rec in non_validated_sheets: |
||||
|
mail_template.send_mail(rec.id, True) |
@ -0,0 +1,63 @@ |
|||||
|
# 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_attendance.card_support", |
||||
|
) |
||||
|
pre_filled_task_type_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.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_attendance.card_support", str(self.card_support), |
||||
|
) |
||||
|
parameters.set_param( |
||||
|
"beesdoo_shift_attendance.pre_filled_task_type_id", |
||||
|
str(self.pre_filled_task_type_id.id), |
||||
|
) |
||||
|
parameters.set_param( |
||||
|
"beesdoo_shift_attendance.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_attendance.card_support" |
||||
|
), |
||||
|
), |
||||
|
pre_filled_task_type_id=int( |
||||
|
self.env["ir.config_parameter"].get_param( |
||||
|
"beesdoo_shift_attendance.pre_filled_task_type_id" |
||||
|
) |
||||
|
), |
||||
|
) |
||||
|
return res |
@ -0,0 +1,15 @@ |
|||||
|
<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="group_shift_attendance_sheet_validation" model="res.groups"> |
||||
|
<field name="name">Attendance Sheet Validation</field> |
||||
|
<field name="category_id" ref="base.module_category_cooperative_management"/> |
||||
|
<field name="implied_ids" eval="[(4, ref('group_shift_attendance_sheet'))]"/> |
||||
|
<field name="users" eval="[(6, 0, [ref('base.user_root')])]" /> |
||||
|
</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,beesdoo_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,beesdoo_shift.model_beesdoo_shift_template,group_shift_attendance_sheet,1,0,0,0 |
||||
|
sheet_access_beesdoo_shift_type,sheet_access_beesdoo_shift_type,beesdoo_shift.model_beesdoo_shift_type,group_shift_attendance_sheet,1,0,0,0 |
||||
|
access_beesdoo_shift_daynumber,access_beesdoo_shift_daynumber,beesdoo_shift.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,beesdoo_shift.group_shift_attendance,1,1,1,1 |
||||
|
manage_beesdoo_shift_sheet_expected,manage_beesdoo_shift_sheet_expected,model_beesdoo_shift_sheet_expected,beesdoo_shift.group_shift_attendance,1,1,1,1 |
@ -0,0 +1 @@ |
|||||
|
from . import test_beesdoo_shift |
@ -0,0 +1,393 @@ |
|||||
|
# 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.pre_filled_task_type_id = self.env["ir.config_parameter"].sudo().get_param( |
||||
|
"beesdoo_shift.pre_filled_task_type_id" |
||||
|
) |
||||
|
|
||||
|
self.current_time = datetime.now() |
||||
|
self.user_admin = self.env.ref("base.user_root") |
||||
|
self.user_generic = self.env.ref( |
||||
|
"beesdoo_shift_attendance.beesdoo_shift_user_1_demo" |
||||
|
) |
||||
|
self.user_permanent = self.env.ref( |
||||
|
"beesdoo_shift_attendance.beesdoo_shift_user_2_demo" |
||||
|
) |
||||
|
|
||||
|
self.setting_wizard = self.env["res.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_worker_status" |
||||
|
".beesdoo_shift_task_template_1_demo" |
||||
|
) |
||||
|
self.task_template_2 = self.env.ref( |
||||
|
"beesdoo_worker_status" |
||||
|
".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, |
||||
|
} |
||||
|
) |
||||
|
self.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 search_sheets(self, start_time, end_time): |
||||
|
if (type(start_time) and type(end_time)) == datetime: |
||||
|
return self.attendance_sheet_model.search( |
||||
|
[("start_time", "=", start_time), ("end_time", "=", end_time)] |
||||
|
) |
||||
|
|
||||
|
def test_attendance_sheet_creation(self): |
||||
|
"Test creation of an attendance sheet with all its expected shifts" |
||||
|
|
||||
|
# Set generation interval setting |
||||
|
setting_wizard_1 = self.setting_wizard.create( |
||||
|
{ |
||||
|
"pre_filled_task_type_id": self.task_type_1.id, |
||||
|
"attendance_sheet_generation_interval": 15, |
||||
|
} |
||||
|
) |
||||
|
setting_wizard_1.execute() |
||||
|
|
||||
|
# Test attendance sheets creation |
||||
|
self.attendance_sheet_model._generate_attendance_sheet() |
||||
|
|
||||
|
self.assertEqual( |
||||
|
len(self.search_sheets(self.start_in_1, self.end_in_1)), 1 |
||||
|
) |
||||
|
self.assertEqual( |
||||
|
len(self.search_sheets(self.start_in_2, self.end_in_2)), 1 |
||||
|
) |
||||
|
self.assertEqual( |
||||
|
len(self.search_sheets(self.start_out_1, self.end_out_1)), 0 |
||||
|
) |
||||
|
self.assertEqual( |
||||
|
len(self.search_sheets(self.start_out_2, self.end_out_2)), 0 |
||||
|
) |
||||
|
|
||||
|
# Test expected shifts creation |
||||
|
# Sheet 1 starts at current time + 2 secs, ends at current time + 10 min |
||||
|
# Sheet 2 starts at current time + 9 min, ends at current time + 21 min |
||||
|
|
||||
|
sheet_1 = self.search_sheets(self.start_in_1, self.end_in_1) |
||||
|
sheet_2 = self.search_sheets(self.start_in_2, self.end_in_2) |
||||
|
|
||||
|
self.assertTrue(sheet_1.start_time) |
||||
|
self.assertTrue(sheet_1.end_time) |
||||
|
|
||||
|
# Empty shift should not be added |
||||
|
self.assertEqual(len(sheet_1.expected_shift_ids), 3) |
||||
|
self.assertEqual(len(sheet_1.added_shift_ids), 0) |
||||
|
self.assertEqual(len(sheet_2.expected_shift_ids), 1) |
||||
|
|
||||
|
# Test consistency with actual shift for sheet 1 |
||||
|
for shift in sheet_1.expected_shift_ids: |
||||
|
self.assertEquals(shift.worker_id, shift.task_id.worker_id) |
||||
|
self.assertEquals( |
||||
|
shift.replaced_id, shift.task_id.replaced_id |
||||
|
) |
||||
|
self.assertEqual(shift.task_type_id, shift.task_id.task_type_id) |
||||
|
self.assertEqual(shift.super_coop_id, shift.task_id.super_coop_id) |
||||
|
self.assertEqual(shift.working_mode, shift.task_id.working_mode) |
||||
|
|
||||
|
# Status should be "absent" for all shifts |
||||
|
self.assertEquals(shift.state, "absent_2") |
||||
|
|
||||
|
# Empty shift should be considered in max worker number calculation |
||||
|
self.assertEqual(sheet_1.max_worker_no, 4) |
||||
|
|
||||
|
# Test default values creation |
||||
|
self.assertTrue(sheet_1.time_slot) |
||||
|
self.assertEqual(sheet_1.day, self.start_in_1.date()) |
||||
|
self.assertEqual(sheet_1.day_abbrevation, "Lundi") |
||||
|
self.assertEqual(sheet_1.week, "Semaine A") |
||||
|
self.assertTrue(sheet_1.name) |
||||
|
self.assertFalse(sheet_1.notes) |
||||
|
self.assertFalse(sheet_1.is_annotated) |
||||
|
|
||||
|
def test_attendance_sheet_barcode_scan(self): |
||||
|
""" |
||||
|
Edition of an attendance sheet |
||||
|
with barcode scanner, as a generic user" |
||||
|
""" |
||||
|
|
||||
|
# Attendance sheet generation |
||||
|
self.attendance_sheet_model.sudo( |
||||
|
self.user_generic |
||||
|
)._generate_attendance_sheet() |
||||
|
sheet_1 = self.search_sheets(self.start_in_1, self.end_in_1,) |
||||
|
sheet_1 = sheet_1.sudo(self.user_generic) |
||||
|
|
||||
|
""" |
||||
|
Expected workers are : |
||||
|
worker_regular_1 (barcode : 421457731745) |
||||
|
worker_regular_3 replaced by worker_regular_2 (barcode : 421457731744)) |
||||
|
worker_irregular_1 (barcode : 429919251493) |
||||
|
""" |
||||
|
|
||||
|
# Scan barcode for expected workers |
||||
|
for barcode in [421457731745, 421457731744, 429919251493]: |
||||
|
sheet_1.on_barcode_scanned(barcode) |
||||
|
|
||||
|
# Check expected shifts update |
||||
|
for id in sheet_1.expected_shift_ids.ids: |
||||
|
shift = sheet_1.expected_shift_ids.browse(id) |
||||
|
self.assertEqual(shift.state, "done") |
||||
|
|
||||
|
""" |
||||
|
Added workers are : |
||||
|
worker_regular_super_1 (barcode : 421457731741) |
||||
|
worker_irregular_2 (barcode : 421457731743) |
||||
|
""" |
||||
|
|
||||
|
# Workararound for _onchange method |
||||
|
# (not applying on temporary object in tests) |
||||
|
sheet_1._origin = sheet_1 |
||||
|
|
||||
|
# Scan barcode for added workers |
||||
|
sheet_1.on_barcode_scanned(421457731741) |
||||
|
self.assertEqual(len(sheet_1.added_shift_ids), 1) |
||||
|
sheet_1.on_barcode_scanned(421457731743) |
||||
|
# Scan an already added worker should not change anything |
||||
|
sheet_1.on_barcode_scanned(421457731743) |
||||
|
self.assertEqual(len(sheet_1.added_shift_ids), 2) |
||||
|
|
||||
|
# Check added shifts fields |
||||
|
for id in sheet_1.added_shift_ids.ids: |
||||
|
shift = sheet_1.added_shift_ids.browse(id) |
||||
|
self.assertEqual(sheet_1, shift.attendance_sheet_id) |
||||
|
self.assertEqual(shift.state, "done") |
||||
|
self.assertEqual( |
||||
|
shift.task_type_id, |
||||
|
self.attendance_sheet_shift_model.pre_filled_task_type_id(), |
||||
|
) |
||||
|
if shift.working_mode == "regular": |
||||
|
self.assertTrue(shift.is_compensation) |
||||
|
else: |
||||
|
self.assertFalse(shift.is_compensation) |
||||
|
|
||||
|
# Add a worker that should be replaced |
||||
|
with self.assertRaises(UserError): |
||||
|
sheet_1.on_barcode_scanned(421457731742) |
||||
|
# Wrong barcode |
||||
|
with self.assertRaises(UserError): |
||||
|
sheet_1.on_barcode_scanned(101010) |
||||
|
|
||||
|
# Add an unsubscribed worker |
||||
|
self.worker_regular_1.cooperative_status_ids.sr = -2 |
||||
|
self.worker_regular_1.cooperative_status_ids.sc = -2 |
||||
|
with self.assertRaises(UserError): |
||||
|
sheet_1.on_barcode_scanned(421457731745) |
||||
|
|
||||
|
def test_attendance_sheet_edition(self): |
||||
|
|
||||
|
# Attendance sheet generation |
||||
|
self.attendance_sheet_model.sudo( |
||||
|
self.user_generic |
||||
|
)._generate_attendance_sheet() |
||||
|
sheet_1 = self.search_sheets(self.start_in_1, self.end_in_1) |
||||
|
|
||||
|
# Expected shifts edition |
||||
|
sheet_1.expected_shift_ids[1].state = "done" |
||||
|
sheet_1.expected_shift_ids[2].state = "absent_1" |
||||
|
|
||||
|
# Added shits addition |
||||
|
sheet_1.added_shift_ids |= sheet_1.added_shift_ids.new( |
||||
|
{ |
||||
|
"task_type_id": self.task_type_2.id, |
||||
|
"state": "done", |
||||
|
"attendance_sheet_id": sheet_1.id, |
||||
|
"worker_id": self.worker_irregular_2.id, |
||||
|
"is_compensation": False, |
||||
|
"is_regular": False, |
||||
|
} |
||||
|
) |
||||
|
# Same task type as empty shift (should edit it on validation) |
||||
|
sheet_1.added_shift_ids |= sheet_1.added_shift_ids.new( |
||||
|
{ |
||||
|
"task_type_id": self.task_type_1.id, |
||||
|
"state": "done", |
||||
|
"attendance_sheet_id": sheet_1.id, |
||||
|
"worker_id": self.worker_regular_super_1.id, |
||||
|
"is_compensation": True, |
||||
|
"is_regular": False, |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
# TODO: test validation with wizard (as generic user) |
||||
|
# class odoo.tests.common.Form(recordp, view=None) |
||||
|
# is only available from version 12 |
||||
|
|
||||
|
# sheet_1 = sheet_1.sudo(self.user_generic) |
||||
|
|
||||
|
# Validation without wizard (as admin user) |
||||
|
sheet_1 = sheet_1.sudo(self.user_admin) |
||||
|
|
||||
|
# Wait necessary time for shifts to begin |
||||
|
waiting_time = (self.start_in_1 - datetime.now()).total_seconds() |
||||
|
if waiting_time > 0: |
||||
|
with self.assertRaises(UserError) as econtext: |
||||
|
sheet_1.validate_with_checks() |
||||
|
self.assertIn("once the shifts have started", str(econtext.exception)) |
||||
|
time.sleep(waiting_time) |
||||
|
|
||||
|
sheet_1.worker_nb_feedback = "enough" |
||||
|
sheet_1.feedback = "Great session." |
||||
|
sheet_1.notes = "Important information." |
||||
|
|
||||
|
sheet_1.validate_with_checks() |
||||
|
|
||||
|
with self.assertRaises(UserError) as econtext: |
||||
|
sheet_1.validate_with_checks() |
||||
|
self.assertIn("already been validated", str(econtext.exception)) |
||||
|
|
||||
|
self.assertEqual(sheet_1.state, "validated") |
||||
|
self.assertEqual(sheet_1.validated_by, self.user_admin.partner_id) |
||||
|
self.assertTrue(sheet_1.is_annotated) |
||||
|
self.assertFalse(sheet_1.is_read) |
||||
|
|
||||
|
# Check actual shifts update |
||||
|
workers = sheet_1.expected_shift_ids.mapped( |
||||
|
"worker_id" |
||||
|
) | sheet_1.added_shift_ids.mapped("worker_id") |
||||
|
self.assertEqual(len(workers), 5) |
||||
|
self.assertEqual( |
||||
|
sheet_1.expected_shift_ids[0].task_id.state, "absent_2" |
||||
|
) |
||||
|
self.assertEqual(sheet_1.expected_shift_ids[1].task_id.state, "done") |
||||
|
self.assertEqual( |
||||
|
sheet_1.expected_shift_ids[2].task_id.state, "absent_1" |
||||
|
) |
||||
|
self.assertEqual(sheet_1.added_shift_ids[0].task_id.state, "done") |
||||
|
self.assertEqual(sheet_1.added_shift_ids[1].task_id.state, "done") |
||||
|
|
||||
|
# Empty shift should have been updated |
||||
|
self.assertEqual( |
||||
|
sheet_1.added_shift_ids[0].task_id, self.shift_empty_1 |
||||
|
) |
||||
|
|
||||
|
# sheet_1.expected_shift_ids[0].worker_id |
@ -0,0 +1,263 @@ |
|||||
|
<odoo> |
||||
|
|
||||
|
<!-- Attendance Sheet Shifts Views --> |
||||
|
|
||||
|
<record model="ir.ui.view" id="sheet_view_search"> |
||||
|
<field name="name">Attendance Sheet Search</field> |
||||
|
<field name="model">beesdoo.shift.sheet</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<search> |
||||
|
<field name="day" /> |
||||
|
<field name="validated_by" /> |
||||
|
<filter string="Annotated (unread)" |
||||
|
name="annotated" |
||||
|
domain="[('is_annotated', '=', True), |
||||
|
('is_read', '=', False)]" /> |
||||
|
<filter string="Annotated (read)" |
||||
|
name="annotated_read" |
||||
|
domain="[('is_annotated', '=', True), |
||||
|
('is_read', '=', True)]" /> |
||||
|
<separator /> |
||||
|
<filter string="Archived" |
||||
|
name="archived" |
||||
|
domain="[('active', '=', False)]" /> |
||||
|
<group expand="1" string="Group By"> |
||||
|
<filter string="Day" name="gb_day" |
||||
|
context="{'group_by' : 'start_time:day'}" /> |
||||
|
<filter string="Feedback on number of workers" |
||||
|
name="gb_worker_nb_feedback" |
||||
|
context="{'group_by' : 'worker_nb_feedback'}" /> |
||||
|
</group> |
||||
|
</search> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record model="ir.ui.view" id="sheet_expected_view_tree"> |
||||
|
<field name="name">Expected Shifts List</field> |
||||
|
<field name="model">beesdoo.shift.sheet.expected</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<tree create="false" |
||||
|
decoration-danger="state in ['absent_0', 'absent_1', 'absent_2'] " |
||||
|
decoration-success="state == 'done'"> |
||||
|
<field name="task_type_id" readonly="True" options="{'no_open': True}"/> |
||||
|
<field name="super_coop_id" readonly="True" options="{'no_open': True}"/> |
||||
|
<field name="worker_id" readonly="True" options="{'no_open': True}"/> |
||||
|
<field name="working_mode" /> |
||||
|
<field name="replaced_id" readonly="True"/> |
||||
|
<field name="state" readonly="True" /> |
||||
|
</tree> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record model="ir.ui.view" id="sheet_added_view_tree"> |
||||
|
<field name="view_mode">tree</field> |
||||
|
<field name="name">Added Shifts List</field> |
||||
|
<field name="model">beesdoo.shift.sheet.added</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<tree decoration-danger="state in ['absent_0', 'absent_1', 'absent_2'] " |
||||
|
decoration-success="state == 'done'"> |
||||
|
<field name="task_type_id" |
||||
|
options="{'no_open': True, 'no_create': True, 'no_create_edit':True }" /> |
||||
|
<field name="worker_id" |
||||
|
domain="[ |
||||
|
('eater', '=', 'worker_eater'), |
||||
|
('working_mode', 'in', ('regular', 'irregular')), |
||||
|
('state', 'not in', ('unsubscribed', 'resigning')), |
||||
|
]" |
||||
|
options="{'no_open': True, 'no_create': True, 'no_create_edit':True }" /> |
||||
|
<field name="working_mode" /> |
||||
|
<field name="is_compensation" |
||||
|
attrs="{'invisible': |
||||
|
[('working_mode','=','irregular')]}"/> |
||||
|
<field name="state"/> |
||||
|
</tree> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record model="ir.ui.view" id="sheet_expected_view_form"> |
||||
|
<field name="view_mode">tree</field> |
||||
|
<field name="name">Expected Shifts Form</field> |
||||
|
<field name="model">beesdoo.shift.sheet.expected</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form readonly="True"> |
||||
|
<group string="Expected Shift"> |
||||
|
<field name="task_type_id" readonly="True" options="{'no_open': True}"/> |
||||
|
<field name="worker_id" readonly="True" options="{'no_open': True}"/> |
||||
|
<field name="working_mode" /> |
||||
|
<field name="replaced_id" |
||||
|
attrs="{'invisible': |
||||
|
[('working_mode','=','irregular')]}" |
||||
|
options="{'no_create': True, 'no_create_edit':True}"/> |
||||
|
<field name="state" /> |
||||
|
</group> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record model="ir.ui.view" id="sheet_added_view_form"> |
||||
|
<field name="view_mode">tree</field> |
||||
|
<field name="name">Added Shifts Form</field> |
||||
|
<field name="model">beesdoo.shift.sheet.added</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form readonly="True" > |
||||
|
<group> |
||||
|
<field name="task_type_id" |
||||
|
options="{'no_open': True, 'no_create': True, 'no_create_edit':True }" /> |
||||
|
<field name="worker_id" |
||||
|
domain="[ |
||||
|
('eater', '=', 'worker_eater'), |
||||
|
('working_mode', 'in', ('regular', 'irregular')), |
||||
|
('state', 'not in', ('unsubscribed', 'resigning')), |
||||
|
]" |
||||
|
options="{'no_open': True, 'no_create': True, 'no_create_edit':True }" /> |
||||
|
<field name="working_mode" /> |
||||
|
<field name="is_compensation" |
||||
|
attrs="{'invisible': |
||||
|
[('working_mode','=','irregular')]}"/> |
||||
|
<field name="state"/> |
||||
|
</group> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<!-- Attendance Sheets Views --> |
||||
|
|
||||
|
<record model="ir.ui.view" id="sheet_view_tree"> |
||||
|
<field name="name">Attendance Sheet List</field> |
||||
|
<field name="model">beesdoo.shift.sheet</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<tree create="false" delete="false" decoration-danger="state == 'not_validated'"> |
||||
|
<field name="week" /> |
||||
|
<field name="day" /> |
||||
|
<field name="time_slot" /> |
||||
|
<field name="max_worker_no" type="char"/> |
||||
|
<field name="state" /> |
||||
|
<field name="validated_by" /> |
||||
|
<field name="attended_worker_no" type="char"/> |
||||
|
<field name="is_annotated" /> |
||||
|
</tree> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record model="ir.ui.view" id="sheet_view_form"> |
||||
|
<field name="name">Attendance Sheet Form</field> |
||||
|
<field name="model">beesdoo.shift.sheet</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form create="false" delete="false"> |
||||
|
<field name="_barcode_scanned" widget="barcode_handler"/> |
||||
|
<header> |
||||
|
<field name="state" widget="statusbar" readonly="True"/> |
||||
|
<button type="object" |
||||
|
name="validate_with_checks" |
||||
|
string="Validate Sheet" |
||||
|
confirm="Beware : a validated sheet cannot be edited anymore |
||||
|
and you won't be able to add any latecomers. |
||||
|
The counters of those who didn't attend will be updated |
||||
|
and they will get warning emails." |
||||
|
attrs="{'invisible': [('state', '=', 'validated')]}" |
||||
|
/> |
||||
|
</header> |
||||
|
<sheet > |
||||
|
<div class="oe_button_box" name="button_box"> |
||||
|
<button name="button_mark_as_read" type="object" |
||||
|
class="oe_stat_button" icon="fa-check" |
||||
|
string="Mark as read" |
||||
|
groups="beesdoo_shift.group_shift_attendance" |
||||
|
/> |
||||
|
<button name="toggle_active" |
||||
|
type="object" |
||||
|
groups="beesdoo_shift.group_shift_attendance" |
||||
|
class="oe_stat_button" |
||||
|
icon="fa-archive"> |
||||
|
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/> |
||||
|
</button> |
||||
|
</div> |
||||
|
<separator string="Expected Shifts" style="position:relative;padding:0px;margin:0px;" /> |
||||
|
<field name="expected_shift_ids" /> |
||||
|
<separator string="Added Shifts" style="position:relative;padding:0px;margin-top:15px;margin-bottom:0px;" /> |
||||
|
<field name="added_shift_ids" /> |
||||
|
<group> |
||||
|
<field name="max_worker_no" /> |
||||
|
</group> |
||||
|
<group string="Feedback" |
||||
|
groups="beesdoo_shift.group_shift_attendance"> |
||||
|
<field name="attended_worker_no" /> |
||||
|
<field name="notes" /> |
||||
|
<field name="feedback" /> |
||||
|
<field name="worker_nb_feedback" /> |
||||
|
<field name="validated_by" readonly="True"/> |
||||
|
</group> |
||||
|
</sheet> |
||||
|
<div class="oe_chatter"> |
||||
|
<field name="message_ids" |
||||
|
widget="mail_thread" |
||||
|
groups="beesdoo_shift.group_shift_attendance" |
||||
|
/> |
||||
|
</div> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<!-- Actions --> |
||||
|
|
||||
|
<act_window id="action_sheet_admin_non_validated" |
||||
|
name="Non-validated sheets" |
||||
|
res_model="beesdoo.shift.sheet" |
||||
|
view_mode="tree,form" |
||||
|
domain="[('state','=','not_validated')]" |
||||
|
/> |
||||
|
<!-- Annotated sheets should display only the both annotated and validated ones--> |
||||
|
<act_window id="action_sheet_admin_annotated" |
||||
|
name="Unread notes" |
||||
|
res_model="beesdoo.shift.sheet" |
||||
|
view_mode="tree,form" |
||||
|
domain="[('is_annotated','=',True), |
||||
|
('is_read','=',False)]" |
||||
|
/> |
||||
|
<act_window id="action_sheet_admin_list" |
||||
|
name="All sheets" |
||||
|
res_model="beesdoo.shift.sheet" |
||||
|
view_mode="tree,form" |
||||
|
/> |
||||
|
<act_window id="action_sheet_daily" |
||||
|
name="Daily attendance sheets" |
||||
|
res_model="beesdoo.shift.sheet" |
||||
|
view_mode="tree,form" |
||||
|
domain="[('end_time','>', datetime.datetime.now().replace(hour=00, minute=00, second=10)), |
||||
|
('start_time','<', datetime.datetime.now().replace(hour=23, minute=59, second=59))]" |
||||
|
/> |
||||
|
|
||||
|
<!-- Top menu item --> |
||||
|
<menuitem id="menu_sheet_top" |
||||
|
name="Attendance Sheets" |
||||
|
parent="beesdoo_shift.menu_root" |
||||
|
groups="beesdoo_shift_attendance.group_shift_attendance_sheet" |
||||
|
sequence="1" |
||||
|
/> |
||||
|
|
||||
|
<!-- Menu actions --> |
||||
|
<menuitem id="menu_sheet" |
||||
|
name="Daily attendance sheets" |
||||
|
parent="menu_sheet_top" |
||||
|
action="action_sheet_daily" |
||||
|
sequence="2" |
||||
|
/> |
||||
|
<menuitem id="menu_sheet_admin_non_validated" |
||||
|
name="Non-validated sheets" |
||||
|
parent="menu_sheet_top" |
||||
|
action="action_sheet_admin_non_validated" |
||||
|
groups="beesdoo_shift.group_shift_attendance" |
||||
|
/> |
||||
|
<menuitem id="menu_sheet_admin_annotated" |
||||
|
name="Unread notes" |
||||
|
parent="menu_sheet_top" |
||||
|
action="action_sheet_admin_annotated" |
||||
|
groups="beesdoo_shift.group_shift_attendance" |
||||
|
/> |
||||
|
<menuitem id="menu_sheet_admin_list" |
||||
|
name="All sheets" |
||||
|
parent="menu_sheet_top" |
||||
|
action="action_sheet_admin_list" |
||||
|
groups="beesdoo_shift.group_shift_attendance" |
||||
|
/> |
||||
|
</odoo> |
@ -0,0 +1,93 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- |
||||
|
Copyright 2019-2020 Elouan Le Bars <elouan@coopiteasy.be> |
||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
--> |
||||
|
<odoo> |
||||
|
<data> |
||||
|
|
||||
|
<record id="res_config_settings_view_form" model="ir.ui.view"> |
||||
|
<field name="name">res.config.settings.view.form.inherit.beesdoo.shift</field> |
||||
|
<field name="model">res.config.settings</field> |
||||
|
<field name="priority" eval="50"/> |
||||
|
<field name="inherit_id" ref="base.res_config_settings_view_form"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<xpath expr="//div[hasclass('settings')]" position="inside"> |
||||
|
<div class="app_settings_block" data-string="Shifts Management" string="Shifts Management" data-key="shifts_management" groups="beesdoo_shift.group_cooperative_admin"> |
||||
|
<field name="has_accounting_entries" invisible="1"/> |
||||
|
<h2>Attendance Sheets</h2> |
||||
|
<div class="row mt16 o_settings_container"> |
||||
|
<div class="col-xs-12 col-md-6 o_setting_box"> |
||||
|
<div class="o_setting_left_pane"> |
||||
|
<field name="card_support"/> |
||||
|
</div> |
||||
|
<div class="o_setting_right_pane"> |
||||
|
<label for="card_support" string="Scan cards for validation" /> |
||||
|
<div class="text-muted"> |
||||
|
If not checked, user credentials are asked. |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row mt16 o_settings_container"> |
||||
|
<div class="col-12 col-lg-6 o_setting_box"> |
||||
|
<div class="o_setting_right_pane"> |
||||
|
<span class="o_form_label">Attendance Sheets Generation Interval</span> |
||||
|
<div class="text-muted"> |
||||
|
Generate attendance sheets before shifts start. |
||||
|
</div> |
||||
|
<div class="content-group"> |
||||
|
<div class="mt16 row"> |
||||
|
<label for="attendance_sheet_generation_interval" string="Interval (minutes)" class="col-3 col-lg-3 o_light_label"/> |
||||
|
<field name="attendance_sheet_generation_interval" class="oe_inline" required="1"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row mt16 o_settings_container"> |
||||
|
<div class="col-12 col-lg-6 o_setting_box"> |
||||
|
<div class="o_setting_right_pane"> |
||||
|
<span class="o_form_label">Default Task Type</span> |
||||
|
<div class="text-muted"> |
||||
|
For attendance sheets automatic pre-filling. |
||||
|
</div> |
||||
|
<div class="content-group"> |
||||
|
<div class="mt16 row"> |
||||
|
<label for="pre_filled_task_type_id" string="Default Task Type" class="col-3 col-lg-3 o_light_label"/> |
||||
|
<field name="pre_filled_task_type_id" class="oe_inline" required="1"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</xpath> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
|
||||
|
<record model="ir.actions.act_window" id="action_missing_attendance_sheets"> |
||||
|
<field name="name">Generate missing past attendance sheets</field> |
||||
|
<field name="res_model">beesdoo.shift.generate_missing_attendance_sheets</field> |
||||
|
<field name="view_mode">form</field> |
||||
|
</record> |
||||
|
|
||||
|
<record model="ir.actions.act_window" id="action_shift_settings"> |
||||
|
<field name="name">Settings</field> |
||||
|
<field name="type">ir.actions.act_window</field> |
||||
|
<field name="res_model">res.config.settings</field> |
||||
|
<field name="view_mode">form</field> |
||||
|
<field name="target">inline</field> |
||||
|
<field name="context">{'module' : 'beesdoo_shift'}</field> |
||||
|
</record> |
||||
|
|
||||
|
<menuitem name="Shift Settings" |
||||
|
id="menu_shift_settings" |
||||
|
parent="beesdoo_shift.menu_configuration_top" |
||||
|
action="action_shift_settings" |
||||
|
groups="beesdoo_shift.group_cooperative_admin" |
||||
|
/> |
||||
|
|
||||
|
</data> |
||||
|
</odoo> |
@ -0,0 +1,2 @@ |
|||||
|
from . import validate_attendance_sheet |
||||
|
from . import generate_missing_attendance_sheets |
@ -0,0 +1,57 @@ |
|||||
|
from odoo import models, fields, api, _ |
||||
|
from odoo.exceptions import UserError, ValidationError |
||||
|
|
||||
|
from datetime import datetime |
||||
|
|
||||
|
|
||||
|
class GenerateMissingAttendanceSheets(models.TransientModel): |
||||
|
""" |
||||
|
Generate missing past sheets |
||||
|
""" |
||||
|
|
||||
|
_name = "beesdoo.shift.generate_missing_attendance_sheets" |
||||
|
|
||||
|
date_start = fields.Datetime("Start date", required=True) |
||||
|
date_end = fields.Datetime("End date", required=True) |
||||
|
|
||||
|
@api.multi |
||||
|
def generate_missing_attendance_sheets(self): |
||||
|
self.ensure_one() |
||||
|
tasks = self.env["beesdoo.shift.shift"] |
||||
|
sheets = self.env["beesdoo.shift.sheet"] |
||||
|
|
||||
|
tasks = tasks.search( |
||||
|
[ |
||||
|
("start_time", ">", self.date_start), |
||||
|
("start_time", "<", self.date_end), |
||||
|
] |
||||
|
) |
||||
|
|
||||
|
# We should not loop on task with same start_time and end_time |
||||
|
# To improve performances |
||||
|
for task in tasks: |
||||
|
start_time = task.start_time |
||||
|
end_time = task.end_time |
||||
|
sheet = sheets.search( |
||||
|
[("start_time", "=", start_time), ("end_time", "=", end_time),] |
||||
|
) |
||||
|
|
||||
|
if not sheet: |
||||
|
sheets |= sheets.create( |
||||
|
{"start_time": start_time, "end_time": end_time} |
||||
|
) |
||||
|
|
||||
|
return { |
||||
|
"name": _("Generated Missing Sheets"), |
||||
|
"type": "ir.actions.act_window", |
||||
|
"view_type": "form", |
||||
|
"view_mode": "tree,form", |
||||
|
"res_model": "beesdoo.shift.sheet", |
||||
|
"target": "current", |
||||
|
"domain": [("id", "in", sheets.ids)], |
||||
|
} |
||||
|
|
||||
|
@api.constrains("date_start", "date_end") |
||||
|
def constrains_dates(self): |
||||
|
if self.date_start > datetime.now() or self.date_end > datetime.now(): |
||||
|
raise UserError(_("Only past attendance sheets can be generated")) |
@ -0,0 +1,35 @@ |
|||||
|
<odoo> |
||||
|
<record model="ir.ui.view" id="generate_attendance_sheet_form"> |
||||
|
<field name="name">Generate Missing Attendance Sheets</field> |
||||
|
<field name="model">beesdoo.shift.generate_missing_attendance_sheets</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form> |
||||
|
<sheet> |
||||
|
<label for="date_start" string="Generate missing attendance sheets in a given time interval" /> |
||||
|
<group> |
||||
|
<field name="date_start" /> |
||||
|
<field name="date_end" /> |
||||
|
</group> |
||||
|
</sheet> |
||||
|
<footer> |
||||
|
<button type="object" |
||||
|
name="generate_missing_attendance_sheets" |
||||
|
string="Generate" |
||||
|
class="oe_highlight" |
||||
|
/> |
||||
|
</footer> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<act_window id="action_missing_attendance_sheets" name="Generate Missing Sheets" |
||||
|
res_model="beesdoo.shift.generate_missing_attendance_sheets" |
||||
|
view_mode="form" target="new" /> |
||||
|
|
||||
|
<menuitem id="menu_missing_attendance_sheets" |
||||
|
name="Generate missing past attendance sheets" |
||||
|
parent="beesdoo_shift.menu_configuration_top" |
||||
|
action="action_missing_attendance_sheets" |
||||
|
sequence="2" |
||||
|
/> |
||||
|
</odoo> |
@ -0,0 +1,138 @@ |
|||||
|
|
||||
|
import ast |
||||
|
|
||||
|
from odoo import _, api, exceptions, fields, models |
||||
|
from odoo.exceptions import UserError, ValidationError |
||||
|
|
||||
|
|
||||
|
class ValidateAttendanceSheet(models.TransientModel): |
||||
|
_name = "beesdoo.shift.sheet.validate" |
||||
|
_description = """Check the user name and validate sheet. |
||||
|
Useless for users in group_shift_attendance""" |
||||
|
_inherit = ["barcodes.barcode_events_mixin"] |
||||
|
|
||||
|
@api.multi |
||||
|
def _get_active_sheet(self): |
||||
|
sheet_id = self._context.get("active_id") |
||||
|
sheet_model = self._context.get("active_model") |
||||
|
|
||||
|
if sheet_id and sheet_model: |
||||
|
return self.env[sheet_model].browse(sheet_id) |
||||
|
|
||||
|
def _get_card_support_setting(self): |
||||
|
return ast.literal_eval( |
||||
|
self.env["ir.config_parameter"].sudo().get_param( |
||||
|
"beesdoo_shift_attendance.card_support" |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
@api.multi |
||||
|
def _get_warning_regular_workers(self): |
||||
|
""" |
||||
|
A warning is shown if some regular workers were not expected |
||||
|
but should be doing their regular shifts. This warning is added |
||||
|
to sheet's notes at validation. |
||||
|
""" |
||||
|
sheet = self._get_active_sheet() |
||||
|
|
||||
|
warning_message = "" |
||||
|
if sheet: |
||||
|
for added_shift in sheet.added_shift_ids: |
||||
|
is_regular_worker = ( |
||||
|
added_shift.worker_id.working_mode == "regular" |
||||
|
) |
||||
|
is_compensation = added_shift.is_compensation |
||||
|
|
||||
|
if is_regular_worker and not is_compensation: |
||||
|
warning_message += ( |
||||
|
_( |
||||
|
"\n%s attended its shift as a normal one but was not expected. " |
||||
|
"Something may be wrong in his/her personnal informations.\n" |
||||
|
) |
||||
|
% added_shift.worker_id.name |
||||
|
) |
||||
|
return warning_message |
||||
|
|
||||
|
active_sheet = fields.Many2one( |
||||
|
"beesdoo.shift.sheet", default=_get_active_sheet |
||||
|
) |
||||
|
card_support = fields.Boolean( |
||||
|
default=_get_card_support_setting, string="Card validation" |
||||
|
) |
||||
|
login = fields.Char(string="Login") |
||||
|
password = fields.Char(string="Password") |
||||
|
barcode = fields.Char(string="Barcode") |
||||
|
warning_regular_workers = fields.Text( |
||||
|
"Warning", |
||||
|
default=_get_warning_regular_workers, |
||||
|
help="Is any regular worker doing its regular shift as an added one ?", |
||||
|
) |
||||
|
notes = fields.Text( |
||||
|
related="active_sheet.notes", |
||||
|
string="Notes about the attendance for the Members Office", |
||||
|
default="", |
||||
|
readonly=False, |
||||
|
) |
||||
|
feedback = fields.Text( |
||||
|
related="active_sheet.feedback", |
||||
|
string="Comments about the shift", |
||||
|
default="", |
||||
|
readonly=False, |
||||
|
) |
||||
|
worker_nb_feedback = fields.Selection( |
||||
|
related="active_sheet.worker_nb_feedback", readonly=False |
||||
|
) |
||||
|
|
||||
|
def on_barcode_scanned(self, barcode): |
||||
|
self.barcode = barcode |
||||
|
|
||||
|
@api.multi |
||||
|
def save(self): |
||||
|
sheet = self.active_sheet |
||||
|
sheet.notes = self.notes |
||||
|
sheet.feedback = self.feedback |
||||
|
sheet.worker_nb_feedback = self.worker_nb_feedback |
||||
|
|
||||
|
@api.multi |
||||
|
def validate_sheet(self): |
||||
|
sheet = self.active_sheet |
||||
|
|
||||
|
if not self.worker_nb_feedback: |
||||
|
raise UserError( |
||||
|
_("Please give your feedback on the number of workers.") |
||||
|
) |
||||
|
|
||||
|
if self.card_support: |
||||
|
# Login with barcode |
||||
|
card = self.env["member.card"].search( |
||||
|
[("barcode", "=", self.barcode)] |
||||
|
) |
||||
|
if not len(card): |
||||
|
raise UserError(_("Please set a correct barcode.")) |
||||
|
partner = card[0].partner_id |
||||
|
else: |
||||
|
# Login with credentials |
||||
|
if not self.login: |
||||
|
raise UserError(_("Please enter your login.")) |
||||
|
user = self.env["res.users"].search([("login", "=", self.login)]) |
||||
|
user.sudo(user.id)._check_credentials(self.password) |
||||
|
partner = user.partner_id |
||||
|
|
||||
|
can_validate = partner.user_ids.has_group( |
||||
|
"beesdoo_shift_attendance.group_shift_attendance_sheet_validation" |
||||
|
) |
||||
|
|
||||
|
if not partner.super and not can_validate: |
||||
|
raise UserError( |
||||
|
_( |
||||
|
"Only super-cooperators and administrators can validate attendance sheets." |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
if self.notes and self.warning_regular_workers: |
||||
|
self.notes += self.warning_regular_workers |
||||
|
elif self.warning_regular_workers: |
||||
|
self.notes = self.warning_regular_workers |
||||
|
|
||||
|
self.save() |
||||
|
sheet._validate(partner or self.env.user.partner_id) |
@ -0,0 +1,48 @@ |
|||||
|
<odoo> |
||||
|
<record model="ir.ui.view" id="validate_attendance_sheet_form"> |
||||
|
<field name="name">Validate Attendance Sheet</field> |
||||
|
<field name="model">beesdoo.shift.sheet.validate</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form> |
||||
|
<sheet> |
||||
|
<separator string="Validation"/> |
||||
|
<field name="card_support" invisible="1"/> |
||||
|
<field name="active_sheet" invisible="1"/> |
||||
|
<field name="_barcode_scanned" widget="barcode_handler"/> |
||||
|
<label for="notes"/> |
||||
|
<field name="notes"/> |
||||
|
<label for="feedback"/> |
||||
|
<field name="feedback"/> |
||||
|
<group> |
||||
|
<field name="worker_nb_feedback"/> |
||||
|
</group> |
||||
|
<group string="Login" |
||||
|
attrs="{'invisible': [('card_support', '=', True)]}" |
||||
|
> |
||||
|
<field name="login" /> |
||||
|
<field name="password" password="True"/> |
||||
|
</group> |
||||
|
<group string="Scan your card" |
||||
|
attrs="{'invisible': [('card_support', '=', False)]}" |
||||
|
> |
||||
|
<field name="barcode" /> |
||||
|
</group> |
||||
|
<field name="warning_regular_workers" |
||||
|
readonly="1" |
||||
|
attrs="{'invisible': [('warning_regular_workers', '=', False)]}" |
||||
|
/> |
||||
|
</sheet> |
||||
|
<footer> |
||||
|
<button type="object" |
||||
|
name="validate_sheet" |
||||
|
string="Validate" |
||||
|
class="oe_highlight" |
||||
|
/> |
||||
|
<button type="object" name="save" |
||||
|
string="Save" /> |
||||
|
<button special="cancel" string="Cancel" /> |
||||
|
</footer> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,26 @@ |
|||||
|
# Copyright 2017-2020 Coop IT Easy (http://coopiteasy.be) |
||||
|
# Rémy Taymans <remy@coopiteasy.be> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
{ |
||||
|
'name': 'BEES coop Website Shift', |
||||
|
'summary': """ |
||||
|
Show available shifts for regular and irregular workers on the |
||||
|
website and let workers manage their shifts with an |
||||
|
easy web interface. |
||||
|
""", |
||||
|
'description': """ |
||||
|
""", |
||||
|
"author": "Coop IT Easy SCRLfs", |
||||
|
"license": "AGPL-3", |
||||
|
"version": "12.0.1.0.0", |
||||
|
"website": "https://github.com/beescoop/Obeesdoo", |
||||
|
"category": "Cooperative management", |
||||
|
"depends": ["portal", "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", |
||||
|
], |
||||
|
} |
@ -1,32 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
|
|
||||
# Copyright 2017-2018 Rémy Taymans <remytaymans@gmail.com> |
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|
||||
|
|
||||
{ |
|
||||
'name': 'BEES coop Website Shift', |
|
||||
|
|
||||
'summary': """ |
|
||||
Show available shifts for regular and irregular workers on the |
|
||||
website and let workers manage their shifts with an |
|
||||
easy web interface. |
|
||||
""", |
|
||||
'description': """ |
|
||||
""", |
|
||||
|
|
||||
'author': 'Rémy Taymans', |
|
||||
'license': 'AGPL-3', |
|
||||
'version': '9.0.2.3.1', |
|
||||
'website': "https://github.com/beescoop/Obeesdoo", |
|
||||
|
|
||||
'category': 'Cooperative management', |
|
||||
|
|
||||
'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', |
|
||||
] |
|
||||
} |
|
@ -1,11 +0,0 @@ |
|||||
# coding: utf-8 |
|
||||
|
|
||||
|
|
||||
def migrate(cr, version): |
|
||||
"""Create a sequence for beesdoo_website_shift_config_settings.""" |
|
||||
cr.execute( |
|
||||
""" |
|
||||
CREATE SEQUENCE IF NOT EXISTS |
|
||||
beesdoo_website_shift_config_settings_id_seq |
|
||||
""" |
|
||||
) |
|
@ -1 +1,2 @@ |
|||||
|
from . import website |
||||
from . import res_config |
from . import res_config |
@ -1,136 +1,45 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
|
|
||||
# Copyright 2017-2018 Rémy Taymans <remytaymans@gmail.com> |
|
||||
|
# Copyright 2017-2020 Rémy Taymans <remytaymans@gmail.com> |
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
from ast import literal_eval |
from ast import literal_eval |
||||
from openerp import fields, models, api |
|
||||
|
|
||||
PARAMS = [ |
|
||||
('irregular_shift_limit', 'beesdoo_website_shift.irregular_shift_limit'), |
|
||||
('highlight_rule_pc', 'beesdoo_website_shift.highlight_rule_pc'), |
|
||||
('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'), |
|
||||
('regular_highlight_rule', |
|
||||
'beesdoo_website_shift.regular_highlight_rule'), |
|
||||
] |
|
||||
|
from odoo import fields, models, api |
||||
|
|
||||
|
|
||||
class WebsiteShiftConfigSettings(models.TransientModel): |
class WebsiteShiftConfigSettings(models.TransientModel): |
||||
_name = 'beesdoo.website.shift.config.settings' |
|
||||
_inherit = 'res.config.settings' |
_inherit = 'res.config.settings' |
||||
|
|
||||
# Irregular worker settings |
# Irregular worker settings |
||||
irregular_shift_limit = fields.Integer( |
irregular_shift_limit = fields.Integer( |
||||
help="Maximum shift that will be shown" |
|
||||
|
related='website_id.irregular_shift_limit', |
||||
|
readonly=False, |
||||
) |
) |
||||
highlight_rule_pc = fields.Integer( |
highlight_rule_pc = fields.Integer( |
||||
help="Treshold (in %) of available space in a shift that trigger the " |
|
||||
"highlight of the shift" |
|
||||
|
related='website_id.highlight_rule_pc', |
||||
|
readonly=False, |
||||
) |
) |
||||
hide_rule = fields.Integer( |
hide_rule = fields.Integer( |
||||
help="Treshold ((available space)/(max space)) in percentage of " |
|
||||
"available space under wich the shift is hidden" |
|
||||
|
related='website_id.highlight_rule_pc', |
||||
|
readonly=False, |
||||
) |
) |
||||
irregular_enable_sign_up = fields.Boolean( |
irregular_enable_sign_up = fields.Boolean( |
||||
help="Enable shift sign up for irregular worker" |
|
||||
|
related='website_id.irregular_enable_sign_up', |
||||
|
readonly=False, |
||||
) |
) |
||||
irregular_past_shift_limit = fields.Integer( |
irregular_past_shift_limit = fields.Integer( |
||||
help="Maximum past shift that will be shown for irregular worker" |
|
||||
|
related='website_id.irregular_past_shift_limit', |
||||
|
readonly=False, |
||||
) |
) |
||||
|
|
||||
# Regular worker settings |
# Regular worker settings |
||||
regular_past_shift_limit = fields.Integer( |
regular_past_shift_limit = fields.Integer( |
||||
help="Maximum past shift that will be shown for regular worker" |
|
||||
|
related='website_id.regular_past_shift_limit', |
||||
|
readonly=False, |
||||
) |
) |
||||
regular_next_shift_limit = fields.Integer( |
regular_next_shift_limit = fields.Integer( |
||||
help="Maximun number of next shift that will be shown" |
|
||||
|
related='website_id.regular_next_shift_limit', |
||||
|
readonly=False, |
||||
) |
) |
||||
regular_highlight_rule = fields.Integer( |
regular_highlight_rule = fields.Integer( |
||||
help="Treshold (in %) of available space in a shift that trigger the " |
|
||||
"the highlight of a shift template." |
|
||||
|
related='website_id.regular_highlight_rule', |
||||
|
readonly=False, |
||||
) |
) |
||||
|
|
||||
@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_pc(self): |
|
||||
return { |
|
||||
'highlight_rule_pc': int( |
|
||||
self.env['ir.config_parameter'] |
|
||||
.get_param("beesdoo_website_shift.highlight_rule_pc") |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
@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') |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
@api.multi |
|
||||
def get_default_regular_highlight_rule(self): |
|
||||
return { |
|
||||
'regular_highlight_rule': int( |
|
||||
self.env['ir.config_parameter'] |
|
||||
.get_param('beesdoo_website_shift.regular_highlight_rule') |
|
||||
) |
|
||||
} |
|
@ -0,0 +1,47 @@ |
|||||
|
# Copyright 2017-2020 Rémy Taymans <remytaymans@gmail.com> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from odoo import fields, models |
||||
|
|
||||
|
|
||||
|
class Website(models.Model): |
||||
|
_inherit = 'website' |
||||
|
|
||||
|
# Irregular worker settings |
||||
|
irregular_shift_limit = fields.Integer( |
||||
|
default=0, |
||||
|
help="Maximum shift that will be shown" |
||||
|
) |
||||
|
highlight_rule_pc = fields.Integer( |
||||
|
default=30, |
||||
|
help="Treshold (in %) of available space in a shift that trigger the " |
||||
|
"highlight of the shift" |
||||
|
) |
||||
|
hide_rule = fields.Integer( |
||||
|
default=20, |
||||
|
help="Treshold ((available space)/(max space)) in percentage of " |
||||
|
"available space under wich the shift is hidden" |
||||
|
) |
||||
|
irregular_enable_sign_up = fields.Boolean( |
||||
|
default=True, |
||||
|
help="Enable shift sign up for irregular worker" |
||||
|
) |
||||
|
irregular_past_shift_limit = fields.Integer( |
||||
|
default=10, |
||||
|
help="Maximum past shift that will be shown for irregular worker" |
||||
|
) |
||||
|
|
||||
|
# Regular worker settings |
||||
|
regular_past_shift_limit = fields.Integer( |
||||
|
default=10, |
||||
|
help="Maximum past shift that will be shown for regular worker" |
||||
|
) |
||||
|
regular_next_shift_limit = fields.Integer( |
||||
|
default=13, |
||||
|
help="Maximun number of next shift that will be shown" |
||||
|
) |
||||
|
regular_highlight_rule = fields.Integer( |
||||
|
default=20, |
||||
|
help="Treshold (in %) of available space in a shift that trigger the " |
||||
|
"the highlight of a shift template." |
||||
|
) |
1417
beesdoo_website_shift/views/my_shift_website_templates.xml
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,209 +1,159 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<!-- |
<!-- |
||||
Copyright 2017-2018 Rémy Taymans <remytaymans@gmail.com> |
|
||||
|
Copyright 2017-2020 Rémy Taymans <remytaymans@gmail.com> |
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
--> |
--> |
||||
<openerp> |
|
||||
|
|
||||
<!-- Add menu entries --> |
|
||||
<data noupdate="1"> |
|
||||
<record id="menu_work_irregular" model="website.menu"> |
|
||||
<field name="name">Shifts Irregular</field> |
|
||||
<field name="url">/shift_irregular_worker</field> |
|
||||
<field name="parent_id" ref="website.main_menu"/> |
|
||||
<field name="sequence" type="int">50</field> |
|
||||
</record> |
|
||||
<record id="menu_work_regular" model="website.menu"> |
|
||||
<field name="name">Shifts Regular</field> |
|
||||
<field name="url">/shift_template_regular_worker</field> |
|
||||
<field name="parent_id" ref="website.main_menu"/> |
|
||||
<field name="sequence" type="int">51</field> |
|
||||
</record> |
|
||||
</data> |
|
||||
|
|
||||
<!-- Public Available Tasks Templates for Regular Workers --> |
|
||||
<template |
|
||||
id="public_shift_template_regular_worker" |
|
||||
name="Available Tasks Templates for Regular Workers" |
|
||||
page="True"> |
|
||||
<t t-call="website.layout"> |
|
||||
|
|
||||
<div class="oe_structure"/> |
|
||||
|
|
||||
<section class="wrap"> |
|
||||
<div class="container"> |
|
||||
<div class="row"> |
|
||||
<div class="col-md-12"> |
|
||||
<h1 class="text-center"> |
|
||||
Available Tasks Templates for Regular Workers |
|
||||
</h1> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</section> |
|
||||
|
|
||||
<div class="oe_structure"/> |
|
||||
|
|
||||
<section class="wrap"> |
|
||||
<div class="container"> |
|
||||
<div class="row"> |
|
||||
<div class="col-md-12"> |
|
||||
<p class="text-center"> |
|
||||
Subscribe via the member office or via |
|
||||
<a href="mailto:membre@bees-coop.be">membre@bees-coop.be</a> |
|
||||
</p> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</section> |
|
||||
|
|
||||
<section class="wrap"> |
|
||||
<div class="container"> |
|
||||
<div class="row"> |
|
||||
<div class="col-md-12"> |
|
||||
|
|
||||
<div class="oe_structure"/> |
|
||||
|
|
||||
<div class="visible-xs" t-foreach="task_tpls_data" t-as="template_data"> |
|
||||
<t t-set="template" t-value="template_data[0]"/> |
|
||||
<t t-set="has_enough_workers" t-value="template_data[1]"/> |
|
||||
<t t-set="highlight_class" t-value="'panel-warning' if not has_enough_workers else 'panel-default'"/> |
|
||||
<div t-att-class="'panel %s' % highlight_class"> |
|
||||
<div class="panel-heading clearfix"> |
|
||||
<div class="panel-title pull-left"> |
|
||||
<t t-esc="template.planning_id.name"/> : |
|
||||
<t t-esc="template.day_nb_id.name"/> |
|
||||
<t t-esc='float_to_time(template.start_time)' /> - |
|
||||
<t t-esc='float_to_time(template.end_time)'/> |
|
||||
</div> |
|
||||
<div class="label label-default pull-right" |
|
||||
t-if="template.remaining_worker > 0"> |
|
||||
<t t-esc="template.remaining_worker"/> space(s) |
|
||||
|
<odoo> |
||||
|
|
||||
|
<!-- Add menu entries --> |
||||
|
<data noupdate="1"> |
||||
|
<record id="menu_work_irregular" model="website.menu"> |
||||
|
<field name="name">Shifts Irregular</field> |
||||
|
<field name="url">/shift_irregular_worker</field> |
||||
|
<field name="parent_id" ref="website.main_menu"/> |
||||
|
<field name="sequence">50</field> |
||||
|
</record> |
||||
|
<record id="menu_work_regular" model="website.menu"> |
||||
|
<field name="name">Shifts Regular</field> |
||||
|
<field name="url">/shift_template_regular_worker</field> |
||||
|
<field name="parent_id" ref="website.main_menu"/> |
||||
|
<field name="sequence">51</field> |
||||
|
</record> |
||||
|
</data> |
||||
|
|
||||
|
|
||||
|
<!-- Help texts --> |
||||
|
<template |
||||
|
id="help_text_public_shift_template_regular_worker" |
||||
|
name="Help text for public available shifts template for irregular worker" |
||||
|
> |
||||
|
<p class="text-center"> |
||||
|
Help text or information text. |
||||
|
</p> |
||||
|
</template> |
||||
|
|
||||
|
|
||||
|
<template |
||||
|
id="help_text_public_shift_irregular_worker" |
||||
|
name="Help text for public Available Shifts for Irregular Worker" |
||||
|
> |
||||
|
<p class="text-center"> |
||||
|
Help text or information text. |
||||
|
</p> |
||||
|
</template> |
||||
|
|
||||
|
|
||||
|
<!-- Public Available Tasks Templates for Regular Workers --> |
||||
|
<template |
||||
|
id="public_shift_template_regular_worker" |
||||
|
name="Available Tasks Templates for Regular Workers" |
||||
|
> |
||||
|
<t t-call="portal.portal_layout"> |
||||
|
<t t-set="no_breadcrumbs" t-value="True"/> |
||||
|
|
||||
|
<div class="o_regular_shift_template"> |
||||
|
|
||||
|
<div class="container mt32"> |
||||
|
<div class="row mt4"> |
||||
|
<div class="col"> |
||||
|
<h1 class="text-center"> |
||||
|
Available Tasks Templates for Regular Workers |
||||
|
</h1> |
||||
|
</div> |
||||
</div> |
</div> |
||||
<div class="label label-default pull-right" |
|
||||
t-if="template.remaining_worker == 0"> |
|
||||
full |
|
||||
|
|
||||
|
<div class="row mt4"> |
||||
|
<div class="col"> |
||||
|
<t t-call="beesdoo_website_shift.help_text_public_shift_template_regular_worker"/> |
||||
|
</div> |
||||
</div> |
</div> |
||||
</div> |
|
||||
<div class="panel-body clearfix"> |
|
||||
<t t-esc="template.task_type_id.name"/> |
|
||||
<div class="label label-warning pull-right" |
|
||||
t-if="not template.super_coop_id"> |
|
||||
Need Super Co-operator |
|
||||
|
</div> |
||||
|
|
||||
|
<div class="container mb64"> |
||||
|
<div class="row mt4 justify-content-center"> |
||||
|
<div class="col-12 col-lg-6"> |
||||
|
|
||||
|
<t t-foreach="task_tpls_data" t-as="template_data"> |
||||
|
<t t-set="template" t-value="template_data[0]"/> |
||||
|
<t t-set="has_enough_workers" t-value="template_data[1]"/> |
||||
|
<t t-set="highlight_header_class" t-value="'bg-warning' if not has_enough_workers else ''"/> |
||||
|
<t t-set="highlight_class" t-value="'border-warning' if not has_enough_workers else ''"/> |
||||
|
<div t-att-class="'card mt-4 %s' % highlight_class"> |
||||
|
<div t-att-class="'card-header %s clearfix' % highlight_header_class"> |
||||
|
<div class="pull-left"> |
||||
|
<t t-esc="template.planning_id.name"/> : |
||||
|
<t t-esc="template.day_nb_id.name"/> |
||||
|
<t t-esc='float_to_time(template.start_time)' /> - |
||||
|
<t t-esc='float_to_time(template.end_time)'/> |
||||
|
</div> |
||||
|
<div class="badge badge-secondary pull-right" |
||||
|
t-if="template.remaining_worker > 0"> |
||||
|
<t t-esc="template.remaining_worker"/> space(s) |
||||
|
</div> |
||||
|
<div class="badge badge-secondary pull-right" |
||||
|
t-if="template.remaining_worker == 0"> |
||||
|
full |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="card-body clearfix"> |
||||
|
<t t-esc="template.task_type_id.name"/> |
||||
|
<div class="badge badge-warning pull-right" |
||||
|
t-if="not template.super_coop_id"> |
||||
|
Need Super Co-operator |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</t> |
||||
|
|
||||
|
</div> |
||||
</div> |
</div> |
||||
</div> |
|
||||
</div> |
</div> |
||||
</div> |
|
||||
|
|
||||
<table class="hidden-xs table table-striped"> |
|
||||
<thead> |
|
||||
<tr> |
|
||||
<th>Week</th> |
|
||||
<th>Day</th> |
|
||||
<th>Time</th> |
|
||||
<th>Type of Task</th> |
|
||||
<th>Super Co-operator</th> |
|
||||
<th class="text-center">Available Spaces</th> |
|
||||
</tr> |
|
||||
</thead> |
|
||||
<tbody> |
|
||||
<t t-foreach="task_tpls_data" t-as="template_data"> |
|
||||
<t t-set="template" t-value="template_data[0]"/> |
|
||||
<t t-set="has_enough_workers" t-value="template_data[1]"/> |
|
||||
<!-- Row with no super coop will be shown in color --> |
|
||||
<tr t-attf-class="{{ 'warning' if not has_enough_workers else '' }}"> |
|
||||
<td> |
|
||||
<t t-esc="template.planning_id.name"/> |
|
||||
</td> |
|
||||
<td> |
|
||||
<t t-esc="template.day_nb_id.name"/> |
|
||||
</td> |
|
||||
<td> |
|
||||
<t t-esc='float_to_time(template.start_time)' /> - |
|
||||
<t t-esc='float_to_time(template.end_time)'/> |
|
||||
</td> |
|
||||
<td> |
|
||||
<t t-esc="template.task_type_id.name"/> |
|
||||
</td> |
|
||||
<td> |
|
||||
<t t-if="template.super_coop_id"> |
|
||||
Yes |
|
||||
</t> |
|
||||
<t t-if="not template.super_coop_id"> |
|
||||
Not yet |
|
||||
</t> |
|
||||
</td> |
|
||||
<td class="text-center"> |
|
||||
<t t-esc="template.remaining_worker"/> |
|
||||
</td> |
|
||||
</tr> |
|
||||
</t> |
|
||||
</tbody> |
|
||||
</table> |
|
||||
|
|
||||
</div> <!-- col-md --> |
|
||||
</div> <!-- row --> |
|
||||
</div> <!-- container --> |
|
||||
</section> |
|
||||
|
|
||||
<div class="oe_structure"/> |
|
||||
|
|
||||
</t> |
|
||||
</template> |
|
||||
|
|
||||
|
|
||||
<!-- Public Available Shifts for Irregular Workers --> |
|
||||
<template |
|
||||
id="public_shift_irregular_worker" |
|
||||
name="Available Shifts for Irregular Workers" |
|
||||
page="True"> |
|
||||
<t t-call="website.layout"> |
|
||||
|
|
||||
<div class="oe_structure"/> |
|
||||
|
|
||||
<section class="wrap"> |
|
||||
<div class="container"> |
|
||||
<div class="row"> |
|
||||
<div class="col-md-12"> |
|
||||
<h1 class="text-center"> |
|
||||
Available Shifts for Irregular Workers |
|
||||
</h1> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</section> |
|
||||
|
|
||||
<div class="oe_structure"/> |
|
||||
|
|
||||
<section class="wrap"> |
|
||||
<div class="container"> |
|
||||
<div class="row"> |
|
||||
<div class="col-md-12"> |
|
||||
<p class="text-center"> |
|
||||
Subscribe via <a href="mailto:volant@bees-coop.be">volant@bees-coop.be</a> |
|
||||
</p> |
|
||||
|
|
||||
</div> |
</div> |
||||
</div> |
|
||||
</div> |
|
||||
</section> |
|
||||
|
|
||||
<div class="oe_structure"/> |
|
||||
|
</t> |
||||
|
</template> |
||||
|
|
||||
<section class="wrap"> |
|
||||
<div class="container"> |
|
||||
<div class="row"> |
|
||||
<div class="col-md-12"> |
|
||||
|
|
||||
<t t-call="beesdoo_website_shift.available_shift_irregular_worker"/> |
|
||||
|
<!-- Public Available Shifts for Irregular Workers --> |
||||
|
<template |
||||
|
id="public_shift_irregular_worker" |
||||
|
name="Available Shifts for Irregular Workers" |
||||
|
> |
||||
|
<t t-call="portal.portal_layout"> |
||||
|
<t t-set="no_breadcrumbs" t-value="True"/> |
||||
|
|
||||
</div> <!-- col-md --> |
|
||||
</div> <!-- row --> |
|
||||
</div> <!-- container --> |
|
||||
</section> |
|
||||
|
<div class="o_regular_shift_template"> |
||||
|
|
||||
|
<div class="container mt32"> |
||||
|
<div class="row mt4"> |
||||
|
<div class="col"> |
||||
|
<h1 class="text-center"> |
||||
|
Available Shifts for Irregular Workers |
||||
|
</h1> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
<div class="oe_structure"/> |
|
||||
|
<div class="row mt4"> |
||||
|
<div class="col"> |
||||
|
<t t-call="beesdoo_website_shift.help_text_public_shift_irregular_worker"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="container mb64"> |
||||
|
<div class="row mt4 justify-content-center"> |
||||
|
<div class="col-12 col-lg-6"> |
||||
|
|
||||
|
<t t-call="beesdoo_website_shift.available_shift_irregular_worker"/> |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
</t> |
|
||||
</template> |
|
||||
|
</t> |
||||
|
</template> |
||||
|
|
||||
</openerp> |
|
||||
|
</odoo> |
@ -0,0 +1 @@ |
|||||
|
from . import models |
@ -0,0 +1,29 @@ |
|||||
|
{ |
||||
|
'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': [ |
||||
|
"demo/cooperators.xml", |
||||
|
"demo/workers.xml", |
||||
|
"demo/tasks.xml", |
||||
|
] |
||||
|
} |
@ -0,0 +1,30 @@ |
|||||
|
<?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="beesdoo_base.res_partner_cooperator_1_demo" model="res.partner"> |
||||
|
<field name="cooperator_type">share_a</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_base.res_partner_cooperator_2_demo" model="res.partner"> |
||||
|
<field name="cooperator_type">share_a</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_base.res_partner_cooperator_3_demo" model="res.partner"> |
||||
|
<field name="cooperator_type">share_a</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_base.res_partner_cooperator_4_demo" model="res.partner"> |
||||
|
<field name="cooperator_type">share_a</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_base.res_partner_cooperator_5_demo" model="res.partner"> |
||||
|
<field name="cooperator_type">share_a</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_base.res_partner_cooperator_6_demo" model="res.partner"> |
||||
|
<field name="cooperator_type">share_a</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,46 @@ |
|||||
|
<?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="beesdoo_shift_task_template_1_demo" model="beesdoo.shift.template"> |
||||
|
<field name="name">A_LUN_7:00-9:30</field> |
||||
|
<field name="planning_id" ref="beesdoo_shift.beesdoo_shift_planning_1_demo" /> |
||||
|
<field name="day_nb_id" ref="beesdoo_shift.beesdoo_shift_daynumber_1_demo" /> |
||||
|
<field name="task_type_id" ref="beesdoo_shift.beesdoo_shift_task_type_3_demo" /> |
||||
|
<field name="start_time">7</field> |
||||
|
<field name="end_time">9.5</field> |
||||
|
<field name="duration">2.5</field> |
||||
|
<field name="worker_nb">12</field> |
||||
|
<field name="worker_ids" eval="[(6, 0, [ref('beesdoo_base.res_partner_cooperator_1_demo')])]"/> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_task_template_2_demo" model="beesdoo.shift.template"> |
||||
|
<field name="name">A_MAR_12:00-14:30</field> |
||||
|
<field name="planning_id" ref="beesdoo_shift.beesdoo_shift_planning_1_demo" /> |
||||
|
<field name="day_nb_id" ref="beesdoo_shift.beesdoo_shift_daynumber_2_demo" /> |
||||
|
<field name="task_type_id" ref="beesdoo_shift.beesdoo_shift_task_type_2_demo" /> |
||||
|
<field name="start_time">12</field> |
||||
|
<field name="end_time">14.5</field> |
||||
|
<field name="duration">2.5</field> |
||||
|
<field name="worker_nb">9</field> |
||||
|
<!-- WARNING: issue, worker not registered in demo --> |
||||
|
<field name="worker_ids" eval="[(6, 0, [ref('beesdoo_base.res_partner_cooperator_3_demo')])]"/> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_task_template_3_demo" model="beesdoo.shift.template"> |
||||
|
<field name="name">A_VEN_7:00-9:30</field> |
||||
|
<field name="planning_id" ref="beesdoo_shift.beesdoo_shift_planning_1_demo" /> |
||||
|
<field name="day_nb_id" ref="beesdoo_shift.beesdoo_shift_daynumber_5_demo" /> |
||||
|
<field name="task_type_id" ref="beesdoo_shift.beesdoo_shift_task_type_1_demo" /> |
||||
|
<field name="start_time">7</field> |
||||
|
<field name="end_time">9.5</field> |
||||
|
<field name="duration">2.5</field> |
||||
|
<field name="worker_nb">7</field> |
||||
|
<!-- WARNING: issue, worker not registered in demo --> |
||||
|
<field name="worker_ids" eval="[(6, 0, [ref('beesdoo_base.res_partner_cooperator_6_demo')])]"/> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,77 @@ |
|||||
|
<?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="beesdoo_shift_cooperative_status_1_demo" model="cooperative.status"> |
||||
|
<field name="cooperator_id" ref="beesdoo_base.res_partner_cooperator_1_demo" /> |
||||
|
<field name="info_session" eval="True" /> |
||||
|
<field name="info_session_date" eval="datetime.now() - timedelta(days=58)" /> |
||||
|
<field name="super" eval="True" /> |
||||
|
<field name="sr">2</field> |
||||
|
<field name="working_mode">regular</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_base.res_partner_cooperator_1_demo" model="res.partner"> |
||||
|
<field name="working_mode">regular</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_cooperative_status_2_demo" model="cooperative.status"> |
||||
|
<field name="cooperator_id" ref="beesdoo_base.res_partner_cooperator_2_demo" /> |
||||
|
<field name="info_session" eval="False" /> |
||||
|
<field name="sr">2</field> |
||||
|
<field name="working_mode">irregular</field> |
||||
|
<field name="irregular_start_date" eval="datetime.now() - timedelta(days=3)" /> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_base.res_partner_cooperator_2_demo" model="res.partner"> |
||||
|
<field name="working_mode">irregular</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_cooperative_status_3_demo" model="cooperative.status"> |
||||
|
<field name="cooperator_id" ref="beesdoo_base.res_partner_cooperator_3_demo" /> |
||||
|
<field name="info_session" eval="True" /> |
||||
|
<field name="info_session_date" eval="datetime.now() - timedelta(days=98)" /> |
||||
|
<field name="sc">2</field> |
||||
|
<field name="working_mode">regular</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_base.res_partner_cooperator_3_demo" model="res.partner"> |
||||
|
<field name="working_mode">regular</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_cooperative_status_4_demo" model="cooperative.status"> |
||||
|
<field name="cooperator_id" ref="beesdoo_base.res_partner_cooperator_4_demo" /> |
||||
|
<field name="sr">2</field> |
||||
|
<field name="working_mode">irregular</field> |
||||
|
<field name="irregular_start_date" eval="datetime.now() - timedelta(days=6)" /> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_base.res_partner_cooperator_4_demo" model="res.partner"> |
||||
|
<field name="working_mode">irregular</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_cooperative_status_5_demo" model="cooperative.status"> |
||||
|
<field name="cooperator_id" ref="beesdoo_base.res_partner_cooperator_5_demo" /> |
||||
|
<field name="sr">2</field> |
||||
|
<field name="working_mode">regular</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_base.res_partner_cooperator_5_demo" model="res.partner"> |
||||
|
<field name="working_mode">regular</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_shift_cooperative_status_6_demo" model="cooperative.status"> |
||||
|
<field name="cooperator_id" ref="beesdoo_base.res_partner_cooperator_6_demo" /> |
||||
|
<field name="info_session" eval="True" /> |
||||
|
<field name="info_session_date" eval="datetime.now() - timedelta(days=36)" /> |
||||
|
<field name="sc">2</field> |
||||
|
<field name="working_mode">regular</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="beesdoo_base.res_partner_cooperator_6_demo" model="res.partner"> |
||||
|
<field name="working_mode">regular</field> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,2 @@ |
|||||
|
from . import cooperative_status |
||||
|
from . import task |
@ -0,0 +1,284 @@ |
|||||
|
from odoo import models, fields, api, _ |
||||
|
from odoo.addons.beesdoo_shift.models.cooperative_status import add_days_delta |
||||
|
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 = 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 |
||||
|
if ( |
||||
|
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 |
||||
|
# Otherwise |
||||
|
date = add_days_delta(date, 1) |
||||
|
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 |
||||
|
if ( |
||||
|
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 |
||||
|
# Otherwise |
||||
|
next_countdown_date = date |
||||
|
rec.next_countdown_date = next_countdown_date |
||||
|
|
||||
|
##################################### |
||||
|
# Status Change implementation # |
||||
|
##################################### |
||||
|
def _get_regular_status(self): |
||||
|
self.ensure_one() |
||||
|
counter_unsubscribe = int(self.env['ir.config_parameter'].sudo().get_param('regular_counter_to_unsubscribe', -4)) |
||||
|
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)) |
||||
|
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 _get_irregular_status(self): |
||||
|
self.ensure_one() |
||||
|
counter_unsubscribe = int(self.env['ir.config_parameter'].sudo().get_param('irregular_counter_to_unsubscribe', -3)) |
||||
|
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)) |
||||
|
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, **kwargs): |
||||
|
today = kwargs.get("today") or self.today |
||||
|
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 % self._period: |
||||
|
return today |
||||
|
return add_days_delta(today, self._period - (delta % self._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", readonly=True, related="") |
||||
|
|
||||
|
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(self, state): |
||||
|
return { |
||||
|
"draft": 0, |
||||
|
"open": 1, |
||||
|
"done": 5, |
||||
|
"absent_2": 2, |
||||
|
"absent_1": 7, |
||||
|
"absent_0": 3, |
||||
|
"cancel": 9, |
||||
|
}[state] |
||||
|
|
||||
|
def _get_final_state(self): |
||||
|
return ["done", "absent_2", "absent_1", "absent_0"] |
||||
|
|
||||
|
state = fields.Selection(selection=_get_selection_status) |
||||
|
|
||||
|
############################################## |
||||
|
# 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, status |
@ -0,0 +1 @@ |
|||||
|
from . import test_beesdoo_shift |
@ -0,0 +1,228 @@ |
|||||
|
# 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.current_time = datetime.now() |
||||
|
self.user_admin = self.env.ref("base.user_root") |
||||
|
|
||||
|
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_worker_status.beesdoo_shift_task_template_1_demo" |
||||
|
) |
||||
|
self.task_template_2 = self.env.ref( |
||||
|
"beesdoo_worker_status.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, |
||||
|
} |
||||
|
) |
||||
|
self.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) |
@ -0,0 +1 @@ |
|||||
|
from . import models |
@ -0,0 +1,23 @@ |
|||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
{ |
||||
|
'name': "Macavrac Base Module", |
||||
|
|
||||
|
'summary': """ |
||||
|
Module with basic customizations for the Macavrac cooperative. |
||||
|
""", |
||||
|
|
||||
|
'description': """ |
||||
|
""", |
||||
|
|
||||
|
'author': "Patricia Daloze", |
||||
|
|
||||
|
'category': 'Sales', |
||||
|
'version': '12.0.1.0.0', |
||||
|
|
||||
|
'depends': ['beesdoo_shift', 'contacts'], |
||||
|
|
||||
|
'data': [ |
||||
|
'views/res_partner.xml', |
||||
|
], |
||||
|
'installable': True, |
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
from . import res_partner |
@ -0,0 +1,49 @@ |
|||||
|
from odoo import models, fields, api, _ |
||||
|
from odoo.exceptions import ValidationError |
||||
|
|
||||
|
class Partner(models.Model): |
||||
|
|
||||
|
_inherit = 'res.partner' |
||||
|
|
||||
|
date_stamp = fields.Date(string="Timestamp", help="Date de remplissage du formulaire") |
||||
|
birthdate = fields.Date(string="Date d'anniversaire") |
||||
|
payment_date = fields.Date(string="Date de paiement") |
||||
|
certificate_sent_date = fields.Date(string="Certificat envoyé le") |
||||
|
fiscal_certificate_sent_date = fields.Date(string="Attestation fiscale envoyée le") |
||||
|
|
||||
|
coop_number = fields.Integer(string="Coop N°") |
||||
|
share_qty = fields.Integer(string="Nombre de part") |
||||
|
|
||||
|
share_amount = fields.Float(string="Montant", compute="_compute_share_amount") |
||||
|
|
||||
|
gender = fields.Selection([('female','Féminin'),('male','Masculin'),('other','Autre')], string="Genre") |
||||
|
cooperator_type = fields.Selection([('share_a', 'Part A'), ('share_b', 'Part B'), ('share_c', 'Part C'), ('share_d', 'Part D')], string="Type de Part") |
||||
|
state_request = fields.Selection([('ok',"En ordre"),('waiting_payment','En attente de paiement'), |
||||
|
('certificate_to_send', 'Certificat à envoyer'), ('resigning', 'Parts revendues')]) #TODO should we use the cooperative.status model instead? |
||||
|
|
||||
|
national_register_number = fields.Char(string="Numéro de registre national") #TODO add constraint / check consistancy |
||||
|
share_numbers = fields.Char(string="Numéro de parts") |
||||
|
payment_details = fields.Char(string="Détail de paiement") |
||||
|
iban = fields.Char(string="IBAN") #TODO remove. Temp for import purpose. |
||||
|
comment_request = fields.Char(string="Commentaire") |
||||
|
|
||||
|
email_sent = fields.Boolean(string="Email envoyé") |
||||
|
is_worker = fields.Boolean(compute="_compute_is_worker", search="_search_is_worker", string="is Worker", readonly=True, related="") |
||||
|
|
||||
|
|
||||
|
@api.depends('share_qty') |
||||
|
def _compute_share_amount(self): |
||||
|
for rec in self: |
||||
|
rec.share_amount = rec.share_qty * 25.0 #TODO add ir.config_parameter to make this amount editable |
||||
|
|
||||
|
|
||||
|
@api.depends('cooperator_type') |
||||
|
def _compute_is_worker(self): |
||||
|
for rec in self: |
||||
|
rec.is_worker = rec.cooperator_type == 'share_b' |
||||
|
|
||||
|
def _search_is_worker(self, operator, value): |
||||
|
if (operator == '=' and value) or (operator == '!=' and not value): |
||||
|
return [('cooperator_type', '=', 'share_b')] |
||||
|
else: |
||||
|
return [('cooperator_type', '!=', 'share_b')] |
@ -0,0 +1,35 @@ |
|||||
|
<odoo> |
||||
|
<record model="ir.ui.view" id="macavrac_coop_partner_inherited_view_form"> |
||||
|
<field name="name">Macavrac Coop</field> |
||||
|
<field name="model">res.partner</field> |
||||
|
<field name="inherit_id" ref="base.view_partner_form"/> |
||||
|
<field name="priority">99</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<xpath expr="//notebook/page[1]" position="attributes"> |
||||
|
<attribute name="autofocus">0</attribute> |
||||
|
</xpath> |
||||
|
<xpath expr="//notebook" position="inside"> |
||||
|
<page string="Google Sheet Infos" name="google_sheet" autofocus="autofocus"> |
||||
|
<group> |
||||
|
<field name="date_stamp"/> |
||||
|
<field name="coop_number"/> |
||||
|
<field name="gender"/> |
||||
|
<field name="birthdate"/> |
||||
|
<field name="national_register_number"/> |
||||
|
<field name="cooperator_type"/> |
||||
|
<field name="share_qty"/> |
||||
|
<field name="share_numbers"/> |
||||
|
<field name="share_amount"/> |
||||
|
<field name="state_request"/> |
||||
|
<field name="email_sent"/> |
||||
|
<field name="payment_date"/> |
||||
|
<field name="payment_details"/> |
||||
|
<field name="certificate_sent_date"/> |
||||
|
<field name="comment_request"/> |
||||
|
<field name="fiscal_certificate_sent_date"/> |
||||
|
</group> |
||||
|
</page> |
||||
|
</xpath> |
||||
|
</field> |
||||
|
</record> |
||||
|
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue