add sales order generation functionality
This commit is contained in:
parent
796b835c08
commit
9c9050c558
@ -74,4 +74,52 @@ def get_addresses(fields=["*"], filters={}):
|
||||
return build_success_response(addresses)
|
||||
except Exception as e:
|
||||
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
|
||||
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
|
||||
@ -94,15 +97,9 @@ def get_client(client_name):
|
||||
print("DEBUG: get_client called with client_name:", client_name)
|
||||
try:
|
||||
clientData = {"addresses": [], "contacts": [], "jobs": [], "sales_invoices": [], "payment_entries": [], "sales_orders": [], "tasks": []}
|
||||
client_exists = frappe.db.exists("Customer", 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:
|
||||
return build_error_response(f"Client '{client_name}' not found as Customer or Lead.", 404)
|
||||
customer = check_and_get_client_doc(client_name)
|
||||
if not customer:
|
||||
return build_error_response(f"Client with name '{client_name}' does not exist.", 404)
|
||||
print("DEBUG: Retrieved customer/lead document:", customer.as_dict())
|
||||
clientData = {**clientData, **customer.as_dict()}
|
||||
if customer.doctype == "Lead":
|
||||
@ -117,7 +114,7 @@ def get_client(client_name):
|
||||
"Dynamic Link",
|
||||
filters={
|
||||
"link_doctype": "Lead",
|
||||
"link_name": lead_name,
|
||||
"link_name": customer.name,
|
||||
"parenttype": ["in", ["Address", "Contact"]],
|
||||
},
|
||||
fields=[
|
||||
@ -218,67 +215,55 @@ def upsert_client(data):
|
||||
"""Create or update a client (customer and address)."""
|
||||
try:
|
||||
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
|
||||
print("#####DEBUG: Upsert client data received:", data)
|
||||
|
||||
print("#####DEBUG: Checking for existing customer with name:", data.get("customer_name"))
|
||||
customer = frappe.db.exists("Customer", {"customer_name": data.get("customer_name")})
|
||||
|
||||
if not customer:
|
||||
print("#####DEBUG: No existing customer found. Checking for existing lead")
|
||||
customer = frappe.db.exists("Lead", {"lead_name": data.get("customer_name")})
|
||||
if not customer:
|
||||
print("#####DEBUG: No existing lead found. Creating new lead.")
|
||||
primary_contact = next((c for c in data.get("contacts", []) if c.get("is_primary")), None)
|
||||
if not primary_contact:
|
||||
return build_error_response("Primary contact information is required to create a new customer.", 400)
|
||||
print("#####DEBUG: Primary contact found:", primary_contact)
|
||||
client_doc = create_lead({
|
||||
"lead_name": data.get("customer_name"),
|
||||
"first_name": primary_contact.get("first_name"),
|
||||
"last_name": primary_contact.get("last_name"),
|
||||
"email_id": primary_contact.get("email"),
|
||||
"phone": primary_contact.get("phone_number"),
|
||||
"customer_type": data.get("customer_type"),
|
||||
"company": data.get("company")
|
||||
})
|
||||
else:
|
||||
print("#####DEBUG: Existing lead found:", customer)
|
||||
client_doc = frappe.get_doc("Lead", customer)
|
||||
else:
|
||||
print("#####DEBUG: Existing customer found:", customer)
|
||||
|
||||
if not customer:
|
||||
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)
|
||||
if not primary_contact:
|
||||
return build_error_response("Primary contact information is required to create a new customer.", 400)
|
||||
print("#####DEBUG: Primary contact found:", primary_contact)
|
||||
|
||||
new_lead_data = {
|
||||
"doctype": "Lead",
|
||||
"lead_name": data.get("customer_name"),
|
||||
"first_name": primary_contact.get("first_name"),
|
||||
"last_name": primary_contact.get("last_name"),
|
||||
"email_id": primary_contact.get("email"),
|
||||
"phone": primary_contact.get("phone_number"),
|
||||
"customer_type": data.get("customer_type"),
|
||||
"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:
|
||||
new_client_doc = frappe.get_doc("Customer", data.get("customer_name"))
|
||||
print(f"#####DEBUG: {new_client_doc.doctype}:", new_client_doc.as_dict())
|
||||
client_doc = frappe.get_doc("Customer", customer)
|
||||
print(f"#####DEBUG: {client_doc.doctype}:", client_doc.as_dict())
|
||||
|
||||
# Handle address creation
|
||||
print("#####DEBUG: Checking for existing address for customer/lead:", data.get("customer_name"))
|
||||
existing_address = frappe.db.exists(
|
||||
"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_doc = create_address({
|
||||
"address_title": build_address_title(data.get("customer_name"), data),
|
||||
"address_line1": data.get("address_line1"),
|
||||
"address_line2": data.get("address_line2"),
|
||||
"city": data.get("city"),
|
||||
"state": data.get("state"),
|
||||
"country": "United States",
|
||||
"pincode": data.get("pincode")
|
||||
}).insert(ignore_permissions=True)
|
||||
print("Address:", address_doc.as_dict())
|
||||
})
|
||||
|
||||
#Handle contact creation
|
||||
contact_docs = []
|
||||
@ -286,19 +271,22 @@ def upsert_client(data):
|
||||
if isinstance(contact_data, str):
|
||||
contact_data = json.loads(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")})
|
||||
print("Contact exists check:", contact_exists)
|
||||
if not contact_exists:
|
||||
contact_doc = frappe.get_doc({
|
||||
"doctype": "Contact",
|
||||
contact_doc = check_and_get_contact(
|
||||
contact_data.get("first_name"),
|
||||
contact_data.get("last_name"),
|
||||
contact_data.get("email"),
|
||||
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"),
|
||||
"last_name": contact_data.get("last_name"),
|
||||
# "email_id": contact_data.get("email"),
|
||||
# "phone": contact_data.get("phone_number"),
|
||||
"custom_customer": customer_doc.name,
|
||||
"role": contact_data.get("contact_role", "Other"),
|
||||
"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_id": contact_data.get("email"),
|
||||
"is_primary": 1
|
||||
@ -308,85 +296,37 @@ def upsert_client(data):
|
||||
"is_primary_mobile_no": 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)
|
||||
|
||||
##### Create links
|
||||
# Customer -> Address
|
||||
if new_client_doc.doctype == "Customer":
|
||||
print("#####DEBUG: Creating links between customer, address, and contacts.")
|
||||
new_client_doc.append("custom_select_address", {
|
||||
if client_doc.doctype == "Customer":
|
||||
print("#####DEBUG: Linking address to customer.")
|
||||
client_doc.append("custom_select_address", {
|
||||
"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
|
||||
print("#####DEBUG: Linking contacts to customer.")
|
||||
for contact_doc in contact_docs:
|
||||
print("Linking contact:", contact_doc.as_dict())
|
||||
print("with role:", contact_doc.role)
|
||||
print("customer to append to:", new_client_doc.as_dict())
|
||||
new_client_doc.append("custom_add_contacts", {
|
||||
client_doc.append("custom_add_contacts", {
|
||||
"contact": contact_doc.name,
|
||||
"email": contact_doc.custom_email,
|
||||
"phone": contact_doc.phone,
|
||||
"role": contact_doc.role
|
||||
})
|
||||
new_client_doc.append("links", {
|
||||
"link_doctype": "Contact",
|
||||
"link_name": contact_doc.name
|
||||
}
|
||||
)
|
||||
new_client_doc.save(ignore_permissions=True)
|
||||
client_doc.save(ignore_permissions=True)
|
||||
|
||||
# Address -> Customer/Lead
|
||||
print("#####DEBUG: Linking address to customer.")
|
||||
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)
|
||||
create_address_links(address_doc, client_doc, contact_docs)
|
||||
|
||||
# Contact -> Customer/Lead & Address
|
||||
print("#####DEBUG: Linking contacts to customer.")
|
||||
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)
|
||||
create_contact_links(contact_docs, client_doc, address_doc)
|
||||
|
||||
frappe.local.message_log = []
|
||||
return build_success_response({
|
||||
"customer": new_client_doc.as_dict(),
|
||||
"customer": client_doc.as_dict(),
|
||||
"address": address_doc.as_dict(),
|
||||
"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}%"
|
||||
client_names = frappe.db.get_all(
|
||||
"Customer",
|
||||
filters={"customer_name": ["like", search_pattern]},
|
||||
pluck="name")
|
||||
return build_success_response(client_names)
|
||||
except Exception as e:
|
||||
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 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 custom_ui.api.db.clients import check_if_customer, convert_lead_to_customer
|
||||
|
||||
# ===============================================================================
|
||||
# ESTIMATES & INVOICES API METHODS
|
||||
@ -152,6 +153,8 @@ def send_estimate_email(estimate_name):
|
||||
quotation.custom_current_status = "Submitted"
|
||||
quotation.custom_sent = 1
|
||||
quotation.save()
|
||||
quotation.submit()
|
||||
frappe.db.commit()
|
||||
updated_quotation = frappe.get_doc("Quotation", estimate_name)
|
||||
return build_success_response(updated_quotation.as_dict())
|
||||
except Exception as e:
|
||||
@ -166,20 +169,26 @@ def update_response(name, response):
|
||||
if not frappe.db.exists("Quotation", name):
|
||||
raise Exception("Estimate not found.")
|
||||
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
|
||||
new_status = "Estimate Accepted" if accepted else "Lost"
|
||||
|
||||
estimate.custom_response = response
|
||||
estimate.custom_current_status = new_status
|
||||
estimate.custom_followup_needed = 1 if response == "Requested call" else 0
|
||||
estimate.status = "Ordered" if accepted else "Closed"
|
||||
estimate.flags.ignore_permissions = True
|
||||
print("DEBUG: Updating estimate with response:", response, "and status:", new_status)
|
||||
# estimate.save()
|
||||
estimate.submit()
|
||||
frappe.db.commit()
|
||||
estimate.save()
|
||||
|
||||
if accepted:
|
||||
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":
|
||||
template = "custom_ui/templates/estimates/request-call.html"
|
||||
else:
|
||||
@ -211,6 +220,7 @@ def upsert_estimate(data):
|
||||
# Update fields
|
||||
estimate.custom_installation_address = data.get("address_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
|
||||
estimate.items = []
|
||||
|
||||
@ -102,3 +102,4 @@ def upsert_invoice(data):
|
||||
except Exception as e:
|
||||
return build_error_response(str(e), 500)
|
||||
|
||||
|
||||
|
||||
@ -159,6 +159,14 @@ def build_full_address(doc):
|
||||
return f"{first}, {second}"
|
||||
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):
|
||||
mappings = {
|
||||
"lead_name": "customer_name",
|
||||
@ -170,7 +178,8 @@ def map_lead_client(client_data):
|
||||
if lead_field in client_data:
|
||||
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["customer_group"] = "" # Leads don't have customer groups
|
||||
client_data["customer_group"] = ""
|
||||
print("####DEBUG: Mapped client data:", client_data)
|
||||
return 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.save()
|
||||
|
||||
def after_submit(doc, method):
|
||||
print("DEBUG: on_submit hook triggered for Quotation:", doc.name)
|
||||
def on_update_after_submit(doc, method):
|
||||
print("DEBUG: on_update_after_submit hook triggered for Quotation:", doc.name)
|
||||
if doc.custom_current_status == "Estimate Accepted":
|
||||
doc.custom_current_status = "Won"
|
||||
print("DEBUG: Creating Sales Order from accepted Estimate")
|
||||
address_doc = frappe.get_doc("Address", doc.custom_installation_address)
|
||||
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.custom_requires_half_payment = doc.requires_half_payment
|
||||
new_sales_order.insert()
|
||||
new_sales_order.submit()
|
||||
print("DEBUG: Sales Order created successfully:", new_sales_order.name)
|
||||
except Exception as 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")
|
||||
@ -3,4 +3,42 @@ import frappe
|
||||
def after_insert(doc, method):
|
||||
print(doc.as_dict())
|
||||
# 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": {
|
||||
"after_insert": "custom_ui.events.estimate.after_insert",
|
||||
"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": {
|
||||
"after_insert": "custom_ui.events.sales_order.after_insert"
|
||||
|
||||
@ -2,16 +2,6 @@
|
||||
<div class="form-section">
|
||||
<h3>Address Information</h3>
|
||||
<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">
|
||||
<label for="address-line1"> Address Line 1 <span class="required">*</span> </label>
|
||||
<InputText
|
||||
|
||||
@ -381,11 +381,12 @@ defineExpose({
|
||||
|
||||
.check-btn {
|
||||
border: 1px solid var(--primary-color);
|
||||
color: var(--primary-color);
|
||||
background: var(--surface-card);
|
||||
padding: 0.25rem 0.75rem;
|
||||
min-width: 8rem;
|
||||
color: white;
|
||||
background: var(--primary-color);
|
||||
padding: 0.25rem 0.5rem;
|
||||
min-width: 5rem;
|
||||
justify-content: center;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.check-btn:disabled {
|
||||
|
||||
@ -212,6 +212,7 @@ import DataUtils from "../../utils";
|
||||
import Api from "../../api";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useNotificationStore } from "../../stores/notifications-primevue";
|
||||
import { useCompanyStore } from "../../stores/company";
|
||||
|
||||
const props = defineProps({
|
||||
clientData: {
|
||||
@ -230,6 +231,7 @@ const props = defineProps({
|
||||
|
||||
const router = useRouter();
|
||||
const notificationStore = useNotificationStore();
|
||||
const companyStore = useCompanyStore();
|
||||
|
||||
const navigateTo = (path) => {
|
||||
router.push(path);
|
||||
@ -561,6 +563,7 @@ const handleSave = async () => {
|
||||
const clientData = {
|
||||
customerName: formData.value.customerName,
|
||||
customerType: formData.value.customerType,
|
||||
companyName: companyStore.currentCompany,
|
||||
addressTitle: formData.value.addressTitle,
|
||||
addressLine1: formData.value.addressLine1,
|
||||
addressLine2: formData.value.addressLine2,
|
||||
|
||||
@ -87,6 +87,13 @@
|
||||
<div class="total-section">
|
||||
<strong>Total Cost: ${{ totalCost.toFixed(2) }}</strong>
|
||||
</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">
|
||||
<Button label="Clear Items" @click="clearItems" severity="secondary" />
|
||||
<Button
|
||||
@ -199,6 +206,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
<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>
|
||||
<div class="confirmation-buttons">
|
||||
<Button
|
||||
@ -247,6 +255,7 @@ const formData = reactive({
|
||||
addressName: "",
|
||||
contact: "",
|
||||
estimateName: null,
|
||||
requiresHalfPayment: false,
|
||||
});
|
||||
|
||||
const selectedAddress = ref(null);
|
||||
@ -367,6 +376,7 @@ const saveDraft = async () => {
|
||||
contactName: selectedContact.value.name,
|
||||
items: selectedItems.value.map((i) => ({ itemCode: i.itemCode, qty: i.qty })),
|
||||
estimateName: formData.estimateName,
|
||||
requiresHalfPayment: formData.requiresHalfPayment,
|
||||
company: company.currentCompany
|
||||
};
|
||||
estimate.value = await Api.createEstimate(data);
|
||||
@ -517,6 +527,7 @@ watch(
|
||||
};
|
||||
});
|
||||
}
|
||||
formData.requiresHalfPayment = estimate.value.custom_requires_half_payment || false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading estimate:", error);
|
||||
@ -570,6 +581,7 @@ onMounted(async () => {
|
||||
};
|
||||
});
|
||||
}
|
||||
formData.requiresHalfPayment = estimate.value.custom_requires_half_payment || false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading estimate:", error);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user