diff --git a/customer_pos_pricelist/README.rst b/customer_pos_pricelist/README.rst new file mode 100644 index 000000000..142012bee --- /dev/null +++ b/customer_pos_pricelist/README.rst @@ -0,0 +1,45 @@ + +POS Customer Pricelist v8 +========================= + +This module adds Pricelist to Point Of Sale Session. + + +* Use customer specific pricelist inside POS. +* Assign pricelist while creating customer from POS. +* User can also change pricelist in POS Session. +* Unique Ticket Id is specified for each ticket. + + +Installation +============ + +No extra module is required,other than the depending module POS. + +Usage +===== + +To use this module, you need to: + +* Go to Pont of sale main menu +* Select the Resume session/New session +* Click on 'Unknown customer'-->Edit/Create Customer, then Add pricelist for the customer + + +Implementation +============== + +User can assign POS pricelist from POS session for each customer. +Then price value of all the products in POS will be changed according to the customer selected. +If no pricelist is set for the customer,module will select default pricelist. + + + + + + + + + + + diff --git a/customer_pos_pricelist/__init__.py b/customer_pos_pricelist/__init__.py new file mode 100644 index 000000000..b42bac92f --- /dev/null +++ b/customer_pos_pricelist/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2010-TODAY Cybrosys Technologies(). +# Author: Cybrosys Technologies() +# you can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# It is forbidden to publish, distribute, sublicense, or sell copies +# of the Software or modified copies of the Software. +# +# 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# GENERAL PUBLIC LICENSE (LGPL v3) along with this program. +# If not, see . +# +############################################################################## + +import models diff --git a/customer_pos_pricelist/__openerp__.py b/customer_pos_pricelist/__openerp__.py new file mode 100644 index 000000000..87cf2050c --- /dev/null +++ b/customer_pos_pricelist/__openerp__.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2010-TODAY Cybrosys Technologies(). +# Author: Cybrosys Technologies() +# you can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# It is forbidden to publish, distribute, sublicense, or sell copies +# of the Software or modified copies of the Software. +# +# 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# GENERAL PUBLIC LICENSE (LGPL v3) along with this program. +# If not, see . +# +############################################################################## + +{ + 'name': 'Customer Pricelist In POS', + 'version': '8.0.1.0.0', + 'summary': 'Customer Pricelist in POS.', + 'description': 'Pricelist operations to point of sale', + 'category': 'Point of Sale', + 'author': 'Cybrosys Techno Solutions', + 'website': "http://www.cybrosys.com", + 'company': 'Cybrosys Techno Solutions', + 'depends': ['point_of_sale'], + 'data': [ + "views/new_pricelist.xml", + "views/pos_pricelist_template.xml"], + 'qweb': ['static/src/xml/pos.xml'], + 'images': ['static/description/banner.jpg'], + 'license': 'LGPL-3', + 'installable': True, + 'auto_install': False, + 'application': False, +} diff --git a/customer_pos_pricelist/models/__init__.py b/customer_pos_pricelist/models/__init__.py new file mode 100644 index 000000000..04dfebdf0 --- /dev/null +++ b/customer_pos_pricelist/models/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2010-TODAY Cybrosys Technologies(). +# Author: Cybrosys Technologies() +# you can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# It is forbidden to publish, distribute, sublicense, or sell copies +# of the Software or modified copies of the Software. +# +# 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# GENERAL PUBLIC LICENSE (LGPL v3) along with this program. +# If not, see . +# +############################################################################## + +import model diff --git a/customer_pos_pricelist/models/model.py b/customer_pos_pricelist/models/model.py new file mode 100644 index 000000000..64d2a2f4b --- /dev/null +++ b/customer_pos_pricelist/models/model.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2010-TODAY Cybrosys Technologies(). +# Author: Cybrosys Technologies() +# you can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# It is forbidden to publish, distribute, sublicense, or sell copies +# of the Software or modified copies of the Software. +# +# 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# GENERAL PUBLIC LICENSE (LGPL v3) along with this program. +# If not, see . +# +############################################################################## + +from openerp import fields, models + + +class PosCustomerPricelist(models.Model): + _inherit = 'res.partner' + + pricelist = fields.Many2one('product.pricelist', string='POS Pricelist') diff --git a/customer_pos_pricelist/static/description/11.png b/customer_pos_pricelist/static/description/11.png new file mode 100644 index 000000000..081dc96fd Binary files /dev/null and b/customer_pos_pricelist/static/description/11.png differ diff --git a/customer_pos_pricelist/static/description/3.png b/customer_pos_pricelist/static/description/3.png new file mode 100644 index 000000000..1bcbeff0f Binary files /dev/null and b/customer_pos_pricelist/static/description/3.png differ diff --git a/customer_pos_pricelist/static/description/35.png b/customer_pos_pricelist/static/description/35.png new file mode 100644 index 000000000..90a5da4a8 Binary files /dev/null and b/customer_pos_pricelist/static/description/35.png differ diff --git a/customer_pos_pricelist/static/description/4.png b/customer_pos_pricelist/static/description/4.png new file mode 100644 index 000000000..f7158c2ce Binary files /dev/null and b/customer_pos_pricelist/static/description/4.png differ diff --git a/customer_pos_pricelist/static/description/banner.jpg b/customer_pos_pricelist/static/description/banner.jpg new file mode 100644 index 000000000..f0ef91f73 Binary files /dev/null and b/customer_pos_pricelist/static/description/banner.jpg differ diff --git a/customer_pos_pricelist/static/description/cybro_logo.png b/customer_pos_pricelist/static/description/cybro_logo.png new file mode 100644 index 000000000..bb309114c Binary files /dev/null and b/customer_pos_pricelist/static/description/cybro_logo.png differ diff --git a/customer_pos_pricelist/static/description/icon.png b/customer_pos_pricelist/static/description/icon.png new file mode 100644 index 000000000..96db5eb67 Binary files /dev/null and b/customer_pos_pricelist/static/description/icon.png differ diff --git a/customer_pos_pricelist/static/description/index.html b/customer_pos_pricelist/static/description/index.html new file mode 100644 index 000000000..c343dca8b --- /dev/null +++ b/customer_pos_pricelist/static/description/index.html @@ -0,0 +1,101 @@ +
+
+

