[IMP] project: update closed_subtask_count when state of subtask changed
Before this commit, when the user displays the subtasks list on a kanban card and change the state of a subtask to set to Done then the `closed_subtask_count` field does not changed in the kanban card. This commit updates the `closed_subtask_count` when the subtask will be closed or opened on the subtask list displayed in the kanban card. Also, the `this.props.record.activeFields` is no longer used to fill in the activeFields of Record component because the `activeFields` will change in the future. task-3248592 Part-of: odoo/odoo#120508
This commit is contained in:
@@ -1,28 +1,33 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { Component, onWillStart } from "@odoo/owl";
|
||||
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
import { Record } from "@web/views/record";
|
||||
import { KanbanMany2ManyTagsAvatarUserField } from "@mail/web/fields/many2many_avatar_user_field/many2many_avatar_user_field";
|
||||
import { Field } from "@web/views/fields/field";
|
||||
import { Field, getFieldFromRegistry } from "@web/views/fields/field";
|
||||
import { standardWidgetProps } from "@web/views/widgets/standard_widget_props";
|
||||
|
||||
export class SubtaskKanbanList extends Component {
|
||||
setup() {
|
||||
this.actionService = useService("action");
|
||||
this.orm = useService("orm");
|
||||
this.subTaskIds = [];
|
||||
this.subTasksRead = [];
|
||||
this.subTaskClosed = new Set();
|
||||
|
||||
onWillStart(this.onWillStart);
|
||||
}
|
||||
|
||||
async onWillStart() {
|
||||
this.subTaskIds = await this.orm.search(this.props.record.resModel, [
|
||||
["parent_id", "=", this.props.record.resId],
|
||||
["state", "not in", ["1_done", "1_canceled"]],
|
||||
]);
|
||||
this.subTasksRead = await this.orm.searchRead(
|
||||
this.props.record.resModel, [
|
||||
["parent_id", "=", this.props.record.resId],
|
||||
["state", "not in", ["1_done", "1_canceled"]],
|
||||
],
|
||||
this.fieldNames
|
||||
);
|
||||
}
|
||||
|
||||
async goToSubtask(subtask_id) {
|
||||
@@ -38,44 +43,44 @@ export class SubtaskKanbanList extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
get fieldsInfo() {
|
||||
get fieldNames() {
|
||||
return Object.keys(this.fields);
|
||||
}
|
||||
|
||||
get fields() {
|
||||
const { display_name, state, user_ids } = this.props.record.fields;
|
||||
return {
|
||||
child_ids: {
|
||||
type: "one2many",
|
||||
relation: "project.task",
|
||||
relatedFields: {
|
||||
display_name: { type: "char" },
|
||||
state: {
|
||||
selection: [
|
||||
["01_in_progress", "In Progress"],
|
||||
["02_changes_requested", "Changes Requested"],
|
||||
["03_approved", "Approved"],
|
||||
["04_waiting_normal", "Waiting"],
|
||||
["1_done", "Done"],
|
||||
["1_canceled", "Canceled"],
|
||||
],
|
||||
string: "Status",
|
||||
type: "selection",
|
||||
field: this.props.record.activeFields.state.field,
|
||||
viewType: this.props.record.activeFields.state.viewType,
|
||||
attrs: this.props.record.activeFields.state.attrs,
|
||||
options: this.props.record.activeFields.state.options,
|
||||
},
|
||||
user_ids: {
|
||||
type: "many2many",
|
||||
relation: "res.users",
|
||||
field: this.props.record.activeFields.user_ids.field,
|
||||
relatedFields: this.props.record.activeFields.user_ids.relatedFields,
|
||||
attrs: this.props.record.activeFields.user_ids.attrs,
|
||||
options: this.props.record.activeFields.user_ids.options,
|
||||
},
|
||||
},
|
||||
display_name,
|
||||
state,
|
||||
user_ids,
|
||||
};
|
||||
}
|
||||
|
||||
get activeFields() {
|
||||
return {
|
||||
display_name: {},
|
||||
state: {
|
||||
viewType: "kanban",
|
||||
field: getFieldFromRegistry(this.fields.state.type, "project_task_state_selection", "kanban"),
|
||||
},
|
||||
user_ids: {
|
||||
field: getFieldFromRegistry(this.fields.user_ids.type, "many2many_avatar_user", "kanban"),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
get values() {
|
||||
return { child_ids: this.subTaskIds };
|
||||
async onSubTaskSaved(subTask) {
|
||||
const isKnownAsClosed = this.subTaskClosed.has(subTask.resId);
|
||||
const isClosed = subTask.data.state.startsWith("1_");
|
||||
if (isKnownAsClosed && !isClosed) {
|
||||
this.subTaskClosed.delete(subTask.resId);
|
||||
} else if (!isKnownAsClosed && isClosed) {
|
||||
this.subTaskClosed.add(subTask.resId);
|
||||
} else { // nothing to do
|
||||
return;
|
||||
}
|
||||
await this.props.record.load();
|
||||
this.props.record.model.notify();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,31 +3,38 @@
|
||||
<templates>
|
||||
|
||||
<t t-name="project.SubtaskKanbanList" owl="1">
|
||||
<Record resModel="'project.task'" resId="props.record.resId" fields="fieldsInfo" activeFields="fieldsInfo" values="values" t-slot-scope="data">
|
||||
<div class="subtask_list">
|
||||
<t t-foreach="data.record.data.child_ids.records" t-as="subTask" t-key="subTask.resId">
|
||||
<div class="subtask_list">
|
||||
<t t-foreach="subTasksRead" t-as="subTask" t-key="subTask.id">
|
||||
<Record resModel="'project.task'"
|
||||
resId="subTask.id"
|
||||
fields="fields"
|
||||
activeFields="activeFields"
|
||||
values="subTask"
|
||||
t-slot-scope="data"
|
||||
onRecordSaved.bind="onSubTaskSaved"
|
||||
>
|
||||
<div class="subtask_list_row">
|
||||
<a t-attf-class="subtask_name_col {{['1_done', '1_canceled'].includes(subTask.data.state) ? 'text-muted opacity-50' : ''}}"
|
||||
t-att-title="subTask.data.display_name"
|
||||
<a t-attf-class="subtask_name_col {{['1_done', '1_canceled'].includes(data.record.data.state) ? 'text-muted opacity-50' : ''}}"
|
||||
t-att-title="data.record.data.display_name"
|
||||
style="color: inherit;"
|
||||
t-esc="subTask.data.display_name"
|
||||
t-on-click.prevent="() => this.goToSubtask(subTask.resId)"/>
|
||||
t-esc="data.record.data.display_name"
|
||||
t-on-click.prevent="() => this.goToSubtask(data.record.resId)"/>
|
||||
<Field
|
||||
class="`subtask_user_widget_col justify-content-end align-items-center ${['1_done', '1_canceled'].includes(subTask.data.state) ? 'opacity-50' : ''}`"
|
||||
name="'user_ids'"
|
||||
record="subTask"
|
||||
fieldInfo="subTask.fields.user_ids"
|
||||
readonly="true"
|
||||
type="'many2many_avatar_user'"/>
|
||||
class="`subtask_user_widget_col justify-content-end align-items-center ${['1_done', '1_canceled'].includes(data.record.data.state) ? 'opacity-50' : ''}`"
|
||||
name="'user_ids'"
|
||||
record="data.record"
|
||||
fieldInfo="data.record.activeFields.user_ids"
|
||||
readonly="true"
|
||||
type="'many2many_avatar_user'"/>
|
||||
<Field name="'state'"
|
||||
class="`subtask_state_widget_col d-flex justify-content-center align-items-center`"
|
||||
record="subTask"
|
||||
fieldInfo="subTask.fields.state"
|
||||
type="'project_task_state_selection'"/>
|
||||
class="`subtask_state_widget_col d-flex justify-content-center align-items-center`"
|
||||
record="data.record"
|
||||
fieldInfo="data.record.activeFields.state"
|
||||
type="'project_task_state_selection'"/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</Record>
|
||||
</Record>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -24,12 +24,13 @@ QUnit.module('Subtask Kanban List tests', {
|
||||
{ name: "User One", login: 'one', password: 'one' },
|
||||
]);
|
||||
pyEnv['project.task'].create([
|
||||
{ name: 'task one', project_id: projectId, closed_subtask_count: 2, subtask_count: 3, child_ids: [2, 3, 4], state: '01_in_progress', user_ids: [userId] },
|
||||
{ name: 'task one', project_id: projectId, closed_subtask_count: 1, subtask_count: 4, child_ids: [2, 3, 4, 7], state: '01_in_progress', user_ids: [userId] },
|
||||
{ name: 'task two', parent_id: 1, closed_subtask_count: 0, subtask_count: 0, child_ids: [], state: '03_approved' },
|
||||
{ name: 'task three', parent_id: 1, closed_subtask_count: 0, subtask_count: 0, child_ids: [], state: '02_changes_requested' },
|
||||
{ name: 'task four', parent_id: 1, closed_subtask_count: 0, subtask_count: 0, child_ids: [], state: '1_done' },
|
||||
{ name: 'task five', closed_subtask_count: 0, subtask_count: 1, child_ids: [6], state: '03_approved' },
|
||||
{ name: 'task six', parent_id: 5, closed_subtask_count: 0, subtask_count: 0, child_ids: [], state: '1_canceled' },
|
||||
{ name: 'task seven', parent_id: 1, closed_subtask_count: 0, subtask_count: 0, child_ids: [], state: '01_in_progress', user_ids: [userId] },
|
||||
]);
|
||||
this.views = {
|
||||
"project.task,false,kanban":
|
||||
@@ -39,15 +40,15 @@ QUnit.module('Subtask Kanban List tests', {
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="name" widget="name_with_subtask_count"/>
|
||||
<field name="user_ids" invisible="1"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="name"/>
|
||||
<field name="user_ids" invisible="1" widget="many2many_avatar_user"/>
|
||||
<field name="state" invisible="1" widget="project_task_state_selection"/>
|
||||
<a t-if="record.closed_subtask_count.raw_value">
|
||||
<span title="See Subtasks" class="subtask_list_button fa fa-check-square-o me-1"/>
|
||||
<t t-out="record.closed_subtask_count.value"/>/<t t-out="record.subtask_count.value"/>
|
||||
</a>
|
||||
<div class="kanban_bottom_subtasks_section"/>
|
||||
</div>
|
||||
<div class="kanban_bottom_subtasks_section"/>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
@@ -72,13 +73,44 @@ QUnit.module('Subtask Kanban List tests', {
|
||||
await click(target, '.subtask_list_button');
|
||||
|
||||
assert.containsOnce(target, '.subtask_list', "Clicking on the button should make the subtask list render, in this case we are expectig 1 list");
|
||||
assert.containsN(target, '.subtask_list_row', 2, "The list rendered should show the open subtasks of the task, in this case 2");
|
||||
assert.containsN(target, '.subtask_state_widget_col', 2, "Each of the list's rows should have 1 state widget, thus we are looking for 2 in total");
|
||||
assert.containsN(target, '.subtask_user_widget_col', 2, "Each of the list's rows should have 1 user widgets, thus we are looking for 2 in total");
|
||||
assert.containsN(target, '.subtask_name_col', 2, "Each of the list's rows should display the subtask's name, thus we are looking for 2 in total");
|
||||
assert.containsN(target, '.subtask_list_row', 3, "The list rendered should show the open subtasks of the task, in this case 3");
|
||||
assert.containsN(target, '.subtask_state_widget_col', 3, "Each of the list's rows should have 1 state widget, thus we are looking for 3 in total");
|
||||
assert.containsN(target, '.subtask_user_widget_col', 3, "Each of the list's rows should have 1 user widgets, thus we are looking for 3 in total");
|
||||
assert.containsN(target, '.subtask_name_col', 3, "Each of the list's rows should display the subtask's name, thus we are looking for 3 in total");
|
||||
|
||||
await click(target, '.subtask_list_button');
|
||||
|
||||
assert.containsNone(target, '.subtask_list', "If the drawdown button is clicked again, the subtasks list should be hidden again");
|
||||
});
|
||||
|
||||
QUnit.test("Update closed subtask count in the kanban card when the state of a subtask is set to Done.", async function (assert) {
|
||||
let checkSteps = false;
|
||||
const { openView } = await start({
|
||||
serverData: { views: this.views },
|
||||
mockRPC(route, { model, method }) {
|
||||
if (checkSteps) {
|
||||
assert.step(`${model}/${method}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
await openView({
|
||||
res_model: "project.task",
|
||||
views: [[false, "kanban"]],
|
||||
});
|
||||
|
||||
assert.strictEqual(target.querySelector(".subtask_list_button").parentElement.textContent, "1/4");
|
||||
checkSteps = true;
|
||||
await click(target, '.subtask_list_button');
|
||||
const subtaskEl = target.querySelector(".subtask_list");
|
||||
assert.containsOnce(subtaskEl, ".o_field_widget.o_field_project_task_state_selection.subtask_state_widget_col .o_status:not(.o_status_green,.o_status_bubble)", "The state of the subtask should be in progress");
|
||||
await click(subtaskEl, ".o_field_widget.o_field_project_task_state_selection.subtask_state_widget_col .o_status:not(.o_status_green,.o_status_bubble)");
|
||||
assert.containsNone(subtaskEl, ".o_field_widget.o_field_project_task_state_selection.subtask_state_widget_col .o_status:not(.o_status_green,.o_status_bubble)", "The state of the subtask should no longer be in progress");
|
||||
assert.containsOnce(subtaskEl, ".o_field_widget.o_field_project_task_state_selection.subtask_state_widget_col .fa-check-circle.text-success", "The state of the subtask should be Done");
|
||||
assert.verifySteps([
|
||||
"project.task/search_read",
|
||||
"project.task/write",
|
||||
"project.task/read",
|
||||
"project.task/read", // read the parent task to recompute the subtask count
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user