19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:31:00 +01:00
parent a1137a1456
commit e1d89e11e3
2789 changed files with 1093187 additions and 605897 deletions

View file

@ -1,14 +1,12 @@
# -*- 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 odoo import fields, http, _
from odoo.fields import Domain
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
from odoo.addons.project.controllers.portal import ProjectCustomerPortal
@ -26,99 +24,96 @@ class TimesheetCustomerPortal(CustomerPortal):
def _get_searchbar_inputs(self):
return {
'all': {'input': 'all', 'label': _('Search in All')},
'employee': {'input': 'employee', 'label': _('Search in Employee')},
'project': {'input': 'project', 'label': _('Search in Project')},
'task': {'input': 'task', 'label': _('Search in Task')},
'name': {'input': 'name', 'label': _('Search in Description')},
'name': {'input': 'name', 'label': _('Search in Description'), 'sequence': 10},
'employee_id': {'input': 'employee_id', 'label': _('Search in Employee'), 'sequence': 20},
'project_id': {'input': 'project_id', 'label': _('Search in Project'), 'sequence': 30},
'task_id': {'input': 'task_id', 'label': _('Search in Task'), 'sequence': 40},
'parent_task_id': {'input': 'parent_task_id', 'label': _('Search in Parent Task'), 'sequence': 70},
}
def _task_get_searchbar_sortings(self, milestones_allowed):
values = super()._task_get_searchbar_sortings(milestones_allowed)
values['progress'] = {'label': _('Progress'), 'order': 'progress asc', 'sequence': 10}
return values
def _task_get_searchbar_sortings(self, milestones_allowed, project=False):
return super()._task_get_searchbar_sortings(milestones_allowed, project) | {
'progress asc': {'label': _('Progress'), 'order': 'progress asc', 'sequence': 100},
}
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')}
'none': {'label': _('None'), 'sequence': 10},
'date': {'label': _('Date'), 'sequence': 20},
'project_id': {'label': _('Project'), 'sequence': 30},
'parent_task_id': {'label': _('Parent Task'), 'sequence': 40},
'task_id': {'label': _('Task'), 'sequence': 50},
'employee_id': {'label': _('Employee'), 'sequence': 70},
}
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'
}
if search_in in self._get_searchbar_inputs():
return Domain(search_in, 'ilike', search)
else:
return Domain.FALSE
def _get_searchbar_sortings(self):
return {
'date': {'label': _('Newest'), 'order': 'date desc'},
'employee': {'label': _('Employee'), 'order': 'employee_id'},
'project': {'label': _('Project'), 'order': 'project_id'},
'task': {'label': _('Task'), 'order': 'task_id'},
'name': {'label': _('Description'), 'order': 'name'},
'date desc': {'label': _('Newest')},
'employee_id': {'label': _('Employee')},
'project_id': {'label': _('Project')},
'task_id': {'label': _('Task')},
'name': {'label': _('Description')},
}
def _project_get_page_view_values(self, project, access_token, page=1, date_begin=None, date_end=None, sortby=None, search=None, search_in='content', groupby=None, **kwargs):
values = super()._project_get_page_view_values(project, access_token, page, date_begin, date_end, sortby, search, search_in, groupby, **kwargs)
values['allow_timesheets'] = project.allow_timesheets
return values
@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 = request.env['account.analytic.line']
domain = Timesheet._timesheet_get_portal_domain()
domain = Domain(Timesheet._timesheet_get_portal_domain())
Timesheet_sudo = Timesheet.sudo()
values = self._prepare_portal_layout_values()
_items_per_page = 100
items_per_page = 100
searchbar_sortings = self._get_searchbar_sortings()
searchbar_inputs = self._get_searchbar_inputs()
searchbar_inputs = dict(sorted(self._get_searchbar_inputs().items(), key=lambda item: item[1]['sequence']))
searchbar_groupby = self._get_searchbar_groupby()
searchbar_groupby = dict(sorted(self._get_searchbar_groupby().items(), key=lambda item: item[1]['sequence']))
today = fields.Date.today()
quarter_start, quarter_end = date_utils.get_quarter(today)
last_quarter_date = date_utils.subtract(quarter_start, weeks=1)
last_quarter_start, last_quarter_end = date_utils.get_quarter(last_quarter_date)
last_week = today + relativedelta(weeks=-1)
last_month = today + relativedelta(months=-1)
last_year = today + relativedelta(years=-1)
searchbar_filters = {
'all': {'label': _('All'), 'domain': []},
'last_year': {'label': _('Last Year'), 'domain': [('date', '>=', date_utils.start_of(last_year, 'year')), ('date', '<=', date_utils.end_of(last_year, 'year'))]},
'last_quarter': {'label': _('Last Quarter'), 'domain': [('date', '>=', last_quarter_start), ('date', '<=', last_quarter_end)]},
'last_month': {'label': _('Last Month'), 'domain': [('date', '>=', date_utils.start_of(last_month, 'month')), ('date', '<=', date_utils.end_of(last_month, 'month'))]},
'last_week': {'label': _('Last Week'), 'domain': [('date', '>=', date_utils.start_of(last_week, "week")), ('date', '<=', date_utils.end_of(last_week, 'week'))]},
'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'))]},
'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'))]},
'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'))]},
'year': {'label': _('This Year'), 'domain': [('date', '>=', date_utils.start_of(today, 'year')), ('date', '<=', date_utils.end_of(today, 'year'))]},
}
# default sort by value
if not sortby:
sortby = 'date'
order = searchbar_sortings[sortby]['order']
sortby = 'date desc'
# default filter by value
if not filterby:
filterby = 'all'
domain = AND([domain, searchbar_filters[filterby]['domain']])
domain &= Domain(searchbar_filters[filterby]['domain'])
if search and search_in:
domain += self._get_search_domain(search_in, search)
domain &= self._get_search_domain(search_in, search)
if parent_task_id := kw.get('parent_task_id'):
domain &= Domain('parent_task_id', '=', int(parent_task_id))
timesheet_count = Timesheet_sudo.search_count(domain)
# pager
@ -127,30 +122,29 @@ class TimesheetCustomerPortal(CustomerPortal):
url_args={'sortby': sortby, 'search_in': search_in, 'search': search, 'filterby': filterby, 'groupby': groupby},
total=timesheet_count,
page=page,
step=_items_per_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'])
field = None if groupby == 'none' else groupby
orderby = '%s, %s' % (field, sortby) if field else sortby
timesheets = Timesheet_sudo.search(domain, order=orderby, limit=items_per_page, offset=pager['offset'])
if field:
if groupby == 'date':
raw_timesheets_group = Timesheet_sudo.read_group(
domain, ["unit_amount:sum", "ids:array_agg(id)"], ["date:day"]
raw_timesheets_group = Timesheet_sudo._read_group(
domain, ['date:day'], ['unit_amount:sum', 'id:recordset'], order='date:day desc'
)
grouped_timesheets = [(Timesheet_sudo.browse(group["ids"]), group["unit_amount"]) for group in raw_timesheets_group]
grouped_timesheets = [(records, unit_amount) for __, unit_amount, records in raw_timesheets_group]
else:
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])
time_data = Timesheet_sudo._read_group(domain, [field], ['unit_amount:sum'])
mapped_time = {field.id: unit_amount for field, unit_amount 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'))
Timesheet_sudo._read_group(domain, aggregates=['unit_amount:sum'])[0][0]
)] if timesheets else []
return timesheets, grouped_timesheets
@ -169,23 +163,24 @@ class TimesheetCustomerPortal(CustomerPortal):
'groupby': groupby,
'searchbar_inputs': searchbar_inputs,
'searchbar_groupby': searchbar_groupby,
'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())),
'searchbar_filters': searchbar_filters,
'filterby': filterby,
'is_uom_day': request.env['account.analytic.line']._is_timesheet_encode_uom_day(),
})
return request.render("hr_timesheet.portal_my_timesheets", values)
class TimesheetProjectCustomerPortal(ProjectCustomerPortal):
def _show_task_report(self, task_sudo, report_type, download):
domain = request.env['account.analytic.line']._timesheet_get_portal_domain()
task_domain = AND([domain, [('task_id', '=', task_sudo.id)]])
task_domain = Domain(domain) & Domain('task_id', '=', task_sudo.id)
timesheets = request.env['account.analytic.line'].sudo().search(task_domain)
return self._show_report(model=timesheets,
report_type=report_type, report_ref='hr_timesheet.timesheet_report_task_timesheets', download=download)
def _prepare_tasks_values(self, page, date_begin, date_end, sortby, search, search_in, groupby, url="/my/tasks", domain=None, su=False):
values = super()._prepare_tasks_values(page, date_begin, date_end, sortby, search, search_in, groupby, url, domain, su)
def _prepare_tasks_values(self, page, date_begin, date_end, sortby, search, search_in, groupby, url="/my/tasks", domain=None, su=False, project=False):
values = super()._prepare_tasks_values(page, date_begin, date_end, sortby, search, search_in, groupby, url, domain, su, project)
values.update(
is_uom_day=request.env['account.analytic.line']._is_timesheet_encode_uom_day(),
)

View file

@ -1,21 +1,26 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
from odoo.fields import Domain
from odoo.http import request
from odoo.osv import expression
from odoo.addons.project.controllers.portal import CustomerPortal
class ProjectCustomerPortal(CustomerPortal):
def _prepare_project_sharing_session_info(self, project, task=None):
session_info = super()._prepare_project_sharing_session_info(project, task)
def _get_project_sharing_company(self, project):
company = project.company_id
if not company:
timesheet = request.env['account.analytic.line'].sudo().search([('project_id', '=', project.id)], limit=1)
company = timesheet.company_id or request.env.user.company_id
return company
def _prepare_project_sharing_session_info(self, project):
session_info = super()._prepare_project_sharing_session_info(project)
company = request.env['res.company'].sudo().browse(session_info['user_companies']['current_company'])
timesheet_encode_uom = company.timesheet_encode_uom_id
project_time_mode_uom = company.project_time_mode_id
session_info['user_companies']['allowed_companies'][company.id].update(
timesheet_uom_id=timesheet_encode_uom.id,
timesheet_uom_factor=project_time_mode_uom._compute_quantity(
@ -33,20 +38,16 @@ class ProjectCustomerPortal(CustomerPortal):
'timesheet_widget': uom.timesheet_widget,
} for uom in [timesheet_encode_uom, project_time_mode_uom]
}
session_info['user_context']['allow_timesheets'] = project.allow_timesheets
return session_info
def _task_get_page_view_values(self, task, access_token, **kwargs):
values = super(ProjectCustomerPortal, self)._task_get_page_view_values(task, access_token, **kwargs)
values = super()._task_get_page_view_values(task, access_token, **kwargs)
domain = request.env['account.analytic.line']._timesheet_get_portal_domain()
task_domain = expression.AND([domain, [('task_id', '=', task.id)]])
subtask_domain = expression.AND([domain, [('task_id', 'in', task.child_ids.ids)]])
task_domain = Domain(domain) & Domain('task_id', '=', task.id)
timesheets = request.env['account.analytic.line'].sudo().search(task_domain)
subtasks_timesheets = request.env['account.analytic.line'].sudo().search(subtask_domain)
timesheets_by_subtask = defaultdict(lambda: request.env['account.analytic.line'].sudo())
for timesheet in subtasks_timesheets:
timesheets_by_subtask[timesheet.task_id] |= timesheet
values['allow_timesheets'] = task.allow_timesheets
values['timesheets'] = timesheets
values['timesheets_by_subtask'] = timesheets_by_subtask
values['is_uom_day'] = request.env['account.analytic.line']._is_timesheet_encode_uom_day()
return values