Skip to content

Commit

Permalink
Merge pull request #1 from miikanissi/HR-Kanban-Attendance-Initial-Ve…
Browse files Browse the repository at this point in the history
…rsion

[ADD] hr_attendance_kanban
  • Loading branch information
dbruehlmeier authored Jan 11, 2024
2 parents 2cf303c + 331b647 commit 1510f2f
Show file tree
Hide file tree
Showing 31 changed files with 1,556 additions and 191 deletions.
96 changes: 96 additions & 0 deletions hr_attendance_kanban/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
====================
HR Kanban Attendance
====================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:01df6b36e07ee9ce5048dace28f2c62aecea5768d4abd4b1972dd793e1b296d0
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fhr--attendance-lightgray.png?logo=github
:target: https://github.com/OCA/hr-attendance/tree/16.0/hr_attendance_kanban
:alt: OCA/hr-attendance
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/hr-attendance-16-0/hr-attendance-16-0-hr_attendance_kanban
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/hr-attendance&target_branch=16.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module allows employees to check-in or check-out by moving their avatar on a kanban board.

The kanban board also shows a quick overview of all employees, grouped by attendance type,
such as:

* 👋 Absent
* 🏢 Office
* 🏠 Home-Office
* 🚋 Travelling


**Table of contents**

.. contents::
:local:

Usage
=====

#. Go to *Attendances > Configuration > Attendance Types*
#. Adjust the attendance types to your needs
#. The employees can use *Attendances > Employees Kanban*

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/hr-attendance/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/hr-attendance/issues/new?body=module:%20hr_attendance_kanban%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
~~~~~~~

* Sozialinfo

Contributors
~~~~~~~~~~~~

* `Sozialinfo <https://sozialinfo.ch>`_:

* David Brühlmeier <[email protected]>

* Miika Nissi <[email protected]>

Maintainers
~~~~~~~~~~~

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

