summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier BOL (xbo) <xbo@odoo.com>2020-12-10 18:34:47 +0100
committerXavier BOL (xbo) <xbo@odoo.com>2020-12-21 15:35:20 +0100
commita41df84a83380dded4cb8c9d83444f916b9e004d (patch)
tree72eb76af9b079497898b95eca1983d783f0bbe10
parentdaa1036eb6b54588224bc5d6fe9be6974cde1ea8 (diff)
[IMP] {hr,sale}_timesheet: review portal timesheets
This commit improves the portal view containing the timesheets list (this view is available in the '/my/timesheets' route. In sale_timesheet module, we * add new group by and a search on SOL, * add an SOL field on the left of the unit_amount one, * change the group by by default, to group the timesheets by SOL * display the following information in the group: 'x Hours ordered, x Remaining', when the timesheets are grouped by SOL. However, the 'x Remaining' part should only be visible if the number of timesheets is greater than 0. * move the total number of hours in the group itself and align it to the right. * change the number of items displayed per page, 100 items instead of 20. * reduce the size of the font in tbody tag (0.8rem instead of 0.875rem) * display the content of the field on hover, it's usefull when the field it's cropped. * Add 'View Timesheets' button in portal SO: this new button button called 'View timesheets' is displayed next the name of the product of a SO in Sale Order Portal. it is only visible if the number of timehseets is greater than 0 for this SOL. The button redirects the user to /my/timesheets page with a filter on the corresponding SOL + a group by SOL. task-2409761
-rw-r--r--addons/hr_timesheet/controllers/portal.py79
-rw-r--r--addons/hr_timesheet/views/hr_timesheet_portal_templates.xml100
-rw-r--r--addons/sale_timesheet/controllers/portal.py32
-rw-r--r--addons/sale_timesheet/views/sale_timesheet_portal_templates.xml37
4 files changed, 190 insertions, 58 deletions
diff --git a/addons/hr_timesheet/controllers/portal.py b/addons/hr_timesheet/controllers/portal.py
index 8127b83be6f..b244ee3e656 100644
--- a/addons/hr_timesheet/controllers/portal.py
+++ b/addons/hr_timesheet/controllers/portal.py
@@ -23,32 +23,59 @@ class TimesheetCustomerPortal(CustomerPortal):
values['timesheet_count'] = request.env['account.analytic.line'].sudo().search_count(domain)
return values
+ def _get_searchbar_inputs(self):
+ return {
+ 'all': {'input': 'all', 'label': _('Search in All')},
+ 'project': {'input': 'project', 'label': _('Search in Project')},
+ 'name': {'input': 'name', 'label': _('Search in Description')},
+ 'employee': {'input': 'employee', 'label': _('Search in Employee')},
+ 'task': {'input': 'task', 'label': _('Search in Task')}
+ }
+
+ def _get_searchbar_groupby(self):
+ return {
+ 'none': {'input': 'none', 'label': _('None')},
+ 'project': {'input': 'project', 'label': _('Project')},
+ 'task': {'input': 'task', 'label': _('Task')},
+ 'date': {'input': 'date', 'label': _('Date')},
+ 'employee': {'input': 'employee', 'label': _('Employee')}
+ }
+
+ def _get_search_domain(self, search_in, search):
+ search_domain = []
+ if search_in in ('project', 'all'):
+ search_domain = OR([search_domain, [('project_id', 'ilike', search)]])
+ if search_in in ('name', 'all'):
+ search_domain = OR([search_domain, [('name', 'ilike', search)]])
+ if search_in in ('employee', 'all'):
+ search_domain = OR([search_domain, [('employee_id', 'ilike', search)]])
+ if search_in in ('task', 'all'):
+ search_domain = OR([search_domain, [('task_id', 'ilike', search)]])
+ return search_domain
+
+ def _get_groupby_mapping(self):
+ return {
+ 'project': 'project_id',
+ 'task': 'task_id',
+ 'employee': 'employee_id',
+ 'date': 'date'
+ }
+
@http.route(['/my/timesheets', '/my/timesheets/page/<int:page>'], type='http', auth="user", website=True)
def portal_my_timesheets(self, page=1, sortby=None, filterby=None, search=None, search_in='all', groupby='none', **kw):
Timesheet_sudo = request.env['account.analytic.line'].sudo()
values = self._prepare_portal_layout_values()
domain = request.env['account.analytic.line']._timesheet_get_portal_domain()
+ _items_per_page = 100
searchbar_sortings = {
'date': {'label': _('Newest'), 'order': 'date desc'},
'name': {'label': _('Description'), 'order': 'name'},
}
- searchbar_inputs = {
- 'all': {'input': 'all', 'label': _('Search in All')},
- 'project': {'input': 'project', 'label': _('Search in Project')},
- 'name': {'input': 'name', 'label': _('Search in Description')},
- 'employee': {'input': 'employee', 'label': _('Search in Employee')},
- 'task': {'input': 'task', 'label': _('Search in Task')},
- }
+ searchbar_inputs = self._get_searchbar_inputs()
- searchbar_groupby = {
- 'none': {'input': 'none', 'label': _('None')},
- 'project': {'input': 'project', 'label': _('Project')},
- 'task': {'input': 'task', 'label': _('Task')},
- 'date': {'input': 'date', 'label': _('Date')},
- 'employee': {'input': 'employee', 'label': _('Employee')},
- }
+ searchbar_groupby = self._get_searchbar_groupby()
today = fields.Date.today()
quarter_start, quarter_end = date_utils.get_quarter(today)
@@ -77,16 +104,7 @@ class TimesheetCustomerPortal(CustomerPortal):
domain = AND([domain, searchbar_filters[filterby]['domain']])
if search and search_in:
- search_domain = []
- if search_in in ('project', 'all'):
- search_domain = OR([search_domain, [('project_id', 'ilike', search)]])
- if search_in in ('name', 'all'):
- search_domain = OR([search_domain, [('name', 'ilike', search)]])
- if search_in in ('employee', 'all'):
- search_domain = OR([search_domain, [('employee_id', 'ilike', search)]])
- if search_in in ('task', 'all'):
- search_domain = OR([search_domain, [('task_id', 'ilike', search)]])
- domain += search_domain
+ domain += self._get_search_domain(search_in, search)
timesheet_count = Timesheet_sudo.search_count(domain)
# pager
@@ -95,19 +113,14 @@ class TimesheetCustomerPortal(CustomerPortal):
url_args={'sortby': sortby, 'search_in': search_in, 'search': search, 'filterby': filterby, 'groupby': groupby},
total=timesheet_count,
page=page,
- step=self._items_per_page
+ step=_items_per_page
)
def get_timesheets():
- groupby_mapping = {
- 'project': 'project_id',
- 'task': 'task_id',
- 'employee': 'employee_id',
- 'date': 'date',
- }
+ groupby_mapping = self._get_groupby_mapping()
field = groupby_mapping.get(groupby, None)
orderby = '%s, %s' % (field, order) if field else order
- timesheets = Timesheet_sudo.search(domain, order=orderby, limit=self._items_per_page, offset=pager['offset'])
+ timesheets = Timesheet_sudo.search(domain, order=orderby, limit=_items_per_page, offset=pager['offset'])
if field:
if groupby == 'date':
time_data = Timesheet_sudo.read_group(domain, ['date', 'unit_amount:sum'], ['date:day'])
@@ -115,7 +128,7 @@ class TimesheetCustomerPortal(CustomerPortal):
grouped_timesheets = [(Timesheet_sudo.concat(*g), mapped_time[k]) for k, g in groupbyelem(timesheets, itemgetter('date'))]
else:
time_data = time_data = Timesheet_sudo.read_group(domain, [field, 'unit_amount:sum'], [field])
- mapped_time = dict([(m[field][0], m['unit_amount']) for m in time_data])
+ mapped_time = dict([(m[field][0] if m[field] else False, m['unit_amount']) for m in time_data])
grouped_timesheets = [(Timesheet_sudo.concat(*g), mapped_time[k.id]) for k, g in groupbyelem(timesheets, itemgetter(field))]
return timesheets, grouped_timesheets
diff --git a/addons/hr_timesheet/views/hr_timesheet_portal_templates.xml b/addons/hr_timesheet/views/hr_timesheet_portal_templates.xml
index 90f2d679928..4385bbddd6a 100644
--- a/addons/hr_timesheet/views/hr_timesheet_portal_templates.xml
+++ b/addons/hr_timesheet/views/hr_timesheet_portal_templates.xml
@@ -36,24 +36,74 @@
<t t-foreach="grouped_timesheets" t-as="timesheets_with_hours">
<t t-set="timesheets" t-value="timesheets_with_hours[0]"/>
<t t-set="hours_spent" t-value="timesheets_with_hours[1]"/>
- <thead>
+ <thead style="font-size: 0.8rem">
<tr t-if="not groupby =='none'" t-attf-class="{{'thead-light'}}">
- <th t-if="groupby == 'project'" colspan="5">
- <em class="font-weight-normal text-muted">Timesheets for project:</em>
- <span t-field="timesheets[0].project_id.name"/>
- </th>
- <th t-elif="groupby == 'task'" colspan="5">
- <em class="font-weight-normal text-muted">Timesheets for task:</em>
- <span t-field="timesheets[0].task_id.name"/>
- </th>
- <th t-elif="groupby == 'date'" colspan="5">
- <em class="font-weight-normal text-muted">Timesheets on </em>
- <span t-field="timesheets[0].date"/>
- </th>
- <th t-elif="groupby == 'employee'" colspan="5">
- <em class="font-weight-normal text-muted">Timesheets for employee:</em>
- <span t-field="timesheets[0].employee_id.name"/>
- </th>
+ <t t-if="groupby == 'project'">
+ <th t-if="groupby == 'project'" colspan="5">
+ <em class="font-weight-normal text-muted">Timesheets for project:</em>
+ <span t-field="timesheets[0].project_id.name"/>
+ </th>
+ <th colspan="1" class="text-right text-muted">
+ <t t-if="is_uom_day">
+ Total: <span class="text-muted" t-esc="timesheets[0]._convert_hours_to_days(hours_spent)" t-options='{"widget": "timesheet_uom"}'/>
+ </t>
+ <t t-else="">
+ Total: <span class="text-muted" t-esc="hours_spent" t-options='{"widget": "float_time"}'/>
+ </t>
+ </th>
+ </t>
+ <t t-elif="groupby == 'task'">
+ <th colspan="5">
+ <em class="font-weight-normal text-muted">Timesheets for task:</em>
+ <span t-field="timesheets[0].task_id.name"/>
+ </th>
+ <th colspan="1" class="text-right text-muted">
+ <t t-if="is_uom_day">
+ Total: <span t-esc="timesheets[0]._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>
+ <t t-elif="groupby == 'date'">
+ <th colspan="5">
+ <em class="font-weight-normal text-muted">Timesheets on </em>
+ <span t-field="timesheets[0].date"/>
+ </th>
+ <th colspan="1" class="text-right text-muted">
+ <t t-if="is_uom_day">
+ Total: <span t-esc="timesheets[0]._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>
+ <t t-elif="groupby == 'employee'">
+ <th colspan="5">
+ <em class="font-weight-normal text-muted">Timesheets for employee:</em>
+ <span t-field="timesheets[0].employee_id.name"/>
+ </th>
+ <th colspan="1" class="text-right text-muted">
+ <t t-if="is_uom_day">
+ Total: <span t-esc="timesheets[0]._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>
+ </tr>
+ <tr t-else="">
+ <div style="text-align: right;" class="mr-2 mb-1 text-muted">
+ <t t-if="is_uom_day">
+ Total: <span t-esc="timesheets[0]._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>
+ </div>
</tr>
<tr>
<th t-if="not groupby == 'date'">Date</th>
@@ -61,21 +111,21 @@
<th t-if="not groupby == 'project'">Project</th>
<th t-if="not groupby == 'task'">Task</th>
<th>Description</th>
- <th t-if="is_uom_day" class="text-right">Days Spent <small class="text-muted">(Total: <span t-esc="timesheets[0]._convert_hours_to_days(hours_spent)" t-options='{"widget": "timesheet_uom"}'/>)</small></th>
- <th t-else="" class="text-right">Hours Spent <small class="text-muted">(Total: <span t-esc="hours_spent" t-options='{"widget": "float_time"}'/>)</small></th>
+ <th t-if="is_uom_day" class="text-right">Days Spent</th>
+ <th t-else="" class="text-right">Hours Spent</th>
</tr>
</thead>
- <tbody>
+ <tbody style="font-size: 0.8rem">
<t t-foreach="timesheets" t-as="timesheet">
<tr>
<td t-if="not groupby == 'date'"><span t-field="timesheet.date" t-options='{"widget": "date"}'/></td>
- <td t-if="not groupby == 'employee'"><span t-field="timesheet.employee_id"/></td>
- <td t-if="not groupby == 'project'"><span t-field="timesheet.project_id"/></td>
- <td t-if="not groupby == 'task'"><span t-field="timesheet.task_id"/></td>
- <td><span t-esc="timesheet.name"/></td>
+ <td t-if="not groupby == 'employee'"><span t-field="timesheet.employee_id" t-att-title="timesheet.employee_id.display_name" /></td>
+ <td t-if="not groupby == 'project'"><span t-field="timesheet.project_id" t-att-title="timesheet.project_id.display_name"/></td>
+ <td t-if="not groupby == 'task'"><span t-field="timesheet.task_id" t-att-title="timesheet.task_id.display_name"/></td>
+ <td><span t-esc="timesheet.name" t-att-title="timesheet.name"/></td>
<td class="text-right">
<span t-if="is_uom_day" t-esc="timesheet._get_timesheet_time_day()" t-options='{"widget": "timesheet_uom"}'/>
- <span t-else= "" t-field="timesheet.unit_amount" t-options='{"widget": "float_time"}'/>
+ <span t-else="" t-field="timesheet.unit_amount" t-options='{"widget": "float_time"}'/>
</td>
</tr>
</t>
diff --git a/addons/sale_timesheet/controllers/portal.py b/addons/sale_timesheet/controllers/portal.py
index e4e04da4878..5b6003aeca5 100644
--- a/addons/sale_timesheet/controllers/portal.py
+++ b/addons/sale_timesheet/controllers/portal.py
@@ -1,10 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from odoo import http, _
from odoo.http import request
from odoo.osv import expression
from odoo.addons.account.controllers import portal
+from odoo.addons.hr_timesheet.controllers.portal import TimesheetCustomerPortal
class PortalAccount(portal.PortalAccount):
@@ -38,3 +40,33 @@ class CustomerPortal(portal.CustomerPortal):
values['timesheets'] = request.env['account.analytic.line'].sudo().search(domain)
values['is_uom_day'] = request.env['account.analytic.line'].sudo()._is_timesheet_encode_uom_day()
return values
+
+
+class SaleTimesheetCustomerPortal(TimesheetCustomerPortal):
+
+ def _get_searchbar_inputs(self):
+ searchbar_inputs = super()._get_searchbar_inputs()
+ searchbar_inputs.update(sol={'input': 'sol', 'label': _('Search in Sales Order Item')}, sol_id={'input': 'sol_id', 'label': _('Search in Sales Order Item ID')})
+ return searchbar_inputs
+
+ def _get_searchbar_groupby(self):
+ searchbar_groupby = super()._get_searchbar_groupby()
+ searchbar_groupby.update(sol={'input': 'sol', 'label': _('Sales Order Item')})
+ return searchbar_groupby
+
+ def _get_search_domain(self, search_in, search):
+ search_domain = super()._get_search_domain(search_in, search)
+ if search_in in ('sol', 'all'):
+ search_domain = expression.OR([search_domain, [('so_line', 'ilike', search)]])
+ if search_in == 'sol_id':
+ search_domain = expression.OR([search_domain, [('so_line.id', '=', search)]])
+ return search_domain
+
+ def _get_groupby_mapping(self):
+ groupby_mapping = super()._get_groupby_mapping()
+ groupby_mapping.update(sol='so_line')
+ return groupby_mapping
+
+ @http.route(['/my/timesheets', '/my/timesheets/page/<int:page>'], type='http', auth="user", website=True)
+ def portal_my_timesheets(self, page=1, sortby=None, filterby=None, search=None, search_in='all', groupby='sol', **kw):
+ return super().portal_my_timesheets(page, sortby, filterby, search, search_in, groupby, **kw)
diff --git a/addons/sale_timesheet/views/sale_timesheet_portal_templates.xml b/addons/sale_timesheet/views/sale_timesheet_portal_templates.xml
index 9e198ababee..2f9ae255141 100644
--- a/addons/sale_timesheet/views/sale_timesheet_portal_templates.xml
+++ b/addons/sale_timesheet/views/sale_timesheet_portal_templates.xml
@@ -59,6 +59,12 @@
</xpath>
</template>
+ <template id="sale_order_portal_content_inherit" inherit_id="sale.sale_order_portal_content">
+ <xpath expr="//td[@id='product_name']" position="inside">
+ <a t-if="len(timesheets.filtered(lambda t: t.so_line == line)) > 0" t-att-href="'/my/timesheets?search_in=sol_id&amp;search=%s' % line.id">View Timesheets</a>
+ </xpath>
+ </template>
+
<template id="sale_order_portal_template_inherit" inherit_id="sale.sale_order_portal_template">
<xpath expr="//t[@t-call='portal.portal_record_sidebar']//div[hasclass('o_download_pdf')]" position="after">
<li t-if="timesheets" class="list-group-item flex-grow-1" >
@@ -111,4 +117,35 @@
</xpath>
</template>
+ <template id="portal_my_timesheets_inherit" inherit_id="hr_timesheet.portal_my_timesheets">
+ <xpath expr="//thead/tr[contains(@t-attf-class, 'thead-light')]" position="inside">
+ <t t-elif="groupby == 'sol'">
+ <t t-set="sol" t-value="timesheets[0].so_line"/>
+ <th colspan="5">
+ <t t-if="sol">
+ <em class="font-weight-normal text-muted">Timesheets for sales order item:</em>
+ <span t-field="sol.display_name"/>
+ <t t-if="sol.remaining_hours_available">
+ <span class="text-muted font-weight-normal">(<span t-field="sol.product_uom_qty" t-options='{"widget": "float_time"}'></span> <span t-field="sol.product_uom.display_name"></span> Ordered, <span t-field="sol.remaining_hours" t-options='{"widget": "float_time"}'></span> <span t-field="sol.product_uom.display_name"></span> Remaining)</span>
+ </t>
+ </t>
+ </th>
+ <th colspan="1" class="text-right text-muted font-weight-normal">
+ <t t-if="is_uom_day">
+ Total: <span t-esc="timesheets[0]._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'">Sale Order Item</th>
+ </xpath>
+ <xpath expr="//tbody//td[hasclass('text-right')]" position="before">
+ <td t-if="not groupby == 'sol'"><span t-field="timesheet.so_line" t-att-title="timesheet.so_line.display_name"></span></td>
+ </xpath>
+ </template>
+
</odoo>