Merge branch 'main' of https://githaven.org/CaWittMN08/custom_ui
This commit is contained in:
commit
25f6d73fc7
13 changed files with 289 additions and 143 deletions
|
|
@ -75,3 +75,51 @@ def get_addresses(fields=["*"], filters={}):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.log_error(message=str(e), title="Get Addresses Failed")
|
frappe.log_error(message=str(e), title="Get Addresses Failed")
|
||||||
return build_error_response(str(e), 500)
|
return build_error_response(str(e), 500)
|
||||||
|
|
||||||
|
|
||||||
|
def create_address(address_data):
|
||||||
|
"""Create a new address."""
|
||||||
|
address = frappe.get_doc({
|
||||||
|
"doctype": "Address",
|
||||||
|
**address_data
|
||||||
|
})
|
||||||
|
address.insert(ignore_permissions=True)
|
||||||
|
return address
|
||||||
|
|
||||||
|
def address_exists(address_line1, address_line2, city, state, pincode):
|
||||||
|
"""Check if an address with the given details already exists."""
|
||||||
|
filters = {
|
||||||
|
"address_line1": address_line1,
|
||||||
|
"address_line2": address_line2,
|
||||||
|
"city": city,
|
||||||
|
"state": state,
|
||||||
|
"pincode": pincode
|
||||||
|
}
|
||||||
|
return frappe.db.exists("Address", filters) is not None
|
||||||
|
|
||||||
|
def calculate_address_title(customer_name, address_data):
|
||||||
|
return f"{customer_name} - {address_data.get('address_line1', '')}, {address_data.get('city', '')} - {address_data.get('type', '')}"
|
||||||
|
|
||||||
|
def create_address_links(address_doc, client_doc, contact_docs):
|
||||||
|
print("#####DEBUG: Linking customer to address.")
|
||||||
|
print("#####DEBUG: Client Doc:", client_doc.as_dict(), "Address Doc:", address_doc.as_dict(), "Contact Docs:", [c.as_dict() for c in contact_docs])
|
||||||
|
address_doc.append("links", {
|
||||||
|
"link_doctype": client_doc.doctype,
|
||||||
|
"link_name": client_doc.name
|
||||||
|
})
|
||||||
|
setattr(address_doc, "custom_customer_to_bill" if client_doc.doctype == "Customer" else "lead_name", client_doc.name)
|
||||||
|
# Address -> Contact
|
||||||
|
print("#####DEBUG: Linking contacts to address.")
|
||||||
|
address_doc.custom_contact = next((c.name for c in contact_docs if c.is_primary_contact), contact_docs[0].name)
|
||||||
|
for contact_doc in contact_docs:
|
||||||
|
address_doc.append("custom_linked_contacts", {
|
||||||
|
"contact": contact_doc.name,
|
||||||
|
"email": contact_doc.email_id,
|
||||||
|
"phone": contact_doc.phone,
|
||||||
|
"role": contact_doc.role
|
||||||
|
})
|
||||||
|
address_doc.append("links", {
|
||||||
|
"link_doctype": "Contact",
|
||||||
|
"link_name": contact_doc.name
|
||||||
|
})
|
||||||
|
address_doc.save(ignore_permissions=True)
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
import frappe, json
|
import frappe, json
|
||||||
from custom_ui.db_utils import build_error_response, process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response, map_lead_client
|
from custom_ui.db_utils import build_error_response, process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response, map_lead_client, build_address_title
|
||||||
|
from erpnext.crm.doctype.lead.lead import make_customer
|
||||||
|
from custom_ui.api.db.addresses import address_exists, create_address, create_address_links
|
||||||
|
from custom_ui.api.db.contacts import check_and_get_contact, create_contact, create_contact_links
|
||||||
|
|
||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
# CLIENT MANAGEMENT API METHODS
|
# CLIENT MANAGEMENT API METHODS
|
||||||
|
|
@ -94,15 +97,9 @@ def get_client(client_name):
|
||||||
print("DEBUG: get_client called with client_name:", client_name)
|
print("DEBUG: get_client called with client_name:", client_name)
|
||||||
try:
|
try:
|
||||||
clientData = {"addresses": [], "contacts": [], "jobs": [], "sales_invoices": [], "payment_entries": [], "sales_orders": [], "tasks": []}
|
clientData = {"addresses": [], "contacts": [], "jobs": [], "sales_invoices": [], "payment_entries": [], "sales_orders": [], "tasks": []}
|
||||||
client_exists = frappe.db.exists("Customer", client_name)
|
customer = check_and_get_client_doc(client_name)
|
||||||
if client_exists:
|
|
||||||
customer = frappe.get_doc("Customer", client_name)
|
|
||||||
else:
|
|
||||||
print("DEBUG: Client not found as Customer. Checking Lead.")
|
|
||||||
lead_name = frappe.db.get_all("Lead", pluck="name", filters={"lead_name": client_name})[0]
|
|
||||||
customer = frappe.get_doc("Lead", lead_name)
|
|
||||||
if not customer:
|
if not customer:
|
||||||
return build_error_response(f"Client '{client_name}' not found as Customer or Lead.", 404)
|
return build_error_response(f"Client with name '{client_name}' does not exist.", 404)
|
||||||
print("DEBUG: Retrieved customer/lead document:", customer.as_dict())
|
print("DEBUG: Retrieved customer/lead document:", customer.as_dict())
|
||||||
clientData = {**clientData, **customer.as_dict()}
|
clientData = {**clientData, **customer.as_dict()}
|
||||||
if customer.doctype == "Lead":
|
if customer.doctype == "Lead":
|
||||||
|
|
@ -117,7 +114,7 @@ def get_client(client_name):
|
||||||
"Dynamic Link",
|
"Dynamic Link",
|
||||||
filters={
|
filters={
|
||||||
"link_doctype": "Lead",
|
"link_doctype": "Lead",
|
||||||
"link_name": lead_name,
|
"link_name": customer.name,
|
||||||
"parenttype": ["in", ["Address", "Contact"]],
|
"parenttype": ["in", ["Address", "Contact"]],
|
||||||
},
|
},
|
||||||
fields=[
|
fields=[
|
||||||
|
|
@ -218,30 +215,29 @@ def upsert_client(data):
|
||||||
"""Create or update a client (customer and address)."""
|
"""Create or update a client (customer and address)."""
|
||||||
try:
|
try:
|
||||||
data = json.loads(data)
|
data = json.loads(data)
|
||||||
|
print("#####DEBUG: Upsert client data received:", data)
|
||||||
|
if address_exists(
|
||||||
|
data.get("address_line1"),
|
||||||
|
data.get("address_line2"),
|
||||||
|
data.get("city"),
|
||||||
|
data.get("state"),
|
||||||
|
data.get("pincode")
|
||||||
|
):
|
||||||
|
return build_error_response("This address already exists. Please use a different address or search for the address to find the associated client.", 400)
|
||||||
|
|
||||||
# Handle customer creation/update
|
# Handle customer creation/update
|
||||||
print("#####DEBUG: Upsert client data received:", data)
|
|
||||||
|
|
||||||
print("#####DEBUG: Checking for existing customer with name:", data.get("customer_name"))
|
print("#####DEBUG: Checking for existing customer with name:", data.get("customer_name"))
|
||||||
customer = frappe.db.exists("Customer", {"customer_name": data.get("customer_name")})
|
customer = frappe.db.exists("Customer", {"customer_name": data.get("customer_name")})
|
||||||
|
|
||||||
if not customer:
|
if not customer:
|
||||||
print("#####DEBUG: No existing customer found. Checking for existing lead")
|
print("#####DEBUG: No existing customer found. Checking for existing lead")
|
||||||
customer = frappe.db.exists("Lead", {"lead_name": data.get("customer_name")})
|
customer = frappe.db.exists("Lead", {"lead_name": data.get("customer_name")})
|
||||||
else:
|
|
||||||
print("#####DEBUG: Existing customer found:", customer)
|
|
||||||
|
|
||||||
if not customer:
|
if not customer:
|
||||||
print("#####DEBUG: No existing lead found. Creating new lead.")
|
print("#####DEBUG: No existing lead found. Creating new lead.")
|
||||||
is_individual = data.get("customer_type") == "Individual"
|
|
||||||
|
|
||||||
primary_contact = next((c for c in data.get("contacts", []) if c.get("is_primary")), None)
|
primary_contact = next((c for c in data.get("contacts", []) if c.get("is_primary")), None)
|
||||||
if not primary_contact:
|
if not primary_contact:
|
||||||
return build_error_response("Primary contact information is required to create a new customer.", 400)
|
return build_error_response("Primary contact information is required to create a new customer.", 400)
|
||||||
print("#####DEBUG: Primary contact found:", primary_contact)
|
print("#####DEBUG: Primary contact found:", primary_contact)
|
||||||
|
client_doc = create_lead({
|
||||||
new_lead_data = {
|
|
||||||
"doctype": "Lead",
|
|
||||||
"lead_name": data.get("customer_name"),
|
"lead_name": data.get("customer_name"),
|
||||||
"first_name": primary_contact.get("first_name"),
|
"first_name": primary_contact.get("first_name"),
|
||||||
"last_name": primary_contact.get("last_name"),
|
"last_name": primary_contact.get("last_name"),
|
||||||
|
|
@ -249,36 +245,25 @@ def upsert_client(data):
|
||||||
"phone": primary_contact.get("phone_number"),
|
"phone": primary_contact.get("phone_number"),
|
||||||
"customer_type": data.get("customer_type"),
|
"customer_type": data.get("customer_type"),
|
||||||
"company": data.get("company")
|
"company": data.get("company")
|
||||||
}
|
})
|
||||||
print("#####DEBUG: New lead data prepared:", new_lead_data)
|
|
||||||
new_client_doc = frappe.get_doc(new_lead_data).insert(ignore_permissions=True)
|
|
||||||
else:
|
else:
|
||||||
new_client_doc = frappe.get_doc("Customer", data.get("customer_name"))
|
print("#####DEBUG: Existing lead found:", customer)
|
||||||
print(f"#####DEBUG: {new_client_doc.doctype}:", new_client_doc.as_dict())
|
client_doc = frappe.get_doc("Lead", customer)
|
||||||
|
else:
|
||||||
|
print("#####DEBUG: Existing customer found:", customer)
|
||||||
|
client_doc = frappe.get_doc("Customer", customer)
|
||||||
|
print(f"#####DEBUG: {client_doc.doctype}:", client_doc.as_dict())
|
||||||
|
|
||||||
# Handle address creation
|
# Handle address creation
|
||||||
print("#####DEBUG: Checking for existing address for customer/lead:", data.get("customer_name"))
|
address_doc = create_address({
|
||||||
existing_address = frappe.db.exists(
|
"address_title": build_address_title(data.get("customer_name"), data),
|
||||||
"Address",
|
|
||||||
{
|
|
||||||
"address_line1": data.get("address_line1"),
|
|
||||||
"city": data.get("city"),
|
|
||||||
"state": data.get("state"),
|
|
||||||
})
|
|
||||||
print("Existing address check:", existing_address)
|
|
||||||
if existing_address:
|
|
||||||
return build_error_response("Address already exists for this customer.", 400)
|
|
||||||
address_doc = frappe.get_doc({
|
|
||||||
"doctype": "Address",
|
|
||||||
"address_title": data.get("address_title"),
|
|
||||||
"address_line1": data.get("address_line1"),
|
"address_line1": data.get("address_line1"),
|
||||||
"address_line2": data.get("address_line2"),
|
"address_line2": data.get("address_line2"),
|
||||||
"city": data.get("city"),
|
"city": data.get("city"),
|
||||||
"state": data.get("state"),
|
"state": data.get("state"),
|
||||||
"country": "United States",
|
"country": "United States",
|
||||||
"pincode": data.get("pincode")
|
"pincode": data.get("pincode")
|
||||||
}).insert(ignore_permissions=True)
|
})
|
||||||
print("Address:", address_doc.as_dict())
|
|
||||||
|
|
||||||
#Handle contact creation
|
#Handle contact creation
|
||||||
contact_docs = []
|
contact_docs = []
|
||||||
|
|
@ -286,19 +271,22 @@ def upsert_client(data):
|
||||||
if isinstance(contact_data, str):
|
if isinstance(contact_data, str):
|
||||||
contact_data = json.loads(contact_data)
|
contact_data = json.loads(contact_data)
|
||||||
print("#####DEBUG: Processing contact data:", contact_data)
|
print("#####DEBUG: Processing contact data:", contact_data)
|
||||||
contact_exists = frappe.db.exists("Contact", {"email_id": contact_data.get("email"), "phone": contact_data.get("phone_number")})
|
contact_doc = check_and_get_contact(
|
||||||
print("Contact exists check:", contact_exists)
|
contact_data.get("first_name"),
|
||||||
if not contact_exists:
|
contact_data.get("last_name"),
|
||||||
contact_doc = frappe.get_doc({
|
contact_data.get("email"),
|
||||||
"doctype": "Contact",
|
contact_data.get("phone_number")
|
||||||
|
)
|
||||||
|
if not contact_doc:
|
||||||
|
print("#####DEBUG: No existing contact found. Creating new contact.")
|
||||||
|
contact_doc = create_contact({
|
||||||
"first_name": contact_data.get("first_name"),
|
"first_name": contact_data.get("first_name"),
|
||||||
"last_name": contact_data.get("last_name"),
|
"last_name": contact_data.get("last_name"),
|
||||||
# "email_id": contact_data.get("email"),
|
# "email_id": contact_data.get("email"),
|
||||||
# "phone": contact_data.get("phone_number"),
|
# "phone": contact_data.get("phone_number"),
|
||||||
"custom_customer": customer_doc.name,
|
|
||||||
"role": contact_data.get("contact_role", "Other"),
|
"role": contact_data.get("contact_role", "Other"),
|
||||||
"custom_email": contact_data.get("email"),
|
"custom_email": contact_data.get("email"),
|
||||||
"is_primary_contact":1 if data.get("is_primary", False) else 0,
|
"is_primary_contact":1 if contact_data.get("is_primary", False) else 0,
|
||||||
"email_ids": [{
|
"email_ids": [{
|
||||||
"email_id": contact_data.get("email"),
|
"email_id": contact_data.get("email"),
|
||||||
"is_primary": 1
|
"is_primary": 1
|
||||||
|
|
@ -308,85 +296,37 @@ def upsert_client(data):
|
||||||
"is_primary_mobile_no": 1,
|
"is_primary_mobile_no": 1,
|
||||||
"is_primary_phone": 1
|
"is_primary_phone": 1
|
||||||
}]
|
}]
|
||||||
}).insert(ignore_permissions=True)
|
})
|
||||||
print("Created new contact:", contact_doc.as_dict())
|
|
||||||
else:
|
|
||||||
contact_doc = frappe.get_doc("Contact", {"email_id": contact_data.get("email"), "phone": contact_data.get("phone_number")})
|
|
||||||
print("Contact already exists:", contact_doc.as_dict())
|
|
||||||
contact_docs.append(contact_doc)
|
contact_docs.append(contact_doc)
|
||||||
|
|
||||||
##### Create links
|
##### Create links
|
||||||
# Customer -> Address
|
# Customer -> Address
|
||||||
if new_client_doc.doctype == "Customer":
|
if client_doc.doctype == "Customer":
|
||||||
print("#####DEBUG: Creating links between customer, address, and contacts.")
|
print("#####DEBUG: Linking address to customer.")
|
||||||
new_client_doc.append("custom_select_address", {
|
client_doc.append("custom_select_address", {
|
||||||
"address_name": address_doc.name,
|
"address_name": address_doc.name,
|
||||||
"address_line_1": address_doc.address_line1,
|
|
||||||
"city": address_doc.city,
|
|
||||||
"state": address_doc.state,
|
|
||||||
"pincode": address_doc.pincode
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# Customer -> Contact
|
# Customer -> Contact
|
||||||
print("#####DEBUG: Linking contacts to customer.")
|
print("#####DEBUG: Linking contacts to customer.")
|
||||||
for contact_doc in contact_docs:
|
for contact_doc in contact_docs:
|
||||||
print("Linking contact:", contact_doc.as_dict())
|
client_doc.append("custom_add_contacts", {
|
||||||
print("with role:", contact_doc.role)
|
|
||||||
print("customer to append to:", new_client_doc.as_dict())
|
|
||||||
new_client_doc.append("custom_add_contacts", {
|
|
||||||
"contact": contact_doc.name,
|
"contact": contact_doc.name,
|
||||||
"email": contact_doc.custom_email,
|
"email": contact_doc.custom_email,
|
||||||
"phone": contact_doc.phone,
|
"phone": contact_doc.phone,
|
||||||
"role": contact_doc.role
|
"role": contact_doc.role
|
||||||
})
|
})
|
||||||
new_client_doc.append("links", {
|
client_doc.save(ignore_permissions=True)
|
||||||
"link_doctype": "Contact",
|
|
||||||
"link_name": contact_doc.name
|
|
||||||
}
|
|
||||||
)
|
|
||||||
new_client_doc.save(ignore_permissions=True)
|
|
||||||
|
|
||||||
# Address -> Customer/Lead
|
# Address -> Customer/Lead
|
||||||
print("#####DEBUG: Linking address to customer.")
|
create_address_links(address_doc, client_doc, contact_docs)
|
||||||
address_doc.append("links", {
|
|
||||||
"link_doctype": new_client_doc.doctype,
|
|
||||||
"link_name": new_client_doc.name
|
|
||||||
})
|
|
||||||
if new_client_doc.doctype == "Lead":
|
|
||||||
address_doc.lead_name = new_client_doc.lead_name
|
|
||||||
|
|
||||||
|
|
||||||
# Address -> Contact
|
|
||||||
print("#####DEBUG: Linking address to contacts.")
|
|
||||||
address_doc.custom_contact = next((c.name for c in contact_docs if c.is_primary_contact), contact_docs[0].name)
|
|
||||||
for contact_doc in contact_docs:
|
|
||||||
address_doc.append("custom_linked_contacts", {
|
|
||||||
"contact": contact_doc.name,
|
|
||||||
"email": contact_doc.email_id,
|
|
||||||
"phone": contact_doc.phone,
|
|
||||||
"role": contact_doc.role
|
|
||||||
})
|
|
||||||
|
|
||||||
address_doc.save(ignore_permissions=True)
|
|
||||||
|
|
||||||
# Contact -> Customer/Lead & Address
|
# Contact -> Customer/Lead & Address
|
||||||
print("#####DEBUG: Linking contacts to customer.")
|
create_contact_links(contact_docs, client_doc, address_doc)
|
||||||
for contact_doc in contact_docs:
|
|
||||||
contact_doc.address = address_doc.name
|
|
||||||
contact_doc.append("links", {
|
|
||||||
"link_doctype": new_client_doc.doctype,
|
|
||||||
"link_name": new_client_doc.name
|
|
||||||
})
|
|
||||||
contact_doc.append("links", {
|
|
||||||
"link_doctype": "Address",
|
|
||||||
"link_name": address_doc.name
|
|
||||||
})
|
|
||||||
contact_doc.custom_customer = new_client_doc.name
|
|
||||||
contact_doc.save(ignore_permissions=True)
|
|
||||||
|
|
||||||
frappe.local.message_log = []
|
frappe.local.message_log = []
|
||||||
return build_success_response({
|
return build_success_response({
|
||||||
"customer": new_client_doc.as_dict(),
|
"customer": client_doc.as_dict(),
|
||||||
"address": address_doc.as_dict(),
|
"address": address_doc.as_dict(),
|
||||||
"contacts": [contact_doc.as_dict() for contact_doc in contact_docs]
|
"contacts": [contact_doc.as_dict() for contact_doc in contact_docs]
|
||||||
})
|
})
|
||||||
|
|
@ -402,7 +342,48 @@ def get_client_names(search_term):
|
||||||
search_pattern = f"%{search_term}%"
|
search_pattern = f"%{search_term}%"
|
||||||
client_names = frappe.db.get_all(
|
client_names = frappe.db.get_all(
|
||||||
"Customer",
|
"Customer",
|
||||||
|
filters={"customer_name": ["like", search_pattern]},
|
||||||
pluck="name")
|
pluck="name")
|
||||||
return build_success_response(client_names)
|
return build_success_response(client_names)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return build_error_response(str(e), 500)
|
return build_error_response(str(e), 500)
|
||||||
|
|
||||||
|
def check_if_customer(client_name):
|
||||||
|
"""Check if the given client name corresponds to a Customer."""
|
||||||
|
return frappe.db.exists("Customer", client_name) is not None
|
||||||
|
|
||||||
|
def check_and_get_client_doc(client_name):
|
||||||
|
"""Check if a client exists as Customer or Lead and return the document."""
|
||||||
|
print("DEBUG: Checking for existing client with name:", client_name)
|
||||||
|
customer = None
|
||||||
|
if check_if_customer(client_name):
|
||||||
|
print("DEBUG: Client found as Customer.")
|
||||||
|
customer = frappe.get_doc("Customer", client_name)
|
||||||
|
else:
|
||||||
|
print("DEBUG: Client not found as Customer. Checking Lead.")
|
||||||
|
lead_name = frappe.db.get_all("Lead", pluck="name", filters={"lead_name": client_name})
|
||||||
|
if lead_name:
|
||||||
|
print("DEBUG: Client found as Lead.")
|
||||||
|
customer = frappe.get_doc("Lead", lead_name[0])
|
||||||
|
return customer
|
||||||
|
|
||||||
|
def convert_lead_to_customer(lead_name):
|
||||||
|
lead = frappe.get_doc("Lead", lead_name)
|
||||||
|
customer = make_customer(lead)
|
||||||
|
customer.insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
|
||||||
|
def create_lead(lead_data):
|
||||||
|
lead = frappe.get_doc({
|
||||||
|
"doctype": "Lead",
|
||||||
|
**lead_data
|
||||||
|
})
|
||||||
|
lead.insert(ignore_permissions=True)
|
||||||
|
return lead
|
||||||
|
|
||||||
|
def get_customer_or_lead(client_name):
|
||||||
|
if check_if_customer(client_name):
|
||||||
|
return frappe.get_doc("Customer", client_name)
|
||||||
|
else:
|
||||||
|
lead_name = frappe.db.get_all("Lead", pluck="name", filters={"lead_name": client_name})[0]
|
||||||
|
return frappe.get_doc("Lead", lead_name)
|
||||||
50
custom_ui/api/db/contacts.py
Normal file
50
custom_ui/api/db/contacts.py
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
def existing_contact_name(first_name: str, last_name: str, email: str, phone: str) -> str:
|
||||||
|
"""Check if a contact exists based on provided details."""
|
||||||
|
filters = {
|
||||||
|
"first_name": first_name,
|
||||||
|
"last_name": last_name,
|
||||||
|
"email_id": email,
|
||||||
|
"phone": phone
|
||||||
|
}
|
||||||
|
existing_contacts = frappe.db.get_all("Contact", pluck="name", filters=filters)
|
||||||
|
return existing_contacts[0] if existing_contacts else None
|
||||||
|
|
||||||
|
def get_contact(contact_name: str):
|
||||||
|
"""Retrieve a contact document by name."""
|
||||||
|
contact = frappe.get_doc("Contact", contact_name)
|
||||||
|
print("Retrieved existing contact:", contact.as_dict())
|
||||||
|
return contact
|
||||||
|
|
||||||
|
def check_and_get_contact(first_name: str, last_name: str, email: str, phone: str):
|
||||||
|
"""Check if a contact exists and return the contact document if found."""
|
||||||
|
contact_name = existing_contact_name(first_name, last_name, email, phone)
|
||||||
|
if contact_name:
|
||||||
|
return get_contact(contact_name)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def create_contact(contact_data: dict):
|
||||||
|
"""Create a new contact."""
|
||||||
|
contact = frappe.get_doc({
|
||||||
|
"doctype": "Contact",
|
||||||
|
**contact_data
|
||||||
|
})
|
||||||
|
contact.insert(ignore_permissions=True)
|
||||||
|
print("Created new contact:", contact.as_dict())
|
||||||
|
return contact
|
||||||
|
|
||||||
|
def create_contact_links(contact_docs, client_doc, address_doc):
|
||||||
|
print("#####DEBUG: Linking contacts to client and address.")
|
||||||
|
for contact_doc in contact_docs:
|
||||||
|
contact_doc.address = address_doc.name
|
||||||
|
contact_doc.append("links", {
|
||||||
|
"link_doctype": client_doc.doctype,
|
||||||
|
"link_name": client_doc.name
|
||||||
|
})
|
||||||
|
contact_doc.append("links", {
|
||||||
|
"link_doctype": "Address",
|
||||||
|
"link_name": address_doc.name
|
||||||
|
})
|
||||||
|
contact_doc.custom_customer = client_doc.name
|
||||||
|
contact_doc.save(ignore_permissions=True)
|
||||||
|
|
@ -2,6 +2,7 @@ import frappe, json
|
||||||
from frappe.utils.pdf import get_pdf
|
from frappe.utils.pdf import get_pdf
|
||||||
from custom_ui.db_utils import process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response, build_error_response
|
from custom_ui.db_utils import process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response, build_error_response
|
||||||
from werkzeug.wrappers import Response
|
from werkzeug.wrappers import Response
|
||||||
|
from custom_ui.api.db.clients import check_if_customer, convert_lead_to_customer
|
||||||
|
|
||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
# ESTIMATES & INVOICES API METHODS
|
# ESTIMATES & INVOICES API METHODS
|
||||||
|
|
@ -152,6 +153,8 @@ def send_estimate_email(estimate_name):
|
||||||
quotation.custom_current_status = "Submitted"
|
quotation.custom_current_status = "Submitted"
|
||||||
quotation.custom_sent = 1
|
quotation.custom_sent = 1
|
||||||
quotation.save()
|
quotation.save()
|
||||||
|
quotation.submit()
|
||||||
|
frappe.db.commit()
|
||||||
updated_quotation = frappe.get_doc("Quotation", estimate_name)
|
updated_quotation = frappe.get_doc("Quotation", estimate_name)
|
||||||
return build_success_response(updated_quotation.as_dict())
|
return build_success_response(updated_quotation.as_dict())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -166,20 +169,26 @@ def update_response(name, response):
|
||||||
if not frappe.db.exists("Quotation", name):
|
if not frappe.db.exists("Quotation", name):
|
||||||
raise Exception("Estimate not found.")
|
raise Exception("Estimate not found.")
|
||||||
estimate = frappe.get_doc("Quotation", name)
|
estimate = frappe.get_doc("Quotation", name)
|
||||||
|
if estimate.docstatus != 1:
|
||||||
|
raise Exception("Estimate must be submitted to update response.")
|
||||||
accepted = True if response == "Accepted" else False
|
accepted = True if response == "Accepted" else False
|
||||||
new_status = "Estimate Accepted" if accepted else "Lost"
|
new_status = "Estimate Accepted" if accepted else "Lost"
|
||||||
|
|
||||||
estimate.custom_response = response
|
estimate.custom_response = response
|
||||||
estimate.custom_current_status = new_status
|
estimate.custom_current_status = new_status
|
||||||
estimate.custom_followup_needed = 1 if response == "Requested call" else 0
|
estimate.custom_followup_needed = 1 if response == "Requested call" else 0
|
||||||
|
estimate.status = "Ordered" if accepted else "Closed"
|
||||||
estimate.flags.ignore_permissions = True
|
estimate.flags.ignore_permissions = True
|
||||||
print("DEBUG: Updating estimate with response:", response, "and status:", new_status)
|
print("DEBUG: Updating estimate with response:", response, "and status:", new_status)
|
||||||
# estimate.save()
|
estimate.save()
|
||||||
estimate.submit()
|
|
||||||
frappe.db.commit()
|
|
||||||
|
|
||||||
if accepted:
|
if accepted:
|
||||||
template = "custom_ui/templates/estimates/accepted.html"
|
template = "custom_ui/templates/estimates/accepted.html"
|
||||||
|
if check_if_customer(estimate.party_name):
|
||||||
|
print("DEBUG: Party is already a customer:", estimate.party_name)
|
||||||
|
else:
|
||||||
|
print("DEBUG: Converting lead to customer for party:", estimate.party_name)
|
||||||
|
convert_lead_to_customer(estimate.party_name)
|
||||||
elif response == "Requested call":
|
elif response == "Requested call":
|
||||||
template = "custom_ui/templates/estimates/request-call.html"
|
template = "custom_ui/templates/estimates/request-call.html"
|
||||||
else:
|
else:
|
||||||
|
|
@ -211,6 +220,7 @@ def upsert_estimate(data):
|
||||||
# Update fields
|
# Update fields
|
||||||
estimate.custom_installation_address = data.get("address_name")
|
estimate.custom_installation_address = data.get("address_name")
|
||||||
estimate.party_name = data.get("contact_name")
|
estimate.party_name = data.get("contact_name")
|
||||||
|
estimate.custom_requires_half_payment = data.get("requires_half_payment", 0)
|
||||||
|
|
||||||
# Clear existing items and add new ones
|
# Clear existing items and add new ones
|
||||||
estimate.items = []
|
estimate.items = []
|
||||||
|
|
|
||||||
|
|
@ -102,3 +102,4 @@ def upsert_invoice(data):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return build_error_response(str(e), 500)
|
return build_error_response(str(e), 500)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,14 @@ def build_full_address(doc):
|
||||||
return f"{first}, {second}"
|
return f"{first}, {second}"
|
||||||
return first or second or ""
|
return first or second or ""
|
||||||
|
|
||||||
|
def build_address_title(customer_name, address_data):
|
||||||
|
title_parts = [customer_name]
|
||||||
|
if address_data.get("address_line1"):
|
||||||
|
title_parts.append(address_data["address_line1"])
|
||||||
|
if address_data.get("type"):
|
||||||
|
title_parts.append(address_data["type"])
|
||||||
|
return " - ".join(title_parts)
|
||||||
|
|
||||||
def map_lead_client(client_data):
|
def map_lead_client(client_data):
|
||||||
mappings = {
|
mappings = {
|
||||||
"lead_name": "customer_name",
|
"lead_name": "customer_name",
|
||||||
|
|
@ -170,7 +178,8 @@ def map_lead_client(client_data):
|
||||||
if lead_field in client_data:
|
if lead_field in client_data:
|
||||||
print(f"DEBUG: Mapping field {lead_field} to {client_field} with value {client_data[lead_field]}")
|
print(f"DEBUG: Mapping field {lead_field} to {client_field} with value {client_data[lead_field]}")
|
||||||
client_data[client_field] = client_data[lead_field]
|
client_data[client_field] = client_data[lead_field]
|
||||||
client_data["customer_group"] = "" # Leads don't have customer groups
|
client_data["customer_group"] = ""
|
||||||
|
print("####DEBUG: Mapped client data:", client_data)
|
||||||
return client_data
|
return client_data
|
||||||
|
|
||||||
def map_lead_update(client_data):
|
def map_lead_update(client_data):
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,10 @@ def after_save(doc, method):
|
||||||
address_doc.custom_estimate_sent_status = "Completed"
|
address_doc.custom_estimate_sent_status = "Completed"
|
||||||
address_doc.save()
|
address_doc.save()
|
||||||
|
|
||||||
def after_submit(doc, method):
|
def on_update_after_submit(doc, method):
|
||||||
print("DEBUG: on_submit hook triggered for Quotation:", doc.name)
|
print("DEBUG: on_update_after_submit hook triggered for Quotation:", doc.name)
|
||||||
if doc.custom_current_status == "Estimate Accepted":
|
if doc.custom_current_status == "Estimate Accepted":
|
||||||
|
doc.custom_current_status = "Won"
|
||||||
print("DEBUG: Creating Sales Order from accepted Estimate")
|
print("DEBUG: Creating Sales Order from accepted Estimate")
|
||||||
address_doc = frappe.get_doc("Address", doc.custom_installation_address)
|
address_doc = frappe.get_doc("Address", doc.custom_installation_address)
|
||||||
address_doc.custom_estimate_sent_status = "Completed"
|
address_doc.custom_estimate_sent_status = "Completed"
|
||||||
|
|
@ -33,7 +34,8 @@ def after_submit(doc, method):
|
||||||
new_sales_order = make_sales_order(doc.name)
|
new_sales_order = make_sales_order(doc.name)
|
||||||
new_sales_order.custom_requires_half_payment = doc.requires_half_payment
|
new_sales_order.custom_requires_half_payment = doc.requires_half_payment
|
||||||
new_sales_order.insert()
|
new_sales_order.insert()
|
||||||
|
new_sales_order.submit()
|
||||||
print("DEBUG: Sales Order created successfully:", new_sales_order.name)
|
print("DEBUG: Sales Order created successfully:", new_sales_order.name)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("ERROR creating Sales Order from Estimate:", str(e))
|
print("ERROR creating Sales Order from Estimate:", str(e))
|
||||||
frappe.log_error(f"Error creating Sales Order from Estimate {doc.name}: {str(e)}", "Estimate on_submit Error")
|
frappe.log_error(f"Error creating Sales Order from Estimate {doc.name}: {str(e)}", "Estimate on_update_after_submit Error")
|
||||||
|
|
@ -4,3 +4,41 @@ def after_insert(doc, method):
|
||||||
print(doc.as_dict())
|
print(doc.as_dict())
|
||||||
# Create Invoice and Project from Sales Order
|
# Create Invoice and Project from Sales Order
|
||||||
|
|
||||||
|
def create_sales_invoice_from_sales_order(doc, method):
|
||||||
|
try:
|
||||||
|
print("DEBUG: after_submit hook triggered for Sales Order:", doc.name)
|
||||||
|
invoice_ammount = doc.grand_total / 2 if doc.requires_half_payment else doc.grand_total
|
||||||
|
items = []
|
||||||
|
for so_item in doc.items:
|
||||||
|
# proportionally reduce rate if half-payment
|
||||||
|
rate = so_item.rate / 2 if doc.requires_half_payment else so_item.rate
|
||||||
|
qty = so_item.qty # usually full qty, but depends on half-payment rules
|
||||||
|
items.append({
|
||||||
|
"item_code": so_item.item_code,
|
||||||
|
"qty": qty,
|
||||||
|
"rate": rate,
|
||||||
|
"income_account": so_item.income_account,
|
||||||
|
"cost_center": so_item.cost_center,
|
||||||
|
"so_detail": so_item.name # links item to Sales Order
|
||||||
|
})
|
||||||
|
invoice = frappe.get_doc({
|
||||||
|
"doctype": "Sales Invoice",
|
||||||
|
"customer": doc.customer,
|
||||||
|
"company": doc.company,
|
||||||
|
"posting_date": frappe.utils.nowdate(),
|
||||||
|
"due_date": frappe.utils.nowdate(), # or calculate from payment terms
|
||||||
|
"currency": doc.currency,
|
||||||
|
"update_stock": 0,
|
||||||
|
"items": items,
|
||||||
|
"sales_order": doc.name, # link invoice to Sales Order
|
||||||
|
"ignore_pricing_rule": 1,
|
||||||
|
"payment_schedule": doc.payment_schedule if not half_payment else [] # optional
|
||||||
|
})
|
||||||
|
|
||||||
|
invoice.insert()
|
||||||
|
invoice.submit()
|
||||||
|
frappe.db.commit()
|
||||||
|
return invoice
|
||||||
|
except Exception as e:
|
||||||
|
print("ERROR creating Sales Invoice from Sales Order:", str(e))
|
||||||
|
frappe.log_error(f"Error creating Sales Invoice from Sales Order {doc.name}: {str(e)}", "Sales Order after_submit Error")
|
||||||
|
|
@ -169,7 +169,8 @@ doc_events = {
|
||||||
"Quotation": {
|
"Quotation": {
|
||||||
"after_insert": "custom_ui.events.estimate.after_insert",
|
"after_insert": "custom_ui.events.estimate.after_insert",
|
||||||
"on_update": "custom_ui.events.estimate.after_save",
|
"on_update": "custom_ui.events.estimate.after_save",
|
||||||
"after_submit": "custom_ui.events.estimate.after_submit"
|
"after_submit": "custom_ui.events.estimate.after_submit",
|
||||||
|
"on_update_after_submit": "custom_ui.events.estimate.on_update_after_submit"
|
||||||
},
|
},
|
||||||
"Sales Order": {
|
"Sales Order": {
|
||||||
"after_insert": "custom_ui.events.sales_order.after_insert"
|
"after_insert": "custom_ui.events.sales_order.after_insert"
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,6 @@
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
<h3>Address Information</h3>
|
<h3>Address Information</h3>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="form-field full-width">
|
|
||||||
<label for="address-title"> Address Title <span class="required">*</span> </label>
|
|
||||||
<InputText
|
|
||||||
id="address-title"
|
|
||||||
v-model="localFormData.addressTitle"
|
|
||||||
:disabled="isSubmitting || isEditMode"
|
|
||||||
placeholder="e.g., Home, Office, Site A"
|
|
||||||
class="w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="form-field full-width">
|
<div class="form-field full-width">
|
||||||
<label for="address-line1"> Address Line 1 <span class="required">*</span> </label>
|
<label for="address-line1"> Address Line 1 <span class="required">*</span> </label>
|
||||||
<InputText
|
<InputText
|
||||||
|
|
|
||||||
|
|
@ -381,11 +381,12 @@ defineExpose({
|
||||||
|
|
||||||
.check-btn {
|
.check-btn {
|
||||||
border: 1px solid var(--primary-color);
|
border: 1px solid var(--primary-color);
|
||||||
color: var(--primary-color);
|
color: white;
|
||||||
background: var(--surface-card);
|
background: var(--primary-color);
|
||||||
padding: 0.25rem 0.75rem;
|
padding: 0.25rem 0.5rem;
|
||||||
min-width: 8rem;
|
min-width: 5rem;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.check-btn:disabled {
|
.check-btn:disabled {
|
||||||
|
|
|
||||||
|
|
@ -212,6 +212,7 @@ import DataUtils from "../../utils";
|
||||||
import Api from "../../api";
|
import Api from "../../api";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useNotificationStore } from "../../stores/notifications-primevue";
|
import { useNotificationStore } from "../../stores/notifications-primevue";
|
||||||
|
import { useCompanyStore } from "../../stores/company";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
clientData: {
|
clientData: {
|
||||||
|
|
@ -230,6 +231,7 @@ const props = defineProps({
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const notificationStore = useNotificationStore();
|
const notificationStore = useNotificationStore();
|
||||||
|
const companyStore = useCompanyStore();
|
||||||
|
|
||||||
const navigateTo = (path) => {
|
const navigateTo = (path) => {
|
||||||
router.push(path);
|
router.push(path);
|
||||||
|
|
@ -561,6 +563,7 @@ const handleSave = async () => {
|
||||||
const clientData = {
|
const clientData = {
|
||||||
customerName: formData.value.customerName,
|
customerName: formData.value.customerName,
|
||||||
customerType: formData.value.customerType,
|
customerType: formData.value.customerType,
|
||||||
|
companyName: companyStore.currentCompany,
|
||||||
addressTitle: formData.value.addressTitle,
|
addressTitle: formData.value.addressTitle,
|
||||||
addressLine1: formData.value.addressLine1,
|
addressLine1: formData.value.addressLine1,
|
||||||
addressLine2: formData.value.addressLine2,
|
addressLine2: formData.value.addressLine2,
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,13 @@
|
||||||
<div class="total-section">
|
<div class="total-section">
|
||||||
<strong>Total Cost: ${{ totalCost.toFixed(2) }}</strong>
|
<strong>Total Cost: ${{ totalCost.toFixed(2) }}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="half-payment-section">
|
||||||
|
<v-checkbox
|
||||||
|
v-model="formData.requiresHalfPayment"
|
||||||
|
label="Requires Half Payment"
|
||||||
|
:disabled="!isEditable"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div v-if="isEditable" class="action-buttons">
|
<div v-if="isEditable" class="action-buttons">
|
||||||
<Button label="Clear Items" @click="clearItems" severity="secondary" />
|
<Button label="Clear Items" @click="clearItems" severity="secondary" />
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -199,6 +206,7 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p><strong>Total:</strong> ${{ totalCost.toFixed(2) }}</p>
|
<p><strong>Total:</strong> ${{ totalCost.toFixed(2) }}</p>
|
||||||
|
<p><strong>Requires Half Payment:</strong> {{ formData.requiresHalfPayment ? 'Yes' : 'No' }}</p>
|
||||||
<p class="warning-text"><strong>⚠️ Warning:</strong> After sending this estimate, it will be locked and cannot be edited.</p>
|
<p class="warning-text"><strong>⚠️ Warning:</strong> After sending this estimate, it will be locked and cannot be edited.</p>
|
||||||
<div class="confirmation-buttons">
|
<div class="confirmation-buttons">
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -247,6 +255,7 @@ const formData = reactive({
|
||||||
addressName: "",
|
addressName: "",
|
||||||
contact: "",
|
contact: "",
|
||||||
estimateName: null,
|
estimateName: null,
|
||||||
|
requiresHalfPayment: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedAddress = ref(null);
|
const selectedAddress = ref(null);
|
||||||
|
|
@ -367,6 +376,7 @@ const saveDraft = async () => {
|
||||||
contactName: selectedContact.value.name,
|
contactName: selectedContact.value.name,
|
||||||
items: selectedItems.value.map((i) => ({ itemCode: i.itemCode, qty: i.qty })),
|
items: selectedItems.value.map((i) => ({ itemCode: i.itemCode, qty: i.qty })),
|
||||||
estimateName: formData.estimateName,
|
estimateName: formData.estimateName,
|
||||||
|
requiresHalfPayment: formData.requiresHalfPayment,
|
||||||
company: company.currentCompany
|
company: company.currentCompany
|
||||||
};
|
};
|
||||||
estimate.value = await Api.createEstimate(data);
|
estimate.value = await Api.createEstimate(data);
|
||||||
|
|
@ -517,6 +527,7 @@ watch(
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
formData.requiresHalfPayment = estimate.value.custom_requires_half_payment || false;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading estimate:", error);
|
console.error("Error loading estimate:", error);
|
||||||
|
|
@ -570,6 +581,7 @@ onMounted(async () => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
formData.requiresHalfPayment = estimate.value.custom_requires_half_payment || false;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading estimate:", error);
|
console.error("Error loading estimate:", error);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue