Netlify + Gatsby JS + ERPNext (JAMStack) => Has anyone used it?

I was just wondering if someone had tried to use ERPNext // Frappe Apps as CMS and executed any e-commerce project. Typically someone would use the JAMStack (JavaScript, API, Markdown) and deploy on Netlify.

Wanted to understand their experience on how what limitations // challenges did you face using ERPNext’s REST API.

Would be great to hear some thoughts.

1 Like

I created one ecommerce mobile app in android native. I didn’t create any ecommerce plateform using js framework.

I created some other portal using vuejs and use erpnext as backend.

I didn’t used frappe framework standard Rest api. I developed my custom rest api in frappe framework and in that I didn’t face any limitations yet.

Thanks @Maheshwari_Bhavesh.

Why did you re-build custom REST API instead of using the out of the box? Did you have any particular use cases that were not met? Thanks again.

Yes @Not_a_countant
We started development using frappe provided REST API but after some new updates, we faced the following issue.

  1. You know we need to pass all mandatory param from the mobile app and that we did initially when we started development for a mobile app but after the update or some reason if we make some more field mandatory then we need to update the mobile app again even that mandatory field required some default value or we just need to pull from some other master, for example, we add one mandatory custom field in the sales order form ‘GSTIN’ and that we need to fetch from address master so in this case, if we developed custom API then we can fulfill that requirement without updating the mobile app.

  2. If field_name update in a new update from the community then also we need to update the mobile app. You know e-commerce is client-side so they will update or not that depends on the customer so if the customer, not updates then some feature maybe not work. We faced this issue for stock entry doctype before there is one ‘Select’ field that field_name is entry_type and after new update field name change to stock_entry_type so this scenario hard to handle in out of the box API but in custom, we just need to change field_name in our custom API python function.

  3. In custom API I have more flexibility like:
    - I can write an API that accepts only that param that I want from an external system.
    - I can use ignore permissions, ignore mandatory fields, and many more flags if required.
    - I am able to write error handling logic and generate some logs for API calls if failed so the admin or technical person can understand why that error happened and easy to debug.

3 Likes

Thanks @Maheshwari_Bhavesh
Are you able to share what you used to build your new API?

I can not share the repository due to some contract with the client but I can share one example code.

from __future__ import unicode_literals
import frappe
import erpnext
import json
from frappe.utils import getdate, today, add_days,cstr,cint

@frappe.whitelist()
def generate_response(_type, status=None, message=None, data=None, error=None):
	response = {}
	if _type == "S":
		if status:
			frappe.response["status_code"] = int(status)
		else:
			frappe.response["status_code"] = 200
		frappe.response["msg"] = message
		frappe.response["data"] = data
	else:
		error_log = frappe.log_error(frappe.get_traceback())
		if status:
			frappe.response["status_code"] = status
		else:
			frappe.response["status_code"] = 500
		if message:
			frappe.response["msg"] = message
		else:
			frappe.response["msg"] = "Something Went Wrong"
		frappe.response["msg"] = message
		frappe.response["data"] = []


@frappe.whitelist()
def create_customer(customer_data=None,address_data=None):
	try:
		customer_data = json.loads(customer_data)
		res = get_customer(customer_data.get("mobile_no"))
		if not customer_data.get("customer_name"):
			return generate_response("S","400",message="Customer Name Required For Customer Registration",data=[])
		if not customer_data.get("mobile_no"):
			return generate_response("S","400",message="Mobile No Is Required For Customer Registration",data=[])
		if res:
			return generate_response("S","409",message="Customer Already Registered",data=res)
		doc = frappe.get_doc(dict(
			doctype = "Customer",
			customer_name = customer_data.get("customer_name"),
			mobile_no = customer_data.get("mobile_no"),
			email_id = customer_data.get("email_id")
		)).insert(ignore_permissions = True)
		if address_data:
			address_data = json.loads(address_data)
			if address_data.values():
				create_address(doc.name,address_data)
			return generate_response("S","200",message="Customer Successfully Created",data=doc)
		else:
			return generate_response("S","200",message="Customer Successfully Created",data=doc)
	except Exception as e:
		return generate_response("F", message="Something Wrong Please Check With Administrator",error=e)