This module is part of the `OCA/hr-attendance <https://github.com/OCA/hr-attendance/tree/16.0/hr_attendance_kanban>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
3 changes: 2 additions & 1 deletion hr_attendance_kanban/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from . import models
from . import models, wizard
from .hooks import post_init_hook
20 changes: 14 additions & 6 deletions hr_attendance_kanban/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
# Copyright 2023 Odoo S.A.
# Copyright 2023 Verein sozialinfo.ch
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html

{
"name": "HR Kanban Attendance",
"version": "16.0.0.0.0",
"version": "16.0.1.0.0",
"category": "Human Resources",
"website": "https://github.com/sozialinfo/hr-attendance",
"author": "Odoo S.A., Sozialinfo, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/hr-attendance",
"author": "Sozialinfo, Odoo Community Association (OCA)",
"license": "LGPL-3",
"installable": True,
"depends": ["hr_attendance"],
Expand All @@ -17,6 +16,15 @@
"views/hr_attendance_view.xml",
"views/hr_attendance_type_view.xml",
"views/hr_attendance_kanban_view.xml",
"security/ir.model.access.csv"
]
"wizard/hr_attendance_kanban_wizard_views.xml",
"security/ir.model.access.csv",
],
"assets": {
"web.assets_backend": [
"hr_attendance_kanban/static/src/views/**/*.js",
"hr_attendance_kanban/static/src/views/**/*.xml",
"hr_attendance_kanban/static/src/scss/hr_attendance_kanban.scss",
]
},
"post_init_hook": "post_init_hook",
}
13 changes: 13 additions & 0 deletions hr_attendance_kanban/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2023 Verein sozialinfo.ch
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html

from odoo import SUPERUSER_ID, api


def post_init_hook(cr, registry):
env = api.Environment(cr, SUPERUSER_ID, {})
# Recompute employee attendance type post install to ensure absent employees get
# the default absent type
env.add_to_compute(
env["hr.employee"]._fields["attendance_type_id"], env["hr.employee"].search([])
)
4 changes: 1 addition & 3 deletions hr_attendance_kanban/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
from . import hr_attendance_type
from . import hr_attendance
from . import hr_employee
from . import hr_attendance, hr_attendance_type, hr_employee, hr_employee_public
23 changes: 12 additions & 11 deletions hr_attendance_kanban/models/hr_attendance.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
# Copyright 2017 Odoo S.A.
# Copyright 2023 Verein sozialinfo.ch
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html

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


class HrAttendance(models.Model):
_inherit = "hr.attendance"

attendance_type_id = fields.Many2one('hr.attendance.type', string='Attendance Type')
comment = fields.Char(string="Comment")

def write(self, vals):
if 'attendance_type_id' in vals:
pass
return super(HrAttendance, self).write(vals)
attendance_type_id = fields.Many2one(
"hr.attendance.type",
string="Attendance Type",
ondelete="restrict",
help="Represents the type of employee attendance",
group_expand="_read_group_attendance_type_ids",
)
comment = fields.Char(help="Optional comment for attendance")

@api.model
def _read_group_attendance_type_ids(self, stages, domain, order):
return self.env["hr.attendance.type"].search([])
73 changes: 33 additions & 40 deletions hr_attendance_kanban/models/hr_attendance_type.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# Copyright 2023 Odoo S.A.
# Copyright 2023 ForgeFlow, S.L.
# Copyright 2023 Verein sozialinfo.ch
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html

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


Expand All @@ -19,48 +18,42 @@ class HrAttendanceType(models.Model):
index=True,
)
absent = fields.Boolean(
string="Absent",
help="Defines that the Attendance Type represents the employee being absent. Only one Attendance Type can represent 'absent' and exactly one Attendance Type needs to be defined as 'absent'",
copy=False
help=(
"Defines that the Attendance Type represents the employee being absent."
" Only one Attendance Type can represent 'absent' and exactly one"
" Attendance Type needs to be defined as 'absent'"
),
copy=False,
)
fold = fields.Boolean(string="Folded in Kanban")

@api.onchange('absent')
def _check_only_one_absent(self):

# Get all attendance types which are defined as 'absent'
absent_attendance_types = self.env['hr.attendance.type'].search(
[('absent', '=', True)],
order = 'sequence'
@api.constrains("absent")
def _constrains_only_one_absent(self):
for attendance_type in self:
other_absent_attendance_types = self.env["hr.attendance.type"].search(
[("id", "!=", attendance_type.id), ("absent", "=", True)]
)

if len(absent_attendance_types) == 1:

if absent_attendance_types.id == self.id.origin:
# This was the only attendance type defined as 'absent' and the user
# wants to uncheck it. That's a no-go.
raise UserError("You cannot uncheck this Attendance Type as 'absent'. There needs to be exactly one Attendance Type with 'absent' checked, because it is used to represents absent employees. You can however mark another Attendance Type as 'absent' and then the flag for this record will automatically be removed.")
else:
# There was already _another_ attendance type defined as 'absent'.
# Uncheck that one: there may only ever be one.
absent_attendance_types.write({'absent': False})

if len(absent_attendance_types) > 1:
# This should never happen... but just in case.
# If there is more than one attendance type as 'absent', just mark the first one.
absent_attendance_types.write({'absent': False})
absent_attendance_types[0].write({'absent': True})
if attendance_type.absent and other_absent_attendance_types:
other_absent_attendance_types.absent = False
elif not attendance_type.absent and not other_absent_attendance_types:
raise UserError(
_(
"You cannot uncheck this Attendance Type as 'absent'. There"
" needs to be exactly one Attendance Type with 'absent'"
" checked, because it is used to represents absent"
" employees. You can however mark another Attendance Type"
" as 'absent' and then the flag for this record will"
" automatically be removed."
)
)

@api.ondelete(at_uninstall=False)
def _unlink_except_absent(self):
if any(attendance_type.absent for attendance_type in self):
raise UserError("You cannot delete the Attendance Type marked as 'absent'. There needs to be exactly one Attendance Type with 'absent' checked, because it is used to represents absent employees.")

@api.ondelete(at_uninstall=False)
def _unlink_except_used(self):
for attendance_type in self:
used_attendance_types = self.env['hr.attendance'].search_count(
[('attendance_type_id', '=', attendance_type.id)]
raise UserError(
_(
"You cannot delete the Attendance Type marked as 'absent'. There"
" needs to be exactly one Attendance Type with 'absent' checked,"
" because it is used to represents absent employees."
)
if used_attendance_types > 0:
raise UserError("You cannot delete this Attendance Type because it was already used.")

)
73 changes: 46 additions & 27 deletions hr_attendance_kanban/models/hr_employee.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,60 @@
# Copyright 2017 Odoo S.A.
# Copyright 2023 Verein sozialinfo.ch
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html

from odoo import fields, models, api
from odoo import api, fields, models


class HrEmployee(models.Model):
_inherit = "hr.employee"

attendance_type_id = fields.Many2one('hr.attendance.type', string='Attendance Type', group_expand='_read_attendance_type_ids', compute='_compute_hr_attendance_type', store=True)

@api.depends_context('hr.attendance')
def _compute_hr_attendance_type(self):

attendance_type_id = fields.Many2one(
"hr.attendance.type",
string="Attendance Type",
group_expand="_read_attendance_type_ids",
compute="_compute_attendance_type_id",
inverse="_inverse_attendance_type_id",
store=True,
readonly=False,
groups="hr_attendance.group_hr_attendance_kiosk,hr_attendance.group_hr_attendance,hr.group_hr_user", # noqa:B950
)
last_attendance_comment = fields.Char(
related="last_attendance_id.comment",
store=True,
groups="hr_attendance.group_hr_attendance_user,hr.group_hr_user",
)

@api.depends(
"last_attendance_id.check_in",
"last_attendance_id.check_out",
"last_attendance_id",
)
def _compute_attendance_type_id(self):
"""Gets the employee attendance type from the last attendance if it's not yet
checked out,otherwise employee is absent."""
absent_att_type = self.env["hr.attendance.type"].search(
[("absent", "=", True)], limit=1
)
for employee in self:

# Get the current check-in for the employee. There should always only be one record where the check-out date is empty (Odoo standard)
latest_attendance = self.env['hr.attendance'].search(
[('employee_id', '=', employee.id),
('check_out', '=', False)],
limit=1
att = employee.last_attendance_id.sudo()
employee.attendance_type_id = (
employee.last_attendance_id.attendance_type_id
if att and not att.check_out
else absent_att_type
)

if latest_attendance:
# A record was found. This means the employee is present. Return the attendance type.
employee.attendance_type_id = latest_attendance.attendance_type_id
else:
# No record was found. This means the employee is absent. Return the attendance type which is marked as 'absent'
absent_attendance_type_id = self.env['hr.attendance.type'].search(
[('absent', '=', True)],
limit=1
)
employee.attendance_type_id = absent_attendance_type_id

def _inverse_attendance_type_id(self):
"""Sets the attendance type of the current attendance if checked in."""
for employee in self:
if (
employee.attendance_type_id
and not employee.attendance_type_id.absent
and employee.attendance_state == "checked_in"
):
employee.last_attendance_id.attendance_type_id = (
employee.attendance_type_id
)

# Make sure all attendance types are displayed in the kanban board
@api.model
def _read_attendance_type_ids(self,stages,domain,order):
attendance_type_ids = self.env['hr.attendance.type'].search([])
def _read_attendance_type_ids(self, stages, domain, order):
attendance_type_ids = self.env["hr.attendance.type"].search([])
return attendance_type_ids
Loading

0 comments on commit 1510f2f

Please sign in to comment.