diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/web/static/src/js/views/kanban/kanban_model.js | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/web/static/src/js/views/kanban/kanban_model.js')
| -rw-r--r-- | addons/web/static/src/js/views/kanban/kanban_model.js | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/addons/web/static/src/js/views/kanban/kanban_model.js b/addons/web/static/src/js/views/kanban/kanban_model.js new file mode 100644 index 00000000..7dcfe408 --- /dev/null +++ b/addons/web/static/src/js/views/kanban/kanban_model.js @@ -0,0 +1,445 @@ +odoo.define('web.KanbanModel', function (require) { +"use strict"; + +/** + * The KanbanModel extends the BasicModel to add Kanban specific features like + * moving a record from a group to another, resequencing records... + */ + +var BasicModel = require('web.BasicModel'); +var viewUtils = require('web.viewUtils'); + +var KanbanModel = BasicModel.extend({ + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * Adds a record to a group in the localData, and fetch the record. + * + * @param {string} groupID localID of the group + * @param {integer} resId id of the record + * @returns {Promise<string>} resolves to the local id of the new record + */ + addRecordToGroup: function (groupID, resId) { + var self = this; + var group = this.localData[groupID]; + var new_record = this._makeDataPoint({ + res_id: resId, + modelName: group.model, + fields: group.fields, + fieldsInfo: group.fieldsInfo, + viewType: group.viewType, + parentID: groupID, + }); + + var def = this._fetchRecord(new_record).then(function (result) { + group.data.unshift(new_record.id); + group.res_ids.unshift(resId); + group.count++; + + // update the res_ids and count of the parent + self.localData[group.parentID].count++; + self._updateParentResIDs(group); + + return result.id; + }); + return this._reloadProgressBarGroupFromRecord(new_record.id, def); + }, + /** + * Creates a new group from a name (performs a name_create). + * + * @param {string} name + * @param {string} parentID localID of the parent of the group + * @returns {Promise<string>} resolves to the local id of the new group + */ + createGroup: function (name, parentID) { + var self = this; + var parent = this.localData[parentID]; + var groupBy = parent.groupedBy[0]; + var groupByField = parent.fields[groupBy]; + if (!groupByField || groupByField.type !== 'many2one') { + return Promise.reject(); // only supported when grouped on m2o + } + return this._rpc({ + model: groupByField.relation, + method: 'name_create', + args: [name], + context: parent.context, // todo: combine with view context + }) + .then(function (result) { + const createGroupDataPoint = (model, parent) => { + const newGroup = model._makeDataPoint({ + modelName: parent.model, + context: parent.context, + domain: parent.domain.concat([[groupBy, "=", result[0]]]), + fields: parent.fields, + fieldsInfo: parent.fieldsInfo, + isOpen: true, + limit: parent.limit, + parentID: parent.id, + openGroupByDefault: true, + orderedBy: parent.orderedBy, + value: result, + viewType: parent.viewType, + }); + if (parent.progressBar) { + newGroup.progressBarValues = _.extend({ + counts: {}, + }, parent.progressBar); + } + return newGroup; + }; + const newGroup = createGroupDataPoint(self, parent); + parent.data.push(newGroup.id); + if (self.isInSampleMode()) { + // in sample mode, create the new group in both models (main + sample) + const sampleParent = self.sampleModel.localData[parentID]; + const newSampleGroup = createGroupDataPoint(self.sampleModel, sampleParent); + sampleParent.data.push(newSampleGroup.id); + } + return newGroup.id; + }); + }, + /** + * Creates a new record from the given value, and add it to the given group. + * + * @param {string} groupID + * @param {Object} values + * @returns {Promise} resolved with the local id of the created record + */ + createRecordInGroup: function (groupID, values) { + var self = this; + var group = this.localData[groupID]; + var context = this._getContext(group); + var parent = this.localData[group.parentID]; + var groupedBy = parent.groupedBy; + context['default_' + groupedBy] = viewUtils.getGroupValue(group, groupedBy); + var def; + if (Object.keys(values).length === 1 && 'display_name' in values) { + // only 'display_name is given, perform a 'name_create' + def = this._rpc({ + model: parent.model, + method: 'name_create', + args: [values.display_name], + context: context, + }).then(function (records) { + return records[0]; + }); + } else { + // other fields are specified, perform a classical 'create' + def = this._rpc({ + model: parent.model, + method: 'create', + args: [values], + context: context, + }); + } + return def.then(function (resID) { + return self.addRecordToGroup(group.id, resID); + }); + }, + /** + * Add the following (kanban specific) keys when performing a `get`: + * + * - tooltipData + * - progressBarValues + * - isGroupedByM2ONoColumn + * + * @override + * @see _readTooltipFields + * @returns {Object} + */ + __get: function () { + var result = this._super.apply(this, arguments); + var dp = result && this.localData[result.id]; + if (dp) { + if (dp.tooltipData) { + result.tooltipData = $.extend(true, {}, dp.tooltipData); + } + if (dp.progressBarValues) { + result.progressBarValues = $.extend(true, {}, dp.progressBarValues); + } + if (dp.fields[dp.groupedBy[0]]) { + var groupedByM2O = dp.fields[dp.groupedBy[0]].type === 'many2one'; + result.isGroupedByM2ONoColumn = !dp.data.length && groupedByM2O; + } else { + result.isGroupedByM2ONoColumn = false; + } + } + return result; + }, + /** + * Same as @see get but getting the parent element whose ID is given. + * + * @param {string} id + * @returns {Object} + */ + getColumn: function (id) { + var element = this.localData[id]; + if (element) { + return this.get(element.parentID); + } + return null; + }, + /** + * @override + */ + __load: function (params) { + this.defaultGroupedBy = params.groupBy || []; + params.groupedBy = (params.groupedBy && params.groupedBy.length) ? params.groupedBy : this.defaultGroupedBy; + return this._super(params); + }, + /** + * Load more records in a group. + * + * @param {string} groupID localID of the group + * @returns {Promise<string>} resolves to the localID of the group + */ + loadMore: function (groupID) { + var group = this.localData[groupID]; + var offset = group.loadMoreOffset + group.limit; + return this.reload(group.id, { + loadMoreOffset: offset, + }); + }, + /** + * Moves a record from a group to another. + * + * @param {string} recordID localID of the record + * @param {string} groupID localID of the new group of the record + * @param {string} parentID localID of the parent + * @returns {Promise<string[]>} resolves to a pair [oldGroupID, newGroupID] + */ + moveRecord: function (recordID, groupID, parentID) { + var self = this; + var parent = this.localData[parentID]; + var new_group = this.localData[groupID]; + var changes = {}; + var groupedFieldName = parent.groupedBy[0]; + var groupedField = parent.fields[groupedFieldName]; + if (groupedField.type === 'many2one') { + changes[groupedFieldName] = { + id: new_group.res_id, + display_name: new_group.value, + }; + } else if (groupedField.type === 'selection') { + var value = _.findWhere(groupedField.selection, {1: new_group.value}); + changes[groupedFieldName] = value && value[0] || false; + } else { + changes[groupedFieldName] = new_group.value; + } + + // Manually updates groups data. Note: this is done before the actual + // save as it might need to perform a read group in some cases so those + // updated data might be overridden again. + var record = self.localData[recordID]; + var resID = record.res_id; + // Remove record from its current group + var old_group; + for (var i = 0; i < parent.data.length; i++) { + old_group = self.localData[parent.data[i]]; + var index = _.indexOf(old_group.data, recordID); + if (index >= 0) { + old_group.data.splice(index, 1); + old_group.count--; + old_group.res_ids = _.without(old_group.res_ids, resID); + self._updateParentResIDs(old_group); + break; + } + } + // Add record to its new group + new_group.data.push(recordID); + new_group.res_ids.push(resID); + new_group.count++; + + return this.notifyChanges(recordID, changes).then(function () { + return self.save(recordID); + }).then(function () { + record.parentID = new_group.id; + return [old_group.id, new_group.id]; + }); + }, + /** + * @override + */ + reload: function (id, options) { + // if the groupBy is given in the options and if it is an empty array, + // fallback on the default groupBy + if (options && options.groupBy && !options.groupBy.length) { + options.groupBy = this.defaultGroupedBy; + } + return this._super(id, options); + }, + /** + * @override + */ + __reload: function (id, options) { + var def = this._super(id, options); + if (options && options.loadMoreOffset) { + return def; + } + return this._reloadProgressBarGroupFromRecord(id, def); + }, + /** + * @override + */ + save: function (recordID) { + var def = this._super.apply(this, arguments); + return this._reloadProgressBarGroupFromRecord(recordID, def); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override + */ + _makeDataPoint: function (params) { + var dataPoint = this._super.apply(this, arguments); + if (params.progressBar) { + dataPoint.progressBar = params.progressBar; + } + return dataPoint; + }, + /** + * @override + */ + _load: function (dataPoint, options) { + if (dataPoint.groupedBy.length && dataPoint.progressBar) { + return this._readProgressBarGroup(dataPoint, options); + } + return this._super.apply(this, arguments); + }, + /** + * Ensures that there is no nested groups in Kanban (only the first grouping + * level is taken into account). + * + * @override + * @private + * @param {Object} list valid resource object + */ + _readGroup: function (list) { + var self = this; + if (list.groupedBy.length > 1) { + list.groupedBy = [list.groupedBy[0]]; + } + return this._super.apply(this, arguments).then(function (result) { + return self._readTooltipFields(list).then(_.constant(result)); + }); + }, + /** + * @private + * @param {Object} dataPoint + * @returns {Promise<Object>} + */ + _readProgressBarGroup: function (list, options) { + var self = this; + var groupsDef = this._readGroup(list, options); + var progressBarDef = this._rpc({ + model: list.model, + method: 'read_progress_bar', + kwargs: { + domain: list.domain, + group_by: list.groupedBy[0], + progress_bar: list.progressBar, + context: list.context, + }, + }); + return Promise.all([groupsDef, progressBarDef]).then(function (results) { + var data = results[1]; + _.each(list.data, function (groupID) { + var group = self.localData[groupID]; + group.progressBarValues = _.extend({ + counts: data[group.value] || {}, + }, list.progressBar); + }); + return list; + }); + }, + /** + * Fetches tooltip specific fields on the group by relation and stores it in + * the column datapoint in a special key `tooltipData`. + * Data for the tooltips (group_by_tooltip) are fetched in batch for all + * groups, to avoid doing multiple calls. + * Data are stored in a special key `tooltipData` on the datapoint. + * Note that the option `group_by_tooltip` is only for m2o fields. + * + * @private + * @param {Object} list a list of groups + * @returns {Promise} + */ + _readTooltipFields: function (list) { + var self = this; + var groupedByField = list.fields[list.groupedBy[0].split(':')[0]]; + if (groupedByField.type !== 'many2one') { + return Promise.resolve(); + } + var groupIds = _.reduce(list.data, function (groupIds, id) { + var res_id = self.get(id, {raw: true}).res_id; + // The field on which we are grouping might not be set on all records + if (res_id) { + groupIds.push(res_id); + } + return groupIds; + }, []); + var tooltipFields = []; + var groupedByFieldInfo = list.fieldsInfo.kanban[list.groupedBy[0]]; + if (groupedByFieldInfo && groupedByFieldInfo.options) { + tooltipFields = Object.keys(groupedByFieldInfo.options.group_by_tooltip || {}); + } + if (groupIds.length && tooltipFields.length) { + var fieldNames = _.union(['display_name'], tooltipFields); + return this._rpc({ + model: groupedByField.relation, + method: 'read', + args: [groupIds, fieldNames], + context: list.context, + }).then(function (result) { + _.each(list.data, function (id) { + var dp = self.localData[id]; + dp.tooltipData = _.findWhere(result, {id: dp.res_id}); + }); + }); + } + return Promise.resolve(); + }, + /** + * Reloads all progressbar data. This is done after given promise and + * insures that the given promise's result is not lost. + * + * @private + * @param {string} recordID + * @param {Promise} def + * @returns {Promise} + */ + _reloadProgressBarGroupFromRecord: function (recordID, def) { + var element = this.localData[recordID]; + if (element.type === 'list' && !element.parentID) { + // we are reloading the whole view, so there is no need to manually + // reload the progressbars + return def; + } + + // If we updated a record, then we must potentially update columns' + // progressbars, so we need to load groups info again + var self = this; + while (element) { + if (element.progressBar) { + return def.then(function (data) { + return self._load(element, { + keepEmptyGroups: true, + onlyGroups: true, + }).then(function () { + return data; + }); + }); + } + element = this.localData[element.parentID]; + } + return def; + }, +}); +return KanbanModel; +}); |
