[IMP] mail: create kanban and list activity field components
closes odoo/odoo#101330 X-original-commit: 069220ac94b3e33201991aa04506f1aa4227be1d Related: odoo/enterprise#31869 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com> Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
@@ -45,12 +45,12 @@ tour.register('crm_tour', {
|
||||
position: "right",
|
||||
run: "drag_and_drop_native .o_opportunity_kanban .o_kanban_group:eq(2) ",
|
||||
}, {
|
||||
trigger: ".o_kanban_record:not(.o_updating) .o_activity_color_default",
|
||||
trigger: ".o_kanban_record:not(.o_updating) .o_ActivityButtonView",
|
||||
extra_trigger: ".o_opportunity_kanban",
|
||||
content: Markup(_t("Looks like nothing is planned. :(<br><br><i>Tip : Schedule activities to keep track of everything you have to do!</i>")),
|
||||
position: "bottom",
|
||||
}, {
|
||||
trigger: ".o_schedule_activity",
|
||||
trigger: ".o_ActivityListView_addActivityButton",
|
||||
extra_trigger: ".o_opportunity_kanban",
|
||||
content: Markup(_t("Let's <b>Schedule an Activity.</b>")),
|
||||
position: "bottom",
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useRefToModel } from '@mail/component_hooks/use_ref_to_model';
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class ActivityButtonView extends Component {
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
useRefToModel({ fieldName: 'buttonRef', refName: 'button' });
|
||||
}
|
||||
|
||||
get activityButtonView() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(ActivityButtonView, {
|
||||
props: { record: Object },
|
||||
template: 'mail.ActivityButtonView',
|
||||
});
|
||||
|
||||
registerMessagingComponent(ActivityButtonView);
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.ActivityButtonView" owl="1">
|
||||
<a class="o_ActivityButtonView" role="button" t-on-click.prevent="activityButtonView.onClick" t-ref="button">
|
||||
<i class="o_ActivityButtonView_icon fa fa-fw fa-lg" t-att-class="activityButtonView.buttonClass" role="img"/>
|
||||
</a>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,20 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class ActivityListView extends Component {
|
||||
|
||||
get activityListView() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(ActivityListView, {
|
||||
props: { record: Object },
|
||||
template: 'mail.ActivityListView',
|
||||
});
|
||||
|
||||
registerMessagingComponent(ActivityListView);
|
||||
@@ -0,0 +1,3 @@
|
||||
.o_ActivityListView {
|
||||
min-width: 290px;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="mail.ActivityListView" owl="1">
|
||||
<div class="o_ActivityListView d-flex flex-column" t-ref="root">
|
||||
<t t-if="activityListView.activityListViewItems.length === 0">
|
||||
<span class="p-3 text-center fst-italic text-500 border-bottom">Schedule activities to help you get things done.</span>
|
||||
</t>
|
||||
<t t-if="activityListView.overdueActivityListViewItems.length > 0">
|
||||
<div class="d-flex bg-100 py-2 border-bottom">
|
||||
<span class="text-danger fw-bold mx-3">Overdue</span>
|
||||
<span class="flex-grow-1"/>
|
||||
<span class="badge rounded-pill text-bg-danger border-0 mx-3 align-self-center" t-esc="activityListView.overdueActivityListViewItems.length"/>
|
||||
</div>
|
||||
<t t-foreach="activityListView.overdueActivityListViewItems" t-as="activityListViewItem" t-key="activityListViewItem">
|
||||
<ActivityListViewItem record="activityListViewItem"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="activityListView.todayActivityListViewItems.length > 0">
|
||||
<div class="d-flex bg-100 py-2 border-bottom">
|
||||
<span class="text-warning fw-bold mx-3">Today</span>
|
||||
<span class="flex-grow-1"/>
|
||||
<span class="badge rounded-pill text-bg-warning border-0 mx-3 align-self-center" t-esc="activityListView.todayActivityListViewItems.length"/>
|
||||
</div>
|
||||
<t t-foreach="activityListView.todayActivityListViewItems" t-as="activityListViewItem" t-key="activityListViewItem">
|
||||
<ActivityListViewItem record="activityListViewItem"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="activityListView.plannedActivityListViewItems.length > 0">
|
||||
<div class="d-flex bg-100 py-2 border-bottom">
|
||||
<span class="text-success fw-bold mx-3">Planned</span>
|
||||
<span class="flex-grow-1"/>
|
||||
<span class="badge rounded-pill text-bg-success border-0 mx-3 align-self-center" t-esc="activityListView.plannedActivityListViewItems.length"/>
|
||||
</div>
|
||||
<t t-foreach="activityListView.plannedActivityListViewItems" t-as="activityListViewItem" t-key="activityListViewItem">
|
||||
<ActivityListViewItem record="activityListViewItem"/>
|
||||
</t>
|
||||
</t>
|
||||
<button class="o_ActivityListView_addActivityButton btn btn-secondary p-3 text-center" t-on-click="activityListView.onClickAddActivityButton">
|
||||
<i class="fa fa-plus fa-fw"></i><strong>Schedule an activity</strong>
|
||||
</button>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useRefToModel } from '@mail/component_hooks/use_ref_to_model';
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class ActivityListViewItem extends Component {
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
useRefToModel({ fieldName: 'markDoneButtonRef', refName: 'markDoneButton' });
|
||||
}
|
||||
get activityListViewItem() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(ActivityListViewItem, {
|
||||
props: { record: Object },
|
||||
template: 'mail.ActivityListViewItem',
|
||||
});
|
||||
|
||||
registerMessagingComponent(ActivityListViewItem);
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="mail.ActivityListViewItem" owl="1">
|
||||
<div class="o_ActivityListViewItem d-flex flex-column border-bottom px-3 py-2">
|
||||
<div class="d-flex align-items-baseline">
|
||||
<i class="fa small me-2" t-attf-class="{{ activityListViewItem.activity.icon }}" role="img"/>
|
||||
<t t-if="activityListViewItem.activity.summary">
|
||||
<b class="text-900 me-2" t-esc="activityListViewItem.activity.summary"/>
|
||||
</t>
|
||||
<t t-if="!activityListViewItem.activity.summary and activityListViewItem.activity.type">
|
||||
<b class="text-900 me-2" t-esc="activityListViewItem.activity.type.displayName"/>
|
||||
</t>
|
||||
<button t-if="activityListViewItem.activity.chaining_type === 'suggest' and activityListViewItem.activity.canWrite" class="btn btn-sm btn-link" t-on-click="activityListViewItem.onClickEditActivityButton">
|
||||
<i class="fa fa-pencil"/>
|
||||
</button>
|
||||
<div class="flex-grow-1"/>
|
||||
<t t-if="activityListViewItem.activity.canWrite" class="flex-grow-1 text-end">
|
||||
<a t-if="activityListViewItem.fileUploader" class="fa fa-upload" title="Upload file" role="img" t-on-click="activityListViewItem.onClickUploadDocument"/>
|
||||
<a t-else="" class="o_ActivityListViewItem_markAsDone fa fa-check-circle" t-on-click="activityListViewItem.onClickMarkAsDone" title="Mark as done" role="img" aria-label="Mark as done" t-ref="markDoneButton"/>
|
||||
<!-- TODO sign override to change the button -->
|
||||
<!-- TODO approvals override to change stuff -->
|
||||
</t>
|
||||
</div>
|
||||
<div t-if="activityListViewItem.activity.state !== 'today'" class="d-flex align-items-baseline">
|
||||
<i class="fa fa-clock-o me-2 text-muted" role="img" aria-label="Deadline" title="Deadline"/>
|
||||
<small t-if="!activityListViewItem.activity.isCurrentPartnerAssignee and activityListViewItem.activity.assignee" class="me-1"><span t-esc="activityListViewItem.activity.assignee.displayName"/> -</small>
|
||||
<small t-att-title="activityListViewItem.activity.dateDeadline" t-esc="activityListViewItem.delayLabel"/>
|
||||
</div>
|
||||
<!-- TODO handle "mail_template_ids" -->
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class KanbanFieldActivityView extends Component {
|
||||
|
||||
get kanbanFieldActivityView() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(KanbanFieldActivityView, {
|
||||
props: { record: Object },
|
||||
template: 'mail.KanbanFieldActivityView',
|
||||
});
|
||||
|
||||
registerMessagingComponent(KanbanFieldActivityView);
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.KanbanFieldActivityView" owl="1">
|
||||
<ActivityButtonView record="kanbanFieldActivityView.activityButtonView"/>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
// ensure components are registered beforehand.
|
||||
import '@mail/backend_components/kanban_field_activity_view/kanban_field_activity_view';
|
||||
import { getMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { standardFieldProps } from "@web/views/fields/standard_field_props";
|
||||
|
||||
const { Component, onWillDestroy, onWillUpdateProps } = owl;
|
||||
|
||||
const getNextId = (function () {
|
||||
let tmpId = 0;
|
||||
return () => {
|
||||
tmpId += 1;
|
||||
return tmpId;
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Container for messaging component KanbanFieldActivityView ensuring messaging
|
||||
* records are ready before rendering KanbanFieldActivityView component.
|
||||
*/
|
||||
export class KanbanFieldActivityViewContainer extends Component {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
this.kanbanFieldActivityView = undefined;
|
||||
this.kanbanFieldActivityViewId = getNextId();
|
||||
this._insertFromProps(this.props);
|
||||
onWillUpdateProps(nextProps => this._insertFromProps(nextProps));
|
||||
onWillDestroy(() => this._deleteRecord());
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_deleteRecord() {
|
||||
if (this.kanbanFieldActivityView) {
|
||||
if (this.kanbanFieldActivityView.exists()) {
|
||||
this.kanbanFieldActivityView.delete();
|
||||
}
|
||||
this.kanbanFieldActivityView = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async _insertFromProps(props) {
|
||||
const messaging = await this.env.services.messaging.get();
|
||||
if (owl.status(this) === "destroyed") {
|
||||
this._deleteRecord();
|
||||
return;
|
||||
}
|
||||
const kanbanFieldActivityView = messaging.models['KanbanFieldActivityView'].insert({
|
||||
id: this.kanbanFieldActivityViewId,
|
||||
thread: {
|
||||
activities: this.props.value.records.map(activityData => {
|
||||
return {
|
||||
id: activityData.resId,
|
||||
};
|
||||
}),
|
||||
hasActivities: true,
|
||||
id: props.record.resId,
|
||||
model: props.record.resModel,
|
||||
},
|
||||
webRecord: props.record,
|
||||
});
|
||||
if (kanbanFieldActivityView !== this.kanbanFieldActivityView) {
|
||||
this._deleteRecord();
|
||||
this.kanbanFieldActivityView = kanbanFieldActivityView;
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(KanbanFieldActivityViewContainer, {
|
||||
components: { KanbanFieldActivityView: getMessagingComponent('KanbanFieldActivityView') },
|
||||
fieldDependencies: {
|
||||
activity_exception_decoration: { type: 'selection' },
|
||||
activity_exception_icon: { type: 'char' },
|
||||
activity_state: { type: 'selection' },
|
||||
activity_summary: { type: 'char' },
|
||||
activity_type_icon: { type: 'char' },
|
||||
activity_type_id: { type: 'many2one', relation: 'mail.activity.type' },
|
||||
},
|
||||
props: {
|
||||
...standardFieldProps,
|
||||
},
|
||||
template: 'mail.KanbanFieldActivityViewContainer',
|
||||
});
|
||||
|
||||
registry.category('fields').add('kanban_activity', KanbanFieldActivityViewContainer);
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.KanbanFieldActivityViewContainer" owl="1">
|
||||
<KanbanFieldActivityView t-if="kanbanFieldActivityView" record="kanbanFieldActivityView"/>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class ListFieldActivityView extends Component {
|
||||
|
||||
get listFieldActivityView() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(ListFieldActivityView, {
|
||||
props: { record: Object },
|
||||
template: 'mail.ListFieldActivityView',
|
||||
});
|
||||
|
||||
registerMessagingComponent(ListFieldActivityView);
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.ListFieldActivityView" owl="1">
|
||||
<ActivityButtonView record="listFieldActivityView.activityButtonView"/>
|
||||
<span class="o_ListFieldActivityView_summary" t-out="listFieldActivityView.summaryText"/>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
// ensure components are registered beforehand.
|
||||
import '@mail/backend_components/list_field_activity_view/list_field_activity_view';
|
||||
import { getMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { standardFieldProps } from "@web/views/fields/standard_field_props";
|
||||
|
||||
const { Component, onWillDestroy, onWillUpdateProps } = owl;
|
||||
|
||||
const getNextId = (function () {
|
||||
let tmpId = 0;
|
||||
return () => {
|
||||
tmpId += 1;
|
||||
return tmpId;
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Container for messaging component ListFieldActivityView ensuring messaging
|
||||
* records are ready before rendering ListFieldActivityView component.
|
||||
*/
|
||||
export class ListFieldActivityViewContainer extends Component {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
this.listFieldActivityView = undefined;
|
||||
this.listFieldActivityViewId = getNextId();
|
||||
this._insertFromProps(this.props);
|
||||
onWillUpdateProps(nextProps => this._insertFromProps(nextProps));
|
||||
onWillDestroy(() => this._deleteRecord());
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_deleteRecord() {
|
||||
if (this.listFieldActivityView) {
|
||||
if (this.listFieldActivityView.exists()) {
|
||||
this.listFieldActivityView.delete();
|
||||
}
|
||||
this.listFieldActivityView = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async _insertFromProps(props) {
|
||||
const messaging = await this.env.services.messaging.get();
|
||||
if (owl.status(this) === "destroyed") {
|
||||
this._deleteRecord();
|
||||
return;
|
||||
}
|
||||
const listFieldActivityView = messaging.models['ListFieldActivityView'].insert({
|
||||
id: this.listFieldActivityViewId,
|
||||
thread: {
|
||||
activities: this.props.value.records.map(activityData => {
|
||||
return {
|
||||
id: activityData.resId,
|
||||
};
|
||||
}),
|
||||
hasActivities: true,
|
||||
id: props.record.resId,
|
||||
model: props.record.resModel,
|
||||
},
|
||||
webRecord: props.record,
|
||||
});
|
||||
if (listFieldActivityView !== this.listFieldActivityView) {
|
||||
this._deleteRecord();
|
||||
this.listFieldActivityView = listFieldActivityView;
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(ListFieldActivityViewContainer, {
|
||||
components: { ListFieldActivityView: getMessagingComponent('ListFieldActivityView') },
|
||||
fieldDependencies: {
|
||||
activity_exception_decoration: { type: 'selection' },
|
||||
activity_exception_icon: { type: 'char' },
|
||||
activity_state: { type: 'selection' },
|
||||
activity_summary: { type: 'char' },
|
||||
activity_type_icon: { type: 'char' },
|
||||
activity_type_id: { type: 'many2one', relation: 'mail.activity.type' },
|
||||
},
|
||||
props: {
|
||||
...standardFieldProps,
|
||||
},
|
||||
template: 'mail.ListFieldActivityViewContainer',
|
||||
});
|
||||
|
||||
registry.category('fields').add('list_activity', ListFieldActivityViewContainer);
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.ListFieldActivityViewContainer" owl="1">
|
||||
<ListFieldActivityView t-if="listFieldActivityView" record="listFieldActivityView"/>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
+2
-2
@@ -26,8 +26,8 @@ export class ActivityMarkDonePopoverContent extends LegacyComponent {
|
||||
|
||||
_mounted() {
|
||||
this._feedbackTextareaRef.el.focus();
|
||||
if (this.activityMarkDonePopoverContentView.activityViewOwner.activity.feedbackBackup) {
|
||||
this._feedbackTextareaRef.el.value = this.activityMarkDonePopoverContentView.activityViewOwner.activity.feedbackBackup;
|
||||
if (this.activityMarkDonePopoverContentView.activity.feedbackBackup) {
|
||||
this._feedbackTextareaRef.el.value = this.activityMarkDonePopoverContentView.activity.feedbackBackup;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
<button type="button" class="o_ActivityMarkDonePopoverContent_doneScheduleNextButton btn btn-sm btn-primary" t-on-click="activityMarkDonePopoverContentView.onClickDoneAndScheduleNext">
|
||||
Done & Schedule Next
|
||||
</button>
|
||||
<t t-if="activityMarkDonePopoverContentView.activityViewOwner.activity.chaining_type === 'suggest'">
|
||||
<t t-if="activityMarkDonePopoverContentView.activity.chaining_type === 'suggest'">
|
||||
<button type="button" class="o_ActivityMarkDonePopoverContent_doneButton btn btn-sm btn-primary mx-2" t-on-click="activityMarkDonePopoverContentView.onClickDone">
|
||||
Done
|
||||
</button>
|
||||
|
||||
@@ -37,43 +37,26 @@ const KanbanActivity = AbstractField.extend({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init(parent, name, record) {
|
||||
init(parent, name, record, options = {}) {
|
||||
this._super.apply(this, arguments);
|
||||
this._draftFeedback = {};
|
||||
this.selection = {};
|
||||
for (const [key, value] of record.fields.activity_state.selection) {
|
||||
this.selection[key] = value;
|
||||
}
|
||||
this.defaultActivityType = options.activityType;
|
||||
this.isActivityViewCell = options.isActivityViewCell;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {integer} previousActivityTypeID
|
||||
* @return {Promise}
|
||||
*/
|
||||
scheduleActivity() {
|
||||
const callback = this._reload.bind(this, { activity: true, thread: true });
|
||||
return this._openActivityForm(false, callback);
|
||||
async willStart() {
|
||||
await this._super();
|
||||
await owl.Component.env.services.messaging.modelManager.messagingCreatedPromise;
|
||||
this.messaging = owl.Component.env.services.messaging.modelManager.messaging;
|
||||
},
|
||||
|
||||
//------------------------------------------------------------
|
||||
// Private
|
||||
//------------------------------------------------------------
|
||||
|
||||
_getActivityFormAction(id) {
|
||||
return {
|
||||
type: 'ir.actions.act_window',
|
||||
name: _t("Schedule Activity"),
|
||||
res_model: 'mail.activity',
|
||||
view_mode: 'form',
|
||||
views: [[false, 'form']],
|
||||
target: 'new',
|
||||
context: {
|
||||
default_res_id: this.res_id,
|
||||
default_res_model: this.model,
|
||||
},
|
||||
res_id: id || false,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Send a feedback and reload page in order to mark activity as done
|
||||
*
|
||||
@@ -99,7 +82,7 @@ const KanbanActivity = AbstractField.extend({
|
||||
},
|
||||
context: this.record.getContext(),
|
||||
});
|
||||
this._reload({ activity: true, thread: true });
|
||||
this._reload();
|
||||
},
|
||||
/**
|
||||
* Send a feedback and proposes to schedule next activity
|
||||
@@ -136,10 +119,17 @@ const KanbanActivity = AbstractField.extend({
|
||||
* @param {MouseEvent} ev
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_onEditActivity(ev) {
|
||||
async _onEditActivity(ev) {
|
||||
ev.preventDefault();
|
||||
const activityID = $(ev.currentTarget).data('activity-id');
|
||||
return this._openActivityForm(activityID, this._reload.bind(this, { activity: true, thread: true }));
|
||||
const activityId = $(ev.currentTarget).data('activity-id');
|
||||
const thread = this.messaging.models['Thread'].insert({ id: this.res_id, model: this.model });
|
||||
const activity = this.messaging.models['Activity'].insert({ id: activityId, thread });
|
||||
await this.messaging.openActivityForm({
|
||||
activity,
|
||||
defaultActivityTypeId: this.defaultActivityType,
|
||||
thread,
|
||||
});
|
||||
this._reload();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -353,9 +343,14 @@ const KanbanActivity = AbstractField.extend({
|
||||
* @param {MouseEvent} ev
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_onScheduleActivity(ev) {
|
||||
async _onScheduleActivity(ev) {
|
||||
ev.preventDefault();
|
||||
return this._openActivityForm(false, this._reload.bind(this));
|
||||
const thread = this.messaging.models['Thread'].insert({ id: this.res_id, model: this.model });
|
||||
await this.messaging.openActivityForm({
|
||||
defaultActivityTypeId: this.defaultActivityType,
|
||||
thread,
|
||||
});
|
||||
this._reload();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -371,17 +366,7 @@ const KanbanActivity = AbstractField.extend({
|
||||
method: 'activity_send_mail',
|
||||
args: [[this.res_id], templateID],
|
||||
});
|
||||
this._reload({ activity: true, thread: true, followers: true });
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {integer} id
|
||||
* @param {function} callback
|
||||
* @return {Promise}
|
||||
*/
|
||||
_openActivityForm(id, callback) {
|
||||
const action = this._getActivityFormAction(id);
|
||||
return this.do_action(action, { on_close: callback });
|
||||
this._reload();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -394,18 +379,38 @@ const KanbanActivity = AbstractField.extend({
|
||||
* @private
|
||||
*/
|
||||
_render() {
|
||||
// span classes need to be updated manually because the template cannot
|
||||
// be re-rendered eaasily (because of the dropdown state)
|
||||
const spanClasses = ['fa', 'fa-lg', 'fa-fw'];
|
||||
spanClasses.push('o_activity_color_' + (this.record.data.activity_state || 'default'));
|
||||
if (this.recordData.activity_exception_decoration) {
|
||||
spanClasses.push('text-' + this.recordData.activity_exception_decoration);
|
||||
spanClasses.push(this.recordData.activity_exception_icon);
|
||||
if (this.isActivityViewCell) {
|
||||
// replace clock by closest deadline
|
||||
const $date = $('<div class="o_closest_deadline">');
|
||||
const date = moment(this.record.data.closest_deadline).toDate();
|
||||
// To remove year only if current year
|
||||
if (moment().year() === moment(date).year()) {
|
||||
$date.text(date.toLocaleDateString(moment().locale(), {
|
||||
day: 'numeric', month: 'short'
|
||||
}));
|
||||
} else {
|
||||
$date.text(moment(date).format('ll'));
|
||||
}
|
||||
this.$('a').html($date);
|
||||
if (this.record.data.activity_ids.res_ids.length > 1) {
|
||||
this.$('a').append($('<span>', {
|
||||
class: 'badge bg-light rounded-pill border-0 ' + this.record.data.activity_state,
|
||||
text: this.record.data.activity_ids.res_ids.length,
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
spanClasses.push('fa-clock-o');
|
||||
// span classes need to be updated manually because the template cannot
|
||||
// be re-rendered eaasily (because of the dropdown state)
|
||||
const spanClasses = ['fa', 'fa-lg', 'fa-fw'];
|
||||
spanClasses.push('o_activity_color_' + (this.record.data.activity_state || 'default'));
|
||||
if (this.recordData.activity_exception_decoration) {
|
||||
spanClasses.push('text-' + this.recordData.activity_exception_decoration);
|
||||
spanClasses.push(this.recordData.activity_exception_icon);
|
||||
} else {
|
||||
spanClasses.push('fa-clock-o');
|
||||
}
|
||||
this.$('.o_activity_btn > span').removeClass().addClass(spanClasses.join(' '));
|
||||
}
|
||||
this.$('.o_activity_btn > span').removeClass().addClass(spanClasses.join(' '));
|
||||
|
||||
if (this.$el.hasClass('show')) {
|
||||
// note: this part of the rendering might be asynchronous
|
||||
this._renderDropdown();
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import '@mail/js/activity';
|
||||
|
||||
import field_registry from 'web.field_registry';
|
||||
|
||||
const KanbanActivity = field_registry.get('kanban_activity');
|
||||
|
||||
const ActivityCell = KanbanActivity.extend({
|
||||
init(parent, name, record, options) {
|
||||
this._super.apply(this, arguments);
|
||||
this.activityType = options && options.activityType;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @override
|
||||
*/
|
||||
_getActivityFormAction(id) {
|
||||
const action = this._super.apply(this, arguments);
|
||||
action.context['default_activity_type_id'] = this.activityType;
|
||||
return action;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_render() {
|
||||
// replace clock by closest deadline
|
||||
const $date = $('<div class="o_closest_deadline">');
|
||||
const date = moment(this.record.data.closest_deadline).toDate();
|
||||
// To remove year only if current year
|
||||
if (moment().year() === moment(date).year()) {
|
||||
$date.text(date.toLocaleDateString(moment().locale(), {
|
||||
day: 'numeric', month: 'short'
|
||||
}));
|
||||
} else {
|
||||
$date.text(moment(date).format('ll'));
|
||||
}
|
||||
this.$('a').html($date);
|
||||
if (this.record.data.activity_ids.res_ids.length > 1) {
|
||||
this.$('a').append($('<span>', {
|
||||
class: 'badge bg-light rounded-pill border-0 ' + this.record.data.activity_state,
|
||||
text: this.record.data.activity_ids.res_ids.length,
|
||||
}));
|
||||
}
|
||||
if (this.$el.hasClass('show')) {
|
||||
// note: this part of the rendering might be asynchronous
|
||||
this._renderDropdown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default ActivityCell;
|
||||
@@ -4,11 +4,9 @@ import '@mail/js/activity';
|
||||
|
||||
import BasicController from 'web.BasicController';
|
||||
import core from 'web.core';
|
||||
import field_registry from 'web.field_registry';
|
||||
import { sprintf } from '@web/core/utils/strings';
|
||||
import ViewDialogs from 'web.view_dialogs';
|
||||
|
||||
var KanbanActivity = field_registry.get('kanban_activity');
|
||||
var _t = core._t;
|
||||
|
||||
var ActivityController = BasicController.extend({
|
||||
@@ -57,7 +55,6 @@ var ActivityController = BasicController.extend({
|
||||
* @private
|
||||
*/
|
||||
_onScheduleActivity: function () {
|
||||
var self = this;
|
||||
var state = this.model.get(this.handle);
|
||||
new ViewDialogs.SelectCreateDialog(this, {
|
||||
res_model: state.model,
|
||||
@@ -67,10 +64,11 @@ var ActivityController = BasicController.extend({
|
||||
no_create: !this.activeActions.create,
|
||||
disable_multiple_selection: true,
|
||||
context: state.context,
|
||||
on_selected: function (record) {
|
||||
var fakeRecord = state.getKanbanActivityData({}, record[0].id);
|
||||
var widget = new KanbanActivity(self, 'activity_ids', fakeRecord, {});
|
||||
widget.scheduleActivity();
|
||||
on_selected: async records => {
|
||||
const messaging = await owl.Component.env.services.messaging.get();
|
||||
const thread = messaging.models['Thread'].insert({ id: records[0].id, model: this.model.modelName });
|
||||
await messaging.openActivityForm({ thread });
|
||||
this.trigger_up('reload');
|
||||
},
|
||||
}).open();
|
||||
},
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import ActivityCell from '@mail/js/views/activity/activity_cell';
|
||||
import ActivityRecord from '@mail/js/views/activity/activity_record';
|
||||
|
||||
import AbstractRendererOwl from 'web.AbstractRendererOwl';
|
||||
import core from 'web.core';
|
||||
import core from 'web.core';
|
||||
import field_registry from 'web.field_registry';
|
||||
import KanbanColumnProgressBar from 'web.KanbanColumnProgressBar';
|
||||
import { ComponentAdapter } from 'web.OwlCompatibility';
|
||||
import QWeb from 'web.QWeb';
|
||||
@@ -13,6 +13,7 @@ import utils from 'web.utils';
|
||||
|
||||
const { useState } = owl;
|
||||
const _t = core._t;
|
||||
const KanbanActivity = field_registry.get('kanban_activity');
|
||||
|
||||
/**
|
||||
* Owl Component Adapter for ActivityRecord which is KanbanRecord (Odoo Widget)
|
||||
@@ -31,8 +32,8 @@ class ActivityRecordAdapter extends ComponentAdapter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Owl Component Adapter for ActivityCell which is KanbanActivity (AbstractField)
|
||||
* TODO: Remove this adapter when ActivityCell is a Component
|
||||
* Owl Component Adapter for KanbanActivity.
|
||||
* TODO: Remove this adapter when KanbanActivity is a Component.
|
||||
*/
|
||||
class ActivityCellAdapter extends ComponentAdapter {
|
||||
renderWidget() {
|
||||
@@ -93,7 +94,7 @@ class ActivityRenderer extends AbstractRendererOwl {
|
||||
});
|
||||
this.widgetComponents = {
|
||||
ActivityRecord,
|
||||
ActivityCell,
|
||||
KanbanActivity,
|
||||
KanbanColumnProgressBar,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ registerModel({
|
||||
if ('mail_template_ids' in data) {
|
||||
data2.mailTemplates = insert(data.mail_template_ids);
|
||||
}
|
||||
if ('res_id' in data && 'res_model' in data) {
|
||||
if (data.res_id && data.res_model) {
|
||||
data2.thread = insert({
|
||||
id: data.res_id,
|
||||
model: data.res_model,
|
||||
@@ -121,28 +121,11 @@ registerModel({
|
||||
*
|
||||
* @return {Promise} promise that is fulfilled when the form has been closed
|
||||
*/
|
||||
edit() {
|
||||
const action = {
|
||||
type: 'ir.actions.act_window',
|
||||
name: this.env._t("Schedule Activity"),
|
||||
res_model: 'mail.activity',
|
||||
view_mode: 'form',
|
||||
views: [[false, 'form']],
|
||||
target: 'new',
|
||||
context: {
|
||||
default_res_id: this.thread.id,
|
||||
default_res_model: this.thread.model,
|
||||
},
|
||||
res_id: this.id,
|
||||
};
|
||||
return new Promise(resolve => {
|
||||
this.env.services.action.doAction(action, {
|
||||
onClose: () => {
|
||||
resolve();
|
||||
this.fetchAndUpdate();
|
||||
},
|
||||
})
|
||||
});
|
||||
async edit() {
|
||||
await this.messaging.openActivityForm({ activity: this });
|
||||
if (this.exists()) {
|
||||
this.fetchAndUpdate();
|
||||
}
|
||||
},
|
||||
async fetchAndUpdate() {
|
||||
const [data] = await this.messaging.rpc({
|
||||
@@ -151,7 +134,7 @@ registerModel({
|
||||
args: [this.id],
|
||||
}, { shadow: true }).catch(e => {
|
||||
const errorName = e.message && e.message.data && e.message.data.name;
|
||||
if (errorName === 'odoo.exceptions.MissingError') {
|
||||
if ([errorName, e.exceptionName].includes('odoo.exceptions.MissingError')) {
|
||||
return [];
|
||||
} else {
|
||||
throw e;
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, one } from '@mail/model/model_field';
|
||||
import { clear } from '@mail/model/model_field_command';
|
||||
|
||||
registerModel({
|
||||
name: 'ActivityButtonView',
|
||||
identifyingMode: 'xor',
|
||||
recordMethods: {
|
||||
onClick(ev) {
|
||||
if (!this.exists()) {
|
||||
return;
|
||||
}
|
||||
if (this.listFieldActivityViewOwner) {
|
||||
ev.stopPropagation(); // prevents list view click that opens form view. TODO use special_click instead?
|
||||
}
|
||||
this.update({ activityListPopoverView: this.activityListPopoverView ? clear() : {} });
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
activityListPopoverView: one('PopoverView', {
|
||||
inverse: 'activityButtonViewOwnerAsActivityList',
|
||||
}),
|
||||
buttonClass: attr({
|
||||
compute() {
|
||||
if (!this.thread) {
|
||||
return clear();
|
||||
}
|
||||
const classes = [];
|
||||
switch (this.webRecord.data.activity_state) {
|
||||
case 'overdue':
|
||||
classes.push('text-danger');
|
||||
break;
|
||||
case 'today':
|
||||
classes.push('text-warning');
|
||||
break;
|
||||
case 'planned':
|
||||
classes.push('text-success');
|
||||
break;
|
||||
default:
|
||||
classes.push('text-muted');
|
||||
break;
|
||||
}
|
||||
switch (this.webRecord.data.activity_exception_decoration) {
|
||||
case 'warning':
|
||||
classes.push('text-warning');
|
||||
classes.push(this.webRecord.data.activity_exception_icon);
|
||||
break;
|
||||
case 'danger':
|
||||
classes.push('text-danger');
|
||||
classes.push(this.webRecord.data.activity_exception_icon);
|
||||
break;
|
||||
default:
|
||||
if (this.webRecord.data.activity_type_icon) {
|
||||
classes.push(this.webRecord.data.activity_type_icon);
|
||||
break;
|
||||
}
|
||||
classes.push('fa-clock-o');
|
||||
break;
|
||||
}
|
||||
return classes.join(' ');
|
||||
},
|
||||
}),
|
||||
buttonRef: attr(),
|
||||
kanbanFieldActivityViewOwner: one('KanbanFieldActivityView', {
|
||||
identifying: true,
|
||||
inverse: 'activityButtonView',
|
||||
}),
|
||||
listFieldActivityViewOwner: one('ListFieldActivityView', {
|
||||
identifying: true,
|
||||
inverse: 'activityButtonView',
|
||||
}),
|
||||
thread: one('Thread', {
|
||||
compute() {
|
||||
if (this.kanbanFieldActivityViewOwner) {
|
||||
return this.kanbanFieldActivityViewOwner.thread;
|
||||
}
|
||||
if (this.listFieldActivityViewOwner) {
|
||||
return this.listFieldActivityViewOwner.thread;
|
||||
}
|
||||
return clear();
|
||||
},
|
||||
required: true,
|
||||
}),
|
||||
webRecord: attr({
|
||||
compute() {
|
||||
if (this.kanbanFieldActivityViewOwner) {
|
||||
return this.kanbanFieldActivityViewOwner.webRecord;
|
||||
}
|
||||
if (this.listFieldActivityViewOwner) {
|
||||
return this.listFieldActivityViewOwner.webRecord;
|
||||
}
|
||||
return clear();
|
||||
},
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,89 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, many, one } from '@mail/model/model_field';
|
||||
|
||||
import session from 'web.session';
|
||||
|
||||
registerModel({
|
||||
name: 'ActivityListView',
|
||||
lifecycleHooks: {
|
||||
async _created() {
|
||||
if (this.activities.length === 0) {
|
||||
return;
|
||||
}
|
||||
const messaging = this.messaging;
|
||||
const activitiesData = await this.messaging.rpc({
|
||||
model: 'mail.activity',
|
||||
method: 'activity_format',
|
||||
args: [this.activities.map(activity => activity.id)],
|
||||
kwargs: { context: session.user_context },
|
||||
}, { shadow: true });
|
||||
if (!messaging.exists()) {
|
||||
return;
|
||||
}
|
||||
messaging.models['Activity'].insert(
|
||||
activitiesData.map(activityData => messaging.models['Activity'].convertData(activityData))
|
||||
);
|
||||
},
|
||||
},
|
||||
recordMethods: {
|
||||
onClickAddActivityButton() {
|
||||
const thread = this.thread;
|
||||
const webRecord = this.webRecord;
|
||||
this.messaging.openActivityForm({
|
||||
thread,
|
||||
}).then(() => {
|
||||
thread.fetchData(['activities']);
|
||||
webRecord.model.load({ resId: thread.id });
|
||||
});
|
||||
this.popoverViewOwner.delete();
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
activities: many('Activity', {
|
||||
compute() {
|
||||
return this.thread && this.thread.activities;
|
||||
},
|
||||
sort: [
|
||||
['truthy-first', 'dateDeadline'],
|
||||
['case-insensitive-asc', 'dateDeadline'],
|
||||
],
|
||||
}),
|
||||
activityListViewItems: many('ActivityListViewItem', {
|
||||
compute() {
|
||||
return this.activities.map(activity => {
|
||||
return {
|
||||
activity,
|
||||
};
|
||||
});
|
||||
},
|
||||
inverse: 'activityListViewOwner',
|
||||
}),
|
||||
overdueActivityListViewItems: many('ActivityListViewItem', {
|
||||
inverse: 'activityListViewOwnerAsOverdue',
|
||||
}),
|
||||
plannedActivityListViewItems: many('ActivityListViewItem', {
|
||||
inverse: 'activityListViewOwnerAsPlanned',
|
||||
}),
|
||||
popoverViewOwner: one('PopoverView', {
|
||||
identifying: true,
|
||||
inverse: 'activityListView',
|
||||
}),
|
||||
thread: one('Thread', {
|
||||
compute() {
|
||||
return this.popoverViewOwner.activityButtonViewOwnerAsActivityList.thread;
|
||||
},
|
||||
required: true,
|
||||
}),
|
||||
todayActivityListViewItems: many('ActivityListViewItem', {
|
||||
inverse: 'activityListViewOwnerAsToday',
|
||||
}),
|
||||
webRecord: attr({
|
||||
compute() {
|
||||
return this.popoverViewOwner.activityButtonViewOwnerAsActivityList.webRecord;
|
||||
},
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, one } from '@mail/model/model_field';
|
||||
import { clear } from '@mail/model/model_field_command';
|
||||
|
||||
import { auto_str_to_date } from 'web.time';
|
||||
import { sprintf } from '@web/core/utils/strings';
|
||||
|
||||
registerModel({
|
||||
name: 'ActivityListViewItem',
|
||||
recordMethods: {
|
||||
onClickEditActivityButton() {
|
||||
const popoverViewOwner = this.activityListViewOwner.popoverViewOwner;
|
||||
const webRecord = this.webRecord;
|
||||
const thread = this.activity.thread;
|
||||
this.activity.edit().then(() => {
|
||||
webRecord.model.load({ resId: thread.id });
|
||||
});
|
||||
popoverViewOwner.delete();
|
||||
},
|
||||
onClickMarkAsDone() {
|
||||
this.update({ markDonePopoverView: this.markDonePopoverView ? clear() : {} });
|
||||
},
|
||||
/**
|
||||
* Handles the click on the upload document button. This open the file
|
||||
* explorer for upload.
|
||||
*/
|
||||
onClickUploadDocument() {
|
||||
this.fileUploader.openBrowserFileUploader();
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
activity: one('Activity', {
|
||||
identifying: true,
|
||||
}),
|
||||
activityListViewOwner: one('ActivityListView', {
|
||||
identifying: true,
|
||||
inverse: 'activityListViewItems',
|
||||
}),
|
||||
activityListViewOwnerAsOverdue: one('ActivityListView', {
|
||||
compute() {
|
||||
return this.activity.state === 'overdue' ? this.activityListViewOwner : clear();
|
||||
},
|
||||
inverse: 'overdueActivityListViewItems',
|
||||
}),
|
||||
activityListViewOwnerAsPlanned: one('ActivityListView', {
|
||||
compute() {
|
||||
return this.activity.state === 'planned' ? this.activityListViewOwner : clear();
|
||||
},
|
||||
inverse: 'plannedActivityListViewItems',
|
||||
}),
|
||||
activityListViewOwnerAsToday: one('ActivityListView', {
|
||||
compute() {
|
||||
return this.activity.state === 'today' ? this.activityListViewOwner : clear();
|
||||
},
|
||||
inverse: 'todayActivityListViewItems',
|
||||
}),
|
||||
clockWatcher: one('ClockWatcher', {
|
||||
default: {
|
||||
clock: {
|
||||
frequency: 60 * 1000,
|
||||
},
|
||||
},
|
||||
inverse: 'activityListViewItemOwner',
|
||||
}),
|
||||
/**
|
||||
* Compute the label for "when" the activity is due.
|
||||
*/
|
||||
delayLabel: attr({
|
||||
compute() {
|
||||
if (!this.activity.dateDeadline) {
|
||||
return clear();
|
||||
}
|
||||
if (!this.clockWatcher.clock.date) {
|
||||
return clear();
|
||||
}
|
||||
const today = moment(this.clockWatcher.clock.date.getTime()).startOf('day');
|
||||
const momentDeadlineDate = moment(auto_str_to_date(this.activity.dateDeadline));
|
||||
// true means no rounding
|
||||
const diff = momentDeadlineDate.diff(today, 'days', true);
|
||||
if (diff === 0) {
|
||||
return this.env._t("Today");
|
||||
} else if (diff === -1) {
|
||||
return this.env._t("Yesterday");
|
||||
} else if (diff < 0) {
|
||||
return sprintf(this.env._t("%s days overdue"), Math.round(Math.abs(diff)));
|
||||
} else if (diff === 1) {
|
||||
return this.env._t("Tomorrow");
|
||||
} else {
|
||||
return sprintf(this.env._t("Due in %s days"), Math.round(Math.abs(diff)));
|
||||
}
|
||||
},
|
||||
}),
|
||||
fileUploader: one('FileUploader', {
|
||||
compute() {
|
||||
return this.activity.category === 'upload_file' ? {} : clear();
|
||||
},
|
||||
inverse: 'activityListViewItemOwner',
|
||||
}),
|
||||
markDoneButtonRef: attr(),
|
||||
markDonePopoverView: one('PopoverView', {
|
||||
inverse: 'activityListViewItemOwnerAsMarkDone',
|
||||
}),
|
||||
webRecord: attr({
|
||||
compute() {
|
||||
return this.activityListViewOwner.webRecord;
|
||||
},
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -26,27 +26,39 @@ registerModel({
|
||||
* Handles click on this "Done" button.
|
||||
*/
|
||||
async onClickDone() {
|
||||
const { chatter } = this.activityViewOwner.activityBoxView;
|
||||
await this.activityViewOwner.activity.markAsDone({
|
||||
const chatter = this.activityViewOwner && this.activityViewOwner.activityBoxView.chatter;
|
||||
const webRecord = this.webRecord;
|
||||
const thread = this.activity.thread;
|
||||
await this.activity.markAsDone({
|
||||
feedback: this.feedbackTextareaRef.el.value,
|
||||
});
|
||||
if (!chatter.exists() || !chatter.component) {
|
||||
return;
|
||||
if (chatter && chatter.exists() && chatter.component) {
|
||||
chatter.reloadParentView();
|
||||
}
|
||||
if (webRecord) {
|
||||
webRecord.model.load({ resId: thread.id });
|
||||
}
|
||||
chatter.reloadParentView();
|
||||
},
|
||||
/**
|
||||
* Handles click on this "Done & Schedule Next" button.
|
||||
*/
|
||||
async onClickDoneAndScheduleNext() {
|
||||
const { chatter } = this.activityViewOwner.activityBoxView;
|
||||
await this.activityViewOwner.activity.markAsDoneAndScheduleNext({
|
||||
const chatter = this.activityViewOwner && this.activityViewOwner.activityBoxView.chatter;
|
||||
const webRecord = this.webRecord;
|
||||
const thread = this.activity.thread;
|
||||
const activityListViewOwner = this.activityListViewItemOwner && this.activityListViewItemOwner.activityListViewOwner;
|
||||
await this.activity.markAsDoneAndScheduleNext({
|
||||
feedback: this.feedbackTextareaRef.el.value,
|
||||
});
|
||||
if (!chatter.exists() || !chatter.component) {
|
||||
return;
|
||||
if (chatter && chatter.exists() && chatter.component) {
|
||||
chatter.reloadParentView();
|
||||
}
|
||||
if (webRecord) {
|
||||
webRecord.model.load({ resId: thread.id });
|
||||
}
|
||||
if (activityListViewOwner && activityListViewOwner.exists()) {
|
||||
activityListViewOwner.popoverViewOwner.delete();
|
||||
}
|
||||
chatter.reloadParentView();
|
||||
},
|
||||
/**
|
||||
* Handles keydown on this activity mark done.
|
||||
@@ -60,7 +72,7 @@ registerModel({
|
||||
* @private
|
||||
*/
|
||||
_backupFeedback() {
|
||||
this.activityViewOwner.activity.update({
|
||||
this.activity.update({
|
||||
feedbackBackup: this.feedbackTextareaRef.el.value,
|
||||
});
|
||||
},
|
||||
@@ -69,10 +81,37 @@ registerModel({
|
||||
*/
|
||||
_close() {
|
||||
this._backupFeedback();
|
||||
this.activityViewOwner.update({ markDonePopoverView: clear() });
|
||||
if (this.activityViewOwner) {
|
||||
this.activityViewOwner.update({ markDonePopoverView: clear() });
|
||||
return;
|
||||
}
|
||||
if (this.activityListViewItemOwner) {
|
||||
this.activityListViewItemOwner.markDonePopoverView.delete();
|
||||
return;
|
||||
}
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
activity: one('Activity', {
|
||||
compute() {
|
||||
if (this.activityListViewItemOwner) {
|
||||
return this.activityListViewItemOwner.activity;
|
||||
}
|
||||
if (this.activityViewOwner) {
|
||||
return this.activityViewOwner.activity;
|
||||
}
|
||||
return clear();
|
||||
},
|
||||
required: true,
|
||||
}),
|
||||
activityListViewItemOwner: one('ActivityListViewItem', {
|
||||
compute() {
|
||||
if (this.popoverViewOwner.activityListViewItemOwnerAsMarkDone) {
|
||||
return this.popoverViewOwner.activityListViewItemOwnerAsMarkDone;
|
||||
}
|
||||
return clear();
|
||||
},
|
||||
}),
|
||||
activityViewOwner: one('ActivityView', {
|
||||
compute() {
|
||||
if (this.popoverViewOwner.activityViewOwnerAsMarkDone) {
|
||||
@@ -88,13 +127,20 @@ registerModel({
|
||||
if (this.activityViewOwner) {
|
||||
return this.activityViewOwner.markDoneText;
|
||||
}
|
||||
return clear();
|
||||
return this.env._t("Mark Done");
|
||||
},
|
||||
default: '',
|
||||
}),
|
||||
popoverViewOwner: one('PopoverView', {
|
||||
identifying: true,
|
||||
inverse: 'activityMarkDonePopoverContentView',
|
||||
}),
|
||||
webRecord: attr({
|
||||
compute() {
|
||||
if (this.activityListViewItemOwner) {
|
||||
return this.activityListViewItemOwner.webRecord;
|
||||
}
|
||||
return clear();
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -71,26 +71,11 @@ registerModel({
|
||||
*
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickScheduleActivity(ev) {
|
||||
const action = {
|
||||
type: 'ir.actions.act_window',
|
||||
name: this.env._t("Schedule Activity"),
|
||||
res_model: 'mail.activity',
|
||||
view_mode: 'form',
|
||||
views: [[false, 'form']],
|
||||
target: 'new',
|
||||
context: {
|
||||
default_res_id: this.thread.id,
|
||||
default_res_model: this.thread.model,
|
||||
},
|
||||
res_id: false,
|
||||
};
|
||||
return this.env.services.action.doAction(
|
||||
action,
|
||||
{
|
||||
onClose: () => this.reloadParentView(),
|
||||
}
|
||||
);
|
||||
async onClickScheduleActivity(ev) {
|
||||
await this.messaging.openActivityForm({ thread: this.thread });
|
||||
if (this.exists()) {
|
||||
this.reloadParentView();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Handles click on "send message" button.
|
||||
|
||||
@@ -10,6 +10,10 @@ registerModel({
|
||||
name: 'ClockWatcher',
|
||||
identifyingMode: 'xor',
|
||||
fields: {
|
||||
activityListViewItemOwner: one('ActivityListViewItem', {
|
||||
identifying: true,
|
||||
inverse: 'clockWatcher',
|
||||
}),
|
||||
activityViewOwner: one('ActivityView', {
|
||||
identifying: true,
|
||||
inverse: 'clockWatcher',
|
||||
|
||||
@@ -94,6 +94,7 @@ registerModel({
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async _performUpload({ files }) {
|
||||
const webRecord = this.activityListViewItemOwner && this.activityListViewItemOwner.webRecord;
|
||||
const composer = this.composerView && this.composerView.composer; // save before async
|
||||
const thread = this.thread; // save before async
|
||||
const chatter = (
|
||||
@@ -101,7 +102,10 @@ registerModel({
|
||||
(this.attachmentBoxView && this.attachmentBoxView.chatter) ||
|
||||
(this.activityView && this.activityView.activityBoxView.chatter)
|
||||
); // save before async
|
||||
const activity = this.activityView && this.activityView.activity; // save before async
|
||||
const activity = (
|
||||
this.activityView && this.activityView.activity ||
|
||||
this.activityListViewItemOwner && this.activityListViewItemOwner.activity
|
||||
); // save before async
|
||||
const uploadingAttachments = new Map();
|
||||
for (const file of files) {
|
||||
uploadingAttachments.set(file, this.messaging.models['Attachment'].insert({
|
||||
@@ -145,6 +149,9 @@ registerModel({
|
||||
}
|
||||
}
|
||||
}
|
||||
if (webRecord) {
|
||||
webRecord.model.load({ resId: thread.id });
|
||||
}
|
||||
if (chatter && chatter.exists() && chatter.hasParentReloadOnAttachmentsChanged) {
|
||||
chatter.reloadParentView();
|
||||
}
|
||||
@@ -154,6 +161,10 @@ registerModel({
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
activityListViewItemOwner: one('ActivityListViewItem', {
|
||||
identifying: true,
|
||||
inverse: 'fileUploader',
|
||||
}),
|
||||
activityView: one('ActivityView', {
|
||||
identifying: true,
|
||||
inverse: 'fileUploader',
|
||||
@@ -189,6 +200,9 @@ registerModel({
|
||||
if (this.activityView) {
|
||||
return this.activityView.activity.thread;
|
||||
}
|
||||
if (this.activityListViewItemOwner) {
|
||||
return this.activityListViewItemOwner.activity.thread;
|
||||
}
|
||||
if (this.attachmentBoxView) {
|
||||
return this.attachmentBoxView.chatter.thread;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, one } from '@mail/model/model_field';
|
||||
|
||||
registerModel({
|
||||
name: 'KanbanFieldActivityView',
|
||||
fields: {
|
||||
activityButtonView: one('ActivityButtonView', {
|
||||
default: {},
|
||||
inverse: 'kanbanFieldActivityViewOwner',
|
||||
required: true,
|
||||
}),
|
||||
id: attr({
|
||||
identifying: true,
|
||||
}),
|
||||
thread: one('Thread', {
|
||||
required: true,
|
||||
}),
|
||||
webRecord: attr({
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, one } from '@mail/model/model_field';
|
||||
import { clear } from '@mail/model/model_field_command';
|
||||
|
||||
registerModel({
|
||||
name: 'ListFieldActivityView',
|
||||
fields: {
|
||||
activityButtonView: one('ActivityButtonView', {
|
||||
default: {},
|
||||
inverse: 'listFieldActivityViewOwner',
|
||||
required: true,
|
||||
}),
|
||||
id: attr({
|
||||
identifying: true,
|
||||
}),
|
||||
summaryText: attr({
|
||||
compute() {
|
||||
if (this.thread.activities.length === 0) {
|
||||
return clear();
|
||||
}
|
||||
if (this.webRecord.data.activity_exception_decoration) {
|
||||
return this.env._t("Warning");
|
||||
}
|
||||
if (this.webRecord.data.activity_summary) {
|
||||
return this.webRecord.data.activity_summary;
|
||||
}
|
||||
if (this.webRecord.data.activity_type_id) {
|
||||
return this.webRecord.data.activity_type_id[1]; // 1 = display_name
|
||||
}
|
||||
return clear();
|
||||
},
|
||||
default: '',
|
||||
}),
|
||||
thread: one('Thread', {
|
||||
required: true,
|
||||
}),
|
||||
webRecord: attr({
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -93,6 +93,42 @@ addRecordMethods('Messaging', {
|
||||
const { message, ...options } = params;
|
||||
return this.env.services.notification.add(message, options);
|
||||
},
|
||||
/**
|
||||
* Opens the activity form view for creating an activity on the given
|
||||
* thread (if no activity is specified) or to update an existing
|
||||
* activity (if specified).
|
||||
*
|
||||
* @param {Object} param0
|
||||
* @param {Activity} [param0.activity]
|
||||
* @param {integer} [param0.defaultActivityTypeId]
|
||||
* @param {Thread} [param0.thread]
|
||||
* @returns {Promise} resolved when the form is closed
|
||||
*/
|
||||
async openActivityForm({ activity, defaultActivityTypeId, thread }) {
|
||||
const targetThread = activity && activity.thread || thread;
|
||||
const context = {
|
||||
default_res_id: targetThread.id,
|
||||
default_res_model: targetThread.model,
|
||||
};
|
||||
if (defaultActivityTypeId !== undefined) {
|
||||
context.default_activity_type_id = defaultActivityTypeId;
|
||||
}
|
||||
const action = {
|
||||
type: 'ir.actions.act_window',
|
||||
name: this.env._t("Schedule Activity"),
|
||||
res_model: 'mail.activity',
|
||||
view_mode: 'form',
|
||||
views: [[false, 'form']],
|
||||
target: 'new',
|
||||
context,
|
||||
res_id: activity ? activity.id : false,
|
||||
};
|
||||
return new Promise(resolve => {
|
||||
this.env.services.action.doAction(action, {
|
||||
onClose: resolve,
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Opens a chat with the provided person and returns it.
|
||||
*
|
||||
|
||||
@@ -42,13 +42,34 @@ registerModel({
|
||||
if (this.component.root.el.contains(ev.target)) {
|
||||
return;
|
||||
}
|
||||
if (this.activityListView) {
|
||||
for (const activityListViewItem of this.activityListView.activityListViewItems) {
|
||||
if (activityListViewItem.markDonePopoverView && activityListViewItem.markDonePopoverView.contains(ev.target)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.delete();
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
activityButtonViewOwnerAsActivityList: one('ActivityButtonView', {
|
||||
identifying: true,
|
||||
inverse: 'activityListPopoverView',
|
||||
}),
|
||||
activityListView: one('ActivityListView', {
|
||||
compute() {
|
||||
return this.activityButtonViewOwnerAsActivityList ? {} : clear();
|
||||
},
|
||||
inverse: 'popoverViewOwner',
|
||||
}),
|
||||
activityListViewItemOwnerAsMarkDone: one('ActivityListViewItem', {
|
||||
identifying: true,
|
||||
inverse: 'markDonePopoverView',
|
||||
}),
|
||||
activityMarkDonePopoverContentView: one('ActivityMarkDonePopoverContentView', {
|
||||
compute() {
|
||||
if (this.activityViewOwnerAsMarkDone) {
|
||||
if (this.activityViewOwnerAsMarkDone || this.activityListViewItemOwnerAsMarkDone) {
|
||||
return {};
|
||||
}
|
||||
return clear();
|
||||
@@ -64,6 +85,9 @@ registerModel({
|
||||
*/
|
||||
anchorRef: attr({
|
||||
compute() {
|
||||
if (this.activityListViewItemOwnerAsMarkDone) {
|
||||
return this.activityListViewItemOwnerAsMarkDone.markDoneButtonRef;
|
||||
}
|
||||
if (this.activityViewOwnerAsMarkDone) {
|
||||
return this.activityViewOwnerAsMarkDone.markDoneButtonRef;
|
||||
}
|
||||
@@ -79,6 +103,9 @@ registerModel({
|
||||
if (this.composerViewOwnerAsEmoji) {
|
||||
return this.composerViewOwnerAsEmoji.buttonEmojisRef;
|
||||
}
|
||||
if (this.activityButtonViewOwnerAsActivityList) {
|
||||
return this.activityButtonViewOwnerAsActivityList.buttonRef;
|
||||
}
|
||||
if (this.messageActionViewOwnerAsReaction) {
|
||||
return this.messageActionViewOwnerAsReaction.actionRef;
|
||||
}
|
||||
@@ -158,6 +185,9 @@ registerModel({
|
||||
if (this.emojiPickerView) {
|
||||
return this.emojiPickerView;
|
||||
}
|
||||
if (this.activityButtonViewOwnerAsActivityList) {
|
||||
return this.activityListView;
|
||||
}
|
||||
if (this.messageNotificationPopoverContentView) {
|
||||
return this.messageNotificationPopoverContentView;
|
||||
}
|
||||
@@ -201,6 +231,9 @@ registerModel({
|
||||
if (this.emojiPickerView) {
|
||||
return 'EmojiPickerView';
|
||||
}
|
||||
if (this.activityButtonViewOwnerAsActivityList) {
|
||||
return 'ActivityListView';
|
||||
}
|
||||
if (this.messageNotificationPopoverContentView) {
|
||||
return 'MessageNotificationPopoverContent';
|
||||
}
|
||||
@@ -259,6 +292,9 @@ registerModel({
|
||||
*/
|
||||
position: attr({
|
||||
compute() {
|
||||
if (this.activityListViewItemOwnerAsMarkDone) {
|
||||
return 'right';
|
||||
}
|
||||
if (this.activityViewOwnerAsMarkDone) {
|
||||
return 'right';
|
||||
}
|
||||
@@ -274,6 +310,9 @@ registerModel({
|
||||
if (this.composerViewOwnerAsEmoji) {
|
||||
return 'top';
|
||||
}
|
||||
if (this.activityButtonViewOwnerAsActivityList) {
|
||||
return 'bottom-start';
|
||||
}
|
||||
if (this.messageActionViewOwnerAsReaction) {
|
||||
return 'top';
|
||||
}
|
||||
|
||||
@@ -1088,6 +1088,20 @@ registerModel({
|
||||
activities: many('Activity', {
|
||||
inverse: 'thread',
|
||||
}),
|
||||
activity_state: attr({
|
||||
compute() {
|
||||
if (this.overdueActivities.length > 0) {
|
||||
return 'overdue';
|
||||
}
|
||||
if (this.todayActivities.length > 0) {
|
||||
return 'today';
|
||||
}
|
||||
if (this.futureActivities.length > 0) {
|
||||
return 'planned';
|
||||
}
|
||||
return clear();
|
||||
},
|
||||
}),
|
||||
allAttachments: many('Attachment', {
|
||||
compute() {
|
||||
const allAttachments = [...new Set(this.originThreadAttachments.concat(this.attachments))]
|
||||
|
||||
@@ -61,8 +61,8 @@
|
||||
<t t-set="isCellHidden" t-value="activeFilter.resIds.length and !activeFilter.resIds.includes(resId) and activeFilter.activityTypeId === type[0]"/>
|
||||
<td t-if="activityGroup.state and !isCellHidden" t-att-data-res-id="resId" t-att-data-activity-type-id="type[0]"
|
||||
t-attf-class="o_activity_summary_cell {{activityGroup.state}} {{ activeFilter.resIds.includes(resId) ? 'o_activity_filter_' + activeFilter.state.value : '' }}">
|
||||
<ActivityCellAdapter Component="widgetComponents.ActivityCell"
|
||||
widgetArgs="['activity_ids', props.getKanbanActivityData(activityGroup, resId), { activityType: type[0] }]"/>
|
||||
<ActivityCellAdapter Component="widgetComponents.KanbanActivity"
|
||||
widgetArgs="['activity_ids', props.getKanbanActivityData(activityGroup, resId), { activityType: type[0], isActivityViewCell: true }]"/>
|
||||
</td>
|
||||
<td t-else="" t-att-data-res-id="resId" t-att-data-activity-type-id="type[0]"
|
||||
class="o_activity_summary_cell o_activity_empty_cell"
|
||||
|
||||
@@ -677,7 +677,7 @@ patch(MockServer.prototype, 'mail', {
|
||||
res_model: 'mail.activity',
|
||||
views: [[false, 'form']],
|
||||
type: 'ir.actions.act_window',
|
||||
}
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Simulates `get_activity_data` on `mail.activity`.
|
||||
|
||||
@@ -34,8 +34,8 @@ QUnit.test('list activity widget with no activity', async function (assert) {
|
||||
views: [[false, 'list']],
|
||||
});
|
||||
|
||||
assert.containsOnce(document.body, '.o_mail_activity .o_activity_color_default');
|
||||
assert.strictEqual(document.querySelector('.o_activity_summary').innerText, '');
|
||||
assert.containsOnce(document.body, '.o_ActivityButtonView_icon.text-muted');
|
||||
assert.strictEqual(document.querySelector('.o_ListFieldActivityView_summary').innerText, '');
|
||||
|
||||
assert.verifySteps(['/web/dataset/call_kw/res.users/web_search_read']);
|
||||
});
|
||||
@@ -83,12 +83,12 @@ QUnit.test('list activity widget with activities', async function (assert) {
|
||||
});
|
||||
|
||||
const firstRow = document.querySelector('.o_data_row');
|
||||
assert.containsOnce(firstRow, '.o_mail_activity .o_activity_color_today.fa-phone');
|
||||
assert.strictEqual(firstRow.querySelector('.o_activity_summary').innerText, 'Call with Al');
|
||||
assert.containsOnce(firstRow, '.o_ActivityButtonView_icon.text-warning.fa-phone');
|
||||
assert.strictEqual(firstRow.querySelector('.o_ListFieldActivityView_summary').innerText, 'Call with Al');
|
||||
|
||||
const secondRow = document.querySelectorAll('.o_data_row')[1];
|
||||
assert.containsOnce(secondRow, '.o_mail_activity .o_activity_color_planned.fa-clock-o');
|
||||
assert.strictEqual(secondRow.querySelector('.o_activity_summary').innerText, 'Type 2');
|
||||
assert.containsOnce(secondRow, '.o_ActivityButtonView_icon.text-success.fa-clock-o');
|
||||
assert.strictEqual(secondRow.querySelector('.o_ListFieldActivityView_summary').innerText, 'Type 2');
|
||||
|
||||
assert.verifySteps(['/web/dataset/call_kw/res.users/web_search_read']);
|
||||
});
|
||||
@@ -127,8 +127,8 @@ QUnit.test('list activity widget with exception', async function (assert) {
|
||||
views: [[false, 'list']],
|
||||
});
|
||||
|
||||
assert.containsOnce(document.body, '.o_activity_color_today.text-warning.fa-warning');
|
||||
assert.strictEqual(document.querySelector('.o_activity_summary').innerText, 'Warning');
|
||||
assert.containsOnce(document.body, '.o_ActivityButtonView_icon.text-warning.fa-warning');
|
||||
assert.strictEqual(document.querySelector('.o_ListFieldActivityView_summary').innerText, 'Warning');
|
||||
|
||||
assert.verifySteps(['/web/dataset/call_kw/res.users/web_search_read']);
|
||||
});
|
||||
@@ -168,7 +168,7 @@ QUnit.test('list activity widget: open dropdown', async function (assert) {
|
||||
const views = {
|
||||
'res.users,false,list': '<list><field name="activity_ids" widget="list_activity"/></list>',
|
||||
};
|
||||
const { openView } = await start({
|
||||
const { click, openView } = await start({
|
||||
mockRPC: function (route, args) {
|
||||
if (
|
||||
args.method !== 'get_views' &&
|
||||
@@ -197,7 +197,7 @@ QUnit.test('list activity widget: open dropdown', async function (assert) {
|
||||
this.props.selectRecord = (...args) => {
|
||||
assert.step(`select_record ${JSON.stringify(args)}`);
|
||||
return selectRecord(...args);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -206,29 +206,21 @@ QUnit.test('list activity widget: open dropdown', async function (assert) {
|
||||
views: [[false, 'list']],
|
||||
});
|
||||
|
||||
assert.strictEqual(document.querySelector('.o_activity_summary').innerText, 'Call with Al');
|
||||
assert.strictEqual(document.querySelector('.o_ListFieldActivityView_summary').innerText, 'Call with Al');
|
||||
|
||||
// click on the first record to open it, to ensure that the 'switch_view'
|
||||
// assertion is relevant (it won't be opened as there is no action manager,
|
||||
// but we'll log the 'switch_view' event)
|
||||
await testUtils.dom.click(document.querySelector('.o_data_cell'));
|
||||
await click('.o_ActivityButtonView'); // open the popover
|
||||
await click('.o_ActivityListViewItem_markAsDone'); // mark the first activity as done
|
||||
await click('.o_ActivityMarkDonePopoverContent_doneButton'); // confirm
|
||||
|
||||
// from this point, no 'switch_view' event should be triggered, as we
|
||||
// interact with the activity widget
|
||||
assert.step('open dropdown');
|
||||
await testUtils.dom.click(document.querySelector('.o_activity_btn span')); // open the popover
|
||||
await testUtils.dom.click(document.querySelector('.o_mark_as_done')); // mark the first activity as done
|
||||
await testUtils.dom.click(document.querySelector('.o_activity_popover_done')); // confirm
|
||||
|
||||
assert.strictEqual(document.querySelector('.o_activity_summary').innerText, 'Meet FP');
|
||||
assert.strictEqual(document.querySelector('.o_ListFieldActivityView_summary').innerText, 'Meet FP');
|
||||
|
||||
assert.verifySteps([
|
||||
'web_search_read',
|
||||
'select_record [2,{"activeIds":[2]}]',
|
||||
'open dropdown',
|
||||
'activity_format',
|
||||
'action_feedback',
|
||||
'read',
|
||||
'/mail/thread/messages',
|
||||
'/mail/thread/data',
|
||||
'web_search_read',
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -287,108 +279,6 @@ QUnit.test('list activity exception widget with activity', async function (asser
|
||||
assert.containsOnce(document.querySelectorAll('.o_data_row .o_activity_exception_cell')[1], '.o_ActivityException', "there is an exception on a record");
|
||||
});
|
||||
|
||||
QUnit.test('list activity widget: done the activity with "ENTER" keyboard shortcut', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailActivityTypeId1 = pyEnv['mail.activity.type'].create({});
|
||||
const mailActivityId1 = pyEnv['mail.activity'].create([
|
||||
{
|
||||
display_name: "Call with Al",
|
||||
date_deadline: moment().format("YYYY-MM-DD"), // now
|
||||
can_write: true,
|
||||
state: "today",
|
||||
user_id: pyEnv.currentUserId,
|
||||
create_uid: pyEnv.currentUserId,
|
||||
activity_type_id: mailActivityTypeId1,
|
||||
}
|
||||
]);
|
||||
pyEnv['res.users'].write([pyEnv.currentUserId], {
|
||||
activity_ids: [mailActivityId1],
|
||||
activity_state: 'today',
|
||||
activity_summary: 'Call with Al',
|
||||
activity_type_id: mailActivityTypeId1,
|
||||
});
|
||||
const views = {
|
||||
'res.users,false,list':
|
||||
`<list>
|
||||
<field name="activity_ids" widget="list_activity"/>
|
||||
</list>`,
|
||||
};
|
||||
const { openView } = await start({
|
||||
serverData: { views },
|
||||
});
|
||||
await openView({
|
||||
res_model: 'res.users',
|
||||
views: [[false, 'list']],
|
||||
});
|
||||
|
||||
await testUtils.dom.click(document.querySelector('.o_activity_btn span'));
|
||||
await testUtils.dom.click(document.querySelector('.o_mark_as_done'));
|
||||
pyEnv['res.users'].write([pyEnv.currentUserId], {
|
||||
activity_state: '',
|
||||
});
|
||||
await testUtils.dom.triggerEvent(
|
||||
document.querySelector('.o_activity_popover_done'),
|
||||
'keydown',
|
||||
{ key: 'Enter' },
|
||||
);
|
||||
|
||||
assert.containsOnce(document.body, '.o_mail_activity .o_activity_color_default');
|
||||
});
|
||||
|
||||
QUnit.test('list activity widget: done and schedule the next activity with "ENTER" keyboard shortcut', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailActivityTypeId1 = pyEnv['mail.activity.type'].create({});
|
||||
const mailActivityId1 = pyEnv['mail.activity'].create([
|
||||
{
|
||||
display_name: "Call with Al",
|
||||
date_deadline: moment().format("YYYY-MM-DD"), // now
|
||||
can_write: true,
|
||||
state: "today",
|
||||
user_id: pyEnv.currentUserId,
|
||||
create_uid: pyEnv.currentUserId,
|
||||
activity_type_id: mailActivityTypeId1,
|
||||
}
|
||||
]);
|
||||
pyEnv['res.users'].write([pyEnv.currentUserId], {
|
||||
activity_ids: [mailActivityId1],
|
||||
activity_state: 'today',
|
||||
activity_summary: 'Call with Al',
|
||||
activity_type_id: mailActivityTypeId1,
|
||||
});
|
||||
|
||||
const views = {
|
||||
'res.users,false,list':
|
||||
`<list>
|
||||
<field name="activity_ids" widget="list_activity"/>
|
||||
</list>`,
|
||||
};
|
||||
const { env, openView } = await start({
|
||||
serverData: { views },
|
||||
});
|
||||
await openView({
|
||||
res_model: 'res.users',
|
||||
views: [[false, 'list']],
|
||||
});
|
||||
patchWithCleanup(env.services.action, {
|
||||
doAction(action) {
|
||||
assert.strictEqual(action.name, "Schedule an Activity", 'should do a do_action with correct parameters');
|
||||
return this._super(...arguments);
|
||||
}
|
||||
});
|
||||
|
||||
await testUtils.dom.click(document.querySelector('.o_activity_btn span'));
|
||||
await testUtils.dom.click(document.querySelector('.o_mark_as_done'));
|
||||
await testUtils.dom.triggerEvent(
|
||||
document.querySelector('.o_activity_popover_done_next'),
|
||||
'keydown',
|
||||
{ key: 'Enter' },
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.module('FieldMany2ManyTagsEmail');
|
||||
|
||||
QUnit.test('fieldmany2many tags email (edition)', async function (assert) {
|
||||
|
||||
@@ -387,7 +387,7 @@ QUnit.test('activity view: search more to schedule an activity for a record of a
|
||||
var $modal = $('.modal-lg');
|
||||
assert.strictEqual($modal.find('.o_data_row').length, 3, "all mail.test.activity should be available to select");
|
||||
// select a record to schedule an activity for it (this triggers a do_action)
|
||||
testUtils.dom.click($modal.find('.o_data_row:last'));
|
||||
await testUtils.dom.click($modal.find('.o_data_row:last'));
|
||||
assert.verifySteps(['doAction']);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user