diff --git a/pos_access_right/README.rst b/pos_access_right/README.rst new file mode 100644 index 00000000..68f7ce93 --- /dev/null +++ b/pos_access_right/README.rst @@ -0,0 +1,106 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +====================================================== +Point of Sale - Extra Access Right for Certain Actions +====================================================== + +This module extends Odoo Point Of Sale features, restricting possibility +to cashier to make some actions in the Point of Sale (set discount, change +unit prices, etc...) + +this module can be usefull to limit errors and / or fraud. + +This module will add the following groups to Odoo: + +* PoS - Negative Quantity: The cashier can sell negative quantity in Point Of + Sale (ie, can return products); + +* PoS - Discount: The cashier can set Discount in Point Of Sale; + +* PoS - Change Unit Price: The cashier can change the unit price of a product + in Point Of Sale; + +* PoS - Many Orders: The cashier can many orders at the same time; + +* PoS - Delete Order: The cashier can not delete a full order; + +.. image:: /pos_access_right/static/description/new_groups.png + +If a user doesn't belong to a group, he can not use the according feature. + +For example, here is the default numpad for the demo data user : + +.. image:: /pos_access_right/static/description/demo_numpad.png + +If the user try to use a forbidden feature, here is the warning displayed. + +.. image:: /pos_access_right/static/description/demo_error.png + +If the cashier changed, the new right are applied : + +.. image:: /pos_access_right/static/description/admin_numpad.png + +Installation +============ + +Normal installation. + +Configuration +============= + +Once installed, you have to give correct access right to your cashiers. + +Limits / Roadmap +================ + +The feature are only blocked on the Point of sale Frond End UI. it could be +interesting do the same in the back-end office for some of this blockages. + +Usage +===== + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/184/9.0 + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed `feedback +`_. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Sylvain LE GAL + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/pos_access_right/__init__.py b/pos_access_right/__init__.py new file mode 100644 index 00000000..a0fdc10f --- /dev/null +++ b/pos_access_right/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import models diff --git a/pos_access_right/__manifest__.py b/pos_access_right/__manifest__.py new file mode 100644 index 00000000..a75a0769 --- /dev/null +++ b/pos_access_right/__manifest__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2016-Today: La Louve () +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + 'name': 'Point of Sale - Extra Access Right', + 'version': '9.0.1.0.0', + 'category': 'Point Of Sale', + 'summary': 'Point of Sale - Extra Access Right for certain actions', + 'author': 'La Louve, GRAP, Odoo Community Association (OCA)', + 'website': 'http://www.lalouve.net/', + 'license': 'AGPL-3', + 'depends': [ + 'point_of_sale', + ], + 'data': [ + 'security/res_groups.yml', + 'static/src/xml/templates.xml', + ], + 'demo': [ + 'demo/res_groups.yml', + ], + 'installable': False, +} diff --git a/pos_access_right/demo/res_groups.yml b/pos_access_right/demo/res_groups.yml new file mode 100644 index 00000000..fd74b5ec --- /dev/null +++ b/pos_access_right/demo/res_groups.yml @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2016-Today: La Louve () +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + + +- !record {model: res.groups, id: group_negative_qty}: + users: + - base.user_root + +- !record {model: res.groups, id: group_discount}: + users: + - base.user_root + - base.user_demo + +- !record {model: res.groups, id: group_change_unit_price}: + users: + - base.user_root + +- !record {model: res.groups, id: group_multi_order}: + users: + - base.user_root + +- !record {model: res.groups, id: group_delete_order}: + users: + - base.user_root diff --git a/pos_access_right/i18n/fr.po b/pos_access_right/i18n/fr.po new file mode 100644 index 00000000..d718fb7f --- /dev/null +++ b/pos_access_right/i18n/fr.po @@ -0,0 +1,135 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_access_right +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 9.0c\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-11-02 11:49+0000\n" +"PO-Revision-Date: 2016-11-02 11:49+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: pos_access_right +#. openerp-web +#: code:addons/pos_access_right/static/src/js/pos_access_right.js:131 +#, python-format +msgid "Change Unit Price - Unauthorized function" +msgstr "Changer le prix unitaire - Fonctionnalité non autorisée" + +#. module: pos_access_right +#. openerp-web +#: code:addons/pos_access_right/static/src/js/pos_access_right.js:83 +#, python-format +msgid "Delete Order - Unauthorized function" +msgstr "Supprimer une commande de vente - Fonctionnalité non autorisée" + +#. module: pos_access_right +#. openerp-web +#: code:addons/pos_access_right/static/src/js/pos_access_right.js:124 +#, python-format +msgid "Discount - Unauthorized function" +msgstr "Remise - Fonctionnalité non autorisée" + +#. module: pos_access_right +#. openerp-web +#: code:addons/pos_access_right/static/src/js/pos_access_right.js:72 +#, python-format +msgid "Many Orders - Unauthorized function" +msgstr "Plusieurs Commandes simultanées - Fonctionnalité non autorisée" + +#. module: pos_access_right +#. openerp-web +#: code:addons/pos_access_right/static/src/js/pos_access_right.js:110 +#, python-format +msgid "Negative Quantity - Unauthorized function" +msgstr "Quantité négative - Fonctionnalité non autorisée" + +#. module: pos_access_right +#. openerp-web +#: code:addons/pos_access_right/static/src/js/pos_access_right.js:73 +#: code:addons/pos_access_right/static/src/js/pos_access_right.js:84 +#: code:addons/pos_access_right/static/src/js/pos_access_right.js:111 +#: code:addons/pos_access_right/static/src/js/pos_access_right.js:125 +#: code:addons/pos_access_right/static/src/js/pos_access_right.js:132 +#, python-format +msgid "Please ask your manager to do it." +msgstr "Veuillez vous rapprocher d'un responsable" + +#. module: pos_access_right +#: model:ir.model.fields,field_description:pos_access_right.field_pos_config_group_discount_id +msgid "Point of Sale - Allow Discount" +msgstr "Point de vente - Autoriser les remises" + +#. module: pos_access_right +#: model:ir.model.fields,field_description:pos_access_right.field_pos_config_group_negative_qty_id +msgid "Point of Sale - Allow Negative Quantity" +msgstr "Point de vente - Autoriser les quantités négatives" + +#. module: pos_access_right +#: model:ir.model.fields,field_description:pos_access_right.field_pos_config_group_change_unit_price_id +msgid "Point of Sale - Allow Unit Price Change" +msgstr "Point de vente - Autoriser le changement de prix unitaire" + +#. module: pos_access_right +#: model:ir.model.fields,field_description:pos_access_right.field_pos_config_group_delete_order_id +#: model:res.groups,name:pos_access_right.group_delete_order +msgid "Point of Sale - Delete Order" +msgstr "Point de Vente - Supprimer une commande" + +#. module: pos_access_right +#: model:res.groups,name:pos_access_right.group_discount +msgid "Point of Sale - Discount" +msgstr "Point de Vente - Remise" + +#. module: pos_access_right +#: model:ir.model.fields,field_description:pos_access_right.field_pos_config_group_multi_order_id +#: model:res.groups,name:pos_access_right.group_multi_order +msgid "Point of Sale - Many Orders" +msgstr "Point de Vente - Plusieurs commandes simultanées" + +#. module: pos_access_right +#: model:res.groups,name:pos_access_right.group_negative_qty +msgid "Point of Sale - Negative Quantity" +msgstr "Point de Vente - Quantité négative" + +#. module: pos_access_right +#: model:res.groups,name:pos_access_right.group_change_unit_price +msgid "Point of Sale - Unit Price Change" +msgstr "Point de Vente - Changer de prix" + +#. module: pos_access_right +#: model:ir.model.fields,help:pos_access_right.field_pos_config_group_discount_id +msgid "This field is there to pass the id of the 'PoS - Allow Discount' Group to the Point of Sale Frontend." +msgstr "Ce champs existe pour passer l'ID du groupe 'PdV - Autoriser les remises' au sein du point de vente tactile." + +#. module: pos_access_right +#: model:ir.model.fields,help:pos_access_right.field_pos_config_group_negative_qty_id +msgid "This field is there to pass the id of the 'PoS - Allow Negative Quantity' Group to the Point of Sale Frontend." +msgstr "Ce champs existe pour passer l'ID du groupe 'PdV - Autoriser les quantités négatives' au sein du point de vente tactile." + +#. module: pos_access_right +#: model:ir.model.fields,help:pos_access_right.field_pos_config_group_change_unit_price_id +msgid "This field is there to pass the id of the 'PoS - Allow Unit Price Change' Group to the Point of Sale Frontend." +msgstr "Ce champs existe pour passer l'ID du groupe 'PdV - Autoriser les changements de prix unitaires' au sein du point de vente tactile." + +#. module: pos_access_right +#: model:ir.model.fields,help:pos_access_right.field_pos_config_group_delete_order_id +msgid "This field is there to pass the id of the 'PoS - Delete Order' Group to the Point of Sale Frontend." +msgstr "Ce champs existe pour passer l'ID du groupe 'PdV - Supprimer une commande' au sein du point de vente tactile." + +#. module: pos_access_right +#: model:ir.model.fields,help:pos_access_right.field_pos_config_group_multi_order_id +msgid "This field is there to pass the id of the 'PoS - Many Orders Group to the Point of Sale Frontend." +msgstr "Ce champs existe pour passer l'ID du groupe 'PdV - Plusieurs commandes' au sein du point de vente tactile." + +#. module: pos_access_right +#: model:ir.model,name:pos_access_right.model_pos_config +msgid "pos.config" +msgstr "pos.config" + diff --git a/pos_access_right/models/__init__.py b/pos_access_right/models/__init__.py new file mode 100644 index 00000000..e77b6015 --- /dev/null +++ b/pos_access_right/models/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import pos_config diff --git a/pos_access_right/models/pos_config.py b/pos_access_right/models/pos_config.py new file mode 100644 index 00000000..be39876c --- /dev/null +++ b/pos_access_right/models/pos_config.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2016-Today: La Louve () +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import fields, models, api + + +class PosConfig(models.Model): + _inherit = 'pos.config' + + group_negative_qty_id = fields.Many2one( + comodel_name='res.groups', + compute='_compute_group_negative_qty_id', + string='Point of Sale - Allow Negative Quantity', + help="This field is there to pass the id of the 'PoS - Allow Negative" + " Quantity' Group to the Point of Sale Frontend.") + + group_discount_id = fields.Many2one( + comodel_name='res.groups', + compute='_compute_group_discount_id', + string='Point of Sale - Allow Discount', + help="This field is there to pass the id of the 'PoS - Allow Discount'" + " Group to the Point of Sale Frontend.") + + group_change_unit_price_id = fields.Many2one( + comodel_name='res.groups', + compute='_compute_group_change_unit_price_id', + string='Point of Sale - Allow Unit Price Change', + help="This field is there to pass the id of the 'PoS - Allow Unit" + " Price Change' Group to the Point of Sale Frontend.") + + group_multi_order_id = fields.Many2one( + comodel_name='res.groups', + compute='_compute_group_multi_order_id', + string='Point of Sale - Many Orders', + help="This field is there to pass the id of the 'PoS - Many Orders" + " Group to the Point of Sale Frontend.") + + group_delete_order_id = fields.Many2one( + comodel_name='res.groups', + compute='_compute_group_delete_order_id', + string='Point of Sale - Delete Order', + help="This field is there to pass the id of the 'PoS - Delete Order'" + " Group to the Point of Sale Frontend.") + + @api.multi + def _compute_group_negative_qty_id(self): + for config in self: + self.group_negative_qty_id = \ + self.env.ref('pos_access_right.group_negative_qty') + + @api.multi + def _compute_group_discount_id(self): + for config in self: + self.group_discount_id = \ + self.env.ref('pos_access_right.group_discount') + + @api.multi + def _compute_group_change_unit_price_id(self): + for config in self: + self.group_change_unit_price_id = \ + self.env.ref('pos_access_right.group_change_unit_price') + + @api.multi + def _compute_group_multi_order_id(self): + for config in self: + self.group_multi_order_id = \ + self.env.ref('pos_access_right.group_multi_order') + + @api.multi + def _compute_group_delete_order_id(self): + for config in self: + self.group_delete_order_id = \ + self.env.ref('pos_access_right.group_delete_order') diff --git a/pos_access_right/security/res_groups.yml b/pos_access_right/security/res_groups.yml new file mode 100644 index 00000000..3813e4e8 --- /dev/null +++ b/pos_access_right/security/res_groups.yml @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2016-Today: La Louve () +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + + +- !record {model: res.groups, id: group_negative_qty}: + name: Point of Sale - Negative Quantity + category_id: base.module_category_usability + +- !record {model: res.groups, id: group_discount}: + name: Point of Sale - Discount + category_id: base.module_category_usability + +- !record {model: res.groups, id: group_change_unit_price}: + name: Point of Sale - Unit Price Change + category_id: base.module_category_usability + +- !record {model: res.groups, id: group_multi_order}: + name: Point of Sale - Many Orders + category_id: base.module_category_usability + +- !record {model: res.groups, id: group_delete_order}: + name: Point of Sale - Delete Order + category_id: base.module_category_usability diff --git a/pos_access_right/static/description/admin_numpad.png b/pos_access_right/static/description/admin_numpad.png new file mode 100644 index 00000000..08c889c2 Binary files /dev/null and b/pos_access_right/static/description/admin_numpad.png differ diff --git a/pos_access_right/static/description/demo_error.png b/pos_access_right/static/description/demo_error.png new file mode 100644 index 00000000..7c86cea2 Binary files /dev/null and b/pos_access_right/static/description/demo_error.png differ diff --git a/pos_access_right/static/description/demo_numpad.png b/pos_access_right/static/description/demo_numpad.png new file mode 100644 index 00000000..448dc2fb Binary files /dev/null and b/pos_access_right/static/description/demo_numpad.png differ diff --git a/pos_access_right/static/description/icon.png b/pos_access_right/static/description/icon.png new file mode 100644 index 00000000..2c83d710 Binary files /dev/null and b/pos_access_right/static/description/icon.png differ diff --git a/pos_access_right/static/description/new_groups.png b/pos_access_right/static/description/new_groups.png new file mode 100644 index 00000000..0d610e2e Binary files /dev/null and b/pos_access_right/static/description/new_groups.png differ diff --git a/pos_access_right/static/src/css/pos_access_right.css b/pos_access_right/static/src/css/pos_access_right.css new file mode 100644 index 00000000..e10fe1d7 --- /dev/null +++ b/pos_access_right/static/src/css/pos_access_right.css @@ -0,0 +1,13 @@ +/* + Copyright (C) 2016-Today: La Louve () + @author: Sylvain LE GAL (https://twitter.com/legalsylvain) + License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +*/ + +.pos-disabled-mode { + color: #bbb !important; + background-color: #d3d3d3 !important; +} +.pos-disabled-mode:hover { + background: #e2e2e2 !important; +} diff --git a/pos_access_right/static/src/js/pos_access_right.js b/pos_access_right/static/src/js/pos_access_right.js new file mode 100644 index 00000000..2781c888 --- /dev/null +++ b/pos_access_right/static/src/js/pos_access_right.js @@ -0,0 +1,140 @@ +/* + Copyright (C) 2016-Today: La Louve () + @author: Sylvain LE GAL (https://twitter.com/legalsylvain) + License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +*/ + + +odoo.define('pos_access_right.pos_access_right', function (require) { + "use strict"; + + var screens = require('point_of_sale.screens'); + var chrome = require('point_of_sale.chrome'); + var models = require('point_of_sale.models'); + var gui = require('point_of_sale.gui'); + var core = require('web.core'); + var _t = core._t; + +/* ******************************************************** +point_of_sale.gui +******************************************************** */ + + // New function 'display_access_right' to display disabled functions + gui.Gui.prototype.display_access_right = function(user){ + if (user.groups_id.indexOf(this.pos.config.group_negative_qty_id[0]) != -1){ + $('.numpad-minus').removeClass('pos-disabled-mode'); + } + else{ + $('.numpad-minus').addClass('pos-disabled-mode'); + } + if (user.groups_id.indexOf(this.pos.config.group_discount_id[0]) != -1){ + $(".mode-button[data-mode='discount']").removeClass('pos-disabled-mode'); + } + else{ + $(".mode-button[data-mode='discount']").addClass('pos-disabled-mode'); + } + if (user.groups_id.indexOf(this.pos.config.group_change_unit_price_id[0]) != -1){ + $(".mode-button[data-mode='price']").removeClass('pos-disabled-mode'); + } + else{ + $(".mode-button[data-mode='price']").addClass('pos-disabled-mode'); + } + }; + + +/* ******************************************************** +point_of_sale.models +******************************************************** */ + + // load extra data from 'pos_config' (ids of new groups) + models.load_fields("pos.config", "group_negative_qty_id"); + models.load_fields("pos.config", "group_discount_id"); + models.load_fields("pos.config", "group_change_unit_price_id"); + models.load_fields("pos.config", "group_multi_order_id"); + models.load_fields("pos.config", "group_delete_order_id"); + + // Overload 'set_cashier' function to display correctly + // unauthorized function after cashier changed + var _set_cashier_ = models.PosModel.prototype.set_cashier; + models.PosModel.prototype.set_cashier = function(user){ + this.gui.display_access_right(user); + _set_cashier_.call(this, user); + }; + +/* ******************************************************** +chrome.OrderSelectorWidget +******************************************************** */ + chrome.OrderSelectorWidget.include({ + + neworder_click_handler: function(event, $el) { + if (this.pos.get_cashier().groups_id.indexOf(this.pos.config.group_multi_order_id[0]) == -1) { + this.gui.show_popup('error',{ + 'title': _t('Many Orders - Unauthorized function'), + 'body': _t('Please ask your manager to do it.'), + }); + } + else { + return this._super(); + } + }, + deleteorder_click_handler: function(event, $el) { + if (this.pos.get_cashier().groups_id.indexOf(this.pos.config.group_delete_order_id[0]) == -1) { + this.gui.show_popup('error',{ + 'title': _t('Delete Order - Unauthorized function'), + 'body': _t('Please ask your manager to do it.'), + }); + } + else { + return this._super(); + } + }, + }); + + +/* ******************************************************** +screens.NumpadWidget +******************************************************** */ + screens.NumpadWidget.include({ + + // Overload 'start' function to display correctly unauthorized function + // at the beginning of the session, based on current user + start: function() { + this._super(); + this.gui.display_access_right(this.pos.get_cashier()); + }, + + // block '+/-' button if user doesn't belong to the correct group + clickSwitchSign: function() { + if (this.pos.get_cashier().groups_id.indexOf(this.pos.config.group_negative_qty_id[0]) == -1) { + this.gui.show_popup('error',{ + 'title': _t('Negative Quantity - Unauthorized function'), + 'body': _t('Please ask your manager to do it.'), + }); + } + else { + return this._super(); + } + }, + + // block 'discount' or 'price' button if user doesn't belong to the correct group + clickChangeMode: function(event) { + if (event.currentTarget.attributes['data-mode'].nodeValue == 'discount' && + this.pos.get_cashier().groups_id.indexOf(this.pos.config.group_discount_id[0]) == -1) { + this.gui.show_popup('error',{ + 'title': _t('Discount - Unauthorized function'), + 'body': _t('Please ask your manager to do it.'), + }); + } + else if (event.currentTarget.attributes['data-mode'].nodeValue == 'price' && + this.pos.get_cashier().groups_id.indexOf(this.pos.config.group_change_unit_price_id[0]) == -1) { + this.gui.show_popup('error',{ + 'title': _t('Change Unit Price - Unauthorized function'), + 'body': _t('Please ask your manager to do it.'), + }); + } + else { + return this._super(event); + } + }, + }); +}); diff --git a/pos_access_right/static/src/xml/templates.xml b/pos_access_right/static/src/xml/templates.xml new file mode 100644 index 00000000..37422698 --- /dev/null +++ b/pos_access_right/static/src/xml/templates.xml @@ -0,0 +1,17 @@ + + + + +