Browse Source
Merge pull request #6 from taktik/8.0-pos_dynamic_price
Merge pull request #6 from taktik/8.0-pos_dynamic_price
[ADD][8.0] : POS - manage pricelist in Front End POSpull/13/head
Sylvain LE GAL
10 years ago
13 changed files with 1351 additions and 0 deletions
-
45pos_pricelist/README.rst
-
18pos_pricelist/__init__.py
-
43pos_pricelist/__openerp__.py
-
95pos_pricelist/demo/pos_pricelist_demo.yml
-
46pos_pricelist/static/src/css/style.css
-
155pos_pricelist/static/src/js/db.js
-
24pos_pricelist/static/src/js/main.js
-
631pos_pricelist/static/src/js/models.js
-
32pos_pricelist/static/src/js/screens.js
-
164pos_pricelist/static/src/js/tests.js
-
41pos_pricelist/static/src/js/widgets.js
-
28pos_pricelist/test/test.py
-
29pos_pricelist/views/pos_pricelist_template.xml
@ -0,0 +1,45 @@ |
|||||
|
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. <https://github.com/odoo/odoo/issues/3579>`_ |
||||
|
|
||||
|
`ODOO POS Pricelist - Public Price & Discounted Price in Receipt. <https://github.com/odoo/odoo/issues/1758>`_ |
||||
|
|
||||
|
`V8.0 pos gives wrong price when using min qty in pos pricelist <https://github.com/odoo/odoo/issues/2297>`_ |
||||
|
|
||||
|
******************** |
||||
|
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 : |
||||
|
********************** |
||||
|
|
||||
|
1. 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. |
||||
|
|
||||
|
2. 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. |
@ -0,0 +1,18 @@ |
|||||
|
# -#- coding: utf-8 -#- |
||||
|
############################################################################## |
||||
|
# Point Of Sale - Pricelist 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,43 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################## |
||||
|
# Point Of Sale - Pricelist 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 Pricelist', |
||||
|
'version': '1.0.0', |
||||
|
'category': 'Point Of Sale', |
||||
|
'sequence': 1, |
||||
|
'author': 'Adil Houmadi @Taktik', |
||||
|
'summary': 'Pricelist for Point of sale', |
||||
|
'description': """ |
||||
|
New feature for the Point Of Sale: |
||||
|
============================================= |
||||
|
Add support for pricelist on the point of sale |
||||
|
""", |
||||
|
'depends': [ |
||||
|
"point_of_sale", |
||||
|
], |
||||
|
'data': [ |
||||
|
"views/pos_pricelist_template.xml", |
||||
|
], |
||||
|
'demo': [ |
||||
|
'demo/pos_pricelist_demo.yml', |
||||
|
], |
||||
|
'installable': True, |
||||
|
'application': False, |
||||
|
'auto_install': False, |
||||
|
} |
@ -0,0 +1,95 @@ |
|||||
|
- |
||||
|
This product will have two rule (min_qty:3 => 10%, min_qty:5 => 30%) |
||||
|
- |
||||
|
!record {model: product.product, id: pos_product_product_1}: |
||||
|
default_code: ABC123 |
||||
|
name: POS Product 1 |
||||
|
type: product |
||||
|
categ_id: product.product_category_1 |
||||
|
list_price: 100.0 |
||||
|
standard_price: 50.0 |
||||
|
uom_id: product.product_uom_unit |
||||
|
uom_po_id: product.product_uom_unit |
||||
|
available_in_pos: True |
||||
|
|
||||
|
- |
||||
|
This product will have one rule (min_qty:2 => 10%) |
||||
|
- |
||||
|
!record {model: product.product, id: pos_product_product_2}: |
||||
|
default_code: ABC124 |
||||
|
name: POS Product 2 |
||||
|
type: product |
||||
|
categ_id: product.product_category_1 |
||||
|
list_price: 100.0 |
||||
|
standard_price: 100.0 |
||||
|
uom_id: product.product_uom_unit |
||||
|
uom_po_id: product.product_uom_unit |
||||
|
available_in_pos: True |
||||
|
|
||||
|
- |
||||
|
This product will have a rule that (based on supplier discount) |
||||
|
- |
||||
|
!record {model: product.product, id: pos_product_product_3}: |
||||
|
default_code: ABC125 |
||||
|
name: POS Product 3 |
||||
|
type: product |
||||
|
categ_id: product.product_category_1 |
||||
|
list_price: 100.0 |
||||
|
standard_price: 50.0 |
||||
|
uom_id: product.product_uom_unit |
||||
|
uom_po_id: product.product_uom_unit |
||||
|
available_in_pos: True |
||||
|
seller_ids: |
||||
|
- delay: 1 |
||||
|
name: base.res_partner_3 |
||||
|
pricelist_ids: |
||||
|
- min_quantity : 2.0 |
||||
|
price : 80 |
||||
|
|
||||
|
- |
||||
|
This product belgon to computer category (5% dicount) |
||||
|
- |
||||
|
!record {model: product.product, id: pos_product_product_4}: |
||||
|
default_code: ABC125 |
||||
|
name: POS Product 4 |
||||
|
type: product |
||||
|
categ_id: product.product_category_4 |
||||
|
list_price: 100.0 |
||||
|
standard_price: 50.0 |
||||
|
uom_id: product.product_uom_unit |
||||
|
uom_po_id: product.product_uom_unit |
||||
|
available_in_pos: True |
||||
|
- |
||||
|
Prepare pricelist items |
||||
|
- |
||||
|
!record {model: product.pricelist.version, id: product.ver0}: |
||||
|
items_id: |
||||
|
- name: 10% Discount on POS Product 1 (Qty 3) |
||||
|
sequence: 2 |
||||
|
product_id: pos_product_product_1 |
||||
|
base: 1 |
||||
|
price_discount: -0.10 |
||||
|
min_quantity: 3 |
||||
|
- name: 30% Discount on POS Product 1 (Qty 5) |
||||
|
sequence: 1 |
||||
|
product_id: pos_product_product_1 |
||||
|
min_quantity: 5 |
||||
|
base: 1 |
||||
|
price_discount: -0.30 |
||||
|
- name: 10% Discount (POS Product 2) |
||||
|
sequence: 1 |
||||
|
product_id: pos_product_product_2 |
||||
|
base: 2 |
||||
|
price_discount: -0.10 |
||||
|
min_quantity: 2 |
||||
|
- name: 20% Discount given by supplier |
||||
|
sequence: 1 |
||||
|
min_quantity: 2 |
||||
|
product_id: pos_product_product_3 |
||||
|
base: -2 |
||||
|
- name: 5% Discount on all Computer related products (Qty 2) |
||||
|
sequence: 1 |
||||
|
min_quantity: 2 |
||||
|
base: 1 |
||||
|
categ_id: product.product_category_4 |
||||
|
price_discount: -0.05 |
@ -0,0 +1,46 @@ |
|||||
|
/****************************************************************************** |
||||
|
* Point Of Sale - Pricelist 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/>. |
||||
|
* |
||||
|
******************************************************************************/ |
||||
|
.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; |
||||
|
} |
@ -0,0 +1,155 @@ |
|||||
|
/****************************************************************************** |
||||
|
* Point Of Sale - Pricelist 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 pos_pricelist_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_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_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]; |
||||
|
} |
||||
|
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; |
||||
|
}, |
||||
|
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,24 @@ |
|||||
|
/****************************************************************************** |
||||
|
* Point Of Sale - Pricelist 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_pricelist = function (instance) { |
||||
|
var module = instance.point_of_sale; |
||||
|
pos_pricelist_db(instance, module); |
||||
|
pos_pricelist_models(instance, module); |
||||
|
pos_pricelist_screens(instance, module); |
||||
|
pos_pricelist_widgets(instance, module); |
||||
|
}; |
@ -0,0 +1,631 @@ |
|||||
|
/****************************************************************************** |
||||
|
* Point Of Sale - Pricelist 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 pos_pricelist_models(instance, module) { |
||||
|
|
||||
|
var _t = instance.web._t; |
||||
|
var round_pr = instance.web.round_precision; |
||||
|
var round_di = instance.web.round_decimals; |
||||
|
|
||||
|
/** |
||||
|
* @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); |
||||
|
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 |
||||
|
}, |
||||
|
on_removed_order: function (removed_order, index, reason) { |
||||
|
this._super('on_removed_order', removed_order, index, reason); |
||||
|
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 |
||||
|
*/ |
||||
|
module.Orderline = module.Orderline.extend({ |
||||
|
initialize: function (attr, options) { |
||||
|
this._super('initialize', attr, options); |
||||
|
this.manuel_price = false; |
||||
|
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 = this.pos.db; |
||||
|
var price = this.pos.pricelist_engine.compute_price_all(db, product, partner, qty); |
||||
|
if (price !== false && price !== 0.0) { |
||||
|
this.price = price; |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
/** |
||||
|
* @param state |
||||
|
*/ |
||||
|
set_manuel_price: function (state) { |
||||
|
this.manuel_price = state; |
||||
|
}, |
||||
|
/** |
||||
|
* @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.pos.pricelist_engine.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 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 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 taxes = this.get_applicable_taxes(); |
||||
|
var taxtotal = 0; |
||||
|
var taxdetail = {}; |
||||
|
|
||||
|
// Add by pos_pricelist
|
||||
|
var partner = this.order.get_client(); |
||||
|
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 = []; |
||||
|
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.length; j < jlen; j++) { |
||||
|
var p_tax = taxes[j]; |
||||
|
if (fp_tax && p_tax && fp_tax.tax_src_id[0] === p_tax.id) { |
||||
|
var dest_tax = _.detect(this.pos.taxes, function (t) { |
||||
|
return t.id === fp_tax.tax_dest_id[0]; |
||||
|
}); |
||||
|
product_taxes.push(dest_tax); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
if (product_taxes.length === 0) { |
||||
|
for (var i = 0, ilen = product.taxes_id; i < ilen; i++) { |
||||
|
var _id = product.taxes_id[i]; |
||||
|
var p_tax = _.detect(this.pos.taxes, function (t) { |
||||
|
return t.id === _id; |
||||
|
}); |
||||
|
product_taxes.push(p_tax); |
||||
|
} |
||||
|
} |
||||
|
_.each(product_taxes, function (tax) { |
||||
|
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); |
||||
|
if (tax.include_base_amount) { |
||||
|
base += tmp; |
||||
|
} |
||||
|
taxtotal += tmp; |
||||
|
totalTax += tmp; |
||||
|
taxdetail[tax.id] = tmp; |
||||
|
} |
||||
|
}); |
||||
|
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 = this._super('can_be_merged_with', orderline); |
||||
|
if (!result) { |
||||
|
if (!this.manuel_price) { |
||||
|
return (this.get_product().id === orderline.get_product().id); |
||||
|
} else { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
return true; |
||||
|
}, |
||||
|
/** |
||||
|
* 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 && !orderlines[i].manuel_price) { |
||||
|
qty += orderlines[i].quantity; |
||||
|
} |
||||
|
} |
||||
|
return qty; |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
/** |
||||
|
* Pricelist Engine to compute price |
||||
|
*/ |
||||
|
module.PricelistEngine = instance.web.Class.extend({ |
||||
|
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.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)); |
||||
|
}, |
||||
|
/** |
||||
|
* 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) { |
||||
|
|
||||
|
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_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 !== 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 = db.get_product_by_id(product_id); |
||||
|
var price = this.compute_price_all(db, product, partner, 1); |
||||
|
if (price !== false && price !== 0.0) { |
||||
|
price = round_di(parseFloat(price) || 0, this.pos.dp['Product Price']); |
||||
|
price = this.pos_widget.format_currency(price); |
||||
|
$(product_ui).find('.price-tag').html(price); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
/** |
||||
|
* |
||||
|
* @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 && price !== 0.0) { |
||||
|
line.price = price; |
||||
|
} |
||||
|
line.trigger('change', line); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
/** |
||||
|
* 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'); |
||||
|
} |
||||
|
|
||||
|
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: 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) { |
||||
|
// 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'); |
||||
|
if (_.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,32 @@ |
|||||
|
/****************************************************************************** |
||||
|
* Point Of Sale - Pricelist 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 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); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
@ -0,0 +1,164 @@ |
|||||
|
/****************************************************************************** |
||||
|
* Point Of Sale - Pricelist 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 () { |
||||
|
'use strict'; |
||||
|
|
||||
|
openerp.Tour.register({ |
||||
|
id: 'pos_pricelist_order', |
||||
|
name: 'Complete a order trough the Front-End using POS Pricelist', |
||||
|
path: '/web#model=pos.session.opening&action=point_of_sale.action_pos_session_opening', |
||||
|
mode: 'test', |
||||
|
steps: [ |
||||
|
{ |
||||
|
wait: 2000, |
||||
|
title: 'Wait for screen to be ready' |
||||
|
}, |
||||
|
{ |
||||
|
wait: 2000, |
||||
|
title: 'Load the Session', |
||||
|
waitNot: '.oe_loading:visible', |
||||
|
element: 'span:contains("Resume Session"),span:contains("Start Session")' |
||||
|
}, |
||||
|
{ |
||||
|
title: 'Loading Screen', |
||||
|
waitFor: '.loader' |
||||
|
}, |
||||
|
{ |
||||
|
wait: 2000, |
||||
|
title: 'The Point of Sale', |
||||
|
waitFor: '.pos' |
||||
|
}, |
||||
|
{ |
||||
|
title: "We will buy some Products!, let's add (POS Product 1)", |
||||
|
element: '.product-list .product-name:contains("POS Product 1")' |
||||
|
}, |
||||
|
{ |
||||
|
wait: 5000, |
||||
|
title: 'The order total has been updated to the correct value : 100€', |
||||
|
waitFor: '.order .total .value:contains("100.00 €")' |
||||
|
}, |
||||
|
{ |
||||
|
wait: 5000, |
||||
|
title: 'We will add one more unit!', |
||||
|
element: '.product-list .product-name:contains("POS Product 1")' |
||||
|
}, |
||||
|
{ |
||||
|
wait: 4000, |
||||
|
title: 'We will add another unit', |
||||
|
element: '.product-list .product-name:contains("POS Product 1")' |
||||
|
}, |
||||
|
{ |
||||
|
wait: 4000, |
||||
|
title: 'The order total should be updated : 270€ which means 90€/Unit (Rule 10% Discount from 3 Units)', |
||||
|
waitFor: '.order .total .value:contains("270.00 €")' |
||||
|
}, |
||||
|
{ |
||||
|
wait: 8000, |
||||
|
title: 'We will add another product', |
||||
|
element: '.product-list .product-name:contains("POS Product 2")' |
||||
|
}, |
||||
|
{ |
||||
|
wait: 4000, |
||||
|
title: 'We will add another unit for this product (POS Product 2)', |
||||
|
element: '.product-list .product-name:contains("POS Product 2")' |
||||
|
}, |
||||
|
{ |
||||
|
wait: 4000, |
||||
|
title: "Let's verify the total that we should pay, it's should be equal to : 450€, which means that <br>" + |
||||
|
"10% Discount if offered if we buy 2 units of (POS Product 2), Rule based on standard price", |
||||
|
waitFor: '.order .total .value:contains("450.00 €")' |
||||
|
}, |
||||
|
{ |
||||
|
wait: 10000, |
||||
|
title: "Now, we will add (POS Product 3), for this product if we buy more then 2 units <br>" + |
||||
|
"20% Discount is given by supplier to our customers", |
||||
|
element: '.product-list .product-name:contains("POS Product 3")' |
||||
|
}, |
||||
|
{ |
||||
|
wait: 10000, |
||||
|
title: 'We will add another unit for this product (POS Product 3)', |
||||
|
element: '.product-list .product-name:contains("POS Product 3")' |
||||
|
}, |
||||
|
{ |
||||
|
wait: 5000, |
||||
|
title: "Let's check the total (610€)", |
||||
|
waitFor: '.order .total .value:contains("610.00 €")' |
||||
|
}, |
||||
|
{ |
||||
|
wait: 5000, |
||||
|
title: "Now, we will add (POS Product 4), this product belong to (Comptuer) category in which " + |
||||
|
"we apply 5% if customer buy more then 2 products", |
||||
|
element: '.product-list .product-name:contains("POS Product 4")' |
||||
|
}, |
||||
|
{ |
||||
|
wait: 10000, |
||||
|
title: 'We will add another unit for this product (POS Product 4)', |
||||
|
element: '.product-list .product-name:contains("POS Product 4")' |
||||
|
}, |
||||
|
{ |
||||
|
wait: 5000, |
||||
|
title: "Let's check the total again (800€)", |
||||
|
waitFor: '.order .total .value:contains("800.00 €")' |
||||
|
}, |
||||
|
{ |
||||
|
wait: 5000, |
||||
|
title: "Let's pay the order", |
||||
|
element: ".paypad-button:contains('Bank')" |
||||
|
}, |
||||
|
{ |
||||
|
wait: 1000, |
||||
|
title: "Let's accept the payment", |
||||
|
onload: function () { |
||||
|
window._print = window.print; |
||||
|
window.print = function () { |
||||
|
console.log('Print!') |
||||
|
}; |
||||
|
}, |
||||
|
element: ".button .iconlabel:contains('Validate'):visible" |
||||
|
}, |
||||
|
{ |
||||
|
wait: 1000, |
||||
|
title: "Let's finish the order", |
||||
|
element: ".button:not(.disabled) .iconlabel:contains('Next'):visible" |
||||
|
}, |
||||
|
{ |
||||
|
wait: 1000, |
||||
|
onload: function () { |
||||
|
window.print = window._print; |
||||
|
window._print = undefined; |
||||
|
}, |
||||
|
title: "Let's wait for the order posting", |
||||
|
waitFor: ".oe_status.js_synch .js_connected:visible" |
||||
|
}, |
||||
|
{ |
||||
|
wait: 1000, |
||||
|
title: "Let's close the Point of Sale", |
||||
|
element: ".header-button:contains('Close')" |
||||
|
}, |
||||
|
{ |
||||
|
title: "Let's confirm", |
||||
|
element: ".header-button.confirm:contains('Confirm')" |
||||
|
}, |
||||
|
{ |
||||
|
title: "Wait for the backend to ready itself", |
||||
|
element: 'span:contains("Resume Session"),span:contains("Start Session")' |
||||
|
} |
||||
|
] |
||||
|
}); |
||||
|
|
||||
|
})(); |
@ -0,0 +1,41 @@ |
|||||
|
/****************************************************************************** |
||||
|
* Point Of Sale - Pricelist 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 pos_pricelist_widgets(instance, module) { |
||||
|
|
||||
|
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_manuel_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); |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
@ -0,0 +1,28 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################## |
||||
|
# Point Of Sale - Pricelist 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/>. |
||||
|
# |
||||
|
############################################################################## |
||||
|
import openerp.tests |
||||
|
|
||||
|
|
||||
|
@openerp.tests.common.at_install(False) |
||||
|
@openerp.tests.common.post_install(True) |
||||
|
class TestPOS(openerp.tests.HttpCase): |
||||
|
def test_01_pos(self): |
||||
|
self.phantom_js("/", "openerp.Tour.run('pos_pricelist_order', 'test')", |
||||
|
"openerp.Tour.tours.pos_pricelist_order", |
||||
|
login="admin") |
@ -0,0 +1,29 @@ |
|||||
|
<openerp> |
||||
|
<data> |
||||
|
<template id="pos_pricelist_assets_backend" |
||||
|
name="pos_pricelist_assets_backend" |
||||
|
inherit_id="point_of_sale.assets_backend"> |
||||
|
<xpath expr="." position="inside"> |
||||
|
<script src="/pos_pricelist/static/src/js/db.js" |
||||
|
type="text/javascript"></script> |
||||
|
<script src="/pos_pricelist/static/src/js/models.js" |
||||
|
type="text/javascript"></script> |
||||
|
<script src="/pos_pricelist/static/src/js/widgets.js" |
||||
|
type="text/javascript"></script> |
||||
|
<script src="/pos_pricelist/static/src/js/screens.js" |
||||
|
type="text/javascript"></script> |
||||
|
<script src="/pos_pricelist/static/src/js/tests.js" |
||||
|
type="text/javascript"></script> |
||||
|
<script src="/pos_pricelist/static/src/js/main.js" |
||||
|
type="text/javascript"></script> |
||||
|
</xpath> |
||||
|
</template> |
||||
|
|
||||
|
<template id="pos_pricelist_assets" name="pos_pricelist_css" inherit_id="point_of_sale.index"> |
||||
|
<xpath expr="//script[@id='loading-script']" position="before"> |
||||
|
<link rel="stylesheet" href="/pos_pricelist/static/src/css/style.css"/> |
||||
|
</xpath> |
||||
|
</template> |
||||
|
|
||||
|
</data> |
||||
|
</openerp> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue