diff --git a/addons/sale_advance_pricelist/__init__.py b/addons/sale_advance_pricelist/__init__.py new file mode 100644 index 00000000..6a64595a --- /dev/null +++ b/addons/sale_advance_pricelist/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of flectra. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/addons/sale_advance_pricelist/__manifest__.py b/addons/sale_advance_pricelist/__manifest__.py new file mode 100644 index 00000000..7933f43f --- /dev/null +++ b/addons/sale_advance_pricelist/__manifest__.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Part of flectra. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'Sale Advance Ecommerce Pricelist', + 'version': '1.0.0', + 'category': 'Sale', + 'author': 'FlectraHQ', + 'website': 'https://flectrahq.com', + 'sequence': 25, + 'summary': 'Add Price Rules, Cart Rules and Coupon Rules', + 'description': """ +Pricelist +========= +Main Features +------------- +* Price Rules +* Cart Rules +* Coupon Rules +* View Calculation of all discount +""", + 'depends': ['sales_discount'], + 'data': [ + 'security/ir.model.access.csv', + 'data/pricelist_view.xml', + 'views/pricelist_view.xml', + 'views/price_rule_view.xml', + 'views/sale_views.xml', + 'views/sale_pricelist.xml', + ], + 'demo': [ + 'demo/res_partner_demo.xml', + 'demo/price_rule_demo.xml', + 'demo/cart_rule_view_data.xml', + 'demo/coupon_code_demo.xml', + 'demo/sale_order_demo.xml', + ], + 'qweb': [ + "static/src/xml/discount_details_view.xml", + ], + 'auto_install': False +} diff --git a/addons/sale_advance_pricelist/data/pricelist_view.xml b/addons/sale_advance_pricelist/data/pricelist_view.xml new file mode 100644 index 00000000..75ae5cd8 --- /dev/null +++ b/addons/sale_advance_pricelist/data/pricelist_view.xml @@ -0,0 +1,33 @@ + + + + + + Advance Pricelist (First Matched Rule) + 2 + without_discount + advance + + + + + + Advance Pricelist (First Matched Rule With Coupon) + 3 + without_discount + advance + true + + + + + + Advance Pricelist with All Matched Rule + 3 + without_discount + advance + all_matched_rules + + + + diff --git a/addons/sale_advance_pricelist/demo/cart_rule_view_data.xml b/addons/sale_advance_pricelist/demo/cart_rule_view_data.xml new file mode 100644 index 00000000..5902c9a7 --- /dev/null +++ b/addons/sale_advance_pricelist/demo/cart_rule_view_data.xml @@ -0,0 +1,63 @@ + + + + + + subtotal_at_least + 1 + 6.5 + 2500 + + + + + one_product_al_least + 2 + 5 + + + + + + + + subtotal_less_than + 1 + 2.5 + 3000 + + + + + item_sum_qty_atleast + 2 + 10 + 7 + + + + + item_count_atleast + 1 + 7 + 6 + + + + + item_count_less_than + 2 + 4 + 4 + + + + + one_categ_al_least + 3 + 3.5 + + + + + diff --git a/addons/sale_advance_pricelist/demo/coupon_code_demo.xml b/addons/sale_advance_pricelist/demo/coupon_code_demo.xml new file mode 100644 index 00000000..53cbadff --- /dev/null +++ b/addons/sale_advance_pricelist/demo/coupon_code_demo.xml @@ -0,0 +1,87 @@ + + + + + Get10% + Get10Peroff + + + percent + 5 + 10 + 2500 + all + + + + + Get20-Off-per-Quantity + Get20off + + + fixed_amount + 5 + 20 + 2500 + all + + + + + Buy X Product Get Y Product Free + BXGYFree + + + buy_x_get_y + 5 + 3 + 1 + 4000 + all + + + + + Buy X Product Get Y Other Product Free + BXGYOtherFree + + + buy_x_get_y_other + 5 + 2 + 1 + + 4000 + all + + + + + Clubbed Discount + CD15Per + + + clubbed + 5 + 10 + 5 + 3000 + all + + + + + Buy X Product Get Percent Free + BXGPercentFree + + + buy_x_get_percent + 3 + 3 + 7.75 + 2500 + all + + + + \ No newline at end of file diff --git a/addons/sale_advance_pricelist/demo/price_rule_demo.xml b/addons/sale_advance_pricelist/demo/price_rule_demo.xml new file mode 100644 index 00000000..17845baa --- /dev/null +++ b/addons/sale_advance_pricelist/demo/price_rule_demo.xml @@ -0,0 +1,125 @@ + + + + + + all + 1 + + + + + + 1 + 1 + 5 + fixed_amount + 50 + + + + + 2 + 11 + 15 + percent + 12 + + + + + + + + category + + 2 + + + + + + + 1 + 1 + 5 + percent + 5 + + + + + + + 2 + 8 + 15 + percent + 8 + + + + + + + + all + 1 + + + + + + 1 + 1 + 6 + percent + 4.9 + + + + + 1 + 11 + 15 + percent + 9.5 + + + + + + all + 1 + + + + + + 1 + 1 + 3 + percent + 3 + + + + + 1 + 4 + 8 + percent + 5 + + + + + 1 + 9 + 15 + percent + 7 + + + + diff --git a/addons/sale_advance_pricelist/demo/res_partner_demo.xml b/addons/sale_advance_pricelist/demo/res_partner_demo.xml new file mode 100644 index 00000000..6a0a4372 --- /dev/null +++ b/addons/sale_advance_pricelist/demo/res_partner_demo.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + Josheph + + 1 + Baltimore + 21201 + + Josheph@yourcompany.example.com + + + \ No newline at end of file diff --git a/addons/sale_advance_pricelist/demo/sale_order_demo.xml b/addons/sale_advance_pricelist/demo/sale_order_demo.xml new file mode 100644 index 00000000..902031f3 --- /dev/null +++ b/addons/sale_advance_pricelist/demo/sale_order_demo.xml @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + Graphics Card + + 5 + + 885.0 + + + + + Laptop E5023 + + 3 + + 2950.00 + + + + + Computer Case + + 3 + + 25 + + + + + + + + + + + + + + + + + + Graphics Card + + 5 + + 885.0 + + + + + Laptop E5023 + + 3 + + 2950.00 + + + + + + Get10Peroff + + + + + + + + + + + + + + + + + + Graphics Card + + 5 + + 885.0 + + + + + Laptop E5023 + + 3 + + 2950.00 + + + + + + Get20off + + + + + + + + + + + + + + + + + + + Graphics Card + + 5 + + 885.0 + + + + + Laptop E5023 + + 3 + + 2950.00 + + + + + + BXGYFree + + + + + + + + + + + + + + + + + + Graphics Card + + 5 + + 885.0 + + + + + Laptop E5023 + + 3 + + 2950.00 + + + + + + BXGYOtherFree + + + + + + + + + + + + + + + + + + Graphics Card + + 5 + + 885.0 + + + + + Laptop E5023 + + 2 + + 2950.00 + + + + + + BXGPercentFree + + + + + + + + + + + + + + + + + + Graphics Card + + 5 + + 885.0 + + + + + Laptop E5023 + + 3 + + 2950.00 + + + + + + CD15Per + + + + + + + + + + + + + + + + + + Graphics Card + + 5 + + 885.0 + + + + + Laptop E5023 + + 3 + + 2950.00 + + + + + \ No newline at end of file diff --git a/addons/sale_advance_pricelist/i18n/sale_advance_pricelist.pot b/addons/sale_advance_pricelist/i18n/sale_advance_pricelist.pot new file mode 100644 index 00000000..704e521f --- /dev/null +++ b/addons/sale_advance_pricelist/i18n/sale_advance_pricelist.pot @@ -0,0 +1,607 @@ +# Translation of Flectra Server. +# This file contains the translation of the following modules: +# * sale_advance_pricelist +# +msgid "" +msgstr "" +"Project-Id-Version: Flectra Server 1.1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-04-12 05:50+0000\n" +"PO-Revision-Date: 2018-04-12 05:50+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_sale_order_count +msgid "# of Sale Order" +msgstr "# of Sale Order" + +#. module: sale_advance_pricelist +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_coupon_code_form +msgid "+ Extra" +msgstr "+ Extra" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule_active +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_active +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_price_rule_active +msgid "Active" +msgstr "Active" + +#. module: sale_advance_pricelist +#: model:product.pricelist,name:sale_advance_pricelist.advance_pricelist_with_coupon +msgid "Advance Pricelist (First Matched Rule With Coupon)" +msgstr "Advance Pricelist (First Matched Rule With Coupon)" + +#. module: sale_advance_pricelist +#: model:product.pricelist,name:sale_advance_pricelist.advance_pricelist +msgid "Advance Pricelist (First Matched Rule) " +msgstr "Advance Pricelist (First Matched Rule) " + +#. module: sale_advance_pricelist +#: model:product.pricelist,name:sale_advance_pricelist.advance_pricelist_all +msgid "Advance Pricelist with All Matched Rule" +msgstr "Advance Pricelist with All Matched Rule" + +#. module: sale_advance_pricelist +#: selection:product.pricelist,pricelist_type:0 +msgid "Advanced" +msgstr "Advanced" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule_amt_value +msgid "Amount" +msgstr "Amount" + +#. module: sale_advance_pricelist +#: model:ir.ui.view,arch_db:sale_advance_pricelist.product_pricelist_view_flectra +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_cart_rule_tree +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_coupon_code_tree +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_price_rule_tree +msgid "Applicable On" +msgstr "Applicable On" + +#. module: sale_advance_pricelist +#: selection:product.pricelist,apply_method:0 +msgid "Apply All Matched Rules" +msgstr "Apply All Matched Rules" + +#. module: sale_advance_pricelist +#: selection:product.pricelist,apply_method:0 +msgid "Apply Biggest Matched Discount" +msgstr "Apply Biggest Matched Discount" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_sale_order_line_check_coupon +msgid "Apply Coupon" +msgstr "Apply Coupon" + +#. module: sale_advance_pricelist +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_order_form_flectra +msgid "Apply Coupon Code" +msgstr "Apply Coupon Code" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_product_pricelist_apply_coupon_code +msgid "Apply Coupon Code?" +msgstr "Apply Coupon Code?" + +#. module: sale_advance_pricelist +#: selection:product.pricelist,apply_method:0 +msgid "Apply First Matched Rule" +msgstr "Apply First Matched Rule" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_product_pricelist_apply_method +msgid "Apply Method" +msgstr "Apply Method" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule_apply_on +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_apply_on +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_price_rule_apply_on +msgid "Apply On" +msgstr "Apply On" + +#. module: sale_advance_pricelist +#: selection:product.pricelist,apply_method:0 +msgid "Apply Smallest Matched Discount" +msgstr "Apply Smallest Matched Discount" + +#. module: sale_advance_pricelist +#: selection:cart.rule,apply_on:0 +msgid "At least one category in order" +msgstr "At least one category in order" + +#. module: sale_advance_pricelist +#: selection:cart.rule,apply_on:0 +msgid "At least one product in order" +msgstr "At least one product in order" + +#. module: sale_advance_pricelist +#: selection:product.pricelist,pricelist_type:0 +msgid "Basic" +msgstr "Basic" + +#. module: sale_advance_pricelist +#: selection:coupon.code,coupon_type:0 +msgid "Buy X Product Get Y Other Product Free" +msgstr "Buy X Product Get Y Other Product Free" + +#. module: sale_advance_pricelist +#: selection:coupon.code,coupon_type:0 +msgid "Buy X Product Get Y Product Free" +msgstr "Buy X Product Get Y Product Free" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_sale_order_cart_discount +msgid "Cart Discount" +msgstr "Cart Discount" + +#. module: sale_advance_pricelist +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_cart_rule_form +msgid "Cart Rule" +msgstr "Cart Rule" + +#. module: sale_advance_pricelist +#: model:ir.model,name:sale_advance_pricelist.model_cart_rule +#: model:ir.ui.view,arch_db:sale_advance_pricelist.product_pricelist_view_flectra +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_cart_rule_tree +msgid "Cart Rules" +msgstr "Cart Rules" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_product_pricelist_cart_rule_ids +msgid "Cart Rules Items" +msgstr "Cart Rules Items" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule_categ_ids +msgid "Categories" +msgstr "Categories" + +#. module: sale_advance_pricelist +#: selection:coupon.code,apply_on:0 +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule_categ_id +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_categ_id +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_other_categ_id +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_price_rule_categ_id +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_categ_id +#: selection:price.rule,apply_on:0 +msgid "Category" +msgstr "Category" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_sale_order_coupon_flag +msgid "Check Coupon Apply" +msgstr "Check Coupon Apply" + +#. module: sale_advance_pricelist +#: selection:coupon.code,coupon_type:0 +msgid "Clubbed Discount" +msgstr "Clubbed Discount" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_model_id +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_model_id +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_coupon_code_form +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_price_rule_form +msgid "Condition" +msgstr "Condition" + +#. module: sale_advance_pricelist +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_coupon_code_form +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_coupon_code_tree +msgid "Coupon" +msgstr "Coupon" + +#. module: sale_advance_pricelist +#: model:ir.model,name:sale_advance_pricelist.model_coupon_code +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_coupon_code +#: model:ir.ui.view,arch_db:sale_advance_pricelist.product_pricelist_view_flectra +msgid "Coupon Code" +msgstr "Coupon Code" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_product_pricelist_coupon_code_lines +msgid "Coupon Code Items" +msgstr "Coupon Code Items" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_sale_order_coupon_code_id +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_sale_order_line_coupon_code_id +msgid "Coupon Ref" +msgstr "Coupon Ref" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_coupon_type +msgid "Coupon Type" +msgstr "Coupon Type" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule_create_uid +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_create_uid +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_price_rule_create_uid +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_create_uid +msgid "Created by" +msgstr "Created by" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule_create_date +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_create_date +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_price_rule_create_date +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_create_date +msgid "Created on" +msgstr "Created on" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule_note +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_price_rule_note +msgid "Description" +msgstr "Description" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule_discount_percentage +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_sale_order_line_dummy_discount +msgid "Discount (%)" +msgstr "Discount (%)" + +#. module: sale_advance_pricelist +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_coupon_code_form +msgid "Discount =" +msgstr "Discount =" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_discount_amount +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_discount_amount +msgid "Discount Amount" +msgstr "Discount Amount" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_sale_order_amount_words +msgid "Discount Amount Words" +msgstr "Discount Amount Words" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_sale_order_discount_widget +msgid "Discount Widget" +msgstr "Discount Widget" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule_display_name +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_display_name +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_price_rule_display_name +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_display_name +msgid "Display Name" +msgstr "Display Name" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_model_domain +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_model_domain +msgid "Domain" +msgstr "Domain" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule_end_date +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_price_rule_end_date +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_end_date +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_rule_id_end_date +msgid "End Date" +msgstr "End Date" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_extra_discount_percentage +msgid "Extra Discount" +msgstr "Extra Discount" + +#. module: sale_advance_pricelist +#: selection:coupon.code,coupon_type:0 +#: selection:rule.line,rule_type:0 +msgid "Fixed Amount" +msgstr "Fixed Amount" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_flat_discount +msgid "Flat Discount" +msgstr "Flat Discount" + +#. module: sale_advance_pricelist +#: selection:coupon.code,apply_on:0 +#: selection:price.rule,apply_on:0 +msgid "Global" +msgstr "Global" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_sale_order_have_coupon_code +msgid "Have Coupon Code" +msgstr "Have Coupon Code" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule_id +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_id +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_price_rule_id +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_id +msgid "ID" +msgstr "ID" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule___last_update +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code___last_update +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_price_rule___last_update +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line___last_update +msgid "Last Modified on" +msgstr "Last Modified on" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule_write_uid +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_write_uid +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_price_rule_write_uid +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_write_uid +msgid "Last Updated by" +msgstr "Last Updated by" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule_write_date +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_write_date +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_price_rule_write_date +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_write_date +msgid "Last Updated on" +msgstr "Last Updated on" + +#. module: sale_advance_pricelist +#: selection:cart.rule,apply_on:0 +msgid "Lines Count at least" +msgstr "Lines Count at least" + +#. module: sale_advance_pricelist +#: selection:cart.rule,apply_on:0 +msgid "Lines less than" +msgstr "Lines less than" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_max_qty +msgid "Max. Quantity" +msgstr "Max. Quantity" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_min_order_amount +msgid "Min Order Amount" +msgstr "Min Order Amount" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_min_qty +msgid "Min. Quantity" +msgstr "Min. Quantity" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_model_name +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_model_name +msgid "Model Name" +msgstr "Model Name" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule_name +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_name +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_price_rule_name +msgid "Name" +msgstr "Name" + +#. module: sale_advance_pricelist +#: selection:cart.rule,apply_on:0 +msgid "None of selected Categories" +msgstr "None of selected Categories" + +#. module: sale_advance_pricelist +#: selection:cart.rule,apply_on:0 +msgid "None of selected Products" +msgstr "None of selected Products" + +#. module: sale_advance_pricelist +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_cart_rule_form +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_price_rule_form +msgid "Note" +msgstr "Note" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_number_of_x_product +msgid "Number Of X Product" +msgstr "Number Of X Product" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_number_of_y_product +msgid "Number Of Y Product" +msgstr "Number Of Y Product" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_other_product_id +msgid "Other Product" +msgstr "Other Product" + +#. module: sale_advance_pricelist +#: selection:coupon.code,coupon_type:0 +#: selection:rule.line,rule_type:0 +msgid "Percent" +msgstr "Percent" + +#. module: sale_advance_pricelist +#: model:ir.ui.view,arch_db:sale_advance_pricelist.product_pricelist_view_flectra +msgid "Price" +msgstr "Price" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_price_rule_id +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_price_rule_form +msgid "Price Rule" +msgstr "Price Rule" + +#. module: sale_advance_pricelist +#: model:ir.model,name:sale_advance_pricelist.model_price_rule +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_product_pricelist_rule_ids +#: model:ir.ui.view,arch_db:sale_advance_pricelist.product_pricelist_view_flectra +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_price_rule_tree +msgid "Price Rules" +msgstr "Price Rules" + +#. module: sale_advance_pricelist +#: model:ir.model,name:sale_advance_pricelist.model_product_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule_pricelist_id +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_pricelist_id +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_price_rule_pricelist_id +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_pricelist_id +msgid "Pricelist" +msgstr "Pricelist" + +#. module: sale_advance_pricelist +#: model:ir.ui.view,arch_db:sale_advance_pricelist.product_pricelist_view_flectra +msgid "Pricelist Items" +msgstr "Pricelist Items" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_product_pricelist_pricelist_type +msgid "Pricelist Type" +msgstr "Pricelist Type" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule_product_id +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_product_id +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_price_rule_product_id +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_product_id +msgid "Product" +msgstr "Product" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_price_rule_rule_lines +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_price_rule_form +msgid "Product Rule Lines" +msgstr "Product Rule Lines" + +#. module: sale_advance_pricelist +#: selection:coupon.code,apply_on:0 +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_product_tmpl_id +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_price_rule_product_tmpl_id +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_product_tmpl_id +#: selection:price.rule,apply_on:0 +msgid "Product Template" +msgstr "Product Template" + +#. module: sale_advance_pricelist +#: selection:coupon.code,apply_on:0 +#: selection:price.rule,apply_on:0 +msgid "Product Variant" +msgstr "Product Variant" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule_product_ids +msgid "Products" +msgstr "Products" + +#. module: sale_advance_pricelist +#: model:ir.model,name:sale_advance_pricelist.model_sale_order +msgid "Quotation" +msgstr "Quotation" + +#. module: sale_advance_pricelist +#: selection:coupon.code,coupon_type:0 +msgid "Range Based Discount(Buy X Product Get Percent Free)" +msgstr "Range Based Discount(Buy X Product Get Percent Free)" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_model_real +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_model_real +msgid "Real Model" +msgstr "Real Model" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_remaining_limit +msgid "Remaining Usage Limit" +msgstr "Remaining Usage Limit" + +#. module: sale_advance_pricelist +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_order_form_flectra +msgid "Remove Coupon Code" +msgstr "Remove Coupon Code" + +#. module: sale_advance_pricelist +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_price_rule_form +msgid "Rule Line" +msgstr "Rule Line" + +#. module: sale_advance_pricelist +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_price_rule_form +msgid "Rule Lines" +msgstr "Rule Lines" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_rule_type +msgid "Rule Type" +msgstr "Rule Type" + +#. module: sale_advance_pricelist +#: model:ir.ui.view,arch_db:sale_advance_pricelist.view_coupon_code_form +msgid "Sales" +msgstr "Sales" + +#. module: sale_advance_pricelist +#: model:ir.model,name:sale_advance_pricelist.model_sale_order_line +msgid "Sales Order Line" +msgstr "Sales Order Line" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule_sequence +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_price_rule_sequence +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_sequence +msgid "Sequence" +msgstr "Sequence" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_cart_rule_start_date +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_price_rule_start_date +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_rule_id_start_date +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_rule_line_start_date +msgid "Start Date" +msgstr "Start Date" + +#. module: sale_advance_pricelist +#: selection:cart.rule,apply_on:0 +msgid "Subtotal At Least" +msgstr "Subtotal At Least" + +#. module: sale_advance_pricelist +#: selection:cart.rule,apply_on:0 +msgid "Subtotal less than" +msgstr "Subtotal less than" + +#. module: sale_advance_pricelist +#: selection:cart.rule,apply_on:0 +msgid "Sum of Item Qty at least" +msgstr "Sum of Item Qty at least" + +#. module: sale_advance_pricelist +#: selection:cart.rule,apply_on:0 +msgid "Sum of Item Qty less than" +msgstr "Sum of Item Qty less than" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_usage_limit +msgid "Total Usage Limit" +msgstr "Total Usage Limit" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_code_valid_from +msgid "Valid From" +msgstr "Valid From" + +#. module: sale_advance_pricelist +#: model:ir.model.fields,field_description:sale_advance_pricelist.field_coupon_code_code_valid_to +msgid "Valid To" +msgstr "Valid To" + +#. module: sale_advance_pricelist +#: model:ir.model,name:sale_advance_pricelist.model_rule_line +msgid "rule.line" +msgstr "rule.line" + diff --git a/addons/sale_advance_pricelist/models/__init__.py b/addons/sale_advance_pricelist/models/__init__.py new file mode 100644 index 00000000..08d2e84c --- /dev/null +++ b/addons/sale_advance_pricelist/models/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Part of flectra. See LICENSE file for full copyright and licensing details. + +from . import price_rules +from . import sale +from . import pricelist diff --git a/addons/sale_advance_pricelist/models/price_rules.py b/addons/sale_advance_pricelist/models/price_rules.py new file mode 100644 index 00000000..430161e3 --- /dev/null +++ b/addons/sale_advance_pricelist/models/price_rules.py @@ -0,0 +1,444 @@ +# -*- coding: utf-8 -*- +# Part of flectra. See LICENSE file for full copyright and licensing details. + +from flectra import api, fields, models, _ +from flectra.exceptions import Warning +from flectra.tools.safe_eval import safe_eval +from flectra.tools.misc import formatLang + +MODELS_LIST = [ + 'res.partner', + 'res.partner.category', + 'res.users', + 'res.groups', + 'res.country.state', + 'res.country', +] + +Cart_Option = [ + ('subtotal_at_least', 'Subtotal At Least'), + ('subtotal_less_than', 'Subtotal less than'), + ('item_count_atleast', 'Lines Count at least'), + ('item_count_less_than', 'Lines less than'), + ('item_sum_qty_atleast', 'Sum of Item Qty at least'), + ('item_sum_qty_less_than', 'Sum of Item Qty less than'), + ('one_product_al_least', 'At least one product in order'), + ('none_of_sel_products', 'None of selected Products'), + ('one_categ_al_least', 'At least one category in order'), + ('none_of_sel_categs', 'None of selected Categories'), +] + + +class RulesLine(models.Model): + _name = 'rule.line' + _order = 'sequence' + + sequence = fields.Integer('Sequence') + start_date = fields.Date('Start Date') + end_date = fields.Date('End Date') + min_qty = fields.Float('Min. Quantity') + max_qty = fields.Float('Max. Quantity') + rule_type = fields.Selection([ + ('percent', 'Percent'), + ('fixed_amount', 'Fixed Amount'), + ], 'Rule Type', required=True, default='percent') + discount_amount = fields.Float('Discount Amount') + price_rule_id = fields.Many2one('price.rule', 'Price Rule') + model_id = fields.Many2one( + 'ir.model', string='Condition', domain=[('model', 'in', MODELS_LIST)]) + model_name = fields.Char(related='model_id.model', string='Model Name') + model_domain = fields.Char(string='Domain', oldname='domain', default=[]) + model_real = fields.Char(compute='_compute_model', string='Real Model') + pricelist_id = fields.Many2one( + 'product.pricelist', related='price_rule_id.pricelist_id', store=True) + categ_id = fields.Many2one( + 'product.category', related='price_rule_id.categ_id', store=True) + product_tmpl_id = fields.Many2one( + 'product.template', + related='price_rule_id.product_tmpl_id', store=True) + product_id = fields.Many2one( + 'product.product', related='price_rule_id.product_id', store=True) + rule_id_start_date = fields.Date( + related='price_rule_id.start_date', store=True) + rule_id_end_date = fields.Date( + related='price_rule_id.end_date', store=True) + + @api.onchange('rule_type', 'discount_amount') + def check_percentage(self): + warning = {} + if self.rule_type == 'percent' and ( + self.discount_amount > 100 or + self.discount_amount < 0): + warning.update({ + 'title': _("Warning"), + 'message': _("Percentage should be between 0% to 100%!")}) + self.discount_amount = 0.0 + return {'warning': warning} + + @api.depends('model_id') + def _compute_model(self): + for record in self: + if record.model_id: + record.model_real = record.model_name or 'res.partner' + + @api.constrains('start_date', 'end_date', + 'rule_id_start_date', 'rule_id_end_date') + def check_date(self): + for rule_line_id in self: + parent_start_date = rule_line_id.rule_id_start_date + child_start_date = rule_line_id.start_date + if parent_start_date and \ + child_start_date and parent_start_date > child_start_date: + raise Warning(_("Start Date date not valid in " + "Product Rule Lines!")) + parent_end_date = rule_line_id.rule_id_end_date + child_end_date = rule_line_id.end_date + if parent_end_date and \ + child_end_date and parent_end_date < child_end_date: + raise Warning(_("End Date date not valid in " + "Product Rule Lines!")) + + +class PriceRules(models.Model): + _name = 'price.rule' + _description = 'Price Rules' + _order = 'sequence' + + @api.multi + @api.depends('apply_on', 'categ_id', 'product_tmpl_id', 'product_id') + def _get_pricerule_name_price(self): + for record in self: + if record.categ_id: + record.name = _("Category: %s") % (record.categ_id.name) + elif record.product_tmpl_id: + record.name = record.product_tmpl_id.name + elif record.product_id: + record.name = record.product_id.display_name.replace( + '[%s]' % record.product_id.code, '') + else: + record.name = _("All Products") + + name = fields.Char('Name', compute='_get_pricerule_name_price') + sequence = fields.Integer('Sequence') + apply_on = fields.Selection([ + ('all', 'Global'), + ('category', 'Category'), + ('product_template', 'Product Template'), + ('product', 'Product Variant') + ], required=True, default='all', string="Apply On") + categ_id = fields.Many2one('product.category', 'Category') + product_tmpl_id = fields.Many2one('product.template', 'Product Template') + product_id = fields.Many2one('product.product', 'Product') + active = fields.Boolean('Active', default=True) + start_date = fields.Date('Start Date') + end_date = fields.Date('End Date') + note = fields.Text('Description') + pricelist_id = fields.Many2one('product.pricelist', + 'Pricelist', index=True, ondelete='cascade') + rule_lines = fields.One2many('rule.line', 'price_rule_id', + 'Product Rule Lines') + + @api.onchange('apply_on') + def _onchange_apply_on(self): + if self.apply_on != 'product': + self.product_id = False + if self.apply_on != 'product_template': + self.product_tmpl_id = False + if self.apply_on != 'category': + self.categ_id = False + + @api.multi + def get_rules(self, pricelist_id, date): + date = fields.Date.context_today(self) + self._cr.execute( + 'SELECT rule.id ' + 'FROM price_rule AS rule ' + 'WHERE (rule.pricelist_id = %s) ' + 'AND (rule.start_date IS NULL OR rule.start_date<=%s) ' + 'AND (rule.end_date IS NULL OR rule.end_date>=%s)' + 'ORDER BY rule.sequence', + (pricelist_id.id, date, date)) + rules_ids = [x[0] for x in self._cr.fetchall()] + rules = self.browse(rules_ids) + return rules + + +class CartRules(models.Model): + _name = 'cart.rule' + _description = 'Cart Rules' + _order = 'sequence' + + @api.multi + @api.depends('apply_on', 'amt_value', 'product_id', 'categ_id') + def _get_cart_name_price(self): + for record in self: + select_option = dict(Cart_Option) + if record.apply_on in ['subtotal_at_least', 'subtotal_less_than', + 'item_count_atleast', + 'item_count_less_than', + 'item_sum_qty_atleast', + 'item_sum_qty_less_than']: + record.name = select_option[record.apply_on] + ' : ' + str( + formatLang(self.env, record.amt_value, digits=2)) + elif record.apply_on == 'one_product_al_least' and \ + record.product_id: + record.name = select_option[record.apply_on] + ' : ' + str( + record.product_id.name) + elif record.apply_on == 'one_categ_al_least' and \ + record.categ_id: + record.name = select_option[record.apply_on] + ' : ' + str( + record.categ_id.name) + elif record.apply_on: + record.name = select_option[record.apply_on] + + name = fields.Char('Name', compute='_get_cart_name_price') + sequence = fields.Integer('Sequence') + active = fields.Boolean('Active', default=True) + start_date = fields.Date('Start Date') + end_date = fields.Date('End Date') + discount_percentage = fields.Float('Discount (%)') + apply_on = fields.Selection(Cart_Option, 'Apply On') + amt_value = fields.Float('Amount') + product_id = fields.Many2one('product.product', 'Product') + product_ids = fields.Many2many('product.product', column1='cart_line_id', + column2='product_id', string='Products') + categ_id = fields.Many2one('product.category', 'Category') + categ_ids = fields.Many2many('product.category', column1='cart_line_id', + column2='category_id', string='Categories') + note = fields.Text('Description') + pricelist_id = fields.Many2one('product.pricelist') + + @api.onchange('discount_percentage') + def check_percentage(self): + warning = {} + if self.discount_percentage > 100 or self.discount_percentage < 0: + warning.update({ + 'title': _("Warning"), + 'message': _("Percentage should be between 0% to 100%!")}) + self.discount_percentage = 0.0 + return {'warning': warning} + + def _get_cart_discount_amt(self, pricelist, total=0.0, + item_count=0.0, item_sum_count=0.0, + product_ids=[], categ_ids=[], order=False): + discount_flag = False + dis_price = 0.0 + if self.apply_on == 'subtotal_at_least' \ + and total >= self.amt_value: + discount_flag = True + elif self.apply_on == 'subtotal_less_than' and total <= self.amt_value: + discount_flag = True + elif self.apply_on == 'item_count_atleast' \ + and item_count >= self.amt_value: + discount_flag = True + elif self.apply_on == 'item_count_less_than' \ + and item_count <= self.amt_value: + discount_flag = True + elif self.apply_on == 'item_sum_qty_atleast' \ + and item_sum_count >= self.amt_value: + discount_flag = True + elif self.apply_on == 'item_sum_qty_less_than' \ + and item_sum_count <= self.amt_value: + discount_flag = True + elif self.apply_on == 'one_product_al_least' \ + and self.product_id.id in product_ids: + discount_flag = True + elif self.apply_on == 'none_of_sel_products' \ + and not any(map(lambda v: v in [ + x.id for x in self.product_ids], + product_ids)): + discount_flag = True + elif self.apply_on == 'one_categ_al_least' \ + and self.categ_id.id in categ_ids: + discount_flag = True + elif self.apply_on == 'none_of_sel_categs' \ + and not any(map(lambda v: v in [ + x.id for x in self.categ_ids], categ_ids)): + discount_flag = True + if discount_flag: + dis_price = self.discount_percentage + return dis_price + + +class CouponCode(models.Model): + _name = 'coupon.code' + _description = 'Coupon Code' + + @api.multi + def _compute_order_count(self): + sale_order_ids = self.env['sale.order'].search_count([ + ('coupon_code_id', '=', self.id), ('state', '=', 'sale')]) + self.sale_order_count = sale_order_ids + self.remaining_limit = self.usage_limit - sale_order_ids + + name = fields.Char('Name') + coupon_code = fields.Char('Coupon Code') + code_valid_from = fields.Date('Valid From') + code_valid_to = fields.Date('Valid To') + sale_order_count = fields.Integer( + compute='_compute_order_count', string='# of Sale Order') + coupon_type = fields.Selection([ + ('percent', 'Percent'), + ('fixed_amount', 'Fixed Amount'), + ('buy_x_get_y', 'Buy X Product Get Y Product Free'), + ('buy_x_get_y_other', 'Buy X Product Get Y Other Product Free'), + ('buy_x_get_percent', 'Range Based Discount(' + 'Buy X Product Get Percent Free)'), + ('clubbed', 'Clubbed Discount'), + ], 'Coupon Type', default='percent', required=True) + number_of_x_product = fields.Float('Number Of X Product') + number_of_y_product = fields.Float('Number Of Y Product') + other_categ_id = fields.Many2one('product.category', 'Category') + discount_amount = fields.Float('Discount Amount') + flat_discount = fields.Float('Flat Discount') + extra_discount_percentage = fields.Float('Extra Discount') + usage_limit = fields.Integer('Total Usage Limit') + remaining_limit = fields.Integer(compute='_compute_order_count', + string='Remaining Usage Limit') + min_order_amount = fields.Float('Min Order Amount') + active = fields.Boolean('Active', default=True) + apply_on = fields.Selection([ + ('all', 'Global'), + ('category', 'Category'), + ('product_template', 'Product Template'), + ('product', 'Product Variant'), + ], required=True, default='all', string="Apply On") + categ_id = fields.Many2one('product.category', 'Category') + product_tmpl_id = fields.Many2one('product.template', 'Product Template') + product_id = fields.Many2one('product.product', 'Product') + other_product_id = fields.Many2one('product.product', 'Other Product') + pricelist_id = fields.Many2one('product.pricelist', + 'Pricelist', index=True, ondelete='cascade') + model_id = fields.Many2one('ir.model', string='Condition', + domain=[('model', 'in', MODELS_LIST)]) + model_name = fields.Char(related='model_id.model', string='Model Name') + model_domain = fields.Char(string='Domain', oldname='domain', default=[]) + model_real = fields.Char(compute='_compute_model', string='Real Model') + + @api.onchange('apply_on') + def _onchange_apply_on(self): + if self.apply_on != 'product': + self.product_id = False + if self.apply_on != 'product_template': + self.product_tmpl_id = False + if self.apply_on != 'category': + self.categ_id = False + + @api.onchange('coupon_type', 'discount_amount') + def check_percentage(self): + warning = {} + if self.coupon_type == 'percent' and self.discount_amount > 100 \ + or self.discount_amount < 0: + warning.update({ + 'title': _("Warning"), + 'message': _("Percentage should be between 0% to 100%!")}) + self.discount_amount = 0.0 + return {'warning': warning} + + @api.onchange('flat_discount', 'extra_discount_percentage') + def check_clubbed_percentage(self): + warning = {} + percent = self.flat_discount + self.extra_discount_percentage + if percent > 100 or percent < 0: + warning.update({ + 'title': _("Warning"), + 'message': _("Total Percentage ( Discount + Extra ) " + "should be between 0% to 100%!")}) + self.flat_discount = 0.0 + self.extra_discount_percentage = 0.0 + return {'warning': warning} + + @api.depends('model_id') + def _compute_model(self): + for record in self: + if record.model_id: + record.model_real = record.model_name or 'res.partner' + + @api.multi + def view_sale_order(self): + sale_order_ids = self.env['sale.order'].search([ + ('coupon_code_id', '=', self.id), ('state', '=', 'sale')]) + return { + 'name': 'Sales Orders', + 'type': 'ir.actions.act_window', + 'view_type': 'form', + 'view_mode': 'tree,form,kanban,pivot,graph', + 'res_model': 'sale.order', + 'domain': [('id', 'in', sale_order_ids.ids)], + } + + @api.constrains('coupon_code') + def check_duplicate_coupon_code(self): + check_coupon_id = self.search([ + ('coupon_code', '=', self.coupon_code), + ('id', '!=', self.id)]) + if check_coupon_id: + raise Warning(_("Coupon code (%s) already exists!") % ( + self.coupon_code)) + + @api.multi + def check_condition(self, record, partner_id): + domain = safe_eval(record.model_domain) + if record.model_real == 'res.partner': + domain += [('id', '=', partner_id.id)] + elif record.model_real == 'res.partner.category': + domain += [('id', 'in', partner_id.category_id.ids)] + elif record.model_real == 'res.users': + domain += [('id', 'in', self.env.user.id)] + elif record.model_real == 'res.groups': + domain += [('users', 'in', self.env.user.id)] + elif record.model_real == 'res.country.state': + domain += [('id', '=', partner_id.state_id.id)] + elif record.model_real == 'res.country': + domain += [('id', '=', partner_id.country_id.id)] + if self.env[record.model_real].search(domain): + return False + return True + + @api.multi + def get_coupon_records(self, coupon_code, pricelist_id): + coupon_ids = [] + if coupon_code: + date = fields.Date.context_today(self) + self._cr.execute( + 'SELECT code.id ' + 'FROM coupon_code AS code ' + 'WHERE (code.coupon_code = %s) ' + 'AND (code.code_valid_from IS NULL OR ' + 'code.code_valid_from<=%s) ' + 'AND (code.code_valid_to IS NULL OR code.code_valid_to>=%s) ' + 'AND (code.pricelist_id = %s) ', + (coupon_code, date, date, pricelist_id.id)) + coupons = [x[0] for x in self._cr.fetchall()] + coupon_ids = self.env['coupon.code'].browse(coupons) + return coupon_ids + + @api.multi + def get_coupon_discount(self, line, cal_coupon): + if not line.order_id.pricelist_id.apply_coupon_code or not \ + line.coupon_code_id: + return 0.0 + coupon_amount = 0.0 + onchange_context = True + coupon_ids = self.get_coupon_records(line.order_id.have_coupon_code, + line.order_id.pricelist_id) + for coupon_id in coupon_ids: + if coupon_id.coupon_type == 'percent' or \ + coupon_id.coupon_type == 'clubbed': + discount_per = coupon_id.discount_amount + if coupon_id.coupon_type == 'clubbed': + discount_per = \ + coupon_id.flat_discount + \ + coupon_id.extra_discount_percentage + coupon_amount += line.order_id._get_percentage_coupon_discount( + line, coupon_id, onchange_context, + cal_coupon, discount_per, False) + elif coupon_id.coupon_type == 'fixed_amount': + coupon_amount += line.order_id._get_fixed_coupon_discount( + line, coupon_id, onchange_context, cal_coupon, False) + elif coupon_id.coupon_type == 'buy_x_get_percent' \ + and line.product_uom_qty >= coupon_id.number_of_x_product: + coupon_amount += \ + line.order_id.buy_x_get_percentage_coupon_discount( + line, coupon_id, onchange_context, cal_coupon, False) + return coupon_amount diff --git a/addons/sale_advance_pricelist/models/pricelist.py b/addons/sale_advance_pricelist/models/pricelist.py new file mode 100644 index 00000000..32ec37b2 --- /dev/null +++ b/addons/sale_advance_pricelist/models/pricelist.py @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- +# Part of flectra. See LICENSE file for full copyright and licensing details. + +from flectra import api, fields, models +from flectra.exceptions import UserError +from itertools import chain + + +class Pricelist(models.Model): + _inherit = "product.pricelist" + + pricelist_type = fields.Selection( + [('basic', 'Basic'), + ('advance', 'Advanced'), + ], 'Pricelist Type', default='basic', required=True) + apply_method = fields.Selection( + [('first_matched_rule', 'Apply First Matched Rule'), + ('all_matched_rules', 'Apply All Matched Rules'), + ('smallest_discount', 'Apply Smallest Matched Discount'), + ('biggest_discount', 'Apply Biggest Matched Discount') + ], 'Apply Method', default='first_matched_rule', required=True) + rule_ids = fields.One2many( + 'price.rule', 'pricelist_id', 'Price Rules', + copy=True) + cart_rule_ids = fields.One2many( + 'cart.rule', 'pricelist_id', 'Cart Rules Items', + copy=True) + apply_coupon_code = fields.Boolean('Apply Coupon Code?') + coupon_code_lines = fields.One2many( + 'coupon.code', 'pricelist_id', 'Coupon Code Items', + copy=True) + + def get_product_price_rule_flectra( + self, product, quantity, partner, + rule_id, price_unit, date=False, uom_id=False): + self.ensure_one() + return self._compute_price_rule_flectra( + [(product, quantity, partner)], + rule_id, price_unit, date=date, uom_id=uom_id)[product.id] + + @api.multi + def _compute_price_rule_flectra(self, products_qty_partner, + rule_id, price_unit, + date=False, uom_id=False): + self.ensure_one() + if not date: + date = fields.Datetime.now() + if not uom_id and self._context.get('uom'): + uom_id = self._context['uom'] + if uom_id: + products = [item[0].with_context( + uom=uom_id) for item in products_qty_partner] + products_qty_partner = [(products[index], data_struct[1], + data_struct[2]) + for index, data_struct in + enumerate(products_qty_partner)] + else: + products = [item[0] for item in products_qty_partner] + + if not products: + return {} + + categ_ids = {} + for p in products: + categ = p.categ_id + while categ: + categ_ids[categ.id] = True + categ = categ.parent_id + categ_ids = list(categ_ids) + + is_product_template = products[0]._name == "product.template" + if is_product_template: + prod_tmpl_ids = [tmpl.id for tmpl in products] + prod_ids = [p.id for p in + list(chain.from_iterable( + [t.product_variant_ids for t in products]))] + else: + prod_ids = [product.id for product in products] + prod_tmpl_ids = [ + product.product_tmpl_id.id for product in products] + self._cr.execute( + 'SELECT item.id ' + 'FROM rule_line AS item ' + 'LEFT JOIN product_category AS categ ' + 'ON item.categ_id = categ.id ' + 'WHERE (item.product_tmpl_id IS NULL ' + 'OR item.product_tmpl_id = any(%s))' + 'AND (item.price_rule_id = any(%s))' + 'AND (item.product_id IS NULL OR item.product_id = any(%s))' + 'AND (item.categ_id IS NULL OR item.categ_id = any(%s)) ' + 'AND (item.pricelist_id = %s) ' + 'AND (item.start_date IS NULL OR item.start_date<=%s) ' + 'AND (item.end_date IS NULL OR item.end_date>=%s)' + 'ORDER BY item.sequence,categ.parent_left desc', + (prod_tmpl_ids, rule_id.ids, prod_ids, + categ_ids, self.id, date, date)) + + item_ids = [x[0] for x in self._cr.fetchall()] + items = self.env['rule.line'].browse(item_ids) + results = {} + coupon_obj = self.env['coupon.code'] + partner_obj = self.env['res.partner'] + for product, qty, partner in products_qty_partner: + results[product.id] = 0.0 + suitable_rule = False + qty_uom_id = self._context.get('uom') or product.uom_id.id + qty_in_product_uom = qty + if qty_uom_id != product.uom_id.id: + try: + qty_in_product_uom = \ + self.env['product.uom'].browse( + [self._context['uom']])._compute_quantity( + qty, product.uom_id) + except UserError: + pass + price = product.price_compute('list_price')[product.id] + one_dis_price = all_dis_price = 0.0 + max_dis_price = [] + min_dis_price = [] + partner_id = partner + if isinstance(partner, int): + partner_id = partner_obj.browse(partner) + for rule in items: + if rule.min_qty and qty_in_product_uom < rule.min_qty: + continue + if rule.max_qty and qty_in_product_uom > rule.max_qty: + continue + if rule.model_id: + check = coupon_obj.check_condition(rule, partner_id) + if check: + continue + if is_product_template: + if rule.product_tmpl_id and \ + product.id != rule.product_tmpl_id.id: + continue + if rule.product_id and not ( + product.product_variant_count == 1 and + product.product_variant_id.id == + rule.product_id.id): + continue + else: + if rule.product_tmpl_id and \ + product.product_tmpl_id.id != \ + rule.product_tmpl_id.id: + continue + if rule.product_id and product.id != rule.product_id.id: + continue + if rule.categ_id: + cat = product.categ_id + while cat: + if cat.id == rule.categ_id.id: + break + cat = cat.parent_id + if not cat: + continue + dis_price = 0.0 + if price is not False: + if rule.rule_type == 'fixed_amount': + if price != price_unit: + price = price_unit + if price < rule.discount_amount: + price = 0 + suitable_rule = rule + break + else: + dis_price = price - rule.discount_amount + elif rule.rule_type == 'percent': + dis_price = (price - (price * ( + rule.discount_amount / 100))) or 0.0 + suitable_rule = rule + if self.apply_method == 'first_matched_rule': + one_dis_price = dis_price + break + elif self.apply_method == 'all_matched_rules': + all_dis_price += price - dis_price + elif self.apply_method == 'smallest_discount': + min_dis_price.append(price - dis_price) + else: + max_dis_price.append(price - dis_price) + if one_dis_price > 0.0: + price = one_dis_price + elif all_dis_price > 0.0: + price = price - all_dis_price + elif min_dis_price: + price = price - min(min_dis_price) + elif max_dis_price: + price = price - max(max_dis_price) + price = product.currency_id.compute( + price, self.currency_id, round=False) + results[product.id] = (price, + suitable_rule and suitable_rule.id or False) + return results diff --git a/addons/sale_advance_pricelist/models/sale.py b/addons/sale_advance_pricelist/models/sale.py new file mode 100644 index 00000000..fd11345d --- /dev/null +++ b/addons/sale_advance_pricelist/models/sale.py @@ -0,0 +1,610 @@ +# -*- coding: utf-8 -*- +# Part of flectra. See LICENSE file for full copyright and licensing details. + +import json + +import flectra.addons.decimal_precision as dp +from flectra import api, fields, models, _ +from flectra.exceptions import Warning, UserError +from flectra.tools.misc import formatLang + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + @api.depends('amount_total', 'currency_id') + def _get_amount_word(self): + for order in self: + if not order.currency_id: + return + order.amount_words = order.currency_id.amount_to_text( + order.discount) + + @api.depends('order_line', 'partner_id', 'coupon_flag') + def _check_cart_rules(self): + for order in self: + if order.pricelist_id.pricelist_type == 'advance': + order._update_all() + cart_discount = 0.0 + cart_discount_per = \ + order.get_cart_rules_discount(order.get_values()) + if order.pricelist_id.discount_policy == 'without_discount': + for line in order.order_line: + if line.get_line_percentage(cart_discount_per) < 100: + cart_discount += \ + ((line.price_unit * line.product_uom_qty + ) * cart_discount_per) / 100 + order.cart_discount = cart_discount + else: + for line in order.order_line: + line.set_line_amount(cart_discount_per) + + @api.multi + def get_cart_discount(self, cart_rule_id, values): + cart_discount = cart_rule_id._get_cart_discount_amt( + self.pricelist_id, total=values.get('amount_untaxed'), + item_count=values.get('item_count'), + item_sum_count=values.get('item_sum_count'), + product_ids=values.get('product_ids'), + categ_ids=values.get('categ_ids'), order=self) + return cart_discount + + @api.multi + def get_cart_rules_discount(self, values): + if not self.pricelist_id: + return 0.0 + date = fields.Date.context_today(self) + self._cr.execute( + 'SELECT rule.id ' + 'FROM cart_rule AS rule ' + 'WHERE (rule.pricelist_id = %s) ' + 'AND (rule.start_date IS NULL OR rule.start_date<=%s) ' + 'AND (rule.end_date IS NULL OR rule.end_date>=%s)' + 'ORDER BY rule.sequence', + (self.pricelist_id.id, date, date)) + item_ids = [x[0] for x in self._cr.fetchall()] + cart_rule_ids = self.env['cart.rule'].browse(item_ids) + if not cart_rule_ids: + return 0.0 + final_dis_price = 0.0 + one_dis_price = all_dis_price = 0.0 + max_dis_price = [] + min_dis_price = [] + cart_discount = 0.0 + for cart_rule_id in cart_rule_ids: + cart_discount = self.get_cart_discount(cart_rule_id, values) + if cart_rule_id.pricelist_id.apply_method == \ + 'first_matched_rule' and \ + cart_discount > 0.0: + one_dis_price = cart_discount + break + elif cart_rule_id.pricelist_id.apply_method == 'all_matched_rules': + all_dis_price += cart_discount + elif cart_rule_id.pricelist_id.apply_method == \ + 'smallest_discount' and cart_discount: + min_dis_price.append(cart_discount) + else: + max_dis_price.append(cart_discount) + if one_dis_price > 0.0: + final_dis_price = one_dis_price + elif all_dis_price > 0.0: + final_dis_price = all_dis_price + elif min_dis_price: + final_dis_price = min(min_dis_price) + elif max_dis_price: + final_dis_price = max(max_dis_price) + return final_dis_price + + @api.model + def _get_discount_vals(self): + payment_vals = [] + price_list_discount = price_rule_discount = coupon_code_discount = 0.0 + coupon_code_obj = self.env['coupon.code'] + partner_id = self.partner_id + pricelist_id = self.pricelist_id + for line in self.order_line: + if not (line.product_id and line.product_uom and + partner_id and pricelist_id and + pricelist_id.discount_policy == 'without_discount' and + self.env.user.has_group( + 'sale.group_discount_per_so_line')): + return + if pricelist_id.pricelist_type == 'basic': + price_list_discount = self.discount + else: + if line.product_uom_qty < 0 and line.coupon_code_id: + continue + if line.order_id.have_coupon_code and line.coupon_code_id: + coupon_code_discount += \ + coupon_code_obj.get_coupon_discount(line, True) + if line.coupon_code_id and line.price_unit == 0: + continue + if pricelist_id.pricelist_type != 'basic': + price_rule_discount = (self.discount - coupon_code_discount + ) - self.cart_discount + untaxed_amount = self.gross_amount - self.discount + payment_vals.append({ + 'gross_amount': formatLang(self.env, self.gross_amount, digits=2), + 'price_list_discount': + formatLang(self.env, price_list_discount, digits=2), + 'price_rule_discount': + formatLang(self.env, price_rule_discount, digits=2), + 'cart_rule_discount': + formatLang(self.env, self.cart_discount, digits=2), + 'coupon_code_discount': + formatLang(self.env, coupon_code_discount, digits=2), + 'currency': self.pricelist_id.currency_id.symbol, + 'untaxed_amount': formatLang(self.env, untaxed_amount, digits=2), + 'position': self.pricelist_id.currency_id.position, + 'amount_words': self.amount_words, + 'discount': formatLang(self.env, self.discount, digits=2), + }) + return payment_vals + + @api.depends('discount') + def _get_discount_info_JSON(self): + for record in self: + info = {'title': _('Discount'), 'outstanding': False, + 'content': record._get_discount_vals()} + record.discount_widget = json.dumps(info) + + def _update_all(self): + for order in self: + amount_untaxed = amount_tax = item_count = item_sum_count = 0.0 + product_ids = categ_ids = [] + check_dup_product = [] + for line in order.order_line: + amount_untaxed += line.price_subtotal + amount_tax += line.price_tax + item_sum_count += line.product_uom_qty + if line.product_id.id not in check_dup_product: + item_count += 1 + check_dup_product.append(line.product_id.id) + product_ids.append(line.product_id.id) + categ_ids.append(line.product_id.categ_id.id) + return { + 'amount_untaxed': amount_untaxed, + 'amount_tax': amount_tax, + 'item_sum_count': item_sum_count, + 'item_count': item_count, + 'product_ids': product_ids, + 'categ_ids': categ_ids, + } + + @api.multi + def get_values(self): + return self._update_all() + + have_coupon_code = fields.Char('Have Coupon Code') + cart_discount = fields.Float( + compute='_check_cart_rules', string='Cart Discount') + coupon_flag = fields.Boolean('Check Coupon Apply') + amount_words = fields.Char(string="Discount Amount Words", + compute='_get_amount_word', store=True) + discount_widget = fields.Text(compute='_get_discount_info_JSON') + coupon_code_id = fields.Many2one('coupon.code', 'Coupon Ref') + + @api.multi + def _get_percentage_coupon_discount(self, line, coupon_code_id, + onchange_context, cal_coupon, + discount_per, remove): + coupon_discount_amount = 0.0 + total_price = (line.product_uom_qty * line.price_unit) + if self.coupon_flag and not onchange_context and line.coupon_code_id: + line.write({'check_coupon': False, + 'discount': line.discount - discount_per, + 'coupon_code_id': False, + 'check_coupon': False}) + elif not cal_coupon and not remove: + coupon_discount_amount = (total_price * discount_per) / 100 + line.discount = line.discount + discount_per + line.coupon_code_id = coupon_code_id.id + line.check_coupon = True + else: + coupon_discount_amount = (total_price * discount_per) / 100 + return coupon_discount_amount + + @api.multi + def _get_fixed_coupon_discount(self, line, coupon_code_id, + onchange_context, cal_coupon, remove): + line_discount = 0.0 + if not line.price_unit: + return 0.0 + discount_amount = 0.0 + if line.price_unit < coupon_code_id.discount_amount: + discount_amount = line.price_unit + if self.coupon_flag and not onchange_context and line.coupon_code_id: + if discount_amount: + line_discount = (line.price_unit * line.dummy_discount / 100 + ) - line.price_unit + line.write({'check_coupon': False, + 'discount': line_discount / line.price_unit * 100, + 'coupon_code_id': False}) + else: + if line.dummy_discount: + line_discount = \ + (line.price_unit * line.dummy_discount / 100 + ) - coupon_code_id.discount_amount + else: + line_discount = (line.price_unit * line.discount / 100 + ) - coupon_code_id.discount_amount + line.write({'check_coupon': False, + 'discount': line_discount / line.price_unit * 100, + 'coupon_code_id': False}) + elif not cal_coupon and not remove: + if discount_amount: + line_discount = (line.price_unit * line.discount / 100 + ) + discount_amount + else: + line_discount = (line.price_unit * line.discount / 100 + ) + coupon_code_id.discount_amount + percent = line_discount / line.price_unit * 100 + if percent > 100: + line.dummy_discount = percent + line.discount = 100 + else: + line.discount = percent + line.coupon_code_id = coupon_code_id.id + line.check_coupon = True + return line.product_uom_qty * coupon_code_id.discount_amount + + @api.multi + def _get_same_product_coupon_discount(self, line, coupon_code_id, remove): + qty = int((line.product_uom_qty / coupon_code_id.number_of_x_product) + ) * coupon_code_id.number_of_y_product + if line.coupon_code_id and line.check_coupon: + line.write({'check_coupon': False, 'coupon_code_id': False}) + elif self.coupon_flag and line.coupon_code_id and \ + not line.check_coupon: + line.unlink() + elif line.product_uom_qty == coupon_code_id.number_of_x_product and \ + not remove: + line.write({'check_coupon': True, + 'coupon_code_id': coupon_code_id.id}) + self.env['sale.order.line'].create( + { + 'product_id': line.product_id.id, + 'product_uom_qty': coupon_code_id.number_of_y_product, + 'order_id': self.id, + 'discount': 0.0, + 'coupon_code_id': coupon_code_id.id, + 'price_unit': 0.0 + }) + elif not self.coupon_flag and qty >= 1: + line.write({'check_coupon': True, + 'coupon_code_id': coupon_code_id.id}) + self.env['sale.order.line'].create( + { + 'product_id': line.product_id.id, + 'product_uom_qty': int(qty), + 'order_id': self.id, + 'discount': 0.0, + 'coupon_code_id': coupon_code_id.id, + 'price_unit': 0.0 + }) + + @api.multi + def _get_other_product_coupon_discount(self, line, coupon_code_id): + qty = int((line.product_uom_qty / coupon_code_id.number_of_x_product) + ) * coupon_code_id.number_of_y_product + if line.coupon_code_id and line.check_coupon: + line.write({'check_coupon': False, 'coupon_code_id': False}) + return 0.0 + elif self.coupon_flag and line.coupon_code_id \ + and not line.check_coupon: + line.unlink() + return 0.0 + elif not self.coupon_flag and qty >= 1: + line.write({'check_coupon': True, + 'coupon_code_id': coupon_code_id.id}) + return qty + return 0.0 + + @api.multi + def buy_x_get_percentage_coupon_discount( + self, line, coupon_code_id, onchange_context, cal_coupon, remove): + coupon_discount_amount = 0.0 + total_price = (line.product_uom_qty * line.price_unit) + if self.coupon_flag and not onchange_context and line.coupon_code_id \ + and line.product_uom_qty > coupon_code_id.number_of_x_product: + line.write({'check_coupon': False, + 'discount': + line.discount - coupon_code_id.discount_amount, + 'coupon_code_id': False, + 'check_coupon': False}) + elif not cal_coupon and not remove and line.product_uom_qty >= \ + coupon_code_id.number_of_x_product: + coupon_discount_amount = \ + (total_price * coupon_code_id.discount_amount) / 100 + line.discount = line.discount + coupon_code_id.discount_amount + line.coupon_code_id = coupon_code_id.id + line.check_coupon = True + else: + coupon_discount_amount = \ + (total_price * coupon_code_id.discount_amount) / 100 + return coupon_discount_amount + + @api.multi + def _check_Constraints(self): + self.get_values() + order_line = self.order_line + if not self.have_coupon_code: + raise UserError(_("Please enter the Coupon code!")) + if not order_line: + raise UserError(_("There is no sale order line!")) + if self.pricelist_id.pricelist_type != 'advance' or not \ + self.pricelist_id.apply_coupon_code: + raise UserError(_("Coupon code does not apply to " + "sale order pricelist!")) + coupon_obj = self.env['coupon.code'] + coupon_code_id = coupon_obj.get_coupon_records( + self.have_coupon_code, self.pricelist_id) + if not coupon_code_id: + raise UserError(_("Coupon code (%s) not found!" + ) % (self.have_coupon_code)) + if coupon_code_id.usage_limit > 0 \ + and coupon_code_id.remaining_limit <= 0: + raise UserError(_("Coupon code (%s) Remaining Limit exceeds!" + ) % (self.have_coupon_code)) + if coupon_code_id.min_order_amount \ + and self.amount_untaxed < coupon_code_id.min_order_amount \ + and not self.env.context.get('remove', False): + raise UserError(_("Untaxed Amount (%s) must be greater than " + "Min Order Amount (%s) which required for " + "the apply coupon code!") % ( + formatLang(self.env, self.amount_untaxed, digits=2), + formatLang(self.env, coupon_code_id.min_order_amount, + digits=2))) + if coupon_code_id.model_id: + check_coupon = coupon_obj.check_condition( + coupon_code_id, self.partner_id) + if check_coupon: + raise Warning(_("Coupon code (%s) condition criteria not " + "match!") % (self.have_coupon_code)) + return coupon_code_id + + @api.multi + def apply_coupon_code(self): + coupon_id = self._check_Constraints() + order_line = self.order_line + coupon_discount_amount = 0.0 + have_coupon_code = self.have_coupon_code + coupon_flag = True + onchange_context = True + coupon_ref_id = coupon_id.id + remove = False + cal_coupon = False + if self.coupon_flag: + have_coupon_code = '' + coupon_flag = False + onchange_context = False + coupon_ref_id = False + remove = True + check_coupon = True + qty = 0.0 + for line in order_line: + if coupon_id.apply_on == 'category' and not \ + line.product_id.categ_id == coupon_id.categ_id: + continue + elif coupon_id.apply_on == 'product_template' and not \ + line.product_id.product_tmpl_id == \ + coupon_id.product_tmpl_id: + continue + elif coupon_id.apply_on == 'product' and not \ + line.product_id == coupon_id.product_id: + continue + else: + if coupon_id.coupon_type == 'percent' or \ + coupon_id.coupon_type == 'clubbed': + discount_per = coupon_id.discount_amount + if coupon_id.coupon_type == 'clubbed': + discount_per = coupon_id.flat_discount + \ + coupon_id.extra_discount_percentage + coupon_discount_amount += \ + self._get_percentage_coupon_discount( + line, coupon_id, onchange_context, + cal_coupon, discount_per, remove) + check_coupon = False + elif coupon_id.coupon_type == 'fixed_amount': + coupon_discount_amount += self._get_fixed_coupon_discount( + line, coupon_id, onchange_context, cal_coupon, remove) + check_coupon = False + elif coupon_id.coupon_type == 'buy_x_get_y' and \ + coupon_id.number_of_x_product and \ + coupon_id.number_of_y_product: + if line.product_uom_qty < \ + coupon_id.number_of_x_product and not \ + line.coupon_code_id and check_coupon: + check_coupon = True + continue + self._get_same_product_coupon_discount( + line, coupon_id, remove) + check_coupon = False + elif coupon_id.coupon_type == 'buy_x_get_y_other': + if line.product_uom_qty < \ + coupon_id.number_of_x_product and not \ + line.coupon_code_id and check_coupon: + check_coupon = True + continue + qty += self._get_other_product_coupon_discount( + line, coupon_id) + check_coupon = False + elif coupon_id.coupon_type == 'buy_x_get_percent': + if line.product_uom_qty < \ + coupon_id.number_of_x_product and not \ + line.coupon_code_id and check_coupon: + check_coupon = True + continue + coupon_discount_amount += \ + self.buy_x_get_percentage_coupon_discount( + line, coupon_id, onchange_context, + cal_coupon, remove) + check_coupon = False + if check_coupon: + raise Warning(_("Coupon code (%s) condition criteria not match!" + ) % (self.have_coupon_code)) + if qty: + self.env['sale.order.line'].create({ + 'product_id': coupon_id.other_product_id.id, + 'product_uom_qty': int(qty), 'order_id': self.id, + 'coupon_code_id': coupon_id.id, 'price_unit': 0.0}) + self.write({'have_coupon_code': have_coupon_code, + 'coupon_flag': coupon_flag, + 'coupon_code_id': coupon_ref_id}) + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + coupon_code_id = fields.Many2one('coupon.code', 'Coupon Ref') + check_coupon = fields.Boolean('Apply Coupon') + dummy_discount = fields.Float( + string='Discount (%)', + digits=dp.get_precision('Discount'), default=0.0) + + @api.multi + def get_line_percentage(self, percent): + self._onchange_discount() + discount = self.discount + percent + if discount > 100: + self.dummy_discount = discount + discount = 100 + self.discount = discount + return discount + + @api.multi + def set_line_amount(self, percent): + discount, product_price = self.get_rule_discount() + if product_price: + discount = product_price * (discount + percent) / 100 + self.price_unit = product_price - discount + + @api.multi + def get_total_coupon_code(self): + return self.env['coupon.code'].get_coupon_discount(self, False) + + def _get_real_price_currency_flectra(self, product, uom, + pricelist_id, price_unit): + currency_id = pricelist_id.currency_id + product_currency = \ + (product.company_id and product.company_id.currency_id + ) or self.env.user.company_id.currency_id + if currency_id.id == product_currency.id: + cur_factor = 1.0 + else: + cur_factor = currency_id._get_conversion_rate( + product_currency, currency_id) + product_uom = self.env.context.get('uom') or product.uom_id.id + if uom and uom.id != product_uom: + uom_factor = uom._compute_price(1.0, product.uom_id) + else: + uom_factor = 1.0 + return price_unit * uom_factor * cur_factor, currency_id.id + + @api.multi + def get_rule_discount(self): + date = fields.Date.context_today(self) + rules = self.env['price.rule'].get_rules( + self.order_id.pricelist_id, date) + max_dis_price = [] + min_dis_price = [] + discount_per = 0.0 + apply_method = self.order_id.pricelist_id.apply_method + discount = 0.0 + context_partner = dict(self.env.context, + partner_id=self.order_id.partner_id.id, + date=self.order_id.date_order) + pricelist_context = dict(context_partner, uom=self.product_uom.id) + product_price = 0.0 + for rule in rules: + adv_price, adv_rule_id = \ + self.order_id.pricelist_id.with_context( + pricelist_context).get_product_price_rule_flectra( + self.product_id, self.product_uom_qty, + self.order_id.partner_id, rule, self.price_unit) + rule_line_id = self.env['rule.line'].browse(adv_rule_id) + if not rule_line_id: + return discount, product_price + adv_new_price = 0.0 + currency_id = False + if rule_line_id.rule_type == 'percent': + adv_new_price, currency_id = self.with_context( + context_partner)._get_real_price_currency( + self.product_id, False, + self.product_uom_qty, + self.product_uom, + self.order_id.pricelist_id.id) + else: + adv_new_price, currency_id = self.with_context( + context_partner)._get_real_price_currency_flectra( + self.product_id, + self.product_uom, + self.order_id.pricelist_id, self.price_unit) + if adv_new_price != 0: + if self.order_id.pricelist_id.currency_id.id != currency_id: + adv_new_price = self.env['res.currency'].browse( + currency_id).with_context(context_partner).compute( + adv_new_price, rule.pricelist_id.currency_id) + if not product_price: + product_price = adv_new_price + discount_per =\ + (adv_new_price - adv_price) / adv_new_price * 100 + if apply_method == 'first_matched_rule': + discount += discount_per + break + elif apply_method == 'all_matched_rules': + discount += discount_per + elif apply_method == 'smallest_discount' and adv_rule_id: + min_dis_price.append(discount_per) + else: + max_dis_price.append(discount_per) + if min_dis_price: + discount += min(min_dis_price) + if max_dis_price: + discount += max(max_dis_price) + return discount, product_price + + # Overrides Function + @api.onchange('product_id', 'price_unit', 'product_uom', + 'product_uom_qty', 'tax_id') + def _onchange_discount(self): + self.discount = 0.0 + if not (self.product_id and self.product_uom and + self.order_id.partner_id and self.order_id.pricelist_id and + self.order_id.pricelist_id.discount_policy == + 'without_discount' and + self.env.user.has_group('sale.group_discount_per_so_line')): + return + discount = 0.0 + context_partner = dict(self.env.context, + partner_id=self.order_id.partner_id.id, + date=self.order_id.date_order) + pricelist_context = dict(context_partner, uom=self.product_uom.id) + if self.order_id.pricelist_id.pricelist_type == 'basic': + price, rule_id = self.order_id.pricelist_id.with_context( + pricelist_context).get_product_price_rule( + self.product_id, self.product_uom_qty or 1.0, + self.order_id.partner_id) + new_list_price, currency_id = self.with_context( + context_partner)._get_real_price_currency( + self.product_id, rule_id, self.product_uom_qty, + self.product_uom, self.order_id.pricelist_id.id) + if new_list_price != 0: + if self.order_id.pricelist_id.currency_id.id != currency_id: + new_list_price = self.env['res.currency'].browse( + currency_id).with_context(context_partner).compute( + new_list_price, self.order_id.pricelist_id.currency_id) + discount = (new_list_price - price) / new_list_price * 100 + if discount > 0: + self.discount = discount + else: + if self.coupon_code_id and (self._context.get( + 'quantity', False) or self._context.get( + 'price_unit', False) or self._context.get('tax', False)): + raise Warning(_('You can not change order line. ' + 'Please remove coupon code first!')) + discount, product_price = self.get_rule_discount() + if discount > 0: + self.discount = discount + if self.order_id.have_coupon_code and self.coupon_code_id: + self.get_total_coupon_code() diff --git a/addons/sale_advance_pricelist/security/ir.model.access.csv b/addons/sale_advance_pricelist/security/ir.model.access.csv new file mode 100644 index 00000000..3fa4bbd0 --- /dev/null +++ b/addons/sale_advance_pricelist/security/ir.model.access.csv @@ -0,0 +1,9 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_rule_line_user,access_rule_line_user,model_rule_line,base.group_user,1,0,0,0 +access_price_rule_user,access_price_rule_user,model_price_rule,base.group_user,1,0,0,0 +access_cart_rule_user,access_cart_rule_user,model_cart_rule,base.group_user,1,0,0,0 +access_coupon_code_user,access_coupon_code_user,model_coupon_code,base.group_user,1,0,0,0 +access_rule_line_manager,access_rule_line_manager,model_rule_line,base.group_partner_manager,1,0,0,0 +access_price_rule_manager,access_price_rule_manager,model_price_rule,base.group_partner_manager,1,0,0,0 +access_cart_rule_manager,access_cart_rule_manager,model_cart_rule,base.group_partner_manager,1,0,0,0 +access_coupon_code_manager,access_coupon_code_manager,model_coupon_code,base.group_partner_manager,1,0,0,0 diff --git a/addons/sale_advance_pricelist/static/description/calculation_view.png b/addons/sale_advance_pricelist/static/description/calculation_view.png new file mode 100644 index 00000000..9266e2e5 Binary files /dev/null and b/addons/sale_advance_pricelist/static/description/calculation_view.png differ diff --git a/addons/sale_advance_pricelist/static/description/cart_rules_preview.gif b/addons/sale_advance_pricelist/static/description/cart_rules_preview.gif new file mode 100644 index 00000000..6457d746 Binary files /dev/null and b/addons/sale_advance_pricelist/static/description/cart_rules_preview.gif differ diff --git a/addons/sale_advance_pricelist/static/description/coupon_code_preview.gif b/addons/sale_advance_pricelist/static/description/coupon_code_preview.gif new file mode 100644 index 00000000..a30cd398 Binary files /dev/null and b/addons/sale_advance_pricelist/static/description/coupon_code_preview.gif differ diff --git a/addons/sale_advance_pricelist/static/description/icon.png b/addons/sale_advance_pricelist/static/description/icon.png new file mode 100644 index 00000000..11f5cf66 Binary files /dev/null and b/addons/sale_advance_pricelist/static/description/icon.png differ diff --git a/addons/sale_advance_pricelist/static/description/index.html b/addons/sale_advance_pricelist/static/description/index.html new file mode 100644 index 00000000..47a8383c --- /dev/null +++ b/addons/sale_advance_pricelist/static/description/index.html @@ -0,0 +1,237 @@ +
+
+

