diff options
| author | Xavier BOL (xbo) <xbo@odoo.com> | 2020-12-10 18:34:47 +0100 |
|---|---|---|
| committer | Xavier BOL (xbo) <xbo@odoo.com> | 2020-12-21 15:35:20 +0100 |
| commit | a41df84a83380dded4cb8c9d83444f916b9e004d (patch) | |
| tree | 72eb76af9b079497898b95eca1983d783f0bbe10 /addons | |
| parent | daa1036eb6b54588224bc5d6fe9be6974cde1ea8 (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
Diffstat (limited to 'addons')
| -rw-r--r-- | addons/hr_timesheet/controllers/portal.py | 79 | ||||
| -rw-r--r-- | addons/hr_timesheet/views/hr_timesheet_portal_templates.xml | 100 | ||||
| -rw-r--r-- | addons/sale_timesheet/controllers/portal.py | 32 | ||||
| -rw-r--r-- | addons/sale_timesheet/views/sale_timesheet_portal_templates.xml | 37 |
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&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> |
