diff options
| author | MaxLarue <mla@odoo.com> | 2019-04-17 12:36:01 +0000 |
|---|---|---|
| committer | MaxLarue <mla@odoo.com> | 2019-05-29 07:29:21 +0000 |
| commit | e0b33e280e6f9739e5015f936674456dfd0f8bcd (patch) | |
| tree | 68c028b0719fa475105eeac4c127f4e08519d43e | |
| parent | 1f3a00fb3e5c7c9a90776a8a13b1f83e8b3e25bd (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.py | 6 | ||||
| -rw-r--r-- | addons/sale_timesheet/models/project_overview.py | 33 | ||||
| -rw-r--r-- | addons/sale_timesheet/static/src/scss/sale_timesheet.scss | 40 | ||||
| -rw-r--r-- | addons/sale_timesheet/views/hr_timesheet_templates.xml | 311 | ||||
| -rw-r--r-- | addons/sale_timesheet/views/hr_timesheet_views.xml | 6 |
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 "Timesheet cost" 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 & (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> |