Advance Pricelist

+

Apply Price Rules on Sale Orders

+
+ +
+
+
+ +
+
+
+

+

- Add new option Pricelist Type (Basic, Advanced).


+

- Apply Method :-


+
    +
  • Apply First Matched Rules (Calculate discount of first match condition of Price Rules and Cart Rules)
  • +
  • Apply All Matched Rules (Calculate discount of all match condition of Price Rules and Cart Rules)
  • +
  • Apply Smallest Matched Discount (Append minimum discount of every match condition of Price Rules and Cart Rules and get minimum values from the list of Price Rules and Cart Rules)
  • +
  • Apply Biggest Matched Discount (Append maximum discount of every match condition of Price Rules and Cart Rules and get maximum values from the list of Price Rules and Cart Rules)
  • +
+

Price Rules applicable on the selected option:-

+
    +
  • Global: Apply for all Product.
  • +
  • Category: Apply only for selected category.
  • +
  • Product Template: Apply only for selected Product Template variants.
  • +
  • Product Variant:Apply only for selected Product variants.
  • +
+

- Product Rules Line:-


+
  • Discount applicable based on match condition which defines in Product Rules Line.
+

+
+
+
+ +
+
+

Cart Rules

+

Apply Cart Rules on Sale Orders.

+
+ +
+
+
+ +
+
+
+

+

- Apply On :-


+
    +
  • Subtotal At Least :- Cart Rules not apply if Untaxed Amount is less than define amount in cart line.
  • +
  • Subtotal less than :- Cart Rules apply if Untaxed Amount is less than define amount in cart line.
  • +
  • Lines Count at least :- Cart Rules not apply if line count of sale order(duplicate product exclude) is less than define amount in cart line.
  • +
  • Lines less than :- Cart Rules apply if line count of sale order(duplicate product exclude) is less than define amount in cart line.
  • +
  • Sum of Item Qty at least :- Cart Rules is not apply if total quantity of product in sale order is less than define amount in cart line.
  • +
  • Sum of Item Qty less than :- Cart Rules apply if the total quantity of product in sale order is less than define amount in cart line.
  • +
  • At least one product in order :- Cart Rules apply if product match in sale order which defines in cart line.
  • +
  • None of selected Products :- Cart Rules do not apply if product match in sale order which defines in cart line.
  • +
  • At least one category in order :- Cart Rules apply if product category match in sale order which defines in cart line.
  • +
  • None of selected Categories :- Cart Rules do not apply if product category match in sale order which defines in cart line.
  • +
+

+
+
+
+ +
+
+

Coupon Rules

+

Apply Coupon Rules on Sale Orders based on type and define conditions.

