You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

445 lines
16 KiB

/* Copyright 2015 Holger Brunn <hbrunn@therp.nl>
* Copyright 2016 Pedro M. Baeza <pedro.baeza@tecnativa.com>
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
openerp.web_widget_x2many_2d_matrix = function(instance)
{
instance.web.form.widgets.add(
'x2many_2d_matrix',
'instance.web_widget_x2many_2d_matrix.FieldX2Many2dMatrix');
instance.web_widget_x2many_2d_matrix.FieldX2Many2dMatrix = instance.web.form.FieldOne2Many.extend({
template: 'FieldX2Many2dMatrix',
widget_class: 'oe_form_field_x2many_2d_matrix',
// those will be filled with rows from the dataset
by_x_axis: {},
by_y_axis: {},
by_x_axis_sorted: [],
by_y_axis_sorted: [],
by_id: {},
// configuration values
field_x_axis: 'x',
field_label_x_axis: 'x',
field_y_axis: 'y',
field_label_y_axis: 'y',
field_value: 'value',
// information about our datatype
is_numeric: false,
show_row_totals: true,
show_column_totals: true,
// this will be filled with the model's fields_get
fields: {},
// Store fields used to fill HTML attributes
fields_att: {},
x_axis_clickable: true,
y_axis_clickable: true,
// read parameters
init: function(field_manager, node)
{
this.field_x_axis = node.attrs.field_x_axis || this.field_x_axis;
this.field_y_axis = node.attrs.field_y_axis || this.field_y_axis;
this.x_axis_clickable = node.attrs.x_axis_clickable || this.x_axis_clickable;
this.y_axis_clickable = node.attrs.y_axis_clickable || this.y_axis_clickable;
this.field_label_x_axis = node.attrs.field_label_x_axis || this.field_x_axis;
this.field_label_y_axis = node.attrs.field_label_y_axis || this.field_y_axis;
this.field_value = node.attrs.field_value || this.field_value;
for (var property in node.attrs) {
if (property.indexOf("field_att_") === 0) {
this.fields_att[property.substring(10)] = node.attrs[property];
}
}
this.field_editability = node.attrs.field_editability || this.field_editability;
this.show_row_totals = node.attrs.show_row_totals || this.show_row_totals;
this.show_column_totals = node.attrs.show_column_totals || this.show_column_totals;
return this._super.apply(this, arguments);
},
// return a field's value, id in case it's a one2many field
get_field_value: function(row, field, many2one_as_name)
{
if(this.fields[field].type == 'many2one' && _.isArray(row[field]))
{
if(many2one_as_name)
{
return row[field][1];
}
else
{
return row[field][0];
}
}
return row[field];
},
// setup our datastructure for simple access in the template
set_value: function()
{
var self = this,
result = this._super.apply(this, arguments);
self.by_x_axis_sorted = []
self.by_y_axis_sorted = []
self.by_x_axis = {};
self.by_y_axis = {};
self.by_id = {};
return jQuery.when(result).then(function()
{
return self.dataset._model.call('fields_get').then(function(fields)
{
self.fields = fields;
self.is_numeric = fields[self.field_value].type == 'float';
self.show_row_totals &= self.is_numeric;
self.show_column_totals &= self.is_numeric;
self.x_axis_clickable &= self.is_numeric;
self.y_axis_clickable &= self.is_numeric;
})
// if there are cached writes on the parent dataset, read below
// only returns the written data, which is not enough to properly
// set up our data structure. Read those ids here and patch the
// cache
.then(function()
{
var ids_written = _.map(
self.dataset.to_write, function(x) { return x.id });
if(!ids_written.length)
{
return;
}
return (new instance.web.Query(self.dataset._model))
.filter([['id', 'in', ids_written]])
.all()
.then(function(rows)
{
_.each(rows, function(row)
{
var cache = _.find(
self.dataset.cache,
function(x) { return x.id == row.id }
);
_.extend(cache.values, row, _.clone(cache.values));
})
})
})
.then(function()
{
return self.dataset.read_ids(self.dataset.ids).then(function(rows)
{
var read_many2one = {},
many2one_fields = [
self.field_x_axis, self.field_y_axis,
self.field_label_x_axis, self.field_label_y_axis
];
// prepare to read many2one names if necessary (we can get (id, name) or just id as value)
_.each(many2one_fields, function(field)
{
if(self.fields[field].type == 'many2one')
{
read_many2one[field] = {};
}
});
// setup data structure
_.each(rows, function(row)
{
self.add_xy_row(row);
_.each(read_many2one, function(rows, field)
{
if(!_.isArray(row[field]))
{
rows[row[field]] = rows[row[field]] || []
rows[row[field]].push(row);
}
});
});
// read many2one fields if necessary
var deferrends = [];
_.each(read_many2one, function(rows, field)
{
if(_.isEmpty(rows))
{
return;
}
var model = new instance.web.Model(self.fields[field].relation);
deferrends.push(model.call(
'name_get',
[_.map(_.keys(rows), function(key) {return parseInt(key)})])
.then(function(names)
{
_.each(names, function(name)
{
_.each(rows[name[0]], function(row)
{
row[field] = name;
});
});
}));
})
if(self.is_started && !self.no_rerender)
{
self.renderElement();
self.compute_totals();
self.setup_many2one_axes();
self.$el.find('.edit').on(
'change', self.proxy(self.xy_value_change));
self.effective_readonly_change();
}
return jQuery.when.apply(jQuery, deferrends);
});
});
});
},
// do whatever needed to setup internal data structure
add_xy_row: function(row)
{
var x = this.get_field_value(row, this.field_x_axis),
y = this.get_field_value(row, this.field_y_axis);
// row is a *copy* of a row in dataset.cache, fetch
// a reference to this row in order to have the
// internal data structure point to the same data
// the dataset manipulates
_.every(this.dataset.cache, function(cached_row)
{
if(cached_row.id == row.id)
{
row = cached_row.values;
// new rows don't have that
row.id = cached_row.id;
return false;
}
return true;
});
if(this.by_x_axis_sorted.indexOf(x) == -1)
this.by_x_axis_sorted.push(x)
if(this.by_y_axis_sorted.indexOf(y) == -1)
this.by_y_axis_sorted.push(y)
this.by_x_axis[x] = this.by_x_axis[x] || {};
this.by_y_axis[y] = this.by_y_axis[y] || {};
this.by_x_axis[x][y] = row;
this.by_y_axis[y][x] = row;
this.by_id[row.id] = row;
},
// get x axis values in the correct order
get_x_axis_values: function()
{
return this.by_x_axis_sorted
},
// get y axis values in the correct order
get_y_axis_values: function()
{
return this.by_y_axis_sorted
},
// get the label for a value on the x axis
get_x_axis_label: function(x)
{
return this.get_field_value(
_.first(_.values(this.by_x_axis[x])),
this.field_label_x_axis, true);
},
// get the label for a value on the y axis
get_y_axis_label: function(y)
{
return this.get_field_value(
_.first(_.values(this.by_y_axis[y])),
this.field_label_y_axis, true);
},
// return the class(es) the inputs should have
get_xy_value_class: function()
{
var classes = 'oe_form_field oe_form_required';
if(this.is_numeric)
{
classes += ' oe_form_field_float';
}
return classes;
},
// return row id of a coordinate
get_xy_id: function(x, y)
{
return this.by_x_axis[x][y]['id'];
},
get_xy_att: function(x, y)
{
var vals = {};
for (var att in this.fields_att) {
var val = this.get_field_value(
this.by_x_axis[x][y], this.fields_att[att]);
// Discard empty values
if (val) {
vals[att] = val;
}
}
return vals;
},
// return the value of a coordinate
get_xy_value: function(x, y)
{
return this.get_field_value(
this.by_x_axis[x][y], this.field_value);
},
// validate a value
validate_xy_value: function(val)
{
try
{
this.parse_xy_value(val);
}
catch(e)
{
return false;
}
return true;
},
// parse a value from user input
parse_xy_value: function(val)
{
return instance.web.parse_value(
val, {'type': this.fields[this.field_value].type});
},
// format a value from the database for display
format_xy_value: function(val)
{
return instance.web.format_value(
val, {'type': this.fields[this.field_value].type});
},
// compute totals
compute_totals: function()
{
var self = this,
grand_total = 0,
totals_x = {},
totals_y = {},
rows = this.by_id,
deferred = jQuery.Deferred();
_.each(rows, function(row)
{
var key_x = self.get_field_value(row, self.field_x_axis),
key_y = self.get_field_value(row, self.field_y_axis);
totals_x[key_x] = (totals_x[key_x] || 0) + self.get_field_value(row, self.field_value);
totals_y[key_y] = (totals_y[key_y] || 0) + self.get_field_value(row, self.field_value);
grand_total += self.get_field_value(row, self.field_value);
});
_.each(totals_y, function(total, y)
{
self.$el.find(
_.str.sprintf('td.row_total[data-y="%s"]', y)).text(
self.format_xy_value(total));
});
_.each(totals_x, function(total, x)
{
self.$el.find(
_.str.sprintf('td.column_total[data-x="%s"]', x)).text(
self.format_xy_value(total));
});
self.$el.find('.grand_total').text(
self.format_xy_value(grand_total))
deferred.resolve({
totals_x: totals_x,
totals_y: totals_y,
grand_total: grand_total,
rows: rows,
});
return deferred;
},
setup_many2one_axes: function()
{
if(this.fields[this.field_x_axis].type == 'many2one' && this.x_axis_clickable)
{
this.$el.find('th[data-x]').addClass('oe_link')
.click(_.partial(
this.proxy(this.many2one_axis_click),
this.field_x_axis, 'x'));
}
if(this.fields[this.field_y_axis].type == 'many2one' && this.y_axis_clickable)
{
this.$el.find('tr[data-y] th').addClass('oe_link')
.click(_.partial(
this.proxy(this.many2one_axis_click),
this.field_y_axis, 'y'));
}
},
many2one_axis_click: function(field, id_attribute, e)
{
this.do_action({
type: 'ir.actions.act_window',
name: this.fields[field].string,
res_model: this.fields[field].relation,
res_id: jQuery(e.currentTarget).data(id_attribute),
views: [[false, 'form']],
target: 'current',
})
},
start: function()
{
var self = this;
this.$el.find('.edit').on(
'change', self.proxy(this.xy_value_change));
this.compute_totals();
this.setup_many2one_axes();
this.on("change:effective_readonly",
this, this.proxy(this.effective_readonly_change));
this.effective_readonly_change();
return this._super.apply(this, arguments);
},
xy_value_change: function(e)
{
var $this = jQuery(e.currentTarget),
val = $this.val();
if(this.validate_xy_value(val))
{
var data = {}, value = this.parse_xy_value(val);
data[this.field_value] = value;
$this.siblings('.read').text(this.format_xy_value(value));
$this.val(this.format_xy_value(value));
this.dataset.write($this.data('id'), data);
$this.parent().removeClass('oe_form_invalid');
this.compute_totals();
}
else
{
$this.parent().addClass('oe_form_invalid');
}
},
effective_readonly_change: function()
{
this.$el
.find('tbody td.oe_list_field_cell span.oe_form_field .edit')
.toggle(!this.get('effective_readonly'));
this.$el
.find('tbody td.oe_list_field_cell span.oe_form_field .read')
.toggle(this.get('effective_readonly'));
this.$el.find('.edit').first().focus();
},
is_syntax_valid: function()
{
return this.$el.find('.oe_form_invalid').length == 0;
},
// deactivate view related functions
load_views: function() {},
reload_current_view: function() {},
get_active_view: function() {},
});
}