Skip to content
Commits on Source (4)
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Patrik Dufresne
# Copyright 2017 Patrik Dufresne Service Logiciel inc.
#
# 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/>.
#
##############################################################################
\ No newline at end of file
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Patrik Dufresne
# Copyright 2017 Patrik Dufresne Service Logiciel inc.
#
# 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": "Project Timesheet printing",
"description": """
TODO
This module adds a report on timesheet lines (hr.analytic.timesheet) to print
out the detailed of hours passed.
""",
"version": "10.0.1",
'author': 'Patrik Dufresne',
'website': 'http://www.patrikdufresne.com',
'company': 'Patrik Dufresne Service Logiciel inc.',
'category': 'Human Resources',
"license": 'AGPL-3',
"depends": [
"hr_timesheet_project_sheet",
],
"data": [
"report/report_timesheet_templates.xml",
],
'installable': True
}
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * hr_timesheet_project_print
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-01-15 18:34+0000\n"
"PO-Revision-Date: 2018-01-15 18:34+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_project_print
#: model:ir.ui.view,arch_db:hr_timesheet_project_print.report_project_timesheet
msgid "<strong>Comment:</strong>"
msgstr "<strong>Commentaire:</strong>"
#. module: hr_timesheet_project_print
#: model:ir.ui.view,arch_db:hr_timesheet_project_print.report_project_timesheet
msgid "<strong>Location</strong>"
msgstr "<strong>Emplacement</strong>"
#. module: hr_timesheet_project_print
#: model:ir.ui.view,arch_db:hr_timesheet_project_print.report_project_timesheet
msgid "<strong>Remote Contact</strong>"
msgstr "<strong>Contact sur place</strong>"
#. module: hr_timesheet_project_print
#: model:ir.ui.view,arch_db:hr_timesheet_project_print.report_project_timesheet
msgid "<strong>Responsible</strong>"
msgstr "<strong>Responsable</strong>"
#. module: hr_timesheet_project_print
#: model:ir.ui.view,arch_db:hr_timesheet_project_print.report_project_timesheet
msgid "<strong>Timesheet Period</strong>"
msgstr "<strong>Période de la feuille de temps</strong>"
#. module: hr_timesheet_project_print
#: model:ir.ui.view,arch_db:hr_timesheet_project_print.report_project_timesheet
msgid "<strong>Total</strong>"
msgstr "<strong>Total</strong>"
#. module: hr_timesheet_project_print
#: model:ir.ui.view,arch_db:hr_timesheet_project_print.report_project_timesheet
msgid "Begin - End"
msgstr "Début - Fin"
#. module: hr_timesheet_project_print
#: model:ir.ui.view,arch_db:hr_timesheet_project_print.report_project_timesheet
msgid "Break"
msgstr "Pause"
#. module: hr_timesheet_project_print
#: model:ir.ui.view,arch_db:hr_timesheet_project_print.report_project_timesheet
msgid "Date"
msgstr "Date "
#. module: hr_timesheet_project_print
#: model:ir.ui.view,arch_db:hr_timesheet_project_print.report_project_timesheet
msgid "Employee"
msgstr "Employé"
#. module: hr_timesheet_project_print
#: model:ir.ui.view,arch_db:hr_timesheet_project_print.report_project_timesheet
msgid "Employee Category"
msgstr "Catégorie d'employé"
#. module: hr_timesheet_project_print
#: model:ir.ui.view,arch_db:hr_timesheet_project_print.report_project_timesheet
msgid "Initial"
msgstr "Initial"
#. module: hr_timesheet_project_print
#: model:ir.actions.report.xml,name:hr_timesheet_project_print.timesheet_project_report_sheet
msgid "Project Timesheet"
msgstr "Feuilles de temps projet"
#. module: hr_timesheet_project_print
#: model:ir.ui.view,arch_db:hr_timesheet_project_print.report_project_timesheet
msgid "Time"
msgstr "Heure"
#. module: hr_timesheet_project_print
#: model:ir.ui.view,arch_db:hr_timesheet_project_print.report_project_timesheet
msgid "to"
msgstr "au"
......@@ -35,7 +35,9 @@
'data': [
'security/ir.model.access.csv',
'security/hr_timesheet_project_sheet_security.xml',
"report/report_timesheet_templates.xml",
'data/hr_timesheet_project_sheet_data.xml',
'data/hr_timesheet_action_data.xml',
'views/hr_analytic_timesheet.xml',
'views/hr_timesheet_project_sheet_templates.xml',
'views/hr_timesheet_project_sheet_views.xml',
......
<?xml version="1.0" ?>
<odoo>
<!-- Mail template are declared in a NOUPDATE block
so users can freely customize/delete them -->
<data>
<!--Email template -->
<record id="email_template_timesheet" model="mail.template">
<field name="name">Timesheet - Send by Email</field>
<field name="email_from">${(object.user_id.email and '%s &lt;%s&gt;' % (object.user_id.name, object.user_id.email) or '')|safe}</field>
<field name="subject">${object.company_id.name} Timesheet (Ref ${object.number or 'n/a'})</field>
<field name="partner_to">${object.partner_id.id}</field>
<field name="model_id" ref="hr_timesheet_project_sheet.model_hr_timesheet_project_sheet_sheet"/>
<field name="auto_delete" eval="True"/>
<field name="report_template" ref="timesheet_project_report_sheet"/>
<field name="report_name">Timesheet_${(object.number or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''}</field>
<field name="lang">${object.partner_id.lang}</field>
<field name="body_html"><![CDATA[
<p>Dear ${object.partner_id.name}
% set access_action = object.get_access_action()
% set access_url = access_action['type'] == 'ir.actions.act_url' and access_action['url'] or '/report/pdf/account.report_invoice/' + str(object.id)
% set is_online = access_action and access_action['type'] == 'ir.actions.act_url'
% if object.partner_id.parent_id:
(<i>${object.partner_id.parent_id.name}</i>)
% endif
,</p>
<p>Here is your invoice <strong>${object.number}</strong>
% if object.origin:
(with reference: ${object.origin} )
% endif
amounting in <strong>${object.amount_total} ${object.currency_id.name}</strong>
from ${object.company_id.name}.
</p>
% if is_online:
<br/><br/>
<center>
<a href="${access_url}" style="background-color: #1abc9c; padding: 20px; text-decoration: none; color: #fff; border-radius: 5px; font-size: 16px;" class="o_default_snippet_text">View Invoice</a>
</center>
% endif
<br/><br/>
% if object.state=='paid':
<p>This invoice is already paid.</p>
% else:
<p>Please remit payment at your earliest convenience.</p>
% endif
<p>Thank you,</p>
% if object.user_id and object.user_id.signature:
${object.user_id.signature | safe}
% endif
]]></field>
</record>
</data>
</odoo>
......@@ -125,23 +125,24 @@ class AccountAnalyticLine(models.Model):
float_time_convert(hours))
)
# check if lines overlap
others = self.search([
('id', '!=', self.id),
('user_id', '=', self.user_id.id),
('date', '=', self.date),
('time_start', '<', self.time_stop),
('time_stop', '>', self.time_start),
])
if others:
message = _("Lines can't overlap:\n")
message += '\n'.join(['%s - %s' %
(float_time_convert(line.time_start),
float_time_convert(line.time_stop))
for line
in (self + others).sorted(
lambda l: l.time_start
)])
raise exceptions.ValidationError(message)
if self.user_id:
others = self.search([
('id', '!=', self.id),
('user_id', '=', self.user_id.id),
('date', '=', self.date),
('time_start', '<', self.time_stop),
('time_stop', '>', self.time_start),
])
if others:
message = _("Lines can't overlap:\n")
message += '\n'.join(['%s - %s' %
(float_time_convert(line.time_start),
float_time_convert(line.time_stop))
for line
in (self + others).sorted(
lambda l: l.time_start
)])
raise exceptions.ValidationError(message)
@api.onchange('time_start', 'time_stop', 'time_break')
def onchange_hours_start_stop(self):
......
......@@ -19,9 +19,7 @@
#
##############################################################################
import time
from datetime import datetime
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models
from odoo.tools.translate import _
......@@ -64,6 +62,7 @@ class HrTimesheetSheet(models.Model):
contact_id = fields.Many2one('res.partner', string='Remote Contact', store=True, readonly=False)
location = fields.Char(string="Location", help="Short description describing the location of the event.", store=True, readonly=False)
comment = fields.Text('Additional Information', readonly=True, states={'draft': [('readonly', False)], 'new': [('readonly', False)]})
sent = fields.Boolean(readonly=True, default=False, copy=False, help="It indicates that the timesheet has been sent.")
@api.constrains('date_to', 'date_from', 'project_id')
def _check_sheet_date(self, forced_project_id=False):
......@@ -109,6 +108,44 @@ class HrTimesheetSheet(models.Model):
raise UserError(_("Cannot approve a non-submitted timesheet."))
self.write({'state': 'done'})
@api.multi
def timesheet_print(self):
""" Print the timesheet and mark it as sent, so that we can see more
easily the next step of the workflow
"""
self.ensure_one()
self.sent = True
return self.env['report'].get_action(self, 'hr_timesheet_project_sheet.report_project_timesheet')
@api.multi
def action_timesheet_sent(self):
""" Open a window to compose an email, with the edit invoice template
message loaded by default.
"""
self.ensure_one()
template = self.env.ref('account.email_template_edi_invoice', False)
compose_form = self.env.ref('mail.email_compose_message_wizard_form', False)
ctx = dict(
default_model='account.invoice',
default_res_id=self.id,
default_use_template=bool(template),
default_template_id=template and template.id or False,
default_composition_mode='comment',
mark_invoice_as_sent=True,
custom_layout="account.mail_template_data_notification_email_account_invoice"
)
return {
'name': _('Compose Email'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'mail.compose.message',
'views': [(compose_form.id, 'form')],
'view_id': compose_form.id,
'target': 'new',
'context': ctx,
}
@api.multi
def name_get(self):
# week number according to ISO 8601 Calendar
......
......@@ -68,8 +68,10 @@
<tr>
<th>Date</th>
<th>Employee</th>
<th>Employee Category</th>
<th class="text-right">Begin - End</th>
<th>Position</th>
<th>Comments</th>
<th class="text-right">Begin</th>
<th class="text-right">End</th>
<th class="text-right">Break</th>
<th class="text-right">Total</th>
<th class="text-right" t-if="show_draft">Initial</th>
......@@ -80,7 +82,7 @@
<t t-foreach="o.timesheet_ids.sorted(key=lambda r: (r.date, r.time_start, r.time_stop))" t-as="l">
<!-- Group by timerange for display for Draft -->
<t t-if="show_draft and prev_timerange != (l.date, l.time_start, l.time_stop)">
<tr><td colspan="7"><strong>
<tr><td colspan="9"><strong>
<span t-field="l.date" t-field-options="{&quot;format&quot;: &quot;d MMM y&quot;}"/> -
<span t-esc="'%s:%02.0f' % tuple(int(round(x)) for x in divmod(l.time_start*60,60))"/>
to
......@@ -98,12 +100,14 @@
<td>
<span t-field="l.employee_category"/>
</td>
<td>
<span t-if="l.name != '/'" t-field="l.name"/>
</td>
<td class="text-right">
<t t-if="not show_draft">
<span t-esc="'%s:%02.0f' % tuple(int(round(x)) for x in divmod(l.time_start*60,60))"/>
to
<span t-esc="'%s:%02.0f' % tuple(int(round(x)) for x in divmod(l.time_stop*60,60))"/>
</t>
<span t-if="not show_draft" t-esc="'%s:%02.0f' % tuple(int(round(x)) for x in divmod(l.time_start*60,60))"/>
</td>
<td class="text-right">
<span t-if="not show_draft" t-esc="'%s:%02.0f' % tuple(int(round(x)) for x in divmod(l.time_stop*60,60))"/>
</td>
<td class="text-right">
<t t-if="not show_draft">
......@@ -123,11 +127,13 @@
<td/>
<td/>
<td/>
<td/>
<td/>
<td class="text-right"><strong>Total</strong></td>
<td class="text-right"><strong t-esc="'%s:%02.0f' % tuple(int(round(x)) for x in divmod(sum(o.timesheet_ids.mapped('unit_amount'))*60,60))"/></td>
</tr>
<tr t-if="show_draft">
<td colspan="7"/>
<td colspan="9"/>
</tr>
</tbody>
</table>
......@@ -170,7 +176,7 @@
model="hr_timesheet_project_sheet.sheet"
string="Project Timesheet"
report_type="qweb-pdf"
name="hr_timesheet_project_print.report_project_timesheet"
name="hr_timesheet_project_sheet.report_project_timesheet"
file="report_project_timesheet"
/>
......
......@@ -60,6 +60,10 @@
<button name="action_timesheet_done" states="confirm" string="Approve" type="object" groups="hr_timesheet.group_hr_timesheet_user" class="oe_highlight"/>
<button name="action_timesheet_draft" states="done" string="Set to Draft" type="object" groups="hr_timesheet.group_hr_timesheet_user"/>
<button name="action_timesheet_draft" states="confirm" string="Refuse" type="object" groups="hr_timesheet.group_hr_timesheet_user"/>
<button name="action_timesheet_sent" type="object" string="Send by Email" attrs="{'invisible':['|',('sent','=',True), ('state', '!=', 'draft')]}" class="oe_highlight" groups="base.group_user"/>
<button name="timesheet_print" string="Print" type="object" attrs="{'invisible':['|',('sent','=',True), ('state', '!=', 'draft')]}" class="oe_highlight" groups="base.group_user"/>
<button name="action_timesheet_sent" type="object" string="Send by Email" attrs="{'invisible':['|',('sent','=',False), ('state', '!=', 'draft')]}" groups="base.group_user"/>
<button name="timesheet_print" string="Print Invoice" type="object" attrs="{'invisible':['|',('sent','=',False), ('state', '!=', 'draft')]}" groups="base.group_user"/>
<field name="state" widget="statusbar" statusbar_visible="new,confirm,done"/>
</header>
<sheet>
......@@ -79,31 +83,33 @@
<group>
<field name="user_id" />
<field name="contact_id" />
<field name="sent" invisible="1"/>
</group>
</group>
<notebook>
<page string="Details" class="o_hr_timesheet_project_sheet_details">
<field context="{'default_project_id': project_id, 'default_name': '/', 'default_date': date_from, 'default_user_id': None}" name="timesheet_ids" nolabel="1">
<tree editable="bottom" string="Timesheet Activities">
<tree editable="bottom" default_order="date" string="Timesheet Activities">
<field name="date"/>
<field name="name" invisible="1"/>
<field name="time_start" widget="float_time"/>
<field name="time_stop" widget="float_time"/>
<field name="time_break" widget="float_time"/>
<field name="unit_amount" widget="float_time" string="Hours" sum="Hours"/>
<field name="employee_category"/>
<field name="employee_category" string="Position"/>
<field name="name"/>
<field name="user_id" domain="[('employee_ids.category_ids', 'in', employee_category)]" string="Employee"/>
<field name="project_id" invisible="1"/>
<button name="copy" type="object" icon="fa-files-o" />
</tree>
<form string="Timesheet Activities">
<group>
<field name="date"/>
<field name="name" invisible="1"/>
<field name="time_start" widget="float_time"/>
<field name="time_stop" widget="float_time"/>
<field name="time_break" widget="float_time"/>
<field name="unit_amount" widget="float_time" string="Hours"/>
<field name="employee_category"/>
<field name="employee_category" string="Position"/>
<field name="name"/>
<field name="user_id" domain="[('employee_ids.category_ids', 'in', employee_category)]" string="Employee"/>
<field name="project_id" invisible="1"/>
</group>
......