+
+ +
+
+
+ +
+
+
+

+

- Apply Coupon Code:

Select apply coupon code option in pricelist for apply coupon code.
+

- Coupon Type:

+
    +
  • Percent :- Add Percentage in sale order line which defines in Discount Amount.
  • +
  • Fixed Amount :- Convert fixed amount values in percentage based on the unit price of sale order line.
  • +
  • Buy X Product Get Y Product Free :- Get Y same product unit free when buying X product.
  • +
  • Buy X Product Get Y Other Product Free :- Get Y other product(which define in coupon code) unit free when buying X product.
  • +
  • Range Based Discount(Buy X Product Get Percent Free) :- Get percent free(which define in coupon code) when buying X product.
  • +
  • Clubbed Discount :- Add Discount and Extra Discount on Sale order line.
  • +
+

+
+
+
+ +
+
+

Price Rule line

+

Match first product rule line for first sale order

+
+ +
+
+
+ +
+
+

Cart Rule line

+

Match first Cart rule line for first sale order

+
+ +
+
+
+ +
+
+

Discount Calculation

+

Sale Order line discount calculation.

+
+ +
+
+
+ +
+
+
+

+

First order line discount caculation:


+

First order line quantity 5 is between 1 to 5 in Product Rule Line.

+

Here define fixed amount = 50 in match Product Rule Line.

