diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index 90393d3299e..20276d285a8 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -506,6 +506,7 @@ td { .pos .control-buttons { display: flex; + flex-flow: row wrap; padding: 8px 13px 0px 11px; } .pos .control-button { @@ -521,6 +522,7 @@ td { font-size: 18px; margin-left: 6px; cursor: pointer; + overflow: hidden; } .pos .control-button .fa{ margin-right: 4px; @@ -1921,6 +1923,17 @@ td { box-sizing: border-box; width: 80%; } +.pos .popup.popup-selection .selection { + overflow-y: auto; + max-height: 270px; +} +.pos .popup.popup-selection .selection .button { + float: none; + display: block; + width: auto; + margin: 8px; +} + /* ********* The Webkit Scrollbar ********* */ .pos *::-webkit-scrollbar{ diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 8b59049c97c..a8d2394d1f7 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -375,6 +375,13 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }else{ var model = self.models[index]; self.pos_widget.loading_message(_t('Loading')+' '+(model.label || model.model || ''), progress); + + var cond = typeof model.condition === 'function' ? model.condition(self,tmp) : true; + if (!cond) { + load_model(index+1); + return; + } + 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; diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index dc7a44ab8c5..e58170ab9ce 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -378,6 +378,53 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, }); + /** + * A popup that allows the user to select one item from a list. + * + * show_popup('selection',{ + * message: 'Pick an Option', + * message: "Popup Title", + * list: [ + * { label: 'foobar', item: 45 }, + * { label: 'bar foo', item: 'stuff' }, + * ], + * confirm: function(item) { + * // get the item selected by the user. + * }, + * cancel: function(){ + * // user chose nothing + * } + * }); + */ + + module.SelectionPopupWidget = module.PopUpWidget.extend({ + template: 'SelectionPopupWidget', + show: function(options){ + var self = this; + this._super(); + + this.message = options.message || ''; + this.list = options.list || []; + this.renderElement(); + + this.$('.button.cancel').click(function(){ + self.pos_widget.screen_selector.close_popup(); + if (options.cancel){ + options.cancel.call(self); + } + }); + + this.$('.button.item').click(function(){ + self.pos_widget.screen_selector.close_popup(); + if (options.confirm) { + var item = self.list[parseInt($(this).data('item-index'))]; + item = item ? item.item : item; + options.confirm.call(self,item); + } + }); + }, + }); + module.TextInputPopupWidget = module.PopUpWidget.extend({ template: 'TextInputPopupWidget', show: function(options){ diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js index 423c34ced4f..111292c82d1 100644 --- a/addons/point_of_sale/static/src/js/widgets.js +++ b/addons/point_of_sale/static/src/js/widgets.js @@ -1006,6 +1006,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.fullscreen_popup = new module.FullscreenPopup(this,{}); this.fullscreen_popup.appendTo(this.$el); + this.selection_popup = new module.SelectionPopupWidget(this,{}); + this.selection_popup.appendTo(this.$el); + this.textinput_popup = new module.TextInputPopupWidget(this,{}); this.textinput_popup.appendTo(this.$el); @@ -1081,6 +1084,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa 'textinput': this.textinput_popup, 'confirm': this.confirm_popup, 'fullscreen': this.fullscreen_popup, + 'selection': this.selection_popup, 'unsent-orders': this.unsent_orders_popup, }, default_screen: 'products', diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index c766a7a8366..4012547066b 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -958,6 +958,26 @@ + + + +
diff --git a/addons/pos_loyalty/__init__.py b/addons/pos_loyalty/__init__.py new file mode 100644 index 00000000000..ec876f764af --- /dev/null +++ b/addons/pos_loyalty/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +import loyalty + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: + diff --git a/addons/pos_loyalty/__openerp__.py b/addons/pos_loyalty/__openerp__.py new file mode 100644 index 00000000000..7b7985ae9f1 --- /dev/null +++ b/addons/pos_loyalty/__openerp__.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## + + +{ + 'name': 'Loyalty Program', + 'version': '1.0', + 'category': 'Point of Sale', + 'sequence': 6, + 'summary': 'Loyalty Program for the Point of Sale ', + 'description': """ + +======================= + +This module allows you to define a loyalty program in +the point of sale, where the customers earn loyalty points +and get rewards. + +""", + 'author': 'OpenERP SA', + 'depends': ['point_of_sale'], + 'data': [ + 'views/views.xml', + 'views/templates.xml' + ], + 'qweb': ['static/src/xml/loyalty.xml'], + 'installable': True, + 'auto_install': False, +} + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/pos_loyalty/loyalty.py b/addons/pos_loyalty/loyalty.py new file mode 100644 index 00000000000..ce80f6e16d2 --- /dev/null +++ b/addons/pos_loyalty/loyalty.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +import logging + +import openerp + +from openerp import tools +from openerp.osv import fields, osv +from openerp.tools.translate import _ + +_logger = logging.getLogger(__name__) + +class loyalty_program(osv.osv): + _name = 'loyalty.program' + _columns = { + 'name' : fields.char('Loyalty Program Name', size=32, select=1, + required=True, help="An internal identification for the loyalty program configuration"), + 'pp_currency': fields.float('Points per currency',help="How many loyalty points are given to the customer by sold currency"), + 'pp_product': fields.float('Points per product',help="How many loyalty points are given to the customer by product sold"), + 'pp_order': fields.float('Points per order',help="How many loyalty points are given to the customer for each sale or order"), + 'rounding': fields.float('Points Rounding', help="The loyalty point amounts are rounded to multiples of this value."), + 'rule_ids': fields.one2many('loyalty.rule','loyalty_program_id','Rules'), + 'reward_ids': fields.one2many('loyalty.reward','loyalty_program_id','Rewards'), + + } + _defaults = { + 'rounding': 1, + } + +class loyalty_rule(osv.osv): + _name = 'loyalty.rule' + _columns = { + 'name': fields.char('Name', size=32, select=1, required=True, help="An internal identification for this loyalty program rule"), + 'loyalty_program_id': fields.many2one('loyalty.program', 'Loyalty Program', help='The Loyalty Program this exception belongs to'), + 'product_id': fields.many2one('product.product','Target Product', help='The product affected by the rule'), + 'override': fields.boolean('Override Base Rules', help='A product targetted by this rule will be ignored by other loyalty rules, including the default one'), + 'pp_product': fields.float('Points per product', help='How many points the product will earn per product ordered'), + 'pp_currency': fields.float('Points per currency', help='How many points the product will earn per value sold'), + } + +class loyalty_reward(osv.osv): + _name = 'loyalty.reward' + _columns = { + 'name': fields.char('Name', size=32, select=1, required=True, help='An internal identification for this loyalty reward'), + 'loyalty_program_id': fields.many2one('loyalty.program', 'Loyalty Program', help='The Loyalty Program this reward belongs to'), + 'minimum_points': fields.float('Minimum Points', help='The minimum amount of points the customer must have to be eligible for this reward'), + 'type': fields.selection((('gift','Gift'),('discount','Discount')), 'Type', required=True, help='The type of the reward'), + 'gift_product_id': fields.many2one('product.product','Gift Product', help='The product given as a reward'), + 'point_cost': fields.float('Point Cost', help='The cost of the reward'), + 'discount_product_id': fields.many2one('product.product','Discount Product', help='The product used to apply discounts'), + 'discount': fields.float('Discount',help='The discount percentage'), + } + + def _check_gift_product(self, cr, uid, ids, context=None): + for reward in self.browse(cr, uid, ids, context=context): + print reward.type + if reward.type == 'gift': + return bool(reward.gift_product_id) + else: + return True + + def _check_discount_product(self, cr, uid, ids, context=None): + for reward in self.browse(cr, uid, ids, context=context): + print reward.type + if reward.type == 'discount': + return bool(reward.discount_product_id) + else: + return True + + _constraints = [ + (_check_gift_product, "The gift product field is mandatory for gift rewards", ["type","gift_product_id"]), + (_check_discount_product, "The discount product field is mandatory for discount rewards", ["type","discount_product_id"]), + ] + +class pos_config(osv.osv): + _inherit = 'pos.config' + _columns = { + 'loyalty_id': fields.many2one('loyalty.program','Loyalty Program', help='The loyalty program used by this point_of_sale'), + + } + +class res_partner(osv.osv): + _inherit = 'res.partner' + _columns = { + 'loyalty_points': fields.float('Loyalty Points', help='The loyalty points the user won as part of a Loyalty Program') + } + +class pos_order(osv.osv): + _inherit = 'pos.order' + + _columns = { + 'loyalty_points': fields.float('Loyalty Points', help='The amount of Loyalty points the customer won or lost with this order'), + } + + def _order_fields(self, cr, uid, ui_order, context=None): + fields = super(pos_order,self)._order_fields(cr,uid,ui_order,context) + fields['loyalty_points'] = ui_order.get('loyalty_points',0) + return fields + + def create_from_ui(self, cr, uid, orders, context=None): + ids = super(pos_order,self).create_from_ui(cr,uid,orders,context=context) + for order in orders: + if order['data']['loyalty_points'] != 0 and order['data']['partner_id']: + partner = self.pool.get('res.partner').browse(cr,uid,order['data']['partner_id'], context=context) + partner.write({'loyalty_points': partner['loyalty_points'] + order['data']['loyalty_points']}) + + return ids + + + + + + diff --git a/addons/pos_loyalty/static/src/css/loyalty.css b/addons/pos_loyalty/static/src/css/loyalty.css new file mode 100644 index 00000000000..4d141918617 --- /dev/null +++ b/addons/pos_loyalty/static/src/css/loyalty.css @@ -0,0 +1,21 @@ + +.pos .order .summary .loyalty-points{ + margin-left: 20px; + float: left; + padding: 10px; + max-width: 216px; + text-align: left; + color: #6EC89B; + background: rgba(110, 200, 155, 0.17); + border-radius: 3px; +} +.pos .order .summary .loyalty-points.negative{ + color: #C86E6E; + background: rgba(200, 110, 110, 0.17); +} +.pos .order .summary .loyalty-points-total { + border-top: solid 2px; + text-align: center; + padding-top: 4px; + margin-top: 4px; +} diff --git a/addons/pos_loyalty/static/src/js/loyalty.js b/addons/pos_loyalty/static/src/js/loyalty.js new file mode 100644 index 00000000000..da8b8e0ba3b --- /dev/null +++ b/addons/pos_loyalty/static/src/js/loyalty.js @@ -0,0 +1,337 @@ +openerp.pos_loyalty = function(instance){ + + var module = instance.point_of_sale; + var round_pr = instance.web.round_precision + var QWeb = instance.web.qweb; + + var models = module.PosModel.prototype.models; + for (var i = 0; i < models.length; i++) { + var model = models[i]; + if (model.model === 'product.product') { + model.fields.push('loyalty_points'); + model.fields.push('loyalty_override'); + } else if (model.model === 'res.partner') { + model.fields.push('loyalty_points'); + } else if (model.model === 'pos.config') { + // load loyalty after pos.config + models.splice(i+1,0,{ + model: 'loyalty.program', + condition: function(self){ return !!self.config.loyalty_id[0]; }, + fields: ['name','pp_currency','pp_product','pp_order','rounding'], + domain: function(self){ return [['id','=',self.config.loyalty_id[0]]]; }, + loaded: function(self,loyalties){ self.loyalty = loyalties[0]; }, + },{ + model: 'loyalty.rule', + condition: function(self){ return !!self.loyalty; }, + fields: ['name','type','product_id','category_id','override','pp_product','pp_currency'], + domain: function(self){ return [['loyalty_program_id','=',self.loyalty.id]]; }, + loaded: function(self,rules){ + + self.loyalty.rules = rules; + self.loyalty.rules_by_product_id = {}; + + for (var i = 0; i < rules.length; i++){ + var rule = rules[i]; + if (!self.loyalty.rules_by_product_id[rule.product_id[0]]) { + self.loyalty.rules_by_product_id[rule.product_id[0]] = [rule]; + } else if (rule.override) { + self.loyalty.rules_by_product_id[rule.product_id[0]].unshift(rule); + } else { + self.loyalty.rules_by_product_id[rule.product_id[0]].push(rule); + } + } + }, + },{ + model: 'loyalty.reward', + condition: function(self){ return !!self.loyalty; }, + fields: ['name','type','minimum_points','gift_product_id','point_cost','discount_product_id','discount'], + domain: function(self){ return [['loyalty_program_id','=',self.loyalty.id]]; }, + loaded: function(self,rewards){ + self.loyalty.rewards = rewards; + }, + }); + } + } + + var _super = module.Order; + module.Order = module.Order.extend({ + + /* The total of points won, excluding the points spent on rewards */ + get_won_points: function(){ + if (!this.pos.loyalty || !this.get('client')) { + return 0; + } + + var orderLines = this.get('orderLines').models; + var rounding = this.pos.loyalty.rounding; + + var product_sold = 0; + var total_sold = 0; + var total_points = 0; + + for (var i = 0; i < orderLines.length; i++) { + var line = orderLines[i]; + var product = line.get_product(); + var rules = this.pos.loyalty.rules_by_product_id[product.id] || []; + var overriden = false; + + if (line.reward) { // Reward products are ignored + continue; + } + + for (var j = 0; j < rules.length; j++) { + var rule = rules[j]; + total_points += round_pr(line.get_quantity() * rule.pp_product, rounding); + total_points += round_pr(line.get_price_with_tax() * rule.pp_currency, rounding); + if (rule.override) { + overriden = true; + break; + } + } + + if (!overriden) { + product_sold += line.get_quantity(); + total_sold += line.get_price_with_tax(); + } + } + + total_points += round_pr( total_sold * this.pos.loyalty.pp_currency, rounding ); + total_points += round_pr( product_sold * this.pos.loyalty.pp_product, rounding ); + total_points += round_pr( this.pos.loyalty.pp_order, rounding ); + + return total_points; + }, + + /* The total number of points spent on rewards */ + get_spent_points: function() { + if (!this.pos.loyalty || !this.get('client')) { + return 0; + } else { + var lines = this.get('orderLines').models; + var rounding = this.pos.loyalty.rounding; + var points = 0; + + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + if (line.reward) { + if (line.reward.type === 'gift') { + points += round_pr(line.get_quantity() * line.reward.point_cost, rounding); + } else if (line.reward.type === 'discount') { + points += round_pr(-line.get_price_with_tax() * line.reward.point_cost, rounding); + } + } + } + return points; + } + }, + + /* The total number of points lost or won after the order is validated */ + get_new_points: function() { + if (!this.pos.loyalty || !this.get('client')) { + return 0; + } else { + return round_pr(this.get_won_points() - this.get_spent_points(), this.pos.loyalty.rounding); + } + }, + + /* The total number of points that the customer will have after this order is validated */ + get_new_total_points: function() { + if (!this.pos.loyalty || !this.get('client')) { + return 0; + } else { + return round_pr(this.get('client').loyalty_points + this.get_new_points(), this.pos.loyalty.rounding); + } + }, + + /* The number of loyalty points currently owned by the customer */ + get_current_points: function(){ + return this.get('client') ? this.get('client').loyalty_points : 0; + }, + + /* The total number of points spendable on rewards */ + get_spendable_points: function(){ + if (!this.pos.loyalty || !this.get('client')) { + return 0; + } else { + return round_pr(this.get('client').loyalty_points - this.get_spent_points(), this.pos.loyalty.rounding); + } + }, + + /* The list of rewards that the current customer can get */ + get_available_rewards: function(){ + var client = this.get('client'); + if (!client) { + return []; + } + + var rewards = []; + for (var i = 0; i < this.pos.loyalty.rewards.length; i++) { + var reward = this.pos.loyalty.rewards[i]; + if (reward.minimum_points > this.get_spendable_points()) { + continue; + } else if(reward.type === 'gift' && reward.point_cost > this.get_spendable_points()) { + continue; + } + rewards.push(reward); + } + return rewards; + }, + + apply_reward: function(reward){ + var client = this.get('client'); + if (!client) { + return; + } else if (reward.type === 'gift') { + var product = this.pos.db.get_product_by_id(reward.gift_product_id[0]); + if (!product) { + this.pos.pos_widget.screen_selector.show_popup('error',{ + 'message':'Configuration Error', + 'comment':'The product associated with the reward "'+reward.name+'" could not be found. Make sure it is available for sale in the point of sale.', + }); + return; + } + + var line = this.addProduct(product, { + price: 0, + quantity: 1, + merge: false, + extras: { reward: reward }, + }); + + } else if (reward.type === 'discount') { + + var lrounding = this.pos.loyalty.rounding; + var crounding = this.pos.currency.rounding; + var spendable = this.get_spendable_points(); + var order_total = this.getTotalTaxIncluded(); + var discount = round_pr(order_total * reward.discount,crounding); + + if ( round_pr(discount * reward.point_cost,lrounding) > spendable ) { + discount = round_pr(Math.floor( spendable / reward.point_cost ), crounding); + } + + var product = this.pos.db.get_product_by_id(reward.discount_product_id[0]); + if (!product) { + this.pos.pos_widget.screen_selector.show_popup('error',{ + 'message':'Configuration Error', + 'comment':'The product associated with the reward "'+reward.name+'" could not be found. Make sure it is available for sale in the point of sale.', + }); + return; + } + + var line = this.addProduct(product, { + price: -discount, + quantity: 1, + merge: false, + extras: { reward: reward }, + }); + } + }, + + validate: function(){ + var client = this.get('client'); + if ( client ) { + client.loyalty_points = this.get_new_total_points(); + } + _super.prototype.validate.apply(this,arguments); + }, + export_for_printing: function(){ + var json = _super.prototype.export_for_printing.apply(this,arguments); + json.loyalty_points = this.get_new_points(); + return json; + }, + export_as_JSON: function(){ + var json = _super.prototype.export_as_JSON.apply(this,arguments); + json.loyalty_points = this.get_new_points(); + return json; + }, + }); + + module.PosWidget.include({ + loyalty_reward_click: function(){ + var self = this; + var order = this.pos.get('selectedOrder'); + var client = order.get('client'); + if (!client) { + this.screen_selector.set_current_screen('clientlist'); + return; + } + + var rewards = order.get_available_rewards(); + if (rewards.length === 0) { + this.screen_selector.show_popup('error',{ + 'message': 'No Rewards Available', + 'comment': 'There are no rewards available for this customer as part of the loyalty program', + }); + return; + } else if (rewards.length === 1) { + order.apply_reward(rewards[0]); + return; + } else { + var list = []; + for (var i = 0; i < rewards.length; i++) { + list.push({ + label: rewards[i].name, + item: rewards[i], + }); + } + this.screen_selector.show_popup('selection',{ + 'message': 'Please select a reward', + 'list': list, + 'confirm': function(reward){ + order.apply_reward(reward); + }, + }); + } + }, + + build_widgets: function(){ + var self = this; + this._super(); + + if(this.pos.loyalty && this.pos.loyalty.rewards.length ){ + var button = $(QWeb.render('LoyaltyButton')); + button.click(function(){ self.loyalty_reward_click(); }); + button.appendTo(this.$('.control-buttons')); + this.$('.control-buttons').removeClass('oe_hidden'); + } + + }, + }); + + module.OrderWidget.include({ + update_summary: function(){ + this._super(); + + var order = this.pos.get_order(); + + var $loypoints = $(this.el).find('.summary .loyalty-points'); + + if(this.pos.loyalty && order.get_client()){ + var points_won = order.get_won_points(); + var points_spent = order.get_spent_points(); + var points_total = order.get_new_total_points(); + $loypoints.replaceWith($(QWeb.render('LoyaltyPoints',{ + widget: this, + rounding: this.pos.loyalty.rounding, + points_won: points_won, + points_spent: points_spent, + points_total: points_total, + }))); + $loypoints = $(this.el).find('.summary .loyalty-points'); + $loypoints.removeClass('oe_hidden'); + + if(points_total < 0){ + $loypoints.addClass('negative'); + }else{ + $loypoints.removeClass('negative'); + } + }else{ + $loypoints.empty(); + $loypoints.addClass('oe_hidden'); + } + }, + }); +}; + + diff --git a/addons/pos_loyalty/static/src/xml/loyalty.xml b/addons/pos_loyalty/static/src/xml/loyalty.xml new file mode 100644 index 00000000000..77f70475cd5 --- /dev/null +++ b/addons/pos_loyalty/static/src/xml/loyalty.xml @@ -0,0 +1,60 @@ + + + + +
+
Points
+ +
+ + +
+
+ +
+ - +
+
+
+ +
+
+
+ + +
+ Rewards +
+
+ + + +
.
+
+
+ + + + + + + + + + + +
+ Points + + + +
+
+
+ + + + Points + + + +
diff --git a/addons/pos_loyalty/views/templates.xml b/addons/pos_loyalty/views/templates.xml new file mode 100644 index 00000000000..15366d1af36 --- /dev/null +++ b/addons/pos_loyalty/views/templates.xml @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/addons/pos_loyalty/views/views.xml b/addons/pos_loyalty/views/views.xml new file mode 100644 index 00000000000..bcc431c0543 --- /dev/null +++ b/addons/pos_loyalty/views/views.xml @@ -0,0 +1,167 @@ + + + + + + pos.config.form.view + pos.config + + + + + + + + + + + + res.partner.product.property.form.inherit + res.partner + + + + + + + + + + + + pos.order.inherit + pos.order + + + + + + + + + + + + loyalty.program.form.view + loyalty.program + +
+ + +
+
+ + + + + + + + + +

Rules change how loyalty points are earned for specific products

+ + + + + + + + + + + +

Reward the customer with gifts or discounts for loyalty points

+ + + + + + + +
+
+
+
+ + + loyalty.rule.form.view + loyalty.rule + +
+
+
+ + + + + + +
+
+
+ + + loyalty.reward.form.view + loyalty.reward + +
+
+
+ + + + + + + + + + + + +
+
+
+ + + loyalty.program.form.view + loyalty.program + + + + + + + + + Loyalty Programs + ir.actions.act_window + loyalty.program + form + tree,form + +

+ Click to create a Loyalty Program. +

+ Loyalty Programs allows you customer to earn points + and rewards when doing business at your shops. +

+
+
+ + + +
+