19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:32:12 +01:00
parent 79f83631d5
commit 73afc09215
6267 changed files with 1534193 additions and 1130106 deletions

View file

@ -3,9 +3,8 @@
<record id="action_timesheet_from_invoice" model="ir.actions.act_window">
<field name="name">Timesheets</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">account.analytic.line</field>
<field name="view_mode">tree,form,graph,pivot,kanban</field>
<field name="view_mode">list,form,graph,pivot,kanban</field>
<field name="context">{
'create': False,
'edit': False,
@ -25,7 +24,7 @@
<record id="action_timesheet_from_invoice_view_tree" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_mode">list</field>
<field name="view_id" ref="hr_timesheet.hr_timesheet_line_tree"/>
<field name="act_window_id" ref="action_timesheet_from_invoice"/>
</record>
@ -65,10 +64,10 @@
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="inside">
<field name="timesheet_count" invisible="1"/>
<button name="%(sale_timesheet.action_timesheet_from_invoice)d" type="action" class="oe_stat_button" icon="fa-clock-o" attrs="{'invisible': [('timesheet_count', '=', 0)]}" groups="hr_timesheet.group_hr_timesheet_user">
<button name="%(sale_timesheet.action_timesheet_from_invoice)d" type="action" class="oe_stat_button" icon="fa-clock-o" invisible="timesheet_count == 0" groups="hr_timesheet.group_hr_timesheet_user">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="timesheet_total_duration" class="mr4" widget="statinfo" nolabel="1"/>
<field name="timesheet_total_duration" class="mr4" nolabel="1"/>
<field name="timesheet_encode_uom_id" options="{'no_open': True}"/>
</span>
<span class="o_stat_text">Recorded</span>

View file

@ -7,8 +7,7 @@
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_search"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='task_id']" position="after">
<field name="order_id" string="Sales Order" groups="sales_team.group_sale_salesman"/>
<field name="so_line" groups="sales_team.group_sale_salesman"/>
<field name="order_id" string="Sales Order" filter_domain="['|', ('so_line', 'ilike', self), ('order_id', 'ilike', self)]"/>
</xpath>
<xpath expr="//filter[@name='month']" position="before">
<filter name="billable_fixed" string="Billed at a Fixed Price" domain="[('timesheet_invoice_type', '=', 'billable_fixed')]"
@ -24,8 +23,6 @@
<separator/>
</xpath>
<xpath expr="//filter[@name='groupby_employee']" position="after">
<filter string="Sales Order" name="groupby_sale_order" domain="[]" context="{'group_by': 'order_id'}"
groups="sales_team.group_sale_salesman"/>
<filter string="Sales Order Item" name="groupby_sale_order_item" domain="[]" context="{'group_by': 'so_line'}"
groups="sales_team.group_sale_salesman"/>
<filter string="Invoice" name="groupby_invoice" domain="[]" context="{'group_by': 'timesheet_invoice_id'}"
@ -37,14 +34,15 @@
</record>
<record id="hr_timesheet_line_tree_inherit" model="ir.ui.view">
<field name="name">account.analytic.line.tree.inherit</field>
<field name="name">account.analytic.line.list.inherit</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_tree"/>
<field name="arch" type="xml">
<xpath expr="//tree/field[@name='name']" position="after">
<field name="commercial_partner_id" invisible="1" groups="sales_team.group_sale_salesman"/>
<field name="is_so_line_edited" invisible="1" groups="sales_team.group_sale_salesman"/>
<field name="so_line" widget="so_line_field" optional="hide" options="{'no_create': True}" context="{'create': False, 'edit': False, 'delete': False}" groups="sales_team.group_sale_salesman"/>
<xpath expr="//list/field[@name='name']" position="after">
<field name="commercial_partner_id" column_invisible="True" groups="sales_team.group_sale_salesman"/>
<field name="is_so_line_edited" column_invisible="True" groups="sales_team.group_sale_salesman"/>
<field name="allow_billable" column_invisible="True" groups="sales_team.group_sale_salesman"/>
<field name="so_line" widget="so_line_field" optional="show" options="{'no_create': True, 'no_open': True}" context="{'create': False, 'edit': False, 'delete': False}" invisible="not allow_billable" readonly="readonly_timesheet" column_invisible="context.get('hide_so_line')" placeholder="Non-billable" groups="sales_team.group_sale_salesman"/>
</xpath>
</field>
</record>
@ -57,7 +55,35 @@
<xpath expr="//field[@name='task_id']" position="after">
<field name="commercial_partner_id" invisible="1" groups="sales_team.group_sale_salesman"/>
<field name="is_so_line_edited" invisible="1" groups="sales_team.group_sale_salesman"/>
<field name="so_line" widget="so_line_field" options='{"no_create": True}' context="{'create': False, 'edit': False, 'delete': False}" groups="sales_team.group_sale_salesman"/>
<field name="allow_billable" invisible="1" groups="sales_team.group_sale_salesman"/>
<field name="sale_order_state" invisible="1"/>
<label for="so_line" invisible="not allow_billable" groups="sales_team.group_sale_salesman"/>
<div class="o_row" invisible="not allow_billable" groups="sales_team.group_sale_salesman">
<field name="so_line" widget="so_line_field" options='{"no_create": True}' context="{'create': False, 'edit': False, 'delete': False, 'with_price_unit': True}" readonly="readonly_timesheet" placeholder="Non-billable"/>
<span
class="fa fa-exclamation-triangle text-warning"
title="The sales order associated with this timesheet entry has been cancelled."
invisible="sale_order_state != 'cancel'"
/>
</div>
</xpath>
<xpath expr="//group" position="before">
<t groups="sales_team.group_sale_salesman">
<field name="order_id" invisible="1"/>
<field name="timesheet_invoice_id" invisible="1"/>
<div class="oe_button_box" name="button_box">
<button name="action_sale_order_from_timesheet" type="object" class="oe_stat_button" icon="fa-dollar" invisible="not order_id">
<div class="o_stat_info">
<span class="o_stat_text">Sales Order</span>
</div>
</button>
<button name="action_invoice_from_timesheet" type="object" class="oe_stat_button" icon="fa-pencil-square-o" invisible="not timesheet_invoice_id">
<div class="o_stat_info">
<span class="o_stat_text">Invoice</span>
</div>
</button>
</div>
</t>
</xpath>
</field>
</record>
@ -69,7 +95,7 @@
<pivot string="Timesheets" sample="1">
<field name="date" interval="month" type="row"/>
<field name="timesheet_invoice_type" type="col"/>
<field name="unit_amount" type="measure" widget="timesheet_uom"/>
<field name="unit_amount" string="Time Spent" type="measure" widget="timesheet_uom"/>
<field name="amount" string="Timesheet Costs"/>
</pivot>
</field>
@ -83,11 +109,35 @@
<field name="date" interval="month" />
<field name="employee_id"/>
<field name="amount" type="measure" string="Timesheet Costs"/>
<field name="unit_amount" type="measure" widget="timesheet_uom"/>
<field name="unit_amount" string="Time Spent" type="measure" widget="timesheet_uom"/>
</graph>
</field>
</record>
<record id="view_hr_timesheet_line_graph_invoice_employee" model="ir.ui.view">
<field name="name">account.analytic.line.graph.invoice.employee</field>
<field name="model">account.analytic.line</field>
<field name="mode">primary</field>
<field name="inherit_id" ref="view_hr_timesheet_line_graph_employee_per_date"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='date']" position="replace">
<field name="timesheet_invoice_id"/>
</xpath>
</field>
</record>
<record id="view_hr_timesheet_line_pivot_inherited" model="ir.ui.view">
<field name="name">account.analytic.line.pivot</field>
<field name="model">account.analytic.line</field>
<field name="mode">primary</field>
<field name="inherit_id" ref="hr_timesheet.view_hr_timesheet_line_pivot"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='amount']" position="attributes">
<attribute name="type">measure</attribute>
</xpath>
</field>
</record>
<!--
Timesheet from Sales Order
-->
@ -103,7 +153,7 @@
<record id="timesheet_action_from_sales_order_tree" model="ir.actions.act_window.view">
<field name="sequence" eval="4"/>
<field name="view_mode">tree</field>
<field name="view_mode">list</field>
<field name="view_id" ref="hr_timesheet.timesheet_view_tree_user"/>
<field name="act_window_id" ref="timesheet_action_from_sales_order"/>
</record>
@ -130,17 +180,17 @@
}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No timesheets found. Let's create one!
No activities found. Let's start a new one!
</p>
<p>
Keep track of your working hours by project every day and bill your customers for that time.
Track your working hours by projects every day and invoice this time to your customers.
</p>
</field>
</record>
<record id="timesheet_action_from_sales_order_item_tree" model="ir.actions.act_window.view">
<field name="sequence" eval="10"/>
<field name="view_mode">tree</field>
<field name="view_mode">list</field>
<field name="view_id" ref="hr_timesheet.timesheet_view_tree_user"/>
<field name="act_window_id" ref="timesheet_action_from_sales_order_item"/>
</record>
@ -155,7 +205,7 @@
<record id="timesheet_action_from_sales_order_item_pivot" model="ir.actions.act_window.view">
<field name="sequence" eval="30"/>
<field name="view_mode">pivot</field>
<field name="view_id" ref="hr_timesheet.view_hr_timesheet_line_pivot"/>
<field name="view_id" ref="view_hr_timesheet_line_pivot_inherited"/>
<field name="act_window_id" ref="timesheet_action_from_sales_order_item"/>
</record>
@ -179,7 +229,7 @@
<record id="timesheet_action_plan_pivot" model="ir.actions.act_window">
<field name="name">Timesheet</field>
<field name="res_model">account.analytic.line</field>
<field name="view_mode">pivot,tree,form</field>
<field name="view_mode">pivot,list,form</field>
<field name="domain">[('project_id', '!=', False)]</field>
<field name="context">{
"is_timesheet": 1,
@ -190,7 +240,7 @@
<record id="timesheet_action_from_plan" model="ir.actions.act_window">
<field name="name">Timesheet</field>
<field name="res_model">account.analytic.line</field>
<field name="view_mode">tree,form</field>
<field name="view_mode">list,form</field>
<field name="domain">[('project_id', '!=', False)]</field>
<field name="context">{
"is_timesheet": 1,
@ -198,4 +248,27 @@
<field name="search_view_id" ref="hr_timesheet.hr_timesheet_line_search"/>
</record>
<record id="view_calendar_account_analytic_line" model="ir.ui.view">
<field name="name">account.analytic.line.calendar</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="hr_timesheet.view_calendar_account_analytic_line"/>
<field name="arch" type="xml">
<field name="name" position="before">
<field name="so_line" invisible="not so_line"/>
</field>
</field>
</record>
<record id="view_calendar_account_analytic_line_multi_create" model="ir.ui.view">
<field name="name">account.analytic.line.calendar.multi_create</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="hr_timesheet.view_calendar_account_analytic_line_multi_create"/>
<field name="arch" type="xml">
<field name="unit_amount" position="before">
<field name="allow_billable" invisible="1" groups="sales_team.group_sale_salesman"/>
<field name="so_line" groups="sales_team.group_sale_salesman" options='{"no_create": True}' context="{'create': False, 'edit': False, 'delete': False, 'with_price_unit': True}" invisible="not allow_billable" placeholder="Non-billable"/>
</field>
</field>
</record>
</odoo>

View file

@ -6,11 +6,25 @@
<field name="inherit_id" ref="sale.product_template_form_view"/>
<field name="arch" type="xml">
<field name="product_tooltip" position="after">
<label for="product_tooltip" string="" attrs="{'invisible': ['|', ('type', '!=', 'service'), ('service_policy', '!=', 'ordered_prepaid')]}"/>
<div attrs="{'invisible': ['|', ('type', '!=', 'service'), ('service_policy', '!=', 'ordered_prepaid')]}" class="fst-italic text-muted">
<label for="service_upsell_threshold" string=""
invisible="type != 'service'
or service_policy != 'ordered_prepaid'
or not sale_ok
or service_tracking not in ['no', 'task_global_project', 'task_in_project', 'project_only']"
/>
<div invisible="type != 'service'
or service_policy != 'ordered_prepaid'
or not sale_ok
or service_tracking not in ['no', 'task_global_project', 'task_in_project', 'project_only']"
class="fst-italic text-muted"
>
Warn the salesperson for an upsell when work done exceeds
<field name="service_upsell_threshold" widget="percentage" class="oe_inline"/>
of hours sold. <field name="service_upsell_threshold_ratio" class="oe_inline" attrs="{'invisible': [('service_upsell_threshold_ratio', '=', False)]}"/>
<field
name="service_upsell_threshold"
widget="percentage"
class="oe_inline o_field_highlight"
/>
of hours sold. <field name="service_upsell_threshold_ratio" class="oe_inline" invisible="not service_upsell_threshold_ratio"/>
</div>
</field>
</field>
@ -22,21 +36,21 @@
<field name="inherit_id" ref="product.product_template_search_view"/>
<field name="mode">primary</field>
<field name="arch" type="xml">
<xpath expr="//filter[@name='consumable']" position="after">
<filter name="combo" position="after">
<separator/>
<filter string="Time-based services" name="product_time_based" domain="[('type', '=', 'service'), ('invoice_policy', '=', 'delivery'), ('service_type', '=', 'timesheet')]"/>
<filter string="Fixed price services" name="product_service_fixed" domain="[('type', '=', 'service'), ('invoice_policy', '=', 'order'), ('service_type', '=', 'timesheet')]"/>
<filter string="Milestone services" name="product_service_milestone" domain="[('type', '=', 'service'), ('invoice_policy', '=', 'delivery'), ('service_type', '=', 'manual')]"/>
</xpath>
</filter>
</field>
</record>
<record id="product_template_action_default_services" model="ir.actions.act_window">
<field name="name">Services</field>
<field name="res_model">product.template</field>
<field name="view_mode">tree,form</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="sale_timesheet.product_template_view_search_sale_timesheet"/>
<field name="context">{'search_default_services': 1, 'default_detailed_type': 'service'}</field>
<field name="context">{'search_default_services': 1, 'default_type': 'service'}</field>
</record>
</odoo>

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="portal_timesheet_table_inherit" inherit_id="hr_timesheet.portal_timesheet_table">
<th name="t_label" position="before">
<t t-set="display_sol" t-value="False"/>
<t t-foreach="timesheets" t-as="timesheet">
<t t-if="timesheet.so_line != task.sale_line_id">
<t t-set="display_sol" t-value="True"/>
</t>
</t>
<th t-if="display_sol">Sales Order Item</th>
</th>
<xpath expr="//tr/td[t[@t-esc='timesheet.name']]" position="after">
<td t-if="display_sol">
<t t-if="timesheet.so_line.order_id.access_url and so_accessible"><a t-att-href="'%s' % timesheet.so_line.order_id.access_url"><t t-out="timesheet.so_line.display_name"/></a></t>
<t t-elif="timesheet.so_line.display_name" t-out="timesheet.so_line.display_name"></t>
<span t-else="" class="text-muted">Non-billable</span>
</td>
</xpath>
<xpath expr="//div[@name='allocated_time']" position="after">
<tr t-if="task.allow_billable and task.sale_line_id and task.sale_line_id.remaining_hours_available" t-attf-class="{{task.remaining_hours_so &lt; 0 and 'text-danger'}}">
<td><strong>Time Remaining on SO: </strong></td>
<td class="text-end">
<span t-if="is_uom_day" t-esc="timesheets._convert_hours_to_days(task.remaining_hours_so)" t-options='{"widget": "timesheet_uom"}'/>
<span t-else="" t-esc="task.remaining_hours_so" t-options='{"widget": "float_time"}'/>
</td>
</tr>
</xpath>
</template>
</odoo>

View file

@ -4,51 +4,38 @@
<record id="project_sharing_inherit_project_task_view_form" model="ir.ui.view">
<field name="name">project.task.form.inherit.timesheet</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.project_sharing_project_task_view_form"/>
<field name="priority">600</field>
<field name="inherit_id" ref="hr_timesheet.project_sharing_inherit_project_task_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='timesheet_ids']/tree" position="attributes">
<xpath expr="//field[@name='timesheet_ids']/list" position="attributes">
<attribute name="decoration-muted">timesheet_invoice_id != False</attribute>
</xpath>
<xpath expr="//field[@name='timesheet_ids']/tree/field[@name='unit_amount']" position="before">
<field name="timesheet_invoice_id" invisible="1"/>
<field name="so_line"
attrs="{'column_invisible': [('parent.allow_billable', '=', False)]}"
<xpath expr="//field[@name='timesheet_ids']/list/field[@name='unit_amount']" position="before">
<field name="timesheet_invoice_id" column_invisible="True"/>
<field name="so_line" widget="so_line_field"
column_invisible="not parent.allow_billable"
context="{'with_remaining_hours': True, 'with_price_unit': True}" options="{'no_create': True, 'no_open': True}"
optional="hide"/>
</xpath>
<xpath expr="//field[@name='child_ids']/tree/field[@name='remaining_hours']" position="after">
<field name="remaining_hours_so" optional="hide" widget="timesheet_uom"
attrs="{'column_invisible': ['|', ('parent.allow_billable', '=', False), ('parent.allow_timesheets', '=', False)]}" />
<xpath expr="//field[@name='child_ids']/list/field[@name='remaining_hours']" position="after">
<field name="remaining_hours_available" column_invisible="True"/>
<field name="remaining_hours_so" optional="hide" widget="timesheet_uom" column_invisible="not parent.allow_timesheets"/>
</xpath>
<xpath expr="//field[@name='depend_on_ids']/list/field[@name='remaining_hours']" position="after">
<field name="remaining_hours_available" column_invisible="True"/>
<field name="remaining_hours_so" optional="hide" widget="timesheet_uom" column_invisible="not parent.allow_timesheets"/>
</xpath>
<xpath expr="//field[@name='remaining_hours']" position="after">
<field name="allow_billable" invisible="1" />
<field name="remaining_hours_available" invisible="1"/>
<field name="sale_order_id" invisible="1"/>
<span id="remaining_hours_so_label" attrs="{'invisible': ['|', '|', ('allow_billable', '=', False), ('allow_timesheets', '=', False), ('remaining_hours_available', '=', False)]}" class="o_td_label float-start">
<label class="fw-bold" for="remaining_hours_so" string="Remaining Hours on SO"
attrs="{'invisible': ['|', ('encode_uom_in_days', '=', True), ('remaining_hours_so', '&lt;', 0)]}"/>
<label class="fw-bold" for="remaining_hours_so" string="Remaining Days on SO"
attrs="{'invisible': ['|', ('encode_uom_in_days', '=', False), ('remaining_hours_so', '&lt;', 0)]}"/>
<label class="fw-bold text-danger" for="remaining_hours_so" string="Remaining Hours on SO"
attrs="{'invisible': ['|', ('encode_uom_in_days', '=', True), ('remaining_hours_so', '&gt;=', 0)]}"/>
<label class="fw-bold text-danger" for="remaining_hours_so" string="Remaining Days on SO"
attrs="{'invisible': ['|', ('encode_uom_in_days', '=', False), ('remaining_hours_so', '&gt;=', 0)]}"/>
<span id="remaining_hours_so_label" invisible="not allow_billable or not sale_order_id or not partner_id or not sale_line_id or not remaining_hours_available" class="o_td_label float-start">
<label class="fw-bold" for="remaining_hours_so"
invisible="remaining_hours_so &lt; 0"/>
<label class="fw-bold text-danger" for="remaining_hours_so"
invisible="remaining_hours_so &gt;= 0"/>
</span>
<field name="remaining_hours_so" nolabel="1" widget="timesheet_uom" attrs="{'invisible': ['|', '|',('allow_billable', '=', False), ('allow_timesheets', '=', False), ('remaining_hours_available', '=', False)]}" decoration-danger="remaining_hours_so &lt; 0"></field>
</xpath>
</field>
</record>
<record id="project_sharing_inherit_project_task_view_tree_sale_timesheet" model="ir.ui.view">
<field name="name">project.task.tree.inherit.sale.timesheet</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="hr_timesheet.project_sharing_inherit_project_task_view_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='remaining_hours']" position="after">
<field name="allow_billable" invisible="1" />
<field name="allow_timesheets" invisible="1"/>
<field name="remaining_hours_so" optional="hide" widget="timesheet_uom"
attrs="{'column_invisible': ['|', ('allow_billable', '=', False), ('allow_timesheets', '=', False)]}" />
<field name="remaining_hours_so" nolabel="1" widget="timesheet_uom" invisible="not allow_billable or not sale_order_id or not partner_id or not sale_line_id or not remaining_hours_available" decoration-danger="remaining_hours_so &lt; 0"></field>
</xpath>
</field>
</record>

View file

@ -6,53 +6,107 @@
<field name="model">project.project</field>
<field name="inherit_id" ref="hr_timesheet.project_invoice_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='action_view_sos'][1]" position="attributes">
<attribute name="context">{'create_for_project_id': id, 'default_project_id': id, 'default_partner_id': partner_id}</attribute>
</xpath>
<xpath expr="//button[@name='action_view_sos'][2]" position="attributes">
<attribute name="context">{'create_for_project_id': id, 'default_project_id': id, 'default_partner_id': partner_id}</attribute>
</xpath>
<xpath expr="//page[@name='settings']" position="after">
<page name="billing_employee_rate" string="Invoicing" attrs="{'invisible': ['|', ('allow_billable', '=', False), ('partner_id', '=', False)]}">
<group>
<group>
<field name="display_create_order" invisible="1"/>
<field name="pricing_type" invisible="1" widget="radio"/>
<field name="timesheet_product_id" string="Default Service" invisible="1" context="{'default_detailed_type': 'service', 'default_service_policy': 'delivered_timesheet', 'default_service_type': 'timesheet'}"/>
<field name="sale_order_id" invisible="1" options="{'no_create': True, 'no_edit': True, 'delete': False, 'no_open': True}"/>
<field name="sale_line_id" groups="!sales_team.group_sale_salesman" options="{'no_create': True, 'no_edit': True, 'delete': False, 'no_open': True}"/>
<field name="sale_line_id" groups="sales_team.group_sale_salesman" options="{'no_create': True, 'no_edit': True, 'delete': False}"/>
</group>
</group>
<field name="sale_line_employee_ids">
<tree editable="bottom">
<field name="company_id" invisible="1"/>
<field name="partner_id" invisible="1"/>
<field name="employee_id" options="{'no_create': True}"/>
<field name="sale_line_id" attrs="{'required': True}" options="{'no_create': True}"/>
<page name="billing_employee_rate" string="Invoicing" invisible="not allow_billable or not partner_id or is_template">
<field name="sale_line_employee_ids" mode="list,kanban" context="{'default_sale_line_id': sale_line_id}">
<list editable="bottom">
<field name="company_id" column_invisible="True"/>
<field name="partner_id" column_invisible="True"/>
<field name="sale_order_id" column_invisible="True"/>
<field name="employee_id" widget="many2one_avatar_employee" context="{'create_project_employee_mapping': company_id}"/>
<field name="existing_employee_ids" column_invisible="True"/>
<field name="sale_line_id" column_invisible="parent.id" required="True" options="{'no_create': True}" context="{'search_default_order_id': sale_order_id}"/>
<field name="sale_line_id" column_invisible="not parent.id" required="True" groups="!sales_team.group_sale_salesman" options="{'no_create': True}"
context="{'search_default_order_id': sale_order_id}"/>
<field name="sale_line_id" column_invisible="not parent.id" required="True" groups="sales_team.group_sale_salesman"
options="{'no_create': True, 'no_open': True}"
context="{
'search_default_order_id': sale_order_id,
'so_form_view_ref': 'sale_project.view_order_simple_form',
'create_for_employee_mapping': True,
'default_partner_id': partner_id,
'default_company_id': company_id,
'default_project_id': project_id,
}"
widget="so_line_create_button"
/>
<field name="price_unit" widget="monetary" force_save="1" options="{'currency_field': 'currency_id'}"/>
<field name="display_cost" widget="monetary" options="{'currency_field': 'cost_currency_id'}"/>
<field name="is_cost_changed" invisible="1"/>
<field name="currency_id" invisible="1"/>
<field name="cost_currency_id" invisible="1"/>
</tree>
<field name="is_cost_changed" column_invisible="True"/>
<field name="currency_id" column_invisible="True"/>
<field name="cost_currency_id" column_invisible="True"/>
</list>
<kanban class="o_kanban_mobile">
<field name="currency_id"/>
<templates>
<t t-name="card">
<div class="row">
<div class="col-8 d-flex">
<field name="employee_id" widget="many2one_avatar_employee"/>
<field name="employee_id" class="fw-bold ps-1"/>
</div>
<div class="col-4 float-end text-end">
<b>Unit Price: </b>
<field name="price_unit" widget="monetary" force_save="1" options="{'currency_field': 'currency_id'}"/>
</div>
</div>
<div class="row">
<field name="sale_line_id" class="col-8 text-muted"/>
<div class="col-4 float-end text-end">
<b>Daily Cost: </b>
<field name="display_cost" widget="monetary" options="{'currency_field': 'currency_id'}"/>
</div>
</div>
</t>
</templates>
</kanban>
<form string="Timesheet Activities">
<sheet>
<group>
<field name="existing_employee_ids" invisible="1"/>
<field name="partner_id" invisible="1"/>
<field name="sale_order_id" invisible="1"/>
<field name="currency_id" invisible="1"/>
<field name="employee_id" widget="many2one_avatar_employee" required="1"/>
<field name="sale_line_id" invisible="parent.id" options="{'no_create': True}" context="{'search_default_order_id': sale_order_id}"/>
<field name="sale_line_id" invisible="not parent.id" required="True" groups="!sales_team.group_sale_salesman" options="{'no_create': True}"
context="{'search_default_order_id': sale_order_id}"/>
<field name="sale_line_id" invisible="not parent.id" required="True" groups="sales_team.group_sale_salesman" options="{'no_create': True}"
widget="so_line_create_button"
context="{
'search_default_order_id': sale_order_id,
'so_form_view_ref': 'sale_project.view_order_simple_form',
'create_for_employee_mapping': True,
'default_partner_id': partner_id,
'default_company_id': company_id,
'default_project_id': project_id,
}"
/>
<field name="price_unit" widget="monetary" readonly="True" force_save="1" options="{'currency_field': 'currency_id'}"/>
<field name="display_cost" widget="monetary" options="{'currency_field': 'currency_id'}"/>
</group>
</sheet>
</form>
</field>
<p class="text-muted">
<i class="fa fa-lightbulb-o"/>
<span>
Define the rate at which an employee's time is billed based on their expertise, skills, or experience.
To bill the same service at a different rate, create separate sales order items.
</span>
</p>
</page>
</xpath>
</field>
</record>
<record id="project_project_view_form_simplified_inherit" model="ir.ui.view">
<field name="name">project.project.view.form.simplified.inherit</field>
<field name="model">project.project</field>
<field name="inherit_id" ref="hr_timesheet.project_project_view_form_simplified_inherit_timesheet"/>
<field name="arch" type="xml">
<xpath expr="//div[hasclass('o_setting_box')]" position="before">
<div class="col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="company_id" invisible="1"/>
<field name="allow_billable"/>
</div>
<div class="o_setting_right_pane">
<label for="allow_billable"/>
<div class="text-muted" id="allow_billable_setting">
Invoice your time and material to customers
</div>
</div>
<xpath expr="//page[@name='settings']//field[@name='allow_billable']" position="after">
<div invisible="not allow_billable or not allow_timesheets" class="text-muted">
Timesheets without a sales order item are reported as
<field name="billing_type" nolabel="1" class="w-auto"/>
</div>
</xpath>
</field>
@ -70,11 +124,11 @@
<field name="pricing_type" invisible="1"/>
</xpath>
<xpath expr="//div[hasclass('o_kanban_manage_reporting')]" position="inside">
<div role="menuitem" t-if="record.rating_active.raw_value" groups="project.group_project_manager">
<div role="menuitem" t-if="record.show_ratings.raw_value" groups="project.group_project_manager">
<a name="action_view_all_rating" type="object">
Customer Ratings
</a>
</div>
</div>
</xpath>
</field>
</record>
@ -84,9 +138,9 @@
<field name="name">project.project.kanban.inherit.sale.timesheet.so.button</field>
<field name="model">project.project</field>
<field name="inherit_id" ref="project.view_project_kanban"/>
<field name="priority">30</field>
<field name="priority">32</field>
<field name="arch" type="xml">
<xpath expr="//div[hasclass('o_kanban_manage_view')]" position="inside">
<xpath expr="//div[@name='card_menu_view']" position="inside">
<div t-if="record.allow_billable.raw_value and record.sale_order_id.raw_value and record.pricing_type.raw_value != 'task_rate'"
role="menuitem"
groups="sales_team.group_sale_salesman_all_leads">
@ -96,31 +150,16 @@
</field>
</record>
<record id="view_sale_service_inherit_form2" model="ir.ui.view">
<field name="name">sale.service.form.view.inherit</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="hr_timesheet.view_task_form2_inherited"/>
<field name="arch" type="xml">
<xpath expr="//header" position="inside">
<field name="allow_billable" invisible="1"/>
</xpath>
<xpath expr="//field[@name='child_ids']/tree//field[@name='remaining_hours']" position="after">
<field name="remaining_hours_so" widget="timesheet_uom" optional="hide" groups="base.group_user"/>
</xpath>
<xpath expr="//field[@name='depend_on_ids']/tree//field[@name='remaining_hours']" position="after">
<field name="remaining_hours_so" widget="timesheet_uom" optional="hide" groups="base.group_user"/>
</xpath>
</field>
</record>
<record id="view_task_tree2_inherited" model="ir.ui.view">
<field name="name">project.task.tree.inherited</field>
<field name="name">project.task.list.inherited</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="hr_timesheet.view_task_tree2_inherited" />
<field name="arch" type="xml">
<xpath expr="//field[@name='remaining_hours']" position="after">
<field name="remaining_hours_so" widget="timesheet_uom" optional="hide"/>
<field name="sale_line_id" column_invisible="True"/>
<field name="remaining_hours_available" column_invisible="True"/>
<field name="remaining_hours_so" invisible="not sale_line_id or not remaining_hours_available" widget="timesheet_uom" optional="hide"
groups="hr_timesheet.group_hr_timesheet_user" column_invisible="not (context.get('allow_billable', True) and context.get('allow_timesheets', True)) or context.get('template_project')"/>
</xpath>
</field>
</record>
@ -130,71 +169,75 @@
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_form2"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='timesheet_ids']/tree" position="attributes">
<!-- <field name="timesheet_ids"/> is already inside a block groups="hr_timesheet.group_hr_timesheet_user" -->
<attribute name="decoration-muted">timesheet_invoice_id != False</attribute>
</xpath>
<xpath expr="//field[@name='user_ids']" position="after">
<field name="is_project_map_empty" invisible="1" groups="hr_timesheet.group_hr_timesheet_user"/>
<field name="has_multi_sol" invisible="1" groups="hr_timesheet.group_hr_timesheet_user"/>
</xpath>
<xpath expr="//field[@name='partner_phone']" position="after">
<xpath expr="//field[@name='partner_id']" position="after">
<field name="pricing_type" invisible="1" groups="hr_timesheet.group_hr_timesheet_user"/>
</xpath>
<xpath expr="//field[@name='timesheet_ids']" position="attributes">
<xpath expr="//field[@name='timesheet_ids']/list" position="inside">
<!-- <field name="timesheet_ids"/> is already inside a block groups="hr_timesheet.group_hr_timesheet_user" -->
<attribute name="widget">so_line_one2many</attribute>
<field name="is_so_line_edited" column_invisible="True"/>
</xpath>
<xpath expr="//field[@name='timesheet_ids']/tree" position="inside">
<xpath expr="//field[@name='timesheet_ids']/list/field[@name='unit_amount']" position="before">
<!-- <field name="timesheet_ids"/> is already inside a block groups="hr_timesheet.group_hr_timesheet_user" -->
<field name="is_so_line_edited" invisible="1" />
</xpath>
<xpath expr="//field[@name='timesheet_ids']/tree/field[@name='unit_amount']" position="before">
<!-- <field name="timesheet_ids"/> is already inside a block groups="hr_timesheet.group_hr_timesheet_user" -->
<field name="timesheet_invoice_id" invisible="1"/>
<field name="timesheet_invoice_id" column_invisible="True"/>
<field name="so_line" widget="so_line_field" groups="!sales_team.group_sale_salesman"
attrs="{'column_invisible': [('parent.allow_billable', '=', False)]}"
column_invisible="not parent.allow_billable"
readonly="readonly_timesheet"
context="{'with_remaining_hours': True, 'with_price_unit': True}" options="{'no_create': True, 'no_open': True}"
domain="[('is_service', '=', True), ('order_partner_id', 'child_of', parent.commercial_partner_id), ('is_expense', '=', False), ('state', 'in', ['sale', 'done'])]"
domain="[('is_service', '=', True), ('order_partner_id.commercial_partner_id.id', 'parent_of', parent.partner_id), ('is_expense', '=', False), ('state', '=', 'sale'), ('is_downpayment', '=', False)]"
optional="hide"/>
<field name="so_line" widget="so_line_field" groups="sales_team.group_sale_salesman"
attrs="{'column_invisible': [('parent.allow_billable', '=', False)]}"
context="{'with_remaining_hours': True, 'with_price_unit': True}" options="{'no_create': True}"
domain="[('is_service', '=', True), ('order_partner_id', 'child_of', parent.commercial_partner_id), ('is_expense', '=', False), ('state', 'in', ['sale', 'done'])]"
column_invisible="not parent.allow_billable"
readonly="readonly_timesheet"
context="{'with_remaining_hours': True, 'with_price_unit': True}" options="{'no_create': True, 'no_open': True}"
domain="[('is_service', '=', True), ('order_partner_id.commercial_partner_id.id', 'parent_of', parent.partner_id), ('is_expense', '=', False), ('state', '=', 'sale'), ('is_downpayment', '=', False)]"
optional="hide"/>
</xpath>
<xpath expr="//field[@name='timesheet_ids']/form//field[@name='unit_amount']" position="after">
<field name="timesheet_invoice_id" invisible="1"/>
<field name="so_line" widget="so_line_field" groups="!sales_team.group_sale_salesman"
invisible="not parent.allow_billable"
readonly="readonly_timesheet"
context="{'with_remaining_hours': True, 'with_price_unit': True}" options="{'no_create': True, 'no_open': True}"
domain="[('is_service', '=', True), ('order_partner_id', 'child_of', parent.partner_id), ('is_expense', '=', False), ('state', '=', 'sale')]"
placeholder="Non-billable"
optional="hide"/>
<field
name="so_line" widget="so_line_field" groups="sales_team.group_sale_salesman"
options="{'no_create': True, 'no_open': True}"
context="{'create': False, 'edit': False, 'delete': False, 'with_price_unit': True}"
invisible="not parent.allow_billable"
domain="[('is_service', '=', True), ('order_partner_id', 'child_of', parent.partner_id), ('is_expense', '=', False), ('state', '=', 'sale')]"
readonly="readonly_timesheet"
placeholder="Non-billable"/>
</xpath>
<xpath expr="//field[@name='remaining_hours']" position="after">
<t groups="hr_timesheet.group_hr_timesheet_user">
<field name="sale_order_id" invisible="1"/>
<field name="remaining_hours_available" invisible="1"/>
<span id="remaining_hours_so_label" attrs="{'invisible': ['|', '|', '|', '|', ('allow_billable', '=', False), ('sale_order_id', '=', False), ('partner_id', '=', False), ('sale_line_id', '=', False), ('remaining_hours_available', '=', False)]}" class="o_td_label float-start">
<label class="fw-bold" for="remaining_hours_so" string="Remaining Hours on SO"
attrs="{'invisible': ['|', ('encode_uom_in_days', '=', True), ('remaining_hours_so', '&lt;', 0)]}"/>
<label class="fw-bold" for="remaining_hours_so" string="Remaining Days on SO"
attrs="{'invisible': ['|', ('encode_uom_in_days', '=', False), ('remaining_hours_so', '&lt;', 0)]}"/>
<label class="fw-bold text-danger" for="remaining_hours_so" string="Remaining Hours on SO"
attrs="{'invisible': ['|', ('encode_uom_in_days', '=', True), ('remaining_hours_so', '&gt;=', 0)]}"/>
<label class="fw-bold text-danger" for="remaining_hours_so" string="Remaining Days on SO"
attrs="{'invisible': ['|', ('encode_uom_in_days', '=', False), ('remaining_hours_so', '&gt;=', 0)]}"/>
<span id="remaining_hours_so_label" invisible="not allow_billable or not sale_order_id or not partner_id or not sale_line_id or not remaining_hours_available" class="o_td_label float-start">
<label class="fw-bold" for="remaining_hours_so"
invisible="remaining_hours_so &lt; 0"/>
<label class="fw-bold text-danger" for="remaining_hours_so"
invisible="remaining_hours_so &gt;= 0"/>
</span>
<field name="remaining_hours_so" nolabel="1" widget="timesheet_uom" attrs="{'invisible': ['|', '|', '|', '|', ('allow_billable', '=', False), ('sale_order_id', '=', False), ('partner_id', '=', False), ('sale_line_id', '=', False), ('remaining_hours_available', '=', False)]}" decoration-danger="remaining_hours_so &lt; 0"></field>
<field name="remaining_hours_so" nolabel="1" widget="timesheet_uom" invisible="not allow_billable or not sale_order_id or not partner_id or not sale_line_id or not remaining_hours_available" decoration-danger="remaining_hours_so &lt; 0"></field>
</t>
</xpath>
</field>
</record>
<record id="view_task_form2_inherit_sale_timesheet" model="ir.ui.view">
<field name="name">view.task.form2.inherit</field>
<record id="project_task_view_search_inherit_sale_timesheet" model="ir.ui.view">
<field name="name">project.task.view.search.inherit</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="sale_project.view_sale_project_inherit_form"/>
<field name="inherit_id" ref="hr_timesheet.project_task_view_search"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='sale_line_id'][2]" position="attributes">
<attribute name="context">{'create': False, 'edit': False, 'delete': False, 'with_price_unit': True, 'with_remaining_hours': True}</attribute>
<!-- To do: move allow_billable field in sale_project and add attrs directly on field in master -->
<attribute name="attrs">
{'invisible': [('allow_billable', '=', False)]}
</attribute>
</xpath>
<filter name="timesheet_exceeded" position="attributes">
<attribute name="domain">['|', ('overtime', '&gt;', 0), ('remaining_hours_so', '&lt;', 0)]</attribute>
</filter>
</field>
</record>
</odoo>

