all flow
This commit is contained in:
parent
c81beb5290
commit
38514fef47
24 changed files with 2087 additions and 322 deletions
|
|
@ -70,8 +70,10 @@ const FRAPPE_GET_EMPLOYEES_ORGANIZED_METHOD = "custom_ui.api.db.employees.get_em
|
|||
const FRAPPE_GET_WEEK_HOLIDAYS_METHOD = "custom_ui.api.db.general.get_week_holidays";
|
||||
const FRAPPE_GET_DOC_LIST_METHOD = "custom_ui.api.db.general.get_doc_list";
|
||||
// Service Appointment methods
|
||||
const FRAPPE_GET_SERVICE_APPOINTMENT_METHOD = "custom_ui.api.db.service_appointments.get_service_appointment";
|
||||
const FRAPPE_GET_SERVICE_APPOINTMENTS_METHOD = "custom_ui.api.db.service_appointments.get_service_appointments";
|
||||
const FRAPPE_UPDATE_SERVICE_APPOINTMENT_SCHEDULED_DATES_METHOD = "custom_ui.api.db.service_appointments.update_service_appointment_scheduled_dates";
|
||||
const FRAPPE_UPDATE_SERVICE_APPOINTMENT_STATUS_METHOD = "custom_ui.api.db.service_appointments.update_service_appointment_status";
|
||||
class Api {
|
||||
// ============================================================================
|
||||
// CORE REQUEST METHOPD
|
||||
|
|
@ -457,6 +459,10 @@ class Api {
|
|||
// SERVICE APPOINTMENT METHODS
|
||||
// ============================================================================
|
||||
|
||||
static async getServiceAppointment(serviceAppointmentName) {
|
||||
return await this.request(FRAPPE_GET_SERVICE_APPOINTMENT_METHOD, { serviceAppointmentName });
|
||||
}
|
||||
|
||||
static async getServiceAppointments(companies = [], filters = {}) {
|
||||
return await this.request(FRAPPE_GET_SERVICE_APPOINTMENTS_METHOD, { companies, filters });
|
||||
}
|
||||
|
|
@ -472,6 +478,10 @@ class Api {
|
|||
})
|
||||
}
|
||||
|
||||
static async setServiceAppointmentStatus(serviceAppointmentName, newStatus) {
|
||||
return await this.request(FRAPPE_UPDATE_SERVICE_APPOINTMENT_STATUS_METHOD, { serviceAppointmentName, newStatus });
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TASK METHODS
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="calendar-navigation">
|
||||
<Tabs value="0" v-if="companyStore.currentCompany == 'Sprinklers Northwest'">
|
||||
<Tabs :value="defaultTab" v-if="companyStore.currentCompany == 'Sprinklers Northwest'">
|
||||
<TabList>
|
||||
<Tab value="0">Bids</Tab>
|
||||
<Tab value="1">Projects</Tab>
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
<Tabs v-else value="0">
|
||||
<Tabs v-else :value="defaultTab">
|
||||
<TabList>
|
||||
<Tab value="0">Bids</Tab>
|
||||
<Tab value="1">Projects</Tab>
|
||||
|
|
@ -61,20 +61,36 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import Tab from 'primevue/tab';
|
||||
import Tabs from 'primevue/tabs';
|
||||
import TabList from 'primevue/tablist';
|
||||
import TabPanel from 'primevue/tabpanel';
|
||||
import TabPanels from 'primevue/tabpanels';
|
||||
import ScheduleBid from '../calendar/bids/ScheduleBid.vue';
|
||||
import JobsCalendar from '../calendar/jobs/JobsCalendar.vue';
|
||||
import SNWProjectCalendar from './jobs/SNWProjectCalendar.vue';
|
||||
import { useNotificationStore } from '../../stores/notifications-primevue';
|
||||
import { useCompanyStore } from '../../stores/company';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const notifications = useNotificationStore();
|
||||
const companyStore = useCompanyStore();
|
||||
const route = useRoute();
|
||||
|
||||
const defaultTab = ref('0');
|
||||
|
||||
const mappedTabs = {
|
||||
'bid': '0',
|
||||
'projects': '1',
|
||||
'service': '2',
|
||||
'warranties': companyStore.currentCompany == 'Sprinklers Northwest' ? '6' : '3'
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// Check for route query parameter to set default tab
|
||||
if (route.query.tab) {
|
||||
defaultTab.value = mappedTabs[route.query.tab] || '0';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -330,10 +330,14 @@ import { ref, onMounted, computed, watch } from "vue";
|
|||
import Api from "../../../api";
|
||||
import { useNotificationStore } from "../../../stores/notifications-primevue";
|
||||
import { useCompanyStore } from "../../../stores/company";
|
||||
import { useRoute } from "vue-router";
|
||||
import JobDetailsModal from "../../modals/JobDetailsModal.vue";
|
||||
|
||||
const notifications = useNotificationStore();
|
||||
const companyStore = useCompanyStore();
|
||||
const route = useRoute();
|
||||
|
||||
const serviceAptToFind = route.query.apt || null;
|
||||
|
||||
// Reactive data
|
||||
const scheduledServices = ref([]);
|
||||
|
|
@ -1302,6 +1306,40 @@ const stopResize = async () => {
|
|||
originalEndDate.value = null;
|
||||
};
|
||||
|
||||
const handleServiceAptToFind = async () => {
|
||||
if (!serviceAptToFind) return;
|
||||
|
||||
try {
|
||||
// Fetch the specific service appointment
|
||||
const appointment = await Api.getServiceAppointment(serviceAptToFind);
|
||||
|
||||
if (appointment && appointment.expectedStartDate) {
|
||||
// Navigate to the week containing this appointment
|
||||
weekStartDate.value = getWeekStart(parseLocalDate(appointment.expectedStartDate));
|
||||
|
||||
// Wait for data to load
|
||||
await fetchServiceAppointments();
|
||||
|
||||
// Find the appointment in the loaded data
|
||||
const foundAppointment = scheduledServices.value.find(s => s.name === serviceAptToFind) ||
|
||||
unscheduledServices.value.find(s => s.name === serviceAptToFind);
|
||||
|
||||
if (foundAppointment) {
|
||||
// Open the details modal
|
||||
selectedEvent.value = foundAppointment;
|
||||
eventDialog.value = true;
|
||||
} else {
|
||||
notifications.addWarning("Service appointment found but not visible in current filters");
|
||||
}
|
||||
} else {
|
||||
notifications.addWarning("Service appointment not scheduled yet");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error finding service appointment:", error);
|
||||
notifications.addError("Failed to find service appointment");
|
||||
}
|
||||
};
|
||||
|
||||
const fetchServiceAppointments = async (currentDate) => {
|
||||
try {
|
||||
// Calculate date range for the week
|
||||
|
|
@ -1397,6 +1435,9 @@ onMounted(async () => {
|
|||
// await fetchProjects();
|
||||
await fetchServiceAppointments();
|
||||
await fetchHolidays();
|
||||
|
||||
// Handle serviceAptToFind query parameter
|
||||
await handleServiceAptToFind();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -187,6 +187,17 @@
|
|||
<v-icon left>mdi-open-in-new</v-icon>
|
||||
View Job
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="job.status !== 'Completed'"
|
||||
color="success"
|
||||
variant="flat"
|
||||
@click="updateStatus('Completed')"
|
||||
:loading="isUpdatingStatus"
|
||||
:disabled="isUpdatingStatus"
|
||||
>
|
||||
<v-icon left>mdi-check-circle</v-icon>
|
||||
Mark as Completed
|
||||
</v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn variant="text" @click="handleClose">Close</v-btn>
|
||||
</v-card-actions>
|
||||
|
|
@ -196,6 +207,10 @@
|
|||
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import Api from "../../api";
|
||||
import { useNotificationStore } from "../../stores/notifications-primevue";
|
||||
|
||||
const notifications = useNotificationStore();
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
|
|
@ -214,7 +229,10 @@ const props = defineProps({
|
|||
});
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits(["update:modelValue", "close"]);
|
||||
const emit = defineEmits(["update:modelValue", "close", "statusUpdated"]);
|
||||
|
||||
// State
|
||||
const isUpdatingStatus = ref(false);
|
||||
|
||||
// Computed
|
||||
const showModal = computed({
|
||||
|
|
@ -304,6 +322,34 @@ const getPriorityColor = (priority) => {
|
|||
}
|
||||
};
|
||||
|
||||
const updateStatus = async (newStatus) => {
|
||||
if (!props.job?.name) return;
|
||||
|
||||
isUpdatingStatus.value = true;
|
||||
try {
|
||||
await Api.setServiceAppointmentStatus(props.job.name, newStatus);
|
||||
notifications.addSuccess(`Service appointment marked as ${newStatus}`);
|
||||
|
||||
// Update local job status
|
||||
if (props.job) {
|
||||
props.job.status = newStatus;
|
||||
}
|
||||
|
||||
// Emit event to parent to refresh data
|
||||
emit("statusUpdated", { name: props.job.name, status: newStatus });
|
||||
|
||||
// Close modal after short delay
|
||||
setTimeout(() => {
|
||||
handleClose();
|
||||
}, 1000);
|
||||
} catch (error) {
|
||||
console.error("Error updating service appointment status:", error);
|
||||
notifications.addError("Failed to update service appointment status");
|
||||
} finally {
|
||||
isUpdatingStatus.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const viewJob = () => {
|
||||
if (props.job?.name) {
|
||||
window.location.href = `/job?name=${encodeURIComponent(props.job.name)}`;
|
||||
|
|
|
|||
|
|
@ -1163,6 +1163,8 @@ onMounted(async () => {
|
|||
qty: item.qty,
|
||||
rate: item.rate,
|
||||
standardRate: item.rate,
|
||||
bom: item.bom || null,
|
||||
uom: item.uom || item.stockUom || item.stock_uom || 'Nos',
|
||||
discountAmount: discountAmount === 0 ? null : discountAmount,
|
||||
discountPercentage: discountPercentage === 0 ? null : discountPercentage,
|
||||
discountType: discountPercentage > 0 ? 'percentage' : 'currency'
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -136,9 +136,9 @@ const chartData = ref({
|
|||
})
|
||||
|
||||
const columns = [
|
||||
{ label: "Job ID", fieldName: "name", type: "text", sortable: true, filterable: true },
|
||||
{ label: "Address", fieldName: "jobAddress", type: "text", sortable: true },
|
||||
{ label: "Customer", fieldName: "customer", type: "text", sortable: true, filterable: true },
|
||||
{ label: "Address", fieldName: "jobAddress", type: "text", sortable: true },
|
||||
{ label: "Job ID", fieldName: "name", type: "text", sortable: true, filterable: true },
|
||||
{ label: "Overall Status", fieldName: "status", type: "status", sortable: true },
|
||||
{ label: "Invoice Status", fieldName: "invoiceStatus", type: "text", sortable: true },
|
||||
{ label: "Progress", fieldName: "percentComplete", type: "text", sortable: true }
|
||||
|
|
@ -275,7 +275,6 @@ const loadChartData = async () => {
|
|||
|
||||
// Load initial data
|
||||
onMounted(async () => {
|
||||
notifications.addWarning("Jobs page coming soon");
|
||||
// Initialize pagination and filters
|
||||
paginationStore.initializeTablePagination("jobs", { rows: 10 });
|
||||
filtersStore.initializeTableFilters("jobs", columns);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue