You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
477 lines
17 KiB
477 lines
17 KiB
/* Copyright 2004-2010 OpenERP SA
|
|
* Copyright 2017 RGB Consulting S.L. (https://www.rgbconsulting.com)
|
|
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
|
|
|
|
odoo.define('pos_loyalty.loyalty_program', function(require) {
|
|
"use strict"
|
|
|
|
var models = require('point_of_sale.models');
|
|
var screens = require('point_of_sale.screens');
|
|
|
|
var utils = require('web.utils');
|
|
var round_pr = utils.round_precision;
|
|
|
|
var core = require('web.core');
|
|
var QWeb = core.qweb;
|
|
var _t = core._t;
|
|
|
|
models.load_fields('res.partner', 'loyalty_points');
|
|
|
|
models.load_models([{
|
|
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', 'cumulative', '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 = {};
|
|
self.loyalty.rules_by_category_id = {};
|
|
|
|
function update_rules(rules, rule, id) {
|
|
if (!rules[id]) {
|
|
rules[id] = [rule];
|
|
} else if (rule.cumulative) {
|
|
rules[id].unshift(rule);
|
|
} else {
|
|
rules[id].push(rule);
|
|
}
|
|
}
|
|
|
|
_.each(rules, function(rule) {
|
|
if (rule.type === 'product')
|
|
update_rules(self.loyalty.rules_by_product_id, rule, rule.product_id[0])
|
|
else if (rule.type === 'category')
|
|
update_rules(self.loyalty.rules_by_category_id, rule, rule.category_id[0]);
|
|
});
|
|
},
|
|
}, {
|
|
model: 'loyalty.reward',
|
|
condition: function(self) {
|
|
return !!self.loyalty;
|
|
},
|
|
fields: ['name', 'type', 'minimum_points', 'gift_product_id', 'point_cost', 'discount_product_id', 'discount', 'discount_max', 'point_product_id'],
|
|
domain: function(self) {
|
|
return [
|
|
['loyalty_program_id', '=', self.loyalty.id]
|
|
];
|
|
},
|
|
loaded: function(self, rewards) {
|
|
self.loyalty.rewards = rewards;
|
|
self.loyalty.rewards_by_id = {};
|
|
for (var i = 0; i < rewards.length; i++) {
|
|
self.loyalty.rewards_by_id[rewards[i].id] = rewards[i];
|
|
}
|
|
},
|
|
}, ], {
|
|
'after': 'product.product'
|
|
});
|
|
|
|
var _orderline_super = models.Orderline.prototype;
|
|
models.Orderline = models.Orderline.extend({
|
|
get_reward: function() {
|
|
return this.pos.loyalty.rewards_by_id[this.reward_id];
|
|
},
|
|
set_reward: function(reward) {
|
|
this.reward_id = reward.id;
|
|
},
|
|
export_as_JSON: function() {
|
|
var json = _orderline_super.export_as_JSON.apply(this, arguments);
|
|
json.reward_id = this.reward_id;
|
|
return json;
|
|
},
|
|
init_from_JSON: function(json) {
|
|
_orderline_super.init_from_JSON.apply(this, arguments);
|
|
this.reward_id = json.reward_id;
|
|
},
|
|
});
|
|
|
|
var _order_super = models.Order.prototype;
|
|
models.Order = models.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();
|
|
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.get_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 affected by a non cumulative rule, skip the others. (non cumulative rules are put
|
|
// at the beginning of the list when they are loaded )
|
|
if (!rule.cumulative) {
|
|
overriden = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Test the category rules
|
|
if (product.pos_categ_id) {
|
|
var category = this.pos.db.get_category_by_id(product.pos_categ_id[0]);
|
|
while (category && !overriden) {
|
|
var rules = this.pos.loyalty.rules_by_category_id[category.id] || [];
|
|
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.cumulative) {
|
|
overriden = true;
|
|
break;
|
|
}
|
|
}
|
|
var _category = category;
|
|
category = this.pos.db.get_category_by_id(this.pos.db.get_category_parent_id(category.id));
|
|
if (_category === category) {
|
|
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();
|
|
var rounding = this.pos.loyalty.rounding;
|
|
var points = 0;
|
|
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var line = lines[i];
|
|
var reward = line.get_reward();
|
|
if (reward) {
|
|
if (reward.type === 'gift') {
|
|
points += round_pr(line.get_quantity() * reward.point_cost, rounding);
|
|
} else if (reward.type === 'discount') {
|
|
points += reward.point_cost;
|
|
} else if (reward.type === 'resale') {
|
|
points += (-line.get_quantity());
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
},
|
|
|
|
has_discount_reward: function() {
|
|
var res = false;
|
|
var lines = this.get_orderlines();
|
|
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var line = lines[i];
|
|
var reward = line.get_reward();
|
|
if (reward && reward.type === 'discount') {
|
|
res = true;
|
|
break;
|
|
}
|
|
}
|
|
return res;
|
|
},
|
|
|
|
/* The list of rewards that the current customer can get */
|
|
get_available_rewards: function() {
|
|
var client = this.get_client();
|
|
if (!client) {
|
|
return [];
|
|
}
|
|
var rewards = [];
|
|
var discount_reward_set = this.has_discount_reward();
|
|
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;
|
|
} else if (reward.type === 'discount' &&
|
|
(discount_reward_set || 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) {
|
|
return;
|
|
}
|
|
|
|
this.add_product(product, {
|
|
price: 0,
|
|
quantity: 1,
|
|
merge: false,
|
|
extras: {
|
|
reward_id: reward.id
|
|
},
|
|
});
|
|
|
|
} else if (reward.type === 'discount') {
|
|
|
|
var crounding = this.pos.currency.rounding;
|
|
var order_total = this.get_total_with_tax();
|
|
var discount = round_pr(order_total * reward.discount, crounding);
|
|
var discount_max = reward.discount_max
|
|
|
|
if (discount_max && discount > discount_max) {
|
|
discount = discount_max;
|
|
}
|
|
|
|
var product = this.pos.db.get_product_by_id(reward.discount_product_id[0]);
|
|
|
|
if (!product) {
|
|
return;
|
|
}
|
|
|
|
this.add_product(product, {
|
|
price: -discount,
|
|
quantity: 1,
|
|
merge: false,
|
|
extras: {
|
|
reward_id: reward.id
|
|
},
|
|
});
|
|
|
|
} else if (reward.type === 'resale') {
|
|
|
|
var lrounding = this.pos.loyalty.rounding;
|
|
var crounding = this.pos.currency.rounding;
|
|
var spendable = this.get_spendable_points();
|
|
|
|
var order_total = this.get_total_with_tax();
|
|
var product = this.pos.db.get_product_by_id(reward.point_product_id[0]);
|
|
|
|
if (!product) {
|
|
return;
|
|
}
|
|
|
|
if (round_pr(spendable * product.price, crounding) > order_total) {
|
|
spendable = round_pr(Math.floor(order_total / product.price), lrounding);
|
|
}
|
|
|
|
if (spendable < 0.00001) {
|
|
return;
|
|
}
|
|
|
|
this.add_product(product, {
|
|
quantity: -spendable,
|
|
merge: false,
|
|
extras: {
|
|
reward_id: reward.id
|
|
},
|
|
});
|
|
}
|
|
},
|
|
|
|
finalize: function() {
|
|
var client = this.get_client();
|
|
if (client) {
|
|
client.loyalty_points = this.get_new_total_points();
|
|
this.pos.gui.screen_instances.clientlist.partner_cache.clear_node(client.id);
|
|
}
|
|
_order_super.finalize.apply(this, arguments);
|
|
},
|
|
|
|
export_for_printing: function() {
|
|
var json = _order_super.export_for_printing.apply(this, arguments);
|
|
if (this.pos.loyalty && this.get_client()) {
|
|
json.loyalty = {
|
|
rounding: this.pos.loyalty.rounding || 1,
|
|
name: this.pos.loyalty.name,
|
|
client: this.get_client().name,
|
|
points_won: this.get_won_points(),
|
|
points_spent: this.get_spent_points(),
|
|
points_total: this.get_new_total_points(),
|
|
};
|
|
}
|
|
return json;
|
|
},
|
|
|
|
export_as_JSON: function() {
|
|
var json = _order_super.export_as_JSON.apply(this, arguments);
|
|
json.loyalty_points = this.get_new_points();
|
|
return json;
|
|
},
|
|
});
|
|
|
|
var LoyaltyButton = screens.ActionButtonWidget.extend({
|
|
template: 'LoyaltyButton',
|
|
button_click: function() {
|
|
var self = this;
|
|
var order = this.pos.get_order();
|
|
var client = order.get_client();
|
|
if (!client) {
|
|
this.gui.show_screen('clientlist');
|
|
return;
|
|
}
|
|
|
|
var rewards = order.get_available_rewards();
|
|
if (rewards.length === 0) {
|
|
this.gui.show_popup('error', {
|
|
'title': _t('No Rewards Available'),
|
|
'body': _t('There are no rewards available for this customer as part of the loyalty program'),
|
|
});
|
|
} else if (rewards.length === 1 && this.pos.loyalty.rewards.length === 1) {
|
|
order.apply_reward(rewards[0]);
|
|
} else {
|
|
var list = [];
|
|
for (var i = 0; i < rewards.length; i++) {
|
|
list.push({
|
|
label: rewards[i].name,
|
|
item: rewards[i],
|
|
});
|
|
}
|
|
this.gui.show_popup('selection', {
|
|
'title': _t('Please select a reward'),
|
|
'list': list,
|
|
'confirm': function(reward) {
|
|
order.apply_reward(reward);
|
|
},
|
|
});
|
|
}
|
|
},
|
|
});
|
|
|
|
screens.define_action_button({
|
|
'name': 'loyalty',
|
|
'widget': LoyaltyButton,
|
|
'condition': function() {
|
|
return this.pos.loyalty && this.pos.loyalty.rewards.length;
|
|
},
|
|
});
|
|
|
|
screens.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');
|
|
}
|
|
|
|
if (this.pos.loyalty &&
|
|
order.get_client() &&
|
|
this.getParent().action_buttons &&
|
|
this.getParent().action_buttons.loyalty) {
|
|
|
|
var rewards = order.get_available_rewards();
|
|
this.getParent().action_buttons.loyalty.highlight(!!rewards.length);
|
|
}
|
|
},
|
|
});
|
|
});
|