Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions webshop/templates/pages/order.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,18 @@ <h3 class="m-0">{{ doc.name }}</h3>
</div>
<div class="form-column col-sm-6">
<div class="page-header-actions-block" data-html-block="header-actions">
<p>
{% if enabled_checkout and (doc.doctype != "Sales Invoice" or doc.outstanding_amount > 0) %}
<a href="/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request?dn={{ doc.name }}&dt={{ doc.doctype }}&submit_doc=1&order_type=Shopping Cart"
class="btn btn-primary btn-sm" id="pay-for-order">
{{ _("Pay") }} {{doc.get_formatted("grand_total") }}
</a>
{% endif %}
</p>
{% if enabled_checkout and show_pay_button %}
<form method="post" action="/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request">
<input type="hidden" name="csrf_token" value="{{ csrf_token | e }}">
<input type="hidden" name="dn" value="{{ doc.name | e }}">
<input type="hidden" name="dt" value="{{ doc.doctype | e }}">
<input type="hidden" name="submit_doc" value="1">
<input type="hidden" name="order_type" value="Shopping Cart">
<button type="submit" class="btn btn-primary btn-sm" id="pay-for-order">
{{ _("Pay") }} {{ frappe.utils.fmt_money(pay_amount, currency=doc.currency) }}
</button>
</form>
{% endif %}
</div>
</div>
</div>
Expand Down
6 changes: 6 additions & 0 deletions webshop/templates/pages/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion webshop/webshop/doctype/override_doctype/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion webshop/webshop/product_data_engine/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion webshop/webshop/redisearch_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)

Expand Down
1 change: 0 additions & 1 deletion webshop/webshop/shopping_cart/cart.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
71 changes: 71 additions & 0 deletions webshop/webshop/shopping_cart/test_shopping_cart.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion webshop/webshop/variant_selector/item_variants_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
15 changes: 9 additions & 6 deletions webshop/webshop/variant_selector/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Expand All @@ -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)

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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")
Expand Down
Loading