diff --git a/webshop/hooks.py b/webshop/hooks.py
index ed854d07cd..4e1cb67cc0 100644
--- a/webshop/hooks.py
+++ b/webshop/hooks.py
@@ -12,7 +12,7 @@
web_include_css = "webshop-web.bundle.css"
-web_include_js = "web.bundle.js"
+web_include_js =["web.bundle.js", "assets/webshop/js/momo_checkout_override.js"]
after_install = "webshop.setup.install.after_install"
on_logout = "webshop.webshop.shopping_cart.utils.clear_cart_count"
diff --git a/webshop/public/js/momo_checkout_override.js b/webshop/public/js/momo_checkout_override.js
new file mode 100644
index 0000000000..a47a103306
--- /dev/null
+++ b/webshop/public/js/momo_checkout_override.js
@@ -0,0 +1,34 @@
+// Override place_order to handle MoMo payment redirect
+frappe.ready(function() {
+ // Hook into the checkout form submit
+ // The webshop uses a Shopping Cart controller — we patch after DOM ready
+ $(document).on("click", ".btn-place-order, [data-action='place_order'], #btn-place-order", function(e) {
+ e.preventDefault();
+ e.stopImmediatePropagation();
+
+ var btn = $(this);
+ btn.prop("disabled", true).text(__("Processing..."));
+
+ frappe.call({
+ method: "webshop.webshop.shopping_cart.cart.place_order",
+ callback: function(r) {
+ if (r.message && r.message.payment_url) {
+ // MoMo flow: redirect to MoMo checkout page
+ window.location.href = r.message.payment_url;
+ } else if (r.message && r.message.sales_order) {
+ // Fallback: go to order page
+ window.location.href = "/orders?name=" + r.message.sales_order;
+ } else if (typeof r.message === "string") {
+ // Original webshop response (plain SO name)
+ window.location.href = "/orders?name=" + r.message;
+ } else {
+ btn.prop("disabled", false).text(__("Place Order"));
+ frappe.msgprint(__("Something went wrong. Please try again."));
+ }
+ },
+ error: function() {
+ btn.prop("disabled", false).text(__("Place Order"));
+ }
+ });
+ });
+});
diff --git a/webshop/templates/pages/cart.js b/webshop/templates/pages/cart.js
index dd03f3d1c7..5022feab06 100644
--- a/webshop/templates/pages/cart.js
+++ b/webshop/templates/pages/cart.js
@@ -6,244 +6,238 @@ frappe.provide("webshop.webshop.shopping_cart");
var shopping_cart = webshop.webshop.shopping_cart;
$.extend(shopping_cart, {
- show_error: function(title, text) {
- $("#cart-container").html('
' +
- title + '
' + text + '
');
- },
-
- bind_events: function() {
- shopping_cart.bind_place_order();
- shopping_cart.bind_request_quotation();
- shopping_cart.bind_change_qty();
- shopping_cart.bind_remove_cart_item();
- shopping_cart.bind_change_notes();
- shopping_cart.bind_coupon_code();
- shopping_cart.bind_remove_coupon_code();
- },
-
- bind_place_order: function() {
- $(".btn-place-order").on("click", function() {
- shopping_cart.place_order(this);
- });
- },
-
- bind_request_quotation: function() {
- $('.btn-request-for-quotation').on('click', function() {
- shopping_cart.request_quotation(this);
- });
- },
-
- bind_change_qty: function() {
- // bind update button
- $(".cart-items").on("change", ".cart-qty", function() {
- var item_code = $(this).attr("data-item-code");
- var newVal = $(this).val();
- shopping_cart.shopping_cart_update({item_code, qty: newVal});
- });
-
- $(".cart-items").on('click', '.number-spinner button', function () {
- var btn = $(this),
- input = btn.closest('.number-spinner').find('input'),
- oldValue = input.val().trim(),
- newVal = 0;
-
- if (btn.attr('data-dir') == 'up') {
- newVal = parseInt(oldValue) + 1;
- } else {
- if (oldValue > 1) {
- newVal = parseInt(oldValue) - 1;
- }
- }
- input.val(newVal);
-
- let notes = input.closest("td").siblings().find(".notes").text().trim();
- var item_code = input.attr("data-item-code");
- shopping_cart.shopping_cart_update({
- item_code,
- qty: newVal,
- additional_notes: notes
- });
- });
- },
-
- bind_change_notes: function() {
- $('.cart-items').on('change', 'textarea', function() {
- const $textarea = $(this);
- const item_code = $textarea.attr('data-item-code');
- const qty = $textarea.closest('tr').find('.cart-qty').val();
- const notes = $textarea.val();
- shopping_cart.shopping_cart_update({
- item_code,
- qty,
- additional_notes: notes
- });
- });
- },
-
- bind_remove_cart_item: function() {
- $(".cart-items").on("click", ".remove-cart-item", (e) => {
- const $remove_cart_item_btn = $(e.currentTarget);
- var item_code = $remove_cart_item_btn.data("item-code");
-
- shopping_cart.shopping_cart_update({
- item_code: item_code,
- qty: 0
- });
- });
- },
-
- render_tax_row: function($cart_taxes, doc, shipping_rules) {
- var shipping_selector;
- if(shipping_rules) {
- shipping_selector = '';
- }
-
- var $tax_row = $(repl('\
-
\
-
\
-
' +
- (shipping_selector || '
%(description)s
') +
- '
\
-
\
-
\
-
\
-
%(formatted_tax_amount)s
\
-
\
-
', doc)).appendTo($cart_taxes);
-
- if(shipping_selector) {
- $tax_row.find('select option').each(function(i, opt) {
- if($(opt).html() == doc.description) {
- $(opt).attr("selected", "selected");
- }
- });
- $tax_row.find('select').on("change", function() {
- shopping_cart.apply_shipping_rule($(this).val(), this);
- });
- }
- },
-
- apply_shipping_rule: function(rule, btn) {
- return frappe.call({
- btn: btn,
- type: "POST",
- method: "webshop.webshop.shopping_cart.cart.apply_shipping_rule",
- args: { shipping_rule: rule },
- callback: function(r) {
- if(!r.exc) {
- shopping_cart.render(r.message);
- }
- }
- });
- },
-
- place_order: function(btn) {
- shopping_cart.freeze();
-
- return frappe.call({
- type: "POST",
- method: "webshop.webshop.shopping_cart.cart.place_order",
- btn: btn,
- callback: function(r) {
- if(r.exc) {
- shopping_cart.unfreeze();
- var msg = "";
- if(r._server_messages) {
- msg = JSON.parse(r._server_messages || []).join("
");
- }
-
- $("#cart-error")
- .empty()
- .html(msg || frappe._("Something went wrong!"))
- .toggle(true);
- } else {
- $(btn).hide();
- window.location.href = '/orders/' + encodeURIComponent(r.message);
- }
- }
- });
- },
-
- request_quotation: function(btn) {
- shopping_cart.freeze();
-
- return frappe.call({
- type: "POST",
- method: "webshop.webshop.shopping_cart.cart.request_for_quotation",
- btn: btn,
- callback: function(r) {
- if(r.exc) {
- shopping_cart.unfreeze();
- var msg = "";
- if(r._server_messages) {
- msg = JSON.parse(r._server_messages || []).join("
");
- }
-
- $("#cart-error")
- .empty()
- .html(msg || frappe._("Something went wrong!"))
- .toggle(true);
- } else {
- $(btn).hide();
- window.location.href = '/quotations/' + encodeURIComponent(r.message);
- }
- }
- });
- },
-
- bind_coupon_code: function() {
- $(".bt-coupon").on("click", function() {
- shopping_cart.apply_coupon_code(this);
- });
- },
-
- apply_coupon_code: function(btn) {
- return frappe.call({
- type: "POST",
- method: "webshop.webshop.shopping_cart.cart.apply_coupon_code",
- btn: btn,
- args : {
- applied_code : $('.txtcoupon').val(),
- applied_referral_sales_partner: $('.txtreferral_sales_partner').val()
- },
- callback: function(r) {
- if (r && r.message){
- location.reload();
- }
- }
- });
- },
-
- bind_remove_coupon_code: function() {
- $(".bt-remove-coupon-code").on("click", function() {
- shopping_cart.remove_coupon_code(this);
- });
- },
- remove_coupon_code: function(btn) {
- return frappe.call({
- type: "POST",
- method: "webshop.webshop.shopping_cart.cart.remove_coupon_code",
- btn: btn,
- callback: function(r) {
- if (r && r.message){
- location.reload();
- }
- }
- });
- },
+ show_error: function(title, text) {
+ $("#cart-container").html('' +
+ title + '
' + text + '
');
+ },
+
+ bind_events: function() {
+ shopping_cart.bind_place_order();
+ shopping_cart.bind_request_quotation();
+ shopping_cart.bind_change_qty();
+ shopping_cart.bind_remove_cart_item();
+ shopping_cart.bind_change_notes();
+ shopping_cart.bind_coupon_code();
+ shopping_cart.bind_remove_coupon_code();
+ },
+
+ bind_place_order: function() {
+ $(".btn-place-order").on("click", function() {
+ shopping_cart.place_order(this);
+ });
+ },
+
+ bind_request_quotation: function() {
+ $('.btn-request-for-quotation').on('click', function() {
+ shopping_cart.request_quotation(this);
+ });
+ },
+
+ bind_change_qty: function() {
+ // bind update button
+ $(".cart-items").on("change", ".cart-qty", function() {
+ var item_code = $(this).attr("data-item-code");
+ var newVal = $(this).val();
+ shopping_cart.shopping_cart_update({
+ item_code,
+ qty: newVal
+ });
+ });
+
+ $(".cart-items").on('click', '.number-spinner button', function() {
+ var btn = $(this),
+ input = btn.closest('.number-spinner').find('input'),
+ oldValue = input.val().trim(),
+ newVal = 0;
+
+ if (btn.attr('data-dir') == 'up') {
+ newVal = parseInt(oldValue) + 1;
+ } else {
+ if (oldValue > 1) {
+ newVal = parseInt(oldValue) - 1;
+ }
+ }
+ input.val(newVal);
+
+ let notes = input.closest("td").siblings().find(".notes").text().trim();
+ var item_code = input.attr("data-item-code");
+ shopping_cart.shopping_cart_update({
+ item_code,
+ qty: newVal,
+ additional_notes: notes
+ });
+ });
+ },
+
+ bind_change_notes: function() {
+ $('.cart-items').on('change', 'textarea', function() {
+ const $textarea = $(this);
+ const item_code = $textarea.attr('data-item-code');
+ const qty = $textarea.closest('tr').find('.cart-qty').val();
+ const notes = $textarea.val();
+ shopping_cart.shopping_cart_update({
+ item_code,
+ qty,
+ additional_notes: notes
+ });
+ });
+ },
+
+ bind_remove_cart_item: function() {
+ $(".cart-items").on("click", ".remove-cart-item", (e) => {
+ const $remove_cart_item_btn = $(e.currentTarget);
+ var item_code = $remove_cart_item_btn.data("item-code");
+
+ shopping_cart.shopping_cart_update({
+ item_code: item_code,
+ qty: 0
+ });
+ });
+ },
+
+ render_tax_row: function($cart_taxes, doc, shipping_rules) {
+ var shipping_selector;
+ if (shipping_rules) {
+ shipping_selector = '';
+ }
+
+ var $tax_row = $(repl('\
+
\
+
\
+
' +
+ (shipping_selector || '
%(description)s
') +
+ '
\
+
\
+
\
+
\
+
%(formatted_tax_amount)s
\
+
\
+
', doc)).appendTo($cart_taxes);
+
+ if (shipping_selector) {
+ $tax_row.find('select option').each(function(i, opt) {
+ if ($(opt).html() == doc.description) {
+ $(opt).attr("selected", "selected");
+ }
+ });
+ $tax_row.find('select').on("change", function() {
+ shopping_cart.apply_shipping_rule($(this).val(), this);
+ });
+ }
+ },
+
+ apply_shipping_rule: function(rule, btn) {
+ return frappe.call({
+ btn: btn,
+ type: "POST",
+ method: "webshop.webshop.shopping_cart.cart.apply_shipping_rule",
+ args: {
+ shipping_rule: rule
+ },
+ callback: function(r) {
+ if (!r.exc) {
+ shopping_cart.render(r.message);
+ }
+ }
+ });
+ },
+
+ place_order: function(btn) {
+ frappe.call({
+ method: "webshop.webshop.shopping_cart.cart.place_order",
+ btn: btn,
+ callback: function(r) {
+ if (r.message && r.message.payment_url) {
+ window.location.href = r.message.payment_url;
+ } else if (typeof r.message === "string") {
+ // fallback for non-MoMo
+ window.location.href = "/orders?name=" + r.message;
+ }
+ }
+ });
+ },
+
+ request_quotation: function(btn) {
+ shopping_cart.freeze();
+
+ return frappe.call({
+ type: "POST",
+ method: "webshop.webshop.shopping_cart.cart.request_for_quotation",
+ btn: btn,
+ callback: function(r) {
+ if (r.exc) {
+ shopping_cart.unfreeze();
+ var msg = "";
+ if (r._server_messages) {
+ msg = JSON.parse(r._server_messages || []).join("
");
+ }
+
+ $("#cart-error")
+ .empty()
+ .html(msg || frappe._("Something went wrong!"))
+ .toggle(true);
+ } else {
+ $(btn).hide();
+ window.location.href = '/quotations/' + encodeURIComponent(r.message);
+ }
+ }
+ });
+ },
+
+ bind_coupon_code: function() {
+ $(".bt-coupon").on("click", function() {
+ shopping_cart.apply_coupon_code(this);
+ });
+ },
+
+ apply_coupon_code: function(btn) {
+ return frappe.call({
+ type: "POST",
+ method: "webshop.webshop.shopping_cart.cart.apply_coupon_code",
+ btn: btn,
+ args: {
+ applied_code: $('.txtcoupon').val(),
+ applied_referral_sales_partner: $('.txtreferral_sales_partner').val()
+ },
+ callback: function(r) {
+ if (r && r.message) {
+ location.reload();
+ }
+ }
+ });
+ },
+
+ bind_remove_coupon_code: function() {
+ $(".bt-remove-coupon-code").on("click", function() {
+ shopping_cart.remove_coupon_code(this);
+ });
+ },
+
+ remove_coupon_code: function(btn) {
+ return frappe.call({
+ type: "POST",
+ method: "webshop.webshop.shopping_cart.cart.remove_coupon_code",
+ btn: btn,
+ callback: function(r) {
+ if (r && r.message) {
+ location.reload();
+ }
+ }
+ });
+ },
});
frappe.ready(function() {
- if (window.location.pathname === "/cart") {
- $(".cart-icon").hide();
- }
- shopping_cart.parent = $(".cart-container");
- shopping_cart.bind_events();
+ if (window.location.pathname === "/cart") {
+ $(".cart-icon").hide();
+ }
+ shopping_cart.parent = $(".cart-container");
+ shopping_cart.bind_events();
});
function show_terms() {
- var html = $(".cart-terms").html();
- frappe.msgprint(html);
+ var html = $(".cart-terms").html();
+ frappe.msgprint(html);
}
diff --git a/webshop/webshop/shopping_cart/cart.py b/webshop/webshop/shopping_cart/cart.py
index f4ed155e42..220c0d661e 100644
--- a/webshop/webshop/shopping_cart/cart.py
+++ b/webshop/webshop/shopping_cart/cart.py
@@ -1,20 +1,25 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
+import json
+import uuid
+from urllib.parse import urlencode
+
import frappe
import frappe.defaults
from frappe import _, throw
from frappe.contacts.doctype.address.address import get_address_display
from frappe.contacts.doctype.contact.contact import get_contact_name
-from frappe.utils import cint, cstr, flt, get_fullname
+from frappe.utils import cint, cstr, flt, get_fullname, get_url
from frappe.utils.nestedset import get_root_of
+from payments.utils import get_payment_gateway_controller
from erpnext.accounts.utils import get_account_name
+from erpnext.selling.doctype.quotation.quotation import _make_sales_order
from webshop.webshop.doctype.webshop_settings.webshop_settings import (
get_shopping_cart_settings,
)
from webshop.webshop.utils.product import get_web_item_qty_in_stock
-from erpnext.selling.doctype.quotation.quotation import _make_sales_order
class WebsitePriceListMissingError(frappe.ValidationError):
@@ -22,633 +27,703 @@ class WebsitePriceListMissingError(frappe.ValidationError):
def set_cart_count(quotation=None):
- if cint(frappe.db.get_singles_value("Webshop Settings", "enabled")):
- if not quotation:
- quotation = _get_cart_quotation()
- cart_count = cstr(cint(quotation.get("total_qty")))
+ if cint(frappe.db.get_singles_value("Webshop Settings", "enabled")):
+ if not quotation:
+ quotation = _get_cart_quotation()
+ cart_count = cstr(cint(quotation.get("total_qty")))
- if hasattr(frappe.local, "cookie_manager"):
- frappe.local.cookie_manager.set_cookie("cart_count", cart_count)
+ 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()
+ party = get_party()
- if not doc:
- quotation = _get_cart_quotation(party)
- doc = quotation
- set_cart_count(quotation)
+ if not doc:
+ quotation = _get_cart_quotation(party)
+ doc = quotation
+ set_cart_count(quotation)
- addresses = get_address_docs(party=party)
+ addresses = get_address_docs(party=party)
- if not doc.customer_address and addresses:
- update_cart_address("billing", addresses[0].name)
+ if not doc.customer_address and addresses:
+ update_cart_address("billing", addresses[0].name)
- return {
- "doc": decorate_quotation_doc(doc),
- "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("Webshop Settings"),
- }
+ return {
+ "doc": decorate_quotation_doc(doc),
+ "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("Webshop 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"
- ]
+ 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"
+ ]
@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"
- ]
+ 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"
+ ]
@frappe.whitelist()
def place_order():
- quotation = _get_cart_quotation()
- cart_settings = frappe.get_cached_doc("Webshop Settings")
- quotation.company = cart_settings.company
-
- quotation.flags.ignore_permissions = True
- quotation.submit()
-
- if quotation.quotation_to == "Lead" and quotation.party_name:
- # company used to create customer accounts
- frappe.defaults.set_user_default("company", quotation.company)
-
- if not (quotation.shipping_address_name or quotation.customer_address):
- frappe.throw(_("Set Shipping Address or Billing Address"))
-
- 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"
- )
- is_stock_item = frappe.db.get_value("Item", item.item_code, "is_stock_item")
-
- if is_stock_item:
- item_stock = get_web_item_qty_in_stock(
- item.item_code, "website_warehouse"
- )
- if not cint(item_stock.in_stock):
- throw(_("{0} Not in Stock").format(item.item_code))
- if item.qty > item_stock.stock_qty:
- throw(
- _("Only {0} in Stock for item {1}").format(
- item_stock.stock_qty, item.item_code
- )
- )
-
- sales_order.flags.ignore_permissions = True
- sales_order.insert()
- sales_order.submit()
-
- if hasattr(frappe.local, "cookie_manager"):
- frappe.local.cookie_manager.delete_cookie("cart_count")
-
- return sales_order.name
+ frappe.logger("momo").error("MOMO_DEBUG: place_order called")
+ quotation = _get_cart_quotation()
+ cart_settings = frappe.get_cached_doc("Webshop Settings")
+
+ if not (quotation.shipping_address_name or quotation.customer_address):
+ frappe.throw(_("Set Shipping Address or Billing Address"))
+
+ # Snapshot the quotation data into a cache token
+ token = str(uuid.uuid4())
+
+ # Reject if this quotation already has a completed SO
+ existing_so = frappe.db.get_value(
+ "Sales Order", {"po_no": quotation.name, "docstatus": 1}, "name"
+ )
+ if existing_so:
+ has_pe = frappe.db.exists(
+ "Payment Entry Reference",
+ {
+ "reference_doctype": "Sales Order",
+ "reference_name": existing_so,
+ },
+ )
+ if has_pe:
+ frappe.throw(
+ _(
+ "This cart has already been paid. Please refresh the page to start a new order."
+ ),
+ title=_("Already Paid"),
+ )
+
+ cart_data = {
+ "quotation_name": quotation.name,
+ "customer": quotation.party_name,
+ "company": cart_settings.company,
+ "currency": quotation.currency,
+ "grand_total": quotation.grand_total,
+ "items": [
+ {
+ "item_code": d.item_code,
+ "qty": d.qty,
+ "rate": d.rate,
+ "warehouse": d.warehouse,
+ "uom": d.uom,
+ }
+ for d in quotation.get("items")
+ ],
+ "taxes": [
+ {
+ "charge_type": t.charge_type,
+ "account_head": t.account_head,
+ "description": t.description,
+ "rate": t.rate,
+ }
+ for t in quotation.get("taxes", [])
+ ],
+ "shipping_address_name": quotation.shipping_address_name,
+ "customer_address": quotation.customer_address,
+ "contact_email": quotation.contact_email,
+ "customer_name": quotation.customer_name,
+ }
+
+ # Store in Frappe cache for 30 minutes
+ frappe.cache().set_value(
+ f"momo_pending_{token}", json.dumps(cart_data), expires_in_sec=1800
+ )
+
+ # Get gateway
+ momo_gateway = get_default_momo_gateway()
+ controller = get_payment_gateway_controller("MoMo-" + momo_gateway)
+
+ params = urlencode(
+ {
+ "token": token,
+ "amount": quotation.grand_total,
+ "currency": quotation.currency,
+ "gateway_name": momo_gateway,
+ "title": f"Order from {quotation.customer_name}",
+ }
+ )
+ payment_url = get_url(f"/momo_checkout?{params}")
+
+ return {"payment_url": payment_url}
@frappe.whitelist()
def request_for_quotation():
- quotation = _get_cart_quotation()
- quotation.flags.ignore_permissions = True
+ quotation = _get_cart_quotation()
+ quotation.flags.ignore_permissions = True
- if get_shopping_cart_settings().save_quotations_as_draft:
- quotation.save()
- else:
- quotation.submit()
+ if get_shopping_cart_settings().save_quotations_as_draft:
+ quotation.save()
+ else:
+ quotation.submit()
- return quotation.name
+ return quotation.name
@frappe.whitelist()
def update_cart(item_code, qty, additional_notes=None, with_items=False):
- quotation = _get_cart_quotation()
-
- empty_card = False
- qty = flt(qty)
- if qty == 0:
- quotation_items = quotation.get("items", {"item_code": ["!=", item_code]})
- if quotation_items:
- quotation.set("items", quotation_items)
- else:
- empty_card = True
-
- else:
- warehouse = frappe.get_cached_value(
- "Website Item", {"item_code": item_code}, "website_warehouse"
- )
-
- 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,
- "warehouse": warehouse,
- },
- )
- else:
- quotation_items[0].qty = qty
- quotation_items[0].warehouse = warehouse
- quotation_items[0].additional_notes = additional_notes
-
- apply_cart_settings(quotation=quotation)
-
- quotation.flags.ignore_permissions = True
- quotation.payment_schedule = []
- if not empty_card:
- quotation.save()
- else:
- quotation.delete()
- quotation = None
-
- set_cart_count(quotation)
-
- 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
- ),
- }
- else:
- return {"name": quotation.name}
+ quotation = _get_cart_quotation()
+
+ empty_card = False
+ qty = flt(qty)
+ if qty == 0:
+ quotation_items = quotation.get("items", {"item_code": ["!=", item_code]})
+ if quotation_items:
+ quotation.set("items", quotation_items)
+ else:
+ empty_card = True
+ else:
+ warehouse = frappe.get_cached_value(
+ "Website Item", {"item_code": item_code}, "website_warehouse"
+ )
+
+ 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,
+ "warehouse": warehouse,
+ },
+ )
+ else:
+ quotation_items[0].qty = qty
+ quotation_items[0].warehouse = warehouse
+ quotation_items[0].additional_notes = additional_notes
+
+ apply_cart_settings(quotation=quotation)
+
+ quotation.flags.ignore_permissions = True
+ quotation.payment_schedule = []
+ if not empty_card:
+ quotation.save()
+ else:
+ quotation.delete()
+ quotation = None
+
+ set_cart_count(quotation)
+
+ 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
+ ),
+ }
+ else:
+ return {"name": quotation.name}
@frappe.whitelist()
def get_shopping_cart_menu(context=None):
- if not context:
- context = get_cart_quotation()
+ 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"})
- address = frappe.get_doc(doc)
- address.save(ignore_permissions=True)
+ doc = frappe.parse_json(doc)
+ doc.update({"doctype": "Address"})
+ address = frappe.get_doc(doc)
+ address.save(ignore_permissions=True)
- return address
+ 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")
- for fieldname in ("lead_name", "company_name", "email_id", "phone"):
- lead_doc.set(fieldname, lead.get(fieldname))
+ lead = frappe.parse_json(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"]})
+ 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.add_comment(
- "Comment",
- text="""
-
-
{subject}
-
{message}
-
- """.format(
- subject=subject, message=message
- ),
- )
+ lead_doc.add_comment(
+ "Comment",
+ text="""
+
+
{subject}
+
{message}
+
+ """.format(
+ subject=subject, message=message
+ ),
+ )
- return lead_doc
+ 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):
- quotation = _get_cart_quotation()
- address_doc = frappe.get_doc("Address", address_name).as_dict()
- address_display = get_address_display(address_doc)
-
- if address_type.lower() == "billing":
- quotation.customer_address = address_name
- quotation.address_display = address_display
- quotation.shipping_address_name = (
- quotation.shipping_address_name or address_name
- )
- address_doc = next(
- (doc for doc in get_billing_addresses() if doc["name"] == address_name),
- None,
- )
- elif address_type.lower() == "shipping":
- 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,
- )
- apply_cart_settings(quotation=quotation)
-
- quotation.flags.ignore_permissions = True
- quotation.save()
-
- context = get_cart_quotation(quotation)
- 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
- ),
- }
+ quotation = _get_cart_quotation()
+ address_doc = frappe.get_doc("Address", address_name).as_dict()
+ address_display = get_address_display(address_doc)
+
+ if address_type.lower() == "billing":
+ quotation.customer_address = address_name
+ quotation.address_display = address_display
+ quotation.shipping_address_name = quotation.shipping_address_name or address_name
+ address_doc = next(
+ (doc for doc in get_billing_addresses() if doc["name"] == address_name),
+ None,
+ )
+ elif address_type.lower() == "shipping":
+ 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,
+ )
+ apply_cart_settings(quotation=quotation)
+
+ quotation.flags.ignore_permissions = True
+ quotation.save()
+
+ context = get_cart_quotation(quotation)
+ 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
+ ),
+ }
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 get_root_of("Territory")
- )
+ territory = None
+ geoip_country = frappe.session.get("session_country")
+ if geoip_country:
+ territory = frappe.db.get_value("Territory", geoip_country)
+
+ return territory or get_root_of("Territory")
+
+
+def get_default_momo_gateway():
+ """Return the first active MoMo Settings gateway name."""
+ momo = frappe.db.get_value("MoMo Settings", {}, "gateway_name")
+ if not momo:
+ frappe.throw(_("No MoMo gateway configured in MoMo Settings"))
+ return momo
+
+
+def create_momo_payment_request(sales_order_name, gateway_name):
+ """Create a draft Payment Request for a Sales Order (no auto‑push)."""
+ so = frappe.get_doc("Sales Order", sales_order_name)
+ gateway_full = (
+ "MoMo-" + gateway_name if not gateway_name.startswith("MoMo-") else gateway_name
+ )
+
+ pga = frappe.db.get_value(
+ "Payment Gateway Account",
+ {"payment_gateway": gateway_full},
+ ["name", "payment_account", "currency"],
+ as_dict=True,
+ )
+
+ pr = frappe.new_doc("Payment Request")
+ pr.payment_request_type = "Inward"
+ pr.party_type = "Customer"
+ pr.party = so.customer
+ pr.reference_doctype = "Sales Order"
+ pr.reference_name = so.name
+ pr.payment_gateway_account = pga.name if pga else ""
+ pr.payment_gateway = gateway_full
+ pr.payment_account = pga.payment_account if pga else ""
+ pr.currency = so.currency
+ pr.grand_total = so.grand_total
+ pr.base_grand_total = so.base_grand_total
+ pr.outstanding_amount = so.grand_total
+ pr.base_outstanding_amount = so.base_grand_total
+ pr.email_to = so.customer
+ pr.subject = f"Payment Request for {so.name}"
+
+ # IMPORTANT: Do NOT submit (docstatus=0) so that the automatic push is not triggered.
+ pr.flags.ignore_permissions = True
+ pr.insert()
+ return pr
def decorate_quotation_doc(doc):
- for d in doc.get("items", []):
- item_code = d.item_code
- fields = ["web_item_name", "thumbnail", "website_image", "description", "route"]
+ 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.web_item_name = variant_data.item_name
+ # 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.web_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:]
+ 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
+ )
+ )
- website_warehouse = frappe.get_cached_value(
- "Website Item", {"item_code": item_code}, "website_warehouse"
- )
+ website_warehouse = frappe.get_cached_value(
+ "Website Item", {"item_code": item_code}, "website_warehouse"
+ )
- d.warehouse = website_warehouse
+ d.warehouse = website_warehouse
- return doc
+ return doc
def _get_cart_quotation(party=None):
- """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,
- )
-
- if quotation:
- qdoc = frappe.get_doc("Quotation", quotation[0].name)
- else:
- company = frappe.db.get_single_value("Webshop Settings", "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.contact_person = frappe.db.get_value(
- "Contact", {"email_id": frappe.session.user}
- )
- qdoc.contact_email = frappe.session.user
-
- qdoc.flags.ignore_permissions = True
- qdoc.run_method("set_missing_values")
- apply_cart_settings(party, qdoc)
-
- return qdoc
+ """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,
+ )
+
+ if quotation:
+ qdoc = frappe.get_doc("Quotation", quotation[0].name)
+ else:
+ company = frappe.db.get_single_value("Webshop Settings", "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.contact_person = frappe.db.get_value(
+ "Contact", {"email_id": frappe.session.user}
+ )
+ qdoc.contact_email = frappe.session.user
+
+ qdoc.flags.ignore_permissions = True
+ qdoc.run_method("set_missing_values")
+ apply_cart_settings(party, qdoc)
+
+ return qdoc
def update_party(fullname, company_name=None, mobile_no=None, phone=None):
- party = get_party()
+ party = get_party()
- party.customer_name = company_name or fullname
- party.customer_type = "Company" if company_name else "Individual"
+ party.customer_name = company_name or fullname
+ party.customer_type = "Company" if company_name else "Individual"
- contact_name = frappe.db.get_value("Contact", {"email_id": frappe.session.user})
- contact = frappe.get_doc("Contact", contact_name)
- contact.first_name = fullname
- contact.last_name = None
- contact.customer_name = party.customer_name
- contact.mobile_no = mobile_no
- contact.phone = phone
- contact.flags.ignore_permissions = True
- contact.save()
+ contact_name = frappe.db.get_value("Contact", {"email_id": frappe.session.user})
+ contact = frappe.get_doc("Contact", contact_name)
+ contact.first_name = fullname
+ contact.last_name = None
+ contact.customer_name = party.customer_name
+ contact.mobile_no = mobile_no
+ contact.phone = phone
+ contact.flags.ignore_permissions = True
+ contact.save()
- party_doc = frappe.get_doc(party.as_dict())
- party_doc.flags.ignore_permissions = True
- party_doc.save()
+ party_doc = frappe.get_doc(party.as_dict())
+ party_doc.flags.ignore_permissions = True
+ party_doc.save()
- qdoc = _get_cart_quotation(party)
- if not qdoc.get("__islocal"):
- qdoc.customer_name = company_name or fullname
- qdoc.run_method("set_missing_lead_customer_details")
- qdoc.flags.ignore_permissions = True
- qdoc.save()
+ qdoc = _get_cart_quotation(party)
+ if not qdoc.get("__islocal"):
+ qdoc.customer_name = company_name or fullname
+ qdoc.run_method("set_missing_lead_customer_details")
+ qdoc.flags.ignore_permissions = True
+ qdoc.save()
def apply_cart_settings(party=None, quotation=None):
- if not party:
- party = get_party()
- if not quotation:
- quotation = _get_cart_quotation(party)
+ if not party:
+ party = get_party()
+ if not quotation:
+ quotation = _get_cart_quotation(party)
- cart_settings = frappe.get_cached_doc("Webshop Settings")
+ cart_settings = frappe.get_cached_doc("Webshop Settings")
- set_price_list_and_rate(quotation, cart_settings)
+ set_price_list_and_rate(quotation, cart_settings)
- quotation.run_method("calculate_taxes_and_totals")
+ quotation.run_method("calculate_taxes_and_totals")
- set_taxes(quotation, cart_settings)
+ set_taxes(quotation, cart_settings)
- _apply_shipping_rule(party, quotation, cart_settings)
+ _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 based on billing territory"""
- _set_price_list(cart_settings, quotation)
+ _set_price_list(cart_settings, quotation)
- # reset values
- 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
+ # reset values
+ 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
- # refetch values
- quotation.run_method("set_price_list_and_item_details")
+ # refetch values
+ quotation.run_method("set_price_list_and_item_details")
- if hasattr(frappe.local, "cookie_manager"):
- # set it in cookies for using in product page
- frappe.local.cookie_manager.set_cookie(
- "selling_price_list", quotation.selling_price_list
- )
+ if hasattr(frappe.local, "cookie_manager"):
+ # set it in cookies for using in product page
+ frappe.local.cookie_manager.set_cookie(
+ "selling_price_list", quotation.selling_price_list
+ )
def _set_price_list(cart_settings, quotation=None):
- """Set price list based on customer or shopping cart default"""
- from erpnext.accounts.party import get_default_price_list
+ """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
+ party_name = quotation.get("party_name") if quotation else get_party().get("name")
+ selling_price_list = None
- # check if default customer price list exists
- if party_name and frappe.db.exists("Customer", party_name):
- selling_price_list = get_default_price_list(
- frappe.get_doc("Customer", party_name)
- )
+ # check if default customer price list exists
+ if party_name and frappe.db.exists("Customer", party_name):
+ selling_price_list = get_default_price_list(
+ frappe.get_doc("Customer", party_name)
+ )
- # check default price list in shopping cart
- if not selling_price_list:
- selling_price_list = cart_settings.price_list
+ # check default price list in shopping cart
+ if not selling_price_list:
+ selling_price_list = cart_settings.price_list
- if quotation:
- quotation.selling_price_list = selling_price_list
+ if quotation:
+ quotation.selling_price_list = selling_price_list
- return selling_price_list
+ 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.set("taxes", [])
- #
- # # append taxes
- quotation.append_taxes_from_master()
- quotation.append_taxes_from_item_tax_template()
+ """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.set("taxes", [])
+
+ # append taxes
+ quotation.append_taxes_from_master()
+ quotation.append_taxes_from_item_tax_template()
def get_party(user=None):
- if not user:
- user = frappe.session.user
-
- contact_name = get_contact_name(user)
- party = None
-
- if 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_cached_doc("Webshop Settings")
-
- debtors_account = ""
-
- if cart_settings.enable_checkout:
- debtors_account = get_debtors_account(cart_settings)
-
- if party:
- doc = frappe.get_doc(party_doctype, party)
- if doc.doctype in ["Customer", "Supplier"]:
- if not frappe.db.exists("Portal User", {"parent": doc.name, "user": user}):
- doc.append("portal_users", {"user": user})
- doc.flags.ignore_permissions = True
- doc.flags.ignore_mandatory = True
- doc.save()
-
- return doc
-
- elif not frappe.db.exists("Portal User", {"user": user}):
- if not cart_settings.enabled:
- frappe.local.flags.redirect_location = "/contact"
- 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.append("portal_users", {"user": user})
-
- if 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.flags.ignore_mandatory = True
- contact.insert(ignore_permissions=True)
-
- return customer
- else:
- customer = frappe.db.get_value(
- "Portal User", {"user": user}, ["parent"]
- )
-
- if frappe.db.exists("Customer", customer):
- return frappe.get_doc("Customer", customer)
+ if not user:
+ user = frappe.session.user
+
+ contact_name = get_contact_name(user)
+ party = None
+
+ if 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_cached_doc("Webshop Settings")
+
+ debtors_account = ""
+
+ if cart_settings.enable_checkout:
+ debtors_account = get_debtors_account(cart_settings)
+
+ if party:
+ doc = frappe.get_doc(party_doctype, party)
+ if doc.doctype in ["Customer", "Supplier"]:
+ if not frappe.db.exists("Portal User", {"parent": doc.name, "user": user}):
+ doc.append("portal_users", {"user": user})
+ doc.flags.ignore_permissions = True
+ doc.flags.ignore_mandatory = True
+ doc.save()
+
+ return doc
+
+ elif not frappe.db.exists("Portal User", {"user": user}):
+ if not cart_settings.enabled:
+ frappe.local.flags.redirect_location = "/contact"
+ 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.append("portal_users", {"user": user})
+
+ if 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.flags.ignore_mandatory = True
+ contact.insert(ignore_permissions=True)
+
+ return customer
+ else:
+ customer = frappe.db.get_value("Portal User", {"user": user}, ["parent"])
+
+ if frappe.db.exists("Customer", customer):
+ return frappe.get_doc("Customer", 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
-
- 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,
- )
-
- 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)
-
- return debtors_account.name
-
- else:
- return debtors_account_name
+ 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
+
+ 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,
+ )
+
+ 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)
+
+ return debtors_account.name
+
+ else:
+ return debtors_account_name
def get_address_docs(
@@ -659,162 +734,162 @@ def get_address_docs(
limit_page_length=20,
party=None,
):
- if not party:
- party = get_party()
+ if not party:
+ party = get_party()
- if not party:
- return []
+ 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 = []
+ out = []
- for a in address_names:
- address = frappe.get_doc("Address", a.parent)
- address.display = get_address_display(address.as_dict())
- out.append(address)
+ for a in address_names:
+ address = frappe.get_doc("Address", a.parent)
+ address.display = get_address_display(address.as_dict())
+ out.append(address)
- return out
+ return out
@frappe.whitelist()
def apply_shipping_rule(shipping_rule):
- quotation = _get_cart_quotation()
+ quotation = _get_cart_quotation()
- quotation.shipping_rule = shipping_rule
+ quotation.shipping_rule = shipping_rule
- apply_cart_settings(quotation=quotation)
+ apply_cart_settings(quotation=quotation)
- quotation.flags.ignore_permissions = True
- quotation.save()
+ quotation.flags.ignore_permissions = True
+ quotation.save()
- return get_cart_quotation(quotation)
+ 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)
+ if not quotation.shipping_rule:
+ shipping_rules = get_shipping_rules(quotation, cart_settings)
- if not shipping_rules:
- return
+ if not shipping_rules:
+ return
- elif quotation.shipping_rule not in shipping_rules:
- quotation.shipping_rule = shipping_rules[0]
+ elif quotation.shipping_rule not in shipping_rules:
+ quotation.shipping_rule = shipping_rules[0]
- if quotation.shipping_rule:
- quotation.run_method("apply_shipping_rule")
- quotation.run_method("calculate_taxes_and_totals")
+ if quotation.shipping_rule:
+ quotation.run_method("apply_shipping_rule")
+ quotation.run_method("calculate_taxes_and_totals")
def get_applicable_shipping_rules(party=None, quotation=None):
- shipping_rules = get_shipping_rules(quotation)
+ shipping_rules = get_shipping_rules(quotation)
- if shipping_rules:
- rule_label_map = frappe.db.get_values("Shipping Rule", shipping_rules, "label")
- # we need this in sorted order as per the position of the rule in the settings page
- return [[rule, rule] for rule in shipping_rules]
+ if shipping_rules:
+ # 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()
-
- shipping_rules = []
- if quotation.shipping_address_name:
- country = frappe.db.get_value(
- "Address", quotation.shipping_address_name, "country"
- )
- if country:
- sr_country = frappe.qb.DocType("Shipping Rule Country")
- sr = frappe.qb.DocType("Shipping Rule")
- query = (
- frappe.qb.from_(sr_country)
- .join(sr)
- .on(sr.name == sr_country.parent)
- .select(sr.name)
- .distinct()
- .where((sr_country.country == country) & (sr.disabled != 1) & (sr.shipping_rule_type == "Selling"))
- )
- result = query.run(as_list=True)
- shipping_rules = [x[0] for x in result]
-
- return shipping_rules
+ if not quotation:
+ quotation = _get_cart_quotation()
+
+ shipping_rules = []
+ if quotation.shipping_address_name:
+ country = frappe.db.get_value(
+ "Address", quotation.shipping_address_name, "country"
+ )
+ if country:
+ sr_country = frappe.qb.DocType("Shipping Rule Country")
+ sr = frappe.qb.DocType("Shipping Rule")
+ query = (
+ frappe.qb.from_(sr_country)
+ .join(sr)
+ .on(sr.name == sr_country.parent)
+ .select(sr.name)
+ .distinct()
+ .where(
+ (sr_country.country == country)
+ & (sr.disabled != 1)
+ & (sr.shipping_rule_type == "Selling")
+ )
+ )
+ 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
+ """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"]
- )
- for value in address_fields:
- territory = frappe.db.get_value("Territory", value)
- if territory:
- break
+ if address_name:
+ 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:
+ break
- return territory
+ return territory
def show_terms(doc):
- return doc.tc_name
+ return doc.tc_name
@frappe.whitelist(allow_guest=True)
def apply_coupon_code(applied_code, applied_referral_sales_partner):
- quotation = True
+ if not applied_code:
+ frappe.throw(_("Please enter a coupon code"))
- if not applied_code:
- frappe.throw(_("Please enter a coupon 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_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
- coupon_name = coupon_list[0].name
+ from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code
- from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code
+ validate_coupon_code(coupon_name)
+ quotation = _get_cart_quotation()
+ quotation.ignore_pricing_rule = 0
+ quotation.coupon_code = coupon_name
+ quotation.flags.ignore_permissions = True
+ quotation.save()
- validate_coupon_code(coupon_name)
- quotation = _get_cart_quotation()
- quotation.ignore_pricing_rule = 0
- quotation.coupon_code = coupon_name
- quotation.flags.ignore_permissions = True
- quotation.save()
+ if 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
+ quotation.flags.ignore_permissions = True
+ quotation.save()
- if 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
- quotation.flags.ignore_permissions = True
- quotation.save()
+ return quotation
- return quotation
-
@frappe.whitelist(allow_guest=True)
def remove_coupon_code():
- quotation = _get_cart_quotation()
- quotation.coupon_code = ""
- quotation.referral_sales_partner = ""
- quotation.flags.ignore_permissions = True
+ quotation = _get_cart_quotation()
+ quotation.coupon_code = ""
+ quotation.referral_sales_partner = ""
+ quotation.flags.ignore_permissions = True
- # reset discount amount if coupon code is removed (on desk it is done in client side)
- # as we are enabling ignore_pricing_rule, so we also need to manually reset discount percentage
- quotation.discount_amount = 0
- quotation.additional_discount_percentage = 0
- quotation.ignore_pricing_rule = 1
+ # reset discount amount if coupon code is removed
+ quotation.discount_amount = 0
+ quotation.additional_discount_percentage = 0
+ quotation.ignore_pricing_rule = 1
- quotation.save()
+ quotation.save()
- return quotation
\ No newline at end of file
+ return quotation