diff --git a/webshop/templates/pages/order.html b/webshop/templates/pages/order.html index 2e11784b91..6af149d54d 100644 --- a/webshop/templates/pages/order.html +++ b/webshop/templates/pages/order.html @@ -36,14 +36,18 @@

{{ doc.name }}

-

- {% if enabled_checkout and (doc.doctype != "Sales Invoice" or doc.outstanding_amount > 0) %} - - {{ _("Pay") }} {{doc.get_formatted("grand_total") }} - - {% endif %} -

+ {% if enabled_checkout and show_pay_button %} +
+ + + + + + +
+ {% endif %}
diff --git a/webshop/templates/pages/order.py b/webshop/templates/pages/order.py index c97f3eaa62..7da78c4cf4 100644 --- a/webshop/templates/pages/order.py +++ b/webshop/templates/pages/order.py @@ -2,7 +2,9 @@ # License: GNU General Public License v3. See license.txt import frappe +from erpnext.templates.pages.order import get_payment_details from frappe import _ +from frappe.sessions import get_csrf_token from webshop.webshop.doctype.webshop_settings.webshop_settings import show_attachments @@ -38,6 +40,10 @@ def get_context(context): if not frappe.has_website_permission(context.doc): frappe.throw(_("Not Permitted"), frappe.PermissionError) + context.show_pay_button, context.pay_amount = get_payment_details(context.doc) + if context.enabled_checkout and context.show_pay_button: + context.csrf_token = get_csrf_token() + if context.doc.get("customer"): # check for the loyalty program of the customer customer_loyalty_program = frappe.db.get_value("Customer", context.doc.customer, "loyalty_program") diff --git a/webshop/webshop/crud_events/item/validate_duplicate_website_item.py b/webshop/webshop/crud_events/item/validate_duplicate_website_item.py index 53ff840968..ce2c9c1f40 100644 --- a/webshop/webshop/crud_events/item/validate_duplicate_website_item.py +++ b/webshop/webshop/crud_events/item/validate_duplicate_website_item.py @@ -24,7 +24,7 @@ def execute(doc, method=None, old_name=None, new_name=None, merge=False): if len(web_items) <= 1: return - old_web_item = [d.get("name") for d in web_items if d.get("item_code") == old_name][0] + old_web_item = next(d.get("name") for d in web_items if d.get("item_code") == old_name) web_item_link = get_link_to_form("Website Item", old_web_item) old_name, new_name = frappe.bold(old_name), frappe.bold(new_name) diff --git a/webshop/webshop/doctype/override_doctype/item.py b/webshop/webshop/doctype/override_doctype/item.py index 2045439a5e..466101c896 100644 --- a/webshop/webshop/doctype/override_doctype/item.py +++ b/webshop/webshop/doctype/override_doctype/item.py @@ -34,7 +34,7 @@ def validate_duplicate_website_item_before_merge(self, old_name, new_name): if len(web_items) <= 1: return - old_web_item = [d.get("name") for d in web_items if d.get("item_code") == old_name][0] + old_web_item = next(d.get("name") for d in web_items if d.get("item_code") == old_name) web_item_link = get_link_to_form("Website Item", old_web_item) old_name, new_name = frappe.bold(old_name), frappe.bold(new_name) diff --git a/webshop/webshop/product_data_engine/query.py b/webshop/webshop/product_data_engine/query.py index 4fb6c60543..1fb0f9981f 100644 --- a/webshop/webshop/product_data_engine/query.py +++ b/webshop/webshop/product_data_engine/query.py @@ -56,7 +56,7 @@ def query(self, attributes=None, fields=None, search_term=None, start=0, item_gr """ # track if discounts included in field filters self.filter_with_discount = bool(fields.get("discount")) - result, discount_list, website_item_groups, cart_items, count = [], [], [], [], 0 + result, discount_list, cart_items, count = [], [], [], 0 if fields: self.build_fields_filters(fields) diff --git a/webshop/webshop/redisearch_utils.py b/webshop/webshop/redisearch_utils.py index 55cbb4bc7d..9270b504c2 100644 --- a/webshop/webshop/redisearch_utils.py +++ b/webshop/webshop/redisearch_utils.py @@ -94,7 +94,7 @@ def create_website_items_index(): # TODO: sortable? index.create_index( - [TextField("web_item_name", sortable=True)] + idx_fields, + [TextField("web_item_name", sortable=True), *idx_fields], definition=idx_def, ) diff --git a/webshop/webshop/shopping_cart/cart.py b/webshop/webshop/shopping_cart/cart.py index 9bde7119cc..1f6bf808f7 100644 --- a/webshop/webshop/shopping_cart/cart.py +++ b/webshop/webshop/shopping_cart/cart.py @@ -708,7 +708,6 @@ def get_applicable_shipping_rules(party=None, quotation=None): shipping_rules = get_shipping_rules(quotation) if shipping_rules: - rule_label_map = frappe.db.get_values("Shipping Rule", shipping_rules, "label") # we need this in sorted order as per the position of the rule in the settings page return [[rule, rule] for rule in shipping_rules] diff --git a/webshop/webshop/shopping_cart/test_shopping_cart.py b/webshop/webshop/shopping_cart/test_shopping_cart.py index 79ff5d6140..2d57abff15 100644 --- a/webshop/webshop/shopping_cart/test_shopping_cart.py +++ b/webshop/webshop/shopping_cart/test_shopping_cart.py @@ -5,10 +5,13 @@ import unittest import frappe +from bs4 import BeautifulSoup from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule +from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from frappe.tests.utils import change_settings from frappe.utils import add_months, cint, nowdate +from webshop.templates.pages.order import get_context from webshop.webshop.doctype.website_item.website_item import make_website_item from webshop.webshop.shopping_cart.cart import ( _get_cart_quotation, @@ -63,6 +66,74 @@ def test_get_cart_new_user(self): return quotation + def test_order_payment_form(self): + order = make_sales_order(qty=1, rate=1000) + draft = make_sales_order(do_not_submit=True) + cancelled = make_sales_order() + cancelled.cancel() + gateway = frappe.get_doc({"doctype": "Payment Gateway", "gateway": "_Test Webshop Gateway"}).insert() + gateway_account = frappe.get_doc( + { + "doctype": "Payment Gateway Account", + "payment_gateway": gateway.name, + "payment_account": frappe.get_cached_value("Company", order.company, "default_cash_account"), + "company": order.company, + "currency": order.currency, + } + ).insert() + settings = frappe.get_doc("Webshop Settings") + settings.enable_checkout = 1 + settings.payment_gateway_account = gateway_account.name + settings.save() + user = frappe.get_doc( + { + "doctype": "User", + "email": "_test_webshop_payment@example.com", + "first_name": "Webshop Customer", + "user_type": "Website User", + "send_welcome_email": 0, + "roles": [{"role": "Customer"}], + } + ).insert() + customer = frappe.get_doc("Customer", order.customer) + customer.append("portal_users", {"user": user.name}) + customer.save() + self.addCleanup(setattr, frappe.local, "form_dict", frappe.local.form_dict) + frappe.set_user(user.name) + self.assertFalse(frappe.has_permission("Payment Request", "create")) + + for document in (order, draft, cancelled): + with self.subTest(docstatus=document.docstatus): + frappe.local.form_dict = frappe._dict(doctype=document.doctype, name=document.name) + context = frappe._dict() + get_context(context) + template = frappe.get_template("webshop/templates/pages/order.html") + html = "".join(template.blocks["header_actions"](template.new_context(context))) + button = BeautifulSoup(html, "html.parser").find(id="pay-for-order") + if document.docstatus != 1: + self.assertIsNone(button) + continue + + self.assertIsNotNone(button) + form = button.find_parent("form") + self.assertIsNotNone(form) + self.assertEqual(form["method"], "post") + self.assertEqual( + form["action"], + "/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request", + ) + self.assertEqual(context.pay_amount, order.grand_total) + self.assertEqual( + {field["name"]: field["value"] for field in form.find_all("input", type="hidden")}, + { + "csrf_token": context.csrf_token, + "dn": order.name, + "dt": order.doctype, + "submit_doc": "1", + "order_type": "Shopping Cart", + }, + ) + def test_get_cart_customer(self, customer="_Test Customer 2"): def validate_quotation(customer_name): # test if quotation with customer is fetched diff --git a/webshop/webshop/variant_selector/item_variants_cache.py b/webshop/webshop/variant_selector/item_variants_cache.py index 1339d16f58..5f74d626db 100644 --- a/webshop/webshop/variant_selector/item_variants_cache.py +++ b/webshop/webshop/variant_selector/item_variants_cache.py @@ -87,7 +87,7 @@ def build_cache(self): item_attribute_value_map.setdefault(item_code, {})[attribute] = attribute_value optional_attributes = set() - for item_code, attr_dict in item_attribute_value_map.items(): + for attr_dict in item_attribute_value_map.values(): for attribute in attributes: if attribute not in attr_dict: optional_attributes.add(attribute) diff --git a/webshop/webshop/variant_selector/utils.py b/webshop/webshop/variant_selector/utils.py index 7a31879d8d..35b546b9bc 100644 --- a/webshop/webshop/variant_selector/utils.py +++ b/webshop/webshop/variant_selector/utils.py @@ -68,8 +68,9 @@ def get_item_codes_by_attributes(attribute_filters, template_item_code=None): return res -@frappe.whitelist(allow_guest=True) -def get_attributes_and_values(item_code): +# Guest access is required by the storefront variant selector. +@frappe.whitelist(allow_guest=True) # nosemgrep: guest-whitelisted-method +def get_attributes_and_values(item_code: str): """Build a list of attributes and their possible values. This will ignore the values upon selection of which there cannot exist one item. """ @@ -80,7 +81,8 @@ def get_attributes_and_values(item_code): attribute_list = [a.attribute for a in attributes] valid_options = {} - for item_code, attribute, attribute_value in item_variants_data: + for row in item_variants_data: + attribute, attribute_value = row[1:] if attribute in attribute_list: valid_options.setdefault(attribute, set()).add(attribute_value) @@ -112,8 +114,9 @@ def get_attributes_and_values(item_code): return attributes -@frappe.whitelist(allow_guest=True) -def get_next_attribute_and_values(item_code, selected_attributes): +# Guest access is required by the storefront variant selector. +@frappe.whitelist(allow_guest=True) # nosemgrep: guest-whitelisted-method +def get_next_attribute_and_values(item_code: str, selected_attributes: str | dict): from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses """Find the count of Items that match the selected attributes. @@ -183,7 +186,7 @@ def get_next_attribute_and_values(item_code, selected_attributes): if exact_match and len(exact_match) == 1: product_id = exact_match[0] elif filtered_items_count == 1: - product_id = list(filtered_items)[0] + product_id = next(iter(filtered_items)) if product_id: warehouse = frappe.get_cached_value("Website Item", {"item_code": product_id}, "website_warehouse")