project modifications

This commit is contained in:
karuna 2026-05-19 12:08:41 +05:30
parent f6bfd46f2c
commit c2e33753bb
17 changed files with 380 additions and 345 deletions

View File

@ -103,7 +103,7 @@ class ImBus(models.Model):
"""Low-level method to send ``notification_type`` and ``message`` to ``target``.
Using ``_bus_send()`` from ``bus.listener.mixin`` is recommended for simplicity and
security.
security.
When using ``_sendone`` directly, ``target`` (if str) should not be guessable by an
attacker.

View File

@ -6,7 +6,7 @@
'version': '1.0',
'category': 'Accounting/Localizations/Point of Sale',
'description': """
This add-on brings the technical requirements of the French regulation CGI art. 286, I. 3° bis that stipulates certain criteria concerning the inalterability, security, storage and archiving of data related to sales to private individuals (B2C).
This add-on brings the technical requirements of the French regulation CGI art. 286, I. 3° bis that stipulates certain criteria concerning the inalterability,security, storage and archiving of data related to sales to private individuals (B2C).
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Install it if you use the Point of Sale app to sell to individuals.

View File

@ -41,7 +41,7 @@ class Survey(http.Controller):
def _check_validity(self, survey_token, answer_token, ensure_token=True, check_partner=True):
""" Check survey is open and can be taken. This does not checks for
security rules, only functional / business rules. It returns a string key
security rules, only functional / business rules. It returns a string key
allowing further manipulation of validity issues
* survey_wrong: survey does not exist;

View File

@ -24,7 +24,7 @@
},
'installable': True,
'data': [
# security.xml first, data.xml need the group to exist (checking it)
#security.xml first, data.xml need the group to exist (checking it)
'security/website_security.xml',
'security/ir.model.access.csv',
'data/image_library.xml',

View File

@ -1,62 +1,62 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--security.xml -->
<odoo>
<!--security.xml -->
<odoo>
<record id="category_employee_appraisal" model="ir.module.category">
<field name="name">Appraisal</field>
<field name="sequence">50</field>
</record>
<!-- Define the user groups -->
<record id="group_appraisal_officer" model="res.groups">
<field name="name">Appraisal Officer</field>
<field name="category_id" ref="category_employee_appraisal"/>
</record>
<record id="group_appraisal_manager" model="res.groups">
<field name="name">Appraisal HR Manager</field>
<field name="category_id" ref="category_employee_appraisal"/>
<field name="implied_ids" eval="[(4, ref('group_appraisal_officer'))]"/> <!-- Inherit Appraisal User permissions -->
</record>
<record id="group_appraisal_administrator" model="res.groups">
<field name="name">Appraisal Administrator</field>
<field name="category_id" ref="category_employee_appraisal"/>
<field name="implied_ids" eval="[(4, ref('group_appraisal_officer'))]"/> <!-- Inherit Appraisal User permissions -->
</record>
<record id="employee_appraisal_base_user_rule" model="ir.rule">
<field name="name">User can only see his/her own appraisals</field>
<field name="model_id" ref="model_employee_appraisal"/>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="domain_force">[('user_id','=',user.id),('state','!=','draft')]</field>
</record>
<record id="employee_appraisal_officer_rule" model="ir.rule">
<field name="name">User can only see the records of people under him/her</field>
<field name="model_id" ref="model_employee_appraisal"/>
<field name="groups" eval="[(4, ref('group_appraisal_officer'))]"/>
<field name="domain_force">[('reviewers_name.user_id','=',user.id),('state','!=','draft')]</field>
</record>
<record id="employee_appraisal_manager_rule" model="ir.rule">
<field name="name">User can only see the all the appraisal records where he/she is set as HR</field>
<field name="model_id" ref="model_employee_appraisal"/>
<field name="groups" eval="[(4, ref('group_appraisal_manager'))]"/>
<field name="domain_force">[('appraisal_hr_id','=',user.id)]</field>
</record>
<record id="employee_appraisal_md_rule" model="ir.rule">
<field name="name">User can only see the all the appraisal records where he/she is set as MD</field>
<field name="model_id" ref="model_employee_appraisal"/>
<field name="groups" eval="[(4, ref('group_appraisal_administrator'))]"/>
<field name="domain_force">[('appraisal_md_id','=',user.id)]</field>
</record>
</odoo>
</odoo>
<?xml version="1.0" encoding="UTF-8" ?>
<!--security.xml -->
<odoo>
<!--security.xml -->
<odoo>
<record id="category_employee_appraisal" model="ir.module.category">
<field name="name">Appraisal</field>
<field name="sequence">50</field>
</record>
<!-- Define the user groups -->
<record id="group_appraisal_officer" model="res.groups">
<field name="name">Appraisal Officer</field>
<field name="category_id" ref="category_employee_appraisal"/>
</record>
<record id="group_appraisal_manager" model="res.groups">
<field name="name">Appraisal HR Manager</field>
<field name="category_id" ref="category_employee_appraisal"/>
<field name="implied_ids" eval="[(4, ref('group_appraisal_officer'))]"/> <!-- Inherit Appraisal User permissions -->
</record>
<record id="group_appraisal_administrator" model="res.groups">
<field name="name">Appraisal Administrator</field>
<field name="category_id" ref="category_employee_appraisal"/>
<field name="implied_ids" eval="[(4, ref('group_appraisal_officer'))]"/> <!-- Inherit Appraisal User permissions -->
</record>
<record id="employee_appraisal_base_user_rule" model="ir.rule">
<field name="name">User can only see his/her own appraisals</field>
<field name="model_id" ref="model_employee_appraisal"/>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="domain_force">[('user_id','=',user.id),('state','!=','draft')]</field>
</record>
<record id="employee_appraisal_officer_rule" model="ir.rule">
<field name="name">User can only see the records of people under him/her</field>
<field name="model_id" ref="model_employee_appraisal"/>
<field name="groups" eval="[(4, ref('group_appraisal_officer'))]"/>
<field name="domain_force">[('reviewers_name.user_id','=',user.id),('state','!=','draft')]</field>
</record>
<record id="employee_appraisal_manager_rule" model="ir.rule">
<field name="name">User can only see the all the appraisal records where he/she is set as HR</field>
<field name="model_id" ref="model_employee_appraisal"/>
<field name="groups" eval="[(4, ref('group_appraisal_manager'))]"/>
<field name="domain_force">[('appraisal_hr_id','=',user.id)]</field>
</record>
<record id="employee_appraisal_md_rule" model="ir.rule">
<field name="name">User can only see the all the appraisal records where he/she is set as MD</field>
<field name="model_id" ref="model_employee_appraisal"/>
<field name="groups" eval="[(4, ref('group_appraisal_administrator'))]"/>
<field name="domain_force">[('appraisal_md_id','=',user.id)]</field>
</record>
</odoo>
</odoo>

View File

@ -1,15 +1,15 @@
{
"name": "Custom Module Switcher",
"version": "1.0",
"depends": ["web","menu_control_center"],
'data': [
'security/ir.model.access.csv',
],
"assets": {
"web.assets_backend": [
"module_selector_sidebar/static/src/js/module_switcher.js",
"module_selector_sidebar/static/src/xml/module_switcher.xml",
],
},
"installable": True,
}
{
"name": "Custom Module Switcher",
"version": "1.0",
"depends": ["web","menu_control_center"],
'data': [
'security/ir.model.access.csv',
],
"assets": {
"web.assets_backend": [
"module_selector_sidebar/static/src/js/module_switcher.js",
"module_selector_sidebar/static/src/xml/module_switcher.xml",
],
},
"installable": True,
}

View File

@ -1,4 +1,4 @@
from . import project_kudo_extend
from . import task_assignee_domain
from . import project_task
from . import project_kudo_extend
from . import task_assignee_domain
from . import project_task
from . import account_analytic_line

View File

@ -49,9 +49,9 @@ Key Features:
'view/maintenance_support.xml',
'view/project_closer.xml',
'view/project_actual_costings.xml',
'view/project_task.xml',
'view/project.xml',
'view/project_portfolio.xml',
'view/project_task.xml',
'view/timesheets.xml',
'view/pro_task_gantt.xml',
'view/user_availability.xml',
@ -61,9 +61,10 @@ Key Features:
'view/stage_approval_wizard.xml',
],
'assets': {
'web.assets_backend':{
'project_task_timesheet_extended/static/src/css/delopyment.css'
}
'web.assets_backend': [
'project_task_timesheet_extended/static/src/css/delopyment.css',
'project_task_timesheet_extended/static/src/js/involved_assignee_avatar_user_field.js',
]
},
'installable': True,
'application': False,

View File

@ -263,4 +263,5 @@
</record>
</data>
<function model="project.task" name="_sync_all_involved_assignees_from_timelines"/>
</odoo>

View File

@ -120,14 +120,18 @@ def post_init_hook(env):
'|',
'&',
('task_id.is_generic', '=', False),
('user_id', 'in', 'task_id.user_ids'),
'|',
('user_id', 'in', 'task_id.user_ids'),
('user_id', 'in', 'task_id.involved_user_ids'),
'&',
('task_id.is_generic', '=', True),
('user_id.partner_id', 'in', 'project_id.message_partner_ids'),
'&', '&',
('project_id.privacy_visibility', '!=', 'followers'),
('task_id.is_generic', '=', False),
('user_id', 'in', 'task_id.user_ids')
'|',
('user_id', 'in', 'task_id.user_ids'),
('user_id', 'in', 'task_id.involved_user_ids')
]
"""
})
@ -162,12 +166,12 @@ def post_init_hook(env):
project_tasks[task.project_id.id] = []
project_tasks[task.project_id.id].append(task)
# Assign sequence numbers to tasks
for project_id, task_list in project_tasks.items():
project = env['project.project'].browse(project_id)
if project.task_sequence_id:
for task in task_list:
task.sequence_name = project.task_sequence_id.next_by_id()
# Normalize task stages so each project owns its workflow configuration.
env['project.project'].search([])._ensure_project_owned_task_stages()
# Assign sequence numbers to tasks
for project_id, task_list in project_tasks.items():
project = env['project.project'].browse(project_id)
if project.task_sequence_id:
for task in task_list:
task.sequence_name = project.task_sequence_id.next_by_id()
# Normalize task stages so each project owns its workflow configuration.
env['project.project'].search([])._ensure_project_owned_task_stages()

