← Back to team overview

openerp-community-reviewer team mailing list archive

lp:~camptocamp/hr-timesheet/7.0-timesheet-add-department-by-default into lp:hr-timesheet

 

Matthieu Dietrich @ camptocamp has proposed merging lp:~camptocamp/hr-timesheet/7.0-timesheet-add-department-by-default into lp:hr-timesheet.

Requested reviews:
  Vincent Renaville@camptocamp (vrenaville-c2c)

For more details, see:
https://code.launchpad.net/~camptocamp/hr-timesheet/7.0-timesheet-add-department-by-default/+merge/195953

Adding a default method to fill the "department" field in timesheet activities.
-- 
https://code.launchpad.net/~camptocamp/hr-timesheet/7.0-timesheet-add-department-by-default/+merge/195953
Your team HR Core Editors is subscribed to branch 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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-09-23 05:34+0000\n"
+"X-Generator: Launchpad (build 16771)\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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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&nbsp;<span class="page"/></td>
+                <td style="text-align:left;font-size:10;width:30px">&nbsp;of&nbsp;<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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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'),"
                                       "('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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +0000
@@ -0,0 +1,103 @@
+# -*- 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, osv
+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
+from openerp.tools.translate import _
+
+
+
+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.
+        """
+        return True
+
+    _constraints = [(_altern_si_so, 'Error ! Sign in (resp. Sign out) must follow Sign out (resp. Sign in)', ['action'])]
+
+    _defaults = {
+        'name': _default_date,
+        }
+
+    def check_alter_si_so(self, cr, uid, ids, context=None):
+        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
+               raise osv.except_osv(_('UserError'),_('%s and %s are both a %s')%(prev_atts[0].name,att.name,att.action)) 
+            if next_atts and next_atts[0].action == att.action: # next exists and is same action
+               raise osv.except_osv(_('UserError'),_('%s and %s are both a %s')%(next_atts[0].name,att.name,att.action)) 
+            if (not prev_atts) and att.action != 'sign_in': # first attendance must be sign_in
+               raise osv.except_osv(_('UserError'),_('%s does not have a previous Sign in, please Sign in before Sign out')%(att.name)) 
+        return True

=== 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-20 12:36:30 +0000
@@ -0,0 +1,88 @@
+# -*- 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),
+                }
+            ),
+        }
+
+    def _check_sheet_state(self, cr, uid, ids, context=None):
+        ## Deactivate contraint to allow modification of timesheet by project manager
+        return True
+
+    _constraints = [
+        (_check_sheet_state, 'You cannot modify an entry in a Confirmed/Done timesheet !', ['state']),
+    ]
+
+    def _get_department(self, cr, uid, ids, context=None):
+        employee_obj = self.pool.get('hr.employee')
+        department_id = False
+        employee_ids = employee_obj.search(cr, uid, [('user_id','=', uid)])
+        if employee_ids:
+            department_id = employee_obj.browse(cr, uid, employee_ids[0], context=context).department_id.id
+        return department_id
+
+    _defaults = {
+        'department_id': _get_department,
+    }
+
+    
+class HrTimesheetsheet(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_timesheet_sheet.sheet"
+
+    def button_confirm(self, cr, uid, ids, context=None):
+        attendances_obj = self.pool.get('hr.attendance')
+        for sheet in self.browse(cr, uid, ids, context=context):
+            ids_attendances = attendances_obj.search(cr,uid,[('sheet_id', '=', sheet.id)])
+            attendances_obj.check_alter_si_so(cr,uid,ids_attendances,context=context)
+        return super(HrTimesheetsheet,self).button_confirm(cr, uid, ids, context=context)
+            
+

=== 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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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','&lt;=', time.strftime('%%Y-%%m-%%d')),('date','&gt;=',time.strftime('%%Y-01-01'))]"
                         help="Current Year"/>
-                    <filter icon="terp-go-month" string="   Month   " name="month"
-                        domain="[('date','&lt;=',(datetime.date.today()+relativedelta(day=31)).strftime('%%Y-%%m-%%d')),('date','&gt;=',(datetime.date.today()-relativedelta(day=1)).strftime('%%Y-%%m-%%d'))]"
+                    <filter icon="terp-go-month" string="Current Month" name="month"
+                        domain="[('date','&lt;=',(context_today()+relativedelta(day=31)).strftime('%%Y-%%m-%%d')),('date','&gt;=',(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','&gt;=',(datetime.date.today()+relativedelta(days=-6,weekday=0)).strftime('%%Y-%%m-%%d')),('date','&lt;=',(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','&gt;=',(context_today()+relativedelta(days=-6,weekday=0)).strftime('%%Y-%%m-%%d')),('date','&lt;=',(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','&lt;&gt;',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-20 12:36:30 +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','&lt;=', time.strftime('%%Y-%%m-%%d')),('date_from','&gt;=',time.strftime('%%Y-01-01'))]"
                         help="Current Year"/>
-                    <filter icon="terp-go-month" string="   Month   " name="month"
-                        domain="[('date_from','&lt;=',(datetime.date.today()+relativedelta(day=31)).strftime('%%Y-%%m-%%d')),('date_from','&gt;=',(datetime.date.today()-relativedelta(day=1)).strftime('%%Y-%%m-%%d'))]"
+                    <filter icon="terp-go-month" string="Current Month" name="month"
+                        domain="[('date_from','&lt;=',(context_today()+relativedelta(day=31)).strftime('%%Y-%%m-%%d')),('date_from','&gt;=',(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','&gt;=',(datetime.date.today()+relativedelta(days=-6,weekday=0)).strftime('%%Y-%%m-%%d')),('date_from','&lt;=',(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','&gt;=',(context_today()+relativedelta(days=-6,weekday=0)).strftime('%%Y-%%m-%%d')),('date_from','&lt;=',(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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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-20 12:36:30 +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