Skip to content

Commit

Permalink
Merge pull request #5 from miikanissi/16.0-hr-kanban-attendance-break…
Browse files Browse the repository at this point in the history
…-time

[IMP] hr_attendance_kanban: Ability to manage breaks
  • Loading branch information
janikvonrotz authored Jul 8, 2024
2 parents 58226c6 + c9cff60 commit 25c1e77
Show file tree
Hide file tree
Showing 17 changed files with 522 additions and 12 deletions.
6 changes: 5 additions & 1 deletion hr_attendance_kanban/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ HR Kanban Attendance
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:e8956851175b45aa9841a7824a0797b40933d6f32eabe174a4bb6ea54ed98804
!! source digest: sha256:da8f298b12a35459d46da47157c9e26e70bc0ae50cfe53fc246e0094cf4f382c
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
Expand Down Expand Up @@ -38,6 +38,10 @@ such as:
* 🏠 Home-Office
* 🚋 Travelling

This module also adds the ability for employees to go on a break by using the kanban board.
Upon returning, they can end the break which will add the spent time to their attendance as
"break time". Once the employee checks out, they will be able to modify the total break time
if needed.

**Table of contents**

Expand Down
3 changes: 2 additions & 1 deletion hr_attendance_kanban/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

{
"name": "HR Kanban Attendance",
"version": "16.0.1.1.0",
"version": "16.0.2.0.0",
"category": "Human Resources",
"website": "https://github.com/OCA/hr-attendance",
"author": "Sozialinfo, Odoo Community Association (OCA), Miika Nissi",
Expand All @@ -18,6 +18,7 @@
"views/hr_attendance_type_view.xml",
"views/hr_attendance_kanban_view.xml",
"wizard/hr_attendance_kanban_wizard_views.xml",
"wizard/hr_attendance_kanban_break_views.xml",
"security/ir.model.access.csv",
],
"assets": {
Expand Down
54 changes: 53 additions & 1 deletion hr_attendance_kanban/models/hr_attendance.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Copyright 2023 Verein sozialinfo.ch
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html

from odoo import api, fields, models
from odoo import _, api, fields, models
from odoo.exceptions import UserError


class HrAttendance(models.Model):
Expand All @@ -16,6 +17,57 @@ class HrAttendance(models.Model):
)
comment = fields.Char(help="Optional comment for attendance")

break_time = fields.Float(help="Duration of the break in hours", default=0.0)
on_break = fields.Datetime(
help=(
"Start time of the break. This is a technical field used to calculate break"
" time."
),
readonly=True,
)

_sql_constraints = [
(
"check_break_time_positive",
"CHECK(break_time >= 0.0)",
"The break time cannot be negative.",
),
]

@api.model
def _read_group_attendance_type_ids(self, stages, domain, order):
return self.env["hr.attendance.type"].search([])

@api.depends("check_in", "check_out", "break_time")
def _compute_worked_hours(self):
attendance_with_break = self.filtered(lambda a: a.break_time > 0)
for attendance in attendance_with_break:
if attendance.check_out and attendance.check_in:
delta = attendance.check_out - attendance.check_in
worked_hours = (delta.total_seconds() / 3600.0) - attendance.break_time
attendance.worked_hours = max(0.0, worked_hours)
else:
attendance.worked_hours = False
return super(HrAttendance, self - attendance_with_break)._compute_worked_hours()

def _action_start_break(self, start_time):
self.write({"on_break": start_time})

def _action_end_break(self, start_time=None, end_time=None):
if not end_time:
self.write({"on_break": False})
return
for attendance in self:
if not attendance.on_break:
raise UserError(_("Employee is not on break."))
if not start_time:
start_time = attendance.on_break
attendance.write(
{
"break_time": (
attendance.break_time
+ (end_time - start_time).total_seconds() / 3600
),
"on_break": False,
}
)
5 changes: 5 additions & 0 deletions hr_attendance_kanban/models/hr_employee.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ class HrEmployee(models.Model):
store=True,
groups="hr_attendance.group_hr_attendance_user,hr.group_hr_user",
)
on_break = fields.Datetime(
related="last_attendance_id.on_break",
store=True,
groups="hr_attendance.group_hr_attendance_user,hr.group_hr_user",
)
is_kanban_attendance = fields.Boolean(
string="Kanban Attendance",
groups="hr_attendance.group_hr_attendance_user,hr.group_hr_user",
Expand Down
26 changes: 26 additions & 0 deletions hr_attendance_kanban/models/hr_employee_public.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright 2023 Verein sozialinfo.ch
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html


from odoo import _, api, fields, models
from odoo.exceptions import AccessError, UserError
from odoo.osv import expression
Expand Down Expand Up @@ -44,6 +45,12 @@ class HrEmployeePublic(models.Model):
help="Technical field to see if the current user is the user of the record.",
)