View file

@ -1,53 +0,0 @@
<?xml version="1.0"?>
<odoo>
<template id="project_update_default_description" inherit_id="project.project_update_default_description">
<!--As this template is rendered in an html field, the spaces may be interpreted as nbsp while editing. -->
<xpath expr="//div[@name='milestone']" position="before">
<br/>
<div t-if="show_sold">
<h3 style="font-weight: bolder"><u>Sold</u></h3>
<table class="table table-bordered table-striped">
<tbody>
<thead>
<td style="font-weight: bolder">Service</td>
<td style="font-weight: bolder">Sold</td>
<td style="font-weight: bolder">Effective</td>
<td style="font-weight: bolder">Remaining</td>
</thead>
<tr t-foreach="services['data']" t-as="service">
<t t-set="is_unit" t-value="service['is_unit']"/>
<td t-attf-class="#{ 'fst-italic' if is_unit else ''}"><t t-esc="service['name']"/></td>
<td t-attf-class="#{ 'fst-italic' if is_unit else ''}" style="text-align: right; vertical-align: middle;"><t t-esc="format_value(service['sold_value'], service['is_hour'])"/> <t t-esc="service['unit']"/></td>
<td t-attf-class="#{ 'fst-italic' if is_unit else ''}" style="text-align: right; vertical-align: middle;"><t t-esc="format_value(service['effective_value'], service['is_hour'])"/> <t t-esc="service['unit']"/></td>
<td t-attf-class="#{ 'fst-italic' if is_unit else ''}" style="text-align: right; vertical-align: middle;"><t t-esc="format_value(service['remaining_value'], service['is_hour'])"/> <t t-esc="service['unit']"/></td>
</tr>
<tfoot>
<td style="font-weight: bolder; text-align: right">Total</td>
<td style="font-weight: bolder; text-align: right; vertical-align: middle;"><t t-esc="format_value(services['total_sold'], services['is_hour'])"/> <t t-esc="services['company_unit_name']"/></td>
<td style="font-weight: bolder; text-align: right; vertical-align: middle;"><t t-esc="format_value(services['total_effective'], services['is_hour'])"/> <t t-esc="services['company_unit_name']"/></td>
<td style="font-weight: bolder; text-align: right; vertical-align: middle;"><t t-esc="format_value(services['total_remaining'], services['is_hour'])"/> <t t-esc="services['company_unit_name']"/></td>
</tfoot>
</tbody>
</table>
<br/>
</div>
<div name="profitability" t-if="show_profitability">
<t t-if="project.analytic_account_id and project.allow_billable and user.has_group('project.group_project_manager')" name="costs">
<h3 style="font-weight: bolder"><u>Profitability</u></h3>
The cost of the project is now at <t t-esc="profitability['costs_formatted']"/>, for a revenue of <t t-esc="profitability['revenues_formatted']"/>, leading to a
<span>
<font t-if="profitability['margin'] &gt; 0" style="color: rgb(0, 128, 0)">
<b><t t-esc="profitability['margin_formatted']"/></b>
</font>
<font t-elif="profitability['margin'] &lt; 0" style="color: rgb(128, 0, 0)">
<b><t t-esc="profitability['margin_formatted']"/></b>
</font>
<t t-else="" t-esc="profitability['margin_formatted']"/>
</span> margin (<t t-esc="profitability['margin_percentage']"/>%).
</t>
</div>
</xpath>
</template>
</odoo>

View file

@ -8,35 +8,14 @@
<field name="inherit_id" ref="hr_timesheet.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='section_leaves']" position="before">
<h2>Billing</h2>
<div name="timesheet_billing" class="row mt16 o_settings_container">
<div class="col-12 col-lg-6 o_setting_box" id="time_billing_setting">
<div class="o_setting_right_pane">
<span class="o_form_label">Time Billing</span>
<div class="text-muted">
Sell services and invoice time spent
</div>
<div class="content-group" name="msg_module_sale_timesheet">
<div class="mt8">
<div>
<button name="%(sale_timesheet.product_template_action_default_services)d" string="Configure your services" type="action" class="btn-link" icon="fa-arrow-right"/>
</div>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6 o_setting_box" name="invoice_policy">
<div class="o_setting_left_pane">
<field name="invoice_policy" widget="upgrade_boolean"/>
</div>
<div class="o_setting_right_pane">
<label for="invoice_policy"/>
<div class="text-muted">
Timesheets taken into account when invoicing your time
</div>
</div>
</div>
</div>
<block title="Billing">
<setting string="Time Billing" help="Sell services and invoice time spent" id="time_billing_setting">
<button name="%(sale_timesheet.product_template_action_default_services)d" string="Configure your services" type="action" class="btn-link" icon="oi-arrow-right"/>
</setting>
<setting help="Timesheets taken into account when invoicing your time" name="invoice_policy">
<field name="invoice_policy" widget="upgrade_boolean"/>
</setting>
</block>
</xpath>
</field>
</record>

