[IMP] hr_timesheet_attendance: remove timesheet_sheet dependency
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import report
|
||||
from . import report
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': "Timesheets/attendances reporting",
|
||||
'description': """
|
||||
@@ -7,11 +9,10 @@
|
||||
'category': 'Hidden',
|
||||
'version': '1.0',
|
||||
|
||||
'depends': ['hr_timesheet_sheet', 'hr_attendance'],
|
||||
'depends': ['hr_timesheet', 'hr_attendance'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'report/hr_timesheet_attendance_report_view.xml',
|
||||
'views/hr_timesheet_sheet_views.xml',
|
||||
],
|
||||
'auto_install': True,
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# -*- 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
|
||||
@@ -1,120 +0,0 @@
|
||||
# -*- 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
|
||||
@@ -1,13 +0,0 @@
|
||||
# -*- 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)
|
||||
@@ -1,3 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="view_hr_timesheet_attendance_report_search" model="ir.ui.view">
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
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
|
||||
|
||||
|
@@ -1,3 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import test_hr_timesheet_sheet
|
||||
@@ -1,79 +0,0 @@
|
||||
# -*- 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'
|
||||
Reference in New Issue
Block a user