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
-
399beesdoo_shift/models/cooperative_status.py
-
53beesdoo_shift/models/planning.py
-
243beesdoo_shift/models/task.py
-
21beesdoo_shift/security/group.xml
-
35beesdoo_shift/security/ir.model.access.csv
-
47beesdoo_shift/views/cooperative_status.xml
-
6beesdoo_shift/views/exempt_reason.xml
-
62beesdoo_shift/views/menu.xml
-
39beesdoo_shift/views/planning.xml
-
96beesdoo_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
-
639beesdoo_website_shift/views/my_shift_website_templates.xml
-
132beesdoo_website_shift/views/res_config_views.xml
-
192beesdoo_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 -*- |
|||
import planning |
|||
import task |
|||
import cooperative_status |
|||
from . import task |
|||
from . import planning |
|||
from . import cooperative_status |
@ -1,23 +1,28 @@ |
|||
<odoo> |
|||
<data noupdate="0"> |
|||
<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"/> |
|||
</record> |
|||
<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="implied_ids" eval="[(4, ref('group_shift_attendance'))]"/> |
|||
</record> |
|||
<record id="group_planning_management" model="res.groups"> |
|||
<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 id="group_cooperative_admin" model="res.groups"> |
|||
<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> |
|||
</data> |
|||
</odoo> |
@ -1,20 +1,17 @@ |
|||
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 |
@ -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). |
|||
|
|||
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): |
|||
_name = 'beesdoo.website.shift.config.settings' |
|||
_inherit = 'res.config.settings' |
|||
|
|||
# Irregular worker settings |
|||
irregular_shift_limit = fields.Integer( |
|||
help="Maximum shift that will be shown" |
|||
related='website_id.irregular_shift_limit', |
|||
readonly=False, |
|||
) |
|||
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( |
|||
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( |
|||
help="Enable shift sign up for irregular worker" |
|||
related='website_id.irregular_enable_sign_up', |
|||
readonly=False, |
|||
) |
|||
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_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( |
|||
help="Maximun number of next shift that will be shown" |
|||
related='website_id.regular_next_shift_limit', |
|||
readonly=False, |
|||
) |
|||
regular_highlight_rule = fields.Integer( |
|||
help="Treshold (in %) of available space in a shift that trigger the " |
|||
"the highlight of a shift template." |
|||
) |
|||
|
|||
@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') |
|||
related='website_id.regular_highlight_rule', |
|||
readonly=False, |
|||
) |
|||
} |
@ -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." |
|||
) |
639
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
@ -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