View file

@ -1,29 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_order_form_inherit_sale_timesheet" model="ir.ui.view">
<field name="name">sale.order.form.sale.timesheet</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale_project.view_order_form_inherit_sale_project"/>
<field name="arch" type="xml">
<data>
<xpath expr="//button[@name='action_view_task']" position="after">
<field name="timesheet_count" invisible="1"/>
<button type="object"
name="action_view_timesheet"
class="oe_stat_button"
icon="fa-clock-o"
attrs="{'invisible': [('timesheet_count', '=', 0)]}"
groups="hr_timesheet.group_hr_timesheet_user">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="timesheet_total_duration" class="mr4" widget="statinfo" nolabel="1"/>
<field name="timesheet_encode_uom_id" options="{'no_open': True}"/>
</span>
<span class="o_stat_text">Recorded</span>
</div>
</button>
</xpath>
</data>
</field>
</record>
<record id="view_order_form_inherit_sale_timesheet" model="ir.ui.view">
<field name="name">sale.order.form.sale.timesheet</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale_project.view_order_form_inherit_sale_project"/>
<field name="arch" type="xml">
<button name="action_view_milestone" position="before">
<button
name="action_view_timesheet"
type="object"
class="oe_stat_button"
icon="fa-clock-o"
invisible="not show_hours_recorded_button"
groups="hr_timesheet.group_hr_timesheet_user"
>
<div class="o_stat_info">
<span class="o_stat_value">
<field name="timesheet_total_duration" class="mr4"/>
<field name="timesheet_encode_uom_id" options="{'no_open': True}"/>
</span>
<span class="o_stat_text">Recorded</span>
</div>
</button>
</button>
</field>
</record>
</odoo>

