@ -1,47 +1,50 @@
//-*- coding: utf-8 -*-
//############################################################################
//
// OpenERP, Open Source Management Solution
// This module copyright (C) 2015 Therp BV <http://therp.nl>.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
//############################################################################
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 ( {
/ * C o p y r i g h t 2 0 1 5 H o l g e r B r u n n < h b r u n n @ t h e r p . n l >
* Copyright 2016 Pedro M . Baeza < pedro . baeza @ tecnativa . com >
* License AGPL - 3.0 or later ( http : //www.gnu.org/licenses/agpl). */
odoo . define ( 'web_widget_x2many_2d_matrix.widget' , function ( require ) {
"use strict" ;
var core = require ( 'web.core' ) ;
var formats = require ( 'web.formats' ) ;
var FieldOne2Many = core . form_widget_registry . get ( 'one2many' ) ;
var Model = require ( 'web.Model' ) ;
var data = require ( 'web.data' ) ;
var _ = require ( '_' ) ;
var $ = require ( '$' ) ;
var WidgetX2Many2dMatrix = 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_id : { } ,
// configuration values
field_x_axis : 'x' ,
field_label_x_axis : 'x' ,
field_y_axis : 'y' ,
field_label_y_axis : 'y' ,
field_value : 'value' ,
x_axis_clickable : true ,
y_axis_clickable : true ,
// 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 : { } ,
parse_boolean : function ( val )
{
if ( val . toLowerCase ( ) === 'true' || val === '1' ) {
return true ;
}
return false ;
} ,
// read parameters
init : function ( field_manager , node )
@ -50,10 +53,18 @@ openerp.web_widget_x2many_2d_matrix = function(instance)
this . field_y_axis = node . attrs . field_y_axis || this . field_y_axis ;
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 . x_axis_clickable = this . parse_boolean ( node . attrs . x_axis_clickable || '1' ) ;
this . y_axis_clickable = this . parse_boolean ( node . attrs . y_axis_clickable || '1' ) ;
this . field_value = node . attrs . field_value || this . field_value ;
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 ) ;
for ( var property in node . attrs ) {
if ( property . startsWith ( "field_att_" ) ) {
this . fields_att [ property . substring ( 10 ) ] = node . attrs [ property ] ;
}
}
this . field_editability = node . attrs . field_editability || this . field_editability ;
this . show_row_totals = this . parse_boolean ( node . attrs . show_row_totals || '1' ) ;
this . show_column_totals = this . parse_boolean ( node . attrs . show_column_totals || '1' ) ;
return this . _super ( field_manager , node ) ;
} ,
// return a field's value, id in case it's a one2many field
@ -74,15 +85,16 @@ openerp.web_widget_x2many_2d_matrix = function(instance)
} ,
// setup our datastructure for simple access in the template
set_value : function ( )
set_value : function ( value_ )
{
var self = this ,
result = this . _super . apply ( this , arguments ) ;
result = this . _super ( value_ ) ;
self . by_x_axis = { } ;
self . by_y_axis = { } ;
return jQuery . when ( result ) . then ( function ( )
self . by_id = { } ;
return $ . when ( result ) . then ( function ( )
{
return self . dataset . _model . call ( 'fields_get' ) . then ( function ( fields )
{
@ -90,83 +102,82 @@ openerp.web_widget_x2many_2d_matrix = function(instance)
self . is_numeric = fields [ self . field_value ] . type == 'float' ;
self . show_row_totals &= self . is_numeric ;
self . show_column_totals &= self . is_numeric ;
} ) . then ( function ( )
} )
// 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 ( )
{
return self . dataset . read_ids ( self . dataset . ids ) . then ( function ( rows )
var ids_written = _ . map (
self . dataset . to_write , function ( x ) { return x . id } ) ;
if ( ! ids_written . length )
{
return ;
}
return ( new data . Query ( self . dataset . _model ) )
. filter ( [ [ 'id' , 'in' , ids_written ] ] )
. all ( )
. 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 )
_ . each ( rows , function ( row )
{
if ( self . fields [ field ] . type == 'many2one' )
{
read_many2one [ field ] = { } ;
}
} ) ;
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 )
{
// 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 ) ) ;
'change' , self . proxy ( self . xy_value_change ) ) ;
self . effective_readonly_change ( ) ;
}
return jQuery . when . apply ( jQuery , deferrends ) ;
} ) ;
} ) ;
} ) ;
} ,
// t o whatever needed to setup internal data structure
// 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 ;
} ) ;
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
@ -214,6 +225,20 @@ openerp.web_widget_x2many_2d_matrix = function(instance)
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 )
{
@ -238,14 +263,14 @@ openerp.web_widget_x2many_2d_matrix = function(instance)
// parse a value from user input
parse_xy_value : function ( val )
{
return instance . web . parse_value (
return formats . 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 (
return formats . format_value (
val , { 'type' : this . fields [ this . field_value ] . type } ) ;
} ,
@ -255,51 +280,50 @@ openerp.web_widget_x2many_2d_matrix = function(instance)
var self = this ,
grand_total = 0 ,
totals_x = { } ,
totals_y = { } ;
return self . dataset . read_ids ( self . dataset . ids ) . then ( function ( rows )
totals_y = { } ,
rows = this . by_id ,
deferred = $ . Deferred ( ) ;
_ . each ( rows , function ( row )
{
_ . 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 ) ;
} ) ;
} ) . then ( function ( )
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 )
{
_ . 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 ) )
return {
totals_x : totals_x ,
totals_y : totals_y ,
grand_total : grand_total ,
} ;
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' )
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' )
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 (
@ -314,7 +338,7 @@ openerp.web_widget_x2many_2d_matrix = function(instance)
type : 'ir.actions.act_window' ,
name : this . fields [ field ] . string ,
res_model : this . fields [ field ] . relation ,
res_id : jQuery ( e . currentTarget ) . data ( id_attribute ) ,
res_id : $ ( e . currentTarget ) . data ( id_attribute ) ,
views : [ [ false , 'form' ] ] ,
target : 'current' ,
} )
@ -330,12 +354,12 @@ openerp.web_widget_x2many_2d_matrix = function(instance)
this . on ( "change:effective_readonly" ,
this , this . proxy ( this . effective_readonly_change ) ) ;
this . effective_readonly_change ( ) ;
return this . _super . apply ( this , arguments ) ;
return this . _super ( ) ;
} ,
xy_value_change : function ( e )
{
var $this = jQuery ( e . currentTarget ) ,
var $this = $ ( e . currentTarget ) ,
val = $this . val ( ) ;
if ( this . validate_xy_value ( val ) )
{
@ -372,9 +396,20 @@ openerp.web_widget_x2many_2d_matrix = function(instance)
return this . $el . find ( '.oe_form_invalid' ) . length == 0 ;
} ,
// deactivate view related functions
load_views : function ( ) { } ,
reload_current_view : function ( ) { } ,
get_active_view : function ( ) { } ,
load_views : function ( ) {
// Needed for removing the initial empty tree view when the widget
// is loaded
var self = this ,
result = this . _super ( ) ;
return $ . when ( result ) . then ( function ( )
{
self . renderElement ( ) ;
} ) ;
} ,
} ) ;
}
core . form_widget_registry . add ( 'x2many_2d_matrix' , WidgetX2Many2dMatrix ) ;
return WidgetX2Many2dMatrix ;
} ) ;