job creation working
This commit is contained in:
parent
bd9e00c6f1
commit
d3818d1985
22 changed files with 591 additions and 179 deletions
|
|
@ -50,19 +50,30 @@
|
|||
placeholder="Select a contact"
|
||||
:disabled="!formData.address || !isEditable"
|
||||
fluid
|
||||
/>
|
||||
>
|
||||
<template #option="slotProps">
|
||||
<div class="contact-option">
|
||||
<div class="contact-name">{{ slotProps.option.label }}</div>
|
||||
<div class="contact-detail" v-if="slotProps.option.email">{{ slotProps.option.email }}</div>
|
||||
<div class="contact-detail" v-if="slotProps.option.phone">{{ slotProps.option.phone }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</Select>
|
||||
<div v-if="selectedContact" class="verification-info">
|
||||
<strong>Email:</strong> {{ selectedContact.emailId || "N/A" }} <br />
|
||||
<strong>Phone:</strong> {{ selectedContact.phone || "N/A" }} <br />
|
||||
<strong>Primary Contact:</strong>
|
||||
{{ selectedContact.isPrimaryContact ? "Yes" : "No" }}
|
||||
{{ selectedAddress?.primaryContact === selectedContact.name ? "Yes" : "No" }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Template Section -->
|
||||
<div class="template-section">
|
||||
<div v-if="isNew">
|
||||
<label for="template" class="field-label">From Template</label>
|
||||
<label for="template" class="field-label">
|
||||
From Template
|
||||
<i class="pi pi-question-circle help-icon" v-tooltip.right="'Pre-fills estimate items and sets default Project Template. Serves as a starting point for this estimate.'"></i>
|
||||
</label>
|
||||
<div class="template-input-group">
|
||||
<Select
|
||||
v-model="selectedTemplate"
|
||||
|
|
@ -99,6 +110,7 @@
|
|||
<label for="projectTemplate" class="field-label">
|
||||
Project Template
|
||||
<span class="required">*</span>
|
||||
<i class="pi pi-question-circle help-icon" v-tooltip.right="'Used when generating a Project from this estimate. Defines tasks and default settings for the new Project.'"></i>
|
||||
</label>
|
||||
<Select
|
||||
v-model="formData.projectTemplate"
|
||||
|
|
@ -122,57 +134,63 @@
|
|||
/>
|
||||
<div v-for="(item, index) in selectedItems" :key="item.itemCode" class="item-row">
|
||||
<span>{{ item.itemName }}</span>
|
||||
<InputNumber
|
||||
v-model="item.qty"
|
||||
:min="1"
|
||||
:disabled="!isEditable"
|
||||
showButtons
|
||||
buttonLayout="horizontal"
|
||||
@input="onQtyChange(item)"
|
||||
class="qty-input"
|
||||
/>
|
||||
<div class="input-wrapper">
|
||||
<span class="input-label">Quantity</span>
|
||||
<InputNumber
|
||||
v-model="item.qty"
|
||||
:min="1"
|
||||
:disabled="!isEditable"
|
||||
showButtons
|
||||
buttonLayout="horizontal"
|
||||
@input="onQtyChange(item)"
|
||||
class="qty-input"
|
||||
/>
|
||||
</div>
|
||||
<span>Price: ${{ (item.standardRate || 0).toFixed(2) }}</span>
|
||||
<div class="discount-container">
|
||||
<div class="discount-input-wrapper">
|
||||
<InputNumber
|
||||
v-if="item.discountType === 'currency'"
|
||||
v-model="item.discountAmount"
|
||||
mode="currency"
|
||||
currency="USD"
|
||||
locale="en-US"
|
||||
:min="0"
|
||||
:disabled="!isEditable"
|
||||
@input="updateDiscountFromAmount(item)"
|
||||
placeholder="$0.00"
|
||||
class="discount-input"
|
||||
/>
|
||||
<InputNumber
|
||||
v-else
|
||||
v-model="item.discountPercentage"
|
||||
suffix="%"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:disabled="!isEditable"
|
||||
@input="updateDiscountFromPercentage(item)"
|
||||
placeholder="0%"
|
||||
class="discount-input"
|
||||
/>
|
||||
</div>
|
||||
<div class="discount-toggle">
|
||||
<Button
|
||||
icon="pi pi-dollar"
|
||||
class="p-button-sm p-button-outlined"
|
||||
:class="{ 'p-button-secondary': item.discountType !== 'currency' }"
|
||||
@click="toggleDiscountType(item, 'currency')"
|
||||
:disabled="!isEditable"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-percentage"
|
||||
class="p-button-sm p-button-outlined"
|
||||
:class="{ 'p-button-secondary': item.discountType !== 'percentage' }"
|
||||
@click="toggleDiscountType(item, 'percentage')"
|
||||
:disabled="!isEditable"
|
||||
/>
|
||||
<div class="input-wrapper">
|
||||
<span class="input-label">Discount</span>
|
||||
<div class="discount-container">
|
||||
<div class="discount-input-wrapper">
|
||||
<InputNumber
|
||||
v-if="item.discountType === 'currency'"
|
||||
v-model="item.discountAmount"
|
||||
mode="currency"
|
||||
currency="USD"
|
||||
locale="en-US"
|
||||
:min="0"
|
||||
:disabled="!isEditable"
|
||||
@input="updateDiscountFromAmount(item)"
|
||||
placeholder="$0.00"
|
||||
class="discount-input"
|
||||
/>
|
||||
<InputNumber
|
||||
v-else
|
||||
v-model="item.discountPercentage"
|
||||
suffix="%"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:disabled="!isEditable"
|
||||
@input="updateDiscountFromPercentage(item)"
|
||||
placeholder="0%"
|
||||
class="discount-input"
|
||||
/>
|
||||
</div>
|
||||
<div class="discount-toggle">
|
||||
<Button
|
||||
icon="pi pi-dollar"
|
||||
class="p-button-sm p-button-outlined"
|
||||
:class="{ 'p-button-secondary': item.discountType !== 'currency' }"
|
||||
@click="toggleDiscountType(item, 'currency')"
|
||||
:disabled="!isEditable"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-percentage"
|
||||
class="p-button-sm p-button-outlined"
|
||||
:class="{ 'p-button-secondary': item.discountType !== 'percentage' }"
|
||||
@click="toggleDiscountType(item, 'percentage')"
|
||||
:disabled="!isEditable"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span>Total: ${{ ((item.qty || 0) * (item.standardRate || 0) - (item.discountAmount || 0)).toFixed(2) }}</span>
|
||||
|
|
@ -202,7 +220,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div v-if="estimate">
|
||||
<Button label="Send Estimate" @click="showConfirmationModal = true" :disabled="estimate.customSent === 1"/>
|
||||
<Button label="Send Estimate" @click="initiateSendEstimate" :disabled="estimate.customSent === 1"/>
|
||||
</div>
|
||||
<div v-if="estimate && estimate.customSent === 1" class="response-status">
|
||||
<h4>Customer Response:</h4>
|
||||
|
|
@ -296,6 +314,27 @@
|
|||
</div>
|
||||
</Modal>
|
||||
|
||||
<!-- Down Payment Warning Modal -->
|
||||
<Modal
|
||||
:visible="showDownPaymentWarningModal"
|
||||
@update:visible="showDownPaymentWarningModal = $event"
|
||||
@close="showDownPaymentWarningModal = false"
|
||||
:options="{ showActions: false }"
|
||||
>
|
||||
<template #title>Warning</template>
|
||||
<div class="modal-content">
|
||||
<p>Down payment is not required for this estimate. Ok to proceed?</p>
|
||||
<div class="confirmation-buttons">
|
||||
<Button
|
||||
label="No"
|
||||
@click="showDownPaymentWarningModal = false"
|
||||
severity="secondary"
|
||||
/>
|
||||
<Button label="Yes" @click="proceedFromWarning" />
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<!-- Confirmation Modal -->
|
||||
<Modal
|
||||
:visible="showConfirmationModal"
|
||||
|
|
@ -356,6 +395,7 @@ import InputText from "primevue/inputtext";
|
|||
import InputNumber from "primevue/inputnumber";
|
||||
import Button from "primevue/button";
|
||||
import Select from "primevue/select";
|
||||
import Tooltip from "primevue/tooltip";
|
||||
import Api from "../../api";
|
||||
import DataUtils from "../../utils";
|
||||
import { useLoadingStore } from "../../stores/loading";
|
||||
|
|
@ -367,6 +407,7 @@ const router = useRouter();
|
|||
const loadingStore = useLoadingStore();
|
||||
const notificationStore = useNotificationStore();
|
||||
const company = useCompanyStore();
|
||||
const vTooltip = Tooltip;
|
||||
|
||||
const addressQuery = computed(() => route.query.address || "");
|
||||
const nameQuery = computed(() => route.query.name || "");
|
||||
|
|
@ -406,6 +447,7 @@ const selectedTemplate = ref(null);
|
|||
const showAddressModal = ref(false);
|
||||
const showAddItemModal = ref(false);
|
||||
const showConfirmationModal = ref(false);
|
||||
const showDownPaymentWarningModal = ref(false);
|
||||
const showResponseModal = ref(false);
|
||||
const showSaveTemplateModal = ref(false);
|
||||
const addressSearchResults = ref([]);
|
||||
|
|
@ -459,10 +501,20 @@ const fetchTemplates = async () => {
|
|||
const templateParam = route.query.template;
|
||||
if (templateParam) {
|
||||
console.log("DEBUG: Setting template from query param:", templateParam);
|
||||
console.log("DEBUG: Available templates:", templates.value.map(t => t.name));
|
||||
selectedTemplate.value = templateParam;
|
||||
// Trigger template change to load items and project template
|
||||
onTemplateChange();
|
||||
|
||||
// Find template by name (ID) or templateName (Label)
|
||||
const matchedTemplate = templates.value.find(t =>
|
||||
t.name === templateParam || t.templateName === templateParam
|
||||
);
|
||||
|
||||
if (matchedTemplate) {
|
||||
console.log("DEBUG: Found matched template:", matchedTemplate);
|
||||
selectedTemplate.value = matchedTemplate.name;
|
||||
// Trigger template change to load items and project template
|
||||
onTemplateChange();
|
||||
} else {
|
||||
console.log("DEBUG: No matching template found for param:", templateParam);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching templates:", error);
|
||||
|
|
@ -580,8 +632,10 @@ const selectAddress = async (address) => {
|
|||
contactOptions.value = contacts.value.map((c) => ({
|
||||
label: `${c.firstName || ""} ${c.lastName || ""}`.trim() || c.name,
|
||||
value: c.name,
|
||||
email: c.emailId,
|
||||
phone: c.phone || c.mobileNo
|
||||
}));
|
||||
const primary = contacts.value.find((c) => c.isPrimaryContact);
|
||||
const primary = contacts.value.find((c) => c.name === selectedAddress.value.primaryContact);
|
||||
console.log("DEBUG: Selected address contacts:", contacts.value);
|
||||
const existingContactName = estimate.value ? contacts.value.find((c) => c.fullName === estimate.value.partyName)?.name || "" : null;
|
||||
// Check for contact query param, then existing contact, then primary, then first contact
|
||||
|
|
@ -721,6 +775,19 @@ const getResponseText = (response) => {
|
|||
return "No response yet";
|
||||
};
|
||||
|
||||
const initiateSendEstimate = () => {
|
||||
if (!formData.requiresHalfPayment) {
|
||||
showDownPaymentWarningModal.value = true;
|
||||
} else {
|
||||
showConfirmationModal.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const proceedFromWarning = () => {
|
||||
showDownPaymentWarningModal.value = false;
|
||||
showConfirmationModal.value = true;
|
||||
};
|
||||
|
||||
const confirmAndSendEstimate = async () => {
|
||||
loadingStore.setLoading(true, "Sending estimate...");
|
||||
const updatedEstimate = await Api.sendEstimateEmail(estimate.value.name);
|
||||
|
|
@ -1265,5 +1332,38 @@ onMounted(async () => {
|
|||
.field-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.help-icon {
|
||||
margin-left: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
color: #2196f3;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
font-size: 0.8rem;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.contact-option {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.contact-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.contact-detail {
|
||||
font-size: 0.85rem;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
<parameter name="filePath"></parameter>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue