[IMP] project: improve various mark as done features
New features developed in this PR: - Write Javascript tests for the project.task.state widget - Tests are in the project_task_state_selection.js - rewrite Research & Development demo data - Created a digest tip for task state selection (in Settings > technical > digest tips) - Tip: Use task state to keep track of the task progression - Fix Sharingview (Readonly) icons for the project.task.state widget, previously only the color bubble were displayed in readonly sharing views, now all of the icons are displayed - Display state in the Calendar view - Display the same widget as in the kanban/list views - Remove is_closed References - Adapt Kanban stages Exemples - Kanban exemples are available when creating a new stage in kanban view - improve Progressbar colors (different color for approved/done, in_progress/waiting) Task-3213526 closes odoo/odoo#117968 Related: odoo/enterprise#39714 Related: odoo/upgrade#4548 Signed-off-by: Xavier Bol (xbo) <xbo@odoo.com>
This commit is contained in:
committed by
Xavier Bol (xbo)
parent
fc61ea4acb
commit
1a44b84997
@@ -102,7 +102,7 @@ class Project(models.Model):
|
||||
WHERE Project.allocated_hours > 0
|
||||
AND Project.allow_timesheets = TRUE
|
||||
AND Task.parent_id IS NULL
|
||||
AND Task.is_closed IS FALSE
|
||||
AND Task.state NOT IN ('1_done', '1_canceled')
|
||||
GROUP BY Project.id
|
||||
HAVING Project.allocated_hours - SUM(Task.effective_hours) < 0
|
||||
"""
|
||||
|
||||
@@ -7,6 +7,21 @@
|
||||
</data>
|
||||
|
||||
<data>
|
||||
<record id="digest_tip_project_0" model="digest.tip">
|
||||
<field name="name">Tip: Use task states to keep track of your tasks' progression</field>
|
||||
<field name="sequence">1200</field>
|
||||
<field name="group_id" ref="project.group_project_user"/>
|
||||
<field name="tip_description" type="html">
|
||||
<div>
|
||||
<p class="tip_title">Tip: Use task states to keep track of your tasks' progression</p>
|
||||
<p class="tip_content">
|
||||
Quickly check the status of tasks for approvals or change requests and identify those on hold until dependencies are resolved with the hourglass icon.
|
||||
</p>
|
||||
<img src="https://download.odoocdn.com/digests/project/static/src/img/task-state-img.png" width="720" class="illustration_border" />
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="digest_tip_project_1" model="digest.tip">
|
||||
<field name="name">Tip: Create tasks from incoming emails</field>
|
||||
<field name="sequence">1300</field>
|
||||
|
||||
@@ -725,6 +725,7 @@
|
||||
<field name="priority">0</field>
|
||||
<field name="project_id" ref="project.project_project_2"/>
|
||||
<field name="name">Customer analysis + Architecture</field>
|
||||
<field name="state">1_done</field>
|
||||
<field name="color">7</field>
|
||||
<field name="create_date" eval="DateTime.now() - relativedelta(months=5)"/>
|
||||
</record>
|
||||
@@ -771,9 +772,10 @@
|
||||
<field name="user_ids" eval="[Command.link(ref('base.user_admin'))]"/>
|
||||
<field name="project_id" ref="project.project_project_2"/>
|
||||
<field name="name">Basic outline</field>
|
||||
<field name="state">1_done</field>
|
||||
<field name="depend_on_ids" eval="[Command.link(ref('project.project_2_task_1'))]"/>
|
||||
<field name="tag_ids" eval="[Command.set([ref('project_tags_02')])]"/>
|
||||
<field name="create_date" eval="DateTime.now() - relativedelta(months=5)"/>
|
||||
<field name="depend_on_ids" eval="[Command.link(ref('project.project_2_task_1'))]"/>
|
||||
</record>
|
||||
<record id="project_2_task_2_mail_message_1" model="mail.message">
|
||||
<field name="model">project.task</field>
|
||||
@@ -818,10 +820,10 @@
|
||||
<field name="priority">1</field>
|
||||
<field name="project_id" ref="project.project_project_2"/>
|
||||
<field name="name">Planning and budget</field>
|
||||
<field name="stage_id" ref="project_stage_1"/>
|
||||
<field name="state">1_done</field>
|
||||
<field name="stage_id" ref="project_stage_2"/>
|
||||
<field name="color">6</field>
|
||||
<field name="create_date" eval="DateTime.now() - relativedelta(months=5)"/>
|
||||
<field name="depend_on_ids" eval="[Command.link(ref('project.project_2_task_2'))]"/>
|
||||
<field name="date_deadline" eval="DateTime.now() - relativedelta(days=4)"/>
|
||||
</record>
|
||||
<record id="project_2_task_3_mail_message_1" model="mail.message">
|
||||
@@ -916,7 +918,6 @@ Send it ASAP, its urgent.</field>
|
||||
<field name="project_id" ref="project.project_project_2"/>
|
||||
<field name="name">Social network integration</field>
|
||||
<field name="description">Facebook and Twitter integration</field>
|
||||
<field name="state">1_canceled</field>
|
||||
<field name="stage_id" ref="project_stage_1"/>
|
||||
<field name="create_date" eval="DateTime.now() - relativedelta(months=5)"/>
|
||||
<field name="depend_on_ids" eval="[Command.link(ref('project.project_2_task_3'))]"/>
|
||||
@@ -979,10 +980,11 @@ Send it ASAP, its urgent.</field>
|
||||
<field name="project_id" ref="project.project_project_2"/>
|
||||
<field name="name">New portal system</field>
|
||||
<field name="priority">0</field>
|
||||
<field name="state">1_done</field>
|
||||
<field name="stage_id" ref="project_stage_2"/>
|
||||
<field name="tag_ids" eval="[Command.set([ref('project.project_tags_02')])]"/>
|
||||
<field name="create_date" eval="DateTime.now() - relativedelta(months=5)"/>
|
||||
<field name="depend_on_ids" eval="[Command.link(ref('project.project_2_task_3'))]"/>
|
||||
<field name="depend_on_ids" eval="[Command.link(ref('project.project_2_task_3')), Command.link(ref('project_2_task_2'))]"/>
|
||||
</record>
|
||||
<record id="project_2_task_7_mail_message_1" model="mail.message">
|
||||
<field name="model">project.task</field>
|
||||
@@ -1060,7 +1062,7 @@ Send it ASAP, its urgent.</field>
|
||||
<field name="project_id" ref="project.project_project_2"/>
|
||||
<field name="name">Document management</field>
|
||||
<field name="stage_id" ref="project_stage_0"/>
|
||||
<field name="state">1_done</field>
|
||||
<field name="state">1_canceled</field>
|
||||
<field name="create_date" eval="DateTime.now() - relativedelta(months=5)"/>
|
||||
<field name="depend_on_ids" eval="[Command.link(ref('project.project_2_task_8'))]"/>
|
||||
<field name="date_deadline" eval="DateTime.now() + relativedelta(days=15)"/>
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
from . import account_analytic_account
|
||||
from . import mail_message
|
||||
from . import project_milestone
|
||||
from . import project_project_stage
|
||||
from . import project_task_recurrence
|
||||
# `project_task_stage_personal` has to be loaded before `project`
|
||||
# `project_task_stage_personal` has to be loaded before `project_project` and `project_milestone`
|
||||
from . import project_task_stage_personal
|
||||
from . import project_milestone
|
||||
from . import project_project
|
||||
from . import project_task
|
||||
from . import project_task_type
|
||||
|
||||
@@ -5,6 +5,8 @@ from collections import defaultdict
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
from .project_task import CLOSED_STATES
|
||||
|
||||
class ProjectMilestone(models.Model):
|
||||
_name = 'project.milestone'
|
||||
_description = "Project Milestone"
|
||||
@@ -47,10 +49,10 @@ class ProjectMilestone(models.Model):
|
||||
@api.depends('task_ids.milestone_id')
|
||||
def _compute_task_count(self):
|
||||
all_and_done_task_count_per_milestone = {
|
||||
milestone.id: (count, is_closed_list.count(True))
|
||||
for milestone, count, is_closed_list in self.env['project.task']._read_group(
|
||||
milestone.id: (count, sum(state in CLOSED_STATES for state in state_list))
|
||||
for milestone, count, state_list in self.env['project.task']._read_group(
|
||||
[('milestone_id', 'in', self.ids), ('allow_milestones', '=', True)],
|
||||
['milestone_id'], ['__count', 'is_closed:array_agg'],
|
||||
['milestone_id'], ['__count', 'state:array_agg'],
|
||||
)
|
||||
}
|
||||
for milestone in self:
|
||||
@@ -59,19 +61,20 @@ class ProjectMilestone(models.Model):
|
||||
def _compute_can_be_marked_as_done(self):
|
||||
if not any(self._ids):
|
||||
for milestone in self:
|
||||
milestone.can_be_marked_as_done = not milestone.is_reached and all(milestone.task_ids.is_closed)
|
||||
milestone.can_be_marked_as_done = not milestone.is_reached and all(milestone.task_ids.mapped(lambda t: t.state in CLOSED_STATES))
|
||||
return
|
||||
|
||||
unreached_milestones = self.filtered(lambda milestone: not milestone.is_reached)
|
||||
(self - unreached_milestones).can_be_marked_as_done = False
|
||||
task_read_group = self.env['project.task']._read_group(
|
||||
[('milestone_id', 'in', unreached_milestones.ids)],
|
||||
['milestone_id', 'is_closed'],
|
||||
['milestone_id', 'state'],
|
||||
['__count'],
|
||||
)
|
||||
task_count_per_milestones = defaultdict(lambda: (0, 0))
|
||||
for milestone, is_closed, count in task_read_group:
|
||||
for milestone, state, count in task_read_group:
|
||||
opened_task_count, closed_task_count = task_count_per_milestones[milestone.id]
|
||||
if is_closed:
|
||||
if state in CLOSED_STATES:
|
||||
closed_task_count += count
|
||||
else:
|
||||
opened_task_count += count
|
||||
|
||||
@@ -11,7 +11,7 @@ from odoo.addons.rating.models import rating_data
|
||||
from odoo.tools.misc import get_lang
|
||||
|
||||
from .project_update import STATUS_COLOR
|
||||
|
||||
from .project_task import CLOSED_STATES
|
||||
|
||||
class Project(models.Model):
|
||||
_name = "project.project"
|
||||
@@ -55,7 +55,7 @@ class Project(models.Model):
|
||||
for project, count in self.env['project.task'].with_context(
|
||||
active_test=any(project.active for project in self)
|
||||
)._read_group(
|
||||
[('is_closed', '=', False), ('project_id', 'in', self.ids)],
|
||||
[('state', 'not in', list(CLOSED_STATES)), ('project_id', 'in', self.ids)],
|
||||
['project_id'],
|
||||
['__count'],
|
||||
)
|
||||
@@ -128,7 +128,7 @@ class Project(models.Model):
|
||||
type_ids = fields.Many2many('project.task.type', 'project_task_type_rel', 'project_id', 'type_id', string='Tasks Stages')
|
||||
task_count = fields.Integer(compute='_compute_task_count', string="Task Count")
|
||||
task_ids = fields.One2many('project.task', 'project_id', string='Tasks',
|
||||
domain=[('is_closed', '=', False)])
|
||||
domain=[('state', 'not in', list(CLOSED_STATES))])
|
||||
color = fields.Integer(string='Color Index')
|
||||
user_id = fields.Many2one('res.users', string='Project Manager', default=lambda self: self.env.user, tracking=True)
|
||||
alias_enabled = fields.Boolean(string='Use Email Alias', compute='_compute_alias_enabled', readonly=False)
|
||||
|
||||
@@ -50,7 +50,6 @@ PROJECT_TASK_WRITABLE_FIELDS = {
|
||||
'date_last_stage_update',
|
||||
'tag_ids',
|
||||
'sequence',
|
||||
'is_closed',
|
||||
'stage_id',
|
||||
'child_ids',
|
||||
'parent_id',
|
||||
@@ -133,7 +132,7 @@ class Task(models.Model):
|
||||
('03_approved', 'Approved'),
|
||||
*CLOSED_STATES.items(),
|
||||
('04_waiting_normal', 'Waiting'),
|
||||
], string='Status', copy=False, default='01_in_progress', required=True, compute='_compute_state', inverse='_inverse_state', readonly=False, store=True, recursive=True, tracking=True)
|
||||
], string='State', copy=False, default='01_in_progress', required=True, compute='_compute_state', inverse='_inverse_state', readonly=False, store=True, recursive=True, tracking=True)
|
||||
|
||||
create_date = fields.Datetime("Created On", readonly=True)
|
||||
write_date = fields.Datetime("Last Updated On", readonly=True)
|
||||
@@ -190,7 +189,6 @@ class Task(models.Model):
|
||||
# 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
|
||||
displayed_image_id = fields.Many2one('ir.attachment', domain="[('res_model', '=', 'project.task'), ('res_id', '=', id), ('mimetype', 'ilike', 'image')]", string='Cover Image')
|
||||
|
||||
is_closed = fields.Boolean(compute='_compute_is_closed', string="Closed State", store=True, index=True)
|
||||
parent_id = fields.Many2one('project.task', string='Parent Task', index=True)
|
||||
child_ids = fields.One2many('project.task', 'parent_id', string="Sub-tasks", domain="[('recurring_task', '=', False)]")
|
||||
subtask_count = fields.Integer("Sub-task Count", compute='_compute_subtask_count')
|
||||
|
||||
@@ -42,11 +42,10 @@ class ReportProjectTaskUser(models.Model):
|
||||
('03_approved', 'Approved'),
|
||||
('1_canceled', 'Canceled'),
|
||||
('02_changes_requested', 'Changes Requested'),
|
||||
], string='Status', readonly=True)
|
||||
], string='State', readonly=True)
|
||||
company_id = fields.Many2one('res.company', string='Company', readonly=True)
|
||||
partner_id = fields.Many2one('res.partner', string='Customer', readonly=True)
|
||||
stage_id = fields.Many2one('project.task.type', string='Stage', readonly=True)
|
||||
is_closed = fields.Boolean("Closing Stage", readonly=True, help="Folded in Kanban stages are closing stages.")
|
||||
task_id = fields.Many2one('project.task', string='Tasks', readonly=True)
|
||||
active = fields.Boolean(readonly=True)
|
||||
tag_ids = fields.Many2many('project.tags', relation='project_tags_project_task_rel',
|
||||
@@ -81,7 +80,6 @@ class ReportProjectTaskUser(models.Model):
|
||||
t.partner_id,
|
||||
t.parent_id,
|
||||
t.stage_id,
|
||||
t.is_closed,
|
||||
t.state,
|
||||
t.milestone_id,
|
||||
CASE WHEN pm.id IS NOT NULL THEN true ELSE false END as has_late_and_unreached_milestone,
|
||||
@@ -113,7 +111,6 @@ class ReportProjectTaskUser(models.Model):
|
||||
t.partner_id,
|
||||
t.parent_id,
|
||||
t.stage_id,
|
||||
t.is_closed,
|
||||
t.state,
|
||||
t.rating_last_value,
|
||||
t.working_days_close,
|
||||
|
||||
@@ -17,7 +17,14 @@ class ReportProjectTaskBurndownChart(models.AbstractModel):
|
||||
date_assign = fields.Datetime(string='Assignment Date', readonly=True)
|
||||
date_deadline = fields.Date(string='Deadline', readonly=True)
|
||||
date_last_stage_update = fields.Date(string='Last Stage Update', readonly=True)
|
||||
is_closed = fields.Boolean("Closing Stage", readonly=True)
|
||||
state = fields.Selection([
|
||||
('01_in_progress', 'In Progress'),
|
||||
('1_done', 'Done'),
|
||||
('04_waiting_normal', 'Waiting'),
|
||||
('03_approved', 'Approved'),
|
||||
('1_canceled', 'Canceled'),
|
||||
('02_changes_requested', 'Changes Requested'),
|
||||
], string='State', readonly=True)
|
||||
milestone_id = fields.Many2one('project.milestone', readonly=True)
|
||||
partner_id = fields.Many2one('res.partner', string='Customer', readonly=True)
|
||||
project_id = fields.Many2one('project.project', readonly=True)
|
||||
@@ -38,7 +45,7 @@ class ReportProjectTaskBurndownChart(models.AbstractModel):
|
||||
'date_assign',
|
||||
'date_deadline',
|
||||
'date_last_stage_update',
|
||||
'is_closed',
|
||||
'state',
|
||||
'milestone_id',
|
||||
'partner_id',
|
||||
'project_id',
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
<filter name="filter_date_deadline" date="date_deadline"/>
|
||||
<filter string="Last Month" invisible="1" name="last_month" domain="[('date','>=', (context_today() - datetime.timedelta(days=30)).strftime('%Y-%m-%d'))]"/>
|
||||
<separator/>
|
||||
<filter string="Open Tasks" name="open_tasks" domain="[('is_closed', '=', False)]"/>
|
||||
<filter string="Closed Tasks" name="closed_tasks" domain="[('is_closed', '=', True)]"/>
|
||||
<filter string="Open Tasks" name="open_tasks" domain="[('state', 'not in', ['1_done', '1_canceled'])]"/>
|
||||
<filter string="Closed Tasks" name="closed_tasks" domain="[('state', 'in', ['1_done', '1_canceled'])]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Date" name="date" context="{'group_by': 'date'}" />
|
||||
<filter string="Stage" name="stage" context="{'group_by': 'stage_id'}" invisible="1"/>
|
||||
|
||||
+11
-2
@@ -6,6 +6,7 @@ import {
|
||||
} from "@web/views/fields/state_selection/state_selection_field";
|
||||
import { sprintf } from "@web/core/utils/strings";
|
||||
import { useCommand } from "@web/core/commands/command_hook";
|
||||
import { formatSelection } from "@web/views/fields/formatters";
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
@@ -92,6 +93,14 @@ export class ProjectTaskStateSelection extends StateSelectionField {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
get label() {
|
||||
const fullSelection = [...this.options];
|
||||
fullSelection.push(["04_waiting_normal", "Waiting"]);
|
||||
return formatSelection(this.currentValue, {
|
||||
selection: fullSelection,
|
||||
});
|
||||
}
|
||||
|
||||
stateIcon(value) {
|
||||
return this.icons[value] || "";
|
||||
}
|
||||
@@ -122,14 +131,14 @@ export class ProjectTaskStateSelection extends StateSelectionField {
|
||||
}
|
||||
|
||||
getDropdownPosition() {
|
||||
if (this.isView(['kanban', 'list']) || this.env.isSmall) {
|
||||
if (this.isView(['kanban', 'list', 'calendar']) || this.env.isSmall) {
|
||||
return '';
|
||||
}
|
||||
return 'bottom-end';
|
||||
}
|
||||
|
||||
getTogglerClass(currentValue) {
|
||||
if (this.isView(['kanban', 'list']) || this.env.isSmall) {
|
||||
if (this.isView(['kanban', 'list', 'calendar']) || this.env.isSmall) {
|
||||
return 'btn btn-link d-flex p-0';
|
||||
}
|
||||
return 'o_state_button btn rounded-pill ' + this.colorButton[currentValue];
|
||||
|
||||
+7
-3
@@ -7,9 +7,13 @@
|
||||
<xpath expr="//t[@t-if='props.readonly']/button/span[1]" position="attributes">
|
||||
<attribute name="t-attf-class">{{ stateIcon(currentValue) }} {{ statusColor(currentValue) }}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-if='props.readonly']/button" position="attributes">
|
||||
<attribute name="style">cursor: default;</attribute>
|
||||
<attribute name="t-att-title">label</attribute>
|
||||
</xpath>
|
||||
<!-- Waiting state button -->
|
||||
<xpath expr="//t[@t-if='props.readonly']" position="after">
|
||||
<t t-elif="currentValue == '04_waiting_normal' and isView(['kanban', 'list'])">
|
||||
<t t-elif="currentValue == '04_waiting_normal' and isView(['kanban', 'list', 'calendar'])">
|
||||
<button class="d-flex align-items-center btn fw-normal p-0 justify-content-center " title="This task is blocked by another unfinished task" t-att-class="{'o_task_state_list_view': isView(['list'])}">
|
||||
<i class="fa fa-lg fa-hourglass-o"></i>
|
||||
</button>
|
||||
@@ -18,7 +22,7 @@
|
||||
<!-- The toggle mark as done button -->
|
||||
<xpath expr="//t[@t-if='props.readonly']" position="after">
|
||||
<t t-elif="isToggleMode and currentValue == '01_in_progress'">
|
||||
<button t-if="isView(['kanban', 'list']) or this.env.isSmall"
|
||||
<button t-if="isView(['kanban', 'list', 'calendar']) or this.env.isSmall"
|
||||
class="d-flex align-items-center btn fw-normal p-0"
|
||||
tabindex="-1"
|
||||
t-att-class="{'o_task_state_list_view': isView(['list'])}"
|
||||
@@ -50,7 +54,7 @@
|
||||
|
||||
<!-- Normal dropdown mode toggle button (displayed on the card/record by default) -->
|
||||
<xpath expr="//t[@t-set-slot='toggler']/div" position="replace">
|
||||
<div t-if="isView(['kanban', 'list']) or this.env.isSmall" class="d-flex align-items-center" t-att-class="{'o_task_state_list_view': isView(['list'])}" t-att-title="label">
|
||||
<div t-if="isView(['kanban', 'list', 'calendar']) or this.env.isSmall" class="d-flex align-items-center" t-att-class="{'o_task_state_list_view': isView(['list'])}" t-att-title="label">
|
||||
<i t-if="currentValue == '04_waiting_normal'" t-attf-class="{{ stateIcon(currentValue) }} {{ statusColor(currentValue) }}" style="color: #4A4F59;"/>
|
||||
<i t-else="" t-attf-class="{{ stateIcon(currentValue) }} {{ statusColor(currentValue) }} {{ ['1_done', '1_canceled'].includes(currentValue) and isView(['kanban']) ? 'opacity-50' : '' }}"/>
|
||||
</div>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
@@ -6,7 +6,7 @@ import { renderToMarkup } from '@web/core/utils/render';
|
||||
|
||||
const { markup } = owl;
|
||||
const greenBullet = markup(`<span class="o_status d-inline-block o_status_green"></span>`);
|
||||
const redBullet = markup(`<span class="o_status d-inline-block o_status_red"></span>`);
|
||||
const orangeBullet = markup(`<span class="o_status d-inline-block text-warning"></span>`);
|
||||
const star = markup(`<a style="color: gold;" class="fa fa-star"></a>`);
|
||||
const clock = markup(`<a class="fa fa-clock-o"></a>`);
|
||||
|
||||
@@ -22,7 +22,7 @@ const exampleData = {
|
||||
get description() {
|
||||
return renderToMarkup("project.example.generic");
|
||||
},
|
||||
bullets: [greenBullet, redBullet, star],
|
||||
bullets: [greenBullet, orangeBullet, star],
|
||||
}, {
|
||||
name: _lt('Agile Scrum'),
|
||||
columns: [_lt('Backlog'), _lt('Sprint Backlog'), _lt('Sprint in Progress')],
|
||||
@@ -30,7 +30,7 @@ const exampleData = {
|
||||
get description() {
|
||||
return renderToMarkup("project.example.agilescrum");
|
||||
},
|
||||
bullets: [greenBullet, redBullet],
|
||||
bullets: [greenBullet, orangeBullet],
|
||||
}, {
|
||||
name: _lt('Digital Marketing'),
|
||||
columns: [_lt('Ideas'), _lt('Researching'), _lt('Writing'), _lt('Editing')],
|
||||
@@ -38,7 +38,7 @@ const exampleData = {
|
||||
get description() {
|
||||
return renderToMarkup("project.example.digitalmarketing");
|
||||
},
|
||||
bullets: [greenBullet, redBullet],
|
||||
bullets: [greenBullet, orangeBullet],
|
||||
}, {
|
||||
name: _lt('Customer Feedback'),
|
||||
columns: [_lt('New'), _lt('In development')],
|
||||
@@ -46,7 +46,7 @@ const exampleData = {
|
||||
get description() {
|
||||
return renderToMarkup("project.example.customerfeedback");
|
||||
},
|
||||
bullets: [greenBullet, redBullet],
|
||||
bullets: [greenBullet, orangeBullet],
|
||||
}, {
|
||||
name: _lt('Consulting'),
|
||||
columns: [_lt('New Projects'), _lt('Resources Allocation'), _lt('In Progress')],
|
||||
@@ -54,7 +54,7 @@ const exampleData = {
|
||||
get description() {
|
||||
return renderToMarkup("project.example.consulting");
|
||||
},
|
||||
bullets: [greenBullet, redBullet],
|
||||
bullets: [greenBullet, orangeBullet],
|
||||
}, {
|
||||
name: _lt('Research Project'),
|
||||
columns: [_lt('Brainstorm'), _lt('Research'), _lt('Draft')],
|
||||
@@ -62,7 +62,7 @@ const exampleData = {
|
||||
get description() {
|
||||
return renderToMarkup("project.example.researchproject");
|
||||
},
|
||||
bullets: [greenBullet, redBullet],
|
||||
bullets: [greenBullet, orangeBullet],
|
||||
}, {
|
||||
name: _lt('Website Redesign'),
|
||||
columns: [_lt('Page Ideas'), _lt('Copywriting'), _lt('Design')],
|
||||
@@ -85,7 +85,7 @@ const exampleData = {
|
||||
get description() {
|
||||
return renderToMarkup("project.example.generic");
|
||||
},
|
||||
bullets: [greenBullet, redBullet, star, clock],
|
||||
bullets: [greenBullet, orangeBullet, star, clock],
|
||||
}, {
|
||||
name: _lt('Publishing'),
|
||||
columns: [_lt('Ideas'), _lt('Writing'), _lt('Editing')],
|
||||
@@ -93,7 +93,7 @@ const exampleData = {
|
||||
get description() {
|
||||
return renderToMarkup("project.example.generic");
|
||||
},
|
||||
bullets: [greenBullet, redBullet, star, clock],
|
||||
bullets: [greenBullet, orangeBullet, star, clock],
|
||||
}, {
|
||||
name: _lt('Manufacturing'),
|
||||
columns: [_lt('New Orders'), _lt('Material Sourcing'), _lt('Manufacturing'), _lt('Assembling')],
|
||||
@@ -101,7 +101,7 @@ const exampleData = {
|
||||
get description() {
|
||||
return renderToMarkup("project.example.generic");
|
||||
},
|
||||
bullets: [greenBullet, redBullet, star, clock],
|
||||
bullets: [greenBullet, orangeBullet, star, clock],
|
||||
}, {
|
||||
name: _lt('Podcast and Video Production'),
|
||||
columns: [_lt('Research'), _lt('Script'), _lt('Recording'), _lt('Mixing')],
|
||||
@@ -109,7 +109,7 @@ const exampleData = {
|
||||
get description() {
|
||||
return renderToMarkup("project.example.generic");
|
||||
},
|
||||
bullets: [greenBullet, redBullet, star, clock],
|
||||
bullets: [greenBullet, orangeBullet, star, clock],
|
||||
}],
|
||||
};
|
||||
|
||||
|
||||
@@ -9,4 +9,9 @@
|
||||
padding-right: 3px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.o_kanban_header .o_column_progress .bg-success-done {
|
||||
--bs-bg-opacity: 1;
|
||||
background-color: rgba(25, 135, 84, var(--bs-bg-opacity)) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,23 +2,31 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="project.example.generic" owl="1">
|
||||
Prioritize Tasks by using the <a style="color: gold;" class="fa fa-star"></a> icon.
|
||||
Prioritize your tasks by marking important ones using the <a style="color: gold;" class="fa fa-star"></a> button.
|
||||
<br/><br/>
|
||||
Use the <span class="o_status d-inline-block o_status_green"></span> state to inform your colleagues that a task is approved for the next stage.
|
||||
<br/>
|
||||
Use the <span class="o_status d-inline-block o_status_green"></span> button to inform your colleagues that a task is ready for the next stage.
|
||||
<br/>
|
||||
Use the <span class="o_status d-inline-block o_status_red"></span> to indicate a problem or a need for discussion on a task.
|
||||
Use the <span class="o_status d-inline-block bg-warning"></span> state to indicate a request for changes or a need for discussion on a task.
|
||||
<br/><br/>
|
||||
Use the <span class="fa fa-check-circle text-success d-inline-block"></span> state to mark the task as complete.
|
||||
<br/>
|
||||
Use the <span class="fa fa-times-circle text-danger d-inline-block"></span> state to mark the task as canceled.
|
||||
<br/><br/>
|
||||
Look for the <span class="fa fa-hourglass-o d-inline-block"></span> icon to see tasks waiting on other ones. Once a task is marked as complete or canceled, all of its dependencies will be unblocked.
|
||||
<br/><br/>
|
||||
Use the <a class="fa fa-clock-o"></a> icon to organize your daily activities.
|
||||
</t>
|
||||
|
||||
<t t-name="project.example.agilescrum" owl="1">
|
||||
Use <span class="o_status d-inline-block o_status_green"></span> and <span class="o_status d-inline-block o_status_red"></span> bullets to indicate the status of a task.
|
||||
Use the <span class="o_status d-inline-block o_status_green"></span> state to inform your colleagues that a task is approved for the next stage.
|
||||
<br/>
|
||||
Use the <span class="o_status d-inline-block bg-warning"></span> state to indicate a request for changes or a need for discussion on a task.
|
||||
<br/><br/>
|
||||
Use the <a class="fa fa-clock-o"></a> icon to organize your daily activities.
|
||||
</t>
|
||||
|
||||
<t t-name="project.example.digitalmarketing" owl="1">
|
||||
Everyone can propose ideas, and the Editor marks the best ones as <span class="o_status d-inline-block o_status_green"></span>.
|
||||
Everyone can propose ideas, and the Editor marks the best ones as <span class="o_status d-inline-block o_status_green"></span>.<br/>
|
||||
Attach all documents or links to the task directly, to have all research information centralized.
|
||||
<br/>
|
||||
Use the <a class="fa fa-clock-o"></a> icon to organize your daily activities.
|
||||
@@ -26,7 +34,7 @@
|
||||
|
||||
<t t-name="project.example.customerfeedback" owl="1">
|
||||
Customers propose feedbacks by email; Odoo creates tasks automatically, and you can
|
||||
communicate on the task directly. Your managers decide which feedback is accepted
|
||||
communicate on the task directly. <br/>Your managers decide which feedback is accepted
|
||||
<span class="o_status d-inline-block o_status_green"></span> and which feedback is
|
||||
moved to the "Refused" column.
|
||||
<br/>
|
||||
@@ -36,15 +44,15 @@
|
||||
<t t-name="project.example.consulting" owl="1">
|
||||
Manage the lifecycle of your project using the kanban view. Add newly acquired projects,
|
||||
assign them and use the <span class="o_status d-inline-block o_status_green"></span> and
|
||||
<span class="o_status d-inline-block o_status_red"></span> to define if the project is
|
||||
<span class="o_status d-inline-block bg-warning"></span> to define if the project is
|
||||
ready for the next step.
|
||||
<br/>
|
||||
Use the <a class="fa fa-clock-o"></a> icon to organize your daily activities.
|
||||
</t>
|
||||
|
||||
<t t-name="project.example.researchproject" owl="1">
|
||||
Handle your idea gathering within Tasks of your new Project and discuss them in the chatter of the tasks. Use the
|
||||
<span class="o_status d-inline-block o_status_green"></span> and <span class="o_status d-inline-block o_status_red"></span>
|
||||
Handle your idea gathering within Tasks of your new Project and discuss them in the chatter of the tasks. <br/>Use the
|
||||
<span class="o_status d-inline-block o_status_green"></span> and <span class="o_status d-inline-block bg-warning"></span>
|
||||
to signalize what is the current status of your Idea.
|
||||
<br/>
|
||||
Use the <a class="fa fa-clock-o"></a> icon to organize your daily activities.
|
||||
@@ -52,7 +60,7 @@
|
||||
|
||||
<t t-name="project.example.tshirtprinting" owl="1">
|
||||
Communicate with customers on the task using the email gateway. Attach logo designs to the task, so that information flows from
|
||||
designers to the workers who print the t-shirt. Organize priorities amongst orders using the
|
||||
designers to the workers who print the t-shirt. <br/>Organize priorities amongst orders using the
|
||||
<a style="color: gold;" class="fa fa-star"></a> icon.
|
||||
<br/>
|
||||
Use the <a class="fa fa-clock-o"></a> icon to organize your daily activities.
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/** @odoo-module */
|
||||
import {
|
||||
click,
|
||||
getFixture,
|
||||
} from '@web/../tests/helpers/utils';
|
||||
import { setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { start, startServer } from '@mail/../tests/helpers/test_utils';
|
||||
import { addModelNamesToFetch } from '@bus/../tests/helpers/model_definitions_helpers';
|
||||
|
||||
addModelNamesToFetch([
|
||||
'project.project',
|
||||
'project.task',
|
||||
]);
|
||||
|
||||
let target;
|
||||
|
||||
QUnit.module('Task State Tests', {
|
||||
beforeEach: async function () {
|
||||
const pyEnv = await startServer();
|
||||
const projectId = pyEnv['project.project'].create([
|
||||
{ name: "Project One" },
|
||||
]);
|
||||
const userId = pyEnv['res.users'].create([
|
||||
{ name: "User One", login: 'one', password: 'one' },
|
||||
]);
|
||||
pyEnv['project.task'].create([
|
||||
{ name: 'task one', project_id: projectId, state: '01_in_progress', user_ids: [userId] },
|
||||
{ name: 'task two', state: '03_approved' },
|
||||
{ name: 'task three', state: '04_waiting_normal' },
|
||||
]);
|
||||
this.views = {
|
||||
"project.task,false,kanban":
|
||||
`<kanban js_class="project_task_kanban">
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="state" widget="project_task_state_selection" class="project_task_state_test"/>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
};
|
||||
target = getFixture();
|
||||
setupViewRegistries();
|
||||
}
|
||||
}, function () {
|
||||
QUnit.test("Check whether task state widget works as intended", async function (assert) {
|
||||
const views = this.views;
|
||||
const { openView } = await start({ serverData: { views } });
|
||||
await openView({
|
||||
res_model: "project.task",
|
||||
views: [[false, "kanban"]],
|
||||
});
|
||||
|
||||
assert.containsNone(target, '.o_field_project_task_state_selection .dropdown-menu', "If the state button has not been pressed yet, no dropdown should be displayed");
|
||||
await click(target.querySelector('div[name="state"]:first-child button.dropdown-toggle'));
|
||||
assert.containsOnce(target, '.o_field_project_task_state_selection .dropdown-menu', "Once the button has been pressed the dropdown should appear");
|
||||
|
||||
await click(target.querySelector('div[name="state"] .dropdown-menu span.text-danger'));
|
||||
const times_button = target.querySelector('div[name="state"]:first-child button.dropdown-toggle i.fa-times-circle');
|
||||
assert.ok(times_button, "If the canceled state as been selected, the fa-times-circle icon should be displayed");
|
||||
|
||||
await click(target.querySelector('div[name="state"] i.fa-hourglass-o'));
|
||||
const waiting_dropdown_menu = target.querySelector('div[name="state"]:nth-of-type(3) button.dropdown-toggle dropdown-menu');
|
||||
assert.notOk(waiting_dropdown_menu , "When trying to click on the waiting icon, no dropdown menu should display");
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,7 @@
|
||||
<template id="portal_my_tasks_state_widget_template" name="Status Widget Template">
|
||||
<span
|
||||
t-att-title="dict(task.fields_get(allfields=['state'])['state']['selection'])[task.state]"
|
||||
t-attf-class="o_status rounded-circle #{'bg-success' if task.state == '1_done' else 'bg-danger' if task.state == '1_canceled' else 'bg-warning' if task.state == '02_changes_requested' else 'bg-success' if task.state == '03_approved' else ''}"
|
||||
t-attf-class="#{'fa' if task.state in ['1_done','1_canceled','04_waiting_normal'] else 'o_status rounded-circle' } #{'fa-check-circle text-success' if task.state == '1_done' else 'fa-times-circle text-danger' if task.state == '1_canceled' else 'bg-warning' if task.state == '02_changes_requested' else 'bg-success' if task.state == '03_approved' else 'fa-hourglass-o' if task.state == '04_waiting_normal' else ''}"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -35,10 +35,9 @@
|
||||
<field name="portal_user_names"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="sequence"/>
|
||||
<field name="is_closed"/>
|
||||
<field name="state"/>
|
||||
<field name="displayed_image_id"/>
|
||||
<field name="active"/>
|
||||
<field name="is_closed"/>
|
||||
<field name="closed_subtask_count"/>
|
||||
<field name="subtask_count"/>
|
||||
<field name="allow_milestones" />
|
||||
@@ -72,7 +71,7 @@
|
||||
</div>
|
||||
<div class="o_kanban_record_body">
|
||||
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}" context="{'project_id': project_id}"/>
|
||||
<div t-if="record.date_deadline.raw_value" name="date_deadline" attrs="{'invisible': [('is_closed', '=', True)]}">
|
||||
<div t-if="record.date_deadline.raw_value" name="date_deadline" attrs="{'invisible': [('state', 'in', ['1_done','1_canceled'])]}">
|
||||
<field name="date_deadline" widget="remaining_days"/>
|
||||
</div>
|
||||
<div t-if="record.displayed_image_id.value" groups="base.group_user">
|
||||
@@ -175,9 +174,9 @@
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="parent_id" invisible="1" />
|
||||
<field name="company_id" invisible="1" />
|
||||
<field name="is_closed" invisible="1" />
|
||||
<field name="state" invisible="1" />
|
||||
<field name="partner_id" options="{'no_open': True, 'no_create': True, 'no_edit': True}" attrs="{'invisible': [('project_id', '=', False)]}"/>
|
||||
<field name="date_deadline" attrs="{'invisible': [('is_closed', '=', True)]}"/>
|
||||
<field name="date_deadline" attrs="{'invisible': [('state', 'in', ['1_done','1_canceled'])]}"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
@@ -188,7 +187,7 @@
|
||||
<field name="child_ids" context="{'default_project_id': project_id, 'default_parent_id': id, 'default_partner_id': partner_id, 'form_view_ref' : 'project.project_sharing_project_task_view_form'}">
|
||||
<tree editable="bottom">
|
||||
<field name="project_id" invisible="1"/>
|
||||
<field name="is_closed" invisible="1"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="priority" widget="priority" optional="show" nolabel="1"/>
|
||||
<field name="name"/>
|
||||
@@ -202,7 +201,7 @@
|
||||
<field name="partner_id" options="{'no_open': True, 'no_create': True, 'no_edit': True}" optional="hide" attrs="{'invisible': [('project_id', '=', False)]}"/>
|
||||
<field name="user_ids" invisible="1" />
|
||||
<field name="portal_user_names" string="Assignees" optional="show"/>
|
||||
<field name="date_deadline" attrs="{'invisible': [('is_closed', '=', True)]}" optional="show"/>
|
||||
<field name="date_deadline" attrs="{'invisible': [('state', 'in', ['1_done','1_canceled'])]}" optional="show"/>
|
||||
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}" optional="hide"/>
|
||||
<field name="stage_id" optional="show"/>
|
||||
<button name="action_open_task" type="object" title="View Task" string="View Task" class="btn btn-link float-end"
|
||||
|
||||
@@ -21,13 +21,13 @@
|
||||
<filter string="Starred Tasks" name="starred_tasks" domain="[('priority', '=', '1')]"/>
|
||||
<separator groups="project.group_project_task_dependencies"/>
|
||||
<filter string="Blocked" name="blocked" domain="[('state', '=', '04_waiting_normal')]" groups="project.group_project_task_dependencies"/>
|
||||
<filter string="Blocking" name="blocking" domain="[('is_closed', '=', False), ('dependent_ids', '!=', False)]" groups="project.group_project_task_dependencies"/>
|
||||
<filter string="Blocking" name="blocking" domain="[('state', 'not in', ['1_done','1_canceled']), ('dependent_ids', '!=', False)]" groups="project.group_project_task_dependencies"/>
|
||||
<separator/>
|
||||
<filter string="Last Stage Update" name="date_last_stage_update" date="date_last_stage_update"/>
|
||||
<separator/>
|
||||
<filter string="Open Tasks" name="open_tasks" domain="[('is_closed', '=', False)]"/>
|
||||
<filter string="Closed Tasks" name="closed_tasks" domain="[('is_closed', '=', True)]"/>
|
||||
<filter string="Closed On" name="closed_on" domain="[('is_closed', '=', True)]" date="date_last_stage_update"/>
|
||||
<filter string="Open Tasks" name="open_tasks" domain="[('state', 'not in', ['1_done','1_canceled'])]"/>
|
||||
<filter string="Closed Tasks" name="closed_tasks" domain="[('state', 'in', ['1_done','1_canceled'])]"/>
|
||||
<filter string="Closed On" name="closed_on" domain="[('state', 'in', ['1_done','1_canceled'])]" date="date_last_stage_update"/>
|
||||
<separator/>
|
||||
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
|
||||
<group expand="0" string="Group By">
|
||||
@@ -240,7 +240,6 @@
|
||||
<field eval="2" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Task" class="o_form_project_tasks" js_class="project_task_form">
|
||||
<field name="is_closed" invisible="1" />
|
||||
<field name="allow_recurring_tasks" invisible="1" />
|
||||
<field name="recurrence_id" invisible="1" />
|
||||
<field name="allow_task_dependencies" invisible="1" />
|
||||
@@ -374,8 +373,7 @@
|
||||
context="{'default_user_ids': user_ids, 'default_parent_id': id,
|
||||
'default_partner_id': partner_id, 'default_milestone_id': allow_milestones and milestone_id}"
|
||||
widget="subtasks_one2many">
|
||||
<tree editable="bottom" decoration-muted="is_closed == True">
|
||||
<field name="is_closed" invisible="1"/>
|
||||
<tree editable="bottom" decoration-muted="state in ['1_done','1_canceled']">
|
||||
<field name="allow_milestones" invisible="1"/>
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="id" optional="hide"/>
|
||||
@@ -394,7 +392,7 @@
|
||||
<field name="user_ids" widget="many2many_avatar_user" optional="show" domain="[('share', '=', False), ('active', '=', True)]"/>
|
||||
<field name="company_id" groups="base.group_multi_company" optional="hide"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="date_deadline" attrs="{'invisible': [('is_closed', '=', True)]}" optional="hide"/>
|
||||
<field name="date_deadline" attrs="{'invisible': [('state', 'in', ['1_done','1_canceled'])]}" optional="hide"/>
|
||||
<field name="activity_ids" string="Next Activity" widget="list_activity" optional="hide"/>
|
||||
<field name="my_activity_date_deadline" string="My Deadline" widget="remaining_days" options="{'allow_order': '1'}" optional="hide"/>
|
||||
<field name="rating_last_text" string="Rating" decoration-danger="rating_last_text == 'ko'"
|
||||
@@ -410,10 +408,9 @@
|
||||
<page name="task_dependencies" string="Blocked By" attrs="{'invisible': [('allow_task_dependencies', '=', False)]}" groups="project.group_project_task_dependencies">
|
||||
<field name="depend_on_ids" nolabel="1"
|
||||
context="{'default_project_id': project_id, 'search_view_ref' : 'project.view_task_search_form', 'search_default_project_id': project_id, 'tree_view_ref': 'project.open_view_all_tasks_list_view', 'search_default_open_tasks': 1}">
|
||||
<tree editable="bottom" decoration-muted="is_closed == True">
|
||||
<tree editable="bottom" decoration-muted="state in ['1_done','1_canceled']">
|
||||
<field name="allow_milestones" invisible="1"/>
|
||||
<field name="parent_id" invisible="1" />
|
||||
<field name="is_closed" invisible="1" />
|
||||
<field name="subtask_count" invisible="1"/>
|
||||
<field name="closed_subtask_count" invisible="1"/>
|
||||
<field name="id" optional="hide"/>
|
||||
@@ -431,7 +428,7 @@
|
||||
<field name="user_ids" widget="many2many_avatar_user" optional="show" domain="[('share', '=', False), ('active', '=', True)]"/>
|
||||
<field name="company_id" optional="hide" groups="base.group_multi_company" />
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="date_deadline" attrs="{'invisible': [('is_closed', '=', True)]}" optional="hide" />
|
||||
<field name="date_deadline" attrs="{'invisible': [('state', 'in', ['1_done','1_canceled'])]}" optional="hide" />
|
||||
<field name="activity_ids" string="Next Activity" widget="list_activity" optional="hide"/>
|
||||
<field name="my_activity_date_deadline" string="My Deadline" widget="remaining_days" options="{'allow_order': '1'}" optional="hide"/>
|
||||
<field name="rating_last_text" string="Rating" decoration-danger="rating_last_text == 'ko'"
|
||||
@@ -534,7 +531,6 @@
|
||||
<field name="user_ids"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="sequence"/>
|
||||
<field name="is_closed"/>
|
||||
<field name="displayed_image_id"/>
|
||||
<field name="active"/>
|
||||
<field name="activity_ids"/>
|
||||
@@ -551,7 +547,7 @@
|
||||
<field name="subtask_count"/>
|
||||
<field name="closed_subtask_count"/>
|
||||
<field name="subtask_count"/>
|
||||
<progressbar field="state" colors='{"1_done": "success", "03_approved": "success", "02_changes_requested": "warning", "1_canceled": "danger", "01_in_progress": "200", "04_waiting_normal": "200" }'/>
|
||||
<progressbar field="state" colors='{"1_done": "success-done", "1_canceled": "danger", "03_approved": "success", "02_changes_requested": "warning", "04_waiting_normal": "info", "01_in_progress": "200" }'/>
|
||||
<templates>
|
||||
<t t-name="kanban-menu" t-if="!selection_mode" groups="base.group_user">
|
||||
<a t-if="widget.editable" role="menuitem" type="set_cover" class="dropdown-item" data-field="displayed_image_id">Set Cover Image</a>
|
||||
@@ -563,7 +559,7 @@
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="{{!selection_mode ? 'oe_kanban_color_' + kanban_getcolor(record.color.raw_value) : ''}} oe_kanban_card oe_kanban_global_click">
|
||||
<div class="oe_kanban_content">
|
||||
<div class="o_kanban_record_top" t-att-class="record.is_closed.raw_value ? 'opacity-50' : ''">
|
||||
<div class="o_kanban_record_top" t-att-class="['1_done', '1_canceled'].includes(record.state.raw_value) ? 'opacity-50' : ''">
|
||||
<div class="o_kanban_record_headings text-muted">
|
||||
<strong class="o_kanban_record_title">
|
||||
<s t-if="!record.active.raw_value">
|
||||
@@ -587,9 +583,9 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_kanban_record_body" t-att-class="record.is_closed.raw_value ? 'opacity-50' : 'text-muted'">
|
||||
<div class="o_kanban_record_body" t-att-class="['1_done', '1_canceled'].includes(record.state.raw_value) ? 'opacity-50' : 'text-muted'">
|
||||
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
|
||||
<div t-if="record.date_deadline.raw_value" name="date_deadline" attrs="{'invisible': [('is_closed', '=', True)]}">
|
||||
<div t-if="record.date_deadline.raw_value" name="date_deadline" attrs="{'invisible': [('state', 'in', ['1_done','1_canceled'])]}">
|
||||
<field name="date_deadline" widget="remaining_days"/>
|
||||
</div>
|
||||
<div t-if="record.displayed_image_id.value">
|
||||
@@ -597,7 +593,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_kanban_record_bottom" t-if="!selection_mode">
|
||||
<div class="oe_kanban_bottom_left" t-att-class="record.is_closed.raw_value ? 'opacity-50' : ''">
|
||||
<div class="oe_kanban_bottom_left" t-att-class="['1_done', '1_canceled'].includes(record.state.raw_value) ? 'opacity-50' : ''">
|
||||
<field name="priority" widget="priority" style="margin-right: 5px;"/>
|
||||
<field name="activity_ids" widget="kanban_activity" style="padding-top: 1.5px;"/>
|
||||
<b t-if="record.rating_active.raw_value and record.rating_count.raw_value > 0" groups="project.group_project_rating">
|
||||
@@ -622,7 +618,7 @@
|
||||
</t>
|
||||
</div>
|
||||
<div class="oe_kanban_bottom_right" t-if="!selection_mode">
|
||||
<div t-att-class="record.is_closed.raw_value ? 'opacity-50' : ''">
|
||||
<div t-att-class="['1_done', '1_canceled'].includes(record.state.raw_value) ? 'opacity-50' : ''">
|
||||
<field name="user_ids" widget="many2many_avatar_user" domain="[('share', '=', False)]"/>
|
||||
</div>
|
||||
<field name="state" widget="project_task_state_selection" options="{'hide_label': 1, 'is_toggle_mode': false}"/>
|
||||
@@ -646,7 +642,7 @@
|
||||
<field name="model">project.task</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Tasks" sample="1" default_order="priority desc, state, date_deadline asc, sequence, id desc">
|
||||
<field name="is_closed" invisible="1" />
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="is_private" invisible="1" />
|
||||
<field name="sequence" invisible="1" readonly="1"/>
|
||||
<field name="allow_milestones" invisible="1"/>
|
||||
@@ -662,7 +658,7 @@
|
||||
<field name="user_ids" optional="show" widget="many2many_avatar_user" domain="[('share', '=', False), ('active', '=', True)]"/>
|
||||
<field name="company_id" groups="base.group_multi_company" optional="show" invisible="context.get('default_project_id')"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<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': [('state', 'in', ['1_done','1_canceled'])]}"/>
|
||||
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}" optional="show" context="{'project_id': project_id}"/>
|
||||
<field name="stage_id" invisible="context.get('set_visible',False)" optional="show"/>
|
||||
</tree>
|
||||
@@ -732,6 +728,7 @@
|
||||
<field name="date_deadline"/>
|
||||
<field name="tag_ids" widget="many2many_tags" attrs="{'invisible': [('tag_ids', '=', [])]}"/>
|
||||
<field name="stage_id"/>
|
||||
<field name="state" widget="project_task_state_selection" readonly="1"/>
|
||||
</calendar>
|
||||
</field>
|
||||
</record>
|
||||
@@ -1000,7 +997,7 @@
|
||||
<field name="name">Overpassed Tasks</field>
|
||||
<field name="res_model">project.task</field>
|
||||
<field name="view_mode">tree,form,calendar,graph,kanban</field>
|
||||
<field name="domain">[('is_closed', '=', False), ('date_deadline','<',time.strftime('%Y-%m-%d')), ('project_id', '!=', False)]</field>
|
||||
<field name="domain">[('state', 'not in', ['1_done', '1_canceled']), ('date_deadline','<',time.strftime('%Y-%m-%d')), ('project_id', '!=', False)]</field>
|
||||
<field name="filter" eval="True"/>
|
||||
<field name="search_view_id" ref="view_task_search_form"/>
|
||||
</record>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<field name="message_partner_ids"/>
|
||||
<field name="activity_ids" />
|
||||
<field name="activity_state" />
|
||||
<field name="is_closed"/>
|
||||
<field name="state"/>
|
||||
<progressbar field="activity_state" colors='{"planned": "success", "today": "warning", "overdue": "danger"}'/>
|
||||
<templates>
|
||||
<t t-name="kanban-menu">
|
||||
@@ -39,7 +39,7 @@
|
||||
<div>
|
||||
<field name="state" widget="todo_done_checkmark" options="{'hide_label': 1}"/>
|
||||
</div>
|
||||
<div t-att-class="record.is_closed.raw_value ? 'opacity-50' : ''">
|
||||
<div t-att-class="['1_done', '1_canceled'].includes(record.state.raw_value) ? 'opacity-50' : ''">
|
||||
<strong class="o_kanban_record_title align-middle">
|
||||
<field name="name" widget="name_with_subtask_count"/>
|
||||
</strong>
|
||||
@@ -47,12 +47,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_kanban_record_body o_todo_kanban_card_body" t-att-class="record.is_closed.raw_value ? 'opacity-50' : ''">
|
||||
<div class="o_kanban_record_body o_todo_kanban_card_body" t-att-class="['1_done', '1_canceled'].includes(record.state.raw_value) ? 'opacity-50' : ''">
|
||||
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
|
||||
</div>
|
||||
<div class="o_kanban_record_bottom">
|
||||
<div class="oe_kanban_bottom_left"/>
|
||||
<div class="oe_kanban_bottom_right" t-att-class="record.is_closed.raw_value ? 'opacity-50' : ''">
|
||||
<div class="oe_kanban_bottom_right" t-att-class="['1_done', '1_canceled'].includes(record.state.raw_value) ? 'opacity-50' : ''">
|
||||
<div t-attf-class="d-flex #{todoHasAssignees ? 'w-100 align-items-center justify-content-end' : 'align-items-end'}">
|
||||
<div class="d-flex align-items-center me-2 mt-2">
|
||||
<div t-if="todoHasAssignees">
|
||||
@@ -215,9 +215,9 @@
|
||||
<field name="tag_ids"/>
|
||||
<field name="user_ids"/>
|
||||
<field name="personal_stage_type_ids" string="Stage"/>
|
||||
<filter name="open_tasks" string="Open" domain="[('is_closed', '=', False)]"/>
|
||||
<filter name="closed_tasks" string="Closed" domain="[('is_closed', '=', True)]"/>
|
||||
<filter string="Closed On" name="closed_on" domain="[('is_closed', '=', True)]" date="date_last_stage_update"/>
|
||||
<filter name="open_tasks" string="Open" domain="[('state', 'not in', ['1_done','1_canceled'])]"/>
|
||||
<filter name="closed_tasks" string="Closed" domain="[('state', 'in', ['1_done','1_canceled'])]"/>
|
||||
<filter string="Closed On" name="closed_on" domain="[('state', 'in', ['1_done','1_canceled'])]" date="date_last_stage_update"/>
|
||||
<separator/>
|
||||
<filter name="active_false" string="Archived" domain="[('active', '=', False)]"/>
|
||||
<filter invisible="1" string="Late Activities" name="activities_overdue"
|
||||
|
||||
@@ -11,6 +11,8 @@ from odoo.tools import Query
|
||||
from datetime import date
|
||||
from functools import reduce
|
||||
|
||||
from odoo.addons.project.models.project_task import CLOSED_STATES
|
||||
|
||||
class Project(models.Model):
|
||||
_inherit = 'project.project'
|
||||
|
||||
@@ -86,7 +88,7 @@ class Project(models.Model):
|
||||
|
||||
@api.depends('sale_order_id', 'task_ids.sale_order_id')
|
||||
def _compute_sale_order_count(self):
|
||||
sale_order_items_per_project_id = self._fetch_sale_order_items_per_project_id({'project.task': [('is_closed', '=', False)]})
|
||||
sale_order_items_per_project_id = self._fetch_sale_order_items_per_project_id({'project.task': [('state', 'not in', list(CLOSED_STATES))]})
|
||||
for project in self:
|
||||
project.sale_order_count = len(sale_order_items_per_project_id.get(project.id, self.env['sale.order.line']).order_id)
|
||||
|
||||
@@ -107,7 +109,7 @@ class Project(models.Model):
|
||||
|
||||
def action_view_sos(self):
|
||||
self.ensure_one()
|
||||
all_sale_orders = self._fetch_sale_order_items({'project.task': [('is_closed', '=', False)]}).order_id
|
||||
all_sale_orders = self._fetch_sale_order_items({'project.task': [('state', 'not in', list(CLOSED_STATES))]}).order_id
|
||||
action_window = {
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": "sale.order",
|
||||
|
||||
Reference in New Issue
Block a user