on_break = fields.Datetime(
related="employee_id.on_break",
readonly=True,
groups="hr_attendance.group_hr_attendance,hr.group_hr_user",
)

@api.depends("employee_id.attendance_type_id")
def _compute_attendance_type_id(self):
"""Gets the public employee attendance type from the employee"""
Expand Down Expand Up @@ -297,6 +304,25 @@ def action_check_in_out_wizard(self, manual_mode=True):
)
return wizard_action

def action_break_wizard(self):
self.ensure_one()
ctx = dict(self.env.context)
wizard_action = self.env["ir.actions.act_window"]._for_xml_id(
"hr_attendance_kanban.hr_attendance_kanban_break_action"
)
if not self:
raise UserError(_("A valid employee must be selected to go on a break."))

self.check_attendance_access()

ctx.update({"default_public_employee_id": self.id})
wizard_action.update(
{
"context": ctx,
}
)
return wizard_action

@api.model
def _read_attendance_type_ids(self, stages, domain, order):
attendance_type_ids = self.env["hr.attendance.type"].search([])
Expand Down
4 changes: 4 additions & 0 deletions hr_attendance_kanban/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ such as:
* 🏠 Home-Office
* 🚋 Travelling

This module also adds the ability for employees to go on a break by using the kanban board.
Upon returning, they can end the break which will add the spent time to their attendance as
"break time". Once the employee checks out, they will be able to modify the total break time
if needed.
1 change: 1 addition & 0 deletions hr_attendance_kanban/security/ir.model.access.csv
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hr_attendance_type_manager,hr.attendance.type.manager,model_hr_attendance_type,hr_attendance.group_hr_attendance_manager,1,1,1,1
access_hr_attendance_type_employee,hr.attendance.type.employee,model_hr_attendance_type,base.group_user,1,0,0,0
access_hr_attendance_kanban_wizard_user,hr.attendance.kanban.wizard.user,model_hr_attendance_kanban_wizard,base.group_user,1,1,1,1
access_hr_attendance_kanban_break_user,hr.attendance.kanban.break.user,model_hr_attendance_kanban_break,base.group_user,1,1,1,1
6 changes: 5 additions & 1 deletion hr_attendance_kanban/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ <h1 class="title">HR Kanban Attendance</h1>
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:e8956851175b45aa9841a7824a0797b40933d6f32eabe174a4bb6ea54ed98804
!! source digest: sha256:da8f298b12a35459d46da47157c9e26e70bc0ae50cfe53fc246e0094cf4f382c
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/hr-attendance/tree/16.0/hr_attendance_kanban"><img alt="OCA/hr-attendance" src="https://img.shields.io/badge/github-OCA%2Fhr--attendance-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/hr-attendance-16-0/hr-attendance-16-0-hr_attendance_kanban"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/hr-attendance&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module allows employees to check-in or check-out by moving their avatar on a kanban board.</p>
Expand All @@ -380,6 +380,10 @@ <h1 class="title">HR Kanban Attendance</h1>
<li>🏠 Home-Office</li>
<li>🚋 Travelling</li>
</ul>
<p>This module also adds the ability for employees to go on a break by using the kanban board.
Upon returning, they can end the break which will add the spent time to their attendance as
“break time”. Once the employee checks out, they will be able to modify the total break time
if needed.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
Expand Down
31 changes: 31 additions & 0 deletions hr_attendance_kanban/static/src/scss/hr_attendance_kanban.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,34 @@
.o_kanban_record > div.o_hr_kanban_own_employee {
border: 2px solid #f06050;
}
.o_kanban_view .o_hr_kanban_record {
.ribbon {
&::before,
&::after {
display: none;
}

span {
padding: 5px;
font-size: x-small;
z-index: unset;
height: auto;
}
}
.ribbon-top-right {
margin-top: -$o-kanban-dashboard-vpadding;

span {
top: 15px;
left: 10px;
}
}
.o_kanban_pause_button {
font-size: 1.5rem;
color: $danger;
}
.o_kanban_play_button {
font-size: 1.5rem;
color: $success;
}
}
81 changes: 80 additions & 1 deletion hr_attendance_kanban/tests/test_hr_attendance_kanban.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright 2023 Verein sozialinfo.ch
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html

from datetime import datetime
from datetime import datetime, timedelta