View File

@ -333,8 +333,6 @@ class ProjectProject(models.Model):
users_list = list()
if project.assign_approval_flow:
users_list.extend(project.project_stages.involved_users.ids)
else:
users_list.extend(project.showable_stage_ids.user_ids.ids)
if project.project_sponsor:
users_list.append(project.project_sponsor.id)

View File

@ -167,12 +167,12 @@ class projectTask(models.Model):
task.assignee_domain_ids = all_internal_users
continue
# # GENERIC all internal
# # GENERIC: all internal
# if getattr(task, 'is_generic', False):
# task.assignee_domain_ids = all_internal_users
# continue
# PRIVATE invited users only
# PRIVATE: invited users only
if task.project_id.privacy_visibility == 'followers':
task.assignee_domain_ids = (
task.project_id.message_partner_ids
@ -196,18 +196,18 @@ class projectTask(models.Model):
for task in self:
employees = Employee.browse()
# 1GENERIC TASK
# GENERIC TASK
if task.is_generic and task.project_id:
project = task.project_id
# 🔐 Private → followers only
# Private: followers only
if project.privacy_visibility == 'followers':
users = (
project.message_partner_ids
.mapped('user_ids')
.filtered(lambda u: u and not u.share)
)
# 🌍 Internal / Public → all internal users
# Internal / Public: all internal users
else:
users = self.env['res.users'].search([
('share', '=', False),
@ -216,7 +216,7 @@ class projectTask(models.Model):
employees = users.mapped('employee_id').filtered(lambda e: e)
# 2⃣ NORMAL TASK → task assignees only
# NORMAL TASK: assignees and involved collaborators
else:
employees = (
task.user_ids
@ -393,7 +393,7 @@ class projectTask(models.Model):
if start_dt.tzinfo is None:
start_dt = pytz.UTC.localize(start_dt)
# Convert UTC calendar timezone
# Convert UTC to calendar timezone
start_dt_tz = start_dt.astimezone(tz)
# Call plan_hours
@ -459,8 +459,8 @@ class projectTask(models.Model):
self.env.user.name,
task.suggested_deadline.strftime('%Y-%m-%d %H:%M') if task.suggested_deadline else _('Not available')
))
@api.depends("project_id", "stage_id")
@api.depends("project_id")
def _compute_has_supervisor_access(self):
administrative_users = self.env['project.role'].search([
('role_level', '=', 'administrative')
@ -478,26 +478,13 @@ class projectTask(models.Model):
stages = project.type_ids.sorted("sequence")
if not stages:
continue
if first_stage:
create_access_users = first_stage.team_id.team_lead + first_stage.involved_user_ids + administrative_users.user_ids
else:
create_access_users = administrative_users.user_ids
first_stage = stages[0]
create_access_users = (
first_stage.team_id.team_lead
+ first_stage.involved_user_ids
+ administrative_users
)
if (
current_user.has_group("project.group_project_manager")
or current_user == project.user_id
or current_user == project.project_lead
or (
current_user in create_access_users
and task.stage_id == first_stage
)
):
if current_user.has_group("project.group_project_manager") or current_user == task.project_id.user_id or current_user == task.project_id.project_lead or (current_user.id in list(set(create_access_users.ids)) and task.stage_id.id == first_stage.id):
task.has_supervisor_access = True
@api.depends('assignees_timelines.estimated_time', 'show_approval_flow')
@ -637,11 +624,11 @@ class projectTask(models.Model):
task.show_approval_button = True
task.show_refuse_button = True # both approve & refuse in review state
# b) No assigned user directly approvable
# b) No assigned user: directly approvable
elif not assigned_to and (responsible_lead == user or project_manager == user):
task.show_approval_button = True
# c) Assigned_to == responsible_lead no submission needed, direct approve
# c) Assigned_to == responsible_lead: no submission needed, direct approve
elif (
assigned_to
and assigned_to == responsible_lead
@ -797,7 +784,7 @@ class projectTask(models.Model):
task.stage_id = n_stage
task.approval_status = "approved"
activity_log = "%s: approved by %s and moved to %s" % (
activity_log = "%s: approved by %s and moved to %s" % (
current_stage.name,
self.env.user.employee_id.name,
n_stage.name)
@ -838,9 +825,9 @@ class projectTask(models.Model):
)
else:
task.approval_status = "approved"
notes = "%s: Task approved and completed by %s" % (task.sequence_name, self.env.user.employee_id.name)
notes = "%s: Task approved and completed by %s" % (task.sequence_name, self.env.user.employee_id.name)
activity_log = "%s: approved by %s" % (
activity_log = "%s: approved by %s" % (
current_stage.name,
self.env.user.employee_id.name)
@ -876,9 +863,9 @@ class projectTask(models.Model):
# Optional: find previous stage if you want to send back
stage = task.assignees_timelines.filtered(lambda s: s.stage_id == task.stage_id)
notes = "%s: %s rejected by %s" % (task.sequence_name, current_stage.name, self.env.user.employee_id.name)
notes = "%s: %s rejected by %s" % (task.sequence_name, current_stage.name, self.env.user.employee_id.name)
activity_log = "%s: rejected by %s: %s" % (
activity_log = "%s: rejected by %s: %s" % (
current_stage.name,
self.env.user.employee_id.name,
reason)
@ -1017,19 +1004,27 @@ class projectTask(models.Model):
"You are not allowed to change the stage of this task because stage editing is restricted."
))
return super(projectTask, self).write(vals)
result = super(projectTask, self).write(vals)
if any(field in vals for field in ['allocation_start_date', 'allocation_end_date']):
self._sync_allocated_hours_from_allocation_dates()
if any(field in vals for field in ['user_ids', 'is_generic']):
self._sync_involved_assignees_from_timelines()
return result
def button_update_assignees(self):
def _sync_involved_assignees_from_timelines(self):
for task in self:
if task.assignees_timelines:
users_list = list(
set(task.assignees_timelines.responsible_lead.ids + task.assignees_timelines.assigned_to.ids + task.assignees_timelines.team_id.team_lead.ids))
task.user_ids = [(6, 0, users_list)]
if task.is_generic:
continue
# Post to project channel about assignee update
channel_message = _("Assignees updated for task %s") % (task.sequence_name or task.name)
task._post_to_project_channel(channel_message)
timeline_user_ids = set(
task.assignees_timelines.responsible_lead.ids
+ task.assignees_timelines.assigned_to.ids
+ task.assignees_timelines.team_id.team_lead.ids
)
existing_user_ids = set(task.involved_user_ids.ids)
involved_users = list((existing_user_ids | timeline_user_ids) - set(task.user_ids.ids))
task.involved_user_ids = [(6, 0, involved_users)]
def _fetch_planning_overlap(self, additional_domain=None):
use_timeline_logic = any(
@ -1419,7 +1414,7 @@ class projectTask(models.Model):
FROM project_task T1
INNER JOIN project_task T2 ON T1.id <> T2.id
INNER JOIN project_task_user_rel U1 ON T1.id = U1.task_id
INNER JOIN project_task_user_rel U2
INNER JOIN project_task_user_rel U2
ON T2.id = U2.task_id
AND U1.user_id = U2.user_id
WHERE

View File

@ -1,161 +1,161 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="group_project_supervisor" model="res.groups">
<field name="name">Manager</field>
<field name="category_id" ref="base.module_category_services_project"/>
<field name="implied_ids" eval="[(4, ref('project.group_project_user'))]"/>
</record>
<record id="group_project_lead" model="res.groups">
<field name="name">Project Lead</field>
<field name="category_id" ref="base.module_category_services_project"/>
</record>
<record id="project.group_project_manager" model="res.groups">
<field name="implied_ids" eval="[(4, ref('project.group_project_user')),(4, ref('group_project_supervisor')), (4, ref('mail.group_mail_canned_response_admin'))]"/>
</record>
<data>
<record id="portfolio_rule_company_projects" model="ir.rule">
<field name="name">company: Own Company</field>
<field name="model_id" ref="model_project_portfolio"/>
<field name="domain_force">[('company_id', 'in', company_ids + [False])]</field>
</record>
<record id="project_rule_manager_own_projects" model="ir.rule">
<field name="name">Manager: Own Projects</field>
<field name="model_id" ref="project.model_project_project"/>
<field name="groups" eval="[(4, ref('project_task_timesheet_extended.group_project_supervisor'))]"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="0"/>
</record>
<record model="ir.rule" id="project_supervisor_all_project_tasks_rule">
<field name="name">Project/Task: project supervisor: see all tasks linked to his assigned project or its own tasks</field>
<field name="model_id" ref="project.model_project_task"/>
<field name="domain_force">[
('project_id.user_id','=',user.id),
'|', ('project_id', '!=', False),
('user_ids', 'in', user.id),
]</field>
<field name="groups" eval="[(4,ref('project_task_timesheet_extended.group_project_supervisor'))]"/>
</record>
<record model="ir.rule" id="project_users_project_tasks_rule">
<field name="name">Project/Task: project users: don't see non generic tasks</field>
<field name="model_id" ref="project.model_project_task"/>
<field name="domain_force">[
'&amp;', '&amp;',
('project_id', '!=', False),
('is_generic', '=', False),
('user_ids', 'not in', user.id),
]
</field>
<field name="groups" eval="[(4,ref('base.group_user')),(4,ref('project.group_project_user'))]"/>
<field name="perm_read" eval="0"/>
</record>
<record model="ir.rule" id="project_users_project_lead_rule">
<field name="name">Project/Task: project lead: see all tasks</field>
<field name="model_id" ref="project.model_project_task"/>
<field name="domain_force">[
'&amp;', '&amp;', '&amp;',
('project_id', '!=', False),
('project_id.project_lead', '=', user.id),
'|', ('is_generic', '=', True), ('is_generic', '=', False),
'|', ('user_ids', 'in', user.id), ('user_ids', 'not in', user.id)
]
</field>
<field name="groups" eval="[(4,ref('base.group_user')),(4,ref('project.group_project_user'))]"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="0"/>
</record>
<record model="ir.rule" id="user_task_availability_project_lead_rule">
<field name="name">Task Availability: project lead: see all user tasks</field>
<field name="model_id" ref="model_user_task_availability"/>
<field name="groups" eval="[(4,ref('base.group_user')),(4,ref('project.group_project_user'))]"/>
<field name="domain_force">[
'|', '|',
('project_id.project_lead', '=', user.id),
('user_id', '=', user.id),
('project_id.user_id', '=', user.id),
]
</field>
</record>
<!-- <record model="ir.rule" id="timesheet_users_normal_timesheets">-->
<!-- <field name="name">timesheet: users: see own tasks</field>-->
<!-- <field name="model_id" ref="analytic.model_account_analytic_line"/>-->
<!-- <field name="domain_force">[-->
<!-- '&amp;','&amp;', '&amp;', '&amp;', '&amp;','&amp;',-->
<!-- ('project_id.privacy_visibility','=','followers'),-->
<!-- ('task_id', '!=', False),-->
<!-- ('project_id', '!=', False),-->
<!-- ('project_id.project_lead', '!=', user.id),-->
<!-- ('project_id.user_id', '!=', user.id),-->
<!-- ('user_id','not in',[user.id]),-->
<!-- '|',-->
<!-- '&amp;',-->
<!-- ('task_id.is_generic', '=', False),-->
<!-- ('task_id.user_ids', 'not in', [user.id]),-->
<!-- '&amp;',-->
<!-- ('task_id.is_generic', '=', True),-->
<!-- ('project_id.message_partner_ids', 'not in', [user.partner_id.id]),-->
<!-- ]</field>-->
<!-- <field name="groups" eval="[(4,ref('base.group_user')),(4,ref('project.group_project_user')),(4,ref('project_task_timesheet_extended.group_project_supervisor')),(4,ref('hr_timesheet.group_hr_timesheet_user')),(4,ref('hr_timesheet.group_hr_timesheet_approver'))]"/>-->
<!-- <field name="perm_read" eval="0"/>-->
<!-- <field name="perm_unlink" eval="1"/>-->
<!-- <field name="perm_write" eval="1"/>-->
<!-- <field name="perm_create" eval="1"/>-->
<!-- </record>-->
<!-- <record model="ir.rule" id="timesheet_users_non_generic_timesheets">-->
<!-- <field name="name">timesheet: users: see own tasks</field>-->
<!-- <field name="model_id" ref="analytic.model_account_analytic_line"/>-->
<!-- <field name="domain_force">[-->
<!-- ('project_id.privacy_visibility','!=','followers'),-->
<!-- ('task_id', '!=', False),-->
<!-- ('project_id', '!=', False),-->
<!-- ('project_id.project_lead', '!=', user.id),-->
<!-- ('project_id.user_id', '!=', user.id),-->
<!-- ('task_id.is_generic', '=', False),-->
<!-- ('task_id.user_ids', 'not in', [user.id]),-->
<!-- ('user_id','not in',[user.id]),-->
<!-- ]</field>-->
<!-- <field name="groups" eval="[(4,ref('base.group_user')),(4,ref('project.group_project_user')),(4,ref('project_task_timesheet_extended.group_project_supervisor')),(4,ref('hr_timesheet.group_hr_timesheet_user')),(4,ref('hr_timesheet.group_hr_timesheet_approver'))]"/>-->
<!-- <field name="perm_read" eval="0"/>-->
<!-- <field name="perm_unlink" eval="1"/>-->
<!-- <field name="perm_write" eval="1"/>-->
<!-- <field name="perm_create" eval="1"/>-->
<!-- </record>-->
<!-- <record model="ir.rule" id="timesheet_team_lead_normal_timesheets">-->
<!-- <field name="name">timesheet: Lead: see related tasks</field>-->
<!-- <field name="model_id" ref="analytic.model_account_analytic_line"/>-->
<!-- <field name="domain_force">[-->
<!-- '&amp;', '&amp;', '&amp;',-->
<!-- ('project_id', '!=', False),-->
<!-- ('project_id.project_lead', '=', user.id),-->
<!-- '|', ('task_id.is_generic', '=', True), ('task_id.is_generic', '=', False),-->
<!-- '|', ('task_id.user_ids', 'in', user.id), ('task_id.user_ids', 'not in', user.id)-->
<!-- ]-->
<!-- </field>-->
<!-- <field name="perm_read" eval="1"/>-->
<!-- <field name="perm_write" eval="1"/>-->
<!-- <field name="perm_create" eval="1"/>-->
<!-- <field name="perm_unlink" eval="0"/>-->
<!-- </record>-->
</data>
<data>
</data>
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="group_project_supervisor" model="res.groups">
<field name="name">Manager</field>
<field name="category_id" ref="base.module_category_services_project"/>
<field name="implied_ids" eval="[(4, ref('project.group_project_user'))]"/>
</record>
<record id="group_project_lead" model="res.groups">
<field name="name">Project Lead</field>
<field name="category_id" ref="base.module_category_services_project"/>
</record>
<record id="project.group_project_manager" model="res.groups">
<field name="implied_ids" eval="[(4, ref('project.group_project_user')),(4, ref('group_project_supervisor')), (4, ref('mail.group_mail_canned_response_admin'))]"/>
</record>
<data>
<record id="portfolio_rule_company_projects" model="ir.rule">
<field name="name">company: Own Company</field>
<field name="model_id" ref="model_project_portfolio"/>
<field name="domain_force">[('company_id', 'in', company_ids + [False])]</field>
</record>
<record id="project_rule_manager_own_projects" model="ir.rule">
<field name="name">Manager: Own Projects</field>
<field name="model_id" ref="project.model_project_project"/>
<field name="groups" eval="[(4, ref('project_task_timesheet_extended.group_project_supervisor'))]"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="0"/>
</record>
<record model="ir.rule" id="project_supervisor_all_project_tasks_rule">
<field name="name">Project/Task: project supervisor: see all tasks linked to his assigned project or its own tasks</field>
<field name="model_id" ref="project.model_project_task"/>
<field name="domain_force">[
('project_id.user_id','=',user.id),
'|', ('project_id', '!=', False),
('user_ids', 'in', user.id),
]</field>
<field name="groups" eval="[(4,ref('project_task_timesheet_extended.group_project_supervisor'))]"/>
</record>
<record model="ir.rule" id="project_users_project_tasks_rule">
<field name="name">Project/Task: project users: don't see non generic tasks</field>
<field name="model_id" ref="project.model_project_task"/>
<field name="domain_force">[
'&amp;', '&amp;',
('project_id', '!=', False),
('is_generic', '=', False),
('user_ids', 'not in', user.id),
]
</field>
<field name="groups" eval="[(4,ref('base.group_user')),(4,ref('project.group_project_user'))]"/>
<field name="perm_read" eval="0"/>
</record>
<record model="ir.rule" id="project_users_project_lead_rule">
<field name="name">Project/Task: project lead: see all tasks</field>
<field name="model_id" ref="project.model_project_task"/>
<field name="domain_force">[
'&amp;', '&amp;', '&amp;',
('project_id', '!=', False),
('project_id.project_lead', '=', user.id),
'|', ('is_generic', '=', True), ('is_generic', '=', False),
'|', ('user_ids', 'in', user.id), ('user_ids', 'not in', user.id)
]
</field>
<field name="groups" eval="[(4,ref('base.group_user')),(4,ref('project.group_project_user'))]"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="0"/>
</record>
<record model="ir.rule" id="user_task_availability_project_lead_rule">
<field name="name">Task Availability: project lead: see all user tasks</field>
<field name="model_id" ref="model_user_task_availability"/>
<field name="groups" eval="[(4,ref('base.group_user')),(4,ref('project.group_project_user'))]"/>
<field name="domain_force">[
'|', '|',
('project_id.project_lead', '=', user.id),
('user_id', '=', user.id),
('project_id.user_id', '=', user.id),
]
</field>
</record>
<!-- <record model="ir.rule" id="timesheet_users_normal_timesheets">-->
<!-- <field name="name">timesheet: users: see own tasks</field>-->
<!-- <field name="model_id" ref="analytic.model_account_analytic_line"/>-->
<!-- <field name="domain_force">[-->
<!-- '&amp;','&amp;', '&amp;', '&amp;', '&amp;','&amp;',-->
<!-- ('project_id.privacy_visibility','=','followers'),-->
<!-- ('task_id', '!=', False),-->
<!-- ('project_id', '!=', False),-->
<!-- ('project_id.project_lead', '!=', user.id),-->
<!-- ('project_id.user_id', '!=', user.id),-->
<!-- ('user_id','not in',[user.id]),-->
<!-- '|',-->
<!-- '&amp;',-->
<!-- ('task_id.is_generic', '=', False),-->
<!-- ('task_id.user_ids', 'not in', [user.id]),-->
<!-- '&amp;',-->
<!-- ('task_id.is_generic', '=', True),-->
<!-- ('project_id.message_partner_ids', 'not in', [user.partner_id.id]),-->
<!-- ]</field>-->
<!-- <field name="groups" eval="[(4,ref('base.group_user')),(4,ref('project.group_project_user')),(4,ref('project_task_timesheet_extended.group_project_supervisor')),(4,ref('hr_timesheet.group_hr_timesheet_user')),(4,ref('hr_timesheet.group_hr_timesheet_approver'))]"/>-->
<!-- <field name="perm_read" eval="0"/>-->
<!-- <field name="perm_unlink" eval="1"/>-->
<!-- <field name="perm_write" eval="1"/>-->
<!-- <field name="perm_create" eval="1"/>-->
<!-- </record>-->
<!-- <record model="ir.rule" id="timesheet_users_non_generic_timesheets">-->
<!-- <field name="name">timesheet: users: see own tasks</field>-->
<!-- <field name="model_id" ref="analytic.model_account_analytic_line"/>-->
<!-- <field name="domain_force">[-->
<!-- ('project_id.privacy_visibility','!=','followers'),-->
<!-- ('task_id', '!=', False),-->
<!-- ('project_id', '!=', False),-->
<!-- ('project_id.project_lead', '!=', user.id),-->
<!-- ('project_id.user_id', '!=', user.id),-->
<!-- ('task_id.is_generic', '=', False),-->
<!-- ('task_id.user_ids', 'not in', [user.id]),-->
<!-- ('user_id','not in',[user.id]),-->
<!-- ]</field>-->
<!-- <field name="groups" eval="[(4,ref('base.group_user')),(4,ref('project.group_project_user')),(4,ref('project_task_timesheet_extended.group_project_supervisor')),(4,ref('hr_timesheet.group_hr_timesheet_user')),(4,ref('hr_timesheet.group_hr_timesheet_approver'))]"/>-->
<!-- <field name="perm_read" eval="0"/>-->
<!-- <field name="perm_unlink" eval="1"/>-->
<!-- <field name="perm_write" eval="1"/>-->
<!-- <field name="perm_create" eval="1"/>-->
<!-- </record>-->
<!-- <record model="ir.rule" id="timesheet_team_lead_normal_timesheets">-->
<!-- <field name="name">timesheet: Lead: see related tasks</field>-->
<!-- <field name="model_id" ref="analytic.model_account_analytic_line"/>-->
<!-- <field name="domain_force">[-->
<!-- '&amp;', '&amp;', '&amp;',-->
<!-- ('project_id', '!=', False),-->
<!-- ('project_id.project_lead', '=', user.id),-->
<!-- '|', ('task_id.is_generic', '=', True), ('task_id.is_generic', '=', False),-->
<!-- '|', ('task_id.user_ids', 'in', user.id), ('task_id.user_ids', 'not in', user.id)-->
<!-- ]-->
<!-- </field>-->
<!-- <field name="perm_read" eval="1"/>-->
<!-- <field name="perm_write" eval="1"/>-->
<!-- <field name="perm_create" eval="1"/>-->
<!-- <field name="perm_unlink" eval="0"/>-->
<!-- </record>-->
</data>
<data>
</data>
</odoo>

View File

@ -0,0 +1,34 @@
/** @odoo-module **/
import {
Many2ManyTagsAvatarUserField,
many2ManyTagsAvatarUserField,
} from "@mail/views/web/fields/many2many_avatar_user_field/many2many_avatar_user_field";
import { registry } from "@web/core/registry";
export class InvolvedAssigneeAvatarUserField extends Many2ManyTagsAvatarUserField {
getDomain() {
const involved = this.props.record.data.involved_user_ids;
const involvedIds = involved?.records?.map((record) => record.resId).filter(Boolean) || [];
if (involvedIds.length) {
return [["id", "in", involvedIds]];
}
return [["id", "=", false]];
}
}
export const involvedAssigneeAvatarUserField = {
...many2ManyTagsAvatarUserField,
component: InvolvedAssigneeAvatarUserField,
extractProps(fieldInfo, dynamicInfo) {
const props = many2ManyTagsAvatarUserField.extractProps(fieldInfo, dynamicInfo);
return {
...props,
canCreate: false,
canQuickCreate: false,
canCreateEdit: false,
};
},
};
registry.category("fields").add("involved_assignee_avatar_user", involvedAssigneeAvatarUserField);

View File

@ -41,29 +41,31 @@
[('id', 'in', parent.allowed_employee_ids)]
</attribute>
</xpath>
<!-- <xpath expr="//field[@name='timesheet_ids']//field[@name='stage_id']"-->
<!-- position="attributes">-->
<!-- <attribute name="domain">-->
<!-- [('assigned_user_ids.employee_id', '=', employee_id)]-->
<!-- </attribute>-->
<!-- </xpath>-->
<!-- <xpath expr="//field[@name='timesheet_ids']//field[@name='stage_id']"-->
<!-- position="attributes">-->
<!-- <attribute name="domain">-->
<!-- [('assigned_user_ids.employee_id', '=', employee_id)]-->
<!-- </attribute>-->
<!-- </xpath>-->
<xpath expr="//div[hasclass('oe_title','pe-0')]" position="after">
<group>
<h1><field name="sequence_name" readonly="1"/></h1>
</group>
</xpath>
<!-- <xpath expr="//field[@name='user_ids']" position="before">-->
<!-- <field name="assigned_team"/>-->
<!-- </xpath>-->
<!-- <xpath expr="//field[@name='user_ids']" position="before">-->
<!-- <field name="assigned_team"/>-->
<!-- </xpath>-->
<xpath expr="//field[@name='user_ids']" position="after">
<field name="is_generic" readonly="not has_supervisor_access"/>
<field name="record_paused" invisible="1"/>
<field name="model_id" readonly="not has_supervisor_access" options="{'no_open': True}"/>
</xpath>
<!-- <xpath expr="//field[@name='allocated_hours']" position="after">-->
<!-- <field name="estimated_hours"/>-->
<!-- <field name="actual_hours"/>-->
<!-- </xpath>-->
<!-- <xpath expr="//field[@name='allocated_hours']" position="after">-->
<!-- <field name="estimated_hours"/>-->
<!-- <field name="actual_hours"/>-->
<!-- </xpath>-->
<xpath expr="//sheet/notebook" position="inside">
<page string="Assignees Timelines" invisible="not show_approval_flow">
@ -132,7 +134,7 @@
<div class="d-flex align-items-center">
<i class="fa fa-exclamation-triangle text-danger me-2" role="img" title="Deadline Issue"/>
Based on the timelines, the deadline can't be met
Based on the timelines, the deadline can't be met
</div>
<div class="d-flex align-items-center">
@ -143,16 +145,17 @@
</div>
<!-- <div>-->
<!-- <field name="suggested_deadline" invisible="not suggested_deadline or not show_approval_flow or not timelines_requested"/>-->
<!-- <field name="is_suggested_deadline_warning" invisible="1"/>-->
<!-- <label for="suggested_deadline"-->
<!-- class="text-warning"-->
<!-- invisible="not is_suggested_deadline_warning">-->
<!-- ⚠ Based on the timelines, the deadline can't be met-->
<!-- </label>-->
<!-- </div>-->
<field name="estimated_hours" widget="timesheet_uom_no_toggle" readonly="show_approval_flow and timelines_requested"/>
<!-- <div>-->
<!-- <field name="suggested_deadline" invisible="not suggested_deadline or not show_approval_flow or not timelines_requested"/>-->
<!-- <field name="is_suggested_deadline_warning" invisible="1"/>-->
<!-- <label for="suggested_deadline"-->
<!-- class="text-warning"-->
<!-- invisible="not is_suggested_deadline_warning">-->
<!-- Based on the timelines, the deadline can't be met -->
<!-- </label>-->
<!-- </div>-->
<field name="estimated_hours" widget="timesheet_uom_no_toggle"
readonly="show_approval_flow and timelines_requested"/>
<field name="actual_hours" widget="timesheet_uom_no_toggle"/>
<field name="is_suggested_deadline_warning" />
</xpath>

View File

@ -34,7 +34,7 @@
<!-- </div>-->
<!-- <div name="project_id">-->
<!-- <strong>Project </strong>-->
<!-- <strong>Project —</strong>-->
<!-- <t t-if="project_id" t-esc="project_id[1]"/>-->
<!-- <t t-else="">-->
<!-- <span class="fst-italic text-muted">-->
@ -45,7 +45,7 @@
<!-- </div>-->
<!-- <div t-if="show_approval_flow and current_stage_performance">-->
<!-- <strong>Performance </strong>-->
<!-- <strong>Performance —</strong>-->
<!-- <t t-if="current_stage_performance == 'good'">-->
<!-- <span class="text-success">-->
<!-- <i class="fa fa-smile-o"></i>-->
@ -67,21 +67,21 @@
<!-- </div>-->
<!-- <div t-if="allow_milestones and milestone_id" groups="project.group_project_milestone">-->
<!-- <strong>Milestone </strong>-->
<!-- <strong>Milestone —</strong>-->
<!-- <t t-esc="milestone_id[1]"/>-->
<!-- </div>-->
<!-- <div t-if="user_names">-->
<!-- <strong>Assignees </strong>-->
<!-- <strong>Assignees —</strong>-->
<!-- <t t-esc="user_names"/>-->
<!-- </div>-->
<!-- <div t-if="partner_id">-->
<!-- <strong>Customer </strong>-->
<!-- <strong>Customer —</strong>-->
<!-- <t t-esc="partner_id[1]"/>-->
<!-- </div>-->
<!-- <div t-if="show_approval_flow">-->
<!-- <strong>Timeline </strong>-->
<!-- <strong>Timeline —</strong>-->
<!-- <t t-esc="estimated_hours"/>-->
<!-- hours estimated /-->
<!-- <t t-esc="actual_hours"/>-->
@ -89,7 +89,7 @@
<!-- </div>-->
<!-- <div t-if="project_id" name="allocated_hours">-->
<!-- <strong>Allocated Time </strong>-->
<!-- <strong>Allocated Time —</strong>-->
<!-- <t t-esc="allocated_hours"/>-->
<!-- </div>-->
@ -201,7 +201,7 @@
<div class="mt-2">
<div name="project_id">
<strong>Project </strong>
<strong>Project —</strong>
<t t-if="project_id" t-esc="project_id[1]"/>
<t t-else="">
<span class="fst-italic text-muted">
@ -211,7 +211,7 @@
</div>
<div t-if="show_approval_flow and current_stage_performance and not is_completed" class="mt-1">
<strong>Performance </strong>
<strong>Performance —</strong>
<t t-if="current_stage_performance == 'good'">
<span class="text-success">
<i class="fa fa-smile-o"></i> Good (Actual &lt; Estimated)
@ -230,24 +230,24 @@
</div>
<div t-if="allow_milestones and milestone_id" groups="project.group_project_milestone" class="mt-1">
<strong>Milestone </strong> <t t-esc="milestone_id[1]"/>
<strong>Milestone —</strong> <t t-esc="milestone_id[1]"/>
</div>
<div t-if="user_names" class="mt-1">
<strong>Assignees </strong> <t t-esc="user_names"/>
<strong>Assignees —</strong> <t t-esc="user_names"/>
</div>
<div t-if="partner_id" class="mt-1">
<strong>Customer </strong> <t t-esc="partner_id[1]"/>
<strong>Customer —</strong> <t t-esc="partner_id[1]"/>
</div>
<div t-if="show_approval_flow and not is_completed" class="mt-1">
<strong>Timeline </strong>
<strong>Timeline —</strong>
<t t-esc="estimated_hours"/> hours estimated /
<t t-esc="actual_hours"/> hours actual
</div>
<div t-if="project_id" name="allocated_hours" class="mt-1">
<strong>Allocated Time </strong> <t t-esc="allocated_hours"/>
<strong>Allocated Time —</strong> <t t-esc="allocated_hours"/>
</div>
<div class="mt-2">
@ -302,7 +302,6 @@
<footer class="mt-3">
<button name="action_unschedule_task" type="object" string="Unschedule" class="btn btn-sm btn-secondary"/>
<button name="button_update_assignees" type="object" string="Update Assignees" class="btn btn-sm btn-primary"/>
</footer>
</div>

View File

@ -412,7 +412,7 @@ token.QWEB = token.NT_OFFSET - 1
token.tok_name[token.QWEB] = 'QWEB'
# security safe eval opcodes for generated expression validation, used in `_compile_expr`
#security safe eval opcodes for generated expression validation, used in `_compile_expr`
_SAFE_QWEB_OPCODES = _EXPR_OPCODES.union(to_opcodes([
'MAKE_FUNCTION', 'CALL_FUNCTION', 'CALL_FUNCTION_KW', 'CALL_FUNCTION_EX',
'CALL_METHOD', 'LOAD_METHOD',