Merge pull request #4033 from rmehta/portal-fixes

Portal Fixes
This commit is contained in:
Anand Doshi 2015-09-23 16:05:12 +05:30
commit 6e4f5a214a
61 changed files with 1346 additions and 1651 deletions

View File

@ -35,6 +35,15 @@ class SalesInvoice(SellingController):
'overflow_type': 'billing'
}]
def set_indicator(self):
"""Set indicator for portal"""
if self.outstanding_amount > 0:
self.indicator_color = "orange"
self.indicator_title = _("Unpaid")
else:
self.indicator_color = "green"
self.indicator_title = _("Paid")
def validate(self):
super(SalesInvoice, self).validate()
self.validate_posting_time()

View File

@ -5,6 +5,3 @@ cur_frm.cscript.tax_table = "Sales Taxes and Charges";
{% include "public/js/controllers/accounts.js" %}
frappe.ui.form.on("Sales Taxes and Charges Template", "onload", function(frm) {
erpnext.add_applicable_territory();
});

View File

@ -164,29 +164,6 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "Specify a list of Territories, for which, this Taxes Master is valid",
"fieldname": "territories",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Valid for Territories",
"no_copy": 0,
"options": "Applicable Territory",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
@ -198,7 +175,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-11 12:19:46.488710",
"modified": "2015-09-17 07:09:28.797959",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Taxes and Charges Template",
@ -206,7 +183,7 @@
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"apply_user_permissions": 1,
"cancel": 0,
"create": 0,
"delete": 0,

View File

@ -5,7 +5,6 @@ from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from erpnext.controllers.accounts_controller import validate_taxes_and_charges, validate_inclusive_tax
from frappe.utils.nestedset import get_root_of
class SalesTaxesandChargesTemplate(Document):
def validate(self):
@ -20,10 +19,6 @@ def valdiate_taxes_and_charges_template(doc):
where ifnull(is_default,0) = 1 and name != %s and company = %s""".format(doc.doctype),
(doc.name, doc.company))
if doc.meta.get_field("territories"):
if not doc.territories:
doc.append("territories", {"territory": get_root_of("Territory") })
for tax in doc.get("taxes"):
validate_taxes_and_charges(tax)
validate_inclusive_tax(tax, doc)

View File

@ -20,19 +20,7 @@
"rate": 6.36
}
],
"title": "_Test Sales Taxes and Charges Template",
"territories": [
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "All Territories"
},
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "_Test Territory Rest Of The World"
}
]
"title": "_Test Sales Taxes and Charges Template"
},
{
"company": "_Test Company",
@ -115,14 +103,7 @@
"row_id": 7
}
],
"title": "_Test India Tax Master",
"territories": [
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "_Test Territory India"
}
]
"title": "_Test India Tax Master"
},
{
"company": "_Test Company",
@ -145,14 +126,7 @@
"rate": 4
}
],
"title": "_Test Sales Taxes and Charges Template - Rest of the World",
"territories": [
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "_Test Territory Rest Of The World"
}
]
"title": "_Test Sales Taxes and Charges Template - Rest of the World"
},
{
"company": "_Test Company",
@ -175,14 +149,7 @@
"rate": 4
}
],
"title": "_Test Sales Taxes and Charges Template 1",
"territories": [
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "_Test Territory Rest Of The World"
}
]
"title": "_Test Sales Taxes and Charges Template 1"
},
{
"company": "_Test Company",
@ -205,14 +172,7 @@
"rate": 4
}
],
"title": "_Test Sales Taxes and Charges Template 2",
"territories": [
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "_Test Territory Rest Of The World"
}
]
"title": "_Test Sales Taxes and Charges Template 2"
},
{
"doctype" : "Sales Taxes and Charges Template",
@ -224,9 +184,6 @@
"cost_center": "Main - _TC",
"description": "Test Shopping cart taxes with Tax Rule",
"tax_amount": 1000
}],
"territories":[{
"territory" : "All Territories"
}]
},
{
@ -239,9 +196,6 @@
"cost_center": "Main - _TC",
"description": "Test Shopping cart taxes with Tax Rule",
"tax_amount": 200
}],
"territories":[{
"territory" : "All Territories"
}]
}
]

View File

@ -1,8 +1,3 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
$.extend(cur_frm.cscript, {
onload: function() {
erpnext.add_applicable_territory();
}
});

View File

@ -35,14 +35,16 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"fieldname": "disabled",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Disabled",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
@ -78,6 +80,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:!doc.disabled",
"fieldname": "rule_conditions_section",
"fieldtype": "Section Break",
"hidden": 0,
@ -121,12 +124,14 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:!doc.disabled",
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Valid for Countries",
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
@ -141,21 +146,20 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "Specify a list of Territories, for which, this Shipping Rule is valid",
"fieldname": "territories",
"fieldtype": "Table",
"fieldname": "worldwide_shipping",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Valid For Territories",
"label": "Worldwide Shipping",
"no_copy": 0,
"options": "Applicable Territory",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
@ -164,8 +168,33 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_8",
"fieldtype": "Column Break",
"depends_on": "eval:!doc.worldwide_shipping",
"fieldname": "countries",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Valid for Countries",
"no_copy": 0,
"options": "Shipping Rule Country",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:!doc.disabled",
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
@ -206,8 +235,8 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"fieldname": "column_break_12",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
@ -244,26 +273,6 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_12",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
@ -296,7 +305,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-07 15:51:26",
"modified": "2015-09-22 08:30:57.226342",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Shipping Rule",
@ -304,7 +313,7 @@
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"apply_user_permissions": 1,
"cancel": 0,
"create": 0,
"delete": 0,
@ -324,7 +333,7 @@
},
{
"amend": 0,
"apply_user_permissions": 0,
"apply_user_permissions": 1,
"cancel": 0,
"create": 0,
"delete": 0,

View File

@ -22,6 +22,12 @@ class ShippingRule(Document):
self.sort_shipping_rule_conditions()
self.validate_overlapping_shipping_rule_conditions()
if self.worldwide_shipping:
self.countries = []
elif not len([d.country for d in self.countries if d.country]):
frappe.throw(_("Please specify a country for this Shipping Rule or check Worldwide Shipping"))
def validate_from_to_values(self):
zero_to_values = []

View File

@ -29,13 +29,7 @@
"shipping_amount": 0.0
}
],
"territories": [
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "_Test Territory"
}
]
"worldwide_shipping": 1
},
{
"account": "_Test Account Shipping Charges - _TC",
@ -67,12 +61,8 @@
"shipping_amount": 0.0
}
],
"territories": [
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "_Test Territory India"
}
"countries": [
{"country": "India"}
]
},
{
@ -105,12 +95,6 @@
"shipping_amount": 1500.0
}
],
"territories": [
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "_Test Territory Rest Of The World"
}
]
"worldwide_shipping": 1
}
]

View File

@ -2,25 +2,27 @@
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"creation": "2013-06-20 16:00:18",
"creation": "2015-09-17 06:43:22.767534",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Other",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "selling_price_list",
"fieldname": "country",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Price List",
"label": "Country",
"no_copy": 0,
"options": "Price List",
"options": "Country",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
@ -32,18 +34,20 @@
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2013-12-20 19:30:47",
"modified": "2015-09-17 06:43:22.767534",
"modified_by": "Administrator",
"module": "Shopping Cart",
"name": "Shopping Cart Price List",
"module": "Accounts",
"name": "Shipping Rule Country",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"read_only": 0,
"read_only_onload": 0
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@ -1,12 +1,10 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class ApplicableTerritory(Document):
class ShippingRuleCountry(Document):
pass

View File

@ -13,6 +13,9 @@ class IncorrectSupplierType(frappe.ValidationError): pass
class ConflictingTaxRule(frappe.ValidationError): pass
class TaxRule(Document):
def __setup__(self):
self.flags.ignore_these_exceptions_in_test = [ConflictingTaxRule]
def validate(self):
self.validate_tax_template()
self.validate_customer_group()
@ -117,6 +120,9 @@ def get_tax_template(posting_date, args):
conditions = []
for key, value in args.iteritems():
if key in "use_for_shopping_cart":
conditions.append("use_for_shopping_cart = {0}".format(1 if value else 0))
else:
conditions.append("ifnull({0}, '') in ('', '{1}')".format(key, frappe.db.escape(cstr(value))))
matching = frappe.db.sql("""select * from `tabTax Rule`

View File

