[ADD] Web Gantt Chart

This commit is contained in:
Rohit Prajapati
2021-01-08 13:25:26 +00:00
committed by Parthiv Patel
parent 0088cf48d6
commit 7d98387f0d
17 changed files with 1029 additions and 0 deletions

View File

@@ -0,0 +1 @@
from . import models

View File

@@ -0,0 +1,18 @@
{
'name': 'Project Gantt Chart Demo',
'category': 'Project',
'version': '2.0',
'summary': 'Project Gantt Chart Demo',
'depends': [
'web_gantt',
'project'
],
'data': [
'views/project_task_inherit.xml',
],
'demo': ['data/project_task_demo.xml'],
'qweb': [],
'installable': True,
'auto_install': False,
'application': False,
}

View File

@@ -0,0 +1,119 @@
<flectra>
<data noupdate="1">
<record id="project.project_task_1" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=1))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=1))"/>
<field name="progress">9</field>
</record>
<record id="project.project_task_2" model="project.task">
<field name="start_date" eval="(DateTime.today())"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=1))"/>
<field name="progress">7</field>
</record>
<record id="project.project_task_3" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=2))"/>
<field name="end_date" eval="(DateTime.today())"/>
<field name="progress">5</field>
</record>
<record id="project.project_task_4" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=2))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=2))"/>
<field name="progress">5</field>
</record>
<record id="project.project_task_5" model="project.task">
<field name="start_date" eval="(DateTime.today())"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=2))"/>
<field name="progress">4</field>
</record>
<record id="project.project_task_6" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=1))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=3))"/>
<field name="progress">9</field>
</record>
<record id="project.project_task_7" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=2))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=1))"/>
<field name="progress">8</field>
</record>
<record id="project.project_task_8" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=2))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=3))"/>
<field name="progress">10</field>
</record>
<record id="project.project_task_9" model="project.task">
<field name="start_date" eval="(DateTime.today())"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=1))"/>
<field name="progress">0</field>
</record>
<record id="project.project_task_10" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=1))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=1))"/>
<field name="progress">7</field>
</record>
<record id="project.project_task_11" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=1))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=3))"/>
<field name="progress">1</field>
</record>
<record id="project.project_task_12" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=1))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=1))"/>
<field name="progress">2</field>
</record>
<record id="project.project_task_19" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=1))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=2))"/>
<field name="progress">3</field>
</record>
<record id="project.project_task_20" model="project.task">
<field name="start_date" eval="(DateTime.today())"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=3))"/>
<field name="progress">3</field>
</record>
<record id="project.project_task_21" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=2))"/>
<field name="end_date" eval="(DateTime.today())"/>
<field name="progress">4</field>
</record>
<record id="project.project_task_22" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=2))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=1))"/>
<field name="progress">7</field>
</record>
<record id="project.project_task_24" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=1))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=1))"/>
<field name="progress">4</field>
</record>
<record id="project.project_task_25" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=1))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=2))"/>
<field name="progress">2</field>
</record>
<record id="project.project_task_26" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=1))"/>
<field name="end_date" eval="(DateTime.today())"/>
<field name="progress">10</field>
</record>
</data>
</flectra>

View File

@@ -0,0 +1 @@
from . import project_task

View File

@@ -0,0 +1,9 @@
from flectra import fields, models
class ProjectTaskInherit(models.Model):
_inherit = 'project.task'
start_date = fields.Date(string='Start Date')
end_date = fields.Date(string='End Date')
progress = fields.Integer(string='Progress')

View File