+

Percentage Formule = Discount Amount * 100 / Unit Price

+

50 * 100 / 885 = 5.65

+

6.5 % of Match first Cart Rules beacuse Subtotal(Untaxed Amount) At Least : 2,500.00.

+

Discount Formula= Product Rule line + Cart Rules

+

so discount is 5.65 + 6.5 = 12.15%

+

+
+
+

+

Second order line discount caculation:


+

Second order line quantity 3 is between 1 to 5 in Product Rule Line.

+

Discount = Discount Amount * 100 / Unit Price

+

50 * 100 / 2950 = 1.69

+

6.5 % of Match first Cart Rules beacuse Subtotal(Untaxed Amount) At Least : 2,500.00.

+

so discount is 1.69 + 6.5 = 8.19%

+

Third order line discount caculation:


+

Third order line quantity 3 is between 1 to 5 in Product Rule Line.

+

Unit Price(25) is less than Fixed Amount(50) of Product Rule Lines.

+

so, it give 100% discount for that line.

+

+
+
+
+ + +
+
+

Price Rule line

+

Match first product rule line for Second Sale Order

+
+ +
+
+
+ +
+
+

Cart Rule line

+

Match first Cart rule line for Second Sale Order

+
+ +
+
+
+ +
+
+

Sale Order With Coupon Code