@ -103,6 +103,19 @@ def set_other_values(out, party, party_type):
if party.get("default_" + f):
out[f] = party.get("default_" + f)
def get_default_price_list(party):
"""Return default price list for party (Document object)"""
if party.default_price_list:
return party.default_price_list
if party.doctype == "Customer":
price_list = frappe.db.get_value("Customer Group",
party.customer_group, "default_price_list")
if price_list:
return price_list
return None
def set_price_list(out, party, party_type, given_price_list):
# price list
price_list = filter(None, get_user_permissions().get("Price List", []))
@ -110,11 +123,7 @@ def set_price_list(out, party, party_type, given_price_list):
price_list = price_list[0] if len(price_list)==1 else None
if not price_list:
price_list = party.default_price_list
if not price_list and party_type=="Customer":
price_list = frappe.db.get_value("Customer Group",
party.customer_group, "default_price_list")
price_list = get_default_price_list(party)
if not price_list:
price_list = given_price_list
@ -271,7 +280,7 @@ def set_taxes(party, party_type, posting_date, company, customer_group=None, sup
billing_address=None, shipping_address=None, use_for_shopping_cart=None):
from erpnext.accounts.doctype.tax_rule.tax_rule import get_tax_template, get_party_details
args = {
party_type: party,
party_type.lower(): party,
"customer_group": customer_group,
"supplier_type": supplier_type,
"company": company

View File

@ -21,37 +21,57 @@ def get_list_context(context=None):
def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20):
from frappe.templates.pages.list import get_list
user = frappe.session.user
key = None
if not filters: filters = []
filters.append((doctype, "docstatus", "=", 1))
if user != "Guest" and is_website_user():
# find party for this contact
customers, suppliers = get_customers_suppliers(doctype, user)
if customers:
return post_process(get_list(doctype, txt, filters=[(doctype, "customer", "in", customers)],
limit_start=limit_start, limit_page_length=limit_page_length, ignore_permissions=True))
key, parties = "customer", customers
elif suppliers:
return post_process(get_list(doctype, txt, filters=[(doctype, "supplier", "in", suppliers)],
limit_start=limit_start, limit_page_length=limit_page_length, ignore_permissions=True))
key, parties = "supplier", suppliers
filters.append((doctype, key, "in", parties))
if key:
return post_process(doctype, get_list(doctype, txt,
filters=filters, fields = "name",
limit_start=limit_start, limit_page_length=limit_page_length,
ignore_permissions=True,
order_by = "modified desc"))
else:
return []
return post_process(get_list(doctype, txt, filters, limit_start, limit_page_length))
return post_process(doctype, get_list(doctype, txt, filters, limit_start, limit_page_length,
fields="name", order_by = "modified desc"))
def post_process(result):
for r in result:
r.status_percent = 0
r.status_display = []
def post_process(doctype, data):
result = []
for d in data:
doc = frappe.get_doc(doctype, d.name)
if r.get("per_billed"):
r.status_percent += flt(r.per_billed)
r.status_display.append(_("Billed") if r.per_billed==100 else _("{0}% Billed").format(r.per_billed))
doc.status_percent = 0
doc.status_display = []
if r.get("per_delivered"):
r.status_percent += flt(r.per_delivered)
r.status_display.append(_("Delivered") if r.per_delivered==100 else _("{0}% Delivered").format(r.per_delivered))
if doc.get("per_billed"):
doc.status_percent += flt(doc.per_billed)
doc.status_display.append(_("Billed") if doc.per_billed==100 else _("{0}% Billed").format(doc.per_billed))
r.status_display = ", ".join(r.status_display)
if doc.get("per_delivered"):
doc.status_percent += flt(doc.per_delivered)
doc.status_display.append(_("Delivered") if doc.per_delivered==100 else _("{0}% Delivered").format(doc.per_delivered))
if hasattr(doc, "set_indicator"):
doc.set_indicator()
doc.status_display = ", ".join(doc.status_display)
doc.items_preview = ", ".join([d.item_name for d in doc.items])
result.append(doc)
return result

View File

