summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/views/pivot/pivot_view.js
blob: ea3ab9c744282895dee12beb9af20ea3c666a395 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
odoo.define('web.PivotView', function (require) {
    "use strict";

    /**
     * The Pivot View is a view that represents data in a 'pivot grid' form. It
     * aggregates data on 2 dimensions and displays the result, allows the user to
     * 'zoom in' data.
     */

    const AbstractView = require('web.AbstractView');
    const config = require('web.config');
    const core = require('web.core');
    const PivotModel = require('web.PivotModel');
    const PivotController = require('web.PivotController');
    const PivotRenderer = require('web.PivotRenderer');
    const RendererWrapper = require('web.RendererWrapper');

    const _t = core._t;
    const _lt = core._lt;

    const searchUtils = require('web.searchUtils');
    const GROUPABLE_TYPES = searchUtils.GROUPABLE_TYPES;

    const PivotView = AbstractView.extend({
        display_name: _lt('Pivot'),
        icon: 'fa-table',
        config: Object.assign({}, AbstractView.prototype.config, {
            Model: PivotModel,
            Controller: PivotController,
            Renderer: PivotRenderer,
        }),
        viewType: 'pivot',
        searchMenuTypes: ['filter', 'groupBy', 'comparison', 'favorite'],

        /**
         * @override
         * @param {Object} params
         * @param {Array} params.additionalMeasures
         */
        init: function (viewInfo, params) {
            this._super.apply(this, arguments);

            const activeMeasures = []; // Store the defined active measures
            const colGroupBys = []; // Store the defined group_by used on cols
            const rowGroupBys = []; // Store the defined group_by used on rows
            const measures = {}; // All the available measures
            const groupableFields = {}; // The fields which can be used to group data
            const widgets = {}; // Wigdets defined in the arch
            const additionalMeasures = params.additionalMeasures || [];

            this.fields.__count = { string: _t("Count"), type: "integer" };

            //Compute the measures and the groupableFields
            Object.keys(this.fields).forEach(name => {
                const field = this.fields[name];
                if (name !== 'id' && field.store === true) {
                    if (['integer', 'float', 'monetary'].includes(field.type) || additionalMeasures.includes(name)) {
                        measures[name] = field;
                    }
                    if (GROUPABLE_TYPES.includes(field.type)) {
                        groupableFields[name] = field;
                    }
                }
            });
            measures.__count = { string: _t("Count"), type: "integer" };


            this.arch.children.forEach(field => {
                let name = field.attrs.name;
                // Remove invisible fields from the measures if not in additionalMeasures
                if (field.attrs.invisible && py.eval(field.attrs.invisible)) {
                    if (name in groupableFields) {
                        delete groupableFields[name];
                    }
                    if (!additionalMeasures.includes(name)) {
                        delete measures[name];
                        return;
                    }
                }
                if (field.attrs.interval) {
                    name += ':' + field.attrs.interval;
                }
                if (field.attrs.widget) {
                    widgets[name] = field.attrs.widget;
                }
                // add active measures to the measure list.  This is very rarely
                // necessary, but it can be useful if one is working with a
                // functional field non stored, but in a model with an overrided
                // read_group method.  In this case, the pivot view could work, and
                // the measure should be allowed.  However, be careful if you define
                // a measure in your pivot view: non stored functional fields will
                // probably not work (their aggregate will always be 0).
                if (field.attrs.type === 'measure' && !(name in measures)) {
                    measures[name] = this.fields[name];
                }
                if (field.attrs.string && name in measures) {
                    measures[name].string = field.attrs.string;
                }
                if (field.attrs.type === 'measure' || 'operator' in field.attrs) {
                    activeMeasures.push(name);
                    measures[name] = this.fields[name];
                }
                if (field.attrs.type === 'col') {
                    colGroupBys.push(name);
                }
                if (field.attrs.type === 'row') {
                    rowGroupBys.push(name);
                }
            });
            if ((!activeMeasures.length) || this.arch.attrs.display_quantity) {
                activeMeasures.splice(0, 0, '__count');
            }

            this.loadParams.measures = activeMeasures;
            this.loadParams.colGroupBys = config.device.isMobile ? [] : colGroupBys;
            this.loadParams.rowGroupBys = rowGroupBys;
            this.loadParams.fields = this.fields;
            this.loadParams.default_order = params.default_order || this.arch.attrs.default_order;
            this.loadParams.groupableFields = groupableFields;

            const disableLinking = !!(this.arch.attrs.disable_linking &&
                                        JSON.stringify(this.arch.attrs.disable_linking));

            this.rendererParams.widgets = widgets;
            this.rendererParams.disableLinking = disableLinking;

            this.controllerParams.disableLinking = disableLinking;
            this.controllerParams.title = params.title || this.arch.attrs.string || _t("Untitled");
            this.controllerParams.measures = measures;

            // retrieve form and list view ids from the action to open those views
            // when a data cell of the pivot view is clicked
            this.controllerParams.views = [
                _findView(params.actionViews, 'list'),
                _findView(params.actionViews, 'form'),
            ];

            function _findView(views, viewType) {
                const view = views.find(view => {
                    return view.type === viewType;
                });
                return [view ? view.viewID : false, viewType];
            }
        },

        /**
         *
         * @override
         */
        getRenderer(parent, state) {
            state = Object.assign(state || {}, this.rendererParams);
            return new RendererWrapper(parent, this.config.Renderer, state);
        },
    });

    return PivotView;

});