from odoo.exceptions import AccessError, UserError
from odoo.tests import common, new_test_user, users
Expand Down Expand Up @@ -109,6 +109,13 @@ def test_employee_access_own_only(self):
"start_time": datetime.now().strftime(DF),
}
).action_change()
with self.assertRaises(AccessError):
self.env["hr.attendance.kanban.break"].create(
{
"public_employee_id": public_admin.id,
"start_time": datetime.now().strftime(DF),
}
).action_start_break()

@users("test-user-admin")
def test_attendance_type(self):
Expand Down Expand Up @@ -166,3 +173,75 @@ def test_attendance_manual(self):

# Ensure employee attendance went back to absent
self.assertEqual(self.att_type_absent, public_employee.attendance_type_id)

@users("test-user")
def test_employee_break(self):
"""Test employee user can start and end a break in attendance kanban"""
public_employee = self.env["hr.employee.public"].browse(self.employee.ids)

check_in_time = datetime.now()

# Check in
self.env["hr.attendance.kanban.wizard"].create(
{
"public_employee_id": public_employee.id,
"next_attendance_type_id": self.att_type_office.id,
"start_time": check_in_time.strftime(DF),
"comment": self.att_comment,
}
).action_change()

# Add 10 minutes
break_start_time = check_in_time + timedelta(minutes=10)

# Start break
self.env["hr.attendance.kanban.break"].create(
{
"public_employee_id": public_employee.id,
"start_time": break_start_time.strftime(DF),
}
).action_start_break()

self.assertEqual(
public_employee.on_break, public_employee.last_attendance_id.on_break
)
self.assertEqual(
public_employee.on_break, break_start_time.replace(microsecond=0)
)
self.assertEqual(public_employee.last_attendance_id.break_time, 0.0)

# Add 30 minutes
break_end_time = break_start_time + timedelta(minutes=30)

# End break
self.env["hr.attendance.kanban.break"].create(
{
"public_employee_id": public_employee.id,
"end_time": break_end_time.strftime(DF),
}
).action_end_break()

self.assertEqual(
public_employee.on_break, public_employee.last_attendance_id.on_break
)
self.assertFalse(public_employee.on_break)
self.assertEqual(public_employee.last_attendance_id.break_time, 0.5)

# Add 8 hours since check in
check_out_time = check_in_time + timedelta(hours=8)

# Check out
self.env["hr.attendance.kanban.wizard"].create(
{
"public_employee_id": public_employee.id,
"next_attendance_type_id": self.att_type_absent.id,
"end_time": check_out_time.strftime(DF),
}
).action_change()

self.assertEqual(
public_employee.on_break, public_employee.last_attendance_id.on_break
)
self.assertFalse(public_employee.on_break)
self.assertEqual(public_employee.last_attendance_id.break_time, 0.5)
self.assertEqual(public_employee.last_attendance_id.worked_hours, 7.5)
33 changes: 32 additions & 1 deletion hr_attendance_kanban/views/hr_attendance_kanban_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<field name="last_check_out" />
<field name="last_attendance_comment" />
<field name="attendance_state" />
<field name="on_break" />
<progressbar field="attendance_type_id" colors='{}' />
<templates>
<t t-name="kanban-box">
Expand All @@ -40,6 +41,12 @@
options="{'zoom': true, 'zoom_delay': 1000}"
/>

<div
class="ribbon ribbon-top-right"
attrs="{'invisible': [('on_break', '=', False)]}"
>
<span class="bg-odoo">On Break</span>
</div>
<div class="oe_kanban_details">
<div class="o_kanban_record_top">
<div class="o_kanban_record_headings">
Expand All @@ -62,7 +69,25 @@
><field name="last_attendance_comment" /></li>
</ul>
</div>
<div class="o_kanban_record_bottom" />
<div class="o_kanban_record_bottom">
<div class="oe_kanban_bottom_left" />
<div class="oe_kanban_bottom_right">
<button
t-if="record.attendance_state.raw_value == 'checked_in' and record.on_break.raw_value == false"
class="btn o_kanban_pause_button fa fa-pause-circle"
title="Start Break"
name="action_break_wizard"
type="object"
/>
<button
t-if="record.attendance_state.raw_value == 'checked_in' and record.on_break.raw_value != false"
class="btn o_kanban_play_button fa fa-play-circle"
title="End Break"
name="action_break_wizard"
type="object"
/>
</div>
</div>
</div>
</div>
</t>
Expand All @@ -84,6 +109,12 @@
domain="[('is_cur_user', '=', True)]"
/>
<separator />
<filter
name="filter_on_break"
string="On Break"
domain="[('on_break', '!=', False)]"
/>
<separator />
</xpath>
</field>
</record>
Expand Down
Loading

0 comments on commit 25c1e77

Please sign in to comment.