update bugs

This commit is contained in:
Casey 2025-12-02 16:39:10 -06:00
parent 520e239741
commit fe46f18d60
6 changed files with 198 additions and 203 deletions

View File

@ -253,17 +253,25 @@ def upsert_client(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")})
if not contact_exists:
is_primary_contact = 1 if contact_data.get("is_primary_contact") else 0
contact_doc = frappe.get_doc({
"doctype": "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"),
# "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": is_primary_contact
"is_primary_contact": data.get("is_primary", False),
"email_ids": [{
"email_id": contact_data.get("email"),
"is_primary": 1
}],
"phone_nos": [{
"phone": contact_data.get("phone_number"),
"is_primary_mobile_no": 1,
"is_primary_phone": 1
}]
}).insert(ignore_permissions=True)
print("Created new contact:", contact_doc.as_dict())
else:
@ -316,6 +324,7 @@ def upsert_client(data):
# Contact -> Customer & 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": "Customer",
"link_name": customer_doc.name

View File

@ -66,12 +66,6 @@ def get_estimate(estimate_name):
except Exception as e:
return build_error_response(str(e), 500)
@frappe.whitelist()
def upsert_estimate(data):
"""Create or update an estimate."""
# TODO: Implement estimate creation/update logic
pass
@frappe.whitelist()
def get_estimate_items():
items = frappe.db.get_all("Quotation Item", fields=["*"])
@ -98,25 +92,54 @@ def get_estimate_from_address(full_address):
@frappe.whitelist()
def upsert_estimate(data):
"""Create or update an estimate."""
print("DOIFJSEOFJISLFK")
try:
data = json.loads(data) if isinstance(data, str) else data
print("DEBUG: Retrieved address name:", data.get("address_name"))
new_estimate = frappe.get_doc({
"doctype": "Quotation",
"custom_installation_address": data.get("address_name"),
"contact_email": data.get("contact_email"),
"party_name": data.get("contact_name"),
"customer_name": data.get("customer_name"),
})
for item in data.get("items", []):
item = json.loads(item) if isinstance(item, str) else item
new_estimate.append("items", {
"item_code": item.get("item_code"),
"qty": item.get("qty"),
print("DEBUG: Upsert estimate data:", data)
estimate_name = data.get("estimate_name")
# If estimate_name exists, update existing estimate
if estimate_name:
print(f"DEBUG: Updating existing estimate: {estimate_name}")
estimate = frappe.get_doc("Quotation", estimate_name)
# Update fields
estimate.custom_installation_address = data.get("address_name")
estimate.party_name = data.get("contact_name")
# Clear existing items and add new ones
estimate.items = []
for item in data.get("items", []):
item = json.loads(item) if isinstance(item, str) else item
estimate.append("items", {
"item_code": item.get("item_code"),
"qty": item.get("qty"),
})
estimate.save()
print(f"DEBUG: Estimate updated: {estimate.name}")
return build_success_response(estimate.as_dict())
# Otherwise, create new estimate
else:
print("DEBUG: Creating new estimate")
print("DEBUG: Retrieved address name:", data.get("address_name"))
new_estimate = frappe.get_doc({
"doctype": "Quotation",
"custom_installation_address": data.get("address_name"),
"contact_email": data.get("contact_email"),
"party_name": data.get("contact_name"),
"customer_name": data.get("customer_name"),
})
new_estimate.insert()
print("DEBUG: New estimate created with name:", new_estimate.name)
return build_success_response(new_estimate.as_dict())
for item in data.get("items", []):
item = json.loads(item) if isinstance(item, str) else item
new_estimate.append("items", {
"item_code": item.get("item_code"),
"qty": item.get("qty"),
})
new_estimate.insert()
print("DEBUG: New estimate created with name:", new_estimate.name)
return build_success_response(new_estimate.as_dict())
except Exception as e:
print(f"DEBUG: Error in upsert_estimate: {str(e)}")
return build_error_response(str(e), 500)

View File

@ -269,41 +269,11 @@ const handleLazyLoad = async (event) => {
});
}
// Clear cache when filters or sorting are active to ensure fresh data
const hasActiveFilters = Object.keys(filters).length > 0;
const hasActiveSorting = event.sortField && event.sortOrder;
if (hasActiveFilters || hasActiveSorting) {
paginationStore.clearTableCache("clients");
}
// For cache key, use primary sort field/order for compatibility
const primarySortField = filtersStore.getPrimarySortField("clients") || event.sortField;
const primarySortOrder = filtersStore.getPrimarySortOrder("clients") || event.sortOrder;
// Check cache first
const cachedData = paginationStore.getCachedPage(
"clients",
paginationParams.page,
paginationParams.pageSize,
primarySortField,
primarySortOrder,
filters,
);
if (cachedData) {
// Use cached data
tableData.value = cachedData.records;
totalRecords.value = cachedData.totalRecords;
paginationStore.setTotalRecords("clients", cachedData.totalRecords);
console.log("Loaded from cache:", {
records: cachedData.records.length,
total: cachedData.totalRecords,
page: paginationParams.page + 1,
});
return;
}
// Always fetch fresh data from API (cache only stores pagination/filter/sort state, not data)
// Call API with pagination, filters, and sorting in backend format
console.log("Making API call with:", {
paginationParams,
@ -325,19 +295,6 @@ const handleLazyLoad = async (event) => {
// Update pagination store with new total
paginationStore.setTotalRecords("clients", result.pagination.total);
// Cache the result using primary sort for compatibility
paginationStore.setCachedPage(
"clients",
paginationParams.page,
paginationParams.pageSize,
primarySortField,
primarySortOrder,
filters,
{
records: result.data,
totalRecords: result.pagination.total,
},
);
} catch (error) {
console.error("Error loading client data:", error);
// You could also show a toast or other error notification here

View File

@ -46,7 +46,7 @@
fluid
/>
<div v-if="selectedContact" class="verification-info">
<strong>Email:</strong> {{ selectedContact.customEmail || "N/A" }} <br />
<strong>Email:</strong> {{ selectedContact.emailId || "N/A" }} <br />
<strong>Phone:</strong> {{ selectedContact.phone || "N/A" }} <br />
<strong>Primary Contact:</strong>
{{ selectedContact.isPrimaryContact ? "Yes" : "No" }}
@ -87,13 +87,13 @@
<div v-if="isEditable" class="action-buttons">
<Button label="Clear Items" @click="clearItems" severity="secondary" />
<Button
label="Submit"
@click="showConfirmationModal = true"
label="Save Draft"
@click="saveDraft"
:disabled="selectedItems.length === 0"
/>
</div>
<div>
<Button label="Send Estimate" @click="showSubmitEstimateModal = true"/>
<div v-if="!isNew && estimate">
<Button label="Send Estimate" @click="showConfirmationModal = true"/>
</div>
</div>
@ -141,6 +141,10 @@
fluid
/>
</div>
<div class="tip-section">
<i class="pi pi-info-circle"></i>
<span>Tip: Hold <kbd>Ctrl</kbd> (or <kbd>Cmd</kbd> on Mac) to select multiple items</span>
</div>
<DataTable
:data="filteredItems"
:columns="itemColumns"
@ -158,8 +162,9 @@
:visible="showConfirmationModal"
@update:visible="showConfirmationModal = $event"
@close="showConfirmationModal = false"
:options="{ showActions: false }"
>
<template #title>Confirm Estimate</template>
<template #title>Confirm Send Estimate</template>
<div class="modal-content">
<h4>Does this information look correct?</h4>
<p><strong>Address:</strong> {{ formData.address }}</p>
@ -171,6 +176,10 @@
: ""
}}
</p>
<p>
<strong>Email:</strong>
{{ selectedContact?.emailId || "N/A" }}
</p>
<p><strong>Items:</strong></p>
<ul>
<li v-for="item in selectedItems" :key="item.itemCode">
@ -180,37 +189,18 @@
</li>
</ul>
<p><strong>Total:</strong> ${{ totalCost.toFixed(2) }}</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
label="No"
label="Cancel"
@click="showConfirmationModal = false"
severity="secondary"
/>
<Button label="Yes" @click="confirmSubmit" />
<Button label="Send Estimate" @click="confirmAndSendEstimate" />
</div>
</div>
</Modal>
<!-- Submit Estimate Modal -->
<Modal
:visible="showSubmitEstimateModal"
@update:visible="showSubmitEstimateModal = $event"
@close="showSubmitEstimateModal = false"
:options="{ showActions: false }"
>
<p><strong>Submit Estimate and Email {{ selectedContact.firstName }} {{
selectedContact.lastName}}?</strong></p>
<p>This cannot be undone, please make sure all information is correct.</p>
<div class="confirmation-buttons">
<Button
label="No"
@click="showSubmitEstimateModal = false"
severity="secondary"
/>
<Button label="Yes" @click="sendEstimate" />
</div>
</Modal>
</div>
</template>
@ -242,6 +232,7 @@ const formData = reactive({
address: "",
addressName: "",
contact: "",
estimateName: null,
});
const selectedAddress = ref(null);
@ -254,7 +245,6 @@ const selectedItems = ref([]);
const showAddressModal = ref(false);
const showAddItemModal = ref(false);
const showConfirmationModal = ref(false);
const showSubmitEstimateModal = ref(false);
const addressSearchResults = ref([]);
const itemSearchTerm = ref("");
@ -355,33 +345,37 @@ const updateTotal = () => {
// Computed will update
};
const confirmSubmit = async () => {
const saveDraft = async () => {
isSubmitting.value = true;
showConfirmationModal.value = false;
try {
const data = {
addressName: formData.addressName,
contactName: selectedContact.value.name,
items: selectedItems.value.map((i) => ({ itemCode: i.itemCode, qty: i.qty })),
estimateName: formData.estimateName,
};
await Api.createEstimate(data);
notificationStore.addSuccess("Estimate created successfully", "success");
estimate.value = await Api.createEstimate(data);
notificationStore.addSuccess(
formData.estimateName ? "Estimate updated successfully" : "Estimate created successfully",
"success"
);
// Redirect to view mode (remove new param)
router.push(`/estimate?address=${encodeURIComponent(formData.address)}`);
// Reset form
formData.address = "";
formData.addressName = "";
formData.contact = "";
selectedAddress.value = null;
selectedContact.value = null;
selectedItems.value = [];
} catch (error) {
console.error("Error creating estimate:", error);
notificationStore.addError("Failed to create estimate", "error");
console.error("Error saving estimate:", error);
notificationStore.addNotification("Failed to save estimate", "error");
} finally {
isSubmitting.value = false;
}
};
const confirmAndSendEstimate = async () => {
showConfirmationModal.value = false;
// TODO: Implement send estimate functionality
notificationStore.addWarning("Send estimate functionality coming soon");
};
const tableActions = [
{
label: "Add Selected Items",
@ -420,6 +414,67 @@ watch(
},
);
// Watch for query param changes to refresh page behavior
watch(
() => route.query,
async (newQuery, oldQuery) => {
// If 'new' param or address changed, reload component state
if (newQuery.new !== oldQuery.new || newQuery.address !== oldQuery.address) {
// Reset all state
formData.address = "";
formData.addressName = "";
formData.contact = "";
formData.estimateName = null;
selectedAddress.value = null;
selectedContact.value = null;
contacts.value = [];
contactOptions.value = [];
selectedItems.value = [];
estimate.value = null;
// Reload data based on new query params
const newIsNew = newQuery.new === "true";
const newAddressQuery = newQuery.address;
if (newAddressQuery && newIsNew) {
// Creating new estimate - pre-fill address
await selectAddress(newAddressQuery);
} else if (newAddressQuery && !newIsNew) {
// Viewing existing estimate - load and populate all fields
try {
estimate.value = await Api.getEstimateFromAddress(newAddressQuery);
if (estimate.value) {
formData.estimateName = estimate.value.name;
await selectAddress(newAddressQuery);
formData.contact = estimate.value.partyName;
selectedContact.value = contacts.value.find((c) => c.name === estimate.value.partyName) || null;
if (estimate.value.items && estimate.value.items.length > 0) {
selectedItems.value = estimate.value.items.map(item => {
const fullItem = quotationItems.value.find(qi => qi.itemCode === item.itemCode);
return {
itemCode: item.itemCode,
itemName: item.itemName,
qty: item.qty,
standardRate: item.rate || fullItem?.standardRate || 0,
};
});
}
}
} catch (error) {
console.error("Error loading estimate:", error);
notificationStore.addNotification(
"Failed to load estimate details.",
"error"
);
}
}
}
},
{ deep: true }
);
onMounted(async () => {
console.log("DEBUG: Query params:", route.query);
try {
@ -438,6 +493,9 @@ onMounted(async () => {
console.log("DEBUG: Loaded estimate:", estimate.value);
if (estimate.value) {
// Set the estimate name for upserting
formData.estimateName = estimate.value.name;
await selectAddress(addressQuery);
// Set the contact from the estimate
formData.contact = estimate.value.partyName;
@ -549,6 +607,33 @@ onMounted(async () => {
margin-bottom: 1rem;
}
.tip-section {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem;
margin-bottom: 1rem;
background-color: #e3f2fd;
border: 1px solid #2196f3;
border-radius: 4px;
color: #1565c0;
font-size: 0.9rem;
}
.tip-section i {
color: #2196f3;
}
.tip-section kbd {
background-color: #fff;
border: 1px solid #ccc;
border-radius: 3px;
padding: 2px 6px;
font-family: monospace;
font-size: 0.85em;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.confirmation-buttons {
display: flex;
gap: 1rem;
@ -556,6 +641,15 @@ onMounted(async () => {
margin-top: 1rem;
}
.warning-text {
margin-top: 1rem;
padding: 0.75rem;
background-color: #fff3cd;
border: 1px solid #ffc107;
border-radius: 4px;
color: #856404;
}
.address-search-results {
min-height: 200px;
}

View File

@ -128,37 +128,7 @@ const handleLazyLoad = async (event) => {
});
}
// Clear cache when filters or sorting are active to ensure fresh data
const hasActiveFilters = Object.keys(filters).length > 0;
const hasActiveSorting = paginationParams.sortField && paginationParams.sortOrder;
if (hasActiveFilters || hasActiveSorting) {
paginationStore.clearTableCache("estimates");
}
// Check cache first
const cachedData = paginationStore.getCachedPage(
"estimates",
paginationParams.page,
paginationParams.pageSize,
sorting.field || paginationParams.sortField,
sorting.order || paginationParams.sortOrder,
filters,
);
if (cachedData) {
// Use cached data
tableData.value = cachedData.records;
totalRecords.value = cachedData.totalRecords;
paginationStore.setTotalRecords("estimates", cachedData.totalRecords);
console.log("Loaded from cache:", {
records: cachedData.records.length,
total: cachedData.totalRecords,
page: paginationParams.page + 1,
});
return;
}
// Always fetch fresh data from API (cache only stores pagination/filter/sort state, not data)
console.log("Making API call with:", { paginationParams, filters });
// Call API with pagination, filters, and sorting
@ -180,20 +150,6 @@ const handleLazyLoad = async (event) => {
storeTotalPages: paginationStore.getTotalPages("estimates"),
});
// Cache the result
paginationStore.setCachedPage(
"estimates",
paginationParams.page,
paginationParams.pageSize,
sorting.field || paginationParams.sortField,
sorting.order || paginationParams.sortOrder,
filters,
{
records: result.data,
totalRecords: result.pagination.total,
},
);
console.log("Loaded from API:", {
records: result.data.length,
total: result.pagination.total,

View File

@ -75,37 +75,7 @@ const handleLazyLoad = async (event) => {
});
}
// Clear cache when filters or sorting are active to ensure fresh data
const hasActiveFilters = Object.keys(filters).length > 0;
const hasActiveSorting = paginationParams.sortField && paginationParams.sortOrder;
if (hasActiveFilters || hasActiveSorting) {
paginationStore.clearTableCache("invoices");
}
// Check cache first
const cachedData = paginationStore.getCachedPage(
"invoices",
paginationParams.page,
paginationParams.pageSize,
sorting.field || paginationParams.sortField,
sorting.order || paginationParams.sortOrder,
filters,
);
if (cachedData) {
// Use cached data
tableData.value = cachedData.records;
totalRecords.value = cachedData.totalRecords;
paginationStore.setTotalRecords("invoices", cachedData.totalRecords);
console.log("Loaded from cache:", {
records: cachedData.records.length,
total: cachedData.totalRecords,
page: paginationParams.page + 1,
});
return;
}
// Always fetch fresh data from API (cache only stores pagination/filter/sort state, not data)
console.log("Making API call with:", { paginationParams, filters });
// Call API with pagination, filters, and sorting
@ -127,20 +97,6 @@ const handleLazyLoad = async (event) => {
storeTotalPages: paginationStore.getTotalPages("invoices"),
});
// Cache the result
paginationStore.setCachedPage(
"invoices",
paginationParams.page,
paginationParams.pageSize,
sorting.field || paginationParams.sortField,
sorting.order || paginationParams.sortOrder,
filters,
{
records: result.data,
totalRecords: result.pagination.total,
},
);
console.log("Loaded from API:", {
records: result.data.length,
total: result.pagination.total,