+

Sale Order line discount calculation with Coupon Code.

+
+ +
+
+
+ +
+
+
+

+

First order line discount caculation:


+

Product Lines Rules:

+

4.9 % of Match first Product Rule Lines beacuse ordered Qty 5 is between 1 to 6.

+

Cart Rules:

+

First cart Rules not match beacuse Subtotal is greater than : 3,000.00 so that rules skip.

+

Second Cart Rules match beacuse Sum of Item Qty at least : 7.00 (here 8 = 5 + 3 ). so add 10 %.

+

Coupon Code: Coupon code get 10 percent free. +

Discount Formula= Product Rule line + Cart Rules + Coupon Code

+

First line discount is 24.9 %( 4.9 + 10 + 10)

+

+
+
+
+ +
+
+

Discount Calculation View

+

Display Price Rules, Cart Rules, Coupon Coupon discoun details.

+
+ +
+
+
+ +
+
+
+

+

Price Rule:

+

Percent 4.9%.

+

Sale Order Discount = sum of (Unit Price * Ordered Qty) * 4.9 / 100 of every line

+

(885 * 5) + (2950 * 3) * 4.9 / 100 = 650.48

+

Cart Rules:

+

Percent 10%.

+

Cart Rules: (Gross Amount * 10) / 100

+

13275 * 10 / 100 = 1327.50

+

Coupon Code Rules:

+

Percent 10%.

+

Coupon Code Rules: (Gross Amount * 10) / 100

+

13275 * 10 / 100 = 1327.50

+

