summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLucas Lefèvre <lul@odoo.com>2019-06-07 08:54:27 +0000
committerYannick Tivisse <yti@odoo.com>2019-06-26 06:52:31 +0000
commitee1776dc5f12fa5055ab0ff0998edbc08da3c8c9 (patch)
tree2f4c81be754385be2feb52d32d2fa9cfdcef1848
parent242be00429a9b1bc128a08808ebc34d9b13e49ce (diff)
[IMP] hr_attendance: Display checked in hours today
Purpose ====== When an empoyee wants to check out, he does not know the number of hours he worked today. Specification ========== In the check out view, display how many hours the employee was checked in today. closes odoo/odoo#33974 Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
-rw-r--r--addons/hr_attendance/models/hr_employee.py24
-rw-r--r--addons/hr_attendance/static/src/js/employee_kanban_view_handler.js1
-rw-r--r--addons/hr_attendance/static/src/js/kiosk_confirm.js2
-rw-r--r--addons/hr_attendance/static/src/js/my_attendances.js6
-rw-r--r--addons/hr_attendance/static/src/xml/attendance.xml2
-rw-r--r--addons/hr_attendance/static/tests/hr_attendance_tests.js1
-rw-r--r--addons/hr_attendance/tests/test_hr_attendance_process.py27
-rw-r--r--addons/hr_attendance/views/hr_employee_view.xml1
8 files changed, 63 insertions, 1 deletions
diff --git a/addons/hr_attendance/models/hr_employee.py b/addons/hr_attendance/models/hr_employee.py
index def4f72c2d4..c526230dc93 100644
--- a/addons/hr_attendance/models/hr_employee.py
+++ b/addons/hr_attendance/models/hr_employee.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import pytz
from datetime import datetime
from dateutil.relativedelta import relativedelta
@@ -14,6 +15,7 @@ class HrEmployeeBase(models.AbstractModel):
last_attendance_id = fields.Many2one('hr.attendance', compute='_compute_last_attendance_id', store=True)
attendance_state = fields.Selection(string="Attendance Status", compute='_compute_attendance_state', selection=[('checked_out', "Checked out"), ('checked_in', "Checked in")])
hours_last_month = fields.Float(compute='_compute_hours_last_month')
+ hours_today = fields.Float(compute='_compute_hours_today')
def _compute_hours_last_month(self):
for employee in self:
@@ -27,6 +29,28 @@ class HrEmployeeBase(models.AbstractModel):
])
employee.hours_last_month = sum(attendances.mapped('worked_hours'))
+ def _compute_hours_today(self):
+ now = fields.Datetime.now()
+ now_utc = pytz.utc.localize(now)
+ for employee in self:
+ # start of day in the employee's timezone might be the previous day in utc
+ tz = pytz.timezone(employee.tz)
+ now_tz = now_utc.astimezone(tz)
+ start_tz = now_tz + relativedelta(hour=0, minute=0) # day start in the employee's timezone
+ start_naive = start_tz.astimezone(pytz.utc).replace(tzinfo=None)
+
+ attendances = self.env['hr.attendance'].search([
+ ('employee_id', '=', employee.id),
+ ('check_in', '<=', now),
+ '|', ('check_out', '>=', start_naive), ('check_out', '=', False),
+ ])
+
+ worked_hours = 0
+ for attendance in attendances:
+ delta = (attendance.check_out or now) - max(attendance.check_in, start_naive)
+ worked_hours += delta.total_seconds() / 3600.0
+ employee.hours_today = worked_hours
+
@api.depends('attendance_ids')
def _compute_last_attendance_id(self):
for employee in self:
diff --git a/addons/hr_attendance/static/src/js/employee_kanban_view_handler.js b/addons/hr_attendance/static/src/js/employee_kanban_view_handler.js
index 9949b9d8db2..eb9133e5d21 100644
--- a/addons/hr_attendance/static/src/js/employee_kanban_view_handler.js
+++ b/addons/hr_attendance/static/src/js/employee_kanban_view_handler.js
@@ -23,6 +23,7 @@ KanbanRecord.include({
employee_id: this.record.id.raw_value,
employee_name: this.record.name.raw_value,
employee_state: this.record.attendance_state.raw_value,
+ employee_hours_today: this.record.hours_today.raw_value,
};
this.do_action(action);
} else {
diff --git a/addons/hr_attendance/static/src/js/kiosk_confirm.js b/addons/hr_attendance/static/src/js/kiosk_confirm.js
index c7640527297..8262b24c8f8 100644
--- a/addons/hr_attendance/static/src/js/kiosk_confirm.js
+++ b/addons/hr_attendance/static/src/js/kiosk_confirm.js
@@ -3,6 +3,7 @@ odoo.define('hr_attendance.kiosk_confirm', function (require) {
var AbstractAction = require('web.AbstractAction');
var core = require('web.core');
+var field_utils = require('web.field_utils');
var QWeb = core.qweb;
@@ -61,6 +62,7 @@ var KioskConfirm = AbstractAction.extend({
this.employee_id = action.employee_id;
this.employee_name = action.employee_name;
this.employee_state = action.employee_state;
+ this.employee_hours_today = field_utils.format.float_time(action.employee_hours_today);
},
start: function () {
diff --git a/addons/hr_attendance/static/src/js/my_attendances.js b/addons/hr_attendance/static/src/js/my_attendances.js
index 5bf9884875d..91acb7f1599 100644
--- a/addons/hr_attendance/static/src/js/my_attendances.js
+++ b/addons/hr_attendance/static/src/js/my_attendances.js
@@ -3,6 +3,7 @@ odoo.define('hr_attendance.my_attendances', function (require) {
var AbstractAction = require('web.AbstractAction');
var core = require('web.core');
+var field_utils = require('web.field_utils');
var MyAttendances = AbstractAction.extend({
@@ -19,10 +20,13 @@ var MyAttendances = AbstractAction.extend({
var def = this._rpc({
model: 'hr.employee',
method: 'search_read',
- args: [[['user_id', '=', this.getSession().uid]], ['attendance_state', 'name']],
+ args: [[['user_id', '=', this.getSession().uid]], ['attendance_state', 'name', 'hours_today']],
})
.then(function (res) {
self.employee = res.length && res[0];
+ if (res.length) {
+ self.hours_today = field_utils.format.float_time(self.employee.hours_today);
+ }
});
return Promise.all([def, this._super.apply(this, arguments)]);
diff --git a/addons/hr_attendance/static/src/xml/attendance.xml b/addons/hr_attendance/static/src/xml/attendance.xml
index 3becfa5cccd..f38dd7cfa7c 100644
--- a/addons/hr_attendance/static/src/xml/attendance.xml
+++ b/addons/hr_attendance/static/src/xml/attendance.xml
@@ -42,6 +42,7 @@
</div>
<h1 class="mb8"><t t-esc="widget.employee.name"/></h1>
<h3 class="mt8 mb24"><t t-if="!checked_in">Welcome!</t><t t-else="">Want to check out?</t></h3>
+ <h4 class="mt0 mb0 text-muted" t-if="checked_in">Today's work hours: <span t-esc="widget.hours_today"/></h4>
<a class="fa fa-7x o_hr_attendance_sign_in_out_icon fa-sign-out btn-warning" t-if="checked_in" aria-label="Sign out" title="Sign out"/>
<a class="fa fa-7x o_hr_attendance_sign_in_out_icon fa-sign-in btn-secondary" t-if="!checked_in" aria-label="Sign in" title="Sign in"/>
<h3 class="mt0 mb0 text-muted">Click to <b t-if="checked_in">check out</b><b t-if="!checked_in">check in</b></h3>
@@ -69,6 +70,7 @@
</div>
<h1 class="mb8"><t t-esc="widget.employee_name"/></h1>
<h3 class="mt8 mb24"><t t-if="!checked_in">Welcome!</t><t t-else="">Want to check out?</t></h3>
+ <h4 class="mt0 mb0 text-muted" t-if="checked_in">Today's work hours: <span t-esc="widget.employee_hours_today"/></h4>
<t t-if="!widget.use_pin">
<a class="fa fa-7x o_hr_attendance_sign_in_out_icon fa-sign-out btn-warning" t-if="checked_in" aria-label="Sign out" title="Sign out"/>
<a class="fa fa-7x o_hr_attendance_sign_in_out_icon fa-sign-in btn-secondary" t-if="!checked_in" aria-label="Sign in" title="Sign in"/>
diff --git a/addons/hr_attendance/static/tests/hr_attendance_tests.js b/addons/hr_attendance/static/tests/hr_attendance_tests.js
index a1cf6955791..22de18817ad 100644
--- a/addons/hr_attendance/static/tests/hr_attendance_tests.js
+++ b/addons/hr_attendance/static/tests/hr_attendance_tests.js
@@ -23,6 +23,7 @@ QUnit.module('HR Attendance', {
},
user_id: {string: 'user ID', type: 'integer'},
barcode: {string:'barcode', type: 'integer'},
+ hours_today: {string:'Hours today', type: 'float'},
},
records: [{
id: 1,
diff --git a/addons/hr_attendance/tests/test_hr_attendance_process.py b/addons/hr_attendance/tests/test_hr_attendance_process.py
index dd9d5b90dbd..91b3186b895 100644
--- a/addons/hr_attendance/tests/test_hr_attendance_process.py
+++ b/addons/hr_attendance/tests/test_hr_attendance_process.py
@@ -1,5 +1,10 @@
# -*- coding: utf-8 -*-
+import pytz
+from datetime import datetime
+from unittest.mock import patch
+
+from odoo import fields
from odoo.tests import new_test_user
from odoo.tests.common import TransactionCase
@@ -81,3 +86,25 @@ class TestHrAttendance(TransactionCase):
self.assertEqual(employee.attendance_state, 'checked_in', "He should be able to check in with his pin")
employee.attendance_manual({}, entered_pin=None)
self.assertEqual(employee.attendance_state, 'checked_out', "He should be able to check out with his pin")
+
+ def test_hours_today(self):
+ """ Test day start is correctly computed according to the employee's timezone """
+
+ def tz_datetime(year, month, day, hour, minute):
+ tz = pytz.timezone('Europe/Brussels')
+ return tz.localize(datetime(year, month, day, hour, minute)).astimezone(pytz.utc)
+
+ employee = self.env['hr.employee'].create({'name': 'Cunégonde', 'tz': 'Europe/Brussels'})
+ self.env['hr.attendance'].create({
+ 'employee_id': employee.id,
+ 'check_in': tz_datetime(2019, 3, 1, 22, 0), # should count from midnight in the employee's timezone (=the previous day in utc!)
+ 'check_out': tz_datetime(2019, 3, 2, 2, 0),
+ })
+ self.env['hr.attendance'].create({
+ 'employee_id': employee.id,
+ 'check_in': tz_datetime(2019, 3, 2, 11, 0),
+ })
+
+ # now = 2019/3/2 14:00 in the employee's timezone
+ with patch.object(fields.Datetime, 'now', lambda: tz_datetime(2019, 3, 2, 14, 0).astimezone(pytz.utc).replace(tzinfo=None)):
+ self.assertEqual(employee.hours_today, 5, "It should have counted 5 hours")
diff --git a/addons/hr_attendance/views/hr_employee_view.xml b/addons/hr_attendance/views/hr_employee_view.xml
index 6e6b6d7df56..7603422ef4b 100644
--- a/addons/hr_attendance/views/hr_employee_view.xml
+++ b/addons/hr_attendance/views/hr_employee_view.xml
@@ -101,6 +101,7 @@
<field name="arch" type="xml">
<kanban class="o_hr_employee_attendance_kanban" create="false">
<field name="attendance_state"/>
+ <field name="hours_today"/>
<field name="id"/>
<templates>
<t t-name="kanban-box">