Customer Pricelist In POS

+

+This module helps to set customer pricelist in the point of sale module. Price of the product is based on the pricelist value +of the selected customer.The pricelist can be set from POS section or customer at backend.
Some other features are as below: +

+
    +
  •    Use customer specific pricelist inside POS.
  • +
  •    Assign pricelist while creating customer from POS.
  • +
  •    User can also change pricelist of customer from POS session.
  • +
  •    Integrated with customer contact details.
  • +
+
+
+ +
+
+

Add Your Pricelist From POS

+
+

+

Point Of Sale >Resume session >Select customer >Set pricelist

+

+
+
+
+ +
+
+
+ +
+
+

Set Your Pricelist At Customer

+
+

+

Sales >Customer >Create/select any customer >Sales & Purchases >Add pricelist to field 'POS Pricelist'

+

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

Change in Price

+

When a Customer is Set

+
+
+

+

No customer is selected

+

The default pricelist is used when no customer is selected. +

+

+
+
+
+ +
+
+
+

+

Customer is selected

+

+ Then price value of all the products in POS will be changed according to + the pricelist of the customer selected. +

+

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

Need Any Help?

+ + +
+ + diff --git a/customer_pos_pricelist/static/description/pos.png b/customer_pos_pricelist/static/description/pos.png new file mode 100644 index 000000000..2d190be51 Binary files /dev/null and b/customer_pos_pricelist/static/description/pos.png differ diff --git a/customer_pos_pricelist/static/description/ticket1.png b/customer_pos_pricelist/static/description/ticket1.png new file mode 100644 index 000000000..d77d8861b Binary files /dev/null and b/customer_pos_pricelist/static/description/ticket1.png differ diff --git a/customer_pos_pricelist/static/src/css/style.css b/customer_pos_pricelist/static/src/css/style.css new file mode 100644 index 000000000..7f5c0f9ee --- /dev/null +++ b/customer_pos_pricelist/static/src/css/style.css @@ -0,0 +1,134 @@ + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: none; + max-width: 276px; + padding: 1px; + text-align: left; + white-space: normal; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + box-shadow: 0 5px 10px rgba(0, 0, 0, .2); +} +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + font-weight: normal; + line-height: 18px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} + +/* ToolTip */ +.tooltip { + position: absolute; + z-index: 1070; + display: block; + font-size: 12px; + line-height: 1.4; + visibility: visible; + filter: alpha(opacity=0); + opacity: 0; +} +.tooltip.in { + filter: alpha(opacity=90); + opacity: .9; +} +.tooltip.top { + padding: 5px 0; + margin-top: -3px; +} +.tooltip.right { + padding: 0 5px; + margin-left: 3px; +} +.tooltip.bottom { + padding: 5px 0; + margin-top: 3px; +} +.tooltip.left { + padding: 0 5px; + margin-left: -3px; +} +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #fff; + text-align: center; + text-decoration: none; + background-color: #000; + border-radius: 4px; +} +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-left .tooltip-arrow { + bottom: 0; + left: 5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-right .tooltip-arrow { + right: 5px; + bottom: 0; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000; +} +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000; +} +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-left .tooltip-arrow { + top: 0; + left: 5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-right .tooltip-arrow { + top: 0; + right: 5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.pos .pos-right-align { + text-align: right; + vertical-align: top; +} diff --git a/customer_pos_pricelist/static/src/js/db.js b/customer_pos_pricelist/static/src/js/db.js new file mode 100644 index 000000000..63fe348c0 --- /dev/null +++ b/customer_pos_pricelist/static/src/js/db.js @@ -0,0 +1,179 @@ + +function customer_pos_pricelist_db(instance, module) { + + module.PosDB = module.PosDB.extend({ + init: function (options) { + options = options || {}; + this._super(options); + this.pricelist_by_id = {}; + this.pricelist_version_by_id = {}; + this.pricelist_item_by_id = {}; + this.pricelist_item_sorted = []; + this.product_category_by_id = {}; + this.product_category_children = {}; + this.product_category_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]; + } + var fiscal_position_tax; + while (fiscal_position_tax = fiscal_position_taxes.pop()) { + 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]; + } + var partner_info; + while (partner_info = pricelist_partnerinfos.pop()) { + this.pricelist_partnerinfo_by_id[partner_info.id] + = partner_info; + } + }, + add_supplierinfo: function (supplierinfos) { + if (!(supplierinfos instanceof Array)) { + supplierinfos = [supplierinfos]; + } + var supplier_info; + while (supplier_info = supplierinfos.pop()) { + this.supplierinfo_by_id[supplier_info.id] = supplier_info; + } + }, + add_pricelists: function (pricelists) { + if (!(pricelists instanceof Array)) { + pricelists = [pricelists]; + } + var pricelist; + while (pricelist = pricelists.pop()) { + this.pricelist_by_id[pricelist.id] = pricelist; + } + }, + add_pricelist_versions: function (versions) { + if (!(versions instanceof Array)) { + versions = [versions]; + } + var version; + while (version = versions.pop()) { + this.pricelist_version_by_id[version.id] = version; + } + }, + add_pricelist_items: function (items) { + if (!(items instanceof Array)) { + items = [items]; + } + var item; + while (item = items.pop()) { + 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]; + } + var ptype; + while (ptype = price_types.pop()) { + this.product_price_type_by_id[ptype.id] = ptype; + } + }, + add_product_categories: function (categories) { + if (!(categories instanceof Array)) { + categories = [categories]; + } + var category; + while (category = categories.pop()) { + this.product_category_by_id[category.id] = category; + this.product_category_children[category.id] = + category.child_id; + } + this._make_ancestors(); + }, + _make_ancestors: function () { + var category, ancestors; + for (var id in this.product_category_by_id) { + category = this.product_category_by_id[id]; + ancestors = []; + while (category.parent_id) { + ancestors.push(category.parent_id[0]); + category = category.parent_id ? + this.product_category_by_id[category.parent_id[0]] : + false; + } + this.product_category_ancestors[parseInt(id)] = ancestors; + } + }, + _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; + }, + map_tax: function (fiscal_position_id, taxes_ids) { + var taxes = []; + var found_taxes = {}; + for (var id in this.fiscal_position_tax_by_id) { + var fp_line = this.fiscal_position_tax_by_id[id]; + if (fp_line && fp_line.position_id && + fp_line.position_id[0] == fiscal_position_id && + taxes_ids.indexOf(fp_line.tax_src_id[0]) > -1) { + taxes.push(fp_line.tax_dest_id[0]); + found_taxes[fp_line.tax_src_id[0]] = true; + } + } + for (var i = 0, len = taxes_ids.length; i < len; i++) { + var tax_id = taxes_ids[i]; + if (!(tax_id in found_taxes)) { + taxes.push(tax_id); + } + } + return taxes; + }, + add_products: function (products) { + this._super(products); + var pos = posmodel.pos_widget.pos; + for (var id in this.product_by_id) { + if (this.product_by_id.hasOwnProperty(id)) { + var product = this.product_by_id[id]; + var orderline = new openerp.point_of_sale.Orderline({}, { + pos: pos, + order: null, + product: product, + price: product.price + }); + var prices = orderline.get_all_prices(); + this.product_by_id[id].price_with_taxes + = prices['priceWithTax'] + } + } + }, + find_product_rules: function (product) { + var len = this.pricelist_item_sorted.length; + var rules = []; + for (var i = 0; i < len; i++) { + var rule = this.pricelist_item_sorted[i]; + if ((rule.product_id && rule.product_id[0] == product.id) || + (rule.categ_id && product.categ_id + && rule.categ_id[0] == product.categ_id[0])) { + rules.push(rule); + } + } + return rules; + } + }) +} diff --git a/customer_pos_pricelist/static/src/js/main.js b/customer_pos_pricelist/static/src/js/main.js new file mode 100644 index 000000000..f843d7730 --- /dev/null +++ b/customer_pos_pricelist/static/src/js/main.js @@ -0,0 +1,8 @@ + +openerp.customer_pos_pricelist = function (instance) { + var module = instance.point_of_sale; + customer_pos_pricelist_db(instance, module); + customer_pos_pricelist_models(instance, module); + customer_pos_pricelist_screens(instance, module); + customer_pos_pricelist_widgets(instance, module); +}; diff --git a/customer_pos_pricelist/static/src/js/models.js b/customer_pos_pricelist/static/src/js/models.js new file mode 100644 index 000000000..cb86d9a7b --- /dev/null +++ b/customer_pos_pricelist/static/src/js/models.js @@ -0,0 +1,835 @@ + +function customer_pos_pricelist_models(instance, module) { + + var _t = instance.web._t; + var round_pr = instance.web.round_precision; + var round_di = instance.web.round_decimals; + + + var PosModelParent = module.PosModel; + module.PosModel = module.PosModel.extend({ + /** + * @param session + * @param attributes + */ + initialize: function (session, attributes) { + PosModelParent.prototype.initialize.apply(this, arguments); + this.pricelist_engine = new module.PricelistEngine({ + 'pos': this, + 'db': this.db, + 'pos_widget': this.pos_widget + }); + 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 + }, + /** + * @param removed_order + * @param index + * @param reason + */ + on_removed_order: function (removed_order, index, reason) { +// console.log('removing the order.......') + PosModelParent.prototype.on_removed_order.apply(this, arguments); + if ((reason === 'abandon' || removed_order.temporary) + && this.get('orders').size() > 0) { + var current_order = (this.get('orders').at(index) + || this.get('orders').last()); + var partner = current_order.get_client() ? + current_order.get_client() : + false; + this.pricelist_engine.update_products_ui(partner); + } + } + }); + + /** + * Extend the order + */ + module.Order = module.Order.extend({ + /** + * override this method to merge lines + * TODO : Need some refactoring in the standard POS to Do it better + * TODO : from line 73 till 85, we need to move this to another method + * @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 + */ + var OrderlineParent = module.Orderline; + module.Orderline = module.Orderline.extend({ + /** + * @param attr + * @param options + */ + initialize: function (attr, options) { + OrderlineParent.prototype.initialize.apply(this, arguments); + this.manual_price = false; + if (this.product !== undefined) { + var qty = this.compute_qty(this.order, this.product); + var partner = this.order ? this.order.get_client() : null; + var product = this.product; +// console.log('orderline!!',product) + var db = this.pos.db; + var price = this.pos.pricelist_engine.compute_price_all( + db, product, partner, qty + ); + if (price !== false) { + this.price = round_di(parseFloat(price) || 0, this.pos.dp['Product Price']); + } + } + }, + /** + * @param state + */ + set_manual_price: function (state) { + this.manual_price = state; + }, + /** + * @param quantity + */ + set_quantity: function (quantity) { + var partner = this.order ? this.order.get_client() : null; + var product = this.product; + var db = this.pos.db; + var old_price = 0; + if (this.get_quantity()) { +// console.log('QTY!!!!!',this.get_quantity()) + old_price = this.pos.pricelist_engine.compute_price_all( + db, product, partner, this.get_quantity() + ); + } + OrderlineParent.prototype.set_quantity.apply(this, arguments); + var price = this.pos.pricelist_engine.compute_price_all( + db, product, partner, quantity + ); + /* Update the price if the unit price is actually different from + the unit price of the previous quantity, to preserve manually + entered prices as much as possible. */ + if (price !== false && price !== old_price) { + this.set_unit_price(price); + } + }, + /** + * override this method to take fiscal positions in consideration + * get all price + * TODO : find a better way to do it : need some refactoring + * in the pos standard + * @returns {{ + * priceWithTax: *, priceWithoutTax: *, tax: number, taxDetails: {} + * }} + */ + get_all_prices: function () { + var base = this.get_base_price(); + var totalTax = base; + var totalNoTax = base; + var taxtotal = 0; + var taxdetail = {}; + var product_taxes = this.get_applicable_taxes_for_orderline(); + var all_taxes = _(this.compute_all(product_taxes, base)).flatten(); + _(all_taxes).each(function (tax) { + if (tax.price_include) { + totalNoTax -= tax.amount; + } else { + totalTax += tax.amount; + } + taxtotal += tax.amount; + taxdetail[tax.id] = tax.amount; + }); + totalNoTax = round_pr(totalNoTax, this.pos.currency.rounding); + return { + "priceWithTax": totalTax, + "priceWithoutTax": totalNoTax, + "tax": taxtotal, + "taxDetails": taxdetail + }; + }, + /** + * 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) { + var result = OrderlineParent.prototype.can_be_merged_with.apply( + this, arguments + ); + if (!result) { + if (!this.manual_price) { + return ( + this.get_product().id === orderline.get_product().id + ); + } else { + return false; + } + } + return true; + }, + /** + * Override to set price + * @param orderline + */ + merge: function (orderline) { + OrderlineParent.prototype.merge.apply(this, arguments); + this.set_unit_price(orderline.price); + }, + /** + * @param order + * @param product + * @returns {number} + */ + compute_qty: function (order, product) { + var qty = 1; + var orderlines = []; + if (order && 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 + && !orderlines[i].manual_price) { + qty += orderlines[i].quantity; + } + } + return qty; + }, + /** + * @returns {Array} + */ + get_applicable_taxes_for_orderline: function () { + // find applicable taxes for this product and this customer + var product = this.get_product(); + var product_tax_ids = product.taxes_id; + var product_taxes = []; + var taxes = this.pos.taxes; + var partner = this.order ? this.order.get_client() : null; + if (partner && partner.property_account_position) { + product_tax_ids = + this.pos.db.map_tax( + partner.property_account_position[0], product_tax_ids + ); + } + for (var i = 0, ilen = product_tax_ids.length; + i < ilen; i++) { + var tax_id = product_tax_ids[i]; + var tax = _.detect(taxes, function (t) { + return t.id === tax_id; + }); + product_taxes.push(tax); + } + return product_taxes; + }, + get_display_unit_price: function(){ + var rounding = this.pos.currency.rounding; + if (this.pos.config.display_price_with_taxes) { + return round_pr(this.get_price_with_tax() / this.get_quantity(), rounding); + } else { + return round_pr(this.get_base_price() / this.get_quantity(), rounding); + } + }, + /** + * @returns {*} + */ + get_display_price: function () { + if (this.pos.config.display_price_with_taxes) { + return this.get_price_with_tax(); + } + return OrderlineParent.prototype.get_display_price.apply( + this, arguments + ); + }, + + export_as_JSON: function() { + var res = OrderlineParent.prototype.export_as_JSON.apply(this, arguments); + var product_tax_ids = this.get_product().taxes_id || []; + var partner = this.order ? this.order.get_client() : null; + if (partner && partner.property_account_position) { + product_tax_ids = + this.pos.db.map_tax( + partner.property_account_position[0], product_tax_ids + ); + } + res["tax_ids"] = [[6, false, product_tax_ids]]; + return res; + } + }); + + /** + * Pricelist Engine to compute price + */ + module.PricelistEngine = instance.web.Class.extend({ + /** + * @param options + */ + init: function (options) { + options = options || {}; + this.pos = options.pos; + this.db = options.db; + this.pos_widget = options.pos_widget; + }, + /** + * 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.pricelist) { + price_list_id = partner.pricelist[0]; + } else { + price_list_id = this.pos.config.pricelist_id[0]; + } + return this.compute_price( + db, product, partner, qty, parseInt(price_list_id) + ); + }, + /** + * 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]; +// console.log('pricelisttttt!!!!!!!!!!!!!!',pricelist) + 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) { + + var self = this; + var db = database; +// console.log('DATABASE!!!',db) + + // get a valid version + var version = this.find_valid_pricelist_version(db, pricelist_id); + console.log('VERSION!!!',version) + 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_category_ancestors[product.categ_id[0]] + ); + } + + // find items + var items = [], i, len; + for (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 (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_category_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_type.field === 'list_price' && product.price_extra){ + price += product.price_extra; + } + } + } + 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 + }, + /** + * @param partner + */ + update_products_ui: function (partner) { + var db = this.db; + if (!this.pos_widget.product_screen) return; + var product_list_ui + = this.pos_widget.product_screen.$( + '.product-list span.product' + ); + for (var i = 0, len = product_list_ui.length; i < len; i++) { + var product_ui = product_list_ui[i]; + var product_id = $(product_ui).data('product-id'); + var product = $.extend({}, db.get_product_by_id(product_id)); + var rules = db.find_product_rules(product); + var quantities = []; + quantities.push(1); + for (var j = 0; j < rules.length; j++) { + if ($.inArray(rules[j].min_quantity, quantities) === -1) { + quantities.push(rules[j].min_quantity); + } + } + quantities = quantities.sort(); + var prices_displayed = ''; + for (var k = 0; k < quantities.length; k++) { + var qty = quantities[k]; + var price = this.compute_price_all( + db, product, partner, qty + ); + if (price !== false) { + if (this.pos.config.display_price_with_taxes) { + var prices = this.simulate_price( + product, partner, price, qty + ); + price = prices['priceWithTax'] + } + price = round_di(parseFloat(price) + || 0, this.pos.dp['Product Price']); + price = this.pos_widget.format_currency(price); + if (k == 0) { + $(product_ui).find('.price-tag').html(price); + } +// prices_displayed += qty +// + 'x → ' + price + '
'; + } + } + if (prices_displayed != '') { + $(product_ui).find('.price-tag').attr( + 'data-original-title', prices_displayed + ); + $(product_ui).find('.price-tag').attr( + 'data-toggle', 'tooltip' + ); + $(product_ui).find('.price-tag').tooltip( + {delay: {show: 50, hide: 100}} + ); + } + } + }, + simulate_price: function (product, partner, price, qty) { + // create a fake order in order to get price + // for this customer + var order = new module.Order({pos: this.pos}); + order.set_client(partner); + var orderline = new openerp.point_of_sale.Orderline + ({}, { + pos: this.pos, order: order, + product: product, price: price + }); + orderline.set_quantity(qty); + // reset the sequence + this.pos.pos_session.sequence_number--; + var prices = orderline.get_all_prices(); + return prices; + }, + /** + * + * @param partner + * @param orderLines + */ + update_ticket: function (partner, orderLines) { + var db = this.db; + for (var i = 0, len = orderLines.length; i < len; i++) { + var line = orderLines[i]; + var product = line.product; + var quantity = line.quantity; + var price = this.compute_price_all( + db, product, partner, quantity + ); + if (price !== false) { + line.set_unit_price(price); + } + } + } + }); + /** + * show error + * @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 (_.size(product_model) == 1) { + var product_index = parseInt(Object.keys(product_model)[0]); + pos_model.models[product_index].fields.push( + 'categ_id', 'seller_ids', 'price_extra' + ); + } + + var res_product_pricelist = pos_model.find_model('product.pricelist'); + if (_.size(res_product_pricelist) == 1) { + var pricelist_index = parseInt(Object.keys( + res_product_pricelist)[0] + ); + 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: [['product_tmpl_id.available_in_pos','=',true]], + 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: 'product.pricelist', + fields: ['display_name', + 'name', + 'version_id', + 'currency_id'], + domain: function () { + return [ + ['type', '=', 'sale'] + ] + }, + loaded: function (self, pricelists) { + console.log("pricelists...",pricelists) + + self.db.add_pricelists(pricelists); + }}, + { + model: 'product.pricelist', + fields: ['name'], + domain: function () { + return [ + ['type', '=', 'sale'] + ] + }, + loaded: function (self, pricelistss) { + console.log("pricelists...111",pricelistss) + self.pricelistss = pricelistss; + }}, + { + model: 'pos.order', + fields: ['name'], + + loaded: function(self,orders){ + self.order = orders[0]; + },}, + + { + model: 'res.partner', + fields: ['name','street','pricelist','city','state_id','country_id','vat','phone','zip','mobile','email','ean13','write_date'], + domain: [['customer','=',true]], + loaded: function(self,partners){ + console.log("partnerPOS...111",partners) + self.partners = partners; +// self.partner = partners[0] + self.db.add_partners(partners); + }, + }, + { + model: 'product.pricelist.version', + fields: ['name', + 'pricelist_id', + 'date_start', + 'date_end', + 'items'], + domain: null, + loaded: function (self, versions) { + console.log("versions...",versions) + self.versions = 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) { + // we need to add price type + // field to product.product model if not the case + var product_model = + posmodel.find_model('product.product'); + for (var i = 0, len = price_types.length; + i < len; i++) { + var p_type = price_types[i].field; + if (_.size(product_model) == 1) { + var product_index = + parseInt(Object.keys(product_model)[0]); + if (posmodel.models[product_index] + .fields.indexOf(p_type) === -1) { + posmodel.models[product_index].fields.push( + p_type + ); + } + } + } + self.db.add_price_types(price_types); + } + } + ); + } + + var res_partner_model = pos_model.find_model('res.partner'); + + 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', + 'pricelist', + console.log('AAAAAAAAAAA') + ); + + + } + +} diff --git a/customer_pos_pricelist/static/src/js/screens.js b/customer_pos_pricelist/static/src/js/screens.js new file mode 100644 index 000000000..b55551579 --- /dev/null +++ b/customer_pos_pricelist/static/src/js/screens.js @@ -0,0 +1,16 @@ + +function customer_pos_pricelist_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; + var partner = currentOrder.get_client(); + this.pos.pricelist_engine.update_products_ui(partner); + this.pos.pricelist_engine.update_ticket(partner, orderLines); + } + } + }); +} diff --git a/customer_pos_pricelist/static/src/js/widgets.js b/customer_pos_pricelist/static/src/js/widgets.js new file mode 100644 index 000000000..d2db2ee5d --- /dev/null +++ b/customer_pos_pricelist/static/src/js/widgets.js @@ -0,0 +1,71 @@ + +function customer_pos_pricelist_widgets(instance, module) { + + var round_di = instance.web.round_decimals; + + module.OrderWidget = module.OrderWidget.extend({ + set_value: function (val) { + this._super(val); + var order = this.pos.get('selectedOrder'); + if (this.editable && order.getSelectedLine()) { + var mode = this.numpad_state.get('mode'); + if (mode === 'price') { + order.getSelectedLine().set_manual_price(true); + } + } + } + }); + + module.OrderButtonWidget = module.OrderButtonWidget.extend({ + selectOrder: function (event) { + this._super(event); + var partner = this.order.get_client() + ? this.order.get_client() + : false; + this.pos.pricelist_engine.update_products_ui(partner); + } + }); + + instance.point_of_sale.ProductListWidget.include({ + init: function (parent, options) { + this._super(parent, options); + this.display_price_with_taxes = false; + if ( + posmodel + && posmodel.config + && posmodel.config.display_price_with_taxes + ) { + this.display_price_with_taxes + = posmodel.config.display_price_with_taxes + } + }, + renderElement: function () { + this._super(); + var order = posmodel.get_order(); + var customer = null; + if(order) { + customer = order.get_client(); + } + this.pos.pricelist_engine.update_products_ui(customer); + } + }); + + module.PosBaseWidget.include({ + format_pr: function(amount, precision) { + // Do not call _super because no addon or XML is using this method + var currency = (this.pos && this.pos.currency) ? this.pos.currency : {symbol:'$', position: 'after', rounding: 0.01, decimals: 2}; + var decimals = currency.decimals; + + if (precision && this.pos.dp[precision] !== undefined) { + decimals = this.pos.dp[precision]; + } + + if (typeof amount === 'number') { + amount = round_di(amount,decimals).toFixed(decimals); + amount = openerp.instances[this.session.name].web.format_value(round_di(amount, decimals), { type: 'float', digits: [69, decimals]}); + } + return amount + } + }); +} + diff --git a/customer_pos_pricelist/static/src/xml/pos.xml b/customer_pos_pricelist/static/src/xml/pos.xml new file mode 100644 index 000000000..7ef22d9d3 --- /dev/null +++ b/customer_pos_pricelist/static/src/xml/pos.xml @@ -0,0 +1,46 @@ + + + + + + +
+ Pricelist + +
+ +
+ + + + +
+ Pricelist + + + + + N/A + + +
+
+ + + + PriceList + + + + + + + +
\ No newline at end of file diff --git a/customer_pos_pricelist/views/new_pricelist.xml b/customer_pos_pricelist/views/new_pricelist.xml new file mode 100644 index 000000000..9084a9569 --- /dev/null +++ b/customer_pos_pricelist/views/new_pricelist.xml @@ -0,0 +1,16 @@ + + + + + my.view.pricelist.inherit + res.partner + + + + + + + + + + \ No newline at end of file diff --git a/customer_pos_pricelist/views/pos_pricelist_template.xml b/customer_pos_pricelist/views/pos_pricelist_template.xml new file mode 100644 index 000000000..a1383547c --- /dev/null +++ b/customer_pos_pricelist/views/pos_pricelist_template.xml @@ -0,0 +1,29 @@ + + + + + + + + + +