+
+
+
diff --git a/addons/sale_advance_pricelist/static/description/pricelist2_cart_rule.png b/addons/sale_advance_pricelist/static/description/pricelist2_cart_rule.png new file mode 100644 index 00000000..256b8971 Binary files /dev/null and b/addons/sale_advance_pricelist/static/description/pricelist2_cart_rule.png differ diff --git a/addons/sale_advance_pricelist/static/description/pricelist2_rule_line.png b/addons/sale_advance_pricelist/static/description/pricelist2_rule_line.png new file mode 100644 index 00000000..2f77e88e Binary files /dev/null and b/addons/sale_advance_pricelist/static/description/pricelist2_rule_line.png differ diff --git a/addons/sale_advance_pricelist/static/description/pricelist_cart_rule.png b/addons/sale_advance_pricelist/static/description/pricelist_cart_rule.png new file mode 100644 index 00000000..5ff92beb Binary files /dev/null and b/addons/sale_advance_pricelist/static/description/pricelist_cart_rule.png differ diff --git a/addons/sale_advance_pricelist/static/description/pricelist_pricerule_preview.gif b/addons/sale_advance_pricelist/static/description/pricelist_pricerule_preview.gif new file mode 100644 index 00000000..262484b9 Binary files /dev/null and b/addons/sale_advance_pricelist/static/description/pricelist_pricerule_preview.gif differ diff --git a/addons/sale_advance_pricelist/static/description/pricelist_rule_line1.png b/addons/sale_advance_pricelist/static/description/pricelist_rule_line1.png new file mode 100644 index 00000000..65b71ef5 Binary files /dev/null and b/addons/sale_advance_pricelist/static/description/pricelist_rule_line1.png differ diff --git a/addons/sale_advance_pricelist/static/description/sale_order.png b/addons/sale_advance_pricelist/static/description/sale_order.png new file mode 100644 index 00000000..e95d8747 Binary files /dev/null and b/addons/sale_advance_pricelist/static/description/sale_order.png differ diff --git a/addons/sale_advance_pricelist/static/description/sale_order_1.png b/addons/sale_advance_pricelist/static/description/sale_order_1.png new file mode 100644 index 00000000..b6cacdd8 Binary files /dev/null and b/addons/sale_advance_pricelist/static/description/sale_order_1.png differ diff --git a/addons/sale_advance_pricelist/static/src/js/discount_details.js b/addons/sale_advance_pricelist/static/src/js/discount_details.js new file mode 100644 index 00000000..09df1919 --- /dev/null +++ b/addons/sale_advance_pricelist/static/src/js/discount_details.js @@ -0,0 +1,55 @@ +flectra.define('sale.pricelist', function (require) { +"use strict"; +var AbstractField = require('web.AbstractField'); +var core = require('web.core'); +var field_registry = require('web.field_registry'); +var QWeb = core.qweb; +var ShowDiscountDetailsWidget = AbstractField.extend({ + supportedFieldTypes: ['char'], + isSet: function() { + return true; + }, + _render: function() { + var self = this; + var info = JSON.parse(this.value); + if (!info) { + this.$el.html(''); + return; + } + this.$el.html(QWeb.render('ShowDiscountInfo', { + lines: info.content, + outstanding: info.outstanding, + title: info.title + })); + _.each(this.$('.js_discount_info'), function (k, v){ + var content = info.content[v]; + var options = { + content: function () { + var $content = $(QWeb.render('AllDiscountDetails', { + gross_amount: content.gross_amount, + price_list_discount: content.price_list_discount, + price_rule_discount: content.price_rule_discount, + cart_rule_discount: content.cart_rule_discount, + coupon_code_discount: content.coupon_code_discount, + currency: content.currency, + untaxed_amount: content.untaxed_amount, + position: content.position, + amount_words: content.amount_words, + discount: content.discount, + })); + return $content; + }, + html: true, + placement: 'left', + title: 'Discount Calculation Details:', + trigger: 'focus', + delay: { "show": 0, "hide": 100 }, + }; + $(k).popover(options); + }); + }, +}); + +field_registry.add('discount_widget', ShowDiscountDetailsWidget); + +}); diff --git a/addons/sale_advance_pricelist/static/src/xml/discount_details_view.xml b/addons/sale_advance_pricelist/static/src/xml/discount_details_view.xml new file mode 100644 index 00000000..b0b2604c --- /dev/null +++ b/addons/sale_advance_pricelist/static/src/xml/discount_details_view.xml @@ -0,0 +1,119 @@ + + + + + +
+ + + + + + + + + + +
+ +
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Discount Amount to Words: + + + + + + + +
+ +
Gross Amount: + + + + + + + +
Pricelist (-): + + + + + + + +
Price Rule (-): + + + + + + + +
Cart Rule (-): + + + + + + + +
Coupon Code (-): + + + + + + + +
Untaxed Amount: + + + + + + + +
+
+
+ +
diff --git a/addons/sale_advance_pricelist/tests/__init__.py b/addons/sale_advance_pricelist/tests/__init__.py new file mode 100644 index 00000000..9de3fdde --- /dev/null +++ b/addons/sale_advance_pricelist/tests/__init__.py @@ -0,0 +1,7 @@ +# Part of Flectra See LICENSE file for full copyright and licensing details. + +from . import sale_advance_pricelist_common +from . import test_01_first_match +from . import test_02_coupon_code +from . import test_03_all_match +from . import test_04_mix_all diff --git a/addons/sale_advance_pricelist/tests/sale_advance_pricelist_common.py b/addons/sale_advance_pricelist/tests/sale_advance_pricelist_common.py new file mode 100644 index 00000000..da1f08b8 --- /dev/null +++ b/addons/sale_advance_pricelist/tests/sale_advance_pricelist_common.py @@ -0,0 +1,49 @@ +# Part of Flectra See LICENSE file for full copyright and licensing details. + +from flectra.tests.common import TransactionCase + + +class TestAdvSalePricelist(TransactionCase): + def setUp(self): + super(TestAdvSalePricelist, self).setUp() + self.partner_id = self.env.ref( + 'sale_advance_pricelist.res_partner_advance_pricelist') + self.currency_id = self.env.ref('base.USD') + self.category_id = self.env.ref('product.product_category_5') + + self.pricelist_1 = \ + self.env.ref('sale_advance_pricelist.advance_pricelist') + self.pricelist_2 = self.env.ref( + 'sale_advance_pricelist.advance_pricelist_with_coupon') + self.pricelist_3 = \ + self.env.ref('sale_advance_pricelist.advance_pricelist_all') + + self.sale_order_2 = \ + self.env.ref('sale_advance_pricelist.sale_order_ap_2') + self.sale_order_3 = \ + self.env.ref('sale_advance_pricelist.sale_order_ap_3') + self.sale_order_4 = \ + self.env.ref('sale_advance_pricelist.sale_order_ap_4') + self.sale_order_5 = \ + self.env.ref('sale_advance_pricelist.sale_order_ap_5') + self.sale_order_6 = \ + self.env.ref('sale_advance_pricelist.sale_order_ap_6') + self.sale_order_7 = \ + self.env.ref('sale_advance_pricelist.sale_order_ap_7') + self.sale_order_8 = \ + self.env.ref('sale_advance_pricelist.sale_order_ap_all_1') + + self.product_1 = self.env.ref('product.product_product_24') + self.product_2 = self.env.ref('product.product_product_25') + self.product_3 = self.env.ref('product.product_product_16') + + self.SaleOrder = self.env['sale.order'] + self.SaleOrderLine = self.env['sale.order.line'] + self.CouponCode = self.env['coupon.code'] + + def check_all_coupon_code(self, order_id, coupon_code, pricelist_id): + coupon_code_id = self.CouponCode.get_coupon_records( + coupon_code, pricelist_id) + self.assertEqual(len(coupon_code_id), 1, 'Coupon code should be 1!') + order_id.have_coupon_code = coupon_code + order_id.apply_coupon_code() diff --git a/addons/sale_advance_pricelist/tests/test_01_first_match.py b/addons/sale_advance_pricelist/tests/test_01_first_match.py new file mode 100644 index 00000000..0ba0795f --- /dev/null +++ b/addons/sale_advance_pricelist/tests/test_01_first_match.py @@ -0,0 +1,104 @@ +# Part of Flectra See LICENSE file for full copyright and licensing details. + +from .sale_advance_pricelist_common import TestAdvSalePricelist +from flectra.tools import float_compare + + +class TestFirstMatchDiscount(TestAdvSalePricelist): + def setUp(self): + super(TestFirstMatchDiscount, self).setUp() + + def test_discount_rule_and_cart(self): + so = self.SaleOrder.create({ + 'partner_id': self.partner_id.id, + 'partner_invoice_id': self.partner_id.id, + 'partner_shipping_id': self.partner_id.id, + 'pricelist_id': self.pricelist_1.id, + }) + line_1 = self.SaleOrderLine.create({ + 'name': self.product_1.name, + 'product_id': self.product_1.id, + 'product_uom_qty': 5, + 'product_uom': self.product_1.uom_id.id, + 'price_unit': self.product_1.list_price, + 'order_id': so.id, + }) + self.SaleOrderLine.create({ + 'name': self.product_2.name, + 'product_id': self.product_2.id, + 'product_uom_qty': 3, + 'product_uom': self.product_2.uom_id.id, + 'price_unit': self.product_2.list_price, + 'order_id': so.id, + }) + + self.SaleOrderLine.create({ + 'name': self.product_3.name, + 'product_id': self.product_3.id, + 'product_uom_qty': 3, + 'product_uom': self.product_3.uom_id.id, + 'price_unit': self.product_3.list_price, + 'order_id': so.id, + }) + + so._check_cart_rules() + so._get_discount_info_JSON() + self.assertEqual(float_compare( + so.order_line[0].discount, 12.15, + precision_digits=2), 0, 'Discount Line: the discount of first' + 'sale order line should be 12.15!') + + line_1.write({'price_unit': 1200}) + so._check_cart_rules() + self.assertEqual(float_compare( + so.order_line[0].discount, 10.67, + precision_digits=2), 0, 'Discount Line: the discount of first' + 'sale order line should be 10.67!') + + self.assertEqual(float_compare( + so.order_line[1].discount, 8.19, + precision_digits=2), 0, 'Discount Line: the discount of second' + 'sale order line should be 8.19!') + + line_1.write({'price_unit': 885}) + so._check_cart_rules() + self.assertEqual(so.discount, 1337.45, + 'Sale Discount: the discount for the ' + 'sale order should be 1337.45!') + + # Change discount policy + so.pricelist_id.discount_policy = 'with_discount' + line_1.write({'product_uom_qty': 1}) + line_1._onchange_discount() + so._check_cart_rules() + self.assertEqual(line_1.price_unit, 777.48, + "Price unit of the line should be 777.48!") + self.assertEqual(line_1.discount, 0.0, + "Price unit of the line should be 0.0!") + + self.assertEqual(float_compare( + so.order_line[0].discount, 00.00, + precision_digits=2), 0, 'Discount Line: the discount of first' + 'sale order line should be 00.00!') + + basic_pricelist = self.env['product.pricelist'].create({ + 'name': 'Basic Pricelist', + 'sequence': 1, + 'discount_policy': 'without_discount', + 'pricelist_type': 'basic', + 'currency_id': self.currency_id.id, + 'item_ids': [(0, 0, {'compute_price': 'percentage', + 'percent_price': 10})], + }) + so_1 = so.copy() + so_1.pricelist_id = basic_pricelist.id + self.assertEqual(float_compare( + so.order_line[0].discount, 00.00, + precision_digits=2), 0, 'Discount Line: the discount of first' + 'sale order line should be 00.00!') + so_1.order_line[0]._onchange_discount() + so_1._get_discount_vals() + self.assertEqual(float_compare( + so_1.order_line[0].discount, 10.00, + precision_digits=2), 0, 'Discount Line: the discount of first' + 'sale order line should be 10.00!') diff --git a/addons/sale_advance_pricelist/tests/test_02_coupon_code.py b/addons/sale_advance_pricelist/tests/test_02_coupon_code.py new file mode 100644 index 00000000..a852f09d --- /dev/null +++ b/addons/sale_advance_pricelist/tests/test_02_coupon_code.py @@ -0,0 +1,136 @@ +# Part of Flectra See LICENSE file for full copyright and licensing details. + +from .sale_advance_pricelist_common import TestAdvSalePricelist +from flectra.tools import float_compare + + +class TestCouponCodeMatchDiscount(TestAdvSalePricelist): + def setUp(self): + super(TestCouponCodeMatchDiscount, self).setUp() + + def test_Percentage_coupon_code(self): + if not self.sale_order_2.coupon_flag: + self.check_all_coupon_code(self.sale_order_2, + 'Get10Peroff', self.pricelist_2) + self.assertTrue(self.sale_order_2.have_coupon_code != '', + 'Coupon Code: Please enter the coupon code!') + self.assertEqual(float_compare( + self.sale_order_2.order_line[0].discount, 24.90, + precision_digits=2), 0, 'Discount Line: the discount of first' + 'sale order line should be 24.90!') + self.assertEqual(self.sale_order_2.discount, 3305.48, + 'Sale Discount: the discount for the ' + 'sale order should be 3305.48!') + + def test_Fixed_coupon_code(self): + if not self.sale_order_3.coupon_flag: + self.check_all_coupon_code(self.sale_order_3, + 'Get20off', self.pricelist_2) + self.assertTrue(self.sale_order_3.have_coupon_code != '', + 'Coupon Code: Please enter the coupon code!') + self.assertEqual(float_compare( + self.sale_order_3.order_line[0].discount, 17.16, + precision_digits=2), 0, 'Discount Line: the discount of first' + 'sale order line should be 17.16!') + self.assertEqual(float_compare( + self.sale_order_3.order_line[1].discount, 15.58, + precision_digits=2), 0, 'Discount Line: the discount of second' + 'sale order line should be 15.58!') + self.assertEqual(self.sale_order_3.discount, 2138.16, + 'Sale Discount: the discount for the ' + 'sale order should be 2138.16!') + + # Remove Coupon Code + self.sale_order_3.apply_coupon_code() + coupon_code_id = self.CouponCode.search([ + ('coupon_code', '=', 'Get20off')]) + coupon_code_id.write({'discount_amount': 30}) + + self.SaleOrderLine.create({ + 'name': self.product_3.name, + 'product_id': self.product_3.id, + 'product_uom_qty': 3, + 'product_uom': self.product_3.uom_id.id, + 'price_unit': self.product_3.list_price, + 'order_id': self.sale_order_3.id, + }) + self.sale_order_3._check_cart_rules() + self.check_all_coupon_code(self.sale_order_3, + 'Get20off', self.pricelist_2) + + def test_Buy_X_Product_Get_Y_Product_Free_coupon_code(self): + if not self.sale_order_4.coupon_flag: + self.check_all_coupon_code(self.sale_order_4, + 'BXGYFree', self.pricelist_2) + self.assertTrue(self.sale_order_4.have_coupon_code != '', + 'Coupon Code: Please enter the coupon code!') + self.assertEqual(float_compare( + self.sale_order_4.order_line[0].discount, 14.90, + precision_digits=2), 0, 'Discount Line: the discount of first' + 'sale order line should be 14.90!') + self.assertEqual(self.sale_order_4.discount, 1977.98, + 'Sale Discount: the discount for the' + ' sale order should be 1977.98!') + self.assertTrue(len(self.sale_order_4.order_line) == 4, + 'Sale: Order Line is missing') + self.assertEqual(self.sale_order_4.order_line[2].price_unit, 0.0, + 'Price unit of the line should be 0.0!') + self.assertEqual(self.sale_order_4.order_line[3].price_unit, 0.0, + 'Price unit of the line should be 0.0!') + + def test_Buy_X_Product_Get_Y_Other_Product_Free_coupon_code(self): + if not self.sale_order_5.coupon_flag: + self.check_all_coupon_code(self.sale_order_5, + 'BXGYOtherFree', self.pricelist_2) + self.assertTrue(self.sale_order_5.have_coupon_code != '', + 'Coupon Code: Please enter coupon code!') + self.assertEqual(float_compare( + self.sale_order_5.order_line[0].discount, 14.90, + precision_digits=2), 0, 'Discount Line: the discount of first' + 'sale order line should be 14.90!') + self.assertEqual(self.sale_order_5.discount, 1977.98, + 'Sale Discount: the discount for the ' + 'sale order should be 1977.98!') + self.assertTrue(len(self.sale_order_5.order_line) == 3, + 'Sale: Order Line is missing') + self.assertEqual(self.sale_order_5.order_line[2].price_unit, 0.0, + 'Price unit of the line should be 0.0!') + + def test_Clubbed_Discount_coupon_code(self): + if not self.sale_order_6.coupon_flag: + self.check_all_coupon_code(self.sale_order_6, + 'CD15Per', self.pricelist_2) + self.assertTrue(self.sale_order_6.have_coupon_code != '', + 'Coupon Code: Please enter coupon code!') + self.assertEqual(float_compare( + self.sale_order_6.order_line[0].discount, 29.90, + precision_digits=2), 0, 'Discount Line: the discount of first' + 'sale order line should be 29.90!') + self.assertEqual(self.sale_order_6.discount, 3969.23, + 'Sale Discount: the discount for the ' + 'sale order should be 3969.23!') + self.assertEqual(float_compare( + self.sale_order_6.order_line[1].discount, 29.90, + precision_digits=2), 0, 'Discount Line: the discount of first' + 'sale order line should be 29.90!') + self.assertEqual(float_compare( + self.sale_order_6.amount_total, 9305.78, precision_digits=2), + 0, "Total not correct") + + def test_Buy_X_Product_Get_Percent_Free_coupon_code(self): + if not self.sale_order_7.coupon_flag: + self.check_all_coupon_code(self.sale_order_7, + 'BXGPercentFree', self.pricelist_2) + self.assertTrue(self.sale_order_7.have_coupon_code != '', + 'Coupon Code: Please enter the coupon code!') + self.assertEqual(float_compare( + self.sale_order_7.order_line[0].discount, 22.65, + precision_digits=2), 0, 'Discount Line: the discount of first' + 'sale order line should be 22.65!') + self.assertEqual(float_compare( + self.sale_order_7.order_line[1].discount, 14.90, + precision_digits=2), 0, 'Discount Line: the discount of first' + 'sale order line should be 14.90!') + self.assertEqual(self.sale_order_7.discount, 1881.36, + 'Sale Discount: the discount for the' + ' sale order should be 1881.36!') diff --git a/addons/sale_advance_pricelist/tests/test_03_all_match.py b/addons/sale_advance_pricelist/tests/test_03_all_match.py new file mode 100644 index 00000000..cd4dd2e9 --- /dev/null +++ b/addons/sale_advance_pricelist/tests/test_03_all_match.py @@ -0,0 +1,32 @@ +# Part of Flectra See LICENSE file for full copyright and licensing details. + +from .sale_advance_pricelist_common import TestAdvSalePricelist + + +class TestAllMatchDiscount(TestAdvSalePricelist): + def setUp(self): + super(TestAllMatchDiscount, self).setUp() + + def test_All_Match(self): + self.assertEqual(self.sale_order_8.order_line[0].discount, 12.50, + "Discount of first line should be 12.50!") + self.assertEqual(self.sale_order_8.order_line[1].discount, 10.50, + "Discount of Second line should be 10.50!") + self.assertEqual(self.sale_order_8.discount, 1482.38, + 'Sale Discount: the discount for the ' + 'sale order should be 1482.38!') + + # Change discount in rules lines + self.pricelist_3.rule_ids.rule_lines[0].discount_amount = 5 + self.pricelist_3.rule_ids.rule_lines[1].discount_amount = 7 + self.pricelist_3.rule_ids.rule_lines[2].discount_amount = 9 + + # Check again percentage and discount in Sale order + self.sale_order_8._check_cart_rules() + self.assertEqual(self.sale_order_8.order_line[0].discount, 14.50, + "Discount of first line should be 14.50!") + self.assertEqual(self.sale_order_8.order_line[1].discount, 12.50, + "Discount of second line should be 12.50!") + self.assertEqual(self.sale_order_8.discount, 1747.88, + 'Sale Discount: the discount for the ' + 'sale order should be 1747.88!') diff --git a/addons/sale_advance_pricelist/tests/test_04_mix_all.py b/addons/sale_advance_pricelist/tests/test_04_mix_all.py new file mode 100644 index 00000000..99819438 --- /dev/null +++ b/addons/sale_advance_pricelist/tests/test_04_mix_all.py @@ -0,0 +1,199 @@ +# Part of Flectra See LICENSE file for full copyright and licensing details. + +from .sale_advance_pricelist_common import TestAdvSalePricelist + + +class TestMixAllDiscount(TestAdvSalePricelist): + def setUp(self): + super(TestMixAllDiscount, self).setUp() + + def test_Mix_All_Match(self): + PriceRule = self.env['price.rule'] + RuleLine = self.env['rule.line'] + ProductPricelis = self.env['product.pricelist'] + + self.pricelist_id = ProductPricelis.create({ + 'name': 'Test Pricelist', + 'sequence': 4, + 'discount_policy': 'without_discount', + 'pricelist_type': 'advance', + 'currency_id': self.currency_id.id, + 'apply_method': 'all_matched_rules', + 'apply_coupon_code': True, + }) + + price_rule_id = PriceRule.create({ + 'sequence': 1, + 'apply_on': 'category', + 'categ_id': self.category_id.id, + 'pricelist_id': self.pricelist_id.id, + }) + price_rule_id._get_pricerule_name_price() + price_rule_id._onchange_apply_on() + + rule_line_1 = RuleLine.create({ + 'sequence': 1, + 'min_qty': 1, + 'max_qty': 10, + 'rule_type': 'percent', + 'discount_amount': 12.5, + 'price_rule_id': price_rule_id.id, + 'model_id': self.env.ref('base.model_res_partner').id, + }) + + rule_line_1.check_percentage() + rule_line_1.check_date() + + rule_line_2 = RuleLine.create({ + 'sequence': 1, + 'min_qty': 5, + 'max_qty': 20, + 'rule_type': 'percent', + 'discount_amount': 15, + 'price_rule_id': price_rule_id.id, + }) + rule_line_2._compute_model() + + cart_rule_1 = self.env.ref('sale_advance_pricelist.cart_rule_1').copy() + cart_rule_1.pricelist_id = self.pricelist_id.id + cart_rule_1._get_cart_name_price() + cart_rule_1.check_percentage() + + cart_rule_2 = self.env.ref('sale_advance_pricelist.cart_rule_2').copy() + cart_rule_2.pricelist_id = self.pricelist_id.id + cart_rule_2._get_cart_name_price() + + self.coupon_code_id = self.CouponCode.create({ + 'name': 'Get 7%', + 'coupon_code': 'GetDiscount', + 'apply_on': 'category', + 'categ_id': self.category_id.id, + 'pricelist_id': self.pricelist_id.id, + 'discount_amount': 7, + 'coupon_type': 'percent', + 'model_id': self.env.ref('base.model_res_partner').id, + }) + self.coupon_code_id._onchange_apply_on() + self.coupon_code_id.check_percentage() + self.coupon_code_id.check_clubbed_percentage() + self.coupon_code_id._compute_model() + check_coupon = self.CouponCode.check_condition( + self.coupon_code_id, self.partner_id) + self.assertEqual(check_coupon, False, + "Coupon code condition criteria not match!") + + # Check for All Match Discount + sale_order = self.sale_order_2.copy() + sale_order.pricelist_id = self.pricelist_id.id + first_order_line = sale_order.order_line[0] + first_order_line._onchange_discount() + second_order_line = sale_order.order_line[1] + second_order_line._onchange_discount() + sale_order._check_cart_rules() + + self.assertEqual(first_order_line.discount, 34.00, + 'Discount Percentage: the discount for the' + ' first sale order line should be 34!') + + self.assertEqual(second_order_line.discount, 19.00, + 'Discount Percentage: the discount for the' + ' second sale order line should be 19.00!') + + # Check for Smallest Discount + self.pricelist_id.apply_method = 'smallest_discount' + tmp_id = self.product_1.product_tmpl_id.id + price_rule_id.write({'apply_on': 'product_template', + 'categ_id': False, + 'product_tmpl_id': tmp_id}) + price_rule_id._get_pricerule_name_price() + price_rule_id._onchange_apply_on() + self.sale_order = self.sale_order_2.copy() + self.sale_order.pricelist_id = self.pricelist_id.id + + first_order_line = self.sale_order.order_line[0] + first_order_line._onchange_discount() + self.assertEqual(first_order_line.discount, 12.5, + 'Discount Percentage: the discount for the' + ' sale order line should be 12.5!') + self.sale_order._check_cart_rules() + + # Set coupon code with percent + self.check_all_condition(self.sale_order, 'percent') + # Set coupon code with fixed_amount + self.check_all_condition(self.sale_order, 'fixed_amount') + # Set coupon code with buy_x_get_y + self.check_all_condition(self.sale_order, 'buy_x_get_y') + # Set coupon code with buy_x_get_y_other + self.check_all_condition(self.sale_order, 'buy_x_get_y_other') + + # Check for Biggest Discount + self.pricelist_id.apply_method = 'biggest_discount' + price_rule_id.write({'apply_on': 'product', + 'categ_id': False, + 'product_tmpl_id': False, + 'product_id': self.product_1.id}) + price_rule_id._get_pricerule_name_price() + cart_rule_3 = \ + self.env.ref('sale_advance_pricelist.cart_rule_all_3').copy() + cart_rule_3.pricelist_id = self.pricelist_id.id + cart_rule_3._get_cart_name_price() + self.sale_order_1 = self.sale_order_2.copy() + self.sale_order_1.pricelist_id = self.pricelist_id.id + + first_order_line = self.sale_order_1.order_line[0] + first_order_line._onchange_discount() + self.assertEqual(first_order_line.discount, 15.00, + 'Discount Percentage: the discount for the' + ' first sale order line should be 15!') + + self.sale_order_1._check_cart_rules() + self.assertEqual(first_order_line.discount, 21.50, + 'Discount Percentage: the discount for the' + ' first sale order line should be 21.50!') + + self.sale_order_1.coupon_flag = False + # # Set coupon code with buy_x_get_percent_free + self.check_all_condition(self.sale_order_1, 'buy_x_get_percent') + self.assertEqual(first_order_line.discount, 41.50, + 'Discount Percentage: the discount for the' + ' first sale order line should be 41.50!') + + def check_all_condition(self, sale_order, coupon_type): + + self.coupon_code_id.write({'coupon_type': coupon_type, + 'apply_on': 'category', + 'categ_id': self.category_id.id, + 'discount_amount': 0.0, + 'number_of_y_product': 0.0, + 'number_of_y_product': 0.0, + 'other_product_id': False}) + if coupon_type in ['percent', 'fixed_amount']: + self.coupon_code_id.write({'discount_amount': 20}) + elif coupon_type == 'buy_x_get_percent': + self.coupon_code_id.write({'discount_amount': 20, + 'number_of_x_product': 3}) + elif coupon_type == 'buy_x_get_y': + self.coupon_code_id.write({'number_of_x_product': 2, + 'number_of_y_product': 1, + 'discount_amount': 0.0}) + else: + self.coupon_code_id.write({'number_of_x_product': 2, + 'number_of_y_product': 1, + 'discount_amount': 0.0, + 'other_product_id': self.product_3.id}) + # Category Coupon Code + self.check_all_coupon_code(sale_order, + 'GetDiscount', self.pricelist_id) + + # Product Coupon Code + self.coupon_code_id.write({'apply_on': 'product', + 'product_id': self.product_1.id}) + self.check_all_coupon_code(sale_order, + 'GetDiscount', self.pricelist_id) + + # Product Template Coupon Code + template_id = self.product_1.product_tmpl_id.id + self.coupon_code_id.write({'apply_on': 'product_template', + 'product_tmpl_id': template_id}) + self.check_all_coupon_code(sale_order, + 'GetDiscount', self.pricelist_id) diff --git a/addons/sale_advance_pricelist/views/price_rule_view.xml b/addons/sale_advance_pricelist/views/price_rule_view.xml new file mode 100644 index 00000000..3d317bf6 --- /dev/null +++ b/addons/sale_advance_pricelist/views/price_rule_view.xml @@ -0,0 +1,204 @@ + + + + + price.rule.tree + price.rule + + + + + + + + + + + + price.rule.form + price.rule + +
+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + cart.rule.tree + cart.rule + + + + + + + + + + + + + cart.rule.form + cart.rule + +
+

