[IMP] hr_timesheet: added a timesheet for portal user
Purpose of the commit is to add a timesheet section on portal where you can see all the timesheets and user can see and use the filter options on them (available when only hr_timesheet is installed) task-1932488
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import controllers
|
||||
from . import models
|
||||
from . import report
|
||||
|
||||
@@ -29,6 +29,7 @@ up a management by affair.
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/project_views.xml',
|
||||
'views/project_portal_templates.xml',
|
||||
'views/hr_timesheet_portal_templates.xml',
|
||||
'report/hr_timesheet_report_view.xml',
|
||||
'report/report_timesheet_templates.xml',
|
||||
'views/hr_views.xml',
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import portal
|
||||
from . import project
|
||||
@@ -0,0 +1,106 @@
|
||||
# -*- 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.http import request
|
||||
from odoo.tools import date_utils, groupby as groupbyelem
|
||||
from odoo.osv.expression import AND
|
||||
|
||||
from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager
|
||||
|
||||
|
||||
class TimesheetCustomerPortal(CustomerPortal):
|
||||
|
||||
def _prepare_portal_layout_values(self):
|
||||
values = super(TimesheetCustomerPortal, self)._prepare_portal_layout_values()
|
||||
domain = request.env['account.analytic.line']._timesheet_get_portal_domain()
|
||||
values['timesheet_count'] = request.env['account.analytic.line'].sudo().search_count(domain)
|
||||
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='project', **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()
|
||||
|
||||
searchbar_sortings = {
|
||||
'date': {'label': _('Newest'), 'order': 'date desc'},
|
||||
'name': {'label': _('Name'), 'order': 'name'},
|
||||
}
|
||||
|
||||
searchbar_inputs = {
|
||||
'all': {'input': 'all', 'label': _('Search in All')},
|
||||
}
|
||||
|
||||
searchbar_groupby = {
|
||||
'none': {'input': 'none', 'label': _('None')},
|
||||
'project': {'input': 'project', 'label': _('Project')},
|
||||
}
|
||||
|
||||
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 = AND([domain, [('name', 'ilike', 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},
|
||||
total=timesheet_count,
|
||||
page=page,
|
||||
step=self._items_per_page
|
||||
)
|
||||
|
||||
if groupby == 'project':
|
||||
order = "project_id, %s" % order
|
||||
timesheets = Timesheet_sudo.search(domain, order=order, limit=self._items_per_page, offset=pager['offset'])
|
||||
if groupby == 'project':
|
||||
grouped_timesheets = [Timesheet_sudo.concat(*g) for k, g in groupbyelem(timesheets, itemgetter('project_id'))]
|
||||
else:
|
||||
grouped_timesheets = [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,
|
||||
'sortby': sortby,
|
||||
'groupby': groupby,
|
||||
'searchbar_inputs': searchbar_inputs,
|
||||
'searchbar_groupby': searchbar_groupby,
|
||||
'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())),
|
||||
'filterby': filterby,
|
||||
})
|
||||
return request.render("hr_timesheet.portal_my_timesheets", values)
|
||||
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.http import request
|
||||
from odoo.osv import expression
|
||||
|
||||
from odoo.addons.project.controllers.portal import CustomerPortal
|
||||
|
||||
|
||||
class ProjectCustomerPortal(CustomerPortal):
|
||||
|
||||
def _task_get_page_view_values(self, task, access_token, **kwargs):
|
||||
values = super(ProjectCustomerPortal, self)._task_get_page_view_values(task, access_token, **kwargs)
|
||||
domain = request.env['account.analytic.line']._timesheet_get_portal_domain()
|
||||
domain = expression.AND([domain, [('task_id', '=', task.id)]])
|
||||
timesheets = request.env['account.analytic.line'].sudo().search(domain)
|
||||
values['timesheets'] = timesheets
|
||||
return values
|
||||
@@ -102,6 +102,14 @@ class AccountAnalyticLine(models.Model):
|
||||
# Business Methods
|
||||
# ----------------------------------------------------
|
||||
|
||||
def _timesheet_get_portal_domain(self):
|
||||
return ['|', '&',
|
||||
('task_id.project_id.privacy_visibility', '=', 'portal'),
|
||||
('task_id.project_id.message_partner_ids', 'child_of', [self.env.user.partner_id.commercial_partner_id.id]),
|
||||
'&',
|
||||
('task_id.project_id.privacy_visibility', '=', 'portal'),
|
||||
('task_id.message_partner_ids', 'child_of', [self.env.user.partner_id.commercial_partner_id.id])]
|
||||
|
||||
def _timesheet_preprocess(self, vals):
|
||||
""" Deduce other field values from the one given.
|
||||
Overrride this to compute on the fly some field that can not be computed fields.
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<template id="portal_layout" name="Portal layout: timesheet menu entry" inherit_id="portal.portal_breadcrumbs" priority="35">
|
||||
<xpath expr="//ol[hasclass('o_portal_submenu')]" position="inside">
|
||||
<li t-if="page_name == 'timesheet' or timesheet" t-attf-class="breadcrumb-item #{'active' if not timesheet else ''}">
|
||||
<t>Timesheets</t>
|
||||
</li>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="portal_my_home_timesheet" name="Portal My Home : timesheets entries" inherit_id="portal.portal_my_home" priority="45">
|
||||
<xpath expr="//div[hasclass('o_portal_docs')]" position="inside">
|
||||
<t t-if="timesheet_count" t-call="portal.portal_docs_entry">
|
||||
<t t-set="title">Timesheets</t>
|
||||
<t t-set="url" t-value="'/my/timesheets'"/>
|
||||
<t t-set="count" t-value="timesheet_count"/>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="portal_my_timesheets" name="My Timesheets">
|
||||
<t t-call="portal.portal_layout">
|
||||
<t t-set="breadcrumbs_searchbar" t-value="True"/>
|
||||
|
||||
<t t-call="portal.portal_searchbar">
|
||||
<t t-set="title">Timesheets</t>
|
||||
</t>
|
||||
<t t-if="not grouped_timesheets">
|
||||
<div class="alert alert-warning mt8" role="alert">
|
||||
There are no timesheets.
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="grouped_timesheets">
|
||||
<t t-call="portal.portal_table">
|
||||
<t t-foreach="grouped_timesheets" t-as="timesheets">
|
||||
<thead>
|
||||
<tr t-attf-class="{{'thead-light' if not groupby == 'none' else ''}}">
|
||||
<th t-if="groupby == 'none'">Description</th>
|
||||
<th t-else="">
|
||||
<em class="font-weight-normal text-muted">Timesheets for project:</em>
|
||||
<span t-field="timesheets[0].project_id.name"/></th>
|
||||
<th>Date</th>
|
||||
<th>Employee</th>
|
||||
<th class="text-right">Duration</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-foreach="timesheets" t-as="timesheet">
|
||||
<tr>
|
||||
<td><span t-esc="timesheet.name"/></td>
|
||||
<td><span t-field="timesheet.date" t-options='{"widget": "date"}'/></td>
|
||||
<td><span t-field="timesheet.employee_id"/></td>
|
||||
<td class="text-right"><span t-field="timesheet.unit_amount" t-options='{"widget": "duration", "unit": "hour", "round": "minute"}'/></td>
|
||||
</tr>
|
||||
</t>
|
||||
</tbody>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
@@ -3,27 +3,31 @@
|
||||
|
||||
<template id="portal_my_task" inherit_id="project.portal_my_task" name="Portal: My Task with Timesheets">
|
||||
<xpath expr="//t[@t-set='card_body']" position="inside">
|
||||
<div class="container" t-if="task.timesheet_ids">
|
||||
<div class="container" t-if="timesheets">
|
||||
<hr class="mt-4 mb-1"/>
|
||||
<h5 class="mt-2 mb-2">Timesheets</h5>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Description</th>
|
||||
<th>Responsible</th>
|
||||
<th class="text-right">Duration</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr t-foreach="task.timesheet_ids" t-as="timesheet">
|
||||
<td><t t-esc="timesheet.date"/></td>
|
||||
<td><t t-esc="timesheet.name"/></td>
|
||||
<td><t t-esc="timesheet.sudo().user_id.name"/></td>
|
||||
<td class="text-right"><span t-field="timesheet.unit_amount" t-options='{"widget": "duration", "unit": "hour", "round": "minute"}'/></td>
|
||||
</tr>
|
||||
</table>
|
||||
<t t-call="hr_timesheet.portal_timesheet_table"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="portal_timesheet_table" name="Portal Timesheet Table">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Description</th>
|
||||
<th>Employee</th>
|
||||
<th class="text-right">Duration</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr t-foreach="timesheets" t-as="timesheet">
|
||||
<td><t t-esc="timesheet.date" t-options='{"widget": "date"}'/></td>
|
||||
<td><t t-esc="timesheet.name"/></td>
|
||||
<td><t t-esc="timesheet.employee_id.name"/></td>
|
||||
<td class="text-right"><span t-field="timesheet.unit_amount" t-options='{"widget": "duration", "unit": "hour", "round": "minute"}'/></td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
|
||||
Reference in New Issue
Block a user