summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaxLarue <mla@odoo.com>2019-04-17 12:36:01 +0000
committerMaxLarue <mla@odoo.com>2019-05-29 07:29:21 +0000
commite0b33e280e6f9739e5015f936674456dfd0f8bcd (patch)
tree68c028b0719fa475105eeac4c127f4e08519d43e
parent1f3a00fb3e5c7c9a90776a8a13b1f83e8b3e25bd (diff)
[IMP] sale_timesheet: overview usability and styling
In order to make the overview more understandable, we provide some modifications: - remove some value when they are zero ("No task found", ...) - the whole middle column (percentages) is merged inside the hours one - provide some tooltips - renaming titles - ... To do so and to avoid leaving empty lines or doing complicated template branching, the above table is split in two different tables, so that we can remove lines independently. We also added a new category in the overview, concerning timesheets that are related to a cancelled so. This meant tweaking the project_overview plan_prepare_value method, in order to have the new numbers calculated and keep the others consitent. Task-1968121 closes odoo/odoo#33015 Signed-off-by: Jérome Maes (jem) <jem@openerp.com>
-rw-r--r--addons/sale_timesheet/models/account.py6
-rw-r--r--addons/sale_timesheet/models/project_overview.py33
-rw-r--r--addons/sale_timesheet/static/src/scss/sale_timesheet.scss40
-rw-r--r--addons/sale_timesheet/views/hr_timesheet_templates.xml311
-rw-r--r--addons/sale_timesheet/views/hr_timesheet_views.xml6
5 files changed, 248 insertions, 148 deletions
diff --git a/addons/sale_timesheet/models/account.py b/addons/sale_timesheet/models/account.py
index 81895718a96..6838b6b37cd 100644
--- a/addons/sale_timesheet/models/account.py
+++ b/addons/sale_timesheet/models/account.py
@@ -15,9 +15,9 @@ class AccountAnalyticLine(models.Model):
return expression.OR([domain, [('qty_delivered_method', '=', 'timesheet')]])
timesheet_invoice_type = fields.Selection([
- ('billable_time', 'Billable Time'),
- ('billable_fixed', 'Billable Fixed'),
- ('non_billable', 'Non Billable'),
+ ('billable_time', 'Billed on Timesheets'),
+ ('billable_fixed', 'Billed at a Fixed price'),
+ ('non_billable', 'Non Billable Tasks'),
('non_billable_project', 'No task found')], string="Billable Type", compute='_compute_timesheet_invoice_type', compute_sudo=True, store=True, readonly=True)
timesheet_invoice_id = fields.Many2one('account.invoice', string="Invoice", readonly=True, copy=False, help="Invoice created from the timesheet")
diff --git a/addons/sale_timesheet/models/project_overview.py b/addons/sale_timesheet/models/project_overview.py
index 13f975b086d..b68bc998f95 100644
--- a/addons/sale_timesheet/models/project_overview.py
+++ b/addons/sale_timesheet/models/project_overview.py
@@ -53,10 +53,16 @@ class Project(models.Model):
}
}
+ # hours from non-invoiced timesheets that are linked to canceled so
+ canceled_hours_domain = [('project_id', 'in', self.ids), ('timesheet_invoice_type', '!=', False), ('so_line.state', '=', 'cancel')]
+ total_canceled_hours = sum(self.env['account.analytic.line'].search(canceled_hours_domain).mapped('unit_amount'))
+ dashboard_values['hours']['canceled'] = float_round(total_canceled_hours, precision_rounding=hour_rounding)
+ dashboard_values['hours']['total'] += float_round(total_canceled_hours, precision_rounding=hour_rounding)
+
# hours (from timesheet) and rates (by billable type)
- dashboard_domain = [('project_id', 'in', self.ids), ('timesheet_invoice_type', '!=', False)] # force billable type
+ dashboard_domain = [('project_id', 'in', self.ids), ('timesheet_invoice_type', '!=', False), '|', ('so_line', '=', False), ('so_line.state', '!=', 'cancel')] # force billable type
dashboard_data = self.env['account.analytic.line'].read_group(dashboard_domain, ['unit_amount', 'timesheet_invoice_type'], ['timesheet_invoice_type'])
- dashboard_total_hours = sum([data['unit_amount'] for data in dashboard_data])
+ dashboard_total_hours = sum([data['unit_amount'] for data in dashboard_data]) + total_canceled_hours
for data in dashboard_data:
billable_type = data['timesheet_invoice_type']
dashboard_values['hours'][billable_type] = float_round(data.get('unit_amount'), precision_rounding=hour_rounding)
@@ -66,6 +72,9 @@ class Project(models.Model):
dashboard_values['rates'][billable_type] = rate
dashboard_values['rates']['total'] += rate
+ # rates from non-invoiced timesheets that are linked to canceled so
+ dashboard_values['rates']['canceled'] = float_round(100 * total_canceled_hours / (dashboard_total_hours or 1), precision_rounding=hour_rounding)
+
# profitability, using profitability SQL report
profit = dict.fromkeys(['invoiced', 'to_invoice', 'cost', 'expense_cost', 'expense_amount_untaxed_invoiced', 'total'], 0.0)
profitability_raw_data = self.env['project.profitability.report'].read_group([('project_id', 'in', self.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_invoiced'], ['project_id'])
@@ -90,7 +99,11 @@ class Project(models.Model):
employee_ids = list(itertools.chain.from_iterable([employee_id['employee_ids'] for employee_id in employee_ids]))
employees = self.env['hr.employee'].sudo().browse(employee_ids) | self.env['account.analytic.line'].search([('project_id', 'in', self.ids)]).mapped('employee_id')
repartition_domain = [('project_id', 'in', self.ids), ('employee_id', '!=', False), ('timesheet_invoice_type', '!=', False)] # force billable type
- repartition_data = self.env['account.analytic.line'].read_group(repartition_domain, ['employee_id', 'timesheet_invoice_type', 'unit_amount'], ['employee_id', 'timesheet_invoice_type'], lazy=False)
+ # repartition data, without timesheet on cancelled so
+ repartition_data = self.env['account.analytic.line'].read_group(repartition_domain + ['|', ('so_line', '=', False), ('so_line.state', '!=', 'cancel')], ['employee_id', 'timesheet_invoice_type', 'unit_amount'], ['employee_id', 'timesheet_invoice_type'], lazy=False)
+ # read timesheet on cancelled so
+ cancelled_so_timesheet = self.env['account.analytic.line'].read_group(repartition_domain + [('so_line.state', '=', 'cancel')], ['employee_id', 'unit_amount'], ['employee_id'], lazy=False)
+ repartition_data += [{**canceled, 'timesheet_invoice_type': 'canceled'} for canceled in cancelled_so_timesheet]
# set repartition per type per employee
repartition_employee = {}
@@ -102,6 +115,7 @@ class Project(models.Model):
non_billable=0.0,
billable_time=0.0,
billable_fixed=0.0,
+ canceled=0.0,
total=0.0,
)
for data in repartition_data:
@@ -113,14 +127,13 @@ class Project(models.Model):
non_billable=0.0,
billable_time=0.0,
billable_fixed=0.0,
+ canceled=0.0,
total=0.0,
))[data['timesheet_invoice_type']] = float_round(data.get('unit_amount', 0.0), precision_rounding=hour_rounding)
repartition_employee[employee_id]['__domain_' + data['timesheet_invoice_type']] = data['__domain']
-
# compute total
for employee_id, vals in repartition_employee.items():
- repartition_employee[employee_id]['total'] = sum([vals[inv_type] for inv_type in billable_types])
-
+ repartition_employee[employee_id]['total'] = sum([vals[inv_type] for inv_type in [*billable_types, 'canceled']])
hours_per_employee = [repartition_employee[employee_id]['total'] for employee_id in repartition_employee]
values['repartition_employee_max'] = (max(hours_per_employee) if hours_per_employee else 1) or 1
values['repartition_employee'] = repartition_employee
@@ -158,8 +171,10 @@ class Project(models.Model):
if row[0]['sale_order_id']:
sale_order_ids.add(row[0]['sale_order_id'])
+ sale_orders = self.env['sale.order'].sudo().browse(sale_order_ids | empty_order_ids)
sale_order_lines = self.env['sale.order.line'].sudo().browse(sale_line_ids | empty_line_ids)
- map_so_names = {so.id: so.name for so in self.env['sale.order'].sudo().browse(sale_order_ids | empty_order_ids)}
+ map_so_names = {so.id: so.name for so in sale_orders}
+ map_so_cancel = {so.id: so.state == 'cancel' for so in sale_orders}
map_sol = {sol.id: sol for sol in sale_order_lines}
map_sol_names = {sol.id: sol.name.split('\n')[0] if sol.name else _('No Sales Order Line') for sol in sale_order_lines}
map_sol_so = {sol.id: sol.order_id.id for sol in sale_order_lines}
@@ -199,7 +214,7 @@ class Project(models.Model):
sale_order_id = row_key[0]
# sale order row
if sale_order_id not in rows_sale_order:
- rows_sale_order[sale_order_id] = [{'label': map_so_names.get(sale_order_id, _('No Sales Order')), 'res_id': sale_order_id, 'res_model': 'sale.order', 'type': 'sale_order'}] + default_row_vals[:] # INFO, before, M1, M2, M3, Done, M3, M4, M5, After, Forecasted
+ rows_sale_order[sale_order_id] = [{'label': map_so_names.get(sale_order_id, _('No Sales Order')), 'canceled': map_so_cancel.get(sale_order_id, False), 'res_id': sale_order_id, 'res_model': 'sale.order', 'type': 'sale_order'}] + default_row_vals[:] # INFO, before, M1, M2, M3, Done, M3, M4, M5, After, Forecasted
for index in range(len(rows_sale_line[row_key])):
if index != 0:
@@ -239,7 +254,7 @@ class Project(models.Model):
month_index = fields.Date.from_string(date).month
return babel.dates.get_month_names('abbreviated', locale=self.env.context.get('lang', 'en_US'))[month_index]
- header_names = [_('Name'), _('Before')] + [_to_short_month_name(date) for date in ts_months] + [_('Done'), _('Sold'), _('Remaining')]
+ header_names = [_('Name'), _('Before')] + [_to_short_month_name(date) for date in ts_months] + [_('Total'), _('Sold'), _('Remaining')]
result = []
for name in header_names:
diff --git a/addons/sale_timesheet/static/src/scss/sale_timesheet.scss b/addons/sale_timesheet/static/src/scss/sale_timesheet.scss
index 0b2280bf1dd..edc98499354 100644
--- a/addons/sale_timesheet/static/src/scss/sale_timesheet.scss
+++ b/addons/sale_timesheet/static/src/scss/sale_timesheet.scss
@@ -1,6 +1,13 @@
+$canceled-color: gray;
+
.o_timesheet_plan_sale_timesheet {
+ .o_title {
+ margin-top: 30px;
+ margin-bottom: 30px;
+ }
+
[type='action'] {
color: $o-brand-primary;
cursor: pointer;
@@ -12,6 +19,19 @@
width: auto;
}
+ .o_profitability_wrapper {
+ display: flex;
+ flex: 1;
+ flex-wrap: wrap;
+
+ .o_profitability_section {
+ display: flex;
+ flex: 1;
+ justify-content: flex-start;
+ }
+
+ }
+
.o_timesheet_plan_sale_timesheet_dashboard {
.table {
@@ -32,6 +52,10 @@
text-align: right;
}
+ .section {
+
+ }
+
}
.o_timesheet_plan_sale_timesheet_people_time {
@@ -52,7 +76,12 @@
cursor: pointer;
}
.o_progress_non_billable {
- background-color: gray;
+ background-color: purple;
+ color: white;
+ cursor: pointer;
+ }
+ .o_progress_canceled {
+ background-color: $canceled-color;
color: white;
cursor: pointer;
}
@@ -78,6 +107,9 @@
}
.o_project_plan_project_timesheet_forecast {
+
+ margin-top: 50px;
+
th{
text-align: center;
vertical-align: top;
@@ -111,6 +143,12 @@
.o_timesheet_plan_redirect {
cursor: pointer;
}
+
+ .o_canceled_tag {
+ background-color: $canceled-color;
+ color: white;
+ border: 0px;
+ }
}
}
diff --git a/addons/sale_timesheet/views/hr_timesheet_templates.xml b/addons/sale_timesheet/views/hr_timesheet_templates.xml
index 200d9af637d..17b0bc2799f 100644
--- a/addons/sale_timesheet/views/hr_timesheet_templates.xml
+++ b/addons/sale_timesheet/views/hr_timesheet_templates.xml
@@ -46,146 +46,175 @@
</t>
</div>
+ <div class="o_title">
+ <h2>Hours recorded and Profitability</h2>
+ </div>
+
<t t-set="display_cost" t-value="dashboard['profit']['expense_cost'] != 0.0"/>
- <div class="table-responsive">
- <table class="table">
- <tbody>
- <tr>
- <th rowspan="6">
- <a type="action" data-model="account.analytic.line" t-att-data-domain="json.dumps(timesheet_domain)" data-context='{"pivot_row_groupby": ["date:month"],"pivot_column_groupby": ["timesheet_invoice_type"], "pivot_measures": ["unit_amount"]}' data-views='[[0, "pivot"], [0, "list"]]' tabindex="-1">Hours</a>
- </th>
- <td class="o_timesheet_plan_dashboard_cell">
- <t t-esc="dashboard['hours']['billable_time']" t-options="{'widget': 'float_time'}"/>
- </td>
- <td>Timesheets</td>
- <th rowspan="6">
- <a type="action" data-model="account.analytic.line" t-att-data-domain="json.dumps(timesheet_domain)" data-context='{"pivot_row_groupby": ["date:month", "employee_id"], "pivot_column_groupby": ["timesheet_invoice_type"],"pivot_measures": ["unit_amount"]}' data-views='[[0, "pivot"], [0, "list"]]' tabindex="-1">Rates</a>
- </th>
- <td class="o_timesheet_plan_dashboard_cell">
- <t t-esc="dashboard['rates']['billable_time']"/> %
- </td>
- <td>Timesheets</td>
- <th rowspan="6">
- <a type="action" data-model="project.profitability.report" t-att-data-domain="json.dumps(profitability_domain)" data-context="{'group_by_no_leaf':1, 'group_by':[], 'sale_show_order_product_name': 1}" data-views='[[0, "pivot"], [0, "graph"]]' tabindex="-1">Profitability</a>
- </th>
- <td class="o_timesheet_plan_dashboard_cell">
- <t t-esc="dashboard['profit']['invoiced']" t-options='{"widget": "monetary", "display_currency": currency}'/>
- </td>
- <td>Time invoiced</td>
- </tr>
- <tr>
- <td class="o_timesheet_plan_dashboard_cell">
- <t t-esc="dashboard['hours']['billable_fixed']" t-options="{'widget': 'float_time'}"/>
- </td>
- <td>Fixed</td>
- <td class="o_timesheet_plan_dashboard_cell">
- <t t-esc="dashboard['rates']['billable_fixed']"/> %
- </td>
- <td>Fixed</td>
- <td class="o_timesheet_plan_dashboard_cell">
- <t t-esc="dashboard['profit']['to_invoice']" t-options='{"widget": "monetary", "display_currency": currency}'/>
- </td>
- <td>Time to invoice</td>
- </tr>
- <tr>
- <td class="o_timesheet_plan_dashboard_cell">
- <t t-esc="dashboard['hours']['non_billable_project']" t-options="{'widget': 'float_time'}"/>
- </td>
- <td>No task found</td>
- <td class="o_timesheet_plan_dashboard_cell">
- <t t-esc="dashboard['rates']['non_billable_project']"/> %
- </td>
- <td>No task found</td>
- <td class="o_timesheet_plan_dashboard_cell">
- <t t-esc="dashboard['profit']['cost']" t-options='{"widget": "monetary", "display_currency": currency}'/>
- </td>
- <td>Timesheet costs</td>
- </tr>
- <tr>
- <td class="o_timesheet_plan_dashboard_cell">
- <t t-esc="dashboard['hours']['non_billable']" t-options="{'widget': 'float_time'}"/>
- </td>
- <td><a type="action"
- title="Non Billable Tasks"
- data-model="project.task"
- data-views='[[false, "list"], [false, "form"]]'
- t-att-data-domain="json.dumps([['project_id', 'in', projects.ids], ['sale_line_id', '=', False]])"
- >Non billable</a></td>
- <td class="o_timesheet_plan_dashboard_cell">
- <t t-esc="dashboard['rates']['non_billable']"/> %
- </td>
- <td>Non billable</td>
- <t t-if="display_cost">
- <td class="o_timesheet_plan_dashboard_cell">
- <t t-esc="dashboard['profit']['expense_cost']" t-options='{"widget": "monetary", "display_currency": currency}'/>
- </td>
- <td>Other costs</td>
- </t>
- <t t-else="">
- <td class="o_timesheet_plan_dashboard_total">
- <t t-esc="dashboard['profit']['total']" t-options='{"widget": "monetary", "display_currency": currency}'/>
- </td>
- <td><b>Total</b></td>
- </t>
- </tr>
- <tr>
- <td class="o_timesheet_plan_dashboard_total">
- <t t-esc="dashboard['hours']['total']" t-options="{'widget': 'float_time'}"/>
- </td>
- <td><b>Total</b></td>
- <td></td>
- <td></td>
- <t t-if="display_cost">
- <td class="o_timesheet_plan_dashboard_cell">
- <t t-esc="dashboard['profit']['expense_amount_untaxed_invoiced']" t-options='{"widget": "monetary", "display_currency": currency}'/>
- </td>
- <td>Re-invoiced costs</td>
- </t>
- <t t-else="">
- <td></td>
- <td></td>
- </t>
- </tr>
- <tr>
- <td></td>
- <td></td>
- <td></td>
- <td></td>
- <t t-if="display_cost">
- <td class="o_timesheet_plan_dashboard_total">
- <t t-esc="dashboard['profit']['total']" t-options='{"widget": "monetary", "display_currency": currency}'/>
- </td>
- <td><b>Total</b></td>
- </t>
- <t t-else="">
- <td></td>
- <td></td>
- </t>
- </tr>
- </tbody>
- </table>
+ <div class="o_profitability_wrapper">
+ <div class="o_profitability_section">
+ <div>
+ <table class="table">
+ <tbody>
+ <th>
+ <a type="action" data-model="account.analytic.line" t-att-data-domain="json.dumps(timesheet_domain)" data-context='{"pivot_row_groupby": ["date:month"],"pivot_column_groupby": ["timesheet_invoice_type"], "pivot_measures": ["unit_amount"]}' data-views='[[0, "pivot"], [0, "list"]]' tabindex="-1">Hours</a>
+ </th>
+ <tr>
+ <td class="o_timesheet_plan_dashboard_cell">
+ <t t-esc="dashboard['hours']['billable_time']" t-options="{'widget': 'float_time'}"/>
+ </td>
+ <td class="o_timesheet_plan_dashboard_cell">
+ (<t t-esc="dashboard['rates']['billable_time']"/> %)
+ </td>
+ <td title=" Includes the time logged into tasks for which you invoice based on ordered quantities or on milestones.">
+ Billed on Timesheets
+ </td>
+ </tr>
+ <tr>
+ <td class="o_timesheet_plan_dashboard_cell">
+ <t t-esc="dashboard['hours']['billable_fixed']" t-options="{'widget': 'float_time'}"/>
+ </td>
+ <td class="o_timesheet_plan_dashboard_cell">
+ (<t t-esc="dashboard['rates']['billable_fixed']"/> %)
+ </td>
+ <td title="Includes the time logged into tasks for which you invoice based on timesheets on tasks.">
+ Billed at a Fixed price
+ </td>
+ </tr>
+ <tr t-if="dashboard['hours']['non_billable_project'] != 0">
+ <td class="o_timesheet_plan_dashboard_cell">
+ <t t-esc="dashboard['hours']['non_billable_project']" t-options="{'widget': 'float_time'}"/>
+ </td>
+ <td class="o_timesheet_plan_dashboard_cell">
+ (<t t-esc="dashboard['rates']['non_billable_project']"/> %)
+ </td>
+ <td title="Includes the time logged from the Timesheet module that is linked to a project, but not to a task.">
+ No task found
+ </td>
+ </tr>
+ <tr>
+ <td class="o_timesheet_plan_dashboard_cell">
+ <t t-esc="dashboard['hours']['non_billable']" t-options="{'widget': 'float_time'}"/>
+ </td>
+ <td class="o_timesheet_plan_dashboard_cell">
+ (<t t-esc="dashboard['rates']['non_billable']"/> %)
+ </td>
+ <td>
+ <a type="action"
+ data-model="project.task"
+ data-views='[[false, "list"], [false, "form"]]'
+ t-att-data-domain="json.dumps([['project_id', 'in', projects.ids], ['sale_line_id', '=', False]])"
+ >
+ <span class="btn-link"
+ style="font-weight:normal;"
+ title="Includes the time logged into a task which is not linked to any Sales Order.">
+ Non Billable Tasks
+ </span>
+ </a>
+ </td>
+ </tr>
+ <tr t-if="dashboard['hours']['canceled'] > 0">
+ <td class="o_timesheet_plan_dashboard_cell">
+ <t t-esc="dashboard['hours']['canceled']" t-options="{'widget': 'float_time'}"/>
+ </td>
+ <td class="o_timesheet_plan_dashboard_cell">
+ (<t t-esc="dashboard['rates']['canceled']"/> %)
+ </td>
+ <td title="Includes the time logged into a task which is linked to a cancelled Sales Order.">
+ Cancelled
+ </td>
+ </tr>
+ <tr>
+ <td class="o_timesheet_plan_dashboard_total"><b><t t-esc="dashboard['hours']['total']" t-options="{'widget': 'float_time'}"/></b></td>
+ <td><b>Total</b></td>
+ <td></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ <div class="o_profitability_section">
+ <div>
+ <table class="table">
+ <tbody>
+ <th>
+ <a type="action" data-model="project.profitability.report" t-att-data-domain="json.dumps(profitability_domain)" data-context="{'group_by_no_leaf':1, 'group_by':[], 'sale_show_order_product_name': 1}" data-views='[[0, "pivot"], [0, "graph"]]' tabindex="-1">Profitability</a>
+ </th>
+ <tr>
+ <td class="o_timesheet_plan_dashboard_cell">
+ <t t-esc="dashboard['profit']['invoiced']" t-options='{"widget": "monetary", "display_currency": currency}'/>
+ </td>
+ <td>Invoiced</td>
+ </tr>
+ <tr>
+ <td class="o_timesheet_plan_dashboard_cell">
+ <t t-esc="dashboard['profit']['to_invoice']" t-options='{"widget": "monetary", "display_currency": currency}'/>
+ </td>
+ <td>To invoice</td>
+ </tr>
+ <tr>
+ <td class="o_timesheet_plan_dashboard_cell">
+ <t t-esc="dashboard['profit']['cost']" t-options='{"widget": "monetary", "display_currency": currency}'/>
+ </td>
+ <td title="This cost is based on the &quot;Timesheet cost&quot; set in the HR Settings of your employees.">
+ Timesheet costs
+ </td>
+ </tr>
+ <tr t-if="display_cost">
+ <td class="o_timesheet_plan_dashboard_cell">
+ <t t-esc="dashboard['profit']['expense_cost']" t-options='{"widget": "monetary", "display_currency": currency}'/>
+ </td>
+ <td>Other costs</td>
+ </tr>
+ <tr t-if="display_cost &amp; (dashboard['profit']['expense_amount_untaxed_invoiced'] != 0)">
+ <td class="o_timesheet_plan_dashboard_cell">
+ <t t-esc="dashboard['profit']['expense_amount_untaxed_invoiced']" t-options='{"widget": "monetary", "display_currency": currency}'/>
+ </td>
+ <td>Re-invoiced costs</td>
+ </tr>
+ <tr>
+ <td class="o_timesheet_plan_dashboard_total">
+ <b>
+ <t t-esc="dashboard['profit']['total']" t-options='{"widget": "monetary", "display_currency": currency}'/>
+ </b>
+ </td>
+ <td><b>Total</b></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
</div>
</div>
+ <div class="o_title">
+ <h2>Time by people</h2>
+ </div>
+
<div class="o_timesheet_plan_sale_timesheet_people_time">
- <h3>Time by people</h3>
<t t-if="not repartition_employee">
<p>There is no timesheet for now.</p>
</t>
<t t-if="repartition_employee">
<div class="float-right o_timesheet_plan_badge">
- <span class="badge badge-pill o_progress_billable_fixed">
- <a type="action" data-model="account.analytic.line" t-att-data-domain="json.dumps(timesheet_domain + [('timesheet_invoice_type','=','billable_fixed')])" tabindex="-1">Billable fixed</a>
- </span>
<span class="badge badge-pill o_progress_billable_time">
- <a type="action" data-model="account.analytic.line" t-att-data-domain="json.dumps(timesheet_domain + [('timesheet_invoice_type','=','billable_time')])" tabindex="-1">Billable time</a>
+ <a type="action" data-model="account.analytic.line" t-att-data-domain="json.dumps(timesheet_domain + [('timesheet_invoice_type','=','billable_time')])" tabindex="-1">Billed on Timesheets</a>
</span>
- <span class="badge badge-pill o_progress_non_billable_project">
+ <span class="badge badge-pill o_progress_billable_fixed">
+ <a type="action" data-model="account.analytic.line" t-att-data-domain="json.dumps(timesheet_domain + [('timesheet_invoice_type','=','billable_fixed')])" tabindex="-1">Billed at a Fixed price</a>
+ </span>
+ <span t-if="dashboard['hours']['non_billable_project'] != 0" class="badge badge-pill o_progress_non_billable_project">
<a type="action" data-model="account.analytic.line" t-att-data-domain="json.dumps(timesheet_domain + [('timesheet_invoice_type','=','non_billable_project')])" tabindex="-1">No task found</a>
</span>
<span class="badge badge-pill o_progress_non_billable">
- <a type="action" data-model="account.analytic.line" t-att-data-domain="json.dumps(timesheet_domain + [('timesheet_invoice_type','=','non_billable')])" tabindex="-1">Non billable</a>
+ <a type="action" data-model="account.analytic.line" t-att-data-domain="json.dumps(timesheet_domain + [('timesheet_invoice_type','=','non_billable')])" tabindex="-1">Non billable tasks</a>
</span>
+ <!-- only show the canceled pill if there were timesheets on canceled so -->
+ <t t-if="sum([employee.get('canceled', 0.0) for employee in repartition_employee.values()]) > 0">
+ <span class="badge badge-pill o_progress_canceled">
+ <a type="action" data-model="account.analytic.line" t-att-data-domain="json.dumps(timesheet_domain + [('so_line.state', '=', 'cancel')])" tabindex="-1">Cancelled</a>
+ </span>
+ </t>
</div>
<div class="table-responsive">
<table class="table">
@@ -206,21 +235,25 @@
<t t-set="total" t-value="employee['total'] or 1.0" />
<t t-call="sale_timesheet.progressbar">
- <t t-set="label">Billable fixed</t>
- <t t-set="key">billable_fixed</t>
+ <t t-set="label">Billed on Timesheets</t>
+ <t t-set="key">billable_time</t>
</t>
<t t-call="sale_timesheet.progressbar">
- <t t-set="label">Billable time</t>
- <t t-set="key">billable_time</t>
+ <t t-set="label">Billed at a Fixed price</t>
+ <t t-set="key">billable_fixed</t>
</t>
<t t-call="sale_timesheet.progressbar">
<t t-set="label">No task found</t>
<t t-set="key">non_billable_project</t>
</t>
<t t-call="sale_timesheet.progressbar">
- <t t-set="label">Non billable</t>
+ <t t-set="label">Non billable tasks</t>
<t t-set="key">non_billable</t>
</t>
+ <t t-call="sale_timesheet.progressbar">
+ <t t-set="label">Cancelled</t>
+ <t t-set="key">canceled</t>
+ </t>
</div>
</td>
</tr>
@@ -231,14 +264,22 @@
</t>
</div>
+ <div class="o_title">
+ <h2>Timesheets</h2>
+ </div>
+
<!-- NOTE: this template to display a table works whatever the length of the rows, as project_timesheet_forecast_sale extends the table to add forecasts -->
<div class="o_project_plan_project_timesheet_forecast">
<t t-if="timesheet_forecast_table and timesheet_forecast_table['rows']">
- <h3 name="table_plan_title">Timesheets</h3>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
+ <th></th>
+ <th colspan="5" id="table_plan_title" class="o_right_bordered"><h3>Timesheets</h3></th>
+ <th colspan="2" id="table_plan_total"></th>
+ </tr>
+ <tr>
<t t-foreach="timesheet_forecast_table['header']" t-as="header_val">
<th t-att-class="'o_right_bordered' if header_val_index in [5,10] else ''">
<span t-att-title="header_val['tooltip']"><t t-esc="header_val['label']"/></span>
@@ -261,6 +302,9 @@
<a type="action" t-att-data-model="row_value['res_model']" t-att-data-res-id="row_value['res_id']" t-att-class="'o_timesheet_plan_redirect' if row_value['res_id'] else ''">
<t t-esc="row_value.get('label')"/>
</a>
+ <span t-if="row_value.get('canceled')" class="badge badge-pill o_canceled_tag">
+ Cancelled
+ </span>
</t>
<t t-if="row_type != 'sale_order'">
<t t-if="not row_is_milestone">
@@ -309,10 +353,13 @@
<t t-set="title"><t t-esc="label"/>: <t t-esc="amount" t-options="{'widget': 'float_time'}"/></t>
<a t-attf-class="progress-bar o_progress_{{key}}"
t-attf-style="width: {{amount / total * 100}}%"
- t-att-title="title"
type="action" data-model="account.analytic.line"
t-att-data-domain="json.dumps(employee['__domain_' + key])"
- />
+ >
+ <span t-att-title="title" style="font-size: 0px; width: 100%; height: 100%;">
+ <t t-esc="label" />
+ </span>
+ </a>
</t>
</template>
diff --git a/addons/sale_timesheet/views/hr_timesheet_views.xml b/addons/sale_timesheet/views/hr_timesheet_views.xml
index a561a6e35fa..4a1f0d45619 100644
--- a/addons/sale_timesheet/views/hr_timesheet_views.xml
+++ b/addons/sale_timesheet/views/hr_timesheet_views.xml
@@ -9,9 +9,9 @@
<xpath expr="//filter[@name='month']" position="before">
<field name="timesheet_invoice_type"/>
<field name="timesheet_invoice_id"/>
- <filter name="billable_time" string="Billable Hours" domain="[('timesheet_invoice_type', '=', 'billable_time')]"/>
- <filter name="billable_fixed" string="Fixed Price Projects" domain="[('timesheet_invoice_type', '=', 'billable_fixed')]"/>
- <filter name="non_billable" string="Non Billable Hours" domain="[('timesheet_invoice_type', '=', 'non_billable')]"/>
+ <filter name="billable_time" string="Billed on Timesheets" domain="[('timesheet_invoice_type', '=', 'billable_time')]"/>
+ <filter name="billable_fixed" string="Billed at a Fixed Price" domain="[('timesheet_invoice_type', '=', 'billable_fixed')]"/>
+ <filter name="non_billable" string="Non Billable Tasks" domain="[('timesheet_invoice_type', '=', 'non_billable')]"/>
<separator/>
</xpath>
</field>