Adil Houmadi
10 years ago
9 changed files with 905 additions and 0 deletions
-
34pos_dynamic_price/README.md
-
18pos_dynamic_price/__init__.py
-
40pos_dynamic_price/__openerp__.py
-
156pos_dynamic_price/static/src/js/db.js
-
27pos_dynamic_price/static/src/js/helper.js
-
23pos_dynamic_price/static/src/js/main.js
-
546pos_dynamic_price/static/src/js/models.js
-
41pos_dynamic_price/static/src/js/screens.js
-
20pos_dynamic_price/view/pos_dynamic_price_template.xml
@ -0,0 +1,34 @@ |
|||
Dynamic Price for Odoo Point of Sale |
|||
------------------------------------ |
|||
|
|||
### Motivation : |
|||
|
|||
Many issues report this feature. This why I took decision to start this module |
|||
|
|||
Reported issues : |
|||
|
|||
###<a href="https://github.com/odoo/odoo/issues/3579">odoo 8 POS price list discount has no effect.</a> |
|||
###<a href="https://github.com/odoo/odoo/issues/1758">ODOO POS Pricelist - Public Price & Discounted Price in Receipt</a> |
|||
###<a href="https://github.com/odoo/odoo/issues/2297">V8.0 pos gives wrong price when using min qty in pos pricelist</a> |
|||
|
|||
### Goal of the module : |
|||
|
|||
The goal of this module is to bring the pricelist computation engine to the POS. |
|||
This module loads all the necessary data into the POS in order to have a coherent behaviour (offline/online/backend). |
|||
|
|||
### Implemented features : |
|||
|
|||
- Attached pricelist on partner will take effect on the POS, which means that if we attach a pricelist to a partner. |
|||
The POS will recognize it and will compute the price according to the rule defined. |
|||
|
|||
- Fiscal Position of each partner will also be present so taxes will be correctly computed (conforming to the fiscal position). |
|||
|
|||
- Implemented Rules are : |
|||
1. (-1) : Rule based on other pricelist<br/> |
|||
2. (-2) : Rule based on supplierinfo<br/> |
|||
3. (default) : Any price type which is set on the product form<br/> |
|||
|
|||
### Missing features : |
|||
|
|||
- As you may know, product template is not fully implemented in the POS, so I decided to drop it from |
|||
this module. |
@ -0,0 +1,18 @@ |
|||
# -#- coding: utf-8 -#- |
|||
############################################################################## |
|||
# Point Of Sale - Dynamic Price for POS Odoo |
|||
# Copyright (C) 2014 Taktik (http://www.taktik.be) |
|||
# @author Adil Houmadi <ah@taktik.be> |
|||
# |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################## |
@ -0,0 +1,40 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################## |
|||
# Point Of Sale - Dynamic Price for POS Odoo |
|||
# Copyright (C) 2014 Taktik (http://www.taktik.be) |
|||
# @author Adil Houmadi <ah@taktik.be> |
|||
# |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################## |
|||
{ |
|||
'name': 'POS Dynamic Price', |
|||
'version': '1.0.0', |
|||
'category': 'Point Of Sale', |
|||
'sequence': 1, |
|||
'author': 'Adil Houmadi @Taktik', |
|||
'summary': 'Dyanmic Price Point of sale', |
|||
'description': """ |
|||
New features for the Point Of Sale: |
|||
============================================= |
|||
Dynamic price on the point of sale |
|||
""", |
|||
'depends': [ |
|||
"point_of_sale", |
|||
], |
|||
'data': [ |
|||
"view/pos_dynamic_price_template.xml", |
|||
], |
|||
'installable': True, |
|||
'application': False, |
|||
'auto_install': False, |
|||
} |
@ -0,0 +1,156 @@ |
|||
/****************************************************************************** |
|||
* Point Of Sale - Dynamic Price for POS Odoo |
|||
* Copyright (C) 2014 Taktik (http://www.taktik.be)
|
|||
* @author Adil Houmadi <ah@taktik.be> |
|||
* |
|||
* 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 <http://www.gnu.org/licenses/>.
|
|||
* |
|||
******************************************************************************/ |
|||
function pdp_db(instance, module) { |
|||
|
|||
module.PosDB = module.PosDB.extend({ |
|||
init: function (options) { |
|||
options = options || {}; |
|||
this._super(options); |
|||
this.default_pricelist_id = 0; |
|||
this.pricelist_by_id = {}; |
|||
this.pricelist_version_by_id = {}; |
|||
this.pricelist_item_by_id = {}; |
|||
this.pricelist_item_sorted = []; |
|||
this.product_catrgory_by_id = {}; |
|||
this.product_catrgory_children = {}; |
|||
this.product_catrgory_ancestors = {}; |
|||
this.product_price_type_by_id = {}; |
|||
this.supplierinfo_by_id = {}; |
|||
this.pricelist_partnerinfo_by_id = {}; |
|||
this.fiscal_position_tax_by_id = {}; |
|||
}, |
|||
add_fiscal_position_taxes: function (fiscal_position_taxes) { |
|||
if (!(fiscal_position_taxes instanceof Array)) { |
|||
fiscal_position_taxes = [fiscal_position_taxes]; |
|||
} |
|||
for (var i = 0, len = fiscal_position_taxes.length; i < len; i++) { |
|||
var fiscal_position_tax = fiscal_position_taxes[i]; |
|||
this.fiscal_position_tax_by_id[fiscal_position_tax.id] = fiscal_position_tax; |
|||
} |
|||
}, |
|||
add_pricelist_partnerinfo: function (pricelist_partnerinfos) { |
|||
if (!(pricelist_partnerinfos instanceof Array)) { |
|||
pricelist_partnerinfos = [pricelist_partnerinfos]; |
|||
} |
|||
for (var i = 0, len = pricelist_partnerinfos.length; i < len; i++) { |
|||
var partner_info = pricelist_partnerinfos[i]; |
|||
this.pricelist_partnerinfo_by_id[partner_info.id] = partner_info; |
|||
} |
|||
}, |
|||
add_supplierinfo: function (supplierinfos) { |
|||
if (!(supplierinfos instanceof Array)) { |
|||
supplierinfos = [supplierinfos]; |
|||
} |
|||
for (var i = 0, len = supplierinfos.length; i < len; i++) { |
|||
var supplier_info = supplierinfos[i]; |
|||
this.supplierinfo_by_id[supplier_info.id] = supplier_info; |
|||
} |
|||
}, |
|||
add_default_pricelist: function (res_id) { |
|||
if (res_id && res_id.length) { |
|||
this.default_pricelist_id = res_id[0].res_id; |
|||
} |
|||
}, |
|||
add_pricelists: function (pricelists) { |
|||
if (!(pricelists instanceof Array)) { |
|||
pricelists = [pricelists]; |
|||
} |
|||
for (var i = 0, len = pricelists.length; i < len; i++) { |
|||
var pricelist = pricelists[i]; |
|||
this.pricelist_by_id[pricelist.id] = pricelist; |
|||
} |
|||
}, |
|||
add_pricelist_versions: function (versions) { |
|||
if (!(versions instanceof Array)) { |
|||
versions = [versions]; |
|||
} |
|||
for (var i = 0, len = versions.length; i < len; i++) { |
|||
var version = versions[i]; |
|||
this.pricelist_version_by_id[version.id] = version; |
|||
} |
|||
}, |
|||
add_pricelist_items: function (items) { |
|||
if (!(items instanceof Array)) { |
|||
items = [items]; |
|||
} |
|||
for (var i = 0, len = items.length; i < len; i++) { |
|||
var item = items[i]; |
|||
this.pricelist_item_by_id[item.id] = item; |
|||
} |
|||
this.pricelist_item_sorted = this._items_sorted(); |
|||
}, |
|||
add_price_types: function (price_types) { |
|||
if (!(price_types instanceof Array)) { |
|||
price_types = [price_types]; |
|||
} |
|||
for (var i = 0, len = price_types.length; i < len; i++) { |
|||
var ptype = price_types[i]; |
|||
this.product_price_type_by_id[ptype.id] = ptype; |
|||
} |
|||
}, |
|||
add_product_categories: function (categories) { |
|||
var self = this; |
|||
if (!(categories instanceof Array)) { |
|||
categories = [categories]; |
|||
} |
|||
for (var i = 0, len = categories.length; i < len; i++) { |
|||
var category = categories[i]; |
|||
this.product_catrgory_by_id[category.id] = category; |
|||
this.product_catrgory_children[category.id] = category.child_id |
|||
} |
|||
function make_ancestors(cat_id, ancestors) { |
|||
self.product_catrgory_ancestors[cat_id] = ancestors; |
|||
ancestors = ancestors.slice(0); |
|||
ancestors.push(cat_id); |
|||
var children = self.product_catrgory_children[cat_id] || []; |
|||
for (var i = 0, len = children.length; i < len; i++) { |
|||
make_ancestors(children[i], ancestors); |
|||
} |
|||
} |
|||
if (categories.length) { |
|||
var cat = categories[0]; |
|||
make_ancestors(cat.id, cat.parent_id === false ? [] : [cat.parent_id]) |
|||
} |
|||
}, |
|||
_items_sorted: function () { |
|||
var items = this.pricelist_item_by_id; |
|||
var list = []; |
|||
for (var key in items) { |
|||
list.push(items[key]); |
|||
} |
|||
list.sort(function (a, b) { |
|||
if (a.sequence < b.sequence) return -1; |
|||
if (a.sequence > b.sequence) return 1; |
|||
if (a.min_quantity > b.min_quantity) return -1; |
|||
if (a.min_quantity < b.min_quantity) return 1; |
|||
return 0; |
|||
}); |
|||
return list; |
|||
}, |
|||
find_taxes_by_fiscal_position_id: function (fiscal_position_id) { |
|||
var taxes = []; |
|||
for (var id in this.fiscal_position_tax_by_id) { |
|||
var tax = this.fiscal_position_tax_by_id[id]; |
|||
if (tax && tax.position_id && tax.position_id[0] == fiscal_position_id) { |
|||
taxes.push(tax); |
|||
} |
|||
} |
|||
return taxes; |
|||
} |
|||
}) |
|||
} |
@ -0,0 +1,27 @@ |
|||
/****************************************************************************** |
|||
* Point Of Sale - Dynamic Price for POS Odoo |
|||
* Copyright (C) 2014 Taktik (http://www.taktik.be)
|
|||
* @author Adil Houmadi <ah@taktik.be> |
|||
* |
|||
* 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 <http://www.gnu.org/licenses/>.
|
|||
* |
|||
******************************************************************************/ |
|||
Object.size = function (obj) { |
|||
"use strict"; |
|||
var size = 0, key; |
|||
for (key in obj) { |
|||
if (obj.hasOwnProperty(key)) { |
|||
size += 1; |
|||
} |
|||
} |
|||
return size; |
|||
}; |
@ -0,0 +1,23 @@ |
|||
/****************************************************************************** |
|||
* Point Of Sale - Dynamic Price for POS Odoo |
|||
* Copyright (C) 2014 Taktik (http://www.taktik.be)
|
|||
* @author Adil Houmadi <ah@taktik.be> |
|||
* |
|||
* 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 <http://www.gnu.org/licenses/>.
|
|||
* |
|||
******************************************************************************/ |
|||
openerp.pos_dynamic_price = function (instance) { |
|||
var module = instance.point_of_sale; |
|||
pdp_db(instance, module); |
|||
pdp_models(instance, module); |
|||
pdp_screens(instance, module); |
|||
}; |
@ -0,0 +1,546 @@ |
|||
/****************************************************************************** |
|||
* Point Of Sale - Dynamic Price for POS Odoo |
|||
* Copyright (C) 2014 Taktik (http://www.taktik.be)
|
|||
* @author Adil Houmadi <ah@taktik.be> |
|||
* |
|||
* 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 <http://www.gnu.org/licenses/>.
|
|||
* |
|||
******************************************************************************/ |
|||
function pdp_models(instance, module) { |
|||
|
|||
var _t = instance.web._t; |
|||
var round_pr = instance.web.round_precision |
|||
|
|||
/** |
|||
* @param funcName |
|||
* @returns {*} |
|||
* @private |
|||
*/ |
|||
Backbone.Model.prototype._super = function (funcName) { |
|||
return this.constructor.__super__[funcName].apply(this, _.rest(arguments)); |
|||
}; |
|||
|
|||
/** |
|||
* Extend the POS model |
|||
*/ |
|||
module.PosModel = module.PosModel.extend({ |
|||
initialize: function (session, attributes) { |
|||
this._super('initialize', session, attributes); |
|||
arrange_elements(this); |
|||
}, |
|||
/** |
|||
* find model based on name |
|||
* @param model_name |
|||
* @returns {{}} |
|||
*/ |
|||
find_model: function (model_name) { |
|||
var self = this; |
|||
var lookup = {}; |
|||
for (var i = 0, len = self.models.length; i < len; i++) { |
|||
if (self.models[i].model === model_name) { |
|||
lookup[i] = self.models[i] |
|||
} |
|||
} |
|||
return lookup |
|||
} |
|||
}); |
|||
|
|||
/** |
|||
* Extend the order |
|||
*/ |
|||
module.Order = module.Order.extend({ |
|||
initialize: function (attributes) { |
|||
this._super('initialize', attributes); |
|||
}, |
|||
/** |
|||
* override this method to merge lines |
|||
* TODO : find a better way to do it |
|||
* @param product |
|||
* @param options |
|||
*/ |
|||
addProduct: function (product, options) { |
|||
options = options || {}; |
|||
var attr = JSON.parse(JSON.stringify(product)); |
|||
attr.pos = this.pos; |
|||
attr.order = this; |
|||
var line = new module.Orderline({}, {pos: this.pos, order: this, product: product}); |
|||
var self = this; |
|||
var found = false; |
|||
|
|||
if (options.quantity !== undefined) { |
|||
line.set_quantity(options.quantity); |
|||
} |
|||
if (options.price !== undefined) { |
|||
line.set_unit_price(options.price); |
|||
} |
|||
if (options.discount !== undefined) { |
|||
line.set_discount(options.discount); |
|||
} |
|||
|
|||
var orderlines = []; |
|||
if (self.get('orderLines').models !== undefined) { |
|||
orderlines = self.get('orderLines').models; |
|||
} |
|||
for (var i = 0; i < orderlines.length; i++) { |
|||
var _line = orderlines[i]; |
|||
if (_line && _line.can_be_merged_with(line) && options.merge !== false) { |
|||
_line.merge(line); |
|||
found = true; |
|||
break; |
|||
} |
|||
} |
|||
if (!found) { |
|||
this.get('orderLines').add(line); |
|||
} |
|||
this.selectLine(this.getLastOrderline()); |
|||
} |
|||
}); |
|||
|
|||
/** |
|||
* Extend the Order line |
|||
*/ |
|||
module.Orderline = module.Orderline.extend({ |
|||
initialize: function (attr, options) { |
|||
this._super('initialize', attr, options); |
|||
if (options.product !== undefined) { |
|||
var qty = this.compute_qty(options.order, options.product); |
|||
var partner = options.order.get_client(); |
|||
var product = options.product; |
|||
var db = self.posmodel.db; |
|||
var price = this.compute_price_all(db, product, partner, qty); |
|||
if (price !== false && price !== 0.0) { |
|||
this.price = price; |
|||
} |
|||
} |
|||
}, |
|||
/** |
|||
* @param quantity |
|||
*/ |
|||
set_quantity: function (quantity) { |
|||
this._super('set_quantity', quantity); |
|||
var partner = this.order.get_client(); |
|||
var product = this.product; |
|||
var db = this.pos.db; |
|||
var price = this.compute_price_all(db, product, partner, quantity); |
|||
if (price !== false && price !== 0.0) { |
|||
this.price = price; |
|||
} |
|||
this.trigger('change', this); |
|||
}, |
|||
/** |
|||
* override this method to take fiscal postions in consideration |
|||
* get all price |
|||
* @returns {{priceWithTax: *, priceWithoutTax: *, tax: number, taxDetails: {}}} |
|||
*/ |
|||
get_all_prices: function () { |
|||
|
|||
var self = this; |
|||
var currency_rounding = this.pos.currency.rounding; |
|||
var base = this.get_base_price(); |
|||
var totalTax = base; |
|||
var totalNoTax = base; |
|||
var product = this.get_product(); |
|||
var partner = this.order.get_client(); |
|||
var taxes_ids = product.taxes_id; |
|||
var fiscal_position_taxes = []; |
|||
if (partner && partner.property_account_position) { |
|||
fiscal_position_taxes = self.pos.db.find_taxes_by_fiscal_position_id(partner.property_account_position[0]); |
|||
} |
|||
var product_taxes_ids = []; |
|||
for (var i = 0, ilen = fiscal_position_taxes.length; i < ilen; i++) { |
|||
var fp_tax = fiscal_position_taxes[i]; |
|||
for (var j = 0, jlen = taxes_ids.length; j < jlen; j++) { |
|||
var p_tax = taxes_ids[j]; |
|||
if (fp_tax && p_tax && fp_tax.tax_src_id[0] === p_tax) { |
|||
product_taxes_ids.push(fp_tax.tax_dest_id[0]); |
|||
} |
|||
} |
|||
} |
|||
if (product_taxes_ids.length === 0) { |
|||
product_taxes_ids = taxes_ids; |
|||
} |
|||
var taxes = self.pos.taxes; |
|||
var taxtotal = 0; |
|||
var taxdetail = {}; |
|||
_.each(product_taxes_ids, function (el) { |
|||
var tax = _.detect(taxes, function (t) { |
|||
return t.id === el; |
|||
}); |
|||
if (tax.price_include) { |
|||
var tmp; |
|||
if (tax.type === "percent") { |
|||
tmp = base - round_pr(base / (1 + tax.amount), currency_rounding); |
|||
} else if (tax.type === "fixed") { |
|||
tmp = round_pr(tax.amount * self.get_quantity(), currency_rounding); |
|||
} else { |
|||
throw "This type of tax is not supported by the point of sale: " + tax.type; |
|||
} |
|||
tmp = round_pr(tmp, currency_rounding); |
|||
taxtotal += tmp; |
|||
totalNoTax -= tmp; |
|||
taxdetail[tax.id] = tmp; |
|||
} else { |
|||
var tmp; |
|||
if (tax.type === "percent") { |
|||
tmp = tax.amount * base; |
|||
} else if (tax.type === "fixed") { |
|||
tmp = tax.amount * self.get_quantity(); |
|||
} else { |
|||
throw "This type of tax is not supported by the point of sale: " + tax.type; |
|||
} |
|||
tmp = round_pr(tmp, currency_rounding); |
|||
taxtotal += tmp; |
|||
totalTax += tmp; |
|||
taxdetail[tax.id] = tmp; |
|||
} |
|||
}); |
|||
return { |
|||
"priceWithTax": totalTax, |
|||
"priceWithoutTax": totalNoTax, |
|||
"tax": taxtotal, |
|||
"taxDetails": taxdetail |
|||
}; |
|||
}, |
|||
|
|||
/** |
|||
* compute price for all price list |
|||
* @param db |
|||
* @param product |
|||
* @param partner |
|||
* @param qty |
|||
* @returns {*} |
|||
*/ |
|||
compute_price_all: function (db, product, partner, qty) { |
|||
var price_list_id = false; |
|||
if (partner && partner.property_product_pricelist) { |
|||
price_list_id = partner.property_product_pricelist[0]; |
|||
} else { |
|||
price_list_id = db.default_pricelist_id; |
|||
} |
|||
return this.compute_price(db, product, partner, qty, parseInt(price_list_id)); |
|||
}, |
|||
/** |
|||
* Override this method to avoid a return false if the price is different |
|||
* Check super method : (this.price !== orderline.price) is not necessary in our case |
|||
* @param orderline |
|||
* @returns {boolean} |
|||
*/ |
|||
can_be_merged_with: function (orderline) { |
|||
if (this.get_product().id !== orderline.get_product().id) { |
|||
return false; |
|||
} else if (!this.get_unit()) { |
|||
return false; |
|||
} else if (this.get_product_type() !== orderline.get_product_type()) { |
|||
return false; |
|||
} else return this.get_discount() <= 0; |
|||
}, |
|||
/** |
|||
* Override to set price |
|||
* @param orderline |
|||
*/ |
|||
merge: function (orderline) { |
|||
this._super('merge', orderline); |
|||
this.set_unit_price(orderline.price); |
|||
}, |
|||
/** |
|||
* @param order |
|||
* @param product |
|||
* @returns {number} |
|||
*/ |
|||
compute_qty: function (order, product) { |
|||
var qty = 1; |
|||
var orderlines = []; |
|||
if (order.get('orderLines').models !== undefined) { |
|||
orderlines = order.get('orderLines').models; |
|||
} |
|||
for (var i = 0; i < orderlines.length; i++) { |
|||
if (orderlines[i].product.id === product.id) { |
|||
qty += orderlines[i].quantity; |
|||
} |
|||
} |
|||
return qty; |
|||
}, |
|||
/** |
|||
* loop find a valid version for the price list id given in param |
|||
* @param db |
|||
* @param pricelist_id |
|||
* @returns {boolean} |
|||
*/ |
|||
find_valid_pricelist_version: function (db, pricelist_id) { |
|||
var date = new Date(); |
|||
var version = false; |
|||
var pricelist = db.pricelist_by_id[pricelist_id]; |
|||
for (var i = 0, len = pricelist.version_id.length; i < len; i++) { |
|||
var v = db.pricelist_version_by_id[pricelist.version_id[i]]; |
|||
if (((v.date_start == false) || (new Date(v.date_start) <= date)) && |
|||
((v.date_end == false) || (new Date(v.date_end) >= date))) { |
|||
version = v; |
|||
break; |
|||
} |
|||
} |
|||
return version; |
|||
}, |
|||
/** |
|||
* compute the price for the given product |
|||
* @param database |
|||
* @param product |
|||
* @param partner |
|||
* @param qty |
|||
* @param pricelist_id |
|||
* @returns {boolean} |
|||
*/ |
|||
compute_price: function (database, product, partner, qty, pricelist_id) { |
|||
debugger; |
|||
var self = this; |
|||
var db = database; |
|||
|
|||
// get a valid version
|
|||
var version = this.find_valid_pricelist_version(db, pricelist_id); |
|||
if (version == false) { |
|||
var message = _t('Pricelist Error'); |
|||
var comment = _t('At least one pricelist has no active version ! Please create or activate one.'); |
|||
show_error(this, message, comment); |
|||
return false; |
|||
} |
|||
|
|||
// get categories
|
|||
var categ_ids = []; |
|||
if (product.categ_id) { |
|||
categ_ids.push(product.categ_id[0]); |
|||
categ_ids = categ_ids.concat(db.product_catrgory_ancestors[product.categ_id[0]]); |
|||
} |
|||
|
|||
// find items
|
|||
var items = [], i; |
|||
for (var i = 0, len = db.pricelist_item_sorted.length; i < len; i++) { |
|||
var item = db.pricelist_item_sorted[i]; |
|||
if ((item.product_id === false || item.product_id[0] === product.id) && |
|||
(item.categ_id === false || categ_ids.indexOf(item.categ_id[0]) !== -1) && |
|||
(item.price_version_id[0] === version.id)) { |
|||
items.push(item); |
|||
} |
|||
} |
|||
|
|||
var results = {}; |
|||
results[product.id] = 0.0; |
|||
var price_types = {}; |
|||
var price = false; |
|||
|
|||
// loop through items
|
|||
for (var i = 0, len = items.length; i < len; i++) { |
|||
var rule = items[i]; |
|||
|
|||
if (rule.min_quantity && qty < rule.min_quantity) { |
|||
continue; |
|||
} |
|||
if (rule.product_id && rule.product_id[0] && product.id != rule.product_id[0]) { |
|||
continue; |
|||
} |
|||
if (rule.categ_id) { |
|||
var cat_id = product.categ_id[0]; |
|||
while (cat_id) { |
|||
if (cat_id == rule.categ_id[0]) { |
|||
break; |
|||
} |
|||
cat_id = db.product_catrgory_by_id[cat_id].parent_id[0]; |
|||
} |
|||
if (!(cat_id)) { |
|||
continue; |
|||
} |
|||
} |
|||
// Based on field
|
|||
switch (rule.base) { |
|||
case -1: |
|||
if (rule.base_pricelist_id) { |
|||
price = self.compute_price(db, product, false, qty, rule.base_pricelist_id[0]); |
|||
} |
|||
break; |
|||
case -2: |
|||
var seller = false; |
|||
for (var index in product.seller_ids) { |
|||
var seller_id = product.seller_ids[index]; |
|||
var _tmp_seller = db.supplierinfo_by_id[seller_id]; |
|||
if ((!partner) || (_tmp_seller.name.length && _tmp_seller.name[0] != partner.name)) |
|||
continue; |
|||
seller = _tmp_seller |
|||
} |
|||
if (!seller && product.seller_ids) { |
|||
seller = db.supplierinfo_by_id[product.seller_ids[0]]; |
|||
} |
|||
if (seller) { |
|||
for (var _id in seller.pricelist_ids) { |
|||
var info_id = seller.pricelist_ids[_id]; |
|||
var line = db.pricelist_partnerinfo_by_id[info_id]; |
|||
if (line.min_quantity <= qty) { |
|||
price = line.price |
|||
} |
|||
} |
|||
} |
|||
break; |
|||
default: |
|||
if (!price_types.hasOwnProperty(rule.base)) { |
|||
price_types[rule.base] = db.product_price_type_by_id[rule.base]; |
|||
} |
|||
var price_type = price_types[rule.base]; |
|||
if (db.product_by_id[product.id].hasOwnProperty(price_type.field)) { |
|||
price = db.product_by_id[product.id][price_type.field]; |
|||
} |
|||
} |
|||
if (price !== false) { |
|||
var price_limit = price; |
|||
price = price * (1.0 + (rule['price_discount'] ? rule['price_discount'] : 0.0)) |
|||
if (rule['price_round']) { |
|||
price = parseFloat(price.toFixed(Math.ceil(Math.log(1.0 / rule['price_round']) / Math.log(10)))); |
|||
} |
|||
price += (rule['price_surcharge'] ? rule['price_surcharge'] : 0.0); |
|||
if (rule['price_min_margin']) { |
|||
price = Math.max(price, price_limit + rule['price_min_margin']) |
|||
} |
|||
if (rule['price_max_margin']) { |
|||
price = Math.min(price, price_limit + rule['price_min_margin']) |
|||
} |
|||
} |
|||
break; |
|||
} |
|||
return price |
|||
} |
|||
}); |
|||
|
|||
/** |
|||
* show error based on pop up |
|||
* @param context |
|||
* @param message |
|||
* @param comment |
|||
*/ |
|||
function show_error(context, message, comment) { |
|||
context.pos.pos_widget.screen_selector.show_popup('error', { |
|||
'message': message, |
|||
'comment': comment |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* patch models to load some entities |
|||
* @param pos_model |
|||
*/ |
|||
function arrange_elements(pos_model) { |
|||
|
|||
var product_model = pos_model.find_model('product.product'); |
|||
if (Object.size(product_model) == 1) { |
|||
var product_index = parseInt(Object.keys(product_model)[0]); |
|||
pos_model.models[product_index].fields.push('categ_id', 'seller_ids'); |
|||
} |
|||
|
|||
var res_product_pricelist = pos_model.find_model('product.pricelist'); |
|||
if (Object.size(res_product_pricelist) == 1) { |
|||
var pricelist_index = parseInt(Object.keys(res_product_pricelist)[0]); |
|||
|
|||
// after the pricelist we can load all pricelists, versions and items
|
|||
pos_model.models.splice(++pricelist_index, 0, |
|||
{ |
|||
model: 'account.fiscal.position.tax', |
|||
fields: ['display_name', 'position_id', 'tax_src_id', 'tax_dest_id'], |
|||
domain: null, |
|||
loaded: function (self, fiscal_position_taxes) { |
|||
self.db.add_fiscal_position_taxes(fiscal_position_taxes); |
|||
} |
|||
}, |
|||
{ |
|||
model: 'pricelist.partnerinfo', |
|||
fields: ['display_name', 'min_quantity', 'name', 'price', 'suppinfo_id'], |
|||
domain: null, |
|||
loaded: function (self, pricelist_partnerinfos) { |
|||
self.db.add_pricelist_partnerinfo(pricelist_partnerinfos); |
|||
} |
|||
}, |
|||
{ |
|||
model: 'product.supplierinfo', |
|||
fields: ['delay', 'name', 'min_qty', 'pricelist_ids', 'product_code', 'product_name', 'sequence', |
|||
'qty', 'product_tmpl_id'], |
|||
domain: null, |
|||
loaded: function (self, supplierinfos) { |
|||
self.db.add_supplierinfo(supplierinfos); |
|||
} |
|||
}, |
|||
{ |
|||
model: 'product.category', |
|||
fields: ['name', 'display_name', 'parent_id', 'child_id'], |
|||
domain: null, |
|||
loaded: function (self, categories) { |
|||
self.db.add_product_categories(categories); |
|||
|
|||
} |
|||
}, |
|||
{ |
|||
model: 'ir.model.data', |
|||
fields: ['res_id'], |
|||
domain: function () { |
|||
return [ |
|||
['module', '=', 'product'], |
|||
['name', '=', 'property_product_pricelist'] |
|||
] |
|||
}, |
|||
loaded: function (self, res) { |
|||
self.db.add_default_pricelist(res); |
|||
} |
|||
}, |
|||
{ |
|||
model: 'product.pricelist', |
|||
fields: ['display_name', 'name', 'version_id', 'currency_id'], |
|||
domain: function () { |
|||
return [ |
|||
['type', '=', 'sale'] |
|||
] |
|||
}, |
|||
loaded: function (self, pricelists) { |
|||
self.db.add_pricelists(pricelists); |
|||
} |
|||
}, |
|||
{ |
|||
model: 'product.pricelist.version', |
|||
fields: ['name', 'pricelist_id', 'date_start', 'date_end', 'items'], |
|||
domain: null, |
|||
loaded: function (self, versions) { |
|||
self.db.add_pricelist_versions(versions); |
|||
} |
|||
}, |
|||
{ |
|||
model: 'product.pricelist.item', |
|||
fields: ['name', 'base', 'base_pricelist_id', 'categ_id', 'min_quantity', |
|||
'price_discount', 'price_max_margin', 'price_min_margin', 'price_round', 'price_surcharge', |
|||
'price_version_id', 'product_id', 'product_tmpl_id', 'sequence' |
|||
], |
|||
domain: null, |
|||
loaded: function (self, items) { |
|||
self.db.add_pricelist_items(items); |
|||
} |
|||
}, |
|||
{ |
|||
model: 'product.price.type', |
|||
fields: ['name', 'field', 'currency_id'], |
|||
domain: null, |
|||
loaded: function (self, price_types) { |
|||
self.db.add_price_types(price_types); |
|||
} |
|||
} |
|||
); |
|||
} |
|||
|
|||
var res_partner_model = pos_model.find_model('res.partner'); |
|||
if (Object.size(res_partner_model) == 1) { |
|||
var res_partner_index = parseInt(Object.keys(res_partner_model)[0]); |
|||
pos_model.models[res_partner_index].fields.push('property_account_position', 'property_product_pricelist'); |
|||
} |
|||
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,41 @@ |
|||
/****************************************************************************** |
|||
* Point Of Sale - Dynamic Price for POS Odoo |
|||
* Copyright (C) 2014 Taktik (http://www.taktik.be)
|
|||
* @author Adil Houmadi <ah@taktik.be> |
|||
* |
|||
* 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 <http://www.gnu.org/licenses/>.
|
|||
* |
|||
******************************************************************************/ |
|||
function pdp_screens(instance, module) { |
|||
|
|||
module.ClientListScreenWidget = module.ClientListScreenWidget.extend({ |
|||
save_changes: function () { |
|||
this._super(); |
|||
if (this.has_client_changed()) { |
|||
var currentOrder = this.pos.get('selectedOrder'); |
|||
var orderLines = currentOrder.get('orderLines').models; |
|||
for (var i = 0, len = orderLines.length; i < len; i++) { |
|||
var line = orderLines[i]; |
|||
var partner = currentOrder.get_client(); |
|||
var product = line.product; |
|||
var db = self.posmodel.db; |
|||
var quantity = line.quantity; |
|||
var price = line.compute_price_all(db, product, partner, quantity); |
|||
if (price !== false && price !== 0.0) { |
|||
line.price = price; |
|||
} |
|||
line.trigger('change', line); |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
} |
@ -0,0 +1,20 @@ |
|||
<openerp> |
|||
<data> |
|||
<template id="pos_dynamic_price_assets_backend" |
|||
name="pos_dynamic_price_assets_backend" |
|||
inherit_id="point_of_sale.assets_backend"> |
|||
<xpath expr="." position="inside"> |
|||
<script src="/pos_dynamic_price/static/src/js/helper.js" |
|||
type="text/javascript"></script> |
|||
<script src="/pos_dynamic_price/static/src/js/db.js" |
|||
type="text/javascript"></script> |
|||
<script src="/pos_dynamic_price/static/src/js/models.js" |
|||
type="text/javascript"></script> |
|||
<script src="/pos_dynamic_price/static/src/js/screens.js" |
|||
type="text/javascript"></script> |
|||
<script src="/pos_dynamic_price/static/src/js/main.js" |
|||
type="text/javascript"></script> |
|||
</xpath> |
|||
</template> |
|||
</data> |
|||
</openerp> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue