Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ADD] time_tracking_kiosk: record working hours for projects and tasks from kiosk #645

Draft
wants to merge 1 commit into
base: 18.0
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions time_tracking_kiosk/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import Command
from . import models
from . import controllers
from . import tests

def set_menu_visibility_for_kiosk(env):
"""Set visibility of menus to show only kiosk app for internal users."""
menus = env['ir.ui.menu'].search([])
internal_user_group = env.ref('base.group_user')
system_admin_group = env.ref('base.group_system')
kiosk_root_menu = env.ref('time_tracking_kiosk.menu_timesheet_kiosk_root')
kiosk_mode_menu = env.ref('time_tracking_kiosk.menu_timesheet_kiosk')

for menu in menus:
if menu.id in [kiosk_root_menu.id, kiosk_mode_menu.id]:
menu.groups_id = [Command.set([internal_user_group.id, system_admin_group.id])]
else:
menu.groups_id = [Command.set([system_admin_group.id])]
39 changes: 39 additions & 0 deletions time_tracking_kiosk/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

{
"name": "Employee Timesheet Kiosk",
"version": "1.0",
"summary": "Record working hours from kiosk mode for projects and tasks",
"category": "Tutorials",
"description": """
This module allows employees to record their working hours via a kiosk interface:
- Scan employee badge to identify user
- Select projects and tasks assigned to the employee
- Start/stop timers to track work accurately
- Configure maximum allowed hours and minimum time entries
- Allow portal users access to timesheets
- Notify project managers via configurable email templates
""",
"author": "nmak",
"depends": ["base", "hr_timesheet", "project", "hr_attendance",],
"data": [
"security/ir.model.access.csv",
"security/ir.rule.xml",
"data/email_template.xml",
"views/timesheet_kiosk_actions.xml",
"views/hr_employee_form_views.xml",
"views/res_config_settings_view.xml",
"views/timesheet_menus.xml",
],
"assets": {
"web.assets_backend": [
"time_tracking_kiosk/static/src/kiosk_main.js",
"time_tracking_kiosk/static/src/scss/style.scss",
"time_tracking_kiosk/static/src/timesheet_kiosk_templates.xml",
],
},
"application": True,
'post_init_hook': 'set_menu_visibility_for_kiosk',
"installable": True,
"license": "LGPL-3",
}
3 changes: 3 additions & 0 deletions time_tracking_kiosk/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from . import timesheet_controller
78 changes: 78 additions & 0 deletions time_tracking_kiosk/controllers/timesheet_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import http, fields
from odoo.http import request


class TimesheetController(http.Controller):

@http.route("/timesheet/create", type="json", auth="user")
def create_timesheet(self, **kwargs):
"""Create a new timesheet entry when the timer starts."""
try:
params = kwargs.get("params", kwargs)
project_id = params.get("project_id")
task_id = params.get("task_id")
employee_id = params.get("employee_id")

new_timesheet = request.env["account.analytic.line"].sudo().create({
"project_id": project_id,
"task_id": task_id,
"employee_id": employee_id,
"name": "Work in Progress",
"unit_amount": 0.0,
"date": fields.Date.today(),
"timer_active": True,
"timer_start_time": fields.Datetime.now(),
})

return {"id": new_timesheet.id, "name": new_timesheet.name}
except Exception as error:
return {"id": False, "error": str(error)}

@http.route("/timesheet/stop", type="json", auth="user")
def stop_timesheet(self, **kwargs):
"""Stop the timer, record hours worked, and notify the project manager via email."""
try:
params = kwargs.get("params", kwargs)
timesheet_id = params.get("timesheet_id")
timesheet = request.env["account.analytic.line"].browse(timesheet_id)

if not timesheet.exists():
return {"id": False, "error": "Timesheet not found"}

if timesheet.employee_id.user_id != request.env.user and not request.env.user._is_admin():
return {"error": "Access denied"}

max_work_hours_per_day = float(
request.env["ir.config_parameter"]
.sudo()
.get_param("time_tracking_kiosk.max_work_hours_per_day", 8)
)

start_time = timesheet.timer_start_time
end_time = fields.Datetime.now()
hours_worked = (end_time - start_time).total_seconds() / 3600
hours_worked = min(hours_worked, max_work_hours_per_day)

timesheet.sudo().write({
"unit_amount": hours_worked,
"name": "Work Done",
"timer_active": False,
})

project_manager = timesheet.task_id.project_id.user_id
if project_manager:
email_template = request.env.ref(
"time_tracking_kiosk.email_template_pm_notification",
raise_if_not_found=True,
)
email_template.sudo().send_mail(timesheet.id, force_send=True)

return {
"id": timesheet.id,
"unit_amount": timesheet.unit_amount,
"name": timesheet.name,
}
except Exception as error:
return {"id": False, "error": str(error)}
22 changes: 22 additions & 0 deletions time_tracking_kiosk/data/email_template.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="email_template_pm_notification" model="mail.template">
<field name="name">Project Manager Timesheet Alert</field>
<field name="model_id" ref="model_account_analytic_line"/>
<field name="email_from">${(object.create_uid.email or '[email protected]')|safe}</field>
<field name="email_to">${(object.task_id.project_id.user_id.partner_id.email or '[email protected]')|safe}</field>
<field name="subject">[Timesheet Alert] Employee Exceeded Work Hours</field>
<field name="body_html"><![CDATA[
<p>Dear ${object.task_id.project_id.user_id.name},</p>
<p>The following employee has recorded work hours:</p>
<ul>
<li><strong>Employee:</strong> ${object.employee_id.name or 'Unknown'}</li>
<li><strong>Worked Hours:</strong> ${object.unit_amount or '0'}</li>
<li><strong>Allowed Hours:</strong> ${object.env['ir.config_parameter'].sudo().get_param('time_tracking_kiosk.max_work_hours_per_day', 8)}</li>
</ul>
<p>Please review the timesheet.</p>
<p>Best regards,</p>
<p>Your HR Team</p>
]]></field>
</record>
</odoo>
5 changes: 5 additions & 0 deletions time_tracking_kiosk/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from . import res_config_settings
from . import project_task
from . import account_analytic_line
11 changes: 11 additions & 0 deletions time_tracking_kiosk/models/account_analytic_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import models, fields, api


class AccountAnalyticLine(models.Model):
_inherit = "account.analytic.line"

timer_active = fields.Boolean()
timer_start_time = fields.Datetime()
max_work_hours_per_day = fields.Float(string='Maximum Work Hours', default=8.0)
17 changes: 17 additions & 0 deletions time_tracking_kiosk/models/project_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import models, fields


class ProjectTask(models.Model):
_inherit = "project.task"

user_ids = fields.Many2many(
comodel_name="res.users",
relation="project_task_user_rel",
column1="task_id",
column2="user_id",
string="Assignees",
tracking=True,
domain=[("share", "=", True), ("active", "=", True)],
)
21 changes: 21 additions & 0 deletions time_tracking_kiosk/models/res_config_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import api, models, fields


class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"

max_work_hours_per_day = fields.Float(
string="Max Work Hours per Day",
config_parameter="time_tracking_kiosk.max_work_hours_per_day",
help="Maximum allowed work hours per day for employees using the kiosk.",
)

pm_notification_template_id = fields.Many2one(
"mail.template",
string="PM Notification Email Template",
domain=[("model", "=", "account.analytic.line")],
config_parameter="time_tracking_kiosk.pm_notification_template_id",
help="Select the email template to notify project managers.",
)
4 changes: 4 additions & 0 deletions time_tracking_kiosk/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_project_task_portal,project.task.portal,project.model_project_task,base.group_portal,1,1,0,0
access_project_project_portal,project.project.portal,project.model_project_project,base.group_portal,1,0,0,0
access_account_analytic_line_portal,account.analytic.line.portal,analytic.model_account_analytic_line,base.group_portal,1,1,1,0
8 changes: 8 additions & 0 deletions time_tracking_kiosk/security/ir.rule.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<odoo>
<record id="rule_project_task_portal_user_assignment" model="ir.rule">
<field name="name">Allow Portal Users to be Assigned to Tasks</field>
<field name="model_id" ref="project.model_project_task"/>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
<field name="domain_force">[('user_ids', 'in', user.id)]</field>
</record>
</odoo>
Binary file added time_tracking_kiosk/static/description/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading