[FIX] hr_timesheet: make portal view readonly for ts

Currently, there is an access right problem concerning timesheet. The
module mrp gives write access to portal user on analytic line, but it is not
something we want for the project app.

step to reproduce:
- install hr_timesheet on a saas-16.3 db
- go to project app, open office design
- select any task, and add a subtask to it
- go the the view form of the subtask
- create a new timesheet for this subtask
- go back to the view form of the parent task
a button 'subtask timesheet' is now visible
- share the project in edit mode with a portal user
- connect with that portal user
- open the office design project
- go to the view form of the parent task and click on the button
- the user is redirected to a view tree with the timesheet of the
subtask
this view tree is editable

Source of the problem:
in the mrp module, the access rights of the analytic line is overwritten
to give write access to it to portal user.
Soluce:
We have to make the view in readonly mode for timesheet when the
connected user is a portal one. Unfortunately, it is not 100% possible
because setting the field to readonly still gives the user the
opportunity to click on those fields (which triggers access errors). So
we have to create new views and specifically open those views when the
user is a portal one.

affected version: saas-16.3 - master
task - 3751315

closes odoo/odoo#160959

X-original-commit: 7b733f118f60ff98d9f82c69453187d70e6bece0
Signed-off-by: Xavier Bol (xbo) <xbo@odoo.com>
This commit is contained in:
damr
2024-04-10 08:17:49 +00:00
parent 079cc8b10a
commit 2cfb25c033
4 changed files with 127 additions and 3 deletions
+8 -3
View File
@@ -84,9 +84,14 @@ class AccountAnalyticLine(models.Model):
return False
def _compute_readonly_timesheet(self):
readonly_timesheets = self.filtered(lambda timesheet: timesheet._is_readonly())
readonly_timesheets.readonly_timesheet = True
(self - readonly_timesheets).readonly_timesheet = False
# Since the mrp_module gives write access to portal user on timesheet, we check that the user is an internal one before giving the write access.
# It is not supposed to be needed, since portal user are not supposed to have access to the views using this field, but better be safe than sorry
if not self.env.user.has_group('base.group_user'):
self.readonly_timesheet = True
else:
readonly_timesheets = self.filtered(lambda timesheet: timesheet._is_readonly())
readonly_timesheets.readonly_timesheet = True
(self - readonly_timesheets).readonly_timesheet = False
def _compute_encoding_uom_id(self):
for analytic_line in self:
@@ -161,14 +161,32 @@ class Task(models.Model):
def action_view_subtask_timesheet(self):
self.ensure_one()
is_internal_user = self.env.user.has_group('base.group_user')
task_ids = self.with_context(active_test=False)._get_subtask_ids_per_task_id().get(self.id, [])
action = self.env["ir.actions.actions"]._for_xml_id("hr_timesheet.timesheet_action_all")
graph_view_id = self.env.ref("hr_timesheet.view_hr_timesheet_line_graph_by_employee").id
new_views = []
for view in action['views']:
if not is_internal_user:
if view[1] == 'tree':
tree_view_id = self.env['ir.model.data']._xmlid_to_res_id('hr_timesheet.hr_timesheet_line_portal_tree')
if tree_view_id:
new_views.insert(0, (tree_view_id, 'tree'))
continue
elif view[1] == 'form':
form_view_id = self.env['ir.model.data']._xmlid_to_res_id('hr_timesheet.timesheet_view_form_portal_user')
if form_view_id:
new_views.append((form_view_id, 'form'))
continue
elif view[1] == 'kanban':
kanban_view_id = self.env['ir.model.data']._xmlid_to_res_id('hr_timesheet.view_kanban_account_analytic_line_portal_user')
if kanban_view_id:
new_views.append((kanban_view_id, 'kanban'))
continue
if view[1] == 'graph':
view = (graph_view_id, 'graph')
new_views.insert(0, view) if view[1] == 'tree' else new_views.append(view)
action.update({
'display_name': _('Timesheets'),
'context': {'default_project_id': self.project_id.id, 'grid_range': 'week'},
@@ -23,3 +23,48 @@ class TestPortalTimesheet(TestProjectSharingCommon):
self.env.invalidate_all()
# Should not raise any access error
self.env['account.analytic.line'].with_user(self.user_portal).get_view(view_type=view)
def test_action_view_subtask_timesheet(self):
""" Ensure that the action view_subtask_timesheet is accessible without
raising an error for all portal users
"""
# A portal collaborator is added to a project to enable the rule analytic.account.analytic.line.timesheet.portal.user
self.project_portal.write({
'collaborator_ids': [
Command.create({'partner_id': self.user_portal.partner_id.id}),
],
})
action = self.task_portal.action_view_subtask_timesheet()
tree_view_id = form_view_id = kanban_view_id = False
for view_id, view_type in action['views']:
if view_type == 'tree':
tree_view_id = view_id
elif view_type == 'form':
form_view_id = view_id
elif view_type == 'kanban':
kanban_view_id = view_id
action = self.task_portal.with_user(self.user_portal).action_view_subtask_timesheet()
portal_tree_view_id = self.env['ir.model.data']._xmlid_to_res_id('hr_timesheet.hr_timesheet_line_portal_tree')
portal_form_view_id = self.env['ir.model.data']._xmlid_to_res_id('hr_timesheet.timesheet_view_form_portal_user')
portal_kanban_view_id = self.env['ir.model.data']._xmlid_to_res_id('hr_timesheet.view_kanban_account_analytic_line_portal_user')
if portal_tree_view_id and portal_form_view_id and portal_kanban_view_id:
# no need to check that if the views are not installed or already removed
for view_id, view_type in action['views']:
if view_type == 'tree':
self.assertEqual(view_id, portal_tree_view_id)
elif view_type == 'form':
self.assertEqual(view_id, portal_form_view_id)
elif view_type == 'kanban':
self.assertEqual(view_id, portal_kanban_view_id)
self.env['ir.ui.view'].browse([portal_tree_view_id, portal_form_view_id, portal_kanban_view_id]).unlink()
action = self.task_portal.with_user(self.user_portal).action_view_subtask_timesheet()
for view_id, view_type in action['views']:
if view_type == 'tree':
self.assertEqual(view_id, tree_view_id)
elif view_type == 'form':
self.assertEqual(view_id, form_view_id)
elif view_type == 'kanban':
self.assertEqual(view_id, kanban_view_id)
@@ -23,6 +23,24 @@
</field>
</record>
<record id="hr_timesheet_line_portal_tree" model="ir.ui.view">
<field name="name">portal.hr_timesheet.account.analytic.line.tree</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="hr_timesheet_line_tree"/>
<field name="mode">primary</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<xpath expr="//tree" position="attributes">
<attribute name="edit">0</attribute>
<attribute name="create">0</attribute>
<attribute name="delete">0</attribute>
</xpath>
<xpath expr="//field[@name='task_id']" position="attributes">
<attribute name="options">{'no_create_edit': True, 'no_open': True}</attribute>
</xpath>
</field>
</record>
<record id="timesheet_view_tree_user" model="ir.ui.view">
<field name="name">account.analytic.line.view.tree.with.user</field>
<field name="model">account.analytic.line</field>
@@ -194,6 +212,31 @@
</field>
</record>
<record id="timesheet_view_form_portal_user" model="ir.ui.view">
<field name="name">account.analytic.line.form</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="hr_timesheet.timesheet_view_form_user"/>
<field name="mode">primary</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<xpath expr="//field[@name='employee_id']" position="attributes">
<attribute name="required">1</attribute>
<attribute name="widget">many2one_avatar_employee</attribute>
<attribute name="context">{'active_test': True}</attribute>
<attribute name="options">{'no_open': True}</attribute>
<attribute name="readonly">1</attribute>
</xpath>
<xpath expr="//field[@name='project_id']" position="attributes">
<attribute name="options">{'no_create_edit': True, 'no_open': True}</attribute>
<attribute name="readonly">1</attribute>
</xpath>
<xpath expr="//field[@name='task_id']" position="attributes">
<attribute name="options">{'no_create_edit': True, 'no_open': True}</attribute>
<attribute name="readonly">1</attribute>
</xpath>
</field>
</record>
<record id="hr_timesheet_line_my_timesheet_search" model="ir.ui.view">
<field name="name">view.search.my.timesheet.menu</field>
<field name="model">account.analytic.line</field>
@@ -261,6 +304,19 @@
</field>
</record>
<record id="view_kanban_account_analytic_line_portal_user" model="ir.ui.view">
<field name="name">portal.account.analytic.line.kanban</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="hr_timesheet.view_kanban_account_analytic_line"/>
<field name="mode">primary</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<xpath expr="//img[hasclass('o_image_40_cover')]" position="replace"/>
<xpath expr="//div[hasclass('o_kanban_record_headings')]" position="attributes">
<attribute name="class">o_kanban_record_headings pe-2</attribute>
</xpath>
</field>
</record>
<!--
Actions
-->