[webshop] shopping cart with shipping rule selector, totals
This commit is contained in:
parent
a749996747
commit
0b4943cec1
@ -58,7 +58,7 @@ class DocType(DocListController):
|
|||||||
then condition y can only be like 50 to 99 or 301 to 400
|
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)
|
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)
|
return (not separate)
|
||||||
|
|
||||||
overlaps = []
|
overlaps = []
|
||||||
|
@ -74,7 +74,6 @@ class SellingController(StockController):
|
|||||||
shipping_amount = condition.shipping_amount
|
shipping_amount = condition.shipping_amount
|
||||||
break
|
break
|
||||||
|
|
||||||
if shipping_amount:
|
|
||||||
self.doclist.append({
|
self.doclist.append({
|
||||||
"doctype": "Sales Taxes and Charges",
|
"doctype": "Sales Taxes and Charges",
|
||||||
"parentfield": "other_charges",
|
"parentfield": "other_charges",
|
||||||
|
@ -63,12 +63,15 @@ class DocType(DocListController):
|
|||||||
# if list against each territory has more than one element, raise exception
|
# if list against each territory has more than one element, raise exception
|
||||||
territory_name = webnotes.conn.sql("""select `territory`, `parent`
|
territory_name = webnotes.conn.sql("""select `territory`, `parent`
|
||||||
from `tabFor Territory`
|
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))
|
("%s", ", ".join(["%s"]*len(names))), tuple([parenttype] + names))
|
||||||
|
|
||||||
for territory, name in territory_name:
|
for territory, name in territory_name:
|
||||||
territory_name_map.setdefault(territory, []).append(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
|
return territory_name_map
|
||||||
|
|
||||||
def validate_exchange_rates_exist(self):
|
def validate_exchange_rates_exist(self):
|
||||||
@ -101,24 +104,26 @@ class DocType(DocListController):
|
|||||||
territory_name_map = self.get_territory_name_map(parentfield, fieldname)
|
territory_name_map = self.get_territory_name_map(parentfield, fieldname)
|
||||||
|
|
||||||
if territory_name_map.get(territory):
|
if territory_name_map.get(territory):
|
||||||
name = territory_name_map.get(territory)[0]
|
name = territory_name_map.get(territory)
|
||||||
else:
|
else:
|
||||||
territory_ancestry = self.get_territory_ancestry(territory)
|
territory_ancestry = self.get_territory_ancestry(territory)
|
||||||
for ancestor in territory_ancestry:
|
for ancestor in territory_ancestry:
|
||||||
if territory_name_map.get(ancestor):
|
if territory_name_map.get(ancestor):
|
||||||
name = territory_name_map.get(ancestor)[0]
|
name = territory_name_map.get(ancestor)
|
||||||
break
|
break
|
||||||
|
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def get_price_list(self, billing_territory):
|
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):
|
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")
|
"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")
|
return self.get_name_from_territory(shipping_territory, "shipping_rules", "shipping_rule")
|
||||||
|
|
||||||
def get_territory_ancestry(self, territory):
|
def get_territory_ancestry(self, territory):
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
"creation": "2013-06-19 15:57:32",
|
"creation": "2013-06-19 15:57:32",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"modified": "2013-07-03 21:00:00",
|
"modified": "2013-07-03 21:01:00",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"owner": "Administrator"
|
"owner": "Administrator"
|
||||||
},
|
},
|
||||||
@ -69,7 +69,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"fieldname": "shopping_cart_shipping_rules",
|
"fieldname": "shipping_rules",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Shopping Cart Shipping Rules",
|
"label": "Shopping Cart Shipping Rules",
|
||||||
"options": "Shopping Cart Shipping Rule",
|
"options": "Shopping Cart Shipping Rule",
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import webnotes
|
import webnotes
|
||||||
import webnotes.defaults
|
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
|
class WebsitePriceListMissingError(webnotes.ValidationError): pass
|
||||||
|
|
||||||
@ -18,14 +18,15 @@ def get_cart_quotation(doclist=None):
|
|||||||
return {
|
return {
|
||||||
"doclist": decorate_quotation_doclist(doclist),
|
"doclist": decorate_quotation_doclist(doclist),
|
||||||
"addresses": [{"name": address.name, "display": address.display}
|
"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()
|
@webnotes.whitelist()
|
||||||
def update_cart(item_code, qty, with_doclist=0):
|
def update_cart(item_code, qty, with_doclist=0):
|
||||||
quotation = _get_cart_quotation()
|
quotation = _get_cart_quotation()
|
||||||
|
|
||||||
qty = cint(qty)
|
qty = flt(qty)
|
||||||
if qty == 0:
|
if qty == 0:
|
||||||
quotation.set_doclist(quotation.doclist.get({"item_code": ["!=", item_code]}))
|
quotation.set_doclist(quotation.doclist.get({"item_code": ["!=", item_code]}))
|
||||||
else:
|
else:
|
||||||
@ -40,8 +41,7 @@ def update_cart(item_code, qty, with_doclist=0):
|
|||||||
else:
|
else:
|
||||||
quotation_items[0].qty = qty
|
quotation_items[0].qty = qty
|
||||||
|
|
||||||
quotation.ignore_permissions = True
|
apply_cart_settings(quotation=quotation)
|
||||||
quotation.save()
|
|
||||||
|
|
||||||
if with_doclist:
|
if with_doclist:
|
||||||
return get_cart_quotation(quotation.doclist)
|
return get_cart_quotation(quotation.doclist)
|
||||||
@ -158,6 +158,12 @@ def decorate_quotation_doclist(doclist):
|
|||||||
["website_image", "web_short_description", "page_name"], as_dict=True))
|
["website_image", "web_short_description", "page_name"], as_dict=True))
|
||||||
d.formatted_rate = fmt_money(d.export_rate, currency=doclist[0].currency)
|
d.formatted_rate = fmt_money(d.export_rate, currency=doclist[0].currency)
|
||||||
d.formatted_amount = fmt_money(d.export_amount, 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]
|
return [d.fields for d in doclist]
|
||||||
|
|
||||||
@ -199,16 +205,13 @@ def apply_cart_settings(party=None, quotation=None):
|
|||||||
|
|
||||||
set_price_list_and_rate(quotation, cart_settings, billing_territory)
|
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")
|
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()
|
quotation.save()
|
||||||
|
|
||||||
def set_price_list_and_rate(quotation, cart_settings, billing_territory):
|
def set_price_list_and_rate(quotation, cart_settings, billing_territory):
|
||||||
@ -236,9 +239,53 @@ def set_taxes(quotation, cart_settings, billing_territory):
|
|||||||
controller.append_taxes_from_master("other_charges", "charge")
|
controller.append_taxes_from_master("other_charges", "charge")
|
||||||
quotation.set_doclist(controller.doclist)
|
quotation.set_doclist(controller.doclist)
|
||||||
|
|
||||||
def apply_shipping_rule(quotation, cart_settings, shipping_territory):
|
@webnotes.whitelist()
|
||||||
quotation.doc.shipping_rule = cart_settings.get_shipping_rule(shipping_territory)
|
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]
|
||||||
|
|
||||||
quotation.run_method("apply_shipping_rule")
|
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):
|
def get_address_territory(address_name):
|
||||||
"""Tries to match city, state and country of address to existing territory"""
|
"""Tries to match city, state and country of address to existing territory"""
|
||||||
@ -254,9 +301,6 @@ def get_address_territory(address_name):
|
|||||||
|
|
||||||
return territory
|
return territory
|
||||||
|
|
||||||
def get_cart_price_list(territory_list, territory_name_map):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@webnotes.whitelist()
|
@webnotes.whitelist()
|
||||||
def checkout():
|
def checkout():
|
||||||
quotation = _get_cart_quotation()
|
quotation = _get_cart_quotation()
|
||||||
|
@ -38,7 +38,6 @@ $(document).ready(function() {
|
|||||||
} else {
|
} else {
|
||||||
wn.cart.render(r.message);
|
wn.cart.render(r.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -84,6 +83,7 @@ $.extend(wn.cart, {
|
|||||||
|
|
||||||
var $cart_items = $("#cart-items").empty();
|
var $cart_items = $("#cart-items").empty();
|
||||||
var $cart_taxes = $("#cart-taxes").empty();
|
var $cart_taxes = $("#cart-taxes").empty();
|
||||||
|
var $cart_totals = $("#cart-totals").empty();
|
||||||
var $cart_billing_address = $("#cart-billing-address").empty();
|
var $cart_billing_address = $("#cart-billing-address").empty();
|
||||||
var $cart_shipping_address = $("#cart-shipping-address").empty();
|
var $cart_shipping_address = $("#cart-shipping-address").empty();
|
||||||
|
|
||||||
@ -94,10 +94,37 @@ $.extend(wn.cart, {
|
|||||||
return;
|
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) {
|
$.each(doclist, function(i, doc) {
|
||||||
if(doc.doctype === "Quotation Item") {
|
if(doc.doctype === "Quotation Item") {
|
||||||
wn.cart.render_item_row($cart_items, doc);
|
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)
|
||||||
|
$('<hr>').appendTo($cart_taxes);
|
||||||
|
|
||||||
|
wn.cart.render_tax_row($cart_totals, {
|
||||||
|
description: "<strong>Total</strong>",
|
||||||
|
formatted_tax_amount: "<strong>" + doclist[0].formatted_grand_total_export + "</strong>"
|
||||||
});
|
});
|
||||||
|
|
||||||
if(!(addresses && addresses.length)) {
|
if(!(addresses && addresses.length)) {
|
||||||
@ -125,10 +152,10 @@ $.extend(wn.cart, {
|
|||||||
</div>\
|
</div>\
|
||||||
</div>\
|
</div>\
|
||||||
</div>\
|
</div>\
|
||||||
<div class="col col-lg-3 col-sm-3">\
|
<div class="col col-lg-3 col-sm-3 text-right">\
|
||||||
<div class="input-group item-update-cart">\
|
<div class="input-group item-update-cart">\
|
||||||
<input type="text" placeholder="Qty" value="%(qty)s" \
|
<input type="text" placeholder="Qty" value="%(qty)s" \
|
||||||
data-item-code="%(item_code)s">\
|
data-item-code="%(item_code)s" class="text-right">\
|
||||||
<div class="input-group-btn">\
|
<div class="input-group-btn">\
|
||||||
<button class="btn btn-primary" data-item-code="%(item_code)s">\
|
<button class="btn btn-primary" data-item-code="%(item_code)s">\
|
||||||
<i class="icon-ok"></i></button>\
|
<i class="icon-ok"></i></button>\
|
||||||
@ -140,6 +167,53 @@ $.extend(wn.cart, {
|
|||||||
</div><hr>', doc)).appendTo($cart_items);
|
</div><hr>', doc)).appendTo($cart_items);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
render_tax_row: function($cart_taxes, doc, shipping_rules) {
|
||||||
|
var shipping_selector;
|
||||||
|
if(shipping_rules) {
|
||||||
|
shipping_selector = '<select>' + $.map(shipping_rules, function(rule) {
|
||||||
|
return '<option value="' + rule[0] + '">' + rule[1] + '</option>' }).join("\n") +
|
||||||
|
'</select>';
|
||||||
|
}
|
||||||
|
|
||||||
|
var $tax_row = $(repl('<div class="row">\
|
||||||
|
<div class="col col-lg-9 col-sm-9">\
|
||||||
|
<div class="row">\
|
||||||
|
<div class="col col-lg-9 col-offset-3">' +
|
||||||
|
(shipping_selector || '<p>%(description)s</p>') +
|
||||||
|
'</div>\
|
||||||
|
</div>\
|
||||||
|
</div>\
|
||||||
|
<div class="col col-lg-3 col-sm-3 text-right">\
|
||||||
|
<p' + (shipping_selector ? ' style="margin-top: 5px;"' : "") + '>%(formatted_tax_amount)s</p>\
|
||||||
|
</div>\
|
||||||
|
</div>', 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) {
|
render_address: function($address_wrapper, addresses, address_name) {
|
||||||
$.each(addresses, function(i, address) {
|
$.each(addresses, function(i, address) {
|
||||||
$(repl('<div class="accordion-group"> \
|
$(repl('<div class="accordion-group"> \
|
||||||
|
@ -19,16 +19,17 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-lg-9 col-sm-9">
|
<div class="col col-lg-9 col-sm-9">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-lg-3"></div>
|
<div class="col col-lg-9 col-offset-3"><h4>Item Details</h4></div>
|
||||||
<div class="col col-lg-9"><h4>Item Details</h4></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-lg-3 col-sm-3"><h4>Qty</h4></div>
|
<div class="col col-lg-3 col-sm-3 text-right"><h4>Qty, Amount</h4></div>
|
||||||
</div><hr>
|
</div><hr>
|
||||||
<div id="cart-items">
|
<div id="cart-items">
|
||||||
</div>
|
</div>
|
||||||
<div id="cart-taxes">
|
<div id="cart-taxes">
|
||||||
</div>
|
</div>
|
||||||
|
<div id="cart-totals">
|
||||||
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div id="cart-addresses">
|
<div id="cart-addresses">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@ -37,16 +38,15 @@
|
|||||||
<div id="cart-shipping-address" class="accordion"
|
<div id="cart-shipping-address" class="accordion"
|
||||||
data-fieldname="shipping_address_name"></div>
|
data-fieldname="shipping_address_name"></div>
|
||||||
<button class="btn btn-default" type="button" id="cart-add-shipping-address">
|
<button class="btn btn-default" type="button" id="cart-add-shipping-address">
|
||||||
<span class="icon icon-plus"></span> New Address</button>
|
<span class="icon icon-plus"></span> New Shipping Address</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-lg-6">
|
<div class="col col-lg-6">
|
||||||
<h4>Billing Address</h4>
|
<h4>Billing Address</h4>
|
||||||
<div id="cart-billing-address" class="accordion"
|
<div id="cart-billing-address" class="accordion"
|
||||||
data-fieldname="customer_address"></div>
|
data-fieldname="customer_address"></div>
|
||||||
<button class="btn btn-default" type="button" id="cart-add-billing-address">
|
<button class="btn btn-default" type="button" id="cart-add-billing-address">
|
||||||
<span class="icon icon-plus"></span> New Address</button>
|
<span class="icon icon-plus"></span> New Billing Address</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user