@@ -0,0 +1,66 @@
<flectra>
<record id="project_task_view_gantt_inherit" model="ir.ui.view">
<field name="name">project.task.form.view</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_form2"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='partner_id']" position="after">
<field name="start_date"/>
<field name="end_date"/>
<field name="progress"/>
</xpath>
</field>
</record>
<record id="view_project_task_gantt" model="ir.ui.view">
<field name="name">project.task.gantt</field>
<field name="model">project.task</field>
<field name="arch" type="xml">
<gantt string="Project Tasks" mode="day">
<field name="progress" type="progress" scale="10"/>
<field name="start_date" type="start"/>
<field name="end_date" type="end"/>
<!-- <field name="child_task_ids" type="child" start="sd" end="ed"/>-->
</gantt>
</field>
</record>
<record id="action_view_all_task_tree_action_inherit" model="ir.actions.act_window.view">
<field eval="1" name="sequence"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="project.view_task_tree2"/>
<field name="act_window_id" ref="project.action_view_all_task"/>
</record>
<record id="action_view_all_task_kanban_action_inherit" model="ir.actions.act_window.view">
<field eval="2" name="sequence"/>
<field name="view_mode">kanban</field>
<field name="view_id" ref="project.view_task_kanban"/>
<field name="act_window_id" ref="project.action_view_all_task"/>
</record>
<record id="action_view_all_task_form_action_inherit" model="ir.actions.act_window.view">
<field eval="3" name="sequence"/>
<field name="view_mode">form</field>
<field name="view_id" ref="project.view_task_form2"/>
<field name="act_window_id" ref="project.action_view_all_task"/>
</record>
<record id="action_view_all_task_calendar_action_inherit" model="ir.actions.act_window.view">
<field eval="4" name="sequence"/>
<field name="view_mode">calendar</field>
<field name="view_id" ref="project.view_task_calendar"/>
<field name="act_window_id" ref="project.action_view_all_task"/>
</record>
<record id="action_view_all_task_gantt_chart_inherit" model="ir.actions.act_window.view">
<field eval="5" name="sequence"/>
<field name="view_mode">gantt</field>
<field name="view_id" ref="project_gantt.view_project_task_gantt"/>
<field name="act_window_id" ref="project.action_view_all_task"/>
</record>
</flectra>

View File

View File

@@ -0,0 +1,18 @@
{
'name': 'Web Gantt Chart',
'category': 'Project',
'version': '2.0',
'summary': 'Gantt Chart',
'depends': [
'web'
],
'data': [
'views/assets.xml',
],
'qweb': [
'static/src/xml/*.xml'
],
'installable': True,
'auto_install': False,
'application': False,
}

View File

@@ -0,0 +1,12 @@
.o_gantt_renderer{
height: 100%;
}
.gantt_grid_head_add, .gantt_add{
display: none;
}
.gantt_task_progress_drag{
display: none !important;
}
.gantt_scale_line > .gantt_scale_cell, .gantt_grid_scale > .gantt_grid_head_cell{
font-weight: bold !important;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,147 @@
flectra.define('web_gantt.GanttController', function (require) {
"use strict";
const AbstractController = require('web.AbstractController');
const { ComponentWrapper } = require('web.OwlCompatibility');
const DropdownMenu = require('web.DropdownMenu');
const { DEFAULT_INTERVAL, INTERVAL_OPTIONS } = require('web.searchUtils');
const { qweb } = require('web.core');
var view_dialogs = require('web.view_dialogs');
class CarretDropdownMenu extends DropdownMenu {
/**
* @override
*/
get displayCaret() {
return true;
}
}
var GanttChartController = AbstractController.extend({
events: _.extend({}, AbstractController.prototype.events, {
'click .gantt_bar_task': '_onTaskClicked',
}),
custom_events: _.extend({}, AbstractController.prototype.custom_events, {
item_selected: '_onItemSelected',
open_view: '_onOpenView',
}),
/**
* @override
* @param {Widget} parent
* @param {GraphModel} model
* @param {GraphRenderer} renderer
* @param {Object} params
* @param {string[]} params.measures
* @param {boolean} params.isEmbedded
* @param {string[]} params.groupableFields,
*/
init: function (parent, model, renderer, params) {
this._super.apply(this, arguments);
this.measures = params.measures;
this.child = params.child;
this.childExist = params.childExist;
// this parameter condition the appearance of a 'Group By'
// button in the control panel owned by the graph view.
this.isEmbedded = params.isEmbedded;
this.withButtons = params.withButtons;
// views to use in the action triggered when the graph is clicked
this.views = params.views;
this.title = params.title;
// this parameter determines what is the list of fields
// that may be used within the groupby menu available when
// the view is embedded
this.groupableFields = params.groupableFields;
this.buttonDropdownPromises = [];
},
/**
* @override
*/
start: function () {
this.$el.addClass('o_gantt_controller');
return this._super.apply(this, arguments);
},
getOwnedQueryParams: function () {
var state = this.model.get();
return {
context: {
gantt_mode: state.mode,
}
};
},
destroy: function () {
this._super.apply(this, arguments);
},
_onTaskClicked:function(ev){
var $target = $(ev.currentTarget);
if($target.attr('task_id').startsWith('child_')){
let id = $target.attr('task_id').substring("child_".length);
return new view_dialogs.FormViewDialog(this, {
title: $target.find('.gantt_task_content').text(),
res_model: this.child.model,
view_id: false,
res_id: parseInt(id),
on_saved: this.reload.bind(this, {}),
}).open();
}else{
return new view_dialogs.FormViewDialog(this, {
title: $target.find('.gantt_task_content').text(),
res_model: this.modelName,
view_id: false,
res_id: parseInt($target.attr('task_id')),
on_saved: this.reload.bind(this, {}),
}).open();
}
},
_onButtonClick:function(ev){
var $target = $(ev.target);
if($target.hasClass('o_gantt_button')){
this.update({ mode: $target.data('mode') });
}else{
return new view_dialogs.FormViewDialog(this, {
res_model: this.modelName,
view_id: false,
res_id: false,
on_saved: this.reload.bind(this, {}),
}).open();
}
},
updateButtons: function () {
if (!this.$buttons) {
return;
}
var state = this.model.get();
this.$buttons.find('.o_gantt_button').removeClass('active');
this.$buttons
.find('.o_gantt_button[data-mode="' + state.mode + '"]')
.addClass('active');
},
formatDate: function(date){
var d = new Date(date),
month = '' + (d.getMonth() + 1),
day = '' + d.getDate(),
year = d.getFullYear();
if (month.length < 2)
month = '0' + month;
if (day.length < 2)
day = '0' + day;
return [day, month, year].join('-');
},
reload: async function () {
const promises = [this._super(...arguments)];
return Promise.all(promises);
},
renderButtons: function ($node) {
this.$buttons = $(qweb.render('GanttView.buttons'));
this.$buttons.click(ev => this._onButtonClick(ev));
},
});
return GanttChartController;
});

View File

@@ -0,0 +1,232 @@
flectra.define('web_gantt.GanttModel', function (require) {
"use strict";
var core = require('web.core');
const { DEFAULT_INTERVAL, rankInterval } = require('web.searchUtils');
var _t = core._t;
var AbstractModel = require('web.AbstractModel');
return AbstractModel.extend({
/**
* @override
* @param {Widget} parent
*/
init: function () {
this._super.apply(this, arguments);
this.chart = null;
this.GanttData = [];
},
__get: function () {
return Object.assign({ isSample: this.isSampleModel }, this.chart);
},
__load: function (params) {
var groupBys = params.context.graph_groupbys || params.groupBys;
this.initialGroupBys = groupBys;
this.fields = params.fields;
this.modelName = params.modelName;
this.chart = Object.assign({
context: params.context,
dataPoints: [],
domain: params.domain,
groupBy: params.groupedBy.length ? params.groupedBy : groupBys,
measure: params.context.graph_measure || params.measure,
mode: params.context.graph_mode || params.mode,
origins: [],
stacked: params.stacked,
timeRanges: params.timeRanges,
orderBy: params.orderBy,
startDate: params.startDate,
endDate: params.endDate,
child: params.child,
childExist: params.childExist,
progress: params.progress,
titleField: params.titleField,
});
this._computeDerivedParams();
return this._loadGraph();
},
__reload: function (handle, params) {
if ('context' in params) {
this.chart.context = params.context;
this.chart.groupBy = params.context.graph_groupbys || this.chart.groupBy;
this.chart.measure = params.context.graph_measure || this.chart.measure;
this.chart.mode = params.context.graph_mode || this.chart.mode;
}
if ('domain' in params) {
this.chart.domain = params.domain;
}
if ('groupBy' in params) {
this.chart.groupBy = params.groupBy.length ? params.groupBy : this.initialGroupBys;
}
if ('measure' in params) {
this.chart.measure = params.measure;
}
if ('timeRanges' in params) {
this.chart.timeRanges = params.timeRanges;
}
this._computeDerivedParams();
if ('mode' in params) {
this.chart.mode = params.mode;
return Promise.resolve();
}
if ('stacked' in params) {
this.chart.stacked = params.stacked;
return Promise.resolve();
}
if ('orderBy' in params) {
this.chart.orderBy = params.orderBy;
return Promise.resolve();
}
return this._loadGraph();
},
_computeDerivedParams: function () {
this.chart.processedGroupBy = this._processGroupBy(this.chart.groupBy);
const { range, rangeDescription, comparisonRange, comparisonRangeDescription, fieldName } = this.chart.timeRanges;
if (range) {
this.chart.domains = [
this.chart.domain.concat(range),
this.chart.domain.concat(comparisonRange),
];
this.chart.origins = [rangeDescription, comparisonRangeDescription];
const groupBys = this.chart.processedGroupBy.map(function (gb) {
return gb.split(":")[0];
});
this.chart.comparisonFieldIndex = groupBys.indexOf(fieldName);
} else {
this.chart.domains = [this.chart.domain];
this.chart.origins = [""];
this.chart.comparisonFieldIndex = -1;
}
},
_loadGraph: function () {
var self = this;
this.chart.dataPoints = [];
this.chart.GanttData = [];
var groupBy = this.chart.processedGroupBy;
var fields = _.map(groupBy, function (groupBy) {
return groupBy.split(':')[0];
});
if (this.chart.measure !== '__count__') {
if (this.fields[this.chart.measure].type === 'many2one') {
fields = fields.concat(this.chart.measure + ":count_distinct");
}
else {
}
}
fields = fields.concat(this.chart.startDate);
fields = fields.concat(this.chart.endDate);
fields = fields.concat(this.chart.titleField);
fields = fields.concat(this.chart.progress);
if(this.chart.childExist){
fields = fields.concat(this.chart.child.name);
}
var context = _.extend({fill_temporal: true}, this.chart.context);
var proms = [];
proms.push(this._rpc({
route: '/web/dataset/search_read',
model: this.modelName,
fields: fields,
context: context,
domain: this.chart.domain,
}).then(self._processGanttData.bind(self)));
return Promise.all(proms);
},
_processGanttData: async function(rawData) {
var self = this;
var childFields = ['display_name'];
var proms = [];
if(this.chart.childExist){
childFields = childFields.concat(this.chart.child.startDate);
childFields = childFields.concat(this.chart.child.endDate);
}
rawData.records.forEach(function (dataPt){
if(dataPt[self.chart.startDate] != false && dataPt[self.chart.endDate] != false){
var start_date = self.formatDate(dataPt[self.chart.startDate]);
var end_date = self.formatDate(dataPt[self.chart.endDate]);
var type = '';
if(self.chart.childExist){
type = dataPt[self.chart.child.name].length != 0 ? "project" : ''
}
self.chart.GanttData.push({
resId: dataPt.id,
startDate: start_date,
type: type,
endDate: end_date,
titleField: dataPt[self.chart.titleField],
progress: dataPt[self.chart.progress],
});
}
if(self.chart.childExist && dataPt[self.chart.child.name].length != 0){
proms.push(self._rpc({
route: '/web/dataset/search_read',
model: self.chart.child.model,
fields: childFields,
domain: [['id', 'in', dataPt[self.chart.child.name]]],
}).then(self._processGanttChildData.bind(self,dataPt)));
}
});
return Promise.all(proms);
},
_processGanttChildData: async function(data,rawData){
var self = this;
rawData.records.forEach(function (dataPt){
var start_date = self.formatDate(dataPt[self.chart.child.startDate]);
var end_date = self.formatDate(dataPt[self.chart.child.endDate]);
self.chart.GanttData.push({
resId: 'child_' + dataPt.id,
startDate: start_date,
endDate: end_date,
titleField: dataPt['display_name'],
parent: data.id,
});
});
},
formatDate: function(date){
var d = new Date(date),
month = '' + (d.getMonth() + 1),
day = '' + d.getDate(),
year = d.getFullYear();
if (month.length < 2)
month = '0' + month;
if (day.length < 2)
day = '0' + day;
return [day, month, year].join('-');
},
_processGroupBy: function(groupBy) {
const groupBysMap = new Map();
for (const gb of groupBy) {
let [fieldName, interval] = gb.split(':');
const field = this.fields[fieldName];
if (['date', 'datetime'].includes(field.type)) {
interval = interval || DEFAULT_INTERVAL;
}
if (groupBysMap.has(fieldName)) {
const registeredInterval = groupBysMap.get(fieldName);
if (rankInterval(registeredInterval) < rankInterval(interval)) {
groupBysMap.set(fieldName, interval);
}
} else {
groupBysMap.set(fieldName, interval);
}
}
return [...groupBysMap].map(([fieldName, interval]) => {
if (interval) {
return `${fieldName}:${interval}`;
}
return fieldName;
});
},
});
});

View File

@@ -0,0 +1,167 @@
flectra.define('web_gantt.GanttRenderer', function (require) {
"use strict";
var AbstractRenderer = require('web.AbstractRenderer');
var config = require('web.config');
var core = require('web.core');
var dataComparisonUtils = require('web.dataComparisonUtils');
var fieldUtils = require('web.field_utils');
var _t = core._t;
var DateClasses = dataComparisonUtils.DateClasses;
var qweb = core.qweb;
return AbstractRenderer.extend({
className: "o_gantt_renderer",
id: "o_gantt_renderer",
sampleDataTargets: ['.o_graph_canvas_container'],
/**
* @override
* @param {Widget} parent
* @param {Object} state
* @param {Object} params
* @param {boolean} [params.isEmbedded]
* @param {Object} [params.fields]
* @param {string} [params.title]
*/
init: function (parent, state, params) {
this._super.apply(this, arguments);
this.isEmbedded = params.isEmbedded || false;
this.title = params.title || '';
this.fields = params.fields || {};
this.modelName = params.modelName;
this.startDate = params.startDate;
this.endDate = params.endDate;
this.titleField = params.titleField;
this.progress_scale = params.progress_scale;
this.child = params.child;
this.childExist = params.childExist;
this.disableLinking = params.disableLinking;
this.chart = null;
this.ganttId = _.uniqueId('gantt');
this.$legendTooltip = null;
this.$tooltip = null;
this.rtlEnable = _t.database.parameters.direction == "rtl" ? true : false;
},
on_attach_callback: function () {
this._super.apply(this, arguments);
this.isInDOM = true;
this._render();
},
/**
* @override
*/
on_detach_callback: function () {
this._super.apply(this, arguments);
this.isInDOM = false;
},
_prepareGanttData: function(){
var self = this;
this.GanttChartData = {"data": [],"links": []};
this.state.GanttData.forEach(function (data){
var progress_bar = data.progress;
if(self.progress_scale){
progress_bar = (data.progress) / self.progress_scale;
}
self.GanttChartData.data.push({
id: data.resId,
start_date: data.startDate,
end_date: data.endDate,
text: data.titleField,
type: data.type || '',
progress: progress_bar ? progress_bar : 0,
parent: data.parent ? data.parent : false,
});
});
},
async _renderView() {
var self = this;
this._prepareGanttData();
if (this.chart) {
this.chart.clearAll();
this.chart.parse(this.GanttChartData);
this._changeState();
}else{
if (this.isInDOM) {
this._renderTitle();
gantt.plugins({
tooltip: true
});
gantt.config.show_links = false;
if(this.rtlEnable){
gantt.config.rtl = true;
}
gantt.config.date_grid = _t.database.parameters.date_format;
gantt.config.auto_scheduling = true;
gantt.config.auto_scheduling_strict = true;
gantt.i18n.setLocale(moment.locale());
gantt.init('o_gantt_renderer');
gantt.parse(this.GanttChartData);
this.chart = gantt;
this.chart.attachEvent("onBeforeLightbox", function(){
return false;
});
this.chart.attachEvent("onAfterTaskDrag", function(id, mode, e){
var currentTask = this.getTask(id);
self._updateData(currentTask);
});
this._changeState();
}
}
},
_renderTitle: function () {
if (this.title) {
this.$el.prepend($('<label/>', {
text: this.title,
}));
}
},
_changeState: function(){
if(this.state.mode == 'month'){
this.chart.config.scale_unit = "month";
this.chart.config.date_scale = "%F, %Y";
}else if(this.state.mode == 'week'){
this.chart.config.scale_unit = "week";
this.chart.config.date_scale = "Week #%W";
}else{
this.chart.config.scale_unit = "day";
this.chart.config.date_scale = "%d %M";
}
this.chart.render();
},
_updateData: function(data){
var updatedData = {};
if(data.id.toString().startsWith('child_')){
updatedData[this.child.startDate] = this.formatDate(data.start_date);
updatedData[this.child.endDate] = this.formatDate(data.end_date);
return this._rpc({
model: this.child.model,
method: 'write',
args: [parseInt(data.id.substring("child_".length)), updatedData],
});
}else{
updatedData[this.startDate] = this.formatDate(data.start_date);
updatedData[this.endDate] = this.formatDate(data.end_date);
return this._rpc({
model: this.modelName,
method: 'write',
args: [data.id, updatedData],
});
}
},
formatDate: function(date){
var d = new Date(date),
month = '' + (d.getMonth() + 1),
day = '' + d.getDate(),
year = d.getFullYear();
if (month.length < 2)
month = '0' + month;
if (day.length < 2)
day = '0' + day;
return [year, month, day].join('-');
},
});
});

View File

@@ -0,0 +1,171 @@
flectra.define('web_gantt.GanttView', function (require) {
"use strict";
var AbstractView = require('web.AbstractView');
var core = require('web.core');
var GraphModel = require('web.GraphModel');
var Controller = require('web_gantt.GanttController');
var GanttRenderer = require('web_gantt.GanttRenderer');
var GanttModel = require('web_gantt.GanttModel');
var view_registry = require('web.view_registry');
var _t = core._t;
var _lt = core._lt;
var searchUtils = require('web.searchUtils');
var GanttView = AbstractView.extend({
display_name: _lt('Gantt'),
icon: 'fa-tasks',
config: _.extend({}, AbstractView.prototype.config, {
Model: GanttModel,
Controller: Controller,
Renderer: GanttRenderer,
}),
viewType: 'gantt',
searchMenuTypes: ['filter', 'groupBy', 'comparison', 'favorite'],
/**
* @override
*/
init: function (viewInfo, params) {
this._super.apply(this, arguments);
const additionalMeasures = params.additionalMeasures || [];
let title;
let startDate;
let endDate;
let progress;
let measure;
let progress_scale;
let childExist = false;
let child = {};
const measures = {};
const measureStrings = {};
let groupBys = [];
const groupableFields = {};
this.fields.__count__ = { string: _t("Count"), type: 'integer' };
this.arch.children.forEach(field => {
let fieldName = field.attrs.name;
if (fieldName === "id") {
return;
}
const interval = field.attrs.interval;
if (interval) {
fieldName = fieldName + ':' + interval;
}
if (field.attrs.type === 'start') {
startDate = fieldName;
}
if(field.attrs.type === 'end'){
endDate = fieldName;
}
if(field.attrs.type === 'title'){
title = fieldName;
}
if(field.attrs.type === 'progress'){
progress = fieldName;
if(field.attrs.scale){
progress_scale = parseInt(field.attrs.scale);
}
}
if(field.attrs.type === 'child'){
if(this.fields[fieldName].type == 'many2many' || this.fields[fieldName].type == 'one2many'){
if(field.attrs.start && field.attrs.end){
child['name'] = fieldName;
child['startDate'] = field.attrs.start;
child['endDate'] = field.attrs.end;
child['model'] = this.fields[fieldName].relation;
childExist = true;
}
}
}
if (field.attrs.string) {
measureStrings[fieldName] = field.attrs.string;
}
});
for (const name in measureStrings) {
if (measures[name]) {
measures[name].description = measureStrings[name];
}
}
// Remove invisible fields from the measures
this.arch.children.forEach(field => {
let fieldName = field.attrs.name;
if (field.attrs.invisible && py.eval(field.attrs.invisible)) {
groupBys = groupBys.filter(groupBy => groupBy !== fieldName);
if (fieldName in groupableFields) {
delete groupableFields[fieldName];
}
if (!additionalMeasures.includes(fieldName)) {
delete measures[fieldName];
}
}
});
const sortedMeasures = Object.values(measures).sort((a, b) => {
const descA = a.description.toLowerCase();
const descB = b.description.toLowerCase();
return descA > descB ? 1 : descA < descB ? -1 : 0;
});
const countMeasure = {
description: _t("Count"),
fieldName: '__count__',
groupNumber: 1,
isActive: false,
itemType: 'measure',
};
this.controllerParams.withButtons = params.withButtons !== false;
this.controllerParams.measures = [...sortedMeasures, countMeasure];
this.controllerParams.groupableFields = groupableFields;
this.controllerParams.child = child;
this.controllerParams.childExist = childExist;
this.controllerParams.title = params.title || this.arch.attrs.string || _t("Untitled");
// retrieve form and list view ids from the action to open those views
// when the graph is clicked
function _findView(views, viewType) {
const view = views.find(view => {
return view.type === viewType;
});
return [view ? view.viewID : false, viewType];
}
this.controllerParams.views = [
_findView(params.actionViews, 'list'),
_findView(params.actionViews, 'form'),
];
this.rendererParams.fields = this.fields;
this.rendererParams.modelName = params.modelName;
this.rendererParams.startDate = startDate;
this.rendererParams.endDate = endDate;
this.rendererParams.progress = progress;
this.rendererParams.child = child;
this.rendererParams.childExist = childExist;
this.rendererParams.progress_scale = progress_scale;
this.rendererParams.titleField = 'display_name';
this.rendererParams.title = this.arch.attrs.title; // TODO: use attrs.string instead
this.rendererParams.disableLinking = !!JSON.parse(this.arch.attrs.disable_linking || '0');
this.loadParams.startDate = startDate;
this.loadParams.endDate = endDate;
this.loadParams.progress = progress;
this.loadParams.child = child;
this.loadParams.childExist = childExist;
this.loadParams.titleField = 'display_name';
this.loadParams.mode = this.arch.attrs.type || 'day';
this.loadParams.orderBy = this.arch.attrs.order;
this.loadParams.measure = measure || '__count__';
this.loadParams.groupBys = groupBys;
this.loadParams.fields = this.fields;
this.loadParams.comparisonDomain = params.comparisonDomain;
this.loadParams.stacked = this.arch.attrs.stacked !== "False";
},
});
view_registry.add('gantt', GanttView);
return GanttView;
});

View File

@@ -0,0 +1,34 @@
<templates>
<t t-name="gantt_chart_popover_template">
<div class="oe_tooltip_string" role="tooltip">
<t t-esc="title"/>
</div>
<ul class="oe_tooltip_technical" role="tooltip">
<li>
<span class="oe_tooltip_technical_title">Start Date:</span>
<t t-esc="startDate"/>
</li>
<li data-item="object">
<span class="oe_tooltip_technical_title">End Date:</span>
<t t-esc="endDate"/>
</li>
</ul>
</t>
<t t-name="GanttView.buttons">
<button class="btn btn-primary">Add</button>
<div class="btn-group" role="toolbar" aria-label="Change Mode">
<button class="btn btn-secondary o_gantt_button" title="Month Mode" aria-label="Month Modet" data-mode="month">
Month
</button>
<button class="btn btn-secondary o_gantt_button" title="Week Mode" aria-label="Week Mode" data-mode="week">
Week
</button>
<button class="btn btn-secondary o_gantt_button" title="Day Mode" aria-label="Day Mode" data-mode="day">
Day
</button>
</div>
</t>
</templates>

View File

@@ -0,0 +1,19 @@
<flectra>
<template id="assets_backend_gantt_chart" inherit_id="web.assets_backend" name="Website Backend Assets Gantt Chart">
<xpath expr="//script[last()]" position="after">
<script type="text/javascript" src="/web_gantt/static/src/dhtmlx/js/dhtmlxgantt.js"/>
<script type="text/javascript" src="/web_gantt/static/src/js/gantt_controller.js"/>
<script type="text/javascript" src="/web_gantt/static/src/js/gantt_renderer.js"/>
<script type="text/javascript" src="/web_gantt/static/src/js/gantt_model.js"/>
<script type="text/javascript" src="/web_gantt/static/src/js/gantt_view.js"/>
</xpath>
<xpath expr="//link[last()]" position="after">
<link rel="stylesheet" type="text/scss" href="/web_gantt/static/src/dhtmlx/css/dhtmlxgantt.css"/>
<link rel="stylesheet" type="text/scss" href="/web_gantt/static/src/css/style.css"/>
</xpath>
</template>
</flectra>