View file

@ -2,20 +2,19 @@
<odoo>
<template id="sale_order_portal_content_inherit" inherit_id="sale.sale_order_portal_template">
<xpath expr="//li[.//a[@id='print_invoice_report']]" position="after">
<li t-if="sale_order.timesheet_count > 0 and sale_order.state in ('sale', 'done')" class="list-group-item flex-grow-1">
<div class="btn-toolbar flex-sm-nowrap justify-content-center">
<div class="btn-group">
<a t-att-href="'/my/timesheets?search_in=so&amp;search=%s' % sale_order.name">View Timesheets</a>
</div>
</div>
</li>
<xpath expr="//t[@t-set='entries']/div/div/div[hasclass('o_download_pdf')]" position="inside">
<t t-if="sale_order.timesheet_count > 0 and sale_order.state == 'sale' and sale_order.env['account.analytic.line']._show_portal_timesheets()">
<a class="btn btn-light flex-grow-1" t-att-href="'/my/timesheets?search_in=so&amp;search=%s' % sale_order.name" title="View Timesheets" target="_blank" role="button">View Timesheets</a>
</t>
</xpath>
</template>
<template id="portal_my_timesheets_inherit" inherit_id="hr_timesheet.portal_my_timesheets">
<xpath expr="//t[@t-foreach='grouped_timesheets']/tbody/tr[hasclass('table-light')]" position="inside">
<t t-elif="groupby == 'sol'">
<xpath expr="//t[@t-foreach='grouped_timesheets']/tbody/tr[hasclass('table-light')]/th[hasclass('text-end')]" position="attributes">
<attribute name="colspan">2</attribute>
</xpath>
<xpath expr="//t[@t-foreach='grouped_timesheets']/tbody/tr[hasclass('table-light')]/th[hasclass('text-end')]" position="before">
<t t-elif="groupby == 'so_line'">
<t t-set="sol" t-value="timesheets[0].so_line"/>
<th colspan="5">
<t t-if="sol">
@ -32,38 +31,22 @@
</t>
</t>
<t t-else="">
No Sales Order Item
</t>
</th>
<th colspan="1" class="text-end text-muted fw-normal">
<t t-if="is_uom_day">
Total: <span t-esc="timesheets._convert_hours_to_days(hours_spent)" t-options='{"widget": "timesheet_uom"}'/>
</t>
<t t-else="">
Total: <span t-esc="hours_spent" t-options='{"widget": "float_time"}'/>
Not Billed
</t>
</th>
</t>
<t t-elif="groupby == 'so'">
<t t-elif="groupby == 'order_id'">
<t t-set="so" t-value="timesheets[0].order_id"/>
<th colspan="6">
<t t-if="so">
<span t-field="so.display_name"/>
</t>
<t t-else="">
No Sales Order
</t>
</th>
<th colspan="1" class="text-end text-muted">
<t t-if="is_uom_day">
Total: <span t-esc="timesheets._convert_hours_to_days(hours_spent)" t-options='{"widget": "timesheet_uom"}'/>
</t>
<t t-else="">
Total: <span t-esc="hours_spent" t-options='{"widget": "float_time"}'/>
Not Billed
</t>
</th>
</t>
<t t-elif="groupby == 'invoice'">
<t t-elif="groupby == 'timesheet_invoice_id'">
<t t-set="invoice" t-value="timesheets.timesheet_invoice_id"/>
<th colspan="6">
<t t-if="invoice">
@ -73,42 +56,37 @@
No Invoice
</t>
</th>
<th colspan="1" class="text-end text-muted">
<t t-if="is_uom_day">
Total: <span t-esc="timesheets._convert_hours_to_days(hours_spent)" t-options='{"widget": "timesheet_uom"}'/>
</t>
<t t-else="">
Total: <span t-esc="hours_spent" t-options='{"widget": "float_time"}'/>
</t>
</th>
</t>
</xpath>
<xpath expr="//thead/tr/th[@t-if='is_uom_day']" position="before">
<th t-if="not groupby == 'sol'">Sales Order Item</th>
</xpath>
<th name="t_label" position="before">
<th t-if="not groupby == 'so_line'">Sales Order Item</th>
<th t-if="not groupby == 'timesheet_invoice_id'">Invoice</th>
</th>
<xpath expr="//tbody//td[hasclass('text-end')]" position="before">
<td t-if="not groupby == 'sol'"><span t-field="timesheet.so_line" t-att-title="timesheet.so_line.display_name"></span></td>
<td t-if="not groupby == 'so_line' and timesheets[0].env['account.analytic.line']._show_portal_timesheets()">
<span t-if="timesheet.so_line" t-field="timesheet.so_line" t-att-title="timesheet.so_line.display_name"></span>
<span t-else="" class="text-muted">Non-billable</span>
</td>
<td t-if="not groupby == 'timesheet_invoice_id'"><span t-field="timesheet.timesheet_invoice_id" t-att-title="timesheet.timesheet_invoice_id.display_name"></span></td>
</xpath>
</template>
<template id="portal_invoice_page_inherit" inherit_id="account.portal_invoice_page">
<xpath expr="//t[@t-set='entries']/ul/li[.//a[@id='print_invoice_report']]" position="after">
<li class="list-group-item flex-grow-1">
<div class="btn-toolbar flex-sm-nowrap justify-content-center">
<div class="btn-group mb-1">
<a t-if="invoice.move_type == 'out_invoice' and invoice.state in ('draft', 'posted') and invoice.timesheet_count > 0"
target="_blank" t-att-href="'/my/timesheets?search_in=invoice&amp;search=%s' % invoice.name">View Timesheets</a>
</div>
</div>
</li>
<xpath expr="//t[@t-set='entries']/div/div/div[hasclass('o_download_pdf')]" position="after">
<t t-if="invoice.timesheet_count > 0 and invoice.env['account.analytic.line']._show_portal_timesheets()">
<t t-set="search_value" t-value="invoice.name"/>
<t t-if="invoice.state == 'draft'" t-set="search_value" t-value="invoice.id"/>
<a t-if="invoice.move_type == 'out_invoice' and invoice.state in ('draft', 'posted') and invoice.timesheet_count > 0"
target="_blank" t-att-href="'/my/timesheets?search_in=invoice&amp;search=%s' % search_value" class="btn btn-light" role="button" title="View Timesheet">View Timesheets</a>
</t>
</xpath>
</template>
<template id="portal_my_task_inherit" inherit_id="project.portal_my_task">
<xpath expr="//div[@name='portal_my_task_second_column']" position="inside">
<t t-if="task.project_id.allow_billable">
<t t-if="task.project_id.allow_billable and env.user.has_group('sales_team.group_sale_salesman')">
<div t-if="task.sale_order_id"><strong>Sales Order:</strong>
<span t-if="so_accessible"><a t-attf-href="/my/orders/{{ task.sale_order_id.id }}" t-field="task.sale_order_id"></a></span>
<span t-if="so_accessible"><a t-attf-href="{{ task.sale_order_id.access_url }}" t-field="task.sale_order_id"></a></span>
<span t-else="" t-field="task.sale_order_id"></span>
</div>
<div t-if="invoices_accessible"><strong>Invoices:</strong>
@ -125,20 +103,11 @@
<div t-if="task.sale_line_id.untaxed_amount_invoiced > 0"><strong>Invoiced:</strong>
<span t-field="task.sale_line_id.untaxed_amount_invoiced"/>
</div>
<div t-if="task.sale_line_id.untaxed_amount_to_invoice > 0"><strong>To invoice:</strong>
<div name="amount_due" t-if="task.sale_line_id.untaxed_amount_to_invoice > 0"><strong>Amount Due:</strong>
<span t-field="task.sale_line_id.untaxed_amount_to_invoice"/>
</div>
</t>
</xpath>
</template>
<template id="portal_timesheet_table_inherit" inherit_id="hr_timesheet.portal_timesheet_table">
<xpath expr="//div[@name='planned_time']" position="after">
<span t-if="task.allow_billable and task.sale_line_id and task.sale_line_id.remaining_hours_available" t-attf-class="{{task.remaining_hours_so &lt; 0 and 'text-danger' or ''}}">
<div t-if="is_uom_day">Remaining Days on SO: <span t-esc="timesheets._convert_hours_to_days(task.remaining_hours_so)" t-options='{"widget": "timesheet_uom"}'/></div>
<div t-else="">Remaining Hours on SO: <span t-esc="task.remaining_hours_so" t-options='{"widget": "float_time"}'/></div>
</span>
</xpath>
</template>
</odoo>