def create_address(customer,address_data=None):
	address_doc = frappe.get_doc(dict(
		doctype = "Address",
		address_line1 = address_data.get("address_line1"),
		address_line2 = address_data.get("address_line2"),
		city = address_data.get("city"),
		state = address_data.get("state"),
		country = address_data.get("country"),
		pincode = address_data.get("pincode")
	))
	address_doc.append("links",{
		"link_doctype" : "Customer",
		"link_name" : customer
	})
	address_doc.insert(ignore_permissions = True)


@frappe.whitelist()
def create_order(order_data=None):
	try:
		customer_id = ""
		order_data = json.loads(order_data)
		validation_result = validate_order(order_data)
		if not validation_result:
			return validation_result
		if order_data.get("mobile_no"):
			customer_id = get_customer(order_data)
			if not customer_id:
				customer_id = make_customer(customer_data=order_data)
		order = frappe.get_doc(dict(
			doctype = "Sales Order",
			customer = customer_id,
			delivery_date = add_days(order_data.get("posting_date"),5),
			company = frappe.db.get_value("Global Defaults","Global Defaults","default_company"),
			posting_date = today(),
			items = order_data.get("items"),
			selling_price_list = "Standard"
		))
		order.run_method("set_missing_values")
		order.ignore_mandatory = True
		order.insert(ignore_permissions = True)
		if order:
			return generate_response("S","200",message="Order Successfully Places",data=order)
	except Exception as e:
		return generate_response("F", message="Something Wrong Please Check With Administrator",error=e)

def validate_order(order_data):
	field_lable_map = {
		"mobile_no":"Mobile Number",
		"customer_name" : "Customer Name",
		"items" : "Items"
	}
	field_val_list = ["mobile_no","customer_name","items"]
	for row in field_val_list: 
		if not order_data.get(row):
			msg = "{0} Is Required For Place Order".format(field_lable_map.get(row))
			return generate_response("S","400",message=msg,data=order_data)
	item_exist = True
	item_code = ''
	for item in order_data.get("items"):
		# return item.get("item_code")
		if frappe.db.exists("Item",item.get("item_code")):
			continue
		else:
			item_exist = False
			item_code = item.get("item_code")
			break
	if not item_exist:
		return generate_response("S","404",message="{0} Item Code(SKU) Not Match".format(item_code),data=order_data)
	return False
	
@frappe.whitelist()
def get_customer(mobile_no):
	customers = frappe.get_all("Customer",filters={"mobile_no":mobile_no},fields=["name"])
	if customers:
		return customers[0].name
	else:
		return False


def make_customer(customer_data):
	doc = frappe.get_doc(dict(
		doctype = "Customer",
		customer_name = customer_data.get("customer_name"),
		mobile_no = customer_data.get("mobile_no"),
		email_id = customer_data.get("email_id")
	)).insert(ignore_permissions = True)
	return doc.name
4 Likes

Thank you for code sharing, can I ask you about the user login method which one you are using, and why?

Initially, I used the standard login method you know /API/method/login but sometimes I need to perform some operation so now I am overriding the login function and writing whatever logic I want like below i want to validate branch and send key details as well in response if logged in successfully.

@frappe.whitelist(allow_guest = True)
def pos_login(usr,pwd):
    login_manager = LoginManager()
    login_manager.authenticate(usr,pwd)
    login_manager.post_login()
    validate_branch(login_manager.user)
    if frappe.response['message'] == 'Logged In':
        frappe.response['user'] = login_manager.user
        frappe.response['key_details'] = generate_key(login_manager.user)
3 Likes

Thank you, This helped me a lot

That’s great.

Hello Bhavesh Sir, I have one doubt regarding above code snap. While authenticating user, can we check status field value in other custom doctype. I have one custom doctype in which status is set. If that status is active then I want to allow user to login, if not active then prevent user from login.