[IMP] project: add personal task stages and multiple assignments
Adds personal stages to tasks. Users assigned to tasks will be able to have their own pipeline to handle their tasks independently of the project's pipeline. The assignee field on Tasks has also been changed to a Many2many to support multiple assignees on one task. The same table is used to store those information, essentially a triplet (task, user, stage), to ensure that: 1) personal stages only apply to tasks to which you are assigned to. 2) synchronizing to make sure that you don't have a personal stage for a task on which you are not assigned anymore. Alongside those changes, some minor changes have also been made: - Modified the task's tree view. - Renamed the Tasks menu to 'My Tasks'. - The default view is now the kanban view for the 'My Tasks' action. - project_id is not required anymore, tasks with no project are considered 'private', those tasks are only visible to those that are assigned to it. - Added tracking of both user_ids and depend_on_ids in the chatter. Closes odoo/odoo#74087 Task ID: 2398734
This commit is contained in:
@@ -42,7 +42,7 @@
|
|||||||
<filter string="My Team's Projects" name="my_team_projects" domain="[('project_id.user_id.employee_id.parent_id.user_id', '=', uid), ('project_id.user_id', '!=', uid), ('user_id', '!=', uid), ('user_id', '!=', False)]"/>
|
<filter string="My Team's Projects" name="my_team_projects" domain="[('project_id.user_id.employee_id.parent_id.user_id', '=', uid), ('project_id.user_id', '!=', uid), ('user_id', '!=', uid), ('user_id', '!=', False)]"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//filter[@name='my_tasks']" position="after">
|
<xpath expr="//filter[@name='my_tasks']" position="after">
|
||||||
<filter string="My Team's Tasks" name="my_team_tasks" domain="[('task_id.user_id.employee_id.parent_id.user_id', '=', uid)]" />
|
<filter string="My Team's Tasks" name="my_team_tasks" domain="[('task_id.user_ids.employee_id.parent_id.user_id', '=', uid)]" />
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
@@ -147,7 +147,7 @@
|
|||||||
<field name="model">project.task</field>
|
<field name="model">project.task</field>
|
||||||
<field name="inherit_id" ref="project.project_sharing_project_task_view_tree" />
|
<field name="inherit_id" ref="project.project_sharing_project_task_view_tree" />
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<field name="user_id" position="after">
|
<field name="user_ids" position="after">
|
||||||
<field name="allow_subtasks" invisible="1"/>
|
<field name="allow_subtasks" invisible="1"/>
|
||||||
<field name="allow_timesheets" invisible="1"/>
|
<field name="allow_timesheets" invisible="1"/>
|
||||||
<field name="planned_hours" widget="timesheet_uom_no_toggle" sum="Initially Planned Hours" optional="hide" attrs="{'column_invisible': [('allow_timesheets', '=', False)]}"/>
|
<field name="planned_hours" widget="timesheet_uom_no_toggle" sum="Initially Planned Hours" optional="hide" attrs="{'column_invisible': [('allow_timesheets', '=', False)]}"/>
|
||||||
|
|||||||
@@ -296,7 +296,7 @@
|
|||||||
<field name="inherit_id" ref="project.view_task_search_form"/>
|
<field name="inherit_id" ref="project.view_task_search_form"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//filter[@name='my_tasks']" position='after'>
|
<xpath expr="//filter[@name='my_tasks']" position='after'>
|
||||||
<filter string="My Team's Tasks" name="my_team_tasks" domain="[('user_id.employee_parent_id.user_id', '=', uid)]"/>
|
<filter string="My Team's Tasks" name="my_team_tasks" domain="[('user_ids.employee_parent_id.user_id', '=', uid)]"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//filter[@name='late']" position='after'>
|
<xpath expr="//filter[@name='late']" position='after'>
|
||||||
<filter string="Tasks in Overtime" name="overtime" domain="[('overtime', '>', 0)]"/>
|
<filter string="Tasks in Overtime" name="overtime" domain="[('overtime', '>', 0)]"/>
|
||||||
|
|||||||
@@ -88,10 +88,88 @@
|
|||||||
<field name="stage_id" ref="project.project_project_stage_2"/>
|
<field name="stage_id" ref="project.project_project_stage_2"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<!-- Personal Stages: Mitchell Admin-->
|
||||||
|
<record id="project_personal_stage_admin_0" model="project.task.type">
|
||||||
|
<field name="sequence">1</field>
|
||||||
|
<field name="name">Inbox</field>
|
||||||
|
<field name="user_id" ref="base.user_admin"/>
|
||||||
|
</record>
|
||||||
|
<record id="project_personal_stage_admin_1" model="project.task.type">
|
||||||
|
<field name="sequence">2</field>
|
||||||
|
<field name="name">Today</field>
|
||||||
|
<field name="user_id" ref="base.user_admin"/>
|
||||||
|
</record>
|
||||||
|
<record id="project_personal_stage_admin_2" model="project.task.type">
|
||||||
|
<field name="sequence">3</field>
|
||||||
|
<field name="name">This Week</field>
|
||||||
|
<field name="user_id" ref="base.user_admin"/>
|
||||||
|
</record>
|
||||||
|
<record id="project_personal_stage_admin_3" model="project.task.type">
|
||||||
|
<field name="sequence">4</field>
|
||||||
|
<field name="name">This Month</field>
|
||||||
|
<field name="user_id" ref="base.user_admin"/>
|
||||||
|
</record>
|
||||||
|
<record id="project_personal_stage_admin_4" model="project.task.type">
|
||||||
|
<field name="sequence">5</field>
|
||||||
|
<field name="name">Later</field>
|
||||||
|
<field name="user_id" ref="base.user_admin"/>
|
||||||
|
</record>
|
||||||
|
<record id="project_personal_stage_admin_5" model="project.task.type">
|
||||||
|
<field name="sequence">6</field>
|
||||||
|
<field name="name">Done</field>
|
||||||
|
<field name="fold" eval="True"/>
|
||||||
|
<field name="user_id" ref="base.user_admin"/>
|
||||||
|
</record>
|
||||||
|
<record id="project_personal_stage_admin_6" model="project.task.type">
|
||||||
|
<field name="sequence">7</field>
|
||||||
|
<field name="name">Canceled</field>
|
||||||
|
<field name="fold" eval="True"/>
|
||||||
|
<field name="user_id" ref="base.user_admin"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Personal Stages: Marc Demo -->
|
||||||
|
<record id="project_personal_stage_demo_0" model="project.task.type">
|
||||||
|
<field name="sequence">1</field>
|
||||||
|
<field name="name">Inbox</field>
|
||||||
|
<field name="user_id" ref="base.user_demo"/>
|
||||||
|
</record>
|
||||||
|
<record id="project_personal_stage_demo_1" model="project.task.type">
|
||||||
|
<field name="sequence">2</field>
|
||||||
|
<field name="name">Today</field>
|
||||||
|
<field name="user_id" ref="base.user_demo"/>
|
||||||
|
</record>
|
||||||
|
<record id="project_personal_stage_demo_2" model="project.task.type">
|
||||||
|
<field name="sequence">3</field>
|
||||||
|
<field name="name">This Week</field>
|
||||||
|
<field name="user_id" ref="base.user_demo"/>
|
||||||
|
</record>
|
||||||
|
<record id="project_personal_stage_demo_3" model="project.task.type">
|
||||||
|
<field name="sequence">4</field>
|
||||||
|
<field name="name">This Month</field>
|
||||||
|
<field name="user_id" ref="base.user_demo"/>
|
||||||
|
</record>
|
||||||
|
<record id="project_personal_stage_demo_4" model="project.task.type">
|
||||||
|
<field name="sequence">5</field>
|
||||||
|
<field name="name">Later</field>
|
||||||
|
<field name="user_id" ref="base.user_demo"/>
|
||||||
|
</record>
|
||||||
|
<record id="project_personal_stage_demo_5" model="project.task.type">
|
||||||
|
<field name="sequence">6</field>
|
||||||
|
<field name="name">Done</field>
|
||||||
|
<field name="fold" eval="True"/>
|
||||||
|
<field name="user_id" ref="base.user_demo"/>
|
||||||
|
</record>
|
||||||
|
<record id="project_personal_stage_demo_6" model="project.task.type">
|
||||||
|
<field name="sequence">7</field>
|
||||||
|
<field name="name">Canceled</field>
|
||||||
|
<field name="fold" eval="True"/>
|
||||||
|
<field name="user_id" ref="base.user_demo"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
<!-- Tasks -->
|
<!-- Tasks -->
|
||||||
<record id="project_task_1" model="project.task">
|
<record id="project_task_1" model="project.task">
|
||||||
<field name="planned_hours" eval="40.0"/>
|
<field name="planned_hours" eval="40.0"/>
|
||||||
<field name="user_id" ref="base.user_demo"/>
|
<field name="user_ids" eval="[(4, ref('base.user_demo'))]"/>
|
||||||
<field name="priority">0</field>
|
<field name="priority">0</field>
|
||||||
<field name="project_id" ref="project.project_project_1"/>
|
<field name="project_id" ref="project.project_project_1"/>
|
||||||
<field name="name">Meeting Room Furnitures</field>
|
<field name="name">Meeting Room Furnitures</field>
|
||||||
@@ -101,7 +179,7 @@
|
|||||||
</record>
|
</record>
|
||||||
<record id="project_task_2" model="project.task">
|
<record id="project_task_2" model="project.task">
|
||||||
<field name="planned_hours" eval="32.0"/>
|
<field name="planned_hours" eval="32.0"/>
|
||||||
<field name="user_id" ref="base.user_demo"/>
|
<field name="user_ids" eval="[(4, ref('base.user_demo'))]"/>
|
||||||
<field name="priority">0</field>
|
<field name="priority">0</field>
|
||||||
<field name="project_id" ref="project.project_project_1"/>
|
<field name="project_id" ref="project.project_project_1"/>
|
||||||
<field name="name">Lunch Room: kitchen</field>
|
<field name="name">Lunch Room: kitchen</field>
|
||||||
@@ -147,7 +225,7 @@
|
|||||||
|
|
||||||
<record id="project_task_3" model="project.task">
|
<record id="project_task_3" model="project.task">
|
||||||
<field name="planned_hours" eval="10.0"/>
|
<field name="planned_hours" eval="10.0"/>
|
||||||
<field name="user_id" ref="base.user_admin"/>
|
<field name="user_ids" eval="[(4, ref('base.user_admin'))]"/>
|
||||||
<field name="priority">0</field>
|
<field name="priority">0</field>
|
||||||
<field name="project_id" ref="project.project_project_1"/>
|
<field name="project_id" ref="project.project_project_1"/>
|
||||||
<field name="name">Noise Reduction</field>
|
<field name="name">Noise Reduction</field>
|
||||||
@@ -195,7 +273,7 @@
|
|||||||
|
|
||||||
<record id="project_task_4" model="project.task">
|
<record id="project_task_4" model="project.task">
|
||||||
<field name="planned_hours" eval="60.0"/>
|
<field name="planned_hours" eval="60.0"/>
|
||||||
<field name="user_id" ref="base.user_demo"/>
|
<field name="user_ids" eval="[(4, ref('base.user_demo'))]"/>
|
||||||
<field name="priority">0</field>
|
<field name="priority">0</field>
|
||||||
<field name="project_id" ref="project.project_project_1"/>
|
<field name="project_id" ref="project.project_project_1"/>
|
||||||
<field name="name">Black Chairs for managers</field>
|
<field name="name">Black Chairs for managers</field>
|
||||||
@@ -228,7 +306,7 @@
|
|||||||
|
|
||||||
<record id="project_task_5" model="project.task">
|
<record id="project_task_5" model="project.task">
|
||||||
<field name="planned_hours" eval="76.0"/>
|
<field name="planned_hours" eval="76.0"/>
|
||||||
<field name="user_id" ref="base.user_admin"/>
|
<field name="user_ids" eval="[(4, ref('base.user_admin'))]"/>
|
||||||
<field name="priority">1</field>
|
<field name="priority">1</field>
|
||||||
<field name="project_id" ref="project.project_project_1"/>
|
<field name="project_id" ref="project.project_project_1"/>
|
||||||
<field name="name">Room 1: Decoration</field>
|
<field name="name">Room 1: Decoration</field>
|
||||||
@@ -261,7 +339,7 @@
|
|||||||
|
|
||||||
<record id="project_task_6" model="project.task">
|
<record id="project_task_6" model="project.task">
|
||||||
<field name="planned_hours" eval="24.0"/>
|
<field name="planned_hours" eval="24.0"/>
|
||||||
<field name="user_id" ref="base.user_admin"/>
|
<field name="user_ids" eval="[(4, ref('base.user_admin'))]"/>
|
||||||
<field name="priority">1</field>
|
<field name="priority">1</field>
|
||||||
<field name="project_id" ref="project.project_project_1"/>
|
<field name="project_id" ref="project.project_project_1"/>
|
||||||
<field name="name">Room 2: Decoration</field>
|
<field name="name">Room 2: Decoration</field>
|
||||||
@@ -289,7 +367,7 @@
|
|||||||
|
|
||||||
<record id="project_task_7" model="project.task">
|
<record id="project_task_7" model="project.task">
|
||||||
<field name="planned_hours" eval="15.0"/>
|
<field name="planned_hours" eval="15.0"/>
|
||||||
<field name="user_id" ref="base.user_admin"/>
|
<field name="user_ids" eval="[(4, ref('base.user_admin'))]"/>
|
||||||
<field name="priority">1</field>
|
<field name="priority">1</field>
|
||||||
<field name="project_id" ref="project.project_project_1"/>
|
<field name="project_id" ref="project.project_project_1"/>
|
||||||
<field name="name">Energy Certificate</field>
|
<field name="name">Energy Certificate</field>
|
||||||
@@ -317,7 +395,7 @@
|
|||||||
|
|
||||||
<record id="project_task_8" model="project.task">
|
<record id="project_task_8" model="project.task">
|
||||||
<field name="planned_hours" eval="22.0"/>
|
<field name="planned_hours" eval="22.0"/>
|
||||||
<field name="user_id" ref="base.user_demo"/>
|
<field name="user_ids" eval="[(4, ref('base.user_demo'))]"/>
|
||||||
<field name="priority">1</field>
|
<field name="priority">1</field>
|
||||||
<field name="project_id" ref="project.project_project_2"/>
|
<field name="project_id" ref="project.project_project_2"/>
|
||||||
<field name="name">New portal system</field>
|
<field name="name">New portal system</field>
|
||||||
@@ -366,7 +444,7 @@
|
|||||||
|
|
||||||
<record id="project_task_9" model="project.task">
|
<record id="project_task_9" model="project.task">
|
||||||
<field name="planned_hours" eval="18.0"/>
|
<field name="planned_hours" eval="18.0"/>
|
||||||
<field name="user_id" ref="base.user_demo"/>
|
<field name="user_ids" eval="[(4, ref('base.user_demo'))]"/>
|
||||||
<field name="priority">1</field>
|
<field name="priority">1</field>
|
||||||
<field name="project_id" ref="project.project_project_2"/>
|
<field name="project_id" ref="project.project_project_2"/>
|
||||||
<field name="name">Document management</field>
|
<field name="name">Document management</field>
|
||||||
@@ -375,7 +453,7 @@
|
|||||||
</record>
|
</record>
|
||||||
<record id="project_task_10" model="project.task">
|
<record id="project_task_10" model="project.task">
|
||||||
<field name="planned_hours" eval="38.0"/>
|
<field name="planned_hours" eval="38.0"/>
|
||||||
<field name="user_id" ref="base.user_demo"/>
|
<field name="user_ids" eval="[(4, ref('base.user_demo'))]"/>
|
||||||
<field name="priority">1</field>
|
<field name="priority">1</field>
|
||||||
<field name="project_id" ref="project.project_project_2"/>
|
<field name="project_id" ref="project.project_project_2"/>
|
||||||
<field name="name">Social network integration</field>
|
<field name="name">Social network integration</field>
|
||||||
@@ -404,7 +482,7 @@
|
|||||||
|
|
||||||
<record id="project_task_11" model="project.task">
|
<record id="project_task_11" model="project.task">
|
||||||
<field name="planned_hours" eval="16.0"/>
|
<field name="planned_hours" eval="16.0"/>
|
||||||
<field name="user_id" ref="base.user_admin"/>
|
<field name="user_ids" eval="[(4, ref('base.user_admin'))]"/>
|
||||||
<field name="priority">1</field>
|
<field name="priority">1</field>
|
||||||
<field name="project_id" ref="project.project_project_2"/>
|
<field name="project_id" ref="project.project_project_2"/>
|
||||||
<field name="name">User interface improvements</field>
|
<field name="name">User interface improvements</field>
|
||||||
@@ -435,7 +513,7 @@
|
|||||||
|
|
||||||
<record id="project_task_12" model="project.task">
|
<record id="project_task_12" model="project.task">
|
||||||
<field name="planned_hours" eval="40.0"/>
|
<field name="planned_hours" eval="40.0"/>
|
||||||
<field name="user_id" ref="base.user_admin"/>
|
<field name="user_ids" eval="[(4, ref('base.user_admin'))]"/>
|
||||||
<field name="priority">1</field>
|
<field name="priority">1</field>
|
||||||
<field name="project_id" ref="project.project_project_2"/>
|
<field name="project_id" ref="project.project_project_2"/>
|
||||||
<field name="name">Planning and budget</field>
|
<field name="name">Planning and budget</field>
|
||||||
@@ -447,7 +525,7 @@
|
|||||||
<record id="project_task_19" model="project.task">
|
<record id="project_task_19" model="project.task">
|
||||||
<field name="planned_hours">24.0</field>
|
<field name="planned_hours">24.0</field>
|
||||||
<field name="stage_id" ref="project_stage_3"/>
|
<field name="stage_id" ref="project_stage_3"/>
|
||||||
<field name="user_id" eval="False"/>
|
<field name="user_ids" eval="False"/>
|
||||||
<field name="project_id" ref="project.project_project_2"/>
|
<field name="project_id" ref="project.project_project_2"/>
|
||||||
<field name="name">Basic outline</field>
|
<field name="name">Basic outline</field>
|
||||||
<field name="tag_ids" eval="[(6, 0, [
|
<field name="tag_ids" eval="[(6, 0, [
|
||||||
@@ -493,7 +571,7 @@
|
|||||||
|
|
||||||
<record id="project_task_20" model="project.task">
|
<record id="project_task_20" model="project.task">
|
||||||
<field name="planned_hours">42.0</field>
|
<field name="planned_hours">42.0</field>
|
||||||
<field name="user_id" eval="False"/>
|
<field name="user_ids" eval="False"/>
|
||||||
<field name="stage_id" ref="project_stage_0"/>
|
<field name="stage_id" ref="project_stage_0"/>
|
||||||
<field name="project_id" ref="project.project_project_2"/>
|
<field name="project_id" ref="project.project_project_2"/>
|
||||||
<field name="name">Create new components</field>
|
<field name="name">Create new components</field>
|
||||||
@@ -502,7 +580,7 @@
|
|||||||
|
|
||||||
<record id="project_task_21" model="project.task">
|
<record id="project_task_21" model="project.task">
|
||||||
<field name="planned_hours">14.0</field>
|
<field name="planned_hours">14.0</field>
|
||||||
<field name="user_id" eval="False"/>
|
<field name="user_ids" eval="False"/>
|
||||||
<field name="stage_id" ref="project_stage_1"/>
|
<field name="stage_id" ref="project_stage_1"/>
|
||||||
<field name="project_id" ref="project.project_project_2"/>
|
<field name="project_id" ref="project.project_project_2"/>
|
||||||
<field name="name">Useablity review</field>
|
<field name="name">Useablity review</field>
|
||||||
@@ -532,7 +610,7 @@
|
|||||||
<record id="project_task_22" model="project.task">
|
<record id="project_task_22" model="project.task">
|
||||||
<field name="planned_hours">12.0</field>
|
<field name="planned_hours">12.0</field>
|
||||||
<field name="stage_id" ref="project_stage_1"/>
|
<field name="stage_id" ref="project_stage_1"/>
|
||||||
<field name="user_id" eval="False"/>
|
<field name="user_ids" eval="False"/>
|
||||||
<field name="priority">0</field>
|
<field name="priority">0</field>
|
||||||
<field name="project_id" ref="project.project_project_2"/>
|
<field name="project_id" ref="project.project_project_2"/>
|
||||||
<field name="name">Customer analysis + Architecture</field>
|
<field name="name">Customer analysis + Architecture</field>
|
||||||
@@ -562,7 +640,7 @@
|
|||||||
<field name="sequence">17</field>
|
<field name="sequence">17</field>
|
||||||
<field name="planned_hours">8.0</field>
|
<field name="planned_hours">8.0</field>
|
||||||
<field name="stage_id" ref="project_stage_2"/>
|
<field name="stage_id" ref="project_stage_2"/>
|
||||||
<field name="user_id" eval="False"/>
|
<field name="user_ids" eval="False"/>
|
||||||
<field name="priority">1</field>
|
<field name="priority">1</field>
|
||||||
<field name="project_id" ref="project.project_project_1"/>
|
<field name="project_id" ref="project.project_project_1"/>
|
||||||
<field name="name">Modifications asked by the customer</field>
|
<field name="name">Modifications asked by the customer</field>
|
||||||
@@ -610,7 +688,7 @@
|
|||||||
<record id="project_task_25" model="project.task">
|
<record id="project_task_25" model="project.task">
|
||||||
<field name="sequence">20</field>
|
<field name="sequence">20</field>
|
||||||
<field name="planned_hours">20.0</field>
|
<field name="planned_hours">20.0</field>
|
||||||
<field name="user_id" eval="False"/>
|
<field name="user_ids" eval="False"/>
|
||||||
<field name="project_id" ref="project.project_project_1"/>
|
<field name="project_id" ref="project.project_project_1"/>
|
||||||
<field name="name">Office planning</field>
|
<field name="name">Office planning</field>
|
||||||
<field name="stage_id" ref="project_stage_0"/>
|
<field name="stage_id" ref="project_stage_0"/>
|
||||||
@@ -620,7 +698,7 @@
|
|||||||
<record id="project_task_26" model="project.task">
|
<record id="project_task_26" model="project.task">
|
||||||
<field name="sequence">20</field>
|
<field name="sequence">20</field>
|
||||||
<field name="planned_hours">35.0</field>
|
<field name="planned_hours">35.0</field>
|
||||||
<field name="user_id" eval="False"/>
|
<field name="user_ids" eval="False"/>
|
||||||
<field name="project_id" ref="project.project_project_2"/>
|
<field name="project_id" ref="project.project_project_2"/>
|
||||||
<field name="name">Unit Testing</field>
|
<field name="name">Unit Testing</field>
|
||||||
<field name="stage_id" ref="project_stage_2"/>
|
<field name="stage_id" ref="project_stage_2"/>
|
||||||
@@ -665,7 +743,7 @@
|
|||||||
|
|
||||||
<record id="project_task_30" model="project.task">
|
<record id="project_task_30" model="project.task">
|
||||||
<field name="planned_hours" eval="40.0"/>
|
<field name="planned_hours" eval="40.0"/>
|
||||||
<field name="user_id" ref="base.user_demo"/>
|
<field name="user_ids" eval="[(4, ref('base.user_demo'))]"/>
|
||||||
<field name="priority">0</field>
|
<field name="priority">0</field>
|
||||||
<field name="project_id" ref="project.project_project_3"/>
|
<field name="project_id" ref="project.project_project_3"/>
|
||||||
<field name="name">Entry Hall</field>
|
<field name="name">Entry Hall</field>
|
||||||
@@ -674,7 +752,7 @@
|
|||||||
</record>
|
</record>
|
||||||
<record id="project_task_31" model="project.task">
|
<record id="project_task_31" model="project.task">
|
||||||
<field name="planned_hours" eval="10.0"/>
|
<field name="planned_hours" eval="10.0"/>
|
||||||
<field name="user_id" ref="base.user_admin"/>
|
<field name="user_ids" eval="[(4, ref('base.user_admin'))]"/>
|
||||||
<field name="priority">0</field>
|
<field name="priority">0</field>
|
||||||
<field name="project_id" ref="project.project_project_3"/>
|
<field name="project_id" ref="project.project_project_3"/>
|
||||||
<field name="name">Check Lift</field>
|
<field name="name">Check Lift</field>
|
||||||
@@ -684,7 +762,7 @@
|
|||||||
</record>
|
</record>
|
||||||
<record id="project_task_32" model="project.task">
|
<record id="project_task_32" model="project.task">
|
||||||
<field name="planned_hours" eval="24.0"/>
|
<field name="planned_hours" eval="24.0"/>
|
||||||
<field name="user_id" ref="base.user_admin"/>
|
<field name="user_ids" eval="[(4, ref('base.user_admin'))]"/>
|
||||||
<field name="priority">1</field>
|
<field name="priority">1</field>
|
||||||
<field name="project_id" ref="project.project_project_3"/>
|
<field name="project_id" ref="project.project_project_3"/>
|
||||||
<field name="name">Room 1: Paint</field>
|
<field name="name">Room 1: Paint</field>
|
||||||
@@ -696,7 +774,7 @@
|
|||||||
</record>
|
</record>
|
||||||
<record id="project_task_33" model="project.task">
|
<record id="project_task_33" model="project.task">
|
||||||
<field name="planned_hours" eval="76.0"/>
|
<field name="planned_hours" eval="76.0"/>
|
||||||
<field name="user_id" ref="base.user_admin"/>
|
<field name="user_ids" eval="[(4, ref('base.user_admin'))]"/>
|
||||||
<field name="priority">1</field>
|
<field name="priority">1</field>
|
||||||
<field name="project_id" ref="project.project_project_3"/>
|
<field name="project_id" ref="project.project_project_3"/>
|
||||||
<field name="name">Bathroom</field>
|
<field name="name">Bathroom</field>
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ from . import analytic_account
|
|||||||
from . import project_milestone
|
from . import project_milestone
|
||||||
from . import project_project_stage
|
from . import project_project_stage
|
||||||
from . import project_task_recurrence
|
from . import project_task_recurrence
|
||||||
|
# `project_task_stage_personal` has to be loaded before `project`
|
||||||
|
from . import project_task_stage_personal
|
||||||
from . import project
|
from . import project
|
||||||
from . import project_collaborator
|
from . import project_collaborator
|
||||||
from . import project_update
|
from . import project_update
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ PROJECT_TASK_WRITABLE_FIELDS = {
|
|||||||
'name',
|
'name',
|
||||||
'partner_id',
|
'partner_id',
|
||||||
'partner_email',
|
'partner_email',
|
||||||
'user_id',
|
'user_ids',
|
||||||
'date_deadline',
|
'date_deadline',
|
||||||
'tag_ids',
|
'tag_ids',
|
||||||
'sequence',
|
'sequence',
|
||||||
@@ -53,7 +53,6 @@ PROJECT_TASK_WRITABLE_FIELDS = {
|
|||||||
'parent_id',
|
'parent_id',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ProjectTaskType(models.Model):
|
class ProjectTaskType(models.Model):
|
||||||
_name = 'project.task.type'
|
_name = 'project.task.type'
|
||||||
_description = 'Task Stage'
|
_description = 'Task Stage'
|
||||||
@@ -97,6 +96,8 @@ class ProjectTaskType(models.Model):
|
|||||||
is_closed = fields.Boolean('Closing Stage', help="Tasks in this stage are considered as closed.")
|
is_closed = fields.Boolean('Closing Stage', help="Tasks in this stage are considered as closed.")
|
||||||
disabled_rating_warning = fields.Text(compute='_compute_disabled_rating_warning')
|
disabled_rating_warning = fields.Text(compute='_compute_disabled_rating_warning')
|
||||||
|
|
||||||
|
user_id = fields.Many2one('res.users', 'Stage Owner', index=True)
|
||||||
|
|
||||||
def unlink_wizard(self, stage_view=False):
|
def unlink_wizard(self, stage_view=False):
|
||||||
self = self.with_context(active_test=False)
|
self = self.with_context(active_test=False)
|
||||||
# retrieves all the projects with a least 1 task in that stage
|
# retrieves all the projects with a least 1 task in that stage
|
||||||
@@ -846,6 +847,10 @@ class Task(models.Model):
|
|||||||
stage_ids = stages._search(search_domain, order=order, access_rights_uid=SUPERUSER_ID)
|
stage_ids = stages._search(search_domain, order=order, access_rights_uid=SUPERUSER_ID)
|
||||||
return stages.browse(stage_ids)
|
return stages.browse(stage_ids)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _read_group_personal_stage_type_ids(self, stages, domain, order):
|
||||||
|
return stages.search(['|', ('id', 'in', stages.ids), ('user_id', '=', self.env.user.id)])
|
||||||
|
|
||||||
active = fields.Boolean(default=True)
|
active = fields.Boolean(default=True)
|
||||||
name = fields.Char(string='Title', tracking=True, required=True, index=True)
|
name = fields.Char(string='Title', tracking=True, required=True, index=True)
|
||||||
description = fields.Html(string='Description')
|
description = fields.Html(string='Description')
|
||||||
@@ -886,10 +891,24 @@ class Task(models.Model):
|
|||||||
planned_hours = fields.Float("Initially Planned Hours", help='Time planned to achieve this task (including its sub-tasks).', tracking=True)
|
planned_hours = fields.Float("Initially Planned Hours", help='Time planned to achieve this task (including its sub-tasks).', tracking=True)
|
||||||
subtask_planned_hours = fields.Float("Sub-tasks Planned Hours", compute='_compute_subtask_planned_hours',
|
subtask_planned_hours = fields.Float("Sub-tasks Planned Hours", compute='_compute_subtask_planned_hours',
|
||||||
help="Sum of the time planned of all the sub-tasks linked to this task. Usually less than or equal to the initially planned time of this task.")
|
help="Sum of the time planned of all the sub-tasks linked to this task. Usually less than or equal to the initially planned time of this task.")
|
||||||
user_id = fields.Many2one('res.users',
|
# Tracking of this field is done in the write function
|
||||||
string='Assigned to',
|
user_ids = fields.Many2many('res.users', relation='project_task_user_rel', column1='task_id', column2='user_id',
|
||||||
default=lambda self: self.env.uid,
|
string='Assignees', default=lambda self: self.env.user)
|
||||||
index=True, tracking=True)
|
# Second Many2many containing the actual personal stage for the current user
|
||||||
|
# See project_task_stage_personal.py for the model defininition
|
||||||
|
personal_stage_type_ids = fields.Many2many('project.task.type', 'project_task_user_rel', column1='task_id', column2='stage_id',
|
||||||
|
ondelete='restrict', group_expand='_read_group_personal_stage_type_ids',
|
||||||
|
domain="[('user_id', '=', user.id)]", depends=['user_ids'], string='Personal Stage')
|
||||||
|
# Personal Stage computed from the user
|
||||||
|
personal_stage_id = fields.Many2one('project.task.stage.personal', string='Personal Stage State', compute_sudo=False,
|
||||||
|
compute='_compute_personal_stage_id', help="The current user's personal stage.")
|
||||||
|
# This field is actually a related field on personal_stage_id.stage_id
|
||||||
|
# However due to the fact that personal_stage_id is computed, the orm throws out errors
|
||||||
|
# saying the field cannot be searched.
|
||||||
|
personal_stage_type_id = fields.Many2one('project.task.type', string='Personal User Stage',
|
||||||
|
compute='_compute_personal_stage_type_id', inverse='_inverse_personal_stage_type_id', store=False,
|
||||||
|
search='_search_personal_stage_type_id',
|
||||||
|
help="The current user's personal task stage.")
|
||||||
partner_id = fields.Many2one('res.partner',
|
partner_id = fields.Many2one('res.partner',
|
||||||
string='Customer',
|
string='Customer',
|
||||||
compute='_compute_partner_id', recursive=True, store=True, readonly=False, tracking=True,
|
compute='_compute_partner_id', recursive=True, store=True, readonly=False, tracking=True,
|
||||||
@@ -908,7 +927,6 @@ class Task(models.Model):
|
|||||||
'res.company', string='Company', compute='_compute_company_id', store=True, readonly=False,
|
'res.company', string='Company', compute='_compute_company_id', store=True, readonly=False,
|
||||||
required=True, copy=True, default=_default_company_id)
|
required=True, copy=True, default=_default_company_id)
|
||||||
color = fields.Integer(string='Color Index')
|
color = fields.Integer(string='Color Index')
|
||||||
user_email = fields.Char(related='user_id.email', string='User Email', readonly=True, related_sudo=False)
|
|
||||||
attachment_ids = fields.One2many('ir.attachment', compute='_compute_attachment_ids', string="Main Attachments",
|
attachment_ids = fields.One2many('ir.attachment', compute='_compute_attachment_ids', string="Main Attachments",
|
||||||
help="Attachments that don't come from a message.")
|
help="Attachments that don't come from a message.")
|
||||||
# In the domain of displayed_image_id, we couln't use attachment_ids because a one2many is represented as a list of commands so we used res_model & res_id
|
# In the domain of displayed_image_id, we couln't use attachment_ids because a one2many is represented as a list of commands so we used res_model & res_id
|
||||||
@@ -932,9 +950,11 @@ class Task(models.Model):
|
|||||||
working_days_close = fields.Float(compute='_compute_elapsed', string='Working Days to Close', store=True, group_operator="avg")
|
working_days_close = fields.Float(compute='_compute_elapsed', string='Working Days to Close', store=True, group_operator="avg")
|
||||||
# customer portal: include comment and incoming emails in communication history
|
# customer portal: include comment and incoming emails in communication history
|
||||||
website_message_ids = fields.One2many(domain=lambda self: [('model', '=', self._name), ('message_type', 'in', ['email', 'comment'])])
|
website_message_ids = fields.One2many(domain=lambda self: [('model', '=', self._name), ('message_type', 'in', ['email', 'comment'])])
|
||||||
|
is_private = fields.Boolean(compute='_compute_is_private')
|
||||||
|
|
||||||
# Task Dependencies fields
|
# Task Dependencies fields
|
||||||
allow_task_dependencies = fields.Boolean(related='project_id.allow_task_dependencies')
|
allow_task_dependencies = fields.Boolean(related='project_id.allow_task_dependencies')
|
||||||
|
# Tracking of this field is done in the write function
|
||||||
depend_on_ids = fields.Many2many('project.task', relation="task_dependencies_rel", column1="task_id", column2="depends_on_id", string="Blocked By", domain="[('allow_task_dependencies', '=', True), ('id', '!=', id)]")
|
depend_on_ids = fields.Many2many('project.task', relation="task_dependencies_rel", column1="task_id", column2="depends_on_id", string="Blocked By", domain="[('allow_task_dependencies', '=', True), ('id', '!=', id)]")
|
||||||
|
|
||||||
# recurrence fields
|
# recurrence fields
|
||||||
@@ -1028,6 +1048,64 @@ class Task(models.Model):
|
|||||||
def SELF_WRITABLE_FIELDS(self):
|
def SELF_WRITABLE_FIELDS(self):
|
||||||
return PROJECT_TASK_WRITABLE_FIELDS
|
return PROJECT_TASK_WRITABLE_FIELDS
|
||||||
|
|
||||||
|
@api.depends('project_id', 'parent_id')
|
||||||
|
def _compute_is_private(self):
|
||||||
|
# Modify accordingly, this field is used to display the lock on the task's kanban card
|
||||||
|
for task in self:
|
||||||
|
task.is_private = not task.project_id and not task.parent_id
|
||||||
|
|
||||||
|
@api.depends_context('uid')
|
||||||
|
@api.depends('user_ids')
|
||||||
|
def _compute_personal_stage_id(self):
|
||||||
|
# An user may only access his own 'personal stage' and there can only be one pair (user, task_id)
|
||||||
|
personal_stages = self.env['project.task.stage.personal'].search([('user_id', '=', self.env.uid), ('task_id', 'in', self.ids)])
|
||||||
|
self.personal_stage_id = False
|
||||||
|
for personal_stage in personal_stages:
|
||||||
|
personal_stage.task_id.personal_stage_id = personal_stage
|
||||||
|
|
||||||
|
@api.depends('personal_stage_id')
|
||||||
|
def _compute_personal_stage_type_id(self):
|
||||||
|
for task in self:
|
||||||
|
task.personal_stage_type_id = task.personal_stage_id.stage_id
|
||||||
|
|
||||||
|
def _inverse_personal_stage_type_id(self):
|
||||||
|
for task in self:
|
||||||
|
task.personal_stage_id.stage_id = task.personal_stage_type_id
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _search_personal_stage_type_id(self, operator, value):
|
||||||
|
return [('personal_stage_type_ids', operator, value)]
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_default_personal_stage_create_vals(self, user_id):
|
||||||
|
return [
|
||||||
|
{'sequence': 1, 'name': _('Inbox'), 'user_id': user_id, 'fold': False},
|
||||||
|
{'sequence': 2, 'name': _('Today'), 'user_id': user_id, 'fold': False},
|
||||||
|
{'sequence': 3, 'name': _('This Week'), 'user_id': user_id, 'fold': False},
|
||||||
|
{'sequence': 4, 'name': _('This Month'), 'user_id': user_id, 'fold': False},
|
||||||
|
{'sequence': 5, 'name': _('Later'), 'user_id': user_id, 'fold': False},
|
||||||
|
{'sequence': 6, 'name': _('Done'), 'user_id': user_id, 'fold': True},
|
||||||
|
{'sequence': 7, 'name': _('Canceled'), 'user_id': user_id, 'fold': True},
|
||||||
|
]
|
||||||
|
|
||||||
|
def _populate_missing_personal_stages(self):
|
||||||
|
# Assign the default personal stage for those that are missing
|
||||||
|
personal_stages_without_stage = self.env['project.task.stage.personal'].sudo().search([('task_id', 'in', self.ids), ('stage_id', '=', False)])
|
||||||
|
if personal_stages_without_stage:
|
||||||
|
user_ids = personal_stages_without_stage.user_id
|
||||||
|
personal_stage_by_user = defaultdict(lambda: self.env['project.task.stage.personal'])
|
||||||
|
for personal_stage in personal_stages_without_stage:
|
||||||
|
personal_stage_by_user[personal_stage.user_id] |= personal_stage
|
||||||
|
for user_id in user_ids:
|
||||||
|
stage = self.env['project.task.type'].sudo().search([('user_id', '=', user_id.id)], limit=1)
|
||||||
|
# In the case no stages have been found, we create the default stages for the user
|
||||||
|
if not stage:
|
||||||
|
stages = self.env['project.task.type'].sudo().with_context(lang=user_id.partner_id.lang, default_project_id=False).create(
|
||||||
|
self._get_default_personal_stage_create_vals(user_id.id)
|
||||||
|
)
|
||||||
|
stage = stages[0]
|
||||||
|
personal_stage_by_user[user_id].sudo().write({'stage_id': stage.id})
|
||||||
|
|
||||||
@api.constrains('depend_on_ids')
|
@api.constrains('depend_on_ids')
|
||||||
def _check_no_cyclic_dependencies(self):
|
def _check_no_cyclic_dependencies(self):
|
||||||
if not self._check_m2m_recursion('depend_on_ids'):
|
if not self._check_m2m_recursion('depend_on_ids'):
|
||||||
@@ -1449,8 +1527,8 @@ class Task(models.Model):
|
|||||||
default_project_id=project_id
|
default_project_id=project_id
|
||||||
).default_get(['stage_id']).get('stage_id')
|
).default_get(['stage_id']).get('stage_id')
|
||||||
vals["stage_id"] = default_stage[project_id]
|
vals["stage_id"] = default_stage[project_id]
|
||||||
# user_id change: update date_assign
|
# user_ids change: update date_assign
|
||||||
if vals.get('user_id'):
|
if vals.get('user_ids'):
|
||||||
vals['date_assign'] = fields.Datetime.now()
|
vals['date_assign'] = fields.Datetime.now()
|
||||||
# Stage change: Update date_end if folded stage and date_last_stage_update
|
# Stage change: Update date_end if folded stage and date_last_stage_update
|
||||||
if vals.get('stage_id'):
|
if vals.get('stage_id'):
|
||||||
@@ -1475,6 +1553,7 @@ class Task(models.Model):
|
|||||||
}
|
}
|
||||||
self = self.with_context(ctx).sudo()
|
self = self.with_context(ctx).sudo()
|
||||||
tasks = super(Task, self).create(vals_list)
|
tasks = super(Task, self).create(vals_list)
|
||||||
|
tasks._populate_missing_personal_stages()
|
||||||
if is_portal_user:
|
if is_portal_user:
|
||||||
# since we use sudo to create tasks, we need to check
|
# since we use sudo to create tasks, we need to check
|
||||||
# if the portal user could really create the tasks based on the ir rule.
|
# if the portal user could really create the tasks based on the ir rule.
|
||||||
@@ -1506,8 +1585,8 @@ class Task(models.Model):
|
|||||||
# reset kanban state when changing stage
|
# reset kanban state when changing stage
|
||||||
if 'kanban_state' not in vals:
|
if 'kanban_state' not in vals:
|
||||||
vals['kanban_state'] = 'normal'
|
vals['kanban_state'] = 'normal'
|
||||||
# user_id change: update date_assign
|
# user_ids change: update date_assign
|
||||||
if vals.get('user_id') and 'date_assign' not in vals:
|
if vals.get('user_ids') and 'date_assign' not in vals:
|
||||||
vals['date_assign'] = now
|
vals['date_assign'] = now
|
||||||
|
|
||||||
# recurrence fields
|
# recurrence fields
|
||||||
@@ -1542,7 +1621,45 @@ class Task(models.Model):
|
|||||||
if portal_can_write:
|
if portal_can_write:
|
||||||
tasks = tasks.sudo()
|
tasks = tasks.sudo()
|
||||||
|
|
||||||
|
# X2Many Field Tracking
|
||||||
|
# Extract to a separate function if necessary
|
||||||
|
x2m_tracked_fields = {'user_ids', 'depend_on_ids'}
|
||||||
|
x2m_vals_common_fields = vals.keys() & x2m_tracked_fields
|
||||||
|
x2m_tracking_values = dict()
|
||||||
|
# Structured like so
|
||||||
|
# {
|
||||||
|
# task: {
|
||||||
|
# field_name: (value, display_value)
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
if not self._context.get('mail_notrack') and x2m_vals_common_fields:
|
||||||
|
# Compute the value before the changes
|
||||||
|
for task in self:
|
||||||
|
task_values = x2m_tracking_values.setdefault(task, {})
|
||||||
|
for field in x2m_vals_common_fields:
|
||||||
|
task_values[field] = (task[field], ', '.join(record.display_name for record in task[field]))
|
||||||
|
|
||||||
result = super(Task, tasks).write(vals)
|
result = super(Task, tasks).write(vals)
|
||||||
|
if x2m_tracking_values:
|
||||||
|
for task, tracking_values in x2m_tracking_values.items():
|
||||||
|
# Compile the different changes
|
||||||
|
MailTracking = self.env['mail.tracking.value']
|
||||||
|
tracking_value_ids = []
|
||||||
|
# Use mail.tracking.value to track our changes, this is to use the same format as the default tracking one
|
||||||
|
# we just hack the record to think it is a text field and compile the data ourself beforehand
|
||||||
|
for field in tracking_values:
|
||||||
|
if task[field] != tracking_values[field][0]:
|
||||||
|
field_desc = task._fields[field]._description_string(self.env)
|
||||||
|
old_value = tracking_values[field][1]
|
||||||
|
new_value = ', '.join(record.display_name for record in task[field])
|
||||||
|
tracking_value_ids.append((0, 0, MailTracking.create_tracking_values(
|
||||||
|
old_value, new_value, field, {'type': 'char', 'string': field_desc}, 100, self._name)))
|
||||||
|
if tracking_value_ids:
|
||||||
|
task._message_log(tracking_value_ids=tracking_value_ids)
|
||||||
|
|
||||||
|
if 'user_ids' in vals:
|
||||||
|
tasks._populate_missing_personal_stages()
|
||||||
|
|
||||||
# rating on stage
|
# rating on stage
|
||||||
if 'stage_id' in vals and vals.get('stage_id'):
|
if 'stage_id' in vals and vals.get('stage_id'):
|
||||||
self.filtered(lambda x: x.project_id.rating_active and x.project_id.rating_status == 'stage')._send_task_rating_mail(force_send=True)
|
self.filtered(lambda x: x.project_id.rating_active and x.project_id.rating_status == 'stage')._send_task_rating_mail(force_send=True)
|
||||||
@@ -1568,11 +1685,6 @@ class Task(models.Model):
|
|||||||
# Subtasks
|
# Subtasks
|
||||||
# ---------------------------------------------------
|
# ---------------------------------------------------
|
||||||
|
|
||||||
@api.depends('parent_id.user_id')
|
|
||||||
def _compute_user_id(self):
|
|
||||||
for task in self:
|
|
||||||
task.user_id = task.user_id or task.parent_id.user_id or self.env.uid
|
|
||||||
|
|
||||||
@api.depends('parent_id', 'project_id', 'display_project_id')
|
@api.depends('parent_id', 'project_id', 'display_project_id')
|
||||||
def _compute_partner_id(self):
|
def _compute_partner_id(self):
|
||||||
""" Compute the partner_id when the tasks have no partner_id.
|
""" Compute the partner_id when the tasks have no partner_id.
|
||||||
@@ -1673,7 +1785,7 @@ class Task(models.Model):
|
|||||||
|
|
||||||
project_user_group_id = self.env.ref('project.group_project_user').id
|
project_user_group_id = self.env.ref('project.group_project_user').id
|
||||||
new_group = ('group_project_user', lambda pdata: pdata['type'] == 'user' and project_user_group_id in pdata['groups'], {})
|
new_group = ('group_project_user', lambda pdata: pdata['type'] == 'user' and project_user_group_id in pdata['groups'], {})
|
||||||
if not self.user_id and not self.stage_id.fold:
|
if not self.user_ids and not self.stage_id.fold:
|
||||||
take_action = self._notify_get_action_link('assign', **local_msg_vals)
|
take_action = self._notify_get_action_link('assign', **local_msg_vals)
|
||||||
project_actions = [{'url': take_action, 'title': _('I take it')}]
|
project_actions = [{'url': take_action, 'title': _('I take it')}]
|
||||||
new_group[2]['actions'] = project_actions
|
new_group[2]['actions'] = project_actions
|
||||||
@@ -1720,7 +1832,7 @@ class Task(models.Model):
|
|||||||
# want the gateway user to be responsible if no other responsible is
|
# want the gateway user to be responsible if no other responsible is
|
||||||
# found.
|
# found.
|
||||||
create_context = dict(self.env.context or {})
|
create_context = dict(self.env.context or {})
|
||||||
create_context['default_user_id'] = False
|
create_context['default_user_ids'] = False
|
||||||
if custom_values is None:
|
if custom_values is None:
|
||||||
custom_values = {}
|
custom_values = {}
|
||||||
defaults = {
|
defaults = {
|
||||||
@@ -1783,7 +1895,7 @@ class Task(models.Model):
|
|||||||
return super(Task, self)._message_post_after_hook(message, msg_vals)
|
return super(Task, self)._message_post_after_hook(message, msg_vals)
|
||||||
|
|
||||||
def action_assign_to_me(self):
|
def action_assign_to_me(self):
|
||||||
self.write({'user_id': self.env.user.id})
|
self.write({'user_ids': [(4, self.env.user.id)]})
|
||||||
|
|
||||||
# If depth == 1, return only direct children
|
# If depth == 1, return only direct children
|
||||||
# If depth == 3, return children to third generation
|
# If depth == 3, return children to third generation
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ class ProjectTaskRecurrence(models.Model):
|
|||||||
field: value[0] if isinstance(value, tuple) else value for field, value in task_values.items()
|
field: value[0] if isinstance(value, tuple) else value for field, value in task_values.items()
|
||||||
}
|
}
|
||||||
create_values['stage_id'] = task.project_id.type_ids[0].id if task.project_id.type_ids else task.stage_id.id
|
create_values['stage_id'] = task.project_id.type_ids[0].id if task.project_id.type_ids else task.stage_id.id
|
||||||
create_values['user_id'] = False
|
create_values['user_ids'] = False
|
||||||
return create_values
|
return create_values
|
||||||
|
|
||||||
def _create_next_task(self):
|
def _create_next_task(self):
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
class ProjectTaskStagePersonal(models.Model):
|
||||||
|
_name = 'project.task.stage.personal'
|
||||||
|
_description = 'Personal Task Stage'
|
||||||
|
_table = 'project_task_user_rel'
|
||||||
|
_rec_name = 'stage_id'
|
||||||
|
|
||||||
|
task_id = fields.Many2one('project.task', required=True, ondelete='cascade', index=True)
|
||||||
|
user_id = fields.Many2one('res.users', required=True, ondelete='cascade', index=True)
|
||||||
|
stage_id = fields.Many2one('project.task.type', domain="[('user_id', '=', user_id)]", ondelete='restrict')
|
||||||
|
|
||||||
|
_sql_constraints = [
|
||||||
|
('project_personal_stage_unique', 'UNIQUE (task_id, user_id)', 'A task can only have a single personal stage per user.'),
|
||||||
|
]
|
||||||
@@ -55,7 +55,7 @@ class ReportProjectTaskUser(models.Model):
|
|||||||
t.date_end as date_end,
|
t.date_end as date_end,
|
||||||
t.date_last_stage_update as date_last_stage_update,
|
t.date_last_stage_update as date_last_stage_update,
|
||||||
t.date_deadline as date_deadline,
|
t.date_deadline as date_deadline,
|
||||||
t.user_id,
|
tu.user_id,
|
||||||
t.project_id,
|
t.project_id,
|
||||||
t.priority,
|
t.priority,
|
||||||
t.name as name,
|
t.name as name,
|
||||||
@@ -82,7 +82,7 @@ class ReportProjectTaskUser(models.Model):
|
|||||||
t.date_end,
|
t.date_end,
|
||||||
t.date_deadline,
|
t.date_deadline,
|
||||||
t.date_last_stage_update,
|
t.date_last_stage_update,
|
||||||
t.user_id,
|
tu.user_id,
|
||||||
t.project_id,
|
t.project_id,
|
||||||
t.priority,
|
t.priority,
|
||||||
t.name,
|
t.name,
|
||||||
@@ -98,6 +98,7 @@ class ReportProjectTaskUser(models.Model):
|
|||||||
CREATE view %s as
|
CREATE view %s as
|
||||||
%s
|
%s
|
||||||
FROM project_task t
|
FROM project_task t
|
||||||
|
LEFT JOIN project_task_user_rel tu on t.id=tu.task_id
|
||||||
WHERE t.active = 'true'
|
WHERE t.active = 'true'
|
||||||
%s
|
%s
|
||||||
""" % (self._table, self._select(), self._group_by()))
|
""" % (self._table, self._select(), self._group_by()))
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
<field name="date_deadline"/>
|
<field name="date_deadline"/>
|
||||||
<field name="date_last_stage_update"/>
|
<field name="date_last_stage_update"/>
|
||||||
<filter string="My Projects" name="own_projects" domain="[('project_id.user_id', '=', uid)]"/>
|
<filter string="My Projects" name="own_projects" domain="[('project_id.user_id', '=', uid)]"/>
|
||||||
<filter string="My Tasks" name="my_tasks" domain="[('task_id.user_id', '=', uid)]"/>
|
<filter string="My Tasks" name="my_tasks" domain="[('task_id.user_ids', 'in', uid)]"/>
|
||||||
<separator/>
|
<separator/>
|
||||||
<filter string="Starred" name="starred" domain="[('task_id.priority', 'in', [0, 1])]"/>
|
<filter string="Starred" name="starred" domain="[('task_id.priority', 'in', [0, 1])]"/>
|
||||||
<separator/>
|
<separator/>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ WITH all_moves_stage_task AS (
|
|||||||
COALESCE(LAG(mm.date) OVER (PARTITION BY mm.res_id ORDER BY mm.id), pt.create_date) as date_begin,
|
COALESCE(LAG(mm.date) OVER (PARTITION BY mm.res_id ORDER BY mm.id), pt.create_date) as date_begin,
|
||||||
mm.date as date_end,
|
mm.date as date_end,
|
||||||
mtv.old_value_integer as stage_id,
|
mtv.old_value_integer as stage_id,
|
||||||
pt.user_id,
|
usr_rel.user_id,
|
||||||
pt.date_assign,
|
pt.date_assign,
|
||||||
pt.date_deadline,
|
pt.date_deadline,
|
||||||
pt.partner_id
|
pt.partner_id
|
||||||
@@ -71,6 +71,7 @@ WITH all_moves_stage_task AS (
|
|||||||
JOIN ir_model_fields imf ON mtv.field = imf.id
|
JOIN ir_model_fields imf ON mtv.field = imf.id
|
||||||
AND imf.model = 'project.task'
|
AND imf.model = 'project.task'
|
||||||
AND imf.name = 'stage_id'
|
AND imf.name = 'stage_id'
|
||||||
|
JOIN project_task_user_rel usr_rel ON pt.id=usr_rel.task_id
|
||||||
WHERE pt.active
|
WHERE pt.active
|
||||||
|
|
||||||
--We compute the last reached stage
|
--We compute the last reached stage
|
||||||
@@ -82,7 +83,7 @@ WITH all_moves_stage_task AS (
|
|||||||
COALESCE(md.date, pt.create_date) as date_begin,
|
COALESCE(md.date, pt.create_date) as date_begin,
|
||||||
(CURRENT_DATE + interval '1 month')::date as date_end,
|
(CURRENT_DATE + interval '1 month')::date as date_end,
|
||||||
pt.stage_id,
|
pt.stage_id,
|
||||||
pt.user_id,
|
usr_rel.user_id,
|
||||||
pt.date_assign,
|
pt.date_assign,
|
||||||
pt.date_deadline,
|
pt.date_deadline,
|
||||||
pt.partner_id
|
pt.partner_id
|
||||||
@@ -98,6 +99,7 @@ WITH all_moves_stage_task AS (
|
|||||||
AND mm.model = 'project.task'
|
AND mm.model = 'project.task'
|
||||||
ORDER BY mm.id DESC
|
ORDER BY mm.id DESC
|
||||||
FETCH FIRST ROW ONLY) md ON TRUE
|
FETCH FIRST ROW ONLY) md ON TRUE
|
||||||
|
JOIN project_task_user_rel usr_rel ON pt.id=usr_rel.task_id
|
||||||
WHERE pt.active
|
WHERE pt.active
|
||||||
)
|
)
|
||||||
SELECT (task_id*10^7 + 10^6 + to_char(d, 'YYMMDD')::integer)::bigint as id,
|
SELECT (task_id*10^7 + 10^6 + to_char(d, 'YYMMDD')::integer)::bigint as id,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ access_project_project_manager,project.project,model_project_project,project.gro
|
|||||||
access_project_project_stage,project.project_stage,model_project_project_stage,base.group_user,1,0,0,0
|
access_project_project_stage,project.project_stage,model_project_project_stage,base.group_user,1,0,0,0
|
||||||
access_project_project_stage_manager,project.project_stage.manager,model_project_project_stage,project.group_project_manager,1,1,1,1
|
access_project_project_stage_manager,project.project_stage.manager,model_project_project_stage,project.group_project_manager,1,1,1,1
|
||||||
access_project_task_type_user,project.task.type.user,model_project_task_type,base.group_user,1,0,0,0
|
access_project_task_type_user,project.task.type.user,model_project_task_type,base.group_user,1,0,0,0
|
||||||
access_project_task_type_project_user,project.task.type.project.user,model_project_task_type,project.group_project_user,1,0,0,0
|
access_project_task_type_project_user,project.task.type.project.user,model_project_task_type,project.group_project_user,1,1,1,1
|
||||||
access_project_task_type_manager,project.task.type manager,model_project_task_type,project.group_project_manager,1,1,1,1
|
access_project_task_type_manager,project.task.type manager,model_project_task_type,project.group_project_manager,1,1,1,1
|
||||||
access_project_task_type_portal,task_type_portal,project.model_project_task_type,base.group_portal,1,0,0,0
|
access_project_task_type_portal,task_type_portal,project.model_project_task_type,base.group_portal,1,0,0,0
|
||||||
access_project_task,project.task,model_project_task,project.group_project_user,1,1,1,1
|
access_project_task,project.task,model_project_task,project.group_project_user,1,1,1,1
|
||||||
@@ -39,3 +39,4 @@ access_project_milestone_project_manager,project.milestone.project.manager,model
|
|||||||
access_project_collaborator_manager,project.collaborator.manager,model_project_collaborator,project.group_project_manager,1,1,1,1
|
access_project_collaborator_manager,project.collaborator.manager,model_project_collaborator,project.group_project_manager,1,1,1,1
|
||||||
access_project_collaborator_portal,project.collaborator.portal,model_project_collaborator,base.group_portal,1,0,0,0
|
access_project_collaborator_portal,project.collaborator.portal,model_project_collaborator,base.group_portal,1,0,0,0
|
||||||
access_project_share_manager,project.share.wizard.manager,model_project_share_wizard,project.group_project_manager,1,1,1,0
|
access_project_share_manager,project.share.wizard.manager,model_project_share_wizard,project.group_project_manager,1,1,1,0
|
||||||
|
access_project_personal_stage,project.personal.stage.user,model_project_task_stage_personal,base.group_user,1,1,1,1
|
||||||
|
|||||||
|
@@ -89,7 +89,7 @@
|
|||||||
'|',
|
'|',
|
||||||
('message_partner_ids', 'in', [user.partner_id.id]),
|
('message_partner_ids', 'in', [user.partner_id.id]),
|
||||||
# to subscribe check access to the record, follower is not enough at creation
|
# to subscribe check access to the record, follower is not enough at creation
|
||||||
('user_id', '=', user.id)
|
('user_ids', 'in', user.id)
|
||||||
]</field>
|
]</field>
|
||||||
<field name="groups" eval="[(4,ref('base.group_user'))]"/>
|
<field name="groups" eval="[(4,ref('base.group_user'))]"/>
|
||||||
</record>
|
</record>
|
||||||
@@ -101,12 +101,54 @@
|
|||||||
<field name="groups" eval="[(4,ref('project.group_project_manager'))]"/>
|
<field name="groups" eval="[(4,ref('project.group_project_manager'))]"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.rule" id="task_type_manager_rule">
|
||||||
|
<field name="name">Project/Task Type: manager sees all</field>
|
||||||
|
<field name="model_id" ref="model_project_task_type"/>
|
||||||
|
<field name="domain_force">[(1, '=', 1)]</field>
|
||||||
|
<field name="groups" eval="[(4,ref('project.group_project_manager'))]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.rule" id="task_type_visibility_rule">
|
||||||
|
<field name="name">Project/Task Type: see own or unowned stages</field>
|
||||||
|
<field name="model_id" ref="model_project_task_type"/>
|
||||||
|
<field name="domain_force">[('user_id', 'in', (False, user.id))]</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.rule" id="task_type_own_write_rule">
|
||||||
|
<field name="name">Project/Task Type: write own stages</field>
|
||||||
|
<field name="model_id" ref="model_project_task_type"/>
|
||||||
|
<field name="domain_force">[('user_id', '=', user.id)]</field>
|
||||||
|
<field name="perm_read" eval="False"/>
|
||||||
|
<field name="perm_write" eval="True"/>
|
||||||
|
<field name="perm_create" eval="True"/>
|
||||||
|
<field name="perm_unlink" eval="True"/>
|
||||||
|
<field name="groups" eval="[(4,ref('project.group_project_user'))]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
<record model="ir.rule" id="report_project_task_user_report_comp_rule">
|
<record model="ir.rule" id="report_project_task_user_report_comp_rule">
|
||||||
<field name="name">Task Analysis multi-company</field>
|
<field name="name">Task Analysis multi-company</field>
|
||||||
<field name="model_id" ref="model_report_project_task_user"/>
|
<field name="model_id" ref="model_report_project_task_user"/>
|
||||||
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
|
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record id="ir_rule_project_personal_stage_my" model="ir.rule">
|
||||||
|
<field name="name">Project: See my own personal stage</field>
|
||||||
|
<field name="model_id" ref="project.model_project_task_stage_personal"/>
|
||||||
|
<field name="domain_force">[('user_id', '=', user.id)]</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="ir_rule_private_task" model="ir.rule">
|
||||||
|
<field name="name">Project: See private tasks</field>
|
||||||
|
<field name="model_id" ref="project.model_project_task"/>
|
||||||
|
<field name="domain_force">[
|
||||||
|
('project_id.privacy_visibility', '!=', 'followers'),
|
||||||
|
'|', '|', ('project_id', '!=', False),
|
||||||
|
('parent_id', '!=', False),
|
||||||
|
('user_ids', 'in', user.id),
|
||||||
|
]</field>
|
||||||
|
<field name="groups" eval="[(4,ref('project.group_project_user'))]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
<!-- Portal -->
|
<!-- Portal -->
|
||||||
<record id="project_project_rule_portal" model="ir.rule">
|
<record id="project_project_rule_portal" model="ir.rule">
|
||||||
<field name="name">Project: portal users: portal and following</field>
|
<field name="name">Project: portal users: portal and following</field>
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import KanbanController from 'web.KanbanController';
|
|||||||
import KanbanRenderer from 'web.KanbanRenderer';
|
import KanbanRenderer from 'web.KanbanRenderer';
|
||||||
import KanbanView from 'web.KanbanView';
|
import KanbanView from 'web.KanbanView';
|
||||||
import KanbanColumn from 'web.KanbanColumn';
|
import KanbanColumn from 'web.KanbanColumn';
|
||||||
import viewRegistry from 'web.view_registry';
|
|
||||||
import KanbanRecord from 'web.KanbanRecord';
|
import KanbanRecord from 'web.KanbanRecord';
|
||||||
|
import KanbanModel from 'web.KanbanModel';
|
||||||
|
import viewRegistry from 'web.view_registry';
|
||||||
import { ProjectControlPanel } from '@project/js/project_control_panel';
|
import { ProjectControlPanel } from '@project/js/project_control_panel';
|
||||||
|
import viewUtils from 'web.viewUtils';
|
||||||
|
|
||||||
// PROJECTS
|
// PROJECTS
|
||||||
|
|
||||||
@@ -70,6 +72,30 @@ const ProjectTaskKanbanRenderer = KanbanRenderer.extend({
|
|||||||
config: Object.assign({}, KanbanRenderer.prototype.config, {
|
config: Object.assign({}, KanbanRenderer.prototype.config, {
|
||||||
KanbanColumn: ProjectTaskKanbanColumn,
|
KanbanColumn: ProjectTaskKanbanColumn,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows record drag when grouping by `personal_stage_type_ids`
|
||||||
|
*
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
_setState() {
|
||||||
|
this._super(...arguments);
|
||||||
|
const groupedBy = this.state.groupedBy[0];
|
||||||
|
const groupByFieldName = viewUtils.getGroupByField(groupedBy);
|
||||||
|
const field = this.state.fields[groupByFieldName] || {};
|
||||||
|
const fieldInfo = this.state.fieldsInfo.kanban[groupByFieldName] || {};
|
||||||
|
|
||||||
|
const grouped_by_date = ["date", "datetime"].includes(field.type);
|
||||||
|
const grouped_by_m2m = field.type === "many2many";
|
||||||
|
const readonly = !!field.readonly || !!fieldInfo.readonly;
|
||||||
|
|
||||||
|
const draggable = !readonly && (!grouped_by_m2m || groupByFieldName == 'personal_stage_type_ids') &&
|
||||||
|
(!grouped_by_date || fieldInfo.allowGroupRangeValue);
|
||||||
|
|
||||||
|
Object.assign(this.columnOptions, {
|
||||||
|
draggable,
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ProjectKanbanController = KanbanController.extend({
|
export const ProjectKanbanController = KanbanController.extend({
|
||||||
@@ -93,8 +119,81 @@ export const ProjectKanbanController = KanbanController.extend({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ProjectTaskKanbanModel = KanbanModel.extend({
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upon updating `personal_stage_type_ids` we actually want to update the `personal_stage_type_id` field.
|
||||||
|
*
|
||||||
|
* @override
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
moveRecord: function (recordID, groupID, parentID) {
|
||||||
|
var self = this;
|
||||||
|
var parent = this.localData[parentID];
|
||||||
|
var new_group = this.localData[groupID];
|
||||||
|
var changes = {};
|
||||||
|
var groupedFieldName = viewUtils.getGroupByField(parent.groupedBy[0]);
|
||||||
|
var groupedField = parent.fields[groupedFieldName];
|
||||||
|
// for a date/datetime field, we take the last moment of the group as the group value
|
||||||
|
if (['date', 'datetime'].includes(groupedField.type)) {
|
||||||
|
changes[groupedFieldName] = viewUtils.getGroupValue(new_group, groupedFieldName);
|
||||||
|
} else if (groupedField.type === 'many2one') {
|
||||||
|
changes[groupedFieldName] = {
|
||||||
|
id: new_group.res_id,
|
||||||
|
display_name: new_group.value,
|
||||||
|
};
|
||||||
|
} else if (groupedField.type === 'selection') {
|
||||||
|
var value = _.findWhere(groupedField.selection, {1: new_group.value});
|
||||||
|
changes[groupedFieldName] = value && value[0] || false;
|
||||||
|
} else if (groupedField.type == 'many2many' && groupedFieldName == 'personal_stage_type_ids') {
|
||||||
|
changes['personal_stage_type_id'] = {
|
||||||
|
id: new_group.res_id,
|
||||||
|
display_name: new_group.value,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
changes[groupedFieldName] = new_group.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manually updates groups data. Note: this is done before the actual
|
||||||
|
// save as it might need to perform a read group in some cases so those
|
||||||
|
// updated data might be overridden again.
|
||||||
|
var record = self.localData[recordID];
|
||||||
|
var resID = record.res_id;
|
||||||
|
// Remove record from its current group
|
||||||
|
var old_group;
|
||||||
|
for (var i = 0; i < parent.data.length; i++) {
|
||||||
|
old_group = self.localData[parent.data[i]];
|
||||||
|
var index = _.indexOf(old_group.data, recordID);
|
||||||
|
if (index >= 0) {
|
||||||
|
old_group.data.splice(index, 1);
|
||||||
|
old_group.count--;
|
||||||
|
if (!old_group.activeFilter || old_group.activeFilter.value === record.data[parent.progressBar.field]) {
|
||||||
|
// Here, the record leaving the old group matches its domain,
|
||||||
|
// so we must decrease the domainCount too.
|
||||||
|
old_group.domainCount--;
|
||||||
|
}
|
||||||
|
old_group.res_ids = _.without(old_group.res_ids, resID);
|
||||||
|
self._updateParentResIDs(old_group);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add record to its new group
|
||||||
|
new_group.data.push(recordID);
|
||||||
|
new_group.res_ids.push(resID);
|
||||||
|
new_group.count++;
|
||||||
|
|
||||||
|
return this.notifyChanges(recordID, changes).then(function () {
|
||||||
|
return self.save(recordID);
|
||||||
|
}).then(function () {
|
||||||
|
record.parentID = new_group.id;
|
||||||
|
return [old_group.id, new_group.id];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const ProjectKanbanView = KanbanView.extend({
|
const ProjectKanbanView = KanbanView.extend({
|
||||||
config: _.extend({}, KanbanView.prototype.config, {
|
config: _.extend({}, KanbanView.prototype.config, {
|
||||||
|
Model: ProjectTaskKanbanModel,
|
||||||
Controller: ProjectKanbanController,
|
Controller: ProjectKanbanController,
|
||||||
Renderer: ProjectTaskKanbanRenderer,
|
Renderer: ProjectTaskKanbanRenderer,
|
||||||
ControlPanel: ProjectControlPanel,
|
ControlPanel: ProjectControlPanel,
|
||||||
|
|||||||
@@ -34,3 +34,8 @@
|
|||||||
color: $o-main-text-color;
|
color: $o-main-text-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.o_form_project_tasks .o_project_task_project_field input::placeholder {
|
||||||
|
color: darkred;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,3 +62,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.o_kanban_project_tasks .o_field_many2manytags, .o_kanban_tags{
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.o_project_m2m_avatar {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-right: -1px;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
object-fit: cover;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ from . import test_project_update_flow
|
|||||||
from . import test_project_update_ui
|
from . import test_project_update_ui
|
||||||
from . import test_portal
|
from . import test_portal
|
||||||
from . import test_multicompany
|
from . import test_multicompany
|
||||||
|
from . import test_personal_stages
|
||||||
from . import test_task_dependencies
|
from . import test_task_dependencies
|
||||||
|
|||||||
@@ -235,13 +235,13 @@ class TestProjectPortalCommon(TestProjectCommon):
|
|||||||
'groups_id': [(6, 0, [])]})
|
'groups_id': [(6, 0, [])]})
|
||||||
|
|
||||||
self.task_3 = self.env['project.task'].with_context({'mail_create_nolog': True}).create({
|
self.task_3 = self.env['project.task'].with_context({'mail_create_nolog': True}).create({
|
||||||
'name': 'Test3', 'user_id': self.user_portal.id, 'project_id': self.project_pigs.id})
|
'name': 'Test3', 'user_ids': self.user_portal, 'project_id': self.project_pigs.id})
|
||||||
self.task_4 = self.env['project.task'].with_context({'mail_create_nolog': True}).create({
|
self.task_4 = self.env['project.task'].with_context({'mail_create_nolog': True}).create({
|
||||||
'name': 'Test4', 'user_id': self.user_public.id, 'project_id': self.project_pigs.id})
|
'name': 'Test4', 'user_ids': self.user_public, 'project_id': self.project_pigs.id})
|
||||||
self.task_5 = self.env['project.task'].with_context({'mail_create_nolog': True}).create({
|
self.task_5 = self.env['project.task'].with_context({'mail_create_nolog': True}).create({
|
||||||
'name': 'Test5', 'user_id': False, 'project_id': self.project_pigs.id})
|
'name': 'Test5', 'user_ids': False, 'project_id': self.project_pigs.id})
|
||||||
self.task_6 = self.env['project.task'].with_context({'mail_create_nolog': True}).create({
|
self.task_6 = self.env['project.task'].with_context({'mail_create_nolog': True}).create({
|
||||||
'name': 'Test5', 'user_id': False, 'project_id': self.project_pigs.id})
|
'name': 'Test5', 'user_ids': False, 'project_id': self.project_pigs.id})
|
||||||
|
|
||||||
class TestPortalProject(TestProjectPortalCommon):
|
class TestPortalProject(TestProjectPortalCommon):
|
||||||
|
|
||||||
|
|||||||
@@ -179,12 +179,12 @@ class TestMultiCompanyProject(TestMultiCompanyCommon):
|
|||||||
Task = cls.env['project.task'].with_context({'mail_create_nolog': True, 'tracking_disable': True})
|
Task = cls.env['project.task'].with_context({'mail_create_nolog': True, 'tracking_disable': True})
|
||||||
cls.task_1 = Task.create({
|
cls.task_1 = Task.create({
|
||||||
'name': 'Task 1 in Project A',
|
'name': 'Task 1 in Project A',
|
||||||
'user_id': cls.user_employee_company_a.id,
|
'user_ids': cls.user_employee_company_a,
|
||||||
'project_id': cls.project_company_a.id
|
'project_id': cls.project_company_a.id
|
||||||
})
|
})
|
||||||
cls.task_2 = Task.create({
|
cls.task_2 = Task.create({
|
||||||
'name': 'Task 2 in Project B',
|
'name': 'Task 2 in Project B',
|
||||||
'user_id': cls.user_employee_company_b.id,
|
'user_ids': cls.user_employee_company_b,
|
||||||
'project_id': cls.project_company_b.id
|
'project_id': cls.project_company_b.id
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from odoo.tests import tagged
|
||||||
|
|
||||||
|
from .test_project_base import TestProjectCommon
|
||||||
|
|
||||||
|
@tagged('-at_install', 'post_install', 'personal_stages')
|
||||||
|
class TestPersonalStages(TestProjectCommon):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
cls.user_stages = cls.env['project.task.type'].search([('user_id', '=', cls.user_projectuser.id)])
|
||||||
|
cls.manager_stages = cls.env['project.task.type'].search([('user_id', '=', cls.user_projectmanager.id)])
|
||||||
|
|
||||||
|
def test_personal_stage_base(self):
|
||||||
|
# Project User is assigned to task_1 he should be able to see a personal stage
|
||||||
|
self.task_1.with_user(self.user_projectuser)._compute_personal_stage_id()
|
||||||
|
self.assertTrue(self.task_1.with_user(self.user_projectuser).personal_stage_type_id,
|
||||||
|
'Project User is assigned to task 1, he should have a personal stage assigned.')
|
||||||
|
|
||||||
|
self.task_1.with_user(self.user_projectmanager)._compute_personal_stage_id()
|
||||||
|
self.assertFalse(self.env['project.task'].browse(self.task_1.id).with_user(self.user_projectmanager).personal_stage_type_id,
|
||||||
|
'Project Manager is not assigned to task 1, he should not have a personal stage assigned.')
|
||||||
|
|
||||||
|
# Now assign a second user to our task_1
|
||||||
|
self.task_1.user_ids += self.user_projectmanager
|
||||||
|
self.assertTrue(self.task_1.with_user(self.user_projectmanager).personal_stage_type_id,
|
||||||
|
'Project Manager has now been assigned to task 1 and should have a personal stage assigned.')
|
||||||
|
|
||||||
|
self.task_1.with_user(self.user_projectmanager)._compute_personal_stage_id()
|
||||||
|
task_1_manager_stage = self.task_1.with_user(self.user_projectmanager).personal_stage_type_id
|
||||||
|
|
||||||
|
self.task_1.with_user(self.user_projectuser)._compute_personal_stage_id()
|
||||||
|
self.task_1.with_user(self.user_projectuser).personal_stage_type_id = self.user_stages[1]
|
||||||
|
self.assertEqual(self.task_1.with_user(self.user_projectuser).personal_stage_type_id, self.user_stages[1],
|
||||||
|
'Assigning another personal stage to the task should have changed it for user 1.')
|
||||||
|
|
||||||
|
self.task_1.with_user(self.user_projectmanager)._compute_personal_stage_id()
|
||||||
|
self.assertEqual(self.task_1.with_user(self.user_projectmanager).personal_stage_type_id, task_1_manager_stage,
|
||||||
|
'Modifying the personal stage of Project User should not have affected the personal stage of Project Manager.')
|
||||||
|
|
||||||
|
self.task_2.with_user(self.user_projectmanager).personal_stage_type_id = self.manager_stages[1]
|
||||||
|
self.assertEqual(self.task_1.with_user(self.user_projectmanager).personal_stage_type_id, task_1_manager_stage,
|
||||||
|
'Modifying the personal stage on task 2 for Project Manager should not have affected the stage on task 1.')
|
||||||
|
|
||||||
|
def test_personal_stage_search(self):
|
||||||
|
self.task_2.user_ids += self.user_projectuser
|
||||||
|
# Make sure both personal stages are different
|
||||||
|
self.task_1.with_user(self.user_projectuser).personal_stage_type_id = self.user_stages[0]
|
||||||
|
self.task_2.with_user(self.user_projectuser).personal_stage_type_id = self.user_stages[1]
|
||||||
|
tasks = self.env['project.task'].with_user(self.user_projectuser).search([('personal_stage_type_id', '=', self.user_stages[0].id)])
|
||||||
|
self.assertTrue(tasks, 'The search result should not be empty.')
|
||||||
|
for task in tasks:
|
||||||
|
self.assertEqual(task.personal_stage_type_id, self.user_stages[0],
|
||||||
|
'The search should only have returned task that are in the inbox personal stage.')
|
||||||
|
|
||||||
|
def test_personal_stage_read_group(self):
|
||||||
|
self.task_1.user_ids += self.user_projectmanager
|
||||||
|
self.task_1.with_user(self.user_projectmanager).personal_stage_type_id = self.manager_stages[1]
|
||||||
|
#Makes sure the personal stage for project manager is saved in the database
|
||||||
|
self.env['project.task'].flush()
|
||||||
|
read_group_user = self.env['project.task'].with_user(self.user_projectuser).read_group(
|
||||||
|
[('user_ids', '=', self.user_projectuser.id)], fields=['sequence:avg'], groupby=['personal_stage_type_ids'])
|
||||||
|
# Check that the result is at least a bit coherent
|
||||||
|
self.assertEqual(len(self.user_stages), len(read_group_user),
|
||||||
|
'read_group should return %d groups' % len(self.user_stages))
|
||||||
|
# User has only one task assigned the sum of all counts should be 1
|
||||||
|
total = 0
|
||||||
|
for group in read_group_user:
|
||||||
|
total += group['personal_stage_type_ids_count']
|
||||||
|
self.assertEqual(1, total,
|
||||||
|
'read_group should not have returned more tasks than the user is assigned to.')
|
||||||
|
read_group_manager = self.env['project.task'].with_user(self.user_projectmanager).read_group(
|
||||||
|
[('user_ids', '=', self.user_projectmanager.id)], fields=['sequence:avg'], groupby=['personal_stage_type_ids'])
|
||||||
|
self.assertEqual(len(self.manager_stages), len(read_group_manager),
|
||||||
|
'read_group should return %d groups' % len(self.user_stages))
|
||||||
|
total = 0
|
||||||
|
total_stage_0 = 0
|
||||||
|
total_stage_1 = 0
|
||||||
|
for group in read_group_manager:
|
||||||
|
total += group['personal_stage_type_ids_count']
|
||||||
|
# Check that we have a task in both stages
|
||||||
|
if group['personal_stage_type_ids'][0] == self.manager_stages[0].id:
|
||||||
|
total_stage_0 += 1
|
||||||
|
elif group['personal_stage_type_ids'][0] == self.manager_stages[1].id:
|
||||||
|
total_stage_1 += 1
|
||||||
|
self.assertEqual(2, total,
|
||||||
|
'read_group should not have returned more tasks than the user is assigned to.')
|
||||||
|
self.assertEqual(1, total_stage_0)
|
||||||
|
self.assertEqual(1, total_stage_1)
|
||||||
@@ -60,11 +60,11 @@ class TestProjectCommon(TransactionCase):
|
|||||||
# Already-existing tasks in Pigs
|
# Already-existing tasks in Pigs
|
||||||
cls.task_1 = cls.env['project.task'].with_context({'mail_create_nolog': True}).create({
|
cls.task_1 = cls.env['project.task'].with_context({'mail_create_nolog': True}).create({
|
||||||
'name': 'Pigs UserTask',
|
'name': 'Pigs UserTask',
|
||||||
'user_id': cls.user_projectuser.id,
|
'user_ids': cls.user_projectuser,
|
||||||
'project_id': cls.project_pigs.id})
|
'project_id': cls.project_pigs.id})
|
||||||
cls.task_2 = cls.env['project.task'].with_context({'mail_create_nolog': True}).create({
|
cls.task_2 = cls.env['project.task'].with_context({'mail_create_nolog': True}).create({
|
||||||
'name': 'Pigs ManagerTask',
|
'name': 'Pigs ManagerTask',
|
||||||
'user_id': cls.user_projectmanager.id,
|
'user_ids': cls.user_projectmanager,
|
||||||
'project_id': cls.project_pigs.id})
|
'project_id': cls.project_pigs.id})
|
||||||
|
|
||||||
# Test 'Goats' project, same as 'Pigs', but with 2 stages
|
# Test 'Goats' project, same as 'Pigs', but with 2 stages
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ class TestProjectFlow(TestProjectCommon):
|
|||||||
|
|
||||||
parent_task = Task.create({
|
parent_task = Task.create({
|
||||||
'name': 'Mother Task',
|
'name': 'Mother Task',
|
||||||
'user_id': self.user_projectuser.id,
|
'user_ids': self.user_projectuser,
|
||||||
'project_id': self.project_pigs.id,
|
'project_id': self.project_pigs.id,
|
||||||
'partner_id': self.partner_2.id,
|
'partner_id': self.partner_2.id,
|
||||||
'planned_hours': 12,
|
'planned_hours': 12,
|
||||||
@@ -120,7 +120,7 @@ class TestProjectFlow(TestProjectCommon):
|
|||||||
|
|
||||||
another_parent_task = Task.create({
|
another_parent_task = Task.create({
|
||||||
'name': 'Another Mother Task',
|
'name': 'Another Mother Task',
|
||||||
'user_id': self.user_projectuser.id,
|
'user_ids': self.user_projectuser,
|
||||||
'project_id': self.project_pigs.id,
|
'project_id': self.project_pigs.id,
|
||||||
'partner_id': self.partner_3.id,
|
'partner_id': self.partner_3.id,
|
||||||
'planned_hours': 0,
|
'planned_hours': 0,
|
||||||
@@ -210,7 +210,7 @@ class TestProjectFlow(TestProjectCommon):
|
|||||||
Task = self.env['project.task'].with_context({'tracking_disable': True})
|
Task = self.env['project.task'].with_context({'tracking_disable': True})
|
||||||
first_task = Task.create({
|
first_task = Task.create({
|
||||||
'name': 'first task',
|
'name': 'first task',
|
||||||
'user_id': self.user_projectuser.id,
|
'user_ids': self.user_projectuser,
|
||||||
'project_id': self.project_pigs.id,
|
'project_id': self.project_pigs.id,
|
||||||
'partner_id': self.partner_2.id,
|
'partner_id': self.partner_2.id,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -42,12 +42,12 @@ class TestProjectSharingCommon(TestProjectCommon):
|
|||||||
|
|
||||||
cls.task_cow = cls.env['project.task'].with_context({'mail_create_nolog': True}).create({
|
cls.task_cow = cls.env['project.task'].with_context({'mail_create_nolog': True}).create({
|
||||||
'name': 'Cow UserTask',
|
'name': 'Cow UserTask',
|
||||||
'user_id': cls.user_projectuser.id,
|
'user_ids': cls.user_projectuser,
|
||||||
'project_id': cls.project_cows.id,
|
'project_id': cls.project_cows.id,
|
||||||
})
|
})
|
||||||
cls.task_portal = cls.env['project.task'].with_context({'mail_create_nolog': True}).create({
|
cls.task_portal = cls.env['project.task'].with_context({'mail_create_nolog': True}).create({
|
||||||
'name': 'Portal UserTask',
|
'name': 'Portal UserTask',
|
||||||
'user_id': cls.user_projectuser.id,
|
'user_ids': cls.user_projectuser,
|
||||||
'project_id': cls.project_portal.id,
|
'project_id': cls.project_portal.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ class TestProjectSharing(TestProjectSharingCommon):
|
|||||||
task = form.save()
|
task = form.save()
|
||||||
self.assertEqual(task.name, 'Test')
|
self.assertEqual(task.name, 'Test')
|
||||||
self.assertEqual(task.project_id, self.project_portal)
|
self.assertEqual(task.project_id, self.project_portal)
|
||||||
self.assertEqual(task.user_id, self.user_portal)
|
self.assertEqual(task.user_ids, self.user_portal)
|
||||||
# 3.1) Try to change the project of the new task with this user.
|
# 3.1) Try to change the project of the new task with this user.
|
||||||
with self.assertRaises(AssertionError, msg="Should not accept the portal user changes the project of the task."):
|
with self.assertRaises(AssertionError, msg="Should not accept the portal user changes the project of the task."):
|
||||||
form.project_id = self.project_cows
|
form.project_id = self.project_cows
|
||||||
@@ -172,4 +172,4 @@ class TestProjectSharing(TestProjectSharingCommon):
|
|||||||
form.save()
|
form.save()
|
||||||
self.assertEqual(task.child_ids.name, 'Test Subtask')
|
self.assertEqual(task.child_ids.name, 'Test Subtask')
|
||||||
self.assertEqual(task.child_ids.project_id, self.project_cows)
|
self.assertEqual(task.child_ids.project_id, self.project_cows)
|
||||||
self.assertEqual(task.child_ids.user_id, self.user_portal)
|
self.assertEqual(task.child_ids.user_ids, self.user_portal)
|
||||||
|
|||||||
@@ -10,10 +10,6 @@ class TestProjectSubtasks(TestProjectCommon):
|
|||||||
"""
|
"""
|
||||||
Create a task in the default task form should take the project set in the form or the default project in the context
|
Create a task in the default task form should take the project set in the form or the default project in the context
|
||||||
"""
|
"""
|
||||||
with self.assertRaises(AssertionError, msg="Should not accept a form without project. Project is required"):
|
|
||||||
with Form(self.env['project.task'].with_context({'tracking_disable': True})) as task_form:
|
|
||||||
task_form.name = 'Test Task 1'
|
|
||||||
|
|
||||||
with Form(self.env['project.task'].with_context({'tracking_disable': True})) as task_form:
|
with Form(self.env['project.task'].with_context({'tracking_disable': True})) as task_form:
|
||||||
task_form.name = 'Test Task 1'
|
task_form.name = 'Test Task 1'
|
||||||
task_form.project_id = self.project_pigs
|
task_form.project_id = self.project_pigs
|
||||||
@@ -33,10 +29,6 @@ class TestProjectSubtasks(TestProjectCommon):
|
|||||||
"""
|
"""
|
||||||
Create a task in the task form 2 should take the project set in the form or the default project in the context
|
Create a task in the task form 2 should take the project set in the form or the default project in the context
|
||||||
"""
|
"""
|
||||||
with self.assertRaises(AssertionError, msg="Should not accept a form without project. Project is required"):
|
|
||||||
with Form(self.env['project.task'].with_context({'tracking_disable': True}), view="project.view_task_form2") as task_form:
|
|
||||||
task_form.name = 'Test Task 1'
|
|
||||||
|
|
||||||
with Form(self.env['project.task'].with_context({'tracking_disable': True}), view="project.view_task_form2") as task_form:
|
with Form(self.env['project.task'].with_context({'tracking_disable': True}), view="project.view_task_form2") as task_form:
|
||||||
task_form.name = 'Test Task 1'
|
task_form.name = 'Test Task 1'
|
||||||
task_form.project_id = self.project_pigs
|
task_form.project_id = self.project_pigs
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class TestTaskDependencies(TestProjectCommon):
|
|||||||
})
|
})
|
||||||
cls.task_3 = cls.env['project.task'].with_context({'mail_create_nolog': True}).create({
|
cls.task_3 = cls.env['project.task'].with_context({'mail_create_nolog': True}).create({
|
||||||
'name': 'Pigs UserTask 2',
|
'name': 'Pigs UserTask 2',
|
||||||
'user_id': cls.user_projectuser.id,
|
'user_ids': cls.user_projectuser,
|
||||||
'project_id': cls.project_pigs.id,
|
'project_id': cls.project_pigs.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -97,7 +97,8 @@ class TestTaskDependencies(TestProjectCommon):
|
|||||||
self.env['res.config.settings'].create({
|
self.env['res.config.settings'].create({
|
||||||
'group_project_task_dependencies': True
|
'group_project_task_dependencies': True
|
||||||
}).execute()
|
}).execute()
|
||||||
self.task_1.write({
|
# `depend_on_ids` is tracked
|
||||||
|
self.task_1.with_context(mail_notrack=True).write({
|
||||||
'depend_on_ids': [Command.link(self.task_2.id)]
|
'depend_on_ids': [Command.link(self.task_2.id)]
|
||||||
})
|
})
|
||||||
self.cr.precommit.clear()
|
self.cr.precommit.clear()
|
||||||
|
|||||||
@@ -103,8 +103,8 @@
|
|||||||
<em class="font-weight-normal text-muted"><span t-field="tasks[0].sudo().project_id.label_tasks"/> in stage:</em>
|
<em class="font-weight-normal text-muted"><span t-field="tasks[0].sudo().project_id.label_tasks"/> in stage:</em>
|
||||||
<span class="text-truncate" t-field="tasks[0].sudo().stage_id.name"/></th>
|
<span class="text-truncate" t-field="tasks[0].sudo().stage_id.name"/></th>
|
||||||
<th t-if="groupby == 'user'">
|
<th t-if="groupby == 'user'">
|
||||||
<em class="font-weight-normal text-muted" t-if="tasks[0].sudo().user_id"><span t-field="tasks[0].sudo().project_id.label_tasks"/> assigned to:</em>
|
<em class="font-weight-normal text-muted" t-if="tasks[0].sudo().user_ids"><span t-field="tasks[0].sudo().project_id.label_tasks"/> assigned to:</em>
|
||||||
<span class="text-truncate" t-field="tasks[0].sudo().user_id.name"/></th>
|
<span class="text-truncate" t-esc="', '.join(tasks[0].sudo().mapped('user_ids.name'))"/></th>
|
||||||
<th t-if="groupby == 'customer'">
|
<th t-if="groupby == 'customer'">
|
||||||
<em class="font-weight-normal text-muted" t-if="tasks[0].sudo().partner_id"><span t-field="tasks[0].sudo().project_id.label_tasks"/> for customer:</em>
|
<em class="font-weight-normal text-muted" t-if="tasks[0].sudo().partner_id"><span t-field="tasks[0].sudo().project_id.label_tasks"/> for customer:</em>
|
||||||
<span class="text-truncate" t-field="tasks[0].sudo().partner_id.name"/></th>
|
<span class="text-truncate" t-field="tasks[0].sudo().partner_id.name"/></th>
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
#<span t-esc="task.id"/>
|
#<span t-esc="task.id"/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a t-attf-href="/my/#{task_url}/#{task.id}?{{ keep_query() }}"><span t-field="task.name"/></a>
|
<a t-attf-href="/my/task/#{task.id}?{{ keep_query() }}"><span t-field="task.name"/></a>
|
||||||
</td>
|
</td>
|
||||||
<td t-if="groupby != 'project'" class="text-center">
|
<td t-if="groupby != 'project'" class="text-center">
|
||||||
<span class="badge badge-pill badge-info" title="Current project of the task" t-esc="task.project_id.name" />
|
<span class="badge badge-pill badge-info" title="Current project of the task" t-esc="task.project_id.name" />
|
||||||
@@ -177,18 +177,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
<t t-set="card_body">
|
<t t-set="card_body">
|
||||||
<div class="row" t-if="task.user_id or task.partner_id">
|
<div class="row" t-if="task.user_ids or task.partner_id">
|
||||||
<div class="col-12 col-md-6 pb-2" t-if="task.user_id">
|
<div class="col-12 col-md-6 pb-2" t-if="task.user_ids">
|
||||||
<strong>Assigned to</strong>
|
<strong>Assigned to</strong>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col d-flex align-items-center flex-grow-0 pr-3">
|
<t t-foreach="task.user_ids" t-as="user">
|
||||||
<img class="rounded-circle mt-1 o_portal_contact_img" t-att-src="image_data_uri(task.user_id.avatar_1024)" alt="Contact"/>
|
<div class="col d-flex align-items-center flex-grow-0 pr-3">
|
||||||
</div>
|
<img class="rounded-circle mt-1 o_portal_contact_img" t-att-src="image_data_uri(user.avatar_1024)" alt="Contact"/>
|
||||||
<div class="col pl-md-0">
|
</div>
|
||||||
<div t-field="task.user_id" t-options='{"widget": "contact", "fields": ["name"]}'/>
|
<div class="col pl-md-0">
|
||||||
<a t-attf-href="mailto:{{task.user_id.email}}" t-if="task.user_id.email"><div t-field="task.user_id" t-options='{"widget": "contact", "fields": ["email"]}'/></a>
|
<div t-esc="user" t-options='{"widget": "contact", "fields": ["name"]}'/>
|
||||||
<a t-attf-href="tel:{{task.user_id.phone}}" t-if="task.user_id.phone"><div t-field="task.user_id" t-options='{"widget": "contact", "fields": ["phone"]}'/></a>
|
<a t-attf-href="mailto:{{user.email}}" t-if="user.email"><div t-esc="user" t-options='{"widget": "contact", "fields": ["email"]}'/></a>
|
||||||
</div>
|
<a t-attf-href="tel:{{user.phone}}" t-if="user.phone"><div t-esc="user" t-options='{"widget": "contact", "fields": ["phone"]}'/></a>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="coll-12 col-md-6 pb-2" t-if="task.partner_id">
|
<div class="coll-12 col-md-6 pb-2" t-if="task.partner_id">
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
<field name="color"/>
|
<field name="color"/>
|
||||||
<field name="priority"/>
|
<field name="priority"/>
|
||||||
<field name="stage_id" options='{"group_by_tooltip": {"description": "Description"}}'/>
|
<field name="stage_id" options='{"group_by_tooltip": {"description": "Description"}}'/>
|
||||||
<field name="user_id"/>
|
<field name="user_ids"/>
|
||||||
<field name="partner_id"/>
|
<field name="partner_id"/>
|
||||||
<field name="sequence"/>
|
<field name="sequence"/>
|
||||||
<field name="is_closed"/>
|
<field name="is_closed"/>
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="oe_kanban_bottom_right" t-if="!selection_mode">
|
<div class="oe_kanban_bottom_right" t-if="!selection_mode">
|
||||||
<field name="kanban_state" widget="state_selection"/>
|
<field name="kanban_state" widget="state_selection"/>
|
||||||
<field name="user_id" widget="many2one_avatar_user" options="{'no_open_chat': True}"/>
|
<field name="user_ids" widget="many2many_avatar_user" options="{'no_open_chat': True}"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
<field name="child_text" invisible="1"/>
|
<field name="child_text" invisible="1"/>
|
||||||
<field name="company_id" invisible="1"/>
|
<field name="company_id" invisible="1"/>
|
||||||
<field name="partner_id" optional="hide"/>
|
<field name="partner_id" optional="hide"/>
|
||||||
<field name="user_id" optional="show" widget="many2one_avatar_user" options="{'no_open_chat': True}"/>
|
<field name="user_ids" optional="show" widget="many2many_avatar_user" options="{'no_open_chat': True}"/>
|
||||||
<field name="date_deadline" optional="hide" widget="remaining_days" attrs="{'invisible': [('is_closed', '=', True)]}"/>
|
<field name="date_deadline" optional="hide" widget="remaining_days" attrs="{'invisible': [('is_closed', '=', True)]}"/>
|
||||||
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}" optional="show"/>
|
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}" optional="show"/>
|
||||||
<field name="kanban_state" widget="state_selection" optional="hide"/>
|
<field name="kanban_state" widget="state_selection" optional="hide"/>
|
||||||
@@ -151,10 +151,10 @@
|
|||||||
<group>
|
<group>
|
||||||
<field name="project_id" invisible="1"/>
|
<field name="project_id" invisible="1"/>
|
||||||
<field name="display_project_id" string="Project" invisible="1"/>
|
<field name="display_project_id" string="Project" invisible="1"/>
|
||||||
<field name="user_id"
|
<field name="user_ids"
|
||||||
class="o_task_user_field"
|
class="o_task_user_field"
|
||||||
options="{'no_open': True, 'no_open_chat': True}"
|
options="{'no_open': True, 'no_open_chat': True}"
|
||||||
widget="many2one_avatar_user"
|
widget="many2many_avatar_user"
|
||||||
domain="[('share', '=', False)]"/>
|
domain="[('share', '=', False)]"/>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
@@ -173,7 +173,7 @@
|
|||||||
<field name="description" type="html"/>
|
<field name="description" type="html"/>
|
||||||
</page>
|
</page>
|
||||||
<page name="sub_tasks_page" string="Sub-tasks" attrs="{'invisible': [('allow_subtasks', '=', False)]}">
|
<page name="sub_tasks_page" string="Sub-tasks" attrs="{'invisible': [('allow_subtasks', '=', False)]}">
|
||||||
<field name="child_ids" context="{'default_project_id': project_id if not parent_id or not display_project_id else display_project_id, 'default_user_id': uid, 'default_parent_id': id, 'default_partner_id': partner_id}">
|
<field name="child_ids" context="{'default_project_id': project_id if not parent_id or not display_project_id else display_project_id, 'default_user_ids': [(6, 0, [uid])], 'default_parent_id': id, 'default_partner_id': partner_id}">
|
||||||
<tree editable="bottom">
|
<tree editable="bottom">
|
||||||
<field name="project_id" invisible="1"/>
|
<field name="project_id" invisible="1"/>
|
||||||
<field name="is_closed" invisible="1"/>
|
<field name="is_closed" invisible="1"/>
|
||||||
@@ -182,7 +182,7 @@
|
|||||||
<field name="display_project_id" string="Project" optional="hide" invisible="1"/>
|
<field name="display_project_id" string="Project" optional="hide" invisible="1"/>
|
||||||
<field name="company_id" invisible="1"/>
|
<field name="company_id" invisible="1"/>
|
||||||
<field name="partner_id" options="{'no_open': True, 'no_create': True, 'no_edit': True}" optional="hide"/>
|
<field name="partner_id" options="{'no_open': True, 'no_create': True, 'no_edit': True}" optional="hide"/>
|
||||||
<field name="user_id" widget="many2one_avatar_user" optional="show" options="{'no_open_chat': True}"/>
|
<field name="user_ids" widget="many2many_avatar_user" optional="show" options="{'no_open_chat': True}"/>
|
||||||
<field name="date_deadline" attrs="{'invisible': [('is_closed', '=', True)]}" optional="show"/>
|
<field name="date_deadline" attrs="{'invisible': [('is_closed', '=', True)]}" optional="show"/>
|
||||||
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}" optional="hide"/>
|
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}" optional="hide"/>
|
||||||
<field name="kanban_state" widget="state_selection" optional="hide"/>
|
<field name="kanban_state" widget="state_selection" optional="hide"/>
|
||||||
@@ -208,11 +208,11 @@
|
|||||||
<search string="Tasks">
|
<search string="Tasks">
|
||||||
<field name="name" string="Task"/>
|
<field name="name" string="Task"/>
|
||||||
<field name="tag_ids"/>
|
<field name="tag_ids"/>
|
||||||
<field name="user_id"/>
|
<field name="user_ids"/>
|
||||||
<field name="partner_id" operator="child_of"/>
|
<field name="partner_id" operator="child_of"/>
|
||||||
<field name="stage_id"/>
|
<field name="stage_id"/>
|
||||||
<field string="Project" name="display_project_id"/>
|
<field string="Project" name="display_project_id"/>
|
||||||
<filter string="Unassigned" name="unassigned" domain="[('user_id', '=', False)]"/>
|
<filter string="Unassigned" name="unassigned" domain="[('user_ids', '=', False)]"/>
|
||||||
<separator/>
|
<separator/>
|
||||||
<filter string="Starred" name="starred" domain="[('priority', 'in', [1, 2])]"/>
|
<filter string="Starred" name="starred" domain="[('priority', 'in', [1, 2])]"/>
|
||||||
<filter string="Late Tasks" name="late" domain="[('date_deadline', '<', context_today().strftime('%Y-%m-%d'))]"/>
|
<filter string="Late Tasks" name="late" domain="[('date_deadline', '<', context_today().strftime('%Y-%m-%d'))]"/>
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
domain="[('activity_ids.date_deadline', '>', context_today().strftime('%Y-%m-%d'))]"/>
|
domain="[('activity_ids.date_deadline', '>', context_today().strftime('%Y-%m-%d'))]"/>
|
||||||
<group expand="0" string="Group By">
|
<group expand="0" string="Group By">
|
||||||
<filter string="Stage" name="stage" context="{'group_by': 'stage_id'}"/>
|
<filter string="Stage" name="stage" context="{'group_by': 'stage_id'}"/>
|
||||||
<filter string="Assigned to" name="user" context="{'group_by': 'user_id'}"/>
|
<filter string="Assigned to" name="user" context="{'group_by': 'user_ids'}"/>
|
||||||
<filter string="Project" name="project" context="{'group_by': 'project_id'}"/>
|
<filter string="Project" name="project" context="{'group_by': 'project_id'}"/>
|
||||||
<filter string="Customer" name="customer" context="{'group_by': 'partner_id'}"/>
|
<filter string="Customer" name="customer" context="{'group_by': 'partner_id'}"/>
|
||||||
<filter string="Kanban State" name="kanban_state" context="{'group_by': 'kanban_state'}"/>
|
<filter string="Kanban State" name="kanban_state" context="{'group_by': 'kanban_state'}"/>
|
||||||
|
|||||||
@@ -18,14 +18,13 @@
|
|||||||
<search string="Tasks">
|
<search string="Tasks">
|
||||||
<field name="name" string="Task"/>
|
<field name="name" string="Task"/>
|
||||||
<field name="tag_ids"/>
|
<field name="tag_ids"/>
|
||||||
<field name="user_id"/>
|
<field name="user_ids"/>
|
||||||
<field string="Project" name="display_project_id"/>
|
<field string="Project" name="display_project_id"/>
|
||||||
<field name="stage_id"/>
|
<field name="stage_id"/>
|
||||||
<field name="partner_id" operator="child_of"/>
|
<field name="partner_id" operator="child_of"/>
|
||||||
<field name="parent_id"/>
|
<field name="parent_id"/>
|
||||||
<filter string="My Tasks" name="my_tasks" domain="[('user_id', '=', uid)]"/>
|
<filter string="My Tasks" name="my_tasks" domain="[('user_ids', 'in', uid)]"/>
|
||||||
<filter string="Followed Tasks" name="my_followed_tasks" domain="[('message_is_follower', '=', True)]" />
|
<filter string="Unassigned" name="unassigned" domain="[('user_ids', '=', False)]"/>
|
||||||
<filter string="Unassigned" name="unassigned" domain="[('user_id', '=', False)]"/>
|
|
||||||
<separator/>
|
<separator/>
|
||||||
<filter string="Starred" name="starred" domain="[('priority', 'in', [0, 1])]"/>
|
<filter string="Starred" name="starred" domain="[('priority', 'in', [0, 1])]"/>
|
||||||
<separator/>
|
<separator/>
|
||||||
@@ -46,7 +45,8 @@
|
|||||||
domain="[('my_activity_date_deadline', '>', context_today().strftime('%Y-%m-%d'))]"/>
|
domain="[('my_activity_date_deadline', '>', context_today().strftime('%Y-%m-%d'))]"/>
|
||||||
<group expand="0" string="Group By">
|
<group expand="0" string="Group By">
|
||||||
<filter string="Stage" name="stage" context="{'group_by': 'stage_id'}"/>
|
<filter string="Stage" name="stage" context="{'group_by': 'stage_id'}"/>
|
||||||
<filter string="Assigned to" name="user" context="{'group_by': 'user_id'}"/>
|
<filter string="Personal Stage" name="personal_stage" context="{'group_by': 'personal_stage_type_ids'}"/>
|
||||||
|
<filter string="Assigned to" name="user" context="{'group_by': 'user_ids'}"/>
|
||||||
<filter string="Project" name="project" context="{'group_by': 'project_id'}"/>
|
<filter string="Project" name="project" context="{'group_by': 'project_id'}"/>
|
||||||
<filter string="Customer" name="customer" context="{'group_by': 'partner_id'}"/>
|
<filter string="Customer" name="customer" context="{'group_by': 'partner_id'}"/>
|
||||||
<filter string="Kanban State" name="kanban_state" context="{'group_by': 'kanban_state'}"/>
|
<filter string="Kanban State" name="kanban_state" context="{'group_by': 'kanban_state'}"/>
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
<field name="view_mode">kanban,tree,form,calendar,pivot,graph,activity</field>
|
<field name="view_mode">kanban,tree,form,calendar,pivot,graph,activity</field>
|
||||||
<field name="domain">[('display_project_id', '=', active_id)]</field>
|
<field name="domain">[('display_project_id', '=', active_id)]</field>
|
||||||
<field name="context">{
|
<field name="context">{
|
||||||
'pivot_row_groupby': ['user_id'],
|
'pivot_row_groupby': ['user_ids'],
|
||||||
'default_project_id': active_id,
|
'default_project_id': active_id,
|
||||||
'show_project_update': True,
|
'show_project_update': True,
|
||||||
}</field>
|
}</field>
|
||||||
@@ -184,6 +184,7 @@
|
|||||||
<field name="rating_template_id" optional="hide" groups="project.group_project_rating"/>
|
<field name="rating_template_id" optional="hide" groups="project.group_project_rating"/>
|
||||||
<field name="project_ids" optional="show" widget="many2many_tags" options="{'color_field': 'color'}" />
|
<field name="project_ids" optional="show" widget="many2many_tags" options="{'color_field': 'color'}" />
|
||||||
<field name="fold" optional="show"/>
|
<field name="fold" optional="show"/>
|
||||||
|
<field name="user_id" optional="show"/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
@@ -783,7 +784,7 @@
|
|||||||
<field name="allow_task_dependencies" invisible="1" />
|
<field name="allow_task_dependencies" invisible="1" />
|
||||||
<header>
|
<header>
|
||||||
<button name="action_assign_to_me" string="Assign to Me" type="object" class="oe_highlight"
|
<button name="action_assign_to_me" string="Assign to Me" type="object" class="oe_highlight"
|
||||||
attrs="{'invisible' : [('user_id', '!=', False)]}" data-hotkey="q"/>
|
attrs="{'invisible' : [('user_ids', '!=', False)]}" data-hotkey="q"/>
|
||||||
<field name="stage_id" widget="statusbar" options="{'clickable': '1', 'fold_field': 'fold'}" attrs="{'invisible': [('project_id', '=', False), ('stage_id', '=', False)]}"/>
|
<field name="stage_id" widget="statusbar" options="{'clickable': '1', 'fold_field': 'fold'}" attrs="{'invisible': [('project_id', '=', False), ('stage_id', '=', False)]}"/>
|
||||||
</header>
|
</header>
|
||||||
<div class="alert alert-info oe_edit_only mb-0" role="status" attrs="{'invisible': ['|', ('recurring_task', '=', False), ('recurrence_id', '=', False)]}">
|
<div class="alert alert-info oe_edit_only mb-0" role="status" attrs="{'invisible': ['|', ('recurring_task', '=', False), ('recurrence_id', '=', False)]}">
|
||||||
@@ -816,12 +817,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field name="project_id" attrs="{'required': [('parent_id', '=', False)], 'invisible': [('parent_id', '!=', False)]}" domain="[('active', '=', True), ('company_id', '=', company_id)]"/>
|
<label for="project_id" class="oe_read_only"/>
|
||||||
|
<div colspan="1" class="oe_read_only">
|
||||||
|
<i class="o_project_task_project_field text-danger" attrs="{'invisible': [('project_id', '!=', False)]}">Private</i>
|
||||||
|
<field name="project_id" attrs="{'invisible': [('project_id', '=', False)]}" nolabel="1"/>
|
||||||
|
</div>
|
||||||
|
<field name="project_id" attrs="{'invisible': [('parent_id', '!=', False)]}" domain="[('active', '=', True), ('company_id', '=', company_id)]"
|
||||||
|
placeholder="Private" class="o_project_task_project_field oe_edit_only"/>
|
||||||
<field name="display_project_id" string="Project" attrs="{'invisible': [('parent_id', '=', False)]}" domain="[('active', '=', True), ('company_id', '=', company_id)]"/>
|
<field name="display_project_id" string="Project" attrs="{'invisible': [('parent_id', '=', False)]}" domain="[('active', '=', True), ('company_id', '=', company_id)]"/>
|
||||||
<field name="user_id"
|
<field name="user_ids"
|
||||||
class="o_task_user_field"
|
class="o_task_user_field"
|
||||||
options="{'no_open': True}"
|
options="{'no_open': True}"
|
||||||
widget="many2one_avatar_user"
|
widget="many2many_avatar_user"
|
||||||
domain="[('share', '=', False)]"/>
|
domain="[('share', '=', False)]"/>
|
||||||
<field name="parent_id"
|
<field name="parent_id"
|
||||||
domain="[('parent_id', '=', False)]"
|
domain="[('parent_id', '=', False)]"
|
||||||
@@ -846,7 +853,7 @@
|
|||||||
<field name="description" type="html"/>
|
<field name="description" type="html"/>
|
||||||
</page>
|
</page>
|
||||||
<page name="sub_tasks_page" string="Sub-tasks" attrs="{'invisible': [('allow_subtasks', '=', False)]}">
|
<page name="sub_tasks_page" string="Sub-tasks" attrs="{'invisible': [('allow_subtasks', '=', False)]}">
|
||||||
<field name="child_ids" context="{'default_project_id': project_id if not parent_id or not display_project_id else display_project_id, 'default_user_id': user_id, 'default_parent_id': id, 'default_partner_id': partner_id}">
|
<field name="child_ids" context="{'default_project_id': project_id if not parent_id or not display_project_id else display_project_id, 'default_user_ids': user_ids, 'default_parent_id': id, 'default_partner_id': partner_id}">
|
||||||
<tree editable="bottom">
|
<tree editable="bottom">
|
||||||
<field name="project_id" invisible="1"/>
|
<field name="project_id" invisible="1"/>
|
||||||
<field name="is_closed" invisible="1"/>
|
<field name="is_closed" invisible="1"/>
|
||||||
@@ -854,7 +861,7 @@
|
|||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="display_project_id" string="Project" optional="hide"/>
|
<field name="display_project_id" string="Project" optional="hide"/>
|
||||||
<field name="partner_id" optional="hide"/>
|
<field name="partner_id" optional="hide"/>
|
||||||
<field name="user_id" widget="many2one_avatar_user" optional="show"/>
|
<field name="user_ids" widget="many2many_avatar_user" optional="show"/>
|
||||||
<field name="company_id" groups="base.group_multi_company" optional="hide"/>
|
<field name="company_id" groups="base.group_multi_company" optional="hide"/>
|
||||||
<field name="activity_ids" widget="list_activity" optional="hide"/>
|
<field name="activity_ids" widget="list_activity" optional="hide"/>
|
||||||
<field name="date_deadline" attrs="{'invisible': [('is_closed', '=', True)]}" optional="show"/>
|
<field name="date_deadline" attrs="{'invisible': [('is_closed', '=', True)]}" optional="show"/>
|
||||||
@@ -871,7 +878,7 @@
|
|||||||
<field name="is_closed" invisible="1" />
|
<field name="is_closed" invisible="1" />
|
||||||
<field name="name" />
|
<field name="name" />
|
||||||
<field name="project_id" optional="hide" />
|
<field name="project_id" optional="hide" />
|
||||||
<field name="user_id" widget="many2one_avatar_user" optional="show"/>
|
<field name="user_ids" widget="many2many_avatar_user" optional="show"/>
|
||||||
<field name="company_id" optional="hide" groups="base.group_multi_company" />
|
<field name="company_id" optional="hide" groups="base.group_multi_company" />
|
||||||
<field name="activity_ids" widget="list_activity" optional="hide"/>
|
<field name="activity_ids" widget="list_activity" optional="hide"/>
|
||||||
<field name="date_deadline" attrs="{'invisible': [('is_closed', '=', True)]}" optional="show" />
|
<field name="date_deadline" attrs="{'invisible': [('is_closed', '=', True)]}" optional="show" />
|
||||||
@@ -980,7 +987,8 @@
|
|||||||
<form>
|
<form>
|
||||||
<group>
|
<group>
|
||||||
<field name="name" string = "Task Title" placeholder="e.g. New Design"/>
|
<field name="name" string = "Task Title" placeholder="e.g. New Design"/>
|
||||||
<field name="user_id" options="{'no_open': True,'no_create': True}" domain="[('share', '=', False)]"/>
|
<field name="user_ids" options="{'no_open': True,'no_create': True}" domain="[('share', '=', False)]"
|
||||||
|
widget="many2many_tags"/>
|
||||||
<field name="company_id" invisible="1"/>
|
<field name="company_id" invisible="1"/>
|
||||||
<field name="parent_id" invisible="1" groups="base.group_no_one"/>
|
<field name="parent_id" invisible="1" groups="base.group_no_one"/>
|
||||||
</group>
|
</group>
|
||||||
@@ -993,11 +1001,12 @@
|
|||||||
<field name="name">project.task.kanban</field>
|
<field name="name">project.task.kanban</field>
|
||||||
<field name="model">project.task</field>
|
<field name="model">project.task</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<kanban default_group_by="stage_id" class="o_kanban_small_column o_kanban_project_tasks" on_create="quick_create" quick_create_view="project.quick_create_task_form" examples="project" js_class="project_task_kanban" sample="1">
|
<kanban default_group_by="stage_id" class="o_kanban_small_column o_kanban_project_tasks" on_create="quick_create"
|
||||||
|
quick_create_view="project.quick_create_task_form" examples="project" js_class="project_task_kanban" sample="1">
|
||||||
<field name="color"/>
|
<field name="color"/>
|
||||||
<field name="priority"/>
|
<field name="priority"/>
|
||||||
<field name="stage_id" options='{"group_by_tooltip": {"description": "Description"}}'/>
|
<field name="stage_id" options='{"group_by_tooltip": {"description": "Description"}}'/>
|
||||||
<field name="user_id"/>
|
<field name="user_ids"/>
|
||||||
<field name="partner_id"/>
|
<field name="partner_id"/>
|
||||||
<field name="sequence"/>
|
<field name="sequence"/>
|
||||||
<field name="is_closed"/>
|
<field name="is_closed"/>
|
||||||
@@ -1013,6 +1022,7 @@
|
|||||||
<field name="rating_ids"/>
|
<field name="rating_ids"/>
|
||||||
<field name="allow_subtasks"/>
|
<field name="allow_subtasks"/>
|
||||||
<field name="child_text"/>
|
<field name="child_text"/>
|
||||||
|
<field name="is_private"/>
|
||||||
<progressbar field="kanban_state" colors='{"done": "success", "blocked": "danger", "normal": "muted"}'/>
|
<progressbar field="kanban_state" colors='{"done": "success", "blocked": "danger", "normal": "muted"}'/>
|
||||||
<templates>
|
<templates>
|
||||||
<t t-name="kanban-box">
|
<t t-name="kanban-box">
|
||||||
@@ -1024,7 +1034,13 @@
|
|||||||
<s t-if="!record.active.raw_value"><field name="name" widget="name_with_subtask_count"/></s>
|
<s t-if="!record.active.raw_value"><field name="name" widget="name_with_subtask_count"/></s>
|
||||||
<t t-else=""><field name="name" widget="name_with_subtask_count"/></t>
|
<t t-else=""><field name="name" widget="name_with_subtask_count"/></t>
|
||||||
</strong>
|
</strong>
|
||||||
<span invisible="context.get('default_project_id', False)"><br/><field name="project_id" required="1"/></span>
|
<t t-if="!record.is_private.raw_value">
|
||||||
|
<span invisible="context.get('default_project_id', False)"><br/><field name="project_id" required="1"/></span>
|
||||||
|
</t>
|
||||||
|
<t t-else="">
|
||||||
|
<br/>
|
||||||
|
<span class="fa fa-lock text-muted"/><span class="text-muted"> Private</span>
|
||||||
|
</t>
|
||||||
<br />
|
<br />
|
||||||
<t t-if="record.partner_id.value">
|
<t t-if="record.partner_id.value">
|
||||||
<span t-if="!record.partner_is_company.raw_value">
|
<span t-if="!record.partner_is_company.raw_value">
|
||||||
@@ -1070,7 +1086,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="oe_kanban_bottom_right" t-if="!selection_mode">
|
<div class="oe_kanban_bottom_right" t-if="!selection_mode">
|
||||||
<field name="kanban_state" widget="state_selection" groups="base.group_user" invisible="context.get('fsm_mode', False)"/>
|
<field name="kanban_state" widget="state_selection" groups="base.group_user" invisible="context.get('fsm_mode', False)"/>
|
||||||
<t t-if="record.user_id.raw_value"><field name="user_id" widget="many2one_avatar_user"/></t>
|
<t t-if="record.user_ids.raw_value"><field name="user_ids" widget="many2many_avatar_user"/></t>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1098,7 +1114,7 @@
|
|||||||
<field name="project_id" optional="show" readonly="1"/>
|
<field name="project_id" optional="show" readonly="1"/>
|
||||||
<field name="partner_id" optional="hide"/>
|
<field name="partner_id" optional="hide"/>
|
||||||
<field name="parent_id" optional="hide" attrs="{'invisible': [('allow_subtasks', '=', False)]}" groups="base.group_no_one"/>
|
<field name="parent_id" optional="hide" attrs="{'invisible': [('allow_subtasks', '=', False)]}" groups="base.group_no_one"/>
|
||||||
<field name="user_id" optional="show" widget="many2one_avatar_user"/>
|
<field name="user_ids" optional="show" widget="many2many_avatar_user"/>
|
||||||
<field name="company_id" groups="base.group_multi_company" optional="show"/>
|
<field name="company_id" groups="base.group_multi_company" optional="show"/>
|
||||||
<field name="activity_ids" widget="list_activity" optional="show"/>
|
<field name="activity_ids" widget="list_activity" optional="show"/>
|
||||||
<field name="date_deadline" optional="hide" widget="remaining_days" attrs="{'invisible': [('is_closed', '=', True)]}"/>
|
<field name="date_deadline" optional="hide" widget="remaining_days" attrs="{'invisible': [('is_closed', '=', True)]}"/>
|
||||||
@@ -1131,10 +1147,10 @@
|
|||||||
<field name="model">project.task</field>
|
<field name="model">project.task</field>
|
||||||
<field eval="2" name="priority"/>
|
<field eval="2" name="priority"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<calendar date_start="date_deadline" string="Tasks" mode="month" color="user_id" event_limit="5"
|
<calendar date_start="date_deadline" string="Tasks" mode="month" color="user_ids" event_limit="5"
|
||||||
hide_time="true" js_class="project_calendar" event_open_popup="true" quick_add="false">
|
hide_time="true" js_class="project_calendar" event_open_popup="true" quick_add="false">
|
||||||
<field name="project_id"/>
|
<field name="project_id" filters="1"/>
|
||||||
<field name="user_id" avatar_field="avatar_128" filters="1"/>
|
<field name="user_ids" widget="many2many_avatar_user"/>
|
||||||
<field name="partner_id" attrs="{'invisible': [('partner_id', '=', False)]}"/>
|
<field name="partner_id" attrs="{'invisible': [('partner_id', '=', False)]}"/>
|
||||||
<field name="priority" widget="priority"/>
|
<field name="priority" widget="priority"/>
|
||||||
<field name="date_deadline"/>
|
<field name="date_deadline"/>
|
||||||
@@ -1181,10 +1197,10 @@
|
|||||||
<field name="model">project.task</field>
|
<field name="model">project.task</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<activity string="Project Tasks" js_class="project_activity">
|
<activity string="Project Tasks" js_class="project_activity">
|
||||||
<field name="user_id"/>
|
<field name="user_ids"/>
|
||||||
<templates>
|
<templates>
|
||||||
<div t-name="activity-box">
|
<div t-name="activity-box">
|
||||||
<field name="user_id" widget="many2one_avatar_user"/>
|
<field name="user_ids" widget="many2many_avatar_user"/>
|
||||||
<div>
|
<div>
|
||||||
<field name="name" display="full"/>
|
<field name="name" display="full"/>
|
||||||
<field name="project_id" muted="1" display="full" invisible="context.get('default_project_id', False)"/>
|
<field name="project_id" muted="1" display="full" invisible="context.get('default_project_id', False)"/>
|
||||||
@@ -1213,10 +1229,10 @@
|
|||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="action_view_all_task" model="ir.actions.act_window">
|
<record id="action_view_all_task" model="ir.actions.act_window">
|
||||||
<field name="name">Tasks</field>
|
<field name="name">My Tasks</field>
|
||||||
<field name="res_model">project.task</field>
|
<field name="res_model">project.task</field>
|
||||||
<field name="view_mode">tree,kanban,form,calendar,pivot,graph,activity</field>
|
<field name="view_mode">kanban,tree,form,calendar,pivot,graph,activity</field>
|
||||||
<field name="context">{'search_default_my_tasks': 1, 'all_task': 0}</field>
|
<field name="context">{'search_default_my_tasks': 1, 'search_default_personal_stage': 1, 'all_task': 0}</field>
|
||||||
<field name="search_view_id" ref="view_task_search_form_extended"/>
|
<field name="search_view_id" ref="view_task_search_form_extended"/>
|
||||||
<field name="help" type="html">
|
<field name="help" type="html">
|
||||||
<p class="o_view_nocontent_smiling_face">
|
<p class="o_view_nocontent_smiling_face">
|
||||||
@@ -1239,7 +1255,7 @@
|
|||||||
<field name="act_window_id" ref="action_view_task"/>
|
<field name="act_window_id" ref="action_view_task"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<menuitem name="Tasks" id="menu_project_management" parent="menu_main_pm"
|
<menuitem name="My Tasks" id="menu_project_management" parent="menu_main_pm"
|
||||||
action="action_view_all_task" sequence="2" groups="base.group_no_one,group_project_user"/>
|
action="action_view_all_task" sequence="2" groups="base.group_no_one,group_project_user"/>
|
||||||
|
|
||||||
<record id="project_task_action_from_partner" model="ir.actions.act_window">
|
<record id="project_task_action_from_partner" model="ir.actions.act_window">
|
||||||
@@ -1283,7 +1299,7 @@
|
|||||||
<field name="name">Assigned Tasks</field>
|
<field name="name">Assigned Tasks</field>
|
||||||
<field name="res_model">project.task</field>
|
<field name="res_model">project.task</field>
|
||||||
<field name="view_mode">tree,form,calendar,graph</field>
|
<field name="view_mode">tree,form,calendar,graph</field>
|
||||||
<field name="context">{'search_default_user_id': [active_id], 'default_user_id': active_id}</field>
|
<field name="context">{'search_default_user_ids': [active_id], 'default_user_ids': active_id}</field>
|
||||||
<field name="domain">[('display_project_id', '!=', False)]</field>
|
<field name="domain">[('display_project_id', '!=', False)]</field>
|
||||||
<field name="binding_model_id" ref="base.model_res_users"/>
|
<field name="binding_model_id" ref="base.model_res_users"/>
|
||||||
<field name="binding_view_types">form</field>
|
<field name="binding_view_types">form</field>
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ class SaleOrderLine(models.Model):
|
|||||||
'sale_line_id': self.id,
|
'sale_line_id': self.id,
|
||||||
'sale_order_id': self.order_id.id,
|
'sale_order_id': self.order_id.id,
|
||||||
'company_id': project.company_id.id,
|
'company_id': project.company_id.id,
|
||||||
'user_id': False, # force non assigned task, as created as sudo()
|
'user_ids': False, # force non assigned task, as created as sudo()
|
||||||
}
|
}
|
||||||
|
|
||||||
def _timesheet_create_task(self, project):
|
def _timesheet_create_task(self, project):
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ class TestNestedTaskUpdate(TransactionCase):
|
|||||||
#----------------------------------
|
#----------------------------------
|
||||||
|
|
||||||
def test_creating_subtask_user_id_on_parent_dont_go_on_child(self):
|
def test_creating_subtask_user_id_on_parent_dont_go_on_child(self):
|
||||||
parent = self.env['project.task'].create({'name': 'parent', 'user_id': self.user.id})
|
parent = self.env['project.task'].create({'name': 'parent', 'user_ids': [(4, self.user.id)]})
|
||||||
child = self.env['project.task'].create({'name': 'child', 'parent_id': parent.id, 'user_id': False})
|
child = self.env['project.task'].create({'name': 'child', 'parent_id': parent.id, 'user_ids': False})
|
||||||
self.assertFalse(child.user_id)
|
self.assertFalse(child.user_ids)
|
||||||
|
|
||||||
def test_creating_subtask_partner_id_on_parent_goes_on_child(self):
|
def test_creating_subtask_partner_id_on_parent_goes_on_child(self):
|
||||||
parent = self.env['project.task'].create({'name': 'parent', 'partner_id': self.user.partner_id.id})
|
parent = self.env['project.task'].create({'name': 'parent', 'partner_id': self.user.partner_id.id})
|
||||||
@@ -88,13 +88,13 @@ class TestNestedTaskUpdate(TransactionCase):
|
|||||||
#----------------------------------------
|
#----------------------------------------
|
||||||
|
|
||||||
def test_write_user_id_on_parent_dont_write_on_child(self):
|
def test_write_user_id_on_parent_dont_write_on_child(self):
|
||||||
parent = self.env['project.task'].create({'name': 'parent', 'user_id': False})
|
parent = self.env['project.task'].create({'name': 'parent', 'user_ids': False})
|
||||||
child = self.env['project.task'].create({'name': 'child', 'user_id': False, 'parent_id': parent.id})
|
child = self.env['project.task'].create({'name': 'child', 'user_ids': False, 'parent_id': parent.id})
|
||||||
self.assertFalse(child.user_id)
|
self.assertFalse(child.user_ids)
|
||||||
parent.write({'user_id': self.user.id})
|
parent.write({'user_ids': [(4, self.user.id)]})
|
||||||
self.assertFalse(child.user_id)
|
self.assertFalse(child.user_ids)
|
||||||
parent.write({'user_id': False})
|
parent.write({'user_ids': False})
|
||||||
self.assertFalse(child.user_id)
|
self.assertFalse(child.user_ids)
|
||||||
|
|
||||||
def test_write_partner_id_on_parent_write_on_child(self):
|
def test_write_partner_id_on_parent_write_on_child(self):
|
||||||
parent = self.env['project.task'].create({'name': 'parent', 'partner_id': False})
|
parent = self.env['project.task'].create({'name': 'parent', 'partner_id': False})
|
||||||
@@ -148,11 +148,11 @@ class TestNestedTaskUpdate(TransactionCase):
|
|||||||
#----------------------------------
|
#----------------------------------
|
||||||
|
|
||||||
def test_linking_user_id_on_parent_dont_write_on_child(self):
|
def test_linking_user_id_on_parent_dont_write_on_child(self):
|
||||||
parent = self.env['project.task'].create({'name': 'parent', 'user_id': self.user.id})
|
parent = self.env['project.task'].create({'name': 'parent', 'user_ids': [(4, self.user.id)]})
|
||||||
child = self.env['project.task'].create({'name': 'child', 'user_id': False})
|
child = self.env['project.task'].create({'name': 'child', 'user_ids': False})
|
||||||
self.assertFalse(child.user_id)
|
self.assertFalse(child.user_ids)
|
||||||
child.write({'parent_id': parent.id})
|
child.write({'parent_id': parent.id})
|
||||||
self.assertFalse(child.user_id)
|
self.assertFalse(child.user_ids)
|
||||||
|
|
||||||
def test_linking_partner_id_on_parent_write_on_child(self):
|
def test_linking_partner_id_on_parent_write_on_child(self):
|
||||||
parent = self.env['project.task'].create({'name': 'parent', 'partner_id': self.user.partner_id.id})
|
parent = self.env['project.task'].create({'name': 'parent', 'partner_id': self.user.partner_id.id})
|
||||||
@@ -195,8 +195,8 @@ class TestNestedTaskUpdate(TransactionCase):
|
|||||||
self.assertFalse(child.sale_line_id)
|
self.assertFalse(child.sale_line_id)
|
||||||
|
|
||||||
def test_writing_on_parent_with_multiple_tasks(self):
|
def test_writing_on_parent_with_multiple_tasks(self):
|
||||||
parent = self.env['project.task'].create({'name': 'parent', 'user_id': False, 'partner_id': self.partner.id})
|
parent = self.env['project.task'].create({'name': 'parent', 'user_ids': False, 'partner_id': self.partner.id})
|
||||||
children_values = [{'name': 'child%s' % i, 'user_id': False, 'parent_id': parent.id} for i in range(5)]
|
children_values = [{'name': 'child%s' % i, 'user_ids': False, 'parent_id': parent.id} for i in range(5)]
|
||||||
children = self.env['project.task'].create(children_values)
|
children = self.env['project.task'].create(children_values)
|
||||||
children._compute_partner_id()
|
children._compute_partner_id()
|
||||||
# test writing sale_line_id
|
# test writing sale_line_id
|
||||||
@@ -207,17 +207,17 @@ class TestNestedTaskUpdate(TransactionCase):
|
|||||||
self.assertEqual(child.sale_line_id, self.order_line)
|
self.assertEqual(child.sale_line_id, self.order_line)
|
||||||
|
|
||||||
def test_linking_on_parent_with_multiple_tasks(self):
|
def test_linking_on_parent_with_multiple_tasks(self):
|
||||||
parent = self.env['project.task'].create({'name': 'parent', 'partner_id': self.partner.id, 'sale_line_id': self.order_line.id, 'user_id': self.user.id})
|
parent = self.env['project.task'].create({'name': 'parent', 'partner_id': self.partner.id, 'sale_line_id': self.order_line.id, 'user_ids': [(4, self.user.id)]})
|
||||||
children_values = [{'name': 'child%s' % i, 'user_id': False} for i in range(5)]
|
children_values = [{'name': 'child%s' % i, 'user_ids': False} for i in range(5)]
|
||||||
children = self.env['project.task'].create(children_values)
|
children = self.env['project.task'].create(children_values)
|
||||||
# test writing user_id and sale_line_id
|
# test writing user_ids and sale_line_id
|
||||||
|
|
||||||
for child in children:
|
for child in children:
|
||||||
self.assertFalse(child.user_id)
|
self.assertFalse(child.user_ids)
|
||||||
self.assertFalse(child.sale_line_id)
|
self.assertFalse(child.sale_line_id)
|
||||||
|
|
||||||
children.write({'parent_id': parent.id})
|
children.write({'parent_id': parent.id})
|
||||||
|
|
||||||
for child in children:
|
for child in children:
|
||||||
self.assertEqual(child.sale_line_id, self.order_line)
|
self.assertEqual(child.sale_line_id, self.order_line)
|
||||||
self.assertFalse(child.user_id)
|
self.assertFalse(child.user_ids)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<!-- Project Task -->
|
<!-- Project Task -->
|
||||||
<record id="project_task_internal" model="project.task">
|
<record id="project_task_internal" model="project.task">
|
||||||
<field name="name">Internal training</field>
|
<field name="name">Internal training</field>
|
||||||
<field name="user_id" ref="base.user_admin"/>
|
<field name="user_ids" eval="[(4, ref('base.user_admin'))]"/>
|
||||||
<field name="project_id" ref="project.project_project_1"/>
|
<field name="project_id" ref="project.project_project_1"/>
|
||||||
<field name="create_date" eval="DateTime.now() - relativedelta(months=5)"/>
|
<field name="create_date" eval="DateTime.now() - relativedelta(months=5)"/>
|
||||||
</record>
|
</record>
|
||||||
@@ -167,7 +167,7 @@
|
|||||||
<!-- Assign sale order's task to admin -->
|
<!-- Assign sale order's task to admin -->
|
||||||
<function model="project.task" name="write">
|
<function model="project.task" name="write">
|
||||||
<value model="project.task" eval="obj().search([('sale_line_id', '=', ref('sale_line_13'))]).ids"/>
|
<value model="project.task" eval="obj().search([('sale_line_id', '=', ref('sale_line_13'))]).ids"/>
|
||||||
<value eval="{'user_id': ref('base.user_admin')}"/>
|
<value eval="{'user_ids': [(4, ref('base.user_admin'))]}"/>
|
||||||
</function>
|
</function>
|
||||||
|
|
||||||
<!-- Timesheets on sale_order_1 -->
|
<!-- Timesheets on sale_order_1 -->
|
||||||
@@ -482,7 +482,7 @@
|
|||||||
<field name="sale_line_id" ref="sale_timesheet.sale_line_13" />
|
<field name="sale_line_id" ref="sale_timesheet.sale_line_13" />
|
||||||
<field name="sale_order_id" ref="sale_timesheet.sale_order_1" />
|
<field name="sale_order_id" ref="sale_timesheet.sale_order_1" />
|
||||||
<field name="partner_id" ref="base.res_partner_2" />
|
<field name="partner_id" ref="base.res_partner_2" />
|
||||||
<field name="user_id" ref="base.user_admin"/>
|
<field name="user_ids" eval="[(4, ref('base.user_admin'))]"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Timesheet for those tasks -->
|
<!-- Timesheet for those tasks -->
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ class TestSaleService(TestCommonSaleTimesheet):
|
|||||||
self.assertTrue(so_line1.project_id, "SO confirmation should create a project and link it to SOL")
|
self.assertTrue(so_line1.project_id, "SO confirmation should create a project and link it to SOL")
|
||||||
self.assertEqual(self.sale_order.tasks_count, 1, "The SO should have only one task")
|
self.assertEqual(self.sale_order.tasks_count, 1, "The SO should have only one task")
|
||||||
self.assertEqual(so_line1.task_id.sale_line_id, so_line1, "The created task is also linked to its origin sale line, for invoicing purpose.")
|
self.assertEqual(so_line1.task_id.sale_line_id, so_line1, "The created task is also linked to its origin sale line, for invoicing purpose.")
|
||||||
self.assertFalse(so_line1.task_id.user_id, "The created task should be unassigned")
|
self.assertFalse(so_line1.task_id.user_ids, "The created task should be unassigned")
|
||||||
self.assertEqual(so_line1.product_uom_qty, so_line1.task_id.planned_hours, "The planned hours should be the same as the ordered quantity of the native SO line")
|
self.assertEqual(so_line1.product_uom_qty, so_line1.task_id.planned_hours, "The planned hours should be the same as the ordered quantity of the native SO line")
|
||||||
|
|
||||||
so_line1.write({'product_uom_qty': 20})
|
so_line1.write({'product_uom_qty': 20})
|
||||||
|
|||||||
@@ -130,7 +130,7 @@
|
|||||||
<xpath expr="//field[@name='timesheet_ids']/tree" position="attributes">
|
<xpath expr="//field[@name='timesheet_ids']/tree" position="attributes">
|
||||||
<attribute name="decoration-muted">timesheet_invoice_id != False</attribute>
|
<attribute name="decoration-muted">timesheet_invoice_id != False</attribute>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='user_id']" position="after">
|
<xpath expr="//field[@name='user_ids']" position="after">
|
||||||
<field name="is_project_map_empty" invisible="1"/>
|
<field name="is_project_map_empty" invisible="1"/>
|
||||||
<field name="has_multi_sol" invisible="1"/>
|
<field name="has_multi_sol" invisible="1"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|||||||
Reference in New Issue
Block a user