Merge pull request #29396 from frappe/mergify/bp/version-13-pre-release/pr-29383
fix: Cart & Popup Logic of Item variant without Website Item (backport #29383)
This commit is contained in:
@@ -276,10 +276,29 @@ def guess_territory():
|
|||||||
|
|
||||||
def decorate_quotation_doc(doc):
|
def decorate_quotation_doc(doc):
|
||||||
for d in doc.get("items", []):
|
for d in doc.get("items", []):
|
||||||
|
item_code = d.item_code
|
||||||
|
fields = ["web_item_name", "thumbnail", "website_image", "description", "route"]
|
||||||
|
|
||||||
|
# Variant Item
|
||||||
|
if not frappe.db.exists("Website Item", {"item_code": item_code}):
|
||||||
|
variant_data = frappe.db.get_values(
|
||||||
|
"Item",
|
||||||
|
filters={"item_code": item_code},
|
||||||
|
fieldname=["variant_of", "item_name", "image"],
|
||||||
|
as_dict=True
|
||||||
|
)[0]
|
||||||
|
item_code = variant_data.variant_of
|
||||||
|
fields = fields[1:]
|
||||||
|
d.website_item_name = variant_data.item_name
|
||||||
|
|
||||||
|
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(
|
d.update(frappe.db.get_value(
|
||||||
"Website Item",
|
"Website Item",
|
||||||
{"item_code": d.item_code},
|
{"item_code": item_code},
|
||||||
["web_item_name", "thumbnail", "website_image", "description", "route"],
|
fields,
|
||||||
as_dict=True)
|
as_dict=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,13 @@ from frappe.utils import add_months, nowdate
|
|||||||
|
|
||||||
from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule
|
from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule
|
||||||
from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
|
from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
|
||||||
from erpnext.e_commerce.shopping_cart.cart import _get_cart_quotation, get_party, update_cart
|
from erpnext.e_commerce.shopping_cart.cart import (
|
||||||
from erpnext.tests.utils import create_test_contact_and_address
|
_get_cart_quotation,
|
||||||
|
get_cart_quotation,
|
||||||
|
get_party,
|
||||||
|
update_cart,
|
||||||
|
)
|
||||||
|
from erpnext.tests.utils import change_settings, create_test_contact_and_address
|
||||||
|
|
||||||
|
|
||||||
class TestShoppingCart(unittest.TestCase):
|
class TestShoppingCart(unittest.TestCase):
|
||||||
@@ -34,6 +39,7 @@ class TestShoppingCart(unittest.TestCase):
|
|||||||
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):
|
def tearDown(self):
|
||||||
|
frappe.db.rollback()
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
self.disable_shopping_cart()
|
self.disable_shopping_cart()
|
||||||
|
|
||||||
@@ -128,6 +134,40 @@ class TestShoppingCart(unittest.TestCase):
|
|||||||
|
|
||||||
self.remove_test_quotation(quotation)
|
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
|
||||||
|
})
|
||||||
|
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"
|
||||||
|
})
|
||||||
|
variant.save()
|
||||||
|
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
|
||||||
|
doc = cart.get("doc")
|
||||||
|
|
||||||
|
self.assertEqual(doc.get("items")[0].item_name, "Test-Tshirt-Temp-S-R")
|
||||||
|
|
||||||
def create_tax_rule(self):
|
def create_tax_rule(self):
|
||||||
tax_rule = frappe.get_test_records("Tax Rule")[0]
|
tax_rule = frappe.get_test_records("Tax Rule")[0]
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -4,23 +4,22 @@ import frappe
|
|||||||
|
|
||||||
from erpnext.controllers.item_variant import create_variant
|
from erpnext.controllers.item_variant import create_variant
|
||||||
from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
|
from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
|
||||||
|
from erpnext.e_commerce.variant_selector.utils import get_next_attribute_and_values
|
||||||
from erpnext.stock.doctype.item.test_item import make_item
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
from erpnext.tests.utils import ERPNextTestCase, change_settings
|
||||||
|
|
||||||
test_dependencies = ["Item"]
|
test_dependencies = ["Item"]
|
||||||
|
|
||||||
class TestVariantSelector(unittest.TestCase):
|
class TestVariantSelector(ERPNextTestCase):
|
||||||
|
|
||||||
def setUp(self) -> None:
|
@classmethod
|
||||||
self.template_item = make_item("Test-Tshirt-Temp", {
|
def setUpClass(cls):
|
||||||
|
template_item = make_item("Test-Tshirt-Temp", {
|
||||||
"has_variant": 1,
|
"has_variant": 1,
|
||||||
"variant_based_on": "Item Attribute",
|
"variant_based_on": "Item Attribute",
|
||||||
"attributes": [
|
"attributes": [
|
||||||
{
|
{"attribute": "Test Size"},
|
||||||
"attribute": "Test Size"
|
{"attribute": "Test Colour"}
|
||||||
},
|
|
||||||
{
|
|
||||||
"attribute": "Test Colour"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -28,19 +27,16 @@ class TestVariantSelector(unittest.TestCase):
|
|||||||
for size in ("Large", "Medium",):
|
for size in ("Large", "Medium",):
|
||||||
for colour in ("Red", "Green",):
|
for colour in ("Red", "Green",):
|
||||||
variant = create_variant("Test-Tshirt-Temp", {
|
variant = create_variant("Test-Tshirt-Temp", {
|
||||||
"Test Size": size,
|
"Test Size": size, "Test Colour": colour
|
||||||
"Test Colour": colour
|
|
||||||
})
|
})
|
||||||
variant.save()
|
variant.save()
|
||||||
|
|
||||||
variant = create_variant("Test-Tshirt-Temp", {
|
variant = create_variant("Test-Tshirt-Temp", {
|
||||||
"Test Size": "Small",
|
"Test Size": "Small", "Test Colour": "Red"
|
||||||
"Test Colour": "Red"
|
|
||||||
})
|
})
|
||||||
variant.save()
|
variant.save()
|
||||||
|
|
||||||
def tearDown(self):
|
make_website_item(template_item) # publish template not variants
|
||||||
frappe.db.rollback()
|
|
||||||
|
|
||||||
def test_item_attributes(self):
|
def test_item_attributes(self):
|
||||||
"""
|
"""
|
||||||
@@ -51,8 +47,6 @@ class TestVariantSelector(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
from erpnext.e_commerce.variant_selector.utils import get_attributes_and_values
|
from erpnext.e_commerce.variant_selector.utils import get_attributes_and_values
|
||||||
|
|
||||||
make_website_item(self.template_item) # publish template not variants
|
|
||||||
|
|
||||||
attr_data = get_attributes_and_values("Test-Tshirt-Temp")
|
attr_data = get_attributes_and_values("Test-Tshirt-Temp")
|
||||||
|
|
||||||
self.assertEqual(attr_data[0]["attribute"], "Test Size")
|
self.assertEqual(attr_data[0]["attribute"], "Test Size")
|
||||||
@@ -72,7 +66,7 @@ class TestVariantSelector(unittest.TestCase):
|
|||||||
self.assertEqual(len(attr_data[0]["values"]), 2) # ['Medium', 'Large']
|
self.assertEqual(len(attr_data[0]["values"]), 2) # ['Medium', 'Large']
|
||||||
|
|
||||||
# teardown
|
# teardown
|
||||||
small_variant.disabled = 1
|
small_variant.disabled = 0
|
||||||
small_variant.save()
|
small_variant.save()
|
||||||
|
|
||||||
def test_next_item_variant_values(self):
|
def test_next_item_variant_values(self):
|
||||||
@@ -84,8 +78,6 @@ class TestVariantSelector(unittest.TestCase):
|
|||||||
There is a ** Small-Red ** Tshirt. No other colour in this size.
|
There is a ** Small-Red ** Tshirt. No other colour in this size.
|
||||||
On selecting ** Small **, only ** Red ** should be selectable next.
|
On selecting ** Small **, only ** Red ** should be selectable next.
|
||||||
"""
|
"""
|
||||||
from erpnext.e_commerce.variant_selector.utils import get_next_attribute_and_values
|
|
||||||
|
|
||||||
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"]
|
next_colours = next_values["valid_options_for_attributes"]["Test Colour"]
|
||||||
filtered_items = next_values["filtered_items"]
|
filtered_items = next_values["filtered_items"]
|
||||||
@@ -94,3 +86,29 @@ class TestVariantSelector(unittest.TestCase):
|
|||||||
self.assertEqual(next_colours.pop(), "Red")
|
self.assertEqual(next_colours.pop(), "Red")
|
||||||
self.assertEqual(len(filtered_items), 1)
|
self.assertEqual(len(filtered_items), 1)
|
||||||
self.assertEqual(filtered_items.pop(), "Test-Tshirt-Temp-S-R")
|
self.assertEqual(filtered_items.pop(), "Test-Tshirt-Temp-S-R")
|
||||||
|
|
||||||
|
@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_exact_match_with_price(self):
|
||||||
|
"""
|
||||||
|
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")
|
||||||
|
make_web_item_price(item_code="Test-Tshirt-Temp-S-R", price_list_rate=100)
|
||||||
|
next_values = get_next_attribute_and_values(
|
||||||
|
"Test-Tshirt-Temp",
|
||||||
|
selected_attributes={"Test Size": "Small", "Test Colour": "Red"}
|
||||||
|
)
|
||||||
|
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(price_info["price_list_rate"], 100.0)
|
||||||
|
self.assertEqual(price_info["formatted_price_sales_uom"], "₹ 100.00")
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import cint
|
from frappe.utils import cint
|
||||||
|
|
||||||
|
from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
|
||||||
|
get_shopping_cart_settings,
|
||||||
|
)
|
||||||
|
from erpnext.e_commerce.shopping_cart.cart import _set_price_list
|
||||||
from erpnext.e_commerce.variant_selector.item_variants_cache import ItemVariantsCacheManager
|
from erpnext.e_commerce.variant_selector.item_variants_cache import ItemVariantsCacheManager
|
||||||
|
from erpnext.utilities.product import get_price
|
||||||
|
|
||||||
|
|
||||||
def get_item_codes_by_attributes(attribute_filters, template_item_code=None):
|
def get_item_codes_by_attributes(attribute_filters, template_item_code=None):
|
||||||
@@ -143,14 +148,13 @@ def get_next_attribute_and_values(item_code, selected_attributes):
|
|||||||
filtered_items_count = len(filtered_items)
|
filtered_items_count = len(filtered_items)
|
||||||
|
|
||||||
# get product info if exact match
|
# get product info if exact match
|
||||||
from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website
|
# from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website
|
||||||
if exact_match:
|
if exact_match:
|
||||||
data = get_product_info_for_website(exact_match[0])
|
cart_settings = get_shopping_cart_settings()
|
||||||
product_info = data.product_info
|
product_info = get_item_variant_price_dict(exact_match[0], cart_settings)
|
||||||
|
|
||||||
if product_info:
|
if product_info:
|
||||||
product_info["allow_items_not_in_stock"] = cint(data.cart_settings.allow_items_not_in_stock)
|
product_info["allow_items_not_in_stock"] = cint(cart_settings.allow_items_not_in_stock)
|
||||||
if not data.cart_settings.show_price:
|
|
||||||
product_info = None
|
|
||||||
else:
|
else:
|
||||||
product_info = None
|
product_info = None
|
||||||
|
|
||||||
@@ -195,3 +199,20 @@ def get_item_attributes(item_code):
|
|||||||
|
|
||||||
return attributes
|
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"
|
||||||
|
# Show Price if logged in.
|
||||||
|
# If not logged in, check if price is hidden for guest.
|
||||||
|
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
|
||||||
|
)
|
||||||
|
return {"price": price}
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ class ItemConfigure {
|
|||||||
? `<div class="alert alert-success d-flex justify-content-between align-items-center" role="alert">
|
? `<div class="alert alert-success d-flex justify-content-between align-items-center" role="alert">
|
||||||
<div><div>
|
<div><div>
|
||||||
${one_item}
|
${one_item}
|
||||||
${product_info && product_info.price && !$.isEmptyObject()
|
${product_info && product_info.price && !$.isEmptyObject(product_info.price)
|
||||||
? '(' + product_info.price.formatted_price_sales_uom + ')'
|
? '(' + product_info.price.formatted_price_sales_uom + ')'
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user