diff --git a/addons/pos_cache/__init__.py b/addons/pos_cache/__init__.py new file mode 100644 index 00000000000..48f417bbd93 --- /dev/null +++ b/addons/pos_cache/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +import models diff --git a/addons/pos_cache/__openerp__.py b/addons/pos_cache/__openerp__.py new file mode 100644 index 00000000000..708ae46108b --- /dev/null +++ b/addons/pos_cache/__openerp__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +{ + 'name': "pos_cache", + + 'summary': """ + Enable a cache on products for a lower POS loading time.""", + + 'description': """ +This creates a product cache per POS config. It drastically lowers the +time it takes to load a POS session with a lot of products. + """, + + 'author': "Odoo", + 'website': "https://www.odoo.com/page/point-of-sale", + 'category': 'Point Of Sale', + 'version': '1.0', + 'depends': ['point_of_sale'], + 'data': [ + 'data/pos_cache_data.xml', + 'security/ir.model.access.csv', + 'views/pos_cache_views.xml', + 'views/pos_cache_templates.xml', + ] +} diff --git a/addons/pos_cache/data/pos_cache_data.xml b/addons/pos_cache/data/pos_cache_data.xml new file mode 100644 index 00000000000..709e6edea35 --- /dev/null +++ b/addons/pos_cache/data/pos_cache_data.xml @@ -0,0 +1,16 @@ + + + + + Refresh POS Cache + + 1 + hours + -1 + + + + + + + diff --git a/addons/pos_cache/doc/cache.rst b/addons/pos_cache/doc/cache.rst new file mode 100644 index 00000000000..b01c635ef38 --- /dev/null +++ b/addons/pos_cache/doc/cache.rst @@ -0,0 +1,13 @@ +POS Cache ++++++++++ + +This module enables a cache for the products in the pos configs. Each POS Config has his own Cache. + +The Cache is updated every hour by a cron. + +============ +Compute user +============ + +As it's a bad practice to use the admin in a multi-company configuration, a field permit to force a user to compute +the cache. A badly chosen user can result in wrong taxes in POS in a multi-company environment. diff --git a/addons/pos_cache/models/__init__.py b/addons/pos_cache/models/__init__.py new file mode 100644 index 00000000000..8c6b3129856 --- /dev/null +++ b/addons/pos_cache/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +import pos_cache diff --git a/addons/pos_cache/models/pos_cache.py b/addons/pos_cache/models/pos_cache.py new file mode 100644 index 00000000000..0ae91b8bfeb --- /dev/null +++ b/addons/pos_cache/models/pos_cache.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from ast import literal_eval +import cPickle + +from openerp import models, fields, api + + +class pos_cache(models.Model): + _name = 'pos.cache' + + cache = fields.Binary() + product_domain = fields.Text(required=True) + product_fields = fields.Text(required=True) + + config_id = fields.Many2one('pos.config', ondelete='cascade', required=True) + compute_user_id = fields.Many2one('res.users', 'Cache compute user', required=True) + + @api.model + def refresh_all_caches(self): + self.env['pos.cache'].search([]).refresh_cache() + + @api.one + def refresh_cache(self): + products = self.env['product.product'].search(self.get_product_domain()) + prod_ctx = products.with_context(pricelist=self.config_id.pricelist_id.id, display_default_code=False) + prod_ctx = prod_ctx.sudo(self.compute_user_id.id) + res = prod_ctx.read(self.get_product_fields()) + datas = { + 'cache': cPickle.dumps(res, protocol=cPickle.HIGHEST_PROTOCOL), + } + + self.write(datas) + + @api.model + def get_product_domain(self): + return literal_eval(self.product_domain) + + @api.model + def get_product_fields(self): + return literal_eval(self.product_fields) + + @api.model + def get_cache(self, domain, fields): + if not self.cache or domain != self.get_product_domain() or fields != self.get_product_fields(): + self.product_domain = str(domain) + self.product_fields = str(fields) + self.refresh_cache() + + return cPickle.loads(self.cache) + + +class pos_config(models.Model): + _inherit = 'pos.config' + + @api.one + @api.depends('cache_ids') + def _get_oldest_cache_time(self): + pos_cache = self.env['pos.cache'] + oldest_cache = pos_cache.search([('config_id', '=', self.id)], order='write_date', limit=1) + if oldest_cache: + self.oldest_cache_time = oldest_cache.write_date + + # Use a related model to avoid the load of the cache when the pos load his config + cache_ids = fields.One2many('pos.cache', 'config_id') + oldest_cache_time = fields.Datetime(compute='_get_oldest_cache_time', string='Oldest cache time', readonly=True) + + def _get_cache_for_user(self): + pos_cache = self.env['pos.cache'] + cache_for_user = pos_cache.search([('id', 'in', self.cache_ids.ids), ('compute_user_id', '=', self.env.uid)]) + + if cache_for_user: + return cache_for_user[0] + else: + return None + + @api.multi + def get_products_from_cache(self, fields, domain): + cache_for_user = self._get_cache_for_user() + + if cache_for_user: + return cache_for_user.get_cache(domain, fields) + else: + pos_cache = self.env['pos.cache'] + pos_cache.create({ + 'config_id': self.id, + 'product_domain': str(domain), + 'product_fields': str(fields), + 'compute_user_id': self.env.uid + }) + new_cache = self._get_cache_for_user() + return new_cache.get_cache(domain, fields) + + @api.one + def delete_cache(self): + # throw away the old caches + self.cache_ids.unlink() diff --git a/addons/pos_cache/security/ir.model.access.csv b/addons/pos_cache/security/ir.model.access.csv new file mode 100644 index 00000000000..1a2651865f9 --- /dev/null +++ b/addons/pos_cache/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_pos_cache,pos.cache,model_pos_cache,point_of_sale.group_pos_user,1,1,1,1 diff --git a/addons/pos_cache/static/src/js/pos_cache.js b/addons/pos_cache/static/src/js/pos_cache.js new file mode 100644 index 00000000000..29d5e3bfbc4 --- /dev/null +++ b/addons/pos_cache/static/src/js/pos_cache.js @@ -0,0 +1,43 @@ +odoo.define('pos_cache.pos_cache', function (require) { + "use strict"; + var core = require('web.core'); + var models = require('point_of_sale.models'); + var Model = require('web.DataModel'); + var _t = core._t; + + var posmodel_super = models.PosModel.prototype; + models.PosModel = models.PosModel.extend({ + load_server_data: function () { + var self = this; + + var product_index = _.findIndex(this.models, function (model) { + return model.model === "product.product"; + }); + + // Give both the fields and domain to pos_cache in the + // backend. This way we don't have to hardcode these + // values in the backend and they automatically stay in + // sync with whatever is defined (and maybe extended by + // other modules) in js. + var product_model = this.models[product_index]; + var product_fields = product_model.fields; + var product_domain = product_model.domain; + + // We don't want to load product.product the normal + // uncached way, so get rid of it. + if (product_index !== -1) { + this.models.splice(product_index, 1); + } + + return posmodel_super.load_server_data.apply(this, arguments).then(function () { + var records = new Model('pos.config').call('get_products_from_cache', + [self.pos_session.config_id[0], product_fields, product_domain]); + + self.chrome.loading_message(_t('Loading') + ' product.product', 1); + return records.then(function (product) { + self.db.add_products(product); + }); + }); + }, + }); +}); diff --git a/addons/pos_cache/views/pos_cache_templates.xml b/addons/pos_cache/views/pos_cache_templates.xml new file mode 100644 index 00000000000..6866bd2a24f --- /dev/null +++ b/addons/pos_cache/views/pos_cache_templates.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/addons/pos_cache/views/pos_cache_views.xml b/addons/pos_cache/views/pos_cache_views.xml new file mode 100644 index 00000000000..522d9437a0b --- /dev/null +++ b/addons/pos_cache/views/pos_cache_views.xml @@ -0,0 +1,18 @@ + + + + + pos.config.form.view + pos.config + + + +