[MIG] hr_timesheet_sheet: migration to new api

- commented hr_timesheet_sheet dependencies to hr_attendance
- commented hr_timesheet_attendance dependencies to hr_attendance
- migrated hr_timesheet_sheet to new api
This commit is contained in:
Denis Vermylen (dve)
2016-07-06 16:32:11 +02:00
parent 9395b79296
commit 27082bbbf3
18 changed files with 473 additions and 592 deletions
@@ -8,6 +8,6 @@
# any module necessary for this one to work correctly
'depends': ['hr_timesheet', 'hr_attendance'],
'data': ['report/hr_timesheet_attendance_report_view.xml'],
'auto_install': True,
'data': [],#'report/hr_timesheet_attendance_report_view.xml'],
#'auto_install': True,
}
@@ -4,44 +4,44 @@ from openerp import api, fields, models, tools
class TimesheetAttendance(models.Model):
_name = 'hr.timesheet.attendance.report'
_auto = False
# _auto = False
user_id = fields.Many2one('res.users')
date = fields.Date()
total_timesheet = fields.Float()
total_attendance = fields.Float()
total_difference = fields.Float()
# user_id = fields.Many2one('res.users')
# date = fields.Date()
# total_timesheet = fields.Float()
# total_attendance = fields.Float()
# total_difference = fields.Float()
def init(self, cr):
cr.execute("""CREATE OR REPLACE VIEW %s AS (
SELECT
max(id) AS id,
t.user_id,
t.date,
coalesce(sum(t.attendance), 0) AS total_attendance,
coalesce(sum(t.timesheet), 0) AS total_timesheet,
coalesce(sum(t.attendance), 0) - coalesce(sum(t.timesheet), 0) as total_difference
FROM (
SELECT
-hr_attendance.id AS id,
resource_resource.user_id AS user_id,
hr_attendance.worked_hours AS attendance,
NULL AS timesheet,
date_trunc('day', hr_attendance.name) AS date
FROM hr_attendance
LEFT JOIN hr_employee ON hr_employee.id = hr_attendance.employee_id
LEFT JOIN resource_resource on resource_resource.id = hr_employee.resource_id
WHERE hr_attendance.action = 'sign_out'
UNION ALL
SELECT
ts.id AS id,
ts.user_id AS user_id,
NULL AS attendance,
ts.unit_amount AS timesheet,
date_trunc('day', ts.date) AS date
FROM account_analytic_line AS ts
) AS t
GROUP BY t.user_id, t.date
ORDER BY t.date
)
""" % self._table)
# def init(self, cr):
# cr.execute("""CREATE OR REPLACE VIEW %s AS (
# SELECT
# max(id) AS id,
# t.user_id,
# t.date,
# coalesce(sum(t.attendance), 0) AS total_attendance,
# coalesce(sum(t.timesheet), 0) AS total_timesheet,
# coalesce(sum(t.attendance), 0) - coalesce(sum(t.timesheet), 0) as total_difference
# FROM (
# SELECT
# -hr_attendance.id AS id,
# resource_resource.user_id AS user_id,
# hr_attendance.worked_hours AS attendance,
# NULL AS timesheet,
# date_trunc('day', hr_attendance.check_in) AS date --TO DO FIX !
# FROM hr_attendance
# LEFT JOIN hr_employee ON hr_employee.id = hr_attendance.employee_id
# LEFT JOIN resource_resource on resource_resource.id = hr_employee.resource_id
# WHERE hr_attendance.action = 'sign_out'
# UNION ALL
# SELECT
# ts.id AS id,
# ts.user_id AS user_id,
# NULL AS attendance,
# ts.unit_amount AS timesheet,
# date_trunc('day', ts.date) AS date
# FROM account_analytic_line AS ts
# ) AS t
# GROUP BY t.user_id, t.date
# ORDER BY t.date
# )
# """ % self._table)
+1 -1
View File
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import models
# import models
# import wizard
+8 -9
View File
@@ -28,14 +28,13 @@ The validation can be configured in the company:
'website': 'https://www.odoo.com/page/employees',
'depends': ['hr_timesheet', 'hr_attendance'],
'data': [
'security/ir.model.access.csv',
'security/hr_timesheet_sheet_security.xml',
'hr_timesheet_workflow.xml',
'hr_timesheet_sheet_data.xml',
'views/hr_timesheet_sheet.xml',
'hr_timesheet_sheet_view.xml',
'hr_timesheet_sheet_config_settings_views.xml',
'hr_dashboard.xml',
# 'security/ir.model.access.csv',
# 'security/hr_timesheet_sheet_security.xml',
# 'views/hr_timesheet_workflow.xml',
# 'data/hr_timesheet_sheet_data.xml',
# 'views/hr_timesheet_sheet_template.xml',
# 'views/hr_timesheet_sheet_view.xml',
# 'views/hr_department_views.xml',
],
'test': [
# '../account/test/account_minimal_test.xml',
@@ -44,5 +43,5 @@ The validation can be configured in the company:
'installable': True,
'auto_install': False,
'application': True,
'qweb': ['static/src/xml/timesheet.xml',],
'qweb': ['static/src/xml/timesheet.xml', ],
}
@@ -1,5 +1,5 @@
<?xml version="1.0" ?>
<openerp>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- notify all employees of module installation -->
<record model="mail.message" id="hr_timesheet_module_install_notification">
@@ -53,4 +53,4 @@
</record>
</data>
</openerp>
</odoo>
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<!-- Workflow definition -->
@@ -60,5 +59,4 @@
<field name="signal">cancel</field>
<field name="group_id" ref="base.group_hr_user"/>
</record>
</data>
</openerp>
</odoo>
@@ -1,84 +1,62 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import fields, osv
from openerp.tools.translate import _
from openerp.exceptions import UserError
from openerp import api, fields, models, _
from openerp.exceptions import UserError
class account_analytic_line(osv.osv):
class AccountAnalyticLine(models.Model):
_inherit = "account.analytic.line"
sheet_id = fields.Many2one('hr_timesheet_sheet.sheet', string='Sheet', compute='_compute_sheet', index=True, ondelete='cascade',
store=True, search='_search_sheet')
def _get_default_date(self, cr, uid, context=None):
if context is None:
context = {}
# get the default date (should be: today)
res = super(account_analytic_line, self)._get_default_date(cr, uid, context=context)
# if we got the dates from and to from the timesheet and if the default date is in between, we use the default
# but if the default isn't included in those dates, we use the date start of the timesheet as default
if context.get('timesheet_date_from') and context.get('timesheet_date_to'):
if context['timesheet_date_from'] <= res <= context['timesheet_date_to']:
return res
return context.get('timesheet_date_from')
# if we don't get the dates from the timesheet, we return the default value from super()
return res
def _sheet(self, cursor, user, ids, name, args, context=None):
sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
res = {}.fromkeys(ids, False)
for ts_line in self.browse(cursor, user, ids, context=context):
# dependency TO TEST
# shouldn't work ... the search won't be called since store=True, so it'll look up in the column
# will probably need to create a 2nd non-stored related variable.
@api.depends('date', 'user_id', 'project_id', 'sheet_id.date_to', 'sheet_id.date_from', 'sheet_id.employee_id')
def _compute_sheet(self):
"""Links the timesheet line to the corresponding sheet
"""
for ts_line in self:
if not ts_line.project_id:
continue
sheet_ids = sheet_obj.search(cursor, user,
sheets = self.env['hr_timesheet_sheet.sheet'].search(
[('date_to', '>=', ts_line.date), ('date_from', '<=', ts_line.date),
('employee_id.user_id', '=', ts_line.user_id.id),
('state', 'in', ['draft', 'new'])],
context=context)
if sheet_ids:
# [0] because only one sheet possible for an employee between 2 dates
res[ts_line.id] = sheet_obj.name_get(cursor, user, sheet_ids, context=context)[0]
return res
('state', 'in', ['draft', 'new'])])
if sheets:
# [0] because only one sheet possible for an employee between 2 dates
ts_line.sheet_id = sheets[0]
def _get_hr_timesheet_sheet(self, cr, uid, ids, context=None):
ts_line_ids = []
for ts in self.browse(cr, uid, ids, context=context):
cr.execute("""
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 l.id
FROM account_analytic_line l
WHERE %(date_to)s >= l.date
AND %(date_from)s <= l.date
AND %(user_id)s = l.user_id
AND l.project_id IS NOT NULL
GROUP BY l.id""", {'date_from': ts.date_from,
'date_to': ts.date_to,
'user_id': ts.employee_id.user_id.id,})
ts_line_ids.extend([row[0] for row in cr.fetchall()])
return ts_line_ids
'date_to': ts.date_to,
'user_id': ts.employee_id.user_id.id, })
ids.extend([row[0] for row in self._cr.fetchall()])
return [('id', 'in', ids)]
_columns = {
'sheet_id': fields.function(_sheet, string='Sheet', select="1",
type='many2one', relation='hr_timesheet_sheet.sheet', ondelete="cascade",
store={
'hr_timesheet_sheet.sheet': (_get_hr_timesheet_sheet, ['employee_id', 'date_from', 'date_to'], 10),
'account.analytic.line': (lambda self,cr,uid,ids,context=None: ids, ['user_id', 'date'], 10),
},
),
}
@api.multi
def write(self, values):
self._check_state()
return super(AccountAnalyticLine, self).write(values)
def write(self, cr, uid, ids, values, context=None):
if isinstance(ids, (int, long)):
ids = [ids]
self._check(cr, uid, ids)
return super(account_analytic_line, self).write(cr, uid, ids, values,context=context)
@api.multi
def unlink(self):
self._check_state()
return super(AccountAnalyticLine, self).unlink()
def unlink(self, cr, uid, ids, *args, **kwargs):
if isinstance(ids, (int, long)):
ids = [ids]
self._check(cr, uid, ids)
return super(account_analytic_line,self).unlink(cr, uid, ids,*args, **kwargs)
def _check(self, cr, uid, ids):
for att in self.browse(cr, uid, ids):
if att.sheet_id and att.sheet_id.state not in ('draft', 'new'):
def _check_state(self):
for line in self:
if line.sheet_id and line.sheet_id.state not in ('draft', 'new'):
raise UserError(_('You cannot modify an entry in a confirmed timesheet.'))
return True
@@ -6,57 +6,54 @@ from datetime import datetime
from pytz import timezone
import pytz
from openerp.osv import fields, osv
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
from openerp.tools.translate import _
from openerp.exceptions import UserError
from odoo import api, fields, models, _
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
from odoo.exceptions import UserError
class hr_attendance(osv.osv):
class HrAttendance(models.Model):
_inherit = "hr.attendance"
def _get_default_date(self, cr, uid, context=None):
if context is None:
context = {}
if 'name' in context:
return context['name'] + time.strftime(' %H:%M:%S')
return time.strftime('%Y-%m-%d %H:%M:%S')
sheet_id = fields.Many2one('hr_timesheet_sheet.sheet', compute='_compute_sheet', string='Sheet', store=True)
def _get_hr_timesheet_sheet(self, cr, uid, ids, context=None):
attendance_ids = []
for ts in self.browse(cr, uid, ids, context=context):
cr.execute("""
SELECT a.id
FROM hr_attendance a
INNER JOIN hr_employee e
INNER JOIN resource_resource r
ON (e.resource_id = r.id)
ON (a.employee_id = e.id)
LEFT JOIN res_users u
ON r.user_id = u.id
LEFT JOIN res_partner p
ON u.partner_id = p.id
WHERE %(date_to)s >= date_trunc('day', a.name AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC'))
AND %(date_from)s <= date_trunc('day', a.name AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC'))
AND %(user_id)s = r.user_id
GROUP BY a.id""", {'date_from': ts.date_from,
'date_to': ts.date_to,
'user_id': ts.employee_id.user_id.id,})
attendance_ids.extend([row[0] for row in cr.fetchall()])
return attendance_ids
# Same problem as in account.analytic.line for dependance, need to define a search function.
# No need to migrate correctly, hr.attendance will be removed from this module anyways.
def _get_attendance_employee_tz(self, cr, uid, employee_id, date, context=None):
# def _get_hr_timesheet_sheet(self, cr, uid, ids, context=None):
# attendance_ids = []
# for ts in self.browse(cr, uid, ids, context=context):
# cr.execute("""
# SELECT a.id
# FROM hr_attendance a
# INNER JOIN hr_employee e
# INNER JOIN resource_resource r
# ON (e.resource_id = r.id)
# ON (a.employee_id = e.id)
# LEFT JOIN res_users u
# ON r.user_id = u.id
# LEFT JOIN res_partner p
# ON u.partner_id = p.id
# WHERE %(date_to)s >= date_trunc('day', a.name AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC'))
# AND %(date_from)s <= date_trunc('day', a.name AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC'))
# AND %(user_id)s = r.user_id
# GROUP BY a.id""", {'date_from': ts.date_from,
# 'date_to': ts.date_to,
# 'user_id': ts.employee_id.user_id.id,})
# attendance_ids.extend([row[0] for row in cr.fetchall()])
# return attendance_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
"""
employee_obj = self.pool['hr.employee']
employee_obj = self.env['hr.employee']
tz = False
if employee_id:
employee = employee_obj.browse(cr, uid, employee_id, context=context)
employee = employee_obj.browse(employee_id)
tz = employee.user_id.partner_id.tz
if not date:
@@ -73,81 +70,51 @@ class hr_attendance(osv.osv):
att_tz_date_str = datetime.strftime(att_tz_dt, DEFAULT_SERVER_DATE_FORMAT)
return att_tz_date_str
def _get_current_sheet(self, cr, uid, employee_id, date=False, context=None):
sheet_obj = self.pool['hr_timesheet_sheet.sheet']
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(
cr, uid, employee_id,
date=date, context=context)
sheet_ids = sheet_obj.search(cr, uid,
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, context=context)
return sheet_ids and sheet_ids[0] or False
('employee_id', '=', employee_id)], limit=1)
return sheet or False
def _sheet(self, cursor, user, ids, name, args, context=None):
res = {}.fromkeys(ids, False)
for attendance in self.browse(cursor, user, ids, context=context):
res[attendance.id] = self._get_current_sheet(
cursor, user, attendance.employee_id.id, attendance.name,
context=context)
return res
@api.depends('employee_id', 'name', 'day', 'sheet_id.employee_id', 'sheet_id.date_from', 'sheet_id.date_to')
def _compute_sheet(self, name, args):
for attendance in self:
attendance.sheet_id = self._get_current_sheet(attendance.employee_id.id, attendance.name)
_columns = {
'sheet_id': fields.function(_sheet, string='Sheet',
type='many2one', relation='hr_timesheet_sheet.sheet',
store={
'hr_timesheet_sheet.sheet': (_get_hr_timesheet_sheet, ['employee_id', 'date_from', 'date_to'], 10),
'hr.attendance': (lambda self,cr,uid,ids,context=None: ids, ['employee_id', 'name', 'day'], 10),
},
)
}
_defaults = {
'name': _get_default_date,
}
def create(self, cr, uid, vals, context=None):
if context is None:
context = {}
sheet_id = context.get('sheet_id') or self._get_current_sheet(cr, uid, vals.get('employee_id'), vals.get('name'), context=context)
@api.model
def create(self, vals):
sheet_id = self.env.context.get('sheet_id') or self._get_current_sheet(vals.get('employee_id'), vals.get('name'))
if sheet_id:
att_tz_date_str = self._get_attendance_employee_tz(
cr, uid, vals.get('employee_id'),
date=vals.get('name'), context=context)
ts = self.pool.get('hr_timesheet_sheet.sheet').browse(cr, uid, sheet_id, context=context)
att_tz_date_str = self._get_attendance_employee_tz(vals.get('employee_id'), date=vals.get('name'))
ts = self.env['hr_timesheet_sheet.sheet'].browse(sheet_id)
if ts.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 ts.date_from > att_tz_date_str or ts.date_to < att_tz_date_str:
raise UserError(_('You can not enter an attendance date outside the current timesheet dates.'))
return super(hr_attendance,self).create(cr, uid, vals, context=context)
return super(HrAttendance, self).create(vals)
def unlink(self, cr, uid, ids, *args, **kwargs):
if isinstance(ids, (int, long)):
ids = [ids]
self._check(cr, uid, ids)
return super(hr_attendance,self).unlink(cr, uid, ids,*args, **kwargs)
@api.multi
def unlink(self):
self._check()
return super(HrAttendance, self).unlink()
def write(self, cr, uid, ids, vals, context=None):
if context is None:
context = {}
if isinstance(ids, (int, long)):
ids = [ids]
self._check(cr, uid, ids)
res = super(hr_attendance,self).write(cr, uid, ids, vals, context=context)
if 'sheet_id' in context:
for attendance in self.browse(cr, uid, ids, context=context):
if context['sheet_id'] != attendance.sheet_id.id:
raise UserError(_('You cannot enter an attendance ' \
'date outside the current timesheet dates.'))
@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, cr, uid, ids):
for att in self.browse(cr, uid, ids):
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,8 +1,8 @@
# -*- coding: utf-8 -*-
from openerp import api, fields, models
from odoo import api, fields, models
class hr_department(models.Model):
class HrDepartment(models.Model):
_inherit = 'hr.department'
@api.multi
@@ -1,24 +1,16 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import fields, osv
from odoo import api, fields, models
class hr_employee(osv.osv):
'''
Employee
'''
class HrEmployee(models.Model):
_inherit = 'hr.employee'
_description = 'Employee'
def _timesheet_count(self, cr, uid, ids, field_name, arg, context=None):
Sheet = self.pool['hr_timesheet_sheet.sheet']
return {
employee_id: Sheet.search_count(cr,uid, [('employee_id', '=', employee_id)], context=context)
for employee_id in ids
}
timesheet_count = fields.Integer(compute='_compute_timesheet_count', string='Timesheets')
_columns = {
'timesheet_count': fields.function(_timesheet_count, type='integer', string='Timesheets'),
}
@api.multi
def _compute_timesheet_count(self):
for employee in self:
employee.timesheet_count = employee.env['hr_timesheet_sheet.sheet'].search_count([('employee_id', '=', employee.id)])
@@ -4,386 +4,346 @@
import time
from datetime import datetime
from dateutil.relativedelta import relativedelta
from pytz import timezone
import pytz
from openerp.osv import fields, osv
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
from openerp.tools.translate import _
from openerp.exceptions import UserError
from odoo import api, fields, models
from odoo.tools.translate import _
from odoo.exceptions import UserError, ValidationError
class hr_timesheet_sheet(osv.osv):
class HrTimesheetSheet(models.Model):
_name = "hr_timesheet_sheet.sheet"
_inherit = ['mail.thread', 'ir.needaction_mixin']
_table = 'hr_timesheet_sheet_sheet'
_order = "id desc"
_description = "Timesheet"
def _total(self, cr, uid, ids, name, args, context=None):
""" Compute the attendances, analytic lines timesheets and differences between them
for all the days of a timesheet and the current day
"""
res = dict.fromkeys(ids, {
'total_attendance': 0.0,
'total_timesheet': 0.0,
'total_difference': 0.0,
})
# @api.depends('period_ids.total_attendance', 'period_ids.total_timesheet', 'period_ids.total_difference')
# def _total(self):
# """ Compute the attendances, analytic lines timesheets and differences
# between them for all the days of a timesheet and the current day
# """
cr.execute("""
SELECT sheet_id as id,
sum(total_attendance) as total_attendance,
sum(total_timesheet) as total_timesheet,
sum(total_difference) as total_difference
FROM hr_timesheet_sheet_sheet_day
WHERE sheet_id IN %s
GROUP BY sheet_id
""", (tuple(ids),))
# self.env.cr.execute("""
# SELECT sheet_id as id,
# sum(total_attendance) as total_attendance,
# sum(total_timesheet) as total_timesheet,
# sum(total_difference) as total_difference
# FROM hr_timesheet_sheet_sheet_day
# WHERE sheet_id IN %s
# GROUP BY sheet_id
# """, (tuple(self.ids),))
res.update(dict((x.pop('id'), x) for x in cr.dictfetchall()))
# for x in self.env.cr.dictfetchall():
# sheet = self.browse(x.pop('id'))
# sheet.total_attendance = x.pop('total_attendance')
# sheet.total_timesheet = x.pop('total_timesheet')
# sheet.total_difference = x.pop('total_difference')
return res
# def check_employee_attendance_state(self):
# """ Checks the attendance records of the timesheet, make sure they are all closed
# (by making sure they have a check_out time)
# """
# if any(self.attendances_ids.filtered(lambda r: not r.check_out)):
# raise UserError(_("The timesheet cannot be validated as it contains an attendance record with no Check Out)."))
# return True
def check_employee_attendance_state(self, cr, uid, sheet_id, context=None):
ids_signin = self.pool.get('hr.attendance').search(cr,uid,[('sheet_id', '=', sheet_id),('action','=','sign_in')])
ids_signout = self.pool.get('hr.attendance').search(cr,uid,[('sheet_id', '=', sheet_id),('action','=','sign_out')])
if len(ids_signin) != len(ids_signout):
raise UserError(_('The timesheet cannot be validated as it does not contain an equal number of sign ins and sign outs.'))
return True
def copy(self, cr, uid, ids, *args, **argv):
def copy(self, *args, **argv):
raise UserError(_('You cannot duplicate a timesheet.'))
def create(self, cr, uid, vals, context=None):
@api.model
def create(self, vals):
if 'employee_id' in vals:
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id'], context=context).user_id:
if not self.env['hr.employee'].browse(vals['employee_id']).user_id:
raise UserError(_('In order to create a timesheet for this employee, you must link him/her to a user.'))
if vals.get('attendances_ids'):
# If attendances, we sort them by date asc before writing them, to satisfy the alternance constraint
vals['attendances_ids'] = self.sort_attendances(cr, uid, vals['attendances_ids'], context=context)
return super(hr_timesheet_sheet, self).create(cr, uid, vals, context=context)
# No more need to sort the attendances, except for user lookupability maybe ...
return super(HrTimesheetSheet, self).create(vals)
def write(self, cr, uid, ids, vals, context=None):
@api.multi
def write(self, vals):
if 'employee_id' in vals:
new_user_id = self.pool.get('hr.employee').browse(cr, uid, vals['employee_id'], context=context).user_id.id or False
new_user_id = self.env['hr.employee'].browse(vals['employee_id']).user_id.id
if not new_user_id:
raise UserError(_('In order to create a timesheet for this employee, you must link him/her to a user.'))
if not self._sheet_date(cr, uid, ids, forced_user_id=new_user_id, context=context):
if not self._sheet_date(forced_user_id=new_user_id):
raise UserError(_('You cannot have 2 timesheets that overlap!\nYou should use the menu \'My Timesheet\' to avoid this problem.'))
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id'], context=context).product_id:
raise UserError(_('In order to create a timesheet for this employee, you must link the employee to a product.'))
if vals.get('attendances_ids'):
# If attendances, we sort them by date asc before writing them, to satisfy the alternance constraint
# In addition to the date order, deleting attendances are done before inserting attendances
vals['attendances_ids'] = self.sort_attendances(cr, uid, vals['attendances_ids'], context=context)
res = super(hr_timesheet_sheet, self).write(cr, uid, ids, vals, context=context)
if vals.get('attendances_ids'):
for timesheet in self.browse(cr, uid, ids):
if not self.pool['hr.attendance']._altern_si_so(cr, uid, [att.id for att in timesheet.attendances_ids]):
raise UserError(_('Error ! Sign in (resp. Sign out) must follow Sign out (resp. Sign in)'))
return res
# ??? I don't see a product_id field defined to hr.employee anywhere. (and why would it be used for?)
# if not self.env['hr.employee'].browse(vals['employee_id']).product_id:
# raise UserError(_('In order to create a timesheet for this employee, you must link the employee to a product.'))
# No more need to sort.
return super(HrTimesheetSheet, self).write(vals)
def sort_attendances(self, cr, uid, attendance_tuples, context=None):
date_attendances = []
for att_tuple in attendance_tuples:
if att_tuple[0] in [0,1,4]:
if att_tuple[0] in [0,1]:
if att_tuple[2] and att_tuple[2].has_key('name'):
name = att_tuple[2]['name']
else:
name = self.pool['hr.attendance'].browse(cr, uid, att_tuple[1]).name
else:
name = self.pool['hr.attendance'].browse(cr, uid, att_tuple[1]).name
date_attendances.append((1, name, att_tuple))
elif att_tuple[0] in [2,3]:
date_attendances.append((0, self.pool['hr.attendance'].browse(cr, uid, att_tuple[1]).name, att_tuple))
else:
date_attendances.append((0, False, att_tuple))
date_attendances.sort()
return [att[2] for att in date_attendances]
def button_confirm(self, cr, uid, ids, context=None):
for sheet in self.browse(cr, uid, ids, context=context):
def button_confirm(self):
for sheet in self:
if sheet.employee_id and sheet.employee_id.parent_id and sheet.employee_id.parent_id.user_id:
self.message_subscribe_users(cr, uid, [sheet.id], user_ids=[sheet.employee_id.parent_id.user_id.id], context=context)
self.check_employee_attendance_state(cr, uid, sheet.id, context=context)
di = sheet.user_id.company_id.timesheet_max_difference
if (abs(sheet.total_difference) < di) or not di:
sheet.signal_workflow('confirm')
else:
raise UserError(_('Please verify that the total difference of the sheet is lower than %.2f.') %(di,))
self.message_subscribe_users(user_ids=[sheet.employee_id.parent_id.user_id.id])
# self.check_employee_attendance_state()
# di = sheet.user_id.company_id.timesheet_max_difference
# if (abs(sheet.total_difference) < di) or not di:
sheet.signal_workflow('confirm')
# else:
# raise UserError(_('Please verify that the total difference of the sheet is lower than %.2f.') % (di,))
return True
def attendance_action_change(self, cr, uid, ids, context=None):
hr_employee = self.pool.get('hr.employee')
employee_ids = []
for sheet in self.browse(cr, uid, ids, context=context):
if sheet.employee_id.id not in employee_ids: employee_ids.append(sheet.employee_id.id)
return hr_employee.attendance_action_change(cr, uid, employee_ids, context=context)
# @api.multi # multi no need ? called from xml
# def attendance_action_change(self):
# employee_ids = []
# for sheet in self:
# if sheet.employee_id.id not in employee_ids:
# employee_ids.append(sheet.employee_id.id)
# return self.browse(employee_ids).attendance_action_change()
def _count_attendances(self, cr, uid, ids, field_name, arg, context=None):
res = dict.fromkeys(ids, 0)
attendances_groups = self.pool['hr.attendance'].read_group(cr, uid, [('sheet_id' , 'in' , ids)], ['sheet_id'], 'sheet_id', context=context)
for attendances in attendances_groups:
res[attendances['sheet_id'][0]] = attendances['sheet_id_count']
return res
# @api.depends('attendances_ids')
# def _count_attendances(self):
# res = {}
# for sheet in self:
# res[sheet.id] = len(sheet.attendances_ids)
# return res
_columns = {
'name': fields.char('Note', select=1,
states={'confirm':[('readonly', True)], 'done':[('readonly', True)]}),
'employee_id': fields.many2one('hr.employee', 'Employee', required=True),
'user_id': fields.related('employee_id', 'user_id', type="many2one", relation="res.users", store=True, string="User", required=False, readonly=True),#fields.many2one('res.users', 'User', required=True, select=1, states={'confirm':[('readonly', True)], 'done':[('readonly', True)]}),
'date_from': fields.date('Date from', required=True, select=1, readonly=True, states={'new':[('readonly', False)]}),
'date_to': fields.date('Date to', required=True, select=1, readonly=True, states={'new':[('readonly', False)]}),
'timesheet_ids' : fields.one2many('account.analytic.line', 'sheet_id',
'Timesheet lines',
readonly=True, states={
'draft': [('readonly', False)],
'new': [('readonly', False)]}
),
'attendances_ids' : fields.one2many('hr.attendance', 'sheet_id', 'Attendances'),
'state' : fields.selection([
('new', 'New'),
('draft','Open'),
('confirm','Waiting Approval'),
('done','Approved')], 'Status', select=True, required=True, readonly=True,
track_visibility='onchange',
help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed timesheet. \
\n* The \'Confirmed\' status is used for to confirm the timesheet by user. \
\n* The \'Done\' status is used when users timesheet is accepted by his/her senior.'),
'state_attendance' : fields.related('employee_id', 'state', type='selection', selection=[('absent', 'Absent'), ('present', 'Present')], string='Current Status', readonly=True),
'total_attendance': fields.function(_total, method=True, string='Total Attendance', multi="_total"),
'total_timesheet': fields.function(_total, method=True, string='Total Timesheet', multi="_total"),
'total_difference': fields.function(_total, method=True, string='Difference', multi="_total"),
'period_ids': fields.one2many('hr_timesheet_sheet.sheet.day', 'sheet_id', 'Period', readonly=True),
'account_ids': fields.one2many('hr_timesheet_sheet.sheet.account', 'sheet_id', 'Analytic accounts', readonly=True),
'company_id': fields.many2one('res.company', 'Company'),
'department_id':fields.many2one('hr.department','Department'),
'attendance_count': fields.function(_count_attendances, type='integer', string="Attendances"),
}
def _default_date_from(self, cr, uid, context=None):
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
def _default_date_from(self):
user = self.env['res.users'].browse(self.env.uid)
r = user.company_id and user.company_id.timesheet_range or 'month'
if r=='month':
if r == 'month':
return time.strftime('%Y-%m-01')
elif r=='week':
elif r == 'week':
return (datetime.today() + relativedelta(weekday=0, days=-6)).strftime('%Y-%m-%d')
elif r=='year':
elif r == 'year':
return time.strftime('%Y-01-01')
return fields.date.context_today(self, cr, uid, context)
return fields.date.context_today(self)
def _default_date_to(self, cr, uid, context=None):
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
def _default_date_to(self):
user = self.env['res.users'].browse(self.env.uid)
r = user.company_id and user.company_id.timesheet_range or 'month'
if r=='month':
return (datetime.today() + relativedelta(months=+1,day=1,days=-1)).strftime('%Y-%m-%d')
elif r=='week':
if r == 'month':
return (datetime.today() + relativedelta(months=+1, day=1, days=-1)).strftime('%Y-%m-%d')
elif r == 'week':
return (datetime.today() + relativedelta(weekday=6)).strftime('%Y-%m-%d')
elif r=='year':
elif r == 'year':
return time.strftime('%Y-12-31')
return fields.date.context_today(self, cr, uid, context)
return fields.date.context_today(self)
def _default_employee(self, cr, uid, context=None):
emp_ids = self.pool.get('hr.employee').search(cr, uid, [('user_id','=',uid)], context=context)
def _default_employee(self):
emp_ids = self.env['hr.employee'].search([('user_id', '=', self.env.uid)])
return emp_ids and emp_ids[0] or False
_defaults = {
'date_from' : _default_date_from,
'date_to' : _default_date_to,
'state': 'new',
'employee_id': _default_employee,
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'hr_timesheet_sheet.sheet', context=c)
}
# name had select=1 (= index=True, ) why, no use, is there ???
name = fields.Char(string="Note", states={'confirm': [('readonly', True)], 'done': [('readonly', True)]})
employee_id = fields.Many2one('hr.employee', string='Employee', default=_default_employee, required=True)
user_id = fields.Many2one('res.users', related='employee_id.user_id', string='User', store=True, readonly=True) # required=False, not necessary, right ?
date_from = fields.Date(string='Date From', default=_default_date_from, required=True,
index=True, readonly=True, states={'new': [('readonly', False)]})
date_to = fields.Date(string='Date To', default=_default_date_to, required=True,
index=True, readonly=True, states={'new': [('readonly', False)]})
timesheet_ids = fields.One2many('account.analytic.line', 'sheet_id',
string='Timesheet lines',
readonly=True, states={
'draft': [('readonly', False)],
'new': [('readonly', False)]})
# attendances_ids = fields.One2many('hr.attendance', 'sheet_id', 'Attendances')
state = fields.Selection([
('new', 'New'),
('draft', 'Open'),
('confirm', 'Waiting Approval'),
('done', 'Approved')], default='new', track_visibility='onchange',
string='Status', required=True, readonly=True, index=True,
help=' * The \'Open\' status is used when a user is encoding a new and unconfirmed timesheet. \
\n* The \'Waiting Approval\' status is used to confirm the timesheet by user. \
\n* The \'Approved\' status is used when the users timesheet is accepted by his/her senior.')
# state_attendance = fields.Selection(string='Current Status', related='employee_id.state',
# selection=[('absent', 'Absent'), ('present', 'Present')], readonly=True)
# total_attendance = fields.Integer(string='Total Attendance', compute='_total')
# total_timesheet = fields.Float(string='Total Timesheet', compute="_total")
# total_difference = fields.Float(string='Difference', compute="_total")
# period_ids = fields.One2many('hr_timesheet_sheet.sheet.day', 'sheet_id', string='Period', readonly=True)
account_ids = fields.One2many('hr_timesheet_sheet.sheet.account', 'sheet_id', string='Analytic accounts', readonly=True)
company_id = fields.Many2one('res.company', string='Company')
department_id = fields.Many2one('hr.department', string='Department',
default=lambda self: self.env['res.company']._company_default_get())
# attendance_count = fields.Integer(compute='_count_attendances', string="Attendances")
def _sheet_date(self, cr, uid, ids, forced_user_id=False, context=None):
for sheet in self.browse(cr, uid, ids, context=context):
new_user_id = forced_user_id or sheet.employee_id.user_id and sheet.employee_id.user_id.id
@api.constrains('date_to', 'date_from', 'employee_id')
def _sheet_date(self):
for sheet in self:
new_user_id = sheet.user_id and sheet.user_id.id
if new_user_id:
cr.execute('SELECT id \
self.env.cr.execute('SELECT id \
FROM hr_timesheet_sheet_sheet \
WHERE (date_from <= %s and %s <= date_to) \
AND user_id=%s \
AND id <> %s',(sheet.date_to, sheet.date_from, new_user_id, sheet.id))
if cr.fetchall():
return False
AND id <> %s', (sheet.date_to, sheet.date_from, new_user_id, sheet.id))
if not self.env.cr.fetchall():
raise ValidationError('You cannot have 2 timesheets that overlap!\nPlease use the menu \'My Current Timesheet\' to avoid this problem.')
@api.multi
def action_set_to_draft(self):
self.write({'state': 'draft'})
self.create_workflow()
return True
_constraints = [
(_sheet_date, 'You cannot have 2 timesheets that overlap!\nPlease use the menu \'My Current Timesheet\' to avoid this problem.', ['date_from','date_to']),
]
def action_set_to_draft(self, cr, uid, ids, *args):
self.write(cr, uid, ids, {'state': 'draft'})
self.create_workflow(cr, uid, ids)
return True
def name_get(self, cr, uid, ids, context=None):
if not ids:
return []
if isinstance(ids, (long, int)):
ids = [ids]
@api.multi
def name_get(self):
# week number according to ISO 8601 Calendar
return [(r['id'], _('Week ')+str(datetime.strptime(r['date_from'], '%Y-%m-%d').isocalendar()[1])) \
for r in self.read(cr, uid, ids, ['date_from'],
context=context, load='_classic_write')]
return [(r['id'], _('Week ') + str(datetime.strptime(r['date_from'], '%Y-%m-%d').isocalendar()[1])) \
for r in self.read(['date_from'], load='_classic_write')]
def unlink(self, cr, uid, ids, context=None):
sheets = self.read(cr, uid, ids, ['state','total_attendance'], context=context)
@api.multi
def unlink(self):
# sheets = self.read(cr, uid, ids, ['state','total_attendance'], context=context)
sheets = self.read(['state'])
for sheet in sheets:
if sheet['state'] in ('confirm', 'done'):
raise UserError(_('You cannot delete a timesheet which is already confirmed.'))
elif sheet['total_attendance'] <> 0.00:
raise UserError(_('You cannot delete a timesheet which have attendance entries.'))
# elif sheet['total_attendance'] <> 0.00:
# raise UserError(_('You cannot delete a timesheet which have attendance entries.'))
toremove = []
analytic_timesheet = self.pool.get('account.analytic.line')
for sheet in self.browse(cr, uid, ids, context=context):
for timesheet in sheet.timesheet_ids:
toremove.append(timesheet.id)
analytic_timesheet.unlink(cr, uid, toremove, context=context)
analytic_timesheet_toremove = self.env['account.analytic.line']
for sheet in self:
analytic_timesheet_toremove += sheet.timesheet_ids
analytic_timesheet_toremove.unlink()
return super(hr_timesheet_sheet, self).unlink(cr, uid, ids, context=context)
return super(HrTimesheetSheet, self).unlink()
def onchange_employee_id(self, cr, uid, ids, employee_id, context=None):
department_id = False
user_id = False
if employee_id:
empl_id = self.pool.get('hr.employee').browse(cr, uid, employee_id, context=context)
department_id = empl_id.department_id.id
user_id = empl_id.user_id.id
return {'value': {'department_id': department_id, 'user_id': user_id,}}
@api.multi
@api.onchange('employee_id')
def onchange_employee_id(self, employee_id):
print " ok %d" %employee_id + "\n"
print self
employee = self.env['hr.employee'].browse(employee_id)
for sheet in self:
if employee_id:
sheet.department_id = employee.department_id
sheet.user_id = employee.user_id
# ------------------------------------------------
# OpenChatter methods and notifications
# ------------------------------------------------
def _track_subtype(self, cr, uid, ids, init_values, context=None):
record = self.browse(cr, uid, ids[0], context=context)
if 'state' in init_values and record.state == 'confirm':
return 'hr_timesheet_sheet.mt_timesheet_confirmed'
elif 'state' in init_values and record.state == 'done':
return 'hr_timesheet_sheet.mt_timesheet_approved'
return super(hr_timesheet_sheet, self)._track_subtype(cr, uid, ids, init_values, context=context)
@api.multi
def _track_subtype(self, init_values):
if self:
record = self[0]
if 'state' in init_values and record.state == 'confirm':
return 'hr_timesheet_sheet.mt_timesheet_confirmed'
elif 'state' in init_values and record.state == 'done':
return 'hr_timesheet_sheet.mt_timesheet_approved'
return super(HrTimesheetSheet, self)._track_subtype(init_values)
def _needaction_domain_get(self, cr, uid, context=None):
emp_obj = self.pool.get('hr.employee')
empids = emp_obj.search(cr, uid, [('parent_id.user_id', '=', uid)], context=context)
@api.model
def _needaction_domain_get(self):
empids = self.env['hr.employee'].search([('parent_id.user_id', '=', self.env.uid)])
if not empids:
return False
dom = ['&', ('state', '=', 'confirm'), ('employee_id', 'in', empids)]
return dom
return ['&', ('state', '=', 'confirm'), ('employee_id', 'in', empids)]
# hr_timesheet_sheet_sheet_day Won't be needed as dependancy towards hr_attendance will be removed
# class hr_timesheet_sheet_sheet_day(osv.osv):
# _name = "hr_timesheet_sheet.sheet.day"
# _description = "Timesheets by Period"
# _auto = False
# _order='name'
# _columns = {
# 'name': fields.date('Date', readonly=True),
# 'sheet_id': fields.many2one('hr_timesheet_sheet.sheet', 'Sheet', readonly=True, select="1"),
# 'total_timesheet': fields.float('Total Timesheet', readonly=True),
# 'total_attendance': fields.float('Attendance', readonly=True),
# 'total_difference': fields.float('Difference', readonly=True),
# }
# _depends = {
# 'account.analytic.line': ['date', 'unit_amount'],
# 'hr.attendance': ['action', 'name', 'sheet_id'],
# }
# def init(self, cr):
# cr.execute("""create or replace view hr_timesheet_sheet_sheet_day as
# SELECT
# id,
# name,
# sheet_id,
# total_timesheet,
# total_attendance,
# cast(round(cast(total_attendance - total_timesheet as Numeric),2) as Double Precision) AS total_difference
# FROM
# ((
# SELECT
# MAX(id) as id,
# name,
# sheet_id,
# timezone,
# SUM(total_timesheet) as total_timesheet,
# CASE WHEN SUM(orphan_attendances) != 0
# THEN (SUM(total_attendance) +
# CASE WHEN current_date <> name
# THEN 1440
# ELSE (EXTRACT(hour FROM current_time AT TIME ZONE 'UTC' AT TIME ZONE coalesce(timezone, 'UTC')) * 60) + EXTRACT(minute FROM current_time AT TIME ZONE 'UTC' AT TIME ZONE coalesce(timezone, 'UTC'))
# END
# )
# ELSE SUM(total_attendance)
# END /60 as total_attendance
# FROM
# ((
# select
# min(l.id) as id,
# p.tz as timezone,
# l.date::date as name,
# s.id as sheet_id,
# sum(l.unit_amount) as total_timesheet,
# 0 as orphan_attendances,
# 0.0 as total_attendance
# from
# account_analytic_line l
# LEFT JOIN hr_timesheet_sheet_sheet s ON s.id = l.sheet_id
# JOIN hr_employee e ON s.employee_id = e.id
# JOIN resource_resource r ON e.resource_id = r.id
# LEFT JOIN res_users u ON r.user_id = u.id
# LEFT JOIN res_partner p ON u.partner_id = p.id
# group by l.date::date, s.id, timezone
# ) union (
# select
# -min(a.id) as id,
# p.tz as timezone,
# (a.name AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC'))::date as name,
# s.id as sheet_id,
# 0.0 as total_timesheet,
# SUM(CASE WHEN a.action = 'sign_in' THEN -1 ELSE 1 END) as orphan_attendances,
# SUM(((EXTRACT(hour FROM (a.name AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC'))) * 60) + EXTRACT(minute FROM (a.name AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC')))) * (CASE WHEN a.action = 'sign_in' THEN -1 ELSE 1 END)) as total_attendance
# from
# hr_attendance a
# LEFT JOIN hr_timesheet_sheet_sheet s
# ON s.id = a.sheet_id
# JOIN hr_employee e
# ON a.employee_id = e.id
# JOIN resource_resource r
# ON e.resource_id = r.id
# LEFT JOIN res_users u
# ON r.user_id = u.id
# LEFT JOIN res_partner p
# ON u.partner_id = p.id
# WHERE action in ('sign_in', 'sign_out')
# group by (a.name AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC'))::date, s.id, timezone
# )) AS foo
# GROUP BY name, sheet_id, timezone
# )) AS bar""")
class hr_timesheet_sheet_sheet_day(osv.osv):
_name = "hr_timesheet_sheet.sheet.day"
_description = "Timesheets by Period"
_auto = False
_order='name'
_columns = {
'name': fields.date('Date', readonly=True),
'sheet_id': fields.many2one('hr_timesheet_sheet.sheet', 'Sheet', readonly=True, select="1"),
'total_timesheet': fields.float('Total Timesheet', readonly=True),
'total_attendance': fields.float('Attendance', readonly=True),
'total_difference': fields.float('Difference', readonly=True),
}
_depends = {
'account.analytic.line': ['date', 'unit_amount'],
'hr.attendance': ['action', 'name', 'sheet_id'],
}
def init(self, cr):
cr.execute("""create or replace view hr_timesheet_sheet_sheet_day as
SELECT
id,
name,
sheet_id,
total_timesheet,
total_attendance,
cast(round(cast(total_attendance - total_timesheet as Numeric),2) as Double Precision) AS total_difference
FROM
((
SELECT
MAX(id) as id,
name,
sheet_id,
timezone,
SUM(total_timesheet) as total_timesheet,
CASE WHEN SUM(orphan_attendances) != 0
THEN (SUM(total_attendance) +
CASE WHEN current_date <> name
THEN 1440
ELSE (EXTRACT(hour FROM current_time AT TIME ZONE 'UTC' AT TIME ZONE coalesce(timezone, 'UTC')) * 60) + EXTRACT(minute FROM current_time AT TIME ZONE 'UTC' AT TIME ZONE coalesce(timezone, 'UTC'))
END
)
ELSE SUM(total_attendance)
END /60 as total_attendance
FROM
((
select
min(l.id) as id,
p.tz as timezone,
l.date::date as name,
s.id as sheet_id,
sum(l.unit_amount) as total_timesheet,
0 as orphan_attendances,
0.0 as total_attendance
from
account_analytic_line l
LEFT JOIN hr_timesheet_sheet_sheet s ON s.id = l.sheet_id
JOIN hr_employee e ON s.employee_id = e.id
JOIN resource_resource r ON e.resource_id = r.id
LEFT JOIN res_users u ON r.user_id = u.id
LEFT JOIN res_partner p ON u.partner_id = p.id
group by l.date::date, s.id, timezone
) union (
select
-min(a.id) as id,
p.tz as timezone,
(a.name AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC'))::date as name,
s.id as sheet_id,
0.0 as total_timesheet,
SUM(CASE WHEN a.action = 'sign_in' THEN -1 ELSE 1 END) as orphan_attendances,
SUM(((EXTRACT(hour FROM (a.name AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC'))) * 60) + EXTRACT(minute FROM (a.name AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC')))) * (CASE WHEN a.action = 'sign_in' THEN -1 ELSE 1 END)) as total_attendance
from
hr_attendance a
LEFT JOIN hr_timesheet_sheet_sheet s
ON s.id = a.sheet_id
JOIN hr_employee e
ON a.employee_id = e.id
JOIN resource_resource r
ON e.resource_id = r.id
LEFT JOIN res_users u
ON r.user_id = u.id
LEFT JOIN res_partner p
ON u.partner_id = p.id
WHERE action in ('sign_in', 'sign_out')
group by (a.name AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC'))::date, s.id, timezone
)) AS foo
GROUP BY name, sheet_id, timezone
)) AS bar""")
class hr_timesheet_sheet_sheet_account(osv.osv):
class HrTimesheetSheetSheetAccount(models.Model):
_name = "hr_timesheet_sheet.sheet.account"
_description = "Timesheets by Period"
_auto = False
_order='name'
_columns = {
'name': fields.many2one('account.analytic.account', 'Project / Analytic Account', readonly=True),
'sheet_id': fields.many2one('hr_timesheet_sheet.sheet', 'Sheet', readonly=True),
'total': fields.float('Total Time', digits=(16,2), readonly=True),
}
_order = 'name'
name = fields.Many2one('account.analytic.account', string='Project / Analytic Account', readonly=True)
sheet_id = fields.Many2one('hr_timesheet_sheet.sheet', string='Sheet', readonly=True)
total = fields.Float('Total Time', digits=(16, 2), readonly=True)
# still seing _depends in BaseModel, ok to leave this as is?
_depends = {
'account.analytic.line': ['account_id', 'date', 'unit_amount', 'user_id'],
'hr_timesheet_sheet.sheet': ['date_from', 'date_to', 'user_id'],
}
def init(self, cr):
cr.execute("""create or replace view hr_timesheet_sheet_sheet_account as (
@api.model_cr
def init(self):
self._cr.execute("""create or replace view hr_timesheet_sheet_sheet_account as (
select
min(l.id) as id,
l.account_id as name,
@@ -1,19 +1,15 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import fields, osv
from odoo import fields, models
class res_company(osv.osv):
class ResCompany(models.Model):
_inherit = 'res.company'
_columns = {
'timesheet_range': fields.selection([('week','Week'),('month','Month')], 'Timesheet range',
help="Periodicity on which you validate your timesheets."),
'timesheet_max_difference': fields.float('Timesheet allowed difference(Hours)',
timesheet_range = fields.Selection([('week', 'Week'), ('month', 'Month')],
default='week', string='Timesheet range', help="Periodicity on which you validate your timesheets.")
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."),
}
_defaults = {
'timesheet_range': lambda *args: 'week',
'timesheet_max_difference': lambda *args: 0.0
}
"computation for one sheet. Set this to 0 if you do not want any control.",
default=0.0)
@@ -1,5 +1,5 @@
<?xml version="1.0"?>
<openerp>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record model="ir.rule" id="timesheet_comp_rule">
@@ -10,4 +10,4 @@
</record>
</data>
</openerp>
</odoo>
@@ -1,6 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hr_timesheet_sheet_sheet_user,hr_timesheet_sheet.sheet.user,model_hr_timesheet_sheet_sheet,base.group_user,1,1,1,1
access_hr_timesheet_sheet_sheet_system_employee,hr_timesheet_sheet.sheet.system.employee,model_hr_timesheet_sheet_sheet,base.group_user,1,1,1,0
access_hr_timesheet_sheet_sheet_day,hr_timesheet_sheet.sheet.day,model_hr_timesheet_sheet_sheet_day,base.group_hr_user,1,1,1,1
access_hr_timesheet_sheet_sheet_account,hr_timesheet_sheet.sheet.account,model_hr_timesheet_sheet_sheet_account,base.group_hr_user,1,1,1,1
access_hr_timesheet_sheet_sheet_day,hr.timesheet.sheet.sheet.day.user,model_hr_timesheet_sheet_sheet_day,base.group_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_hr_timesheet_sheet_sheet_user hr_timesheet_sheet.sheet.user model_hr_timesheet_sheet_sheet base.group_user 1 1 1 1
3 access_hr_timesheet_sheet_sheet_system_employee hr_timesheet_sheet.sheet.system.employee model_hr_timesheet_sheet_sheet base.group_user 1 1 1 0
access_hr_timesheet_sheet_sheet_day hr_timesheet_sheet.sheet.day model_hr_timesheet_sheet_sheet_day base.group_hr_user 1 1 1 1
4 access_hr_timesheet_sheet_sheet_account hr_timesheet_sheet.sheet.account model_hr_timesheet_sheet_sheet_account base.group_hr_user 1 1 1 1
access_hr_timesheet_sheet_sheet_day hr.timesheet.sheet.sheet.day.user model_hr_timesheet_sheet_sheet_day base.group_user 1 1 1 0
@@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!--Hr Department Inherit Kanban view-->
<record id="hr_department_view_kanban" model="ir.ui.view">
@@ -1,11 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<template id="assets_backend" name="hr_timesheet_sheet assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/hr_timesheet_sheet/static/src/less/timesheet.less"/>
<script type="text/javascript" src="/hr_timesheet_sheet/static/src/js/timesheet.js"></script>
</xpath>
</template>
</data>
</openerp>
</odoo>
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<record id="hr_timesheet_account_form" model="ir.ui.view">
<field name="name">hr.timesheet.account.form</field>
@@ -49,13 +48,13 @@
</record>
<record model="ir.actions.act_window" id="act_hr_timesheet_sheet_sheet_2_hr_attendance">
<!-- <record model="ir.actions.act_window" id="act_hr_timesheet_sheet_sheet_2_hr_attendance">
<field name="context">{'search_default_sheet_id': [active_id]}</field>
<field name="name">Attendances</field>
<field name="res_model">hr.attendance</field>
<field name="src_model">hr_timesheet_sheet.sheet</field>
</record>
-->
<record id="hr_timesheet_sheet_tree_simplified" model="ir.ui.view">
<field name="name">hr.timesheet.sheet.tree</field>
<field name="model">hr_timesheet_sheet.sheet</field>
@@ -65,9 +64,9 @@
<field name="date_from"/>
<field name="date_to"/>
<field name="department_id" invisible="1"/>
<field name="total_attendance" widget="float_time" groups="base.group_hr_attendance"/>
<!-- <field name="total_attendance" widget="float_time" groups="base.group_hr_attendance"/>
<field name="total_timesheet" widget="float_time"/>
<field name="total_difference" widget="float_time" groups="base.group_hr_attendance"/>
<field name="total_difference" widget="float_time" groups="base.group_hr_attendance"/> -->
<field name="state"/>
<field name="message_needaction" invisible="1"/>
</tree>
@@ -87,7 +86,7 @@
<field name="state" widget="statusbar" statusbar_visible="new,confirm,done"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<!-- <div class="oe_button_box" name="button_box">
<button type="action"
name="%(act_hr_timesheet_sheet_sheet_2_hr_analytic_timesheet)d"
class="oe_stat_button"
@@ -99,10 +98,10 @@
</button>
<button type="action" groups="base.group_hr_attendance"
class="oe_stat_button" icon="fa-users"
name="%(act_hr_timesheet_sheet_sheet_2_hr_attendance)d">
name="% ( act_hr_timesheet_sheet_sheet_2_hr_attendance ) d">
<field name="attendance_count" widget="statinfo" string="Attendances"/>
</button>
</div>
</div> -->
<div class="oe_title">
<label for="employee_id" class="oe_edit_only"/>
<h1><field name="employee_id" on_change="onchange_employee_id(employee_id)" class="oe_inline" placeholder="Employee's Name"/></h1>
@@ -141,7 +140,7 @@
</group>
</form>
</field>
</page>
</page><!--
<page string="Attendances" groups="base.group_hr_attendance">
<group>
<group>
@@ -165,7 +164,7 @@
<field name="total_difference" widget="float_time" sum="Differences"/>
</tree>
</field>
</page>
</page> -->
</notebook>
</sheet>
<div class="oe_chatter">
@@ -309,7 +308,7 @@
<!--
hr.attendance inheritancy
-->
<!--
<record model="ir.ui.view" id="view_hr_attendance_filter">
<field name="name">view_hr_attendance_filter</field>
<field name="model">hr.attendance</field>
@@ -380,7 +379,7 @@
<field name="total_difference" widget="float_time" sum="Total Difference" groups="base.group_hr_attendance"/>
</tree>
</field>
</record>
</record> -->
<!-- Timesheet Button on Employee Form -->
@@ -421,5 +420,4 @@
</field>
</record>
</data>
</openerp>
</odoo>
@@ -1,28 +1,24 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
from openerp.osv import fields, osv
from openerp.tools.translate import _
from openerp.exceptions import UserError
from odoo import api, fields, models, _
class hr_timesheet_current_open(osv.osv_memory):
class HrTimesheetCurrentOpen(models.TransientModel):
_name = 'hr.timesheet.current.open'
_description = 'hr.timesheet.current.open'
def open_timesheet(self, cr, uid, ids, context=None):
ts = self.pool.get('hr_timesheet_sheet.sheet')
if context is None:
context = {}
@api.model
def open_timesheet(self):
view_type = 'form,tree'
ids = ts.search(cr, uid, [('user_id','=',uid),('state','in',('draft','new')),('date_from','<=',time.strftime('%Y-%m-%d')), ('date_to','>=',time.strftime('%Y-%m-%d'))], context=context)
if len(ids) > 1:
sheets = self.env['hr_timesheet_sheet.sheet'].search([('user_id', '=', self._uid),
('state', 'in', ('draft', 'new')),
('date_from', '<=', fields.Date.today()),
('date_to', '>=', fields.Date.today())])
if len(sheets) > 1:
view_type = 'tree,form'
domain = "[('id','in',["+','.join(map(str, ids))+"]),('user_id', '=', uid)]"
elif len(ids)==1:
domain = "[('user_id', '=', uid)]"
domain = "[('id', 'in', " + str(sheets.ids) + "),('user_id', '=', uid)]"
else:
domain = "[('user_id', '=', uid)]"
value = {
@@ -34,6 +30,6 @@ class hr_timesheet_current_open(osv.osv_memory):
'view_id': False,
'type': 'ir.actions.act_window'
}
if len(ids) == 1:
value['res_id'] = ids[0]
if len(sheets) == 1:
value['res_id'] = sheets.ids[0]
return value