+ + + + + + + + + + + + + + + + + + + + +
+
+
+ + + coupon.code.tree + coupon.code + + + + + + + + + + + + + + + + coupon.code.form + coupon.code + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
diff --git a/addons/sale_advance_pricelist/views/pricelist_view.xml b/addons/sale_advance_pricelist/views/pricelist_view.xml new file mode 100644 index 00000000..7ed3fe9d --- /dev/null +++ b/addons/sale_advance_pricelist/views/pricelist_view.xml @@ -0,0 +1,45 @@ + + + + product.pricelist.flectra.form + product.pricelist + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
diff --git a/addons/sale_advance_pricelist/views/sale_pricelist.xml b/addons/sale_advance_pricelist/views/sale_pricelist.xml new file mode 100644 index 00000000..051f24c6 --- /dev/null +++ b/addons/sale_advance_pricelist/views/sale_pricelist.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/addons/sale_advance_pricelist/views/sale_views.xml b/addons/sale_advance_pricelist/views/sale_views.xml new file mode 100644 index 00000000..658b6ab2 --- /dev/null +++ b/addons/sale_advance_pricelist/views/sale_views.xml @@ -0,0 +1,53 @@ + + + + + sale.order.flectra.form + sale.order + + + + + + +
+ +
+
+ +
+
+ + + + + + + + + + + + + + + + +
+ %% +
+
+ + + +
+
+