======= This PR is in the continuity of odoo#62360. - Display remaining hours in task sales_line_id (name_get + task form view) - Remove internal reference from services demo data - Display sol in project timesheets list if project is billable - Remove use of non_allow_billable in timesheets as it has been removed from project and task in previous PR (see above) - Only recompute planned_hours for service product - Determine the correct SOL for timesheet - Set the last SOL of customer on timesheet if none is set on task or project - Allow edition of so_line in timesheet - Restrict SOL on project to sale lines with a service product - Use same widget on partner_id many2One than in sale.order (using ranking) - Remove timesheets table in SO and invoice portal.They were added in previous PR (see above). This introduces the use of links to /my/timesheets/. - Review portal timesheets (my/timesheets/ and link from orders and invoices) - Activate group_uom "Units of Measure" on sale_timesheet install - Hide partner phone number in task form view - Only determine SOL of task and timesheet if allow_billable=True - Filter SOL in task so that it matches the SOL of the project SO - Display red label if remaining hours is negative - Change SO compute behavior - Only open SO for salesman on project overview task-2409761 -- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr closes odoo/odoo#63695 Forward-port-of: #62900 X-original-commit: b1985773f3a67e28049f9e2bb635bcab059ad9c0 Related: odoo/enterprise#15427 Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
161 lines
7.8 KiB
Python
161 lines
7.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from collections import OrderedDict
|
|
from dateutil.relativedelta import relativedelta
|
|
from operator import itemgetter
|
|
from datetime import datetime
|
|
|
|
from odoo import fields, http, _
|
|
from odoo.http import request
|
|
from odoo.tools import date_utils, groupby as groupbyelem
|
|
from odoo.osv.expression import AND, OR
|
|
|
|
from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager
|
|
|
|
|
|
class TimesheetCustomerPortal(CustomerPortal):
|
|
|
|
def _prepare_home_portal_values(self, counters):
|
|
values = super()._prepare_home_portal_values(counters)
|
|
if 'timesheet_count' in counters:
|
|
domain = request.env['account.analytic.line']._timesheet_get_portal_domain()
|
|
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 = self._get_searchbar_inputs()
|
|
|
|
searchbar_groupby = self._get_searchbar_groupby()
|
|
|
|
today = fields.Date.today()
|
|
quarter_start, quarter_end = date_utils.get_quarter(today)
|
|
last_week = today + relativedelta(weeks=-1)
|
|
last_month = today + relativedelta(months=-1)
|
|
last_year = today + relativedelta(years=-1)
|
|
|
|
searchbar_filters = {
|
|
'all': {'label': _('All'), 'domain': []},
|
|
'today': {'label': _('Today'), 'domain': [("date", "=", today)]},
|
|
'week': {'label': _('This week'), 'domain': [('date', '>=', date_utils.start_of(today, "week")), ('date', '<=', date_utils.end_of(today, 'week'))]},
|
|
'month': {'label': _('This month'), 'domain': [('date', '>=', date_utils.start_of(today, 'month')), ('date', '<=', date_utils.end_of(today, 'month'))]},
|
|
'year': {'label': _('This year'), 'domain': [('date', '>=', date_utils.start_of(today, 'year')), ('date', '<=', date_utils.end_of(today, 'year'))]},
|
|
'quarter': {'label': _('This Quarter'), 'domain': [('date', '>=', quarter_start), ('date', '<=', quarter_end)]},
|
|
'last_week': {'label': _('Last week'), 'domain': [('date', '>=', date_utils.start_of(last_week, "week")), ('date', '<=', date_utils.end_of(last_week, 'week'))]},
|
|
'last_month': {'label': _('Last month'), 'domain': [('date', '>=', date_utils.start_of(last_month, 'month')), ('date', '<=', date_utils.end_of(last_month, 'month'))]},
|
|
'last_year': {'label': _('Last year'), 'domain': [('date', '>=', date_utils.start_of(last_year, 'year')), ('date', '<=', date_utils.end_of(last_year, 'year'))]},
|
|
}
|
|
# default sort by value
|
|
if not sortby:
|
|
sortby = 'date'
|
|
order = searchbar_sortings[sortby]['order']
|
|
# default filter by value
|
|
if not filterby:
|
|
filterby = 'all'
|
|
domain = AND([domain, searchbar_filters[filterby]['domain']])
|
|
|
|
if search and search_in:
|
|
domain += self._get_search_domain(search_in, search)
|
|
|
|
timesheet_count = Timesheet_sudo.search_count(domain)
|
|
# pager
|
|
pager = portal_pager(
|
|
url="/my/timesheets",
|
|
url_args={'sortby': sortby, 'search_in': search_in, 'search': search, 'filterby': filterby, 'groupby': groupby},
|
|
total=timesheet_count,
|
|
page=page,
|
|
step=_items_per_page
|
|
)
|
|
|
|
def get_timesheets():
|
|
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=_items_per_page, offset=pager['offset'])
|
|
if field:
|
|
if groupby == 'date':
|
|
time_data = Timesheet_sudo.read_group(domain, ['date', 'unit_amount:sum'], ['date:day'])
|
|
mapped_time = dict([(datetime.strptime(m['date:day'], '%d %b %Y').date(), m['unit_amount']) for m in time_data])
|
|
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] 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
|
|
|
|
grouped_timesheets = [(
|
|
timesheets,
|
|
sum(Timesheet_sudo.search(domain).mapped('unit_amount'))
|
|
)] if timesheets else []
|
|
return timesheets, grouped_timesheets
|
|
|
|
timesheets, grouped_timesheets = get_timesheets()
|
|
|
|
values.update({
|
|
'timesheets': timesheets,
|
|
'grouped_timesheets': grouped_timesheets,
|
|
'page_name': 'timesheet',
|
|
'default_url': '/my/timesheets',
|
|
'pager': pager,
|
|
'searchbar_sortings': searchbar_sortings,
|
|
'search_in': search_in,
|
|
'search': search,
|
|
'sortby': sortby,
|
|
'groupby': groupby,
|
|
'searchbar_inputs': searchbar_inputs,
|
|
'searchbar_groupby': searchbar_groupby,
|
|
'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())),
|
|
'filterby': filterby,
|
|
'is_uom_day': request.env['account.analytic.line']._is_timesheet_encode_uom_day(),
|
|
})
|
|
return request.render("hr_timesheet.portal_my_timesheets", values)
|