From 0b4943cec1f2419f4e341b5e662c523f97d2b814 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Thu, 4 Jul 2013 23:45:22 +0530 Subject: [PATCH] [webshop] shopping cart with shipping rule selector, totals --- .../doctype/shipping_rule/shipping_rule.py | 2 +- controllers/selling_controller.py | 21 +++-- .../shopping_cart_settings.py | 19 +++-- .../shopping_cart_settings.txt | 4 +- website/helpers/cart.py | 80 ++++++++++++++---- website/templates/js/cart.js | 82 ++++++++++++++++++- website/templates/pages/cart.html | 12 +-- 7 files changed, 171 insertions(+), 49 deletions(-) diff --git a/accounts/doctype/shipping_rule/shipping_rule.py b/accounts/doctype/shipping_rule/shipping_rule.py index ade76e536e4..a363b184543 100644 --- a/accounts/doctype/shipping_rule/shipping_rule.py +++ b/accounts/doctype/shipping_rule/shipping_rule.py @@ -58,7 +58,7 @@ class DocType(DocListController): then condition y can only be like 50 to 99 or 301 to 400 hence, non-overlapping condition = (x1 <= x2 < y1 <= y2) or (y1 <= y2 < x1 <= x2) """ - separate = (x1 <= x2 < y1 <= y2) or (y1 <= y2 < x1 <= x2) + separate = (x1 <= x2 <= y1 <= y2) or (y1 <= y2 <= x1 <= x2) return (not separate) overlaps = [] diff --git a/controllers/selling_controller.py b/controllers/selling_controller.py index d9f19ac1466..2f772957faa 100644 --- a/controllers/selling_controller.py +++ b/controllers/selling_controller.py @@ -73,17 +73,16 @@ class SellingController(StockController): if not condition.to_value or (flt(condition.from_value) <= value <= flt(condition.to_value)): shipping_amount = condition.shipping_amount break - - if shipping_amount: - self.doclist.append({ - "doctype": "Sales Taxes and Charges", - "parentfield": "other_charges", - "charge_type": "Actual", - "account_head": shipping_rule.doc.account, - "cost_center": shipping_rule.doc.cost_center, - "description": shipping_rule.doc.label, - "rate": shipping_amount - }) + + self.doclist.append({ + "doctype": "Sales Taxes and Charges", + "parentfield": "other_charges", + "charge_type": "Actual", + "account_head": shipping_rule.doc.account, + "cost_center": shipping_rule.doc.cost_center, + "description": shipping_rule.doc.label, + "rate": shipping_amount + }) def set_total_in_words(self): from webnotes.utils import money_in_words diff --git a/website/doctype/shopping_cart_settings/shopping_cart_settings.py b/website/doctype/shopping_cart_settings/shopping_cart_settings.py index 142482366c3..121422f9be4 100644 --- a/website/doctype/shopping_cart_settings/shopping_cart_settings.py +++ b/website/doctype/shopping_cart_settings/shopping_cart_settings.py @@ -63,12 +63,15 @@ class DocType(DocListController): # if list against each territory has more than one element, raise exception territory_name = webnotes.conn.sql("""select `territory`, `parent` from `tabFor Territory` - where `parenttype`=%s and `parent` in (%s) """ % + where `parenttype`=%s and `parent` in (%s)""" % ("%s", ", ".join(["%s"]*len(names))), tuple([parenttype] + names)) for territory, name in territory_name: territory_name_map.setdefault(territory, []).append(name) - + + if len(territory_name_map[territory]) > 1: + territory_name_map[territory].sort(key=lambda val: names.index(val)) + return territory_name_map def validate_exchange_rates_exist(self): @@ -101,24 +104,26 @@ class DocType(DocListController): territory_name_map = self.get_territory_name_map(parentfield, fieldname) if territory_name_map.get(territory): - name = territory_name_map.get(territory)[0] + name = territory_name_map.get(territory) else: territory_ancestry = self.get_territory_ancestry(territory) for ancestor in territory_ancestry: if territory_name_map.get(ancestor): - name = territory_name_map.get(ancestor)[0] + name = territory_name_map.get(ancestor) break return name def get_price_list(self, billing_territory): - return self.get_name_from_territory(billing_territory, "price_lists", "price_list") + price_list = self.get_name_from_territory(billing_territory, "price_lists", "price_list") + return price_list and price_list[0] or None def get_tax_master(self, billing_territory): - return self.get_name_from_territory(billing_territory, "sales_taxes_and_charges_masters", + tax_master = self.get_name_from_territory(billing_territory, "sales_taxes_and_charges_masters", "sales_taxes_and_charges_master") + return tax_master and tax_master[0] or None - def get_shipping_rule(self, shipping_territory): + def get_shipping_rules(self, shipping_territory): return self.get_name_from_territory(shipping_territory, "shipping_rules", "shipping_rule") def get_territory_ancestry(self, territory): diff --git a/website/doctype/shopping_cart_settings/shopping_cart_settings.txt b/website/doctype/shopping_cart_settings/shopping_cart_settings.txt index fa13f18c172..1d472b7f1b5 100644 --- a/website/doctype/shopping_cart_settings/shopping_cart_settings.txt +++ b/website/doctype/shopping_cart_settings/shopping_cart_settings.txt @@ -2,7 +2,7 @@ { "creation": "2013-06-19 15:57:32", "docstatus": 0, - "modified": "2013-07-03 21:00:00", + "modified": "2013-07-03 21:01:00", "modified_by": "Administrator", "owner": "Administrator" }, @@ -69,7 +69,7 @@ }, { "doctype": "DocField", - "fieldname": "shopping_cart_shipping_rules", + "fieldname": "shipping_rules", "fieldtype": "Table", "label": "Shopping Cart Shipping Rules", "options": "Shopping Cart Shipping Rule", diff --git a/website/helpers/cart.py b/website/helpers/cart.py index 03387098e95..98edc07663b 100644 --- a/website/helpers/cart.py +++ b/website/helpers/cart.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import webnotes import webnotes.defaults -from webnotes.utils import cint, get_fullname, fmt_money +from webnotes.utils import flt, get_fullname, fmt_money class WebsitePriceListMissingError(webnotes.ValidationError): pass @@ -18,14 +18,15 @@ def get_cart_quotation(doclist=None): return { "doclist": decorate_quotation_doclist(doclist), "addresses": [{"name": address.name, "display": address.display} - for address in get_address_docs(party)] + for address in get_address_docs(party)], + "shipping_rules": get_applicable_shipping_rules(party) } @webnotes.whitelist() def update_cart(item_code, qty, with_doclist=0): quotation = _get_cart_quotation() - qty = cint(qty) + qty = flt(qty) if qty == 0: quotation.set_doclist(quotation.doclist.get({"item_code": ["!=", item_code]})) else: @@ -40,8 +41,7 @@ def update_cart(item_code, qty, with_doclist=0): else: quotation_items[0].qty = qty - quotation.ignore_permissions = True - quotation.save() + apply_cart_settings(quotation=quotation) if with_doclist: return get_cart_quotation(quotation.doclist) @@ -158,7 +158,13 @@ def decorate_quotation_doclist(doclist): ["website_image", "web_short_description", "page_name"], as_dict=True)) d.formatted_rate = fmt_money(d.export_rate, currency=doclist[0].currency) d.formatted_amount = fmt_money(d.export_amount, currency=doclist[0].currency) + elif d.charge_type: + d.formatted_tax_amount = fmt_money(d.tax_amount / doclist[0].conversion_rate, + currency=doclist[0].currency) + doclist[0].formatted_grand_total_export = fmt_money(doclist[0].grand_total_export, + currency=doclist[0].currency) + return [d.fields for d in doclist] def _get_cart_quotation(party=None): @@ -199,16 +205,13 @@ def apply_cart_settings(party=None, quotation=None): set_price_list_and_rate(quotation, cart_settings, billing_territory) - set_taxes(quotation, cart_settings, billing_territory) - - # set shipping rule based on shipping territory - shipping_territory = get_address_territory(quotation.doc.shipping_address_name) or \ - party.territory - - apply_shipping_rule(quotation, cart_settings, shipping_territory) - quotation.run_method("calculate_taxes_and_totals") + set_taxes(quotation, cart_settings, billing_territory) + + _apply_shipping_rule(party, quotation, cart_settings) + + quotation.ignore_permissions = True quotation.save() def set_price_list_and_rate(quotation, cart_settings, billing_territory): @@ -235,10 +238,54 @@ def set_taxes(quotation, cart_settings, billing_territory): controller = quotation.make_controller() controller.append_taxes_from_master("other_charges", "charge") quotation.set_doclist(controller.doclist) + +@webnotes.whitelist() +def apply_shipping_rule(shipping_rule): + quotation = _get_cart_quotation() + + quotation.doc.shipping_rule = shipping_rule + + apply_cart_settings(quotation=quotation) + + quotation.save() + + return get_cart_quotation(quotation.doclist) + +def _apply_shipping_rule(party=None, quotation=None, cart_settings=None): + shipping_rules = get_shipping_rules(party, quotation, cart_settings) + + if not shipping_rules: + return + + elif quotation.doc.shipping_rule not in shipping_rules: + quotation.doc.shipping_rule = shipping_rules[0] -def apply_shipping_rule(quotation, cart_settings, shipping_territory): - quotation.doc.shipping_rule = cart_settings.get_shipping_rule(shipping_territory) quotation.run_method("apply_shipping_rule") + quotation.run_method("calculate_taxes_and_totals") + +def get_applicable_shipping_rules(party=None, quotation=None): + shipping_rules = get_shipping_rules(party, quotation) + + if shipping_rules: + rule_label_map = webnotes.conn.get_values("Shipping Rule", shipping_rules, "label") + # we need this in sorted order as per the position of the rule in the settings page + return [[rule, rule_label_map.get(rule)] for rule in shipping_rules] + +def get_shipping_rules(party=None, quotation=None, cart_settings=None): + if not party: + party = get_lead_or_customer() + if not quotation: + quotation = _get_cart_quotation() + if not cart_settings: + cart_settings = webnotes.get_obj("Shopping Cart Settings") + + # set shipping rule based on shipping territory + shipping_territory = get_address_territory(quotation.doc.shipping_address_name) or \ + party.territory + + shipping_rules = cart_settings.get_shipping_rules(shipping_territory) + + return shipping_rules def get_address_territory(address_name): """Tries to match city, state and country of address to existing territory""" @@ -254,9 +301,6 @@ def get_address_territory(address_name): return territory -def get_cart_price_list(territory_list, territory_name_map): - pass - @webnotes.whitelist() def checkout(): quotation = _get_cart_quotation() diff --git a/website/templates/js/cart.js b/website/templates/js/cart.js index db6c5714ec3..f1002fc5994 100644 --- a/website/templates/js/cart.js +++ b/website/templates/js/cart.js @@ -38,7 +38,6 @@ $(document).ready(function() { } else { wn.cart.render(r.message); } - } }); }); @@ -81,9 +80,10 @@ $.extend(wn.cart, { render: function(out) { var doclist = out.doclist; var addresses = out.addresses; - + var $cart_items = $("#cart-items").empty(); var $cart_taxes = $("#cart-taxes").empty(); + var $cart_totals = $("#cart-totals").empty(); var $cart_billing_address = $("#cart-billing-address").empty(); var $cart_shipping_address = $("#cart-shipping-address").empty(); @@ -94,12 +94,39 @@ $.extend(wn.cart, { return; } + var shipping_rule_added = false; + var taxes_exist = false; + var shipping_rule_labels = $.map(out.shipping_rules, function(rule) { return rule[1]; }); $.each(doclist, function(i, doc) { if(doc.doctype === "Quotation Item") { wn.cart.render_item_row($cart_items, doc); + } else if (doc.doctype === "Sales Taxes and Charges") { + if(out.shipping_rules && out.shipping_rules.length && + shipping_rule_labels.indexOf(doc.description)!==-1) { + shipping_rule_added = true; + wn.cart.render_tax_row($cart_taxes, doc, out.shipping_rules); + } else { + wn.cart.render_tax_row($cart_taxes, doc); + } + + taxes_exist = true; } }); + if(out.shipping_rules && out.shipping_rules.length && !shipping_rule_added) { + wn.cart.render_tax_row($cart_taxes, {description: "", formatted_tax_amount: ""}, + out.shipping_rules); + taxes_exist = true; + } + + if(taxes_exist) + $('
').appendTo($cart_taxes); + + wn.cart.render_tax_row($cart_totals, { + description: "Total", + formatted_tax_amount: "" + doclist[0].formatted_grand_total_export + "" + }); + if(!(addresses && addresses.length)) { $cart_shipping_address.html('
Hey! Go ahead and add an address
'); } else { @@ -125,10 +152,10 @@ $.extend(wn.cart, { \ \ \ -
\ +
\
\ \ + data-item-code="%(item_code)s" class="text-right">\
\ \ @@ -140,6 +167,53 @@ $.extend(wn.cart, {

', doc)).appendTo($cart_items); }, + render_tax_row: function($cart_taxes, doc, shipping_rules) { + var shipping_selector; + if(shipping_rules) { + shipping_selector = ''; + } + + var $tax_row = $(repl('
\ +
\ +
\ +
' + + (shipping_selector || '

%(description)s

') + + '
\ +
\ +
\ +
\ + %(formatted_tax_amount)s

\ +
\ +
', doc)).appendTo($cart_taxes); + + if(shipping_selector) { + $tax_row.find('select option').each(function(i, opt) { + if($(opt).html() == doc.description) { + $(opt).attr("selected", "selected"); + } + }); + $tax_row.find('select').on("change", function() { + wn.cart.apply_shipping_rule($(this).val(), this); + }); + } + }, + + apply_shipping_rule: function(rule, btn) { + wn.call({ + btn: btn, + type: "POST", + method: "website.helpers.cart.apply_shipping_rule", + args: { shipping_rule: rule }, + callback: function(r) { + if(!r.exc) { + wn.cart.render(r.message); + } + } + }); + }, + render_address: function($address_wrapper, addresses, address_name) { $.each(addresses, function(i, address) { $(repl('
\ diff --git a/website/templates/pages/cart.html b/website/templates/pages/cart.html index 9d5f100da99..cefcb5afa72 100644 --- a/website/templates/pages/cart.html +++ b/website/templates/pages/cart.html @@ -19,16 +19,17 @@
-
-

Item Details

+

Item Details

-

Qty

+

Qty, Amount


+
+

@@ -37,16 +38,15 @@
+ New Shipping Address

Billing Address

+ New Billing Address
-