Skip to content
Snippets Groups Projects
Commit 2e251f75 authored by Jérome Maes's avatar Jérome Maes
Browse files

[IMP] hr_timesheet_attendance: remove timesheet_sheet dependency

parent 29d10860
No related branches found
No related tags found
No related merge requests found
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models from . import report
from . import report
\ No newline at end of file
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{ {
'name': "Timesheets/attendances reporting", 'name': "Timesheets/attendances reporting",
'description': """ 'description': """
...@@ -7,11 +9,10 @@ ...@@ -7,11 +9,10 @@
'category': 'Hidden', 'category': 'Hidden',
'version': '1.0', 'version': '1.0',
'depends': ['hr_timesheet_sheet', 'hr_attendance'], 'depends': ['hr_timesheet', 'hr_attendance'],
'data': [ 'data': [
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'report/hr_timesheet_attendance_report_view.xml', 'report/hr_timesheet_attendance_report_view.xml',
'views/hr_timesheet_sheet_views.xml',
], ],
'auto_install': True, 'auto_install': True,
} }
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import hr_attendance
from . import hr_timesheet_sheet
from . import res_company
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
from datetime import datetime
from pytz import timezone
import pytz
from odoo import api, fields, models, _
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
from odoo.exceptions import UserError
class HrAttendance(models.Model):
_inherit = "hr.attendance"
sheet_id_computed = fields.Many2one('hr_timesheet_sheet.sheet', string='Sheet', compute='_compute_sheet', index=True, ondelete='cascade',
search='_search_sheet')
sheet_id = fields.Many2one('hr_timesheet_sheet.sheet', compute='_compute_sheet', string='Sheet', store=True)
@api.depends('employee_id', 'check_in', 'check_out', 'sheet_id_computed.date_to', 'sheet_id_computed.date_from', 'sheet_id_computed.employee_id')
def _compute_sheet(self):
"""Links the attendance to the corresponding sheet
"""
for attendance in self:
corresponding_sheet = self.env['hr_timesheet_sheet.sheet'].search(
[('date_to', '>=', attendance.check_in), ('date_from', '<=', attendance.check_in),
('employee_id', '=', attendance.employee_id.id),
('state', 'in', ['draft', 'new'])], limit=1)
if corresponding_sheet:
attendance.sheet_id_computed = corresponding_sheet[0]
attendance.sheet_id = corresponding_sheet[0]
def _search_sheet(self, operator, value):
assert operator == 'in'
ids = []
for ts in self.env['hr_timesheet_sheet.sheet'].browse(value):
self._cr.execute("""
SELECT a.id
FROM hr_attendance a
WHERE %(date_to)s >= a.check_in
AND %(date_from)s <= a.check_in
AND %(employee_id)s = a.employee_id
GROUP BY a.id""", {'date_from': ts.date_from,
'date_to': ts.date_to,
'employee_id': ts.employee_id.id, })
ids.extend([row[0] for row in self._cr.fetchall()])
return [('id', 'in', ids)]
def _get_attendance_employee_tz(self, employee_id, date):
""" Simulate timesheet in employee timezone
Return the attendance date in string format in the employee
tz converted from utc timezone as we consider date of employee
timesheet is in employee timezone
"""
tz = False
if employee_id:
employee = self.env['hr.employee'].browse(employee_id)
tz = employee.user_id.partner_id.tz
if not date:
date = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
att_tz = timezone(tz or 'utc')
attendance_dt = datetime.strptime(date, DEFAULT_SERVER_DATETIME_FORMAT)
att_tz_dt = pytz.utc.localize(attendance_dt)
att_tz_dt = att_tz_dt.astimezone(att_tz)
# We take only the date omiting the hours as we compare with timesheet
# date_from which is a date format thus using hours would lead to
# be out of scope of timesheet
att_tz_date_str = datetime.strftime(att_tz_dt, DEFAULT_SERVER_DATE_FORMAT)
return att_tz_date_str
def _get_current_sheet(self, employee_id, date=False):
if not date:
date = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
att_tz_date_str = self._get_attendance_employee_tz(employee_id, date=date)
sheet = self.env['hr_timesheet_sheet.sheet'].search(
[('date_from', '<=', att_tz_date_str),
('date_to', '>=', att_tz_date_str),
('employee_id', '=', employee_id)], limit=1)
return sheet or False
@api.model
def create(self, vals):
if self.env.context.get('sheet_id'):
sheet = self.env['hr_timesheet_sheet.sheet'].browse(self.env.context.get('sheet_id'))
else:
sheet = self._get_current_sheet(vals.get('employee_id'), vals.get('check_in'))
if sheet:
att_tz_date_str = self._get_attendance_employee_tz(vals.get('employee_id'), date=vals.get('check_in'))
if sheet.state not in ('draft', 'new'):
raise UserError(_('You can not enter an attendance in a submitted timesheet. Ask your manager to reset it before adding attendance.'))
elif sheet.date_from > att_tz_date_str or sheet.date_to < att_tz_date_str:
raise UserError(_('You can not enter an attendance date outside the current timesheet dates.'))
return super(HrAttendance, self).create(vals)
@api.multi
def unlink(self):
self._check()
return super(HrAttendance, self).unlink()
@api.multi
def write(self, vals):
self._check()
res = super(HrAttendance, self).write(vals)
if 'sheet_id' in self.env.context:
for attendance in self:
if self.env.context['sheet_id'] != attendance.sheet_id.id:
raise UserError(_('You cannot enter an attendance date outside the current timesheet dates.'))
return res
def _check(self):
for att in self:
if att.sheet_id and att.sheet_id.state not in ('draft', 'new'):
raise UserError(_('You cannot modify an entry in a confirmed timesheet'))
return True
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class ResCompany(models.Model):
_inherit = 'res.company'
timesheet_max_difference = fields.Float(string='Timesheet allowed difference(Hours)',
help="Allowed difference in hours between the sign in/out and the timesheet " \
"computation for one sheet. Set this to 0 if you do not want any control.",
default=0.0)
<?xml version="1.0" encoding="utf-8"?>
<odoo> <odoo>
<data> <data>
<record id="view_hr_timesheet_attendance_report_search" model="ir.ui.view"> <record id="view_hr_timesheet_attendance_report_search" model="ir.ui.view">
......
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hr_timesheet_sheet_sheet_day,hr_timesheet_sheet.sheet.day,model_hr_timesheet_sheet_sheet_day,hr_timesheet.group_hr_timesheet_user,1,1,1,1
access_hr_timesheet_sheet_sheet_day_user,hr.timesheet.sheet.sheet.day.user,model_hr_timesheet_sheet_sheet_day,base.group_user,1,1,1,0
access_hr_timesheet_attendance_report,access_hr_timesheet_attendance_report,model_hr_timesheet_attendance_report,hr_timesheet.group_hr_timesheet_user,1,0,0,0 access_hr_timesheet_attendance_report,access_hr_timesheet_attendance_report,model_hr_timesheet_attendance_report,hr_timesheet.group_hr_timesheet_user,1,0,0,0
# -*- coding: utf-8 -*-
from . import test_hr_timesheet_sheet
# -*- coding: utf-8 -*-
from odoo.tests.common import TransactionCase
import time
class TestHrTimesheetSheet(TransactionCase):
"""Test for hr_timesheet_sheet.sheet"""
def setUp(self):
super(TestHrTimesheetSheet, self).setUp()
self.attendance = self.env['hr.attendance']
self.timesheet_sheet = self.env['hr_timesheet_sheet.sheet']
self.test_employee = self.browse_ref('hr.employee_qdp')
self.company = self.browse_ref('base.main_company')
self.company.timesheet_max_difference = 1.00
def test_hr_timesheet_sheet(self):
# I create a timesheet for employee "Gilles Gravie".
self.test_timesheet_sheet = self.timesheet_sheet.create({
'date_from': time.strftime('%Y-%m-11'),
'date_to': time.strftime('%Y-%m-17'),
'name': 'Gilles Gravie',
'state': 'new',
'user_id': self.browse_ref('base.user_demo').id,
'employee_id': self.test_employee.id,
})
# I check Gilles in at around 9:00 and out at 17:30
self.attendance.create({
'employee_id': self.test_employee.id,
'check_in': time.strftime('%Y-%m-11 09:12:37'),
'check_out': time.strftime('%Y-%m-11 17:30:00'),
})
# I add 6 hours of work to Gilles' timesheet
self.test_timesheet_sheet.write({'timesheet_ids': [(0, 0, {
'project_id': self.browse_ref('project.project_project_2').id,
'date': time.strftime('%Y-%m-11'),
'name': 'Develop yaml for hr module(1)',
'user_id': self.browse_ref('base.user_demo').id,
'unit_amount': 6.00,
'amount': -90.00,
'product_id': self.browse_ref('product.product_product_1').id,
})]})
# I confirm Gilles' timesheet with over 1 hour difference
# in attendance and actual worked hours
try:
self.test_timesheet_sheet.action_timesheet_confirm()
except Exception:
pass
# I add another 2 hours of work to Gilles' timesheet
self.test_timesheet_sheet.write({'timesheet_ids': [(0, 0, {
'project_id': self.browse_ref('project.project_project_2').id,
'date': time.strftime('%Y-%m-11'),
'name': 'Develop yaml for hr module(2)',
'user_id': self.browse_ref('base.user_demo').id,
'unit_amount': 2.00,
'amount': -90.00,
'product_id': self.browse_ref('product.product_product_1').id,
})]})
# I invalidate the cache, otherwise total_timesheet and total_difference doesn't get updated... /this is a disgrace/
self.test_timesheet_sheet.invalidate_cache(['total_attendance', 'total_timesheet', 'total_difference'])
# I confirm Gilles' timesheet with less than 1 hour difference
# in attendance and actual worked hours
self.test_timesheet_sheet.action_timesheet_confirm()
# I check the state is confirmed
assert self.test_timesheet_sheet.state == 'confirm'
# the manager accepts the timesheet
self.test_timesheet_sheet.write({'state': 'done'})
# I check the state is indeed done
assert self.test_timesheet_sheet.state == 'done'
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment