mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-01 19:29:10 +00:00
style: format code with black
This commit is contained in:
@@ -15,16 +15,16 @@ from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_web
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_product_filter_data(query_args=None):
|
||||
"""
|
||||
Returns filtered products and discount filters.
|
||||
:param query_args (dict): contains filters to get products list
|
||||
Returns filtered products and discount filters.
|
||||
:param query_args (dict): contains filters to get products list
|
||||
|
||||
Query Args filters:
|
||||
search (str): Search Term.
|
||||
field_filters (dict): Keys include item_group, brand, etc.
|
||||
attribute_filters(dict): Keys include Color, Size, etc.
|
||||
start (int): Offset items by
|
||||
item_group (str): Valid Item Group
|
||||
from_filters (bool): Set as True to jump to page 1
|
||||
Query Args filters:
|
||||
search (str): Search Term.
|
||||
field_filters (dict): Keys include item_group, brand, etc.
|
||||
attribute_filters(dict): Keys include Color, Size, etc.
|
||||
start (int): Offset items by
|
||||
item_group (str): Valid Item Group
|
||||
from_filters (bool): Set as True to jump to page 1
|
||||
"""
|
||||
if isinstance(query_args, str):
|
||||
query_args = json.loads(query_args)
|
||||
@@ -53,11 +53,7 @@ def get_product_filter_data(query_args=None):
|
||||
engine = ProductQuery()
|
||||
try:
|
||||
result = engine.query(
|
||||
attribute_filters,
|
||||
field_filters,
|
||||
search_term=search,
|
||||
start=start,
|
||||
item_group=item_group
|
||||
attribute_filters, field_filters, search_term=search, start=start, item_group=item_group
|
||||
)
|
||||
except Exception:
|
||||
traceback = frappe.get_traceback()
|
||||
@@ -77,9 +73,10 @@ def get_product_filter_data(query_args=None):
|
||||
"filters": filters,
|
||||
"settings": engine.settings,
|
||||
"sub_categories": sub_categories,
|
||||
"items_count": result["items_count"]
|
||||
"items_count": result["items_count"],
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_guest_redirect_on_action():
|
||||
return frappe.db.get_single_value("E Commerce Settings", "redirect_on_action")
|
||||
return frappe.db.get_single_value("E Commerce Settings", "redirect_on_action")
|
||||
|
||||
@@ -14,7 +14,9 @@ from erpnext.e_commerce.redisearch_utils import (
|
||||
)
|
||||
|
||||
|
||||
class ShoppingCartSetupError(frappe.ValidationError): pass
|
||||
class ShoppingCartSetupError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class ECommerceSettings(Document):
|
||||
def onload(self):
|
||||
@@ -37,11 +39,17 @@ class ECommerceSettings(Document):
|
||||
return
|
||||
|
||||
item_meta = frappe.get_meta("Item")
|
||||
valid_fields = [df.fieldname for df in item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"]]
|
||||
valid_fields = [
|
||||
df.fieldname for df in item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"]
|
||||
]
|
||||
|
||||
for f in self.filter_fields:
|
||||
if f.fieldname not in valid_fields:
|
||||
frappe.throw(_("Filter Fields Row #{0}: Fieldname <b>{1}</b> must be of type 'Link' or 'Table MultiSelect'").format(f.idx, f.fieldname))
|
||||
frappe.throw(
|
||||
_(
|
||||
"Filter Fields Row #{0}: Fieldname <b>{1}</b> must be of type 'Link' or 'Table MultiSelect'"
|
||||
).format(f.idx, f.fieldname)
|
||||
)
|
||||
|
||||
def validate_attribute_filters(self):
|
||||
if not (self.enable_attribute_filters and self.filter_attributes):
|
||||
@@ -58,8 +66,8 @@ class ECommerceSettings(Document):
|
||||
if not self.search_index_fields:
|
||||
return
|
||||
|
||||
fields = self.search_index_fields.replace(' ', '')
|
||||
fields = unique(fields.strip(',').split(',')) # Remove extra ',' and remove duplicates
|
||||
fields = self.search_index_fields.replace(" ", "")
|
||||
fields = unique(fields.strip(",").split(",")) # Remove extra ',' and remove duplicates
|
||||
|
||||
# All fields should be indexable
|
||||
allowed_indexable_fields = get_indexable_web_fields()
|
||||
@@ -70,18 +78,22 @@ class ECommerceSettings(Document):
|
||||
invalid_fields = comma_and(invalid_fields)
|
||||
|
||||
if num_invalid_fields > 1:
|
||||
frappe.throw(_("{0} are not valid options for Search Index Field.").format(frappe.bold(invalid_fields)))
|
||||
frappe.throw(
|
||||
_("{0} are not valid options for Search Index Field.").format(frappe.bold(invalid_fields))
|
||||
)
|
||||
else:
|
||||
frappe.throw(_("{0} is not a valid option for Search Index Field.").format(frappe.bold(invalid_fields)))
|
||||
frappe.throw(
|
||||
_("{0} is not a valid option for Search Index Field.").format(frappe.bold(invalid_fields))
|
||||
)
|
||||
|
||||
self.search_index_fields = ','.join(fields)
|
||||
self.search_index_fields = ",".join(fields)
|
||||
|
||||
def validate_price_list_exchange_rate(self):
|
||||
"Check if exchange rate exists for Price List currency (to Company's currency)."
|
||||
from erpnext.setup.utils import get_exchange_rate
|
||||
|
||||
if not self.enabled or not self.company or not self.price_list:
|
||||
return # this function is also called from hooks, check values again
|
||||
return # this function is also called from hooks, check values again
|
||||
|
||||
company_currency = frappe.get_cached_value("Company", self.company, "default_currency")
|
||||
price_list_currency = frappe.db.get_value("Price List", self.price_list, "currency")
|
||||
@@ -105,12 +117,13 @@ class ECommerceSettings(Document):
|
||||
frappe.throw(_(msg), title=_("Missing"), exc=ShoppingCartSetupError)
|
||||
|
||||
def validate_tax_rule(self):
|
||||
if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart" : 1}, "name"):
|
||||
if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart": 1}, "name"):
|
||||
frappe.throw(frappe._("Set Tax Rule for shopping cart"), ShoppingCartSetupError)
|
||||
|
||||
def get_tax_master(self, billing_territory):
|
||||
tax_master = self.get_name_from_territory(billing_territory, "sales_taxes_and_charges_masters",
|
||||
"sales_taxes_and_charges_master")
|
||||
tax_master = self.get_name_from_territory(
|
||||
billing_territory, "sales_taxes_and_charges_masters", "sales_taxes_and_charges_master"
|
||||
)
|
||||
return tax_master and tax_master[0] or None
|
||||
|
||||
def get_shipping_rules(self, shipping_territory):
|
||||
@@ -127,25 +140,33 @@ class ECommerceSettings(Document):
|
||||
if not (new_fields == old_fields):
|
||||
create_website_items_index()
|
||||
|
||||
|
||||
def validate_cart_settings(doc=None, method=None):
|
||||
frappe.get_doc("E Commerce Settings", "E Commerce Settings").run_method("validate")
|
||||
|
||||
|
||||
def get_shopping_cart_settings():
|
||||
if not getattr(frappe.local, "shopping_cart_settings", None):
|
||||
frappe.local.shopping_cart_settings = frappe.get_doc("E Commerce Settings", "E Commerce Settings")
|
||||
frappe.local.shopping_cart_settings = frappe.get_doc(
|
||||
"E Commerce Settings", "E Commerce Settings"
|
||||
)
|
||||
|
||||
return frappe.local.shopping_cart_settings
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def is_cart_enabled():
|
||||
return get_shopping_cart_settings().enabled
|
||||
|
||||
|
||||
def show_quantity_in_website():
|
||||
return get_shopping_cart_settings().show_quantity_in_website
|
||||
|
||||
|
||||
def check_shopping_cart_enabled():
|
||||
if not get_shopping_cart_settings().enabled:
|
||||
frappe.throw(_("You need to enable Shopping Cart"), ShoppingCartSetupError)
|
||||
|
||||
|
||||
def show_attachments():
|
||||
return get_shopping_cart_settings().show_attachments
|
||||
|
||||
@@ -15,8 +15,7 @@ class TestECommerceSettings(unittest.TestCase):
|
||||
frappe.db.sql("""delete from `tabSingles` where doctype="Shipping Cart Settings" """)
|
||||
|
||||
def get_cart_settings(self):
|
||||
return frappe.get_doc({"doctype": "E Commerce Settings",
|
||||
"company": "_Test Company"})
|
||||
return frappe.get_doc({"doctype": "E Commerce Settings", "company": "_Test Company"})
|
||||
|
||||
# NOTE: Exchangrate API has all enabled currencies that ERPNext supports.
|
||||
# We aren't checking just currency exchange record anymore
|
||||
@@ -41,7 +40,7 @@ class TestECommerceSettings(unittest.TestCase):
|
||||
|
||||
def test_tax_rule_validation(self):
|
||||
frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0")
|
||||
frappe.db.commit() # nosemgrep
|
||||
frappe.db.commit() # nosemgrep
|
||||
|
||||
cart_settings = self.get_cart_settings()
|
||||
cart_settings.enabled = 1
|
||||
@@ -50,6 +49,7 @@ class TestECommerceSettings(unittest.TestCase):
|
||||
|
||||
frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 1")
|
||||
|
||||
|
||||
def setup_e_commerce_settings(values_dict):
|
||||
"Accepts a dict of values that updates E Commerce Settings."
|
||||
if not values_dict:
|
||||
@@ -59,4 +59,5 @@ def setup_e_commerce_settings(values_dict):
|
||||
doc.update(values_dict)
|
||||
doc.save()
|
||||
|
||||
|
||||
test_dependencies = ["Tax Rule"]
|
||||
|
||||
@@ -18,6 +18,7 @@ from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
|
||||
class UnverifiedReviewer(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class ItemReview(Document):
|
||||
def after_insert(self):
|
||||
# regenerate cache on review creation
|
||||
@@ -54,12 +55,13 @@ def get_item_reviews(web_item, start=0, end=10, data=None):
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_queried_reviews(web_item, start=0, end=10, data=None):
|
||||
"""
|
||||
Query Website Item wise reviews and cache if needed.
|
||||
Cache stores only first page of reviews i.e. 10 reviews maximum.
|
||||
Returns:
|
||||
dict: Containing reviews, average ratings, % of reviews per rating and total reviews.
|
||||
Query Website Item wise reviews and cache if needed.
|
||||
Cache stores only first page of reviews i.e. 10 reviews maximum.
|
||||
Returns:
|
||||
dict: Containing reviews, average ratings, % of reviews per rating and total reviews.
|
||||
"""
|
||||
if not data:
|
||||
data = frappe._dict()
|
||||
@@ -69,13 +71,13 @@ def get_queried_reviews(web_item, start=0, end=10, data=None):
|
||||
filters={"website_item": web_item},
|
||||
fields=["*"],
|
||||
limit_start=start,
|
||||
limit_page_length=end
|
||||
limit_page_length=end,
|
||||
)
|
||||
|
||||
rating_data = frappe.db.get_all(
|
||||
"Item Review",
|
||||
filters={"website_item": web_item},
|
||||
fields=["avg(rating) as average, count(*) as total"]
|
||||
fields=["avg(rating) as average, count(*) as total"],
|
||||
)[0]
|
||||
|
||||
data.average_rating = flt(rating_data.average, 1)
|
||||
@@ -83,11 +85,9 @@ def get_queried_reviews(web_item, start=0, end=10, data=None):
|
||||
|
||||
# get % of reviews per rating
|
||||
reviews_per_rating = []
|
||||
for i in range(1,6):
|
||||
for i in range(1, 6):
|
||||
count = frappe.db.get_all(
|
||||
"Item Review",
|
||||
filters={"website_item": web_item, "rating": i},
|
||||
fields=["count(*) as count"]
|
||||
"Item Review", filters={"website_item": web_item, "rating": i}, fields=["count(*) as count"]
|
||||
)[0].count
|
||||
|
||||
percent = flt((count / rating_data.total or 1) * 100, 0) if count else 0
|
||||
@@ -98,40 +98,45 @@ def get_queried_reviews(web_item, start=0, end=10, data=None):
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def set_reviews_in_cache(web_item, reviews_dict):
|
||||
frappe.cache().hset("item_reviews", web_item, reviews_dict)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_item_review(web_item, title, rating, comment=None):
|
||||
""" Add an Item Review by a user if non-existent. """
|
||||
"""Add an Item Review by a user if non-existent."""
|
||||
if frappe.session.user == "Guest":
|
||||
# guest user should not reach here ideally in the case they do via an API, throw error
|
||||
frappe.throw(_("You are not verified to write a review yet."), exc=UnverifiedReviewer)
|
||||
|
||||
if not frappe.db.exists("Item Review", {"user": frappe.session.user, "website_item": web_item}):
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Item Review",
|
||||
"user": frappe.session.user,
|
||||
"customer": get_customer(),
|
||||
"website_item": web_item,
|
||||
"item": frappe.db.get_value("Website Item", web_item, "item_code"),
|
||||
"review_title": title,
|
||||
"rating": rating,
|
||||
"comment": comment
|
||||
})
|
||||
doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item Review",
|
||||
"user": frappe.session.user,
|
||||
"customer": get_customer(),
|
||||
"website_item": web_item,
|
||||
"item": frappe.db.get_value("Website Item", web_item, "item_code"),
|
||||
"review_title": title,
|
||||
"rating": rating,
|
||||
"comment": comment,
|
||||
}
|
||||
)
|
||||
doc.published_on = datetime.today().strftime("%d %B %Y")
|
||||
doc.insert()
|
||||
|
||||
|
||||
def get_customer(silent=False):
|
||||
"""
|
||||
silent: Return customer if exists else return nothing. Dont throw error.
|
||||
silent: Return customer if exists else return nothing. Dont throw error.
|
||||
"""
|
||||
user = frappe.session.user
|
||||
contact_name = get_contact_name(user)
|
||||
customer = None
|
||||
|
||||
if contact_name:
|
||||
contact = frappe.get_doc('Contact', contact_name)
|
||||
contact = frappe.get_doc("Contact", contact_name)
|
||||
for link in contact.links:
|
||||
if link.link_doctype == "Customer":
|
||||
customer = link.link_name
|
||||
@@ -143,5 +148,6 @@ def get_customer(silent=False):
|
||||
return None
|
||||
else:
|
||||
# should not reach here unless via an API
|
||||
frappe.throw(_("You are not a verified customer yet. Please contact us to proceed."),
|
||||
exc=UnverifiedReviewer)
|
||||
frappe.throw(
|
||||
_("You are not a verified customer yet. Please contact us to proceed."), exc=UnverifiedReviewer
|
||||
)
|
||||
|
||||
@@ -19,17 +19,23 @@ from erpnext.stock.doctype.item.item import DataValidationError
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
WEBITEM_DESK_TESTS = ("test_website_item_desk_item_sync", "test_publish_variant_and_template")
|
||||
WEBITEM_PRICE_TESTS = ('test_website_item_price_for_logged_in_user', 'test_website_item_price_for_guest_user')
|
||||
WEBITEM_PRICE_TESTS = (
|
||||
"test_website_item_price_for_logged_in_user",
|
||||
"test_website_item_price_for_guest_user",
|
||||
)
|
||||
|
||||
|
||||
class TestWebsiteItem(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
setup_e_commerce_settings({
|
||||
"company": "_Test Company",
|
||||
"enabled": 1,
|
||||
"default_customer_group": "_Test Customer Group",
|
||||
"price_list": "_Test Price List India"
|
||||
})
|
||||
setup_e_commerce_settings(
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"enabled": 1,
|
||||
"default_customer_group": "_Test Customer Group",
|
||||
"price_list": "_Test Price List India",
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
@@ -37,40 +43,42 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
if self._testMethodName in WEBITEM_DESK_TESTS:
|
||||
make_item("Test Web Item", {
|
||||
"has_variant": 1,
|
||||
"variant_based_on": "Item Attribute",
|
||||
"attributes": [
|
||||
{
|
||||
"attribute": "Test Size"
|
||||
}
|
||||
]
|
||||
})
|
||||
make_item(
|
||||
"Test Web Item",
|
||||
{
|
||||
"has_variant": 1,
|
||||
"variant_based_on": "Item Attribute",
|
||||
"attributes": [{"attribute": "Test Size"}],
|
||||
},
|
||||
)
|
||||
elif self._testMethodName in WEBITEM_PRICE_TESTS:
|
||||
create_user_and_customer_if_not_exists("test_contact_customer@example.com", "_Test Contact For _Test Customer")
|
||||
create_user_and_customer_if_not_exists(
|
||||
"test_contact_customer@example.com", "_Test Contact For _Test Customer"
|
||||
)
|
||||
create_regular_web_item()
|
||||
make_web_item_price(item_code="Test Mobile Phone")
|
||||
|
||||
# Note: When testing web item pricing rule logged-in user pricing rule must differ from guest pricing rule or test will falsely pass.
|
||||
# This is because make_web_pricing_rule creates a pricing rule "selling": 1, without specifying "applicable_for". Therefor,
|
||||
# when testing for logged-in user the test will get the previous pricing rule because "selling" is still true.
|
||||
# This is because make_web_pricing_rule creates a pricing rule "selling": 1, without specifying "applicable_for". Therefor,
|
||||
# when testing for logged-in user the test will get the previous pricing rule because "selling" is still true.
|
||||
#
|
||||
# I've attempted to mitigate this by setting applicable_for=Customer, and customer=Guest however, this only results in PermissionError failing the test.
|
||||
make_web_pricing_rule(
|
||||
title="Test Pricing Rule for Test Mobile Phone",
|
||||
item_code="Test Mobile Phone",
|
||||
selling=1)
|
||||
title="Test Pricing Rule for Test Mobile Phone", item_code="Test Mobile Phone", selling=1
|
||||
)
|
||||
make_web_pricing_rule(
|
||||
title="Test Pricing Rule for Test Mobile Phone (Customer)",
|
||||
item_code="Test Mobile Phone",
|
||||
selling=1,
|
||||
discount_percentage="25",
|
||||
applicable_for="Customer",
|
||||
customer="_Test Customer")
|
||||
customer="_Test Customer",
|
||||
)
|
||||
|
||||
def test_index_creation(self):
|
||||
"Check if index is getting created in db."
|
||||
from erpnext.e_commerce.doctype.website_item.website_item import on_doctype_update
|
||||
|
||||
on_doctype_update()
|
||||
|
||||
indices = frappe.db.sql("show index from `tabWebsite Item`", as_dict=1)
|
||||
@@ -84,7 +92,7 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
def test_website_item_desk_item_sync(self):
|
||||
"Check creation/updation/deletion of Website Item and its impact on Item master."
|
||||
web_item = None
|
||||
item = make_item("Test Web Item") # will return item if exists
|
||||
item = make_item("Test Web Item") # will return item if exists
|
||||
try:
|
||||
web_item = make_website_item(item, save=False)
|
||||
web_item.save()
|
||||
@@ -97,7 +105,7 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
|
||||
item.reload()
|
||||
self.assertEqual(web_item.published, 1)
|
||||
self.assertEqual(item.published_in_website, 1) # check if item was back updated
|
||||
self.assertEqual(item.published_in_website, 1) # check if item was back updated
|
||||
self.assertEqual(web_item.item_group, item.item_group)
|
||||
|
||||
# check if changing item data changes it in website item
|
||||
@@ -170,9 +178,12 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
from erpnext.setup.doctype.item_group.item_group import get_parent_item_groups
|
||||
|
||||
item_code = "Test Breadcrumb Item"
|
||||
item = make_item(item_code, {
|
||||
"item_group": "_Test Item Group B - 1",
|
||||
})
|
||||
item = make_item(
|
||||
item_code,
|
||||
{
|
||||
"item_group": "_Test Item Group B - 1",
|
||||
},
|
||||
)
|
||||
|
||||
if not frappe.db.exists("Website Item", {"item_code": item_code}):
|
||||
web_item = make_website_item(item, save=False)
|
||||
@@ -187,7 +198,7 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
|
||||
self.assertEqual(breadcrumbs[0]["name"], "Home")
|
||||
self.assertEqual(breadcrumbs[1]["name"], "Shop by Category")
|
||||
self.assertEqual(breadcrumbs[2]["name"], "_Test Item Group B") # parent item group
|
||||
self.assertEqual(breadcrumbs[2]["name"], "_Test Item Group B") # parent item group
|
||||
self.assertEqual(breadcrumbs[3]["name"], "_Test Item Group B - 1")
|
||||
|
||||
# tear down
|
||||
@@ -236,10 +247,7 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
item_code = "Test Mobile Phone"
|
||||
|
||||
# show price for guest user in e commerce settings
|
||||
setup_e_commerce_settings({
|
||||
"show_price": 1,
|
||||
"hide_price_for_guest": 0
|
||||
})
|
||||
setup_e_commerce_settings({"show_price": 1, "hide_price_for_guest": 0})
|
||||
|
||||
# price and pricing rule added via setUp
|
||||
|
||||
@@ -270,11 +278,11 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
|
||||
def test_website_item_stock_when_out_of_stock(self):
|
||||
"""
|
||||
Check if stock details are fetched correctly for empty inventory when:
|
||||
1) Showing stock availability enabled:
|
||||
- Warehouse unset
|
||||
- Warehouse set
|
||||
2) Showing stock availability disabled
|
||||
Check if stock details are fetched correctly for empty inventory when:
|
||||
1) Showing stock availability enabled:
|
||||
- Warehouse unset
|
||||
- Warehouse set
|
||||
2) Showing stock availability disabled
|
||||
"""
|
||||
item_code = "Test Mobile Phone"
|
||||
create_regular_web_item()
|
||||
@@ -288,7 +296,9 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
self.assertFalse(bool(data.product_info["stock_qty"]))
|
||||
|
||||
# set warehouse
|
||||
frappe.db.set_value("Website Item", {"item_code": item_code}, "website_warehouse", "_Test Warehouse - _TC")
|
||||
frappe.db.set_value(
|
||||
"Website Item", {"item_code": item_code}, "website_warehouse", "_Test Warehouse - _TC"
|
||||
)
|
||||
|
||||
# check if stock details are fetched and item not in stock with warehouse set
|
||||
data = get_product_info_for_website(item_code, skip_quotation_creation=True)
|
||||
@@ -310,11 +320,11 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
|
||||
def test_website_item_stock_when_in_stock(self):
|
||||
"""
|
||||
Check if stock details are fetched correctly for available inventory when:
|
||||
1) Showing stock availability enabled:
|
||||
- Warehouse set
|
||||
- Warehouse unset
|
||||
2) Showing stock availability disabled
|
||||
Check if stock details are fetched correctly for available inventory when:
|
||||
1) Showing stock availability enabled:
|
||||
- Warehouse set
|
||||
- Warehouse unset
|
||||
2) Showing stock availability disabled
|
||||
"""
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
@@ -324,10 +334,14 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
frappe.local.shopping_cart_settings = None
|
||||
|
||||
# set warehouse
|
||||
frappe.db.set_value("Website Item", {"item_code": item_code}, "website_warehouse", "_Test Warehouse - _TC")
|
||||
frappe.db.set_value(
|
||||
"Website Item", {"item_code": item_code}, "website_warehouse", "_Test Warehouse - _TC"
|
||||
)
|
||||
|
||||
# stock up item
|
||||
stock_entry = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=2, rate=100)
|
||||
stock_entry = make_stock_entry(
|
||||
item_code=item_code, target="_Test Warehouse - _TC", qty=2, rate=100
|
||||
)
|
||||
|
||||
# check if stock details are fetched and item is in stock with warehouse set
|
||||
data = get_product_info_for_website(item_code, skip_quotation_creation=True)
|
||||
@@ -362,10 +376,7 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
item_code = "Test Mobile Phone"
|
||||
web_item = create_regular_web_item(item_code)
|
||||
|
||||
setup_e_commerce_settings({
|
||||
"enable_recommendations": 1,
|
||||
"show_price": 1
|
||||
})
|
||||
setup_e_commerce_settings({"enable_recommendations": 1, "show_price": 1})
|
||||
|
||||
# create recommended web item and price for it
|
||||
recommended_web_item = create_regular_web_item("Test Mobile Phone 1")
|
||||
@@ -383,7 +394,7 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
self.assertEqual(len(recommended_items), 1)
|
||||
recomm_item = recommended_items[0]
|
||||
self.assertEqual(recomm_item.get("website_item_name"), "Test Mobile Phone 1")
|
||||
self.assertTrue(bool(recomm_item.get("price_info"))) # price fetched
|
||||
self.assertTrue(bool(recomm_item.get("price_info"))) # price fetched
|
||||
|
||||
price_info = recomm_item.get("price_info")
|
||||
self.assertEqual(price_info.get("price_list_rate"), 1000)
|
||||
@@ -397,7 +408,7 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
recommended_items = web_item.get_recommended_items(e_commerce_settings)
|
||||
|
||||
self.assertEqual(len(recommended_items), 1)
|
||||
self.assertFalse(bool(recommended_items[0].get("price_info"))) # price not fetched
|
||||
self.assertFalse(bool(recommended_items[0].get("price_info"))) # price not fetched
|
||||
|
||||
# tear down
|
||||
web_item.delete()
|
||||
@@ -410,11 +421,9 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
web_item = create_regular_web_item(item_code)
|
||||
|
||||
# price visible to guests
|
||||
setup_e_commerce_settings({
|
||||
"enable_recommendations": 1,
|
||||
"show_price": 1,
|
||||
"hide_price_for_guest": 0
|
||||
})
|
||||
setup_e_commerce_settings(
|
||||
{"enable_recommendations": 1, "show_price": 1, "hide_price_for_guest": 0}
|
||||
)
|
||||
|
||||
# create recommended web item and price for it
|
||||
recommended_web_item = create_regular_web_item("Test Mobile Phone 1")
|
||||
@@ -432,7 +441,7 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
|
||||
# test results if show price is enabled
|
||||
self.assertEqual(len(recommended_items), 1)
|
||||
self.assertTrue(bool(recommended_items[0].get("price_info"))) # price fetched
|
||||
self.assertTrue(bool(recommended_items[0].get("price_info"))) # price fetched
|
||||
|
||||
# price hidden from guests
|
||||
frappe.set_user("Administrator")
|
||||
@@ -445,7 +454,7 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
|
||||
# test results if show price is enabled
|
||||
self.assertEqual(len(recommended_items), 1)
|
||||
self.assertFalse(bool(recommended_items[0].get("price_info"))) # price fetched
|
||||
self.assertFalse(bool(recommended_items[0].get("price_info"))) # price fetched
|
||||
|
||||
# tear down
|
||||
frappe.set_user("Administrator")
|
||||
@@ -453,6 +462,7 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
recommended_web_item.delete()
|
||||
frappe.get_cached_doc("Item", "Test Mobile Phone 1").delete()
|
||||
|
||||
|
||||
def create_regular_web_item(item_code=None, item_args=None, web_args=None):
|
||||
"Create Regular Item and Website Item."
|
||||
item_code = item_code or "Test Mobile Phone"
|
||||
@@ -468,47 +478,51 @@ def create_regular_web_item(item_code=None, item_args=None, web_args=None):
|
||||
|
||||
return web_item
|
||||
|
||||
|
||||
def make_web_item_price(**kwargs):
|
||||
item_code = kwargs.get("item_code")
|
||||
if not item_code:
|
||||
return
|
||||
|
||||
if not frappe.db.exists("Item Price", {"item_code": item_code}):
|
||||
item_price = frappe.get_doc({
|
||||
"doctype": "Item Price",
|
||||
"item_code": item_code,
|
||||
"price_list": kwargs.get("price_list") or "_Test Price List India",
|
||||
"price_list_rate": kwargs.get("price_list_rate") or 1000
|
||||
})
|
||||
item_price = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item Price",
|
||||
"item_code": item_code,
|
||||
"price_list": kwargs.get("price_list") or "_Test Price List India",
|
||||
"price_list_rate": kwargs.get("price_list_rate") or 1000,
|
||||
}
|
||||
)
|
||||
item_price.insert()
|
||||
else:
|
||||
item_price = frappe.get_cached_doc("Item Price", {"item_code": item_code})
|
||||
|
||||
return item_price
|
||||
|
||||
|
||||
def make_web_pricing_rule(**kwargs):
|
||||
title = kwargs.get("title")
|
||||
if not title:
|
||||
return
|
||||
|
||||
if not frappe.db.exists("Pricing Rule", title):
|
||||
pricing_rule = frappe.get_doc({
|
||||
"doctype": "Pricing Rule",
|
||||
"title": title,
|
||||
"apply_on": kwargs.get("apply_on") or "Item Code",
|
||||
"items": [{
|
||||
"item_code": kwargs.get("item_code")
|
||||
}],
|
||||
"selling": kwargs.get("selling") or 0,
|
||||
"buying": kwargs.get("buying") or 0,
|
||||
"rate_or_discount": kwargs.get("rate_or_discount") or "Discount Percentage",
|
||||
"discount_percentage": kwargs.get("discount_percentage") or 10,
|
||||
"company": kwargs.get("company") or "_Test Company",
|
||||
"currency": kwargs.get("currency") or "INR",
|
||||
"for_price_list": kwargs.get("price_list") or "_Test Price List India",
|
||||
"applicable_for": kwargs.get("applicable_for") or "",
|
||||
"customer": kwargs.get("customer") or "",
|
||||
})
|
||||
pricing_rule = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Pricing Rule",
|
||||
"title": title,
|
||||
"apply_on": kwargs.get("apply_on") or "Item Code",
|
||||
"items": [{"item_code": kwargs.get("item_code")}],
|
||||
"selling": kwargs.get("selling") or 0,
|
||||
"buying": kwargs.get("buying") or 0,
|
||||
"rate_or_discount": kwargs.get("rate_or_discount") or "Discount Percentage",
|
||||
"discount_percentage": kwargs.get("discount_percentage") or 10,
|
||||
"company": kwargs.get("company") or "_Test Company",
|
||||
"currency": kwargs.get("currency") or "INR",
|
||||
"for_price_list": kwargs.get("price_list") or "_Test Price List India",
|
||||
"applicable_for": kwargs.get("applicable_for") or "",
|
||||
"customer": kwargs.get("customer") or "",
|
||||
}
|
||||
)
|
||||
pricing_rule.insert()
|
||||
else:
|
||||
pricing_rule = frappe.get_doc("Pricing Rule", {"title": title})
|
||||
@@ -516,23 +530,26 @@ def make_web_pricing_rule(**kwargs):
|
||||
return pricing_rule
|
||||
|
||||
|
||||
def create_user_and_customer_if_not_exists(email, first_name = None):
|
||||
def create_user_and_customer_if_not_exists(email, first_name=None):
|
||||
if frappe.db.exists("User", email):
|
||||
return
|
||||
|
||||
frappe.get_doc({
|
||||
"doctype": "User",
|
||||
"user_type": "Website User",
|
||||
"email": email,
|
||||
"send_welcome_email": 0,
|
||||
"first_name": first_name or email.split("@")[0]
|
||||
}).insert(ignore_permissions=True)
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "User",
|
||||
"user_type": "Website User",
|
||||
"email": email,
|
||||
"send_welcome_email": 0,
|
||||
"first_name": first_name or email.split("@")[0],
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
contact = frappe.get_last_doc("Contact", filters={"email_id": email})
|
||||
link = contact.append('links', {})
|
||||
link = contact.append("links", {})
|
||||
link.link_doctype = "Customer"
|
||||
link.link_name = "_Test Customer"
|
||||
link.link_title = "_Test Customer"
|
||||
contact.save()
|
||||
|
||||
|
||||
test_dependencies = ["Price List", "Item Price", "Customer", "Contact", "Item"]
|
||||
|
||||
@@ -29,7 +29,7 @@ class WebsiteItem(WebsiteGenerator):
|
||||
page_title_field="web_item_name",
|
||||
condition_field="published",
|
||||
template="templates/generators/item/item.html",
|
||||
no_cache=1
|
||||
no_cache=1,
|
||||
)
|
||||
|
||||
def autoname(self):
|
||||
@@ -88,14 +88,17 @@ class WebsiteItem(WebsiteGenerator):
|
||||
|
||||
def publish_unpublish_desk_item(self, publish=True):
|
||||
if frappe.db.get_value("Item", self.item_code, "published_in_website") and publish:
|
||||
return # if already published don't publish again
|
||||
return # if already published don't publish again
|
||||
frappe.db.set_value("Item", self.item_code, "published_in_website", publish)
|
||||
|
||||
def make_route(self):
|
||||
"""Called from set_route in WebsiteGenerator."""
|
||||
if not self.route:
|
||||
return cstr(frappe.db.get_value('Item Group', self.item_group,
|
||||
'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5))
|
||||
return (
|
||||
cstr(frappe.db.get_value("Item Group", self.item_group, "route"))
|
||||
+ "/"
|
||||
+ self.scrub((self.item_name if self.item_name else self.item_code) + "-" + random_string(5))
|
||||
)
|
||||
|
||||
def update_template_item(self):
|
||||
"""Publish Template Item if Variant is published."""
|
||||
@@ -124,12 +127,10 @@ class WebsiteItem(WebsiteGenerator):
|
||||
# find if website image url exists as public
|
||||
file_doc = frappe.get_all(
|
||||
"File",
|
||||
filters={
|
||||
"file_url": self.website_image
|
||||
},
|
||||
filters={"file_url": self.website_image},
|
||||
fields=["name", "is_private"],
|
||||
order_by="is_private asc",
|
||||
limit_page_length=1
|
||||
limit_page_length=1,
|
||||
)
|
||||
|
||||
if file_doc:
|
||||
@@ -137,7 +138,11 @@ class WebsiteItem(WebsiteGenerator):
|
||||
|
||||
if not file_doc:
|
||||
if not auto_set_website_image:
|
||||
frappe.msgprint(_("Website Image {0} attached to Item {1} cannot be found").format(self.website_image, self.name))
|
||||
frappe.msgprint(
|
||||
_("Website Image {0} attached to Item {1} cannot be found").format(
|
||||
self.website_image, self.name
|
||||
)
|
||||
)
|
||||
|
||||
self.website_image = None
|
||||
|
||||
@@ -154,18 +159,23 @@ class WebsiteItem(WebsiteGenerator):
|
||||
|
||||
import requests.exceptions
|
||||
|
||||
if not self.is_new() and self.website_image != frappe.db.get_value(self.doctype, self.name, "website_image"):
|
||||
if not self.is_new() and self.website_image != frappe.db.get_value(
|
||||
self.doctype, self.name, "website_image"
|
||||
):
|
||||
self.thumbnail = None
|
||||
|
||||
if self.website_image and not self.thumbnail:
|
||||
file_doc = None
|
||||
|
||||
try:
|
||||
file_doc = frappe.get_doc("File", {
|
||||
"file_url": self.website_image,
|
||||
"attached_to_doctype": "Website Item",
|
||||
"attached_to_name": self.name
|
||||
})
|
||||
file_doc = frappe.get_doc(
|
||||
"File",
|
||||
{
|
||||
"file_url": self.website_image,
|
||||
"attached_to_doctype": "Website Item",
|
||||
"attached_to_name": self.name,
|
||||
},
|
||||
)
|
||||
except frappe.DoesNotExistError:
|
||||
pass
|
||||
# cleanup
|
||||
@@ -177,18 +187,21 @@ class WebsiteItem(WebsiteGenerator):
|
||||
|
||||
except requests.exceptions.SSLError:
|
||||
frappe.msgprint(
|
||||
_("Warning: Invalid SSL certificate on attachment {0}").format(self.website_image))
|
||||
_("Warning: Invalid SSL certificate on attachment {0}").format(self.website_image)
|
||||
)
|
||||
self.website_image = None
|
||||
|
||||
# for CSV import
|
||||
if self.website_image and not file_doc:
|
||||
try:
|
||||
file_doc = frappe.get_doc({
|
||||
"doctype": "File",
|
||||
"file_url": self.website_image,
|
||||
"attached_to_doctype": "Website Item",
|
||||
"attached_to_name": self.name
|
||||
}).save()
|
||||
file_doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "File",
|
||||
"file_url": self.website_image,
|
||||
"attached_to_doctype": "Website Item",
|
||||
"attached_to_name": self.name,
|
||||
}
|
||||
).save()
|
||||
|
||||
except IOError:
|
||||
self.website_image = None
|
||||
@@ -204,11 +217,11 @@ class WebsiteItem(WebsiteGenerator):
|
||||
context.search_link = "/search"
|
||||
context.body_class = "product-page"
|
||||
|
||||
context.parents = get_parent_item_groups(self.item_group, from_item=True) # breadcumbs
|
||||
context.parents = get_parent_item_groups(self.item_group, from_item=True) # breadcumbs
|
||||
self.attributes = frappe.get_all(
|
||||
"Item Variant Attribute",
|
||||
fields=["attribute", "attribute_value"],
|
||||
filters={"parent": self.item_code}
|
||||
filters={"parent": self.item_code},
|
||||
)
|
||||
|
||||
if self.slideshow:
|
||||
@@ -227,7 +240,9 @@ class WebsiteItem(WebsiteGenerator):
|
||||
context.reviews = context.reviews[:4]
|
||||
|
||||
context.wished = False
|
||||
if frappe.db.exists("Wishlist Item", {"item_code": self.item_code, "parent": frappe.session.user}):
|
||||
if frappe.db.exists(
|
||||
"Wishlist Item", {"item_code": self.item_code, "parent": frappe.session.user}
|
||||
):
|
||||
context.wished = True
|
||||
|
||||
context.user_is_customer = check_if_user_is_customer()
|
||||
@@ -243,11 +258,12 @@ class WebsiteItem(WebsiteGenerator):
|
||||
variant.attributes = frappe.get_all(
|
||||
"Item Variant Attribute",
|
||||
filters={"parent": variant.name},
|
||||
fields=["attribute", "attribute_value as value"])
|
||||
fields=["attribute", "attribute_value as value"],
|
||||
)
|
||||
|
||||
# make an attribute-value map for easier access in templates
|
||||
variant.attribute_map = frappe._dict(
|
||||
{attr.attribute : attr.value for attr in variant.attributes}
|
||||
{attr.attribute: attr.value for attr in variant.attributes}
|
||||
)
|
||||
|
||||
for attr in variant.attributes:
|
||||
@@ -267,9 +283,12 @@ class WebsiteItem(WebsiteGenerator):
|
||||
values.append(val)
|
||||
else:
|
||||
# get list of values defined (for sequence)
|
||||
for attr_value in frappe.db.get_all("Item Attribute Value",
|
||||
for attr_value in frappe.db.get_all(
|
||||
"Item Attribute Value",
|
||||
fields=["attribute_value"],
|
||||
filters={"parent": attr.attribute}, order_by="idx asc"):
|
||||
filters={"parent": attr.attribute},
|
||||
order_by="idx asc",
|
||||
):
|
||||
|
||||
if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []):
|
||||
values.append(attr_value.attribute_value)
|
||||
@@ -279,10 +298,10 @@ class WebsiteItem(WebsiteGenerator):
|
||||
|
||||
safe_description = frappe.utils.to_markdown(self.description)
|
||||
|
||||
context.metatags.url = frappe.utils.get_url() + '/' + context.route
|
||||
context.metatags.url = frappe.utils.get_url() + "/" + context.route
|
||||
|
||||
if context.website_image:
|
||||
if context.website_image.startswith('http'):
|
||||
if context.website_image.startswith("http"):
|
||||
url = context.website_image
|
||||
else:
|
||||
url = frappe.utils.get_url() + context.website_image
|
||||
@@ -292,24 +311,28 @@ class WebsiteItem(WebsiteGenerator):
|
||||
|
||||
context.metatags.title = self.web_item_name or self.item_name or self.item_code
|
||||
|
||||
context.metatags['og:type'] = 'product'
|
||||
context.metatags['og:site_name'] = 'ERPNext'
|
||||
context.metatags["og:type"] = "product"
|
||||
context.metatags["og:site_name"] = "ERPNext"
|
||||
|
||||
def set_shopping_cart_data(self, context):
|
||||
from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website
|
||||
context.shopping_cart = get_product_info_for_website(self.item_code, skip_quotation_creation=True)
|
||||
|
||||
context.shopping_cart = get_product_info_for_website(
|
||||
self.item_code, skip_quotation_creation=True
|
||||
)
|
||||
|
||||
def copy_specification_from_item_group(self):
|
||||
self.set("website_specifications", [])
|
||||
if self.item_group:
|
||||
for label, desc in frappe.db.get_values("Item Website Specification",
|
||||
{"parent": self.item_group}, ["label", "description"]):
|
||||
for label, desc in frappe.db.get_values(
|
||||
"Item Website Specification", {"parent": self.item_group}, ["label", "description"]
|
||||
):
|
||||
row = self.append("website_specifications")
|
||||
row.label = label
|
||||
row.description = desc
|
||||
|
||||
def get_product_details_section(self, context):
|
||||
""" Get section with tabs or website specifications. """
|
||||
"""Get section with tabs or website specifications."""
|
||||
context.show_tabs = self.show_tabbed_section
|
||||
if self.show_tabbed_section and (self.tabs or self.website_specifications):
|
||||
context.tabs = self.get_tabs()
|
||||
@@ -321,10 +344,8 @@ class WebsiteItem(WebsiteGenerator):
|
||||
tab_values["tab_1_title"] = "Product Details"
|
||||
tab_values["tab_1_content"] = frappe.render_template(
|
||||
"templates/generators/item/item_specifications.html",
|
||||
{
|
||||
"website_specifications": self.website_specifications,
|
||||
"show_tabs": self.show_tabbed_section
|
||||
})
|
||||
{"website_specifications": self.website_specifications, "show_tabs": self.show_tabbed_section},
|
||||
)
|
||||
|
||||
for row in self.tabs:
|
||||
tab_values[f"tab_{row.idx + 1}_title"] = _(row.label)
|
||||
@@ -338,15 +359,11 @@ class WebsiteItem(WebsiteGenerator):
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(ri)
|
||||
.join(wi).on(ri.item_code == wi.item_code)
|
||||
.select(
|
||||
ri.item_code, ri.route,
|
||||
ri.website_item_name,
|
||||
ri.website_item_thumbnail
|
||||
).where(
|
||||
(ri.parent == self.name)
|
||||
& (wi.published == 1)
|
||||
).orderby(ri.idx)
|
||||
.join(wi)
|
||||
.on(ri.item_code == wi.item_code)
|
||||
.select(ri.item_code, ri.route, ri.website_item_name, ri.website_item_thumbnail)
|
||||
.where((ri.parent == self.name) & (wi.published == 1))
|
||||
.orderby(ri.idx)
|
||||
)
|
||||
items = query.run(as_dict=True)
|
||||
|
||||
@@ -360,22 +377,24 @@ class WebsiteItem(WebsiteGenerator):
|
||||
selling_price_list = _set_price_list(settings, None)
|
||||
for item in items:
|
||||
item.price_info = get_price(
|
||||
item.item_code,
|
||||
selling_price_list,
|
||||
settings.default_customer_group,
|
||||
settings.company
|
||||
item.item_code, selling_price_list, settings.default_customer_group, settings.company
|
||||
)
|
||||
|
||||
return items
|
||||
|
||||
|
||||
def invalidate_cache_for_web_item(doc):
|
||||
"""Invalidate Website Item Group cache and rebuild ItemVariantsCacheManager."""
|
||||
from erpnext.stock.doctype.item.item import invalidate_item_variants_cache_for_website
|
||||
|
||||
invalidate_cache_for(doc, doc.item_group)
|
||||
|
||||
website_item_groups = list(set((doc.get("old_website_item_groups") or [])
|
||||
+ [d.item_group for d in doc.get({"doctype": "Website Item Group"}) if d.item_group]))
|
||||
website_item_groups = list(
|
||||
set(
|
||||
(doc.get("old_website_item_groups") or [])
|
||||
+ [d.item_group for d in doc.get({"doctype": "Website Item Group"}) if d.item_group]
|
||||
)
|
||||
)
|
||||
|
||||
for item_group in website_item_groups:
|
||||
invalidate_cache_for(doc, item_group)
|
||||
@@ -385,6 +404,7 @@ def invalidate_cache_for_web_item(doc):
|
||||
|
||||
invalidate_item_variants_cache_for_website(doc)
|
||||
|
||||
|
||||
def on_doctype_update():
|
||||
# since route is a Text column, it needs a length for indexing
|
||||
frappe.db.add_index("Website Item", ["route(500)"])
|
||||
@@ -392,6 +412,7 @@ def on_doctype_update():
|
||||
frappe.db.add_index("Website Item", ["item_group"])
|
||||
frappe.db.add_index("Website Item", ["brand"])
|
||||
|
||||
|
||||
def check_if_user_is_customer(user=None):
|
||||
from frappe.contacts.doctype.contact.contact import get_contact_name
|
||||
|
||||
@@ -402,7 +423,7 @@ def check_if_user_is_customer(user=None):
|
||||
customer = None
|
||||
|
||||
if contact_name:
|
||||
contact = frappe.get_doc('Contact', contact_name)
|
||||
contact = frappe.get_doc("Contact", contact_name)
|
||||
for link in contact.links:
|
||||
if link.link_doctype == "Customer":
|
||||
customer = link.link_name
|
||||
@@ -410,6 +431,7 @@ def check_if_user_is_customer(user=None):
|
||||
|
||||
return True if customer else False
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_website_item(doc, save=True):
|
||||
if not doc:
|
||||
@@ -425,8 +447,17 @@ def make_website_item(doc, save=True):
|
||||
website_item = frappe.new_doc("Website Item")
|
||||
website_item.web_item_name = doc.get("item_name")
|
||||
|
||||
fields_to_map = ["item_code", "item_name", "item_group", "stock_uom", "brand", "image",
|
||||
"has_variants", "variant_of", "description"]
|
||||
fields_to_map = [
|
||||
"item_code",
|
||||
"item_name",
|
||||
"item_group",
|
||||
"stock_uom",
|
||||
"brand",
|
||||
"image",
|
||||
"has_variants",
|
||||
"variant_of",
|
||||
"description",
|
||||
]
|
||||
for field in fields_to_map:
|
||||
website_item.update({field: doc.get(field)})
|
||||
|
||||
@@ -438,4 +469,4 @@ def make_website_item(doc, save=True):
|
||||
# Add to search cache
|
||||
insert_item_to_index(website_item)
|
||||
|
||||
return [website_item.name, website_item.web_item_name]
|
||||
return [website_item.name, website_item.web_item_name]
|
||||
|
||||
@@ -9,6 +9,7 @@ from frappe.model.document import Document
|
||||
class WebsiteOffer(Document):
|
||||
pass
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_offer_details(offer_id):
|
||||
return frappe.db.get_value('Website Offer', {'name': offer_id}, ['offer_details'])
|
||||
return frappe.db.get_value("Website Offer", {"name": offer_id}, ["offer_details"])
|
||||
|
||||
@@ -34,14 +34,16 @@ class TestWishlist(unittest.TestCase):
|
||||
|
||||
# check if wishlist was created and item was added
|
||||
self.assertTrue(frappe.db.exists("Wishlist", {"user": frappe.session.user}))
|
||||
self.assertTrue(frappe.db.exists("Wishlist Item", {"item_code": "Test Phone Series X", "parent": frappe.session.user}))
|
||||
self.assertTrue(
|
||||
frappe.db.exists(
|
||||
"Wishlist Item", {"item_code": "Test Phone Series X", "parent": frappe.session.user}
|
||||
)
|
||||
)
|
||||
|
||||
# add second item to wishlist
|
||||
add_to_wishlist("Test Phone Series Y")
|
||||
wishlist_length = frappe.db.get_value(
|
||||
"Wishlist Item",
|
||||
{"parent": frappe.session.user},
|
||||
"count(*)"
|
||||
"Wishlist Item", {"parent": frappe.session.user}, "count(*)"
|
||||
)
|
||||
self.assertEqual(wishlist_length, 2)
|
||||
|
||||
@@ -49,9 +51,7 @@ class TestWishlist(unittest.TestCase):
|
||||
remove_from_wishlist("Test Phone Series Y")
|
||||
|
||||
wishlist_length = frappe.db.get_value(
|
||||
"Wishlist Item",
|
||||
{"parent": frappe.session.user},
|
||||
"count(*)"
|
||||
"Wishlist Item", {"parent": frappe.session.user}, "count(*)"
|
||||
)
|
||||
self.assertIsNone(frappe.db.exists("Wishlist Item", {"parent": frappe.session.user}))
|
||||
self.assertEqual(wishlist_length, 0)
|
||||
@@ -74,29 +74,44 @@ class TestWishlist(unittest.TestCase):
|
||||
|
||||
# check wishlist and its content for users
|
||||
self.assertTrue(frappe.db.exists("Wishlist", {"user": test_user.name}))
|
||||
self.assertTrue(frappe.db.exists("Wishlist Item",
|
||||
{"item_code": "Test Phone Series X", "parent": test_user.name}))
|
||||
self.assertTrue(
|
||||
frappe.db.exists(
|
||||
"Wishlist Item", {"item_code": "Test Phone Series X", "parent": test_user.name}
|
||||
)
|
||||
)
|
||||
|
||||
self.assertTrue(frappe.db.exists("Wishlist", {"user": test_user_1.name}))
|
||||
self.assertTrue(frappe.db.exists("Wishlist Item",
|
||||
{"item_code": "Test Phone Series X", "parent": test_user_1.name}))
|
||||
self.assertTrue(
|
||||
frappe.db.exists(
|
||||
"Wishlist Item", {"item_code": "Test Phone Series X", "parent": test_user_1.name}
|
||||
)
|
||||
)
|
||||
|
||||
# remove item for second user
|
||||
remove_from_wishlist("Test Phone Series X")
|
||||
|
||||
# make sure item was removed for second user and not first
|
||||
self.assertFalse(frappe.db.exists("Wishlist Item",
|
||||
{"item_code": "Test Phone Series X", "parent": test_user_1.name}))
|
||||
self.assertTrue(frappe.db.exists("Wishlist Item",
|
||||
{"item_code": "Test Phone Series X", "parent": test_user.name}))
|
||||
self.assertFalse(
|
||||
frappe.db.exists(
|
||||
"Wishlist Item", {"item_code": "Test Phone Series X", "parent": test_user_1.name}
|
||||
)
|
||||
)
|
||||
self.assertTrue(
|
||||
frappe.db.exists(
|
||||
"Wishlist Item", {"item_code": "Test Phone Series X", "parent": test_user.name}
|
||||
)
|
||||
)
|
||||
|
||||
# remove item for first user
|
||||
frappe.set_user(test_user.name)
|
||||
remove_from_wishlist("Test Phone Series X")
|
||||
self.assertFalse(frappe.db.exists("Wishlist Item",
|
||||
{"item_code": "Test Phone Series X", "parent": test_user.name}))
|
||||
self.assertFalse(
|
||||
frappe.db.exists(
|
||||
"Wishlist Item", {"item_code": "Test Phone Series X", "parent": test_user.name}
|
||||
)
|
||||
)
|
||||
|
||||
# tear down
|
||||
frappe.set_user("Administrator")
|
||||
frappe.get_doc("Wishlist", {"user": test_user.name}).delete()
|
||||
frappe.get_doc("Wishlist", {"user": test_user_1.name}).delete()
|
||||
frappe.get_doc("Wishlist", {"user": test_user_1.name}).delete()
|
||||
|
||||
@@ -9,6 +9,7 @@ from frappe.model.document import Document
|
||||
class Wishlist(Document):
|
||||
pass
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_to_wishlist(item_code):
|
||||
"""Insert Item into wishlist."""
|
||||
@@ -20,7 +21,8 @@ def add_to_wishlist(item_code):
|
||||
"Website Item",
|
||||
{"item_code": item_code},
|
||||
["image", "website_warehouse", "name", "web_item_name", "item_name", "item_group", "route"],
|
||||
as_dict=1)
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
wished_item_dict = {
|
||||
"item_code": item_code,
|
||||
@@ -30,7 +32,7 @@ def add_to_wishlist(item_code):
|
||||
"web_item_name": web_item_data.get("web_item_name"),
|
||||
"image": web_item_data.get("image"),
|
||||
"warehouse": web_item_data.get("website_warehouse"),
|
||||
"route": web_item_data.get("route")
|
||||
"route": web_item_data.get("route"),
|
||||
}
|
||||
|
||||
if not frappe.db.exists("Wishlist", frappe.session.user):
|
||||
@@ -41,28 +43,20 @@ def add_to_wishlist(item_code):
|
||||
wishlist.save(ignore_permissions=True)
|
||||
else:
|
||||
wishlist = frappe.get_doc("Wishlist", frappe.session.user)
|
||||
item = wishlist.append('items', wished_item_dict)
|
||||
item = wishlist.append("items", wished_item_dict)
|
||||
item.db_insert()
|
||||
|
||||
if hasattr(frappe.local, "cookie_manager"):
|
||||
frappe.local.cookie_manager.set_cookie("wish_count", str(len(wishlist.items)))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def remove_from_wishlist(item_code):
|
||||
if frappe.db.exists("Wishlist Item", {"item_code": item_code, "parent": frappe.session.user}):
|
||||
frappe.db.delete(
|
||||
"Wishlist Item",
|
||||
{
|
||||
"item_code": item_code,
|
||||
"parent": frappe.session.user
|
||||
}
|
||||
)
|
||||
frappe.db.commit() # nosemgrep
|
||||
frappe.db.delete("Wishlist Item", {"item_code": item_code, "parent": frappe.session.user})
|
||||
frappe.db.commit() # nosemgrep
|
||||
|
||||
wishlist_items = frappe.db.get_values(
|
||||
"Wishlist Item",
|
||||
filters={"parent": frappe.session.user}
|
||||
)
|
||||
wishlist_items = frappe.db.get_values("Wishlist Item", filters={"parent": frappe.session.user})
|
||||
|
||||
if hasattr(frappe.local, "cookie_manager"):
|
||||
frappe.local.cookie_manager.set_cookie("wish_count", str(len(wishlist_items)))
|
||||
frappe.local.cookie_manager.set_cookie("wish_count", str(len(wishlist_items)))
|
||||
|
||||
@@ -9,8 +9,9 @@ from whoosh.query import Prefix
|
||||
# TODO: Make obsolete
|
||||
INDEX_NAME = "products"
|
||||
|
||||
|
||||
class ProductSearch(FullTextSearch):
|
||||
""" Wrapper for WebsiteSearch """
|
||||
"""Wrapper for WebsiteSearch"""
|
||||
|
||||
def get_schema(self):
|
||||
return Schema(
|
||||
@@ -29,7 +30,7 @@ class ProductSearch(FullTextSearch):
|
||||
in www/ and routes from published documents
|
||||
|
||||
Returns:
|
||||
self (object): FullTextSearch Instance
|
||||
self (object): FullTextSearch Instance
|
||||
"""
|
||||
items = get_all_published_items()
|
||||
documents = [self.get_document_to_index(item) for item in items]
|
||||
@@ -69,12 +70,12 @@ class ProductSearch(FullTextSearch):
|
||||
"""Search from the current index
|
||||
|
||||
Args:
|
||||
text (str): String to search for
|
||||
scope (str, optional): Scope to limit the search. Defaults to None.
|
||||
limit (int, optional): Limit number of search results. Defaults to 20.
|
||||
text (str): String to search for
|
||||
scope (str, optional): Scope to limit the search. Defaults to None.
|
||||
limit (int, optional): Limit number of search results. Defaults to 20.
|
||||
|
||||
Returns:
|
||||
[List(_dict)]: Search results
|
||||
[List(_dict)]: Search results
|
||||
"""
|
||||
ix = self.get_index()
|
||||
|
||||
@@ -111,17 +112,23 @@ class ProductSearch(FullTextSearch):
|
||||
keyword_highlights=keyword_highlights,
|
||||
)
|
||||
|
||||
|
||||
def get_all_published_items():
|
||||
return frappe.get_all("Website Item", filters={"variant_of": "", "published": 1}, pluck="item_code")
|
||||
return frappe.get_all(
|
||||
"Website Item", filters={"variant_of": "", "published": 1}, pluck="item_code"
|
||||
)
|
||||
|
||||
|
||||
def update_index_for_path(path):
|
||||
search = ProductSearch(INDEX_NAME)
|
||||
return search.update_index_by_name(path)
|
||||
|
||||
|
||||
def remove_document_from_index(path):
|
||||
search = ProductSearch(INDEX_NAME)
|
||||
return search.remove_document_from_index(path)
|
||||
|
||||
|
||||
def build_index_for_all_routes():
|
||||
search = ProductSearch(INDEX_NAME)
|
||||
return search.build()
|
||||
|
||||
@@ -20,10 +20,10 @@ class ProductFiltersBuilder:
|
||||
return
|
||||
|
||||
fields, filter_data = [], []
|
||||
filter_fields = [row.fieldname for row in self.doc.filter_fields] # fields in settings
|
||||
filter_fields = [row.fieldname for row in self.doc.filter_fields] # fields in settings
|
||||
|
||||
# filter valid field filters i.e. those that exist in Item
|
||||
item_meta = frappe.get_meta('Item', cached=True)
|
||||
item_meta = frappe.get_meta("Item", cached=True)
|
||||
fields = [item_meta.get_field(field) for field in filter_fields if item_meta.has_field(field)]
|
||||
|
||||
for df in fields:
|
||||
@@ -36,15 +36,19 @@ class ProductFiltersBuilder:
|
||||
if include_child:
|
||||
include_groups = get_child_groups_for_website(self.item_group, include_self=True)
|
||||
include_groups = [x.name for x in include_groups]
|
||||
item_or_filters.extend([
|
||||
["item_group", "in", include_groups],
|
||||
["Website Item Group", "item_group", "=", self.item_group] # consider website item groups
|
||||
])
|
||||
item_or_filters.extend(
|
||||
[
|
||||
["item_group", "in", include_groups],
|
||||
["Website Item Group", "item_group", "=", self.item_group], # consider website item groups
|
||||
]
|
||||
)
|
||||
else:
|
||||
item_or_filters.extend([
|
||||
["item_group", "=", self.item_group],
|
||||
["Website Item Group", "item_group", "=", self.item_group] # consider website item groups
|
||||
])
|
||||
item_or_filters.extend(
|
||||
[
|
||||
["item_group", "=", self.item_group],
|
||||
["Website Item Group", "item_group", "=", self.item_group], # consider website item groups
|
||||
]
|
||||
)
|
||||
|
||||
# Get link field values attached to published items
|
||||
item_values = frappe.get_all(
|
||||
@@ -53,10 +57,10 @@ class ProductFiltersBuilder:
|
||||
filters=item_filters,
|
||||
or_filters=item_or_filters,
|
||||
distinct="True",
|
||||
pluck=df.fieldname
|
||||
pluck=df.fieldname,
|
||||
)
|
||||
|
||||
values = list(set(item_values) & link_doctype_values) # intersection of both
|
||||
values = list(set(item_values) & link_doctype_values) # intersection of both
|
||||
else:
|
||||
# table multiselect
|
||||
values = list(link_doctype_values)
|
||||
@@ -72,10 +76,10 @@ class ProductFiltersBuilder:
|
||||
|
||||
def get_filtered_link_doctype_records(self, field):
|
||||
"""
|
||||
Get valid link doctype records depending on filters.
|
||||
Apply enable/disable/show_in_website filter.
|
||||
Returns:
|
||||
set: A set containing valid record names
|
||||
Get valid link doctype records depending on filters.
|
||||
Apply enable/disable/show_in_website filter.
|
||||
Returns:
|
||||
set: A set containing valid record names
|
||||
"""
|
||||
link_doctype = field.get_link_doctype()
|
||||
meta = frappe.get_meta(link_doctype, cached=True) if link_doctype else None
|
||||
@@ -91,12 +95,12 @@ class ProductFiltersBuilder:
|
||||
if not meta:
|
||||
return filters
|
||||
|
||||
if meta.has_field('enabled'):
|
||||
filters['enabled'] = 1
|
||||
if meta.has_field('disabled'):
|
||||
filters['disabled'] = 0
|
||||
if meta.has_field('show_in_website'):
|
||||
filters['show_in_website'] = 1
|
||||
if meta.has_field("enabled"):
|
||||
filters["enabled"] = 1
|
||||
if meta.has_field("disabled"):
|
||||
filters["disabled"] = 0
|
||||
if meta.has_field("show_in_website"):
|
||||
filters["show_in_website"] = 1
|
||||
|
||||
return filters
|
||||
|
||||
@@ -111,12 +115,9 @@ class ProductFiltersBuilder:
|
||||
|
||||
result = frappe.get_all(
|
||||
"Item Variant Attribute",
|
||||
filters={
|
||||
"attribute": ["in", attributes],
|
||||
"attribute_value": ["is", "set"]
|
||||
},
|
||||
filters={"attribute": ["in", attributes], "attribute_value": ["is", "set"]},
|
||||
fields=["attribute", "attribute_value"],
|
||||
distinct=True
|
||||
distinct=True,
|
||||
)
|
||||
|
||||
attribute_value_map = {}
|
||||
@@ -136,11 +137,13 @@ class ProductFiltersBuilder:
|
||||
# [25, 60] rounded min max
|
||||
min_range_absolute, max_range_absolute = floor(min_discount), floor(max_discount)
|
||||
|
||||
min_range = int(min_discount - (min_range_absolute % 10)) # 20
|
||||
max_range = int(max_discount - (max_range_absolute % 10)) # 60
|
||||
min_range = int(min_discount - (min_range_absolute % 10)) # 20
|
||||
max_range = int(max_discount - (max_range_absolute % 10)) # 60
|
||||
|
||||
min_range = (min_range + 10) if min_range != min_range_absolute else min_range # 30 (upper limit of 25.89 in range of 10)
|
||||
max_range = (max_range + 10) if max_range != max_range_absolute else max_range # 60
|
||||
min_range = (
|
||||
(min_range + 10) if min_range != min_range_absolute else min_range
|
||||
) # 30 (upper limit of 25.89 in range of 10)
|
||||
max_range = (max_range + 10) if max_range != max_range_absolute else max_range # 60
|
||||
|
||||
for discount in range(min_range, (max_range + 1), 10):
|
||||
label = f"{discount}% and below"
|
||||
|
||||
@@ -13,12 +13,13 @@ class ProductQuery:
|
||||
"""Query engine for product listing
|
||||
|
||||
Attributes:
|
||||
fields (list): Fields to fetch in query
|
||||
conditions (string): Conditions for query building
|
||||
or_conditions (string): Search conditions
|
||||
page_length (Int): Length of page for the query
|
||||
settings (Document): E Commerce Settings DocType
|
||||
fields (list): Fields to fetch in query
|
||||
conditions (string): Conditions for query building
|
||||
or_conditions (string): Search conditions
|
||||
page_length (Int): Length of page for the query
|
||||
settings (Document): E Commerce Settings DocType
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.settings = frappe.get_doc("E Commerce Settings")
|
||||
self.page_length = self.settings.products_per_page or 20
|
||||
@@ -26,21 +27,33 @@ class ProductQuery:
|
||||
self.or_filters = []
|
||||
self.filters = [["published", "=", 1]]
|
||||
self.fields = [
|
||||
"web_item_name", "name", "item_name", "item_code", "website_image",
|
||||
"variant_of", "has_variants", "item_group", "image", "web_long_description",
|
||||
"short_description", "route", "website_warehouse", "ranking", "on_backorder"
|
||||
"web_item_name",
|
||||
"name",
|
||||
"item_name",
|
||||
"item_code",
|
||||
"website_image",
|
||||
"variant_of",
|
||||
"has_variants",
|
||||
"item_group",
|
||||
"image",
|
||||
"web_long_description",
|
||||
"short_description",
|
||||
"route",
|
||||
"website_warehouse",
|
||||
"ranking",
|
||||
"on_backorder",
|
||||
]
|
||||
|
||||
def query(self, attributes=None, fields=None, search_term=None, start=0, item_group=None):
|
||||
"""
|
||||
Args:
|
||||
attributes (dict, optional): Item Attribute filters
|
||||
fields (dict, optional): Field level filters
|
||||
search_term (str, optional): Search term to lookup
|
||||
start (int, optional): Page start
|
||||
attributes (dict, optional): Item Attribute filters
|
||||
fields (dict, optional): Field level filters
|
||||
search_term (str, optional): Search term to lookup
|
||||
start (int, optional): Page start
|
||||
|
||||
Returns:
|
||||
dict: Dict containing items, item count & discount range
|
||||
dict: Dict containing items, item count & discount range
|
||||
"""
|
||||
# track if discounts included in field filters
|
||||
self.filter_with_discount = bool(fields.get("discount"))
|
||||
@@ -75,11 +88,7 @@ class ProductQuery:
|
||||
|
||||
result = self.filter_results_by_discount(fields, result)
|
||||
|
||||
return {
|
||||
"items": result,
|
||||
"items_count": count,
|
||||
"discounts": discounts
|
||||
}
|
||||
return {"items": result, "items_count": count, "discounts": discounts}
|
||||
|
||||
def query_items(self, start=0):
|
||||
"""Build a query to fetch Website Items based on field filters."""
|
||||
@@ -91,8 +100,9 @@ class ProductQuery:
|
||||
filters=self.filters,
|
||||
or_filters=self.or_filters,
|
||||
limit_page_length=184467440737095516,
|
||||
limit_start=start, # get all items from this offset for total count ahead
|
||||
order_by="ranking desc")
|
||||
limit_start=start, # get all items from this offset for total count ahead
|
||||
order_by="ranking desc",
|
||||
)
|
||||
count = len(count_items)
|
||||
|
||||
# If discounts included, return all rows.
|
||||
@@ -108,7 +118,8 @@ class ProductQuery:
|
||||
or_filters=self.or_filters,
|
||||
limit_page_length=page_length,
|
||||
limit_start=start,
|
||||
order_by="ranking desc")
|
||||
order_by="ranking desc",
|
||||
)
|
||||
|
||||
return items, count
|
||||
|
||||
@@ -127,8 +138,9 @@ class ProductQuery:
|
||||
filters=[
|
||||
["published_in_website", "=", 1],
|
||||
["Item Variant Attribute", "attribute", "=", attribute],
|
||||
["Item Variant Attribute", "attribute_value", "in", values]
|
||||
])
|
||||
["Item Variant Attribute", "attribute_value", "in", values],
|
||||
],
|
||||
)
|
||||
item_codes.append({x.item_code for x in item_code_list})
|
||||
|
||||
if item_codes:
|
||||
@@ -143,21 +155,21 @@ class ProductQuery:
|
||||
"""Build filters for field values
|
||||
|
||||
Args:
|
||||
filters (dict): Filters
|
||||
filters (dict): Filters
|
||||
"""
|
||||
for field, values in filters.items():
|
||||
if not values or field == "discount":
|
||||
continue
|
||||
|
||||
# handle multiselect fields in filter addition
|
||||
meta = frappe.get_meta('Website Item', cached=True)
|
||||
meta = frappe.get_meta("Website Item", cached=True)
|
||||
df = meta.get_field(field)
|
||||
if df.fieldtype == 'Table MultiSelect':
|
||||
if df.fieldtype == "Table MultiSelect":
|
||||
child_doctype = df.options
|
||||
child_meta = frappe.get_meta(child_doctype, cached=True)
|
||||
fields = child_meta.get("fields")
|
||||
if fields:
|
||||
self.filters.append([child_doctype, fields[0].fieldname, 'IN', values])
|
||||
self.filters.append([child_doctype, fields[0].fieldname, "IN", values])
|
||||
elif isinstance(values, list):
|
||||
# If value is a list use `IN` query
|
||||
self.filters.append([field, "in", values])
|
||||
@@ -168,6 +180,7 @@ class ProductQuery:
|
||||
def build_item_group_filters(self, item_group):
|
||||
"Add filters for Item group page and include Website Item Groups."
|
||||
from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_website
|
||||
|
||||
item_group_filters = []
|
||||
|
||||
item_group_filters.append(["Website Item", "item_group", "=", item_group])
|
||||
@@ -188,10 +201,10 @@ class ProductQuery:
|
||||
"""Query search term in specified fields
|
||||
|
||||
Args:
|
||||
search_term (str): Search candidate
|
||||
search_term (str): Search candidate
|
||||
"""
|
||||
# Default fields to search from
|
||||
default_fields = {'item_code', 'item_name', 'web_long_description', 'item_group'}
|
||||
default_fields = {"item_code", "item_name", "web_long_description", "item_group"}
|
||||
|
||||
# Get meta search fields
|
||||
meta = frappe.get_meta("Website Item")
|
||||
@@ -199,22 +212,24 @@ class ProductQuery:
|
||||
|
||||
# Join the meta fields and default fields set
|
||||
search_fields = default_fields.union(meta_fields)
|
||||
if frappe.db.count('Website Item', cache=True) > 50000:
|
||||
search_fields.discard('web_long_description')
|
||||
if frappe.db.count("Website Item", cache=True) > 50000:
|
||||
search_fields.discard("web_long_description")
|
||||
|
||||
# Build or filters for query
|
||||
search = '%{}%'.format(search_term)
|
||||
search = "%{}%".format(search_term)
|
||||
for field in search_fields:
|
||||
self.or_filters.append([field, "like", search])
|
||||
|
||||
def add_display_details(self, result, discount_list, cart_items):
|
||||
"""Add price and availability details in result."""
|
||||
for item in result:
|
||||
product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info')
|
||||
product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get(
|
||||
"product_info"
|
||||
)
|
||||
|
||||
if product_info and product_info['price']:
|
||||
if product_info and product_info["price"]:
|
||||
# update/mutate item and discount_list objects
|
||||
self.get_price_discount_info(item, product_info['price'], discount_list)
|
||||
self.get_price_discount_info(item, product_info["price"], discount_list)
|
||||
|
||||
if self.settings.show_stock_availability:
|
||||
self.get_stock_availability(item)
|
||||
@@ -222,7 +237,9 @@ class ProductQuery:
|
||||
item.in_cart = item.item_code in cart_items
|
||||
|
||||
item.wished = False
|
||||
if frappe.db.exists("Wishlist Item", {"item_code": item.item_code, "parent": frappe.session.user}):
|
||||
if frappe.db.exists(
|
||||
"Wishlist Item", {"item_code": item.item_code, "parent": frappe.session.user}
|
||||
):
|
||||
item.wished = True
|
||||
|
||||
return result, discount_list
|
||||
@@ -233,13 +250,14 @@ class ProductQuery:
|
||||
for field in fields:
|
||||
item[field] = price_object.get(field)
|
||||
|
||||
if price_object.get('discount_percent'):
|
||||
if price_object.get("discount_percent"):
|
||||
item.discount_percent = flt(price_object.discount_percent)
|
||||
discount_list.append(price_object.discount_percent)
|
||||
|
||||
if item.formatted_mrp:
|
||||
item.discount = price_object.get('formatted_discount_percent') or \
|
||||
price_object.get('formatted_discount_rate')
|
||||
item.discount = price_object.get("formatted_discount_percent") or price_object.get(
|
||||
"formatted_discount_rate"
|
||||
)
|
||||
|
||||
def get_stock_availability(self, item):
|
||||
"""Modify item object and add stock details."""
|
||||
@@ -259,24 +277,29 @@ class ProductQuery:
|
||||
elif warehouse:
|
||||
# stock item and has warehouse
|
||||
actual_qty = frappe.db.get_value(
|
||||
"Bin",
|
||||
{"item_code": item.item_code,"warehouse": item.get("website_warehouse")},
|
||||
"actual_qty")
|
||||
"Bin", {"item_code": item.item_code, "warehouse": item.get("website_warehouse")}, "actual_qty"
|
||||
)
|
||||
item.in_stock = bool(flt(actual_qty))
|
||||
|
||||
def get_cart_items(self):
|
||||
customer = get_customer(silent=True)
|
||||
if customer:
|
||||
quotation = frappe.get_all("Quotation", fields=["name"], filters=
|
||||
{"party_name": customer, "contact_email": frappe.session.user, "order_type": "Shopping Cart", "docstatus": 0},
|
||||
order_by="modified desc", limit_page_length=1)
|
||||
quotation = frappe.get_all(
|
||||
"Quotation",
|
||||
fields=["name"],
|
||||
filters={
|
||||
"party_name": customer,
|
||||
"contact_email": frappe.session.user,
|
||||
"order_type": "Shopping Cart",
|
||||
"docstatus": 0,
|
||||
},
|
||||
order_by="modified desc",
|
||||
limit_page_length=1,
|
||||
)
|
||||
if quotation:
|
||||
items = frappe.get_all(
|
||||
"Quotation Item",
|
||||
fields=["item_code"],
|
||||
filters={
|
||||
"parent": quotation[0].get("name")
|
||||
})
|
||||
"Quotation Item", fields=["item_code"], filters={"parent": quotation[0].get("name")}
|
||||
)
|
||||
items = [row.item_code for row in items]
|
||||
return items
|
||||
|
||||
@@ -285,11 +308,15 @@ class ProductQuery:
|
||||
def filter_results_by_discount(self, fields, result):
|
||||
if fields and fields.get("discount"):
|
||||
discount_percent = frappe.utils.flt(fields["discount"][0])
|
||||
result = [row for row in result if row.get("discount_percent") and row.discount_percent <= discount_percent]
|
||||
result = [
|
||||
row
|
||||
for row in result
|
||||
if row.get("discount_percent") and row.discount_percent <= discount_percent
|
||||
]
|
||||
|
||||
if self.filter_with_discount:
|
||||
# no limit was added to results while querying
|
||||
# slice results manually
|
||||
result[:self.page_length]
|
||||
result[: self.page_length]
|
||||
|
||||
return result
|
||||
|
||||
@@ -10,6 +10,7 @@ from erpnext.e_commerce.doctype.website_item.test_website_item import create_reg
|
||||
|
||||
test_dependencies = ["Item", "Item Group"]
|
||||
|
||||
|
||||
class TestItemGroupProductDataEngine(unittest.TestCase):
|
||||
"Test Products & Sub-Category Querying for Product Listing on Item Group Page."
|
||||
|
||||
@@ -19,7 +20,7 @@ class TestItemGroupProductDataEngine(unittest.TestCase):
|
||||
("Test Mobile B", "_Test Item Group B"),
|
||||
("Test Mobile C", "_Test Item Group B - 1"),
|
||||
("Test Mobile D", "_Test Item Group B - 1"),
|
||||
("Test Mobile E", "_Test Item Group B - 2")
|
||||
("Test Mobile E", "_Test Item Group B - 2"),
|
||||
]
|
||||
for item in item_codes:
|
||||
item_code = item[0]
|
||||
@@ -35,12 +36,14 @@ class TestItemGroupProductDataEngine(unittest.TestCase):
|
||||
|
||||
def test_product_listing_in_item_group(self):
|
||||
"Test if only products belonging to the Item Group are fetched."
|
||||
result = get_product_filter_data(query_args={
|
||||
"field_filters": {},
|
||||
"attribute_filters": {},
|
||||
"start": 0,
|
||||
"item_group": "_Test Item Group B"
|
||||
})
|
||||
result = get_product_filter_data(
|
||||
query_args={
|
||||
"field_filters": {},
|
||||
"attribute_filters": {},
|
||||
"start": 0,
|
||||
"item_group": "_Test Item Group B",
|
||||
}
|
||||
)
|
||||
|
||||
items = result.get("items")
|
||||
item_codes = [item.get("item_code") for item in items]
|
||||
@@ -54,48 +57,52 @@ class TestItemGroupProductDataEngine(unittest.TestCase):
|
||||
website_item = frappe.get_doc("Website Item", {"item_code": "Test Mobile E"})
|
||||
|
||||
# show item belonging to '_Test Item Group B - 2' in '_Test Item Group B - 1' as well
|
||||
website_item.append("website_item_groups", {
|
||||
"item_group": "_Test Item Group B - 1"
|
||||
})
|
||||
website_item.append("website_item_groups", {"item_group": "_Test Item Group B - 1"})
|
||||
website_item.save()
|
||||
|
||||
result = get_product_filter_data(query_args={
|
||||
"field_filters": {},
|
||||
"attribute_filters": {},
|
||||
"start": 0,
|
||||
"item_group": "_Test Item Group B - 1"
|
||||
})
|
||||
result = get_product_filter_data(
|
||||
query_args={
|
||||
"field_filters": {},
|
||||
"attribute_filters": {},
|
||||
"start": 0,
|
||||
"item_group": "_Test Item Group B - 1",
|
||||
}
|
||||
)
|
||||
|
||||
items = result.get("items")
|
||||
item_codes = [item.get("item_code") for item in items]
|
||||
|
||||
self.assertEqual(len(items), 3)
|
||||
self.assertIn("Test Mobile E", item_codes) # visible in other item groups
|
||||
self.assertIn("Test Mobile E", item_codes) # visible in other item groups
|
||||
self.assertIn("Test Mobile C", item_codes)
|
||||
self.assertIn("Test Mobile D", item_codes)
|
||||
|
||||
result = get_product_filter_data(query_args={
|
||||
"field_filters": {},
|
||||
"attribute_filters": {},
|
||||
"start": 0,
|
||||
"item_group": "_Test Item Group B - 2"
|
||||
})
|
||||
result = get_product_filter_data(
|
||||
query_args={
|
||||
"field_filters": {},
|
||||
"attribute_filters": {},
|
||||
"start": 0,
|
||||
"item_group": "_Test Item Group B - 2",
|
||||
}
|
||||
)
|
||||
|
||||
items = result.get("items")
|
||||
|
||||
self.assertEqual(len(items), 1)
|
||||
self.assertEqual(items[0].get("item_code"), "Test Mobile E") # visible in own item group
|
||||
self.assertEqual(items[0].get("item_code"), "Test Mobile E") # visible in own item group
|
||||
|
||||
def test_item_group_with_sub_groups(self):
|
||||
"Test Valid Sub Item Groups in Item Group Page."
|
||||
frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 0)
|
||||
|
||||
result = get_product_filter_data(query_args={
|
||||
"field_filters": {},
|
||||
"attribute_filters": {},
|
||||
"start": 0,
|
||||
"item_group": "_Test Item Group B"
|
||||
})
|
||||
result = get_product_filter_data(
|
||||
query_args={
|
||||
"field_filters": {},
|
||||
"attribute_filters": {},
|
||||
"start": 0,
|
||||
"item_group": "_Test Item Group B",
|
||||
}
|
||||
)
|
||||
|
||||
self.assertTrue(bool(result.get("sub_categories")))
|
||||
|
||||
@@ -104,12 +111,14 @@ class TestItemGroupProductDataEngine(unittest.TestCase):
|
||||
self.assertIn("_Test Item Group B - 1", child_groups)
|
||||
|
||||
frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 1)
|
||||
result = get_product_filter_data(query_args={
|
||||
"field_filters": {},
|
||||
"attribute_filters": {},
|
||||
"start": 0,
|
||||
"item_group": "_Test Item Group B"
|
||||
})
|
||||
result = get_product_filter_data(
|
||||
query_args={
|
||||
"field_filters": {},
|
||||
"attribute_filters": {},
|
||||
"start": 0,
|
||||
"item_group": "_Test Item Group B",
|
||||
}
|
||||
)
|
||||
child_groups = [d.name for d in result.get("sub_categories")]
|
||||
|
||||
# check if child group is fetched if shown in website
|
||||
@@ -120,19 +129,20 @@ class TestItemGroupProductDataEngine(unittest.TestCase):
|
||||
"""
|
||||
Test if 'include_descendants' pulls Items belonging to descendant Item Groups (Level 2 & 3).
|
||||
> _Test Item Group B [Level 1]
|
||||
> _Test Item Group B - 1 [Level 2]
|
||||
> _Test Item Group B - 1 - 1 [Level 3]
|
||||
> _Test Item Group B - 1 [Level 2]
|
||||
> _Test Item Group B - 1 - 1 [Level 3]
|
||||
"""
|
||||
frappe.get_doc({ # create Level 3 nested child group
|
||||
"doctype": "Item Group",
|
||||
"is_group": 1,
|
||||
"item_group_name": "_Test Item Group B - 1 - 1",
|
||||
"parent_item_group": "_Test Item Group B - 1"
|
||||
}).insert()
|
||||
frappe.get_doc(
|
||||
{ # create Level 3 nested child group
|
||||
"doctype": "Item Group",
|
||||
"is_group": 1,
|
||||
"item_group_name": "_Test Item Group B - 1 - 1",
|
||||
"parent_item_group": "_Test Item Group B - 1",
|
||||
}
|
||||
).insert()
|
||||
|
||||
create_regular_web_item( # create an item belonging to level 3 item group
|
||||
"Test Mobile F",
|
||||
item_args={"item_group": "_Test Item Group B - 1 - 1"}
|
||||
create_regular_web_item( # create an item belonging to level 3 item group
|
||||
"Test Mobile F", item_args={"item_group": "_Test Item Group B - 1 - 1"}
|
||||
)
|
||||
|
||||
frappe.db.set_value("Item Group", "_Test Item Group B - 1 - 1", "show_in_website", 1)
|
||||
@@ -140,12 +150,14 @@ class TestItemGroupProductDataEngine(unittest.TestCase):
|
||||
# enable 'include descendants' in Level 1
|
||||
frappe.db.set_value("Item Group", "_Test Item Group B", "include_descendants", 1)
|
||||
|
||||
result = get_product_filter_data(query_args={
|
||||
"field_filters": {},
|
||||
"attribute_filters": {},
|
||||
"start": 0,
|
||||
"item_group": "_Test Item Group B"
|
||||
})
|
||||
result = get_product_filter_data(
|
||||
query_args={
|
||||
"field_filters": {},
|
||||
"attribute_filters": {},
|
||||
"start": 0,
|
||||
"item_group": "_Test Item Group B",
|
||||
}
|
||||
)
|
||||
|
||||
items = result.get("items")
|
||||
item_codes = [item.get("item_code") for item in items]
|
||||
@@ -155,4 +167,4 @@ class TestItemGroupProductDataEngine(unittest.TestCase):
|
||||
self.assertIn("Test Mobile A", item_codes)
|
||||
self.assertIn("Test Mobile C", item_codes)
|
||||
self.assertIn("Test Mobile E", item_codes)
|
||||
self.assertIn("Test Mobile F", item_codes)
|
||||
self.assertIn("Test Mobile F", item_codes)
|
||||
|
||||
@@ -14,19 +14,20 @@ from erpnext.e_commerce.product_data_engine.query import ProductQuery
|
||||
|
||||
test_dependencies = ["Item", "Item Group"]
|
||||
|
||||
|
||||
class TestProductDataEngine(unittest.TestCase):
|
||||
"Test Products Querying and Filters for Product Listing."
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
item_codes = [
|
||||
("Test 11I Laptop", "Products"), # rank 1
|
||||
("Test 12I Laptop", "Products"), # rank 2
|
||||
("Test 13I Laptop", "Products"), # rank 3
|
||||
("Test 14I Laptop", "Raw Material"), # rank 4
|
||||
("Test 15I Laptop", "Raw Material"), # rank 5
|
||||
("Test 16I Laptop", "Raw Material"), # rank 6
|
||||
("Test 17I Laptop", "Products") # rank 7
|
||||
("Test 11I Laptop", "Products"), # rank 1
|
||||
("Test 12I Laptop", "Products"), # rank 2
|
||||
("Test 13I Laptop", "Products"), # rank 3
|
||||
("Test 14I Laptop", "Raw Material"), # rank 4
|
||||
("Test 15I Laptop", "Raw Material"), # rank 5
|
||||
("Test 16I Laptop", "Raw Material"), # rank 6
|
||||
("Test 17I Laptop", "Products"), # rank 7
|
||||
]
|
||||
for index, item in enumerate(item_codes, start=1):
|
||||
item_code = item[0]
|
||||
@@ -35,17 +36,19 @@ class TestProductDataEngine(unittest.TestCase):
|
||||
if not frappe.db.exists("Website Item", {"item_code": item_code}):
|
||||
create_regular_web_item(item_code, item_args=item_args, web_args=web_args)
|
||||
|
||||
setup_e_commerce_settings({
|
||||
"products_per_page": 4,
|
||||
"enable_field_filters": 1,
|
||||
"filter_fields": [{"fieldname": "item_group"}],
|
||||
"enable_attribute_filters": 1,
|
||||
"filter_attributes": [{"attribute": "Test Size"}],
|
||||
"company": "_Test Company",
|
||||
"enabled": 1,
|
||||
"default_customer_group": "_Test Customer Group",
|
||||
"price_list": "_Test Price List India"
|
||||
})
|
||||
setup_e_commerce_settings(
|
||||
{
|
||||
"products_per_page": 4,
|
||||
"enable_field_filters": 1,
|
||||
"filter_fields": [{"fieldname": "item_group"}],
|
||||
"enable_attribute_filters": 1,
|
||||
"filter_attributes": [{"attribute": "Test Size"}],
|
||||
"company": "_Test Company",
|
||||
"enabled": 1,
|
||||
"default_customer_group": "_Test Customer Group",
|
||||
"price_list": "_Test Price List India",
|
||||
}
|
||||
)
|
||||
frappe.local.shopping_cart_settings = None
|
||||
|
||||
@classmethod
|
||||
@@ -55,13 +58,7 @@ class TestProductDataEngine(unittest.TestCase):
|
||||
def test_product_list_ordering_and_paging(self):
|
||||
"Test if website items appear by ranking on different pages."
|
||||
engine = ProductQuery()
|
||||
result = engine.query(
|
||||
attributes={},
|
||||
fields={},
|
||||
search_term=None,
|
||||
start=0,
|
||||
item_group=None
|
||||
)
|
||||
result = engine.query(attributes={}, fields={}, search_term=None, start=0, item_group=None)
|
||||
items = result.get("items")
|
||||
|
||||
self.assertIsNotNone(items)
|
||||
@@ -75,13 +72,7 @@ class TestProductDataEngine(unittest.TestCase):
|
||||
self.assertEqual(items[3].get("item_code"), "Test 14I Laptop")
|
||||
|
||||
# check next page
|
||||
result = engine.query(
|
||||
attributes={},
|
||||
fields={},
|
||||
search_term=None,
|
||||
start=4,
|
||||
item_group=None
|
||||
)
|
||||
result = engine.query(attributes={}, fields={}, search_term=None, start=4, item_group=None)
|
||||
items = result.get("items")
|
||||
|
||||
# check if items appear as per ranking set in setUpClass on next page
|
||||
@@ -101,13 +92,7 @@ class TestProductDataEngine(unittest.TestCase):
|
||||
frappe.db.set_value("Website Item", {"item_code": item_code}, "ranking", 10)
|
||||
|
||||
engine = ProductQuery()
|
||||
result = engine.query(
|
||||
attributes={},
|
||||
fields={},
|
||||
search_term=None,
|
||||
start=0,
|
||||
item_group=None
|
||||
)
|
||||
result = engine.query(attributes={}, fields={}, search_term=None, start=0, item_group=None)
|
||||
items = result.get("items")
|
||||
|
||||
# check if item is the first item on the first page
|
||||
@@ -152,11 +137,7 @@ class TestProductDataEngine(unittest.TestCase):
|
||||
|
||||
engine = ProductQuery()
|
||||
result = engine.query(
|
||||
attributes={},
|
||||
fields=field_filters,
|
||||
search_term=None,
|
||||
start=0,
|
||||
item_group=None
|
||||
attributes={}, fields=field_filters, search_term=None, start=0, item_group=None
|
||||
)
|
||||
items = result.get("items")
|
||||
|
||||
@@ -188,11 +169,7 @@ class TestProductDataEngine(unittest.TestCase):
|
||||
attribute_filters = {"Test Size": ["Large"]}
|
||||
engine = ProductQuery()
|
||||
result = engine.query(
|
||||
attributes=attribute_filters,
|
||||
fields={},
|
||||
search_term=None,
|
||||
start=0,
|
||||
item_group=None
|
||||
attributes=attribute_filters, fields={}, search_term=None, start=0, item_group=None
|
||||
)
|
||||
items = result.get("items")
|
||||
|
||||
@@ -209,24 +186,13 @@ class TestProductDataEngine(unittest.TestCase):
|
||||
|
||||
item_code = "Test 12I Laptop"
|
||||
make_web_item_price(item_code=item_code)
|
||||
make_web_pricing_rule(
|
||||
title=f"Test Pricing Rule for {item_code}",
|
||||
item_code=item_code,
|
||||
selling=1
|
||||
)
|
||||
make_web_pricing_rule(title=f"Test Pricing Rule for {item_code}", item_code=item_code, selling=1)
|
||||
|
||||
setup_e_commerce_settings({"show_price": 1})
|
||||
frappe.local.shopping_cart_settings = None
|
||||
|
||||
|
||||
engine = ProductQuery()
|
||||
result = engine.query(
|
||||
attributes={},
|
||||
fields={},
|
||||
search_term=None,
|
||||
start=4,
|
||||
item_group=None
|
||||
)
|
||||
result = engine.query(attributes={}, fields={}, search_term=None, start=4, item_group=None)
|
||||
self.assertTrue(bool(result.get("discounts")))
|
||||
|
||||
filter_engine = ProductFiltersBuilder()
|
||||
@@ -247,16 +213,16 @@ class TestProductDataEngine(unittest.TestCase):
|
||||
|
||||
make_web_item_price(item_code="Test 12I Laptop")
|
||||
make_web_pricing_rule(
|
||||
title="Test Pricing Rule for Test 12I Laptop", # 10% discount
|
||||
title="Test Pricing Rule for Test 12I Laptop", # 10% discount
|
||||
item_code="Test 12I Laptop",
|
||||
selling=1
|
||||
selling=1,
|
||||
)
|
||||
make_web_item_price(item_code="Test 13I Laptop")
|
||||
make_web_pricing_rule(
|
||||
title="Test Pricing Rule for Test 13I Laptop", # 15% discount
|
||||
title="Test Pricing Rule for Test 13I Laptop", # 15% discount
|
||||
item_code="Test 13I Laptop",
|
||||
discount_percentage=15,
|
||||
selling=1
|
||||
selling=1,
|
||||
)
|
||||
|
||||
setup_e_commerce_settings({"show_price": 1})
|
||||
@@ -264,11 +230,7 @@ class TestProductDataEngine(unittest.TestCase):
|
||||
|
||||
engine = ProductQuery()
|
||||
result = engine.query(
|
||||
attributes={},
|
||||
fields=field_filters,
|
||||
search_term=None,
|
||||
start=0,
|
||||
item_group=None
|
||||
attributes={}, fields=field_filters, search_term=None, start=0, item_group=None
|
||||
)
|
||||
items = result.get("items")
|
||||
|
||||
@@ -282,15 +244,13 @@ class TestProductDataEngine(unittest.TestCase):
|
||||
|
||||
create_variant_web_item()
|
||||
|
||||
result = get_product_filter_data(query_args={
|
||||
"field_filters": {
|
||||
"item_group": "Products"
|
||||
},
|
||||
"attribute_filters": {
|
||||
"Test Size": ["Large"]
|
||||
},
|
||||
"start": 0
|
||||
})
|
||||
result = get_product_filter_data(
|
||||
query_args={
|
||||
"field_filters": {"item_group": "Products"},
|
||||
"attribute_filters": {"Test Size": ["Large"]},
|
||||
"start": 0,
|
||||
}
|
||||
)
|
||||
|
||||
items = result.get("items")
|
||||
|
||||
@@ -301,20 +261,13 @@ class TestProductDataEngine(unittest.TestCase):
|
||||
"Test if variants are hideen on hiding variants in settings."
|
||||
create_variant_web_item()
|
||||
|
||||
setup_e_commerce_settings({
|
||||
"enable_attribute_filters": 0,
|
||||
"hide_variants": 1
|
||||
})
|
||||
setup_e_commerce_settings({"enable_attribute_filters": 0, "hide_variants": 1})
|
||||
frappe.local.shopping_cart_settings = None
|
||||
|
||||
attribute_filters = {"Test Size": ["Large"]}
|
||||
engine = ProductQuery()
|
||||
result = engine.query(
|
||||
attributes=attribute_filters,
|
||||
fields={},
|
||||
search_term=None,
|
||||
start=0,
|
||||
item_group=None
|
||||
attributes=attribute_filters, fields={}, search_term=None, start=0, item_group=None
|
||||
)
|
||||
items = result.get("items")
|
||||
|
||||
@@ -322,10 +275,8 @@ class TestProductDataEngine(unittest.TestCase):
|
||||
self.assertEqual(len(items), 0)
|
||||
|
||||
# tear down
|
||||
setup_e_commerce_settings({
|
||||
"enable_attribute_filters": 1,
|
||||
"hide_variants": 0
|
||||
})
|
||||
setup_e_commerce_settings({"enable_attribute_filters": 1, "hide_variants": 0})
|
||||
|
||||
|
||||
def create_variant_web_item():
|
||||
"Create Variant and Template Website Items."
|
||||
@@ -333,18 +284,17 @@ def create_variant_web_item():
|
||||
from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
make_item("Test Web Item", {
|
||||
"has_variant": 1,
|
||||
"variant_based_on": "Item Attribute",
|
||||
"attributes": [
|
||||
{
|
||||
"attribute": "Test Size"
|
||||
}
|
||||
]
|
||||
})
|
||||
make_item(
|
||||
"Test Web Item",
|
||||
{
|
||||
"has_variant": 1,
|
||||
"variant_based_on": "Item Attribute",
|
||||
"attributes": [{"attribute": "Test Size"}],
|
||||
},
|
||||
)
|
||||
if not frappe.db.exists("Item", "Test Web Item-L"):
|
||||
variant = create_variant("Test Web Item", {"Test Size": "Large"})
|
||||
variant.save()
|
||||
|
||||
if not frappe.db.exists("Website Item", {"variant_of": "Test Web Item"}):
|
||||
make_website_item(variant, save=True)
|
||||
make_website_item(variant, save=True)
|
||||
|
||||
@@ -5,24 +5,27 @@ import frappe
|
||||
from frappe.utils.redis_wrapper import RedisWrapper
|
||||
from redisearch import AutoCompleter, Client, IndexDefinition, Suggestion, TagField, TextField
|
||||
|
||||
WEBSITE_ITEM_INDEX = 'website_items_index'
|
||||
WEBSITE_ITEM_KEY_PREFIX = 'website_item:'
|
||||
WEBSITE_ITEM_NAME_AUTOCOMPLETE = 'website_items_name_dict'
|
||||
WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE = 'website_items_category_dict'
|
||||
WEBSITE_ITEM_INDEX = "website_items_index"
|
||||
WEBSITE_ITEM_KEY_PREFIX = "website_item:"
|
||||
WEBSITE_ITEM_NAME_AUTOCOMPLETE = "website_items_name_dict"
|
||||
WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE = "website_items_category_dict"
|
||||
|
||||
|
||||
def get_indexable_web_fields():
|
||||
"Return valid fields from Website Item that can be searched for."
|
||||
web_item_meta = frappe.get_meta("Website Item", cached=True)
|
||||
valid_fields = filter(
|
||||
lambda df: df.fieldtype in ("Link", "Table MultiSelect", "Data", "Small Text", "Text Editor"),
|
||||
web_item_meta.fields)
|
||||
web_item_meta.fields,
|
||||
)
|
||||
|
||||
return [df.fieldname for df in valid_fields]
|
||||
|
||||
|
||||
def is_search_module_loaded():
|
||||
try:
|
||||
cache = frappe.cache()
|
||||
out = cache.execute_command('MODULE LIST')
|
||||
out = cache.execute_command("MODULE LIST")
|
||||
|
||||
parsed_output = " ".join(
|
||||
(" ".join([s.decode() for s in o if not isinstance(s, int)]) for o in out)
|
||||
@@ -31,8 +34,10 @@ def is_search_module_loaded():
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def if_redisearch_loaded(function):
|
||||
"Decorator to check if Redisearch is loaded."
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
if is_search_module_loaded():
|
||||
func = function(*args, **kwargs)
|
||||
@@ -41,8 +46,10 @@ def if_redisearch_loaded(function):
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def make_key(key):
|
||||
return "{0}|{1}".format(frappe.conf.db_name, key).encode('utf-8')
|
||||
return "{0}|{1}".format(frappe.conf.db_name, key).encode("utf-8")
|
||||
|
||||
|
||||
@if_redisearch_loaded
|
||||
def create_website_items_index():
|
||||
@@ -60,14 +67,11 @@ def create_website_items_index():
|
||||
idx_def = IndexDefinition([make_key(WEBSITE_ITEM_KEY_PREFIX)])
|
||||
|
||||
# Based on e-commerce settings
|
||||
idx_fields = frappe.db.get_single_value(
|
||||
'E Commerce Settings',
|
||||
'search_index_fields'
|
||||
)
|
||||
idx_fields = idx_fields.split(',') if idx_fields else []
|
||||
idx_fields = frappe.db.get_single_value("E Commerce Settings", "search_index_fields")
|
||||
idx_fields = idx_fields.split(",") if idx_fields else []
|
||||
|
||||
if 'web_item_name' in idx_fields:
|
||||
idx_fields.remove('web_item_name')
|
||||
if "web_item_name" in idx_fields:
|
||||
idx_fields.remove("web_item_name")
|
||||
|
||||
idx_fields = list(map(to_search_field, idx_fields))
|
||||
|
||||
@@ -79,12 +83,14 @@ def create_website_items_index():
|
||||
reindex_all_web_items()
|
||||
define_autocomplete_dictionary()
|
||||
|
||||
|
||||
def to_search_field(field):
|
||||
if field == "tags":
|
||||
return TagField("tags", separator=",")
|
||||
|
||||
return TextField(field)
|
||||
|
||||
|
||||
@if_redisearch_loaded
|
||||
def insert_item_to_index(website_item_doc):
|
||||
# Insert item to index
|
||||
@@ -97,26 +103,30 @@ def insert_item_to_index(website_item_doc):
|
||||
|
||||
insert_to_name_ac(website_item_doc.web_item_name, website_item_doc.name)
|
||||
|
||||
|
||||
@if_redisearch_loaded
|
||||
def insert_to_name_ac(web_name, doc_name):
|
||||
ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=frappe.cache())
|
||||
ac.add_suggestions(Suggestion(web_name, payload=doc_name))
|
||||
|
||||
|
||||
def create_web_item_map(website_item_doc):
|
||||
fields_to_index = get_fields_indexed()
|
||||
web_item = {}
|
||||
|
||||
for f in fields_to_index:
|
||||
web_item[f] = website_item_doc.get(f) or ''
|
||||
web_item[f] = website_item_doc.get(f) or ""
|
||||
|
||||
return web_item
|
||||
|
||||
|
||||
@if_redisearch_loaded
|
||||
def update_index_for_item(website_item_doc):
|
||||
# Reinsert to Cache
|
||||
insert_item_to_index(website_item_doc)
|
||||
define_autocomplete_dictionary()
|
||||
|
||||
|
||||
@if_redisearch_loaded
|
||||
def delete_item_from_index(website_item_doc):
|
||||
cache = frappe.cache()
|
||||
@@ -130,26 +140,27 @@ def delete_item_from_index(website_item_doc):
|
||||
delete_from_ac_dict(website_item_doc)
|
||||
return True
|
||||
|
||||
|
||||
@if_redisearch_loaded
|
||||
def delete_from_ac_dict(website_item_doc):
|
||||
'''Removes this items's name from autocomplete dictionary'''
|
||||
"""Removes this items's name from autocomplete dictionary"""
|
||||
cache = frappe.cache()
|
||||
name_ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=cache)
|
||||
name_ac.delete(website_item_doc.web_item_name)
|
||||
|
||||
|
||||
@if_redisearch_loaded
|
||||
def define_autocomplete_dictionary():
|
||||
"""Creates an autocomplete search dictionary for `name`.
|
||||
Also creats autocomplete dictionary for `categories` if
|
||||
checked in E Commerce Settings"""
|
||||
Also creats autocomplete dictionary for `categories` if
|
||||
checked in E Commerce Settings"""
|
||||
|
||||
cache = frappe.cache()
|
||||
name_ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=cache)
|
||||
cat_ac = AutoCompleter(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE), conn=cache)
|
||||
|
||||
ac_categories = frappe.db.get_single_value(
|
||||
'E Commerce Settings',
|
||||
'show_categories_in_search_autocomplete'
|
||||
"E Commerce Settings", "show_categories_in_search_autocomplete"
|
||||
)
|
||||
|
||||
# Delete both autocomplete dicts
|
||||
@@ -160,9 +171,7 @@ def define_autocomplete_dictionary():
|
||||
return False
|
||||
|
||||
items = frappe.get_all(
|
||||
'Website Item',
|
||||
fields=['web_item_name', 'item_group'],
|
||||
filters={"published": 1}
|
||||
"Website Item", fields=["web_item_name", "item_group"], filters={"published": 1}
|
||||
)
|
||||
|
||||
for item in items:
|
||||
@@ -172,13 +181,10 @@ def define_autocomplete_dictionary():
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@if_redisearch_loaded
|
||||
def reindex_all_web_items():
|
||||
items = frappe.get_all(
|
||||
'Website Item',
|
||||
fields=get_fields_indexed(),
|
||||
filters={"published": True}
|
||||
)
|
||||
items = frappe.get_all("Website Item", fields=get_fields_indexed(), filters={"published": True})
|
||||
|
||||
cache = frappe.cache()
|
||||
for item in items:
|
||||
@@ -188,22 +194,22 @@ def reindex_all_web_items():
|
||||
for k, v in web_item.items():
|
||||
super(RedisWrapper, cache).hset(key, k, v)
|
||||
|
||||
|
||||
def get_cache_key(name):
|
||||
name = frappe.scrub(name)
|
||||
return f"{WEBSITE_ITEM_KEY_PREFIX}{name}"
|
||||
|
||||
def get_fields_indexed():
|
||||
fields_to_index = frappe.db.get_single_value(
|
||||
'E Commerce Settings',
|
||||
'search_index_fields'
|
||||
)
|
||||
fields_to_index = fields_to_index.split(',') if fields_to_index else []
|
||||
|
||||
mandatory_fields = ['name', 'web_item_name', 'route', 'thumbnail', 'ranking']
|
||||
def get_fields_indexed():
|
||||
fields_to_index = frappe.db.get_single_value("E Commerce Settings", "search_index_fields")
|
||||
fields_to_index = fields_to_index.split(",") if fields_to_index else []
|
||||
|
||||
mandatory_fields = ["name", "web_item_name", "route", "thumbnail", "ranking"]
|
||||
fields_to_index = fields_to_index + mandatory_fields
|
||||
|
||||
return fields_to_index
|
||||
|
||||
|
||||
# TODO: Remove later
|
||||
# # Figure out a way to run this at startup
|
||||
define_autocomplete_dictionary()
|
||||
|
||||
@@ -19,6 +19,7 @@ from erpnext.utilities.product import get_web_item_qty_in_stock
|
||||
class WebsitePriceListMissingError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
def set_cart_count(quotation=None):
|
||||
if cint(frappe.db.get_singles_value("E Commerce Settings", "enabled")):
|
||||
if not quotation:
|
||||
@@ -28,6 +29,7 @@ def set_cart_count(quotation=None):
|
||||
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_party()
|
||||
@@ -47,38 +49,46 @@ def get_cart_quotation(doc=None):
|
||||
"shipping_addresses": get_shipping_addresses(party),
|
||||
"billing_addresses": get_billing_addresses(party),
|
||||
"shipping_rules": get_applicable_shipping_rules(party),
|
||||
"cart_settings": frappe.get_cached_doc("E Commerce Settings")
|
||||
"cart_settings": frappe.get_cached_doc("E Commerce Settings"),
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_shipping_addresses(party=None):
|
||||
if not party:
|
||||
party = get_party()
|
||||
addresses = get_address_docs(party=party)
|
||||
return [{"name": address.name, "title": address.address_title, "display": address.display}
|
||||
for address in addresses if address.address_type == "Shipping"
|
||||
return [
|
||||
{"name": address.name, "title": address.address_title, "display": address.display}
|
||||
for address in addresses
|
||||
if address.address_type == "Shipping"
|
||||
]
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_billing_addresses(party=None):
|
||||
if not party:
|
||||
party = get_party()
|
||||
addresses = get_address_docs(party=party)
|
||||
return [{"name": address.name, "title": address.address_title, "display": address.display}
|
||||
for address in addresses if address.address_type == "Billing"
|
||||
return [
|
||||
{"name": address.name, "title": address.address_title, "display": address.display}
|
||||
for address in addresses
|
||||
if address.address_type == "Billing"
|
||||
]
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def place_order():
|
||||
quotation = _get_cart_quotation()
|
||||
cart_settings = frappe.db.get_value("E Commerce Settings", None,
|
||||
["company", "allow_items_not_in_stock"], as_dict=1)
|
||||
cart_settings = frappe.db.get_value(
|
||||
"E Commerce Settings", None, ["company", "allow_items_not_in_stock"], as_dict=1
|
||||
)
|
||||
quotation.company = cart_settings.company
|
||||
|
||||
quotation.flags.ignore_permissions = True
|
||||
quotation.submit()
|
||||
|
||||
if quotation.quotation_to == 'Lead' and quotation.party_name:
|
||||
if quotation.quotation_to == "Lead" and quotation.party_name:
|
||||
# company used to create customer accounts
|
||||
frappe.defaults.set_user_default("company", quotation.company)
|
||||
|
||||
@@ -86,17 +96,14 @@ def place_order():
|
||||
frappe.throw(_("Set Shipping Address or Billing Address"))
|
||||
|
||||
from erpnext.selling.doctype.quotation.quotation import _make_sales_order
|
||||
|
||||
sales_order = frappe.get_doc(_make_sales_order(quotation.name, ignore_permissions=True))
|
||||
sales_order.payment_schedule = []
|
||||
|
||||
if not cint(cart_settings.allow_items_not_in_stock):
|
||||
for item in sales_order.get("items"):
|
||||
item.warehouse = frappe.db.get_value(
|
||||
"Website Item",
|
||||
{
|
||||
"item_code": item.item_code
|
||||
},
|
||||
"website_warehouse"
|
||||
"Website Item", {"item_code": item.item_code}, "website_warehouse"
|
||||
)
|
||||
is_stock_item = frappe.db.get_value("Item", item.item_code, "is_stock_item")
|
||||
|
||||
@@ -116,6 +123,7 @@ def place_order():
|
||||
|
||||
return sales_order.name
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def request_for_quotation():
|
||||
quotation = _get_cart_quotation()
|
||||
@@ -127,6 +135,7 @@ def request_for_quotation():
|
||||
quotation.submit()
|
||||
return quotation.name
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_cart(item_code, qty, additional_notes=None, with_items=False):
|
||||
quotation = _get_cart_quotation()
|
||||
@@ -143,12 +152,15 @@ def update_cart(item_code, qty, additional_notes=None, with_items=False):
|
||||
else:
|
||||
quotation_items = quotation.get("items", {"item_code": item_code})
|
||||
if not quotation_items:
|
||||
quotation.append("items", {
|
||||
"doctype": "Quotation Item",
|
||||
"item_code": item_code,
|
||||
"qty": qty,
|
||||
"additional_notes": additional_notes
|
||||
})
|
||||
quotation.append(
|
||||
"items",
|
||||
{
|
||||
"doctype": "Quotation Item",
|
||||
"item_code": item_code,
|
||||
"qty": qty,
|
||||
"additional_notes": additional_notes,
|
||||
},
|
||||
)
|
||||
else:
|
||||
quotation_items[0].qty = qty
|
||||
quotation_items[0].additional_notes = additional_notes
|
||||
@@ -168,73 +180,75 @@ def update_cart(item_code, qty, additional_notes=None, with_items=False):
|
||||
if cint(with_items):
|
||||
context = get_cart_quotation(quotation)
|
||||
return {
|
||||
"items": frappe.render_template("templates/includes/cart/cart_items.html",
|
||||
context),
|
||||
"total": frappe.render_template("templates/includes/cart/cart_items_total.html",
|
||||
context),
|
||||
"taxes_and_totals": frappe.render_template("templates/includes/cart/cart_payment_summary.html",
|
||||
context)
|
||||
"items": frappe.render_template("templates/includes/cart/cart_items.html", context),
|
||||
"total": frappe.render_template("templates/includes/cart/cart_items_total.html", context),
|
||||
"taxes_and_totals": frappe.render_template(
|
||||
"templates/includes/cart/cart_payment_summary.html", context
|
||||
),
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'name': quotation.name
|
||||
}
|
||||
return {"name": quotation.name}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_shopping_cart_menu(context=None):
|
||||
if not context:
|
||||
context = get_cart_quotation()
|
||||
|
||||
return frappe.render_template('templates/includes/cart/cart_dropdown.html', context)
|
||||
return frappe.render_template("templates/includes/cart/cart_dropdown.html", context)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_new_address(doc):
|
||||
doc = frappe.parse_json(doc)
|
||||
doc.update({
|
||||
'doctype': 'Address'
|
||||
})
|
||||
doc.update({"doctype": "Address"})
|
||||
address = frappe.get_doc(doc)
|
||||
address.save(ignore_permissions=True)
|
||||
|
||||
return address
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def create_lead_for_item_inquiry(lead, subject, message):
|
||||
lead = frappe.parse_json(lead)
|
||||
lead_doc = frappe.new_doc('Lead')
|
||||
lead_doc = frappe.new_doc("Lead")
|
||||
for fieldname in ("lead_name", "company_name", "email_id", "phone"):
|
||||
lead_doc.set(fieldname, lead.get(fieldname))
|
||||
|
||||
lead_doc.set('lead_owner', '')
|
||||
lead_doc.set("lead_owner", "")
|
||||
|
||||
if not frappe.db.exists('Lead Source', 'Product Inquiry'):
|
||||
frappe.get_doc({
|
||||
'doctype': 'Lead Source',
|
||||
'source_name' : 'Product Inquiry'
|
||||
}).insert(ignore_permissions=True)
|
||||
if not frappe.db.exists("Lead Source", "Product Inquiry"):
|
||||
frappe.get_doc({"doctype": "Lead Source", "source_name": "Product Inquiry"}).insert(
|
||||
ignore_permissions=True
|
||||
)
|
||||
|
||||
lead_doc.set('source', 'Product Inquiry')
|
||||
lead_doc.set("source", "Product Inquiry")
|
||||
|
||||
try:
|
||||
lead_doc.save(ignore_permissions=True)
|
||||
except frappe.exceptions.DuplicateEntryError:
|
||||
frappe.clear_messages()
|
||||
lead_doc = frappe.get_doc('Lead', {'email_id': lead['email_id']})
|
||||
lead_doc = frappe.get_doc("Lead", {"email_id": lead["email_id"]})
|
||||
|
||||
lead_doc.add_comment('Comment', text='''
|
||||
lead_doc.add_comment(
|
||||
"Comment",
|
||||
text="""
|
||||
<div>
|
||||
<h5>{subject}</h5>
|
||||
<p>{message}</p>
|
||||
</div>
|
||||
'''.format(subject=subject, message=message))
|
||||
""".format(
|
||||
subject=subject, message=message
|
||||
),
|
||||
)
|
||||
|
||||
return lead_doc
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_terms_and_conditions(terms_name):
|
||||
return frappe.db.get_value('Terms and Conditions', terms_name, 'terms')
|
||||
return frappe.db.get_value("Terms and Conditions", terms_name, "terms")
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_cart_address(address_type, address_name):
|
||||
@@ -251,31 +265,35 @@ def update_cart_address(address_type, address_name):
|
||||
quotation.shipping_address_name = address_name
|
||||
quotation.shipping_address = address_display
|
||||
quotation.customer_address = quotation.customer_address or address_name
|
||||
address_doc = next((doc for doc in get_shipping_addresses() if doc["name"] == address_name), None)
|
||||
address_doc = next(
|
||||
(doc for doc in get_shipping_addresses() if doc["name"] == address_name), None
|
||||
)
|
||||
apply_cart_settings(quotation=quotation)
|
||||
|
||||
quotation.flags.ignore_permissions = True
|
||||
quotation.save()
|
||||
|
||||
context = get_cart_quotation(quotation)
|
||||
context['address'] = address_doc
|
||||
context["address"] = address_doc
|
||||
|
||||
return {
|
||||
"taxes": frappe.render_template("templates/includes/order/order_taxes.html",
|
||||
context),
|
||||
"address": frappe.render_template("templates/includes/cart/address_card.html",
|
||||
context)
|
||||
"taxes": frappe.render_template("templates/includes/order/order_taxes.html", context),
|
||||
"address": frappe.render_template("templates/includes/cart/address_card.html", context),
|
||||
}
|
||||
|
||||
|
||||
def guess_territory():
|
||||
territory = None
|
||||
geoip_country = frappe.session.get("session_country")
|
||||
if geoip_country:
|
||||
territory = frappe.db.get_value("Territory", geoip_country)
|
||||
|
||||
return territory or \
|
||||
frappe.db.get_value("E Commerce Settings", None, "territory") or \
|
||||
get_root_of("Territory")
|
||||
return (
|
||||
territory
|
||||
or frappe.db.get_value("E Commerce Settings", None, "territory")
|
||||
or get_root_of("Territory")
|
||||
)
|
||||
|
||||
|
||||
def decorate_quotation_doc(doc):
|
||||
for d in doc.get("items", []):
|
||||
@@ -288,50 +306,56 @@ def decorate_quotation_doc(doc):
|
||||
"Item",
|
||||
filters={"item_code": item_code},
|
||||
fieldname=["variant_of", "item_name", "image"],
|
||||
as_dict=True
|
||||
as_dict=True,
|
||||
)[0]
|
||||
item_code = variant_data.variant_of
|
||||
fields = fields[1:]
|
||||
d.web_item_name = variant_data.item_name
|
||||
|
||||
if variant_data.image: # get image from variant or template web item
|
||||
if variant_data.image: # get image from variant or template web item
|
||||
d.thumbnail = variant_data.image
|
||||
fields = fields[2:]
|
||||
|
||||
d.update(frappe.db.get_value(
|
||||
"Website Item",
|
||||
{"item_code": item_code},
|
||||
fields,
|
||||
as_dict=True)
|
||||
)
|
||||
d.update(frappe.db.get_value("Website Item", {"item_code": item_code}, fields, as_dict=True))
|
||||
|
||||
return doc
|
||||
|
||||
|
||||
def _get_cart_quotation(party=None):
|
||||
'''Return the open Quotation of type "Shopping Cart" or make a new one'''
|
||||
"""Return the open Quotation of type "Shopping Cart" or make a new one"""
|
||||
if not party:
|
||||
party = get_party()
|
||||
|
||||
quotation = frappe.get_all("Quotation", fields=["name"], filters=
|
||||
{"party_name": party.name, "contact_email": frappe.session.user, "order_type": "Shopping Cart", "docstatus": 0},
|
||||
order_by="modified desc", limit_page_length=1)
|
||||
quotation = frappe.get_all(
|
||||
"Quotation",
|
||||
fields=["name"],
|
||||
filters={
|
||||
"party_name": party.name,
|
||||
"contact_email": frappe.session.user,
|
||||
"order_type": "Shopping Cart",
|
||||
"docstatus": 0,
|
||||
},
|
||||
order_by="modified desc",
|
||||
limit_page_length=1,
|
||||
)
|
||||
|
||||
if quotation:
|
||||
qdoc = frappe.get_doc("Quotation", quotation[0].name)
|
||||
else:
|
||||
company = frappe.db.get_value("E Commerce Settings", None, ["company"])
|
||||
qdoc = frappe.get_doc({
|
||||
"doctype": "Quotation",
|
||||
"naming_series": get_shopping_cart_settings().quotation_series or "QTN-CART-",
|
||||
"quotation_to": party.doctype,
|
||||
"company": company,
|
||||
"order_type": "Shopping Cart",
|
||||
"status": "Draft",
|
||||
"docstatus": 0,
|
||||
"__islocal": 1,
|
||||
"party_name": party.name
|
||||
})
|
||||
qdoc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Quotation",
|
||||
"naming_series": get_shopping_cart_settings().quotation_series or "QTN-CART-",
|
||||
"quotation_to": party.doctype,
|
||||
"company": company,
|
||||
"order_type": "Shopping Cart",
|
||||
"status": "Draft",
|
||||
"docstatus": 0,
|
||||
"__islocal": 1,
|
||||
"party_name": party.name,
|
||||
}
|
||||
)
|
||||
|
||||
qdoc.contact_person = frappe.db.get_value("Contact", {"email_id": frappe.session.user})
|
||||
qdoc.contact_email = frappe.session.user
|
||||
@@ -342,6 +366,7 @@ def _get_cart_quotation(party=None):
|
||||
|
||||
return qdoc
|
||||
|
||||
|
||||
def update_party(fullname, company_name=None, mobile_no=None, phone=None):
|
||||
party = get_party()
|
||||
|
||||
@@ -369,6 +394,7 @@ def update_party(fullname, company_name=None, mobile_no=None, phone=None):
|
||||
qdoc.flags.ignore_permissions = True
|
||||
qdoc.save()
|
||||
|
||||
|
||||
def apply_cart_settings(party=None, quotation=None):
|
||||
if not party:
|
||||
party = get_party()
|
||||
@@ -385,14 +411,16 @@ def apply_cart_settings(party=None, quotation=None):
|
||||
|
||||
_apply_shipping_rule(party, quotation, cart_settings)
|
||||
|
||||
|
||||
def set_price_list_and_rate(quotation, cart_settings):
|
||||
"""set price list based on billing territory"""
|
||||
|
||||
_set_price_list(cart_settings, quotation)
|
||||
|
||||
# reset values
|
||||
quotation.price_list_currency = quotation.currency = \
|
||||
quotation.plc_conversion_rate = quotation.conversion_rate = None
|
||||
quotation.price_list_currency = (
|
||||
quotation.currency
|
||||
) = quotation.plc_conversion_rate = quotation.conversion_rate = None
|
||||
for item in quotation.get("items"):
|
||||
item.price_list_rate = item.discount_percentage = item.rate = item.amount = None
|
||||
|
||||
@@ -403,9 +431,11 @@ def set_price_list_and_rate(quotation, cart_settings):
|
||||
# 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(cart_settings, quotation=None):
|
||||
"""Set price list based on customer or shopping cart default"""
|
||||
from erpnext.accounts.party import get_default_price_list
|
||||
|
||||
party_name = quotation.get("party_name") if quotation else get_party().get("name")
|
||||
selling_price_list = None
|
||||
|
||||
@@ -422,23 +452,33 @@ def _set_price_list(cart_settings, quotation=None):
|
||||
|
||||
return selling_price_list
|
||||
|
||||
|
||||
def set_taxes(quotation, cart_settings):
|
||||
"""set taxes based on billing territory"""
|
||||
from erpnext.accounts.party import set_taxes
|
||||
|
||||
customer_group = frappe.db.get_value("Customer", quotation.party_name, "customer_group")
|
||||
|
||||
quotation.taxes_and_charges = set_taxes(quotation.party_name, "Customer",
|
||||
quotation.transaction_date, quotation.company, customer_group=customer_group, supplier_group=None,
|
||||
tax_category=quotation.tax_category, billing_address=quotation.customer_address,
|
||||
shipping_address=quotation.shipping_address_name, use_for_shopping_cart=1)
|
||||
#
|
||||
# # clear table
|
||||
quotation.taxes_and_charges = set_taxes(
|
||||
quotation.party_name,
|
||||
"Customer",
|
||||
quotation.transaction_date,
|
||||
quotation.company,
|
||||
customer_group=customer_group,
|
||||
supplier_group=None,
|
||||
tax_category=quotation.tax_category,
|
||||
billing_address=quotation.customer_address,
|
||||
shipping_address=quotation.shipping_address_name,
|
||||
use_for_shopping_cart=1,
|
||||
)
|
||||
#
|
||||
# # clear table
|
||||
quotation.set("taxes", [])
|
||||
#
|
||||
# # append taxes
|
||||
#
|
||||
# # append taxes
|
||||
quotation.append_taxes_from_master()
|
||||
|
||||
|
||||
def get_party(user=None):
|
||||
if not user:
|
||||
user = frappe.session.user
|
||||
@@ -447,14 +487,14 @@ def get_party(user=None):
|
||||
party = None
|
||||
|
||||
if contact_name:
|
||||
contact = frappe.get_doc('Contact', contact_name)
|
||||
contact = frappe.get_doc("Contact", contact_name)
|
||||
if contact.links:
|
||||
party_doctype = contact.links[0].link_doctype
|
||||
party = contact.links[0].link_name
|
||||
|
||||
cart_settings = frappe.get_doc("E Commerce Settings")
|
||||
|
||||
debtors_account = ''
|
||||
debtors_account = ""
|
||||
|
||||
if cart_settings.enable_checkout:
|
||||
debtors_account = get_debtors_account(cart_settings)
|
||||
@@ -468,57 +508,62 @@ def get_party(user=None):
|
||||
raise frappe.Redirect
|
||||
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.update(
|
||||
{
|
||||
"customer_name": fullname,
|
||||
"customer_type": "Individual",
|
||||
"customer_group": get_shopping_cart_settings().default_customer_group,
|
||||
"territory": get_root_of("Territory"),
|
||||
}
|
||||
)
|
||||
|
||||
if debtors_account:
|
||||
customer.update({
|
||||
"accounts": [{
|
||||
"company": cart_settings.company,
|
||||
"account": debtors_account
|
||||
}]
|
||||
})
|
||||
customer.update({"accounts": [{"company": cart_settings.company, "account": debtors_account}]})
|
||||
|
||||
customer.flags.ignore_mandatory = True
|
||||
customer.insert(ignore_permissions=True)
|
||||
|
||||
contact = frappe.new_doc("Contact")
|
||||
contact.update({
|
||||
"first_name": fullname,
|
||||
"email_ids": [{"email_id": user, "is_primary": 1}]
|
||||
})
|
||||
contact.append('links', dict(link_doctype='Customer', link_name=customer.name))
|
||||
contact.update({"first_name": fullname, "email_ids": [{"email_id": user, "is_primary": 1}]})
|
||||
contact.append("links", dict(link_doctype="Customer", link_name=customer.name))
|
||||
contact.flags.ignore_mandatory = True
|
||||
contact.insert(ignore_permissions=True)
|
||||
|
||||
return customer
|
||||
|
||||
|
||||
def get_debtors_account(cart_settings):
|
||||
if not cart_settings.payment_gateway_account:
|
||||
frappe.throw(_("Payment Gateway Account not set"), _("Mandatory"))
|
||||
|
||||
payment_gateway_account_currency = \
|
||||
frappe.get_doc("Payment Gateway Account", cart_settings.payment_gateway_account).currency
|
||||
payment_gateway_account_currency = frappe.get_doc(
|
||||
"Payment Gateway Account", cart_settings.payment_gateway_account
|
||||
).currency
|
||||
|
||||
account_name = _("Debtors ({0})").format(payment_gateway_account_currency)
|
||||
|
||||
debtors_account_name = get_account_name("Receivable", "Asset", is_group=0,\
|
||||
account_currency=payment_gateway_account_currency, company=cart_settings.company)
|
||||
debtors_account_name = get_account_name(
|
||||
"Receivable",
|
||||
"Asset",
|
||||
is_group=0,
|
||||
account_currency=payment_gateway_account_currency,
|
||||
company=cart_settings.company,
|
||||
)
|
||||
|
||||
if not debtors_account_name:
|
||||
debtors_account = frappe.get_doc({
|
||||
"doctype": "Account",
|
||||
"account_type": "Receivable",
|
||||
"root_type": "Asset",
|
||||
"is_group": 0,
|
||||
"parent_account": get_account_name(root_type="Asset", is_group=1, company=cart_settings.company),
|
||||
"account_name": account_name,
|
||||
"currency": payment_gateway_account_currency
|
||||
}).insert(ignore_permissions=True)
|
||||
debtors_account = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Account",
|
||||
"account_type": "Receivable",
|
||||
"root_type": "Asset",
|
||||
"is_group": 0,
|
||||
"parent_account": get_account_name(
|
||||
root_type="Asset", is_group=1, company=cart_settings.company
|
||||
),
|
||||
"account_name": account_name,
|
||||
"currency": payment_gateway_account_currency,
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
return debtors_account.name
|
||||
|
||||
@@ -526,26 +571,31 @@ def get_debtors_account(cart_settings):
|
||||
return debtors_account_name
|
||||
|
||||
|
||||
def get_address_docs(doctype=None, txt=None, filters=None, limit_start=0, limit_page_length=20,
|
||||
party=None):
|
||||
def get_address_docs(
|
||||
doctype=None, txt=None, filters=None, limit_start=0, limit_page_length=20, party=None
|
||||
):
|
||||
if not party:
|
||||
party = get_party()
|
||||
|
||||
if not party:
|
||||
return []
|
||||
|
||||
address_names = frappe.db.get_all('Dynamic Link', fields=('parent'),
|
||||
filters=dict(parenttype='Address', link_doctype=party.doctype, link_name=party.name))
|
||||
address_names = frappe.db.get_all(
|
||||
"Dynamic Link",
|
||||
fields=("parent"),
|
||||
filters=dict(parenttype="Address", link_doctype=party.doctype, link_name=party.name),
|
||||
)
|
||||
|
||||
out = []
|
||||
|
||||
for a in address_names:
|
||||
address = frappe.get_doc('Address', a.parent)
|
||||
address = frappe.get_doc("Address", a.parent)
|
||||
address.display = get_address_display(address.as_dict())
|
||||
out.append(address)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def apply_shipping_rule(shipping_rule):
|
||||
quotation = _get_cart_quotation()
|
||||
@@ -559,6 +609,7 @@ def apply_shipping_rule(shipping_rule):
|
||||
|
||||
return get_cart_quotation(quotation)
|
||||
|
||||
|
||||
def _apply_shipping_rule(party=None, quotation=None, cart_settings=None):
|
||||
if not quotation.shipping_rule:
|
||||
shipping_rules = get_shipping_rules(quotation, cart_settings)
|
||||
@@ -573,6 +624,7 @@ def _apply_shipping_rule(party=None, quotation=None, cart_settings=None):
|
||||
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(quotation)
|
||||
|
||||
@@ -581,6 +633,7 @@ def get_applicable_shipping_rules(party=None, quotation=None):
|
||||
# we need this in sorted order as per the position of the rule in the settings page
|
||||
return [[rule, rule] for rule in shipping_rules]
|
||||
|
||||
|
||||
def get_shipping_rules(quotation=None, cart_settings=None):
|
||||
if not quotation:
|
||||
quotation = _get_cart_quotation()
|
||||
@@ -593,26 +646,24 @@ def get_shipping_rules(quotation=None, cart_settings=None):
|
||||
sr = frappe.qb.DocType("Shipping Rule")
|
||||
query = (
|
||||
frappe.qb.from_(sr_country)
|
||||
.join(sr).on(sr.name == sr_country.parent)
|
||||
.join(sr)
|
||||
.on(sr.name == sr_country.parent)
|
||||
.select(sr.name)
|
||||
.distinct()
|
||||
.where(
|
||||
(sr_country.country == country)
|
||||
& (sr.disabled != 1)
|
||||
)
|
||||
.where((sr_country.country == country) & (sr.disabled != 1))
|
||||
)
|
||||
result = query.run(as_list=True)
|
||||
shipping_rules = [x[0] for x in result]
|
||||
|
||||
return shipping_rules
|
||||
|
||||
|
||||
def get_address_territory(address_name):
|
||||
"""Tries to match city, state and country of address to existing territory"""
|
||||
territory = None
|
||||
|
||||
if address_name:
|
||||
address_fields = frappe.db.get_value("Address", address_name,
|
||||
["city", "state", "country"])
|
||||
address_fields = frappe.db.get_value("Address", address_name, ["city", "state", "country"])
|
||||
for value in address_fields:
|
||||
territory = frappe.db.get_value("Territory", value)
|
||||
if territory:
|
||||
@@ -620,9 +671,11 @@ def get_address_territory(address_name):
|
||||
|
||||
return territory
|
||||
|
||||
|
||||
def show_terms(doc):
|
||||
return doc.tc_name
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def apply_coupon_code(applied_code, applied_referral_sales_partner):
|
||||
quotation = True
|
||||
@@ -630,13 +683,14 @@ def apply_coupon_code(applied_code, applied_referral_sales_partner):
|
||||
if not applied_code:
|
||||
frappe.throw(_("Please enter a coupon code"))
|
||||
|
||||
coupon_list = frappe.get_all('Coupon Code', filters={'coupon_code': applied_code})
|
||||
coupon_list = frappe.get_all("Coupon Code", filters={"coupon_code": applied_code})
|
||||
if not coupon_list:
|
||||
frappe.throw(_("Please enter a valid coupon code"))
|
||||
|
||||
coupon_name = coupon_list[0].name
|
||||
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code
|
||||
|
||||
validate_coupon_code(coupon_name)
|
||||
quotation = _get_cart_quotation()
|
||||
quotation.coupon_code = coupon_name
|
||||
@@ -644,7 +698,9 @@ def apply_coupon_code(applied_code, applied_referral_sales_partner):
|
||||
quotation.save()
|
||||
|
||||
if applied_referral_sales_partner:
|
||||
sales_partner_list = frappe.get_all('Sales Partner', filters={'referral_code': applied_referral_sales_partner})
|
||||
sales_partner_list = frappe.get_all(
|
||||
"Sales Partner", filters={"referral_code": applied_referral_sales_partner}
|
||||
)
|
||||
if sales_partner_list:
|
||||
sales_partner_name = sales_partner_list[0].name
|
||||
quotation.referral_sales_partner = sales_partner_name
|
||||
|
||||
@@ -22,16 +22,17 @@ def get_product_info_for_website(item_code, skip_quotation_creation=False):
|
||||
cart_settings = get_shopping_cart_settings()
|
||||
if not cart_settings.enabled:
|
||||
# return settings even if cart is disabled
|
||||
return frappe._dict({
|
||||
"product_info": {},
|
||||
"cart_settings": cart_settings
|
||||
})
|
||||
return frappe._dict({"product_info": {}, "cart_settings": cart_settings})
|
||||
|
||||
cart_quotation = frappe._dict()
|
||||
if not skip_quotation_creation:
|
||||
cart_quotation = _get_cart_quotation()
|
||||
|
||||
selling_price_list = cart_quotation.get("selling_price_list") if cart_quotation else _set_price_list(cart_settings, None)
|
||||
selling_price_list = (
|
||||
cart_quotation.get("selling_price_list")
|
||||
if cart_quotation
|
||||
else _set_price_list(cart_settings, None)
|
||||
)
|
||||
|
||||
price = {}
|
||||
if cart_settings.show_price:
|
||||
@@ -40,10 +41,7 @@ def get_product_info_for_website(item_code, skip_quotation_creation=False):
|
||||
# If not logged in, check if price is hidden for guest.
|
||||
if not is_guest or not cart_settings.hide_price_for_guest:
|
||||
price = get_price(
|
||||
item_code,
|
||||
selling_price_list,
|
||||
cart_settings.default_customer_group,
|
||||
cart_settings.company
|
||||
item_code, selling_price_list, cart_settings.default_customer_group, cart_settings.company
|
||||
)
|
||||
|
||||
stock_status = None
|
||||
@@ -59,7 +57,7 @@ def get_product_info_for_website(item_code, skip_quotation_creation=False):
|
||||
"price": price,
|
||||
"qty": 0,
|
||||
"uom": frappe.db.get_value("Item", item_code, "stock_uom"),
|
||||
"sales_uom": frappe.db.get_value("Item", item_code, "sales_uom")
|
||||
"sales_uom": frappe.db.get_value("Item", item_code, "sales_uom"),
|
||||
}
|
||||
|
||||
if stock_status:
|
||||
@@ -67,7 +65,11 @@ def get_product_info_for_website(item_code, skip_quotation_creation=False):
|
||||
product_info["on_backorder"] = True
|
||||
else:
|
||||
product_info["stock_qty"] = stock_status.stock_qty
|
||||
product_info["in_stock"] = stock_status.in_stock if stock_status.is_stock_item else get_non_stock_item_status(item_code, "website_warehouse")
|
||||
product_info["in_stock"] = (
|
||||
stock_status.in_stock
|
||||
if stock_status.is_stock_item
|
||||
else get_non_stock_item_status(item_code, "website_warehouse")
|
||||
)
|
||||
product_info["show_stock_qty"] = show_quantity_in_website()
|
||||
|
||||
if product_info["price"]:
|
||||
@@ -76,14 +78,14 @@ def get_product_info_for_website(item_code, skip_quotation_creation=False):
|
||||
if item:
|
||||
product_info["qty"] = item[0].qty
|
||||
|
||||
return frappe._dict({
|
||||
"product_info": product_info,
|
||||
"cart_settings": cart_settings
|
||||
})
|
||||
return frappe._dict({"product_info": product_info, "cart_settings": cart_settings})
|
||||
|
||||
|
||||
def set_product_info_for_website(item):
|
||||
"""set product price uom for website"""
|
||||
product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get("product_info")
|
||||
product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get(
|
||||
"product_info"
|
||||
)
|
||||
|
||||
if product_info:
|
||||
item.update(product_info)
|
||||
|
||||
@@ -22,18 +22,19 @@ from erpnext.tests.utils import create_test_contact_and_address
|
||||
|
||||
class TestShoppingCart(unittest.TestCase):
|
||||
"""
|
||||
Note:
|
||||
Shopping Cart == Quotation
|
||||
Note:
|
||||
Shopping Cart == Quotation
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
frappe.set_user("Administrator")
|
||||
create_test_contact_and_address()
|
||||
self.enable_shopping_cart()
|
||||
if not frappe.db.exists("Website Item", {"item_code": "_Test Item"}):
|
||||
make_website_item(frappe.get_cached_doc("Item", "_Test Item"))
|
||||
make_website_item(frappe.get_cached_doc("Item", "_Test Item"))
|
||||
|
||||
if not frappe.db.exists("Website Item", {"item_code": "_Test Item 2"}):
|
||||
make_website_item(frappe.get_cached_doc("Item", "_Test Item 2"))
|
||||
make_website_item(frappe.get_cached_doc("Item", "_Test Item 2"))
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
@@ -50,8 +51,10 @@ class TestShoppingCart(unittest.TestCase):
|
||||
# test if lead is created and quotation with new lead is fetched
|
||||
quotation = _get_cart_quotation()
|
||||
self.assertEqual(quotation.quotation_to, "Customer")
|
||||
self.assertEqual(quotation.contact_person,
|
||||
frappe.db.get_value("Contact", dict(email_id="test_cart_user@example.com")))
|
||||
self.assertEqual(
|
||||
quotation.contact_person,
|
||||
frappe.db.get_value("Contact", dict(email_id="test_cart_user@example.com")),
|
||||
)
|
||||
self.assertEqual(quotation.contact_email, frappe.session.user)
|
||||
|
||||
return quotation
|
||||
@@ -65,7 +68,9 @@ class TestShoppingCart(unittest.TestCase):
|
||||
self.assertEqual(quotation.contact_email, frappe.session.user)
|
||||
return quotation
|
||||
|
||||
self.login_as_customer("test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer")
|
||||
self.login_as_customer(
|
||||
"test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer"
|
||||
)
|
||||
validate_quotation()
|
||||
|
||||
self.login_as_customer()
|
||||
@@ -131,46 +136,55 @@ class TestShoppingCart(unittest.TestCase):
|
||||
|
||||
from erpnext.accounts.party import set_taxes
|
||||
|
||||
tax_rule_master = set_taxes(quotation.party_name, "Customer",
|
||||
quotation.transaction_date, quotation.company, customer_group=None, supplier_group=None,
|
||||
tax_category=quotation.tax_category, billing_address=quotation.customer_address,
|
||||
shipping_address=quotation.shipping_address_name, use_for_shopping_cart=1)
|
||||
tax_rule_master = set_taxes(
|
||||
quotation.party_name,
|
||||
"Customer",
|
||||
quotation.transaction_date,
|
||||
quotation.company,
|
||||
customer_group=None,
|
||||
supplier_group=None,
|
||||
tax_category=quotation.tax_category,
|
||||
billing_address=quotation.customer_address,
|
||||
shipping_address=quotation.shipping_address_name,
|
||||
use_for_shopping_cart=1,
|
||||
)
|
||||
|
||||
self.assertEqual(quotation.taxes_and_charges, tax_rule_master)
|
||||
self.assertEqual(quotation.total_taxes_and_charges, 1000.0)
|
||||
|
||||
self.remove_test_quotation(quotation)
|
||||
|
||||
@change_settings("E Commerce Settings",{
|
||||
"company": "_Test Company",
|
||||
"enabled": 1,
|
||||
"default_customer_group": "_Test Customer Group",
|
||||
"price_list": "_Test Price List India",
|
||||
"show_price": 1
|
||||
})
|
||||
@change_settings(
|
||||
"E Commerce Settings",
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"enabled": 1,
|
||||
"default_customer_group": "_Test Customer Group",
|
||||
"price_list": "_Test Price List India",
|
||||
"show_price": 1,
|
||||
},
|
||||
)
|
||||
def test_add_item_variant_without_web_item_to_cart(self):
|
||||
"Test adding Variants having no Website Items in cart via Template Web Item."
|
||||
from erpnext.controllers.item_variant import create_variant
|
||||
from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
template_item = make_item("Test-Tshirt-Temp", {
|
||||
"has_variant": 1,
|
||||
"variant_based_on": "Item Attribute",
|
||||
"attributes": [
|
||||
{"attribute": "Test Size"},
|
||||
{"attribute": "Test Colour"}
|
||||
]
|
||||
})
|
||||
variant = create_variant("Test-Tshirt-Temp", {
|
||||
"Test Size": "Small", "Test Colour": "Red"
|
||||
})
|
||||
template_item = make_item(
|
||||
"Test-Tshirt-Temp",
|
||||
{
|
||||
"has_variant": 1,
|
||||
"variant_based_on": "Item Attribute",
|
||||
"attributes": [{"attribute": "Test Size"}, {"attribute": "Test Colour"}],
|
||||
},
|
||||
)
|
||||
variant = create_variant("Test-Tshirt-Temp", {"Test Size": "Small", "Test Colour": "Red"})
|
||||
variant.save()
|
||||
make_website_item(template_item) # publish template not variant
|
||||
make_website_item(template_item) # publish template not variant
|
||||
|
||||
update_cart("Test-Tshirt-Temp-S-R", 1)
|
||||
|
||||
cart = get_cart_quotation() # test if cart page gets data without errors
|
||||
cart = get_cart_quotation() # test if cart page gets data without errors
|
||||
doc = cart.get("doc")
|
||||
|
||||
self.assertEqual(doc.get("items")[0].item_name, "Test-Tshirt-Temp-S-R")
|
||||
@@ -178,16 +192,14 @@ class TestShoppingCart(unittest.TestCase):
|
||||
# test if items are rendered without error
|
||||
frappe.render_template("templates/includes/cart/cart_items.html", cart)
|
||||
|
||||
@change_settings("E Commerce Settings",{
|
||||
"save_quotations_as_draft": 1
|
||||
})
|
||||
@change_settings("E Commerce Settings", {"save_quotations_as_draft": 1})
|
||||
def test_cart_without_checkout_and_draft_quotation(self):
|
||||
"Test impact of 'save_quotations_as_draft' checkbox."
|
||||
frappe.local.shopping_cart_settings = None
|
||||
|
||||
# add item to cart
|
||||
update_cart("_Test Item", 1)
|
||||
quote_name = request_for_quotation() # Request for Quote
|
||||
quote_name = request_for_quotation() # Request for Quote
|
||||
quote_doctstatus = cint(frappe.db.get_value("Quotation", quote_name, "docstatus"))
|
||||
|
||||
self.assertEqual(quote_doctstatus, 0)
|
||||
@@ -195,7 +207,7 @@ class TestShoppingCart(unittest.TestCase):
|
||||
frappe.db.set_value("E Commerce Settings", None, "save_quotations_as_draft", 0)
|
||||
frappe.local.shopping_cart_settings = None
|
||||
update_cart("_Test Item", 1)
|
||||
quote_name = request_for_quotation() # Request for Quote
|
||||
quote_name = request_for_quotation() # Request for Quote
|
||||
quote_doctstatus = cint(frappe.db.get_value("Quotation", quote_name, "docstatus"))
|
||||
|
||||
self.assertEqual(quote_doctstatus, 1)
|
||||
@@ -219,16 +231,13 @@ class TestShoppingCart(unittest.TestCase):
|
||||
"contact_email": frappe.session.user,
|
||||
"selling_price_list": "_Test Price List Rest of the World",
|
||||
"currency": "USD",
|
||||
"taxes_and_charges" : "_Test Tax 1 - _TC",
|
||||
"conversion_rate":1,
|
||||
"transaction_date" : nowdate(),
|
||||
"valid_till" : add_months(nowdate(), 1),
|
||||
"items": [{
|
||||
"item_code": "_Test Item",
|
||||
"qty": 1
|
||||
}],
|
||||
"taxes_and_charges": "_Test Tax 1 - _TC",
|
||||
"conversion_rate": 1,
|
||||
"transaction_date": nowdate(),
|
||||
"valid_till": add_months(nowdate(), 1),
|
||||
"items": [{"item_code": "_Test Item", "qty": 1}],
|
||||
"taxes": frappe.get_doc("Sales Taxes and Charges Template", "_Test Tax 1 - _TC").taxes,
|
||||
"company": "_Test Company"
|
||||
"company": "_Test Company",
|
||||
}
|
||||
|
||||
quotation.update(values)
|
||||
@@ -245,29 +254,36 @@ class TestShoppingCart(unittest.TestCase):
|
||||
def enable_shopping_cart(self):
|
||||
settings = frappe.get_doc("E Commerce Settings", "E Commerce Settings")
|
||||
|
||||
settings.update({
|
||||
"enabled": 1,
|
||||
"company": "_Test Company",
|
||||
"default_customer_group": "_Test Customer Group",
|
||||
"quotation_series": "_T-Quotation-",
|
||||
"price_list": "_Test Price List India"
|
||||
})
|
||||
settings.update(
|
||||
{
|
||||
"enabled": 1,
|
||||
"company": "_Test Company",
|
||||
"default_customer_group": "_Test Customer Group",
|
||||
"quotation_series": "_T-Quotation-",
|
||||
"price_list": "_Test Price List 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()
|
||||
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
|
||||
@@ -282,31 +298,49 @@ class TestShoppingCart(unittest.TestCase):
|
||||
self.create_user_if_not_exists("test_cart_user@example.com")
|
||||
frappe.set_user("test_cart_user@example.com")
|
||||
|
||||
def login_as_customer(self, email="test_contact_customer@example.com", name="_Test Contact For _Test Customer"):
|
||||
def login_as_customer(
|
||||
self, email="test_contact_customer@example.com", name="_Test Contact For _Test Customer"
|
||||
):
|
||||
self.create_user_if_not_exists(email, name)
|
||||
frappe.set_user(email)
|
||||
|
||||
def clear_existing_quotations(self):
|
||||
quotations = frappe.get_all("Quotation", filters={
|
||||
"party_name": get_party().name,
|
||||
"order_type": "Shopping Cart",
|
||||
"docstatus": 0
|
||||
}, order_by="modified desc", pluck="name")
|
||||
quotations = frappe.get_all(
|
||||
"Quotation",
|
||||
filters={"party_name": get_party().name, "order_type": "Shopping Cart", "docstatus": 0},
|
||||
order_by="modified desc",
|
||||
pluck="name",
|
||||
)
|
||||
|
||||
for quotation in quotations:
|
||||
frappe.delete_doc("Quotation", quotation, ignore_permissions=True, force=True)
|
||||
|
||||
def create_user_if_not_exists(self, email, first_name = None):
|
||||
def create_user_if_not_exists(self, email, first_name=None):
|
||||
if frappe.db.exists("User", email):
|
||||
return
|
||||
|
||||
frappe.get_doc({
|
||||
"doctype": "User",
|
||||
"user_type": "Website User",
|
||||
"email": email,
|
||||
"send_welcome_email": 0,
|
||||
"first_name": first_name or email.split("@")[0]
|
||||
}).insert(ignore_permissions=True)
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "User",
|
||||
"user_type": "Website User",
|
||||
"email": email,
|
||||
"send_welcome_email": 0,
|
||||
"first_name": first_name or email.split("@")[0],
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
test_dependencies = ["Sales Taxes and Charges Template", "Price List", "Item Price", "Shipping Rule", "Currency Exchange",
|
||||
"Customer Group", "Lead", "Customer", "Contact", "Address", "Item", "Tax Rule"]
|
||||
|
||||
test_dependencies = [
|
||||
"Sales Taxes and Charges Template",
|
||||
"Price List",
|
||||
"Item Price",
|
||||
"Shipping Rule",
|
||||
"Currency Exchange",
|
||||
"Customer Group",
|
||||
"Lead",
|
||||
"Customer",
|
||||
"Contact",
|
||||
"Address",
|
||||
"Item",
|
||||
"Tax Rule",
|
||||
]
|
||||
|
||||
@@ -6,12 +6,15 @@ from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import i
|
||||
|
||||
|
||||
def show_cart_count():
|
||||
if (is_cart_enabled() and
|
||||
frappe.db.get_value("User", frappe.session.user, "user_type") == "Website User"):
|
||||
if (
|
||||
is_cart_enabled()
|
||||
and frappe.db.get_value("User", frappe.session.user, "user_type") == "Website User"
|
||||
):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def set_cart_count(login_manager):
|
||||
# since this is run only on hooks login event
|
||||
# make sure user is already a customer
|
||||
@@ -28,21 +31,24 @@ def set_cart_count(login_manager):
|
||||
# cart count is calculated from this quotation's items
|
||||
set_cart_count()
|
||||
|
||||
|
||||
def clear_cart_count(login_manager):
|
||||
if show_cart_count():
|
||||
frappe.local.cookie_manager.delete_cookie("cart_count")
|
||||
|
||||
|
||||
def update_website_context(context):
|
||||
cart_enabled = is_cart_enabled()
|
||||
context["shopping_cart_enabled"] = cart_enabled
|
||||
|
||||
|
||||
def is_customer():
|
||||
if frappe.session.user and frappe.session.user != "Guest":
|
||||
contact_name = frappe.get_value("Contact", {"email_id": frappe.session.user})
|
||||
if contact_name:
|
||||
contact = frappe.get_doc('Contact', contact_name)
|
||||
contact = frappe.get_doc("Contact", contact_name)
|
||||
for link in contact.links:
|
||||
if link.link_doctype == 'Customer':
|
||||
if link.link_doctype == "Customer":
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@@ -6,63 +6,60 @@ class ItemVariantsCacheManager:
|
||||
self.item_code = item_code
|
||||
|
||||
def get_item_variants_data(self):
|
||||
val = frappe.cache().hget('item_variants_data', self.item_code)
|
||||
val = frappe.cache().hget("item_variants_data", self.item_code)
|
||||
|
||||
if not val:
|
||||
self.build_cache()
|
||||
|
||||
return frappe.cache().hget('item_variants_data', self.item_code)
|
||||
|
||||
return frappe.cache().hget("item_variants_data", self.item_code)
|
||||
|
||||
def get_attribute_value_item_map(self):
|
||||
val = frappe.cache().hget('attribute_value_item_map', self.item_code)
|
||||
val = frappe.cache().hget("attribute_value_item_map", self.item_code)
|
||||
|
||||
if not val:
|
||||
self.build_cache()
|
||||
|
||||
return frappe.cache().hget('attribute_value_item_map', self.item_code)
|
||||
|
||||
return frappe.cache().hget("attribute_value_item_map", self.item_code)
|
||||
|
||||
def get_item_attribute_value_map(self):
|
||||
val = frappe.cache().hget('item_attribute_value_map', self.item_code)
|
||||
val = frappe.cache().hget("item_attribute_value_map", self.item_code)
|
||||
|
||||
if not val:
|
||||
self.build_cache()
|
||||
|
||||
return frappe.cache().hget('item_attribute_value_map', self.item_code)
|
||||
|
||||
return frappe.cache().hget("item_attribute_value_map", self.item_code)
|
||||
|
||||
def get_optional_attributes(self):
|
||||
val = frappe.cache().hget('optional_attributes', self.item_code)
|
||||
val = frappe.cache().hget("optional_attributes", self.item_code)
|
||||
|
||||
if not val:
|
||||
self.build_cache()
|
||||
|
||||
return frappe.cache().hget('optional_attributes', self.item_code)
|
||||
return frappe.cache().hget("optional_attributes", self.item_code)
|
||||
|
||||
def get_ordered_attribute_values(self):
|
||||
val = frappe.cache().get_value('ordered_attribute_values_map')
|
||||
if val: return val
|
||||
val = frappe.cache().get_value("ordered_attribute_values_map")
|
||||
if val:
|
||||
return val
|
||||
|
||||
all_attribute_values = frappe.get_all('Item Attribute Value',
|
||||
['attribute_value', 'idx', 'parent'], order_by='idx asc')
|
||||
all_attribute_values = frappe.get_all(
|
||||
"Item Attribute Value", ["attribute_value", "idx", "parent"], order_by="idx asc"
|
||||
)
|
||||
|
||||
ordered_attribute_values_map = frappe._dict({})
|
||||
for d in all_attribute_values:
|
||||
ordered_attribute_values_map.setdefault(d.parent, []).append(d.attribute_value)
|
||||
|
||||
frappe.cache().set_value('ordered_attribute_values_map', ordered_attribute_values_map)
|
||||
frappe.cache().set_value("ordered_attribute_values_map", ordered_attribute_values_map)
|
||||
return ordered_attribute_values_map
|
||||
|
||||
def build_cache(self):
|
||||
parent_item_code = self.item_code
|
||||
|
||||
attributes = [
|
||||
a.attribute for a in frappe.get_all(
|
||||
'Item Variant Attribute',
|
||||
{'parent': parent_item_code},
|
||||
['attribute'],
|
||||
order_by='idx asc'
|
||||
a.attribute
|
||||
for a in frappe.get_all(
|
||||
"Item Variant Attribute", {"parent": parent_item_code}, ["attribute"], order_by="idx asc"
|
||||
)
|
||||
]
|
||||
|
||||
@@ -71,13 +68,11 @@ class ItemVariantsCacheManager:
|
||||
item = frappe.qb.DocType("Item")
|
||||
query = (
|
||||
frappe.qb.from_(iva)
|
||||
.join(item).on(item.name == iva.parent)
|
||||
.select(
|
||||
iva.parent, iva.attribute, iva.attribute_value
|
||||
).where(
|
||||
(iva.variant_of == parent_item_code)
|
||||
& (item.disabled == 0)
|
||||
).orderby(iva.name)
|
||||
.join(item)
|
||||
.on(item.name == iva.parent)
|
||||
.select(iva.parent, iva.attribute, iva.attribute_value)
|
||||
.where((iva.variant_of == parent_item_code) & (item.disabled == 0))
|
||||
.orderby(iva.name)
|
||||
)
|
||||
item_variants_data = query.run()
|
||||
|
||||
@@ -97,13 +92,18 @@ class ItemVariantsCacheManager:
|
||||
if attribute not in attr_dict:
|
||||
optional_attributes.add(attribute)
|
||||
|
||||
frappe.cache().hset('attribute_value_item_map', parent_item_code, attribute_value_item_map)
|
||||
frappe.cache().hset('item_attribute_value_map', parent_item_code, item_attribute_value_map)
|
||||
frappe.cache().hset('item_variants_data', parent_item_code, item_variants_data)
|
||||
frappe.cache().hset('optional_attributes', parent_item_code, optional_attributes)
|
||||
frappe.cache().hset("attribute_value_item_map", parent_item_code, attribute_value_item_map)
|
||||
frappe.cache().hset("item_attribute_value_map", parent_item_code, item_attribute_value_map)
|
||||
frappe.cache().hset("item_variants_data", parent_item_code, item_variants_data)
|
||||
frappe.cache().hset("optional_attributes", parent_item_code, optional_attributes)
|
||||
|
||||
def clear_cache(self):
|
||||
keys = ['attribute_value_item_map', 'item_attribute_value_map', 'item_variants_data', 'optional_attributes']
|
||||
keys = [
|
||||
"attribute_value_item_map",
|
||||
"item_attribute_value_map",
|
||||
"item_variants_data",
|
||||
"optional_attributes",
|
||||
]
|
||||
|
||||
for key in keys:
|
||||
frappe.cache().hdel(key, self.item_code)
|
||||
@@ -114,15 +114,17 @@ class ItemVariantsCacheManager:
|
||||
|
||||
|
||||
def build_cache(item_code):
|
||||
frappe.cache().hset('item_cache_build_in_progress', item_code, 1)
|
||||
frappe.cache().hset("item_cache_build_in_progress", item_code, 1)
|
||||
i = ItemVariantsCacheManager(item_code)
|
||||
i.build_cache()
|
||||
frappe.cache().hset('item_cache_build_in_progress', item_code, 0)
|
||||
frappe.cache().hset("item_cache_build_in_progress", item_code, 0)
|
||||
|
||||
|
||||
def enqueue_build_cache(item_code):
|
||||
if frappe.cache().hget('item_cache_build_in_progress', item_code):
|
||||
if frappe.cache().hget("item_cache_build_in_progress", item_code):
|
||||
return
|
||||
frappe.enqueue(
|
||||
"erpnext.e_commerce.variant_selector.item_variants_cache.build_cache",
|
||||
item_code=item_code, queue='long'
|
||||
item_code=item_code,
|
||||
queue="long",
|
||||
)
|
||||
|
||||
@@ -11,41 +11,43 @@ from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
test_dependencies = ["Item"]
|
||||
|
||||
class TestVariantSelector(FrappeTestCase):
|
||||
|
||||
class TestVariantSelector(FrappeTestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
template_item = make_item("Test-Tshirt-Temp", {
|
||||
"has_variant": 1,
|
||||
"variant_based_on": "Item Attribute",
|
||||
"attributes": [
|
||||
{"attribute": "Test Size"},
|
||||
{"attribute": "Test Colour"}
|
||||
]
|
||||
})
|
||||
template_item = make_item(
|
||||
"Test-Tshirt-Temp",
|
||||
{
|
||||
"has_variant": 1,
|
||||
"variant_based_on": "Item Attribute",
|
||||
"attributes": [{"attribute": "Test Size"}, {"attribute": "Test Colour"}],
|
||||
},
|
||||
)
|
||||
|
||||
# create L-R, L-G, M-R, M-G and S-R
|
||||
for size in ("Large", "Medium",):
|
||||
for colour in ("Red", "Green",):
|
||||
variant = create_variant("Test-Tshirt-Temp", {
|
||||
"Test Size": size, "Test Colour": colour
|
||||
})
|
||||
for size in (
|
||||
"Large",
|
||||
"Medium",
|
||||
):
|
||||
for colour in (
|
||||
"Red",
|
||||
"Green",
|
||||
):
|
||||
variant = create_variant("Test-Tshirt-Temp", {"Test Size": size, "Test Colour": colour})
|
||||
variant.save()
|
||||
|
||||
variant = create_variant("Test-Tshirt-Temp", {
|
||||
"Test Size": "Small", "Test Colour": "Red"
|
||||
})
|
||||
variant = create_variant("Test-Tshirt-Temp", {"Test Size": "Small", "Test Colour": "Red"})
|
||||
variant.save()
|
||||
|
||||
make_website_item(template_item) # publish template not variants
|
||||
make_website_item(template_item) # publish template not variants
|
||||
|
||||
def test_item_attributes(self):
|
||||
"""
|
||||
Test if the right attributes are fetched in the popup.
|
||||
(Attributes must only come from active items)
|
||||
Test if the right attributes are fetched in the popup.
|
||||
(Attributes must only come from active items)
|
||||
|
||||
Attribute selection must not be linked to Website Items.
|
||||
Attribute selection must not be linked to Website Items.
|
||||
"""
|
||||
from erpnext.e_commerce.variant_selector.utils import get_attributes_and_values
|
||||
|
||||
@@ -53,14 +55,14 @@ class TestVariantSelector(FrappeTestCase):
|
||||
|
||||
self.assertEqual(attr_data[0]["attribute"], "Test Size")
|
||||
self.assertEqual(attr_data[1]["attribute"], "Test Colour")
|
||||
self.assertEqual(len(attr_data[0]["values"]), 3) # ['Small', 'Medium', 'Large']
|
||||
self.assertEqual(len(attr_data[1]["values"]), 2) # ['Red', 'Green']
|
||||
self.assertEqual(len(attr_data[0]["values"]), 3) # ['Small', 'Medium', 'Large']
|
||||
self.assertEqual(len(attr_data[1]["values"]), 2) # ['Red', 'Green']
|
||||
|
||||
# disable small red tshirt, now there are no small tshirts.
|
||||
# but there are some red tshirts
|
||||
small_variant = frappe.get_doc("Item", "Test-Tshirt-Temp-S-R")
|
||||
small_variant.disabled = 1
|
||||
small_variant.save() # trigger cache rebuild
|
||||
small_variant.save() # trigger cache rebuild
|
||||
|
||||
attr_data = get_attributes_and_values("Test-Tshirt-Temp")
|
||||
|
||||
@@ -73,14 +75,16 @@ class TestVariantSelector(FrappeTestCase):
|
||||
|
||||
def test_next_item_variant_values(self):
|
||||
"""
|
||||
Test if on selecting an attribute value, the next possible values
|
||||
are filtered accordingly.
|
||||
Values that dont apply should not be fetched.
|
||||
E.g.
|
||||
There is a ** Small-Red ** Tshirt. No other colour in this size.
|
||||
On selecting ** Small **, only ** Red ** should be selectable next.
|
||||
Test if on selecting an attribute value, the next possible values
|
||||
are filtered accordingly.
|
||||
Values that dont apply should not be fetched.
|
||||
E.g.
|
||||
There is a ** Small-Red ** Tshirt. No other colour in this size.
|
||||
On selecting ** Small **, only ** Red ** should be selectable next.
|
||||
"""
|
||||
next_values = get_next_attribute_and_values("Test-Tshirt-Temp", selected_attributes={"Test Size": "Small"})
|
||||
next_values = get_next_attribute_and_values(
|
||||
"Test-Tshirt-Temp", selected_attributes={"Test Size": "Small"}
|
||||
)
|
||||
next_colours = next_values["valid_options_for_attributes"]["Test Colour"]
|
||||
filtered_items = next_values["filtered_items"]
|
||||
|
||||
@@ -91,30 +95,31 @@ class TestVariantSelector(FrappeTestCase):
|
||||
|
||||
def test_exact_match_with_price(self):
|
||||
"""
|
||||
Test price fetching and matching of variant without Website Item
|
||||
Test price fetching and matching of variant without Website Item
|
||||
"""
|
||||
from erpnext.e_commerce.doctype.website_item.test_website_item import make_web_item_price
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
setup_e_commerce_settings({
|
||||
"company": "_Test Company",
|
||||
"enabled": 1,
|
||||
"default_customer_group": "_Test Customer Group",
|
||||
"price_list": "_Test Price List India",
|
||||
"show_price": 1
|
||||
})
|
||||
setup_e_commerce_settings(
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"enabled": 1,
|
||||
"default_customer_group": "_Test Customer Group",
|
||||
"price_list": "_Test Price List India",
|
||||
"show_price": 1,
|
||||
}
|
||||
)
|
||||
|
||||
make_web_item_price(item_code="Test-Tshirt-Temp-S-R", price_list_rate=100)
|
||||
|
||||
frappe.local.shopping_cart_settings = None # clear cached settings values
|
||||
frappe.local.shopping_cart_settings = None # clear cached settings values
|
||||
next_values = get_next_attribute_and_values(
|
||||
"Test-Tshirt-Temp",
|
||||
selected_attributes={"Test Size": "Small", "Test Colour": "Red"}
|
||||
"Test-Tshirt-Temp", selected_attributes={"Test Size": "Small", "Test Colour": "Red"}
|
||||
)
|
||||
print(">>>>", next_values)
|
||||
price_info = next_values["product_info"]["price"]
|
||||
|
||||
self.assertEqual(next_values["exact_match"][0],"Test-Tshirt-Temp-S-R")
|
||||
self.assertEqual(next_values["exact_match"][0],"Test-Tshirt-Temp-S-R")
|
||||
self.assertEqual(next_values["exact_match"][0], "Test-Tshirt-Temp-S-R")
|
||||
self.assertEqual(next_values["exact_match"][0], "Test-Tshirt-Temp-S-R")
|
||||
self.assertEqual(price_info["price_list_rate"], 100.0)
|
||||
self.assertEqual(price_info["formatted_price_sales_uom"], "₹ 100.00")
|
||||
|
||||
@@ -24,18 +24,18 @@ def get_item_codes_by_attributes(attribute_filters, template_item_code=None):
|
||||
wheres = []
|
||||
query_values = []
|
||||
for attribute_value in attribute_values:
|
||||
wheres.append('( attribute = %s and attribute_value = %s )')
|
||||
wheres.append("( attribute = %s and attribute_value = %s )")
|
||||
query_values += [attribute, attribute_value]
|
||||
|
||||
attribute_query = ' or '.join(wheres)
|
||||
attribute_query = " or ".join(wheres)
|
||||
|
||||
if template_item_code:
|
||||
variant_of_query = 'AND t2.variant_of = %s'
|
||||
variant_of_query = "AND t2.variant_of = %s"
|
||||
query_values.append(template_item_code)
|
||||
else:
|
||||
variant_of_query = ''
|
||||
variant_of_query = ""
|
||||
|
||||
query = '''
|
||||
query = """
|
||||
SELECT
|
||||
t1.parent
|
||||
FROM
|
||||
@@ -58,20 +58,23 @@ def get_item_codes_by_attributes(attribute_filters, template_item_code=None):
|
||||
t1.parent
|
||||
ORDER BY
|
||||
NULL
|
||||
'''.format(attribute_query=attribute_query, variant_of_query=variant_of_query)
|
||||
""".format(
|
||||
attribute_query=attribute_query, variant_of_query=variant_of_query
|
||||
)
|
||||
|
||||
item_codes = set([r[0] for r in frappe.db.sql(query, query_values)]) # nosemgrep
|
||||
item_codes = set([r[0] for r in frappe.db.sql(query, query_values)]) # nosemgrep
|
||||
items.append(item_codes)
|
||||
|
||||
res = list(set.intersection(*items))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_attributes_and_values(item_code):
|
||||
'''Build a list of attributes and their possible values.
|
||||
"""Build a list of attributes and their possible values.
|
||||
This will ignore the values upon selection of which there cannot exist one item.
|
||||
'''
|
||||
"""
|
||||
item_cache = ItemVariantsCacheManager(item_code)
|
||||
item_variants_data = item_cache.get_item_variants_data()
|
||||
|
||||
@@ -83,8 +86,9 @@ def get_attributes_and_values(item_code):
|
||||
if attribute in attribute_list:
|
||||
valid_options.setdefault(attribute, set()).add(attribute_value)
|
||||
|
||||
item_attribute_values = frappe.db.get_all('Item Attribute Value',
|
||||
['parent', 'attribute_value', 'idx'], order_by='parent asc, idx asc')
|
||||
item_attribute_values = frappe.db.get_all(
|
||||
"Item Attribute Value", ["parent", "attribute_value", "idx"], order_by="parent asc, idx asc"
|
||||
)
|
||||
ordered_attribute_value_map = frappe._dict()
|
||||
for iv in item_attribute_values:
|
||||
ordered_attribute_value_map.setdefault(iv.parent, []).append(iv.attribute_value)
|
||||
@@ -93,18 +97,18 @@ def get_attributes_and_values(item_code):
|
||||
for attr in attributes:
|
||||
valid_attribute_values = valid_options.get(attr.attribute, [])
|
||||
ordered_values = ordered_attribute_value_map.get(attr.attribute, [])
|
||||
attr['values'] = [v for v in ordered_values if v in valid_attribute_values]
|
||||
attr["values"] = [v for v in ordered_values if v in valid_attribute_values]
|
||||
|
||||
return attributes
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_next_attribute_and_values(item_code, selected_attributes):
|
||||
'''Find the count of Items that match the selected attributes.
|
||||
"""Find the count of Items that match the selected attributes.
|
||||
Also, find the attribute values that are not applicable for further searching.
|
||||
If less than equal to 10 items are found, return item_codes of those items.
|
||||
If one item is matched exactly, return item_code of that item.
|
||||
'''
|
||||
"""
|
||||
selected_attributes = frappe.parse_json(selected_attributes)
|
||||
|
||||
item_cache = ItemVariantsCacheManager(item_code)
|
||||
@@ -133,7 +137,11 @@ def get_next_attribute_and_values(item_code, selected_attributes):
|
||||
|
||||
for row in item_variants_data:
|
||||
item_code, attribute, attribute_value = row
|
||||
if item_code in filtered_items and attribute not in selected_attributes and attribute in attribute_list:
|
||||
if (
|
||||
item_code in filtered_items
|
||||
and attribute not in selected_attributes
|
||||
and attribute in attribute_list
|
||||
):
|
||||
valid_options_for_attributes[attribute].add(attribute_value)
|
||||
|
||||
optional_attributes = item_cache.get_optional_attributes()
|
||||
@@ -159,12 +167,12 @@ def get_next_attribute_and_values(item_code, selected_attributes):
|
||||
product_info = None
|
||||
|
||||
return {
|
||||
'next_attribute': next_attribute,
|
||||
'valid_options_for_attributes': valid_options_for_attributes,
|
||||
'filtered_items_count': filtered_items_count,
|
||||
'filtered_items': filtered_items if filtered_items_count < 10 else [],
|
||||
'exact_match': exact_match,
|
||||
'product_info': product_info
|
||||
"next_attribute": next_attribute,
|
||||
"valid_options_for_attributes": valid_options_for_attributes,
|
||||
"filtered_items_count": filtered_items_count,
|
||||
"filtered_items": filtered_items if filtered_items_count < 10 else [],
|
||||
"exact_match": exact_match,
|
||||
"product_info": product_info,
|
||||
}
|
||||
|
||||
|
||||
@@ -179,16 +187,16 @@ def get_items_with_selected_attributes(item_code, selected_attributes):
|
||||
|
||||
return set.intersection(*items)
|
||||
|
||||
|
||||
# utilities
|
||||
|
||||
|
||||
def get_item_attributes(item_code):
|
||||
attributes = frappe.db.get_all('Item Variant Attribute',
|
||||
fields=['attribute'],
|
||||
filters={
|
||||
'parenttype': 'Item',
|
||||
'parent': item_code
|
||||
},
|
||||
order_by='idx asc'
|
||||
attributes = frappe.db.get_all(
|
||||
"Item Variant Attribute",
|
||||
fields=["attribute"],
|
||||
filters={"parenttype": "Item", "parent": item_code},
|
||||
order_by="idx asc",
|
||||
)
|
||||
|
||||
optional_attributes = ItemVariantsCacheManager(item_code).get_optional_attributes()
|
||||
@@ -199,6 +207,7 @@ def get_item_attributes(item_code):
|
||||
|
||||
return attributes
|
||||
|
||||
|
||||
def get_item_variant_price_dict(item_code, cart_settings):
|
||||
if cart_settings.enabled and cart_settings.show_price:
|
||||
is_guest = frappe.session.user == "Guest"
|
||||
@@ -207,12 +216,8 @@ def get_item_variant_price_dict(item_code, cart_settings):
|
||||
if not is_guest or not cart_settings.hide_price_for_guest:
|
||||
price_list = _set_price_list(cart_settings, None)
|
||||
price = get_price(
|
||||
item_code,
|
||||
price_list,
|
||||
cart_settings.default_customer_group,
|
||||
cart_settings.company
|
||||
item_code, price_list, cart_settings.default_customer_group, cart_settings.company
|
||||
)
|
||||
return {"price": price}
|
||||
|
||||
return None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user