project modifications
This commit is contained in:
parent
f6bfd46f2c
commit
c2e33753bb
|
|
@ -103,7 +103,7 @@ class ImBus(models.Model):
|
||||||
"""Low-level method to send ``notification_type`` and ``message`` to ``target``.
|
"""Low-level method to send ``notification_type`` and ``message`` to ``target``.
|
||||||
|
|
||||||
Using ``_bus_send()`` from ``bus.listener.mixin`` is recommended for simplicity and
|
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
|
When using ``_sendone`` directly, ``target`` (if str) should not be guessable by an
|
||||||
attacker.
|
attacker.
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
'version': '1.0',
|
'version': '1.0',
|
||||||
'category': 'Accounting/Localizations/Point of Sale',
|
'category': 'Accounting/Localizations/Point of Sale',
|
||||||
'description': """
|
'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.
|
Install it if you use the Point of Sale app to sell to individuals.
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ class Survey(http.Controller):
|
||||||
|
|
||||||
def _check_validity(self, survey_token, answer_token, ensure_token=True, check_partner=True):
|
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
|
""" 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
|
allowing further manipulation of validity issues
|
||||||
|
|
||||||
* survey_wrong: survey does not exist;
|
* survey_wrong: survey does not exist;
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
},
|
},
|
||||||
'installable': True,
|
'installable': True,
|
||||||
'data': [
|
'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/website_security.xml',
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'data/image_library.xml',
|
'data/image_library.xml',
|
||||||
|
|
|
||||||
|
|
@ -49,9 +49,9 @@ Key Features:
|
||||||
'view/maintenance_support.xml',
|
'view/maintenance_support.xml',
|
||||||
'view/project_closer.xml',
|
'view/project_closer.xml',
|
||||||
'view/project_actual_costings.xml',
|
'view/project_actual_costings.xml',
|
||||||
|
'view/project_task.xml',
|
||||||
'view/project.xml',
|
'view/project.xml',
|
||||||
'view/project_portfolio.xml',
|
'view/project_portfolio.xml',
|
||||||
'view/project_task.xml',
|
|
||||||
'view/timesheets.xml',
|
'view/timesheets.xml',
|
||||||
'view/pro_task_gantt.xml',
|
'view/pro_task_gantt.xml',
|
||||||
'view/user_availability.xml',
|
'view/user_availability.xml',
|
||||||
|
|
@ -61,9 +61,10 @@ Key Features:
|
||||||
'view/stage_approval_wizard.xml',
|
'view/stage_approval_wizard.xml',
|
||||||
],
|
],
|
||||||
'assets': {
|
'assets': {
|
||||||
'web.assets_backend':{
|
'web.assets_backend': [
|
||||||
'project_task_timesheet_extended/static/src/css/delopyment.css'
|
'project_task_timesheet_extended/static/src/css/delopyment.css',
|
||||||
}
|
'project_task_timesheet_extended/static/src/js/involved_assignee_avatar_user_field.js',
|
||||||
|
]
|
||||||
},
|
},
|
||||||
'installable': True,
|
'installable': True,
|
||||||
'application': False,
|
'application': False,
|
||||||
|
|
|
||||||
|
|
@ -263,4 +263,5 @@
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
</data>
|
</data>
|
||||||
|
<function model="project.task" name="_sync_all_involved_assignees_from_timelines"/>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
|
|
@ -120,14 +120,18 @@ def post_init_hook(env):
|
||||||
'|',
|
'|',
|
||||||
'&',
|
'&',
|
||||||
('task_id.is_generic', '=', False),
|
('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),
|
('task_id.is_generic', '=', True),
|
||||||
('user_id.partner_id', 'in', 'project_id.message_partner_ids'),
|
('user_id.partner_id', 'in', 'project_id.message_partner_ids'),
|
||||||
'&', '&',
|
'&', '&',
|
||||||
('project_id.privacy_visibility', '!=', 'followers'),
|
('project_id.privacy_visibility', '!=', 'followers'),
|
||||||
('task_id.is_generic', '=', False),
|
('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')
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -333,8 +333,6 @@ class ProjectProject(models.Model):
|
||||||
users_list = list()
|
users_list = list()
|
||||||
if project.assign_approval_flow:
|
if project.assign_approval_flow:
|
||||||
users_list.extend(project.project_stages.involved_users.ids)
|
users_list.extend(project.project_stages.involved_users.ids)
|
||||||
else:
|
|
||||||
users_list.extend(project.showable_stage_ids.user_ids.ids)
|
|
||||||
|
|
||||||
if project.project_sponsor:
|
if project.project_sponsor:
|
||||||
users_list.append(project.project_sponsor.id)
|
users_list.append(project.project_sponsor.id)
|
||||||
|
|
|
||||||
|
|
@ -167,12 +167,12 @@ class projectTask(models.Model):
|
||||||
task.assignee_domain_ids = all_internal_users
|
task.assignee_domain_ids = all_internal_users
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# # GENERIC → all internal
|
# # GENERIC: all internal
|
||||||
# if getattr(task, 'is_generic', False):
|
# if getattr(task, 'is_generic', False):
|
||||||
# task.assignee_domain_ids = all_internal_users
|
# task.assignee_domain_ids = all_internal_users
|
||||||
# continue
|
# continue
|
||||||
|
|
||||||
# PRIVATE → invited users only
|
# PRIVATE: invited users only
|
||||||
if task.project_id.privacy_visibility == 'followers':
|
if task.project_id.privacy_visibility == 'followers':
|
||||||
task.assignee_domain_ids = (
|
task.assignee_domain_ids = (
|
||||||
task.project_id.message_partner_ids
|
task.project_id.message_partner_ids
|
||||||
|
|
@ -196,18 +196,18 @@ class projectTask(models.Model):
|
||||||
for task in self:
|
for task in self:
|
||||||
employees = Employee.browse()
|
employees = Employee.browse()
|
||||||
|
|
||||||
# 1️⃣ GENERIC TASK
|
# GENERIC TASK
|
||||||
if task.is_generic and task.project_id:
|
if task.is_generic and task.project_id:
|
||||||
project = task.project_id
|
project = task.project_id
|
||||||
|
|
||||||
# 🔐 Private → followers only
|
# Private: followers only
|
||||||
if project.privacy_visibility == 'followers':
|
if project.privacy_visibility == 'followers':
|
||||||
users = (
|
users = (
|
||||||
project.message_partner_ids
|
project.message_partner_ids
|
||||||
.mapped('user_ids')
|
.mapped('user_ids')
|
||||||
.filtered(lambda u: u and not u.share)
|
.filtered(lambda u: u and not u.share)
|
||||||
)
|
)
|
||||||
# 🌍 Internal / Public → all internal users
|
# Internal / Public: all internal users
|
||||||
else:
|
else:
|
||||||
users = self.env['res.users'].search([
|
users = self.env['res.users'].search([
|
||||||
('share', '=', False),
|
('share', '=', False),
|
||||||
|
|
@ -216,7 +216,7 @@ class projectTask(models.Model):
|
||||||
|
|
||||||
employees = users.mapped('employee_id').filtered(lambda e: e)
|
employees = users.mapped('employee_id').filtered(lambda e: e)
|
||||||
|
|
||||||
# 2️⃣ NORMAL TASK → task assignees only
|
# NORMAL TASK: assignees and involved collaborators
|
||||||
else:
|
else:
|
||||||
employees = (
|
employees = (
|
||||||
task.user_ids
|
task.user_ids
|
||||||
|
|
@ -393,7 +393,7 @@ class projectTask(models.Model):
|
||||||
if start_dt.tzinfo is None:
|
if start_dt.tzinfo is None:
|
||||||
start_dt = pytz.UTC.localize(start_dt)
|
start_dt = pytz.UTC.localize(start_dt)
|
||||||
|
|
||||||
# Convert UTC → calendar timezone
|
# Convert UTC to calendar timezone
|
||||||
start_dt_tz = start_dt.astimezone(tz)
|
start_dt_tz = start_dt.astimezone(tz)
|
||||||
|
|
||||||
# Call plan_hours
|
# Call plan_hours
|
||||||
|
|
@ -460,7 +460,7 @@ class projectTask(models.Model):
|
||||||
task.suggested_deadline.strftime('%Y-%m-%d %H:%M') if task.suggested_deadline else _('Not available')
|
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):
|
def _compute_has_supervisor_access(self):
|
||||||
administrative_users = self.env['project.role'].search([
|
administrative_users = self.env['project.role'].search([
|
||||||
('role_level', '=', 'administrative')
|
('role_level', '=', 'administrative')
|
||||||
|
|
@ -478,26 +478,13 @@ class projectTask(models.Model):
|
||||||
|
|
||||||
stages = project.type_ids.sorted("sequence")
|
stages = project.type_ids.sorted("sequence")
|
||||||
|
|
||||||
if not stages:
|
if first_stage:
|
||||||
continue
|
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 = (
|
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):
|
||||||
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
|
|
||||||
)
|
|
||||||
):
|
|
||||||
task.has_supervisor_access = True
|
task.has_supervisor_access = True
|
||||||
|
|
||||||
@api.depends('assignees_timelines.estimated_time', 'show_approval_flow')
|
@api.depends('assignees_timelines.estimated_time', 'show_approval_flow')
|
||||||
|
|
@ -637,11 +624,11 @@ class projectTask(models.Model):
|
||||||
task.show_approval_button = True
|
task.show_approval_button = True
|
||||||
task.show_refuse_button = True # both approve & refuse in review state
|
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):
|
elif not assigned_to and (responsible_lead == user or project_manager == user):
|
||||||
task.show_approval_button = True
|
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 (
|
elif (
|
||||||
assigned_to
|
assigned_to
|
||||||
and assigned_to == responsible_lead
|
and assigned_to == responsible_lead
|
||||||
|
|
@ -797,7 +784,7 @@ class projectTask(models.Model):
|
||||||
task.stage_id = n_stage
|
task.stage_id = n_stage
|
||||||
task.approval_status = "approved"
|
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,
|
current_stage.name,
|
||||||
self.env.user.employee_id.name,
|
self.env.user.employee_id.name,
|
||||||
n_stage.name)
|
n_stage.name)
|
||||||
|
|
@ -838,9 +825,9 @@ class projectTask(models.Model):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
task.approval_status = "approved"
|
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,
|
current_stage.name,
|
||||||
self.env.user.employee_id.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
|
# Optional: find previous stage if you want to send back
|
||||||
stage = task.assignees_timelines.filtered(lambda s: s.stage_id == task.stage_id)
|
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,
|
current_stage.name,
|
||||||
self.env.user.employee_id.name,
|
self.env.user.employee_id.name,
|
||||||
reason)
|
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."
|
"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:
|
for task in self:
|
||||||
if task.assignees_timelines:
|
if task.is_generic:
|
||||||
users_list = list(
|
continue
|
||||||
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)]
|
|
||||||
|
|
||||||
# Post to project channel about assignee update
|
timeline_user_ids = set(
|
||||||
channel_message = _("Assignees updated for task %s") % (task.sequence_name or task.name)
|
task.assignees_timelines.responsible_lead.ids
|
||||||
task._post_to_project_channel(channel_message)
|
+ 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):
|
def _fetch_planning_overlap(self, additional_domain=None):
|
||||||
use_timeline_logic = any(
|
use_timeline_logic = any(
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -41,29 +41,31 @@
|
||||||
[('id', 'in', parent.allowed_employee_ids)]
|
[('id', 'in', parent.allowed_employee_ids)]
|
||||||
</attribute>
|
</attribute>
|
||||||
</xpath>
|
</xpath>
|
||||||
<!-- <xpath expr="//field[@name='timesheet_ids']//field[@name='stage_id']"-->
|
<!-- <xpath expr="//field[@name='timesheet_ids']//field[@name='stage_id']"-->
|
||||||
<!-- position="attributes">-->
|
<!-- position="attributes">-->
|
||||||
<!-- <attribute name="domain">-->
|
<!-- <attribute name="domain">-->
|
||||||
<!-- [('assigned_user_ids.employee_id', '=', employee_id)]-->
|
<!-- [('assigned_user_ids.employee_id', '=', employee_id)]-->
|
||||||
<!-- </attribute>-->
|
<!-- </attribute>-->
|
||||||
<!-- </xpath>-->
|
<!-- </xpath>-->
|
||||||
<xpath expr="//div[hasclass('oe_title','pe-0')]" position="after">
|
<xpath expr="//div[hasclass('oe_title','pe-0')]" position="after">
|
||||||
<group>
|
<group>
|
||||||
<h1><field name="sequence_name" readonly="1"/></h1>
|
<h1><field name="sequence_name" readonly="1"/></h1>
|
||||||
</group>
|
</group>
|
||||||
</xpath>
|
</xpath>
|
||||||
<!-- <xpath expr="//field[@name='user_ids']" position="before">-->
|
<!-- <xpath expr="//field[@name='user_ids']" position="before">-->
|
||||||
<!-- <field name="assigned_team"/>-->
|
<!-- <field name="assigned_team"/>-->
|
||||||
<!-- </xpath>-->
|
<!-- </xpath>-->
|
||||||
|
|
||||||
|
|
||||||
<xpath expr="//field[@name='user_ids']" position="after">
|
<xpath expr="//field[@name='user_ids']" position="after">
|
||||||
<field name="is_generic" readonly="not has_supervisor_access"/>
|
<field name="is_generic" readonly="not has_supervisor_access"/>
|
||||||
<field name="record_paused" invisible="1"/>
|
<field name="record_paused" invisible="1"/>
|
||||||
<field name="model_id" readonly="not has_supervisor_access" options="{'no_open': True}"/>
|
<field name="model_id" readonly="not has_supervisor_access" options="{'no_open': True}"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
<!-- <xpath expr="//field[@name='allocated_hours']" position="after">-->
|
<!-- <xpath expr="//field[@name='allocated_hours']" position="after">-->
|
||||||
<!-- <field name="estimated_hours"/>-->
|
<!-- <field name="estimated_hours"/>-->
|
||||||
<!-- <field name="actual_hours"/>-->
|
<!-- <field name="actual_hours"/>-->
|
||||||
<!-- </xpath>-->
|
<!-- </xpath>-->
|
||||||
|
|
||||||
<xpath expr="//sheet/notebook" position="inside">
|
<xpath expr="//sheet/notebook" position="inside">
|
||||||
<page string="Assignees Timelines" invisible="not show_approval_flow">
|
<page string="Assignees Timelines" invisible="not show_approval_flow">
|
||||||
|
|
@ -132,7 +134,7 @@
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
|
|
||||||
<i class="fa fa-exclamation-triangle text-danger me-2" role="img" title="Deadline Issue"/>
|
<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>
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
|
|
@ -143,16 +145,17 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div>-->
|
<!-- <div>-->
|
||||||
<!-- <field name="suggested_deadline" invisible="not suggested_deadline or not show_approval_flow or not timelines_requested"/>-->
|
<!-- <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"/>-->
|
<!-- <field name="is_suggested_deadline_warning" invisible="1"/>-->
|
||||||
<!-- <label for="suggested_deadline"-->
|
<!-- <label for="suggested_deadline"-->
|
||||||
<!-- class="text-warning"-->
|
<!-- class="text-warning"-->
|
||||||
<!-- invisible="not is_suggested_deadline_warning">-->
|
<!-- invisible="not is_suggested_deadline_warning">-->
|
||||||
<!-- ⚠ Based on the timelines, the deadline can't be met-->
|
<!-- Based on the timelines, the deadline can't be met -->
|
||||||
<!-- </label>-->
|
<!-- </label>-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
<field name="estimated_hours" widget="timesheet_uom_no_toggle" readonly="show_approval_flow and timelines_requested"/>
|
<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="actual_hours" widget="timesheet_uom_no_toggle"/>
|
||||||
<field name="is_suggested_deadline_warning" />
|
<field name="is_suggested_deadline_warning" />
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
|
|
||||||
<!-- <div name="project_id">-->
|
<!-- <div name="project_id">-->
|
||||||
<!-- <strong>Project —</strong>-->
|
<!-- <strong>Project —</strong>-->
|
||||||
<!-- <t t-if="project_id" t-esc="project_id[1]"/>-->
|
<!-- <t t-if="project_id" t-esc="project_id[1]"/>-->
|
||||||
<!-- <t t-else="">-->
|
<!-- <t t-else="">-->
|
||||||
<!-- <span class="fst-italic text-muted">-->
|
<!-- <span class="fst-italic text-muted">-->
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
|
|
||||||
<!-- <div t-if="show_approval_flow and current_stage_performance">-->
|
<!-- <div t-if="show_approval_flow and current_stage_performance">-->
|
||||||
<!-- <strong>Performance —</strong>-->
|
<!-- <strong>Performance —</strong>-->
|
||||||
<!-- <t t-if="current_stage_performance == 'good'">-->
|
<!-- <t t-if="current_stage_performance == 'good'">-->
|
||||||
<!-- <span class="text-success">-->
|
<!-- <span class="text-success">-->
|
||||||
<!-- <i class="fa fa-smile-o"></i>-->
|
<!-- <i class="fa fa-smile-o"></i>-->
|
||||||
|
|
@ -67,21 +67,21 @@
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
|
|
||||||
<!-- <div t-if="allow_milestones and milestone_id" groups="project.group_project_milestone">-->
|
<!-- <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]"/>-->
|
<!-- <t t-esc="milestone_id[1]"/>-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
|
|
||||||
<!-- <div t-if="user_names">-->
|
<!-- <div t-if="user_names">-->
|
||||||
<!-- <strong>Assignees —</strong>-->
|
<!-- <strong>Assignees —</strong>-->
|
||||||
<!-- <t t-esc="user_names"/>-->
|
<!-- <t t-esc="user_names"/>-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
<!-- <div t-if="partner_id">-->
|
<!-- <div t-if="partner_id">-->
|
||||||
<!-- <strong>Customer —</strong>-->
|
<!-- <strong>Customer —</strong>-->
|
||||||
<!-- <t t-esc="partner_id[1]"/>-->
|
<!-- <t t-esc="partner_id[1]"/>-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
|
|
||||||
<!-- <div t-if="show_approval_flow">-->
|
<!-- <div t-if="show_approval_flow">-->
|
||||||
<!-- <strong>Timeline —</strong>-->
|
<!-- <strong>Timeline —</strong>-->
|
||||||
<!-- <t t-esc="estimated_hours"/>-->
|
<!-- <t t-esc="estimated_hours"/>-->
|
||||||
<!-- hours estimated /-->
|
<!-- hours estimated /-->
|
||||||
<!-- <t t-esc="actual_hours"/>-->
|
<!-- <t t-esc="actual_hours"/>-->
|
||||||
|
|
@ -89,7 +89,7 @@
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
|
|
||||||
<!-- <div t-if="project_id" name="allocated_hours">-->
|
<!-- <div t-if="project_id" name="allocated_hours">-->
|
||||||
<!-- <strong>Allocated Time —</strong>-->
|
<!-- <strong>Allocated Time —</strong>-->
|
||||||
<!-- <t t-esc="allocated_hours"/>-->
|
<!-- <t t-esc="allocated_hours"/>-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
|
|
||||||
|
|
@ -201,7 +201,7 @@
|
||||||
|
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<div name="project_id">
|
<div name="project_id">
|
||||||
<strong>Project —</strong>
|
<strong>Project —</strong>
|
||||||
<t t-if="project_id" t-esc="project_id[1]"/>
|
<t t-if="project_id" t-esc="project_id[1]"/>
|
||||||
<t t-else="">
|
<t t-else="">
|
||||||
<span class="fst-italic text-muted">
|
<span class="fst-italic text-muted">
|
||||||
|
|
@ -211,7 +211,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div t-if="show_approval_flow and current_stage_performance and not is_completed" class="mt-1">
|
<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'">
|
<t t-if="current_stage_performance == 'good'">
|
||||||
<span class="text-success">
|
<span class="text-success">
|
||||||
<i class="fa fa-smile-o"></i> Good (Actual < Estimated)
|
<i class="fa fa-smile-o"></i> Good (Actual < Estimated)
|
||||||
|
|
@ -230,24 +230,24 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div t-if="allow_milestones and milestone_id" groups="project.group_project_milestone" class="mt-1">
|
<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>
|
||||||
|
|
||||||
<div t-if="user_names" class="mt-1">
|
<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>
|
||||||
<div t-if="partner_id" class="mt-1">
|
<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>
|
||||||
|
|
||||||
<div t-if="show_approval_flow and not is_completed" class="mt-1">
|
<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="estimated_hours"/> hours estimated /
|
||||||
<t t-esc="actual_hours"/> hours actual
|
<t t-esc="actual_hours"/> hours actual
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div t-if="project_id" name="allocated_hours" class="mt-1">
|
<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>
|
||||||
|
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
|
|
@ -302,7 +302,6 @@
|
||||||
|
|
||||||
<footer class="mt-3">
|
<footer class="mt-3">
|
||||||
<button name="action_unschedule_task" type="object" string="Unschedule" class="btn btn-sm btn-secondary"/>
|
<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>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -412,7 +412,7 @@ token.QWEB = token.NT_OFFSET - 1
|
||||||
token.tok_name[token.QWEB] = 'QWEB'
|
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([
|
_SAFE_QWEB_OPCODES = _EXPR_OPCODES.union(to_opcodes([
|
||||||
'MAKE_FUNCTION', 'CALL_FUNCTION', 'CALL_FUNCTION_KW', 'CALL_FUNCTION_EX',
|
'MAKE_FUNCTION', 'CALL_FUNCTION', 'CALL_FUNCTION_KW', 'CALL_FUNCTION_EX',
|
||||||
'CALL_METHOD', 'LOAD_METHOD',
|
'CALL_METHOD', 'LOAD_METHOD',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue