From 27082bbbf32e75741bf9db944886caa6fa818785 Mon Sep 17 00:00:00 2001 From: "Denis Vermylen (dve)" Date: Wed, 29 Jun 2016 13:54:43 +0200 Subject: [PATCH] [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 --- addons/hr_timesheet_attendance/__openerp__.py | 4 +- .../report/hr_timesheet_attendance_report.py | 78 +-- addons/hr_timesheet_sheet/__init__.py | 2 +- addons/hr_timesheet_sheet/__openerp__.py | 17 +- .../data/hr_timesheet_sheet_data.xml | 6 +- .../data/hr_timesheet_workflow.xml | 6 +- .../models/account_analytic_line.py | 100 ++- .../models/hr_attendance.py | 157 ++--- .../models/hr_department.py | 4 +- .../hr_timesheet_sheet/models/hr_employee.py | 22 +- .../models/hr_timesheet_sheet.py | 576 ++++++++---------- .../hr_timesheet_sheet/models/res_company.py | 20 +- .../security/hr_timesheet_sheet_security.xml | 6 +- .../security/ir.model.access.csv | 2 - .../views/hr_department_views.xml | 1 + .../views/hr_timesheet_sheet_templates.xml | 6 +- .../views/hr_timesheet_sheet_views.xml | 28 +- .../wizard/hr_timesheet_current.py | 30 +- 18 files changed, 473 insertions(+), 592 deletions(-) diff --git a/addons/hr_timesheet_attendance/__openerp__.py b/addons/hr_timesheet_attendance/__openerp__.py index e8f66a2284e..d02ba711157 100644 --- a/addons/hr_timesheet_attendance/__openerp__.py +++ b/addons/hr_timesheet_attendance/__openerp__.py @@ -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, } diff --git a/addons/hr_timesheet_attendance/report/hr_timesheet_attendance_report.py b/addons/hr_timesheet_attendance/report/hr_timesheet_attendance_report.py index c93ea4abf1d..8cd405f7ca5 100644 --- a/addons/hr_timesheet_attendance/report/hr_timesheet_attendance_report.py +++ b/addons/hr_timesheet_attendance/report/hr_timesheet_attendance_report.py @@ -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) diff --git a/addons/hr_timesheet_sheet/__init__.py b/addons/hr_timesheet_sheet/__init__.py index f6e33fef8f5..7c5a17f1dfd 100644 --- a/addons/hr_timesheet_sheet/__init__.py +++ b/addons/hr_timesheet_sheet/__init__.py @@ -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 diff --git a/addons/hr_timesheet_sheet/__openerp__.py b/addons/hr_timesheet_sheet/__openerp__.py index 68e387df4d5..5b88a0a5446 100644 --- a/addons/hr_timesheet_sheet/__openerp__.py +++ b/addons/hr_timesheet_sheet/__openerp__.py @@ -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', ], } diff --git a/addons/hr_timesheet_sheet/data/hr_timesheet_sheet_data.xml b/addons/hr_timesheet_sheet/data/hr_timesheet_sheet_data.xml index ae4a13efa2e..c987acaf52d 100644 --- a/addons/hr_timesheet_sheet/data/hr_timesheet_sheet_data.xml +++ b/addons/hr_timesheet_sheet/data/hr_timesheet_sheet_data.xml @@ -1,5 +1,5 @@ - - + + @@ -53,4 +53,4 @@ - + diff --git a/addons/hr_timesheet_sheet/data/hr_timesheet_workflow.xml b/addons/hr_timesheet_sheet/data/hr_timesheet_workflow.xml index 01c99b1c2a0..0f2e61f012a 100644 --- a/addons/hr_timesheet_sheet/data/hr_timesheet_workflow.xml +++ b/addons/hr_timesheet_sheet/data/hr_timesheet_workflow.xml @@ -1,6 +1,5 @@ - - + @@ -60,5 +59,4 @@ cancel - - + diff --git a/addons/hr_timesheet_sheet/models/account_analytic_line.py b/addons/hr_timesheet_sheet/models/account_analytic_line.py index fd98b7563d2..9493a1728b2 100644 --- a/addons/hr_timesheet_sheet/models/account_analytic_line.py +++ b/addons/hr_timesheet_sheet/models/account_analytic_line.py @@ -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 diff --git a/addons/hr_timesheet_sheet/models/hr_attendance.py b/addons/hr_timesheet_sheet/models/hr_attendance.py index 5fabc476df9..a3658ca2762 100644 --- a/addons/hr_timesheet_sheet/models/hr_attendance.py +++ b/addons/hr_timesheet_sheet/models/hr_attendance.py @@ -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 diff --git a/addons/hr_timesheet_sheet/models/hr_department.py b/addons/hr_timesheet_sheet/models/hr_department.py index 733b2361fd1..9942c1c116e 100644 --- a/addons/hr_timesheet_sheet/models/hr_department.py +++ b/addons/hr_timesheet_sheet/models/hr_department.py @@ -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 diff --git a/addons/hr_timesheet_sheet/models/hr_employee.py b/addons/hr_timesheet_sheet/models/hr_employee.py index 374a1763dbc..598489088fa 100644 --- a/addons/hr_timesheet_sheet/models/hr_employee.py +++ b/addons/hr_timesheet_sheet/models/hr_employee.py @@ -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)]) diff --git a/addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py index 48db1b92452..a90dc349c36 100644 --- a/addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py +++ b/addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py @@ -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, diff --git a/addons/hr_timesheet_sheet/models/res_company.py b/addons/hr_timesheet_sheet/models/res_company.py index 2128614fe64..46267b9b8a2 100644 --- a/addons/hr_timesheet_sheet/models/res_company.py +++ b/addons/hr_timesheet_sheet/models/res_company.py @@ -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 - } \ No newline at end of file + "computation for one sheet. Set this to 0 if you do not want any control.", + default=0.0) diff --git a/addons/hr_timesheet_sheet/security/hr_timesheet_sheet_security.xml b/addons/hr_timesheet_sheet/security/hr_timesheet_sheet_security.xml index 9ef8689f436..63adecca478 100644 --- a/addons/hr_timesheet_sheet/security/hr_timesheet_sheet_security.xml +++ b/addons/hr_timesheet_sheet/security/hr_timesheet_sheet_security.xml @@ -1,5 +1,5 @@ - - + + @@ -10,4 +10,4 @@ - + diff --git a/addons/hr_timesheet_sheet/security/ir.model.access.csv b/addons/hr_timesheet_sheet/security/ir.model.access.csv index d3185141612..48c01c5dc43 100644 --- a/addons/hr_timesheet_sheet/security/ir.model.access.csv +++ b/addons/hr_timesheet_sheet/security/ir.model.access.csv @@ -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 diff --git a/addons/hr_timesheet_sheet/views/hr_department_views.xml b/addons/hr_timesheet_sheet/views/hr_department_views.xml index fac145ae89d..c91f0ccc197 100644 --- a/addons/hr_timesheet_sheet/views/hr_department_views.xml +++ b/addons/hr_timesheet_sheet/views/hr_department_views.xml @@ -1,3 +1,4 @@ + diff --git a/addons/hr_timesheet_sheet/views/hr_timesheet_sheet_templates.xml b/addons/hr_timesheet_sheet/views/hr_timesheet_sheet_templates.xml index 1f6a905756d..bb72332d832 100644 --- a/addons/hr_timesheet_sheet/views/hr_timesheet_sheet_templates.xml +++ b/addons/hr_timesheet_sheet/views/hr_timesheet_sheet_templates.xml @@ -1,11 +1,9 @@ - - + - - + diff --git a/addons/hr_timesheet_sheet/views/hr_timesheet_sheet_views.xml b/addons/hr_timesheet_sheet/views/hr_timesheet_sheet_views.xml index a889cfc7d45..6ea6393b89e 100644 --- a/addons/hr_timesheet_sheet/views/hr_timesheet_sheet_views.xml +++ b/addons/hr_timesheet_sheet/views/hr_timesheet_sheet_views.xml @@ -1,6 +1,5 @@ - - + hr.timesheet.account.form @@ -49,13 +48,13 @@ - + hr.timesheet.sheet.tree hr_timesheet_sheet.sheet @@ -65,9 +64,9 @@ - + @@ -87,7 +86,7 @@ -
+