openerp-community-reviewer team mailing list archive
-
openerp-community-reviewer team
-
Mailing list archive
-
Message #01109
lp:~camptocamp/hr-timesheet/full-fill-timesheet-account-type-vre into lp:hr-timesheet
Vincent Renaville@camptocamp has proposed merging lp:~camptocamp/hr-timesheet/full-fill-timesheet-account-type-vre into lp:hr-timesheet.
Requested reviews:
HR Core Editors (hr-core-editors)
For more details, see:
https://code.launchpad.net/~camptocamp/hr-timesheet/full-fill-timesheet-account-type-vre/+merge/194368
change type from normal to contract to match project linked analytic account instead of standard analytic account
--
https://code.launchpad.net/~camptocamp/hr-timesheet/full-fill-timesheet-account-type-vre/+merge/194368
Your team HR Core Editors is requested to review the proposed merge of lp:~camptocamp/hr-timesheet/full-fill-timesheet-account-type-vre into lp:hr-timesheet.
=== added directory 'hr_attendance_analysis'
=== added file 'hr_attendance_analysis/AUTHORS.txt'
--- hr_attendance_analysis/AUTHORS.txt 1970-01-01 00:00:00 +0000
+++ hr_attendance_analysis/AUTHORS.txt 2013-11-07 15:51:52 +0000
@@ -0,0 +1,1 @@
+Lorenzo Battistini <lorenzo.battistini@xxxxxxxxxxx>
=== added file 'hr_attendance_analysis/__init__.py'
--- hr_attendance_analysis/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_attendance_analysis/__init__.py 2013-11-07 15:51:52 +0000
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (C) 2011 Domsense srl (<http://www.domsense.com>)
+# Copyright (C) 2011-2013 Agile Business Group sagl
+# (<http://www.agilebg.com>)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+import hr_attendance
+import hr_contract
+import report
+import wizard
+import resource
=== added file 'hr_attendance_analysis/__openerp__.py'
--- hr_attendance_analysis/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_attendance_analysis/__openerp__.py 2013-11-07 15:51:52 +0000
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (C) 2011 Domsense srl (<http://www.domsense.com>)
+# Copyright (C) 2011-2013 Agile Business Group sagl
+# (<http://www.agilebg.com>)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+{
+ 'name': "HR - Attendance Analysis",
+ 'version': '0.1',
+ 'category': 'Generic Modules/Human Resources',
+ 'summary': "Dynamic reports based on employee's attendances and contract's calendar",
+ 'description': """
+Dynamic reports based on employee's attendances and contract's calendar.
+Among other things, it lets you see the amount of working hours outside and inside the contract's working schedule (overtime).
+It also provides a daily based report, showing the detailed and total hours compared to calendar hours.
+Several analysis settings can be configured, like:
+ - Tolerance for sign-in and sign-out
+ - Attendances and overtimes roundings
+ - Diffrent types of overtime, according to the overtime amount
+""",
+ 'author': 'Agile Business Group',
+ 'website': 'http://www.agilebg.com',
+ 'license': 'AGPL-3',
+ "depends" : ['hr_attendance', 'hr_contract', 'hr_holidays', 'report_webkit'],
+ "data" : [
+ 'company_view.xml',
+ 'hr_attendance_view.xml',
+ 'reports.xml',
+ 'wizard/print_calendar_report.xml',
+ 'resource_view.xml',
+ 'security/ir.model.access.csv',
+ ],
+ "demo" : [],
+ "active": False,
+ "installable": True
+}
=== added file 'hr_attendance_analysis/company_view.xml'
--- hr_attendance_analysis/company_view.xml 1970-01-01 00:00:00 +0000
+++ hr_attendance_analysis/company_view.xml 2013-11-07 15:51:52 +0000
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<openerp>
+ <data>
+ <record id="view_company_form" model="ir.ui.view">
+ <field name="inherit_id" ref="base.view_company_form"/>
+ <field name="name">view.company.form</field>
+ <field name="model">res.company</field>
+ <field name="arch" type="xml">
+ <page string="Configuration" position="inside">
+ <group name="attendance_analysis_grp" string="Attendance analysis">
+ <field name="working_time_precision" widget="float_time"/>
+ </group>
+ </page>
+ </field>
+ </record>
+ </data>
+</openerp>
=== added file 'hr_attendance_analysis/hr_attendance.py'
--- hr_attendance_analysis/hr_attendance.py 1970-01-01 00:00:00 +0000
+++ hr_attendance_analysis/hr_attendance.py 2013-11-07 15:51:52 +0000
@@ -0,0 +1,419 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (C) 2011 Domsense srl (<http://www.domsense.com>)
+# Copyright (C) 2011-2013 Agile Business Group sagl
+# (<http://www.agilebg.com>)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from __future__ import division
+from openerp.osv import fields, orm
+from openerp.tools.translate import _
+import time
+from datetime import *
+import math
+
+import pytz
+
+class res_company(orm.Model):
+
+ _inherit = 'res.company'
+
+ _columns = {
+ 'working_time_precision': fields.float('Working time precision', help='The precision used to analyse working times over working schedule (hh:mm)', required=True)
+ }
+
+ _defaults = {
+ 'working_time_precision': 1.0 / 60 # hours
+ }
+
+
+class hr_attendance(orm.Model):
+
+ # ref: https://bugs.launchpad.net/openobject-client/+bug/887612
+ # test: 0.9853 - 0.0085
+ def float_time_convert(self, float_val):
+ hours = math.floor(abs(float_val))
+ mins = abs(float_val) - hours
+ mins = round(mins * 60)
+ if mins >= 60.0:
+ hours = hours + 1
+ mins = 0.0
+ float_time = '%02d:%02d' % (hours,mins)
+ return float_time
+
+ def float_to_datetime(self, float_val):
+ str_float = self.float_time_convert(float_val)
+ hours = int(str_float.split(':')[0])
+ minutes = int(str_float.split(':')[1])
+ days = 1
+ if hours / 24 > 0:
+ days += hours / 24
+ hours = hours % 24
+ return datetime(1900, 1, int(days), hours, minutes)
+
+ def float_to_timedelta(self, float_val):
+ str_time = self.float_time_convert(float_val)
+ return timedelta(0, int(str_time.split(':')[0]) * 60.0*60.0
+ + int(str_time.split(':')[1]) * 60.0)
+
+ def total_seconds(self, td):
+ return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
+
+ def time_difference(self, float_start_time, float_end_time):
+ if float_end_time < float_start_time:
+ raise orm.except_orm(_('Error'), _('End time %s < start time %s')
+ % (str(float_end_time),str(float_start_time)))
+ delta = self.float_to_datetime(float_end_time) - self.float_to_datetime(
+ float_start_time)
+ return self.total_seconds(delta) / 60.0 / 60.0
+
+ def time_sum(self, float_first_time, float_second_time):
+ str_first_time = self.float_time_convert(float_first_time)
+ first_timedelta = timedelta(0, int(str_first_time.split(':')[0]) * 60.0*60.0 +
+ int(str_first_time.split(':')[1]) * 60.0)
+ str_second_time = self.float_time_convert(float_second_time)
+ second_timedelta = timedelta(0, int(str_second_time.split(':')[0]) * 60.0*60.0 +
+ int(str_second_time.split(':')[1]) * 60.0)
+ return self.total_seconds(first_timedelta + second_timedelta) / 60.0 / 60.0
+
+ def _split_long_attendances(self, start_datetime, duration):
+ # start_datetime: datetime, duration: hours
+ # returns [(datetime, hours)]
+ res = []
+ if duration > 24:
+ res.append((start_datetime, 24))
+ res.extend(self._split_long_attendances(
+ start_datetime + timedelta(1), duration - 24))
+ else:
+ res.append((start_datetime, duration))
+ return res
+
+ def _split_no_recursive_attendance(self, start_datetime, duration, precision=0.25):
+ # start_datetime: datetime, duration: hours, precision: hours
+ # returns [(datetime, hours)]
+ res = []
+ while (duration > precision):
+ res.append((start_datetime, precision))
+ start_datetime += timedelta(days=0, seconds=0, microseconds=0, milliseconds=0,
+ minutes=0, hours=precision)
+ duration -= precision
+ if duration > precision / 2.0:
+ res.append((start_datetime, precision))
+ return res
+
+ def _split_attendance(self, start_datetime, duration, precision=0.25):
+ # start_datetime: datetime, duration: hours, precision: hours
+ # returns [(datetime, hours)]
+ res = []
+ if duration > precision:
+ res.append((start_datetime, precision))
+ res.extend(self._split_attendance(start_datetime + timedelta(0,0,0,0,0,precision), duration - precision, precision))
+ elif duration > precision / 2.0:
+ res.append((start_datetime, precision))
+ return res
+
+ def get_active_contracts(self, cr, uid, employee_id, date=datetime.now().strftime('%Y-%m-%d')):
+ contract_pool = self.pool.get('hr.contract')
+ active_contract_ids= contract_pool.search(cr, uid, [
+ '&',
+ ('employee_id', '=', employee_id),
+ '|',
+ '&',
+ ('date_start', '<=', date),
+ '|',
+ ('date_end', '>=', date),
+ ('date_end', '=', False),
+ '&',
+ '&',
+ ('trial_date_start', '!=', False),
+ ('trial_date_start', '<=', date),
+ '&',
+ ('trial_date_end', '!=', False),
+ ('trial_date_end', '>=', date),
+ ])
+ if len(active_contract_ids) > 1:
+ employee = self.pool.get('hr.employee').browse(cr,uid,employee_id)
+ raise orm.except_orm(_('Error'), _(
+ 'Too many active contracts for employee %s'
+ ) % employee.name)
+ return active_contract_ids
+
+ def _ceil_rounding(self, rounding, datetime):
+ minutes = (datetime.minute / 60.0
+ + datetime.second / 60.0 / 60.0)
+ return math.ceil(minutes * rounding) / rounding
+
+ def _floor_rounding(self, rounding, datetime):
+ minutes = (datetime.minute / 60.0
+ + datetime.second / 60.0 / 60.0)
+ return math.floor(minutes * rounding) / rounding
+
+ def _get_attendance_duration(self, cr, uid, ids, field_name, arg, context=None):
+ res = {}
+ contract_pool = self.pool.get('hr.contract')
+ resource_pool = self.pool.get('resource.resource')
+ attendance_pool = self.pool.get('resource.calendar.attendance')
+ precision = self.pool.get('res.users').browse(cr, uid, uid).company_id.working_time_precision
+ # 2012.10.16 LF FIX : Get timezone from context
+ active_tz = pytz.timezone(context.get("tz","UTC") if context else "UTC")
+ str_now = datetime.strftime(datetime.now(), '%Y-%m-%d %H:%M:%S')
+ for attendance_id in ids:
+ duration = 0.0
+ outside_calendar_duration = 0.0
+ inside_calendar_duration = 0.0
+ attendance = self.browse(cr, uid, attendance_id)
+ res[attendance.id] = {}
+ # 2012.10.16 LF FIX : Attendance in context timezone
+ attendance_start = datetime.strptime(
+ attendance.name, '%Y-%m-%d %H:%M:%S'
+ ).replace(tzinfo=pytz.utc).astimezone(active_tz)
+ next_attendance_date = str_now
+ next_attendance_ids = False
+ # should we compute for sign out too?
+ if attendance.action == 'sign_in':
+ next_attendance_ids = self.search(cr, uid, [
+ ('employee_id', '=', attendance.employee_id.id),
+ ('name', '>', attendance.name)], order='name')
+ if next_attendance_ids:
+ next_attendance = self.browse(cr, uid, next_attendance_ids[0])
+ if next_attendance.action == 'sign_in':
+ # 2012.10.16 LF FIX : Attendance in context timezone
+ raise orm.except_orm(_('Error'), _(
+ 'Incongruent data: sign-in %s is followed by another sign-in'
+ ) % attendance_start)
+ next_attendance_date = next_attendance.name
+ # 2012.10.16 LF FIX : Attendance in context timezone
+ attendance_stop = datetime.strptime(
+ next_attendance_date, '%Y-%m-%d %H:%M:%S'
+ ).replace(tzinfo=pytz.utc).astimezone(active_tz)
+ duration_delta = attendance_stop - attendance_start
+ duration = self.total_seconds(duration_delta) / 60.0 / 60.0
+ duration = round(duration / precision) * precision
+ res[attendance.id]['duration'] = duration
+ res[attendance.id]['end_datetime'] = next_attendance_date
+ # If contract is not specified: working days = 24/7
+ res[attendance.id]['inside_calendar_duration'] = duration
+ res[attendance.id]['outside_calendar_duration'] = 0.0
+
+ active_contract_ids = self.get_active_contracts(
+ cr, uid, attendance.employee_id.id, date=str_now[:10])
+
+ if active_contract_ids and next_attendance_ids:
+ contract = contract_pool.browse(cr, uid, active_contract_ids[0])
+ if contract.working_hours:
+ # TODO applicare prima arrotondamento o tolleranza?
+ if contract.working_hours.attendance_rounding:
+ float_attendance_rounding = float(contract.working_hours.attendance_rounding)
+ rounded_start_hour = self._ceil_rounding(
+ float_attendance_rounding, attendance_start)
+ rounded_stop_hour = self._floor_rounding(
+ float_attendance_rounding, attendance_stop)
+
+ if abs(1- rounded_start_hour) < 0.01: # if shift == 1 hour
+ attendance_start = datetime(attendance_start.year, attendance_start.month,
+ attendance_start.day, attendance_start.hour + 1)
+ else:
+ attendance_start = datetime(attendance_start.year, attendance_start.month,
+ attendance_start.day, attendance_start.hour, int(round(rounded_start_hour * 60.0)))
+
+ attendance_stop = datetime(attendance_stop.year, attendance_stop.month,
+ attendance_stop.day, attendance_stop.hour,
+ int(round(rounded_stop_hour * 60.0)))
+
+ # again
+ duration_delta = attendance_stop - attendance_start
+ duration = self.total_seconds(duration_delta) / 60.0 / 60.0
+ duration = round(duration / precision) * precision
+ res[attendance.id]['duration'] = duration
+
+ res[attendance.id]['inside_calendar_duration'] = 0.0
+ res[attendance.id]['outside_calendar_duration'] = 0.0
+ calendar_id = contract.working_hours.id
+ intervals_within = 0
+
+ # split attendance in intervals = precision
+ # 2012.10.16 LF FIX : no recursion in split attendance
+ splitted_attendances = self._split_no_recursive_attendance(
+ attendance_start, duration, precision)
+ counter = 0
+ for atomic_attendance in splitted_attendances:
+ counter += 1
+ centered_attendance = atomic_attendance[0] + timedelta(
+ 0,0,0,0,0, atomic_attendance[1] / 2.0)
+ centered_attendance_hour = (
+ centered_attendance.hour + centered_attendance.minute / 60.0
+ + centered_attendance.second / 60.0 / 60.0
+ )
+ # check if centered_attendance is within a working schedule
+ # 2012.10.16 LF FIX : weekday must be single character not int
+ weekday_char = str(unichr(centered_attendance.weekday() + 48))
+ matched_schedule_ids = attendance_pool.search(cr, uid, [
+ '&',
+ '|',
+ ('date_from', '=', False),
+ ('date_from','<=',centered_attendance.date()),
+ '|',
+ ('dayofweek', '=', False),
+ ('dayofweek','=',weekday_char),
+ ('calendar_id','=',calendar_id),
+ ('hour_to','>=',centered_attendance_hour),
+ ('hour_from','<=',centered_attendance_hour),
+ ])
+ if len(matched_schedule_ids) > 1:
+ raise orm.except_orm(_('Error'),
+ _('Wrongly configured working schedule with id %s') % str(calendar_id))
+ if matched_schedule_ids:
+ intervals_within += 1
+ # sign in tolerance
+ if intervals_within == 1:
+ calendar_attendance = attendance_pool.browse(cr, uid, matched_schedule_ids[0])
+ attendance_start_hour = (
+ attendance_start.hour + attendance_start.minute / 60.0
+ + attendance_start.second / 60.0 / 60.0
+ )
+ if attendance_start_hour >= (
+ calendar_attendance.hour_from and
+ (attendance_start_hour - (calendar_attendance.hour_from +
+ calendar_attendance.tolerance_to)) < 0.01
+ ): # handling float roundings (<=)
+ additional_intervals = round(
+ (attendance_start_hour - calendar_attendance.hour_from) / precision)
+ intervals_within += additional_intervals
+ res[attendance.id]['duration'] = self.time_sum(
+ res[attendance.id]['duration'], additional_intervals * precision)
+ # sign out tolerance
+ if len(splitted_attendances) == counter:
+ attendance_stop_hour = (
+ attendance_stop.hour + attendance_stop.minute / 60.0
+ + attendance_stop.second / 60.0 / 60.0
+ )
+ calendar_attendance = attendance_pool.browse(cr, uid, matched_schedule_ids[0])
+ if attendance_stop_hour <= (
+ calendar_attendance.hour_to and
+ (attendance_stop_hour - (calendar_attendance.hour_to -
+ calendar_attendance.tolerance_from)) > -0.01
+ ): # handling float roundings (>=)
+ additional_intervals = round(
+ (calendar_attendance.hour_to - attendance_stop_hour) / precision)
+ intervals_within += additional_intervals
+ res[attendance.id]['duration'] = self.time_sum(
+ res[attendance.id]['duration'], additional_intervals * precision)
+
+ res[attendance.id]['inside_calendar_duration'] = intervals_within * precision
+ # make difference using time in order to avoid rounding errors
+ # inside_calendar_duration can't be > duration
+ res[attendance.id]['outside_calendar_duration'] = self.time_difference(
+ res[attendance.id]['inside_calendar_duration'],
+ res[attendance.id]['duration'])
+
+ if contract.working_hours.overtime_rounding:
+ if res[attendance.id]['outside_calendar_duration']:
+ overtime = res[attendance.id]['outside_calendar_duration']
+ if contract.working_hours.overtime_rounding_tolerance:
+ overtime = self.time_sum(overtime,
+ contract.working_hours.overtime_rounding_tolerance)
+ float_overtime_rounding = float(contract.working_hours.overtime_rounding)
+ res[attendance.id]['outside_calendar_duration'] = math.floor(
+ overtime * float_overtime_rounding) / float_overtime_rounding
+
+ return res
+
+ def _get_by_contracts(self, cr, uid, ids, context=None):
+ attendance_ids = []
+ attendance_pool = self.pool.get('hr.attendance')
+ for contract in self.pool.get('hr.contract').browse(cr, uid, ids, context=context):
+ att_ids = attendance_pool.search(cr, uid, [('employee_id', '=', contract.employee_id.id)])
+ for att_id in att_ids:
+ if att_id not in attendance_ids:
+ attendance_ids.append(att_id)
+ return attendance_ids
+
+ def _get_by_calendars(self, cr, uid, ids, context=None):
+ attendance_ids = []
+ attendance_pool = self.pool.get('hr.attendance')
+ contract_pool = self.pool.get('hr.contract')
+ for calendar in self.pool.get('resource.calendar').browse(cr, uid, ids, context=context):
+ contract_ids = contract_pool.search(cr, uid, [('working_hours', '=', calendar.id)])
+ att_ids = attendance_pool._get_by_contracts(cr, uid, contract_ids, context=None)
+ for att_id in att_ids:
+ if att_id not in attendance_ids:
+ attendance_ids.append(att_id)
+ return attendance_ids
+
+ def _get_by_calendar_attendances(self, cr, uid, ids, context=None):
+ attendance_ids = []
+ attendance_pool = self.pool.get('hr.attendance')
+ for calendar_attendance in self.pool.get('resource.calendar.attendance').browse(cr, uid, ids, context=context):
+ att_ids = attendance_pool._get_by_calendars(cr, uid, [calendar_attendance.calendar_id.id], context=None)
+ for att_id in att_ids:
+ if att_id not in attendance_ids:
+ attendance_ids.append(att_id)
+ return attendance_ids
+
+ def _get_attendances(self, cr, uid, ids, context=None):
+ attendance_ids = []
+ for attendance in self.browse(cr, uid, ids, context=context):
+ if attendance.action == 'sign_in' and attendance.id not in attendance_ids:
+ attendance_ids.append(attendance.id)
+ elif attendance.action == 'sign_out':
+ previous_attendance_ids = self.search(cr, uid, [
+ ('employee_id', '=', attendance.employee_id.id),
+ ('name', '<', attendance.name),
+ ('action', '=', 'sign_in'),
+ ], order='name')
+ if previous_attendance_ids and previous_attendance_ids[len(previous_attendance_ids) - 1] not in attendance_ids:
+ attendance_ids.append(previous_attendance_ids[len(previous_attendance_ids) - 1])
+ return attendance_ids
+
+ _inherit = "hr.attendance"
+
+ _columns = {
+ 'duration': fields.function(_get_attendance_duration, method=True, multi='duration', string="Attendance duration",
+ store={
+ 'hr.attendance': (_get_attendances, ['name', 'action', 'employee_id'], 20),
+ 'hr.contract': (_get_by_contracts, ['employee_id', 'date_start', 'date_end', 'trial_date_start', 'trial_date_end', 'working_hours'], 20),
+ 'resource.calendar': (_get_by_calendars, ['attendance_ids'], 20),
+ 'resource.calendar.attendance': (_get_by_calendar_attendances, ['dayofweek', 'date_from', 'hour_from', 'hour_to', 'calendar_id'], 20),
+ }
+ ),
+ 'end_datetime': fields.function(_get_attendance_duration, method=True, multi='duration', type="datetime", string="End date time",
+ store={
+ 'hr.attendance': (_get_attendances, ['name', 'action', 'employee_id'], 20),
+ 'hr.contract': (_get_by_contracts, ['employee_id', 'date_start', 'date_end', 'trial_date_start', 'trial_date_end', 'working_hours'], 20),
+ 'resource.calendar': (_get_by_calendars, ['attendance_ids'], 20),
+ 'resource.calendar.attendance': (_get_by_calendar_attendances, ['dayofweek', 'date_from', 'hour_from', 'hour_to', 'calendar_id'], 20),
+ }),
+ 'outside_calendar_duration': fields.function(_get_attendance_duration, method=True, multi='duration',
+ string="Overtime",
+ store={
+ 'hr.attendance': (_get_attendances, ['name', 'action', 'employee_id'], 20),
+ 'hr.contract': (_get_by_contracts, ['employee_id', 'date_start', 'date_end', 'trial_date_start', 'trial_date_end', 'working_hours'], 20),
+ 'resource.calendar': (_get_by_calendars, ['attendance_ids'], 20),
+ 'resource.calendar.attendance': (_get_by_calendar_attendances, ['dayofweek', 'date_from', 'hour_from', 'hour_to', 'calendar_id'], 20),
+ }),
+ 'inside_calendar_duration': fields.function(_get_attendance_duration, method=True, multi='duration',
+ string="Duration within working schedule",
+ store={
+ 'hr.attendance': (_get_attendances, ['name', 'action', 'employee_id'], 20),
+ 'hr.contract': (_get_by_contracts, ['employee_id', 'date_start', 'date_end', 'trial_date_start', 'trial_date_end', 'working_hours'], 20),
+ 'resource.calendar': (_get_by_calendars, ['attendance_ids'], 20),
+ 'resource.calendar.attendance': (_get_by_calendar_attendances, ['dayofweek', 'date_from', 'hour_from', 'hour_to', 'calendar_id'], 20),
+ }),
+ }
+
+
=== added file 'hr_attendance_analysis/hr_attendance_view.xml'
--- hr_attendance_analysis/hr_attendance_view.xml 1970-01-01 00:00:00 +0000
+++ hr_attendance_analysis/hr_attendance_view.xml 2013-11-07 15:51:52 +0000
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <record id="view_attendance_form" model="ir.ui.view">
+ <field name="name">hr.attendance.form</field>
+ <field name="model">hr.attendance</field>
+ <field name="inherit_id" ref="hr_attendance.view_attendance_form"></field>
+ <field name="arch" type="xml">
+ <field name="action_desc" position="after">
+ <field name="duration" widget="float_time"/>
+ <field name="outside_calendar_duration" widget="float_time"/>
+ <field name="inside_calendar_duration" widget="float_time" />
+ </field>
+ </field>
+ </record>
+
+ <record id="view_attendance_analysis" model="ir.ui.view">
+ <field name="name">hr.attendance.analysis</field>
+ <field name="model">hr.attendance</field>
+ <field name="priority" eval="17"/>
+ <field name="arch" type="xml">
+ <tree string="Employee attendances analysis">
+ <field name="employee_id" />
+ <field name="name" string="Start date time"/>
+ <field name="end_datetime"/>
+ <field name="duration" sum="Total hours" widget="float_time"/>
+ <field name="outside_calendar_duration" sum="Overtime" widget="float_time"/>
+ <field name="inside_calendar_duration" sum="Within working schedule" widget="float_time" />
+ <field name="day" invisible="1"/>
+ </tree>
+ </field>
+ </record>
+
+ <record model="ir.ui.view" id="view_hr_attendance_filter">
+ <field name="name">view_hr_attendance_filter</field>
+ <field name="model">hr.attendance</field>
+ <field name="arch" type="xml">
+ <search string="Hr Attendance Search">
+ <filter icon="terp-go-today" string="Today" name="today" domain="[('name::date','=',current_date)]" />
+ <separator orientation="vertical"/>
+ <field name="employee_id"/>
+ <field name="name" string="Start date time"/>
+ <field name="end_datetime"/>
+ <newline/>
+ <group expand="0" string="Group By...">
+ <filter name="employee" string="Employee" icon="terp-personal" domain="[]" context="{'group_by':'employee_id'}"/>
+ <separator orientation="vertical"/>
+ <filter string="Day" icon="terp-go-today" domain="[]" context="{'group_by':'day'}"/>
+ </group>
+ </search>
+ </field>
+ </record>
+
+ <record model="ir.ui.view" id="view_hr_attendance_calendar">
+ <field name="name">view_hr_attendance.calendar</field>
+ <field name="model">hr.attendance</field>
+ <field name="arch" type="xml">
+ <calendar string="Calendar View" date_start="name" date_stop="end_datetime" color="employee_id">
+ <field name="duration" />
+ <field name="outside_calendar_duration" />
+ <field name="inside_calendar_duration" />
+ </calendar>
+ </field>
+ </record>
+
+ <record id="open_view_attendance" model="ir.actions.act_window">
+ <field name="name">Attendances analysis</field>
+ <field name="res_model">hr.attendance</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form,calendar</field>
+ <field name="domain">[('action', '=', 'sign_in')]</field>
+ <field name="view_id" ref="view_attendance_analysis"/>
+ <field name="search_view_id" ref="view_hr_attendance_filter" />
+ </record>
+ <menuitem action="open_view_attendance" id="menu_open_view_attendance" parent="hr_attendance.menu_hr_attendance" groups="base.group_hr_manager"/>
+ <menuitem action="resource.action_resource_calendar_form" id="menu_view_resource_calendar" parent="hr_contract.next_id_56" sequence="1"/>
+
+ </data>
+</openerp>
=== added file 'hr_attendance_analysis/hr_contract.py'
--- hr_attendance_analysis/hr_contract.py 1970-01-01 00:00:00 +0000
+++ hr_attendance_analysis/hr_contract.py 2013-11-07 15:51:52 +0000
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Authors: Stéphane Bidoul & Laetitia Gangloff
+# Copyright (c) 2013 Acsone SA/NV (http://www.acsone.eu)
+# All Rights Reserved
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsibility of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs.
+# End users who are looking for a ready-to-use solution with commercial
+# guarantees and support are strongly advised to contact a Free Software
+# Service Company.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+import datetime
+
+from openerp.osv import fields, orm
+
+
+class hr_contract(orm.Model):
+ _inherit = 'hr.contract'
+
+ def copy(self, cr, uid, id, defaults, context=None):
+ """
+ When duplicate a contract set the start date to the last end date + 1 day.
+ If the last end date is False, do nothing
+ """
+ contract = self.browse(cr, uid, id, context=context)
+ end_date_contract_id = self.search(cr, uid, [('employee_id', '=', contract.employee_id.id), ], limit=1, order='date_end desc', context=context)
+ last_end_date = False
+ if end_date_contract_id:
+ contract = self.browse(cr, uid, end_date_contract_id, context=context)
+ last_end_date = contract[0].date_end
+
+ if last_end_date:
+ defaults['date_start'] = datetime.datetime.strftime(datetime.datetime.strptime(last_end_date, "%Y-%m-%d") + datetime.timedelta(days=1), "%Y-%m-%d")
+ defaults['date_end'] = False
+ defaults['trial_date_start'] = False
+ defaults['trial_date_end'] = False
+ return super(hr_contract, self).copy(cr, uid, id, defaults, context=context)
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
=== added directory 'hr_attendance_analysis/i18n'
=== added file 'hr_attendance_analysis/i18n/hr_attendance_analysis.pot'
--- hr_attendance_analysis/i18n/hr_attendance_analysis.pot 1970-01-01 00:00:00 +0000
+++ hr_attendance_analysis/i18n/hr_attendance_analysis.pot 2013-11-07 15:51:52 +0000
@@ -0,0 +1,564 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * hr_attendance_analysis
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.0.3\n"
+"Report-Msgid-Bugs-To: support@xxxxxxxxxxx\n"
+"POT-Creation-Date: 2011-12-23 10:03+0000\n"
+"PO-Revision-Date: 2011-12-23 10:03+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/hr_attendance.py:123
+#, python-format
+msgid "Incongruent data"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/hr_attendance.py:87
+#: code:addons/hr_attendance_analysis/hr_attendance.py:123
+#: code:addons/hr_attendance_analysis/hr_attendance.py:189
+#: code:addons/hr_attendance_analysis/wizard/print_calendar_report.py:58
+#: code:addons/hr_attendance_analysis/wizard/print_calendar_report.py:129
+#, python-format
+msgid "Error"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: help:res.company,working_time_precision:0
+msgid "The precision used to analyse working times over working schedule"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: view:hr.attendance:0
+msgid "Group By..."
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/report/calendar_report.py:47
+#, python-format
+msgid "Sunday"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: field:hr.attendance,end_datetime:0
+msgid "End date time"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: view:hr.attendance:0
+msgid "Today"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: view:hr.attendance:0
+msgid "Within working schedule"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "20"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/report/calendar_report.py:43
+#, python-format
+msgid "Friday"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: model:ir.model,name:hr_attendance_analysis.model_attendance_analysis_wizard_calendar_report
+msgid "attendance_analysis.wizard.calendar_report"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "8"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: view:hr.attendance:0
+msgid "Day"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:36
+msgid "Leave"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: field:resource.calendar.overtime.type,limit:0
+msgid "Limit"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "15"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: help:resource.calendar,attendance_rounding:0
+msgid "For instance, using rounding = 15 minutes, every sign in will be rounded to the following quarter hour and every sign out to the previous quarter hour"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:80
+msgid "Totals"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: help:resource.calendar.attendance,tolerance_to:0
+msgid "Sign in done in the interval \"Work from + Tolerance to\" will be considered done at \"Work from\""
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: field:resource.calendar.attendance,tolerance_from:0
+msgid "Tolerance from"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: field:attendance_analysis.wizard.calendar_report,from_date:0
+msgid "From date"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/hr_attendance.py:87
+#, python-format
+msgid "Too many active contracts for employee %s"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: view:hr.attendance:0
+msgid "Calendar View"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: view:resource.calendar:0
+msgid "Roundings"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:25
+msgid "Third Sign In"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: model:ir.model,name:hr_attendance_analysis.model_hr_attendance
+msgid "Attendance"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: constraint:res.company:0
+msgid "Error! You can not create recursive companies."
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:35
+msgid "Negative"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: view:hr.attendance:0
+msgid "Employee"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: view:resource.calendar:0
+msgid "Type"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: view:hr.attendance:0
+msgid "Hr Attendance Search"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: view:hr.attendance:0
+msgid "Start date time"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: help:resource.calendar,overtime_rounding:0
+msgid "Setting rounding = 30 minutes, an overtime of 29 minutes will be considered as 0 minutes, 31 minutes as 30 minutes, 61 minutes as 1 hour and so on"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: field:resource.calendar,attendance_rounding:0
+msgid "Attendance rounding"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "3"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: constraint:hr.attendance:0
+msgid "Error: Sign in (resp. Sign out) must follow Sign out (resp. Sign in)"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: view:res.company:0
+msgid "Configuration"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/wizard/print_calendar_report.py:58
+#, python-format
+msgid "From date must be < to date"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: view:hr.attendance:0
+msgid "Total hours"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: field:res.company,working_time_precision:0
+msgid "Working time precision"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: view:hr.attendance:0
+msgid "Employee attendances analysis"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: field:resource.calendar.attendance,tolerance_to:0
+msgid "Tolerance to"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: help:resource.calendar,leave_rounding:0
+msgid "On the contrary of overtime rounding, using rounding = 15 minutes, a leave of 1 minute will be considered as 15 minutes, 16 minutes as 30 minutes and so on"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: model:ir.actions.report.xml,name:hr_attendance_analysis.attendance_analysis_report_id
+msgid "Attendances Analysis"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "30"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:110
+#: model:ir.model,name:hr_attendance_analysis.model_resource_calendar_overtime_type
+msgid "Overtime type"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: field:hr.attendance,inside_calendar_duration:0
+msgid "Duration within working schedule"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: field:resource.calendar,leave_rounding:0
+msgid "Leave rounding"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: field:resource.calendar.overtime.type,calendar_id:0
+msgid "Calendar"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:32
+msgid "Due"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: view:attendance_analysis.wizard.calendar_report:0
+#: model:ir.actions.act_window,name:hr_attendance_analysis.action_wizard_calendar_report
+#: model:ir.ui.menu,name:hr_attendance_analysis.menu_action_wizard_calendar_report
+msgid "Attendances Analysis Calendar"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "60"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: view:resource.calendar:0
+#: field:resource.calendar,overtime_type_ids:0
+msgid "Overtime types"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/report/calendar_report.py:35
+#, python-format
+msgid "Monday"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: field:attendance_analysis.wizard.calendar_report,employee_ids:0
+msgid "unknown"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:18
+msgid "First Sign Out"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:15
+msgid "Day of week"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: field:resource.calendar,overtime_rounding_tolerance:0
+msgid "Overtime rounding tolerance"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:29
+msgid "Fourth Sign In"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/wizard/print_calendar_report.py:130
+#, python-format
+msgid "%s: 'Work to' is < 'Work from'"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "2"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:10
+msgid "Employee: "
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "6"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:14
+msgid "Date"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: model:ir.module.module,shortdesc:hr_attendance_analysis.module_meta_information
+msgid "HR - Attendance Analysis"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/hr_attendance.py:190
+#, python-format
+msgid "Wrongly configured working schedule with id %s"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: model:ir.model,name:hr_attendance_analysis.model_res_company
+msgid "Companies"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/report/calendar_report.py:39
+#, python-format
+msgid "Wednesday"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: field:resource.calendar.overtime.type,name:0
+msgid "Type Description"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:17
+msgid "First Sign In"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:30
+msgid "Fourth Sign Out"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: model:ir.module.module,description:hr_attendance_analysis.module_meta_information
+msgid "\n"
+"Dynamic reports based on employee's attendances and contract's calendar.\n"
+"Among other things, it lets you see the amount of working hours outside and inside the contract's working schedule (overtime).\n"
+"It also provides a daily based report, showing the detailed and total hours compared to calendar hours.\n"
+""
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "10"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "12"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:22
+msgid "Second Sign Out"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:34
+#: view:hr.attendance:0
+#: field:hr.attendance,outside_calendar_duration:0
+msgid "Overtime"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: model:ir.model,name:hr_attendance_analysis.model_resource_calendar_attendance
+msgid "Work Detail"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: help:resource.calendar.attendance,tolerance_from:0
+msgid "Sign out done in the interval \"Work to - Tolerance from\" will be considered done at \"Work to\""
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:21
+msgid "Second Sign In"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: view:attendance_analysis.wizard.calendar_report:0
+msgid "Cancel"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/report/calendar_report.py:37
+#, python-format
+msgid "Tuesday"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: model:ir.actions.act_window,name:hr_attendance_analysis.open_view_attendance
+#: model:ir.ui.menu,name:hr_attendance_analysis.menu_open_view_attendance
+msgid "Attendances analysis"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/report/calendar_report.py:41
+#, python-format
+msgid "Thursday"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: view:attendance_analysis.wizard.calendar_report:0
+msgid "Print"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: model:ir.model,name:hr_attendance_analysis.model_resource_calendar
+msgid "Resource Calendar"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: field:hr.attendance,duration:0
+msgid "Attendance duration"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "1"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: field:attendance_analysis.wizard.calendar_report,to_date:0
+msgid "To date"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "5"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:33
+msgid "Working Hours"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: view:resource.calendar:0
+msgid "Types"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: field:resource.calendar,overtime_rounding:0
+msgid "Overtime rounding"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: view:attendance_analysis.wizard.calendar_report:0
+msgid "Employees"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: help:resource.calendar.overtime.type,limit:0
+msgid "Limit, in hours, of overtime that can be imputed to this type of overtime in a day. The surplus is imputed to the subsequent type"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:26
+msgid "Third Sign Out"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: help:resource.calendar,overtime_rounding_tolerance:0
+msgid "Overtime can be rounded using a tolerance. Using tolerance = 3 minutes and rounding = 15 minutes, if employee does overtime of 12 minutes, it will be considered as 15 minutes."
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: field:resource.calendar.overtime.type,sequence:0
+msgid "Sequence"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:111
+msgid "Total"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/report/calendar_report.py:45
+#, python-format
+msgid "Saturday"
+msgstr ""
+
=== added file 'hr_attendance_analysis/i18n/it.po'
--- hr_attendance_analysis/i18n/it.po 1970-01-01 00:00:00 +0000
+++ hr_attendance_analysis/i18n/it.po 2013-11-07 15:51:52 +0000
@@ -0,0 +1,611 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * hr_attendance_analysis
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.0.3\n"
+"Report-Msgid-Bugs-To: support@xxxxxxxxxxx\n"
+"POT-Creation-Date: 2011-12-23 10:03+0000\n"
+"PO-Revision-Date: 2013-09-22 17:18+0000\n"
+"Last-Translator: Lorenzo Battistini - Agile BG "
+"<lorenzo.battistini@xxxxxxxxxxx>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2013-10-16 05:12+0000\n"
+"X-Generator: Launchpad (build 16799)\n"
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/hr_attendance.py:123
+#, python-format
+msgid "Incongruent data"
+msgstr ""
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/hr_attendance.py:87
+#: code:addons/hr_attendance_analysis/hr_attendance.py:123
+#: code:addons/hr_attendance_analysis/hr_attendance.py:189
+#: code:addons/hr_attendance_analysis/wizard/print_calendar_report.py:58
+#: code:addons/hr_attendance_analysis/wizard/print_calendar_report.py:129
+#, python-format
+msgid "Error"
+msgstr "Errore"
+
+#. module: hr_attendance_analysis
+#: help:res.company,working_time_precision:0
+msgid "The precision used to analyse working times over working schedule"
+msgstr ""
+"La precisione utilizzata per analizzare le ore lavorative rispetto al "
+"calendario di lavoro"
+
+#. module: hr_attendance_analysis
+#: view:hr.attendance:0
+msgid "Group By..."
+msgstr "Raggruppa per..."
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/report/calendar_report.py:47
+#, python-format
+msgid "Sunday"
+msgstr "Domenica"
+
+#. module: hr_attendance_analysis
+#: field:hr.attendance,end_datetime:0
+msgid "End date time"
+msgstr "Data e ora di fine"
+
+#. module: hr_attendance_analysis
+#: view:hr.attendance:0
+msgid "Today"
+msgstr "Oggi"
+
+#. module: hr_attendance_analysis
+#: view:hr.attendance:0
+msgid "Within working schedule"
+msgstr "All'interno del calendario di lavoro"
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "20"
+msgstr "20"
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/report/calendar_report.py:43
+#, python-format
+msgid "Friday"
+msgstr "Venerdì"
+
+#. module: hr_attendance_analysis
+#: model:ir.model,name:hr_attendance_analysis.model_attendance_analysis_wizard_calendar_report
+msgid "attendance_analysis.wizard.calendar_report"
+msgstr "attendance_analysis.wizard.calendar_report"
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "8"
+msgstr "8"
+
+#. module: hr_attendance_analysis
+#: view:hr.attendance:0
+msgid "Day"
+msgstr "Giorno"
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:36
+msgid "Leave"
+msgstr "Assenza"
+
+#. module: hr_attendance_analysis
+#: field:resource.calendar.overtime.type,limit:0
+msgid "Limit"
+msgstr "Limite"
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "15"
+msgstr "15"
+
+#. module: hr_attendance_analysis
+#: help:resource.calendar,attendance_rounding:0
+msgid ""
+"For instance, using rounding = 15 minutes, every sign in will be rounded to "
+"the following quarter hour and every sign out to the previous quarter hour"
+msgstr ""
+"Per esempio, utilizzando un arrotondamento = 15 minuti, ogni entrata verrà "
+"arrotondata al quarto d'ora successivo ed ogni uscita al quarto d'ora "
+"precedente"
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:80
+msgid "Totals"
+msgstr "Totali"
+
+#. module: hr_attendance_analysis
+#: help:resource.calendar.attendance,tolerance_to:0
+msgid ""
+"Sign in done in the interval \"Work from + Tolerance to\" will be considered "
+"done at \"Work from\""
+msgstr ""
+"Le entrate effettuate nell'intervallo \"Lavoro da + Tolleranza a\" saranno "
+"considerate come fatte in \"Lavoro da\""
+
+#. module: hr_attendance_analysis
+#: field:resource.calendar.attendance,tolerance_from:0
+msgid "Tolerance from"
+msgstr "Tolleranza da"
+
+#. module: hr_attendance_analysis
+#: field:attendance_analysis.wizard.calendar_report,from_date:0
+msgid "From date"
+msgstr "Dalla data"
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/hr_attendance.py:87
+#, python-format
+msgid "Too many active contracts for employee %s"
+msgstr "Troppi contratti attivi per il dipendente %s"
+
+#. module: hr_attendance_analysis
+#: view:hr.attendance:0
+msgid "Calendar View"
+msgstr "Vista calendario"
+
+#. module: hr_attendance_analysis
+#: view:resource.calendar:0
+msgid "Roundings"
+msgstr "Arrotondamenti"
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:25
+msgid "Third Sign In"
+msgstr "Terza entrata"
+
+#. module: hr_attendance_analysis
+#: model:ir.model,name:hr_attendance_analysis.model_hr_attendance
+msgid "Attendance"
+msgstr "Presenze"
+
+#. module: hr_attendance_analysis
+#: constraint:res.company:0
+msgid "Error! You can not create recursive companies."
+msgstr "Errore! Non è possibile creare aziende ricorsive."
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:35
+msgid "Negative"
+msgstr "Negativo"
+
+#. module: hr_attendance_analysis
+#: view:hr.attendance:0
+msgid "Employee"
+msgstr "Dipendente"
+
+#. module: hr_attendance_analysis
+#: view:resource.calendar:0
+msgid "Type"
+msgstr "Tipo"
+
+#. module: hr_attendance_analysis
+#: view:hr.attendance:0
+msgid "Hr Attendance Search"
+msgstr "HR cerca presenza"
+
+#. module: hr_attendance_analysis
+#: view:hr.attendance:0
+msgid "Start date time"
+msgstr "Data e ora di inizio"
+
+#. module: hr_attendance_analysis
+#: help:resource.calendar,overtime_rounding:0
+msgid ""
+"Setting rounding = 30 minutes, an overtime of 29 minutes will be considered "
+"as 0 minutes, 31 minutes as 30 minutes, 61 minutes as 1 hour and so on"
+msgstr ""
+"Impostando un arrotondamento = 30 minuti, uno straordinario di 29 minuti "
+"sarà considerato come 0 minuti, 31 minuti come 30 minuti, 61 minuti come "
+"un'ora e così via"
+
+#. module: hr_attendance_analysis
+#: field:resource.calendar,attendance_rounding:0
+msgid "Attendance rounding"
+msgstr "Arrotondamento presenza"
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "3"
+msgstr "3"
+
+#. module: hr_attendance_analysis
+#: constraint:hr.attendance:0
+msgid "Error: Sign in (resp. Sign out) must follow Sign out (resp. Sign in)"
+msgstr ""
+"Errore: una operazione di Entrata (Uscita) deve essere seguito da una Uscita "
+"(Entrata)"
+
+#. module: hr_attendance_analysis
+#: view:res.company:0
+msgid "Configuration"
+msgstr "Configurazione"
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/wizard/print_calendar_report.py:58
+#, python-format
+msgid "From date must be < to date"
+msgstr "\"Dalla data\" deve essere < di \"alla data\""
+
+#. module: hr_attendance_analysis
+#: view:hr.attendance:0
+msgid "Total hours"
+msgstr "Ore totali"
+
+#. module: hr_attendance_analysis
+#: field:res.company,working_time_precision:0
+msgid "Working time precision"
+msgstr "Precisione orario lavorativo"
+
+#. module: hr_attendance_analysis
+#: view:hr.attendance:0
+msgid "Employee attendances analysis"
+msgstr "Analisi presenze dipendente"
+
+#. module: hr_attendance_analysis
+#: field:resource.calendar.attendance,tolerance_to:0
+msgid "Tolerance to"
+msgstr "Tolleranza a"
+
+#. module: hr_attendance_analysis
+#: help:resource.calendar,leave_rounding:0
+msgid ""
+"On the contrary of overtime rounding, using rounding = 15 minutes, a leave "
+"of 1 minute will be considered as 15 minutes, 16 minutes as 30 minutes and "
+"so on"
+msgstr ""
+"Al contrario dell'arrotondamento straordinari, utilizzando un arrotondamento "
+"= 15 minuti, un'assenza di 1 minuto verrà considerata come di 15 minuti, 16 "
+"minuti come 30 minuti e così via"
+
+#. module: hr_attendance_analysis
+#: model:ir.actions.report.xml,name:hr_attendance_analysis.attendance_analysis_report_id
+msgid "Attendances Analysis"
+msgstr "Analisi presenze"
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "30"
+msgstr "30"
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:110
+#: model:ir.model,name:hr_attendance_analysis.model_resource_calendar_overtime_type
+msgid "Overtime type"
+msgstr "Tipo di straordinario"
+
+#. module: hr_attendance_analysis
+#: field:hr.attendance,inside_calendar_duration:0
+msgid "Duration within working schedule"
+msgstr "Durata all'interno del calendario lavorativo"
+
+#. module: hr_attendance_analysis
+#: field:resource.calendar,leave_rounding:0
+msgid "Leave rounding"
+msgstr "Arrotonamento assenza"
+
+#. module: hr_attendance_analysis
+#: field:resource.calendar.overtime.type,calendar_id:0
+msgid "Calendar"
+msgstr "Calendario"
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:32
+msgid "Due"
+msgstr "Dovuto"
+
+#. module: hr_attendance_analysis
+#: view:attendance_analysis.wizard.calendar_report:0
+#: model:ir.actions.act_window,name:hr_attendance_analysis.action_wizard_calendar_report
+#: model:ir.ui.menu,name:hr_attendance_analysis.menu_action_wizard_calendar_report
+msgid "Attendances Analysis Calendar"
+msgstr "Calendario di analisi presenze"
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "60"
+msgstr "60"
+
+#. module: hr_attendance_analysis
+#: view:resource.calendar:0
+#: field:resource.calendar,overtime_type_ids:0
+msgid "Overtime types"
+msgstr "Tipi di straordinario"
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/report/calendar_report.py:35
+#, python-format
+msgid "Monday"
+msgstr "Lunedì"
+
+#. module: hr_attendance_analysis
+#: field:attendance_analysis.wizard.calendar_report,employee_ids:0
+msgid "unknown"
+msgstr "sconosciuto"
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:18
+msgid "First Sign Out"
+msgstr "Prima uscita"
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:15
+msgid "Day of week"
+msgstr "Giorno della settimana"
+
+#. module: hr_attendance_analysis
+#: field:resource.calendar,overtime_rounding_tolerance:0
+msgid "Overtime rounding tolerance"
+msgstr "Tolleranza arrotondamento straordinari"
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:29
+msgid "Fourth Sign In"
+msgstr "Quarta entrata"
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/wizard/print_calendar_report.py:130
+#, python-format
+msgid "%s: 'Work to' is < 'Work from'"
+msgstr "%s: 'Lavoro a' è < 'Lavoro da'"
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "2"
+msgstr "2"
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:10
+msgid "Employee: "
+msgstr "Dipendente: "
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "6"
+msgstr "6"
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:14
+msgid "Date"
+msgstr "Data"
+
+#. module: hr_attendance_analysis
+#: model:ir.module.module,shortdesc:hr_attendance_analysis.module_meta_information
+msgid "HR - Attendance Analysis"
+msgstr "HR - Analisi presenze"
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/hr_attendance.py:190
+#, python-format
+msgid "Wrongly configured working schedule with id %s"
+msgstr "Calendario lavorativo con id %s configurato in modo scorretto"
+
+#. module: hr_attendance_analysis
+#: model:ir.model,name:hr_attendance_analysis.model_res_company
+msgid "Companies"
+msgstr "Aziende"
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/report/calendar_report.py:39
+#, python-format
+msgid "Wednesday"
+msgstr "Mercoledì"
+
+#. module: hr_attendance_analysis
+#: field:resource.calendar.overtime.type,name:0
+msgid "Type Description"
+msgstr "Descrizione tipo"
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:17
+msgid "First Sign In"
+msgstr "Prima entrata"
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:30
+msgid "Fourth Sign Out"
+msgstr "Quarta uscita"
+
+#. module: hr_attendance_analysis
+#: model:ir.module.module,description:hr_attendance_analysis.module_meta_information
+msgid ""
+"\n"
+"Dynamic reports based on employee's attendances and contract's calendar.\n"
+"Among other things, it lets you see the amount of working hours outside and "
+"inside the contract's working schedule (overtime).\n"
+"It also provides a daily based report, showing the detailed and total hours "
+"compared to calendar hours.\n"
+msgstr ""
+"\n"
+"Dynamic reports based on employee's attendances and contract's calendar.\n"
+"Among other things, it lets you see the amount of working hours outside and "
+"inside the contract's working schedule (overtime).\n"
+"It also provides a daily based report, showing the detailed and total hours "
+"compared to calendar hours.\n"
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "10"
+msgstr "10"
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "12"
+msgstr "12"
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:22
+msgid "Second Sign Out"
+msgstr "Seconda uscita"
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:34
+#: view:hr.attendance:0
+#: field:hr.attendance,outside_calendar_duration:0
+msgid "Overtime"
+msgstr "Straordinario"
+
+#. module: hr_attendance_analysis
+#: model:ir.model,name:hr_attendance_analysis.model_resource_calendar_attendance
+msgid "Work Detail"
+msgstr "Dettagli del lavoro"
+
+#. module: hr_attendance_analysis
+#: help:resource.calendar.attendance,tolerance_from:0
+msgid ""
+"Sign out done in the interval \"Work to - Tolerance from\" will be "
+"considered done at \"Work to\""
+msgstr ""
+"Le uscite effettuate nell'intervallo \"Lavoro a - Tolleranza da\" saranno "
+"considerate come fatte in \"Lavoro a\""
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:21
+msgid "Second Sign In"
+msgstr "Seconda entrata"
+
+#. module: hr_attendance_analysis
+#: view:attendance_analysis.wizard.calendar_report:0
+msgid "Cancel"
+msgstr "Annulla"
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/report/calendar_report.py:37
+#, python-format
+msgid "Tuesday"
+msgstr "Martedì"
+
+#. module: hr_attendance_analysis
+#: model:ir.actions.act_window,name:hr_attendance_analysis.open_view_attendance
+#: model:ir.ui.menu,name:hr_attendance_analysis.menu_open_view_attendance
+msgid "Attendances analysis"
+msgstr "Analisi presenze"
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/report/calendar_report.py:41
+#, python-format
+msgid "Thursday"
+msgstr "Giovedì"
+
+#. module: hr_attendance_analysis
+#: view:attendance_analysis.wizard.calendar_report:0
+msgid "Print"
+msgstr "Stampa"
+
+#. module: hr_attendance_analysis
+#: model:ir.model,name:hr_attendance_analysis.model_resource_calendar
+msgid "Resource Calendar"
+msgstr "Calendario risorse"
+
+#. module: hr_attendance_analysis
+#: field:hr.attendance,duration:0
+msgid "Attendance duration"
+msgstr "Durata presenza"
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "1"
+msgstr "1"
+
+#. module: hr_attendance_analysis
+#: field:attendance_analysis.wizard.calendar_report,to_date:0
+msgid "To date"
+msgstr "Alla data"
+
+#. module: hr_attendance_analysis
+#: selection:resource.calendar,attendance_rounding:0
+#: selection:resource.calendar,leave_rounding:0
+#: selection:resource.calendar,overtime_rounding:0
+msgid "5"
+msgstr "5"
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:33
+msgid "Working Hours"
+msgstr "Ore lavorative"
+
+#. module: hr_attendance_analysis
+#: view:resource.calendar:0
+msgid "Types"
+msgstr "Tipi"
+
+#. module: hr_attendance_analysis
+#: field:resource.calendar,overtime_rounding:0
+msgid "Overtime rounding"
+msgstr "Arrotondamento straordinario"
+
+#. module: hr_attendance_analysis
+#: view:attendance_analysis.wizard.calendar_report:0
+msgid "Employees"
+msgstr "Dipendenti"
+
+#. module: hr_attendance_analysis
+#: help:resource.calendar.overtime.type,limit:0
+msgid ""
+"Limit, in hours, of overtime that can be imputed to this type of overtime in "
+"a day. The surplus is imputed to the subsequent type"
+msgstr ""
+"Limite, in ore, dello straordinario che può essere attribuito a questo tipo "
+"di straordinario in un giorno. L'eccedenza è attribuita al tipo successivo."
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:26
+msgid "Third Sign Out"
+msgstr "Terza uscita"
+
+#. module: hr_attendance_analysis
+#: help:resource.calendar,overtime_rounding_tolerance:0
+msgid ""
+"Overtime can be rounded using a tolerance. Using tolerance = 3 minutes and "
+"rounding = 15 minutes, if employee does overtime of 12 minutes, it will be "
+"considered as 15 minutes."
+msgstr ""
+"Lo straordinario può essere arrotondato usando una tolleranza. Con una "
+"tolleranza = 3 minuti e arrotondamento = 15 minuti, se il dipendente fa uno "
+"straordinario di 12 minuti, sarà considerato come 15 minuti."
+
+#. module: hr_attendance_analysis
+#: field:resource.calendar.overtime.type,sequence:0
+msgid "Sequence"
+msgstr "Sequenza"
+
+#. module: hr_attendance_analysis
+#: report:addons/hr_attendance_analysis/report/calendar_report.mako:111
+msgid "Total"
+msgstr "Totale"
+
+#. module: hr_attendance_analysis
+#: code:addons/hr_attendance_analysis/report/calendar_report.py:45
+#, python-format
+msgid "Saturday"
+msgstr "Sabato"
=== added directory 'hr_attendance_analysis/report'
=== added file 'hr_attendance_analysis/report/__init__.py'
--- hr_attendance_analysis/report/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_attendance_analysis/report/__init__.py 2013-11-07 15:51:52 +0000
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (C) 2011 Agile Business Group sagl (<http://www.agilebg.com>)
+# Copyright (C) 2011 Domsense srl (<http://www.domsense.com>)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+import calendar_report
=== added file 'hr_attendance_analysis/report/calendar_report.mako'
--- hr_attendance_analysis/report/calendar_report.mako 1970-01-01 00:00:00 +0000
+++ hr_attendance_analysis/report/calendar_report.mako 2013-11-07 15:51:52 +0000
@@ -0,0 +1,138 @@
+<html>
+<head>
+ <style type="text/css">
+ ${css}
+ </style>
+</head>
+<body>
+ <% setLang(objects[0].company_id.partner_id.lang) %>
+ <% from datetime import datetime %>
+ %for employee in objects :
+ <h2>${_("Employee: ")} ${ employee.name or ''|entity }</h2>
+ <table style="width:100%" border="1">
+ <thead style="border-bottom: solid; background-color: WhiteSmoke">
+ <tr style="page-break-inside: avoid">
+ <th style="text-align:left">${_("Date")}</th>
+ <th style="text-align:left">${_("Day of week")}</th>
+ %if max_per_day() >= 1:
+ <th style="text-align:left">${_("First Sign In")}</th>
+ <th style="text-align:left">${_("First Sign Out")}</th>
+ %endif
+ %if max_per_day() >= 2:
+ <th style="text-align:left">${_("Second Sign In")}</th>
+ <th style="text-align:left">${_("Second Sign Out")}</th>
+ %endif
+ %if max_per_day() >= 3:
+ <th style="text-align:left">${_("Third Sign In")}</th>
+ <th style="text-align:left">${_("Third Sign Out")}</th>
+ %endif
+ %if max_per_day() >= 4:
+ <th style="text-align:left">${_("Fourth Sign In")}</th>
+ <th style="text-align:left">${_("Fourth Sign Out")}</th>
+ %endif
+ <th style="text-align:right">${_("Due")}</th>
+ <th style="text-align:right">${_("Working Hours")}</th>
+ <th style="text-align:right">${_("Overtime")}</th>
+ <th style="text-align:right">${_("Negative")}</th>
+ <th style="text-align:right">${_("Leave")}</th>
+ </tr>
+ </thead>
+ <% first_done = 0 %>
+ %for day in sorted(days_by_employee(employee.id).iterkeys()) :
+ %if datetime.strptime(day, '%Y-%m-%d').day == 1 or not first_done:
+ <tr style="page-break-inside: avoid" >
+ <td colspan="15">
+ <strong>${ month_name(day) | entity }</strong>
+ </td>
+ </tr>
+ <% first_done = 1 %>
+ %endif
+ <tr style="page-break-inside: avoid">
+ <td style="text-align:left">${ formatLang(day, date=True) | entity }</td>
+ <td style="text-align:left">${ day_of_week(day) | entity }</td>
+ %if max_per_day() >= 1:
+ <td style="text-align:left">${ days_by_employee(employee.id)[day]['signin_1'] | entity }</td>
+ <td style="text-align:left">${ days_by_employee(employee.id)[day]['signout_1'] | entity }</td>
+ %endif
+ %if max_per_day() >= 2:
+ <td style="text-align:left">${ days_by_employee(employee.id)[day]['signin_2'] | entity }</td>
+ <td style="text-align:left">${ days_by_employee(employee.id)[day]['signout_2'] | entity }</td>
+ %endif
+ %if max_per_day() >= 3:
+ <td style="text-align:left">${ days_by_employee(employee.id)[day]['signin_3'] | entity }</td>
+ <td style="text-align:left">${ days_by_employee(employee.id)[day]['signout_3'] | entity }</td>
+ %endif
+ %if max_per_day() >= 4:
+ <td style="text-align:left">${ days_by_employee(employee.id)[day]['signin_4'] | entity }</td>
+ <td style="text-align:left">${ days_by_employee(employee.id)[day]['signout_4'] | entity }</td>
+ %endif
+ <td style="text-align:right">${ (days_by_employee(employee.id)[day]['due']) | entity }</td>
+ <td style="text-align:right">${ (days_by_employee(employee.id)[day]['attendances']) | entity }</td>
+ %if days_by_employee(employee.id)[day]['overtime'] != '00:00':
+ <td style="text-align:right; background-color:LightGreen">${ (days_by_employee(employee.id)[day]['overtime']) | entity }</td>
+ %else:
+ <td style="text-align:right">${ (days_by_employee(employee.id)[day]['overtime']) | entity }</td>
+ %endif
+ %if days_by_employee(employee.id)[day]['negative'] != '00:00':
+ <td style="text-align:right; background-color: Tomato">${ (days_by_employee(employee.id)[day]['negative']) | entity }</td>
+ %else:
+ <td style="text-align:right">${ (days_by_employee(employee.id)[day]['negative']) | entity }</td>
+ %endif
+ %if days_by_employee(employee.id)[day]['leaves'] != '00:00':
+ <td style="text-align:right; background-color: Silver">${ (days_by_employee(employee.id)[day]['leaves']) | entity }</td>
+ %else:
+ <td style="text-align:right">${ (days_by_employee(employee.id)[day]['leaves']) | entity }</td>
+ %endif
+ </tr>
+ %endfor
+ <tfoot style="font-weight:bold">
+ <tr style="page-break-inside: avoid">
+ <td style="text-align:left">${_("Totals")} </td>
+ <td></td>
+ %if max_per_day() >= 1:
+ <td></td>
+ <td></td>
+ %endif
+ %if max_per_day() >= 2:
+ <td></td>
+ <td></td>
+ %endif
+ %if max_per_day() >= 3:
+ <td></td>
+ <td></td>
+ %endif
+ %if max_per_day() >= 4:
+ <td></td>
+ <td></td>
+ %endif
+ <td style="border-top:1px solid #000; text-align:right">${ (totals_by_employee(employee.id)['total_due']) | entity }</td>
+ <td style="border-top:1px solid #000; text-align:right">${ (totals_by_employee(employee.id)['total_attendances']) | entity }</td>
+ <td style="border-top:1px solid #000; text-align:right">${ (totals_by_employee(employee.id)['total_overtime']) | entity }</td>
+ <td style="border-top:1px solid #000; text-align:right">${ (totals_by_employee(employee.id)['total_negative']) | entity }</td>
+ <td style="border-top:1px solid #000; text-align:right">${ (totals_by_employee(employee.id)['total_leaves']) | entity }</td>
+ </tr>
+ </tfoot>
+ </table>
+ <br/>
+ <table>
+ <thead>
+ <tr>
+ <th style="text-align:left">${_("Overtime type")}</th>
+ <th style="text-align:right">${_("Total")}</th>
+ </tr>
+ </thead>
+ %for type in totals_by_employee(employee.id)['total_types']:
+ <tr>
+ <td style="text-align:left">
+ ${type | entity}
+ </td>
+ <td style="text-align:right">
+ ${(totals_by_employee(employee.id)['total_types'][type]) | entity}
+ </td>
+ </tr>
+ %endfor
+ </table>
+ <p style="page-break-after:always; height: 1px"></p>
+ %endfor
+</body>
+</html>
=== added file 'hr_attendance_analysis/report/calendar_report.py'
--- hr_attendance_analysis/report/calendar_report.py 1970-01-01 00:00:00 +0000
+++ hr_attendance_analysis/report/calendar_report.py 2013-11-07 15:51:52 +0000
@@ -0,0 +1,96 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (C) 2011 Agile Business Group sagl (<http://www.agilebg.com>)
+# Copyright (C) 2011 Domsense srl (<http://www.domsense.com>)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import time
+from report import report_sxw
+from osv import osv
+from datetime import datetime
+from tools.translate import _
+
+class Parser(report_sxw.rml_parse):
+
+ def _get_day_of_week(self, day):
+ WEEKDAYS = {
+ 0: _('Monday'),
+ 1: _('Tuesday'),
+ 2: _('Wednesday'),
+ 3: _('Thursday'),
+ 4: _('Friday'),
+ 5: _('Saturday'),
+ 6: _('Sunday'),
+ }
+ dayofweek=''
+ weekday = datetime.strptime(day,'%Y-%m-%d').weekday()
+ return WEEKDAYS[weekday]
+
+ def _get_month_name(self, day):
+ str_month=''
+ month = datetime.strptime(day,'%Y-%m-%d').month
+ if month == 1:
+ str_month = _('January')
+ elif month == 2:
+ str_month = _('February')
+ elif month == 3:
+ str_month = _('March')
+ elif month == 4:
+ str_month = _('April')
+ elif month == 5:
+ str_month = _('May')
+ elif month == 6:
+ str_month = _('June')
+ elif month == 7:
+ str_month = _('July')
+ elif month == 8:
+ str_month = _('August')
+ elif month == 9:
+ str_month = _('September')
+ elif month == 10:
+ str_month = _('October')
+ elif month == 11:
+ str_month = _('November')
+ elif month == 12:
+ str_month = _('December')
+ return str_month
+
+ def _get_days_by_employee(self, employee_id):
+ return self.localcontext['data']['form']['days_by_employee'][str(employee_id)]
+
+ def _get_totals_by_employee(self, employee_id):
+ return self.localcontext['data']['form']['totals_by_employee'][str(employee_id)]
+
+ def _get_max_per_day(self):
+ return self.localcontext['data']['form']['max_number_of_attendances_per_day']
+
+ def __init__(self, cr, uid, name, context):
+ super(Parser, self).__init__(cr, uid, name, context)
+ self.localcontext.update({
+ 'time': time,
+ 'days_by_employee': self._get_days_by_employee,
+ 'totals_by_employee': self._get_totals_by_employee,
+ 'day_of_week': self._get_day_of_week,
+ 'max_per_day': self._get_max_per_day,
+ 'month_name': self._get_month_name,
+ })
+
+report_sxw.report_sxw('report.attendance_analysis.calendar_report',
+ 'attendance_analysis.calendar_report',
+ 'attendance_analysis/report/calendar_report.mako',
+ parser=Parser)
=== added file 'hr_attendance_analysis/reports.xml'
--- hr_attendance_analysis/reports.xml 1970-01-01 00:00:00 +0000
+++ hr_attendance_analysis/reports.xml 2013-11-07 15:51:52 +0000
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+<openerp>
+ <data><record id="attendances_landscape_header" model="ir.header_webkit">
+ <field name="footer_html"><![CDATA[
+<html>
+ <head>
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type"/>
+ <script>
+ function subst() {
+ var vars={};
+ var x=document.location.search.substring(1).split('&');
+ for(var i in x) {var z=x[i].split('=',2);vars[z[0]] = unescape(z[1]);}
+ var x=['frompage','topage','page','webpage','section','subsection','subsubsection'];
+ for(var i in x) {
+ var y = document.getElementsByClassName(x[i]);
+ for(var j=0; j<y.length; ++j) y[j].textContent = vars[x[i]];
+ }
+ }
+ </script>
+ </head>
+ <% import datetime %>
+ <body style="border:0" onload="subst()">
+ <table style="border-top: 1px solid black; width: 1080px">
+ <tr style="border-collapse:collapse;">
+ <td style="text-align:left;font-size:10;width:350px;">${formatLang( str(datetime.datetime.today()), date_time=True)}</td>
+ <td style="text-align:center;font-size:10;width:350px;"></td>
+ <td style="text-align:right;font-size:10;width:350px;">Page <span class="page"/></td>
+ <td style="text-align:left;font-size:10;width:30px"> of <span class="topage"/></td>
+ </tr>
+ </table>
+ </body>
+</html>]]></field>
+ <field name="orientation">Landscape</field>
+ <field name="format">A4</field>
+ <field name="html"><![CDATA[
+<html>
+ <head>
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type"/>
+ <script>
+ function subst() {
+ var vars={};
+ var x=document.location.search.substring(1).split('&');
+ for(var i in x) {var z=x[i].split('=',2);vars[z[0]] = unescape(z[1]);}
+ var x=['frompage','topage','page','webpage','section','subsection','subsubsection'];
+ for(var i in x) {
+ var y = document.getElementsByClassName(x[i]);
+ for(var j=0; j<y.length; ++j) y[j].textContent = vars[x[i]];
+ }
+ }
+ </script>
+ <style type="text/css">
+ ${css}
+ </style>
+ </head>
+ <body style="border:0; margin: 0;" onload="subst()">
+ <table class="header" style="border-bottom: 0px solid black; width: 100%">
+ <tr>
+ <td><h1>${_("Attendances Analysis")} - ${objects[0].company_id.partner_id.name | entity}</h1></td>
+ </tr>
+ </table> ${_debug or ''|n}
+ </body>
+</html>]]>
+ </field>
+ <field name="css"><![CDATA[
+
+body, table, td, span, div {
+ font-family: Helvetica, Arial;
+}
+
+]]>
+ </field>
+ <field eval="20" name="margin_top"/>
+ <field name="name">Attendances Landscape Header</field>
+ </record>
+ <record id="attendance_analysis_report_id" model="ir.actions.report.xml">
+ <field name="name">Attendances Analysis</field>
+ <field name="type">ir.actions.report.xml</field>
+ <field name="model">hr.employee</field>
+ <field name="report_name">attendance_analysis.calendar_report</field>
+ <field name="report_rml">hr_attendance_analysis/report/calendar_report.mako</field>
+ <field name="report_type">webkit</field>
+ <field name="webkit_header" ref="attendances_landscape_header"/>
+ </record>
+ </data>
+</openerp>
+
=== added file 'hr_attendance_analysis/resource.py'
--- hr_attendance_analysis/resource.py 1970-01-01 00:00:00 +0000
+++ hr_attendance_analysis/resource.py 2013-11-07 15:51:52 +0000
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (C) 2011 Domsense srl (<http://www.domsense.com>)
+# Copyright (C) 2011-2013 Agile Business Group sagl
+# (<http://www.agilebg.com>)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.osv import fields, orm
+
+class resource_calendar_attendance(orm.Model):
+ _inherit = "resource.calendar.attendance"
+ _columns = {
+ 'tolerance_from': fields.float('Tolerance from', size=8,
+ help='Sign out done in the interval "Work to - Tolerance from" will be considered done at "Work to"'),
+ 'tolerance_to': fields.float('Tolerance to', size=8,
+ help='Sign in done in the interval "Work from + Tolerance to" will be considered done at "Work from"'),
+ }
+
+
+class resource_calendar(orm.Model):
+ _inherit = "resource.calendar"
+ _columns = {
+ 'attendance_rounding': fields.selection([
+ ('60', '1'),
+ ('30', '2'),
+ ('20', '3'),
+ ('12', '5'),
+ ('10', '6'),
+ ('7.5', '8'),
+ ('6', '10'),
+ ('5', '12'),
+ ('4', '15'),
+ ('3', '20'),
+ ('2', '30'),
+ ('1', '60'),
+ ],
+ 'Attendance rounding', help='For instance, using rounding = 15 minutes, every sign in will be rounded to the following quarter hour and every sign out to the previous quarter hour'),
+ #'attendance_rounding': fields.float('Attendance rounding', size=8,
+ #help='For instance, using rounding = 15 minutes, every sign in will be rounded to the following quarter hour and every sign out to the previous quarter hour'),
+ 'overtime_rounding': fields.selection([
+ ('60', '1'),
+ ('30', '2'),
+ ('20', '3'),
+ ('12', '5'),
+ ('10', '6'),
+ ('7.5', '8'),
+ ('6', '10'),
+ ('5', '12'),
+ ('4', '15'),
+ ('3', '20'),
+ ('2', '30'),
+ ('1', '60'),
+ ],
+ 'Overtime rounding',
+ help='Setting rounding = 30 minutes, an overtime of 29 minutes will be considered as 0 minutes, 31 minutes as 30 minutes, 61 minutes as 1 hour and so on'),
+ 'overtime_rounding_tolerance': fields.float('Overtime rounding tolerance', size=8,
+ help='Overtime can be rounded using a tolerance. Using tolerance = 3 minutes and rounding = 15 minutes, if employee does overtime of 12 minutes, it will be considered as 15 minutes.'),
+ 'leave_rounding': fields.selection([
+ ('60', '1'),
+ ('30', '2'),
+ ('20', '3'),
+ ('12', '5'),
+ ('10', '6'),
+ ('7.5', '8'),
+ ('6', '10'),
+ ('5', '12'),
+ ('4', '15'),
+ ('3', '20'),
+ ('2', '30'),
+ ('1', '60'),
+ ],
+ 'Leave rounding',
+ help='On the contrary of overtime rounding, using rounding = 15 minutes, a leave of 1 minute will be considered as 15 minutes, 16 minutes as 30 minutes and so on'),
+ 'overtime_type_ids': fields.one2many('resource.calendar.overtime.type', 'calendar_id', 'Overtime types'),
+ }
+
+class resource_calendar_overtime_range(orm.Model):
+ _name = 'resource.calendar.overtime.type'
+ _description = 'Overtime type'
+ _order = 'sequence'
+ _columns = {
+ 'sequence': fields.integer('Sequence', required=True),
+ 'name': fields.char('Type Description', size=64, required=True),
+ 'calendar_id': fields.many2one('resource.calendar', 'Calendar'),
+ 'limit': fields.float('Limit', size=8,
+ help='Limit, in hours, of overtime that can be imputed to this type of overtime in a day. The surplus is imputed to the subsequent type')
+ }
=== added file 'hr_attendance_analysis/resource_view.xml'
--- hr_attendance_analysis/resource_view.xml 1970-01-01 00:00:00 +0000
+++ hr_attendance_analysis/resource_view.xml 2013-11-07 15:51:52 +0000
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <record id="view_resource_calendar_attendance_form" model="ir.ui.view">
+ <field name="name">resource.calendar.attendance.form</field>
+ <field name="model">resource.calendar.attendance</field>
+ <field name="inherit_id" ref="resource.view_resource_calendar_attendance_form"></field>
+ <field name="arch" type="xml">
+ <field name="hour_from" position="after">
+ <field name="tolerance_to" widget="float_time"/>
+ </field>
+ <field name="hour_to" position="after">
+ <field name="tolerance_from" widget="float_time"/>
+ </field>
+ </field>
+ </record>
+ <record id="resource_calendar_form" model="ir.ui.view">
+ <field name="name">resource.calendar.form</field>
+ <field name="model">resource.calendar</field>
+ <field name="inherit_id" ref="resource.resource_calendar_form"></field>
+ <field name="arch" type="xml">
+ <field name="attendance_ids" position="after">
+ <notebook colspan="4">
+ <page string="Roundings">
+ <group colspan="4">
+ <field name="attendance_rounding"/>
+ <field name="leave_rounding"/>
+ <field name="overtime_rounding"/>
+ <field name="overtime_rounding_tolerance" widget="float_time"/>
+ </group>
+ </page>
+ <page string="Overtime types">
+ <field name="overtime_type_ids" colspan="4" nolabel="1">
+ <tree string="Types" editable="bottom">
+ <field name="sequence"/>
+ <field name="name"/>
+ <field name="limit" widget="float_time"/>
+ </tree>
+ <form string="Type">
+ <field name="sequence"/>
+ <field name="name"/>
+ <field name="limit" widget="float_time"/>
+ </form>
+ </field>
+ </page>
+ </notebook>
+ </field>
+ </field>
+ </record>
+ </data>
+</openerp>
=== added directory 'hr_attendance_analysis/security'
=== added file 'hr_attendance_analysis/security/ir.model.access.csv'
--- hr_attendance_analysis/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
+++ hr_attendance_analysis/security/ir.model.access.csv 2013-11-07 15:51:52 +0000
@@ -0,0 +1,3 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_resource_calendar_overtime_type,resource.calendar.overtime.type,model_resource_calendar_overtime_type,base.group_system,1,1,1,1
+access_hr_resource_calendar_overtime_type_user,hr.employee.resource.calendar.overtime.type.user,model_resource_calendar_overtime_type,base.group_hr_user,1,1,1,1
=== added directory 'hr_attendance_analysis/wizard'
=== added file 'hr_attendance_analysis/wizard/__init__.py'
--- hr_attendance_analysis/wizard/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_attendance_analysis/wizard/__init__.py 2013-11-07 15:51:52 +0000
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (C) 2011 Domsense srl (<http://www.domsense.com>)
+# Copyright (C) 2011-2013 Agile Business Group sagl
+# (<http://www.agilebg.com>)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import print_calendar_report
=== added file 'hr_attendance_analysis/wizard/print_calendar_report.py'
--- hr_attendance_analysis/wizard/print_calendar_report.py 1970-01-01 00:00:00 +0000
+++ hr_attendance_analysis/wizard/print_calendar_report.py 2013-11-07 15:51:52 +0000
@@ -0,0 +1,366 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (C) 2011 Domsense srl (<http://www.domsense.com>)
+# Copyright (C) 2011-2013 Agile Business Group sagl
+# (<http://www.agilebg.com>)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.osv import fields, orm
+from openerp.tools.translate import _
+from datetime import *
+import math
+import calendar
+
+class wizard_calendar_report(orm.TransientModel):
+
+ _columns = {
+ 'month': fields.selection([
+ ('1', 'January'),
+ ('2', 'February'),
+ ('3', 'March'),
+ ('4', 'April'),
+ ('5', 'May'),
+ ('6', 'June'),
+ ('7', 'July'),
+ ('8', 'August'),
+ ('9', 'September'),
+ ('10', 'October'),
+ ('11', 'November'),
+ ('12', 'December'),
+ ], 'Month'),
+ 'year': fields.integer('Year'),
+ 'from_date': fields.date('From date', required=True),
+ 'to_date': fields.date('To date', required=True),
+ 'employee_ids': fields.many2many('hr.employee', 'calendar_report_employee_rel', 'employee_id', 'report_id',
+ required=True),
+ }
+
+ _defaults = {
+ 'month': lambda * a: str(datetime.now().month),
+ 'year': lambda * a: datetime.now().year,
+ 'from_date': lambda * a: (datetime.now()-timedelta(30)).strftime('%Y-%m-%d'),
+ 'to_date': lambda * a: datetime.now().strftime('%Y-%m-%d'),
+ 'employee_ids': lambda s, cr, uid, c: s.pool.get('hr.employee').search(cr, uid, []),
+ }
+
+ _name = "attendance_analysis.wizard.calendar_report"
+
+ def on_change_month(self, cr, uid, id, str_month, year):
+ res = {}
+ if year and str_month:
+ month = int(str_month)
+ day = calendar.monthrange(year, month)[1]
+ to_date = date(year, month, day).strftime('%Y-%m-%d')
+ res= {'value':{'to_date': to_date, 'from_date': date(year, month, 1).strftime('%Y-%m-%d')}}
+ return res
+
+
+ def print_calendar(self, cr, uid, ids, context=None):
+ if context is None:
+ context = {}
+ attendance_pool = self.pool.get('hr.attendance')
+ contract_pool = self.pool.get('hr.contract')
+ holidays_pool = self.pool.get('hr.holidays')
+
+ days_by_employee = {}
+
+ form = self.read(cr, uid, ids)[0]
+ from_date = datetime.strptime(form['from_date'], '%Y-%m-%d')
+ to_date = datetime.strptime(form['to_date'], '%Y-%m-%d')
+ if from_date > to_date:
+ raise orm.except_orm(_('Error'), _('From date must be < to date'))
+ employee_ids=form['employee_ids']
+ delta = to_date - from_date
+ max_number_of_attendances_per_day = 0
+
+ for employee_id in employee_ids:
+ employee_id = str(employee_id)
+ days_by_employee[employee_id] = {}
+ day_count=0
+ while day_count <= delta.days:
+ current_date = from_date + timedelta(day_count)
+ current_total_attendances = 0.0
+ current_total_overtime = 0.0
+ current_total_leaves = 0.0
+ current_total_due = 24.0 # If contract is not specified: working days = 24/7
+ current_total_inside_calendar = 0.0
+ str_current_date = current_date.strftime('%Y-%m-%d')
+ days_by_employee[employee_id][str_current_date] = {
+ 'signin_1': '',
+ 'signout_1': '',
+ 'signin_2': '',
+ 'signout_2': '',
+ 'signin_3': '',
+ 'signout_3': '',
+ 'signin_4': '',
+ 'signout_4': '',
+ }
+ current_date_beginning = datetime.combine(current_date, time())
+ str_current_date_beginning = current_date_beginning.strftime(
+ '%Y-%m-%d %H:%M:%S')
+ current_date_end = datetime.combine(current_date, time())+ timedelta(1)
+ str_current_date_end = current_date_end.strftime('%Y-%m-%d %H:%M:%S')
+
+ attendance_ids = attendance_pool.search(cr, uid, [
+ ('employee_id','=',int(employee_id)),
+ ('name','>=',str_current_date_beginning),
+ ('name','<=',str_current_date_end),
+ ('action','=','sign_in'),
+ ])
+ # computing attendance totals
+ for attendance in attendance_pool.browse(cr, uid, attendance_ids):
+ current_total_attendances = attendance_pool.time_sum(
+ current_total_attendances,attendance.duration)
+ current_total_overtime = attendance_pool.time_sum(current_total_overtime,
+ attendance.outside_calendar_duration)
+ current_total_inside_calendar = attendance_pool.time_sum(
+ current_total_inside_calendar,
+ attendance.inside_calendar_duration)
+
+ #printing up to 4 attendances
+ if len(attendance_ids) < 5:
+ count = 1
+ for attendance in sorted(attendance_pool.browse(cr, uid, attendance_ids),
+ key=lambda x: x['name']):
+ days_by_employee[employee_id][str_current_date][
+ 'signin_'+str(count)] = attendance.name[11:16]
+ days_by_employee[employee_id][str_current_date][
+ 'signout_'+str(count)] = attendance.end_datetime[11:16]
+ count += 1
+ if len(attendance_ids) > max_number_of_attendances_per_day:
+ max_number_of_attendances_per_day = len(attendance_ids)
+
+ days_by_employee[employee_id][str_current_date][
+ 'attendances'
+ ] = current_total_attendances
+ days_by_employee[employee_id][str_current_date][
+ 'overtime'
+ ] = current_total_overtime
+
+ active_contract_ids = attendance_pool.get_active_contracts(
+ cr, uid, int(employee_id), date=str_current_date)
+ # computing due total
+ if active_contract_ids:
+ contract = contract_pool.browse(cr, uid, active_contract_ids[0])
+ if contract.working_hours and contract.working_hours.attendance_ids:
+ current_total_due = 0.0
+ for calendar_attendance in contract.working_hours.attendance_ids:
+ if ((
+ not calendar_attendance.dayofweek
+ or int(calendar_attendance.dayofweek) == current_date.weekday()
+ )
+ and (
+ not calendar_attendance.date_from or
+ datetime.strptime(calendar_attendance.date_from,'%Y-%m-%d')
+ <= current_date
+ )):
+ calendar_attendance_duration = attendance_pool.time_difference(
+ calendar_attendance.hour_from, calendar_attendance.hour_to)
+ if calendar_attendance_duration < 0:
+ raise orm.except_orm(_('Error'),
+ _("%s: 'Work to' is < 'Work from'")
+ % calendar_attendance.name)
+ current_total_due = attendance_pool.time_sum(current_total_due,
+ calendar_attendance_duration)
+
+ days_by_employee[employee_id][str_current_date]['due'] = current_total_due
+
+ # computing leaves
+ holidays_ids = holidays_pool.search(cr, uid, [
+ '&',
+ '&',
+ '|',
+ # leave begins today
+ '&',
+ ('date_from', '>=', str_current_date_beginning),
+ ('date_from', '<=', str_current_date_end),
+ '|',
+ # leave ends today
+ '&',
+ ('date_to', '<=', str_current_date_end),
+ ('date_to', '>=', str_current_date_beginning),
+ # leave is ongoing
+ '&',
+ ('date_from', '<', str_current_date_beginning),
+ ('date_to', '>', str_current_date_end),
+ ('state', '=', 'validate'),
+ ('employee_id', '=', int(employee_id)),
+ ])
+ for holiday in holidays_pool.browse(cr, uid, holidays_ids):
+ date_from = datetime.strptime(holiday.date_from, '%Y-%m-%d %H:%M:%S')
+ date_to = datetime.strptime(holiday.date_to, '%Y-%m-%d %H:%M:%S')
+ # if beginned before today
+ if date_from < current_date_beginning:
+ date_from = current_date_beginning
+ # if ends after today
+ if date_to > current_date_end:
+ date_to = current_date_end
+ current_total_leaves = attendance_pool.time_sum(
+ current_total_leaves,
+ (date_to - date_from).total_seconds() / 60.0 / 60.0)
+
+ days_by_employee[employee_id][str_current_date]['leaves'] = current_total_leaves
+ if current_total_leaves > days_by_employee[employee_id][
+ str_current_date]['due']:
+ days_by_employee[employee_id][str_current_date][
+ 'leaves'
+ ] = days_by_employee[employee_id][str_current_date]['due']
+ due_minus_leaves = attendance_pool.time_difference(
+ current_total_leaves, current_total_due)
+ if due_minus_leaves < current_total_inside_calendar:
+ days_by_employee[employee_id][str_current_date]['negative'] = 0.0
+ else:
+ days_by_employee[employee_id][str_current_date][
+ 'negative'
+ ] = attendance_pool.time_difference(
+ current_total_inside_calendar, due_minus_leaves)
+
+ if active_contract_ids:
+ contract = contract_pool.browse(cr, uid, active_contract_ids[0])
+ if contract.working_hours and contract.working_hours.leave_rounding:
+ float_rounding = float(contract.working_hours.leave_rounding)
+ days_by_employee[employee_id][str_current_date][
+ 'negative'
+ ] = math.floor(
+ days_by_employee[employee_id][str_current_date]['negative'] *
+ float_rounding
+ ) / float_rounding
+
+ day_count += 1
+
+ totals_by_employee = {}
+ for employee_id in days_by_employee:
+ totals_by_employee[employee_id] = {
+ 'total_attendances': 0.0,
+ 'total_overtime': 0.0,
+ 'total_negative': 0.0,
+ 'total_leaves': 0.0,
+ 'total_due': 0.0,
+ 'total_types': {},
+ }
+
+ for str_date in days_by_employee[employee_id]:
+ totals_by_employee[employee_id]['total_attendances'] = attendance_pool.time_sum(
+ totals_by_employee[employee_id]['total_attendances'],
+ days_by_employee[employee_id][str_date]['attendances'])
+ totals_by_employee[employee_id]['total_overtime'] = attendance_pool.time_sum(
+ totals_by_employee[employee_id]['total_overtime'],
+ days_by_employee[employee_id][str_date]['overtime'])
+ totals_by_employee[employee_id]['total_negative'] = attendance_pool.time_sum(
+ totals_by_employee[employee_id]['total_negative'],
+ days_by_employee[employee_id][str_date]['negative'])
+ totals_by_employee[employee_id]['total_leaves'] = attendance_pool.time_sum(
+ totals_by_employee[employee_id]['total_leaves'],
+ days_by_employee[employee_id][str_date]['leaves'])
+ totals_by_employee[employee_id]['total_due'] = attendance_pool.time_sum(
+ totals_by_employee[employee_id]['total_due'],
+ days_by_employee[employee_id][str_date]['due'])
+
+ # computing overtime types
+ active_contract_ids = attendance_pool.get_active_contracts(
+ cr, uid, int(employee_id), date=str_date)
+ if active_contract_ids:
+ contract = contract_pool.browse(cr, uid, active_contract_ids[0])
+ if contract.working_hours and contract.working_hours.overtime_type_ids:
+ sorted_types = sorted(
+ contract.working_hours.overtime_type_ids,
+ key=lambda k: k.sequence)
+ current_overtime = days_by_employee[employee_id][
+ str_date]['overtime']
+ for overtime_type in sorted_types:
+ if not totals_by_employee[employee_id]['total_types'].get(
+ overtime_type.name, False):
+ totals_by_employee[employee_id]['total_types'][
+ overtime_type.name] = 0.0
+ if current_overtime:
+ if current_overtime <= overtime_type.limit or not overtime_type.limit:
+ totals_by_employee[employee_id]['total_types'][
+ overtime_type.name] = attendance_pool.time_sum(
+ totals_by_employee[employee_id]
+ ['total_types'][overtime_type.name],
+ current_overtime)
+ current_overtime = 0.0
+ else:
+ totals_by_employee[employee_id]['total_types'][
+ overtime_type.name] = attendance_pool.time_sum(
+ totals_by_employee[employee_id]['total_types']
+ [overtime_type.name], overtime_type.limit)
+ current_overtime = attendance_pool.time_difference(overtime_type.limit,
+ current_overtime)
+
+ days_by_employee[employee_id][str_date][
+ 'attendances'
+ ] = attendance_pool.float_time_convert(
+ days_by_employee[employee_id][str_date]['attendances'])
+ days_by_employee[employee_id][str_date][
+ 'overtime'
+ ] = attendance_pool.float_time_convert(
+ days_by_employee[employee_id][str_date]['overtime'])
+ days_by_employee[employee_id][str_date][
+ 'negative'
+ ] = attendance_pool.float_time_convert(
+ days_by_employee[employee_id][str_date]['negative'])
+ days_by_employee[employee_id][str_date][
+ 'leaves'
+ ] = attendance_pool.float_time_convert(
+ days_by_employee[employee_id][str_date]['leaves'])
+ days_by_employee[employee_id][str_date][
+ 'due'
+ ] = attendance_pool.float_time_convert(
+ days_by_employee[employee_id][str_date]['due'])
+
+ totals_by_employee[employee_id][
+ 'total_attendances'
+ ] = attendance_pool.float_time_convert(
+ totals_by_employee[employee_id]['total_attendances'])
+ totals_by_employee[employee_id][
+ 'total_overtime'
+ ] = attendance_pool.float_time_convert(
+ totals_by_employee[employee_id]['total_overtime'])
+ totals_by_employee[employee_id][
+ 'total_negative'
+ ] = attendance_pool.float_time_convert(
+ totals_by_employee[employee_id]['total_negative'])
+ totals_by_employee[employee_id][
+ 'total_leaves'
+ ] = attendance_pool.float_time_convert(
+ totals_by_employee[employee_id]['total_leaves'])
+ totals_by_employee[employee_id][
+ 'total_due'
+ ] = attendance_pool.float_time_convert(
+ totals_by_employee[employee_id]['total_due'])
+
+ for overtime_type in totals_by_employee[employee_id]['total_types']:
+ totals_by_employee[employee_id]['total_types'][
+ overtime_type
+ ] = attendance_pool.float_time_convert(
+ totals_by_employee[employee_id]['total_types'][overtime_type])
+
+ datas = {'ids': employee_ids}
+ datas['model'] = 'hr.employee'
+ datas['form'] = {}
+ datas['form']['days_by_employee'] = days_by_employee
+ datas['form']['totals_by_employee'] = totals_by_employee
+ datas['form']['max_number_of_attendances_per_day'] = max_number_of_attendances_per_day
+
+ return {
+ 'type': 'ir.actions.report.xml',
+ 'report_name': 'attendance_analysis.calendar_report',
+ 'datas': datas,
+ }
+
=== added file 'hr_attendance_analysis/wizard/print_calendar_report.xml'
--- hr_attendance_analysis/wizard/print_calendar_report.xml 1970-01-01 00:00:00 +0000
+++ hr_attendance_analysis/wizard/print_calendar_report.xml 2013-11-07 15:51:52 +0000
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <record id="wizard_calendar_report" model="ir.ui.view">
+ <field name="name">Attendances Analysis Calendar</field>
+ <field name="model">attendance_analysis.wizard.calendar_report</field>
+ <field name="arch" type="xml">
+ <form string="Attendances Analysis Calendar">
+ <group colspan="4" height="400">
+ <field name="month" on_change="on_change_month(month, year)"/>
+ <field name="year" on_change="on_change_month(month, year)"/>
+ <field name="from_date"/>
+ <field name="to_date"/>
+ <separator colspan="4" string="Employees"/>
+ <field name="employee_ids" colspan="4" nolabel="1"/>
+ <separator colspan="4"/>
+ <button icon="gtk-cancel" special="cancel" string="Cancel" colspan="2"/>
+ <button icon="gtk-ok" name="print_calendar" string="Print" type="object" colspan="2"/>
+ </group>
+ </form>
+ </field>
+ </record>
+
+ <record id="action_wizard_calendar_report" model="ir.actions.act_window">
+ <field name="name">Attendances Analysis Calendar</field>
+ <field name="res_model">attendance_analysis.wizard.calendar_report</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">form</field>
+ <field name="view_id" ref="wizard_calendar_report"/>
+ <field name="target">new</field>
+ </record>
+
+ <menuitem action="action_wizard_calendar_report"
+ id="menu_action_wizard_calendar_report"
+ parent="hr.menu_hr_reporting" />
+ </data>
+</openerp>
=== modified file 'hr_timesheet_fulfill/__init__.py'
--- hr_timesheet_fulfill/__init__.py 2011-08-12 12:53:16 +0000
+++ hr_timesheet_fulfill/__init__.py 2013-11-07 15:51:52 +0000
@@ -1,32 +1,25 @@
# -*- coding: utf-8 -*-
##############################################################################
#
-# Copyright (c) 2011 Camptocamp SA (http://www.camptocamp.com)
-# All Right Reserved
-#
-# Author : Guewen Baconnier (Camptocamp)
-#
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Author: Guewen Baconnier (Camptocamp)
+# Author: Vincent Renaville
+# Copyright 2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import wizard
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
=== modified file 'hr_timesheet_fulfill/__openerp__.py'
--- hr_timesheet_fulfill/__openerp__.py 2011-08-12 12:53:16 +0000
+++ hr_timesheet_fulfill/__openerp__.py 2013-11-07 15:51:52 +0000
@@ -1,57 +1,46 @@
# -*- coding: utf-8 -*-
##############################################################################
#
-# Copyright (c) 2011 Camptocamp SA (http://www.camptocamp.com)
-# All Right Reserved
-#
-# Author : Guewen Baconnier (Camptocamp)
-# Author : Vincent Renaville
-#
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Author: Guewen Baconnier (Camptocamp)
+# Author: Vincent Renaville
+# Copyright 2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
-
-{
- "name" : "Timesheet fullfill wizard",
- "version" : "1.0",
- "author" : "Camptocamp",
- "category" : "Generic Modules/Human Resources",
- "description":
-"""
-Add a wizard into timesheet allowing people to complete a long period of time with the given values.
-This is mainly useful to handle a long period of time like holidays.
-Known limitation:
- - Will complete all day between dates
-""",
- "website": "http://camptocamp.com",
- "depends" : [
- "hr_timesheet_sheet",
- ],
- "init_xml" : [],
- "demo_xml" : [],
- "update_xml" : [
- 'wizard/timesheet_fulfill_view.xml',
- ],
- "active": False,
- "installable": True
+{'name' : 'Timesheet Fullfill Wizard',
+ 'version' : '1.0',
+ 'category' : 'Generic Modules/Human Resources',
+ 'description':
+ '''
+ Add a wizard into timesheet allowing people to complete a long period of time with the given values.
+ This is mainly useful to handle a long period of time like holidays.
+ Known limitation:
+ - Will complete all day between dates
+ ''',
+ 'author' : 'Camptocamp',
+ 'website': 'http://camptocamp.com',
+ 'depends' : ['hr_timesheet_sheet',],
+ 'data' : [
+ 'wizard/timesheet_fulfill_view.xml',
+ ],
+ 'demo' : [],
+ 'test' : [],
+ 'installable': True,
+ 'auto_install' : False,
+ 'application' : False,
}
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
=== modified file 'hr_timesheet_fulfill/wizard/__init__.py'
--- hr_timesheet_fulfill/wizard/__init__.py 2011-08-12 12:53:16 +0000
+++ hr_timesheet_fulfill/wizard/__init__.py 2013-11-07 15:51:52 +0000
@@ -1,1 +1,4 @@
+
import timesheet_fulfill
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
\ No newline at end of file
=== modified file 'hr_timesheet_fulfill/wizard/timesheet_fulfill.py'
--- hr_timesheet_fulfill/wizard/timesheet_fulfill.py 2011-09-01 13:44:19 +0000
+++ hr_timesheet_fulfill/wizard/timesheet_fulfill.py 2013-11-07 15:51:52 +0000
@@ -1,41 +1,29 @@
# -*- coding: utf-8 -*-
##############################################################################
#
-# Copyright (c) 2011 Camptocamp SA (http://www.camptocamp.com)
-# All Right Reserved
-#
-# Author : Guewen Baconnier (Camptocamp)
-# Author : Vincent Renaville
-#
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Author: Guewen Baconnier (Camptocamp)
+# Author: Vincent Renaville
+# Copyright 2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
-
-from osv import fields, osv
+from openerp.osv import fields, osv, orm
from tools.translate import _
from datetime import datetime, timedelta
-
def get_number_days_between_dates(date_from, date_to):
datetime_from = datetime.strptime(date_from, '%Y-%m-%d')
datetime_to = datetime.strptime(date_to, '%Y-%m-%d')
@@ -44,7 +32,7 @@
return difference.days + 1
-class FulfillTimesheet(osv.osv_memory):
+class HrTimesheetFulfill(orm.TransientModel):
_name = 'hr.timesheet.fulfill'
_description = "Wizard to fill-in timesheet for many days"
@@ -52,16 +40,19 @@
'date_from': fields.date('Date From', required=True),
'date_to': fields.date('Date To', required=True),
'description': fields.char('Description', size=100, required=True),
- 'nb_hours': fields.float('Hours per day', digits=(2, 2), required=True),
+ 'nb_hours': fields.float('Hours per Day', digits=(2, 2), required=True),
'analytic_account_id': fields.many2one('account.analytic.account',
'Analytic Account', required=True,
- domain="[('type', '=', 'normal'),"
+ domain="[('type', '=', 'contract'),"
"('state', '!=', 'pending'),"
"('state', '!=', 'close')]"),
- 'task_id':fields.many2one('project.task','Task', required=False)
- }
-
- def fulfill_timesheet(self, cr, uid, ids, context):
+ 'task_id':fields.many2one('project.task', 'Task', required=False)
+ }
+
+ def fulfill_timesheet(self, cr, uid, ids, context=None):
+ if context is None:
+ context = {}
+
employee_obj = self.pool.get('hr.employee')
timesheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
al_ts_obj = self.pool.get('hr.analytic.timesheet')
@@ -117,23 +108,23 @@
'sheet_id': timesheet.id,
'journal_id': journal_id,
}
-
on_change_values = al_ts_obj.\
on_change_unit_amount(cr, uid, False, product_id,
wizard.nb_hours, employee.company_id.id,
- task_id=wizard.task_id.id,
+# task_id=wizard.task_id.id,
unit=unit_id, journal_id=journal_id,
context=context)
if on_change_values:
res.update(on_change_values['value'])
al_ts_obj.create(cr, uid, res, context)
-
# If there is no other attendances, create it
# create the attendances:
- existing_attendances = attendance_obj\
- .search(cr, uid, [('name', '=', datetime_current),
- ('employee_id', '=', employee_id)])
-
+# print attendance_obj.read(cr,uid,['name'])
+ existing_attendances=0
+ att_id= attendance_obj.search(cr, uid, [('employee_id', '=', employee_id)])
+ for record in attendance_obj.read(cr,uid,att_id,['name']):
+ if record['name'].startswith( datetime_current ):
+ existing_attendances=1
if not existing_attendances:
att_date_start = datetime_current + " 00:00:00"
att_start = {
@@ -154,6 +145,7 @@
}
attendance_obj.create(cr, uid, att_start, context)
attendance_obj.create(cr, uid, att_end, context)
+
return {'type': 'ir.actions.act_window_close'}
-FulfillTimesheet()
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
=== modified file 'hr_timesheet_fulfill/wizard/timesheet_fulfill_view.xml'
--- hr_timesheet_fulfill/wizard/timesheet_fulfill_view.xml 2011-08-12 12:53:16 +0000
+++ hr_timesheet_fulfill/wizard/timesheet_fulfill_view.xml 2013-11-07 15:51:52 +0000
@@ -1,43 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
- <data>
-
- <record id="view_hr_timesheet_fulfill_form" model="ir.ui.view">
- <field name="name">hr.timesheet.fulfill.form</field>
- <field name="model">hr.timesheet.fulfill</field>
- <field name="type">form</field>
- <field name="arch" type="xml">
- <form string="Enter the dates : ALL days between those dates will be completed.">
- <field name="date_from"/>
- <field name="date_to"/>
- <field name="analytic_account_id"/>
- <field name="task_id" domain="[('state','!=','cancel'),('state','!=','done')" context="{'account_id':analytic_account_id}"/>
- <field name="nb_hours" widget="float_time"/>
- <field name="description" colspan="4"/>
- <group colspan="4" col="6">
- <button icon="gtk-cancel" special="cancel" string="Cancel"/>
- <button icon="gtk-ok" string="Fill in Timesheet" name="fulfill_timesheet" type="object"/>
- </group>
- </form>
- </field>
- </record>
-
- <record id="action_hr_timesheet_fulfill" model="ir.actions.act_window">
- <field name="name">Fill in Timesheet</field>
- <field name="res_model">hr.timesheet.fulfill</field>
- <field name="view_type">form</field>
- <field name="view_mode">form</field>
- <field name="view_id" ref="view_hr_timesheet_fulfill_form"/>
- <field name="target">new</field>
- </record>
-
- <record id="ir_action_hr_timesheet_fulfill_wizard" model="ir.values">
- <field name="key2">client_action_multi</field>
- <field name="model">hr_timesheet_sheet.sheet</field>
- <field name="name">Fill in Timesheet</field>
- <field eval="'ir.actions.act_window,%d'%action_hr_timesheet_fulfill" name="value"/>
- <field eval="True" name="object"/>
- </record>
-
-</data>
+ <data>
+
+ <record id="view_hr_timesheet_fulfill_form" model="ir.ui.view">
+ <field name="name">hr.timesheet.fulfill.form</field>
+ <field name="model">hr.timesheet.fulfill</field>
+ <field name="arch" type="xml">
+ <form string="Enter the dates : ALL days between those dates will be completed." version="7.0">
+ <group>
+ <group>
+ <field name="date_from"/>
+ </group>
+ <group>
+ <field name="date_to"/>
+ </group>
+ </group>
+ <group>
+ <group>
+ <field name="analytic_account_id"/>
+ </group>
+ <group>
+ <field name="task_id" domain="[('state','!=','cancel'),('state','!=','done')]" context="{'account_id':analytic_account_id}"/>
+ </group>
+ </group>
+ <group>
+ <field name="nb_hours" widget="float_time"/>
+ </group>
+ <group colspan="4">
+ <field name="description"/>
+ </group>
+ <footer>
+ <button icon="gtk-cancel" special="cancel" string="Cancel"/>
+ <button icon="gtk-ok" string="Fill in Timesheet" name="fulfill_timesheet" type="object"/>
+ </footer>
+ </form>
+ </field>
+ </record>
+
+ <record id="action_hr_timesheet_fulfill" model="ir.actions.act_window">
+ <field name="name">Fill in Timesheet</field>
+ <field name="res_model">hr.timesheet.fulfill</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">form</field>
+ <field name="view_id" ref="view_hr_timesheet_fulfill_form"/>
+ <field name="target">new</field>
+ </record>
+
+ <record id="ir_action_hr_timesheet_fulfill_wizard" model="ir.values">
+ <field name="key2">client_action_multi</field>
+ <field name="model">hr_timesheet_sheet.sheet</field>
+ <field name="name">Fill in Timesheet</field>
+ <field eval="'ir.actions.act_window,%d'%action_hr_timesheet_fulfill" name="value"/>
+ <field eval="True" name="object"/>
+ </record>
+
+ </data>
</openerp>
=== modified file 'hr_timesheet_holidays/__openerp__.py'
--- hr_timesheet_holidays/__openerp__.py 2011-08-12 12:53:16 +0000
+++ hr_timesheet_holidays/__openerp__.py 2013-11-07 15:51:52 +0000
@@ -55,5 +55,5 @@
'company_view.xml',
],
"active": False,
- "installable": True
+ 'installable': False
}
=== added directory 'hr_timesheet_improvement'
=== added file 'hr_timesheet_improvement/__init__.py'
--- hr_timesheet_improvement/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_timesheet_improvement/__init__.py 2013-11-07 15:51:52 +0000
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author : Yannick Vaucher (Camptocamp)
+# Copyright 2013 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+import hr_timesheet
+import hr_attendance
=== added file 'hr_timesheet_improvement/__openerp__.py'
--- hr_timesheet_improvement/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_timesheet_improvement/__openerp__.py 2013-11-07 15:51:52 +0000
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Authors: Yannick Vaucher (Camptocamp)
+# Vincent Renaville (Camptocamp)
+# Copyright 2013 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+{'name' : 'Timesheet improvements',
+ 'version' : '0.1',
+ 'author' : 'Camptocamp',
+ 'maintainer': 'Camptocamp',
+ 'category': 'Human Resources',
+ 'depends' : ['hr_timesheet_sheet'],
+ 'description': """
+ Modifies timesheet behavior:
+ - Ensure a DESC order on timesheet lines
+ - Set default date for manually entering attendance to max attendance date
+ - Redefine constraint on timesheets to check alternation of 'sign in' and
+ 'sign out' only on current timesheet instead of doing it on all timesheets
+ of the employee
+ """,
+ 'website': 'http://www.camptocamp.com',
+ 'data': ['hr_timesheet_view.xml'],
+ 'js' : [],
+ 'css': [],
+ 'qweb': [],
+ 'demo': [],
+ 'test': [],
+ 'installable': True,
+ 'images' : [],
+ 'auto_install': False,
+ 'license': 'AGPL-3',
+ 'application': True,
+}
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
=== added file 'hr_timesheet_improvement/hr_attendance.py'
--- hr_timesheet_improvement/hr_attendance.py 1970-01-01 00:00:00 +0000
+++ hr_timesheet_improvement/hr_attendance.py 2013-11-07 15:51:52 +0000
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Authors: Yannick Vaucher (Camptocamp)
+# Vincent Renaville (Camptocamp)
+# Copyright 2013 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+import time
+
+from openerp.osv import orm
+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
+
+
+class HrAttendance(orm.Model):
+ """
+ Alter the default date for manual setting
+ """
+ _inherit = "hr.attendance"
+
+ def _default_date(self, cr, uid, context=None):
+ sheet_id = context.get('sheet_id')
+ if not sheet_id:
+ return time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
+
+ ts_obj = self.pool.get('hr_timesheet_sheet.sheet')
+ timesheet = ts_obj.browse(cr, uid, sheet_id, context=context)
+
+ dates = [a.name for a in timesheet.attendances_ids]
+
+ if not dates:
+ return timesheet.date_from
+
+ return max(dates)
+ """
+ Check sign in signout in the same timesheet only
+ """
+
+ def _altern_si_so(self, cr, uid, ids, context=None):
+ """ Alternance sign_in/sign_out check.
+ Previous (if exists) must be of opposite action.
+ Next (if exists) must be of opposite action.
+ """
+ sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
+ for att in self.browse(cr, uid, ids, context=context):
+ sheet_id = sheet_obj.search(
+ cr, uid, [
+ ('employee_id', '=', att.employee_id.id),
+ ('date_from', '<=', att.name),
+ ('date_to', '>=', att.name),
+ ],
+ limit=1,
+ context=context)
+ sheet_id = sheet_id and sheet_id[0] or False
+
+ # search and browse for first previous and first next records
+ prev_att_ids = self.search(cr, uid, [('employee_id', '=', att.employee_id.id),
+ ('sheet_id', '=', sheet_id),
+ ('name', '<', att.name),
+ ('action', 'in', ('sign_in', 'sign_out'))],
+ limit=1, order='name DESC',
+ context=context)
+ next_add_ids = self.search(cr, uid, [('employee_id', '=', att.employee_id.id),
+ ('sheet_id', '=', sheet_id),
+ ('name', '>', att.name),
+ ('action', 'in', ('sign_in', 'sign_out'))],
+ limit=1, order='name ASC',
+ context=context)
+ prev_atts = self.browse(cr, uid, prev_att_ids, context=context)
+ next_atts = self.browse(cr, uid, next_add_ids, context=context)
+ # check for alternance, return False if at least one condition is not satisfied
+ if prev_atts and prev_atts[0].action == att.action: # previous exists and is same action
+ return False
+ if next_atts and next_atts[0].action == att.action: # next exists and is same action
+ return False
+ if (not prev_atts) and (not next_atts) and att.action != 'sign_in': # first attendance must be sign_in
+ return False
+ return True
+
+ _constraints = [(_altern_si_so, 'Error ! Sign in (resp. Sign out) must follow Sign out (resp. Sign in)', ['action'])]
+
+ _defaults = {
+ 'name': _default_date,
+ }
+
=== added file 'hr_timesheet_improvement/hr_timesheet.py'
--- hr_timesheet_improvement/hr_timesheet.py 1970-01-01 00:00:00 +0000
+++ hr_timesheet_improvement/hr_timesheet.py 2013-11-07 15:51:52 +0000
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author : Yannick Vaucher (Camptocamp)
+# Copyright 2013 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+from openerp.osv import orm, fields
+
+class HrAnalyticTimesheet(orm.Model):
+ """
+ Set order by line date and analytic account name instead of id
+ We create related stored values as _order cannot be used on inherited columns
+ """
+ _inherit = "hr.analytic.timesheet"
+ _order = "date_aal DESC, account_name ASC"
+
+ def _get_account_analytic_line(self, cr, uid, ids, context=None):
+ ts_line_ids = self.pool.get('hr.analytic.timesheet').search(cr, uid, [('line_id', 'in', ids)])
+ return ts_line_ids
+
+ def _get_account_analytic_account(self, cr, uid, ids, context=None):
+ ts_line_ids = self.pool.get('hr.analytic.timesheet').search(cr, uid, [('account_id', 'in', ids)])
+ return ts_line_ids
+
+ _columns = {
+ 'date_aal': fields.related('line_id', 'date', string="Analytic Line Date", type='date',
+ store={
+ 'account.analytic.line': (_get_account_analytic_line, ['date'], 10),
+ 'hr.analytic.timesheet': (lambda self,cr,uid,ids,context=None: ids, None, 10),
+ }),
+ 'account_name': fields.related('account_id', 'name', string="Analytic Account Name", type='char', size=256,
+ store={
+ 'account.analytic.account': (_get_account_analytic_account, ['name'], 10),
+ 'hr.analytic.timesheet': (lambda self,cr,uid,ids,context=None: ids, None, 10),
+ }
+ ),
+ }
=== added file 'hr_timesheet_improvement/hr_timesheet_view.xml'
--- hr_timesheet_improvement/hr_timesheet_view.xml 1970-01-01 00:00:00 +0000
+++ hr_timesheet_improvement/hr_timesheet_view.xml 2013-11-07 15:51:52 +0000
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <record id="hr_timesheet_sheet_form_pass_active_id" model="ir.ui.view">
+ <field name="name">hr.timesheet.sheet.form</field>
+ <field name="model">hr_timesheet_sheet.sheet</field>
+ <field name="inherit_id" ref="hr_timesheet_sheet.hr_timesheet_sheet_form" />
+ <field name="arch" type="xml">
+ <field name="attendances_ids" position="attributes">
+ <attribute name="context">{'employee_id': employee_id, 'user_id':user_id, 'sheet_id':active_id}</attribute>
+ </field>
+ </field>
+ </record>
+
+ </data>
+</openerp>
=== modified file 'hr_timesheet_print/__openerp__.py'
--- hr_timesheet_print/__openerp__.py 2012-12-12 10:35:35 +0000
+++ hr_timesheet_print/__openerp__.py 2013-11-07 15:51:52 +0000
@@ -40,5 +40,5 @@
"report.xml",
],
"active": False,
- "installable": True
+ 'installable': True
}
=== modified file 'hr_timesheet_reminder/__init__.py'
--- hr_timesheet_reminder/__init__.py 2011-08-12 12:53:16 +0000
+++ hr_timesheet_reminder/__init__.py 2013-11-07 15:51:52 +0000
@@ -1,32 +1,21 @@
# -*- coding: utf-8 -*-
##############################################################################
#
-# Copyright (c) 2011 Camptocamp SA (http://www.camptocamp.com)
-# All Right Reserved
-#
-# Author : Arnaud Wüst (Camptocamp)
-# Author : Guewen Baconnier (Camptocamp)
-#
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Author: Arnaud Wüst (Camptocamp)
+# Copyright 2011-2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
=== modified file 'hr_timesheet_reminder/__openerp__.py'
--- hr_timesheet_reminder/__openerp__.py 2011-08-12 12:53:16 +0000
+++ hr_timesheet_reminder/__openerp__.py 2013-11-07 15:51:52 +0000
@@ -1,54 +1,52 @@
# -*- coding: utf-8 -*-
##############################################################################
#
-# Copyright (c) 2011 Camptocamp SA (http://www.camptocamp.com)
-# All Right Reserved
-#
-# Author : Arnaud Wüst (Camptocamp)
-# Author : Nicolas Bessi (Camptocamp)
-# Author : Guewen Baconnier (Camptocamp)
-#
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Author: Arnaud Wüst (Camptocamp)
+# Author: Nicolas Bessi (Camptocamp)
+# Author: Guewen Baconnier (Camptocamp) (port to v7)
+# Copyright 2011-2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
-
{
- "name" : "Timesheet Reminder",
- "version" : "2.0",
- "author" : "Camptocamp",
- "category" : "",
- "website" : "http://www.camptocamp.com",
+ "name": "Timesheet Reminder",
+ "version": "2.0",
+ "author": "Camptocamp",
+ "license": 'AGPL-3',
+ "category": "",
+ "website": "http://www.camptocamp.com",
"description": """
-Timesheet Reports Module:
- * Add a menu in Human Resources / Configuration / Timesheet Reminder. It allows to send automatic emails to those who did not complete their timesheet in the last 5 weeks.
- * Per employee, you can choose to send the reminder or not.
- * Add a report in Human Resources / Reporting / Timesheet / Timesheet Status which displays the state of the last 5 timesheets for all users per company.
-
-This module replaces the modules c2c_timesheet_reports in TinyERP 4 and OpenERP 5.
+Timesheet Reports Module
+========================
+
+ * Add a menu in `Human Resources / Configuration
+ / Timesheet Reminder`.
+ It allows to send automatic emails to those who did
+ not complete their timesheet in the last 5 weeks.
+ * Per employee, you can choose to send the reminder or not.
+ * Add a report in `Human Resources / Reporting / Timesheet
+ / Timesheet Status` which displays the state of the last
+ 5 timesheets for all users per company.
+
+This module replaces the modules c2c_timesheet_reports
+of TinyERP 4 and OpenERP 5.
""",
- "depends" : ["hr_timesheet_sheet"],
- "init_xml" : [],
- "update_xml" : [
+ "depends": ["hr_timesheet_sheet"],
+ "init_xml": [],
+ "update_xml": [
'security/ir.model.access.csv',
'wizard/reminder_config_view.xml',
'wizard/reminder_status_view.xml',
@@ -56,5 +54,5 @@
'timesheet_report.xml',
],
"active": False,
- "installable": True
+ 'installable': True
}
=== modified file 'hr_timesheet_reminder/company.py'
--- hr_timesheet_reminder/company.py 2012-12-13 08:25:08 +0000
+++ hr_timesheet_reminder/company.py 2013-11-07 15:51:52 +0000
@@ -1,43 +1,32 @@
# -*- coding: utf-8 -*-
##############################################################################
#
-# Copyright (c) 2011 Camptocamp SA (http://www.camptocamp.com)
-# All Right Reserved
-#
-# Author : Arnaud Wüst (Camptocamp)
-# Author : Guewen Baconnier (Camptocamp)
-#
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Author: Arnaud Wüst (Camptocamp)
+# Author: Guewen Baconnier (Camptocamp)
+# Copyright 2011-2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
-
-from datetime import date, datetime
-from dateutil.relativedelta import *
-from osv import fields, osv
-from tools.translate import _
-
-
-class res_company(osv.osv):
+from datetime import datetime
+from dateutil.relativedelta import relativedelta, MO, SU
+from openerp.osv import osv, orm
+from openerp.tools.translate import _
+
+
+class res_company(orm.Model):
_inherit = 'res.company'
def get_reminder_recipients(self, cr, uid, ids, context=None):
@@ -46,12 +35,25 @@
employee_obj = self.pool.get('hr.employee')
+<<<<<<< TREE
for company in self.browse(cr, uid, ids, context=context):
employee_ids = employee_obj.search(
cr, uid,
[('company_id', '=', company.id),
('active', '=', True)],
context=context)
+=======
+ for company in self.browse(cr, uid, ids, context=context):
+ employee_ids = employee_obj.search(
+ cr, uid,
+ [('company_id', '=', company.id),
+ ('receive_timesheet_alerts', '=', True)],
+ context=context)
+
+ if not employee_ids:
+ continue
+
+>>>>>>> MERGE-SOURCE
employees = employee_obj.browse(cr, uid, employee_ids, context=context)
#periods
@@ -62,24 +64,26 @@
# for each employee
for employee in employees:
# is timesheet for a period not confirmed ?
- for p_index in range(len(periods)):
- period = periods[p_index]
+ for period in periods:
status = employee_obj.compute_timesheet_status(cr, uid, employee.id, period, context)
# if there is a missing sheet or a draft sheet
# and the user can receive alerts
# then we must alert the user
- if status in ['Missing', 'Draft'] and employee.receive_timesheet_alerts:
+ if status in ['Missing', 'Draft']:
res[company.id].append(employee)
- break # no need to go further for this user, he is now added in the list, go to the next one
+ # no need to go further for this user,
+ # he is now added in the list, go to the next one
+ break
return res
def compute_timesheet_periods(self, cr, uid, company, date, periods_number=5, context=None):
""" return the timeranges to display. This is the 5 last timesheets"""
periods = []
- last_start_date, last_end_date = self.get_last_period_dates(cr, uid, company, date, context=context)
+ last_start_date, last_end_date = self.get_last_period_dates(
+ cr, uid, company, date, context=context)
for cpt in range(periods_number):
- #find the delta between last_XXX_date to XXX_date
+ # find the delta between last_XXX_date to XXX_date
if company.timesheet_range == 'month':
delta = relativedelta(months=-cpt)
elif company.timesheet_range == 'week':
@@ -87,7 +91,9 @@
elif company.timesheet_range == 'year':
delta = relativedelta(years=-cpt)
else:
- raise osv.except_osv(_('Error'), _('Unknow timesheet range: %s') % (company.timesheet_range,))
+ raise osv.except_osv(
+ _('Error'),
+ _('Unknow timesheet range: %s') % company.timesheet_range)
start_date = last_start_date + delta
end_date = last_end_date + delta
@@ -97,26 +103,22 @@
def get_last_period_dates(self, cr, uid, company, date, context=None):
""" return the start date and end date of the last period to display """
-
+
# return the first day and last day of the month
if company.timesheet_range == 'month':
start_date = date
- end_date = start_date + relativedelta(months = +1)
+ end_date = start_date + relativedelta(months=+1)
#return the first and last days of the week
elif company.timesheet_range == 'week':
# get monday of current week
start_date = date + relativedelta(weekday=MO(-1))
- # get sunday of current week
+ # get sunday of current week
end_date = date + relativedelta(weekday=SU(+1))
# return the first and last days of the year
else:
- start_date = datetime(date.year, 1, 1)
+ start_date = datetime(date.year, 1, 1)
end_date = datetime(date.year, 12, 31)
-
return start_date, end_date
-
-
-res_company()
=== modified file 'hr_timesheet_reminder/hr_employee.py'
--- hr_timesheet_reminder/hr_employee.py 2011-09-01 13:44:19 +0000
+++ hr_timesheet_reminder/hr_employee.py 2013-11-07 15:51:52 +0000
@@ -1,47 +1,38 @@
# -*- coding: utf-8 -*-
##############################################################################
#
-# Copyright (c) 2011 Camptocamp SA (http://www.camptocamp.com)
-# All Right Reserved
-#
-# Author : Arnaud Wüst (Camptocamp)
-# Author : Guewen Baconnier (Camptocamp)
-#
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Author: Arnaud Wüst (Camptocamp)
+# Author: Guewen Baconnier (Camptocamp) (port to v7)
+# Copyright 2011-2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
-from datetime import *
-
-from osv import fields, osv
-
-
-class hr_employee(osv.osv):
+
+from openerp.osv import fields, orm
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
+
+
+class hr_employee(orm.Model):
_inherit = 'hr.employee'
+
_columns = {
- 'receive_timesheet_alerts': fields.boolean('Receive Timesheet Alerts'),
+ 'receive_timesheet_alerts': fields.boolean('Receive Timesheet Alerts'),
}
_defaults = {
- 'receive_timesheet_alerts': lambda *a: True,
+ 'receive_timesheet_alerts': True,
}
def compute_timesheet_status(self, cr, uid, ids, period, context):
@@ -50,35 +41,33 @@
status = 'Error'
if isinstance(ids, list):
+ assert len(ids) == 1, "Only 1 ID expected"
ids = ids[0]
employee = self.browse(cr, uid, ids, context=context)
- time_from = period[0]
- time_to = period[1]
-
- # does the timesheet exsists in db and what is its status?
- timeformat = "%Y-%m-%d"
- str_date_from = time_from.strftime(timeformat)
- str_date_to = time_to.strftime(timeformat)
-
- cr.execute("""SELECT state, date_from, date_to
- FROM hr_timesheet_sheet_sheet
- WHERE employee_id = %s
- AND date_from >= %s
- AND date_to <= %s""",
+ time_from, time_to = period
+
+ # does the timesheet exists in db and what is its status?
+ str_date_from = time_from.strftime(DEFAULT_SERVER_DATE_FORMAT)
+ str_date_to = time_to.strftime(DEFAULT_SERVER_DATE_FORMAT)
+
+ cr.execute(
+ """SELECT state, date_from, date_to
+ FROM hr_timesheet_sheet_sheet
+ WHERE employee_id = %s
+ AND date_from >= %s
+ AND date_to <= %s""",
(employee.id, str_date_from, str_date_to))
sheets = cr.dictfetchall()
- #the timesheet does not exists in db
+ # the timesheet does not exists in db
if not sheets:
status = 'Missing'
- if len(sheets) > 0:
+ else:
status = 'Confirmed'
- for s in sheets:
- if s['state'] == 'draft':
+ for sheet in sheets:
+ if sheet['state'] == 'draft':
status = 'Draft'
return status
-
-hr_employee()
=== modified file 'hr_timesheet_reminder/hr_employee_view.xml'
--- hr_timesheet_reminder/hr_employee_view.xml 2011-08-12 12:53:16 +0000
+++ hr_timesheet_reminder/hr_employee_view.xml 2013-11-07 15:51:52 +0000
@@ -5,7 +5,6 @@
<field name="name">hr.timesheet.employee.rmnd_form</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_employee_extd_form"/>
<field name="model">hr.employee</field>
- <field name="type">form</field>
<field name="arch" type="xml">
<field name="journal_id" widget="selection" position="after">
<field name="receive_timesheet_alerts"/>
=== modified file 'hr_timesheet_reminder/reminder.py'
--- hr_timesheet_reminder/reminder.py 2011-09-01 13:44:19 +0000
+++ hr_timesheet_reminder/reminder.py 2013-11-07 15:51:52 +0000
@@ -1,157 +1,170 @@
# -*- coding: utf-8 -*-
##############################################################################
#
-# Copyright (c) 2011 Camptocamp SA (http://www.camptocamp.com)
-# All Right Reserved
-#
-# Author : Arnaud Wüst (Camptocamp)
-# Author : Guewen Baconnier (Camptocamp)
-#
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Author: Arnaud Wüst (Camptocamp)
+# Author: Guewen Baconnier (Camptocamp) (port to v7)
+# Copyright 2011-2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
-import tools
-import time
-
from datetime import datetime, timedelta
-from osv import fields, osv
-from tools.translate import _
-
-class reminder(osv.osv):
+from openerp.osv import fields, orm
+from openerp.tools.translate import _
+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
+
+
+class reminder(orm.Model):
_name = "hr.timesheet.reminder"
_description = "Handle the scheduling of timesheet reminders"
_columns = {
- 'reply_to': fields.char('Reply To', size=100),
- 'message': fields.text('Message'),
- 'subject': fields.char('Subject', size=200),
+ 'reply_to': fields.char('Reply To'),
+ 'message': fields.html('Message'),
+ 'subject': fields.char('Subject'),
}
- #default cron (the one created if missing)
+ # default cron (the one created if missing)
cron = {'active': False,
'priority': 1,
'interval_number': 1,
'interval_type': 'weeks',
- 'nextcall': time.strftime("%Y-%m-%d %H:%M:%S",
- (datetime.today()
- + timedelta(days=1)).timetuple()), # tomorrow same time
+ 'nextcall': False, # to set on the creation of the cron
'numbercall': -1,
- 'doall': True,
+ 'doall': False,
'model': 'hr.timesheet.reminder',
'function': 'run',
'args': '()',
}
- #default message (the one created if missing)
+ # default message (the one created if missing)
message = {'reply_to': 'spam@xxxxxxxxxxxxxx'}
def run(self, cr, uid, context=None):
""" find the reminder recipients and send them an email """
- context = context or {}
-
company_obj = self.pool.get('res.company')
- #get all companies
+ # get all companies
company_ids = company_obj.search(cr, uid, [], context=context)
- #for each company, get all recipients
+ # for each company, get all recipients
recipients = []
- company_recipients = company_obj.get_reminder_recipients(cr, uid, company_ids, context=context)
- for company_id, rec in company_recipients.iteritems():
+ company_recipients = company_obj.get_reminder_recipients(
+ cr, uid, company_ids, context=context)
+ for rec in company_recipients.itervalues():
recipients += rec
- #get the message to send
+ # get the message to send
message_id = self.get_message_id(cr, uid, context)
message_data = self.browse(cr, uid, message_id, context=context)
- #send them email if they have an email defined
- emails = []
+ # send them email if they have an email defined
for employee in recipients:
- if employee.work_email:
- emails.append(employee.work_email)
-
- if emails:
- tools.email_send(message_data.reply_to, [], message_data.subject, message_data.message, email_bcc=emails)
-
- def get_cron_id(self, cr, uid, context):
+ if not employee.work_email:
+ continue
+ vals = {
+ 'state': 'outgoing',
+ 'subject': message_data.subject,
+ 'body_html': message_data.message,
+ 'email_to': employee.work_email,
+ 'email_from': message_data.reply_to,
+ }
+ self.pool.get('mail.mail').create(cr, uid, vals, context=context)
+
+ return True
+
+ def get_cron_id(self, cr, uid, context=None):
"""return the reminder cron's id. Create one if the cron does not exists """
+ if context is None:
+ context = {}
cron_obj = self.pool.get('ir.cron')
# find the cron that send messages
- cron_id = cron_obj.search(cr, uid, [('function', 'ilike', self.cron['function']),
- ('model', 'ilike', self.cron['model'])],
- context={'active_test': False})
- if cron_id:
- cron_id = cron_id[0]
+ ctx = dict(context, active_test=False)
+ cron_ids = cron_obj.search(
+ cr, uid,
+ [('function', 'ilike', self.cron['function']),
+ ('model', 'ilike', self.cron['model'])],
+ context=ctx)
+
+ cron_id = None
+ if cron_ids:
+ cron_id = cron_ids[0]
# the cron does not exists
- if not cron_id:
- self.cron['name'] = _('timesheet status reminder')
- cron_id = cron_obj.create(cr, uid, self.cron, context)
+ if cron_id is None:
+ vals = dict(self.cron,
+ name=_('timesheet status reminder'),
+ nextcall=self._cron_nextcall())
+
+ cron_id = cron_obj.create(cr, uid, vals, context=context)
return cron_id
- def get_message_id(self, cr, uid, context):
- """ return the message'id. create one if the message does not exists """
+ @staticmethod
+ def _cron_nextcall():
+ tomorrow = datetime.today() + timedelta(days=1)
+ return tomorrow.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
+
+ def get_message_id(self, cr, uid, context=None):
+ """ return the message's id. create one if the message does not exists """
#there is only one line in db, let's get it
- message_id = self.search(cr, uid, [], limit=1, context=context)
+ message_ids = self.search(cr, uid, [], limit=1, context=context)
- if message_id:
- message_id = message_id[0]
+ message_id = None
+ if message_ids:
+ message_id = message_ids[0]
#the message does not exists
- if not message_id:
- #translate
- self.message['subject'] = _('Timesheet Reminder')
- self.message['message'] = _('At least one of your last timesheets is still in draft or is missing. Please take time to complete and confirm it.')
+ if message_id is None:
+ vals = dict(self.message,
+ subject=_('Timesheet Reminder'),
+ message=_(
+ 'At least one of your last timesheets is still '
+ 'in draft or is missing. Please take time to '
+ 'complete and confirm it.'))
- message_id = self.create(cr, uid, self.message, context)
+ message_id = self.create(cr, uid, vals, context)
return message_id
- def get_config(self, cr, uid, context):
+ def get_config(self, cr, uid, context=None):
"""return the reminder config from the db """
cron_id = self.get_cron_id(cr, uid, context)
- cron_data = self.pool.get('ir.cron').browse(cr, uid, cron_id)
+ cron_data = self.pool.get('ir.cron').browse(
+ cr, uid, cron_id, context=context)
- #there is only one line in db, let's get it
- message_id = self.get_message_id(cr, uid, context)
- message_data = self.browse(cr, uid, message_id)
+ # there is only one line in db, let's get it
+ message_id = self.get_message_id(cr, uid, context=context)
+ message_data = self.browse(cr, uid, message_id, context=context)
return {'reminder_active': cron_data.active,
'interval_type': cron_data.interval_type,
'interval_number': cron_data.interval_number,
'reply_to': message_data.reply_to,
'message': message_data.message,
'subject': message_data.subject,
- 'nextcall': cron_data.nextcall,
+ 'nextcall': self._cron_nextcall(),
}
- def save_config(self, cr, uid, ids, datas, context):
+ def save_config(self, cr, uid, ids, datas, context=None):
"""save the reminder config """
#modify the cron
- cron_id = self.get_cron_id(cr, uid, context)
- self.pool.get('ir.cron').write(cr, uid, [cron_id],
+ cron_id = self.get_cron_id(cr, uid, context=context)
+ self.pool.get('ir.cron').write(
+ cr, uid, [cron_id],
{'active': datas['reminder_active'],
'interval_number': datas['interval_number'],
'interval_type': datas['interval_type'],
@@ -159,11 +172,10 @@
context=context)
#modify the message
message_id = ids or self.get_message_id(cr, uid, context)
- self.write(cr, uid, [message_id],
+ self.write(
+ cr, uid, [message_id],
{'reply_to': datas['reply_to'],
'message': datas['message'],
'subject': datas['subject'],
}, context=context)
return True
-
-reminder()
=== modified file 'hr_timesheet_reminder/report/__init__.py'
--- hr_timesheet_reminder/report/__init__.py 2011-08-12 12:53:16 +0000
+++ hr_timesheet_reminder/report/__init__.py 2013-11-07 15:51:52 +0000
@@ -1,31 +1,22 @@
# -*- coding: utf-8 -*-
##############################################################################
#
-# Copyright (c) Camptocamp SA
-# Author: Arnaud WÃŒst
-#
-#
-#
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Author: Arnaud Wüst (Camptocamp)
+# Copyright 2011-2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
+
import timesheet_status
=== modified file 'hr_timesheet_reminder/report/timesheet_status.py'
--- hr_timesheet_reminder/report/timesheet_status.py 2011-09-01 13:44:19 +0000
+++ hr_timesheet_reminder/report/timesheet_status.py 2013-11-07 15:51:52 +0000
@@ -1,47 +1,40 @@
# -*- coding: utf-8 -*-
##############################################################################
#
-# Copyright (c) Camptocamp SA
-# Author: Arnaud WÃŒst
-# Author: Guewen Baconnier
-#
-#
-#
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Author: Arnaud Wüst (Camptocamp)
+# Author: Guewen Baconnier (Camptocamp) (port to v7)
+# Copyright 2011-2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import time
from datetime import datetime
-from report import report_sxw
+from openerp.report import report_sxw
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
+from openerp.tools.translate import _
class timesheet_status(report_sxw.rml_parse):
_name = 'report.timesheet.reminder.status'
- def __init__(self, cr, uid, name, context):
- super(timesheet_status, self).__init__(cr, uid, name, context)
+ def __init__(self, cr, uid, name, context=None):
+ super(timesheet_status, self).__init__(cr, uid, name, context=context)
self.data = {}
+ self.end_date = None
self.localcontext.update({
'compute': self.compute,
'time': time,
@@ -60,64 +53,65 @@
"""compute all datas and do all the calculations before to start the rml rendering
- objects are companies
"""
- #init the data array
+ # init the data array
self.data = {}
for o in objects:
self.data[o.id] = {}
- #get the list of employees ids to treat
+ # get the list of employees ids to treat
for o in objects:
self.data[o.id]['employees'] = self._compute_employees_list(o)
- #get the time range for each company
+ # get the time range for each company
+ end_date = datetime.strptime(self.end_date, DEFAULT_SERVER_DATE_FORMAT)
for o in objects:
- self.data[o.id]['time_ranges'] = \
- self._compute_periods(o, datetime.strptime(self.end_date, "%Y-%m-%d"))
+ self.data[o.id]['time_ranges'] = self._compute_periods(o, end_date)
- #get the status of each timesheet for each employee
+ # get the status of each timesheet for each employee
for o in objects:
self.data[o.id]['sheet_status'] = self._compute_all_status(o)
def _compute_employees_list(self, company):
- """ return a dictionnary of lists of employees ids linked to the companies (param company) """
+ """ return a dictionnary of lists of employees ids linked
+ to the companies (param company) """
employee_obj = self.pool.get('hr.employee')
employee_ids = employee_obj.search(self.cr, self.uid,
[('company_id', '=', company.id),
('active', '=', True)],
context=self.localcontext)
- return employee_obj.browse(self.cr, self.uid, employee_ids, context=self.localcontext)
+ return employee_obj.browse(
+ self.cr, self.uid, employee_ids, context=self.localcontext)
def _get_last_period_dates(self, company, date):
""" return the start date of the last period to display """
- return self.pool.get('res.company').\
- get_last_period_dates(self.cr, self.uid, company, date, context=self.localcontext)
+ return self.pool.get('res.company').get_last_period_dates(
+ self.cr,
+ self.uid,
+ company,
+ date,
+ context=self.localcontext)
def _compute_periods(self, company, date):
""" return the timeranges to display. This is the 5 last timesheets """
- return self.pool.get('res.company').\
- compute_timesheet_periods(self.cr, self.uid, company, date, context=self.localcontext)
+ return self.pool.get('res.company').compute_timesheet_periods(
+ self.cr,
+ self.uid,
+ company,
+ date,
+ context=self.localcontext)
def get_title(self, obj):
""" return the title of the main table """
- last_id = len(self.data[obj.id]['time_ranges']) - 1
- start_date = time.strptime(str(self.data[obj.id]['time_ranges'][last_id][0]),
- "%Y-%m-%d %H:%M:%S")
- start_date = time.strftime("%d.%m.%Y", start_date)
-
- end_date = time.strptime(str(self.data[obj.id]['time_ranges'][0][1]),
- "%Y-%m-%d %H:%M:%S")
- end_date = time.strftime("%d.%m.%Y", end_date)
-
- return obj.name + ", " + start_date + " to " + end_date
+ timerange = self.data[obj.id]['time_ranges']
+ start_date = self.formatLang(timerange[-1][0], date=True)
+ end_date = self.formatLang(timerange[0][1], date=True)
+
+ return obj.name + ", " + start_date + _(" to ") + end_date
def get_timerange_title(self, obj, cpt):
""" return a header text for a periods column """
- start_date = self.data[obj.id]['time_ranges'][cpt][0]
- start_date = time.strptime(str(start_date), "%Y-%m-%d %H:%M:%S")
- start_date = time.strftime("%d.%m.%Y", start_date)
-
- end_date = self.data[obj.id]['time_ranges'][cpt][1]
- end_date = time.strptime(str(end_date), "%Y-%m-%d %H:%M:%S")
- end_date = time.strftime("%d.%m.%Y", end_date)
+ timerange = self.data[obj.id]['time_ranges'][cpt]
+ start_date = self.formatLang(timerange[0], date=True)
+ end_date = self.formatLang(timerange[1], date=True)
return start_date + "\n " + end_date
@@ -131,21 +125,25 @@
def _compute_timesheet_status(self, employee_id, period):
""" return the timesheet status for a user and a period """
- return self.pool.get('hr.employee').\
- compute_timesheet_status(self.cr, self.uid, employee_id, period, context=self.localcontext)
+ return self.pool.get('hr.employee').compute_timesheet_status(
+ self.cr,
+ self.uid,
+ employee_id,
+ period,
+ context=self.localcontext)
- def _compute_all_status(self, o):
+ def _compute_all_status(self, obj):
""" compute all status for all employees for all periods """
result = {}
#for each periods
- for p_index in range(len(self.data[o.id]['time_ranges'])):
+ for p_index, period in enumerate(self.data[obj.id]['time_ranges']):
result[p_index] = {}
- period = self.data[o.id]['time_ranges'][p_index]
#for each employees
- for employee in self.data[o.id]['employees']:
+ for employee in self.data[obj.id]['employees']:
#compute the status
- result[p_index][employee.id] = self._compute_timesheet_status(employee.id, period)
+ result[p_index][employee.id] = self._compute_timesheet_status(
+ employee.id, period)
return result
=== modified file 'hr_timesheet_reminder/report/timesheet_status.rml'
--- hr_timesheet_reminder/report/timesheet_status.rml 2011-08-12 12:53:16 +0000
+++ hr_timesheet_reminder/report/timesheet_status.rml 2013-11-07 15:51:52 +0000
@@ -1,165 +1,159 @@
<?xml version="1.0"?>
<document filename="timesheet_status.pdf">
-##############################################################################
-#
-# Copyright (c) Camptocamp SA
-# Author: Arnaud Wüst
-#
-#
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-#
-##############################################################################
-
- <!-- Process all datas -->
- <template pageSize="(21cm,29.7cm)" title="Timesheet Status" author="Camptocamp" allowSplitting="20">
-
-
- <!-- PAGE: template of all pages (= all pages except first and last if defined)-->
- <pageTemplate id="all">
- <pageGraphics>
- <setFont name="Helvetica-Bold" size="9"/>
-
- <!--Header-->
- <drawString x="1.2cm" y="28.1cm">[[ company.name ]]</drawString>
- <drawString x="17.0cm" y="28.1cm">Timesheet Status</drawString>
-
- <lineMode width="0.7"/>
- <lines>1.2cm 28.0cm 19.8cm 28.0cm</lines>
-
- <!-- Footer -->
- <setFont name="Helvetica" size="9"/>
- <drawString x="1.2cm" y="1.3cm"> [[ time.strftime("%m-%d-%y %H:%M", time.localtime()) ]]</drawString>
- <drawString x="18.8cm" y="1.3cm">Page <pageNumber/></drawString>
-
- </pageGraphics>
- <frame id="all" x1="1.2cm" y1="1.7cm" width="18.6cm" height="25.8cm"/>
- </pageTemplate>
-
- </template>
- <stylesheet>
-
- <!--TABLE: standard table type "columns" (which means there is a title, a header of fields names and then lines of values) -->
- <blockTableStyle id="std">
- <blockAlignment value="LEFT"/>
- <blockValign value="TOP"/>
- <blockBottomPadding length="4"/>
- <blockFont name="Helvetica" size="9" start="0,0" stop="-1,-1"/>
-
- <!-- first line: table name, fake as it was only one cell. grey bg -->
- <lineStyle kind="BOX" colorName="black" start="0,0" stop="-1,0"/>
- <blockBackground colorName="#cccccc" start="0,0" stop="-1,0"/>
- <blockFont name="Helvetica" size="9" start="0,0" stop="-1,0"/>
- <!-- second line: header of columns -->
- <lineStyle kind="GRID" colorName="black" start="0,1" stop="-1,1"/>
- <blockFont name="Helvetica-Oblique" start="0,1" stop="-1,1"/>
- <!-- next lines: light grey lines, strong black columns separator, reduce padding to write more data in cells -->
- <lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="0,2" stop="-1,-1"/>
- <lineStyle kind="LINEAFTER" colorName="black" start="0,2" stop="-1,-1"/>
- <!-- last line: line below -->
- <lineStyle kind="OUTLINE" colorName="black" start="0,0" stop="-1,-1"/>
- <!-- all columns centered except the first two (1 system columns + employee) -->
- <blockAlignment value="CENTER" start="2,1" stop="-1,-1" />
-
- </blockTableStyle>
-
- <!-- default para in tables -->
- <paraStyle name="std"
- fontName="Helvetica"
- fontSize="9"
- alignment="LEFT"
- />
-
- <paraStyle name="Confirmed"
- fontName="Helvetica"
- fontSize="9"
- alignment="CENTER"
- backColor="green"
- textColor="white"
- />
-
- <paraStyle name="Missing"
- fontName="Helvetica"
- fontSize="9"
- alignment="CENTER"
- backColor="red"
- textColor="white"
- />
-
- <paraStyle name="Draft"
- fontName="Helvetica"
- fontSize="9"
- alignment="CENTER"
- backColor="orange"
- textColor="black"
- />
-
- <paraStyle name="Error"
- fontName="Helvetica"
- fontSize="9"
- alignment="CENTER"
- textColor="red"
- />
-
- <paraStyle name="Not in Company"
- fontName="Helvetica"
- fontSize="9"
- alignment="CENTER"
- textColor="lightgrey"
- />
-
-
- </stylesheet>
-
- <story>
-
- [[repeatIn(objects, 'o')]]
- <blockTable style="std" repeatRows="2" colWidths="0,5cm,2.7cm,2.7cm,2.7cm,2.7cm,2.7cm" >
-
- <tr>
- <td/>
- <td>[[get_title(o)]]</td>
- </tr>
-
- <tr>
- <td/>
- <td>Employees</td>
- <td>[[ get_timerange_title(o, 4) ]]</td>
- <td>[[ get_timerange_title(o, 3) ]]</td>
- <td>[[ get_timerange_title(o, 2) ]]</td>
- <td>[[ get_timerange_title(o, 1) ]]</td>
- <td>[[ get_timerange_title(o, 0) ]]</td>
- </tr>
-
- <tr>
- <td>[[repeatIn(get_user_list(o),'u')]]</td>
- <td><para style="std">[[ u.name ]]</para></td>
- <td><para>[[ setTag('para','para',{'style':get_timesheet_status(o, u, 4)}) ]][[ get_timesheet_status(o, u, 4) ]]</para></td>
- <td><para>[[ setTag('para','para',{'style':get_timesheet_status(o, u, 3)}) ]][[ get_timesheet_status(o, u, 3) ]]</para></td>
- <td><para>[[ setTag('para','para',{'style':get_timesheet_status(o, u, 2)}) ]][[ get_timesheet_status(o, u, 2) ]]</para></td>
- <td><para>[[ setTag('para','para',{'style':get_timesheet_status(o, u, 1)}) ]][[ get_timesheet_status(o, u, 1) ]]</para></td>
- <td><para>[[ setTag('para','para',{'style':get_timesheet_status(o, u, 0)}) ]][[ get_timesheet_status(o, u, 0) ]]</para></td>
- </tr>
-
- </blockTable>
-
- </story>
- </document>
+ <!--
+ ##############################################################################
+ #
+ # Author: Arnaud Wüst (Camptocamp)
+ # Copyright 2011-2012 Camptocamp SA
+ #
+ # This program is free software: you can redistribute it and/or modify
+ # it under the terms of the GNU Affero General Public License as
+ # published by the Free Software Foundation, either version 3 of the
+ # License, or (at your option) any later version.
+ #
+ # This program is distributed in the hope that it will be useful,
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ # GNU Affero General Public License for more details.
+ #
+ # You should have received a copy of the GNU Affero General Public License
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
+ #
+ ##############################################################################
+ -->
+
+
+ <!-- Process all datas -->
+ <template pageSize="(21cm,29.7cm)" title="Timesheet Status" author="Camptocamp" allowSplitting="20">
+
+
+ <!-- PAGE: template of all pages (= all pages except first and last if defined)-->
+ <pageTemplate id="all">
+ <pageGraphics>
+ <setFont name="Helvetica-Bold" size="9"/>
+
+ <!--Header-->
+ <drawString x="1.2cm" y="28.1cm">[[ company.name ]]</drawString>
+ <drawString x="17.0cm" y="28.1cm">Timesheet Status</drawString>
+
+ <lineMode width="0.7"/>
+ <lines>1.2cm 28.0cm 19.8cm 28.0cm</lines>
+
+ <!-- Footer -->
+ <setFont name="Helvetica" size="9"/>
+ <drawString x="1.2cm" y="1.3cm"> [[ time.strftime("%m-%d-%y %H:%M", time.localtime()) ]]</drawString>
+ <drawString x="18.8cm" y="1.3cm">Page <pageNumber/></drawString>
+
+ </pageGraphics>
+ <frame id="all" x1="1.2cm" y1="1.7cm" width="18.6cm" height="25.8cm"/>
+ </pageTemplate>
+
+ </template>
+ <stylesheet>
+
+ <!--TABLE: standard table type "columns" (which means there is a title, a header of fields names and then lines of values) -->
+ <blockTableStyle id="std">
+ <blockAlignment value="LEFT"/>
+ <blockValign value="TOP"/>
+ <blockBottomPadding length="4"/>
+ <blockFont name="Helvetica" size="9" start="0,0" stop="-1,-1"/>
+
+ <!-- first line: table name, fake as it was only one cell. grey bg -->
+ <lineStyle kind="BOX" colorName="black" start="0,0" stop="-1,0"/>
+ <blockBackground colorName="#cccccc" start="0,0" stop="-1,0"/>
+ <blockFont name="Helvetica" size="9" start="0,0" stop="-1,0"/>
+ <!-- second line: header of columns -->
+ <lineStyle kind="GRID" colorName="black" start="0,1" stop="-1,1"/>
+ <blockFont name="Helvetica-Oblique" start="0,1" stop="-1,1"/>
+ <!-- next lines: light grey lines, strong black columns separator, reduce padding to write more data in cells -->
+ <lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="0,2" stop="-1,-1"/>
+ <lineStyle kind="LINEAFTER" colorName="black" start="0,2" stop="-1,-1"/>
+ <!-- last line: line below -->
+ <lineStyle kind="OUTLINE" colorName="black" start="0,0" stop="-1,-1"/>
+ <!-- all columns centered except the first two (1 system columns + employee) -->
+ <blockAlignment value="CENTER" start="2,1" stop="-1,-1" />
+
+ </blockTableStyle>
+
+ <!-- default para in tables -->
+ <paraStyle name="std"
+ fontName="Helvetica"
+ fontSize="9"
+ alignment="LEFT"
+ />
+
+ <paraStyle name="Confirmed"
+ fontName="Helvetica"
+ fontSize="9"
+ alignment="CENTER"
+ backColor="green"
+ textColor="white"
+ />
+
+ <paraStyle name="Missing"
+ fontName="Helvetica"
+ fontSize="9"
+ alignment="CENTER"
+ backColor="red"
+ textColor="white"
+ />
+
+ <paraStyle name="Draft"
+ fontName="Helvetica"
+ fontSize="9"
+ alignment="CENTER"
+ backColor="orange"
+ textColor="black"
+ />
+
+ <paraStyle name="Error"
+ fontName="Helvetica"
+ fontSize="9"
+ alignment="CENTER"
+ textColor="red"
+ />
+
+ <paraStyle name="Not in Company"
+ fontName="Helvetica"
+ fontSize="9"
+ alignment="CENTER"
+ textColor="lightgrey"
+ />
+
+
+ </stylesheet>
+
+ <story>
+
+ [[repeatIn(objects, 'o')]]
+ <blockTable style="std" repeatRows="2" colWidths="0,5cm,2.7cm,2.7cm,2.7cm,2.7cm,2.7cm" >
+
+ <tr>
+ <td/>
+ <td>[[get_title(o)]]</td>
+ </tr>
+
+ <tr>
+ <td/>
+ <td>Employees</td>
+ <td>[[ get_timerange_title(o, 4) ]]</td>
+ <td>[[ get_timerange_title(o, 3) ]]</td>
+ <td>[[ get_timerange_title(o, 2) ]]</td>
+ <td>[[ get_timerange_title(o, 1) ]]</td>
+ <td>[[ get_timerange_title(o, 0) ]]</td>
+ </tr>
+
+ <tr>
+ <td>[[repeatIn(get_user_list(o),'u')]]</td>
+ <td><para style="std">[[ u.name ]]</para></td>
+ <td><para>[[ setTag('para', 'para', {'style': get_timesheet_status(o, u, 4)}) ]][[ get_timesheet_status(o, u, 4) ]]</para></td>
+ <td><para>[[ setTag('para', 'para', {'style': get_timesheet_status(o, u, 3)}) ]][[ get_timesheet_status(o, u, 3) ]]</para></td>
+ <td><para>[[ setTag('para', 'para', {'style': get_timesheet_status(o, u, 2)}) ]][[ get_timesheet_status(o, u, 2) ]]</para></td>
+ <td><para>[[ setTag('para', 'para', {'style': get_timesheet_status(o, u, 1)}) ]][[ get_timesheet_status(o, u, 1) ]]</para></td>
+ <td><para>[[ setTag('para', 'para', {'style': get_timesheet_status(o, u, 0)}) ]][[ get_timesheet_status(o, u, 0) ]]</para></td>
+ </tr>
+
+ </blockTable>
+
+ </story>
+</document>
=== modified file 'hr_timesheet_reminder/timesheet_report.xml'
--- hr_timesheet_reminder/timesheet_report.xml 2011-08-12 12:53:16 +0000
+++ hr_timesheet_reminder/timesheet_report.xml 2013-11-07 15:51:52 +0000
@@ -1,15 +1,15 @@
<?xml version="1.0"?>
<openerp>
- <data>
- <report
- id="timesheet_status"
- string="Timesheet Status"
- model="res.company"
- name="timesheet.reminder.status"
- rml="hr_timesheet_reminder/report/timesheet_status.rml"
- auto="False"
+ <data>
+ <report
+ id="timesheet_status"
+ string="Timesheet Status"
+ model="res.company"
+ name="timesheet.reminder.status"
+ rml="hr_timesheet_reminder/report/timesheet_status.rml"
+ auto="False"
header="True"
menu="False"/>
-
- </data>
+
+ </data>
</openerp>
=== modified file 'hr_timesheet_reminder/wizard/reminder_config.py'
--- hr_timesheet_reminder/wizard/reminder_config.py 2011-08-12 12:53:16 +0000
+++ hr_timesheet_reminder/wizard/reminder_config.py 2013-11-07 15:51:52 +0000
@@ -1,53 +1,48 @@
# -*- coding: utf-8 -*-
##############################################################################
#
-# Copyright (c) 2011 Camptocamp SA (http://www.camptocamp.com)
-# All Right Reserved
-#
-# Author : Guewen Baconnier (Camptocamp)
-# Author : Arnaud Wüst (Camptocamp)
-#
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Author: Arnaud Wüst (Camptocamp)
+# Author: Guewen Baconnier (Camptocamp) (port to v7)
+# Copyright 2011-2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
-from osv import osv, fields
-
-
-class reminder_config(osv.osv_memory):
+from openerp.osv import orm, fields
+
+
+class reminder_config(orm.TransientModel):
_name = 'hr.timesheet.reminder.config'
_columns = {
'reminder_active': fields.boolean('Reminder Active'),
- 'interval_type': fields.selection([('days','Day(s)'), ('weeks', 'Week(s)'), ('months', 'Month(s)')],
- 'Periodicity Unit',),
- 'interval_number': fields.integer('Periodicity Quantity',),
- 'nextcall': fields.datetime('Next Run',),
- 'message': fields.text('Message', required=True),
- 'subject': fields.char('Subject', size=200, required=True),
- 'reply_to': fields.char('Reply To', size=100, required=True),
+ 'interval_type': fields.selection(
+ [('days', 'Day(s)'),
+ ('weeks', 'Week(s)'),
+ ('months', 'Month(s)')],
+ 'Periodicity Unit'),
+ 'interval_number': fields.integer('Periodicity Quantity'),
+ 'nextcall': fields.datetime('Next Run'),
+ 'message': fields.html('Message', required=True),
+ 'subject': fields.char('Subject', required=True),
+ 'reply_to': fields.char('Reply To', required=True),
}
def _check_interval_number(self, cr, uid, ids, context=None):
+ """This constraint should always have 1 id, we are in a TransientModel"""
+ assert len(ids) == 1, "Only 1 ID expected"
obj = self.browse(cr, uid, ids[0], context=context)
if obj.interval_number < 1:
return False
@@ -60,23 +55,22 @@
def default_get(self, cr, uid, fields, context=None):
res = super(reminder_config, self).default_get(cr, uid, fields, context=context)
data = self.pool.get('hr.timesheet.reminder').\
- get_config(cr, uid, context)
+ get_config(cr, uid, context=context)
res.update(data)
return res
- def run(self, cr, uid, ids, context):
+ def run(self, cr, uid, ids, context=None):
""" execute the timesheets check and send emails """
reminder_obj = self.pool.get('hr.timesheet.reminder')
reminder_obj.run(cr, uid, context=context)
return {'type': 'ir.actions.act_window_close'}
- def save(self, cr, uid, ids, context):
+ def save(self, cr, uid, ids, context=None):
""" save defined settings in db """
-
# get the wizard datas
- wizard = self.browse(cr, uid, ids, context=context)[0]
+ wizard = self.browse(cr, uid, ids[0], context=context)
- #retrieve the default cron values
+ # retrieve the default cron values
reminder_obj = self.pool.get('hr.timesheet.reminder')
values = {}.fromkeys(wizard._columns.keys(), False)
@@ -85,5 +79,3 @@
reminder_obj.save_config(cr, uid, False, values, context=context)
return {'type': 'ir.actions.act_window_close'}
-
-reminder_config()
=== modified file 'hr_timesheet_reminder/wizard/reminder_config_view.xml'
--- hr_timesheet_reminder/wizard/reminder_config_view.xml 2011-08-12 12:53:16 +0000
+++ hr_timesheet_reminder/wizard/reminder_config_view.xml 2013-11-07 15:51:52 +0000
@@ -5,29 +5,39 @@
<record id="view_hr_timesheet_reminder_config_form" model="ir.ui.view">
<field name="name">hr.timesheet.reminder.config.form</field>
<field name="model">hr.timesheet.reminder.config</field>
- <field name="type">form</field>
<field name="arch" type="xml">
- <form string="Timesheet Reminder">
- <group colspan="4" col="4" string="Periodicity" >
- <field name="reminder_active" />
- <newline/>
- <field name="nextcall" attrs="{'required':[('reminder_active','==',True)]}"/>
- <newline/>
- <field name="interval_number" string="Then send message every" attrs="{'required':[('reminder_active','==',True)]}"/>
- <field name="interval_type" nolabel="1" attrs="{'required':[('reminder_active','==',True)]}"/>
- </group>
-
- <group colspan="4" col="2" string="Message">
- <field name="reply_to"/>
- <field name="subject"/>
- <field name="message" height="200"/>
- </group>
-
- <group colspan="4" col="6">
- <button icon="gtk-cancel" special="cancel" string="Cancel"/>
- <button icon="gtk-ok" string="Save configuration" name="save" type="object"/>
- <button icon="gtk-ok" string="Run now" name="run" type="object"/>
- </group>
+ <form string="Timesheet Reminder" version="7.0">
+ <sheet>
+ <group>
+ <separator string="Periodicity" colspan="4"/>
+ <field name="reminder_active" />
+ <newline/>
+ <field name="nextcall" attrs="{'required':[('reminder_active','==',True)]}"/>
+ <newline/>
+ <label for="interval_number" string="Then send message every"/>
+ <div>
+ <field name="interval_number" class="oe_inline"
+ attrs="{'required':[('reminder_active','==',True)]}"/>
+ <field name="interval_type" nolabel="1" class="oe_inline"
+ attrs="{'required':[('reminder_active','==',True)]}"/>
+ </div>
+ </group>
+
+ <group col="2">
+ <separator string="Message" colspan="4"/>
+ <field name="reply_to"/>
+ <field name="subject"/>
+ <field name="message" height="200"/>
+ </group>
+ </sheet>
+
+ <footer>
+ <button name="save" string="Save configuration" colspan="1"
+ type="object" class="oe_highlight"/> or
+ <button name="run" string="Run now" colspan="1"
+ type="object" class="oe_highlight"/> or
+ <button special="cancel" string="Cancel" class="oe_link"/>
+ </footer>
</form>
</field>
</record>
@@ -42,11 +52,11 @@
</record>
<menuitem id="menu_hr_timesheet_reminder_config"
- icon="STOCK_PRINT"
- action="action_hr_timesheet_reminder_config"
- parent="hr.menu_hr_configuration"
- name="Timesheet Reminder"
- groups="base.group_hr_manager"/>
+ icon="STOCK_PRINT"
+ action="action_hr_timesheet_reminder_config"
+ parent="hr.menu_hr_configuration"
+ name="Timesheet Reminder"
+ groups="base.group_hr_manager"/>
-</data>
+ </data>
</openerp>
=== modified file 'hr_timesheet_reminder/wizard/reminder_status.py'
--- hr_timesheet_reminder/wizard/reminder_status.py 2011-11-07 13:58:29 +0000
+++ hr_timesheet_reminder/wizard/reminder_status.py 2013-11-07 15:51:52 +0000
@@ -1,67 +1,58 @@
# -*- coding: utf-8 -*-
##############################################################################
#
-# Copyright (c) 2011 Camptocamp SA (http://www.camptocamp.com)
-# All Right Reserved
-#
-# Author : Guewen Baconnier (Camptocamp)
-#
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Author: Arnaud Wüst (Camptocamp)
+# Author: Guewen Baconnier (Camptocamp) (port to v7)
+# Copyright 2011-2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
-import time
-
-from osv import osv, fields
-
-
-class reminder_status(osv.osv_memory):
+from openerp.osv import orm, fields
+
+
+class reminder_status(orm.TransientModel):
_name = 'hr.timesheet.reminder.status'
_columns = {
- 'company_ids': fields.many2many('res.company', 'reminder_company_rel', 'wid', 'rid', 'Company'),
+ 'company_ids': fields.many2many(
+ 'res.company',
+ 'reminder_company_rel',
+ 'wid',
+ 'rid',
+ string='Company'),
'date': fields.date('End Date', required=True),
}
_defaults = {
- 'date': lambda *a: time.strftime('%Y-%m-%d'),
+ 'date': lambda *a: fields.date.today(),
}
- def print_report(self, cr, uid, ids, context):
- if context is None:
- context = {}
-
- form_values = self.read(cr, uid, ids, ['company_ids', 'date'])[0]
-
+ def print_report(self, cr, uid, ids, context=None):
+ form_values = self.read(
+ cr, uid, ids[0], ['company_ids', 'date'], context=context)
+
+ # when no company is selected, select them all
if not form_values['company_ids']:
form_values['company_ids'] = self.pool.get('res.company').\
- search(cr, uid, [], context=context)
+ search(cr, uid, [], context=context)
+
data = {'ids': form_values['company_ids'],
'model': 'res.company',
- 'form': {}}
- data['form'].update(form_values)
+ 'form': form_values}
return {'type': 'ir.actions.report.xml',
'report_name': 'timesheet.reminder.status',
'datas': data}
-
-reminder_status()
=== modified file 'hr_timesheet_reminder/wizard/reminder_status_view.xml'
--- hr_timesheet_reminder/wizard/reminder_status_view.xml 2011-08-12 12:53:16 +0000
+++ hr_timesheet_reminder/wizard/reminder_status_view.xml 2013-11-07 15:51:52 +0000
@@ -5,23 +5,24 @@
<record id="view_hr_timesheet_reminder_status_form" model="ir.ui.view">
<field name="name">hr.timesheet.reminder.status.form</field>
<field name="model">hr.timesheet.reminder.status</field>
- <field name="type">form</field>
<field name="arch" type="xml">
- <form string="Reminder Status">
- <group colspan="4" col="4" string="Parameter">
- <separator string="End Date (display the 5 timesheets previous to this date)" colspan="4"/>
- <field name="date" colspan="4" nolabel="1" width="400" />
- </group>
+ <form string="Reminder Status" version="7.0">
+ <sheet>
+ <group>
+ <label for="date" string="End Date (display the 5 timesheets previous to this date)"/>
+ <field name="date" colspan="4" nolabel="1" width="400" />
+ </group>
- <group colspan="4" col="4" string="Filter">
- <separator string="Filter by a Company (leave empty to select all companies)" colspan="4"/>
- <field name="company_ids" colspan="4" nolabel="1"/>
- <newline/>
- </group>
- <group colspan="4" col="6">
- <button icon="gtk-cancel" special="cancel" string="Cancel"/>
- <button icon="gtk-print" string="Print" name="print_report" type="object" colspan="2" default_focus="1" />
- </group>
+ <group>
+ <separator string="Companies" colspan="4"/>
+ <label for="company_ids" colspan="4" string="Filter by a Company (leave empty to select all companies)"/>
+ <field name="company_ids" colspan="4" nolabel="1"/>
+ </group>
+ </sheet>
+ <footer>
+ <button name="print_report" string="Print" colspan="1" type="object" class="oe_highlight"/> or
+ <button special="cancel" string="Cancel" class="oe_link"/>
+ </footer>
</form>
</field>
</record>
@@ -36,11 +37,11 @@
</record>
<menuitem id="menu_hr_timesheet_reminder_status"
- icon="STOCK_PRINT"
- action="action_hr_timesheet_reminder_status"
- parent="hr_timesheet.menu_hr_reporting_timesheet"
- name="Timesheet Status"
- groups="base.group_hr_manager"/>
+ icon="STOCK_PRINT"
+ action="action_hr_timesheet_reminder_status"
+ parent="hr.menu_hr_reporting_timesheet"
+ name="Timesheet Status"
+ groups="base.group_hr_manager"/>
-</data>
+ </data>
</openerp>
=== modified file 'hr_timesheet_task/__init__.py'
--- hr_timesheet_task/__init__.py 2012-06-06 13:14:12 +0000
+++ hr_timesheet_task/__init__.py 2013-11-07 15:51:52 +0000
@@ -1,30 +1,20 @@
# -*- coding: utf-8 -*-
##############################################################################
#
-# Copyright (c) 2010 Camptocamp SA (http://www.camptocamp.com)
-# All Right Reserved
-#
-# Author : Joel Grand-guillaume (Camptocamp)
-#
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Author : Joel Grand-guillaume (Camptocamp)
+# Copyright 2013 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
=== modified file 'hr_timesheet_task/__openerp__.py'
--- hr_timesheet_task/__openerp__.py 2012-06-06 13:14:12 +0000
+++ hr_timesheet_task/__openerp__.py 2013-11-07 15:51:52 +0000
@@ -2,7 +2,7 @@
##############################################################################
#
# Author: Nicolas Bessi
-# Copyright 2012 Camptocamp SA
+# Copyright 2013 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -19,21 +19,25 @@
#
##############################################################################
{'name' : 'Task in time sheet',
- 'version' : '0.1',
+ 'version' : '0.2',
'author' : 'Camptocamp',
- 'maintainer': 'Camptocamp',
+ 'maintainer': 'Camptocamp - Acsone SA/NV',
'category': 'Human Resources',
- 'complexity': "normal", #easy, normal, expert
'depends' : ['timesheet_task', 'hr_timesheet_sheet'],
'description': """Replace project.task.work items linked to task
with hr.analytic.timesheet""",
'website': 'http://www.camptocamp.com',
- 'init_xml': [],
- 'update_xml': ['hr_timesheet_sheet_view.xml', 'hr_analytic_timesheet_view.xml'],
- 'demo_xml': [],
- 'tests': [],
+ 'data': ['hr_timesheet_sheet_view.xml', 'hr_analytic_timesheet_view.xml'],
+ 'js' : ['static/src/js/timesheet.js'],
+ 'css': ['static/src/css/timesheet.css',],
+ 'qweb': ['static/src/xml/timesheet.xml'],
+ 'demo': [],
+ 'test': [],
'installable': True,
'images' : [],
'auto_install': False,
'license': 'AGPL-3',
- 'application': True}
\ No newline at end of file
+ 'application': True,
+}
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
=== modified file 'hr_timesheet_task/hr_analytic_timesheet_view.xml'
--- hr_timesheet_task/hr_analytic_timesheet_view.xml 2012-10-23 15:06:03 +0000
+++ hr_timesheet_task/hr_analytic_timesheet_view.xml 2013-11-07 15:51:52 +0000
@@ -9,10 +9,10 @@
<field name="type">form</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_form"/>
<field name="arch" type="xml">
- <xpath expr="/form/group/field[@name='user_id']" position="after">
+ <field name="user_id" position="after">
<field name="task_id" context="{ 'account_id' : account_id}"
- domain="[('state','=','open')]"/>
- </xpath>
+ domain="[('state','=','open'), ('project_id.analytic_account_id','=',account_id)]"/>
+ </field>
</field>
</record>
<record id="hr_timesheet.hr_timesheet_line_tree" model="ir.ui.view">
@@ -23,9 +23,9 @@
<field name="arch" type="xml">
<tree editable="top" string="Timesheet Lines">
<field name="date" on_change="on_change_date(date)"/>
- <field domain="[('type','=','normal')]" name="account_id"/>
+ <field domain="[('type','=','normal'), ('state','=','open')]" name="account_id"/>
<field name="task_id" context="{'account_id' : account_id}"
- domain="[('state','=','open')]"/>
+ domain="[('state','=','open'), ('project_id.analytic_account_id','=',account_id)]"/>
<field name="name"/>
<field name="unit_amount"
on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id, journal_id)"
@@ -52,9 +52,9 @@
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_search"/>
<field name="type">search</field>
<field name="arch" type="xml">
- <xpath expr="/search/group/field[@name='date']" position="after">
+ <field name="date" position="after">
<field name="task_id"/>
- </xpath>
+ </field>
<xpath
expr="/search/group[@string='Group By...']/filter[@string='Analytic account']"
position="after">
@@ -62,16 +62,17 @@
/>
</xpath>
<!-- Add dates filter -->
- <xpath expr="/search/group/filter[@name='today']" position="before">
- <filter icon="terp-go-year" string=" Year "
+ <xpath expr="/search/group" position="before">
+ <separator orientation="vertical"/>
+ <filter icon="terp-go-year" string="Current Year"
domain="[('date','<=', time.strftime('%%Y-%%m-%%d')),('date','>=',time.strftime('%%Y-01-01'))]"
help="Current Year"/>
- <filter icon="terp-go-month" string=" Month " name="month"
- domain="[('date','<=',(datetime.date.today()+relativedelta(day=31)).strftime('%%Y-%%m-%%d')),('date','>=',(datetime.date.today()-relativedelta(day=1)).strftime('%%Y-%%m-%%d'))]"
+ <filter icon="terp-go-month" string="Current Month" name="month"
+ domain="[('date','<=',(context_today()+relativedelta(day=31)).strftime('%%Y-%%m-%%d')),('date','>=',(context_today()-relativedelta(day=1)).strftime('%%Y-%%m-%%d'))]"
help="Current Month"/>
- <filter icon="terp-go-week" string=" Week " separator="1" name="week"
- domain="[('date','>=',(datetime.date.today()+relativedelta(days=-6,weekday=0)).strftime('%%Y-%%m-%%d')),('date','<=',(datetime.date.today()+relativedelta(weekday=6)).strftime('%%Y-%%m-%%d'))]"
- help="Current week"/>
+ <filter icon="terp-go-week" string="Current Week" separator="1" name="week"
+ domain="[('date','>=',(context_today()+relativedelta(days=-6,weekday=0)).strftime('%%Y-%%m-%%d')),('date','<=',(context_today()+relativedelta(weekday=6)).strftime('%%Y-%%m-%%d'))]"
+ help="Current Week"/>
</xpath>
</field>
@@ -80,19 +81,5 @@
<record id="hr_timesheet.act_hr_timesheet_line_evry1_all_form" model="ir.actions.act_window">
<field name="context">{"search_default_user_id":uid, "search_default_week":1}</field>
</record>
- <record id="hr_timesheet_line_search_to_invoice_filter" model="ir.ui.view">
- <field name="name">hr.analytic.timesheet.search.toinvoice.filter</field>
- <field name="model">hr.analytic.timesheet</field>
- <field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_search"/>
- <field name="type">search</field>
- <field name="arch" type="xml">
- <xpath expr="/search/group[1]/filter[@name='today']" position="after">
- <separator orientation="vertical"/>
- <filter name="to_invoice" string="To Invoice" context="{'to_invoice': 1}"
- domain="[('invoice_id','=',False),('to_invoice','<>',False)]"
- icon="terp-dolar"/>
- </xpath>
- </field>
- </record>
</data>
</openerp>
=== modified file 'hr_timesheet_task/hr_timesheet_sheet_view.xml'
--- hr_timesheet_task/hr_timesheet_sheet_view.xml 2012-10-23 15:06:03 +0000
+++ hr_timesheet_task/hr_timesheet_sheet_view.xml 2013-11-07 15:51:52 +0000
@@ -1,27 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
+
<record id="view_hr_timesheet_sheet_filter_custom" model="ir.ui.view">
<field name="name">hr_timesheet_sheet.sheet.filter</field>
<field name="model">hr_timesheet_sheet.sheet</field>
<field name="inherit_id" ref="hr_timesheet_sheet.view_hr_timesheet_sheet_filter"/>
<field name="type">search</field>
<field name="arch" type="xml">
- <xpath expr="/search/group/filter[@string='To Approve']" position="after">
+ <filter name="to_approve" position="after">
<separator orientation="vertical"/>
- <filter icon="terp-go-year" string=" Year "
+ <filter icon="terp-go-year" string="Current Year"
domain="[('date_from','<=', time.strftime('%%Y-%%m-%%d')),('date_from','>=',time.strftime('%%Y-01-01'))]"
help="Current Year"/>
- <filter icon="terp-go-month" string=" Month " name="month"
- domain="[('date_from','<=',(datetime.date.today()+relativedelta(day=31)).strftime('%%Y-%%m-%%d')),('date_from','>=',(datetime.date.today()-relativedelta(day=1)).strftime('%%Y-%%m-%%d'))]"
+ <filter icon="terp-go-month" string="Current Month" name="month"
+ domain="[('date_from','<=',(context_today()+relativedelta(day=31)).strftime('%%Y-%%m-%%d')),('date_from','>=',(context_today()-relativedelta(day=1)).strftime('%%Y-%%m-%%d'))]"
help="Current Month"/>
- <filter icon="terp-go-week" string=" Week " separator="1" name="week"
- domain="[('date_from','>=',(datetime.date.today()+relativedelta(days=-6,weekday=0)).strftime('%%Y-%%m-%%d')),('date_from','<=',(datetime.date.today()+relativedelta(weekday=6)).strftime('%%Y-%%m-%%d'))]"
- help="Current week"/>
- </xpath>
+ <filter icon="terp-go-week" string="Current Week" separator="1" name="week"
+ domain="[('date_from','>=',(context_today()+relativedelta(days=-6,weekday=0)).strftime('%%Y-%%m-%%d')),('date_from','<=',(context_today()+relativedelta(weekday=6)).strftime('%%Y-%%m-%%d'))]"
+ help="Current Week"/>
+ </filter>
</field>
</record>
+
<record id="hr_timesheet_sheet.act_hr_timesheet_sheet_form" model="ir.actions.act_window">
<field name="context">{'search_default_my_timesheet':1}</field>
</record>
@@ -32,16 +34,16 @@
<field name="inherit_id" ref="hr_timesheet_sheet.hr_timesheet_sheet_form"/>
<field name="arch" type="xml">
<xpath
- expr="/form/notebook/page[@string='Daily']/field[@name='timesheet_ids']/tree[@string='Timesheet Lines']/field[@name='account_id']"
+ expr="/form/sheet/notebook/page[@string='Details']/field[@name='timesheet_ids']/tree[@string='Timesheet Activities']/field[@name='account_id']"
position="after">
<field name="task_id" context="{'account_id' : account_id}"
- domain="[('state','=','open')]"/>
+ domain="[('state','=','open'), ('project_id.analytic_account_id','=',account_id)]"/>
</xpath>
<xpath
- expr="/form/notebook/page[@string='Daily']/field[@name='timesheet_ids']/form[@string='Timesheet Lines']/field[@name='account_id']"
+ expr="/form/sheet/notebook/page[@string='Details']/field[@name='timesheet_ids']/form[@string='Timesheet Activities']/field[@name='account_id']"
position="after">
<field name="task_id" context="{'account_id' : account_id}"
- domain="[('state','=','open')]"/>
+ domain="[('state','=','open'), ('project_id.analytic_account_id','=',account_id)]"/>
</xpath>
</field>
</record>
=== added directory 'hr_timesheet_task/i18n'
=== added file 'hr_timesheet_task/i18n/fr.po'
--- hr_timesheet_task/i18n/fr.po 1970-01-01 00:00:00 +0000
+++ hr_timesheet_task/i18n/fr.po 2013-11-07 15:51:52 +0000
@@ -0,0 +1,42 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * hr_timesheet_task
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 7.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-03-28 15:37+0000\n"
+"PO-Revision-Date: 2013-03-28 15:37+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: hr_timesheet_task
+#: view:hr.analytic.timesheet:0
+#: view:hr_timesheet_sheet.sheet:0
+msgid "Current Week"
+msgstr "Semaine en cours"
+
+#. module: hr_timesheet_task
+#: view:hr.analytic.timesheet:0
+#: view:hr_timesheet_sheet.sheet:0
+msgid "Current Year"
+msgstr "Année en cours"
+
+#. module: hr_timesheet_task
+#. openerp-web
+#: code:addons/hr_timesheet_task/static/src/xml/timesheet.xml:8
+#: view:hr.analytic.timesheet:0
+#, python-format
+msgid "Task"
+msgstr "Tâche"
+
+#. module: hr_timesheet_task
+#: view:hr.analytic.timesheet:0
+#: view:hr_timesheet_sheet.sheet:0
+msgid "Current Month"
+msgstr "Mois en cours"
=== added directory 'hr_timesheet_task/static'
=== added directory 'hr_timesheet_task/static/src'
=== added directory 'hr_timesheet_task/static/src/css'
=== added file 'hr_timesheet_task/static/src/css/timesheet.css'
--- hr_timesheet_task/static/src/css/timesheet.css 1970-01-01 00:00:00 +0000
+++ hr_timesheet_task/static/src/css/timesheet.css 2013-11-07 15:51:52 +0000
@@ -0,0 +1,7 @@
+@charset "utf-8";
+.openerp .oe_timesheet_weekly .oe_timesheet_weekly_task {
+ text-align: left;
+}
+.openerp .oe_timesheet_weekly .oe_timesheet_task_col {
+ text-align: left;
+}
\ No newline at end of file
=== added directory 'hr_timesheet_task/static/src/js'
=== added file 'hr_timesheet_task/static/src/js/timesheet.js'
--- hr_timesheet_task/static/src/js/timesheet.js 1970-01-01 00:00:00 +0000
+++ hr_timesheet_task/static/src/js/timesheet.js 2013-11-07 15:51:52 +0000
@@ -0,0 +1,252 @@
+openerp.hr_timesheet_task = function(instance) {
+
+ var module = instance.hr_timesheet_sheet
+
+ module.WeeklyTimesheet.include({
+ events: {
+ "click .oe_timesheet_weekly_account a": "go_to",
+ "click .oe_timesheet_weekly_task a": "go_to_task",
+ },
+ go_to_task : function(event) {
+ var id = JSON.parse($(event.target).data("task-id"));
+ this.do_action({
+ type: 'ir.actions.act_window',
+ res_model: "project.task",
+ res_id: id,
+ views: [[false, 'form']],
+ target: 'current'
+ });
+ },
+ initialize_content: function() {
+ var self = this;
+ if (self.setting)
+ return;
+ // don't render anything until we have date_to and date_from
+ if (!self.get("date_to") || !self.get("date_from"))
+ return;
+ this.destroy_content();
+
+ // it's important to use those vars to avoid race conditions
+ var dates;
+ var accounts;
+ var account_names;
+ var task_names;
+ var default_get;
+ return this.render_drop.add(new instance.web.Model("hr.analytic.timesheet").call("default_get", [
+ ['account_id','task_id','general_account_id','journal_id','date','name','user_id','product_id','product_uom_id','to_invoice','amount','unit_amount'],
+ new instance.web.CompoundContext({'user_id': self.get('user_id')})]).then(function(result) {
+ default_get = result;
+ // calculating dates
+ dates = [];
+ var start = self.get("date_from");
+ var end = self.get("date_to");
+ while (start <= end) {
+ dates.push(start);
+ start = start.clone().addDays(1);
+ }
+
+ timesheet_lines = _(self.get("sheets")).chain()
+ .map(function(el) {
+ // much simpler to use only the id in all cases
+ if (typeof(el.account_id) === "object")
+ el.account_id = el.account_id[0];
+ if (typeof(el.task_id) === "object")
+ el.task_id = el.task_id[0];
+ return el;
+ }).value();
+
+ // group by account
+ var timesheet_lines_by_account_id = _.groupBy(timesheet_lines, function(el) {
+ return el.account_id;
+ });
+
+ // group by account and task
+ var timesheet_lines_by_account_id_task_id = _.groupBy(timesheet_lines, function(el) {
+ return [el.account_id, el.task_id];
+ });
+
+ var account_ids = _.map(_.keys(timesheet_lines_by_account_id), function(el) { return el === "false" ? false : Number(el) });
+
+ return new instance.web.Model("hr.analytic.timesheet").call("multi_on_change_account_id", [[], account_ids,
+ new instance.web.CompoundContext({'user_id': self.get('user_id')})]).then(function(accounts_defaults) {
+ accounts = _(timesheet_lines_by_account_id_task_id).chain().map(function(lines, account_id_task_id) {
+ account_defaults = _.extend({}, default_get, (accounts_defaults[lines[0].account_id] || {}).value || {});
+ // group by days
+ var index = _.groupBy(lines, "date");
+ var days = _.map(dates, function(date) {
+ var day = {day: date, lines: index[instance.web.date_to_str(date)] || []};
+ // add line where we will insert/remove hours
+ var to_add = _.find(day.lines, function(line) { return line.name === self.description_line });
+ if (to_add) {
+ day.lines = _.without(day.lines, to_add);
+ day.lines.unshift(to_add);
+ } else {
+ day.lines.unshift(_.extend(_.clone(account_defaults), {
+ name: self.description_line,
+ unit_amount: 0,
+ date: instance.web.date_to_str(date),
+ account_id: lines[0].account_id,
+ task_id: lines[0].task_id,
+ }));
+ }
+ return day;
+ });
+ return {account_task: account_id_task_id, account: lines[0].account_id, task: lines[0].task_id, days: days, account_defaults: account_defaults};
+ }).value();
+
+ // we need the name_get of the analytic accounts
+ return new instance.web.Model("account.analytic.account").call("name_get", [_.pluck(accounts, "account"),
+ new instance.web.CompoundContext()]).then(function(result) {
+ account_names = {};
+ _.each(result, function(el) {
+ account_names[el[0]] = el[1];
+ });
+ // we need the name_get of the tasks
+ return new instance.web.Model("project.task").call("name_get", [_(accounts).chain().pluck("task").filter(function(el) { return el; }).value(),
+ new instance.web.CompoundContext()]).then(function(result) {
+ task_names = {};
+ _.each(result, function(el) {
+ task_names[el[0]] = el[1];
+ });
+ accounts = _.sortBy(accounts, function(el) {
+ return account_names[el.account];
+ });
+ });
+ });
+ });
+ })).then(function(result) {
+ // we put all the gathered data in self, then we render
+ self.dates = dates;
+ self.accounts = accounts;
+ self.account_names = account_names;
+ self.task_names = task_names;
+ self.default_get = default_get;
+ //real rendering
+ self.display_data();
+ });
+ },
+ init_add_account: function() {
+ var self = this;
+ if (self.dfm)
+ return;
+ self.$(".oe_timesheet_weekly_add_row").show();
+ self.dfm = new instance.web.form.DefaultFieldManager(self);
+ self.dfm.extend_field_desc({
+ account: {
+ relation: "account.analytic.account",
+ },
+ task: {
+ relation: "project.task",
+ },
+ });
+ self.account_m2o = new instance.web.form.FieldMany2One(self.dfm, {
+ attrs: {
+ name: "account",
+ type: "many2one",
+ domain: [
+ ['type','in',['normal', 'contract']],
+ ['state', '=', 'open'],
+ ['use_timesheets','=',1],
+ ],
+ context: {
+ default_use_timesheets: 1,
+ default_type: "contract",
+ },
+ modifiers: '{"required": true}',
+ },
+ });
+ self.task_m2o = new instance.web.form.FieldMany2One(self.dfm, {
+ attrs: {
+ name: "task",
+ type: "many2one",
+ domain: [
+ // at this moment, it is always an empty list
+ ['project_id.analytic_account_id','=',self.account_m2o.get_value()]
+ ],
+ },
+ });
+ self.task_m2o.prependTo(self.$(".oe_timesheet_weekly_add_row td"));
+ self.account_m2o.prependTo(self.$(".oe_timesheet_weekly_add_row td"));
+
+ // when account_m2o loses focus, value can be changed,
+ // update task_m2o to show only tasks related to the selected project
+ self.account_m2o.$input.focusout(function(){
+ var account_id = self.account_m2o.get_value();
+ if (account_id === false) { return; }
+ self.task_m2o.init(self.dfm, {
+ attrs: {
+ name: "task",
+ type: "many2one",
+ domain: [
+ ['state','=','open'],
+ // show only tasks linked to the selected project
+ ['project_id.analytic_account_id','=',account_id],
+ // ignore tasks already in the timesheet
+ ['id', 'not in', _.pluck(self.accounts, "task")],
+ ],
+ context: {
+ 'account_id': account_id,
+ },
+ },
+ });
+ });
+
+ self.$(".oe_timesheet_weekly_add_row button").click(function() {
+ var id = self.account_m2o.get_value();
+ if (id === false) {
+ self.dfm.set({display_invalid_fields: true});
+ return;
+ }
+ var ops = self.generate_o2m_value();
+ new instance.web.Model("hr.analytic.timesheet").call("on_change_account_id", [[], id]).then(function(res) {
+ var def = _.extend({}, self.default_get, res.value, {
+ name: self.description_line,
+ unit_amount: 0,
+ date: instance.web.date_to_str(self.dates[0]),
+ account_id: id,
+ task_id: self.task_m2o.get_value(),
+ });
+ ops.push(def);
+ self.set({"sheets": ops});
+ });
+ });
+ },
+ get_box: function(account, day_count) {
+ return this.$('[data-account-task="' + account.account_task + '"][data-day-count="' + day_count + '"]');
+ },
+ get_total: function(account) {
+ return this.$('[data-account-task-total="' + account.account_task + '"]');
+ },
+ generate_o2m_value: function() {
+ var self = this;
+ var ops = [];
+
+ _.each(self.accounts, function(account) {
+ var auth_keys = _.extend(_.clone(account.account_defaults), {
+ name: true, amount:true, unit_amount: true, date: true, account_id:true, task_id: true,
+ });
+ _.each(account.days, function(day) {
+ _.each(day.lines, function(line) {
+ if (line.unit_amount !== 0) {
+ var tmp = _.clone(line);
+ tmp.id = undefined;
+ _.each(line, function(v, k) {
+ if (v instanceof Array) {
+ tmp[k] = v[0];
+ }
+ });
+ // we have to remove some keys, because analytic lines are shitty
+ _.each(_.keys(tmp), function(key) {
+ if (auth_keys[key] === undefined) {
+ tmp[key] = undefined;
+ }
+ });
+ ops.push(tmp);
+ }
+ });
+ });
+ });
+ return ops;
+ },
+ });
+};
=== added directory 'hr_timesheet_task/static/src/xml'
=== added file 'hr_timesheet_task/static/src/xml/timesheet.xml'
--- hr_timesheet_task/static/src/xml/timesheet.xml 1970-01-01 00:00:00 +0000
+++ hr_timesheet_task/static/src/xml/timesheet.xml 2013-11-07 15:51:52 +0000
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<templates>
+ <t t-extend="hr_timesheet_sheet.WeeklyTimesheet">
+
+ <!-- Add a task column -->
+ <t t-jquery="th.oe_timesheet_first_col" t-operation="after">
+ <th class="oe_timesheet_task_col">Task</th>
+ </t>
+
+ <!-- Replace all line created with the foreach to take account of task -->
+ <t t-jquery="tr:not([class]):nth-child(n+2)" t-operation="replace">
+ <tr t-foreach="widget.accounts" t-as="account">
+ <td class="oe_timesheet_weekly_account"><a href="javascript:void(0)" t-att-data-id="JSON.stringify(account.account)"><t t-esc="widget.account_names[account.account]"/></a></td>
+ <td class="oe_timesheet_weekly_task"><a href="javascript:void(0)" t-att-data-task-id="JSON.stringify(account.task)"><t t-esc="widget.task_names[account.task]" /></a></td>
+ <t t-set="day_count" t-value="0"/>
+ <t t-foreach="account.days" t-as="day">
+ <td t-att-class="(Date.compare(day.day, Date.today()) === 0 ? 'oe_timesheet_weekly_today' : '')">
+ <input t-if="!widget.get('effective_readonly')" class="oe_timesheet_weekly_input"
+ t-att-data-account-task="account.account_task" t-att-data-day-count="day_count" type="text"/>
+ <span t-if="widget.get('effective_readonly')" class="oe_timesheet_weekly_box"
+ t-att-data-account-task="account.account_task" t-att-data-day-count="day_count"/>
+ <t t-set="day_count" t-value="day_count + 1"/>
+ </td>
+ </t>
+ <td t-att-data-account-task-total="account.account_task" class="oe_timesheet_total"> </td>
+ </tr>
+ </t>
+
+ <!-- Add line in the last tr before the first element -->
+ <t t-jquery="tr.oe_timesheet_total > td:first-child" t-operation="after">
+ <td> </td>
+ </t>
+ </t>
+</templates>
=== removed directory 'hr_timesheet_task/wizard'
=== removed file 'hr_timesheet_task/wizard/__init__.py'
--- hr_timesheet_task/wizard/__init__.py 2011-08-12 12:53:16 +0000
+++ hr_timesheet_task/wizard/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,3 +0,0 @@
-import associate_analytic_timesheet
-import dissociate_analytic_timesheet
-import hr_timesheet_invoice_create
=== removed file 'hr_timesheet_task/wizard/associate_analytic_timesheet.py'
--- hr_timesheet_task/wizard/associate_analytic_timesheet.py 2011-08-12 12:53:16 +0000
+++ hr_timesheet_task/wizard/associate_analytic_timesheet.py 1970-01-01 00:00:00 +0000
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# Author Guewen Baconnier. Copyright Camptocamp SA
-#
-##############################################################################
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-#
-##############################################################################
-
-
-from osv import osv
-
-
-class AssociateInvoice(osv.osv_memory):
- _inherit = 'associate.aal.to.invoice'
-
- def associate_aal(self, cr, uid, ids, context):
- """ If the wizard is called from hr.analytic.timesheet (inherits account.analytic.line)
- We get the account.analytic.line ids and call the wizard with them """
- if context.get('active_model', False) == 'hr.analytic.timesheet':
- at_obj = self.pool.get('hr.analytic.timesheet')
- at_ids = context.get('active_ids', False)
- aal_ids = [at.line_id.id for at in at_obj.browse(cr, uid, at_ids, context)]
- context['active_ids'] = aal_ids
-
- return super(AssociateInvoice, self).associate_aal(cr, uid, ids, context)
-
-AssociateInvoice()
=== removed file 'hr_timesheet_task/wizard/dissociate_analytic_timesheet.py'
--- hr_timesheet_task/wizard/dissociate_analytic_timesheet.py 2011-08-12 12:53:16 +0000
+++ hr_timesheet_task/wizard/dissociate_analytic_timesheet.py 1970-01-01 00:00:00 +0000
@@ -1,47 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# Author Guewen Baconnier. Copyright Camptocamp SA
-#
-##############################################################################
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-#
-##############################################################################
-
-
-from osv import osv
-
-
-class DissociateInvoice(osv.osv_memory):
- _inherit = 'dissociate.aal.to.invoice'
-
- def dissociate_aal(self, cr, uid, ids, context):
- """ If the wizard is called from hr.analytic.timesheet (inherits account.analytic.line)
- We get the account.analytic.line ids and call the wizard with them """
- if context.get('active_model', False) == 'hr.analytic.timesheet':
- at_obj = self.pool.get('hr.analytic.timesheet')
- at_ids = context.get('active_ids', False)
- aal_ids = [at.line_id.id for at in at_obj.browse(cr, uid, at_ids, context)]
- context['active_ids'] = aal_ids
- return super(DissociateInvoice, self).dissociate_aal(cr, uid, ids, context)
-
-DissociateInvoice()
=== removed file 'hr_timesheet_task/wizard/hr_timesheet_invoice_create.py'
--- hr_timesheet_task/wizard/hr_timesheet_invoice_create.py 2011-08-12 12:53:16 +0000
+++ hr_timesheet_task/wizard/hr_timesheet_invoice_create.py 1970-01-01 00:00:00 +0000
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# Author Guewen Baconnier. Copyright Camptocamp SA
-#
-##############################################################################
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-#
-##############################################################################
-
-from osv import osv
-
-
-class hr_timesheet_invoice_create(osv.osv_memory):
- _inherit = 'hr.timesheet.invoice.create'
-
- def do_create(self, cr, uid, ids, context=None):
- """ If the wizard is called from hr.analytic.timesheet (inherits account.analytic.line)
- We get the account.analytic.line ids and call the wizard with them """
- if context.get('active_model', False) == 'hr.analytic.timesheet':
- at_obj = self.pool.get('hr.analytic.timesheet')
- at_ids = context.get('active_ids', False)
- aal_ids = [at.line_id.id for at in at_obj.browse(cr, uid, at_ids, context)]
- context['active_ids'] = aal_ids
- return super(hr_timesheet_invoice_create, self).do_create(cr, uid, ids, context)
-
-hr_timesheet_invoice_create()
=== removed file 'hr_timesheet_task/wizard/wizard_actions.xml'
--- hr_timesheet_task/wizard/wizard_actions.xml 2011-08-12 12:53:16 +0000
+++ hr_timesheet_task/wizard/wizard_actions.xml 1970-01-01 00:00:00 +0000
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<openerp>
-<data>
-
- <act_window name="Associate Analytic Lines"
- res_model="associate.aal.to.invoice"
- src_model="hr.analytic.timesheet"
- key2="client_action_multi"
- view_mode="form"
- view_type="form"
- target="new"
- id="action_associate_at_invoice"/>
-
- <act_window name="Dissociate Analytic Lines"
- res_model="dissociate.aal.to.invoice"
- src_model="hr.analytic.timesheet"
- key2="client_action_multi"
- view_mode="form"
- view_type="form"
- target="new"
- id="action_dissociate_at_invoice"/>
-
- <record model="ir.values" id="hr_analytic_timesheet_invoice_create_values">
- <field name="model_id" ref="model_hr_analytic_timesheet" />
- <field name="object" eval="1" />
- <field name="name">Invoice analytic lines</field>
- <field name="key2">client_action_multi</field>
- <field name="value" eval="'ir.actions.act_window,' + str(ref('hr_timesheet_invoice.action_hr_timesheet_invoice_create'))" />
- <field name="key">action</field>
- <field name="model">hr.analytic.timesheet</field>
- </record>
-
-</data>
-</openerp>
=== modified file 'timesheet_task/__init__.py'
--- timesheet_task/__init__.py 2012-06-06 13:14:12 +0000
+++ timesheet_task/__init__.py 2013-11-07 15:51:52 +0000
@@ -1,29 +1,21 @@
# -*- coding: utf-8 -*-
##############################################################################
#
-# author Nicolas Bessi
-# Copyright Camptocamp 2012
-#
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Author: Nicolas Bessi
+# Copyright 2013 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import project_task
\ No newline at end of file
=== modified file 'timesheet_task/__openerp__.py'
--- timesheet_task/__openerp__.py 2012-06-06 13:14:12 +0000
+++ timesheet_task/__openerp__.py 2013-11-07 15:51:52 +0000
@@ -2,7 +2,7 @@
##############################################################################
#
# Author: Nicolas Bessi
-# Copyright 2012 Camptocamp SA
+# Copyright 2013 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -18,24 +18,34 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
-{'name' : 'Analytic Task',
- 'version' : '0.1',
+{'name' : 'Analytic Timesheet In Task',
+ 'version' : '0.2',
'author' : 'Camptocamp',
- 'maintainer': 'Camptocamp',
+ 'maintainer': 'Camptocamp, Acsone SA/NV',
'category': 'Human Resources',
- 'complexity': "normal", #easy, normal, expert
- 'depends' : ['hr_timesheet', 'project', 'hr_timesheet_invoice'],
- 'description': """Replace project.task.work items linked to task
- with hr.analytic.timesheet""",
+ 'depends' : ['project', 'hr_timesheet_invoice'],
+ 'description': """
+Replace task work items (project.task.work) linked to task with
+timesheet lines (hr.analytic.timesheet).
+
+Unless the module project_timesheet, it allows to have only one single
+object that handles and records time spent by employees, making more
+coherence for the end user. This way, time entered through timesheet
+lines or tasks is the same. As long as a timesheet lines has an
+associated task, it will compute the related indicators.
+
+Used with the module hr_timesheet_task, it also allows users to complete
+task information through the timesheet sheet (hr.timesheet.sheet).
+ """,
'website': 'http://www.camptocamp.com',
- 'init_xml': [],
- 'update_xml': ['project_task_view.xml'],
- 'demo_xml': [],
- 'tests': [],
+ 'data': ['project_task_view.xml'],
+ 'demo': [],
+ 'test': ['test/task_timesheet_indicators.yml'],
'installable': True,
'images' : [],
'auto_install': False,
'license': 'AGPL-3',
- 'application': True}
- #'icon': '/MODULE_NAME/static/src/images/XXX.png',
-
\ No newline at end of file
+ 'application': True,
+}
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
=== added directory 'timesheet_task/i18n'
=== added file 'timesheet_task/i18n/fr.po'
--- timesheet_task/i18n/fr.po 1970-01-01 00:00:00 +0000
+++ timesheet_task/i18n/fr.po 2013-11-07 15:51:52 +0000
@@ -0,0 +1,53 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * timesheet_task
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 7.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-03-28 15:26+0000\n"
+"PO-Revision-Date: 2013-03-28 15:26+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: timesheet_task
+#: field:account.analytic.line,task_id:0
+#: model:ir.model,name:timesheet_task.model_project_task
+msgid "Task"
+msgstr "Tâche"
+
+#. module: timesheet_task
+#: model:ir.model,name:timesheet_task.model_account_analytic_line
+msgid "Analytic Line"
+msgstr "Ligne analytique"
+
+#. module: timesheet_task
+#: view:project.task:0
+msgid "Total time"
+msgstr "Temps total"
+
+#. module: timesheet_task
+#: view:project.task:0
+msgid "Task Work"
+msgstr "Travail effectué"
+
+#. module: timesheet_task
+#: field:project.task,timesheet_ids:0
+msgid "Work done"
+msgstr "Travail effectué"
+
+#. module: timesheet_task
+#: model:ir.model,name:timesheet_task.model_hr_analytic_timesheet
+msgid "Timesheet Line"
+msgstr "Ligne de prestation"
+
+#. module: timesheet_task
+#: view:project.task:0
+msgid "Total cost"
+msgstr "Coût total"
+
=== modified file 'timesheet_task/project_task.py'
--- timesheet_task/project_task.py 2012-10-26 13:01:39 +0000
+++ timesheet_task/project_task.py 2013-11-07 15:51:52 +0000
@@ -1,50 +1,42 @@
# -*- coding: utf-8 -*-
##############################################################################
#
-# author Nicolas Bessi
-# Copyright Camptocamp 2012
-#
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Author: Nicolas Bessi
+# Copyright 2013 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
-from osv import osv, fields
-
-TASK_WATCHERS = ['work_ids', 'remaining_hours', 'planned_hours']
-AA_WATCHERS = ['unit_amount', 'product_uom_id', 'account_id', 'to_invoice', 'task_id']
-
-class ProjectTask(osv.osv):
+from openerp.osv import orm, fields
+from openerp import SUPERUSER_ID
+
+TASK_WATCHERS = ['work_ids', 'remaining_hours', 'effective_hours', 'planned_hours']
+TIMESHEET_WATCHERS = ['unit_amount', 'product_uom_id', 'account_id', 'task_id']
+
+class ProjectTask(orm.Model):
_inherit = "project.task"
_name = "project.task"
-
def _progress_rate(self, cr, uid, ids, names, arg, context=None):
"""TODO improve code taken for OpenERP"""
res = {}
- cr.execute("""SELECT task_id, COALESCE(SUM(unit_amount),0)
- FROM account_analytic_line
- WHERE task_id IN %s
+ cr.execute("""SELECT task_id, COALESCE(SUM(unit_amount),0)
+ FROM account_analytic_line
+ WHERE task_id IN %s
GROUP BY task_id""", (tuple(ids),))
-
+
hours = dict(cr.fetchall())
for task in self.browse(cr, uid, ids, context=context):
res[task.id] = {}
@@ -53,60 +45,98 @@
res[task.id]['delay_hours'] = res[task.id]['total_hours'] - task.planned_hours
res[task.id]['progress'] = 0.0
if (task.remaining_hours + hours.get(task.id, 0.0)):
- res[task.id]['progress'] = round(min(100.0 * hours.get(task.id, 0.0) / res[task.id]['total_hours'], 99.99),2)
- if task.state in ('done','cancelled'):
+ res[task.id]['progress'] = round(min(100.0 * hours.get(task.id, 0.0) / res[task.id]['total_hours'], 99.99), 2)
+ if task.state in ('done', 'cancelled'):
res[task.id]['progress'] = 100.0
return res
+ def _store_set_values(self, cr, uid, ids, fields, context=None):
+ # Hack to avoid redefining most of function fields of project.project model
+ # This is mainly due to the fact that orm _store_set_values use direct access to database.
+ # So when modifiy aa line the _store_set_values as it uses cursor directly to update tasks
+ # project triggers on task are not called
+ res = super(ProjectTask, self)._store_set_values(cr, uid, ids, fields, context=context)
+ for row in self.browse(cr, SUPERUSER_ID, ids, context=context):
+ project = row.project_id
+ project.write({'parent_id': project.parent_id.id})
+ return res
+
+
def _get_analytic_line(self, cr, uid, ids, context=None):
result = []
for aal in self.pool.get('account.analytic.line').browse(cr, uid, ids, context=context):
if aal.task_id: result.append(aal.task_id.id)
return result
-
- _columns = {'work_ids': fields.one2many('hr.analytic.timesheet', 'task_id', 'Work done'),
-
- 'effective_hours': fields.function(_progress_rate, multi="progress", method=True, string='Time Spent',
- help="Sum of spent hours of all tasks related to this project and its child projects.",
- store = {'project.task': (lambda self, cr, uid, ids, c={}: ids, TASK_WATCHERS, 20),
- 'account.analytic.line': (_get_analytic_line, AA_WATCHERS, 20)}),
-
- 'delay_hours': fields.function(_progress_rate, multi="progress", method=True, string='Deduced Hours',
- help="Sum of spent hours with invoice factor of all tasks related to this project and its child projects.",
- store = {'project.task': (lambda self, cr, uid, ids, c={}: ids, TASK_WATCHERS, 20),
- 'account.analytic.line': (_get_analytic_line, AA_WATCHERS, 20)}),
-
- 'total_hours': fields.function(_progress_rate, multi="progress", method=True, string='Total Time',
- help="Sum of total hours of all tasks related to this project and its child projects.",
- store = {'project.task': (lambda self, cr, uid, ids, c={}: ids, TASK_WATCHERS, 20),
- 'account.analytic.line': (_get_analytic_line, AA_WATCHERS, 20)}),
-
- 'progress': fields.function(_progress_rate, multi="progress", method=True, string='Progress', type='float', group_operator="avg",
- help="Percent of tasks closed according to the total of tasks todo.",
- store = {'project.task': (lambda self, cr, uid, ids, c={}: ids, TASK_WATCHERS, 20),
- 'account.analytic.line': (_get_analytic_line, AA_WATCHERS, 20)})}
-
+ _columns = {
+ 'work_ids': fields.one2many('hr.analytic.timesheet', 'task_id', 'Work done'),
+ 'effective_hours': fields.function(_progress_rate, multi="progress", string='Time Spent',
+ help="Computed using the sum of the task work done (timesheet lines "
+ "associated on this task).",
+ store={'project.task': (lambda self, cr, uid, ids, c=None: ids, TASK_WATCHERS, 20),
+ 'account.analytic.line': (_get_analytic_line, TIMESHEET_WATCHERS, 20)}),
+ 'delay_hours': fields.function(_progress_rate, multi="progress", string='Deduced Hours',
+ help="Computed as difference between planned hours by the project manager "
+ "and the total hours of the task.",
+ store={'project.task': (lambda self, cr, uid, ids, c=None: ids, TASK_WATCHERS, 20),
+ 'account.analytic.line': (_get_analytic_line, TIMESHEET_WATCHERS, 20)}),
+ 'total_hours': fields.function(_progress_rate, multi="progress", string='Total Time',
+ help="Computed as: Time Spent + Remaining Time.",
+ store={'project.task': (lambda self, cr, uid, ids, c=None: ids, TASK_WATCHERS, 20),
+ 'account.analytic.line': (_get_analytic_line, TIMESHEET_WATCHERS, 20)}),
+ 'progress': fields.function(_progress_rate, multi="progress", string='Progress', type='float', group_operator="avg",
+ help="If the task has a progress of 99.99% you should close the task if it's "
+ "finished or reevaluate the time",
+ store={'project.task': (lambda self, cr, uid, ids, c=None: ids, TASK_WATCHERS, 20),
+ 'account.analytic.line': (_get_analytic_line, TIMESHEET_WATCHERS, 20)})
+ }
+
def write(self, cr, uid, ids, vals, context=None):
res = super(ProjectTask, self).write(cr, uid, ids, vals, context=context)
- if 'project_id' in vals:
+ if vals.get('project_id'):
ts_obj = self.pool.get('hr.analytic.timesheet')
project_obj = self.pool.get('project.project')
- project = project_obj.browse(cr, uid, vals['project_id'], context)
+ project = project_obj.browse(cr, uid, vals['project_id'], context=context)
account_id = project.analytic_account_id.id
for task in self.browse(cr, uid, ids, context=context):
ts_obj.write(cr, uid, [w.id for w in task.work_ids], {'account_id': account_id}, context=context)
return res
-ProjectTask()
-
-class HrAnalyticTimesheet(osv.osv):
+
+class HrAnalyticTimesheet(orm.Model):
+ """
+ Add field:
+ - hr_analytic_timesheet_id:
+ This field is added to make sure a hr.analytic.timesheet can be used
+ instead of a project.task.work.
+
+ This field will always return false as we want to by pass next operations
+ in project.task write method.
+
+ Without this field, it is impossible to write a project.task in which
+ work_ids is empty as a check on it would raise an AttributeError.
+
+ This is because, in project_timesheet module, project.task's write method
+ checks if there is an hr_analytic_timesheet_id on each work_ids.
+
+ (project_timesheet.py, line 250, in write)
+ if not task_work.hr_analytic_timesheet_id:
+ continue
+
+ But as we redefine work_ids to be a relation to hr_analytic_timesheet
+ instead of project.task.work, hr_analytic_timesheet doesn't exists
+ in hr_analytic_timesheet... so it fails.
+
+ An other option would be to monkey patch the project.task's write method...
+ As this method doesn't fit with the change of work_ids relation in model.
+ """
_inherit = "hr.analytic.timesheet"
_name = "hr.analytic.timesheet"
def on_change_unit_amount(self, cr, uid, sheet_id, prod_id, unit_amount, company_id,
- unit=False, journal_id=False, task_id=False, to_invoice=False, context=None):
+ unit=False, journal_id=False, task_id=False, to_invoice=False,
+ context=None):
res = super(HrAnalyticTimesheet, self).on_change_unit_amount(cr,
uid,
sheet_id,
@@ -125,34 +155,53 @@
res['value']['to_invoice'] = p.to_invoice.id
return res
-HrAnalyticTimesheet()
-
-class AccountAnalyticLine(osv.osv):
+ def _get_dummy_hr_analytic_timesheet_id(self, cr, uid, ids, names, arg, context=None):
+ """
+ Ensure all hr_analytic_timesheet_id is always False
+ """
+ return dict.fromkeys(ids, False)
+
+ _columns = {
+ 'hr_analytic_timesheet_id': fields.function(_get_dummy_hr_analytic_timesheet_id,
+ string='Related Timeline Id',
+ type='boolean')
+ }
+
+
+class AccountAnalyticLine(orm.Model):
"""We add task_id on AA and manage update of linked task indicators"""
_inherit = "account.analytic.line"
_name = "account.analytic.line"
-
-
_columns = {'task_id': fields.many2one('project.task', 'Task')}
-
- def _compute_hours_with_factor(self, cr, uid, hours, factor_id, context=None):
- if not hours or not factor_id:
- return 0.0
- fact_obj = self.pool.get('hr_timesheet_invoice.factor')
- factor = 100.0 - float(fact_obj.browse(cr, uid, factor_id).factor)
- return (float(hours)/100.00)*factor
+ def _check_task_project(self, cr, uid, ids):
+ for line in self.browse(cr, uid, ids):
+ if line.task_id and line.account_id:
+ if line.task_id.project_id.analytic_account_id.id != line.account_id.id:
+ return False
+ return True
+
+ _constraints = [
+ (_check_task_project, 'Error! Task must belong to the project.', ['task_id','account_id']),
+ ]
+
+
+ def _trigger_projects(self, cr, uid, task_ids, context=None):
+ t_obj = self.pool['project.task']
+ for task in t_obj.browse(cr, SUPERUSER_ID, task_ids, context=context):
+ project = task.project_id
+ project.write({'parent_id': project.parent_id.id})
+ return task_ids
def _set_remaining_hours_create(self, cr, uid, vals, context=None):
if not vals.get('task_id'):
return
hours = vals.get('unit_amount', 0.0)
- factor_id = vals.get('to_invoice')
- comp_hours = self._compute_hours_with_factor(cr, uid, hours, factor_id, context)
- if comp_hours:
- cr.execute('update project_task set remaining_hours=remaining_hours - %s where id=%s',
- (comp_hours, vals['task_id']))
+ # We can not do a write else we will have a recursion error
+ cr.execute('update project_task set remaining_hours=remaining_hours - %s where id=%s',
+ (hours, vals['task_id']))
+ self._trigger_projects(cr, uid, [vals['task_id']], context=context)
return vals
def _set_remaining_hours_write(self, cr, uid, ids, vals, context=None):
@@ -161,9 +210,9 @@
for line in self.browse(cr, uid, ids):
# in OpenERP if we set a value to nil vals become False
old_task_id = line.task_id and line.task_id.id or None
- new_task_id = vals.get('task_id', old_task_id) #if no task_id in vals we assume it is equal to old
-
- #we look if value has changed
+ # if no task_id in vals we assume it is equal to old
+ new_task_id = vals.get('task_id', old_task_id)
+ # we look if value has changed
if (new_task_id != old_task_id) and old_task_id:
self._set_remaining_hours_unlink(cr, uid, [line.id], context)
if new_task_id:
@@ -172,20 +221,19 @@
line.to_invoice and line.to_invoice.id or False),
'unit_amount': vals.get('unit_amount', line.unit_amount)}
self._set_remaining_hours_create(cr, uid, data, context)
+ self._trigger_projects(cr, uid, list(set([old_task_id, new_task_id])),
+ context=context)
return ids
if new_task_id:
hours = vals.get('unit_amount', line.unit_amount)
- factor_id = vals.get('to_invoice', line.to_invoice and line.to_invoice.id or False)
- comp_hours = self._compute_hours_with_factor(cr, uid, hours, factor_id, context)
- old_factor = line.to_invoice and line.to_invoice.id or False
- old_comp_hours = self._compute_hours_with_factor(cr, uid, line.unit_amount,
- old_factor, context)
- # we always execute request because invoice factor can be set to gratis
+ old_hours = line.unit_amount if old_task_id else 0.0
+ # We can not do a write else we will have a recursion error
cr.execute('update project_task set remaining_hours=remaining_hours - %s + (%s) where id=%s',
- (comp_hours, old_comp_hours, new_task_id))
+ (hours, old_hours, new_task_id))
+ self._trigger_projects(cr, uid, [new_task_id], context=context)
+
return ids
-
def _set_remaining_hours_unlink(self, cr, uid, ids, context=None):
if isinstance(ids, (int, long)):
ids = [ids]
@@ -193,26 +241,19 @@
if not line.task_id:
continue
hours = line.unit_amount or 0.0
- factor_id = line.to_invoice and line.to_invoice.id or False
- comp_hours = self._compute_hours_with_factor(cr, uid, hours, factor_id, context)
- if comp_hours:
- cr.execute('update project_task set remaining_hours=remaining_hours + %s where id=%s',
- (comp_hours, line.task_id.id))
+ cr.execute('update project_task set remaining_hours=remaining_hours + %s where id=%s',
+ (hours, line.task_id.id))
return ids
-
-
def create(self, cr, uid, vals, context=None):
if vals.get('task_id'):
self._set_remaining_hours_create(cr, uid, vals, context)
- return super(AccountAnalyticLine,self).create(cr, uid, vals, context=context)
+ return super(AccountAnalyticLine, self).create(cr, uid, vals, context=context)
def write(self, cr, uid, ids, vals, context=None):
- self. _set_remaining_hours_write(cr, uid, ids, vals, context=context)
+ self._set_remaining_hours_write(cr, uid, ids, vals, context=context)
return super(AccountAnalyticLine, self).write(cr, uid, ids, vals, context=context)
def unlink(self, cr, uid, ids, context=None):
self._set_remaining_hours_unlink(cr, uid, ids, context)
return super(AccountAnalyticLine, self).unlink(cr, uid, ids, context=context)
-
-AccountAnalyticLine()
=== modified file 'timesheet_task/project_task_view.xml'
--- timesheet_task/project_task_view.xml 2012-06-06 13:14:12 +0000
+++ timesheet_task/project_task_view.xml 2013-11-07 15:51:52 +0000
@@ -1,46 +1,212 @@
<openerp>
<data>
- #---------------------------------------------------------------------------------------------------------
- # Adapt task views
- #---------------------------------------------------------------------------------------------------------
- <record id="view_task_form2" model="ir.ui.view">
+
+ #---------------------------------------------------------------------------------------------------------
+ # Adapt task views
+ #---------------------------------------------------------------------------------------------------------
+ <!-- Replace the view project.view_task_form2 instead of inheriting
+ it and set a low priority. This replacement is to avoid the following error:
+
+ ERROR timesheet openerp.osv.orm: Can't find field 'hours' in the following view parts
+ composing the view of object model 'project.task':
+ * project.task.form
+
+ Either you wrongly customized this view, or some modules bringing those views are not
+ compatible with your current data model ERROR timesheet openerp.addons.base.ir.ir_ui_view:
+ Can't render view for model: project.task -->
+
+ <record id="project.view_task_form2" model="ir.ui.view">
<field name="name">project.task.form</field>
<field name="model">project.task</field>
- <field name="inherit_id" ref="project.view_task_form2"/>
- <field name="type">form</field>
+ <field eval="1" name="priority" />
<field name="arch" type="xml">
- <xpath expr="/form/notebook/page/field[@name='work_ids']" position="replace">
- <field colspan="4" name="work_ids" nolabel="1" attrs="{'invisible':[('state','in',['draft'])],'readonly':[('state','=','done')]}">
- <tree string="Task Work" editable="top">
- <field name="user_id" on_change="on_change_user_id(user_id)" required="1" invisible="1"/>
- <field name="name"/>
- <field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id, parent.id, to_invoice)" sum="Total time" widget="float_time"/>
- <field name="date" on_change="on_change_date(date)"/>
- <field name="journal_id" invisible="1"/>
- <field domain="[('type','=','normal')]" name="account_id" invisible="1"/>
- <field name="product_id" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id, parent.id, to_invoice)" required="1" domain="[('type','=','service')]" invisible="1"/>
- <field name="product_uom_id" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id, parent.id, to_invoice)" invisible="1"/>
- <field name="amount" sum="Total cost" invisible="1"/>
- <field name="general_account_id" invisible="1"/>
- <field name="to_invoice" />
- </tree>
- <form string="Task Work" editable="top">
- <field name="user_id" on_change="on_change_user_id(user_id)" required="1"/>
- <field name="name" default_focus="1"/>
- <field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id, parent.id, to_invoice)" sum="Total time" widget="float_time"/>
- <field name="date" on_change="on_change_date(date)"/>
- <field name="journal_id" invisible="1"/>
- <field domain="[('type','=','normal')]" name="account_id" invisible="1"/>
- <field name="product_id" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id, parent.id, to_invoice)" required="1" domain="[('type','=','service')]" invisible="1"/>
- <field name="product_uom_id" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id, parent.id, to_invoice)" invisible="1"/>
- <field name="amount" sum="Total cost" invisible="1"/>
- <field name="general_account_id" invisible="1"/>
- <field name="to_invoice" />
- </form>
- </field>
- </xpath>
+ <form string="Project" version="7.0">
+ <header>
+ <!-- <button name="do_open" string="Start Task" type="object"
+ states="draft,pending" class="oe_highlight"/> <button name="do_draft" string="Draft"
+ type="object" states="cancel,done"/> -->
+ <button name="project_task_reevaluate"
+ string="Reactivate" type="object" states="cancelled,done"
+ context="{'button_reactivate':True}" groups="base.group_user" />
+ <button name="action_close" string="Done"
+ type="object" states="draft,open,pending"
+ groups="base.group_user" />
+ <button name="do_cancel" string="Cancel Task"
+ type="object" states="draft,open,pending"
+ groups="base.group_user" />
+ <field name="stage_id" widget="statusbar"
+ clickable="True" />
+ </header>
+ <sheet string="Task">
+ <h1>
+ <field name="name" placeholder="Task summary..." />
+ </h1>
+ <group>
+ <group>
+ <field name="project_id"
+ on_change="onchange_project(project_id)"
+ context="{'default_use_tasks':1}" />
+ <field name="user_id"
+ attrs="{'readonly':[('state','in',['done', 'cancelled'])]}"
+ options='{"no_open": True}' />
+ <field name="planned_hours"
+ widget="float_time"
+ groups="project.group_time_work_estimation_tasks"
+ on_change="onchange_planned(planned_hours, effective_hours)" />
+ </group>
+ <group>
+ <field name="date_deadline"
+ attrs="{'readonly':[('state','in',['done', 'cancelled'])]}" />
+ <field name="categ_ids" widget="many2many_tags" />
+ <field name="progress" widget="progressbar"
+ groups="project.group_time_work_estimation_tasks"
+ attrs="{'invisible':[('state','=','cancelled')]}" />
+ </group>
+ </group>
+ <notebook>
+ <page string="Description">
+ <field name="description"
+ attrs="{'readonly':[('state','=','done')]}"
+ placeholder="Add a Description..." />
+ <field name="work_ids">
+ <tree string="Task Work"
+ editable="top">
+ <field name="user_id"
+ on_change="on_change_user_id(user_id)"
+ required="1" invisible="1"/>
+ <field name="name" />
+ <field name="unit_amount"
+ on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id, parent.id, to_invoice)"
+ sum="Total time" widget="float_time" />
+ <field name="date"
+ on_change="on_change_date(date)" />
+ <field name="journal_id"
+ invisible="1" />
+ <field domain="[('type','=','normal')]"
+ name="account_id" invisible="1" />
+ <field name="product_id"
+ on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id, parent.id, to_invoice)"
+ required="1"
+ domain="[('type','=','service')]"
+ invisible="1" />
+ <field name="product_uom_id"
+ on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id, parent.id, to_invoice)"
+ invisible="1" />
+ <field name="amount" sum="Total cost"
+ invisible="1" />
+ <field name="general_account_id"
+ invisible="1" />
+ <field name="to_invoice" />
+ </tree>
+ <form string="Task Work"
+ editable="top">
+ <field name="user_id"
+ on_change="on_change_user_id(user_id)"
+ required="1" />
+ <field name="name"
+ default_focus="1" />
+ <field name="unit_amount"
+ on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id, parent.id, to_invoice)"
+ sum="Total time" widget="float_time" />
+ <field name="date"
+ on_change="on_change_date(date)" />
+ <field name="journal_id"
+ invisible="1" />
+ <field domain="[('type','=','normal')]"
+ name="account_id" invisible="1" />
+ <field name="product_id"
+ on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id, parent.id, to_invoice)"
+ required="1"
+ domain="[('type','=','service')]"
+ invisible="1" />
+ <field name="product_uom_id"
+ on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id, parent.id, to_invoice)"
+ invisible="1" />
+ <field name="amount" sum="Total cost"
+ invisible="1" />
+ <field name="general_account_id"
+ invisible="1" />
+ <field name="to_invoice" />
+ </form>
+ </field>
+ <group>
+ <group class="oe_subtotal_footer oe_right"
+ name="project_hours"
+ groups="project.group_time_work_estimation_tasks">
+ <field name="effective_hours"
+ widget="float_time" />
+ <label for="remaining_hours"
+ string="Remaining"
+ groups="project.group_time_work_estimation_tasks" />
+ <div>
+ <field name="remaining_hours"
+ widget="float_time"
+ attrs="{'readonly':[('state','in',('done','cancelled'))]}"
+ groups="project.group_time_work_estimation_tasks" />
+ </div>
+ <field name="total_hours"
+ widget="float_time"
+ class="oe_subtotal_footer_separator" />
+ </group>
+ </group>
+ <div class="oe_clear" />
+ </page>
+ <page string="Delegation"
+ groups="project.group_delegate_task">
+ <button
+ name="%(project.action_project_task_delegate)d"
+ string="Delegate" type="action"
+ states="pending,open,draft" groups="project.group_delegate_task" />
+ <separator string="Parent Tasks" />
+ <field name="parent_ids" />
+ <separator string="Delegated tasks" />
+ <field name="child_ids">
+ <tree string="Delegated tasks">
+ <field name="name" />
+ <field name="user_id" />
+ <field name="stage_id" />
+ <field name="state"
+ invisible="1" />
+ <field name="effective_hours"
+ widget="float_time" />
+ <field name="progress"
+ widget="progressbar" />
+ <field name="remaining_hours"
+ widget="float_time" />
+ <field name="date_deadline" />
+ </tree>
+ </field>
+ </page>
+ <page string="Extra Info"
+ attrs="{'readonly':[('state','=','done')]}">
+ <group col="4">
+ <field name="priority" groups="base.group_user" />
+ <field name="sequence" />
+ <field name="partner_id" />
+ <field name="state" invisible="1" />
+ <field name="company_id"
+ groups="base.group_multi_company"
+ widget="selection" />
+ </group>
+ <group>
+ <group string="Gantt View">
+ <field name="date_start" />
+ <field name="date_end" />
+ </group>
+ <group>
+ </group>
+ </group>
+ </page>
+ </notebook>
+ </sheet>
+ <div class="oe_chatter">
+ <field name="message_follower_ids" widget="mail_followers"
+ groups="base.group_user" />
+ <field name="message_ids" widget="mail_thread" />
+ </div>
+ </form>
</field>
</record>
-
+
</data>
</openerp>
\ No newline at end of file
=== added directory 'timesheet_task/test'
=== added file 'timesheet_task/test/task_timesheet_indicators.yml'
--- timesheet_task/test/task_timesheet_indicators.yml 1970-01-01 00:00:00 +0000
+++ timesheet_task/test/task_timesheet_indicators.yml 2013-11-07 15:51:52 +0000
@@ -0,0 +1,282 @@
+-
+ Create a user 'HR Tester'
+-
+ !record {model: res.users, id: res_users_hrtester0}:
+ company_id: base.main_company
+ name: HR Tester
+ login: hr
+ password: hr
+ groups_id:
+ - base.group_hr_manager
+-
+ Create a product with type service used to specify employees designation
+-
+ !record {model: product.product, id: product_product_hrtester0}:
+ categ_id: product.product_category_6
+ cost_method: standard
+ mes_type: fixed
+ name: HR Tester service
+ standard_price: 10.0
+ type: service
+ uom_id: product.product_uom_hour
+ uom_po_id: product.product_uom_hour
+ volume: 0.0
+ warranty: 0.0
+ weight: 0.0
+ weight_net: 0.0
+-
+ Create an analytic journal for employees timesheet
+-
+ !record {model: account.analytic.journal, id: account_analytic_journal_hrtimesheettest0}:
+ company_id: base.main_company
+ name: HR Timesheet test
+ type: general
+-
+ Create an employee 'HR Tester' for user 'HR Tester'
+-
+ !record {model: hr.employee, id: hr_employee_hrtester0}:
+ name: HR Tester
+ user_id: res_users_hrtester0
+ product_id: product_product_hrtester0
+ journal_id: account_analytic_journal_hrtimesheettest0
+-
+ Create a timesheet invoice factor of 100%
+-
+ !record {model: hr_timesheet_invoice.factor, id: timesheet_invoice_factor0}:
+ name: 100%
+ customer_name: 100%
+ factor: 0.0
+-
+ Create a project 'Timesheet task and indicator tests'
+-
+ !record {model: project.project, id: project_project_timesheettest0}:
+ company_id: base.main_company
+ name: Timesheet task and indicator tests
+ to_invoice: timesheet_invoice_factor0
+-
+ Create a task 'Test timesheet records'
+-
+ !record {model: project.task, id: project_task_testtimesheetrecords0}:
+ date_start: !eval time.strftime('%Y-05-%d %H:%M:%S')
+ name: Test timesheet records
+ planned_hours: 200.0
+ project_id: project_project_timesheettest0
+ user_id: res_users_hrtester0
+-
+ The planned time on project should then be 200
+-
+ !assert {model: project.project, id: project_project_timesheettest0, string: planned_time_on_project_01}:
+ - planned_hours == 200.0
+-
+ The time spent on project should then be 0
+-
+ !assert {model: project.project, id: project_project_timesheettest0, string: time_spent_on_project_01}:
+ - effective_hours == 0.0
+-
+ Make a timesheet line of 10.0 on the project without task
+-
+ !python {model: hr.analytic.timesheet}: |
+ import time
+ project_obj = self.pool.get('project.project')
+ project = project_obj.browse(cr, uid, ref('project_project_timesheettest0'))
+ ts_line= {
+ 'name': '/',
+ 'user_id': ref('res_users_hrtester0'),
+ 'date': time.strftime('%Y-%m-%d'),
+ 'account_id': project.analytic_account_id.id,
+ 'unit_amount': 10.0,
+ 'journal_id': ref('account_analytic_journal_hrtimesheettest0'),
+ 'to_invoice': ref('timesheet_invoice_factor0'),
+ }
+ ts = self.create(cr, uid, ts_line)
+ assert ts, "Timesheet has not been recorded correctly"
+-
+ The time spent on project should still be 0 as no task has been set
+-
+ !assert {model: project.project, id: project_project_timesheettest0}:
+ - effective_hours == 0.0
+-
+ Make a timesheet line of 10.0 on the project with a task assigned
+-
+ !python {model: hr.analytic.timesheet}: |
+ import time
+ project_obj = self.pool.get('project.project')
+ project = project_obj.browse(cr, uid, ref('project_project_timesheettest0'))
+ ts_line= {
+ 'name': '/',
+ 'user_id': ref('res_users_hrtester0'),
+ 'date': time.strftime('%Y-%m-%d'),
+ 'account_id': project.analytic_account_id.id,
+ 'unit_amount': 10.0,
+ 'task_id': ref('project_task_testtimesheetrecords0'),
+ 'to_invoice': ref('timesheet_invoice_factor0'),
+ 'journal_id': ref('account_analytic_journal_hrtimesheettest0'),
+ }
+ ts = self.create(cr, uid, ts_line)
+ assert ts, "Timesheet has not been recorded correctly"
+-
+ The time spent on the task should be 10.0
+-
+ !assert {model: project.task, id: project_task_testtimesheetrecords0, string: time_spent_on_task_01}:
+ - effective_hours == 10.0
+-
+ The remaining time on the task should be 190.0
+-
+ !assert {model: project.task, id: project_task_testtimesheetrecords0, string: remaining_time_on_task_01}:
+ - remaining_hours == 190.0
+-
+ The time spent on project should be 10.0
+-
+ !assert {model: project.project, id: project_project_timesheettest0, string: time_spent_on_project_02}:
+ - effective_hours == 10.0
+-
+ Make a timesheet line of 10.0 on the project without task, then assign one
+-
+ !python {model: hr.analytic.timesheet}: |
+ import time
+ project_obj = self.pool.get('project.project')
+ project = project_obj.browse(cr, uid, ref('project_project_timesheettest0'))
+ ts_line= {
+ 'name': '/',
+ 'user_id': ref('res_users_hrtester0'),
+ 'date': time.strftime('%Y-%m-%d'),
+ 'account_id': project.analytic_account_id.id,
+ 'unit_amount': 10.0,
+ 'to_invoice': ref('timesheet_invoice_factor0'),
+ 'journal_id': ref('account_analytic_journal_hrtimesheettest0'),
+ }
+ ts = self.create(cr, uid, ts_line)
+ assert ts, "Timesheet has not been recorded correctly"
+ vals = {
+ 'task_id': ref('project_task_testtimesheetrecords0')
+ }
+ result = self.write(cr, uid, ts, vals)
+-
+ The time spent on the task should be 20.0
+-
+ !assert {model: project.task, id: project_task_testtimesheetrecords0, string: time_spent_on_task_02}:
+ - effective_hours == 20.0
+-
+ The remaining time on the task should be 180.0
+-
+ !assert {model: project.task, id: project_task_testtimesheetrecords0, string: remaining_time_on_task_02}:
+ - remaining_hours == 180.0
+-
+ The time spent on project should be 20.0
+-
+ !assert {model: project.project, id: project_project_timesheettest0, string: time_spent_on_project_03}:
+ - effective_hours == 20.0
+-
+ Make a timesheet line of 10.0 with task, then remove the task
+-
+ !python {model: hr.analytic.timesheet}: |
+ import time
+ project_obj = self.pool.get('project.project')
+ project = project_obj.browse(cr, uid, ref('project_project_timesheettest0'))
+ task_obj = self.pool.get('project.task')
+ ts_line= {
+ 'name': '/',
+ 'user_id': ref('res_users_hrtester0'),
+ 'date': time.strftime('%Y-%m-%d'),
+ 'account_id': project.analytic_account_id.id,
+ 'unit_amount': 10.0,
+ 'task_id': ref('project_task_testtimesheetrecords0'),
+ 'to_invoice': ref('timesheet_invoice_factor0'),
+ 'journal_id': ref('account_analytic_journal_hrtimesheettest0'),
+ }
+ ts = self.create(cr, uid, ts_line)
+ assert ts, "Timesheet has not been recorded correctly"
+ task = task_obj.browse(cr, uid, ref('project_task_testtimesheetrecords0'))
+ assert task.effective_hours == 30.0, "Effective hours on task is not correct"
+ assert task.remaining_hours == 170.0, "Remaining hours on task is not correct"
+ project.refresh()
+ assert project.effective_hours == 30.0, "Effective hours on project is not correct"
+ vals = {
+ 'task_id': False
+ }
+ result = self.write(cr, uid, ts, vals)
+-
+ The time spent on the task should be 20.0
+-
+ !assert {model: project.task, id: project_task_testtimesheetrecords0, string: time_spent_on_task_03}:
+ - effective_hours == 20.0
+-
+ The remaining time on the task should be 180.0
+-
+ !assert {model: project.task, id: project_task_testtimesheetrecords0, string: remaining_time_on_task_03}:
+ - remaining_hours == 180.0
+-
+ The time spent on project should be 20.0
+-
+ !assert {model: project.project, id: project_project_timesheettest0, string: time_spent_on_project_04}:
+ - effective_hours == 20.0
+-
+ Make a timesheet line of 10.0 with task, then delete the line
+-
+ !python {model: hr.analytic.timesheet}: |
+ import time
+ project_obj = self.pool.get('project.project')
+ project = project_obj.browse(cr, uid, ref('project_project_timesheettest0'))
+ task_obj = self.pool.get('project.task')
+ ts_line= {
+ 'name': '/',
+ 'user_id': ref('res_users_hrtester0'),
+ 'date': time.strftime('%Y-%m-%d'),
+ 'account_id': project.analytic_account_id.id,
+ 'unit_amount': 10.0,
+ 'task_id': ref('project_task_testtimesheetrecords0'),
+ 'to_invoice': ref('timesheet_invoice_factor0'),
+ 'journal_id': ref('account_analytic_journal_hrtimesheettest0'),
+ }
+ ts = self.create(cr, uid, ts_line)
+ assert ts, "Timesheet has not been recorded correctly"
+ task = task_obj.browse(cr, uid, ref('project_task_testtimesheetrecords0'))
+ assert task.effective_hours == 30.0, "Effective hours on task is not correct"
+ assert task.remaining_hours == 170.0, "Remaining hours on task is not correct"
+ project.refresh()
+ assert project.effective_hours == 30.0, "Effective hours on project is not correct"
+ result = self.unlink(cr, uid, [ts])
+-
+ The time spent on the task should be 20.0
+-
+ !assert {model: project.task, id: project_task_testtimesheetrecords0, string: time_spent_on_task_04}:
+ - effective_hours == 20.0
+-
+ The remaining time on the task should be 180.0
+-
+ !assert {model: project.task, id: project_task_testtimesheetrecords0, string: remaining_time_on_task_04}:
+ - remaining_hours == 180.0
+-
+ The time spent on project should be 20.0
+-
+ !assert {model: project.project, id: project_project_timesheettest0, string: time_spent_on_project_05}:
+ - effective_hours == 20.0
+-
+ Change the remaining hours of the task to 200.0
+-
+ !python {model: project.task}: |
+ task = self.browse(cr, uid, ref('project_task_testtimesheetrecords0'))
+ vals = {
+ 'remaining_hours': 200.0,
+ }
+ result = self.write(cr, uid, [task.id], vals)
+-
+ The time spent on the task should still be 20.0
+-
+ !assert {model: project.task, id: project_task_testtimesheetrecords0, string: time_spent_on_task_05}:
+ - effective_hours == 20.0
+-
+ The remaining time on the task should be 200.0
+-
+ !assert {model: project.task, id: project_task_testtimesheetrecords0, string: remaining_time_on_task_05}:
+ - remaining_hours == 200.0
+-
+ The delay in hours on the task should be 20.0
+-
+ !assert {model: project.task, id: project_task_testtimesheetrecords0, string: delay_on_task_01}:
+ - delay_hours == 20.0
+-
+ The total on the task should be 220.0
+-
+ !assert {model: project.task, id: project_task_testtimesheetrecords0, string: total_time_on_task_01}:
+ - total_hours == 220.0
=== removed file 'timesheet_task/tmp_file_for_project_indicator.py'
--- timesheet_task/tmp_file_for_project_indicator.py 2012-06-06 13:14:12 +0000
+++ timesheet_task/tmp_file_for_project_indicator.py 1970-01-01 00:00:00 +0000
@@ -1,29 +0,0 @@
-def _progress_rate(self, cr, uid, ids, names, arg, context=None):
- """As OpenERP SA made a query for this function field (perf. reason obviously),
- I must overide it all."""
- result = {}.fromkeys(ids, 0.0)
- progress = {}
- if not ids:
- return res
- cr.execute('''SELECT project_id,
- sum(planned_hours) as sum_planned_hours,
- sum(total_hours) as sum_total_hours,
- sum(effective_hours) as sum_effective_hours,
- sum(remaining_hours) as remaining_hours,
- sum(deduced_hours) as deduced_hours
- FROM project_task
- WHERE project_id in %s
- AND state<>'cancelled'
- GROUP BY project_id''', (tuple(ids),))
-
- res = cr.dictfetchall()
- for stat in res:
- project = self.browse(cr, uid, res['project_id'], context=context)
- progr = (stat['sum_planned_hours'] and
- round(100.0 * stat['sum_total_hours'] / stat['sum_planned_hours'], 2) or 0.0),
- result[project.id] = {'planned_hours': stat['sum_planned_hours'],
- 'effective_hours': stat['sum_effective_hours'],
- 'total_hours': stat['sum_total_hours'],
- 'progress_rate': progr,
- 'deduced_hours': stat['sum_deduced_hours']}
- return res
\ No newline at end of file
Follow ups