add project template into estimate creation to help project generation flow

This commit is contained in:
Casey 2026-01-07 16:12:31 -06:00
parent 8f90ef09fb
commit 54ae6d14f8
8 changed files with 118 additions and 66 deletions

View file

@ -20,6 +20,7 @@ const FRAPPE_GET_JOBS_METHOD = "custom_ui.api.db.jobs.get_jobs_table_data";
const FRAPPE_UPSERT_JOB_METHOD = "custom_ui.api.db.jobs.upsert_job";
const FRAPPE_GET_JOB_TASK_LIST_METHOD = "custom_ui.api.db.jobs.get_job_task_table_data";
const FRAPPE_GET_INSTALL_PROJECTS_METHOD = "custom_ui.api.db.jobs.get_install_projects";
const FRAPPE_GET_JOB_TEMPLATES_METHOD = "custom_ui.api.db.jobs.get_job_templates";
// Invoice methods
const FRAPPE_GET_INVOICES_METHOD = "custom_ui.api.db.invoices.get_invoice_table_data";
const FRAPPE_UPSERT_INVOICE_METHOD = "custom_ui.api.db.invoices.upsert_invoice";
@ -236,6 +237,10 @@ class Api {
// JOB / PROJECT METHODS
// ============================================================================
static async getJobTemplates(company) {
return await this.request(FRAPPE_GET_JOB_TEMPLATES_METHOD, { company });
}
static async getJobDetails() {
const projects = await this.getDocsList("Project");
const data = [];

View file

@ -59,6 +59,23 @@
</div>
</div>
<!-- Project Template Section -->
<div class="project-template-section">
<label for="projectTemplate" class="field-label">
Project Template
<span class="required">*</span>
</label>
<Select
v-model="formData.projectTemplate"
:options="projectTemplates"
optionLabel="name"
optionValue="name"
placeholder="Select a project template"
:disabled="!isEditable"
fluid
/>
</div>
<!-- Template Section -->
<div class="template-section">
<div v-if="isNew">
@ -356,6 +373,7 @@ const formData = reactive({
contact: "",
estimateName: null,
requiresHalfPayment: false,
projectTemplate: null,
});
const selectedAddress = ref(null);
@ -369,6 +387,7 @@ const quotationItems = ref([]);
const selectedItems = ref([]);
const responses = ref(["Accepted", "Rejected"]);
const templates = ref([]);
const projectTemplates = ref([]);
const selectedTemplate = ref(null);
const showAddressModal = ref(false);
@ -396,6 +415,16 @@ const itemColumns = [
];
// Methods
const fetchProjectTemplates = async () => {
try {
const result = await Api.getJobTemplates(company.currentCompany);
projectTemplates.value = [...result, { name: "Other" }];
} catch (error) {
console.error("Error fetching project templates:", error);
notificationStore.addNotification("Failed to fetch project templates", "error");
}
};
const fetchTemplates = async () => {
if (!isNew.value) return;
try {
@ -556,6 +585,10 @@ const onQtyChange = (item) => {
};
const saveDraft = async () => {
if (!formData.projectTemplate) {
notificationStore.addNotification("Project Template is required.", "error");
return;
}
isSubmitting.value = true;
try {
const data = {
@ -570,6 +603,7 @@ const saveDraft = async () => {
})),
estimateName: formData.estimateName,
requiresHalfPayment: formData.requiresHalfPayment,
projectTemplate: formData.projectTemplate,
company: company.currentCompany
};
estimate.value = await Api.createEstimate(data);
@ -673,7 +707,10 @@ watch(
);
watch(() => company.currentCompany, () => {
fetchTemplates();
if (isNew.value) {
fetchTemplates();
fetchProjectTemplates();
}
});
// Watch for query param changes to refresh page behavior
@ -773,7 +810,8 @@ onMounted(async () => {
} catch (error) {
console.error("Error loading quotation items:", error);
}
fetchProjectTemplates();
if (isNew.value) {
fetchTemplates();
}
@ -854,6 +892,7 @@ onMounted(async () => {
.address-section,
.contact-section,
.project-template-section,
.template-section {
margin-bottom: 1.5rem;
}