Search code examples
javascriptasynchronouspromiseodoo-8odoo

How can I get a variable from a JavaScript promises (python calls), avoiding the pending state in Odoo?


Original code from the Point of Sale module

In the point_of_sale module there is a list of objects as the following

module.PosModel = Backbone.Model.extend({

    models: {

        // [...]

        {
            model:  'pos.session',
            fields: ['id', 'journal_ids','name','user_id','config_id','start_at','stop_at','sequence_number','login_number'],
            domain: function(self){ return [['state','=','opened'],['user_id','=',self.session.uid]]; },
            loaded: function(self,pos_sessions){
                self.pos_session = pos_sessions[0]; 

                var orders = self.db.get_orders();
                for (var i = 0; i < orders.length; i++) {
                    self.pos_session.sequence_number = Math.max(self.pos_session.sequence_number, orders[i].data.sequence_number+1);
                }
            },
        },
        {
            model:  'product.product',
            fields: ['display_name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13', 'default_code', 
                        'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type', 'description_sale', 'description',
                        'product_tmpl_id'],
            domain: [['sale_ok','=',true],['available_in_pos','=',true]],
            context: function(self){ return { pricelist: self.pricelist.id, display_default_code: false }; },
            loaded: function(self, products){
                self.db.add_products(products);
        },

        // [...]

    }

And then the information of the data is loaded like this

load_server_data: function(){
    var self = this;
    var loaded = new $.Deferred();
    var progress = 0;
    var progress_step = 1.0 / self.models.length;
    var tmp = {}; // this is used to share a temporary state between models loaders

    function load_model(index){
        if(index >= self.models.length){
            loaded.resolve();
        }else{
            var model = self.models[index];
            self.pos_widget.loading_message(_t('Loading')+' '+(model.label || model.model || ''), progress);
            var fields =  typeof model.fields === 'function'  ? model.fields(self,tmp)  : model.fields;
            var domain =  typeof model.domain === 'function'  ? model.domain(self,tmp)  : model.domain;
            var context = typeof model.context === 'function' ? model.context(self,tmp) : model.context; 
            var ids     = typeof model.ids === 'function'     ? model.ids(self,tmp) : model.ids;
            progress += progress_step;


            if( model.model ){
                if (model.ids) {
                    var records = new instance.web.Model(model.model).call('read',[ids,fields],context);
                } else {
                    var records = new instance.web.Model(model.model).query(fields).filter(domain).context(context).all()
                }

// [...]

What I have tried. First try

So, I would like to change the domain field of the product.product model. I am trying with this

if (typeof jQuery === 'undefined') { throw new Error('Product multi POS needs jQuery'); }

+function ($) {
    'use strict';

    openerp.pos_product_multi_shop = function(instance, module) {
        var PosModelParent = instance.point_of_sale.PosModel;
        instance.point_of_sale.PosModel = instance.point_of_sale.PosModel.extend({
            load_server_data: function(){
                console.log('-- LOAD SERVER DATA');
                var self = this; 

                self.models.forEach(function(elem) {
                    if (elem.model == 'product.product') {
                        // return [['id', 'in', [2]]];    // if I return this domain it works well
                        domain_loaded = function() {
                            return new instance.web.Model('product.product').call(
                                'get_available_in_pos_ids',
                                [self.pos_session.config_id[0]],
                            )
                        }
                        elem.domain = $.when(domain_loaded);
                    }
                })

                var loaded = PosModelParent.prototype.load_server_data.apply(this, arguments);
                return loaded;
            },
        });
    }
}(jQuery);

If I return a domain directly it works. But if I replace it with a function that calls a python function with call, the domain is not loaded well: [['sale_ok','=',true],['available_in_pos','=',true]]. I've tried with $.when and without it and it does not work.

In addition elem.domain must be a function because self.pos_session only exists when all the previous model information is executed.

Second try

I have tried this following code as well:

if (elem.model == 'product.product') {
    // return [['id', 'in', [2]]];    // if I return the domain like this it works
    console.log('>> OLD DOMAIN')
    console.log(elem.domain);
    elem.domain = function() {
        console.log('>>> PRODUCT SESSION');
        console.log(self.pos_session);
        var product_product_obj = new instance.web.Model('product.product');
        return product_product_obj.call(
            'get_available_in_pos_ids',
            [self.pos_session.config_id[0]],
        )
    }
    console.log('>> NEW DOMAIN')
    console.log(elem.domain);
}

So first '>> OLD DOMAIN' is printed, then '>> NEW DOMAIN' and, at last '>>> PRODUCT SESSION' is printed. So the function is executed. But the the domains is not being returned well.

Third try. With "then"

And I cannot use then because I need to do the variable assignation. But on the other hand the assignation is well done becase when I print the new domain the function appears in the log.

Even if I use then I am getting the result well from python

var domain_return = product_product_obj.call(
    'get_available_in_pos_ids',
    [self.pos_session.config_id[0]],
).then(function(result) {
    console.log('>> RESULT: ');
    console.log(result)
});

I also tried with other promise, but I get a pending result that is ignored and all the products are shown

elem.domain = function() {
    return new Promise(function next(resolve, reject) {
        console.log('>>> PRODUCT SESSION');
        console.log(self.pos_session);
        var product_product_obj = new instance.web.Model('product.product');
        var domain_return = product_product_obj.call(
            'get_available_in_pos_ids',
            [self.pos_session.config_id[0]],
        ).then(function(result) {
            console.log('>> RETURN: ');
            console.log(result);
            resolve(result);
        });
        console.log('>> DOMAIN RETURN: ');
        console.log(domain_return);
    });
}

The rest of the domains of the object are calculated without calling python functions. So I can't copy an example from other place

So, is there a way to avoid the pending result? I cannot use async/await yet.

Maybe to make it syncronous will help but I know this should be avoided


Solution

  • Finally I found a workaround overriding the loaded function where all the products are already loaded

    var PosModelParent = instance.point_of_sale.PosModel;
    instance.point_of_sale.PosModel = instance.point_of_sale.PosModel.extend({
        load_server_data: function(){
            let self = this;
            self.models.forEach(function(elem) {
                if (elem.model == 'product.product') {
                    elem.fields = ['display_name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13', 'default_code', 
                    'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type', 'description_sale', 'description',
                    'product_tmpl_id', 'available_in_pos_ids'];
                    elem.loaded = function(self, products){
                        console.log('>> PRODUCTS: ');
                        console.log(products);
                        var shop_id = self.pos_session.config_id[0];                            
                        var new_products = [];
                        products.forEach(function(prod) {
                            if (prod.available_in_pos_ids.includes(shop_id)) {
                                new_products.push(prod);
                            }
                        })
                        self.db.add_products(new_products);
                    }
                }
            })
            var loaded = PosModelParent.prototype.load_server_data.apply(this, arguments);
            return loaded;
        },
    });