diff --git a/pos_dynamic_price/README.md b/pos_dynamic_price/README.md
new file mode 100644
index 00000000..16d4c188
--- /dev/null
+++ b/pos_dynamic_price/README.md
@@ -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 :
+
+###odoo 8 POS price list discount has no effect.
+###ODOO POS Pricelist - Public Price & Discounted Price in Receipt
+###V8.0 pos gives wrong price when using min qty in pos pricelist
+
+### 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
+ 2. (-2) : Rule based on supplierinfo
+ 3. (default) : Any price type which is set on the product form
+
+### Missing features :
+
+- As you may know, product template is not fully implemented in the POS, so I decided to drop it from
+this module.
\ No newline at end of file
diff --git a/pos_dynamic_price/__init__.py b/pos_dynamic_price/__init__.py
new file mode 100644
index 00000000..0760a2d3
--- /dev/null
+++ b/pos_dynamic_price/__init__.py
@@ -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
+#
+# 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 .
+#
+##############################################################################
\ No newline at end of file
diff --git a/pos_dynamic_price/__openerp__.py b/pos_dynamic_price/__openerp__.py
new file mode 100644
index 00000000..d39dd219
--- /dev/null
+++ b/pos_dynamic_price/__openerp__.py
@@ -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
+#
+# 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': '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,
+}
\ No newline at end of file
diff --git a/pos_dynamic_price/static/src/js/db.js b/pos_dynamic_price/static/src/js/db.js
new file mode 100644
index 00000000..da160bf8
--- /dev/null
+++ b/pos_dynamic_price/static/src/js/db.js
@@ -0,0 +1,156 @@
+/******************************************************************************
+* Point Of Sale - Dynamic Price for POS Odoo
+* Copyright (C) 2014 Taktik (http://www.taktik.be)
+* @author Adil Houmadi
+*
+* 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 .
+*
+******************************************************************************/
+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;
+ }
+ })
+}
\ No newline at end of file
diff --git a/pos_dynamic_price/static/src/js/helper.js b/pos_dynamic_price/static/src/js/helper.js
new file mode 100644
index 00000000..b5a22eb2
--- /dev/null
+++ b/pos_dynamic_price/static/src/js/helper.js
@@ -0,0 +1,27 @@
+/******************************************************************************
+* Point Of Sale - Dynamic Price for POS Odoo
+* Copyright (C) 2014 Taktik (http://www.taktik.be)
+* @author Adil Houmadi
+*
+* 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 .
+*
+******************************************************************************/
+Object.size = function (obj) {
+ "use strict";
+ var size = 0, key;
+ for (key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ size += 1;
+ }
+ }
+ return size;
+};
\ No newline at end of file
diff --git a/pos_dynamic_price/static/src/js/main.js b/pos_dynamic_price/static/src/js/main.js
new file mode 100644
index 00000000..dcf31eed
--- /dev/null
+++ b/pos_dynamic_price/static/src/js/main.js
@@ -0,0 +1,23 @@
+/******************************************************************************
+* Point Of Sale - Dynamic Price for POS Odoo
+* Copyright (C) 2014 Taktik (http://www.taktik.be)
+* @author Adil Houmadi
+*
+* 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 .
+*
+******************************************************************************/
+openerp.pos_dynamic_price = function (instance) {
+ var module = instance.point_of_sale;
+ pdp_db(instance, module);
+ pdp_models(instance, module);
+ pdp_screens(instance, module);
+};
\ No newline at end of file
diff --git a/pos_dynamic_price/static/src/js/models.js b/pos_dynamic_price/static/src/js/models.js
new file mode 100644
index 00000000..7f56a8d0
--- /dev/null
+++ b/pos_dynamic_price/static/src/js/models.js
@@ -0,0 +1,546 @@
+/******************************************************************************
+* Point Of Sale - Dynamic Price for POS Odoo
+* Copyright (C) 2014 Taktik (http://www.taktik.be)
+* @author Adil Houmadi
+*
+* 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 .
+*
+******************************************************************************/
+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');
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/pos_dynamic_price/static/src/js/screens.js b/pos_dynamic_price/static/src/js/screens.js
new file mode 100644
index 00000000..e2d088d0
--- /dev/null
+++ b/pos_dynamic_price/static/src/js/screens.js
@@ -0,0 +1,41 @@
+/******************************************************************************
+* Point Of Sale - Dynamic Price for POS Odoo
+* Copyright (C) 2014 Taktik (http://www.taktik.be)
+* @author Adil Houmadi
+*
+* 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 .
+*
+******************************************************************************/
+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);
+ }
+ }
+ }
+ });
+}
diff --git a/pos_dynamic_price/view/pos_dynamic_price_template.xml b/pos_dynamic_price/view/pos_dynamic_price_template.xml
new file mode 100644
index 00000000..5f9f7b8c
--- /dev/null
+++ b/pos_dynamic_price/view/pos_dynamic_price_template.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+