Custom Quick entry version 1 done. Zip API done and Customer backend added.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
__version__ = "0.1.0"
|
||||
81
ns_app/api/customer.py
Normal file
81
ns_app/api/customer.py
Normal file
@@ -0,0 +1,81 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_customer_full(**data):
|
||||
frappe.only_for("System Manager", "Sales User", "Sales Manager")
|
||||
|
||||
required = [
|
||||
"customer_name",
|
||||
"customer_type",
|
||||
"customer_group",
|
||||
"mobile_no",
|
||||
"address_line1",
|
||||
"pincode",
|
||||
"country"
|
||||
]
|
||||
|
||||
for field in required:
|
||||
if not data.get(field):
|
||||
frappe.throw(_(f"Missing required field: {field}"))
|
||||
|
||||
# Prevent duplicates
|
||||
if frappe.db.exists("Customer", {"customer_name": data["customer_name"]}):
|
||||
frappe.throw(_("Customer already exists"))
|
||||
|
||||
try:
|
||||
frappe.db.begin()
|
||||
|
||||
customer = frappe.get_doc({
|
||||
"doctype": "Customer",
|
||||
"customer_name": data["customer_name"],
|
||||
"customer_type": data["customer_type"],
|
||||
"customer_group": data["customer_group"],
|
||||
"territory": "United States",
|
||||
"custom_auto_pay_status": "Active" if data.get("custom_auto_pay_enabled") else "Disabled",
|
||||
"custom_send_via": data.get("custom_send_via"),
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
contact = frappe.get_doc({
|
||||
"doctype": "Contact",
|
||||
"first_name": data["customer_name"]
|
||||
if data["customer_type"] == "Individual"
|
||||
else data["customer_name"],
|
||||
"email_ids": [{
|
||||
"email_id": data.get("email_id"),
|
||||
"is_primary": 1
|
||||
}] if data.get("email_id") else [],
|
||||
"phone_nos": [{
|
||||
"phone": data["mobile_no"],
|
||||
"is_primary_phone": 1
|
||||
}],
|
||||
"links": [{
|
||||
"link_doctype": "Customer",
|
||||
"link_name": customer.name
|
||||
}]
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
address = frappe.get_doc({
|
||||
"doctype": "Address",
|
||||
"address_title": customer.name,
|
||||
"address_type": "Billing",
|
||||
"address_line1": data["address_line1"],
|
||||
"address_line2": data.get("address_line2"),
|
||||
"city": data.get("city"),
|
||||
"state": data.get("state"),
|
||||
"pincode": data["pincode"],
|
||||
"country": data["country"],
|
||||
"links": [{
|
||||
"link_doctype": "Customer",
|
||||
"link_name": customer.name
|
||||
}]
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
frappe.db.commit()
|
||||
|
||||
return customer.name
|
||||
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
frappe.log_error(frappe.get_traceback(), "NS App: Create Customer Failed")
|
||||
raise
|
||||
@@ -7,7 +7,7 @@ app_license = "MIT"
|
||||
|
||||
# Load on every page
|
||||
app_include_js = [
|
||||
"/assets/ns_erpnext_app/js/custom.js"
|
||||
"/assets/ns_app/js/customer_quick_entry.js"
|
||||
]
|
||||
|
||||
# Load on Sales Invoice form
|
||||
|
||||
200
ns_app/public/js/customer_quick_entry.js
Normal file
200
ns_app/public/js/customer_quick_entry.js
Normal file
@@ -0,0 +1,200 @@
|
||||
frappe.provide("ns_app.customer");
|
||||
|
||||
// Preserve original quick entry
|
||||
const _make_quick_entry = frappe.ui.form.make_quick_entry;
|
||||
|
||||
// Override
|
||||
frappe.ui.form.make_quick_entry = function (doctype, after_insert) {
|
||||
if (doctype === "Customer") {
|
||||
console.log("NS App: Intercepted Customer Quick Entry");
|
||||
ns_app.customer.open_quick_entry({
|
||||
callback: after_insert
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
return _make_quick_entry.apply(this, arguments);
|
||||
};
|
||||
|
||||
ns_app.customer.open_quick_entry = function (opts = {}) {
|
||||
console.log("NS App: Custom Customer Quick Entry OPENED");
|
||||
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: "New Customer",
|
||||
size: "large",
|
||||
|
||||
fields: [
|
||||
// ───────── CUSTOMER ─────────
|
||||
{ fieldtype: "Section Break", label: "Customer" },
|
||||
|
||||
{
|
||||
fieldname: "customer_name",
|
||||
label: "Customer Name",
|
||||
fieldtype: "Data",
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldname: "customer_type",
|
||||
label: "Customer Type",
|
||||
fieldtype: "Select",
|
||||
options: "Company\nIndividual",
|
||||
default: "Company",
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldname: "customer_group",
|
||||
label: "Customer Group",
|
||||
fieldtype: "Link",
|
||||
options: "Customer Group",
|
||||
default: "Commercial",
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldname: "custom_auto_pay_enabled",
|
||||
label: "Auto Pay Enabled",
|
||||
fieldtype: "Check",
|
||||
default: 0
|
||||
},
|
||||
{
|
||||
fieldname: "custom_send_via",
|
||||
label: "Send Via",
|
||||
fieldtype: "Select",
|
||||
options: "Mail\nEmail\nFax"
|
||||
},
|
||||
|
||||
// ───────── CONTACT ─────────
|
||||
{ fieldtype: "Section Break", label: "Primary Contact" },
|
||||
|
||||
{
|
||||
fieldname: "email_id",
|
||||
label: "Email",
|
||||
fieldtype: "Data",
|
||||
options: "Email"
|
||||
},
|
||||
{
|
||||
fieldname: "mobile_no",
|
||||
label: "Mobile",
|
||||
fieldtype: "Data",
|
||||
reqd: 1
|
||||
},
|
||||
|
||||
// ───────── ADDRESS ─────────
|
||||
{ fieldtype: "Section Break", label: "Address" },
|
||||
|
||||
{
|
||||
fieldname: "address_line1",
|
||||
label: "Address Line 1",
|
||||
fieldtype: "Data",
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldname: "address_line2",
|
||||
label: "Address Line 2",
|
||||
fieldtype: "Data"
|
||||
},
|
||||
{
|
||||
fieldname: "pincode",
|
||||
label: "ZIP Code",
|
||||
fieldtype: "Data",
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldname: "city",
|
||||
label: "City",
|
||||
fieldtype: "Data"
|
||||
},
|
||||
{
|
||||
fieldname: "state",
|
||||
label: "State",
|
||||
fieldtype: "Data"
|
||||
},
|
||||
{
|
||||
fieldname: "country",
|
||||
label: "Country",
|
||||
fieldtype: "Link",
|
||||
options: "Country",
|
||||
default: "United States"
|
||||
}
|
||||
],
|
||||
|
||||
primary_action_label: "Create Customer",
|
||||
primary_action(values) {
|
||||
console.log("NS App: Create Customer clicked", values);
|
||||
|
||||
d.disable_primary_action();
|
||||
|
||||
frappe.call({
|
||||
method: "ns_app.api.customer.create_customer_full",
|
||||
args: values,
|
||||
callback(r) {
|
||||
console.log("NS App: Customer created", r.message);
|
||||
|
||||
d.hide();
|
||||
frappe.show_alert({
|
||||
message: "Customer created via NS App",
|
||||
indicator: "green"
|
||||
});
|
||||
|
||||
if (opts.callback) {
|
||||
opts.callback(r.message);
|
||||
}
|
||||
},
|
||||
always() {
|
||||
d.enable_primary_action();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ZIP auto-fill
|
||||
d.fields_dict.pincode.df.onchange = () => {
|
||||
const zip = d.get_value("pincode");
|
||||
if (!zip || zip.length < 5) return;
|
||||
|
||||
console.log("NS App: ZIP lookup", zip);
|
||||
|
||||
fetch(`https://api.zippopotam.us/us/${zip}`)
|
||||
.then(r => r.ok ? r.json() : null)
|
||||
.then(data => {
|
||||
if (!data || !data.places?.length) return;
|
||||
|
||||
const p = data.places[0];
|
||||
d.set_value("city", p["place name"]);
|
||||
d.set_value("state", p["state"]);
|
||||
d.set_value("country", data.country);
|
||||
|
||||
console.log("NS App: ZIP autofill success");
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
d.show();
|
||||
|
||||
// Prevent Enter from submitting unless primary button is focused
|
||||
d.$wrapper.on("keydown", "input, select, textarea", function (e) {
|
||||
if (e.key === "Enter") {
|
||||
const active = document.activeElement;
|
||||
|
||||
// Allow Enter ONLY if primary action button is focused
|
||||
if (
|
||||
active &&
|
||||
active.classList.contains("btn-primary")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
// Move to next field
|
||||
const fields = d.$wrapper
|
||||
.find("input, select, textarea")
|
||||
.filter(":visible:not([disabled])");
|
||||
|
||||
const index = fields.index(this);
|
||||
if (index > -1 && index + 1 < fields.length) {
|
||||
fields.eq(index + 1).focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
12
setup.py
12
setup.py
@@ -0,0 +1,12 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="ns_app",
|
||||
version="0.0.1",
|
||||
description="NS Innovations ERPNext Custom App",
|
||||
author="NS Innovations",
|
||||
author_email="ty@nsinnovations.net",
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user