@ -1,4 +1,6 @@
from __future__ import unicode_literals
from frappe import _
app_name = "erpnext"
app_title = "ERPNext"
app_publisher = "Frappe Technologies Pvt. Ltd."
@ -62,11 +64,26 @@ website_context = {
website_route_rules = [
{"from_route": "/orders", "to_route": "Sales Order"},
{"from_route": "/orders/<path:name>", "to_route": "print", "defaults": {"doctype": "Sales Order"}},
{"from_route": "/orders/<path:name>", "to_route": "order",
"defaults": {
"doctype": "Sales Order",
"parents": [{"title": _("Orders"), "name": "orders"}]
}
},
{"from_route": "/invoices", "to_route": "Sales Invoice"},
{"from_route": "/invoices/<path:name>", "to_route": "print", "defaults": {"doctype": "Sales Invoice"}},
{"from_route": "/invoices/<path:name>", "to_route": "order",
"defaults": {
"doctype": "Sales Invoice",
"parents": [{"title": _("Invoices"), "name": "invoices"}]
}
},
{"from_route": "/shipments", "to_route": "Delivery Note"},
{"from_route": "/shipments/<path:name>", "to_route": "print", "defaults": {"doctype": "Delivery Note"}}
{"from_route": "/shipments/<path:name>", "to_route": "order",
"defaults": {
"doctype": "Delivery Notes",
"parents": [{"title": _("Shipments"), "name": "shipments"}]
}
}
]
has_website_permission = {

View File

@ -147,7 +147,7 @@ cur_frm.fields_dict['project_name'].get_query = function(doc, dt, dn) {
cur_frm.fields_dict['items'].grid.get_field('item_code').get_query = function(doc) {
return{
query: "erpnext.controllers.queries.item_query",
filters: [["Item", "name", "!=", doc.item]]
filters: [["Item", "name", "!=", cur_frm.doc.item]]
}
}

View File

@ -209,3 +209,5 @@ erpnext.patches.v6_0.fix_planned_qty
erpnext.patches.v6_0.multi_currency
erpnext.patches.v6_2.remove_newsletter_duplicates
erpnext.patches.v6_2.fix_missing_default_taxes_and_lead
erpnext.patches.v5_8.tax_rule
erpnext.patches.v6_3.convert_applicable_territory

View File

@ -0,0 +1,17 @@
import frappe
def execute():
# for price list
countries = frappe.db.sql_list("select name from tabCountry")
for doctype in ("Price List", "Shipping Rule"):
for at in frappe.db.sql("""select name, parent, territory from `tabApplicable Territory` where
parenttype = %s """, doctype, as_dict=True):
if at.territory in countries:
parent = frappe.get_doc(doctype, at.parent)
if not parent.countries:
parent.append("countries", {"country": at.territory})
parent.save()
frappe.delete_doc("DocType", "Applicable Territory")

View File

@ -29,6 +29,7 @@
background-repeat: no-repeat;
background-position: center top;
border-radius: 0.5em;
border: 1px solid #ebeff2;
}
.product-image.missing-image {
width: 100%;
@ -38,6 +39,7 @@
background-repeat: no-repeat;
background-position: center top;
border-radius: 0.5em;
border: 1px solid #ebeff2;
border: 1px dashed #d1d8dd;
position: relative;
}

View File

@ -30,7 +30,7 @@ $.extend(shopping_cart, {
args: {
item_code: opts.item_code,
qty: opts.qty,
with_doc: opts.with_doc || 0
with_items: opts.with_items || 0
},
btn: opts.btn,
callback: function(r) {

View File

@ -49,18 +49,6 @@ $.extend(erpnext, {
}
},
add_applicable_territory: function() {
if(cur_frm.doc.__islocal && (cur_frm.doc.territories || []).length===0) {
var default_territory = frappe.defaults.get_user_default("territory");
if(default_territory) {
var territory = frappe.model.add_child(cur_frm.doc, "Applicable Territory",
"territories");
territory.territory = default_territory;
}
}
},
setup_serial_no: function() {
var grid_row = cur_frm.open_grid_row();
if(!grid_row.fields_dict.serial_no ||

View File

@ -1,4 +1,5 @@
@border-color: #d1d8dd;
@light-border-color: #EBEFF2;
.web-long-description {
font-size: 18px;
@ -35,6 +36,7 @@
background-repeat: no-repeat;
background-position: center top;
border-radius: 0.5em;
border: 1px solid @light-border-color;
}
.product-image.missing-image {

View File

@ -174,10 +174,7 @@ def create_price_lists(args):
"enabled": 1,
"buying": 1 if pl_type == "Buying" else 0,
"selling": 1 if pl_type == "Selling" else 0,
"currency": args["currency"],
"territories": [{
"territory": get_root_of("Territory")
}]
"currency": args["currency"]
}).insert()
def set_defaults(args):

View File

@ -1,103 +1,2 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import get_fullname, flt
from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import check_shopping_cart_enabled, get_default_territory
# TODO
# validate stock of each item in Website Warehouse or have a list of possible warehouses in Shopping Cart Settings
# Below functions are used for test cases
def get_quotation(user=None):
if not user:
user = frappe.session.user
if user == "Guest":
raise frappe.PermissionError
check_shopping_cart_enabled()
party = get_party(user)
values = {
"order_type": "Shopping Cart",
party.doctype.lower(): party.name,
"docstatus": 0,
"contact_email": user,
"selling_price_list": "_Test Price List Rest of the World",
"currency": "USD"
}
try:
quotation = frappe.get_doc("Quotation", values)
except frappe.DoesNotExistError:
quotation = frappe.new_doc("Quotation")
quotation.update(values)
if party.doctype == "Customer":
quotation.contact_person = frappe.db.get_value("Contact", {"customer": party.name, "email_id": user})
quotation.insert(ignore_permissions=True)
return quotation
def set_item_in_cart(item_code, qty, user=None):
validate_item(item_code)
quotation = get_quotation(user=user)
qty = flt(qty)
quotation_item = quotation.get("items", {"item_code": item_code})
if qty==0:
if quotation_item:
# remove
quotation.get("items").remove(quotation_item[0])
else:
# add or update
if quotation_item:
quotation_item[0].qty = qty
else:
quotation.append("items", {
"doctype": "Quotation Item",
"item_code": item_code,
"qty": qty
})
quotation.save(ignore_permissions=True)
return quotation
def validate_item(item_code):
item = frappe.db.get_value("Item", item_code, ["item_name", "show_in_website"], as_dict=True)
if not item.show_in_website:
frappe.throw(_("{0} cannot be purchased using Shopping Cart").format(item.item_name))
def get_party(user):
def _get_party(user):
customer = frappe.db.get_value("Contact", {"email_id": user}, "customer")
if customer:
return frappe.get_doc("Customer", customer)
lead = frappe.db.get_value("Lead", {"email_id": user})
if lead:
return frappe.get_doc("Lead", lead)
# create a lead
lead = frappe.new_doc("Lead")
lead.update({
"email_id": user,
"lead_name": get_fullname(user),
"territory": guess_territory()
})
lead.insert(ignore_permissions=True)
return lead
if not getattr(frappe.local, "shopping_cart_party", None):
frappe.local.shopping_cart_party = {}
if not frappe.local.shopping_cart_party.get(user):
frappe.local.shopping_cart_party[user] = _get_party(user)
return frappe.local.shopping_cart_party[user]
def guess_territory():
territory = None
if frappe.session.get("session_country"):
territory = frappe.db.get_value("Territory", frappe.session.get("session_country"))
return territory or get_default_territory()

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe
from frappe import throw, _
import frappe.defaults
from frappe.utils import cint, flt, get_fullname, fmt_money, cstr
from frappe.utils import cint, flt, get_fullname, cstr
from erpnext.utilities.doctype.address.address import get_address_display
from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import get_shopping_cart_settings
from frappe.utils.nestedset import get_root_of
@ -17,11 +17,13 @@ def set_cart_count(quotation=None):
if not quotation:
quotation = _get_cart_quotation()
cart_count = cstr(len(quotation.get("items")))
if hasattr(frappe.local, "cookie_manager"):
frappe.local.cookie_manager.set_cookie("cart_count", cart_count)
@frappe.whitelist()
def get_cart_quotation(doc=None):
party = get_lead_or_customer()
party = get_customer()
if not doc:
quotation = _get_cart_quotation(party)
@ -58,21 +60,19 @@ def place_order():
sales_order.flags.ignore_permissions = True
sales_order.insert()
sales_order.submit()
if hasattr(frappe.local, "cookie_manager"):
frappe.local.cookie_manager.delete_cookie("cart_count")
return sales_order.name
@frappe.whitelist()
def update_cart(item_code, qty, with_doc):
def update_cart(item_code, qty, with_items=False):
quotation = _get_cart_quotation()
qty = flt(qty)
if qty == 0:
quotation.set("items", quotation.get("items", {"item_code": ["!=", item_code]}))
if not quotation.get("items") and \
not quotation.get("__islocal"):
quotation.__delete = True
else:
quotation_items = quotation.get("items", {"item_code": item_code})
if not quotation_items:
@ -86,17 +86,19 @@ def update_cart(item_code, qty, with_doc):
apply_cart_settings(quotation=quotation)
if hasattr(quotation, "__delete"):
frappe.delete_doc("Quotation", quotation.name, ignore_permissions=True)
quotation = _get_cart_quotation()
else:
quotation.flags.ignore_permissions = True
quotation.save()
set_cart_count(quotation)
if with_doc:
return get_cart_quotation(quotation)
if with_items:
context = get_cart_quotation(quotation)
return {
"items": frappe.render_template("templates/includes/cart/cart_items.html",
context),
"taxes": frappe.render_template("templates/includes/order/order_taxes.html",
context),
}
else:
return quotation.name
@ -122,7 +124,11 @@ def update_cart_address(address_fieldname, address_name):
quotation.flags.ignore_permissions = True
quotation.save()
return get_cart_quotation(quotation)
context = get_cart_quotation(quotation)
return {
"taxes": frappe.render_template("templates/includes/order/order_taxes.html",
context),
}
def guess_territory():
territory = None
@ -134,32 +140,23 @@ def guess_territory():
frappe.db.get_value("Shopping Cart Settings", None, "territory") or \
get_root_of("Territory")
def decorate_quotation_doc(quotation_doc):
doc = frappe._dict(quotation_doc.as_dict())
def decorate_quotation_doc(doc):
for d in doc.get("items", []):
d.update(frappe.db.get_value("Item", d["item_code"],
d.update(frappe.db.get_value("Item", d.item_code,
["website_image", "description", "page_name"], as_dict=True))
d["formatted_rate"] = fmt_money(d.get("rate"), currency=doc.currency)
d["formatted_amount"] = fmt_money(d.get("amount"), currency=doc.currency)
for d in doc.get("taxes", []):
d["formatted_tax_amount"] = fmt_money(flt(d.get("tax_amount_after_discount_amount")),
currency=doc.currency)
doc.formatted_grand_total_export = fmt_money(doc.grand_total,
currency=doc.currency)
return doc
def _get_cart_quotation(party=None):
if not party:
party = get_lead_or_customer()
party = get_customer()
quotation = frappe.db.get_value("Quotation",
{party.doctype.lower(): party.name, "order_type": "Shopping Cart", "docstatus": 0})
quotation = frappe.get_all("Quotation", fields=["name"], filters=
{party.doctype.lower(): party.name, "order_type": "Shopping Cart", "docstatus": 0},
order_by="modified desc", limit_page_length=1)
if quotation:
qdoc = frappe.get_doc("Quotation", quotation)
qdoc = frappe.get_doc("Quotation", quotation[0].name)
else:
qdoc = frappe.get_doc({
"doctype": "Quotation",
@ -173,9 +170,9 @@ def _get_cart_quotation(party=None):
(party.doctype.lower()): party.name
})
if party.doctype == "Customer":
qdoc.contact_person = frappe.db.get_value("Contact", {"email_id": frappe.session.user,
"customer": party.name})
qdoc.contact_email = frappe.session.user
qdoc.flags.ignore_permissions = True
qdoc.run_method("set_missing_values")
@ -184,14 +181,8 @@ def _get_cart_quotation(party=None):
return qdoc
def update_party(fullname, company_name=None, mobile_no=None, phone=None):
party = get_lead_or_customer()
party = get_customer()
if party.doctype == "Lead":
party.company_name = company_name
party.lead_name = fullname
party.mobile_no = mobile_no
party.phone = phone
else:
party.customer_name = company_name or fullname
party.customer_type == "Company" if company_name else "Individual"
@ -219,26 +210,24 @@ def update_party(fullname, company_name=None, mobile_no=None, phone=None):
def apply_cart_settings(party=None, quotation=None):
if not party:
party = get_lead_or_customer()
party = get_customer()
if not quotation:
quotation = _get_cart_quotation(party)
cart_settings = frappe.get_doc("Shopping Cart Settings")
billing_territory = get_address_territory(quotation.customer_address) or \
party.territory or get_root_of("Territory")
set_price_list_and_rate(quotation, cart_settings, billing_territory)
set_price_list_and_rate(quotation, cart_settings)
quotation.run_method("calculate_taxes_and_totals")
set_taxes(quotation, cart_settings, billing_territory)
set_taxes(quotation, cart_settings)
_apply_shipping_rule(party, quotation, cart_settings)
def set_price_list_and_rate(quotation, cart_settings, billing_territory):
def set_price_list_and_rate(quotation, cart_settings):
"""set price list based on billing territory"""
_set_price_list(quotation, cart_settings, billing_territory)
_set_price_list(quotation, cart_settings)
# reset values
quotation.price_list_currency = quotation.currency = \
@ -249,22 +238,28 @@ def set_price_list_and_rate(quotation, cart_settings, billing_territory):
# refetch values
quotation.run_method("set_price_list_and_item_details")
if hasattr(frappe.local, "cookie_manager"):
# set it in cookies for using in product page
frappe.local.cookie_manager.set_cookie("selling_price_list", quotation.selling_price_list)
def _set_price_list(quotation, cart_settings, billing_territory):
def _set_price_list(quotation, cart_settings):
"""Set price list based on customer or shopping cart default"""
if quotation.selling_price_list:
return
# check if customer price list exists
selling_price_list = None
if quotation.customer:
selling_price_list = frappe.db.get_value("Customer", quotation.customer, "default_price_list")
from erpnext.accounts.party import get_default_price_list
selling_price_list = get_default_price_list(frappe.get_doc("Customer", quotation.customer))
# else check for territory based price list
if not selling_price_list:
selling_price_list = cart_settings.get_price_list(billing_territory)
selling_price_list = cart_settings.price_list
quotation.selling_price_list = selling_price_list
def set_taxes(quotation, cart_settings, billing_territory):
def set_taxes(quotation, cart_settings):
"""set taxes based on billing territory"""
from erpnext.accounts.party import set_taxes
@ -280,32 +275,38 @@ def set_taxes(quotation, cart_settings, billing_territory):
# # append taxes
quotation.append_taxes_from_master()
def get_lead_or_customer():
customer = frappe.db.get_value("Contact", {"email_id": frappe.session.user}, "customer")
def get_customer(user=None):
if not user:
user = frappe.session.user
customer = frappe.db.get_value("Contact", {"email_id": user}, "customer")
if customer:
return frappe.get_doc("Customer", customer)
lead = frappe.db.get_value("Lead", {"email_id": frappe.session.user})
if lead:
return frappe.get_doc("Lead", lead)
else:
lead_doc = frappe.get_doc({
"doctype": "Lead",
"email_id": frappe.session.user,
"lead_name": get_fullname(frappe.session.user),
"territory": guess_territory(),
"status": "Open" # TODO: set something better???
customer = frappe.new_doc("Customer")
fullname = get_fullname(user)
customer.update({
"customer_name": fullname,
"customer_type": "Individual",
"customer_group": get_shopping_cart_settings().default_customer_group,
"territory": get_root_of("Territory")
})
customer.insert(ignore_permissions=True)
if frappe.session.user not in ("Guest", "Administrator"):
lead_doc.flags.ignore_permissions = True
lead_doc.insert()
contact = frappe.new_doc("Contact")
contact.update({
"customer": customer.name,
"first_name": fullname,
"email_id": user
})
contact.insert(ignore_permissions=True)
return lead_doc
return customer
def get_address_docs(doctype=None, txt=None, filters=None, limit_start=0, limit_page_length=20, party=None):
if not party:
party = get_lead_or_customer()
party = get_customer()
address_docs = frappe.db.sql("""select * from `tabAddress`
where `{0}`=%s order by name limit {1}, {2}""".format(party.doctype.lower(),
@ -314,7 +315,6 @@ def get_address_docs(doctype=None, txt=None, filters=None, limit_start=0, limit_
for address in address_docs:
address.display = get_address_display(address)
address.display = (address.display).replace("\n", "<br>\n")
return address_docs
@ -332,7 +332,8 @@ def apply_shipping_rule(shipping_rule):
return get_cart_quotation(quotation)
def _apply_shipping_rule(party=None, quotation=None, cart_settings=None):
shipping_rules = get_shipping_rules(party, quotation, cart_settings)
if not quotation.shipping_rule:
shipping_rules = get_shipping_rules(quotation, cart_settings)
if not shipping_rules:
return
@ -340,30 +341,30 @@ def _apply_shipping_rule(party=None, quotation=None, cart_settings=None):
elif quotation.shipping_rule not in shipping_rules:
quotation.shipping_rule = shipping_rules[0]
if quotation.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)
shipping_rules = get_shipping_rules(quotation)
if shipping_rules:
rule_label_map = frappe.db.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()
def get_shipping_rules(quotation=None, cart_settings=None):
if not quotation:
quotation = _get_cart_quotation()
if not cart_settings:
cart_settings = frappe.get_doc("Shopping Cart Settings")
# set shipping rule based on shipping territory
shipping_territory = get_address_territory(quotation.shipping_address_name) or \
party.territory
shipping_rules = cart_settings.get_shipping_rules(shipping_territory)
shipping_rules = []
if quotation.shipping_address_name:
country = frappe.db.get_value("Address", quotation.shipping_address_name, "country")
if country:
shipping_rules = frappe.db.sql_list("""select distinct sr.name
from `tabShipping Rule Country` src, `tabShipping Rule` sr
where src.country = %s and
sr.disabled != 1 and sr.name = src.parent""", country)
return shipping_rules

View File

@ -1,12 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class ShoppingCartPriceList(Document):
pass

View File

@ -75,17 +75,17 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "<a href=\"#Sales Browser/Territory\">Add / Edit</a>",
"fieldname": "default_territory",
"fieldname": "price_list",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Default Territory",
"in_list_view": 0,
"label": "Price List",
"no_copy": 0,
"options": "Territory",
"options": "Price List",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
@ -118,7 +118,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "<a href=\"#Sales Browser/Customer Group\">Add / Edit</a>",
"description": "",
"fieldname": "default_customer_group",
"fieldtype": "Link",
"hidden": 0,
@ -157,91 +157,6 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "price_lists",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Price Lists",
"no_copy": 0,
"options": "Shopping Cart Price List",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_10",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "shipping_rules",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Shipping Rules",
"no_copy": 0,
"options": "Shopping Cart Shipping Rule",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
@ -253,7 +168,7 @@
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"modified": "2015-09-11 19:03:54.750937",
"modified": "2015-09-17 07:56:09.176098",
"modified_by": "Administrator",
"module": "Shopping Cart",
"name": "Shopping Cart Settings",

View File

@ -8,8 +8,6 @@ import frappe
from frappe import _, msgprint
from frappe.utils import comma_and
from frappe.model.document import Document
from frappe.utils.nestedset import get_ancestors_of, get_root_of
from erpnext.utilities.doctype.address.address import get_territory_from_address
class ShoppingCartSetupError(frappe.ValidationError): pass
@ -19,57 +17,7 @@ class ShoppingCartSettings(Document):
def validate(self):
if self.enabled:
self.validate_price_lists()
self.validate_exchange_rates_exist()
self.validate_tax_rule()
def validate_overlapping_territories(self, parentfield, fieldname):
# for displaying message
doctype = self.meta.get_field(parentfield).options
# specify atleast one entry in the table
self.validate_table_has_rows(parentfield, raise_exception=ShoppingCartSetupError)
territory_name_map = self.get_territory_name_map(parentfield, fieldname)
for territory, names in territory_name_map.items():
if len(names) > 1:
frappe.throw(_("{0} {1} has a common territory {2}").format(_(doctype), comma_and(names), territory), ShoppingCartSetupError)
return territory_name_map
def validate_price_lists(self):
self.validate_overlapping_territories("price_lists", "selling_price_list")
# validate that a Shopping Cart Price List exists for the default territory as a catch all!
price_list_for_default_territory = self.get_name_from_territory(self.default_territory, "price_lists",
"selling_price_list")
if not price_list_for_default_territory:
msgprint(_("Please specify a Price List which is valid for Territory") +
": " + self.default_territory, raise_exception=ShoppingCartSetupError)
def get_territory_name_map(self, parentfield, fieldname):
territory_name_map = {}
# entries in table
names = [doc.get(fieldname) for doc in self.get(parentfield)]
if names:
# for condition in territory check
parenttype = frappe.get_meta(self.meta.get_options(parentfield)).get_options(fieldname)
# to validate territory overlap
# make a map of territory: [list of names]
# if list against each territory has more than one element, raise exception
territory_name = frappe.db.sql("""select `territory`, `parent`
from `tabApplicable Territory`
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):
"""check if exchange rates exist for all Price List currencies (to company's currency)"""
@ -79,7 +27,7 @@ class ShoppingCartSettings(Document):
raise_exception=ShoppingCartSetupError)
price_list_currency_map = frappe.db.get_values("Price List",
[d.selling_price_list for d in self.get("price_lists")],
[self.price_list],
"currency")
# check if all price lists have a currency
@ -102,28 +50,6 @@ class ShoppingCartSettings(Document):
msgprint(_("Missing Currency Exchange Rates for {0}").format(comma_and(missing)),
raise_exception=ShoppingCartSetupError)
def get_name_from_territory(self, territory, parentfield, fieldname):
name = None
territory_name_map = self.get_territory_name_map(parentfield, fieldname)
if territory_name_map.get(territory):
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)
break
return name
def get_price_list(self, billing_territory):
price_list = self.get_name_from_territory(billing_territory, "price_lists", "selling_price_list")
if not (price_list and price_list[0]):
price_list = self.get_name_from_territory(self.default_territory or get_root_of("Territory"),
"price_lists", "selling_price_list")
return price_list and price_list[0] or None
def validate_tax_rule(self):
if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart" : 1}, "name"):
frappe.throw(frappe._("Set Tax Rule for shopping cart"), ShoppingCartSetupError)
@ -136,15 +62,6 @@ class ShoppingCartSettings(Document):
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):
if not hasattr(self, "_territory_ancestry"):
self._territory_ancestry = {}
if not self._territory_ancestry.get(territory):
self._territory_ancestry[territory] = get_ancestors_of("Territory", territory)
return self._territory_ancestry[territory]
def validate_cart_settings(doc, method):
frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings").run_method("validate")
@ -157,9 +74,6 @@ def get_shopping_cart_settings():
def is_cart_enabled():
return get_shopping_cart_settings().enabled
def get_default_territory():
return get_shopping_cart_settings().default_territory or get_root_of("Territory")
def check_shopping_cart_enabled():
if not get_shopping_cart_settings().enabled:
frappe.throw(_("You need to enable Shopping Cart"), ShoppingCartSetupError)

View File

@ -11,48 +11,22 @@ from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings
class TestShoppingCartSettings(unittest.TestCase):
def setUp(self):
frappe.db.sql("""delete from `tabSingles` where doctype="Shipping Cart Settings" """)
frappe.db.sql("""delete from `tabShopping Cart Price List`""")
frappe.db.sql("""delete from `tabShopping Cart Shipping Rule`""")
def get_cart_settings(self):
return frappe.get_doc({"doctype": "Shopping Cart Settings",
"company": "_Test Company"})
def test_price_list_territory_overlap(self):
cart_settings = self.get_cart_settings()
def _add_price_list(price_list):
cart_settings.append("price_lists", {
"doctype": "Shopping Cart Price List",
"selling_price_list": price_list
})
for price_list in ("_Test Price List Rest of the World", "_Test Price List India",
"_Test Price List"):
_add_price_list(price_list)
controller = cart_settings
controller.validate_overlapping_territories("price_lists", "selling_price_list")
_add_price_list("_Test Price List 2")
controller = cart_settings
self.assertRaises(ShoppingCartSetupError, controller.validate_overlapping_territories,
"price_lists", "selling_price_list")
return cart_settings
def test_exchange_rate_exists(self):
frappe.db.sql("""delete from `tabCurrency Exchange`""")
cart_settings = self.test_price_list_territory_overlap()
controller = cart_settings
self.assertRaises(ShoppingCartSetupError, controller.validate_exchange_rates_exist)
cart_settings = self.get_cart_settings()
cart_settings.price_list = "_Test Price List Rest of the World"
self.assertRaises(ShoppingCartSetupError, cart_settings.validate_exchange_rates_exist)
from erpnext.setup.doctype.currency_exchange.test_currency_exchange import test_records as \
currency_exchange_records
frappe.get_doc(currency_exchange_records[0]).insert()
controller.validate_exchange_rates_exist()
cart_settings.validate_exchange_rates_exist()
def test_tax_rule_validation(self):
frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0")

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals
import unittest
import frappe
from erpnext.shopping_cart import get_quotation, set_item_in_cart, get_party
from erpnext.shopping_cart.cart import _get_cart_quotation, update_cart, get_customer
class TestShoppingCart(unittest.TestCase):
"""
@ -23,23 +23,11 @@ class TestShoppingCart(unittest.TestCase):
self.login_as_new_user()
# test if lead is created and quotation with new lead is fetched
quotation = get_quotation()
self.assertEquals(quotation.quotation_to, "Lead")
self.assertEquals(frappe.db.get_value("Lead", quotation.lead, "email_id"),
quotation = _get_cart_quotation()
self.assertEquals(quotation.quotation_to, "Customer")
self.assertEquals(frappe.db.get_value("Contact", {"customer": quotation.customer}, "email_id"),
"test_cart_user@example.com")
self.assertEquals(quotation.customer, None)
self.assertEquals(quotation.contact_email, frappe.session.user)
return quotation
def test_get_cart_lead(self):
self.login_as_lead()
# test if quotation with lead is fetched
quotation = get_quotation()
self.assertEquals(quotation.quotation_to, "Lead")
self.assertEquals(quotation.lead, frappe.db.get_value("Lead", {"email_id": "test_cart_lead@example.com"}))
self.assertEquals(quotation.customer, None)
self.assertEquals(quotation.lead, None)
self.assertEquals(quotation.contact_email, frappe.session.user)
return quotation
@ -48,7 +36,7 @@ class TestShoppingCart(unittest.TestCase):
self.login_as_customer()
# test if quotation with customer is fetched
quotation = get_quotation()
quotation = _get_cart_quotation()
self.assertEquals(quotation.quotation_to, "Customer")
self.assertEquals(quotation.customer, "_Test Customer")
self.assertEquals(quotation.lead, None)
@ -57,21 +45,24 @@ class TestShoppingCart(unittest.TestCase):
return quotation
def test_add_to_cart(self):
self.login_as_lead()
self.login_as_customer()
# remove from cart
self.remove_all_items_from_cart()
# add first item
set_item_in_cart("_Test Item", 1)
quotation = self.test_get_cart_lead()
update_cart("_Test Item", 1)
quotation = self.test_get_cart_customer()
self.assertEquals(quotation.get("items")[0].item_code, "_Test Item")
self.assertEquals(quotation.get("items")[0].qty, 1)
self.assertEquals(quotation.get("items")[0].amount, 10)
# add second item
set_item_in_cart("_Test Item 2", 1)
quotation = self.test_get_cart_lead()
update_cart("_Test Item 2", 1)
quotation = self.test_get_cart_customer()
self.assertEquals(quotation.get("items")[1].item_code, "_Test Item 2")
self.assertEquals(quotation.get("items")[1].qty, 1)
self.assertEquals(quotation.get("items")[1].amount, 20)
@ -83,8 +74,8 @@ class TestShoppingCart(unittest.TestCase):
self.test_add_to_cart()
# update first item
set_item_in_cart("_Test Item", 5)
quotation = self.test_get_cart_lead()
update_cart("_Test Item", 5)
quotation = self.test_get_cart_customer()
self.assertEquals(quotation.get("items")[0].item_code, "_Test Item")
self.assertEquals(quotation.get("items")[0].qty, 5)
self.assertEquals(quotation.get("items")[0].amount, 50)
@ -96,8 +87,9 @@ class TestShoppingCart(unittest.TestCase):
self.test_add_to_cart()
# remove first item
set_item_in_cart("_Test Item", 0)
quotation = self.test_get_cart_lead()
update_cart("_Test Item", 0)
quotation = self.test_get_cart_customer()
self.assertEquals(quotation.get("items")[0].item_code, "_Test Item 2")
self.assertEquals(quotation.get("items")[0].qty, 1)
self.assertEquals(quotation.get("items")[0].amount, 20)
@ -105,13 +97,13 @@ class TestShoppingCart(unittest.TestCase):
self.assertEquals(len(quotation.get("items")), 1)
# remove second item
set_item_in_cart("_Test Item 2", 0)
quotation = self.test_get_cart_lead()
self.assertEquals(quotation.net_total, 0)
update_cart("_Test Item 2", 0)
quotation = self.test_get_cart_customer()
self.assertEquals(len(quotation.get("items")), 0)
self.assertEquals(quotation.net_total, 0)
def test_taxe_rule(self):
def test_tax_rule(self):
self.login_as_customer()
quotation = self.create_quotation()
@ -133,7 +125,7 @@ class TestShoppingCart(unittest.TestCase):
"doctype": "Quotation",
"quotation_to": "Customer",
"order_type": "Shopping Cart",
"customer": get_party(frappe.session.user).name,
"customer": get_customer(frappe.session.user).name,
"docstatus": 0,
"contact_email": frappe.session.user,
"selling_price_list": "_Test Price List Rest of the World",
@ -161,26 +153,29 @@ class TestShoppingCart(unittest.TestCase):
def enable_shopping_cart(self):
settings = frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings")
if settings.get("price_lists"):
settings.enabled = 1
else:
settings.update({
"enabled": 1,
"company": "_Test Company",
"default_territory": "_Test Territory Rest Of The World",
"default_customer_group": "_Test Customer Group",
"quotation_series": "_T-Quotation-"
"quotation_series": "_T-Quotation-",
"price_list": "_Test Price List India"
})
settings.set("price_lists", [
# price lists
{"doctype": "Shopping Cart Price List", "parentfield": "price_lists",
"selling_price_list": "_Test Price List India"},
{"doctype": "Shopping Cart Price List", "parentfield": "price_lists",
"selling_price_list": "_Test Price List Rest of the World"}
])
settings.set("shipping_rules", {"doctype": "Shopping Cart Shipping Rule", "parentfield": "shipping_rules",
"shipping_rule": "_Test Shipping Rule - India"})
# insert item price
if not frappe.db.get_value("Item Price", {"price_list": "_Test Price List India",
"item_code": "_Test Item"}):
frappe.get_doc({
"doctype": "Item Price",
"price_list": "_Test Price List India",
"item_code": "_Test Item",
"price_list_rate": 10
}).insert()
frappe.get_doc({
"doctype": "Item Price",
"price_list": "_Test Price List India",
"item_code": "_Test Item 2",
"price_list_rate": 20
}).insert()
settings.save()
frappe.local.shopping_cart_settings = None
@ -194,54 +189,11 @@ class TestShoppingCart(unittest.TestCase):
def login_as_new_user(self):
frappe.set_user("test_cart_user@example.com")
def login_as_lead(self):
self.create_lead()
frappe.set_user("test_cart_lead@example.com")
def login_as_customer(self):
frappe.set_user("test_contact_customer@example.com")
def create_lead(self):
if frappe.db.get_value("Lead", {"email_id": "test_cart_lead@example.com"}):
return
lead = frappe.get_doc({
"doctype": "Lead",
"email_id": "test_cart_lead@example.com",
"lead_name": "_Test Website Lead",
"status": "Open",
"territory": "_Test Territory Rest Of The World",
"company": "_Test Company"
})
lead.insert(ignore_permissions=True)
frappe.get_doc({
"doctype": "Address",
"address_line1": "_Test Address Line 1",
"address_title": "_Test Cart Lead Address",
"address_type": "Office",
"city": "_Test City",
"country": "United States",
"lead": lead.name,
"lead_name": "_Test Website Lead",
"is_primary_address": 1,
"phone": "+91 0000000000"
}).insert(ignore_permissions=True)
frappe.get_doc({
"doctype": "Address",
"address_line1": "_Test Address Line 1",
"address_title": "_Test Cart Lead Address",
"address_type": "Personal",
"city": "_Test City",
"country": "India",
"lead": lead.name,
"lead_name": "_Test Website Lead",
"phone": "+91 0000000000"
}).insert(ignore_permissions=True)
def remove_all_items_from_cart(self):
quotation = get_quotation()
quotation = _get_cart_quotation()
quotation.set("items", [])
quotation.save(ignore_permissions=True)

View File

@ -19,6 +19,7 @@ class Item(WebsiteGenerator):
condition_field = "show_in_website",
template = "templates/generators/item.html",
parent_website_route_field = "item_group",
no_cache = 1
)
def onload(self):

View File

@ -2,10 +2,6 @@
// License: GNU General Public License v3. See license.txt
$.extend(cur_frm.cscript, {
onload: function() {
erpnext.add_applicable_territory();
},
refresh: function() {
cur_frm.add_custom_button(__("Add / Edit Prices"), function() {
frappe.route_options = {

View File

@ -163,21 +163,21 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "Specify a list of Territories, for which, this Price List is valid",
"fieldname": "territories",
"fieldname": "countries",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Valid for Territories",
"label": "Applicable for Countries",
"no_copy": 0,
"options": "Applicable Territory",
"options": "Price List Country",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
@ -193,7 +193,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 1,
"modified": "2015-09-14 02:55:58.919822",
"modified": "2015-09-17 06:50:31.465221",
"modified_by": "Administrator",
"module": "Stock",
"name": "Price List",
@ -201,7 +201,7 @@
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"apply_user_permissions": 1,
"cancel": 0,
"create": 0,
"delete": 0,
@ -241,7 +241,7 @@
},
{
"amend": 0,
"apply_user_permissions": 0,
"apply_user_permissions": 1,
"cancel": 0,
"create": 0,
"delete": 0,
@ -281,7 +281,7 @@
},
{
"amend": 0,
"apply_user_permissions": 0,
"apply_user_permissions": 1,
"cancel": 0,
"create": 0,
"delete": 0,

View File

@ -13,19 +13,6 @@ class PriceList(Document):
if not cint(self.buying) and not cint(self.selling):
throw(_("Price List must be applicable for Buying or Selling"))
try:
# at least one territory
self.validate_table_has_rows("territories")
except frappe.EmptyTableError:
# if no territory, set default territory
if frappe.defaults.get_user_default("territory"):
self.append("territories", {
"doctype": "Applicable Territory",
"territory": frappe.defaults.get_user_default("territory")
})
else:
raise
def on_update(self):
self.set_default_if_missing()
self.update_item_price()

View File

@ -5,14 +5,7 @@
"doctype": "Price List",
"enabled": 1,
"price_list_name": "_Test Price List",
"selling": 1,
"territories": [
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "All Territories"
}
]
"selling": 1
},
{
"buying": 1,
@ -20,14 +13,7 @@
"doctype": "Price List",
"enabled": 1,
"price_list_name": "_Test Price List 2",
"selling": 1,
"territories": [
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "_Test Territory Rest Of The World"
}
]
"selling": 1
},
{
"buying": 1,
@ -35,14 +21,7 @@
"doctype": "Price List",
"enabled": 1,
"price_list_name": "_Test Price List India",
"selling": 1,
"territories": [
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "_Test Territory India"
}
]
"selling": 1
},
{
"buying": 1,
@ -50,18 +29,6 @@
"doctype": "Price List",
"enabled": 1,
"price_list_name": "_Test Price List Rest of the World",
"selling": 1,
"territories": [
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "_Test Territory Rest Of The World"
},
{
"doctype": "Applicable Territory",
"parentfield": "territories",
"territory": "_Test Territory United States"
}
]
"selling": 1
}
]

View File

@ -2,26 +2,27 @@
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"creation": "2013-06-20 12:48:38",
"creation": "2015-09-17 06:49:51.810318",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "",
"fieldname": "territory",
"fieldname": "country",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Territory",
"label": "Country",
"no_copy": 0,
"options": "Territory",
"options": "Country",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
@ -33,18 +34,20 @@
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-01-01 14:29:58.724652",
"modified": "2015-09-17 06:49:51.810318",
"modified_by": "Administrator",
"module": "Setup",
"name": "Applicable Territory",
"module": "Stock",
"name": "Price List Country",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"read_only": 0,
"read_only_onload": 0
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class PriceListCountry(Document):
pass

View File

@ -578,10 +578,14 @@ class StockEntry(StockController):
to_warehouse = self.pro_doc.fg_warehouse
else:
item_code = frappe.db.get_value("BOM", self.bom_no, "item")
to_warehouse = ""
to_warehouse = self.to_warehouse
item = frappe.db.get_value("Item", item_code, ["item_name",
"description", "stock_uom", "expense_account", "buying_cost_center", "name"], as_dict=1)
"description", "stock_uom", "expense_account", "buying_cost_center", "name", "default_warehouse"], as_dict=1)
if not self.production_order and not to_warehouse:
# in case of BOM
to_warehouse = item.default_warehouse
self.add_to_stock_entry_detail({
item.name: {

View File

@ -27,19 +27,28 @@
{{ _("Item Code") }}: <span itemprop="productID">{{ name }}</span></p>
<br>
<div style="min-height: 100px; margin: 10px 0;">
<div class="item-price-info" style="display: none;">
<h4 class="item-price" itemprop="price"></h4>
<div class="item-stock" itemprop="availablity"></div>
<div class="item-cart hide">
<div id="item-add-to-cart">
<button class="btn btn-primary">
<i class="icon-shopping-cart"></i> + {{ _("Add to Cart") }}</button>
<button class="btn btn-primary btn-sm">
{{ _("Add to Cart") }}</button>
</div>
<div id="item-update-cart" class="input-group col-md-4" style="display: none;
padding-left: 0px; padding-right: 0px;">
<input class="form-control" type="text">
<div class="input-group-btn">
<button class="btn btn-primary">
<i class="icon-ok"></i></button>
<div id="item-update-cart"
style="display: none;
padding-left: 0px; padding-right: 0px;
padding-top: 10px;">
<div>
<input class="form-control"
type="text" style="max-width: 140px;">
</div>
<div style="margin-top: 10px;">
<button class="btn btn-default btn-sm">
{{ _("Update") }}</button>
</div>
<div style="margin-top: 5px;">
<a href="/cart" class="text-muted small">
{{ _("View Cart") }}</a>
</div>
</div>
</div>

View File

@ -1,19 +1,8 @@
<div class="web-list-item">
<a href="/addresses?name={{ doc.name }}" no-pjax>
<div class="row">
<div class="col-sm-4">
<span class="strong">{{ doc.address_title }}</span>
</div>
<div class="col-sm-2">
{{ doc.address_type }}
</div>
<div class="col-sm-4">
{{ doc.address_line1 }}<br>
{% if doc.address_line2 %}{{ doc.address_line2 }}<br>{% endif %}
{{ doc.city }}<br>
</div>
<div class="col-sm-2">
{% if doc.state %}{{ doc.state }}, {% endif %}{{ doc.country }}
<a href="/addresses?name={{ doc.name }}" no-pjax class="no-decoration">
<h4 class="strong">{{ doc.address_title }}</h4>
<p class="text-muted small">
{{ frappe.get_doc(doc).get_display() }}
</div>
</div>
</a>

View File

@ -0,0 +1,25 @@
.cart-content {
min-height: 400px;
margin-top: 60px;
}
.cart-header, .cart-footer {
margin-bottom: 60px;
}
.cart-item-header {
padding-bottom: 10px;
margin-bottom: 10px;
border-bottom: 1px solid #d1d8dd;
}
.tax-grand-total-row {
font-size: 14px;
margin-top: 30px;
font-weight: bold;
}
.cart-addresses {
margin-top: 80px;
margin-bottom: 60px;
}

View File

@ -13,126 +13,64 @@ $.extend(shopping_cart, {
},
bind_events: function() {
// bind update button
$(document).on("click", ".item-update-cart button", function() {
var item_code = $(this).attr("data-item-code");
shopping_cart.update_cart({
item_code: item_code,
qty: $('input[data-item-code="'+item_code+'"]').val(),
with_doc: 1,
btn: this,
shopping_cart.bind_address_select();
shopping_cart.bind_place_order();
shopping_cart.bind_change_qty();
},
bind_address_select: function() {
$(".cart-addresses").find('input[data-address-name]').on("click", function() {
if($(this).prop("checked")) {
var me = this;
return frappe.call({
type: "POST",
method: "erpnext.shopping_cart.cart.update_cart_address",
args: {
address_fieldname: $(this).attr("data-fieldname"),
address_name: $(this).attr("data-address-name")
},
callback: function(r) {
if(!r.exc) {
shopping_cart.render(r.message);
var $button = $('button[data-item-code="'+item_code+'"]').addClass("btn-success");
setTimeout(function() { $button.removeClass("btn-success"); }, 1000);
$(".cart-tax-items").html(r.message.taxes);
}
}
});
} else {
return false;
}
});
},
});
});
$("#cart-add-shipping-address").on("click", function() {
window.location.href = "addresses";
});
$("#cart-add-billing-address").on("click", function() {
window.location.href = "address";
});
bind_place_order: function() {
$(".btn-place-order").on("click", function() {
shopping_cart.place_order(this);
});
},
render: function(out) {
var doc = out.doc;
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();
var no_items = $.map(doc.items || [],
function(d) { return d.item_code || null;}).length===0;
if(no_items) {
shopping_cart.show_error("Cart Empty", frappe._("Go ahead and add something to your cart."));
$("#cart-addresses").toggle(false);
return;
}
var shipping_rule_added = false;
var taxes_exist = false;
var shipping_rule_labels = $.map(out.shipping_rules || [], function(rule) { return rule[1]; });
$.each(doc.items || [], function(i, d) {
shopping_cart.render_item_row($cart_items, d);
});
$.each(doc.taxes || [], function(i, d) {
if(out.shipping_rules && out.shipping_rules.length &&
shipping_rule_labels.indexOf(d.description)!==-1) {
shipping_rule_added = true;
shopping_cart.render_tax_row($cart_taxes, d, out.shipping_rules);
} else {
shopping_cart.render_tax_row($cart_taxes, d);
}
taxes_exist = true;
});
if(out.shipping_rules && out.shipping_rules.length && !shipping_rule_added) {
shopping_cart.render_tax_row($cart_taxes, {description: "", formatted_tax_amount: ""},
out.shipping_rules);
taxes_exist = true;
}
if(taxes_exist)
$('<hr>').appendTo($cart_taxes);
shopping_cart.render_tax_row($cart_totals, {
description: "<strong>Total</strong>",
formatted_tax_amount: "<strong>" + doc.formatted_grand_total_export + "</strong>"
});
if(!(addresses && addresses.length)) {
$cart_shipping_address.html('<div class="msg-box">'+frappe._("Hey! Go ahead and add an address")+'</div>');
} else {
shopping_cart.render_address($cart_shipping_address, addresses, doc.shipping_address_name);
shopping_cart.render_address($cart_billing_address, addresses, doc.customer_address);
bind_change_qty: function() {
// bind update button
$(".cart-items").on("change", ".cart-qty", function() {
var item_code = $(this).attr("data-item-code");
frappe.freeze();
shopping_cart.update_cart({
item_code: item_code,
qty: $(this).val(),
with_items: 1,
btn: this,
callback: function(r) {
frappe.unfreeze();
if(!r.exc) {
$(".cart-items").html(r.message.items);
$(".cart-tax-items").html(r.message.taxes);
}
$(".tax-grand-total").temp_highlight();
},
});
});
render_item_row: function($cart_items, doc) {
doc.image_html = doc.website_image ?
'<div style="height: 120px; overflow: hidden;"><img src="' + doc.website_image + '" /></div>': "";
if(doc.description === doc.item_name) doc.description = "";
$(repl('<div class="row">\
<div class="col-md-9 col-sm-9">\
<div class="row">\
<div class="col-md-3">%(image_html)s</div>\
<div class="col-md-9">\
<h4><a href="%(page_name)s">%(item_name)s</a></h4>\
<p>%(description)s</p>\
</div>\
</div>\
</div>\
<div class="col-md-3 col-sm-3 text-right">\
<div class="input-group item-update-cart">\
<input type="text" placeholder="Qty" value="%(qty)s" \
data-item-code="%(item_code)s" class="text-right form-control">\
<div class="input-group-btn">\
<button class="btn btn-primary" data-item-code="%(item_code)s">\
<i class="icon-ok"></i></button>\
</div>\
</div>\
<p style="margin-top: 10px;">at %(formatted_rate)s</p>\
<small class="text-muted" style="margin-top: 10px;">= %(formatted_amount)s</small>\
</div>\
</div><hr>', doc)).appendTo($cart_items);
},
render_tax_row: function($cart_taxes, doc, shipping_rules) {
@ -182,72 +120,6 @@ $.extend(shopping_cart, {
});
},
render_address: function($address_wrapper, addresses, address_name) {
$.each(addresses, function(i, address) {
$(repl('<div class="panel panel-default"> \
<div class="panel-heading"> \
<div class="row"> \
<div class="col-md-10 address-title" \
data-address-name="%(name)s"><strong>%(name)s</strong></div> \
<div class="col-md-2"><input type="checkbox" \
data-address-name="%(name)s"></div> \
</div> \
</div> \
<div class="panel-collapse collapse" data-address-name="%(name)s"> \
<div class="panel-body">%(display)s</div> \
</div> \
</div>', address))
.css({"margin": "10px auto"})
.appendTo($address_wrapper);
});
$address_wrapper.find(".panel-heading")
.find(".address-title")
.css({"cursor": "pointer"})
.on("click", function() {
$address_wrapper.find('.panel-collapse[data-address-name="'
+$(this).attr("data-address-name")+'"]').collapse("toggle");
});
$address_wrapper.find('input[type="checkbox"]').on("click", function() {
if($(this).prop("checked")) {
var me = this;
$address_wrapper.find('input[type="checkbox"]').each(function(i, chk) {
if($(chk).attr("data-address-name")!=$(me).attr("data-address-name")) {
$(chk).prop("checked", false);
}
});
return frappe.call({
type: "POST",
method: "erpnext.shopping_cart.cart.update_cart_address",
args: {
address_fieldname: $address_wrapper.attr("data-fieldname"),
address_name: $(this).attr("data-address-name")
},
callback: function(r) {
if(!r.exc) {
shopping_cart.render(r.message);
}
}
});
} else {
return false;
}
});
$address_wrapper.find('input[type="checkbox"][data-address-name="'+ address_name +'"]')
.prop("checked", true);
$address_wrapper.find(".panel-collapse").collapse({
parent: $address_wrapper,
toggle: false
});
$address_wrapper.find('.panel-collapse[data-address-name="'+ address_name +'"]')
.collapse("show");
},
place_order: function(btn) {
return frappe.call({
type: "POST",
@ -274,24 +146,4 @@ $.extend(shopping_cart, {
$(document).ready(function() {
shopping_cart.bind_events();
return frappe.call({
type: "POST",
method: "erpnext.shopping_cart.cart.get_cart_quotation",
callback: function(r) {
$("#cart-container").removeClass("hide");
$(".progress").remove();
if(r.exc) {
if(r.exc.indexOf("WebsitePriceListMissingError")!==-1) {
shopping_cart.show_error("Configuration Error", frappe._("Price List not configured."));
} else if(r["403"]) {
shopping_cart.show_error("Not Allowed", frappe._("You need to be logged in to view your cart."));
} else {
shopping_cart.show_error("Error", frappe._("Something went wrong."));
}
} else {
shopping_cart.set_cart_count();
shopping_cart.render(r.message);
}
}
});
});

View File

@ -0,0 +1,24 @@
{% from "erpnext/templates/includes/cart/cart_macros.html"
import show_address %}
<div class="row">
<div class="col-sm-6">
<h4>{{ _("Shipping Address") }}</h4>
<div id="cart-shipping-address" class="panel-group"
data-fieldname="shipping_address_name">
{% for address in addresses %}
{{ show_address(address, doc, "shipping_address_name") }}
{% endfor %}
</div>
<a class="btn btn-default btn-sm" href="/addresses">
{{ _("Manage Addresses") }}</a>
</div>
<div class="col-sm-6">
<h4>Billing Address</h4>
<div id="cart-billing-address" class="panel-group"
data-fieldname="customer_address">
{% for address in addresses %}
{{ show_address(address, doc, "customer_address") }}
{% endfor %}
</div>
</div>
</div>

View File

@ -0,0 +1,23 @@
{% from "erpnext/templates/includes/order/order_macros.html" import item_name_and_description %}
{% for d in doc.items %}
<div class="cart-item">
<div class="row">
<div class="col-sm-8 col-xs-6" style="margin-bottom: 10px;">
{{ item_name_and_description(d) }}
</div>
<div class="col-sm-2 col-xs-3 text-right">
<span style="max-width: 50px; display: inline-block">
<input class="form-control text-right cart-qty"
value = "{{ d.get_formatted('qty') }}"
data-item-code="{{ d.item_code }}"></span>
<p class="text-muted small" style="margin-top: 10px;">
{{ _("Rate") + ': ' + d.get_formatted("rate") }}
</p>
</div>
<div class="col-sm-2 col-xs-3 text-right">
{{ d.get_formatted("amount") }}
</div>
</div>
</div>
{% endfor %}

View File

@ -0,0 +1,21 @@
{% macro show_address(address, doc, fieldname) %}
{% set selected=address.name==doc.get(fieldname) %}
<div class="panel panel-default">
<div class="panel-heading">
<div class="row">
<div class="col-sm-10 address-title"
data-address-name="{{ address.name }}">
<strong>{{ address.name }}</strong></div>
<div class="col-sm-2 text-right">
<input type="checkbox"
data-fieldname="{{ fieldname }}"
data-address-name="{{ address.name}}"
{{ "checked" if selected else "" }}></div>
</div>
</div>
<div class="panel-collapse"
data-address-name="{{ address.name }}">
<div class="panel-body text-muted small">{{ address.display }}</div>
</div>
</div>
{% endmacro %}

View File

@ -1,21 +1,15 @@
<div class="web-list-item">
<div class="row">
<div class="col-sm-6">
<a class="no-decoration" href="/issues?name={{ doc.name }}" no-pjax>
{{ doc.subject }}
<div class="row">
<div class="col-xs-8">
<span class="indicator {{ "red" if doc.status=="Open" else "darkgrey" }}">
{{ doc.name }}</span>
<span style="margin-left: 15px;">
{{ doc.subject }}</span>
</div>
<div class="col-xs-4 text-right small text-muted">
{{ frappe.format_date(doc.modified) }}
</div>
</div>
</a>
</div>
<div class="col-sm-2">
<span class="indicator {{ "red" if doc.status=="Open" else "blue" }}">
{{ doc.status }}</span>
</div>
<div class="col-sm-2">
<a class="text-muted text-right" href="/issues?name={{ doc.name }}" no-pjax>
{{ doc.name }}
</a>
</div>
<div class="col-sm-2 text-muted text-right small">
{{ frappe.format_date(doc.creation) }}
</div>
</div>
</div>

View File

@ -6,11 +6,12 @@
{% endmacro %}
{% macro product_image(website_image, css_class="") %}
<div class="product-image {% if not website_image -%} missing-image {%- endif %} {{ css_class }}">
<div class="product-image {% if not website_image -%} missing-image {%- endif %} {{ css_class }}">
{% if website_image -%}
<img src="{{ frappe.utils.quoted(website_image) | abs_url }}" class="img-responsive">
{%- else -%}
<i class="centered octicon octicon-device-camera"></i>
{%- endif %}
</div>
</div>
{% endmacro %}

View File

@ -0,0 +1,25 @@
.order-container {
margin: 50px 0px;
}
.order-items {
margin: 20px 0px;
}
.order-item-table {
margin: 0px -15px;
}
.order-item-header {
border-bottom: 1px solid #d1d8dd;
}
.order-image-col {
padding-right: 0px;
}
.order-image {
max-width: 55px;
max-height: 55px;
margin-top: -5px;
}

View File

@ -0,0 +1,15 @@
{% from "erpnext/templates/includes/macros.html" import product_image_square %}
{% macro item_name_and_description(d) %}
<div class="row">
<div class="col-xs-4 col-sm-2 order-image-col">
<div class="order-image">
{{ product_image_square(d.image) }}
</div>
</div>
<div class="col-xs-8 col-sm-10">
{{ d.item_code }}
<p class="text-muted small">{{ d.description }}</p>
</div>
</div>
{% endmacro %}

View File

@ -0,0 +1,22 @@
{% if doc.taxes %}
<div class="row tax-net-total-row">
<div class="col-xs-6 text-right">{{ _("Net Total") }}</div>
<div class="col-xs-6 text-right">
{{ doc.get_formatted("net_total") }}</div>
</div>
{% endif %}
{% for d in doc.taxes %}
<div class="row tax-row">
<div class="col-xs-6 text-right">{{ d.description }}</div>
<div class="col-xs-6 text-right">
{{ d.get_formatted("base_tax_amount") }}</div>
</div>
{% endfor %}
<div class="row tax-grand-total-row">
<div class="col-xs-6 text-right">{{ _("Grand Total") }}</div>
<div class="col-xs-6 text-right">
<span class="tax-grand-total">
{{ doc.get_formatted("grand_total") }}
</span>
</div>
</div>

View File

@ -1,7 +1,7 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
$(document).ready(function() {
frappe.ready(function() {
var item_code = $('[itemscope] [itemprop="productID"]').text().trim();
var qty = 0;
@ -12,6 +12,8 @@ $(document).ready(function() {
item_code: "{{ name }}"
},
callback: function(r) {
console.log(r.message);
$(".item-cart").toggleClass("hide", !!!r.message.price);
if(r.message && r.message.price) {
$(".item-price")
.html(r.message.price.formatted_price + " per " + r.message.uom);
@ -24,12 +26,11 @@ $(document).ready(function() {
<i class='icon-check'></i> Available (in stock)</div>");
}
$(".item-price-info").toggle(true);
if(r.message.qty) {
qty = r.message.qty;
toggle_update_cart(qty);
$("#item-update-cart input").val(qty);
toggle_update_cart(r.message.qty);
} else {
toggle_update_cart(0);
}
}
}

View File

@ -1,29 +1,22 @@
{% set doc = frappe.get_doc(doc) %}
<div class="web-list-item">
<a href="/{{ pathname }}/{{ doc.name }}" no-pjax>
<div class="row">
<div class="col-sm-6 col-xs-7">
<div class="col-sm-8 col-xs-7">
<div class="row">
<div class="col-sm-9">{{ doc.customer or doc.supplier }}</div>
<div class="col-sm-9">
<div>{{ doc.name }}</div>
<div class="small text-muted">{{ doc.items_preview }}</div>
</div>
<div class="col-sm-3">
{%- if doc.status_percent > 0 -%}
{%- if doc.status_percent % 100 == 0 -%}
<span class="indicator green">{{ doc.status_display }}</span>
{%- else -%}
<span class="indicator orange">{{ doc.status_display }}</span>
{%- endif -%}
{%- elif doc.status -%}
<span class="indicator blue">{{ doc.status }}</span>
{%- endif -%}
<span class="indicator {{ doc.indicator_color or "darkgrey" }}">
{{ doc.indicator_title or doc.status or "Submitted" }}
</span>
</div>
</div>
</div>
<div class="col-sm-2 col-xs-5 text-right">
{{ doc.get_formatted("grand_total") }}
</div>
<div class="col-sm-2 text-muted text-right">
{{ doc.name }}
</div>
<div class="col-sm-2 small text-muted text-right" title="{{ frappe.utils.format_datetime(doc.creation, "medium") }}">
{{ frappe.utils.pretty_date(doc.creation) }}</div>
</div>

View File

@ -3,52 +3,62 @@
{% block header %}<h2>{{ _("My Cart") }}</h2>{% endblock %}
{% block script %}{% include "templates/includes/cart.js" %}{% endblock %}
{% block style %}{% include "templates/includes/cart.css" %}{% endblock %}
{% block header_actions %}
{% if doc.items %}
<button class="btn btn-primary btn-place-order btn-sm"
type="button">
{{ _("Place Order") }}</button>
{% endif %}
{% endblock %}
{% block content %}
{% from "templates/includes/macros.html" import item_name_and_description %}
<div class="cart-content">
<div class="text-muted progress">{{ _("Loading") }}...</div>
<div id="cart-container" class="hide">
<p class="pull-right"><button class="btn btn-success btn-place-order" type="button">
{{ _("Place Order") }}</button></p>
<div class="clearfix"></div>
<div id="cart-error" class="alert alert-danger" style="display: none;"></div>
<hr>
<div class="row">
<div class="col-md-9 col-sm-9">
<div class="row">
<div class="col-md-9 col-md-offset-3"><h4>{{ _("Item Details") }}</h4></div>
</div>
</div>
<div class="col-md-3 col-sm-3 text-right"><h4>{{ _("Qty, Amount") }}</h4></div>
</div><hr>
<div id="cart-container">
<div id="cart-error" class="alert alert-danger"
style="display: none;"></div>
<div id="cart-items">
<div class="row cart-item-header">
<div class="col-sm-8 col-xs-6">
Items
</div>
<div class="col-sm-2 col-xs-3 text-right">
Qty
</div>
<div class="col-sm-2 col-xs-3 text-right">
Amount
</div>
</div>
{% if doc.items %}
<div class="cart-items">
{% include "templates/includes/cart/cart_items.html" %}
</div>
{% else %}
<p>{{ _("Cart is Empty") }}</p>
{% endif %}
</div>
{% if doc.items %}
<!-- taxes -->
<div class="cart-taxes row small">
<div class="col-sm-8"><!-- empty --></div>
<div class="col-sm-4 cart-tax-items">
{% include "templates/includes/order/order_taxes.html" %}
</div>
<div id="cart-taxes">
</div>
<div id="cart-totals">
</div>
<hr>
<div id="cart-addresses">
<div class="row">
<div class="col-md-6">
<h4>{{ _("Shipping Address") }}</h4>
<div id="cart-shipping-address" class="panel-group"
data-fieldname="shipping_address_name"></div>
<button class="btn btn-default" type="button" id="cart-add-shipping-address">
<span class="icon icon-list"></span> {{ _("Manage Addresses") }}</button>
<div class="cart-addresses">
{% include "templates/includes/cart/cart_address.html" %}
</div>
<div class="col-md-6">
<h4>Billing Address</h4>
<div id="cart-billing-address" class="panel-group"
data-fieldname="customer_address"></div>
<button class="btn btn-default" type="button" id="cart-add-billing-address">
<span class="icon icon-list"></span> {{ _("Manage Addresses") }}</button>
</div>
</div>
<hr>
</div>
<p class="pull-right"><button class="btn btn-success btn-place-order" type="button">
<p class="cart-footer text-right">
<button class="btn btn-primary btn-place-order btn-sm" type="button">
{{ _("Place Order") }}</button></p>
{% endif %}
</div>
</div>

View File

@ -2,7 +2,11 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
from __future__ import unicode_literals
no_cache = 1
no_sitemap = 1
import frappe
from erpnext.shopping_cart.cart import get_cart_quotation
def get_context(context):
context.update(get_cart_quotation())

View File

@ -0,0 +1,72 @@
{% block header %}
<h1>{{ doc.name }}</h1>
<!-- <h6 class="text-muted">{{ doc._title or doc.doctype }}</h6> -->
{% endblock %}
{% block style %}{% include "templates/includes/order/order.css" %}{% endblock %}
{% block content %}
{% from "erpnext/templates/includes/order/order_macros.html" import item_name_and_description %}
<div class="row">
<div class="col-xs-6">
<span class="indicator {{ doc.indicator_color or "darkgrey" }}">
{{ doc.indicator_title or doc.status or "Submitted" }}
</span>
</div>
<div class="col-xs-6 text-muted text-right h6">
{{ doc.get_formatted("transaction_date") }}
</div>
</div>
{% if doc._header %}
{{ doc._header }}
{% endif %}
<div class="order-container">
<!-- items -->
<div class="order-item-table">
<div class="row order-items order-item-header">
<div class="col-sm-8 col-xs-6 h6">
{{ _("Item") }}
</div>
<div class="col-sm-2 col-xs-3 text-right h6">
{{ _("Quantity") }}
</div>
<div class="col-sm-2 col-xs-3 text-right h6">
{{ _("Amount") }}
</div>
</div>
{% for d in doc.items %}
<div class="row order-items">
<div class="col-sm-8 col-xs-6">
{{ item_name_and_description(d) }}
</div>
<div class="col-sm-2 col-xs-3 text-right">
{{ d.qty }}
{% if d.delivered_qty != None %}
<p class="text-muted small">{{
_("Delivered: {0}").format(d.delivered_qty) }}</p>
{% endif %}
</div>
<div class="col-sm-2 col-xs-3 text-right">
{{ d.get_formatted("amount") }}
<p class="text-muted small">{{
_("Rate: {0}").format(d.get_formatted("rate")) }}</p>
</div>
</div>
{% endfor %}
</div>
<!-- taxes -->
<div class="order-taxes row small">
<div class="col-sm-8"><!-- empty --></div>
<div class="col-sm-4">
{% include "erpnext/templates/includes/order/order_taxes.html" %}
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,18 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
def get_context(context):
context.no_cache = 1
context.doc = frappe.get_doc(frappe.form_dict.doctype, frappe.form_dict.name)
if hasattr(context.doc, "set_indicator"):
context.doc.set_indicator()
context.parents = frappe.form_dict.parents
if not context.doc.has_permission("read"):
frappe.throw(_("Not Permitted"), frappe.PermissionError)

View File

@ -5,17 +5,13 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import cstr
from erpnext.shopping_cart.cart import get_lead_or_customer
from erpnext.shopping_cart.cart import get_customer
no_cache = 1
no_sitemap = 1
def get_context(context):
party = get_lead_or_customer()
if party.doctype == "Lead":
mobile_no = party.mobile_no
phone = party.phone
else:
party = get_customer()
mobile_no, phone = frappe.db.get_value("Contact", {"email_id": frappe.session.user,
"customer": party.name}, ["mobile_no", "phone"])
@ -37,4 +33,3 @@ def update_user(fullname, password=None, company_name=None, mobile_no=None, phon
frappe.local.cookie_manager.set_cookie("full_name", fullname)
return _("Updated")

View File

@ -70,6 +70,9 @@ class Address(Document):
(is_address_type, fieldname, "%s", "%s"), (self.get(fieldname), self.name))
break
def get_display(self):
return get_address_display(self.as_dict())
@frappe.whitelist()
def get_address_display(address_dict):
if not address_dict: