mail/static/tests/helpers/mock_server/models/mail_activity.js

323 lines
12 KiB
JavaScript
Raw Normal View History

2024-05-03 12:40:35 +03:00
/* @odoo-module */
import { Domain } from "@web/core/domain";
import { groupBy, sortBy, unique } from "@web/core/utils/arrays";
import { patch } from "@web/core/utils/patch";
import { MockServer } from "@web/../tests/helpers/mock_server";
import { deserializeDate, serializeDate, today } from "@web/core/l10n/dates";
const { DateTime} = luxon;
patch(MockServer.prototype, {
async _performRPC(route, args) {
if (args.model === "mail.activity" && args.method === "action_feedback") {
const ids = args.args[0];
return this._mockMailActivityActionFeedback(ids);
}
if (args.model === "mail.activity" && args.method === "action_feedback_schedule_next") {
const ids = args.args[0];
return this._mockMailActivityActionFeedbackScheduleNext(ids);
}
if (args.model === "mail.activity" && args.method === "activity_format") {
const ids = args.args[0];
return this._mockMailActivityActivityFormat(ids);
}
if (args.model === "mail.activity" && args.method === "get_activity_data") {
const res_model = args.args[0] || args.kwargs.res_model;
const domain = args.args[1] || args.kwargs.domain;
const limit = args[2] || args.kwargs.limit || 0;
const offset = args[3] || args.kwargs.offset || 0;
const fetch_done = args[4] || args.kwargs.fetch_done || false;
return this._mockMailActivityGetActivityData(
res_model,
domain,
limit,
offset,
fetch_done
);
}
return super._performRPC(route, args);
},
/**
* Simulates `activity_format` on `mail.activity`.
*
* @private
* @param {number[]} ids
* @returns {Object[]}
*/
_mockMailActivityActivityFormat(ids) {
let res = this.mockRead("mail.activity", [ids]);
res = res.map((record) => {
if (record.mail_template_ids) {
record.mail_template_ids = record.mail_template_ids.map((template_id) => {
const template = this.getRecords("mail.template", [
["id", "=", template_id],
])[0];
return {
id: template.id,
name: template.name,
};
});
}
const [activityType] = record.activity_type_id
? this.pyEnv["mail.activity.type"].searchRead([
["id", "=", record.activity_type_id[0]],
])
: [false];
if (activityType) {
record.display_name = activityType.name;
record.icon = activityType.icon;
}
if (record.summary) {
record.display_name = record.summary;
}
return record;
});
return res;
},
/**
* Simulates `_action_done` on `mail.activity`.
*
* @private
* @param {string} model
* @param {integer[]} ids
* @returns {Object}
*/
_mockMailActivityActionDone(ids) {
this._mockMailActivityActionFeedback(ids);
},
/**
* Simulates `action_feedback` on `mail.activity`.
*
* @private
* @param {string} model
* @param {integer[]} ids
* @returns {Object}
*/
_mockMailActivityActionFeedback(ids, attachment_ids= null) {
const activities = this.getRecords("mail.activity", [["id", "in", ids]]);
const activityTypes = this.getRecords("mail.activity.type", [
["id", "in", unique(activities.map((a) => a.activity_type_id))],
]);
const activityTypeById = Object.fromEntries(
activityTypes.map((actType) => [actType.id, actType])
);
this.mockWrite("mail.activity", [
activities
.filter((act) => activityTypeById[act.activity_type_id].keep_done)
.map((act) => act.id),
{ active: false, date_done: serializeDate(today()), state: "done" },
]);
this.mockUnlink("mail.activity", [
activities
.filter((act) => !activityTypeById[act.activity_type_id].keep_done)
.map((act) => act.id),
]);
},
/**
* Simulates `action_feedback_schedule_next` on `mail.activity`.
*
* @private
* @param {string} model
* @param {integer[]} ids
* @returns {Object}
*/
_mockMailActivityActionFeedbackScheduleNext(ids) {
this._mockMailActivityActionDone(ids);
return {
name: "Schedule an Activity",
view_mode: "form",
res_model: "mail.activity",
views: [[false, "form"]],
type: "ir.actions.act_window",
};
},
/**
* Simulates `get_activity_data` on `mail.activity`.
*
* @private
* @param {string} res_model
* @param {string} domain
* @param {number} limit
* @param {number} offset
* @returns {Object}
*/
/**
* Simulate partially (time zone not supported) `_compute_state_from_date` on `mail.activity`.
*
* @param {DateTime} date_deadline to convert into state
* @returns {string} dateline status (today, planned or overdue)
* @private
*/
_mockComputeStateFromDate(date_deadline) {
const now = DateTime.now();
if (date_deadline.hasSame(now, "day")) {
return "today";
} else if (date_deadline > now) {
return "planned";
}
return "overdue";
},
/**
* Simulates `get_activity_data` on `mail.activity`.
*
* @private
* @param {string} res_model
* @param {string} domain
* @param {number} limit
* @param {number} offset
* @param {boolean} fetch_done
* @returns {Object}
*/
_mockMailActivityGetActivityData(res_model, domain, limit = 0, offset = 0, fetch_done = false) {
const self = this;
// 1. Retrieve all ongoing and completed activities according to the parameters
const activityTypes = this.getRecords("mail.activity.type", [
"|",
["res_model", "=", res_model],
["res_model", "=", false],
]);
// Remove domain term used to filter record having "done" activities (not understood by the getRecords mock)
domain = Domain.removeDomainLeaves(new Domain(domain ?? []).toList(), [
"activity_ids.active",
]).toList();
const allRecords = this.getRecords(res_model, domain ?? []);
const records = limit ? allRecords.slice(offset, offset + limit) : allRecords;
const activityDomain = [["res_model", "=", res_model]];
const isFiltered = domain || limit || offset;
const domainResIds = records.map((r) => r.id);
if (isFiltered) {
activityDomain.push(["res_id", "in", domainResIds]);
}
const allActivities = this.getRecords("mail.activity", activityDomain, {
active_test: !fetch_done,
});
const allOngoing = allActivities.filter((a) => a.active);
const allCompleted = allActivities.filter((a) => !a.active);
// 2. Get attachment of completed activities
let attachmentsById;
if (allCompleted.length) {
const attachmentIds = allCompleted.map((a) => a.attachment_ids).flat();
attachmentsById = attachmentIds.length
? Object.fromEntries(
this.getRecords("ir.attachment", [["id", "in", attachmentIds]]).map((a) => [a.id, a])
)
: {};
} else {
attachmentsById = {};
}
// 3. Group activities per records and activity type
const groupedCompleted = groupBy(allCompleted, (a) => [a.res_id, a.activity_type_id]);
const groupedOngoing = groupBy(allOngoing, (a) => [a.res_id, a.activity_type_id]);
// 4. Format data
const resIdToDeadline = {};
const resIdToDateDone = {};
const groupedActivities = {};
for (const resIdStrTuple of new Set([
...Object.keys(groupedCompleted),
...Object.keys(groupedOngoing),
])) {
const [resId, activityTypeId] = resIdStrTuple.split(",").map((n) => Number(n));
const ongoing = groupedOngoing[resIdStrTuple] || [];
const completed = groupedCompleted[resIdStrTuple] || [];
const dateDone = completed.length
? DateTime.max(...completed.map((a) => deserializeDate(a.date_done)))
: false;
const dateDeadline = ongoing.length
? DateTime.min(...ongoing.map((a) => deserializeDate(a.date_deadline)))
: false;
if (
dateDeadline &&
(resIdToDeadline[resId] === undefined || dateDeadline < resIdToDeadline[resId])
) {
resIdToDeadline[resId] = dateDeadline;
}
if (
dateDone &&
(resIdToDateDone[resId] === undefined || dateDone > resIdToDateDone[resId])
) {
resIdToDateDone[resId] = dateDone;
}
const userAssignedIds = unique(
sortBy(
ongoing.filter((a) => a.user_id),
(a) => a.date_deadline
).map((a) => a.user_id)
);
const reportingDate = ongoing.length ? dateDeadline : dateDone;
const attachments = completed
.map((act) => act.attachment_ids)
.flat()
.map((attachmentId) => attachmentsById[attachmentId]);
const attachmentsInfo = {};
if (attachments.length) {
const lastAttachmentCreateDate = DateTime.max(
...attachments.map((a) => deserializeDate(a.create_date))
);
const mostRecentAttachment = attachments.find((a) =>
lastAttachmentCreateDate.equals(deserializeDate(a.create_date))
);
attachmentsInfo.attachments = {
most_recent_id: mostRecentAttachment.id,
most_recent_name: mostRecentAttachment.name,
count: attachments.length,
};
}
if (!(resId in groupedActivities)) {
groupedActivities[resId] = {};
}
groupedActivities[resId][activityTypeId] = {
count_by_state: {
...Object.fromEntries(
Object.entries(
groupBy(ongoing, (a) =>
self._mockComputeStateFromDate(deserializeDate(a.date_deadline))
)
).map(([state, activities]) => [state, activities.length])
),
...(completed.length ? { done: completed.length } : {}),
},
ids: ongoing.map((a) => a.id).concat(completed.map((a) => a.id)),
reporting_date: reportingDate ? reportingDate.toFormat("yyyy-LL-dd") : false,
state: ongoing.length ? self._mockComputeStateFromDate(dateDeadline) : "done",
user_assigned_ids: userAssignedIds,
...attachmentsInfo,
};
}
const ongoingResIds = sortBy(Object.keys(resIdToDeadline), (item) => resIdToDeadline[item]);
const completedResIds = sortBy(
Object.keys(resIdToDateDone).filter((resId) => !(resId in resIdToDeadline)),
(item) => resIdToDateDone[item]
);
return {
activity_types: activityTypes.map((type) => {
let templates = [];
if (type.mail_template_ids) {
templates = type.mail_template_ids.map((template_id) => {
const template = this.getRecords("mail.template", [
["id", "=", template_id],
])[0];
return {
id: template.id,
name: template.name,
};
});
}
return {
id: type.id,
name: type.display_name,
template_ids: templates,
keep_done: type.keep_done,
};
}),
activity_res_ids: ongoingResIds.concat(completedResIds).map((idStr) => Number(idStr)),
grouped_activities: groupedActivities,
};
},
});