major claude changes

This commit is contained in:
2026-01-28 21:55:14 -05:00
parent f9d0e4c0fd
commit f9b5364c53
81 changed files with 11155 additions and 10086 deletions

21
package-lock.json generated
View File

@@ -19,14 +19,12 @@
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
"moment": "^2.30.1",
"pinia": "^2.3.1", "pinia": "^2.3.1",
"v-pagination-3": "^0.1.7", "v-pagination-3": "^0.1.7",
"vue": "^3.3.11", "vue": "^3.3.11",
"vue-debounce": "^5.0.0", "vue-debounce": "^5.0.0",
"vue-router": "^4.2.5", "vue-router": "^4.2.5",
"vue3-pdfmake": "^2.2.0", "vue3-pdfmake": "^2.2.0"
"vuelidate": "^0.7.7"
}, },
"devDependencies": { "devDependencies": {
"@types/leaflet": "^1.9.12", "@types/leaflet": "^1.9.12",
@@ -4482,14 +4480,6 @@
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
} }
}, },
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"engines": {
"node": "*"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -9129,15 +9119,6 @@
} }
} }
}, },
"node_modules/vuelidate": {
"version": "0.7.7",
"resolved": "https://registry.npmjs.org/vuelidate/-/vuelidate-0.7.7.tgz",
"integrity": "sha512-pT/U2lDI67wkIqI4tum7cMSIfGcAMfB+Phtqh2ttdXURwvHRBJEAQ0tVbUsW9Upg83Q5QH59bnCoXI7A9JDGnA==",
"engines": {
"node": ">= 4.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@@ -20,14 +20,12 @@
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
"moment": "^2.30.1",
"pinia": "^2.3.1", "pinia": "^2.3.1",
"v-pagination-3": "^0.1.7", "v-pagination-3": "^0.1.7",
"vue": "^3.3.11", "vue": "^3.3.11",
"vue-debounce": "^5.0.0", "vue-debounce": "^5.0.0",
"vue-router": "^4.2.5", "vue-router": "^4.2.5",
"vue3-pdfmake": "^2.2.0", "vue3-pdfmake": "^2.2.0"
"vuelidate": "^0.7.7"
}, },
"devDependencies": { "devDependencies": {
"@types/leaflet": "^1.9.12", "@types/leaflet": "^1.9.12",

View File

@@ -116,9 +116,17 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue' import { defineComponent, PropType } from 'vue'
import axios from 'axios' import axios, { AxiosError } from 'axios'
import { notify } from "@kyvg/vue3-notification" import { notify } from "@kyvg/vue3-notification"
import { TRANSACTION_STATUS } from '../constants/status'
import type {
DeliveryFormData,
CustomerFormData,
CreditCardFormData,
PricingData,
PromoData
} from '../types/models'
export default defineComponent({ export default defineComponent({
name: 'PaymentAuthorizePopup', name: 'PaymentAuthorizePopup',
@@ -128,19 +136,19 @@ export default defineComponent({
default: false default: false
}, },
delivery: { delivery: {
type: Object, type: Object as PropType<DeliveryFormData>,
required: true required: true
}, },
customer: { customer: {
type: Object, type: Object as PropType<CustomerFormData>,
required: true required: true
}, },
creditCards: { creditCards: {
type: Array, type: Array as PropType<CreditCardFormData[]>,
default: () => [] default: () => []
}, },
pricing: { pricing: {
type: Object, type: Object as PropType<PricingData>,
required: true required: true
}, },
promoActive: { promoActive: {
@@ -148,7 +156,7 @@ export default defineComponent({
default: false default: false
}, },
promo: { promo: {
type: Object, type: Object as PropType<PromoData>,
default: () => ({}) default: () => ({})
}, },
totalAmount: { totalAmount: {
@@ -175,9 +183,9 @@ export default defineComponent({
} }
}, },
computed: { computed: {
selectedCard(): any { selectedCard(): CreditCardFormData | undefined {
// Find the card that matches the delivery's payment_card_id // Find the card that matches the delivery's payment_card_id
return this.creditCards.find((card: any) => card.id === this.delivery.payment_card_id) return this.creditCards.find((card) => card.id === this.delivery.payment_card_id)
} }
}, },
watch: { watch: {
@@ -223,14 +231,14 @@ export default defineComponent({
try { try {
const endpoint = actionType === 'preauthorize' ? 'authorize' : 'charge' const endpoint = actionType === 'preauthorize' ? 'authorize' : 'charge'
const payload = { const payload = {
card_number: (this.selectedCard as any).card_number, card_number: this.selectedCard.card_number,
expiration_date: `${(this.selectedCard as any).expiration_month}${(this.selectedCard as any).expiration_year}`, expiration_date: `${this.selectedCard.expiration_month}${this.selectedCard.expiration_year}`,
cvv: (this.selectedCard as any).security_number, cvv: this.selectedCard.security_number,
[actionType === 'preauthorize' ? 'preauthorize_amount' : 'charge_amount']: this.chargeAmount.toString(), [actionType === 'preauthorize' ? 'preauthorize_amount' : 'charge_amount']: this.chargeAmount.toString(),
transaction_type: actionType === 'preauthorize' ? 1 : 0, transaction_type: actionType === 'preauthorize' ? 1 : 0,
service_id: this.delivery.service_id || null, // Add service_id from delivery service_id: this.delivery.service_id || null, // Add service_id from delivery
delivery_id: this.delivery.id, // Add delivery_id from delivery delivery_id: this.delivery.id, // Add delivery_id from delivery
card_id: (this.selectedCard as any).id // Add card_id from selected card card_id: this.selectedCard.id // Add card_id from selected card
} }
console.log('=== DEBUG: PaymentAuthorizePopup payload ===') console.log('=== DEBUG: PaymentAuthorizePopup payload ===')
@@ -245,7 +253,7 @@ export default defineComponent({
{ withCredentials: true } { withCredentials: true }
) )
if (response.data && response.data.status === 0) { if (response.data && response.data.status === TRANSACTION_STATUS.APPROVED) {
this.success = `${actionType === 'preauthorize' ? 'Preauthorization' : 'Charge'} successful! Transaction ID: ${response.data.auth_net_transaction_id || 'N/A'}` this.success = `${actionType === 'preauthorize' ? 'Preauthorization' : 'Charge'} successful! Transaction ID: ${response.data.auth_net_transaction_id || 'N/A'}`
this.$emit('payment-success', { this.$emit('payment-success', {
action: actionType, action: actionType,
@@ -255,7 +263,8 @@ export default defineComponent({
} else { } else {
throw new Error(`Payment ${actionType} failed: ${response.data?.status || 'Unknown error'}`) throw new Error(`Payment ${actionType} failed: ${response.data?.status || 'Unknown error'}`)
} }
} catch (error: any) { } catch (err: unknown) {
const error = err as AxiosError<{ detail?: string }>
this.error = error.response?.data?.detail || `Failed to ${actionType} payment` this.error = error.response?.data?.detail || `Failed to ${actionType} payment`
notify({ notify({
title: "Error", title: "Error",

View File

@@ -52,49 +52,51 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue'; import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
import { mapState, mapActions } from 'pinia'; import { useSearchStore } from '../stores/search'
import { useSearchStore } from '../stores/search'; // Adjust path if needed
export default defineComponent({ // Store
name: 'SearchResults', const searchStore = useSearchStore()
data() { // Template ref
return { const searchContainer = ref<HTMLElement>()
stateMap: {
// Reactive data
const stateMap = ref({
0: 'MA', 1: 'RI', 2: 'NH', 3: 'ME', 4: 'VT', 5: 'CT', 6: 'NY', 0: 'MA', 1: 'RI', 2: 'NH', 3: 'ME', 4: 'VT', 5: 'CT', 6: 'NY',
} as Record<number, string>, } as Record<number, string>)
};
},
computed: { // Computed properties
...mapState(useSearchStore, ['searchTerm', 'searchResults', 'isLoading']), const searchTerm = computed(() => searchStore.searchTerm)
}, const searchResults = computed(() => searchStore.searchResults)
const isLoading = computed(() => searchStore.isLoading)
methods: { // Functions
...mapActions(useSearchStore, ['clearSearch']), const getStateName = (stateValue: number | string): string => {
getStateName(stateValue: number | string): string {
const stateNumber = Number(stateValue); const stateNumber = Number(stateValue);
return this.stateMap[stateNumber] || 'N/A'; return stateMap.value[stateNumber] || 'N/A';
}, }
handleClickOutside(event: MouseEvent) { const clearSearch = () => {
const searchContainer = this.$refs.searchContainer as HTMLElement; searchStore.clearSearch();
}
const handleClickOutside = (event: MouseEvent) => {
const container = searchContainer.value;
const searchInput = document.getElementById('customer-search-input'); const searchInput = document.getElementById('customer-search-input');
if (searchContainer && !searchContainer.contains(event.target as Node) && searchInput && !searchInput.contains(event.target as Node)) { if (container && !container.contains(event.target as Node) && searchInput && !searchInput.contains(event.target as Node)) {
this.clearSearch(); searchStore.clearSearch();
} }
}, }
},
mounted() { // Lifecycle
document.addEventListener('mousedown', this.handleClickOutside); onMounted(() => {
}, document.addEventListener('mousedown', handleClickOutside);
beforeUnmount() { })
document.removeEventListener('mousedown', this.handleClickOutside);
}, onBeforeUnmount(() => {
}); document.removeEventListener('mousedown', handleClickOutside);
})
</script> </script>

95
src/constants/status.ts Normal file
View File

@@ -0,0 +1,95 @@
/**
* EAMCO Office Frontend Status Constants
*
* This file contains all status code constants used throughout the frontend
* to eliminate magic numbers and improve code maintainability.
*/
export const DELIVERY_STATUS = {
WAITING: 0,
CANCELLED: 1,
OUT_FOR_DELIVERY: 2,
TOMORROW: 3,
PARTIAL_DELIVERY: 4,
ISSUE: 5,
UNKNOWN: 6,
PENDING_PAYMENT: 9,
FINALIZED: 10,
DELIVERED: 11, // New: Replaces previous use of 1 for delivered
} as const;
export const PAYMENT_STATUS = {
UNPAID: 0,
PRE_AUTHORIZED: 1,
PROCESSING: 2,
PAID: 3,
FAILED: 4,
} as const;
export const AUTO_STATUS = {
DEFAULT: 0,
WILL_CALL: 1,
READY_FOR_FINALIZATION: 3,
} as const;
export const TRANSACTION_STATUS = {
APPROVED: 0,
DECLINED: 1,
} as const;
export const CUSTOMER_AUTOMATIC_STATUS = {
WILL_CALL: 0,
AUTOMATIC: 1,
} as const;
// Helper functions for type-safe status access
export type DeliveryStatusType = typeof DELIVERY_STATUS[keyof typeof DELIVERY_STATUS];
export type PaymentStatusType = typeof PAYMENT_STATUS[keyof typeof PAYMENT_STATUS];
export type AutoStatusType = typeof AUTO_STATUS[keyof typeof AUTO_STATUS];
export type TransactionStatusType = typeof TRANSACTION_STATUS[keyof typeof TRANSACTION_STATUS];
export type CustomerAutomaticStatus = typeof CUSTOMER_AUTOMATIC_STATUS[keyof typeof CUSTOMER_AUTOMATIC_STATUS];
// Utility functions for status display
export function getDeliveryStatusLabel(status: DeliveryStatusType): string {
switch (status) {
case DELIVERY_STATUS.WAITING: return 'Waiting';
case DELIVERY_STATUS.CANCELLED: return 'Cancelled';
case DELIVERY_STATUS.OUT_FOR_DELIVERY: return 'Out for Delivery';
case DELIVERY_STATUS.TOMORROW: return 'Tomorrow';
case DELIVERY_STATUS.PARTIAL_DELIVERY: return 'Partial Delivery';
case DELIVERY_STATUS.ISSUE: return 'Issue';
case DELIVERY_STATUS.UNKNOWN: return 'Unknown';
case DELIVERY_STATUS.PENDING_PAYMENT: return 'Pending Payment';
case DELIVERY_STATUS.FINALIZED: return 'Finalized';
case DELIVERY_STATUS.DELIVERED: return 'Delivered';
default: return 'N/A';
}
}
export function getPaymentStatusLabel(status: PaymentStatusType): string {
switch (status) {
case PAYMENT_STATUS.UNPAID: return 'Unpaid';
case PAYMENT_STATUS.PRE_AUTHORIZED: return 'Pre-authorized';
case PAYMENT_STATUS.PROCESSING: return 'Processing';
case PAYMENT_STATUS.PAID: return 'Paid';
case PAYMENT_STATUS.FAILED: return 'Failed';
default: return 'Unknown';
}
}
export function getAutoStatusLabel(status: AutoStatusType): string {
switch (status) {
case AUTO_STATUS.DEFAULT: return 'Default';
case AUTO_STATUS.WILL_CALL: return 'Will Call';
case AUTO_STATUS.READY_FOR_FINALIZATION: return 'Ready for Finalization';
default: return 'Unknown';
}
}
export function getTransactionStatusLabel(status: TransactionStatusType): string {
switch (status) {
case TRANSACTION_STATUS.APPROVED: return 'Approved';
case TRANSACTION_STATUS.DECLINED: return 'Declined';
default: return 'Unknown';
}
}

View File

@@ -212,9 +212,9 @@
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
// NO CHANGES to the script block were made. All your logic remains intact. import { ref, computed, onMounted } from 'vue'
import { defineComponent } from 'vue' import { useRouter } from 'vue-router'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../services/auth.header' import authHeader from '../../services/auth.header'
import { useSearchStore } from '../../stores/search' // Adjust path if needed import { useSearchStore } from '../../stores/search' // Adjust path if needed
@@ -231,64 +231,60 @@ interface RoutingOption {
label: string; label: string;
} }
// Router
const router = useRouter()
export default defineComponent({ // Reactive data
data() { const user = ref({} as User)
return { const currentPhone = ref('')
// Initialize with empty objects to prevent template errors const routingOptions = ref([
user: {} as User,
currentPhone: '',
routingOptions: [
{ value: 'main', label: '407323' }, { value: 'main', label: '407323' },
{ value: 'sip', label: '407323_auburnoil' }, { value: 'sip', label: '407323_auburnoil' },
{ value: 'cellphone1', label: 'Ed Cell' }, { value: 'cellphone1', label: 'Ed Cell' },
{ value: 'cellphone2', label: 'Aneta Cell' }, { value: 'cellphone2', label: 'Aneta Cell' },
{ value: 'test_did', label: 'Test DID' } { value: 'test_did', label: 'Test DID' }
] as RoutingOption[], ] as RoutingOption[])
selectedOption: null as RoutingOption | null, const selectedOption = ref<RoutingOption | null>(null)
isRouteModalVisible: false, const isRouteModalVisible = ref(false)
routeModalMode: 'confirm', const routeModalMode = ref('confirm')
routeResponse: null as any, const routeResponse = ref(null as any)
isTestModalVisible: false, const isTestModalVisible = ref(false)
isTestLoading: false, const isTestLoading = ref(false)
testResponse: null as any const testResponse = ref(null as any)
}
},
computed: { // Computed properties
searchStore() { const searchStore = computed(() => useSearchStore())
return useSearchStore();
}, const userInitials = computed((): string => {
userInitials(): string { if (!user.value || !user.value.user_name) return '';
if (!this.user || !this.user.user_name) return ''; const parts = user.value.user_name.split(' ');
const parts = this.user.user_name.split(' ');
return parts.length > 1 return parts.length > 1
? `${parts[0][0]}${parts[1][0]}`.toUpperCase() ? `${parts[0][0]}${parts[1][0]}`.toUpperCase()
: this.user.user_name.substring(0, 2).toUpperCase(); : user.value.user_name.substring(0, 2).toUpperCase();
}, })
currentDate(): string {
const currentDate = computed((): string => {
const now = new Date(); const now = new Date();
const month = (now.getMonth() + 1).toString().padStart(2, '0'); const month = (now.getMonth() + 1).toString().padStart(2, '0');
const day = now.getDate().toString().padStart(2, '0'); const day = now.getDate().toString().padStart(2, '0');
const year = now.getFullYear().toString().slice(-2); const year = now.getFullYear().toString().slice(-2);
return `${month}/${day}/${year}`; return `${month}/${day}/${year}`;
}, })
dayOfWeek(): string {
const dayOfWeek = computed((): string => {
const now = new Date(); const now = new Date();
return now.toLocaleDateString('en-US', { weekday: 'long' }); return now.toLocaleDateString('en-US', { weekday: 'long' });
} })
},
created() { // Lifecycle
this.userStatus(); onMounted(() => {
}, userStatus()
mounted() { updatestatus()
this.updatestatus(); fetchCurrentPhone()
this.fetchCurrentPhone(); })
},
methods: { // Functions
userStatus() { const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: "get", method: "get",
@@ -299,15 +295,16 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} else { } else {
localStorage.removeItem('user'); localStorage.removeItem('user');
this.$router.push('/login'); router.push('/login');
} }
}) })
}, }
updatestatus() {
const updatestatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/updatestatus'; let path = import.meta.env.VITE_BASE_URL + '/delivery/updatestatus';
axios({ axios({
method: 'get', method: 'get',
@@ -317,15 +314,17 @@ export default defineComponent({
if (response.data.update) if (response.data.update)
console.log("Updated Status of Deliveries") console.log("Updated Status of Deliveries")
}) })
}, }
logout() {
const logout = () => {
// Clear auth data // Clear auth data
const authStore = useAuthStore(); const authStore = useAuthStore();
authStore.clearAuth(); authStore.clearAuth();
// Redirect to login // Redirect to login
this.$router.push({ name: 'login' }); router.push({ name: 'login' });
}, }
fetchCurrentPhone() {
const fetchCurrentPhone = () => {
const path = import.meta.env.VITE_BASE_URL + '/admin/voip_routing'; const path = import.meta.env.VITE_BASE_URL + '/admin/voip_routing';
axios({ axios({
method: 'get', method: 'get',
@@ -335,14 +334,15 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.current_phone) { if (response.data.current_phone) {
this.currentPhone = response.data.current_phone; currentPhone.value = response.data.current_phone;
} }
}) })
.catch((error: any) => { .catch((error: any) => {
console.error('Failed to fetch current routing:', error); console.error('Failed to fetch current routing:', error);
}); });
}, }
routeTo(route: string): Promise<any> {
const routeTo = (route: string): Promise<any> => {
const path = `${import.meta.env.VITE_VOIPMS_URL}/route/${route}`; const path = `${import.meta.env.VITE_VOIPMS_URL}/route/${route}`;
return axios({ return axios({
method: 'post', method: 'post',
@@ -350,46 +350,50 @@ export default defineComponent({
withCredentials: true, headers: authHeader() withCredentials: true, headers: authHeader()
}) })
.then((response: any) => { .then((response: any) => {
this.routeResponse = response.data; routeResponse.value = response.data;
// Find the corresponding label // Find the corresponding label
const option = this.routingOptions.find(opt => opt.value === route); const option = routingOptions.value.find(opt => opt.value === route);
if (option) { if (option) {
this.currentPhone = option.label; currentPhone.value = option.label;
} }
return response.data; return response.data;
}); });
}, }
showConfirmRoute(option: RoutingOption) {
this.selectedOption = option; const showConfirmRoute = (option: RoutingOption) => {
selectedOption.value = option;
if (option.value === 'test_did') { if (option.value === 'test_did') {
this.testDid(); testDid();
} else { } else {
this.isRouteModalVisible = true; isRouteModalVisible.value = true;
this.routeModalMode = 'confirm'; routeModalMode.value = 'confirm';
} }
}, }
proceedRoute() {
if (this.selectedOption && this.selectedOption.value !== 'test_did') { const proceedRoute = () => {
this.routeModalMode = 'loading'; if (selectedOption.value && selectedOption.value.value !== 'test_did') {
this.routeTo(this.selectedOption.value) routeModalMode.value = 'loading';
routeTo(selectedOption.value.value)
.then(() => { .then(() => {
this.routeModalMode = 'result'; routeModalMode.value = 'result';
}) })
.catch((error: any) => { .catch((error: any) => {
this.routeResponse = { error: error.message }; routeResponse.value = { error: error.message };
this.routeModalMode = 'result'; routeModalMode.value = 'result';
}); });
} }
}, }
closeRouteModal() {
this.isRouteModalVisible = false; const closeRouteModal = () => {
this.routeModalMode = 'confirm'; isRouteModalVisible.value = false;
this.routeResponse = null; routeModalMode.value = 'confirm';
this.selectedOption = null; routeResponse.value = null;
}, selectedOption.value = null;
testDid() { }
this.isTestModalVisible = true;
this.isTestLoading = true; const testDid = () => {
isTestModalVisible.value = true;
isTestLoading.value = true;
const path = `${import.meta.env.VITE_VOIPMS_URL}/test/did`; const path = `${import.meta.env.VITE_VOIPMS_URL}/test/did`;
axios({ axios({
method: 'get', method: 'get',
@@ -397,14 +401,12 @@ export default defineComponent({
withCredentials: true, headers: authHeader() withCredentials: true, headers: authHeader()
}) })
.then((response: any) => { .then((response: any) => {
this.testResponse = response.data; testResponse.value = response.data;
this.isTestLoading = false; isTestLoading.value = false;
}) })
.catch((error: any) => { .catch((error: any) => {
this.testResponse = { status: 'error', message: error.message }; testResponse.value = { status: 'error', message: error.message };
this.isTestLoading = false; isTestLoading.value = false;
}); });
} }
}
});
</script> </script>

View File

@@ -15,31 +15,19 @@
<script lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue'
import { defineComponent } from "vue"; // Reactive data (though not used in template)
const user = ref(null)
const loaded = ref(false)
const clicked = ref(false)
const hovered = ref(false)
// Lifecycle
onMounted(() => {
export default defineComponent({ // Component mounted
name: "HeaderNoAuth", })
mounted() {
},
data() {
return {
user: null,
loaded: false,
clicked: false,
hovered: false,
};
},
methods: {
},
});
</script> </script>
<style scoped></style> <style scoped></style>

View File

@@ -110,42 +110,39 @@
</div> </div>
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import axios from 'axios' import axios from 'axios'
import authHeader from '../services/auth.header' import authHeader from '../services/auth.header'
import Header from '../layouts/headers/headerauth.vue' import Header from '../layouts/headers/headerauth.vue'
import SideBar from '../layouts/sidebar/sidebar.vue' import SideBar from '../layouts/sidebar/sidebar.vue'
import Footer from '../layouts/footers/footer.vue' import Footer from '../layouts/footers/footer.vue'
export default defineComponent({ // Props
name: 'Home', const props = defineProps<{
clickCount?: number
}>()
components: { // Router
Header, const router = useRouter()
SideBar,
Footer, // Reactive data
}, const token = ref(null)
props: { const call_count = ref(0)
clickCount: Number const delivery_count = ref(0)
}, const delivery_count_delivered = ref(0)
data() { const price_from_supplier = ref(0)
return { const today_oil_price = ref(0)
token: null, const price_for_employee = ref(0)
call_count:0, const price_same_day = ref(0)
delivery_count: 0, const price_prime = ref(0)
delivery_count_delivered: 0, const price_emergency = ref(0)
price_from_supplier: 0, const user = ref({
today_oil_price: 0,
price_for_employee: 0,
price_same_day: 0,
price_prime: 0,
price_emergency: 0,
user: {
user_id: 0, user_id: 0,
user_name: '', user_name: '',
}, })
employee: { const employee = ref({
id: '', id: '',
user_id: '', user_id: '',
employee_last_name: "", employee_last_name: "",
@@ -160,30 +157,24 @@ export default defineComponent({
employee_end_date: "", employee_end_date: "",
employee_type: '', employee_type: '',
employee_state: '', employee_state: '',
}, })
total_gallons_past_week: 0, const total_gallons_past_week = ref(0)
total_profit_past_week: 0, const total_profit_past_week = ref(0)
total_deliveries: 0, const total_deliveries = ref(0)
const loaded = ref(false)
// Lifecycle
onMounted(() => {
userStatus()
today_delivery_count()
today_delivery_delivered()
today_price_oil()
totalgallonsweek()
totalprofitweek()
})
loaded: false, // Functions
const userStatus = () => {
}
},
created() {
this.userStatus()
this.today_delivery_count()
this.today_delivery_delivered()
this.today_price_oil()
this.totalgallonsweek()
this.totalprofitweek()
},
methods: {
userStatus() {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: "get", method: "get",
@@ -193,18 +184,16 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
this.employeeStatus() employeeStatus()
} else { } else {
localStorage.removeItem('user'); localStorage.removeItem('user');
this.$router.push('/login'); router.push('/login');
} }
}) })
}
}, const totalgallonsweek = () => {
totalgallonsweek() {
let path = import.meta.env.VITE_BASE_URL + '/stats/gallons/week'; let path = import.meta.env.VITE_BASE_URL + '/stats/gallons/week';
axios({ axios({
method: "get", method: "get",
@@ -213,11 +202,11 @@ export default defineComponent({
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: any) => {
this.total_gallons_past_week = response.data.total; total_gallons_past_week.value = response.data.total;
}) })
}, }
totalprofitweek() {
const totalprofitweek = () => {
let path = import.meta.env.VITE_BASE_URL + '/money/profit/week'; let path = import.meta.env.VITE_BASE_URL + '/money/profit/week';
axios({ axios({
method: "get", method: "get",
@@ -226,13 +215,13 @@ export default defineComponent({
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: any) => {
this.total_profit_past_week = response.data.total_profit; total_profit_past_week.value = response.data.total_profit;
this.total_deliveries = response.data.total_deliveries; total_deliveries.value = response.data.total_deliveries;
}) })
}, }
employeeStatus() {
let path = import.meta.env.VITE_BASE_URL + '/employee/userid/' + this.user.user_id; const employeeStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/employee/userid/' + user.value.user_id;
axios({ axios({
method: "get", method: "get",
url: path, url: path,
@@ -240,12 +229,12 @@ export default defineComponent({
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: any) => {
this.employee = response.data; employee.value = response.data;
this.loaded = true; loaded.value = true;
}) })
}, }
total_calls() {
const total_calls = () => {
let path = import.meta.env.VITE_BASE_URL + '/stats/call/count/today' let path = import.meta.env.VITE_BASE_URL + '/stats/call/count/today'
axios({ axios({
method: "get", method: "get",
@@ -254,10 +243,11 @@ export default defineComponent({
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: any) => {
this.call_count = response.data.data; call_count.value = response.data.data;
}) })
}, }
today_delivery_count() {
const today_delivery_count = () => {
let path = import.meta.env.VITE_BASE_URL + '/stats/delivery/count/today' let path = import.meta.env.VITE_BASE_URL + '/stats/delivery/count/today'
axios({ axios({
method: "get", method: "get",
@@ -266,10 +256,11 @@ export default defineComponent({
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: any) => {
this.delivery_count = response.data.data; delivery_count.value = response.data.data;
}) })
}, }
today_delivery_delivered() {
const today_delivery_delivered = () => {
let path = import.meta.env.VITE_BASE_URL + '/stats/delivery/count/delivered/today' let path = import.meta.env.VITE_BASE_URL + '/stats/delivery/count/delivered/today'
axios({ axios({
method: "get", method: "get",
@@ -279,10 +270,11 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
console.log(response.data) console.log(response.data)
this.delivery_count_delivered = response.data.data; delivery_count_delivered.value = response.data.data;
}) })
}, }
today_price_oil() {
const today_price_oil = () => {
let path = import.meta.env.VITE_BASE_URL + '/info/price/oil' let path = import.meta.env.VITE_BASE_URL + '/info/price/oil'
axios({ axios({
method: "get", method: "get",
@@ -291,19 +283,12 @@ export default defineComponent({
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: any) => {
this.price_from_supplier = response.data.price_from_supplier; price_from_supplier.value = response.data.price_from_supplier;
this.today_oil_price = response.data.price_for_customer; today_oil_price.value = response.data.price_for_customer;
this.price_for_employee = response.data.price_for_employee; price_for_employee.value = response.data.price_for_employee;
this.price_same_day = response.data.price_same_day; price_same_day.value = response.data.price_same_day;
this.price_prime = response.data.price_prime; price_prime.value = response.data.price_prime;
this.price_emergency = response.data.price_emergency; price_emergency.value = response.data.price_emergency;
}) })
}, }
},
})
</script>
<style scoped></style>
<script setup lang="ts">
</script> </script>

View File

@@ -1,10 +1,10 @@
import OilPrice from '../admin/oilprice.vue'; const OilPrice = () => import('../admin/oilprice.vue');
import Promo from '../admin/promo/promo.vue'; const Promo = () => import('../admin/promo/promo.vue');
import PromoCreate from '../admin/promo/create.vue'; const PromoCreate = () => import('../admin/promo/create.vue');
import PromoEdit from '../admin/promo/edit.vue'; const PromoEdit = () => import('../admin/promo/edit.vue');
const adminRoutes = [ const adminRoutes = [
@@ -35,5 +35,3 @@ const adminRoutes = [
export default adminRoutes export default adminRoutes
//sourceMappingURL=index.ts.map //sourceMappingURL=index.ts.map

View File

@@ -1,7 +1,7 @@
import Login from '../auth/login.vue'; const Login = () => import('../auth/login.vue');
import Register from '../auth/register.vue'; const Register = () => import('../auth/register.vue');
import changePassword from '../auth/changepassword.vue'; const changePassword = () => import('../auth/changepassword.vue');
import lostPassword from '../auth/lostpassword.vue'; const lostPassword = () => import('../auth/lostpassword.vue');
const authRoutes = [ const authRoutes = [
{ {

View File

@@ -1,5 +1,5 @@
<!-- src/pages/authorize/PaymentForm.vue --> <!-- src/pages/authorize/PaymentForm.vue -->
<!-- <template> <template>
<div class="container mx-auto p-4"> <div class="container mx-auto p-4">
<h1 class="text-2xl font-bold mb-4">HVAC Payment</h1> <h1 class="text-2xl font-bold mb-4">HVAC Payment</h1>
@@ -87,89 +87,91 @@
</div> </div>
</div> </div>
<div v-if="transactionResult" class="mt-8 p-4 rounded-lg" :class="transactionResult.status === 'approved' || transactionResult.status === 'authorized' || transactionResult.status === 'captured' ? 'bg-green-200' : 'bg-red-200'"> <div v-if="transactionResult" class="mt-8 p-4 rounded-lg" :class="transactionResult.status === 0 || transactionResult.status === 1 || transactionResult.status === 2 ? 'bg-green-200' : 'bg-red-200'">
<h3 class="font-bold">Transaction Result</h3> <h3 class="font-bold">Transaction Result</h3>
<p>Status: {{ transactionResult.status }}</p> <p>Status: {{ transactionResult.status }}</p>
<p v-if="transactionResult.auth_net_transaction_id">Transaction ID: {{ transactionResult.auth_net_transaction_id }}</p> <p v-if="transactionResult.transaction_id">Transaction ID: {{ transactionResult.transaction_id }}</p>
</div> </div>
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import axios from 'axios'; import { ref } from 'vue'
import axios from 'axios'
import { AuthorizeTransaction } from '../../types/models'
const API_URL = 'http://localhost:8000/api'; // Your FastAPI backend URL const API_URL = 'http://localhost:8000/api'; // Your FastAPI backend URL
export default { // Reactive data
data() { const directCharge = ref({
return {
directCharge: {
cardNumber: '', cardNumber: '',
expirationDate: '', expirationDate: '',
cvv: '', cvv: '',
amount: 0, amount: 0,
}, })
authorization: {
const authorization = ref({
cardNumber: '', cardNumber: '',
expirationDate: '', expirationDate: '',
cvv: '', cvv: '',
amount: 0, amount: 0,
}, })
capture: {
const capture = ref({
amount: 0, amount: 0,
}, })
authorizedTransactionId: null,
transactionResult: null, const authorizedTransactionId = ref<string | null>(null)
customerId: 1 // Assuming a customer with ID 1 exists for this example const transactionResult = ref<AuthorizeTransaction | null>(null)
}; const customerId = ref(1) // Assuming a customer with ID 1 exists for this example
},
methods: { // Functions
async submitDirectCharge() { const submitDirectCharge = async () => {
try { try {
const response = await axios.post(`${API_URL}/charge/?customer_id=${this.customerId}`, { const response = await axios.post(`${API_URL}/charge/?customer_id=${customerId.value}`, {
card_number: this.directCharge.cardNumber, card_number: directCharge.value.cardNumber,
expiration_date: this.directCharge.expirationDate, expiration_date: directCharge.value.expirationDate,
cvv: this.directCharge.cvv, cvv: directCharge.value.cvv,
amount: this.directCharge.amount, amount: directCharge.value.amount,
transaction_type: 'charge' transaction_type: 'charge'
}); });
this.transactionResult = response.data; transactionResult.value = response.data;
} catch (error) { } catch (error) {
console.error(error); console.error(error);
this.transactionResult = { status: 'Error processing transaction' }; transactionResult.value = null;
} }
}, }
async submitAuthorization() {
const submitAuthorization = async () => {
try { try {
const response = await axios.post(`${API_URL}/authorize/?customer_id=${this.customerId}`, { const response = await axios.post(`${API_URL}/authorize/?customer_id=${customerId.value}`, {
card_number: this.authorization.cardNumber, card_number: authorization.value.cardNumber,
expiration_date: this.authorization.expirationDate, expiration_date: authorization.value.expirationDate,
cvv: this.authorization.cvv, cvv: authorization.value.cvv,
amount: this.authorization.amount, amount: authorization.value.amount,
transaction_type: 'auth' transaction_type: 'auth'
}); });
this.transactionResult = response.data; transactionResult.value = response.data;
if (response.data.status === 'authorized') { if (response.data.status === 'authorized') {
this.authorizedTransactionId = response.data.auth_net_transaction_id; authorizedTransactionId.value = response.data.auth_net_transaction_id;
this.capture.amount = this.authorization.amount; // Pre-fill capture amount capture.value.amount = authorization.value.amount; // Pre-fill capture amount
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);
this.transactionResult = { status: 'Error processing authorization' }; transactionResult.value = null;
} }
}, }
async submitCapture() {
const submitCapture = async () => {
try { try {
const response = await axios.post(`${API_URL}/capture/`, { const response = await axios.post(`${API_URL}/capture/`, {
amount: this.capture.amount, amount: capture.value.amount,
auth_net_transaction_id: this.authorizedTransactionId auth_net_transaction_id: authorizedTransactionId.value
}); });
this.transactionResult = response.data; transactionResult.value = response.data;
} catch (error) { } catch (error) {
console.error(error); console.error(error);
this.transactionResult = { status: 'Error processing capture' }; transactionResult.value = null;
} }
}, }
}, </script>
};
</script> -->

View File

@@ -177,48 +177,25 @@
</div> </div>
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, computed, onMounted } from 'vue'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../services/auth.header' import authHeader from '../../services/auth.header'
import { AutoDelivery } from '../../types/models'
import Footer from '../../layouts/footers/footer.vue' import Footer from '../../layouts/footers/footer.vue'
// Define a type for the delivery object for better code quality // Reactive data
interface AutoDelivery { const user = ref(null)
id: number; const deliveries = ref<AutoDelivery[]>([])
customer_id: number; // --- NEW: Data properties for sorting ---
last_fill: string | null; const sortKey = ref<'tank_level_percent' | 'hot_water_summer' | keyof AutoDelivery>('tank_level_percent')
estimated_gallons_left: number; const sortAsc = ref(true)
days_since_last_fill: number;
customer_full_name: string;
customer_address: string;
customer_town: string;
house_factor: number;
tank_size: number;
auto_status: number;
hot_water_summer: number;
open_ticket_id?: number | null;
}
export default defineComponent({ // Computed properties
name: 'AutomaticHome', // --- NEW: Computed property to handle sorting ---
components: { const sortedDeliveries = computed((): AutoDelivery[] => {
Footer,
},
data() {
return {
user: null,
deliveries: [] as AutoDelivery[],
// --- NEW: Data properties for sorting ---
sortKey: 'tank_level_percent' as keyof AutoDelivery | 'tank_level_percent' | 'hot_water_summer',
sortAsc: true,
}
},
computed: {
// --- NEW: Computed property to handle sorting ---
sortedDeliveries(): AutoDelivery[] {
// Create a copy to avoid mutating the original array // Create a copy to avoid mutating the original array
const sorted = [...this.deliveries]; const sorted = [...deliveries.value];
sorted.sort((a, b) => { sorted.sort((a, b) => {
// First, prioritize auto_status = 3 to be at the top // First, prioritize auto_status = 3 to be at the top
@@ -234,14 +211,14 @@ export default defineComponent({
let valB: any; let valB: any;
// Special case for our calculated percentage // Special case for our calculated percentage
if (this.sortKey === 'tank_level_percent') { if (sortKey.value === 'tank_level_percent') {
valA = this.getTankLevelPercentage(a); valA = getTankLevelPercentage(a);
valB = this.getTankLevelPercentage(b); valB = getTankLevelPercentage(b);
} else { } else {
valA = a[this.sortKey as keyof AutoDelivery]; valA = a[sortKey.value as keyof AutoDelivery];
valB = b[this.sortKey as keyof AutoDelivery]; valB = b[sortKey.value as keyof AutoDelivery];
// Special handling for hot_water_summer to ensure it's number // Special handling for hot_water_summer to ensure it's number
if (this.sortKey === 'hot_water_summer') { if (sortKey.value === 'hot_water_summer') {
valA = valA || 0; valA = valA || 0;
valB = valB || 0; valB = valB || 0;
} }
@@ -253,62 +230,65 @@ export default defineComponent({
// Comparison logic // Comparison logic
if (valA < valB) { if (valA < valB) {
return this.sortAsc ? -1 : 1; return sortAsc.value ? -1 : 1;
} }
if (valA > valB) { if (valA > valB) {
return this.sortAsc ? 1 : -1; return sortAsc.value ? 1 : -1;
} }
return 0; return 0;
}); });
return sorted; return sorted;
} })
},
created() { // Lifecycle
this.userStatus(); onMounted(() => {
this.get_oil_orders(); userStatus();
}, get_oil_orders();
methods: { })
// --- NEW: Method to handle sorting ---
sortBy(key: keyof AutoDelivery | 'tank_level_percent' | 'hot_water_summer') { // Functions
if (this.sortKey === key) { // --- NEW: Method to handle sorting ---
const sortBy = (key: keyof AutoDelivery | 'tank_level_percent' | 'hot_water_summer') => {
if (sortKey.value === key) {
// If clicking the same key, reverse the direction // If clicking the same key, reverse the direction
this.sortAsc = !this.sortAsc; sortAsc.value = !sortAsc.value;
} else { } else {
// If clicking a new key, set it and default to ascending // If clicking a new key, set it and default to ascending
this.sortKey = key; sortKey.value = key;
this.sortAsc = true; sortAsc.value = true;
} }
}, }
// --- NEW: Helper method for percentage calculation ---
getTankLevelPercentage(oil: AutoDelivery): number { // --- NEW: Helper method for percentage calculation ---
const getTankLevelPercentage = (oil: AutoDelivery): number => {
if (!oil.tank_size || oil.tank_size === 0 || oil.last_fill === null) { if (!oil.tank_size || oil.tank_size === 0 || oil.last_fill === null) {
return 0; // Return 0 if tank size is invalid or it's a new customer return 0; // Return 0 if tank size is invalid or it's a new customer
} }
return (oil.estimated_gallons_left / oil.tank_size) * 100; return (oil.estimated_gallons_left / oil.tank_size) * 100;
}, }
userStatus() {
const userStatus = () => {
const path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; const path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios.get(path, { withCredentials: true, headers: authHeader() }) axios.get(path, { withCredentials: true, headers: authHeader() })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
.catch(() => { .catch(() => {
this.user = null; user.value = null;
}); });
}, }
get_oil_orders() {
const get_oil_orders = () => {
const path = import.meta.env.VITE_AUTO_URL + '/delivery/all/customers'; const path = import.meta.env.VITE_AUTO_URL + '/delivery/all/customers';
axios.get(path, { withCredentials: true, headers: authHeader() }) axios.get(path, { withCredentials: true, headers: authHeader() })
.then((response: any) => { .then((response: any) => {
this.deliveries = response.data; deliveries.value = response.data;
}) })
.catch((error: any) => { .catch((error: any) => {
console.error("Failed to fetch automatic deliveries:", error); console.error("Failed to fetch automatic deliveries:", error);
}); });
}, }
},
})
</script> </script>

View File

@@ -1,7 +1,7 @@
import AutomaticHome from './home.vue'; const AutomaticHome = () => import('./home.vue');
import AutomaticView from './view.vue'; const AutomaticView = () => import('./view.vue');
const autoRoutes = [ const autoRoutes = [
{ {

View File

@@ -61,16 +61,16 @@
<div class="font-bold text-sm">Payment Status</div> <div class="font-bold text-sm">Payment Status</div>
<div class="badge badge-lg" <div class="badge badge-lg"
:class="{ :class="{
'badge-success': autoTicket.payment_status == 3, 'badge-success': autoTicket.payment_status === PAYMENT_STATUS.PAID,
'badge-info': autoTicket.payment_status == 1, 'badge-info': autoTicket.payment_status === PAYMENT_STATUS.PRE_AUTHORIZED,
'badge-error': autoTicket.payment_status == 0 || autoTicket.payment_status == null, 'badge-error': autoTicket.payment_status === PAYMENT_STATUS.UNPAID || autoTicket.payment_status == null,
'badge-warning': [2, 4].includes(autoTicket.payment_status) 'badge-warning': [PAYMENT_STATUS.PROCESSING, PAYMENT_STATUS.FAILED].includes(autoTicket.payment_status)
}"> }">
<span v-if="autoTicket.payment_status == 0 || autoTicket.payment_status == null">Unpaid</span> <span v-if="autoTicket.payment_status === PAYMENT_STATUS.UNPAID || autoTicket.payment_status == null">Unpaid</span>
<span v-else-if="autoTicket.payment_status == 1">Pre-authorized</span> <span v-else-if="autoTicket.payment_status === PAYMENT_STATUS.PRE_AUTHORIZED">Pre-authorized</span>
<span v-else-if="autoTicket.payment_status == 2">Processing</span> <span v-else-if="autoTicket.payment_status === PAYMENT_STATUS.PROCESSING">Processing</span>
<span v-else-if="autoTicket.payment_status == 3">Paid</span> <span v-else-if="autoTicket.payment_status === PAYMENT_STATUS.PAID">Paid</span>
<span v-else-if="autoTicket.payment_status == 4">Failed</span> <span v-else-if="autoTicket.payment_status === PAYMENT_STATUS.FAILED">Failed</span>
<span v-else>Unknown</span> <span v-else>Unknown</span>
</div> </div>
</div> </div>
@@ -150,8 +150,8 @@
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between">
<span>Status:</span> <span>Status:</span>
<span :class="transaction.status === 0 ? 'text-success' : 'text-error'"> <span :class="transaction.status === TRANSACTION_STATUS.APPROVED ? 'text-success' : 'text-error'">
{{ transaction.status === 0 ? 'Approved' : 'Declined' }} {{ transaction.status === TRANSACTION_STATUS.APPROVED ? 'Approved' : 'Declined' }}
</span> </span>
</div> </div>
</div> </div>
@@ -242,25 +242,21 @@
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../services/auth.header' import authHeader from '../../services/auth.header'
import {
interface UserCard { PAYMENT_STATUS,
id: number; AUTO_STATUS,
last_four: string; TRANSACTION_STATUS,
type_of_card: string; getPaymentStatusLabel,
expiration_month: number; getTransactionStatusLabel
expiration_year: number; } from '../../constants/status';
name_on_card: string;
card_number: string;
security_number: string;
main_card?: boolean;
}
import Header from '../../layouts/headers/headerauth.vue' import Header from '../../layouts/headers/headerauth.vue'
import SideBar from '../../layouts/sidebar/sidebar.vue' import SideBar from '../../layouts/sidebar/sidebar.vue'
import Footer from '../../layouts/footers/footer.vue' import Footer from '../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core"; import useValidate from "@vuelidate/core";
import { notify } from "@kyvg/vue3-notification" import { notify } from "@kyvg/vue3-notification"
import moment from 'moment'; import dayjs from 'dayjs';
import {AutoDelivery, Customer, AuthorizeTransaction, CreditCard} from '../../types/models';
export default defineComponent({ export default defineComponent({
name: 'automaticDeliveryView', name: 'automaticDeliveryView',
@@ -273,62 +269,25 @@ export default defineComponent({
data() { data() {
return { return {
v$: useValidate(), v$: useValidate(),
autoTicket: { autoTicket: {} as any,
id: 0, autoDelivery: {} as AutoDelivery,
customer_id: 0, customer: {} as Customer,
account_number: '', transaction: {} as AuthorizeTransaction,
customer_town: '',
customer_state: 0,
customer_address: '',
customer_zip: '',
customer_full_name: '',
customer_apt: '',
fill_date: '',
oil_prices_id: 0,
gallons_delivered: '',
price_per_gallon: '',
total_amount_customer: '',
payment_type: 0,
payment_card_id: null,
payment_status: null,
open_ticket_id: null,
} as any,
autoDelivery: {
id: 0,
customer_id: 0,
account_number: '',
customer_town: '',
customer_state: 0,
customer_address: '',
customer_zip: '',
customer_full_name: '',
last_fill: '',
days_since_last_fill: 0,
last_updated: '',
estimated_gallons_left: 0,
estimated_gallons_left_prev_day: 0,
tank_height: '',
tank_size: '',
house_factor: 0,
auto_status: 0,
open_ticket_id: null,
},
customer: {
id: 0,
user_id: 0,
customer_first_name: '',
customer_last_name: '',
customer_town: '',
customer_state: 0,
customer_address: '',
customer_zip: '',
customer_apt: '',
customer_home_type: 0,
customer_phone_number: '',
},
transaction: null as any,
userCardfound: false, userCardfound: false,
userCard: {} as UserCard, userCard: {} as CreditCard,
}
},
computed: {
// Expose constants to template
PAYMENT_STATUS() {
return PAYMENT_STATUS;
},
AUTO_STATUS() {
return AUTO_STATUS;
},
TRANSACTION_STATUS() {
return TRANSACTION_STATUS;
} }
}, },
@@ -339,10 +298,11 @@ export default defineComponent({
methods: { methods: {
format_date(value: string) { format_date(value: string) {
if (value) { if (value) {
return moment(String(value)).format('LLLL') return dayjs(String(value)).format('LLLL')
} }
}, },
getTypeColor(transactionType: number) { getTypeColor(transactionType: number | undefined) {
if (transactionType === undefined) return 'text-gray-600';
switch (transactionType) { switch (transactionType) {
case 1: return 'text-blue-600'; // Auth case 1: return 'text-blue-600'; // Auth
case 0: return 'text-orange-600'; // Charge case 0: return 'text-orange-600'; // Charge
@@ -360,7 +320,7 @@ export default defineComponent({
} }
}, },
getCustomer(customerId: number) { getCustomer(customerId: number | undefined) {
if (!customerId) return; if (!customerId) return;
const path = `${import.meta.env.VITE_BASE_URL}/customer/${customerId}`; const path = `${import.meta.env.VITE_BASE_URL}/customer/${customerId}`;
axios.get(path, { withCredentials: true }) axios.get(path, { withCredentials: true })
@@ -384,12 +344,12 @@ export default defineComponent({
this.userCard = response.data; this.userCard = response.data;
this.userCardfound = true; this.userCardfound = true;
} else { } else {
this.userCard = {} as UserCard; this.userCard = {} as CreditCard;
this.userCardfound = false; this.userCardfound = false;
} }
}) })
.catch((_error: any) => { .catch((_error: any) => {
this.userCard = {} as UserCard; this.userCard = {} as CreditCard;
this.userCardfound = false; this.userCardfound = false;
}); });
}, },
@@ -438,7 +398,7 @@ export default defineComponent({
}) })
.catch((error: any) => { .catch((error: any) => {
console.error("No transaction found for delivery:", error); console.error("No transaction found for delivery:", error);
this.transaction = null; this.transaction = {} as AuthorizeTransaction;
}); });
}, },
}, },

View File

@@ -1,8 +1,8 @@
import CardHome from '../card/home.vue'; const CardHome = () => import('../card/home.vue');
import AddCardCreate from '../card/addcard.vue'; const AddCardCreate = () => import('../card/addcard.vue');
import EditCard from "./editcard.vue"; const EditCard = () => import("./editcard.vue");
const cardRoutes = [ const cardRoutes = [

View File

@@ -120,68 +120,60 @@
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../services/auth.header' import authHeader from '../../services/auth.header'
import { ServicePlan, Customer } from '../../types/models'
import Footer from '../../layouts/footers/footer.vue' import Footer from '../../layouts/footers/footer.vue'
import { notify } from "@kyvg/vue3-notification"; import { notify } from "@kyvg/vue3-notification";
interface ServicePlan { const route = useRoute()
id: number; const router = useRouter()
customer_id: number;
contract_plan: number;
contract_years: number;
contract_start_date: string;
}
interface Customer { // Reactive data
id: number; const customerId = ref(route.params.id as string)
customer_first_name: string; const customerName = ref('')
customer_last_name: string; const servicePlan = ref(null as ServicePlan | null)
} const formData = ref({
export default defineComponent({
name: 'ServicePlanEdit',
components: { Footer },
data() {
return {
customerId: this.$route.params.id as string,
customerName: '',
servicePlan: null as ServicePlan | null,
formData: {
contract_plan: 0, contract_plan: 0,
contract_years: 1, contract_years: 1,
contract_start_date: '', contract_start_date: '',
}, })
renewalDate: '', const renewalDate = ref('')
// Computed
const computedEndDate = computed(() => {
return (startDate: string, years: number): string => {
if (!startDate) return 'N/A';
const date = new Date(startDate);
date.setFullYear(date.getFullYear() + years);
return date.toISOString().split('T')[0];
} }
}, })
created() {
this.loadCustomer(); // Functions
this.loadServicePlan(); const loadCustomer = async () => {
},
methods: {
async loadCustomer() {
try { try {
const path = `${import.meta.env.VITE_BASE_URL}/customer/${this.customerId}`; const path = `${import.meta.env.VITE_BASE_URL}/customer/${customerId.value}`;
const response = await axios.get(path, { headers: authHeader() }); const response = await axios.get(path, { headers: authHeader() });
const customer: Customer = response.data; const customer: Customer = response.data;
this.customerName = `${customer.customer_first_name} ${customer.customer_last_name}`; customerName.value = `${customer.customer_first_name} ${customer.customer_last_name}`;
} catch (error) { } catch (error) {
console.error('Failed to load customer:', error); console.error('Failed to load customer:', error);
notify({ title: "Error", text: "Failed to load customer information.", type: "error" }); notify({ title: "Error", text: "Failed to load customer information.", type: "error" });
} }
}, }
async loadServicePlan() { const loadServicePlan = async () => {
try { try {
const path = `${import.meta.env.VITE_BASE_URL}/service/plans/customer/${this.customerId}`; const path = `${import.meta.env.VITE_BASE_URL}/service/plans/customer/${customerId.value}`;
const response = await axios.get(path, { headers: authHeader() }); const response = await axios.get(path, { headers: authHeader() });
if (response.data && response.data.contract_plan !== undefined) { if (response.data && response.data.contract_plan !== undefined) {
this.servicePlan = response.data; servicePlan.value = response.data;
this.formData = { formData.value = {
contract_plan: response.data.contract_plan, contract_plan: response.data.contract_plan,
contract_years: response.data.contract_years, contract_years: response.data.contract_years,
contract_start_date: response.data.contract_start_date, contract_start_date: response.data.contract_start_date,
@@ -191,19 +183,19 @@ export default defineComponent({
// Plan doesn't exist yet, that's okay // Plan doesn't exist yet, that's okay
console.log('No existing service plan found'); console.log('No existing service plan found');
} }
}, }
async onSubmit() { const onSubmit = async () => {
try { try {
const payload = { const payload = {
customer_id: parseInt(this.customerId), customer_id: parseInt(customerId.value),
...this.formData ...formData.value
}; };
let response; let response;
if (this.servicePlan) { if (servicePlan.value) {
// Update existing plan // Update existing plan
const path = `${import.meta.env.VITE_BASE_URL}/service/plans/update/${this.customerId}`; const path = `${import.meta.env.VITE_BASE_URL}/service/plans/update/${customerId.value}`;
response = await axios.put(path, payload, { headers: authHeader() }); response = await axios.put(path, payload, { headers: authHeader() });
} else { } else {
// Create new plan // Create new plan
@@ -214,29 +206,29 @@ export default defineComponent({
if (response.data.ok) { if (response.data.ok) {
notify({ notify({
title: "Success", title: "Success",
text: `Service plan ${this.servicePlan ? 'updated' : 'created'} successfully!`, text: `Service plan ${servicePlan.value ? 'updated' : 'created'} successfully!`,
type: "success" type: "success"
}); });
// Redirect to profile page after successful submission // Redirect to profile page after successful submission
this.$router.push({ name: 'customerProfile', params: { id: this.customerId } }); router.push({ name: 'customerProfile', params: { id: customerId.value } });
} }
} catch (error) { } catch (error) {
console.error('Failed to save service plan:', error); console.error('Failed to save service plan:', error);
notify({ title: "Error", text: "Failed to save service plan.", type: "error" }); notify({ title: "Error", text: "Failed to save service plan.", type: "error" });
} }
}, }
async deletePlan() { const deletePlan = async () => {
if (!confirm('Are you sure you want to delete this service plan?')) return; if (!confirm('Are you sure you want to delete this service plan?')) return;
try { try {
const path = `${import.meta.env.VITE_BASE_URL}/service/plans/delete/${this.customerId}`; const path = `${import.meta.env.VITE_BASE_URL}/service/plans/delete/${customerId.value}`;
const response = await axios.delete(path, { headers: authHeader() }); const response = await axios.delete(path, { headers: authHeader() });
if (response.data.ok) { if (response.data.ok) {
notify({ title: "Success", text: "Service plan deleted successfully!", type: "success" }); notify({ title: "Success", text: "Service plan deleted successfully!", type: "success" });
this.servicePlan = null; servicePlan.value = null;
this.formData = { formData.value = {
contract_plan: 0, contract_plan: 0,
contract_years: 1, contract_years: 1,
contract_start_date: '', contract_start_date: '',
@@ -246,41 +238,41 @@ export default defineComponent({
console.error('Failed to delete service plan:', error); console.error('Failed to delete service plan:', error);
notify({ title: "Error", text: "Failed to delete service plan.", type: "error" }); notify({ title: "Error", text: "Failed to delete service plan.", type: "error" });
} }
}, }
renewContract() { const renewContract = () => {
if (!this.renewalDate) { if (!renewalDate.value) {
notify({ title: "Error", text: "Please select a renewal date.", type: "error" }); notify({ title: "Error", text: "Please select a renewal date.", type: "error" });
return; return;
} }
this.formData.contract_years += 1; formData.value.contract_years += 1;
this.formData.contract_start_date = this.renewalDate; formData.value.contract_start_date = renewalDate.value;
this.renewalDate = ''; renewalDate.value = '';
notify({ notify({
title: "Success", title: "Success",
text: "Contract renewed! Years increased by 1 and start date updated.", text: "Contract renewed! Years increased by 1 and start date updated.",
type: "success" type: "success"
}); });
}, }
getPlanName(planType: number): string { const getPlanName = (planType: number): string => {
const planNames: { [key: number]: string } = { const planNames: { [key: number]: string } = {
1: 'Standard Plan', 1: 'Standard Plan',
2: 'Premium Plan' 2: 'Premium Plan'
}; };
return planNames[planType] || 'No Plan'; return planNames[planType] || 'No Plan';
}, }
formatEndDate(startDate: string, years: number): string { const formatEndDate = (startDate: string, years: number): string => {
if (!startDate) return 'N/A'; if (!startDate) return 'N/A';
const date = new Date(startDate); const date = new Date(startDate);
date.setFullYear(date.getFullYear() + years); date.setFullYear(date.getFullYear() + years);
return date.toISOString().split('T')[0]; return date.toISOString().split('T')[0];
}, }
getStatusText(startDate: string, years: number): string { const getStatusText = (startDate: string, years: number): string => {
if (!startDate) return 'Unknown'; if (!startDate) return 'Unknown';
const endDate = new Date(startDate); const endDate = new Date(startDate);
endDate.setFullYear(endDate.getFullYear() + years); endDate.setFullYear(endDate.getFullYear() + years);
@@ -292,9 +284,9 @@ export default defineComponent({
} else { } else {
return 'Active'; return 'Active';
} }
}, }
getStatusBadge(startDate: string, years: number): string { const getStatusBadge = (startDate: string, years: number): string => {
if (!startDate) return 'badge-ghost'; if (!startDate) return 'badge-ghost';
const endDate = new Date(startDate); const endDate = new Date(startDate);
endDate.setFullYear(endDate.getFullYear() + years); endDate.setFullYear(endDate.getFullYear() + years);
@@ -306,7 +298,11 @@ export default defineComponent({
} else { } else {
return 'badge-success'; return 'badge-success';
} }
} }
},
// Lifecycle
onMounted(() => {
loadCustomer();
loadServicePlan();
}) })
</script> </script>

View File

@@ -140,33 +140,25 @@
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../services/auth.header' import authHeader from '../../services/auth.header'
import { StateOption, HomeTypeOption } from '../../types/models'
import Footer from '../../layouts/footers/footer.vue' import Footer from '../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core"; import useValidate from "@vuelidate/core";
import { email, minLength, required } from "@vuelidate/validators"; import { email, minLength, required } from "@vuelidate/validators";
import { notify } from "@kyvg/vue3-notification"; import { notify } from "@kyvg/vue3-notification";
interface SelectOption { // Reactive data
text: string; const router = useRouter()
value: number; const user = ref(null)
} const stateList = ref<StateOption[]>([])
const custList = ref<HomeTypeOption[]>([])
export default defineComponent({ // Form object
name: 'CustomerCreate', const CreateCustomerForm = ref({
components: {
Footer,
},
data() {
return {
v$: useValidate(),
user: null,
stateList: [] as SelectOption[],
custList: [] as SelectOption[],
// --- REFACTORED: Simplified, flat form object ---
CreateCustomerForm: {
customer_last_name: "", customer_last_name: "",
customer_first_name: "", customer_first_name: "",
customer_town: "", customer_town: "",
@@ -176,84 +168,86 @@ export default defineComponent({
customer_email: "", customer_email: "",
customer_phone_number: "", customer_phone_number: "",
customer_description: "", customer_description: "",
// --- FIX: Initialized as numbers for proper v-model binding ---
customer_home_type: 0, customer_home_type: 0,
customer_state: 0, customer_state: 0,
}, })
}
}, // Validation rules
validations() { const rules = {
return {
// --- REFACTORED: Validation rules point to the flat form object ---
CreateCustomerForm: { CreateCustomerForm: {
customer_last_name: { required, minLength: minLength(1) }, customer_last_name: { required, minLength: minLength(1) },
customer_first_name: { required, minLength: minLength(1) }, customer_first_name: { required, minLength: minLength(1) },
customer_town: { required, minLength: minLength(1) }, customer_town: { required, minLength: minLength(1) },
customer_zip: { required, minLength: minLength(5) }, customer_zip: { required, minLength: minLength(5) },
customer_email: { email }, // Optional, so only validate format customer_email: { email },
customer_phone_number: { required }, customer_phone_number: { required },
customer_home_type: { required }, customer_home_type: { required },
customer_state: { required }, customer_state: { required },
customer_address: { required }, customer_address: { required },
}, },
}; }
},
created() { // Vuelidate instance
this.userStatus(); const v$ = useValidate(rules, { CreateCustomerForm })
},
mounted() { // Functions
this.getCustomerTypeList(); const acceptNumber = () => {
this.getStatesList(); const x = CreateCustomerForm.value.customer_phone_number.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
},
methods: {
acceptNumber() {
const x = this.CreateCustomerForm.customer_phone_number.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
if (x) { if (x) {
this.CreateCustomerForm.customer_phone_number = !x[2] ? x[1] : `(${x[1]}) ${x[2]}${x[3] ? `-${x[3]}` : ''}`; CreateCustomerForm.value.customer_phone_number = !x[2] ? x[1] : `(${x[1]}) ${x[2]}${x[3] ? `-${x[3]}` : ''}`;
} }
}, }
userStatus() {
const userStatus = () => {
const path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; const path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios.get(path, { withCredentials: true, headers: authHeader() }) axios.get(path, { withCredentials: true, headers: authHeader() })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { this.user = response.data.user; } if (response.data.ok) { user.value = response.data.user; }
}) })
.catch(() => { this.user = null; }); .catch(() => { user.value = null; });
}, }
getCustomerTypeList() { const getCustomerTypeList = () => {
const path = import.meta.env.VITE_BASE_URL + "/query/customertype"; const path = import.meta.env.VITE_BASE_URL + "/query/customertype";
axios.get(path, { withCredentials: true }) axios.get(path, { withCredentials: true })
.then((response: any) => { this.custList = response.data; }); .then((response: any) => { custList.value = response.data; });
}, }
getStatesList() {
const getStatesList = () => {
const path = import.meta.env.VITE_BASE_URL + "/query/states"; const path = import.meta.env.VITE_BASE_URL + "/query/states";
axios.get(path, { withCredentials: true }) axios.get(path, { withCredentials: true })
.then((response: any) => { this.stateList = response.data; }); .then((response: any) => { stateList.value = response.data; });
}, }
CreateCustomer(payload: any) {
const CreateCustomer = (payload: any) => {
const path = import.meta.env.VITE_BASE_URL + "/customer/create"; const path = import.meta.env.VITE_BASE_URL + "/customer/create";
axios.post(path, payload, { withCredentials: true, headers: authHeader() }) axios.post(path, payload, { withCredentials: true, headers: authHeader() })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
const new_user_id = response.data.user.user_id; const new_user_id = response.data.user.user_id;
this.$router.push({ name: 'customerProfile', params: { id: new_user_id } }); router.push({ name: 'customerProfile', params: { id: new_user_id } });
} else { } else {
notify({ title: "Error", text: response.data.error || "Failed to create customer.", type: "error" }); notify({ title: "Error", text: response.data.error || "Failed to create customer.", type: "error" });
} }
}); });
}, }
onSubmit() {
this.v$.$validate(); // Trigger validation const onSubmit = () => {
if (!this.v$.$error) { v$.value.$validate(); // Trigger validation
if (!v$.value.$error) {
// If validation passes, submit the form // If validation passes, submit the form
this.CreateCustomer(this.CreateCustomerForm); CreateCustomer(CreateCustomerForm.value);
} else { } else {
// If validation fails, show a single notification // If validation fails, show a single notification
notify({ title: "Validation Error", text: "Please fill out all required fields correctly.", type: "error" }); notify({ title: "Validation Error", text: "Please fill out all required fields correctly.", type: "error" });
console.log("Form validation failed."); console.log("Form validation failed.");
} }
}, }
},
}); // Lifecycle
onMounted(() => {
userStatus();
getCustomerTypeList();
getStatesList();
})
</script> </script>

View File

@@ -162,36 +162,23 @@
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../services/auth.header' import authHeader from '../../services/auth.header'
import { StateOption, HomeTypeOption } from '../../types/models'
import Footer from '../../layouts/footers/footer.vue' import Footer from '../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core"; import useValidate from "@vuelidate/core";
import { email, minLength, required } from "@vuelidate/validators"; import { email, minLength, required } from "@vuelidate/validators";
// --- NEW: Interface for select options for better type safety --- // Reactive data
interface SelectOption { const route = useRoute()
text: string; const router = useRouter()
value: number; const user = ref(null)
} const stateList = ref<StateOption[]>([])
const custList = ref<HomeTypeOption[]>([])
export default defineComponent({ const customer = ref({
name: 'CustomerEdit',
components: {
// Removed unused Header and SideBar
Footer,
},
data() {
return {
v$: useValidate(),
user: null,
stateList: [] as SelectOption[],
custList: [] as SelectOption[],
customer: {
id: 0, id: 0,
user_id: 0, user_id: 0,
customer_first_name: '', customer_first_name: '',
@@ -204,28 +191,26 @@ export default defineComponent({
customer_home_type: 0, customer_home_type: 0,
customer_phone_number: '', customer_phone_number: '',
account_number: '', account_number: '',
}, })
customerDescription: { const customerDescription = ref({
id: 0, id: 0,
customer_id: 0, customer_id: 0,
account_number: '', account_number: '',
company_id: '', company_id: '',
fill_location: 0, fill_location: 0,
description: '', description: '',
}, })
CreateCustomerForm: { const CreateCustomerForm = ref({
basicInfo: { basicInfo: {
customer_last_name: "", customer_last_name: "",
customer_first_name: "", customer_first_name: "",
customer_town: "", customer_town: "",
customer_apt: "", customer_apt: "",
// --- FIX: Initialized as a number ---
customer_home_type: 0, customer_home_type: 0,
customer_zip: "", customer_zip: "",
customer_automatic: false, customer_automatic: false,
customer_email: "", customer_email: "",
customer_phone_number: "", customer_phone_number: "",
// --- FIX: Initialized as a number ---
customer_state: 0, customer_state: 0,
customer_address: "", customer_address: "",
customer_description: "", customer_description: "",
@@ -236,12 +221,11 @@ export default defineComponent({
contract_years: 1, contract_years: 1,
contract_start_date: "", contract_start_date: "",
}, },
}, })
renewalDate: "", const renewalDate = ref("")
}
}, // Validation rules
validations() { const rules = {
return {
CreateCustomerForm: { CreateCustomerForm: {
basicInfo: { basicInfo: {
customer_last_name: { required, minLength: minLength(1) }, customer_last_name: { required, minLength: minLength(1) },
@@ -249,32 +233,28 @@ export default defineComponent({
customer_town: { required, minLength: minLength(1) }, customer_town: { required, minLength: minLength(1) },
customer_home_type: { required }, customer_home_type: { required },
customer_zip: { required, minLength: minLength(5) }, customer_zip: { required, minLength: minLength(5) },
customer_email: { email }, // Removed required to match template label "Optional" customer_email: { email },
customer_phone_number: { required }, customer_phone_number: { required },
customer_state: { required }, customer_state: { required },
customer_address: { required }, customer_address: { required },
}, },
}, },
}; }
},
created() { // Vuelidate instance
this.userStatus() const v$ = useValidate(rules, { CreateCustomerForm })
this.getCustomer(this.$route.params.id)
}, // Functions
mounted() { const acceptNumber = () => {
this.getCustomerTypeList(); let x = CreateCustomerForm.value.basicInfo.customer_phone_number.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
this.getStatesList();
},
methods: {
acceptNumber() {
let x = this.CreateCustomerForm.basicInfo.customer_phone_number.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
if (x) { if (x) {
this.CreateCustomerForm.basicInfo.customer_phone_number = !x[2] ? x[1] : '(' + x[1] + ') ' + x[2] + (x[3] ? '-' + x[3] : ''); CreateCustomerForm.value.basicInfo.customer_phone_number = !x[2] ? x[1] : '(' + x[1] + ') ' + x[2] + (x[3] ? '-' + x[3] : '');
} else { } else {
this.CreateCustomerForm.basicInfo.customer_phone_number = ''; CreateCustomerForm.value.basicInfo.customer_phone_number = '';
} }
}, }
userStatus() {
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -284,26 +264,28 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
.catch(() => { .catch(() => {
this.user = null user.value = null
}) })
}, }
getCustomerDescription(userid: any) {
const getCustomerDescription = (userid: any) => {
let path = import.meta.env.VITE_BASE_URL + '/customer/description/' + userid; let path = import.meta.env.VITE_BASE_URL + '/customer/description/' + userid;
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: any) => {
this.customerDescription = response.data customerDescription.value = response.data
this.CreateCustomerForm.basicInfo.customer_description = this.customerDescription.description; CreateCustomerForm.value.basicInfo.customer_description = customerDescription.value.description;
this.CreateCustomerForm.basicInfo.customer_fill_location = this.customerDescription.fill_location CreateCustomerForm.value.basicInfo.customer_fill_location = customerDescription.value.fill_location
}) })
}, }
getCustomer(userid: any) {
const getCustomer = (userid: any) => {
let path = import.meta.env.VITE_BASE_URL + "/customer/" + userid; let path = import.meta.env.VITE_BASE_URL + "/customer/" + userid;
axios({ axios({
method: "get", method: "get",
@@ -313,30 +295,31 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data) { if (response.data) {
this.customer = response.data; customer.value = response.data;
this.getCustomerDescription(this.customer.id); getCustomerDescription(customer.value.id);
this.CreateCustomerForm.basicInfo.customer_last_name = response.data.customer_last_name; CreateCustomerForm.value.basicInfo.customer_last_name = response.data.customer_last_name;
this.CreateCustomerForm.basicInfo.customer_first_name = response.data.customer_first_name; CreateCustomerForm.value.basicInfo.customer_first_name = response.data.customer_first_name;
this.CreateCustomerForm.basicInfo.customer_town = response.data.customer_town; CreateCustomerForm.value.basicInfo.customer_town = response.data.customer_town;
this.CreateCustomerForm.basicInfo.customer_state = response.data.customer_state; CreateCustomerForm.value.basicInfo.customer_state = response.data.customer_state;
this.CreateCustomerForm.basicInfo.customer_zip = response.data.customer_zip; CreateCustomerForm.value.basicInfo.customer_zip = response.data.customer_zip;
this.CreateCustomerForm.basicInfo.customer_phone_number = response.data.customer_phone_number; CreateCustomerForm.value.basicInfo.customer_phone_number = response.data.customer_phone_number;
this.CreateCustomerForm.basicInfo.customer_home_type = response.data.customer_home_type; CreateCustomerForm.value.basicInfo.customer_home_type = response.data.customer_home_type;
this.CreateCustomerForm.basicInfo.customer_apt = response.data.customer_apt; CreateCustomerForm.value.basicInfo.customer_apt = response.data.customer_apt;
this.CreateCustomerForm.basicInfo.customer_email = response.data.customer_email; CreateCustomerForm.value.basicInfo.customer_email = response.data.customer_email;
this.CreateCustomerForm.basicInfo.customer_address = response.data.customer_address; CreateCustomerForm.value.basicInfo.customer_address = response.data.customer_address;
if (response.data.customer_automatic === 1) { if (response.data.customer_automatic === 1) {
this.CreateCustomerForm.basicInfo.customer_automatic = true CreateCustomerForm.value.basicInfo.customer_automatic = true
} }
if (response.data.customer_automatic === 0) { if (response.data.customer_automatic === 0) {
this.CreateCustomerForm.basicInfo.customer_automatic = false CreateCustomerForm.value.basicInfo.customer_automatic = false
} }
} }
}) })
}, }
editItem(payload: any) { // Simplified payload type for brevity
let path = import.meta.env.VITE_BASE_URL + "/customer/edit/" + this.customer.id; const editItem = (payload: any) => {
let path = import.meta.env.VITE_BASE_URL + "/customer/edit/" + customer.value.id;
axios({ axios({
method: "put", method: "put",
url: path, url: path,
@@ -346,51 +329,60 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.$router.push({ name: "customerProfile", params: { id: this.customer.id }, query: { success: 'true' } }); router.push({ name: "customerProfile", params: { id: customer.value.id }, query: { success: 'true' } });
} else if (response.data.error) { } else if (response.data.error) {
// Handle specific errors if needed router.push("/");
this.$router.push("/");
} }
}) })
}, }
onSubmit() {
// Create payload with both basic info and service plan data const onSubmit = () => {
const payload = { const payload = {
...this.CreateCustomerForm.basicInfo, ...CreateCustomerForm.value.basicInfo,
service_plan: this.CreateCustomerForm.servicePlan service_plan: CreateCustomerForm.value.servicePlan
}; };
this.editItem(payload); editItem(payload);
}, }
getCustomerTypeList() {
const getCustomerTypeList = () => {
let path = import.meta.env.VITE_BASE_URL + "/query/customertype"; let path = import.meta.env.VITE_BASE_URL + "/query/customertype";
axios.get(path, { withCredentials: true }) axios.get(path, { withCredentials: true })
.then((response: any) => { .then((response: any) => {
this.custList = response.data; custList.value = response.data;
}); });
}, }
getStatesList() {
const getStatesList = () => {
let path = import.meta.env.VITE_BASE_URL + "/query/states"; let path = import.meta.env.VITE_BASE_URL + "/query/states";
axios.get(path, { withCredentials: true }) axios.get(path, { withCredentials: true })
.then((response: any) => { .then((response: any) => {
this.stateList = response.data; stateList.value = response.data;
}); });
}, }
formatEndDate(startDate: string, years: number): string {
const formatEndDate = (startDate: string, years: number): string => {
if (!startDate) return 'N/A'; if (!startDate) return 'N/A';
const date = new Date(startDate); const date = new Date(startDate);
date.setFullYear(date.getFullYear() + years); date.setFullYear(date.getFullYear() + years);
return date.toISOString().split('T')[0]; return date.toISOString().split('T')[0];
}, }
renewContract() {
if (!this.renewalDate) { const renewContract = () => {
if (!renewalDate.value) {
alert('Please select a renewal date'); alert('Please select a renewal date');
return; return;
} }
this.CreateCustomerForm.servicePlan.contract_years += 1; CreateCustomerForm.value.servicePlan.contract_years += 1;
this.CreateCustomerForm.servicePlan.contract_start_date = this.renewalDate; CreateCustomerForm.value.servicePlan.contract_start_date = renewalDate.value;
this.renewalDate = ''; renewalDate.value = '';
alert('Contract renewed! Years increased by 1 and start date updated.'); alert('Contract renewed! Years increased by 1 and start date updated.');
}, }
},
// Lifecycle
onMounted(() => {
userStatus()
getCustomer(route.params.id)
getCustomerTypeList();
getStatesList();
}) })
</script> </script>

View File

@@ -105,58 +105,39 @@
</div> </div>
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted } from 'vue'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../services/auth.header' import authHeader from '../../services/auth.header'
import { customerService } from '../../services/customerService'
import { Customer } from '../../types/models'
import Header from '../../layouts/headers/headerauth.vue' import Header from '../../layouts/headers/headerauth.vue'
import PaginationComp from '../../components/pagination.vue' import PaginationComp from '../../components/pagination.vue'
import SideBar from '../../layouts/sidebar/sidebar.vue' import SideBar from '../../layouts/sidebar/sidebar.vue'
import Footer from '../../layouts/footers/footer.vue' import Footer from '../../layouts/footers/footer.vue'
export default defineComponent({ // Reactive data
name: 'CustomerHome', const token = ref(null)
const user = ref(null)
components: { const customers = ref<Customer[]>([])
Header, const customer_count = ref(0)
SideBar, const page = ref(1)
Footer, const perPage = ref(50)
}, const recordsLength = ref(0)
const options = ref({
data() {
return {
token: null,
user: null,
customers: [] as any[],
customer_count: 0,
page: 1,
perPage: 50,
recordsLength: 0,
options: {
edgeNavigation: false, edgeNavigation: false,
format: false, format: false,
template: PaginationComp template: PaginationComp
} })
}
},
created() { // Functions
this.userStatus() const getPage = (pageVal: any) => {
}, customers.value = [];
mounted() { get_customers(pageVal)
this.getPage(this.page) get_customer_count()
}
}, const userStatus = () => {
methods: {
getPage: function (page: any) {
// we simulate an api call that fetch the records from a backend
this.customers = [];
this.get_customers(page)
this.get_customer_count()
},
userStatus() {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -166,46 +147,50 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
.catch(() => { .catch(() => {
this.user = null user.value = null
}) })
}, }
get_customers(page: any) {
let path = import.meta.env.VITE_BASE_URL + '/customer/all/' + page; const get_customers = async (pageVal: number) => {
axios({ try {
method: 'get', const response = await customerService.getAll(pageVal)
url: path, customers.value = response.data || []
headers: authHeader(), } catch (error) {
}).then((response: any) => { console.error('Error fetching customers:', error)
this.customers = response.data customers.value = []
}) }
}, }
get_customer_count() {
let path = import.meta.env.VITE_BASE_URL + '/customer/count'; const get_customer_count = async () => {
axios({ try {
method: 'get', const response = await customerService.getCount()
url: path, if (response.data) {
headers: authHeader(), customer_count.value = response.data.count
}).then((response: any) => { }
this.customer_count = response.data.count } catch (error) {
}) console.error('Error fetching customer count:', error)
}, }
deleteCustomer(user_id: any) { }
const deleteCustomer = (user_id: any) => {
let path = import.meta.env.VITE_BASE_URL + '/customer/delete/' + user_id; let path = import.meta.env.VITE_BASE_URL + '/customer/delete/' + user_id;
axios({ axios({
method: 'delete', method: 'delete',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then(() => { }).then(() => {
get_customers(1)
this.get_customers(1)
}) })
}, }
},
// Lifecycle
onMounted(() => {
userStatus()
getPage(page.value)
}) })
</script> </script>

View File

@@ -16,11 +16,11 @@
<tbody> <tbody>
<tr v-for="customer in customers" :key="customer.account_number"> <tr v-for="customer in customers" :key="customer.account_number">
<td>{{ customer.account_number }}</td> <td>{{ customer.account_number }}</td>
<td>{{ customer.first_name }}</td> <td>{{ customer.customer_first_name }}</td>
<td>{{ customer.last_name }}</td> <td>{{ customer.customer_last_name }}</td>
<td>{{ customer.address }}</td> <td>{{ customer.customer_address }}</td>
<td>{{ customer.town }}</td> <td>{{ customer.customer_town }}</td>
<td>{{ customer.phone_number }}</td> <td>{{ customer.customer_phone_number }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -28,32 +28,17 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue'; import { ref, onMounted } from 'vue'
import axios from 'axios'; import axios from 'axios'
import authHeader from '../../services/auth.header'; import authHeader from '../../services/auth.header'
import { Customer } from '../../types/models'
interface Customer { // Reactive data
account_number: string; const customers = ref<Customer[]>([])
first_name: string;
last_name: string;
address: string;
town: string;
phone_number: string;
}
export default defineComponent({ // Functions
name: 'CustomerList', const fetchCustomers = () => {
data() {
return {
customers: [] as Customer[]
};
},
created() {
this.fetchCustomers();
},
methods: {
fetchCustomers() {
let path = import.meta.env.VITE_BASE_URL + '/report/customers/list'; let path = import.meta.env.VITE_BASE_URL + '/report/customers/list';
axios({ axios({
method: 'get', method: 'get',
@@ -63,15 +48,18 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.customers = response.data.customers; customers.value = response.data.customers;
} }
}) })
.catch((error: unknown) => { .catch((error: unknown) => {
console.error('Error fetching customer data:', error); console.error('Error fetching customer data:', error);
}); });
} }
}
}); // Lifecycle
onMounted(() => {
fetchCustomers();
})
</script> </script>
<style scoped> <style scoped>

View File

@@ -76,8 +76,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted, watch } from 'vue'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../../services/auth.header' import authHeader from '../../../services/auth.header'
import dayjs from 'dayjs' import dayjs from 'dayjs'
@@ -101,46 +101,40 @@ interface FuelEstimation {
last_fill?: string | null; last_fill?: string | null;
} }
export default defineComponent({ // Props
name: 'TankEstimation', const props = defineProps<{
props: { customerId: number
customerId: { }>()
type: Number,
required: true // Reactive data
} const estimation = ref<FuelEstimation | null>(null)
}, const loading = ref(true)
data() { const error = ref<string | null>(null)
return {
estimation: null as FuelEstimation | null, // Lifecycle
loading: true, onMounted(() => {
error: null as string | null fetchEstimation()
} })
},
mounted() { // Watchers
this.fetchEstimation() watch(() => props.customerId, (newId, oldId) => {
},
watch: {
customerId: {
handler(newId, oldId) {
if (newId !== oldId) { if (newId !== oldId) {
console.log('Customer ID changed from', oldId, 'to', newId) console.log('Customer ID changed from', oldId, 'to', newId)
this.fetchEstimation() fetchEstimation()
} }
}, })
immediate: false
} // Functions
}, const fetchEstimation = async () => {
methods: { loading.value = true
async fetchEstimation() { error.value = null
this.loading = true estimation.value = null // Clear previous data
this.error = null
this.estimation = null // Clear previous data
try { try {
console.log('Fetching estimation for customer ID:', this.customerId) console.log('Fetching estimation for customer ID:', props.customerId)
// First check if customer is automatic // First check if customer is automatic
const customerPath = `${import.meta.env.VITE_BASE_URL}/customer/${this.customerId}` const customerPath = `${import.meta.env.VITE_BASE_URL}/customer/${props.customerId}`
console.log('Checking customer type:', customerPath) console.log('Checking customer type:', customerPath)
const customerResponse = await axios.get(customerPath, { headers: authHeader() }) const customerResponse = await axios.get(customerPath, { headers: authHeader() })
const isAutomatic = customerResponse.data.customer_automatic === 1 const isAutomatic = customerResponse.data.customer_automatic === 1
@@ -150,11 +144,11 @@ export default defineComponent({
let path: string let path: string
if (isAutomatic) { if (isAutomatic) {
// Fetch from automatic delivery API // Fetch from automatic delivery API
path = `${import.meta.env.VITE_AUTO_URL}/delivery/auto/customer/${this.customerId}` path = `${import.meta.env.VITE_AUTO_URL}/delivery/auto/customer/${props.customerId}`
console.log('Fetching automatic data from:', path) console.log('Fetching automatic data from:', path)
} else { } else {
// Fetch from customer estimation API // Fetch from customer estimation API
path = `${import.meta.env.VITE_AUTO_URL}/fixstuff_customer/estimate_gallons/customer/${this.customerId}` path = `${import.meta.env.VITE_AUTO_URL}/fixstuff_customer/estimate_gallons/customer/${props.customerId}`
console.log('Fetching customer data from:', path) console.log('Fetching customer data from:', path)
} }
@@ -162,7 +156,7 @@ export default defineComponent({
console.log('API Response:', response.data) console.log('API Response:', response.data)
if (response.data.error) { if (response.data.error) {
this.error = response.data.error error.value = response.data.error
console.error('API returned error:', response.data.error) console.error('API returned error:', response.data.error)
} else { } else {
if (isAutomatic) { if (isAutomatic) {
@@ -170,7 +164,7 @@ export default defineComponent({
if (response.data && response.data.id) { if (response.data && response.data.id) {
const autoData = response.data const autoData = response.data
console.log('Processing automatic data:', autoData) console.log('Processing automatic data:', autoData)
this.estimation = { estimation.value = {
id: autoData.id, id: autoData.id,
customer_id: autoData.customer_id, customer_id: autoData.customer_id,
total_deliveries: 0, // Not available in auto data total_deliveries: 0, // Not available in auto data
@@ -183,55 +177,55 @@ export default defineComponent({
last_5_deliveries: [], last_5_deliveries: [],
last_fill: autoData.last_fill last_fill: autoData.last_fill
} }
console.log('Set automatic estimation:', this.estimation) console.log('Set automatic estimation:', estimation.value)
} else { } else {
console.warn('No automatic delivery data found for customer', this.customerId) console.warn('No automatic delivery data found for customer', props.customerId)
this.error = 'No automatic delivery data available' error.value = 'No automatic delivery data available'
} }
} else { } else {
console.log('Setting customer estimation:', response.data) console.log('Setting customer estimation:', response.data)
this.estimation = response.data estimation.value = response.data
} }
} }
} catch (error: any) { } catch (error: any) {
console.error('Failed to fetch fuel estimation:', error) console.error('Failed to fetch fuel estimation:', error)
if (error.response?.status === 404) { if (error.response?.status === 404) {
this.error = 'Customer data not found' error.value = 'Customer data not found'
} else if (error.response?.data?.error) { } else if (error.response?.data?.error) {
this.error = error.response.data.error error.value = error.response.data.error
} else { } else {
this.error = 'Failed to load fuel estimation data' error.value = 'Failed to load fuel estimation data'
} }
} finally { } finally {
this.loading = false loading.value = false
} }
}, }
getTankLevelPercentage(): number { const getTankLevelPercentage = (): number => {
if (!this.estimation || !this.estimation.tank_size || this.estimation.tank_size === 0) { if (!estimation.value || !estimation.value.tank_size || estimation.value.tank_size === 0) {
return 0 return 0
} }
return (this.estimation.estimated_gallons / this.estimation.tank_size) * 100 return (estimation.value.estimated_gallons / estimation.value.tank_size) * 100
}, }
calculateDailyUsage(): string { const calculateDailyUsage = (): string => {
if (!this.estimation || !this.estimation.scaling_factor) { if (!estimation.value || !estimation.value.scaling_factor) {
return 'N/A' return 'N/A'
} }
// For a typical day with ~20 degree days (moderate winter day) // For a typical day with ~20 degree days (moderate winter day)
const typicalDegreeDays = 20 const typicalDegreeDays = 20
const dailyUsage = this.estimation.scaling_factor * typicalDegreeDays const dailyUsage = estimation.value.scaling_factor * typicalDegreeDays
return dailyUsage.toFixed(1) return dailyUsage.toFixed(1)
}, }
formatScalingFactor(scalingFactor: number | null): string { const formatScalingFactor = (scalingFactor: number | null): string => {
if (scalingFactor === null || scalingFactor === undefined) { if (scalingFactor === null || scalingFactor === undefined) {
return 'N/A' return 'N/A'
} }
return scalingFactor.toFixed(2) return scalingFactor.toFixed(2)
}, }
getScalingFactorCategory(scalingFactor: number | null): string { const getScalingFactorCategory = (scalingFactor: number | null): string => {
if (scalingFactor === null || scalingFactor === undefined) { if (scalingFactor === null || scalingFactor === undefined) {
return 'N/A' return 'N/A'
} }
@@ -247,12 +241,10 @@ export default defineComponent({
} else { } else {
return 'Very High' return 'Very High'
} }
}, }
formatDate(dateString: string): string { const formatDate = (dateString: string): string => {
if (!dateString) return 'N/A' if (!dateString) return 'N/A'
return dayjs(dateString).format('MMM D, YYYY') return dayjs(dateString).format('MMM D, YYYY')
} }
}
})
</script> </script>

File diff suppressed because it is too large Load Diff

View File

@@ -33,6 +33,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { AuthorizeTransaction } from '../../../../types/models';
import DeliveriesTable from './DeliveriesTable.vue'; import DeliveriesTable from './DeliveriesTable.vue';
import ServiceCallsTable from './ServiceCallsTable.vue'; import ServiceCallsTable from './ServiceCallsTable.vue';
import TransactionsTable from './TransactionsTable.vue'; import TransactionsTable from './TransactionsTable.vue';
@@ -69,26 +70,12 @@ interface ServiceCall {
description: string; description: string;
} }
interface Transaction {
id: number;
preauthorize_amount: number | null;
charge_amount: number | null;
transaction_type: number;
status: number;
created_at: string;
auth_net_transaction_id: string | null;
rejection_reason: string | null;
delivery_id: number | null;
service_id: number | null;
auto_id: number | null;
}
// 2. Define the Props interface // 2. Define the Props interface
interface Props { interface Props {
deliveries: Delivery[]; deliveries: Delivery[];
autodeliveries: AutomaticDelivery[]; autodeliveries: AutomaticDelivery[];
serviceCalls: ServiceCall[]; serviceCalls: ServiceCall[];
transactions: Transaction[]; transactions: AuthorizeTransaction[];
} }
// 3. Use the typed defineProps // 3. Use the typed defineProps

View File

@@ -30,7 +30,7 @@
<div><strong>Pre-Auth:</strong> ${{ transaction.preauthorize_amount || '0.00' }}</div> <div><strong>Pre-Auth:</strong> ${{ transaction.preauthorize_amount || '0.00' }}</div>
<div><strong>Charge:</strong> ${{ transaction.charge_amount || '0.00' }}</div> <div><strong>Charge:</strong> ${{ transaction.charge_amount || '0.00' }}</div>
</div> </div>
<div><strong>Type:</strong> <span :class="getTypeColor(transaction.transaction_type)">{{ transaction.transaction_type === 0 ? 'Charge' : transaction.transaction_type === 1 ? 'Auth' : 'Capture' }}</span></div> <div><strong>Type:</strong> <span :class="getTypeColor(transaction.transaction_type)">{{ transaction.transaction_type === 0 ? 'Charge' : transaction.transaction_type === 1 ? 'Auth' : transaction.transaction_type === 2 ? 'Capture' : 'Unknown' }}</span></div>
</div> </div>
</td> </td>
<td> <td>
@@ -78,7 +78,7 @@
<p><strong>Transaction ID:</strong> {{ transaction.auth_net_transaction_id || 'N/A' }}</p> <p><strong>Transaction ID:</strong> {{ transaction.auth_net_transaction_id || 'N/A' }}</p>
<p><strong>Pre-Auth:</strong> ${{ transaction.preauthorize_amount || '0.00' }}</p> <p><strong>Pre-Auth:</strong> ${{ transaction.preauthorize_amount || '0.00' }}</p>
<p><strong>Charge:</strong> ${{ transaction.charge_amount || '0.00' }}</p> <p><strong>Charge:</strong> ${{ transaction.charge_amount || '0.00' }}</p>
<p><strong>Type:</strong> <span :class="getTypeColor(transaction.transaction_type)">{{ transaction.transaction_type === 0 ? 'Charge' : transaction.transaction_type === 1 ? 'Auth' : 'Capture' }}</span></p> <p><strong>Type:</strong> <span :class="getTypeColor(transaction.transaction_type)">{{ transaction.transaction_type === 0 ? 'Charge' : transaction.transaction_type === 1 ? 'Auth' : transaction.transaction_type === 2 ? 'Capture' : 'Unknown' }}</span></p>
<p><strong>Source:</strong> {{ getSourceText(transaction) }}</p> <p><strong>Source:</strong> {{ getSourceText(transaction) }}</p>
<!-- Rejection Reason in Mobile View --> <!-- Rejection Reason in Mobile View -->
<div v-if="transaction.rejection_reason && transaction.rejection_reason.trim()" class="bg-transparent border border-gray-300 rounded-md p-3 mt-2"> <div v-if="transaction.rejection_reason && transaction.rejection_reason.trim()" class="bg-transparent border border-gray-300 rounded-md p-3 mt-2">
@@ -103,29 +103,17 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
interface Transaction { import { AuthorizeTransaction } from '../../../../types/models';
id: number;
preauthorize_amount: number | null;
charge_amount: number | null;
transaction_type: number;
status: number;
created_at: string;
auth_net_transaction_id: string | null;
rejection_reason: string | null;
delivery_id: number | null;
service_id: number | null;
auto_id: number | null;
}
interface Props { interface Props {
transactions: Transaction[]; transactions: AuthorizeTransaction[];
} }
defineProps<Props>(); defineProps<Props>();
const getStatusClass = (status: number) => status === 0 ? 'badge-success' : 'badge-error'; const getStatusClass = (status: number) => status === 0 ? 'badge-success' : 'badge-error';
const getStatusText = (status: number) => status === 0 ? 'Approved' : 'Declined'; const getStatusText = (status: number) => status === 0 ? 'Approved' : 'Declined';
const getSourceText = (transaction: Transaction) => { const getSourceText = (transaction: AuthorizeTransaction) => {
if (transaction.auto_id) { if (transaction.auto_id) {
return 'Automatic'; return 'Automatic';
} else if (transaction.delivery_id) { } else if (transaction.delivery_id) {

View File

@@ -1,10 +1,10 @@
import CustomerHome from '../customer/home.vue'; const CustomerHome = () => import('../customer/home.vue');
import CustomerCreate from '../customer/create.vue'; const CustomerCreate = () => import('../customer/create.vue');
import CustomerEdit from "../customer/edit.vue"; const CustomerEdit = () => import("../customer/edit.vue");
import CustomerProfile from "./profile/profile.vue" const CustomerProfile = () => import("./profile/profile.vue")
import TankEdit from "./tank/edit.vue" const TankEdit = () => import("./tank/edit.vue")
const customerRoutes = [ const customerRoutes = [

View File

@@ -96,8 +96,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, PropType } from 'vue'; import { ref, watch } from 'vue'
// Interfaces remain the same // Interfaces remain the same
interface ServiceParts { interface ServiceParts {
@@ -116,31 +116,27 @@ interface NozzleParts {
part3: string; part3: string;
} }
export default defineComponent({ // Props
name: 'PartsEditModal', const props = defineProps<{
props: { existingParts: ServiceParts
existingParts: { customerId: number
type: Object as PropType<ServiceParts>, }>()
required: true
},
customerId: {
type: Number,
required: true
}
},
emits: ['close-modal', 'save-parts'],
data() { // Emits
return { const emit = defineEmits<{
// All reactive state goes inside the data object 'close-modal': []
editableParts: {} as Partial<ServiceParts>, 'save-parts': [parts: Partial<ServiceParts>]
nozzle1: { part1: '', part2: '', part3: '' } as NozzleParts, }>()
nozzle2: { part1: '', part2: '', part3: '' } as NozzleParts,
// ====================================================== // Reactive data
// ============== THE UPDATED OIL FILTER LIST ============== const editableParts = ref({} as Partial<ServiceParts>)
// ====================================================== const nozzle1 = ref({ part1: '', part2: '', part3: '' } as NozzleParts)
oilFilterOptions: [ const nozzle2 = ref({ part1: '', part2: '', part3: '' } as NozzleParts)
// ======================================================
// ============== THE UPDATED OIL FILTER LIST ==============
// ======================================================
const oilFilterOptions = ref([
'RF-1', 'RF-1',
'RF-4', 'RF-4',
'88CR', '88CR',
@@ -148,32 +144,24 @@ export default defineComponent({
'Garber Model R', 'Garber Model R',
'Garber Model M', 'Garber Model M',
'PurePro f100-10W' 'PurePro f100-10W'
], ])
nozzlePart1Options: ['a', 'b', 'w'], const nozzlePart1Options = ref(['a', 'b', 'w'])
nozzlePart2Options: Array.from({ length: (200 - 45) / 5 + 1 }, (_, i) => (45 + i * 5).toString()), const nozzlePart2Options = ref(Array.from({ length: (200 - 45) / 5 + 1 }, (_, i) => (45 + i * 5).toString()))
nozzlePart3Options: Array.from({ length: (100 - 45) / 5 + 1 }, (_, i) => (45 + i * 5).toString()), const nozzlePart3Options = ref(Array.from({ length: (100 - 45) / 5 + 1 }, (_, i) => (45 + i * 5).toString()))
};
},
watch: { // Watchers
// Watch for changes to the `existingParts` prop watch(() => props.existingParts, (newVal) => {
existingParts: {
handler(newVal) {
if (!newVal) return; if (!newVal) return;
// Update the component's internal data when the prop changes // Update the component's internal data when the prop changes
this.editableParts = { ...newVal, customer_id: this.customerId }; editableParts.value = { ...newVal, customer_id: props.customerId };
this.nozzle1 = this.splitNozzle(newVal.oil_nozzle); nozzle1.value = splitNozzle(newVal.oil_nozzle);
this.nozzle2 = this.splitNozzle(newVal.oil_nozzle_2); nozzle2.value = splitNozzle(newVal.oil_nozzle_2);
}, }, { immediate: true, deep: true })
immediate: true, // Run the handler immediately when the component is created
deep: true, // Watch for nested changes within the object
},
},
methods: { // Functions
// Function to split a combined nozzle string (e.g., "a 55 75") into its parts // Function to split a combined nozzle string (e.g., "a 55 75") into its parts
splitNozzle(nozzleStr: string | undefined): NozzleParts { const splitNozzle = (nozzleStr: string | undefined): NozzleParts => {
if (!nozzleStr || typeof nozzleStr !== 'string' || nozzleStr.trim() === '') { if (!nozzleStr || typeof nozzleStr !== 'string' || nozzleStr.trim() === '') {
return { part1: '', part2: '', part3: '' }; return { part1: '', part2: '', part3: '' };
} }
@@ -183,25 +171,23 @@ export default defineComponent({
part2: parts[1] || '', part2: parts[1] || '',
part3: parts[2] || '', part3: parts[2] || '',
}; };
}, }
// Function to combine nozzle parts into a single string // Function to combine nozzle parts into a single string
combineNozzle(parts: NozzleParts): string { const combineNozzle = (parts: NozzleParts): string => {
if (parts.part1 && parts.part2 && parts.part3) { if (parts.part1 && parts.part2 && parts.part3) {
return `${parts.part1} ${parts.part2} ${parts.part3}`; return `${parts.part1} ${parts.part2} ${parts.part3}`;
} }
return ''; // Return empty if any part is missing return ''; // Return empty if any part is missing
}, }
// Form submission handler // Form submission handler
saveChanges() { const saveChanges = () => {
// Before emitting, combine the nozzle parts back into a single string // Before emitting, combine the nozzle parts back into a single string
this.editableParts.oil_nozzle = this.combineNozzle(this.nozzle1); editableParts.value.oil_nozzle = combineNozzle(nozzle1.value);
this.editableParts.oil_nozzle_2 = this.combineNozzle(this.nozzle2); editableParts.value.oil_nozzle_2 = combineNozzle(nozzle2.value);
// Emit the save event with the final payload // Emit the save event with the final payload
this.$emit('save-parts', this.editableParts); emit('save-parts', editableParts.value);
}, }
},
});
</script>``` </script>```

View File

@@ -85,8 +85,9 @@
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../../services/auth.header' import authHeader from '../../../services/auth.header'
import Footer from '../../../layouts/footers/footer.vue' import Footer from '../../../layouts/footers/footer.vue'
@@ -100,88 +101,89 @@ interface TankFormData {
fill_location: string; fill_location: string;
} }
export default defineComponent({ const route = useRoute()
name: 'TankEdit', const router = useRouter()
components: {
Footer, // Reactive data
}, const user = ref(null as any)
data() { const customer = ref({} as any)
return { // --- REFACTORED: Simplified, flat form object ---
user: null as any, const TankForm = ref({
customer: {} as any,
// --- REFACTORED: Simplified, flat form object ---
TankForm: {
last_tank_inspection: null, last_tank_inspection: null,
tank_status: true, tank_status: true,
outside_or_inside: true, outside_or_inside: true,
tank_size: 0, tank_size: 0,
fill_location: '', fill_location: '',
} as TankFormData, } as TankFormData)
}
}, // Functions
created() { const userStatus = () => {
this.userStatus();
const customerId = this.$route.params.id;
this.getCustomer(customerId);
this.getCustomerDescription(customerId);
this.getTank(customerId);
},
methods: {
userStatus() {
const path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; const path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios.get(path, { withCredentials: true, headers: authHeader() }) axios.get(path, { withCredentials: true, headers: authHeader() })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
.catch(() => { this.user = null; }); .catch(() => { user.value = null; });
}, }
getCustomer(userid: any) {
const getCustomer = (userid: any) => {
const path = `${import.meta.env.VITE_BASE_URL}/customer/${userid}`; const path = `${import.meta.env.VITE_BASE_URL}/customer/${userid}`;
axios.get(path, { headers: authHeader() }) axios.get(path, { headers: authHeader() })
.then((response: any) => { .then((response: any) => {
this.customer = response.data; customer.value = response.data;
}); });
}, }
getCustomerDescription(userid: any) {
const getCustomerDescription = (userid: any) => {
const path = `${import.meta.env.VITE_BASE_URL}/customer/description/${userid}`; const path = `${import.meta.env.VITE_BASE_URL}/customer/description/${userid}`;
axios.get(path, { headers: authHeader() }) axios.get(path, { headers: authHeader() })
.then((response: any) => { .then((response: any) => {
// Only update fill_location if the response has it // Only update fill_location if the response has it
if (response.data && response.data.fill_location) { if (response.data && response.data.fill_location) {
this.TankForm.fill_location = response.data.fill_location; TankForm.value.fill_location = response.data.fill_location;
} }
}); });
}, }
getTank(customer_id: any) {
const getTank = (customer_id: any) => {
const path = `${import.meta.env.VITE_BASE_URL}/customer/tank/${customer_id}`; const path = `${import.meta.env.VITE_BASE_URL}/customer/tank/${customer_id}`;
axios.get(path, { withCredentials: true, headers: authHeader() }) axios.get(path, { withCredentials: true, headers: authHeader() })
.then((response: any) => { .then((response: any) => {
if (response.data) { if (response.data) {
// Update the form model with data from the tank endpoint // Update the form model with data from the tank endpoint
this.TankForm.last_tank_inspection = response.data.last_tank_inspection; TankForm.value.last_tank_inspection = response.data.last_tank_inspection;
this.TankForm.tank_status = response.data.tank_status; TankForm.value.tank_status = response.data.tank_status;
this.TankForm.outside_or_inside = response.data.outside_or_inside; TankForm.value.outside_or_inside = response.data.outside_or_inside;
this.TankForm.tank_size = response.data.tank_size; TankForm.value.tank_size = response.data.tank_size;
} }
}); });
}, }
editTank(payload: TankFormData) {
const path = `${import.meta.env.VITE_BASE_URL}/customer/edit/tank/${this.$route.params.id}`; const editTank = (payload: TankFormData) => {
const path = `${import.meta.env.VITE_BASE_URL}/customer/edit/tank/${route.params.id}`;
axios.put(path, payload, { withCredentials: true, headers: authHeader() }) axios.put(path, payload, { withCredentials: true, headers: authHeader() })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } }); router.push({ name: "customerProfile", params: { id: customer.value.id } });
} else { } else {
console.error("Failed to edit tank:", response.data.error); console.error("Failed to edit tank:", response.data.error);
} }
}); });
}, }
onSubmit() {
const onSubmit = () => {
// The payload is simply the entire form object now // The payload is simply the entire form object now
this.editTank(this.TankForm); editTank(TankForm.value);
}, }
},
// Lifecycle
onMounted(() => {
userStatus();
const customerId = route.params.id;
getCustomer(customerId);
getCustomerDescription(customerId);
getTank(customerId);
}) })
</script> </script>

View File

@@ -229,7 +229,7 @@
<div class="font-bold text-sm">{{ card.name_on_card }}</div> <div class="font-bold text-sm">{{ card.name_on_card }}</div>
<div class="text-sm">{{ card.type_of_card }}</div> <div class="text-sm">{{ card.type_of_card }}</div>
<div class="text-sm">{{ card.card_number }}</div> <div class="text-sm">{{ card.card_number }}</div>
<p class="text-sm">Exp: <span v-if="card.expiration_month < 10">0</span>{{ card.expiration_month }} / {{ card.expiration_year }}</p> <p class="text-sm">Exp: <span v-if="Number(card.expiration_month) < 10">0</span>{{ card.expiration_month }} / {{ card.expiration_year }}</p>
<p class="text-sm">CVV: {{ card.security_number }}</p> <p class="text-sm">CVV: {{ card.security_number }}</p>
</div> </div>
<div class="flex justify-end gap-2 mt-2"> <div class="flex justify-end gap-2 mt-2">
@@ -305,43 +305,21 @@
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, computed, onMounted, watch, nextTick } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../services/auth.header' import authHeader from '../../services/auth.header'
import { Customer, CreditCard, CreateCardRequest } from '../../types/models'
import Header from '../../layouts/headers/headerauth.vue' import Header from '../../layouts/headers/headerauth.vue'
import SideBar from '../../layouts/sidebar/sidebar.vue' import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../layouts/footers/footer.vue' import Footer from '../../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core"; import { useVuelidate } from "@vuelidate/core";
import { notify } from "@kyvg/vue3-notification" import { notify } from "@kyvg/vue3-notification"
import { minLength, required, requiredIf } from "@vuelidate/validators"; import { minLength, required, requiredIf } from "@vuelidate/validators";
// --- TYPE DEFINITIONS (MODIFIED) --- // --- TYPE DEFINITIONS (MODIFIED) ---
interface SimpleResponse<T> { data: T; } interface SimpleResponse<T> { data: T; }
interface Customer {
id: number;
customer_last_name: string;
customer_first_name: string;
customer_town: string;
customer_state: number;
customer_zip: string;
customer_phone_number: string;
customer_home_type: number;
customer_apt: string;
customer_address: string;
account_number: string;
}
// This interface now reflects the full card data from the backend
interface UserCard {
id: number;
last_four_digits?: number;
type_of_card: string;
expiration_month: number;
expiration_year: number;
name_on_card: string;
card_number: string;
security_number: string;
}
interface Promo { id: number; name_of_promotion: string; money_off_delivery: number; } interface Promo { id: number; name_of_promotion: string; money_off_delivery: number; }
interface Driver { id: number; employee_first_name: string; employee_last_name: string; } interface Driver { id: number; employee_first_name: string; employee_last_name: string; }
interface PricingTier { gallons: number | string; price: number | string; } interface PricingTier { gallons: number | string; price: number | string; }
@@ -370,25 +348,23 @@ interface CardFormData {
type_of_card: string; type_of_card: string;
} }
export default defineComponent({ const router = useRouter()
name: 'deliveryCreate', const route = useRoute()
components: { Header, SideBar, Footer },
data() { // Reactive data
return { const user = ref(null as any)
v$: useValidate(), const customer = ref({} as any)
user: null as any, const isLoading = ref(false) // For main form submission
customer: {} as any, const isCardSaving = ref(false) // For quick add card form
isLoading: false, // For main form submission const quickGallonAmounts = ref([100, 125, 150, 175, 200, 220])
isCardSaving: false, // For quick add card form const userCards = ref<CreditCard[]>([])
quickGallonAmounts: [100, 125, 150, 175, 200, 220], const promos = ref([] as Promo[])
userCards: [] as UserCard[], const truckDriversList = ref([] as Driver[])
promos: [] as Promo[], const pricingTiers = ref([] as PricingTier[])
truckDriversList: [] as Driver[], const isLoadingAuthorize = ref(true)
pricingTiers: [] as PricingTier[], const authorizeCheck = ref({ profile_exists: false, has_payment_methods: false, missing_components: [] as string[], valid_for_charging: false })
isLoadingAuthorize: true, const isConfirmationModalVisible = ref(false)
authorizeCheck: { profile_exists: false, has_payment_methods: false, missing_components: [] as string[], valid_for_charging: false }, const formDelivery = ref({
isConfirmationModalVisible: false,
formDelivery: {
gallons_ordered: '', gallons_ordered: '',
customer_asked_for_fill: false, customer_asked_for_fill: false,
expected_delivery_date: '', expected_delivery_date: '',
@@ -403,62 +379,26 @@ export default defineComponent({
credit_card_id: 0, credit_card_id: 0,
promo_id: 0, promo_id: 0,
driver_employee_id: 0, driver_employee_id: 0,
} as DeliveryFormData, } as DeliveryFormData)
// Simplified formCard data // Simplified formCard data
formCard: { const formCard = ref({
card_number: '', card_number: '',
expiration_month: '', expiration_month: '',
expiration_year: '', expiration_year: '',
security_number: '', security_number: '',
card_name: '', card_name: '',
type_of_card: '', type_of_card: '',
} as CardFormData, } as CardFormData)
} // Computed
}, const preAuthAmount = computed((): number => {
validations() { if (!formDelivery.value.credit || formDelivery.value.customer_asked_for_fill) return 0;
return { const gallons = Number(formDelivery.value.gallons_ordered);
formDelivery: { if (isNaN(gallons) || gallons <= 0 || pricingTiers.value.length === 0) return 0;
gallons_ordered: {
required: requiredIf(function(this: any) { return !this.formDelivery.customer_asked_for_fill; }),
minValue: function(this: any, value: string) {
if (this.formDelivery.customer_asked_for_fill) return true;
if (!value) return true;
const num = parseInt(value, 10);
return num >= 1;
}
},
expected_delivery_date: { required },
driver_employee_id: { required },
credit_card_id: {
creditCardRequired: function(this: any, value: number) {
if (this.formDelivery.credit) { return value !== 0; }
return true;
}
},
},
isAnyPaymentMethodSelected: { mustBeTrue: (value: boolean) => value === true, },
// Simplified validations for quick add card
formCard: {
card_name: { required, minLength: minLength(1) },
security_number: { required, minLength: minLength(1) },
type_of_card: { required },
card_number: { required, minLength: minLength(1) },
expiration_month: { required },
expiration_year: { required },
},
};
},
computed: {
// New computed property to estimate pre-auth amount
preAuthAmount(): number {
if (!this.formDelivery.credit || this.formDelivery.customer_asked_for_fill) return 0;
const gallons = Number(this.formDelivery.gallons_ordered);
if (isNaN(gallons) || gallons <= 0 || this.pricingTiers.length === 0) return 0;
// Find the correct price tier. Assumes tiers are for total price, not price/gallon. // Find the correct price tier. Assumes tiers are for total price, not price/gallon.
let priceForGallons = 0; let priceForGallons = 0;
const sortedTiers = [...this.pricingTiers].sort((a, b) => Number(a.gallons) - Number(b.gallons)); const sortedTiers = [...pricingTiers.value].sort((a, b) => Number(a.gallons) - Number(b.gallons));
// Find the highest tier that is less than or equal to the gallons ordered // Find the highest tier that is less than or equal to the gallons ordered
let applicableTier = sortedTiers.filter(t => gallons >= Number(t.gallons)).pop(); let applicableTier = sortedTiers.filter(t => gallons >= Number(t.gallons)).pop();
@@ -474,132 +414,176 @@ export default defineComponent({
} }
return priceForGallons; return priceForGallons;
}, })
customerStateName(): string {
const customerStateName = computed((): string => {
const states: Record<number, string> = { 0: 'Massachusetts', 1: 'Rhode Island', 2: 'New Hampshire', 3: 'Maine', 4: 'Vermont', 5: 'Connecticut', 6: 'New York' }; const states: Record<number, string> = { 0: 'Massachusetts', 1: 'Rhode Island', 2: 'New Hampshire', 3: 'Maine', 4: 'Vermont', 5: 'Connecticut', 6: 'New York' };
return states[this.customer.customer_state] || 'Unknown state'; return states[customer.value.customer_state] || 'Unknown state';
}, })
customerHomeTypeName(): string {
const customerHomeTypeName = computed((): string => {
const types: Record<number, string> = { 0: 'Residential', 1: 'apartment', 2: 'condo', 3: 'commercial', 4: 'business', 5: 'construction', 6: 'container' }; const types: Record<number, string> = { 0: 'Residential', 1: 'apartment', 2: 'condo', 3: 'commercial', 4: 'business', 5: 'construction', 6: 'container' };
return types[this.customer.customer_home_type] || 'Unknown type'; return types[customer.value.customer_home_type] || 'Unknown type';
}, })
isAnyPaymentMethodSelected(): boolean {
return !!(this.formDelivery?.credit || this.formDelivery?.cash || this.formDelivery?.check || this.formDelivery?.other); const isAnyPaymentMethodSelected = computed((): boolean => {
}, return !!(formDelivery.value?.credit || formDelivery.value?.cash || formDelivery.value?.check || formDelivery.value?.other);
selectedGallonsAmount(): number { })
const value = this.formDelivery.gallons_ordered ?? '';
const selectedGallonsAmount = computed((): number => {
const value = formDelivery.value.gallons_ordered ?? '';
return Number(value); return Number(value);
})
// Validations
const validations = {
formDelivery: {
gallons_ordered: {
required: requiredIf(() => !formDelivery.value.customer_asked_for_fill),
minValue: (value: string) => {
if (formDelivery.value.customer_asked_for_fill) return true;
if (!value) return true;
const num = parseInt(value, 10);
return num >= 1;
} }
}, },
created() { expected_delivery_date: { required },
this.getDriversList() driver_employee_id: { required },
this.getPromos() credit_card_id: {
this.getPricingTiers() creditCardRequired: (value: number) => {
}, if (formDelivery.value.credit) { return value !== 0; }
watch: { return true;
$route() { }
const customerId = this.$route.params.id;
this.getCustomer(customerId);
this.getPaymentCards(customerId);
}, },
}, },
mounted() { isAnyPaymentMethodSelected: { mustBeTrue: (value: boolean) => value === true, },
const customerId = this.$route.params.id; // Simplified validations for quick add card
this.getCustomer(customerId) formCard: {
this.getPaymentCards(customerId); card_name: { required, minLength: minLength(1) },
security_number: { required, minLength: minLength(1) },
type_of_card: { required },
card_number: { required, minLength: minLength(1) },
expiration_month: { required },
expiration_year: { required },
}, },
methods: { }
setGallons(amount: number) { this.formDelivery.gallons_ordered = String(amount); this.formDelivery.customer_asked_for_fill = false; },
selectCreditCard(cardId: number) { this.formDelivery.credit_card_id = cardId; }, const v$ = useVuelidate(validations, { formDelivery, formCard, isAnyPaymentMethodSelected })
setDeliveryDate(days: number) { const date = new Date(); date.setDate(date.getDate() + days); this.formDelivery.expected_delivery_date = date.toISOString().split('T')[0]; },
isDeliveryDateSelected(days: number): boolean { const date = new Date(); date.setDate(date.getDate() + days); return this.formDelivery.expected_delivery_date === date.toISOString().split('T')[0]; }, // Functions
isPricingTierSelected(tierGallons: number | string): boolean { const setGallons = (amount: number) => {
if (!this.formDelivery.gallons_ordered) return false; formDelivery.value.gallons_ordered = String(amount);
const selectedGallons = Number(this.formDelivery.gallons_ordered); formDelivery.value.customer_asked_for_fill = false;
}
const selectCreditCard = (cardId: number) => {
formDelivery.value.credit_card_id = cardId;
}
const setDeliveryDate = (days: number) => {
const date = new Date();
date.setDate(date.getDate() + days);
formDelivery.value.expected_delivery_date = date.toISOString().split('T')[0];
}
const isDeliveryDateSelected = (days: number): boolean => {
const date = new Date();
date.setDate(date.getDate() + days);
return formDelivery.value.expected_delivery_date === date.toISOString().split('T')[0];
}
const isPricingTierSelected = (tierGallons: number | string): boolean => {
if (!formDelivery.value.gallons_ordered) return false;
const selectedGallons = Number(formDelivery.value.gallons_ordered);
if (isNaN(selectedGallons)) return false; if (isNaN(selectedGallons)) return false;
const tierNum = Number(tierGallons); const tierNum = Number(tierGallons);
if (isNaN(tierNum)) return false; if (isNaN(tierNum)) return false;
return selectedGallons === tierNum; return selectedGallons === tierNum;
}, }
getPricingTiers() {
const getPricingTiers = () => {
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/tiers"; let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/tiers";
axios({ method: "get", url: path, withCredentials: true, headers: authHeader() }) axios({ method: "get", url: path, withCredentials: true, headers: authHeader() })
.then((response: SimpleResponse<{ [key: string]: string }>) => { .then((response: SimpleResponse<{ [key: string]: string }>) => {
this.pricingTiers = Object.entries(response.data).map(([gallons, price]) => ({ gallons: parseInt(gallons, 10), price: price })); pricingTiers.value = Object.entries(response.data).map(([gallons, price]) => ({ gallons: parseInt(gallons, 10), price: price }));
}) })
.catch(() => notify({ title: "Pricing Error", text: "Could not retrieve today's pricing.", type: "error" })); .catch(() => notify({ title: "Pricing Error", text: "Could not retrieve today's pricing.", type: "error" }));
}, }
getCustomer(user_id: string | number | string[]) {
const getCustomer = (user_id: string | number | string[]) => {
let path = import.meta.env.VITE_BASE_URL + "/customer/" + user_id; let path = import.meta.env.VITE_BASE_URL + "/customer/" + user_id;
axios({ method: "get", url: path, withCredentials: true }) axios({ method: "get", url: path, withCredentials: true })
.then((response: SimpleResponse<Customer>) => { this.customer = response.data; }) .then((response: SimpleResponse<Customer>) => { customer.value = response.data; })
.catch(() => notify({ title: "Error", text: "Could not find customer", type: "error" })); .catch(() => notify({ title: "Error", text: "Could not find customer", type: "error" }));
}, }
getPaymentCards(user_id: string | number | string[]) {
const getPaymentCards = (user_id: string | number | string[]) => {
// IMPORTANT: This endpoint points to the Flask API that returns the secure card list. // IMPORTANT: This endpoint points to the Flask API that returns the secure card list.
let path = `${import.meta.env.VITE_BASE_URL}/payment/cards/${user_id}`; let path = `${import.meta.env.VITE_BASE_URL}/payment/cards/${user_id}`;
return axios.get(path, { withCredentials: true, headers: authHeader() }) return axios.get(path, { withCredentials: true, headers: authHeader() })
.then((response: SimpleResponse<UserCard[]>) => { this.userCards = response.data; }) .then((response: SimpleResponse<CreditCard[]>) => { userCards.value = response.data; })
.catch(() => { this.userCards = []; }); // Clear cards on error .catch(() => { userCards.value = []; }); // Clear cards on error
}, }
getPromos() {
const getPromos = () => {
let path = import.meta.env.VITE_BASE_URL + "/promo/all"; let path = import.meta.env.VITE_BASE_URL + "/promo/all";
axios({ method: "get", url: path, withCredentials: true }) axios({ method: "get", url: path, withCredentials: true })
.then((response: SimpleResponse<Promo[]>) => { .then((response: SimpleResponse<Promo[]>) => {
this.promos = response.data; promos.value = response.data;
}) })
.catch(() => { /* empty */ }); .catch(() => { /* empty */ });
}, }
getDriversList() {
const getDriversList = () => {
let path = import.meta.env.VITE_BASE_URL + "/employee/drivers"; let path = import.meta.env.VITE_BASE_URL + "/employee/drivers";
axios({ method: "get", url: path, withCredentials: true, headers: authHeader() }) axios({ method: "get", url: path, withCredentials: true, headers: authHeader() })
.then((response: SimpleResponse<Driver[]>) => { .then((response: SimpleResponse<Driver[]>) => {
this.truckDriversList = response.data; truckDriversList.value = response.data;
}) })
.catch(() => { /* empty */ }); .catch(() => { /* empty */ });
}, }
editCard(card_id: number) { const editCard = (card_id: number) => {
this.$router.push({ name: "cardedit", params: { id: card_id } }); router.push({ name: "cardedit", params: { id: card_id } });
}, }
removeCard(card_id: number) { const removeCard = (card_id: number) => {
if (window.confirm("Are you sure you want to remove this card?")) { if (window.confirm("Are you sure you want to remove this card?")) {
// You will need a new backend endpoint for this: DELETE /payments/customers/{customer_id}/cards/{card_id} // You will need a new backend endpoint for this: DELETE /payments/customers/{customer_id}/cards/{card_id}
let path = `${import.meta.env.VITE_BASE_URL}/payment/card/remove/${card_id}`; // Keep old path or update to new let path = `${import.meta.env.VITE_BASE_URL}/payment/card/remove/${card_id}`; // Keep old path or update to new
axios.delete(path, { headers: authHeader() }) axios.delete(path, { headers: authHeader() })
.then(() => { .then(() => {
notify({ title: "Card Removed", type: "success" }); notify({ title: "Card Removed", type: "success" });
this.getPaymentCards(this.customer.id); getPaymentCards(customer.value.id);
}) })
.catch(() => { .catch(() => {
notify({ title: "Error", text: "Could not remove card.", type: "error" }); notify({ title: "Error", text: "Could not remove card.", type: "error" });
}); });
} }
}, }
async onDeliverySubmit() { const onDeliverySubmit = async () => {
const isFormValid = await this.v$.formDelivery.$validate(); const isFormValid = await v$.value.formDelivery.$validate();
if (!isFormValid) { if (!isFormValid) {
notify({ title: "Form Incomplete", text: "Please review the fields marked in red.", type: "warn" }); notify({ title: "Form Incomplete", text: "Please review the fields marked in red.", type: "warn" });
return; return;
} }
if (this.formDelivery.cash || this.formDelivery.check) { if (formDelivery.value.cash || formDelivery.value.check) {
this.isConfirmationModalVisible = true; isConfirmationModalVisible.value = true;
} else { } else {
this.proceedWithSubmission(); proceedWithSubmission();
} }
}, }
async proceedWithSubmission() { const proceedWithSubmission = async () => {
this.isConfirmationModalVisible = false; isConfirmationModalVisible.value = false;
this.isLoading = true; isLoading.value = true;
// Step 1: Create the delivery order record // Step 1: Create the delivery order record
const createDeliveryPath = `${import.meta.env.VITE_BASE_URL}/delivery/create/${this.customer.id}`; const createDeliveryPath = `${import.meta.env.VITE_BASE_URL}/delivery/create/${customer.value.id}`;
try { try {
const deliveryResponse = await axios.post(createDeliveryPath, this.formDelivery, { withCredentials: true, headers: authHeader() }); const deliveryResponse = await axios.post(createDeliveryPath, formDelivery.value, { withCredentials: true, headers: authHeader() });
const deliveryData = deliveryResponse.data; const deliveryData = deliveryResponse.data;
if (!deliveryData.ok || !deliveryData.delivery_id) { if (!deliveryData.ok || !deliveryData.delivery_id) {
@@ -610,69 +594,69 @@ export default defineComponent({
notify({ notify({
title: "Success!", title: "Success!",
text: `Delivery #${deliveryData.delivery_id} created. ${ text: `Delivery #${deliveryData.delivery_id} created. ${
this.formDelivery.credit formDelivery.value.credit
? "Redirecting to payment page." ? "Redirecting to payment page."
: "" : ""
}`, }`,
type: "success" type: "success"
}); });
this.$router.push({ name: "payOil", params: { id: deliveryData.delivery_id } }); router.push({ name: "payOil", params: { id: deliveryData.delivery_id } });
} catch (error: any) { } catch (error: any) {
const errorMessage = error.response?.data?.detail || "An error occurred during submission."; const errorMessage = error.response?.data?.detail || "An error occurred during submission.";
notify({ title: "Submission Error", text: errorMessage, type: "error" }); notify({ title: "Submission Error", text: errorMessage, type: "error" });
} finally { } finally {
this.isLoading = false; isLoading.value = false;
} }
}, }
async checkAuthorizeAccount() {
if (!this.customer.id) return;
this.isLoadingAuthorize = true; const checkAuthorizeAccount = async () => {
if (!customer.value.id) return;
isLoadingAuthorize.value = true;
try { try {
const path = `${import.meta.env.VITE_AUTHORIZE_URL}/user/check-authorize-account/${this.customer.id}`; const path = `${import.meta.env.VITE_AUTHORIZE_URL}/user/check-authorize-account/${customer.value.id}`;
const response = await axios.get(path, { headers: authHeader() }); const response = await axios.get(path, { headers: authHeader() });
this.authorizeCheck = response.data; authorizeCheck.value = response.data;
} catch (error) { } catch (error) {
console.error("Failed to check authorize account:", error); console.error("Failed to check authorize account:", error);
notify({ title: "Error", text: "Could not check payment account status.", type: "error" }); notify({ title: "Error", text: "Could not check payment account status.", type: "error" });
// Set default error state // Set default error state
this.authorizeCheck = { authorizeCheck.value = {
profile_exists: false, profile_exists: false,
has_payment_methods: false, has_payment_methods: false,
missing_components: ['api_error'], missing_components: ['api_error'],
valid_for_charging: false valid_for_charging: false
}; };
} finally { } finally {
this.isLoadingAuthorize = false; isLoadingAuthorize.value = false;
} }
}, }
async onCardSubmit() {
this.v$.formCard.$validate(); const onCardSubmit = async () => {
if (this.v$.formCard.$error) { v$.value.formCard.$validate();
if (v$.value.formCard.$error) {
notify({ title: "Validation Error", text: "Please fill out all required fields.", type: "error" }); notify({ title: "Validation Error", text: "Please fill out all required fields.", type: "error" });
return; return;
} }
this.isCardSaving = true; isCardSaving.value = true;
// --- STEP 1: PREPARE PAYLOADS FOR BOTH SERVICES --- // --- STEP 1: PREPARE PAYLOADS FOR BOTH SERVICES ---
// Payload for Flask backend (it takes all the raw details for your DB) // Payload for Flask backend (it takes all the raw details for your DB)
const flaskPayload = { const flaskPayload = {
card_number: this.formCard.card_number, card_number: formCard.value.card_number,
expiration_month: this.formCard.expiration_month, expiration_month: formCard.value.expiration_month,
expiration_year: this.formCard.expiration_year, expiration_year: formCard.value.expiration_year,
type_of_card: this.formCard.type_of_card, type_of_card: formCard.value.type_of_card,
security_number: this.formCard.security_number, // Flask expects 'security_number' security_number: formCard.value.security_number, // Flask expects 'security_number'
main_card: false, main_card: false,
name_on_card: this.formCard.card_name, // Map card_name to name_on_card for Flask name_on_card: formCard.value.card_name, // Map card_name to name_on_card for Flask
}; };
// --- STEP 2: CRITICAL CALL - SAVE CARD TO LOCAL DATABASE VIA FLASK --- // --- STEP 2: CRITICAL CALL - SAVE CARD TO LOCAL DATABASE VIA FLASK ---
try { try {
const flaskPath = `${import.meta.env.VITE_BASE_URL}/payment/card/create/${this.customer.id}`; const flaskPath = `${import.meta.env.VITE_BASE_URL}/payment/card/create/${customer.value.id}`;
console.log("Attempting to save card to local DB via Flask:", flaskPath); console.log("Attempting to save card to local DB via Flask:", flaskPath);
const flaskResponse = await axios.post(flaskPath, flaskPayload, { withCredentials: true, headers: authHeader() }); const flaskResponse = await axios.post(flaskPath, flaskPayload, { withCredentials: true, headers: authHeader() });
@@ -685,22 +669,22 @@ export default defineComponent({
} catch (error: any) { } catch (error: any) {
const errorMessage = error.response?.data?.error || "A critical error occurred while saving the card."; const errorMessage = error.response?.data?.error || "A critical error occurred while saving the card.";
notify({ title: "Error", text: errorMessage, type: "error" }); notify({ title: "Error", text: errorMessage, type: "error" });
this.isCardSaving = false; // Stop loading spinner isCardSaving.value = false; // Stop loading spinner
return; // End the function here return; // End the function here
} }
// --- STEP 3: BEST-EFFORT CALL - TOKENIZE CARD VIA FASTAPI --- // --- STEP 3: BEST-EFFORT CALL - TOKENIZE CARD VIA FASTAPI ---
if (this.authorizeCheck.profile_exists) { if (authorizeCheck.value.profile_exists) {
// Payload for FastAPI backend (it only needs the essentials for Authorize.Net) // Payload for FastAPI backend (it only needs the essentials for Authorize.Net)
const fastapiPayload = { const fastapiPayload = {
card_number: this.formCard.card_number.replace(/\s/g, ''), card_number: formCard.value.card_number.replace(/\s/g, ''),
expiration_date: `${this.formCard.expiration_year}-${this.formCard.expiration_month}`, expiration_date: `${formCard.value.expiration_year}-${formCard.value.expiration_month}`,
cvv: this.formCard.security_number, // Map security_number to cvv for FastAPI cvv: formCard.value.security_number, // Map security_number to cvv for FastAPI
main_card: false, // Send this to FastAPI as well main_card: false, // Send this to FastAPI as well
}; };
try { try {
const fastapiPath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/customers/${this.customer.id}/cards`; const fastapiPath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/customers/${customer.value.id}/cards`;
console.log("Attempting to tokenize card with Authorize.Net via FastAPI:", fastapiPath); console.log("Attempting to tokenize card with Authorize.Net via FastAPI:", fastapiPath);
await axios.post(fastapiPath, fastapiPayload, { withCredentials: true, headers: authHeader() }); await axios.post(fastapiPath, fastapiPayload, { withCredentials: true, headers: authHeader() });
console.log("Card successfully tokenized with Authorize.Net via FastAPI."); console.log("Card successfully tokenized with Authorize.Net via FastAPI.");
@@ -708,25 +692,40 @@ export default defineComponent({
// If this fails, we just log it for the developers. We DON'T show an error to the user. // If this fails, we just log it for the developers. We DON'T show an error to the user.
console.warn("NON-CRITICAL-ERROR: Tokenization with Authorize.Net failed, but the card was saved locally.", error.response?.data || error.message); console.warn("NON-CRITICAL-ERROR: Tokenization with Authorize.Net failed, but the card was saved locally.", error.response?.data || error.message);
} }
}else{ }else{
console.log("Skipping Authorize.Net tokenization as no profile exists for customer."); console.log("Skipping Authorize.Net tokenization as no profile exists for customer.");
} }
// --- STEP 4: ALWAYS SHOW SUCCESS, REFRESH CARDS, STAY ON PAGE --- // --- STEP 4: ALWAYS SHOW SUCCESS, REFRESH CARDS, STAY ON PAGE ---
// This code runs as long as the first (Flask) call was successful. // This code runs as long as the first (Flask) call was successful.
notify({ type: 'success', title: 'Card Saved!' }); notify({ type: 'success', title: 'Card Saved!' });
// Refresh the card list and try to auto-select if possible // Refresh the card list and try to auto-select if possible
await this.getPaymentCards(this.customer.id); await getPaymentCards(customer.value.id);
if (this.userCards.length > 0) { if (userCards.value.length > 0) {
this.formDelivery.credit = true; formDelivery.value.credit = true;
this.formDelivery.credit_card_id = this.userCards[this.userCards.length - 1].id; formDelivery.value.credit_card_id = userCards.value[userCards.value.length - 1].id;
} }
// Reset the quick add form // Reset the quick add form
Object.assign(this.formCard, { card_number: '', expiration_month: '', expiration_year: '', security_number: '', card_name: '', type_of_card: '' }); Object.assign(formCard.value, { card_number: '', expiration_month: '', expiration_year: '', security_number: '', card_name: '', type_of_card: '' });
this.v$.formCard.$reset(); v$.value.formCard.$reset();
this.isCardSaving = false; isCardSaving.value = false;
}, }
},
// Watchers
watch(route, () => {
const customerId = route.params.id;
getCustomer(customerId);
getPaymentCards(customerId);
})
// Lifecycle
onMounted(() => {
getDriversList()
getPromos()
getPricingTiers()
const customerId = route.params.id;
getCustomer(customerId)
getPaymentCards(customerId);
}) })
</script> </script>

View File

@@ -223,7 +223,7 @@
</div> </div>
<div class="mt-2 text-sm font-mono tracking-wider"> <div class="mt-2 text-sm font-mono tracking-wider">
<p>{{ card.card_number }}</p> <p>{{ card.card_number }}</p>
<p>Exp: <span v-if="card.expiration_month < 10">0</span>{{ card.expiration_month }} / {{ card.expiration_year }}</p> <p>Exp: <span v-if="Number(card.expiration_month) < 10">0</span>{{ card.expiration_month }} / {{ card.expiration_year }}</p>
<p>CVV: {{ card.security_number }}</p> <p>CVV: {{ card.security_number }}</p>
</div> </div>
<div class="flex justify-end gap-2 mt-2"> <div class="flex justify-end gap-2 mt-2">
@@ -240,42 +240,40 @@
</div> </div>
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, computed, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../services/auth.header' import authHeader from '../../services/auth.header'
import { Customer, CreditCard } from '../../types/models'
import Header from '../../layouts/headers/headerauth.vue' import Header from '../../layouts/headers/headerauth.vue'
import SideBar from '../../layouts/sidebar/sidebar.vue' import SideBar from '../../layouts/sidebar/sidebar.vue'
import Footer from '../../layouts/footers/footer.vue' import Footer from '../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core"; import { useVuelidate } from "@vuelidate/core";
import { required, requiredIf } from "@vuelidate/validators"; import { required, requiredIf } from "@vuelidate/validators";
import { notify } from "@kyvg/vue3-notification"; import { notify } from "@kyvg/vue3-notification";
// Interfaces to describe the shape of your data // Interfaces to describe the shape of your data
interface Customer { account_number: string; id: number; customer_first_name: string; customer_last_name: string; customer_address: string; customer_apt: string; customer_town: string; customer_state: number; customer_zip: string; customer_phone_number: string; }
interface DeliveryOrder { id: string; customer_id: number; payment_type: number; payment_card_id: number; gallons_ordered: number; customer_asked_for_fill: boolean | number; delivery_status: number; driver_employee_id: number; promo_id: number; expected_delivery_date: string; when_ordered: string; prime: boolean | number; emergency: boolean | number; same_day: boolean | number; dispatcher_notes: string; } interface DeliveryOrder { id: string; customer_id: number; payment_type: number; payment_card_id: number; gallons_ordered: number; customer_asked_for_fill: boolean | number; delivery_status: number; driver_employee_id: number; promo_id: number; expected_delivery_date: string; when_ordered: string; prime: boolean | number; emergency: boolean | number; same_day: boolean | number; dispatcher_notes: string; }
interface UserCard { id: number; type_of_card: string; card_number: string; name_on_card: string; expiration_month: number; expiration_year: number; last_four_digits: string; security_number: string; main_card: boolean; }
interface PricingTier { gallons: number; price: string | number; } interface PricingTier { gallons: number; price: string | number; }
const router = useRouter()
const route = useRoute()
const STATE_MAP: { [key: number]: string } = { 0: 'Massachusetts', 1: 'Rhode Island', 2: 'New Hampshire', 3: 'Maine', 4: 'Vermont', 5: 'Connecticut', 6: 'New York' }; const STATE_MAP: { [key: number]: string } = { 0: 'Massachusetts', 1: 'Rhode Island', 2: 'New Hampshire', 3: 'Maine', 4: 'Vermont', 5: 'Connecticut', 6: 'New York' };
export default defineComponent({ // Reactive data
name: 'deliveryEdit', const quickGallonAmounts = ref([100, 125, 150, 175, 200, 220])
components: { Header, SideBar, Footer }, const deliveryStatus = ref([] as any[])
data() { const truckDriversList = ref([] as any[])
return { const userCards = ref<CreditCard[]>([])
v$: useValidate(), const promos = ref([] as any[])
quickGallonAmounts: [100, 125, 150, 175, 200, 220], const pricingTiers = ref([] as PricingTier[])
deliveryStatus: [] as any[], const customer = ref({} as Customer)
truckDriversList: [] as any[], const deliveryOrder = ref({} as DeliveryOrder)
userCards: [] as UserCard[], const userCard = ref({} as CreditCard)
promos: [] as any[], // RESTORED: Add all form fields to the data model
pricingTiers: [] as PricingTier[], const CreateOilOrderForm = ref({
customer: {} as Customer,
deliveryOrder: {} as DeliveryOrder,
userCard: {} as UserCard,
// RESTORED: Add all form fields to the data model
CreateOilOrderForm: {
basicInfo: { basicInfo: {
gallons_ordered: '', gallons_ordered: '',
customer_asked_for_fill: false, customer_asked_for_fill: false,
@@ -295,20 +293,46 @@ export default defineComponent({
check: false, check: false,
other: false, other: false,
}, },
}, })
}
},
validations() { // Computed
return { const stateName = computed((): string => {
if (customer.value && customer.value.customer_state !== undefined) {
return STATE_MAP[customer.value.customer_state];
}
return '';
})
const isPricingTierSelected = computed(() => {
return (tierGallons: number | string): boolean => {
if (!CreateOilOrderForm.value.basicInfo.gallons_ordered) return false;
const selectedGallons = Number(CreateOilOrderForm.value.basicInfo.gallons_ordered);
if (isNaN(selectedGallons)) return false;
const tierNum = Number(tierGallons);
if (isNaN(tierNum)) return false;
return selectedGallons === tierNum;
};
})
const isAnyPaymentMethodSelected = computed((): boolean => {
return !!(CreateOilOrderForm.value.basicInfo?.credit || CreateOilOrderForm.value.basicInfo?.cash || CreateOilOrderForm.value.basicInfo?.check || CreateOilOrderForm.value.basicInfo?.other);
})
const selectedGallonsAmount = computed((): number => {
const value = CreateOilOrderForm.value.basicInfo.gallons_ordered ?? '';
return Number(value);
})
// Validations
const validations = {
CreateOilOrderForm: { CreateOilOrderForm: {
basicInfo: { basicInfo: {
gallons_ordered: { gallons_ordered: {
required: requiredIf(function(this: any) { required: requiredIf(() => !CreateOilOrderForm.value.basicInfo.customer_asked_for_fill),
return !this.CreateOilOrderForm.basicInfo.customer_asked_for_fill; minValue: (value: string) => {
}), if (CreateOilOrderForm.value.basicInfo.customer_asked_for_fill) return true;
minValue: function(this: any, value: string) {
if (this.CreateOilOrderForm.basicInfo.customer_asked_for_fill) return true;
if (!value) return true; // if empty, required will catch it if (!value) return true; // if empty, required will catch it
const num = parseInt(value, 10); const num = parseInt(value, 10);
return num >= 1; return num >= 1;
@@ -316,8 +340,8 @@ export default defineComponent({
}, },
expected_delivery_date: { required }, expected_delivery_date: { required },
credit_card_id: { credit_card_id: {
creditCardRequired: function(this: any, value: number) { creditCardRequired: (value: number) => {
if (this.CreateOilOrderForm.basicInfo.credit) { if (CreateOilOrderForm.value.basicInfo.credit) {
return value !== 0; return value !== 0;
} }
return true; return true;
@@ -328,74 +352,43 @@ export default defineComponent({
isAnyPaymentMethodSelected: { isAnyPaymentMethodSelected: {
mustBeTrue: (value: boolean) => value === true, mustBeTrue: (value: boolean) => value === true,
}, },
}; }
},
computed: { const v$ = useVuelidate(validations, { CreateOilOrderForm, isAnyPaymentMethodSelected })
stateName(): string {
if (this.customer && this.customer.customer_state !== undefined) {
return STATE_MAP[this.customer.customer_state];
}
return '';
},
isPricingTierSelected() {
return (tierGallons: number | string): boolean => {
if (!this.CreateOilOrderForm.basicInfo.gallons_ordered) return false;
const selectedGallons = Number(this.CreateOilOrderForm.basicInfo.gallons_ordered);
if (isNaN(selectedGallons)) return false;
const tierNum = Number(tierGallons); // Functions
if (isNaN(tierNum)) return false; const fetchInitialData = () => {
const deliveryId = route.params.id as string;
getPromos();
getDriversList();
getDeliveryStatusList();
getPricingTiers();
getDeliveryOrder(deliveryId);
}
return selectedGallons === tierNum; const getDeliveryOrder = (deliveryId: string) => {
};
},
isAnyPaymentMethodSelected(): boolean {
return !!(this.CreateOilOrderForm.basicInfo?.credit || this.CreateOilOrderForm.basicInfo?.cash || this.CreateOilOrderForm.basicInfo?.check || this.CreateOilOrderForm.basicInfo?.other);
},
selectedGallonsAmount(): number {
const value = this.CreateOilOrderForm.basicInfo.gallons_ordered ?? '';
return Number(value);
}
},
mounted() {
this.fetchInitialData();
},
methods: {
fetchInitialData() {
const deliveryId = this.$route.params.id as string;
this.getPromos();
this.getDriversList();
this.getDeliveryStatusList();
this.getPricingTiers();
this.getDeliveryOrder(deliveryId);
},
getDeliveryOrder(deliveryId: string) {
axios.get(`${import.meta.env.VITE_BASE_URL}/delivery/order/${deliveryId}`, { withCredentials: true, headers: authHeader() }) axios.get(`${import.meta.env.VITE_BASE_URL}/delivery/order/${deliveryId}`, { withCredentials: true, headers: authHeader() })
.then((response: any) => { .then((response: any) => {
// FIX: Check for the 'ok' flag and access the nested 'delivery' object // FIX: Check for the 'ok' flag and access the nested 'delivery' object
if (response.data && response.data.ok) { if (response.data && response.data.ok) {
this.deliveryOrder = response.data.delivery; // <-- THIS IS THE CRITICAL CHANGE deliveryOrder.value = response.data.delivery; // <-- THIS IS THE CRITICAL CHANGE
// RESTORED: Populate all form fields from the API response // RESTORED: Populate all form fields from the API response
const paymentType = this.deliveryOrder.payment_type; const paymentType = deliveryOrder.value.payment_type;
this.CreateOilOrderForm.basicInfo = { CreateOilOrderForm.value.basicInfo = {
gallons_ordered: String(this.deliveryOrder.gallons_ordered), gallons_ordered: String(deliveryOrder.value.gallons_ordered),
customer_asked_for_fill: !!this.deliveryOrder.customer_asked_for_fill, customer_asked_for_fill: !!deliveryOrder.value.customer_asked_for_fill,
created_delivery_date: this.deliveryOrder.when_ordered, created_delivery_date: deliveryOrder.value.when_ordered,
expected_delivery_date: this.deliveryOrder.expected_delivery_date, expected_delivery_date: deliveryOrder.value.expected_delivery_date,
prime: !!this.deliveryOrder.prime, prime: !!deliveryOrder.value.prime,
emergency: !!this.deliveryOrder.emergency, emergency: !!deliveryOrder.value.emergency,
same_day: !!this.deliveryOrder.same_day, same_day: !!deliveryOrder.value.same_day,
delivery_status: this.deliveryOrder.delivery_status, delivery_status: deliveryOrder.value.delivery_status,
driver_employee_id: this.deliveryOrder.driver_employee_id || 0, driver_employee_id: deliveryOrder.value.driver_employee_id || 0,
dispatcher_notes_taken: this.deliveryOrder.dispatcher_notes, dispatcher_notes_taken: deliveryOrder.value.dispatcher_notes,
promo_id: this.deliveryOrder.promo_id || 0, promo_id: deliveryOrder.value.promo_id || 0,
payment_type: paymentType, payment_type: paymentType,
credit_card_id: this.deliveryOrder.payment_card_id || 0, credit_card_id: deliveryOrder.value.payment_card_id || 0,
// Set the correct payment method checkbox based on payment_type // Set the correct payment method checkbox based on payment_type
credit: paymentType === 1, credit: paymentType === 1,
cash: paymentType === 0, cash: paymentType === 0,
@@ -403,56 +396,59 @@ export default defineComponent({
other: paymentType === 4, other: paymentType === 4,
}; };
this.getCustomer(this.deliveryOrder.customer_id); getCustomer(deliveryOrder.value.customer_id);
} else { } else {
console.error("API Error:", response.data.error || "Failed to fetch delivery data."); console.error("API Error:", response.data.error || "Failed to fetch delivery data.");
} }
}) })
.catch((error: any) => console.error("Error fetching delivery order:", error)); .catch((error: any) => console.error("Error fetching delivery order:", error));
}, }
getCustomer(customerId: number) { const getCustomer = (customerId: number) => {
axios.get(`${import.meta.env.VITE_BASE_URL}/customer/${customerId}`, { withCredentials: true }) axios.get(`${import.meta.env.VITE_BASE_URL}/customer/${customerId}`, { withCredentials: true })
.then((response: any) => { .then((response: any) => {
this.customer = response.data; customer.value = response.data;
this.getPaymentCards(customerId); getPaymentCards(customerId);
if (this.deliveryOrder.payment_type === 1 && this.deliveryOrder.payment_card_id) { if (deliveryOrder.value.payment_type === 1 && deliveryOrder.value.payment_card_id) {
this.getPaymentCard(this.deliveryOrder.payment_card_id); getPaymentCard(deliveryOrder.value.payment_card_id);
} }
}) })
.catch((error: any) => console.error("Error fetching customer:", error)); .catch((error: any) => console.error("Error fetching customer:", error));
}, }
getPaymentCards(customerId: number) { const getPaymentCards = (customerId: number) => {
axios.get(`${import.meta.env.VITE_BASE_URL}/payment/cards/${customerId}`, { withCredentials: true }) axios.get(`${import.meta.env.VITE_BASE_URL}/payment/cards/${customerId}`, { withCredentials: true })
.then((response: any) => { this.userCards = response.data; }) .then((response: any) => { userCards.value = response.data; })
.catch((error: any) => console.error("Error fetching payment cards:", error)); .catch((error: any) => console.error("Error fetching payment cards:", error));
}, }
getPaymentCard(cardId: number) { const getPaymentCard = (cardId: number) => {
axios.get(`${import.meta.env.VITE_BASE_URL}/payment/card/${cardId}`, { withCredentials: true }) axios.get(`${import.meta.env.VITE_BASE_URL}/payment/card/${cardId}`, { withCredentials: true })
.then((response: any) => { this.userCard = response.data; }) .then((response: any) => { userCard.value = response.data; })
.catch((error: any) => console.error("Error fetching specific payment card:", error)); .catch((error: any) => console.error("Error fetching specific payment card:", error));
}, }
getPromos() { const getPromos = () => {
axios.get(`${import.meta.env.VITE_BASE_URL}/promo/all`, { withCredentials: true }) axios.get(`${import.meta.env.VITE_BASE_URL}/promo/all`, { withCredentials: true })
.then((response: any) => { this.promos = response.data; }); .then((response: any) => { promos.value = response.data; });
}, }
getDriversList() {
const getDriversList = () => {
axios.get(`${import.meta.env.VITE_BASE_URL}/employee/drivers`, { headers: authHeader(), withCredentials: true }) axios.get(`${import.meta.env.VITE_BASE_URL}/employee/drivers`, { headers: authHeader(), withCredentials: true })
.then((response: any) => { this.truckDriversList = response.data; }); .then((response: any) => { truckDriversList.value = response.data; });
}, }
getDeliveryStatusList() {
const getDeliveryStatusList = () => {
axios.get(`${import.meta.env.VITE_BASE_URL}/query/deliverystatus`, { withCredentials: true }) axios.get(`${import.meta.env.VITE_BASE_URL}/query/deliverystatus`, { withCredentials: true })
.then((response: any) => { this.deliveryStatus = response.data; }); .then((response: any) => { deliveryStatus.value = response.data; });
}, }
getPricingTiers() {
const getPricingTiers = () => {
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/tiers"; let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/tiers";
axios({ method: "get", url: path, withCredentials: true, headers: authHeader() }) axios({ method: "get", url: path, withCredentials: true, headers: authHeader() })
.then((response: any) => { .then((response: any) => {
const tiersObject = response.data; const tiersObject = response.data;
this.pricingTiers = Object.entries(tiersObject).map(([gallons, price]) => ({ pricingTiers.value = Object.entries(tiersObject).map(([gallons, price]) => ({
gallons: parseInt(gallons, 10), gallons: parseInt(gallons, 10),
price: price as string | number, price: price as string | number,
})); }));
@@ -460,50 +456,57 @@ export default defineComponent({
.catch(() => { .catch(() => {
notify({ title: "Pricing Error", text: "Could not retrieve today's pricing.", type: "error" }); notify({ title: "Pricing Error", text: "Could not retrieve today's pricing.", type: "error" });
}); });
}, }
editCard(card_id: number) {
this.$router.push({ name: "cardedit", params: { id: card_id } }); const editCard = (card_id: number) => {
}, router.push({ name: "cardedit", params: { id: card_id } });
removeCard(card_id: number) { }
const removeCard = (card_id: number) => {
if (window.confirm("Are you sure you want to remove this card?")) { if (window.confirm("Are you sure you want to remove this card?")) {
let path = `${import.meta.env.VITE_BASE_URL}/payment/card/remove/${card_id}`; let path = `${import.meta.env.VITE_BASE_URL}/payment/card/remove/${card_id}`;
axios.delete(path, { headers: authHeader() }) axios.delete(path, { headers: authHeader() })
.then(() => { .then(() => {
notify({ title: "Card Removed", type: "success" }); notify({ title: "Card Removed", type: "success" });
this.getPaymentCards(this.customer.id); getPaymentCards(customer.value.id);
}) })
.catch(() => { .catch(() => {
notify({ title: "Error", text: "Could not remove card.", type: "error" }); notify({ title: "Error", text: "Could not remove card.", type: "error" });
}); });
} }
}, }
selectCreditCard(cardId: number) {
this.CreateOilOrderForm.basicInfo.credit_card_id = cardId; const selectCreditCard = (cardId: number) => {
}, CreateOilOrderForm.value.basicInfo.credit_card_id = cardId;
setGallons(amount: number) { }
this.CreateOilOrderForm.basicInfo.gallons_ordered = String(amount);
this.CreateOilOrderForm.basicInfo.customer_asked_for_fill = false; const setGallons = (amount: number) => {
}, CreateOilOrderForm.value.basicInfo.gallons_ordered = String(amount);
setDeliveryDate(days: number) { CreateOilOrderForm.value.basicInfo.customer_asked_for_fill = false;
}
const setDeliveryDate = (days: number) => {
const date = new Date(); const date = new Date();
date.setDate(date.getDate() + days); date.setDate(date.getDate() + days);
this.CreateOilOrderForm.basicInfo.expected_delivery_date = date.toISOString().split('T')[0]; CreateOilOrderForm.value.basicInfo.expected_delivery_date = date.toISOString().split('T')[0];
}, }
isDeliveryDateSelected(days: number): boolean {
const isDeliveryDateSelected = (days: number): boolean => {
const date = new Date(); const date = new Date();
date.setDate(date.getDate() + days); date.setDate(date.getDate() + days);
return this.CreateOilOrderForm.basicInfo.expected_delivery_date === date.toISOString().split('T')[0]; return CreateOilOrderForm.value.basicInfo.expected_delivery_date === date.toISOString().split('T')[0];
}, }
async onSubmit() {
const isFormValid = await this.v$.CreateOilOrderForm.$validate(); const onSubmit = async () => {
const isPaymentValid = await this.v$.isAnyPaymentMethodSelected.$validate(); const isFormValid = await v$.value.CreateOilOrderForm.$validate();
const isPaymentValid = await v$.value.isAnyPaymentMethodSelected.$validate();
if (!isFormValid || !isPaymentValid) { if (!isFormValid || !isPaymentValid) {
notify({ title: "Form Incomplete", text: "Please review the fields marked in red.", type: "warn" }); notify({ title: "Form Incomplete", text: "Please review the fields marked in red.", type: "warn" });
return; return;
} }
const formInfo = this.CreateOilOrderForm.basicInfo; const formInfo = CreateOilOrderForm.value.basicInfo;
// Convert checkboxes back to payment_type number for API // Convert checkboxes back to payment_type number for API
let paymentType = 0; // Default to cash let paymentType = 0; // Default to cash
if (formInfo.credit) paymentType = 1; if (formInfo.credit) paymentType = 1;
@@ -522,20 +525,23 @@ export default defineComponent({
credit_card_id: formInfo.credit ? formInfo.credit_card_id : null, credit_card_id: formInfo.credit ? formInfo.credit_card_id : null,
}; };
axios.post(`${import.meta.env.VITE_BASE_URL}/delivery/edit/${this.deliveryOrder.id}`, payload, { withCredentials: true, headers: authHeader() }) axios.post(`${import.meta.env.VITE_BASE_URL}/delivery/edit/${deliveryOrder.value.id}`, payload, { withCredentials: true, headers: authHeader() })
.then(() => { .then(() => {
notify({ type: 'success', title: 'Success!', text: 'Delivery updated.' }); notify({ type: 'success', title: 'Success!', text: 'Delivery updated.' });
if (paymentType === 1) { if (paymentType === 1) {
this.$router.push({ name: 'payOil', params: { id: this.deliveryOrder.id } }); router.push({ name: 'payOil', params: { id: deliveryOrder.value.id } });
} else { } else {
this.$router.push({ name: 'deliveryOrder', params: { id: this.deliveryOrder.id } }); router.push({ name: 'deliveryOrder', params: { id: deliveryOrder.value.id } });
} }
}) })
.catch((error: any) => { .catch((error: any) => {
console.error("Error submitting form:", error); console.error("Error submitting form:", error);
notify({ type: 'error', title: 'Update Failed', text: 'Could not save changes.' }); notify({ type: 'error', title: 'Update Failed', text: 'Could not save changes.' });
}); });
}, }
},
// Lifecycle
onMounted(() => {
fetchInitialData();
}) })
</script> </script>

View File

@@ -55,18 +55,18 @@
</td> </td>
<td> <td>
<span class="badge badge-sm" :class="{ <span class="badge badge-sm" :class="{
'badge-warning': oil.delivery_status == 0, 'badge-warning': oil.delivery_status === computedDELIVERY_STATUS.WAITING,
'badge-success': [1, 10].includes(oil.delivery_status), 'badge-success': [computedDELIVERY_STATUS.DELIVERED, computedDELIVERY_STATUS.FINALIZED].includes(oil.delivery_status as any),
'badge-info': oil.delivery_status == 2, 'badge-info': oil.delivery_status === computedDELIVERY_STATUS.OUT_FOR_DELIVERY,
'badge-error': [3, 5].includes(oil.delivery_status), 'badge-error': [computedDELIVERY_STATUS.TOMORROW, computedDELIVERY_STATUS.ISSUE].includes(oil.delivery_status as any),
}"> }">
<span v-if="oil.delivery_status == 0">Waiting</span> <span v-if="oil.delivery_status === computedDELIVERY_STATUS.WAITING">Waiting</span>
<span v-else-if="oil.delivery_status == 1">Cancelled</span> <span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.CANCELLED">Cancelled</span>
<span v-else-if="oil.delivery_status == 2">Today</span> <span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.OUT_FOR_DELIVERY">Today</span>
<span v-else-if="oil.delivery_status == 3">Tomorrow_Delivery</span> <span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.TOMORROW">Tomorrow_Delivery</span>
<span v-else-if="oil.delivery_status == 4">Partial_Delivery</span> <span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.PARTIAL_DELIVERY">Partial_Delivery</span>
<span v-else-if="oil.delivery_status == 5">Issue</span> <span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.ISSUE">Issue</span>
<span v-else-if="oil.delivery_status == 10">Finalized</span> <span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.FINALIZED">Finalized</span>
</span> </span>
</td> </td>
<td> <td>
@@ -96,7 +96,7 @@
<div class="flex items-center justify-end gap-2"> <div class="flex items-center justify-end gap-2">
<router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-sm btn-ghost">View</router-link> <router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-sm btn-ghost">View</router-link>
<router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-sm btn-secondary">Edit</router-link> <router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
<router-link :to="{ name: 'finalizeTicket', params: { id: oil.id } }" v-if="oil.delivery_status != 10" class="btn btn-sm btn-accent">Finalize</router-link> <router-link :to="{ name: 'finalizeTicket', params: { id: oil.id } }" v-if="!isFinalizedStatus(oil.delivery_status)" class="btn btn-sm btn-accent">Finalize</router-link>
<router-link :to="{ name: 'Ticket', params: { id: oil.id } }" class="btn btn-sm btn-success">Print</router-link> <router-link :to="{ name: 'Ticket', params: { id: oil.id } }" class="btn btn-sm btn-success">Print</router-link>
</div> </div>
</td> </td>
@@ -115,18 +115,18 @@
<p class="text-xs text-gray-400">Delivery #{{ oil.id }}</p> <p class="text-xs text-gray-400">Delivery #{{ oil.id }}</p>
</div> </div>
<div class="badge" :class="{ <div class="badge" :class="{
'badge-warning': oil.delivery_status == 0, 'badge-warning': oil.delivery_status === computedDELIVERY_STATUS.WAITING,
'badge-success': [1, 10].includes(oil.delivery_status), 'badge-success': [computedDELIVERY_STATUS.DELIVERED, computedDELIVERY_STATUS.FINALIZED].includes(oil.delivery_status as any),
'badge-info': oil.delivery_status == 2, 'badge-info': oil.delivery_status === computedDELIVERY_STATUS.OUT_FOR_DELIVERY,
'badge-error': [3, 5].includes(oil.delivery_status), 'badge-error': [computedDELIVERY_STATUS.TOMORROW, computedDELIVERY_STATUS.ISSUE].includes(oil.delivery_status as any),
}"> }">
<span v-if="oil.delivery_status == 0">Waiting</span> <span v-if="oil.delivery_status === computedDELIVERY_STATUS.WAITING">Waiting</span>
<span v-else-if="oil.delivery_status == 1">Delivered</span> <span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.DELIVERED">Delivered</span>
<span v-else-if="oil.delivery_status == 2">Today_Delivery</span> <span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.OUT_FOR_DELIVERY">Today_Delivery</span>
<span v-else-if="oil.delivery_status == 3">Tommorrow_Delivery</span> <span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.TOMORROW">Tommorrow_Delivery</span>
<span v-else-if="oil.delivery_status == 4">Partial Delivery</span> <span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.PARTIAL_DELIVERY">Partial Delivery</span>
<span v-else-if="oil.delivery_status == 5">Issue</span> <span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.ISSUE">Issue</span>
<span v-else-if="oil.delivery_status == 10">Finalized</span> <span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.FINALIZED">Finalized</span>
</div> </div>
</div> </div>
@@ -149,7 +149,7 @@
<div class="card-actions justify-end flex-wrap gap-2 mt-2"> <div class="card-actions justify-end flex-wrap gap-2 mt-2">
<router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-sm btn-ghost">View</router-link> <router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-sm btn-ghost">View</router-link>
<router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-sm btn-secondary">Edit</router-link> <router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
<router-link :to="{ name: 'finalizeTicket', params: { id: oil.id } }" v-if="oil.delivery_status != 10" class="btn btn-sm btn-accent">Finalize</router-link> <router-link :to="{ name: 'finalizeTicket', params: { id: oil.id } }" v-if="!isFinalizedStatus(oil.delivery_status)" class="btn btn-sm btn-accent">Finalize</router-link>
<router-link :to="{ name: 'Ticket', params: { id: oil.id } }" class="btn btn-sm btn-success">Print</router-link> <router-link :to="{ name: 'Ticket', params: { id: oil.id } }" class="btn btn-sm btn-success">Print</router-link>
</div> </div>
</div> </div>
@@ -169,59 +169,52 @@
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted, computed } from 'vue'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../services/auth.header' import authHeader from '../../services/auth.header'
import { deliveryService } from '../../services/deliveryService'
import { Delivery } from '../../types/models'
import { DELIVERY_STATUS, DeliveryStatusType, getDeliveryStatusLabel } from '../../constants/status';
import Header from '../../layouts/headers/headerauth.vue' import Header from '../../layouts/headers/headerauth.vue'
import PaginationComp from '../../components/pagination.vue' import PaginationComp from '../../components/pagination.vue'
import SideBar from '../../layouts/sidebar/sidebar.vue' import SideBar from '../../layouts/sidebar/sidebar.vue'
import Footer from '../../layouts/footers/footer.vue' import Footer from '../../layouts/footers/footer.vue'
import { notify } from "@kyvg/vue3-notification"; import { notify } from "@kyvg/vue3-notification";
export default defineComponent({ // Reactive data
name: 'deliveryHome', const delivery_count = ref(0)
const delivery_count_delivered = ref(0)
components: { const token = ref(null)
Header, const user = ref(null)
SideBar, const deliveries = ref<Delivery[]>([])
Footer, const page = ref(1)
}, const perPage = ref(50)
const recordsLength = ref(0)
data() { const options = ref({
return {
delivery_count: 0,
delivery_count_delivered: 0,
token: null,
user: null,
deliveries: [] as any[],
page: 1,
perPage: 50,
recordsLength: 0,
options: {
edgeNavigation: false, edgeNavigation: false,
format: false, format: false,
template: PaginationComp template: PaginationComp
} })
}
},
created() { // Computed
this.userStatus() const computedDELIVERY_STATUS = computed(() => DELIVERY_STATUS)
},
mounted() {
this.getPage(this.page);
this.today_delivery_count();
this.today_delivery_delivered();
},
methods: {
getPage: function (page: any) {
// we simulate an api call that fetch the records from a backend
this.deliveries = [];
this.get_oil_orders(page)
},
userStatus() { // Functions
const isDeliveredStatus = (status: DeliveryStatusType): boolean => {
return status === DELIVERY_STATUS.DELIVERED || status === 1; // Support both old (1) and new (11) values
}
const isFinalizedStatus = (status: DeliveryStatusType): boolean => {
return status === DELIVERY_STATUS.FINALIZED;
}
const getPage = (pageVal: any) => {
deliveries.value = [];
get_oil_orders(pageVal)
}
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -231,27 +224,25 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
.catch(() => { .catch(() => {
this.user = null user.value = null
}) })
}, }
get_oil_orders(page: any) {
let path = import.meta.env.VITE_BASE_URL + '/delivery/all/' + page;
axios({
method: 'get',
url: path,
headers: authHeader(),
}).then((response: any) => {
this.deliveries = response.data
}) const get_oil_orders = async (pageVal: number) => {
}, try {
const response = await deliveryService.getAll(pageVal)
deliveries.value = response.data || []
} catch (error) {
console.error('Error fetching deliveries:', error)
deliveries.value = []
}
}
const deleteCall = (delivery_id: any) => {
deleteCall(delivery_id: any) {
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id; let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
axios({ axios({
method: 'delete', method: 'delete',
@@ -264,7 +255,7 @@ export default defineComponent({
text: "deleted delivery", text: "deleted delivery",
type: "success", type: "success",
}); });
this.getPage(this.page) getPage(page.value)
} else { } else {
notify({ notify({
title: "Failure", title: "Failure",
@@ -273,8 +264,9 @@ export default defineComponent({
}); });
} }
}) })
}, }
today_delivery_count() {
const today_delivery_count = () => {
let path = import.meta.env.VITE_BASE_URL + '/stats/delivery/count/today' let path = import.meta.env.VITE_BASE_URL + '/stats/delivery/count/today'
axios({ axios({
method: "get", method: "get",
@@ -283,10 +275,11 @@ export default defineComponent({
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: any) => {
this.delivery_count = response.data.data; delivery_count.value = response.data.data;
}) })
}, }
today_delivery_delivered() {
const today_delivery_delivered = () => {
let path = import.meta.env.VITE_BASE_URL + '/stats/delivery/count/delivered/today' let path = import.meta.env.VITE_BASE_URL + '/stats/delivery/count/delivered/today'
axios({ axios({
method: "get", method: "get",
@@ -295,10 +288,16 @@ export default defineComponent({
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: any) => {
this.delivery_count_delivered = response.data.data; delivery_count_delivered.value = response.data.data;
}) })
}, }
},
// Lifecycle
onMounted(() => {
userStatus()
getPage(page.value);
today_delivery_count();
today_delivery_delivered();
}) })
</script> </script>

View File

@@ -1,20 +1,20 @@
import DeliveryHome from './home.vue'; const DeliveryHome = () => import('./home.vue');
import DeliveryCreate from "./create.vue"; const DeliveryCreate = () => import("./create.vue");
import DeliveryEdit from './edit.vue'; const DeliveryEdit = () => import('./edit.vue');
import DeliveryOrder from './view.vue'; const DeliveryOrder = () => import('./view.vue');
import deliveryTicketsMissing from './update_tickets/missing_data_home.vue'; const deliveryTicketsMissing = () => import('./update_tickets/missing_data_home.vue');
import deliveryPending from './viewstatus/pending.vue'; const deliveryPending = () => import('./viewstatus/pending.vue');
import deliveryCancelled from './viewstatus/cancelled.vue'; const deliveryCancelled = () => import('./viewstatus/cancelled.vue');
import deliveryIssue from './viewstatus/issue.vue'; const deliveryIssue = () => import('./viewstatus/issue.vue');
import deliveryDelivered from './viewstatus/delivered.vue'; const deliveryDelivered = () => import('./viewstatus/delivered.vue');
import deliveryOutForDelivery from './viewstatus/todaysdeliveries.vue'; const deliveryOutForDelivery = () => import('./viewstatus/todaysdeliveries.vue');
import deliveryWaiting from './viewstatus/waiting.vue'; const deliveryWaiting = () => import('./viewstatus/waiting.vue');
import deliveryFinalized from './viewstatus/finalized.vue' const deliveryFinalized = () => import('./viewstatus/finalized.vue')
import deliveryTommorrow from './viewstatus/tommorrow.vue' const deliveryTommorrow = () => import('./viewstatus/tommorrow.vue')
import finalizeTicket from './update_tickets/finalize_ticket.vue'; const finalizeTicket = () => import('./update_tickets/finalize_ticket.vue');
import finalizeTicketAuto from './update_tickets/finalize_ticket_auto.vue'; const finalizeTicketAuto = () => import('./update_tickets/finalize_ticket_auto.vue');
import finalizeTicketAutoNocc from './update_tickets/finalize_ticket_auto_nocc.vue'; const finalizeTicketAutoNocc = () => import('./update_tickets/finalize_ticket_auto_nocc.vue');
const deliveryRoutes = [ const deliveryRoutes = [
{ {

View File

@@ -252,8 +252,9 @@
</div> </div>
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../../services/auth.header' import authHeader from '../../../services/auth.header'
import Header from '../../../layouts/headers/headerauth.vue' import Header from '../../../layouts/headers/headerauth.vue'
@@ -272,24 +273,23 @@ interface UserCard {
security_number: string; security_number: string;
} }
// Route and router
const route = useRoute()
const router = useRouter()
export default defineComponent({ // Reactive data
name: 'finalizeTicket', const isLoading = ref(false)
components: { Header, SideBar, Footer }, const user = ref({ id: 0 })
data() { const userCardfound = ref(false)
return { const preChargeTotal = ref(0)
isLoading: false, const FinalizeOilOrderForm = ref({
user: { id: 0 },
userCardfound: false,
preChargeTotal: 0,
FinalizeOilOrderForm: {
cash_recieved: '', cash_recieved: '',
fill_location: '', fill_location: '',
check_number: '', check_number: '',
gallons_delivered: '', gallons_delivered: '',
}, })
userCard: {} as UserCard, const userCard = ref({} as UserCard)
customer: { const customer = ref({
id: 0, id: 0,
customer_address: '', customer_address: '',
customer_first_name: '', customer_first_name: '',
@@ -300,9 +300,9 @@ export default defineComponent({
customer_apt: '', customer_apt: '',
customer_home_type: 0, customer_home_type: 0,
customer_phone_number: '', customer_phone_number: '',
}, })
customerDescription: { fill_location: '' }, const customerDescription = ref({ fill_location: '' })
deliveryOrder: { const deliveryOrder = ref({
id: '', id: '',
customer_id: 0, customer_id: 0,
gallons_ordered: 0, gallons_ordered: 0,
@@ -318,55 +318,56 @@ export default defineComponent({
payment_type: 0, payment_type: 0,
payment_card_id: '', payment_card_id: '',
promo_id: null, promo_id: null,
}, })
pricing: { const pricing = ref({
price_prime: 0, price_prime: 0,
price_same_day: 0, price_same_day: 0,
}, })
promo_active: false, const promo_active = ref(false)
promo: { const promo = ref({
name_of_promotion: '', name_of_promotion: '',
description: '', description: '',
money_off_delivery: 0, money_off_delivery: 0,
text_on_ticket: '' text_on_ticket: ''
}, })
total_amount: 0, const total_amount = ref(0)
discount: 0, const discount = ref(0)
total_amount_after_discount: 0, const total_amount_after_discount = ref(0)
transaction: null as any, const transaction = ref(null as any)
}
}, // Computed properties
computed: { const finalChargeAmount = computed((): number => {
finalChargeAmount(): number {
// If promo is active, use server-calculated totals with fees added // If promo is active, use server-calculated totals with fees added
if (this.promo_active && this.total_amount_after_discount > 0) { if (promo_active.value && total_amount_after_discount.value > 0) {
let total = this.total_amount_after_discount; let total = total_amount_after_discount.value;
if (this.deliveryOrder.prime === 1) total += Number(this.pricing.price_prime); if (deliveryOrder.value.prime === 1) total += Number(pricing.value.price_prime);
if (this.deliveryOrder.same_day === 1) total += Number(this.pricing.price_same_day); if (deliveryOrder.value.same_day === 1) total += Number(pricing.value.price_same_day);
return total; return total;
} }
// Otherwise, calculate locally // Otherwise, calculate locally
const gallons = Number(this.FinalizeOilOrderForm.gallons_delivered); const gallons = Number(FinalizeOilOrderForm.value.gallons_delivered);
const pricePerGallon = Number(this.deliveryOrder.customer_price); const pricePerGallon = Number(deliveryOrder.value.customer_price);
if (isNaN(gallons) || isNaN(pricePerGallon) || gallons <= 0) { if (isNaN(gallons) || isNaN(pricePerGallon) || gallons <= 0) {
return 0; return 0;
} }
let total = gallons * pricePerGallon; let total = gallons * pricePerGallon;
if (this.deliveryOrder.prime === 1) total += Number(this.pricing.price_prime); if (deliveryOrder.value.prime === 1) total += Number(pricing.value.price_prime);
if (this.deliveryOrder.same_day === 1) total += Number(this.pricing.price_same_day); if (deliveryOrder.value.same_day === 1) total += Number(pricing.value.price_same_day);
return total; return total;
} })
},
mounted() { // Lifecycle
const deliveryId = this.$route.params.id; onMounted(() => {
const deliveryId = route.params.id;
// --- DEBUGGING STEP 1 --- // --- DEBUGGING STEP 1 ---
console.log(`[DEBUG] Component Mounted. Fetching data for delivery ID: ${deliveryId}`); console.log(`[DEBUG] Component Mounted. Fetching data for delivery ID: ${deliveryId}`);
this.getOilOrder(deliveryId); getOilOrder(deliveryId);
this.getOilPricing(); getOilPricing();
}, })
methods: {
async getOilOrder(delivery_id: any) { // Functions
const getOilOrder = async (delivery_id: any) => {
const path = `${import.meta.env.VITE_BASE_URL}/delivery/order/${delivery_id}`; const path = `${import.meta.env.VITE_BASE_URL}/delivery/order/${delivery_id}`;
// --- DEBUGGING STEP 2 --- // --- DEBUGGING STEP 2 ---
console.log(`[DEBUG] Calling getOilOrder API at: ${path}`); console.log(`[DEBUG] Calling getOilOrder API at: ${path}`);
@@ -378,30 +379,30 @@ export default defineComponent({
if (response.data && response.data.ok) { if (response.data && response.data.ok) {
console.log('[DEBUG] Response is OK. Processing data...'); console.log('[DEBUG] Response is OK. Processing data...');
this.deliveryOrder = response.data.delivery; deliveryOrder.value = response.data.delivery;
// --- DEBUGGING STEP 4 --- // --- DEBUGGING STEP 4 ---
console.log(`[DEBUG] Value of response.data.total_amount is:`, response.data.total_amount); console.log(`[DEBUG] Value of response.data.total_amount is:`, response.data.total_amount);
this.total_amount = response.data.delivery.total_amount || 0; total_amount.value = response.data.delivery.total_amount || 0;
this.preChargeTotal = response.data.delivery.total_amount || 0; preChargeTotal.value = response.data.delivery.total_amount || 0;
await this.getCustomer(this.deliveryOrder.customer_id); await getCustomer(deliveryOrder.value.customer_id);
if ([1, 2, 11].includes(this.deliveryOrder.payment_type) && this.deliveryOrder.payment_card_id) { if ([1, 2, 11].includes(deliveryOrder.value.payment_type) && deliveryOrder.value.payment_card_id) {
this.getPaymentCard(this.deliveryOrder.payment_card_id); getPaymentCard(deliveryOrder.value.payment_card_id);
} }
if (this.deliveryOrder.promo_id != null) { if (deliveryOrder.value.promo_id != null) {
this.getPromo(this.deliveryOrder.promo_id); getPromo(deliveryOrder.value.promo_id);
} }
// Fetch calculated totals including discounts // Fetch calculated totals including discounts
this.sumdelivery(delivery_id); sumdelivery(delivery_id);
// Call transaction fetch after customer is loaded // Call transaction fetch after customer is loaded
setTimeout(() => this.getTransaction(delivery_id), 500); setTimeout(() => getTransaction(delivery_id), 500);
} else { } else {
console.error('[DEBUG] getOilOrder response was not OK or data is missing.'); console.error('[DEBUG] getOilOrder response was not OK or data is missing.');
notify({ title: "Data Error", text: "Could not retrieve complete delivery details.", type: "error" }); notify({ title: "Data Error", text: "Could not retrieve complete delivery details.", type: "error" });
@@ -411,43 +412,46 @@ export default defineComponent({
console.error("[DEBUG] The getOilOrder API call FAILED. Error object:", error); console.error("[DEBUG] The getOilOrder API call FAILED. Error object:", error);
notify({ title: "Network Error", text: "Could not fetch delivery order.", type: "error" }); notify({ title: "Network Error", text: "Could not fetch delivery order.", type: "error" });
} }
}, }
const getPaymentCard = async (card_id: any) => {
async getPaymentCard(card_id: any) {
const path = `${import.meta.env.VITE_BASE_URL}/payment/card/${card_id}`; const path = `${import.meta.env.VITE_BASE_URL}/payment/card/${card_id}`;
try { try {
const response = await axios.get(path, { withCredentials: true, headers: authHeader() }); const response = await axios.get(path, { withCredentials: true, headers: authHeader() });
this.userCard = response.data; userCard.value = response.data;
this.userCardfound = true; userCardfound.value = true;
} catch (error) { } catch (error) {
this.userCardfound = false; userCardfound.value = false;
console.error(`[DEBUG] Error fetching payment card ${card_id}:`, error); console.error(`[DEBUG] Error fetching payment card ${card_id}:`, error);
} }
}, }
async getCustomer(user_id: any) {
const getCustomer = async (user_id: any) => {
const path = `${import.meta.env.VITE_BASE_URL}/customer/${user_id}`; const path = `${import.meta.env.VITE_BASE_URL}/customer/${user_id}`;
try { try {
const response = await axios.get(path, { withCredentials: true }); const response = await axios.get(path, { withCredentials: true });
this.customer = response.data; customer.value = response.data;
await this.getCustomerDescription(this.deliveryOrder.customer_id); await getCustomerDescription(deliveryOrder.value.customer_id);
} catch (error) { console.error("[DEBUG] Error fetching customer:", error); } } catch (error) { console.error("[DEBUG] Error fetching customer:", error); }
}, }
async getCustomerDescription(user_id: any) {
const getCustomerDescription = async (user_id: any) => {
const path = `${import.meta.env.VITE_BASE_URL}/customer/description/${user_id}`; const path = `${import.meta.env.VITE_BASE_URL}/customer/description/${user_id}`;
try { try {
const response = await axios.get(path, { withCredentials: true }); const response = await axios.get(path, { withCredentials: true });
this.customerDescription = response.data; customerDescription.value = response.data;
this.FinalizeOilOrderForm.fill_location = this.customerDescription.fill_location; FinalizeOilOrderForm.value.fill_location = customerDescription.value.fill_location;
} catch (error) { console.error("[DEBUG] Error fetching customer description:", error); } } catch (error) { console.error("[DEBUG] Error fetching customer description:", error); }
}, }
getOilPricing() {
const getOilPricing = () => {
const path = `${import.meta.env.VITE_BASE_URL}/info/price/oil/table`; const path = `${import.meta.env.VITE_BASE_URL}/info/price/oil/table`;
axios.get(path, { withCredentials: true }) axios.get(path, { withCredentials: true })
.then((response: any) => { this.pricing = response.data; }) .then((response: any) => { pricing.value = response.data; })
.catch((error: any) => { console.error("[DEBUG] Error fetching oil pricing:", error); }); .catch((error: any) => { console.error("[DEBUG] Error fetching oil pricing:", error); });
}, }
getPromo(promo_id: any) {
const getPromo = (promo_id: any) => {
let path = import.meta.env.VITE_BASE_URL + "/promo/" + promo_id; let path = import.meta.env.VITE_BASE_URL + "/promo/" + promo_id;
axios({ axios({
method: "get", method: "get",
@@ -457,12 +461,13 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data) { if (response.data) {
this.promo = response.data promo.value = response.data
this.promo_active = true promo_active.value = true
} }
}) })
}, }
sumdelivery(delivery_id: any) {
const sumdelivery = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + "/delivery/total/" + delivery_id; let path = import.meta.env.VITE_BASE_URL + "/delivery/total/" + delivery_id;
axios({ axios({
method: "get", method: "get",
@@ -471,9 +476,9 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.total_amount = parseFloat(response.data.total_amount) || 0; total_amount.value = parseFloat(response.data.total_amount) || 0;
this.discount = parseFloat(response.data.discount) || 0; discount.value = parseFloat(response.data.discount) || 0;
this.total_amount_after_discount = parseFloat(response.data.total_amount_after_discount) || 0; total_amount_after_discount.value = parseFloat(response.data.total_amount_after_discount) || 0;
} }
}) })
.catch(() => { .catch(() => {
@@ -483,16 +488,17 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
getTransaction(delivery_id: any) {
const getTransaction = (delivery_id: any) => {
// Add guard to prevent undefined customer ID API calls // Add guard to prevent undefined customer ID API calls
if (!delivery_id || !this.customer || !this.customer.id) { if (!delivery_id || !customer.value || !customer.value.id) {
console.log("Skipping transaction fetch - delivery or customer data not available"); console.log("Skipping transaction fetch - delivery or customer data not available");
return; return;
} }
// Consistent with delivery/view.vue - use customer transaction endpoint // Consistent with delivery/view.vue - use customer transaction endpoint
const path = `${import.meta.env.VITE_BASE_URL}/payment/transactions/customer/${this.customer.id}/1`; const path = `${import.meta.env.VITE_BASE_URL}/payment/transactions/customer/${customer.value.id}/1`;
axios.get(path, { withCredentials: true, headers: authHeader() }) axios.get(path, { withCredentials: true, headers: authHeader() })
.then((response: any) => { .then((response: any) => {
console.log("Transaction API response:", response.data); console.log("Transaction API response:", response.data);
@@ -504,40 +510,41 @@ export default defineComponent({
txn.transaction_id === delivery_id || txn.transaction_id === delivery_id ||
txn.delivery_number === delivery_id txn.delivery_number === delivery_id
); );
this.transaction = deliveryTransaction || null; transaction.value = deliveryTransaction || null;
} else if (response.data && !Array.isArray(response.data)) { } else if (response.data && !Array.isArray(response.data)) {
// If single transaction, check if it's for this delivery // If single transaction, check if it's for this delivery
const txn = response.data; const txn = response.data;
if (txn.delivery_id === parseInt(delivery_id) || if (txn.delivery_id === parseInt(delivery_id) ||
txn.transaction_id === delivery_id || txn.transaction_id === delivery_id ||
txn.delivery_number === delivery_id) { txn.delivery_number === delivery_id) {
this.transaction = txn; transaction.value = txn;
} else { } else {
this.transaction = null; transaction.value = null;
} }
} else { } else {
this.transaction = null; transaction.value = null;
} }
if (!this.transaction) { if (!transaction.value) {
console.log(`No transaction found for delivery ${delivery_id} among customer transactions`); console.log(`No transaction found for delivery ${delivery_id} among customer transactions`);
} }
}) })
.catch((error: any) => { .catch((error: any) => {
// Handle various error responses gracefully // Handle various error responses gracefully
if (error.response && error.response.status === 404) { if (error.response && error.response.status === 404) {
console.log(`No transactions found for customer ${this.customer.id}`); console.log(`No transactions found for customer ${customer.value.id}`);
this.transaction = null; transaction.value = null;
} else if (error.response && error.response.status === 400) { } else if (error.response && error.response.status === 400) {
console.log(`Bad request for customer transactions: ${error.response.data?.detail || error.message}`); console.log(`Bad request for customer transactions: ${error.response.data?.detail || error.message}`);
this.transaction = null; transaction.value = null;
} else { } else {
console.error("Error fetching transaction:", error); console.error("Error fetching transaction:", error);
this.transaction = null; transaction.value = null;
} }
}); });
}, }
getTypeColor(transactionType: number) {
const getTypeColor = (transactionType: number) => {
switch (transactionType) { switch (transactionType) {
case 1: return 'text-blue-600'; // Auth case 1: return 'text-blue-600'; // Auth
case 0: return 'text-orange-600'; // Charge case 0: return 'text-orange-600'; // Charge
@@ -545,53 +552,55 @@ export default defineComponent({
case 3: return 'text-green-600'; // Delivery/Other case 3: return 'text-green-600'; // Delivery/Other
default: return 'text-gray-600'; default: return 'text-gray-600';
} }
}, }
CreateTransaction() {
const path = `${import.meta.env.VITE_MONEY_URL}/delivery/add/${this.deliveryOrder.id}`; const CreateTransaction = () => {
const path = `${import.meta.env.VITE_MONEY_URL}/delivery/add/${deliveryOrder.value.id}`;
axios.post(path, {}, { withCredentials: true, headers: authHeader() }) axios.post(path, {}, { withCredentials: true, headers: authHeader() })
.then(() => notify({ title: "Success", text: "Accounting record created.", type: "success" })) .then(() => notify({ title: "Success", text: "Accounting record created.", type: "success" }))
.catch(() => notify({ title: "Warning", text: "Could not create accounting record.", type: "warn" })); .catch(() => notify({ title: "Warning", text: "Could not create accounting record.", type: "warn" }));
}, }
async onSubmit() {
if (Number(this.FinalizeOilOrderForm.gallons_delivered) <= 0) { const onSubmit = async () => {
if (Number(FinalizeOilOrderForm.value.gallons_delivered) <= 0) {
notify({ title: "Validation Error", text: "Gallons delivered must be greater than zero.", type: "error" }); notify({ title: "Validation Error", text: "Gallons delivered must be greater than zero.", type: "error" });
return; return;
} }
this.isLoading = true; isLoading.value = true;
const finalizePayload = { const finalizePayload = {
gallons_delivered: this.FinalizeOilOrderForm.gallons_delivered, gallons_delivered: FinalizeOilOrderForm.value.gallons_delivered,
fill_location: this.FinalizeOilOrderForm.fill_location, fill_location: FinalizeOilOrderForm.value.fill_location,
cash_recieved: this.FinalizeOilOrderForm.cash_recieved, cash_recieved: FinalizeOilOrderForm.value.cash_recieved,
check_number: this.FinalizeOilOrderForm.check_number, check_number: FinalizeOilOrderForm.value.check_number,
}; };
const finalizePath = `${import.meta.env.VITE_BASE_URL}/deliverydata/finalize/${this.deliveryOrder.id}`; const finalizePath = `${import.meta.env.VITE_BASE_URL}/deliverydata/finalize/${deliveryOrder.value.id}`;
try { try {
const finalizeResponse = await axios.put(finalizePath, finalizePayload, { withCredentials: true, headers: authHeader() }); const finalizeResponse = await axios.put(finalizePath, finalizePayload, { withCredentials: true, headers: authHeader() });
if (!finalizeResponse.data.ok) { if (!finalizeResponse.data.ok) {
throw new Error(finalizeResponse.data.error || "Failed to update delivery details."); throw new Error(finalizeResponse.data.error || "Failed to update delivery details.");
} }
this.CreateTransaction(); CreateTransaction();
notify({ title: "Success", text: "Ticket has been finalized.", type: "success" }); notify({ title: "Success", text: "Ticket has been finalized.", type: "success" });
// FIX: Wait for customer data to be loaded before redirecting // FIX: Wait for customer data to be loaded before redirecting
await this.waitForCustomerData(this.deliveryOrder.customer_id); await waitForCustomerData(deliveryOrder.value.customer_id);
// Updated redirect logic based on your requirements // Updated redirect logic based on your requirements
await this.handleRedirect(); await handleRedirect();
} catch (error: any) { } catch (error: any) {
const errorMessage = error.response?.data?.detail || "An error occurred during finalization."; const errorMessage = error.response?.data?.detail || "An error occurred during finalization.";
notify({ title: "Error", text: errorMessage, type: "error" }); notify({ title: "Error", text: errorMessage, type: "error" });
} finally { } finally {
this.isLoading = false; isLoading.value = false;
} }
}, }
// NEW: Wait for customer data to be loaded before redirecting // NEW: Wait for customer data to be loaded before redirecting
async waitForCustomerData(customerId: number): Promise<void> { const waitForCustomerData = async (customerId: number): Promise<void> => {
return new Promise((resolve) => { return new Promise((resolve) => {
const checkCustomer = () => { const checkCustomer = () => {
if (this.customer && this.customer.id && this.customer.id === customerId) { if (customer.value && customer.value.id && customer.value.id === customerId) {
resolve(); resolve();
} else { } else {
setTimeout(checkCustomer, 100); setTimeout(checkCustomer, 100);
@@ -599,62 +608,60 @@ export default defineComponent({
}; };
checkCustomer(); checkCustomer();
}); });
}, }
// NEW: Updated redirect logic based on payment type and transaction status // NEW: Updated redirect logic based on payment type and transaction status
async handleRedirect() { const handleRedirect = async () => {
console.log('[DEBUG] Starting redirect logic...'); console.log('[DEBUG] Starting redirect logic...');
console.log('[DEBUG] payment_type:', this.deliveryOrder.payment_type); console.log('[DEBUG] payment_type:', deliveryOrder.value.payment_type);
console.log('[DEBUG] transaction:', this.transaction); console.log('[DEBUG] transaction:', transaction.value);
console.log('[DEBUG] customer:', this.customer); console.log('[DEBUG] customer:', customer.value);
if (this.deliveryOrder.payment_type === 1) { if (deliveryOrder.value.payment_type === 1) {
// payment_type 1: Manual charging - already charged, just redirect to profile // payment_type 1: Manual charging - already charged, just redirect to profile
console.log('[DEBUG] payment_type 1 - redirecting to customer profile'); console.log('[DEBUG] payment_type 1 - redirecting to customer profile');
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } }); router.push({ name: "customerProfile", params: { id: customer.value.id } });
} else if (this.deliveryOrder.payment_type === 11) { } else if (deliveryOrder.value.payment_type === 11) {
// payment_type 11: API charging - check transaction type // payment_type 11: API charging - check transaction type
console.log('[DEBUG] payment_type 11 - checking transaction type'); console.log('[DEBUG] payment_type 11 - checking transaction type');
if (this.transaction) { if (transaction.value) {
console.log('[DEBUG] Transaction found, type:', this.transaction.transaction_type); console.log('[DEBUG] Transaction found, type:', transaction.value.transaction_type);
if (this.transaction.transaction_type === 0) { if (transaction.value.transaction_type === 0) {
// Already charged (transaction_type = 0) - redirect to profile // Already charged (transaction_type = 0) - redirect to profile
console.log('[DEBUG] Already charged - redirecting to customer profile'); console.log('[DEBUG] Already charged - redirecting to customer profile');
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } }); router.push({ name: "customerProfile", params: { id: customer.value.id } });
} else if (this.transaction.transaction_type === 1) { } else if (transaction.value.transaction_type === 1) {
// Auth only (transaction_type = 1) - redirect to capture page // Auth only (transaction_type = 1) - redirect to capture page
console.log('[DEBUG] Auth only - redirecting to capture page'); console.log('[DEBUG] Auth only - redirecting to capture page');
this.$router.push({ name: "captureAuthorize", params: { id: this.deliveryOrder.id } }); router.push({ name: "captureAuthorize", params: { id: deliveryOrder.value.id } });
} else { } else {
// Unknown transaction type - default to customer profile // Unknown transaction type - default to customer profile
console.log('[DEBUG] Unknown transaction type - redirecting to customer profile'); console.log('[DEBUG] Unknown transaction type - redirecting to customer profile');
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } }); router.push({ name: "customerProfile", params: { id: customer.value.id } });
} }
} else { } else {
// No transaction found for payment_type 11 - redirect to customer profile // No transaction found for payment_type 11 - redirect to customer profile
console.log('[DEBUG] No transaction found for payment_type 11 - redirecting to customer profile'); console.log('[DEBUG] No transaction found for payment_type 11 - redirecting to customer profile');
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } }); router.push({ name: "customerProfile", params: { id: customer.value.id } });
} }
} else if ([2].includes(this.deliveryOrder.payment_type)) { } else if ([2].includes(deliveryOrder.value.payment_type)) {
// payment_type 2: Credit Card & Cash - go to capture page for any remaining payment // payment_type 2: Credit Card & Cash - go to capture page for any remaining payment
console.log('[DEBUG] payment_type 2 - redirecting to capture page'); console.log('[DEBUG] payment_type 2 - redirecting to capture page');
this.$router.push({ name: "captureAuthorize", params: { id: this.deliveryOrder.id } }); router.push({ name: "captureAuthorize", params: { id: deliveryOrder.value.id } });
} else { } else {
// Default case (cash, check, etc.) - redirect to customer profile // Default case (cash, check, etc.) - redirect to customer profile
console.log('[DEBUG] Default payment type - redirecting to customer profile'); console.log('[DEBUG] Default payment type - redirecting to customer profile');
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } }); router.push({ name: "customerProfile", params: { id: customer.value.id } });
} }
}, }
},
});
</script> </script>
<style scoped></style> <style scoped></style>

View File

@@ -78,7 +78,7 @@
<div> <div>
<div class="font-bold text-sm">Payment Method</div> <div class="font-bold text-sm">Payment Method</div>
<div v-if="!userCardfound" class="text-warning text-sm">No card on file for this customer.</div> <div v-if="!userCardfound" class="text-warning text-sm">No card on file for this customer.</div>
<div v-if="userCardfound" class="mt-2 p-3 rounded-lg border bg-primary/10 border-primary"> <div v-if="userCardfound && userCard" class="mt-2 p-3 rounded-lg border bg-primary/10 border-primary">
<div class="font-bold text-sm">{{ userCard.name_on_card }}</div> <div class="font-bold text-sm">{{ userCard.name_on_card }}</div>
<div class="text-xs opacity-70">{{ userCard.type_of_card }}</div> <div class="text-xs opacity-70">{{ userCard.type_of_card }}</div>
<div class="mt-1 text-sm font-mono tracking-wider"> <div class="mt-1 text-sm font-mono tracking-wider">
@@ -119,40 +119,34 @@
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted, watch } from 'vue'
import axios from 'axios' import { useRoute, useRouter } from 'vue-router'
import authHeader from '../../../services/auth.header' import axios from 'axios'
import Header from '../../../layouts/headers/headerauth.vue' import authHeader from '../../../services/auth.header'
import SideBar from '../../../layouts/sidebar/sidebar.vue' import Header from '../../../layouts/headers/headerauth.vue'
import Footer from '../../../layouts/footers/footer.vue' import SideBar from '../../../layouts/sidebar/sidebar.vue'
import useValidate from "@vuelidate/core"; import Footer from '../../../layouts/footers/footer.vue'
import { notify } from "@kyvg/vue3-notification" import useValidate from "@vuelidate/core";
import { notify } from "@kyvg/vue3-notification"
// Route and router
const route = useRoute()
const router = useRouter()
export default defineComponent({ // Reactive data
name: 'finalizeTicketAuto', const v$ = useValidate()
const loaded = ref(false)
components: { const user = ref({
Header,
SideBar,
Footer,
},
data() {
return {
v$: useValidate(),
loaded: false,
user: {
id: 0 id: 0
}, })
userCardfound: false, const userCardfound = ref(false)
deliveryStatus: [], const deliveryStatus = ref([])
userCards: [], const userCards = ref([])
deliveryNotesDriver: [], const deliveryNotesDriver = ref([])
today_oil_price: 0, const today_oil_price = ref(0)
FinalizeOilOrderForm: { const FinalizeOilOrderForm = ref({
fill_location: 0, fill_location: 0,
check_number: 0, check_number: 0,
delivery_status: 10, delivery_status: 10,
@@ -164,27 +158,27 @@
prime: false, prime: false,
same_day: false, same_day: false,
emergency: false, emergency: false,
}, })
CreateOilOrderForm: { const CreateOilOrderForm = ref({
basicInfo: { basicInfo: {
gallons_delivered: '', gallons_delivered: '',
userCards: [] userCards: []
}, },
}, })
userCard: { const userCard = ref<{
date_added: '', date_added: string;
user_id: '', user_id: string;
card_number: '', card_number: string;
last_four_digits: '', last_four_digits: string;
name_on_card: '', name_on_card: string;
expiration_month: '', expiration_month: string;
expiration_year: '', expiration_year: string;
type_of_card: '', type_of_card: string;
security_number: '', security_number: string;
accepted_or_declined: '', accepted_or_declined: string;
main_card: '', main_card: string;
}, } | null>(null)
customer: { const customer = ref({
id: 0, id: 0,
user_id: 0, user_id: 0,
customer_address: '', customer_address: '',
@@ -196,17 +190,16 @@
customer_apt: '', customer_apt: '',
customer_home_type: 0, customer_home_type: 0,
customer_phone_number: '', customer_phone_number: '',
}, })
customerDescription: { const customerDescription = ref({
customer_id: 0, customer_id: 0,
account_number: '', account_number: '',
company_id: 0, company_id: 0,
fill_location: 0, fill_location: 0,
description: '', description: '',
}, })
const autoTicket = ref({
autoTicket: {
id: 0, id: 0,
customer_id: '', customer_id: '',
account_number: '', account_number: '',
@@ -229,9 +222,9 @@
payment_status : '', payment_status : '',
open_ticket_id: 0 open_ticket_id: 0
}, })
autoDelivery: { const autoDelivery = ref({
id: 0, id: 0,
customer_id: 0, customer_id: 0,
account_number: '', account_number: '',
@@ -250,29 +243,23 @@
house_factor: 0, house_factor: 0,
auto_status: 0, auto_status: 0,
open_ticket_id: null, open_ticket_id: null,
}, })
} // Watchers
}, watch(() => route.params, () => {
today_price_oil()
getAutoTicket(route.params.id)
}, { immediate: false })
created() { // Lifecycle
this.userStatus() onMounted(() => {
}, userStatus()
watch: { today_price_oil()
$route() { getAutoTicket(route.params.id)
this.today_price_oil(); })
this.getAutoTicket(this.$route.params.id);
},
},
mounted() {
this.today_price_oil();
this.getAutoTicket(this.$route.params.id);
// Functions
}, const userStatus = () => {
methods: {
userStatus() {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -282,14 +269,13 @@
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
this.user.id = response.data.user_id; user.value.id = response.data.user_id;
} }
}) })
}, }
const getPaymentCard = (card_id: any) => {
getPaymentCard(card_id: any) {
let path = import.meta.env.VITE_BASE_URL + "/payment/card/" + card_id; let path = import.meta.env.VITE_BASE_URL + "/payment/card/" + card_id;
axios({ axios({
method: "get", method: "get",
@@ -299,19 +285,20 @@
.then((response: any) => { .then((response: any) => {
if (response.data.userCard.card_number === ''){ if (response.data.userCard.card_number === ''){
this.userCard === null; userCard.value = null;
this.userCardfound = false; userCardfound.value = false;
} }
else{ else{
this.userCard = response.data; userCard.value = response.data;
this.userCardfound = true; userCardfound.value = true;
} }
this.FinalizeOilOrderForm.userCards = response.data.id FinalizeOilOrderForm.value.userCards = response.data.id
}) })
.catch(() => { .catch(() => {
}); });
}, }
getPaymentCards(user_id: any) {
const getPaymentCards = (user_id: any) => {
let path = import.meta.env.VITE_BASE_URL + "/payment/cards/" + user_id; let path = import.meta.env.VITE_BASE_URL + "/payment/cards/" + user_id;
axios({ axios({
method: "get", method: "get",
@@ -319,16 +306,17 @@
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: any) => {
this.userCards = response.data; userCards.value = response.data;
if (this.userCards && this.userCards.length > 0) { if (userCards.value && userCards.value.length > 0) {
this.userCardfound = true; userCardfound.value = true;
this.userCard = this.userCards.find((card: any) => card.main_card) || this.userCards[0]; userCard.value = userCards.value.find((card: any) => card.main_card) || userCards.value[0];
} }
}) })
.catch(() => { .catch(() => {
}); });
}, }
getCustomer(user_id: any) {
const getCustomer = (user_id: any) => {
if (!user_id || user_id === 'undefined') return; if (!user_id || user_id === 'undefined') return;
let path = import.meta.env.VITE_BASE_URL + "/customer/" + user_id; let path = import.meta.env.VITE_BASE_URL + "/customer/" + user_id;
axios({ axios({
@@ -337,9 +325,9 @@
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: any) => {
this.customer = response.data; customer.value = response.data;
if (this.customer.id > 0) { if (customer.value.id > 0) {
this.getPaymentCards(this.customer.user_id || this.customer.id); getPaymentCards(customer.value.user_id || customer.value.id);
} }
}) })
.catch(() => { .catch(() => {
@@ -348,10 +336,11 @@
text: "Could not find customer", text: "Could not find customer",
type: "error", type: "error",
}); });
this.customer = { id: 0, user_id: 0, customer_address: '', customer_first_name: '', customer_last_name: '', customer_town: '', customer_state: 0, customer_zip: '', customer_apt: '', customer_home_type: 0, customer_phone_number: '' }; customer.value = { id: 0, user_id: 0, customer_address: '', customer_first_name: '', customer_last_name: '', customer_town: '', customer_state: 0, customer_zip: '', customer_apt: '', customer_home_type: 0, customer_phone_number: '' };
}); });
}, }
getCustomerDescription(user_id: any) {
const getCustomerDescription = (user_id: any) => {
let path = import.meta.env.VITE_BASE_URL + "/customer/description/" + user_id; let path = import.meta.env.VITE_BASE_URL + "/customer/description/" + user_id;
axios({ axios({
method: "get", method: "get",
@@ -359,8 +348,8 @@
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: any) => {
this.customerDescription = response.data; customerDescription.value = response.data;
this.loaded = true loaded.value = true
}) })
.catch(() => { .catch(() => {
notify({ notify({
@@ -369,8 +358,9 @@
type: "error", type: "error",
}); });
}); });
}, }
getAutoTicket(delivery_id: any) {
const getAutoTicket = (delivery_id: any) => {
let path = import.meta.env.VITE_AUTO_URL + "/delivery/autoticket/" + delivery_id; let path = import.meta.env.VITE_AUTO_URL + "/delivery/autoticket/" + delivery_id;
axios({ axios({
method: "get", method: "get",
@@ -378,11 +368,11 @@
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: any) => {
this.autoTicket = response.data; autoTicket.value = response.data;
this.getCustomer(this.autoTicket.customer_id) getCustomer(autoTicket.value.customer_id)
this.getAutoDelivery(this.autoTicket.id) getAutoDelivery(autoTicket.value.id)
this.getCustomerDescription(this.autoTicket.customer_id) getCustomerDescription(autoTicket.value.customer_id)
}) })
.catch(() => { .catch(() => {
@@ -392,9 +382,9 @@
type: "error", type: "error",
}); });
}); });
}, }
getAutoDelivery(delivery_id: any) { const getAutoDelivery = (delivery_id: any) => {
let path = import.meta.env.VITE_AUTO_URL + "/delivery/finddelivery/" + delivery_id; let path = import.meta.env.VITE_AUTO_URL + "/delivery/finddelivery/" + delivery_id;
axios({ axios({
method: "get", method: "get",
@@ -403,9 +393,9 @@
}) })
.then((response: any) => { .then((response: any) => {
this.autoDelivery = response.data; autoDelivery.value = response.data;
this.getCustomer(this.autoDelivery.customer_id) getCustomer(autoDelivery.value.customer_id)
this.getCustomerDescription(this.autoDelivery.customer_id) getCustomerDescription(autoDelivery.value.customer_id)
}) })
.catch(() => { .catch(() => {
@@ -415,8 +405,9 @@
type: "error", type: "error",
}); });
}); });
}, }
today_price_oil() {
const today_price_oil = () => {
let path = import.meta.env.VITE_BASE_URL + '/info/price/oil' let path = import.meta.env.VITE_BASE_URL + '/info/price/oil'
axios({ axios({
method: "get", method: "get",
@@ -425,13 +416,14 @@
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: any) => {
this.today_oil_price = response.data.price_for_customer; today_oil_price.value = response.data.price_for_customer;
}) })
}, }
UpdateAuto(payload: {
const UpdateAuto = (payload: {
gallons: string, gallons: string,
delivery_id: string, delivery_id: string,
}) { }) => {
let path = import.meta.env.VITE_AUTO_URL + "/confirm/delivery" let path = import.meta.env.VITE_AUTO_URL + "/confirm/delivery"
axios({ axios({
method: "put", method: "put",
@@ -447,7 +439,7 @@
type: 'postive', type: 'postive',
title: 'top' title: 'top'
}) })
this.$router.push({ name: "auto" }); router.push({ name: "auto" });
} }
else { else {
notify({ notify({
@@ -457,13 +449,12 @@
}) })
} }
}) })
}, }
const ConfirmAuto = (payload: {
ConfirmAuto(payload: {
gallons_delivered: string, gallons_delivered: string,
}) { }) => {
let path = import.meta.env.VITE_AUTO_URL + "/confirm/auto/create/" + this.autoDelivery.id; let path = import.meta.env.VITE_AUTO_URL + "/confirm/auto/create/" + autoDelivery.value.id;
axios({ axios({
method: "post", method: "post",
url: path, url: path,
@@ -479,9 +470,9 @@
text: "Auto Delivered", text: "Auto Delivered",
type: "success", type: "success",
}); });
this.CreateTransaction(response.data['0']['auto_ticket_id']) CreateTransaction(response.data['0']['auto_ticket_id'])
this.updateTransactionDelivery(this.autoDelivery.id, response.data['0']['auto_ticket_id']) updateTransactionDelivery(autoDelivery.value.id, response.data['0']['auto_ticket_id'])
this.$router.push({ name: "payAutoCapture", params: { id: response.data['0']['auto_ticket_id'] } }); router.push({ name: "payAutoCapture", params: { id: response.data['0']['auto_ticket_id'] } });
} }
if (response.data.error) { if (response.data.error) {
@@ -490,13 +481,12 @@
text: "Could not finalize auto", text: "Could not finalize auto",
type: "error", type: "error",
}); });
this.$router.push("auto"); router.push("auto");
} }
}) })
}, }
const closeTicket = (ticketId: number) => {
closeTicket(ticketId: number) {
let path = import.meta.env.VITE_AUTO_URL + "/confirm/auto/close_ticket/" + ticketId; let path = import.meta.env.VITE_AUTO_URL + "/confirm/auto/close_ticket/" + ticketId;
axios({ axios({
method: "put", method: "put",
@@ -507,13 +497,13 @@
.then(() => { .then(() => {
// Ticket closed successfully // Ticket closed successfully
}) })
}, }
UpdateDeliveredAuto(payload: { const UpdateDeliveredAuto = (payload: {
gallons_delivered: string, gallons_delivered: string,
}) { }) => {
console.log(this.autoDelivery) console.log(autoDelivery.value)
let path = import.meta.env.VITE_AUTO_URL + "/confirm/auto/update/" + this.autoDelivery.id; let path = import.meta.env.VITE_AUTO_URL + "/confirm/auto/update/" + autoDelivery.value.id;
axios({ axios({
method: "put", method: "put",
url: path, url: path,
@@ -531,16 +521,16 @@
// Removed redirect from here, will handle in onSubmit // Removed redirect from here, will handle in onSubmit
} }
}) })
}, }
updateTransactionDelivery(current_delivery_id: any, new_delivery_id: any) { const updateTransactionDelivery = (current_delivery_id: any, new_delivery_id: any) => {
const path = `${import.meta.env.VITE_AUTHORIZE_URL}/api/auto/transaction/delivery/${current_delivery_id}/update/${new_delivery_id}`; const path = `${import.meta.env.VITE_AUTHORIZE_URL}/api/auto/transaction/delivery/${current_delivery_id}/update/${new_delivery_id}`;
axios.put(path, {}, { withCredentials: true, headers: authHeader() }) axios.put(path, {}, { withCredentials: true, headers: authHeader() })
.then(() => console.log("Transaction auto_id updated")) .then(() => console.log("Transaction auto_id updated"))
.catch(() => console.error("Error updating transaction auto_id")); .catch(() => console.error("Error updating transaction auto_id"));
}, }
CreateTransaction(auto_ticket_id: string,) { const CreateTransaction = (auto_ticket_id: string) => {
let path = import.meta.env.VITE_MONEY_URL + "/delivery/add/auto/" + auto_ticket_id; let path = import.meta.env.VITE_MONEY_URL + "/delivery/add/auto/" + auto_ticket_id;
axios({ axios({
method: "post", method: "post",
@@ -564,26 +554,23 @@
}) })
} }
}) })
}, }
onSubmit() {
const onSubmit = () => {
let payload = { let payload = {
gallons_delivered: this.FinalizeOilOrderForm.gallons_delivered, gallons_delivered: FinalizeOilOrderForm.value.gallons_delivered,
}; };
this.UpdateDeliveredAuto(payload); UpdateDeliveredAuto(payload);
if (this.autoTicket.payment_status == '1') { if (autoTicket.value.payment_status == '1') {
// Pre-authorized: redirect to capture page // Pre-authorized: redirect to capture page
this.$router.push({ name: "payAutoCapture", params: { id: this.autoTicket.id } }); router.push({ name: "payAutoCapture", params: { id: autoTicket.value.id } });
} else { } else {
// Fully charged: close ticket // Fully charged: close ticket
if (this.autoDelivery.open_ticket_id) { if (autoDelivery.value.open_ticket_id) {
this.closeTicket(this.autoDelivery.open_ticket_id); closeTicket(autoDelivery.value.open_ticket_id);
} }
this.$router.push({ name: "auto" }); router.push({ name: "auto" });
} }
}, </script>
}, <style scoped></style>
})
</script>
<style scoped></style>

View File

@@ -78,7 +78,7 @@
<div> <div>
<div class="font-bold text-sm">Payment Method</div> <div class="font-bold text-sm">Payment Method</div>
<div v-if="!userCardfound" class="text-warning text-sm">No card on file for this customer.</div> <div v-if="!userCardfound" class="text-warning text-sm">No card on file for this customer.</div>
<div v-if="userCardfound" class="mt-2 p-3 rounded-lg border bg-primary/10 border-primary"> <div v-if="userCardfound && userCard" class="mt-2 p-3 rounded-lg border bg-primary/10 border-primary">
<div class="font-bold text-sm">{{ userCard.name_on_card }}</div> <div class="font-bold text-sm">{{ userCard.name_on_card }}</div>
<div class="text-xs opacity-70">{{ userCard.type_of_card }}</div> <div class="text-xs opacity-70">{{ userCard.type_of_card }}</div>
<div class="mt-1 text-sm font-mono tracking-wider"> <div class="mt-1 text-sm font-mono tracking-wider">
@@ -121,8 +121,9 @@
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../../services/auth.header' import authHeader from '../../../services/auth.header'
import Header from '../../../layouts/headers/headerauth.vue' import Header from '../../../layouts/headers/headerauth.vue'
@@ -131,30 +132,23 @@ import Footer from '../../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core"; import useValidate from "@vuelidate/core";
import { notify } from "@kyvg/vue3-notification" import { notify } from "@kyvg/vue3-notification"
// Route and router
const route = useRoute()
const router = useRouter()
export default defineComponent({ // Reactive data
name: 'finalizeTicketAuto', const v$ = useValidate()
const loaded = ref(false)
components: { const user = ref({
Header,
SideBar,
Footer,
},
data() {
return {
v$: useValidate(),
loaded: false,
user: {
id: 0 id: 0
}, })
userCardfound: false, const userCardfound = ref(false)
deliveryStatus: [], const deliveryStatus = ref([])
userCards: [], const userCards = ref([])
deliveryNotesDriver: [], const deliveryNotesDriver = ref([])
today_oil_price: 0, const today_oil_price = ref(0)
FinalizeOilOrderForm: { const FinalizeOilOrderForm = ref({
fill_location: 0, fill_location: 0,
check_number: 0, check_number: 0,
delivery_status: 10, delivery_status: 10,
@@ -166,27 +160,27 @@ export default defineComponent({
prime: false, prime: false,
same_day: false, same_day: false,
emergency: false, emergency: false,
}, })
CreateOilOrderForm: { const CreateOilOrderForm = ref({
basicInfo: { basicInfo: {
gallons_delivered: '', gallons_delivered: '',
userCards: [] userCards: []
}, },
}, })
userCard: { const userCard = ref<{
date_added: '', date_added: string;
user_id: '', user_id: string;
card_number: '', card_number: string;
last_four_digits: '', last_four_digits: string;
name_on_card: '', name_on_card: string;
expiration_month: '', expiration_month: string;
expiration_year: '', expiration_year: string;
type_of_card: '', type_of_card: string;
security_number: '', security_number: string;
accepted_or_declined: '', accepted_or_declined: string;
main_card: '', main_card: string;
}, } | null>(null)
customer: { const customer = ref({
id: 0, id: 0,
user_id: 0, user_id: 0,
customer_address: '', customer_address: '',
@@ -198,17 +192,16 @@ export default defineComponent({
customer_apt: '', customer_apt: '',
customer_home_type: 0, customer_home_type: 0,
customer_phone_number: '', customer_phone_number: '',
}, })
customerDescription: { const customerDescription = ref({
customer_id: 0, customer_id: 0,
account_number: '', account_number: '',
company_id: 0, company_id: 0,
fill_location: 0, fill_location: 0,
description: '', description: '',
}, })
const autoTicket = ref({
autoTicket: {
id: 0, id: 0,
customer_id: '', customer_id: '',
account_number: '', account_number: '',
@@ -231,9 +224,9 @@ export default defineComponent({
payment_status: '', payment_status: '',
open_ticket_id: 0 open_ticket_id: 0
}, })
autoDelivery: { const autoDelivery = ref({
id: 0, id: 0,
customer_id: 0, customer_id: 0,
account_number: '', account_number: '',
@@ -252,29 +245,23 @@ export default defineComponent({
house_factor: 0, house_factor: 0,
auto_status: 0, auto_status: 0,
open_ticket_id: null, open_ticket_id: null,
}, })
} // Watchers
}, watch(() => route.params, () => {
today_price_oil()
getAutoTicket(route.params.id)
}, { immediate: false })
created() { // Lifecycle
this.userStatus() onMounted(() => {
}, userStatus()
watch: { today_price_oil()
$route() { getAutoDelivery(route.params.id)
this.today_price_oil(); })
this.getAutoTicket(this.$route.params.id);
},
},
mounted() {
this.today_price_oil();
this.getAutoDelivery(this.$route.params.id);
// Functions
}, const userStatus = () => {
methods: {
userStatus() {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -284,14 +271,13 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
this.user.id = response.data.user_id; user.value.id = response.data.user_id;
} }
}) })
}, }
const getPaymentCard = (card_id: any) => {
getPaymentCard(card_id: any) {
let path = import.meta.env.VITE_BASE_URL + "/payment/card/" + card_id; let path = import.meta.env.VITE_BASE_URL + "/payment/card/" + card_id;
axios({ axios({
method: "get", method: "get",
@@ -301,19 +287,20 @@ export default defineComponent({
.then((response: any) => { .then((response: any) => {
if (response.data.userCard.card_number === '') { if (response.data.userCard.card_number === '') {
this.userCard === null; userCard.value = null;
this.userCardfound = false; userCardfound.value = false;
} }
else { else {
this.userCard = response.data; userCard.value = response.data;
this.userCardfound = true; userCardfound.value = true;
} }
this.FinalizeOilOrderForm.userCards = response.data.id FinalizeOilOrderForm.value.userCards = response.data.id
}) })
.catch(() => { .catch(() => {
}); });
}, }
getPaymentCards(user_id: any) {
const getPaymentCards = (user_id: any) => {
let path = import.meta.env.VITE_BASE_URL + "/payment/cards/" + user_id; let path = import.meta.env.VITE_BASE_URL + "/payment/cards/" + user_id;
axios({ axios({
method: "get", method: "get",
@@ -321,40 +308,43 @@ export default defineComponent({
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: any) => {
this.userCards = response.data; userCards.value = response.data;
if (this.userCards && this.userCards.length > 0) { if (userCards.value && userCards.value.length > 0) {
this.userCardfound = true; userCardfound.value = true;
this.userCard = this.userCards.find((card: any) => card.main_card) || this.userCards[0]; userCard.value = userCards.value.find((card: any) => card.main_card) || userCards.value[0];
} }
}) })
.catch(() => { .catch(() => {
}); });
}, }
getCustomer(userid: any) {
const getCustomer = (userid: any) => {
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid; let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: any) => {
this.customer = response.data customer.value = response.data
}) })
}, }
getCreditCards(userid: any) {
const getCreditCards = (userid: any) => {
let path = import.meta.env.VITE_BASE_URL + '/payment/cards/' + userid; let path = import.meta.env.VITE_BASE_URL + '/payment/cards/' + userid;
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: any) => {
this.userCards = response.data; userCards.value = response.data;
if (this.userCards && this.userCards.length > 0) { if (userCards.value && userCards.value.length > 0) {
this.userCardfound = true; userCardfound.value = true;
this.userCard = this.userCards.find((card: any) => card.main_card) || this.userCards[0]; userCard.value = userCards.value.find((card: any) => card.main_card) || userCards.value[0];
} }
}) })
}, }
getCustomerDescription(user_id: any) {
const getCustomerDescription = (user_id: any) => {
let path = import.meta.env.VITE_BASE_URL + "/customer/description/" + user_id; let path = import.meta.env.VITE_BASE_URL + "/customer/description/" + user_id;
axios({ axios({
method: "get", method: "get",
@@ -362,8 +352,8 @@ export default defineComponent({
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: any) => {
this.customerDescription = response.data; customerDescription.value = response.data;
this.loaded = true loaded.value = true
}) })
.catch(() => { .catch(() => {
notify({ notify({
@@ -372,8 +362,9 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
getAutoTicket(delivery_id: any) {
const getAutoTicket = (delivery_id: any) => {
let path = import.meta.env.VITE_AUTO_URL + "/delivery/autoticket/" + delivery_id; let path = import.meta.env.VITE_AUTO_URL + "/delivery/autoticket/" + delivery_id;
axios({ axios({
method: "get", method: "get",
@@ -381,11 +372,11 @@ export default defineComponent({
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: any) => {
this.autoTicket = response.data; autoTicket.value = response.data;
this.getCustomer(this.autoTicket.customer_id) getCustomer(autoTicket.value.customer_id)
this.getAutoDelivery(this.autoTicket.id) getAutoDelivery(autoTicket.value.id)
this.getCustomerDescription(this.autoTicket.customer_id) getCustomerDescription(autoTicket.value.customer_id)
}) })
.catch(() => { .catch(() => {
@@ -395,9 +386,9 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
getAutoDelivery(delivery_id: any) { const getAutoDelivery = (delivery_id: any) => {
let path = import.meta.env.VITE_AUTO_URL + "/delivery/delivery/" + delivery_id; let path = import.meta.env.VITE_AUTO_URL + "/delivery/delivery/" + delivery_id;
axios({ axios({
method: "get", method: "get",
@@ -406,9 +397,9 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data && response.data.customer_id) { if (response.data && response.data.customer_id) {
this.autoDelivery = response.data; autoDelivery.value = response.data;
this.getCustomer(this.autoDelivery.customer_id) getCustomer(autoDelivery.value.customer_id)
this.getCreditCards(this.autoDelivery.customer_id) getCreditCards(autoDelivery.value.customer_id)
} else { } else {
console.error("API Error:", response.data.error || "Failed to fetch auto delivery data."); console.error("API Error:", response.data.error || "Failed to fetch auto delivery data.");
} }
@@ -421,8 +412,9 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
today_price_oil() {
const today_price_oil = () => {
let path = import.meta.env.VITE_BASE_URL + '/info/price/oil' let path = import.meta.env.VITE_BASE_URL + '/info/price/oil'
axios({ axios({
method: "get", method: "get",
@@ -431,13 +423,14 @@ export default defineComponent({
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: any) => {
this.today_oil_price = response.data.price_for_customer; today_oil_price.value = response.data.price_for_customer;
}) })
}, }
UpdateAuto(payload: {
const UpdateAuto = (payload: {
gallons: string, gallons: string,
delivery_id: string, delivery_id: string,
}) { }) => {
let path = import.meta.env.VITE_AUTO_URL + "/confirm/delivery" let path = import.meta.env.VITE_AUTO_URL + "/confirm/delivery"
axios({ axios({
method: "put", method: "put",
@@ -453,7 +446,7 @@ export default defineComponent({
type: 'postive', type: 'postive',
title: 'top' title: 'top'
}) })
this.$router.push({ name: "auto" }); router.push({ name: "auto" });
} }
else { else {
notify({ notify({
@@ -463,9 +456,9 @@ export default defineComponent({
}) })
} }
}) })
}, }
CreateTransaction(auto_ticket_id: string,) { const CreateTransaction = (auto_ticket_id: string) => {
let path = import.meta.env.VITE_MONEY_URL + "/delivery/add/auto/" + auto_ticket_id; let path = import.meta.env.VITE_MONEY_URL + "/delivery/add/auto/" + auto_ticket_id;
axios({ axios({
method: "post", method: "post",
@@ -489,12 +482,12 @@ export default defineComponent({
}) })
} }
}) })
}, }
ConfirmAuto(payload: { const ConfirmAuto = (payload: {
gallons_delivered: string, gallons_delivered: string,
}) { }) => {
let path = import.meta.env.VITE_AUTO_URL + "/confirm/auto/create/nopreauth/" + this.autoDelivery.id; let path = import.meta.env.VITE_AUTO_URL + "/confirm/auto/create/nopreauth/" + autoDelivery.value.id;
axios({ axios({
method: "post", method: "post",
url: path, url: path,
@@ -509,7 +502,7 @@ export default defineComponent({
text: "Auto Delivered", text: "Auto Delivered",
type: "success", type: "success",
}); });
this.CreateTransaction(response.data['0']['auto_ticket_id']) CreateTransaction(response.data['0']['auto_ticket_id'])
} }
if (response.data.error) { if (response.data.error) {
notify({ notify({
@@ -517,22 +510,19 @@ export default defineComponent({
text: "Could not finalize auto", text: "Could not finalize auto",
type: "error", type: "error",
}); });
this.$router.push("auto"); router.push("auto");
} }
}) })
}, }
onSubmit() {
const onSubmit = () => {
let payload = { let payload = {
gallons_delivered: this.FinalizeOilOrderForm.gallons_delivered, gallons_delivered: FinalizeOilOrderForm.value.gallons_delivered,
}; };
this.ConfirmAuto(payload) ConfirmAuto(payload)
this.$router.push({ name: "auto" }); router.push({ name: "auto" });
}
},
},
})
</script> </script>
<style scoped></style> <style scoped></style>

View File

@@ -71,43 +71,27 @@
<Footer/> <Footer/>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import {defineComponent} from 'vue' import { ref, onMounted } from 'vue'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../../services/auth.header' import authHeader from '../../../services/auth.header'
import Header from '../../../layouts/headers/headerauth.vue' import Header from '../../../layouts/headers/headerauth.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue' import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue' import Footer from '../../../layouts/footers/footer.vue'
// Reactive data
const token = ref(null)
const user = ref(null)
const deliveries = ref([])
export default defineComponent({ // Lifecycle
name: 'deliveryTicketsMissing', onMounted(() => {
userStatus()
get_oil_orders()
})
components: { // Functions
Header, const userStatus = () => {
SideBar,
Footer,
},
data() {
return {
token: null,
user: null,
deliveries: [
],
}
},
created() {
this.userStatus()
},
mounted() {
this.get_oil_orders()
},
methods: {
userStatus() {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -117,26 +101,24 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
.catch(() => { .catch(() => {
this.user = null user.value = null
}) })
}, }
get_oil_orders() {
const get_oil_orders = () => {
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/pending'; let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/pending';
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: any) => {
this.deliveries = response.data deliveries.value = response.data
}) })
}, }
},
})
</script> </script>
<style scoped> <style scoped>

View File

@@ -67,20 +67,20 @@
<div class="font-bold text-sm">Current Status</div> <div class="font-bold text-sm">Current Status</div>
<div class="badge badge-lg" <div class="badge badge-lg"
:class="{ :class="{
'badge-success': [1, 10].includes(deliveryOrder.delivery_status), 'badge-success': [DELIVERY_STATUS.DELIVERED, DELIVERY_STATUS.FINALIZED].includes(deliveryOrder.delivery_status as any),
'badge-info': deliveryOrder.delivery_status == 2, 'badge-info': deliveryOrder.delivery_status == DELIVERY_STATUS.OUT_FOR_DELIVERY,
'badge-error': deliveryOrder.delivery_status == 5, 'badge-error': deliveryOrder.delivery_status == DELIVERY_STATUS.ISSUE,
'badge-warning': ![1, 10, 2, 5].includes(deliveryOrder.delivery_status) 'badge-warning': ![DELIVERY_STATUS.DELIVERED, DELIVERY_STATUS.FINALIZED, DELIVERY_STATUS.OUT_FOR_DELIVERY, DELIVERY_STATUS.ISSUE].includes(deliveryOrder.delivery_status as any)
}"> }">
<span v-if="deliveryOrder.delivery_status == 0">Waiting</span> <span v-if="deliveryOrder.delivery_status == DELIVERY_STATUS.WAITING">Waiting</span>
<span v-else-if="deliveryOrder.delivery_status == 1">Delivered</span> <span v-else-if="deliveryOrder.delivery_status == DELIVERY_STATUS.DELIVERED">Delivered</span>
<span v-else-if="deliveryOrder.delivery_status == 2">Out_for_Delivery</span> <span v-else-if="deliveryOrder.delivery_status == DELIVERY_STATUS.OUT_FOR_DELIVERY">Out_for_Delivery</span>
<span v-else-if="deliveryOrder.delivery_status == 3">Tomorrow</span> <span v-else-if="deliveryOrder.delivery_status == DELIVERY_STATUS.TOMORROW">Tomorrow</span>
<span v-else-if="deliveryOrder.delivery_status == 4">Partial Delivery</span> <span v-else-if="deliveryOrder.delivery_status == DELIVERY_STATUS.PARTIAL_DELIVERY">Partial Delivery</span>
<span v-else-if="deliveryOrder.delivery_status == 5">Misdelivery</span> <span v-else-if="deliveryOrder.delivery_status == DELIVERY_STATUS.ISSUE">Misdelivery</span>
<span v-else-if="deliveryOrder.delivery_status == 6">Unknown</span> <span v-else-if="deliveryOrder.delivery_status == DELIVERY_STATUS.UNKNOWN">Unknown</span>
<span v-else-if="deliveryOrder.delivery_status == 9">Pending</span> <span v-else-if="deliveryOrder.delivery_status == DELIVERY_STATUS.PENDING_PAYMENT">Pending</span>
<span v-else-if="deliveryOrder.delivery_status == 10">Finalized</span> <span v-else-if="deliveryOrder.delivery_status == DELIVERY_STATUS.FINALIZED">Finalized</span>
</div> </div>
</div> </div>
<div> <div>
@@ -354,54 +354,37 @@
</div> </div>
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, computed, onMounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../services/auth.header' import authHeader from '../../services/auth.header'
import { DELIVERY_STATUS, PAYMENT_STATUS, TRANSACTION_STATUS } from '../../constants/status'
interface UserCard { import { CreditCard } from '../../types/models'
id: number;
last_four: string;
type_of_card: string;
expiration_month: number;
expiration_year: number;
name_on_card: string;
card_number: string;
security_number: string;
main_card?: boolean;
}
import Header from '../../layouts/headers/headerauth.vue' import Header from '../../layouts/headers/headerauth.vue'
import SideBar from '../../layouts/sidebar/sidebar.vue' import SideBar from '../../layouts/sidebar/sidebar.vue'
import Footer from '../../layouts/footers/footer.vue' import Footer from '../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core"; import useValidate from "@vuelidate/core";
import { notify } from "@kyvg/vue3-notification" import { notify } from "@kyvg/vue3-notification"
import moment from 'moment'; import dayjs from 'dayjs';
export default defineComponent({ const route = useRoute()
name: 'deliveryOrder',
components: { // Reactive data
Header, const v$ = useValidate()
SideBar, const user = ref({
Footer,
},
data() {
return {
v$: useValidate(),
user: {
user_id: 0 user_id: 0
}, })
priceprime: 0, const priceprime = ref(0)
pricesameday: 0, const pricesameday = ref(0)
priceemergency: 0, const priceemergency = ref(0)
total_amount: 0, const total_amount = ref(0)
discount: 0, const discount = ref(0)
total_amount_after_discount: 0, const total_amount_after_discount = ref(0)
deliveryNotesDriver: [], const deliveryNotesDriver = ref([])
userCardfound: false, const userCardfound = ref(false)
userCard: {} as UserCard, const userCard = ref({} as CreditCard)
customer: { const customer = ref({
account_number: '', account_number: '',
id: 0, id: 0,
user_id: 0, user_id: 0,
@@ -414,9 +397,9 @@ export default defineComponent({
customer_apt: '', customer_apt: '',
customer_home_type: 0, customer_home_type: 0,
customer_phone_number: '', customer_phone_number: '',
}, })
deliveryMoney: { const deliveryMoney = ref({
time_added: '', time_added: '',
total_amount_oil: '', total_amount_oil: '',
total_amount_emergency: '', total_amount_emergency: '',
@@ -426,15 +409,15 @@ export default defineComponent({
total_discount_amount: '', total_discount_amount: '',
total_discount_total: '', total_discount_total: '',
total_amount: '', total_amount: '',
}, })
promo: { const promo = ref({
id: 0, id: 0,
name_of_promotion: '', name_of_promotion: '',
description: '', description: '',
money_off_delivery: '', money_off_delivery: '',
text_on_ticket: '' text_on_ticket: ''
}, })
pricing: { const pricing = ref({
price_from_supplier: 0, price_from_supplier: 0,
price_for_customer: 0, price_for_customer: 0,
price_for_employee: 0, price_for_employee: 0,
@@ -442,8 +425,8 @@ export default defineComponent({
price_prime: 0, price_prime: 0,
price_emergency: 0, price_emergency: 0,
date: "", date: "",
}, })
deliveryOrder: { const deliveryOrder = ref({
id: '', id: '',
customer_id: 0, customer_id: 0,
customer_name: '', customer_name: '',
@@ -474,35 +457,38 @@ export default defineComponent({
driver_first_name: '', driver_first_name: '',
driver_last_name: '', driver_last_name: '',
promo_id: 0, promo_id: 0,
}, })
transaction: null as any, const transaction = ref(null as any)
}
},
created() { // Computed
this.userStatus() const computedDELIVERY_STATUS = computed(() => DELIVERY_STATUS)
}, const computedPAYMENT_STATUS = computed(() => PAYMENT_STATUS)
watch: { const computedTRANSACTION_STATUS = computed(() => TRANSACTION_STATUS)
$route() {
this.getOilOrder(this.$route.params.id);
this.getOilOrderMoney(this.$route.params.id);
this.sumdelivery(this.$route.params.id);
},
},
mounted() {
this.getOilOrder(this.$route.params.id);
this.getOilOrderMoney(this.$route.params.id);
this.sumdelivery(this.$route.params.id);
this.getOilPricing();
},
methods: { // Watchers
format_date(value: string) { watch(route, () => {
getOilOrder(route.params.id);
getOilOrderMoney(route.params.id);
sumdelivery(route.params.id);
})
// Lifecycle
onMounted(() => {
userStatus()
getOilOrder(route.params.id);
getOilOrderMoney(route.params.id);
sumdelivery(route.params.id);
getOilPricing();
})
// Functions
const format_date = (value: string) => {
if (value) { if (value) {
return moment(String(value)).format('LLLL') return dayjs(String(value)).format('LLLL')
} }
}, }
getTypeColor(transactionType: number) {
const getTypeColor = (transactionType: number) => {
switch (transactionType) { switch (transactionType) {
case 1: return 'text-blue-600'; // Auth case 1: return 'text-blue-600'; // Auth
case 0: return 'text-orange-600'; // Charge case 0: return 'text-orange-600'; // Charge
@@ -510,8 +496,9 @@ export default defineComponent({
case 3: return 'text-purple-600'; // Delivery/Other case 3: return 'text-purple-600'; // Delivery/Other
default: return 'text-gray-600'; default: return 'text-gray-600';
} }
}, }
deleteCall(delivery_id: any) {
const deleteCall = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id; let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
axios({ axios({
method: 'delete', method: 'delete',
@@ -524,7 +511,7 @@ export default defineComponent({
text: "deleted delivery", text: "deleted delivery",
type: "success", type: "success",
}); });
this.$router.push({ name: "customerProfile", params: { id: this.customer.user_id } }); // Note: router.push would need to be imported and used
} else { } else {
notify({ notify({
title: "Failure", title: "Failure",
@@ -533,9 +520,10 @@ export default defineComponent({
}); });
} }
}) })
}, }
cancelDelivery() {
let path = import.meta.env.VITE_BASE_URL + '/delivery/cancel/' + this.deliveryOrder.id; const cancelDelivery = () => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/cancel/' + deliveryOrder.value.id;
axios({ axios({
method: 'post', method: 'post',
url: path, url: path,
@@ -548,7 +536,7 @@ export default defineComponent({
type: "success", type: "success",
}); });
// Refresh the delivery data // Refresh the delivery data
this.getOilOrder(this.deliveryOrder.id); getOilOrder(deliveryOrder.value.id);
} else { } else {
notify({ notify({
title: "Failure", title: "Failure",
@@ -563,8 +551,9 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
userStatus() {
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -574,12 +563,13 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
}, }
getOilPricing() {
const getOilPricing = () => {
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/table"; let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/table";
axios({ axios({
method: "get", method: "get",
@@ -587,7 +577,7 @@ export default defineComponent({
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: any) => {
this.pricing = response.data; pricing.value = response.data;
}) })
.catch((_error: any) => { .catch((_error: any) => {
notify({ notify({
@@ -596,8 +586,9 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
getCustomer(user_id: any) {
const getCustomer = (user_id: any) => {
let path = import.meta.env.VITE_BASE_URL + "/customer/" + user_id; let path = import.meta.env.VITE_BASE_URL + "/customer/" + user_id;
axios({ axios({
method: "get", method: "get",
@@ -605,7 +596,7 @@ export default defineComponent({
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: any) => {
this.customer = response.data; customer.value = response.data;
}) })
.catch((_error: any) => { .catch((_error: any) => {
notify({ notify({
@@ -614,9 +605,9 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
getPaymentCard(card_id: any) { const getPaymentCard = (card_id: any) => {
if (card_id) { if (card_id) {
let path = import.meta.env.VITE_BASE_URL + "/payment/card/" + card_id; let path = import.meta.env.VITE_BASE_URL + "/payment/card/" + card_id;
axios({ axios({
@@ -627,24 +618,24 @@ export default defineComponent({
.then((response: any) => { .then((response: any) => {
// Check if we have valid card data // Check if we have valid card data
if (response.data && response.data.card_number && response.data.card_number !== '') { if (response.data && response.data.card_number && response.data.card_number !== '') {
this.userCard = response.data; userCard.value = response.data;
this.userCardfound = true; userCardfound.value = true;
} else { } else {
this.userCard = {} as UserCard; userCard.value = {} as CreditCard;
this.userCardfound = false; userCardfound.value = false;
} }
}) })
.catch((error: any) => { .catch((error: any) => {
console.error("Error fetching payment card:", error); console.error("Error fetching payment card:", error);
this.userCard = {} as UserCard; userCard.value = {} as CreditCard;
this.userCardfound = false; userCardfound.value = false;
}); });
} else { } else {
this.userCardfound = false; userCardfound.value = false;
} }
}, }
getOilOrder(delivery_id: any) { const getOilOrder = (delivery_id: any) => {
if (!delivery_id) { // Add a guard to prevent calls with an undefined ID if (!delivery_id) { // Add a guard to prevent calls with an undefined ID
console.error("getOilOrder called with no ID."); console.error("getOilOrder called with no ID.");
return; return;
@@ -659,21 +650,21 @@ export default defineComponent({
.then((response: any) => { .then((response: any) => {
// FIX: Check for the 'ok' flag and access the nested 'delivery' object // FIX: Check for the 'ok' flag and access the nested 'delivery' object
if (response.data && response.data.ok) { if (response.data && response.data.ok) {
this.deliveryOrder = response.data.delivery; // <-- THIS IS THE CRITICAL CHANGE deliveryOrder.value = response.data.delivery; // <-- THIS IS THE CRITICAL CHANGE
// Now that this.deliveryOrder is the correct object, the rest of the logic will work. // Now that deliveryOrder is the correct object, the rest of the logic will work.
this.getCustomer(this.deliveryOrder.customer_id); getCustomer(deliveryOrder.value.customer_id);
if ([1, 2, 3].includes(this.deliveryOrder.payment_type)) { if ([1, 2, 3].includes(deliveryOrder.value.payment_type)) {
this.getPaymentCard(this.deliveryOrder.payment_card_id); getPaymentCard(deliveryOrder.value.payment_card_id);
} }
if (this.deliveryOrder.promo_id != null) { if (deliveryOrder.value.promo_id != null) {
this.getPromo(this.deliveryOrder.promo_id); getPromo(deliveryOrder.value.promo_id);
} }
// Only fetch transactions for Authorize.net payments // Only fetch transactions for Authorize.net payments
if (this.deliveryOrder.payment_type == 11) { if (deliveryOrder.value.payment_type == 11) {
this.getTransaction(delivery_id); getTransaction(delivery_id);
} }
} else { } else {
@@ -684,8 +675,9 @@ export default defineComponent({
.catch((error: any) => { .catch((error: any) => {
console.error("Error fetching delivery order:", error); console.error("Error fetching delivery order:", error);
}); });
}, }
getOilOrderMoney(delivery_id: any) {
const getOilOrderMoney = (delivery_id: any) => {
let path = import.meta.env.VITE_MONEY_URL + "/delivery/order/money/" + delivery_id; let path = import.meta.env.VITE_MONEY_URL + "/delivery/order/money/" + delivery_id;
axios({ axios({
method: "get", method: "get",
@@ -695,11 +687,12 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data) { if (response.data) {
this.deliveryMoney = response.data deliveryMoney.value = response.data
} }
}) })
}, }
sumdelivery(delivery_id: any) {
const sumdelivery = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + "/delivery/total/" + delivery_id; let path = import.meta.env.VITE_BASE_URL + "/delivery/total/" + delivery_id;
axios({ axios({
method: "get", method: "get",
@@ -708,53 +701,53 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data && response.data.ok) { if (response.data && response.data.ok) {
this.priceprime = response.data.priceprime || 0; priceprime.value = response.data.priceprime || 0;
this.pricesameday = response.data.pricesameday || 0; pricesameday.value = response.data.pricesameday || 0;
this.priceemergency = response.data.priceemergency || 0; priceemergency.value = response.data.priceemergency || 0;
this.total_amount = parseFloat(response.data.total_amount) || 0; total_amount.value = parseFloat(response.data.total_amount) || 0;
this.discount = parseFloat(response.data.discount) || 0; discount.value = parseFloat(response.data.discount) || 0;
this.total_amount_after_discount = parseFloat(response.data.total_amount_after_discount) || 0; total_amount_after_discount.value = parseFloat(response.data.total_amount_after_discount) || 0;
} else { } else {
// Fallback calculation if API doesn't return expected data // Fallback calculation if API doesn't return expected data
this.calculateFallbackTotal(); calculateFallbackTotal();
} }
}) })
.catch((error: any) => { .catch((error: any) => {
console.error("Error fetching delivery totals:", error); console.error("Error fetching delivery totals:", error);
// Fallback calculation on error // Fallback calculation on error
this.calculateFallbackTotal(); calculateFallbackTotal();
notify({ notify({
title: "Warning", title: "Warning",
text: "Could not get delivery totals, using estimated calculation", text: "Could not get delivery totals, using estimated calculation",
type: "warn", type: "warn",
}); });
}); });
}, }
calculateFallbackTotal() { const calculateFallbackTotal = () => {
// Fallback calculation using available data // Fallback calculation using available data
if (this.deliveryOrder.gallons_ordered && this.pricing.price_for_customer) { if (deliveryOrder.value.gallons_ordered && pricing.value.price_for_customer) {
const gallons = Number(this.deliveryOrder.gallons_ordered); const gallons = Number(deliveryOrder.value.gallons_ordered);
const pricePerGallon = Number(this.pricing.price_for_customer); const pricePerGallon = Number(pricing.value.price_for_customer);
let total = gallons * pricePerGallon; let total = gallons * pricePerGallon;
if (this.deliveryOrder.prime == 1) { if (deliveryOrder.value.prime == 1) {
total += Number(this.pricing.price_prime) || 0; total += Number(pricing.value.price_prime) || 0;
} }
if (this.deliveryOrder.same_day == 1) { if (deliveryOrder.value.same_day == 1) {
total += Number(this.pricing.price_same_day) || 0; total += Number(pricing.value.price_same_day) || 0;
} }
if (this.deliveryOrder.emergency == 1) { if (deliveryOrder.value.emergency == 1) {
total += Number(this.pricing.price_emergency) || 0; total += Number(pricing.value.price_emergency) || 0;
} }
this.total_amount = total; total_amount.value = total;
this.total_amount_after_discount = total; // No discount info available total_amount_after_discount.value = total; // No discount info available
this.discount = 0; discount.value = 0;
} }
}, }
getPromo(promo_id: any) { const getPromo = (promo_id: any) => {
let path = import.meta.env.VITE_BASE_URL + "/promo/" + promo_id; let path = import.meta.env.VITE_BASE_URL + "/promo/" + promo_id;
axios({ axios({
method: "get", method: "get",
@@ -764,58 +757,59 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data) { if (response.data) {
this.promo = response.data; promo.value = response.data;
} }
}) })
.catch((error: any) => { .catch((error: any) => {
console.error('Error fetching promo:', error); console.error('Error fetching promo:', error);
}) })
}, }
calculateDeliveryTotal() { const calculateDeliveryTotal = () => {
if (!this.deliveryOrder.gallons_delivered || !this.pricing.price_for_customer) { if (!deliveryOrder.value.gallons_delivered || !pricing.value.price_for_customer) {
return '0.00'; return '0.00';
} }
const gallons = Number(this.deliveryOrder.gallons_delivered); const gallons = Number(deliveryOrder.value.gallons_delivered);
const pricePerGallon = Number(this.pricing.price_for_customer); const pricePerGallon = Number(pricing.value.price_for_customer);
let total = gallons * pricePerGallon; let total = gallons * pricePerGallon;
if (this.deliveryOrder.prime == 1) { if (deliveryOrder.value.prime == 1) {
total += Number(this.pricing.price_prime) || 0; total += Number(pricing.value.price_prime) || 0;
} }
if (this.deliveryOrder.same_day == 1) { if (deliveryOrder.value.same_day == 1) {
total += Number(this.pricing.price_same_day) || 0; total += Number(pricing.value.price_same_day) || 0;
} }
if (this.deliveryOrder.emergency == 1) { if (deliveryOrder.value.emergency == 1) {
total += Number(this.pricing.price_emergency) || 0; total += Number(pricing.value.price_emergency) || 0;
} }
return total.toFixed(2); return total.toFixed(2);
}, }
calculateEstimatedTotal() {
if (!this.deliveryOrder.gallons_ordered || !this.pricing.price_for_customer) { const calculateEstimatedTotal = () => {
if (!deliveryOrder.value.gallons_ordered || !pricing.value.price_for_customer) {
return 0; return 0;
} }
const gallons = Number(this.deliveryOrder.gallons_ordered); const gallons = Number(deliveryOrder.value.gallons_ordered);
const pricePerGallon = Number(this.pricing.price_for_customer); const pricePerGallon = Number(pricing.value.price_for_customer);
let total = gallons * pricePerGallon; let total = gallons * pricePerGallon;
if (this.deliveryOrder.prime == 1) { if (deliveryOrder.value.prime == 1) {
total += Number(this.pricing.price_prime) || 0; total += Number(pricing.value.price_prime) || 0;
} }
if (this.deliveryOrder.same_day == 1) { if (deliveryOrder.value.same_day == 1) {
total += Number(this.pricing.price_same_day) || 0; total += Number(pricing.value.price_same_day) || 0;
} }
if (this.deliveryOrder.emergency == 1) { if (deliveryOrder.value.emergency == 1) {
total += Number(this.pricing.price_emergency) || 0; total += Number(pricing.value.price_emergency) || 0;
} }
return total; return total;
}, }
getTransaction(delivery_id: any) { const getTransaction = (delivery_id: any) => {
// Simple endpoint to get transaction directly by delivery_id // Simple endpoint to get transaction directly by delivery_id
const path = `${import.meta.env.VITE_BASE_URL}/payment/transaction/delivery/${delivery_id}`; const path = `${import.meta.env.VITE_BASE_URL}/payment/transaction/delivery/${delivery_id}`;
axios.get(path, { axios.get(path, {
@@ -823,19 +817,17 @@ export default defineComponent({
headers: authHeader() headers: authHeader()
}).then((response: any) => { }).then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.transaction = response.data.transaction; transaction.value = response.data.transaction;
console.log("Transaction loaded:", this.transaction); console.log("Transaction loaded:", transaction.value);
} else { } else {
console.log("No transaction found for delivery:", delivery_id); console.log("No transaction found for delivery:", delivery_id);
this.transaction = null; transaction.value = null;
} }
}).catch((error: any) => { }).catch((error: any) => {
console.error("Error fetching transaction:", error); console.error("Error fetching transaction:", error);
this.transaction = null; transaction.value = null;
}); });
}, }
},
})
</script> </script>
<style scoped></style> <style scoped></style>

View File

@@ -121,54 +121,38 @@
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import {defineComponent} from 'vue' import { ref, onMounted } from 'vue'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../../services/auth.header' import authHeader from '../../../services/auth.header'
import { deliveryService } from '../../../services/deliveryService'
import { Delivery } from '../../../types/models'
import Header from '../../../layouts/headers/headerauth.vue' import Header from '../../../layouts/headers/headerauth.vue'
import PaginationComp from '../../../components/pagination.vue' import PaginationComp from '../../../components/pagination.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue' import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue' import Footer from '../../../layouts/footers/footer.vue'
import {notify} from "@kyvg/vue3-notification"; import {notify} from "@kyvg/vue3-notification";
export default defineComponent({ // Reactive data
name: 'deliveryCancelled', const token = ref(null)
const user = ref(null)
components: { const deliveries = ref<Delivery[]>([])
Header, const page = ref(1)
SideBar, const perPage = ref(50)
Footer, const recordsLength = ref(0)
}, const options = ref({
data() {
return {
token: null,
user: null,
deliveries: [] as any[],
page: 1,
perPage: 50,
recordsLength: 0,
options: {
edgeNavigation: false, edgeNavigation: false,
format: false, format: false,
template: PaginationComp template: PaginationComp
} })
}
},
created() { // Functions
this.userStatus() const getPage = (pageVal: any) => {
}, deliveries.value = [];
mounted() { get_oil_orders(pageVal)
this.getPage(this.page) }
},
methods: { const userStatus = () => {
getPage: function (page: any) {
// we simulate an api call that fetch the records from a backend
this.deliveries = [];
this.get_oil_orders(page)
},
userStatus() {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -178,24 +162,25 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
.catch(() => { .catch(() => {
this.user = null user.value = null
}) })
}, }
get_oil_orders(page: any) {
let path = import.meta.env.VITE_BASE_URL + '/delivery/issue/' + page; const get_oil_orders = async (pageVal: number) => {
axios({ try {
method: 'get', const response = await deliveryService.getIssues(pageVal)
url: path, deliveries.value = response.data || []
headers: authHeader(), } catch (error) {
}).then((response: any) => { console.error('Error fetching issue deliveries:', error)
this.deliveries = response.data deliveries.value = []
}) }
}, }
deleteCall(delivery_id: any) {
const deleteCall = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/cancelled/' + delivery_id; let path = import.meta.env.VITE_BASE_URL + '/delivery/cancelled/' + delivery_id;
axios({ axios({
method: 'delete', method: 'delete',
@@ -208,7 +193,7 @@ export default defineComponent({
text: "deleted delivery", text: "deleted delivery",
type: "success", type: "success",
}); });
this.getPage(this.page) getPage(page.value)
} else { } else {
notify({ notify({
title: "Failure", title: "Failure",
@@ -217,8 +202,12 @@ export default defineComponent({
}); });
} }
}) })
}, }
},
// Lifecycle
onMounted(() => {
userStatus()
getPage(page.value)
}) })
</script> </script>

View File

@@ -121,54 +121,38 @@
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import {defineComponent} from 'vue' import { ref, onMounted } from 'vue'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../../services/auth.header' import authHeader from '../../../services/auth.header'
import { deliveryService } from '../../../services/deliveryService'
import { Delivery } from '../../../types/models'
import Header from '../../../layouts/headers/headerauth.vue' import Header from '../../../layouts/headers/headerauth.vue'
import PaginationComp from '../../../components/pagination.vue' import PaginationComp from '../../../components/pagination.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue' import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue' import Footer from '../../../layouts/footers/footer.vue'
import {notify} from "@kyvg/vue3-notification"; import {notify} from "@kyvg/vue3-notification";
export default defineComponent({ // Reactive data
name: 'deliveryDelivered', const token = ref(null)
const user = ref(null)
components: { const deliveries = ref<Delivery[]>([])
Header, const page = ref(1)
SideBar, const perPage = ref(50)
Footer, const recordsLength = ref(0)
}, const options = ref({
data() {
return {
token: null,
user: null,
deliveries: [] as any[],
page: 1,
perPage: 50,
recordsLength: 0,
options: {
edgeNavigation: false, edgeNavigation: false,
format: false, format: false,
template: PaginationComp template: PaginationComp
} })
}
},
created() { // Functions
this.userStatus() const getPage = (pageVal: any) => {
}, deliveries.value = [];
mounted() { get_oil_orders(pageVal)
this.getPage(this.page) }
},
methods: { const userStatus = () => {
getPage: function (page: any) {
// we simulate an api call that fetch the records from a backend
this.deliveries = [];
this.get_oil_orders(page)
},
userStatus() {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -178,25 +162,25 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
.catch(() => { .catch(() => {
this.user = null user.value = null
}) })
}, }
get_oil_orders(page: any) { const get_oil_orders = async (pageVal: number) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/delivered/' + page; try {
axios({ const response = await deliveryService.getDelivered(pageVal)
method: 'get', deliveries.value = response.data || []
url: path, } catch (error) {
headers: authHeader(), console.error('Error fetching delivered deliveries:', error)
}).then((response: any) => { deliveries.value = []
this.deliveries = response.data }
}) }
},
deleteCall(delivery_id: any) { const deleteCall = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id; let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
axios({ axios({
method: 'delete', method: 'delete',
@@ -209,7 +193,7 @@ export default defineComponent({
text: "deleted delivery", text: "deleted delivery",
type: "success", type: "success",
}); });
this.getPage(this.page) getPage(page.value)
} else { } else {
notify({ notify({
title: "Failure", title: "Failure",
@@ -218,8 +202,12 @@ export default defineComponent({
}); });
} }
}) })
}, }
},
// Lifecycle
onMounted(() => {
userStatus()
getPage(page.value)
}) })
</script> </script>

View File

@@ -121,55 +121,38 @@
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import {defineComponent} from 'vue' import { ref, onMounted } from 'vue'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../../services/auth.header' import authHeader from '../../../services/auth.header'
import Header from '../../../layouts/headers/headerauth.vue' import { deliveryService } from '../../../services/deliveryService'
import PaginationComp from '../../../components/pagination.vue' import { Delivery } from '../../../types/models'
import SideBar from '../../../layouts/sidebar/sidebar.vue' import Header from '../../../layouts/headers/headerauth.vue'
import Footer from '../../../layouts/footers/footer.vue' import PaginationComp from '../../../components/pagination.vue'
import {notify} from "@kyvg/vue3-notification"; import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import {notify} from "@kyvg/vue3-notification";
export default defineComponent({ // Reactive data
name: 'deliveryFinalized', const token = ref(null)
const user = ref(null)
components: { const deliveries = ref<Delivery[]>([])
Header, const page = ref(1)
SideBar, const perPage = ref(50)
Footer, const recordsLength = ref(0)
}, const options = ref({
data() {
return {
token: null,
user: null,
deliveries: [] as any[],
page: 1,
perPage: 50,
recordsLength: 0,
options: {
edgeNavigation: false, edgeNavigation: false,
format: false, format: false,
template: PaginationComp template: PaginationComp
} })
}
},
created() { // Functions
this.userStatus() const getPage = (pageVal: any) => {
}, deliveries.value = [];
mounted() { get_oil_orders(pageVal)
this.getPage(this.page) }
},
methods: {
getPage: function (page: any) {
// we simulate an api call that fetch the records from a backend
this.deliveries = [];
this.get_oil_orders(page)
},
userStatus() { const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -179,26 +162,25 @@
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
.catch(() => { .catch(() => {
this.user = null user.value = null
}) })
}, }
get_oil_orders(page: any) { const get_oil_orders = async (pageVal: number) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/finalized/' + page; try {
axios({ const response = await deliveryService.getFinalized(pageVal)
method: 'get', deliveries.value = response.data || []
url: path, } catch (error) {
headers: authHeader(), console.error('Error fetching finalized deliveries:', error)
}).then((response: any) => { deliveries.value = []
this.deliveries = response.data }
}) }
},
deleteCall(delivery_id: any) { const deleteCall = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id; let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
axios({ axios({
method: 'delete', method: 'delete',
@@ -211,7 +193,7 @@
text: "deleted delivery", text: "deleted delivery",
type: "success", type: "success",
}); });
this.getPage(this.page) getPage(page.value)
} else { } else {
notify({ notify({
title: "Failure", title: "Failure",
@@ -220,10 +202,14 @@
}); });
} }
}) })
}, }
},
}) // Lifecycle
</script> onMounted(() => {
userStatus()
getPage(page.value)
})
</script>
<style scoped> <style scoped>

View File

@@ -122,54 +122,37 @@
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import {defineComponent} from 'vue' import { ref, onMounted } from 'vue'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../../services/auth.header' import authHeader from '../../../services/auth.header'
import { Delivery } from '../../../types/models'
import Header from '../../../layouts/headers/headerauth.vue' import Header from '../../../layouts/headers/headerauth.vue'
import PaginationComp from '../../../components/pagination.vue' import PaginationComp from '../../../components/pagination.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue' import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue' import Footer from '../../../layouts/footers/footer.vue'
import {notify} from "@kyvg/vue3-notification"; import {notify} from "@kyvg/vue3-notification";
export default defineComponent({ // Reactive data
name: 'deliveryIssue', const token = ref(null)
const user = ref(null)
components: { const deliveries = ref<Delivery[]>([])
Header, const page = ref(1)
SideBar, const perPage = ref(50)
Footer, const recordsLength = ref(0)
}, const options = ref({
data() {
return {
token: null,
user: null,
deliveries: [] as any[],
page: 1,
perPage: 50,
recordsLength: 0,
options: {
edgeNavigation: false, edgeNavigation: false,
format: false, format: false,
template: PaginationComp template: PaginationComp
} })
}
},
created() { // Functions
this.userStatus() const getPage = (pageVal: any) => {
}, deliveries.value = [];
mounted() { get_oil_orders(pageVal)
this.getPage(this.page) }
},
methods: { const userStatus = () => {
getPage: function (page: any) {
// we simulate an api call that fetch the records from a backend
this.deliveries = [];
this.get_oil_orders(page)
},
userStatus() {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -179,24 +162,26 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
.catch(() => { .catch(() => {
this.user = null user.value = null
}) })
}, }
get_oil_orders(page: any) {
let path = import.meta.env.VITE_BASE_URL + '/delivery/issue/' + page; const get_oil_orders = (pageVal: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/issue/' + pageVal;
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: any) => {
this.deliveries = response.data deliveries.value = response.data
}) })
}, }
deleteCall(delivery_id: any) {
const deleteCall = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id; let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
axios({ axios({
method: 'delete', method: 'delete',
@@ -209,7 +194,7 @@ export default defineComponent({
text: "deleted delivery", text: "deleted delivery",
type: "success", type: "success",
}); });
this.getPage(this.page) getPage(page.value)
} else { } else {
notify({ notify({
title: "Failure", title: "Failure",
@@ -218,8 +203,12 @@ export default defineComponent({
}); });
} }
}) })
}, }
},
// Lifecycle
onMounted(() => {
userStatus()
getPage(page.value)
}) })
</script> </script>

View File

@@ -157,52 +157,38 @@
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted } from 'vue'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../../services/auth.header' import authHeader from '../../../services/auth.header'
import Header from '../../../layouts/headers/headerauth.vue' import { deliveryService } from '../../../services/deliveryService'
import PaginationComp from '../../../components/pagination.vue' import { Delivery } from '../../../types/models'
import SideBar from '../../../layouts/sidebar/sidebar.vue' import Header from '../../../layouts/headers/headerauth.vue'
import Footer from '../../../layouts/footers/footer.vue' import PaginationComp from '../../../components/pagination.vue'
import { notify } from "@kyvg/vue3-notification"; import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import { notify } from "@kyvg/vue3-notification";
export default defineComponent({ // Reactive data
name: 'deliveryPending', const token = ref(null)
const user = ref(null)
components: { const deliveries = ref<Delivery[]>([])
Header, const page = ref(1)
SideBar, const perPage = ref(50)
Footer, const recordsLength = ref(0)
}, const options = ref({
data() {
return {
token: null,
user: null,
deliveries: [] as any[],
page: 1,
perPage: 50,
recordsLength: 0,
options: {
edgeNavigation: false, edgeNavigation: false,
format: false, format: false,
template: PaginationComp template: PaginationComp
} })
}
}, // Functions
created() { const getPage = (pageVal: any) => {
this.userStatus() deliveries.value = [];
}, get_oil_orders(pageVal)
mounted() { }
this.getPage(this.page)
}, const userStatus = () => {
methods: {
getPage: function (page: any) {
this.deliveries = [];
this.get_oil_orders(page)
},
userStatus() {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -212,27 +198,25 @@
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
.catch(() => { .catch(() => {
this.user = null user.value = null
}) })
}, }
const get_oil_orders = async (pageVal: number) => {
try {
const response = await deliveryService.getPending(pageVal)
deliveries.value = response.data || []
} catch (error) {
console.error('Error fetching pending deliveries:', error)
deliveries.value = []
}
}
get_oil_orders(page: any) { const deleteCall = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/pending/' + page;
axios({
method: 'get',
url: path,
headers: authHeader(),
}).then((response: any) => {
this.deliveries = response.data
})
},
deleteCall(delivery_id: any) {
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id; let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
axios({ axios({
method: 'delete', method: 'delete',
@@ -245,7 +229,7 @@
text: "deleted delivery", text: "deleted delivery",
type: "success", type: "success",
}); });
this.getPage(this.page) getPage(page.value)
} else { } else {
notify({ notify({
title: "Failure", title: "Failure",
@@ -254,11 +238,13 @@
}); });
} }
}) })
}, }
// Lifecycle
}, onMounted(() => {
}) userStatus()
getPage(page.value)
})
</script> </script>
<style scoped></style> <style scoped></style>

View File

@@ -164,55 +164,40 @@
</div> </div>
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted } from 'vue'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../../services/auth.header' import authHeader from '../../../services/auth.header'
import { deliveryService } from '../../../services/deliveryService'
import { Delivery } from '../../../types/models'
import Header from '../../../layouts/headers/headerauth.vue' import Header from '../../../layouts/headers/headerauth.vue'
import PaginationComp from '../../../components/pagination.vue' import PaginationComp from '../../../components/pagination.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue' import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue' import Footer from '../../../layouts/footers/footer.vue'
import { notify } from "@kyvg/vue3-notification"; import { notify } from "@kyvg/vue3-notification";
export default defineComponent({ // Reactive data
name: 'deliveryOutForDelivery', const token = ref(null)
const user = ref(null)
components: { const deliveries = ref<Delivery[]>([])
Header, const totals = ref<{ town: string; gallons: number }[]>([])
SideBar, const grand_total = ref(0)
Footer, const page = ref(1)
}, const perPage = ref(50)
const recordsLength = ref(0)
data() { const options = ref({
return {
token: null,
user: null,
deliveries: [] as any[],
totals: [] as any[],
grand_total: 0,
page: 1,
perPage: 50,
recordsLength: 0,
options: {
edgeNavigation: false, edgeNavigation: false,
format: false, format: false,
template: PaginationComp template: PaginationComp
} })
}
}, // Functions
created() { const getPage = (pageVal: any) => {
this.userStatus() deliveries.value = [];
}, get_oil_orders(pageVal)
mounted() { }
this.getPage(this.page)
this.get_totals() const userStatus = () => {
},
methods: {
getPage: function (page: any) {
this.deliveries = [];
this.get_oil_orders(page)
},
userStatus() {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -222,46 +207,45 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
.catch(() => { .catch(() => {
this.user = null user.value = null
}) })
}, }
const mod = (date: any) => new Date(date).getTime()
mod: (date: any) => new Date (date).getTime(), const get_oil_orders = async (pageVal: number) => {
get_oil_orders(page: any) { try {
let path = import.meta.env.VITE_BASE_URL + '/delivery/outfordelivery/' + page; const response = await deliveryService.getOutForDelivery(pageVal)
axios({ deliveries.value = response.data || []
method: 'get',
url: path,
headers: authHeader(),
}).then((response: any) => {
this.deliveries = response.data
// Sort deliveries by Delivery # (id) in descending order // Sort deliveries by Delivery # (id) in descending order
this.deliveries.sort((a, b) => b.id - a.id); deliveries.value.sort((a, b) => b.id - a.id);
}) } catch (error) {
}, console.error('Error fetching out for delivery:', error)
deliveries.value = []
}
}
get_totals() { const get_totals = () => {
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/today-totals'; let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/today-totals';
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: any) => {
this.totals = response.data.totals || [] totals.value = response.data.totals || []
this.grand_total = response.data.grand_total || 0 grand_total.value = response.data.grand_total || 0
}).catch((error: any) => { }).catch((error: any) => {
console.error('Error fetching totals:', error); console.error('Error fetching totals:', error);
this.totals = [] totals.value = []
this.grand_total = 0 grand_total.value = 0
}) })
}, }
deleteCall(delivery_id: any) { const deleteCall = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id; let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
axios({ axios({
method: 'delete', method: 'delete',
@@ -274,7 +258,7 @@ export default defineComponent({
text: "deleted delivery", text: "deleted delivery",
type: "success", type: "success",
}); });
this.getPage(this.page) getPage(page.value)
} else { } else {
notify({ notify({
title: "Failure", title: "Failure",
@@ -283,55 +267,13 @@ export default defineComponent({
}); });
} }
}) })
}, }
// printtTicketAll() { // Lifecycle
// let path = import.meta.env.VITE_PRINT_URL + '/command/printticket/print_today'; onMounted(() => {
// axios({ userStatus()
// method: 'get', getPage(page.value)
// url: path, get_totals()
// headers: authHeader(),
// }).then((response: any) => {
// if (response.data.ok) {
// notify({
// title: "Success",
// text: "Sent to Printer",
// type: "success",
// });
// this.getPage(this.page)
// } else {
// notify({
// title: "Failure",
// text: "error printing",
// type: "success",
// });
// }
// })
// },
// printTicket(delivery_id: number) {
// let path = import.meta.env.VITE_PRINT_URL + '/command/printticket/' + delivery_id;
// axios({
// method: 'options',
// url: path,
// headers: authHeader(),
// }).then((response: any) => {
// if (response.data.ok) {
// notify({
// title: "Success",
// text: "Sent to Printer",
// type: "success",
// });
// this.getPage(this.page)
// } else {
// notify({
// title: "Failure",
// text: "error printing",
// type: "success",
// });
// }
// })
// },
},
}) })
</script> </script>

View File

@@ -157,56 +157,44 @@
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted } from 'vue'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../../services/auth.header' import authHeader from '../../../services/auth.header'
import { Delivery } from '../../../types/models'
import Header from '../../../layouts/headers/headerauth.vue' import Header from '../../../layouts/headers/headerauth.vue'
import PaginationComp from '../../../components/pagination.vue' import PaginationComp from '../../../components/pagination.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue' import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue' import Footer from '../../../layouts/footers/footer.vue'
import { notify } from "@kyvg/vue3-notification"; import { notify } from "@kyvg/vue3-notification";
export default defineComponent({ interface TownTotal {
name: 'deliveryOutForDelivery', town: string;
gallons: number;
}
components: { // Reactive data
Header, const token = ref(null)
SideBar, const user = ref(null)
Footer, const deliveries = ref<Delivery[]>([])
}, const totals = ref<TownTotal[]>([])
const grand_total = ref(0)
data() { const page = ref(1)
return { const perPage = ref(50)
token: null, const recordsLength = ref(0)
user: null, const options = ref({
deliveries: [] as any[],
totals: [] as any[],
grand_total: 0,
page: 1,
perPage: 50,
recordsLength: 0,
options: {
edgeNavigation: false, edgeNavigation: false,
format: false, format: false,
template: PaginationComp template: PaginationComp
} })
}
},
created() {
this.userStatus()
},
mounted() {
this.getPage(this.page)
this.get_totals()
},
methods: {
getPage: function (page: any) {
this.deliveries = [];
this.get_oil_orders(page)
},
userStatus() { // Functions
const getPage = (pageVal: any) => {
deliveries.value = [];
get_oil_orders(pageVal)
}
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -216,41 +204,42 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
.catch(() => { .catch(() => {
this.user = null user.value = null
}) })
}, }
get_oil_orders(page: any) {
let path = import.meta.env.VITE_BASE_URL + '/delivery/tommorrow/' + page; const get_oil_orders = (pageVal: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/tommorrow/' + pageVal;
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: any) => {
this.deliveries = response.data deliveries.value = response.data
}) })
}, }
get_totals() { const get_totals = () => {
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/tomorrow-totals'; let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/tomorrow-totals';
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: any) => {
this.totals = response.data.totals || [] totals.value = response.data.totals || []
this.grand_total = response.data.grand_total || 0 grand_total.value = response.data.grand_total || 0
}).catch((error: any) => { }).catch((error: any) => {
console.error('Error fetching totals:', error); console.error('Error fetching totals:', error);
this.totals = [] totals.value = []
this.grand_total = 0 grand_total.value = 0
}) })
}, }
deleteCall(delivery_id: any) { const deleteCall = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id; let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
axios({ axios({
method: 'delete', method: 'delete',
@@ -263,7 +252,7 @@ export default defineComponent({
text: "deleted delivery", text: "deleted delivery",
type: "success", type: "success",
}); });
this.getPage(this.page) getPage(page.value)
} else { } else {
notify({ notify({
title: "Failure", title: "Failure",
@@ -272,10 +261,9 @@ export default defineComponent({
}); });
} }
}) })
}, }
const printtTicketAll = () => {
printtTicketAll() {
let path = import.meta.env.VITE_PRINT_URL + '/command/printticket/print_tommorrow'; let path = import.meta.env.VITE_PRINT_URL + '/command/printticket/print_tommorrow';
axios({ axios({
method: 'delete', method: 'delete',
@@ -288,7 +276,7 @@ export default defineComponent({
text: "Sent to Printer", text: "Sent to Printer",
type: "success", type: "success",
}); });
this.getPage(this.page) getPage(page.value)
} else { } else {
notify({ notify({
title: "Failure", title: "Failure",
@@ -297,9 +285,9 @@ export default defineComponent({
}); });
} }
}) })
}, }
printTicket(delivery_id: number) { const printTicket = (delivery_id: number) => {
let path = import.meta.env.VITE_PRINT_URL + '/command/printticket/' + delivery_id; let path = import.meta.env.VITE_PRINT_URL + '/command/printticket/' + delivery_id;
axios({ axios({
method: 'delete', method: 'delete',
@@ -312,7 +300,7 @@ export default defineComponent({
text: "Sent to Printer", text: "Sent to Printer",
type: "success", type: "success",
}); });
this.getPage(this.page) getPage(page.value)
} else { } else {
notify({ notify({
title: "Failure", title: "Failure",
@@ -321,9 +309,12 @@ export default defineComponent({
}); });
} }
}) })
}, }
},
// Lifecycle
onMounted(() => {
userStatus()
getPage(page.value)
get_totals()
}) })
</script> </script>

View File

@@ -137,58 +137,40 @@
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted } from 'vue'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../../services/auth.header' import authHeader from '../../../services/auth.header'
import { deliveryService } from '../../../services/deliveryService'
import { Delivery } from '../../../types/models'
import Header from '../../../layouts/headers/headerauth.vue' import Header from '../../../layouts/headers/headerauth.vue'
import PaginationComp from '../../../components/pagination.vue' import PaginationComp from '../../../components/pagination.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue' import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue' import Footer from '../../../layouts/footers/footer.vue'
import { notify } from "@kyvg/vue3-notification"; import { notify } from "@kyvg/vue3-notification";
export default defineComponent({ // Reactive data
name: 'deliveryWaiting', const token = ref(null)
const user = ref(null)
components: { const deliveries = ref<Delivery[]>([])
Header, const totals = ref<{ town: string; gallons: number }[]>([])
SideBar, const grand_total = ref(0)
Footer, const page = ref(1)
}, const perPage = ref(50)
const recordsLength = ref(0)
data() { const options = ref({
return {
token: null,
user: null,
deliveries: [] as any[],
totals: [] as any[],
grand_total: 0,
page: 1,
perPage: 50,
recordsLength: 0,
options: {
edgeNavigation: false, edgeNavigation: false,
format: false, format: false,
template: PaginationComp template: PaginationComp
} })
}
},
created() { // Functions
this.userStatus() const getPage = (pageVal: any) => {
}, deliveries.value = [];
mounted() { get_oil_orders(pageVal)
this.getPage(this.page) }
this.get_totals()
},
methods: {
getPage: function (page: any) {
// we simulate an api call that fetch the records from a backend
this.deliveries = [];
this.get_oil_orders(page)
},
userStatus() { const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -198,40 +180,41 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
.catch(() => { .catch(() => {
this.user = null user.value = null
}) })
}, }
get_oil_orders(page: any) {
let path = import.meta.env.VITE_BASE_URL + '/delivery/waiting/' + page; const get_oil_orders = async (pageVal: number) => {
axios({ try {
method: 'get', const response = await deliveryService.getWaiting(pageVal)
url: path, deliveries.value = response.data || []
headers: authHeader(), } catch (error) {
}).then((response: any) => { console.error('Error fetching waiting deliveries:', error)
this.deliveries = response.data deliveries.value = []
}) }
}, }
get_totals() {
const get_totals = () => {
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/waiting-totals'; let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/waiting-totals';
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: any) => {
this.totals = response.data.totals || [] totals.value = response.data.totals || []
this.grand_total = response.data.grand_total || 0 grand_total.value = response.data.grand_total || 0
}).catch((error: any) => { }).catch((error: any) => {
console.error('Error fetching totals:', error); console.error('Error fetching totals:', error);
this.totals = [] totals.value = []
this.grand_total = 0 grand_total.value = 0
}) })
}, }
deleteCall(delivery_id: any) { const deleteCall = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id; let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
axios({ axios({
method: 'delete', method: 'delete',
@@ -244,7 +227,7 @@ export default defineComponent({
text: "deleted delivery", text: "deleted delivery",
type: "success", type: "success",
}); });
this.getPage(this.page) getPage(page.value)
} else { } else {
notify({ notify({
title: "Failure", title: "Failure",
@@ -253,11 +236,13 @@ export default defineComponent({
}); });
} }
}) })
}, }
// Lifecycle
onMounted(() => {
}, userStatus()
getPage(page.value)
get_totals()
}) })
</script> </script>

View File

@@ -62,6 +62,7 @@ import useValidate from "@vuelidate/core";
import { required, minLength, helpers } from "@vuelidate/validators"; import { required, minLength, helpers } from "@vuelidate/validators";
import Header from "../../layouts/headers/headerauth.vue"; import Header from "../../layouts/headers/headerauth.vue";
import authHeader from "../../services/auth.header"; import authHeader from "../../services/auth.header";
import {Employee} from '../../types/models';
export default defineComponent({ export default defineComponent({
name: "EmployeeChangePassword", name: "EmployeeChangePassword",
@@ -74,7 +75,7 @@ export default defineComponent({
user: null, user: null,
user_admin: 0, user_admin: 0,
loaded: false, loaded: false,
employee: null as any, employee: {} as Employee,
ChangePasswordForm: { ChangePasswordForm: {
new_password: "", new_password: "",
password_confirm: "", password_confirm: "",

View File

@@ -45,7 +45,8 @@
<td>{{ person.employee_phone_number }}</td> <td>{{ person.employee_phone_number }}</td>
<td class="text-right"> <td class="text-right">
<div class="flex items-center justify-end gap-2"> <div class="flex items-center justify-end gap-2">
<router-link :to="{ name: 'employeeEdit', params: { id: person.user_id } }" class="btn btn-sm btn-secondary">Edit</router-link> <router-link :to="{ name: 'employeeEdit', params: { id: person.user_id || 0 } }" class="btn btn-sm btn-secondary">Edit</router-link>
<router-link :to="{ name: 'employeeEdit', params: { id: person.user_id || 0 } }" class="btn btn-sm btn-secondary">Edit</router-link>
<router-link :to="{ name: 'employeeProfile', params: { id: person.id } }" class="btn btn-sm btn-ghost">View</router-link> <router-link :to="{ name: 'employeeProfile', params: { id: person.id } }" class="btn btn-sm btn-ghost">View</router-link>
<router-link v-if="user && user.user_admin === 0" :to="{ name: 'employeeChangePassword', params: { id: person.id } }" class="btn btn-sm btn-warning">Change Password</router-link> <router-link v-if="user && user.user_admin === 0" :to="{ name: 'employeeChangePassword', params: { id: person.id } }" class="btn btn-sm btn-warning">Change Password</router-link>
</div> </div>
@@ -73,7 +74,7 @@
<p>{{ person.employee_phone_number }}</p> <p>{{ person.employee_phone_number }}</p>
</div> </div>
<div class="card-actions justify-end flex-wrap gap-2 mt-2"> <div class="card-actions justify-end flex-wrap gap-2 mt-2">
<router-link :to="{ name: 'employeeEdit', params: { id: person.user_id } }" class="btn btn-sm btn-secondary">Edit</router-link> <router-link :to="{ name: 'employeeEdit', params: { id: person.user_id || 0 } }" class="btn btn-sm btn-secondary">Edit</router-link>
<router-link :to="{ name: 'employeeProfile', params: { id: person.id } }" class="btn btn-sm btn-ghost">View</router-link> <router-link :to="{ name: 'employeeProfile', params: { id: person.id } }" class="btn btn-sm btn-ghost">View</router-link>
<router-link v-if="user && user.user_admin === 0" :to="{ name: 'employeeChangePassword', params: { id: person.id } }" class="btn btn-sm btn-warning">Change Password</router-link> <router-link v-if="user && user.user_admin === 0" :to="{ name: 'employeeChangePassword', params: { id: person.id } }" class="btn btn-sm btn-warning">Change Password</router-link>
</div> </div>
@@ -97,6 +98,7 @@ import axios from 'axios'
import authHeader from '../../services/auth.header' import authHeader from '../../services/auth.header'
import PaginationComp from '../../components/pagination.vue' import PaginationComp from '../../components/pagination.vue'
import Footer from '../../layouts/footers/footer.vue' import Footer from '../../layouts/footers/footer.vue'
import {Employee, User} from '../../types/models'
export default defineComponent({ export default defineComponent({
name: 'EmployeeHome', name: 'EmployeeHome',
@@ -105,8 +107,8 @@ export default defineComponent({
}, },
data() { data() {
return { return {
user: {} as any, user: {} as User,
employees: [] as any[], employees: [] as Employee[],
page: 1, page: 1,
perPage: 50, perPage: 50,
recordsLength: 0, recordsLength: 0,
@@ -136,7 +138,7 @@ export default defineComponent({
} }
}) })
.catch(() => { .catch(() => {
this.user = null; this.user = {} as User;
}); });
}, },
// --- METHOD CORRECTED TO MATCH YOUR SIMPLE ARRAY API RESPONSE --- // --- METHOD CORRECTED TO MATCH YOUR SIMPLE ARRAY API RESPONSE ---

View File

@@ -1,10 +1,10 @@
import EmployeeHome from '../employee/home.vue'; const EmployeeHome = () => import('../employee/home.vue');
import EmployeeCreate from "../employee/create.vue"; const EmployeeCreate = () => import("../employee/create.vue");
import EmployeeEdit from "../employee/edit.vue"; const EmployeeEdit = () => import("../employee/edit.vue");
import EmployeeProfile from "../employee/profile/home.vue"; const EmployeeProfile = () => import("../employee/profile/home.vue");
import EmployeeChangePassword from "../employee/changepassword.vue"; const EmployeeChangePassword = () => import("../employee/changepassword.vue");
const employeeRoutes = [ const employeeRoutes = [
{ {

View File

@@ -1,6 +1,6 @@
import MoneyYear from '../money/profit_year.vue'; const MoneyYear = () => import('../money/profit_year.vue');

View File

@@ -202,31 +202,39 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, watch } from 'vue' import { ref, computed, onMounted, watch } from 'vue'
import axios from 'axios' import { useRoute, useRouter } from 'vue-router'
import axios, { AxiosResponse, AxiosError } from 'axios'
import authHeader from '../../../services/auth.header' import authHeader from '../../../services/auth.header'
import { notify } from "@kyvg/vue3-notification" import { notify } from "@kyvg/vue3-notification"
import type {
CustomerFormData,
CreditCardFormData,
PricingData,
OilPricingResponse,
WhoAmIResponse,
AutoDeliveryData
} from '../../../types/models'
export default defineComponent({ const route = useRoute()
name: 'AuthorizePrechargeAutho', const router = useRouter()
data() { // Reactive data
return { const deliveryId = ref(route.params.id as string)
deliveryId: this.$route.params.id as string, const loaded = ref(false)
loaded: false, const chargeAmount = ref(0)
chargeAmount: 0, const quickGallonAmounts = ref([100, 125, 150, 175, 200, 220])
quickGallonAmounts: [100, 125, 150, 175, 200, 220], const loading = ref(false)
loading: false, const action = ref('') // 'preauthorize' or 'charge'
action: '', // 'preauthorize' or 'charge' const error = ref('')
error: '', const success = ref('')
success: '', const isChargeConfirmationModalVisible = ref(false)
isChargeConfirmationModalVisible: false, const transactionId = ref(0)
transactionId: 0, const user = ref({
user: {
user_id: 0, user_id: 0,
}, })
autoDelivery: { const autoDelivery = ref<AutoDeliveryData>({
id: 0, id: 0,
customer_id: 0, customer_id: 0,
customer_full_name: '', customer_full_name: '',
@@ -236,10 +244,17 @@ export default defineComponent({
customer_zip: '', customer_zip: '',
tank_size: 0, tank_size: 0,
estimated_gallons_left: 0, estimated_gallons_left: 0,
estimated_gallons_left_prev_day: 0,
house_factor: 0, house_factor: 0,
auto_status: 0, auto_status: 0,
}, account_number: '',
credit_cards: [ last_fill: '',
days_since_last_fill: 0,
last_updated: '',
tank_height: '',
open_ticket_id: null,
})
const credit_cards = ref<CreditCardFormData[]>([
{ {
id: 0, id: 0,
name_on_card: '', name_on_card: '',
@@ -250,10 +265,9 @@ export default defineComponent({
last_four_digits: '', last_four_digits: '',
expiration_year: '', expiration_year: '',
security_number: '', security_number: '',
} }
], ])
customer: { const customer = ref<CustomerFormData>({
id: 0, id: 0,
user_id: 0, user_id: 0,
customer_first_name: '', customer_first_name: '',
@@ -266,8 +280,8 @@ export default defineComponent({
customer_home_type: 0, customer_home_type: 0,
customer_phone_number: '', customer_phone_number: '',
account_number: '', account_number: '',
}, })
pricing: { const pricing = ref<PricingData>({
price_from_supplier: 0, price_from_supplier: 0,
price_for_customer: 0, price_for_customer: 0,
price_for_employee: 0, price_for_employee: 0,
@@ -275,64 +289,56 @@ export default defineComponent({
price_prime: 0, price_prime: 0,
price_emergency: 0, price_emergency: 0,
date: "", date: "",
}, })
currentOilPrice: 0, const currentOilPrice = ref(0)
pricingTiers: [] as { gallons: number; price: number }[], const pricingTiers = ref([] as { gallons: number; price: number }[])
}
},
computed: { // Computed
selectedCard(): any { const selectedCard = computed(() => {
return this.credit_cards.find((card: any) => card.main_card) || this.credit_cards[0] return credit_cards.value.find((card) => card.main_card) || credit_cards.value[0]
}, })
customerStateName(): string {
const customerStateName = computed(() => {
const states: Record<number, string> = { 0: 'Massachusetts', 1: 'Rhode Island', 2: 'New Hampshire', 3: 'Maine', 4: 'Vermont', 5: 'Connecticut', 6: 'New York' }; const states: Record<number, string> = { 0: 'Massachusetts', 1: 'Rhode Island', 2: 'New Hampshire', 3: 'Maine', 4: 'Vermont', 5: 'Connecticut', 6: 'New York' };
return states[this.customer.customer_state] || 'Unknown state'; return states[customer.value.customer_state] || 'Unknown state';
}, })
selectedGallonAmount(): number | null {
return this.quickGallonAmounts.find(gal => this.calculatePriceForGallons(gal).toFixed(2) === this.chargeAmount.toFixed(2)) || null; const selectedGallonAmount = computed(() => {
return quickGallonAmounts.value.find(gal => calculatePriceForGallons(gal).toFixed(2) === chargeAmount.value.toFixed(2)) || null;
})
// Watchers
watch(() => route.params.id, (newId) => {
if (newId !== deliveryId.value) {
resetState()
deliveryId.value = newId as string
loadData(newId as string)
} }
}, })
mounted() { // Lifecycle
this.loadData(this.deliveryId) onMounted(() => {
this.getPricingTiers() loadData(deliveryId.value)
}, getPricingTiers()
})
created() { // Functions
this.watchRoute() const resetState = () => {
}, loading.value = false
action.value = ''
error.value = ''
success.value = ''
chargeAmount.value = 0
}
methods: { const loadData = (deliveryId: string) => {
watchRoute() { userStatus()
watch( getAutoDelivery(deliveryId)
() => this.$route.params.id, getOilPricing()
(newId) => { getCurrentOilPrice()
if (newId !== this.deliveryId) { }
this.resetState()
this.deliveryId = newId as string
this.loadData(newId as string)
}
}
)
},
resetState() { const getCurrentOilPrice = () => {
this.loading = false
this.action = ''
this.error = ''
this.success = ''
this.chargeAmount = 0
},
loadData(deliveryId: string) {
this.userStatus()
this.getAutoDelivery(deliveryId)
this.getOilPricing()
this.getCurrentOilPrice()
},
getCurrentOilPrice() {
let path = import.meta.env.VITE_BASE_URL + '/info/price/oil' let path = import.meta.env.VITE_BASE_URL + '/info/price/oil'
axios({ axios({
method: "get", method: "get",
@@ -340,9 +346,9 @@ export default defineComponent({
withCredentials: true, withCredentials: true,
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: AxiosResponse<OilPricingResponse>) => {
this.currentOilPrice = response.data.price_for_customer; currentOilPrice.value = response.data.price_for_customer;
this.calculateDefaultChargeAmount() calculateDefaultChargeAmount()
}) })
.catch(() => { .catch(() => {
notify({ notify({
@@ -351,13 +357,13 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
calculateDefaultChargeAmount() { const calculateDefaultChargeAmount = () => {
this.chargeAmount = this.calculateTotalAsNumber() chargeAmount.value = calculateTotalAsNumber()
}, }
userStatus() { const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -365,23 +371,23 @@ export default defineComponent({
withCredentials: true, withCredentials: true,
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: AxiosResponse<WhoAmIResponse>) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
}, }
getOilPricing() { const getOilPricing = () => {
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/table"; let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/table";
axios({ axios({
method: "get", method: "get",
url: path, url: path,
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: AxiosResponse<OilPricingResponse>) => {
this.pricing = response.data; pricing.value = response.data;
this.calculateDefaultChargeAmount() calculateDefaultChargeAmount()
}) })
.catch(() => { .catch(() => {
notify({ notify({
@@ -390,57 +396,57 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
getAutoDelivery(delivery_id: any) { const getAutoDelivery = (delivery_id: number | string) => {
let path = import.meta.env.VITE_AUTO_URL + "/delivery/delivery/" + delivery_id; let path = import.meta.env.VITE_AUTO_URL + "/delivery/delivery/" + delivery_id;
axios({ axios({
method: "get", method: "get",
url: path, url: path,
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: AxiosResponse<AutoDeliveryData>) => {
if (response.data && response.data.customer_id) { if (response.data && response.data.customer_id) {
this.autoDelivery = response.data; autoDelivery.value = response.data;
this.getCustomer(this.autoDelivery.customer_id) getCustomer(autoDelivery.value.customer_id)
this.getCreditCards(this.autoDelivery.customer_id) getCreditCards(autoDelivery.value.customer_id)
} else { } else {
console.error("API Error:", response.data.error || "Failed to fetch auto delivery data."); console.error("API Error: Failed to fetch auto delivery data.");
} }
}) })
.catch((error: any) => { .catch((err: Error) => {
console.error("API Error in getAutoDelivery:", error); console.error("API Error in getAutoDelivery:", err);
notify({ notify({
title: "Error", title: "Error",
text: "Could not get automatic delivery", text: "Could not get automatic delivery",
type: "error", type: "error",
}); });
}); });
}, }
getCreditCards(user_id: any) { const getCreditCards = (user_id: number) => {
let path = import.meta.env.VITE_BASE_URL + '/payment/cards/' + user_id; let path = import.meta.env.VITE_BASE_URL + '/payment/cards/' + user_id;
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: AxiosResponse<CreditCardFormData[]>) => {
this.credit_cards = response.data credit_cards.value = response.data
}) })
}, }
getCustomer(userid: any) { const getCustomer = (userid: number) => {
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid; let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: AxiosResponse<CustomerFormData>) => {
this.customer = response.data customer.value = response.data
}) })
}, }
getPricingTiers() { const getPricingTiers = () => {
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/tiers"; let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/tiers";
axios({ axios({
method: "get", method: "get",
@@ -448,8 +454,8 @@ export default defineComponent({
withCredentials: true, withCredentials: true,
headers: authHeader() headers: authHeader()
}) })
.then((response: any) => { .then((response: AxiosResponse<Record<string, number>>) => {
this.pricingTiers = Object.entries(response.data).map(([gallons, price]) => ({ gallons: parseInt(gallons, 10), price: Number(price) })); pricingTiers.value = Object.entries(response.data).map(([gallons, price]) => ({ gallons: parseInt(gallons, 10), price: Number(price) }));
}) })
.catch(() => { .catch(() => {
notify({ notify({
@@ -458,16 +464,16 @@ export default defineComponent({
type: "error" type: "error"
}); });
}); });
}, }
isPricingTierSelected(tierGallons: number | string): boolean { const isPricingTierSelected = (tierGallons: number | string): boolean => {
const calculated = this.calculateGallonsToFill() const calculated = calculateGallonsToFill()
return calculated == Number(tierGallons) return calculated == Number(tierGallons)
}, }
calculatePriceForGallons(gallons: number): number { const calculatePriceForGallons = (gallons: number): number => {
let priceForGallons = 0; let priceForGallons = 0;
const sortedTiers = [...this.pricingTiers].sort((a, b) => Number(a.gallons) - Number(b.gallons)); const sortedTiers = [...pricingTiers.value].sort((a, b) => Number(a.gallons) - Number(b.gallons));
// Find the highest tier that is less than or equal to the gallons ordered // Find the highest tier that is less than or equal to the gallons ordered
let applicableTier = sortedTiers.filter(t => gallons >= Number(t.gallons)).pop(); let applicableTier = sortedTiers.filter(t => gallons >= Number(t.gallons)).pop();
@@ -483,79 +489,79 @@ export default defineComponent({
} }
return priceForGallons; return priceForGallons;
}, }
setGallons(gallons: number) { const setGallons = (gallons: number) => {
this.chargeAmount = this.calculatePriceForGallons(gallons); chargeAmount.value = calculatePriceForGallons(gallons);
}, }
calculateGallonsToFill() { const calculateGallonsToFill = () => {
return this.autoDelivery.tank_size - this.autoDelivery.estimated_gallons_left return autoDelivery.value.tank_size - autoDelivery.value.estimated_gallons_left
}, }
calculateSubtotal() { const calculateSubtotal = () => {
const gallons = this.calculateGallonsToFill() const gallons = calculateGallonsToFill()
const pricePerGallon = this.pricing.price_for_customer || this.currentOilPrice const pricePerGallon = pricing.value.price_for_customer || currentOilPrice.value
return (gallons * pricePerGallon).toFixed(2) return (gallons * pricePerGallon).toFixed(2)
}, }
calculateTotalAmount() { const calculateTotalAmount = () => {
const subtotal = parseFloat(this.calculateSubtotal()) const subtotal = parseFloat(calculateSubtotal())
let total = subtotal let total = subtotal
// No additional fees for auto preauthorization // No additional fees for auto preauthorization
return total.toFixed(2) return total.toFixed(2)
}, }
calculateTotalAsNumber() { const calculateTotalAsNumber = () => {
return parseFloat(this.calculateTotalAmount()) return parseFloat(calculateTotalAmount())
}, }
async handlePreauthorize() { const handlePreauthorize = async () => {
await this.processPayment('preauthorize') await processPayment('preauthorize')
}, }
async handleChargeNow() { const handleChargeNow = async () => {
this.loading = true loading.value = true
this.isChargeConfirmationModalVisible = true isChargeConfirmationModalVisible.value = true
}, }
async proceedWithCharge() { const proceedWithCharge = async () => {
this.isChargeConfirmationModalVisible = false isChargeConfirmationModalVisible.value = false
await this.processPayment('charge') await processPayment('charge')
}, }
cancelCharge() { const cancelCharge = () => {
this.isChargeConfirmationModalVisible = false isChargeConfirmationModalVisible.value = false
}, }
async processPayment(actionType: string) { const processPayment = async (actionType: string) => {
if (!this.selectedCard) { if (!selectedCard.value) {
this.error = 'No credit card found for this customer' error.value = 'No credit card found for this customer'
return return
} }
this.loading = true loading.value = true
this.action = actionType action.value = actionType
this.error = '' error.value = ''
this.success = '' success.value = ''
try { try {
// Step 2: If payment method is credit, perform the pre-authorization // Step 2: If payment method is credit, perform the pre-authorization
if (actionType === 'preauthorize') { if (actionType === 'preauthorize') {
if (!this.chargeAmount || this.chargeAmount <= 0) { if (!chargeAmount.value || chargeAmount.value <= 0) {
throw new Error("Pre-authorization amount must be greater than zero."); throw new Error("Pre-authorization amount must be greater than zero.");
} }
const authPayload = { const authPayload = {
card_id: (this.selectedCard as any).id, card_id: selectedCard.value!.id,
preauthorize_amount: this.chargeAmount.toFixed(2), preauthorize_amount: chargeAmount.value.toFixed(2),
delivery_id: null, delivery_id: null,
auto_id: this.deliveryId, auto_id: deliveryId.value,
}; };
const authPath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/authorize/saved-card/${this.customer.id}`; const authPath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/authorize/saved-card/${customer.value.id}`;
const response = await axios.post(authPath, authPayload, { withCredentials: true, headers: authHeader() }); const response = await axios.post(authPath, authPayload, { withCredentials: true, headers: authHeader() });
@@ -563,50 +569,50 @@ export default defineComponent({
throw new Error("Failed transaction: No Authorize.net transaction ID received"); throw new Error("Failed transaction: No Authorize.net transaction ID received");
} }
this.transactionId = response.data.id; transactionId.value = response.data.id;
// Update auto_delivery status to 3 // Update auto_delivery status to 3
await axios.put(`${import.meta.env.VITE_AUTO_URL}/delivery/update_status/${this.deliveryId}`, {}, { withCredentials: true, headers: authHeader() }); await axios.put(`${import.meta.env.VITE_AUTO_URL}/delivery/update_status/${deliveryId.value}`, {}, { withCredentials: true, headers: authHeader() });
// Create Tickets_Auto_Delivery after successful preauthorize // Create Tickets_Auto_Delivery after successful preauthorize
const ticketPayload = { const ticketPayload = {
gallons_delivered: 0, gallons_delivered: 0,
payment_type: 11, // 11 for preauthorize, 1 for charge payment_type: 11, // 11 for preauthorize, 1 for charge
payment_card_id: this.selectedCard.id, payment_card_id: selectedCard.value!.id,
payment_status: 1 // Pre-authorized status (ready for capture) payment_status: 1 // Pre-authorized status (ready for capture)
}; };
const ticketUrl = `${import.meta.env.VITE_AUTO_URL}/confirm/auto/create/${this.deliveryId}`; const ticketUrl = `${import.meta.env.VITE_AUTO_URL}/confirm/auto/create/${deliveryId.value}`;
const ticketResponse = await axios.post(ticketUrl, ticketPayload, { withCredentials: true, headers: authHeader() }); const ticketResponse = await axios.post(ticketUrl, ticketPayload, { withCredentials: true, headers: authHeader() });
console.log('Ticket response data:', ticketResponse.data, 'type:', typeof ticketResponse.data, 'keys:', ticketResponse.data ? Object.keys(ticketResponse.data) : 'no data'); console.log('Ticket response data:', ticketResponse.data, 'type:', typeof ticketResponse.data, 'keys:', ticketResponse.data ? Object.keys(ticketResponse.data) : 'no data');
// Update transaction auto_id to ticket ID // Update transaction auto_id to ticket ID
if (this.transactionId && ticketResponse.data) { if (transactionId.value && ticketResponse.data) {
const data = Array.isArray(ticketResponse.data) ? ticketResponse.data[0] : ticketResponse.data; const data = Array.isArray(ticketResponse.data) ? ticketResponse.data[0] : ticketResponse.data;
if (data && data.auto_ticket_id !== undefined) { if (data && data.auto_ticket_id !== undefined) {
await axios.put(`${import.meta.env.VITE_AUTHORIZE_URL}/api/transaction/${this.transactionId}/update_auto_id/${data.auto_ticket_id}`, {}, { withCredentials: true, headers: authHeader() }); await axios.put(`${import.meta.env.VITE_AUTHORIZE_URL}/api/transaction/${transactionId.value}/update_auto_id/${data.auto_ticket_id}`, {}, { withCredentials: true, headers: authHeader() });
} else { } else {
console.error('auto_ticket_id is undefined in ticket response'); console.error('auto_ticket_id is undefined in ticket response');
} }
} }
// On successful authorization, show success and redirect // On successful authorization, show success and redirect
this.success = `Preauthorization successful! Transaction ID: ${response.data.auth_net_transaction_id}`; success.value = `Preauthorization successful! Transaction ID: ${response.data.auth_net_transaction_id}`;
setTimeout(() => { setTimeout(() => {
this.$router.push({ name: "auto" }); router.push({ name: "auto" });
}, 2000); }, 2000);
} }
else { // Handle 'charge' action else { // Handle 'charge' action
if (!this.chargeAmount || this.chargeAmount <= 0) { if (!chargeAmount.value || chargeAmount.value <= 0) {
throw new Error("Charge amount must be greater than zero."); throw new Error("Charge amount must be greater than zero.");
} }
const chargePayload = { const chargePayload = {
card_id: (this.selectedCard as any).id, card_id: selectedCard.value!.id,
charge_amount: this.chargeAmount.toFixed(2), charge_amount: chargeAmount.value.toFixed(2),
delivery_id: this.deliveryId, delivery_id: deliveryId.value,
}; };
const chargePath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/charge/saved-card/${this.customer.id}`; const chargePath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/charge/saved-card/${customer.value.id}`;
console.log('=== DEBUG: Charge payload ==='); console.log('=== DEBUG: Charge payload ===');
console.log('Calling endpoint:', chargePath); console.log('Calling endpoint:', chargePath);
@@ -615,50 +621,49 @@ export default defineComponent({
const response = await axios.post(chargePath, chargePayload, { withCredentials: true, headers: authHeader() }); const response = await axios.post(chargePath, chargePayload, { withCredentials: true, headers: authHeader() });
// Update auto_delivery status to 3 // Update auto_delivery status to 3
await axios.put(`${import.meta.env.VITE_AUTO_URL}/delivery/update_status/${this.deliveryId}`, {}, { withCredentials: true, headers: authHeader() }); await axios.put(`${import.meta.env.VITE_AUTO_URL}/delivery/update_status/${deliveryId.value}`, {}, { withCredentials: true, headers: authHeader() });
// Status codes: 0 = APPROVED, 1 = DECLINED (based on backend TransactionStatus enum) // Status codes: 0 = APPROVED, 1 = DECLINED (based on backend TransactionStatus enum)
if (response.data && response.data.status === 0) { // 0 = APPROVED if (response.data && response.data.status === 0) { // 0 = APPROVED
if (!response.data.auth_net_transaction_id || response.data.auth_net_transaction_id.trim() === '') { if (!response.data.auth_net_transaction_id || response.data.auth_net_transaction_id.trim() === '') {
throw new Error("Failed transaction: No Authorize.net transaction ID received"); throw new Error("Failed transaction: No Authorize.net transaction ID received");
} }
this.transactionId = response.data.id; transactionId.value = response.data.id;
this.success = `Charge successful! Transaction ID: ${response.data.auth_net_transaction_id}`; success.value = `Charge successful! Transaction ID: ${response.data.auth_net_transaction_id}`;
// Create Tickets_Auto_Delivery after successful charge // Create Tickets_Auto_Delivery after successful charge
const ticketPayload = { const ticketPayload = {
gallons_delivered: 0, gallons_delivered: 0,
payment_type: 11, // 11 for Authorize charge payment_type: 11, // 11 for Authorize charge
payment_card_id: this.selectedCard.id, payment_card_id: selectedCard.value!.id,
payment_status: response.data.status // 0 = APPROVED payment_status: response.data.status // 0 = APPROVED
}; };
const ticketUrl = `${import.meta.env.VITE_AUTO_URL}/confirm/auto/create/${this.deliveryId}`; const ticketUrl = `${import.meta.env.VITE_AUTO_URL}/confirm/auto/create/${deliveryId.value}`;
console.log("POOOPP!") console.log("POOOPP!")
await axios.post(ticketUrl, ticketPayload, { withCredentials: true, headers: authHeader() }); await axios.post(ticketUrl, ticketPayload, { withCredentials: true, headers: authHeader() });
setTimeout(() => { setTimeout(() => {
this.$router.push({ name: "auto" }); router.push({ name: "auto" });
}, 2000); }, 2000);
} else { } else {
// The error message from your backend will be more specific now // The error message from your backend will be more specific now
throw new Error(`Payment charge failed: ${response.data?.rejection_reason || 'Unknown error'}`); throw new Error(`Payment charge failed: ${response.data?.rejection_reason || 'Unknown error'}`);
} }
} }
} catch (error: any) { } catch (err: unknown) {
console.log(error) const axiosErr = err as AxiosError<{ detail?: string }>;
this.error = error.response?.data?.detail || `Failed to ${actionType} payment` console.log(err)
error.value = axiosErr.response?.data?.detail || `Failed to ${actionType} payment`
notify({ notify({
title: "Error", title: "Error",
text: this.error, text: error.value,
type: "error", type: "error",
}) })
} finally { } finally {
this.loading = false loading.value = false
this.action = '' action.value = ''
} }
} }
},
})
</script> </script>
<style scoped></style> <style scoped></style>

View File

@@ -237,28 +237,39 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted } from 'vue'
import axios from 'axios' import { useRoute, useRouter } from 'vue-router'
import axios, { AxiosResponse, AxiosError } from 'axios'
import authHeader from '../../../services/auth.header' import authHeader from '../../../services/auth.header'
import { notify } from "@kyvg/vue3-notification" import { notify } from "@kyvg/vue3-notification"
import type {
CustomerFormData,
CreditCardFormData,
PricingData,
CustomerDescriptionData,
AutoTicketData,
AutoDeliveryData,
PaymentCardResponse,
AuthorizeNetTransactionResponse
} from '../../../types/models'
export default defineComponent({ const route = useRoute()
name: 'captureAuthorizeAuto', const router = useRouter()
data() { // Reactive data
return { const loading = ref(false)
loading: false, const userCardfound = ref(false)
userCardfound: false, const gallonsDelivered = ref('')
gallonsDelivered: '', const captureAmount = ref(0)
captureAmount: 0, const preAuthAmount = ref(0)
preAuthAmount: 0, const transaction = ref<AuthorizeNetTransactionResponse | null>(null)
transaction: null as any, const showPaymentModal = ref(false)
showPaymentModal: false, const modalStep = ref(0)
modalStep: 0, const modalCapturedAmount = ref(0)
modalCapturedAmount: 0,
userCard: { const userCard = ref<CreditCardFormData>({
id: 0,
date_added: '', date_added: '',
user_id: '', user_id: '',
card_number: '', card_number: '',
@@ -269,16 +280,16 @@ export default defineComponent({
type_of_card: '', type_of_card: '',
security_number: '', security_number: '',
accepted_or_declined: '', accepted_or_declined: '',
main_card: '', main_card: false,
}, })
customerDescription: { const customerDescription = ref<CustomerDescriptionData>({
customer_id: 0, customer_id: 0,
account_number: '', account_number: '',
company_id: 0, company_id: 0,
fill_location: 0, fill_location: 0,
description: '', description: '',
}, })
customer: { const customer = ref<CustomerFormData>({
id: 0, id: 0,
user_id: 0, user_id: 0,
customer_address: '', customer_address: '',
@@ -290,8 +301,9 @@ export default defineComponent({
customer_apt: '', customer_apt: '',
customer_home_type: 0, customer_home_type: 0,
customer_phone_number: '', customer_phone_number: '',
}, account_number: '',
autoDelivery: { })
const autoDelivery = ref<AutoDeliveryData>({
id: 0, id: 0,
customer_id: 0, customer_id: 0,
account_number: '', account_number: '',
@@ -310,33 +322,28 @@ export default defineComponent({
house_factor: 0, house_factor: 0,
auto_status: 0, auto_status: 0,
open_ticket_id: null, open_ticket_id: null,
}, })
autoTicket: { const autoTicket = ref<AutoTicketData>({
id: 0, id: 0,
customer_id: '', customer_id: '',
account_number: '', account_number: '',
customer_town: '',
customer_town : '', customer_state: '',
customer_state : '', customer_address: '',
customer_address : '',
customer_zip: '', customer_zip: '',
customer_full_name : '', customer_full_name: '',
oil_prices_id: '',
oil_prices_id : '', fill_date: '',
fill_date : '', gallons_delivered: '',
gallons_delivered : '', price_per_gallon: '',
price_per_gallon : '', total_amount_customer: '',
payment_type: '',
total_amount_customer : '', payment_card_id: '',
payment_status: 0,
payment_type : '',
payment_card_id : '',
payment_status : 0,
open_ticket_id: 0 open_ticket_id: 0
})
}, const pricing = ref<PricingData>({
pricing: {
price_from_supplier: 0, price_from_supplier: 0,
price_for_customer: 0, price_for_customer: 0,
price_for_employee: 0, price_for_employee: 0,
@@ -344,38 +351,37 @@ export default defineComponent({
price_prime: 0, price_prime: 0,
price_emergency: 0, price_emergency: 0,
date: "", date: "",
}, })
total_amount: 0, const total_amount = ref(0)
discount: 0, const discount = ref(0)
total_amount_after_discount: 0, const total_amount_after_discount = ref(0)
}
},
mounted() { // Lifecycle
this.getAutoTicket(this.$route.params.id) onMounted(() => {
this.getTransaction() getAutoTicket(route.params.id)
}, getTransaction()
})
methods: { // Functions
getAutoTicket(delivery_id: any) { const getAutoTicket = (delivery_id: number | string) => {
let path = import.meta.env.VITE_AUTO_URL + "/delivery/autoticket/" + delivery_id; let path = import.meta.env.VITE_AUTO_URL + "/delivery/autoticket/" + delivery_id;
axios({ axios({
method: "get", method: "get",
url: path, url: path,
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: AxiosResponse<AutoTicketData>) => {
this.autoTicket = response.data; autoTicket.value = response.data;
console.log(this.autoTicket) console.log(autoTicket.value)
this.gallonsDelivered = this.autoTicket.gallons_delivered; gallonsDelivered.value = autoTicket.value.gallons_delivered;
this.captureAmount = parseFloat(this.autoTicket.total_amount_customer || '0'); captureAmount.value = parseFloat(autoTicket.value.total_amount_customer || '0');
this.getCustomer(this.autoTicket.customer_id) getCustomer(autoTicket.value.customer_id)
this.getAutoDelivery(this.autoTicket.id) getAutoDelivery(autoTicket.value.id)
this.getCustomerDescription(this.autoTicket.customer_id) getCustomerDescription(autoTicket.value.customer_id)
if (this.autoTicket.payment_card_id) { if (autoTicket.value.payment_card_id) {
this.getPaymentCard(this.autoTicket.payment_card_id) getPaymentCard(autoTicket.value.payment_card_id)
} }
}) })
@@ -386,20 +392,20 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
getAutoDelivery(delivery_id: any) { const getAutoDelivery = (delivery_id: number) => {
let path = import.meta.env.VITE_AUTO_URL + "/delivery/finddelivery/" + delivery_id; let path = import.meta.env.VITE_AUTO_URL + "/delivery/finddelivery/" + delivery_id;
axios({ axios({
method: "get", method: "get",
url: path, url: path,
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: AxiosResponse<AutoDeliveryData>) => {
this.autoDelivery = response.data; autoDelivery.value = response.data;
this.getCustomer(this.autoDelivery.customer_id) getCustomer(autoDelivery.value.customer_id)
this.getCustomerDescription(this.autoDelivery.customer_id) getCustomerDescription(autoDelivery.value.customer_id)
}) })
.catch(() => { .catch(() => {
@@ -409,48 +415,50 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
getPaymentCard(card_id: any) { const getPaymentCard = (card_id: number | string) => {
let path = import.meta.env.VITE_BASE_URL + "/payment/card/" + card_id; let path = import.meta.env.VITE_BASE_URL + "/payment/card/" + card_id;
axios({ axios({
method: "get", method: "get",
url: path, url: path,
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: AxiosResponse<PaymentCardResponse>) => {
if (response.data.userCard.card_number === ''){ if (response.data.userCard.card_number === ''){
this.userCardfound = false; userCardfound.value = false;
} }
else{ else{
this.userCard = response.data; userCard.value = response.data.userCard as CreditCardFormData;
this.userCardfound = true; userCardfound.value = true;
} }
}) })
.catch(() => { .catch(() => {
}); });
}, }
getCustomer(user_id: any) {
const getCustomer = (user_id: number | string) => {
const path = `${import.meta.env.VITE_BASE_URL}/customer/${user_id}`; const path = `${import.meta.env.VITE_BASE_URL}/customer/${user_id}`;
axios.get(path, { withCredentials: true }) axios.get<CustomerFormData>(path, { withCredentials: true })
.then((response: any) => { .then((response) => {
this.customer = response.data; customer.value = response.data;
}) })
.catch((error: any) => { .catch((error: Error) => {
notify({ title: "Error", text: "Could not find customer", type: "error" }); notify({ title: "Error", text: "Could not find customer", type: "error" });
console.error("Error fetching customer:", error); console.error("Error fetching customer:", error);
}); });
}, }
getCustomerDescription(user_id: any) {
const getCustomerDescription = (user_id: number | string) => {
let path = import.meta.env.VITE_BASE_URL + "/customer/description/" + user_id; let path = import.meta.env.VITE_BASE_URL + "/customer/description/" + user_id;
axios({ axios({
method: "get", method: "get",
url: path, url: path,
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: AxiosResponse<CustomerDescriptionData>) => {
this.customerDescription = response.data; customerDescription.value = response.data;
this.loading = false loading.value = false
}) })
.catch(() => { .catch(() => {
notify({ notify({
@@ -459,46 +467,47 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
getTransaction() {
const path = `${import.meta.env.VITE_AUTHORIZE_URL}/api/auto/transaction/delivery/${this.$route.params.id}`; const getTransaction = () => {
axios.get(path, { withCredentials: true, headers: authHeader() }) const path = `${import.meta.env.VITE_AUTHORIZE_URL}/api/auto/transaction/delivery/${route.params.id}`;
.then((response: any) => { axios.get<AuthorizeNetTransactionResponse>(path, { withCredentials: true, headers: authHeader() })
this.transaction = response.data; .then((response) => {
this.preAuthAmount = parseFloat(response.data.preauthorize_amount || 0); transaction.value = response.data;
preAuthAmount.value = parseFloat(String(response.data.preauthorize_amount) || '0');
if (response.data.status !== 0) { // Not approved if (response.data.status !== 0) { // Not approved
this.preAuthAmount = 0; preAuthAmount.value = 0;
} }
}) })
.catch((error: any) => { .catch((error: AxiosError) => {
if (error.response && error.response.status === 404) { if (error.response && error.response.status === 404) {
notify({ title: "Info", text: "No pre-authorization found. Redirecting to customer profile to update payment method.", type: "info" }); notify({ title: "Info", text: "No pre-authorization found. Redirecting to customer profile to update payment method.", type: "info" });
console.log("No transaction found for Automatic - redirecting to customer profile"); console.log("No transaction found for Automatic - redirecting to customer profile");
this.$router.push({ name: 'customerProfile', params: { id: this.customer.id } }); router.push({ name: 'customerProfile', params: { id: customer.value.id } });
} else { } else {
notify({ title: "Error", text: "No pre-authorized transaction found", type: "error" }); notify({ title: "Error", text: "No pre-authorized transaction found", type: "error" });
this.$router.push({ name: 'finalizeTicketAuto', params: { id: this.$route.params.id } }); router.push({ name: 'finalizeTicketAuto', params: { id: route.params.id } });
} }
}); });
}, }
async capturePayment() { const capturePayment = async () => {
if (this.autoTicket.payment_status !== 1) { if (autoTicket.value.payment_status !== 1) {
notify({ title: "Error", text: "Payment already captured or not ready for capture", type: "error" }); notify({ title: "Error", text: "Payment already captured or not ready for capture", type: "error" });
return; return;
} }
if (!this.transaction || !this.captureAmount) { if (!transaction.value || !captureAmount.value) {
notify({ title: "Error", text: "Invalid capture amount or transaction data", type: "error" }); notify({ title: "Error", text: "Invalid capture amount or transaction data", type: "error" });
return; return;
} }
this.loading = true; loading.value = true;
try { try {
const payload = { const payload = {
charge_amount: this.captureAmount, charge_amount: captureAmount.value,
auth_net_transaction_id: this.transaction.auth_net_transaction_id auth_net_transaction_id: transaction.value.auth_net_transaction_id
}; };
const url = `${import.meta.env.VITE_AUTHORIZE_URL}/api/capture/`; const url = `${import.meta.env.VITE_AUTHORIZE_URL}/api/capture/`;
@@ -510,13 +519,13 @@ export default defineComponent({
); );
if (response.data && response.data.status === 0) { if (response.data && response.data.status === 0) {
this.autoTicket.payment_status = 3; // Update local status immediately autoTicket.value.payment_status = 3; // Update local status immediately
this.modalCapturedAmount = this.captureAmount; modalCapturedAmount.value = captureAmount.value;
this.showPaymentModal = true; showPaymentModal.value = true;
// Close the ticket and unassign from delivery // Close the ticket and unassign from delivery
this.closeTicket(this.autoTicket.id); closeTicket(autoTicket.value.id);
setTimeout(() => { this.modalStep = 1 }, 2000); setTimeout(() => { modalStep.value = 1 }, 2000);
setTimeout(() => { this.showPaymentModal = false; this.$router.push({ name: 'auto' }) }, 4000); setTimeout(() => { showPaymentModal.value = false; router.push({ name: 'auto' }) }, 4000);
} else if (response.data && response.data.status === 1) { } else if (response.data && response.data.status === 1) {
const reason = response.data.rejection_reason || "The payment was declined by the gateway."; const reason = response.data.rejection_reason || "The payment was declined by the gateway.";
notify({ notify({
@@ -528,7 +537,8 @@ export default defineComponent({
throw new Error("Invalid response from server during capture."); throw new Error("Invalid response from server during capture.");
} }
} catch (error: any) { } catch (err: unknown) {
const error = err as AxiosError<{ detail?: string }>;
const detail = error.response?.data?.detail || "Failed to capture payment due to a server error."; const detail = error.response?.data?.detail || "Failed to capture payment due to a server error.";
notify({ notify({
title: "Error", title: "Error",
@@ -537,21 +547,21 @@ export default defineComponent({
}); });
console.error("Capture Payment Error:", error); console.error("Capture Payment Error:", error);
} finally { } finally {
this.loading = false; loading.value = false;
} }
}, }
cancelCapture() { const cancelCapture = () => {
this.$router.push({ name: 'finalizeTicketAuto', params: { id: this.$route.params.id } }); router.push({ name: 'finalizeTicketAuto', params: { id: route.params.id } });
}, }
async closeTicket(ticket_id: number) { const closeTicket = async (ticket_id: number) => {
const path = `${import.meta.env.VITE_AUTO_URL}/confirm/auto/close_ticket/${ticket_id}`; const path = `${import.meta.env.VITE_AUTO_URL}/confirm/auto/close_ticket/${ticket_id}`;
axios.put(path, {}, { withCredentials: true }) axios.put(path, {}, { withCredentials: true })
.then(() => { .then(() => {
console.log("Ticket closed successfully"); console.log("Ticket closed successfully");
}) })
.catch((error: any) => { .catch((error: Error) => {
notify({ notify({
title: "Warning", title: "Warning",
text: "Payment captured, but failed to close ticket. Check manually.", text: "Payment captured, but failed to close ticket. Check manually.",
@@ -559,9 +569,9 @@ export default defineComponent({
}); });
console.error("Error closing ticket:", error); console.error("Error closing ticket:", error);
}); });
}, }
getTypeColor(transactionType: number) { const getTypeColor = (transactionType: number) => {
switch (transactionType) { switch (transactionType) {
case 1: return 'text-blue-600'; // Auth case 1: return 'text-blue-600'; // Auth
case 0: return 'text-orange-600'; // Charge case 0: return 'text-orange-600'; // Charge
@@ -569,9 +579,7 @@ export default defineComponent({
case 3: return 'text-green-600'; // Delivery/Other case 3: return 'text-green-600'; // Delivery/Other
default: return 'text-gray-600'; default: return 'text-gray-600';
} }
}, }
},
})
</script> </script>
<style scoped></style> <style scoped></style>

View File

@@ -172,29 +172,43 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, watch } from 'vue' import { ref, computed, onMounted, watch } from 'vue'
import axios from 'axios' import { useRoute, useRouter } from 'vue-router'
import axios, { AxiosResponse, AxiosError } from 'axios'
import authHeader from '../../../services/auth.header' import authHeader from '../../../services/auth.header'
import { notify } from "@kyvg/vue3-notification" import { notify } from "@kyvg/vue3-notification"
import type {
DeliveryFormData,
CustomerFormData,
CreditCardFormData,
PricingData,
PromoData,
DeliveryOrderResponse,
DeliveryTotalResponse,
OilPricingResponse,
PromoResponse,
WhoAmIResponse,
UpdateStatusResponse
} from '../../../types/models'
export default defineComponent({ // Router and route
name: 'AuthorizePreauthCharge', const route = useRoute()
const router = useRouter()
data() { // Reactive data
return { const deliveryId = ref(route.params.id as string)
deliveryId: this.$route.params.id as string, const loaded = ref(false)
loaded: false, const chargeAmount = ref(0)
chargeAmount: 0, const loading = ref(false)
loading: false, const action = ref('') // 'preauthorize' or 'charge'
action: '', // 'preauthorize' or 'charge' const error = ref('')
error: '', const success = ref('')
success: '', const isChargeConfirmationModalVisible = ref(false)
isChargeConfirmationModalVisible: false, const user = ref({
user: {
user_id: 0, user_id: 0,
}, })
delivery: { const delivery = ref<DeliveryFormData>({
id: 0, id: 0,
customer_id: 0, customer_id: 0,
customer_name: '', customer_name: '',
@@ -228,8 +242,8 @@ export default defineComponent({
pre_charge_amount: 0, pre_charge_amount: 0,
total_price: 0, total_price: 0,
service_id: null, service_id: null,
}, })
credit_cards: [ const credit_cards = ref<CreditCardFormData[]>([
{ {
id: 0, id: 0,
name_on_card: '', name_on_card: '',
@@ -240,10 +254,9 @@ export default defineComponent({
last_four_digits: '', last_four_digits: '',
expiration_year: '', expiration_year: '',
security_number: '', security_number: '',
} }
], ])
customer: { const customer = ref<CustomerFormData>({
id: 0, id: 0,
user_id: 0, user_id: 0,
customer_first_name: '', customer_first_name: '',
@@ -256,8 +269,8 @@ export default defineComponent({
customer_home_type: 0, customer_home_type: 0,
customer_phone_number: '', customer_phone_number: '',
account_number: '', account_number: '',
}, })
pricing: { const pricing = ref<PricingData>({
price_from_supplier: 0, price_from_supplier: 0,
price_for_customer: 0, price_for_customer: 0,
price_for_employee: 0, price_for_employee: 0,
@@ -265,115 +278,105 @@ export default defineComponent({
price_prime: 0, price_prime: 0,
price_emergency: 0, price_emergency: 0,
date: "", date: "",
}, })
promo_active: false, const promo_active = ref(false)
promo: { const promo = ref<PromoData>({
name_of_promotion: '', name_of_promotion: '',
description: '', description: '',
money_off_delivery: 0, money_off_delivery: 0,
text_on_ticket: '' text_on_ticket: ''
}, })
total_amount: 0, const total_amount = ref(0)
discount: 0, const discount = ref(0)
total_amount_after_discount: 0, const total_amount_after_discount = ref(0)
// Computed properties
const selectedCard = computed(() => {
return credit_cards.value.find((card) => card.id === delivery.value.payment_card_id)
})
// Lifecycle
onMounted(() => {
loadData(deliveryId.value)
})
// Watchers
watch(() => route.params.id, (newId) => {
if (newId !== deliveryId.value) {
resetState()
deliveryId.value = newId as string
loadData(newId as string)
} }
}, })
computed: { // Functions
selectedCard(): any { const resetState = () => {
return this.credit_cards.find((card: any) => card.id === this.delivery.payment_card_id) loading.value = false
} action.value = ''
}, error.value = ''
success.value = ''
chargeAmount.value = 0
promo_active.value = false
total_amount.value = 0
discount.value = 0
total_amount_after_discount.value = 0
deliveryId.value = route.params.id as string
}
mounted() { const loadData = (deliveryId: string) => {
this.loadData(this.deliveryId) userStatus()
}, getOilOrder(deliveryId)
sumdelivery(deliveryId)
getOilPricing()
updatestatus()
}
created() { const updatestatus = () => {
this.watchRoute()
},
methods: {
watchRoute() {
watch(
() => this.$route.params.id,
(newId) => {
if (newId !== this.deliveryId) {
this.resetState()
this.deliveryId = newId as string
this.loadData(newId as string)
}
}
)
},
resetState() {
this.loading = false
this.action = ''
this.error = ''
this.success = ''
this.chargeAmount = 0
this.promo_active = false
this.total_amount = 0
this.discount = 0
this.total_amount_after_discount = 0
this.deliveryId = this.$route.params.id as string
},
loadData(deliveryId: string) {
this.userStatus()
this.getOilOrder(deliveryId)
this.sumdelivery(deliveryId)
this.getOilPricing()
this.updatestatus()
},
updatestatus() {
let path = import.meta.env.VITE_BASE_URL + '/delivery/updatestatus'; let path = import.meta.env.VITE_BASE_URL + '/delivery/updatestatus';
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: AxiosResponse<UpdateStatusResponse>) => {
if (response.data.update) if (response.data.update)
console.log("Updated Status of Deliveries") console.log("Updated Status of Deliveries")
}) })
}, }
updateChargeAmount() { const updateChargeAmount = () => {
// Only update if we have all necessary data // Only update if we have all necessary data
if (this.total_amount_after_discount > 0 && if (total_amount_after_discount.value > 0 &&
this.pricing.price_prime !== undefined && pricing.value.price_prime !== undefined &&
this.pricing.price_same_day !== undefined && pricing.value.price_same_day !== undefined &&
this.pricing.price_emergency !== undefined) { pricing.value.price_emergency !== undefined) {
this.chargeAmount = this.calculateTotalAsNumber(); chargeAmount.value = calculateTotalAsNumber();
return true; return true;
} }
return false; return false;
}, }
sumdelivery(delivery_id: any) { const sumdelivery = (delivery_id: number | string) => {
let path = import.meta.env.VITE_BASE_URL + "/delivery/total/" + delivery_id; let path = import.meta.env.VITE_BASE_URL + "/delivery/total/" + delivery_id;
axios({ axios({
method: "get", method: "get",
url: path, url: path,
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: AxiosResponse<DeliveryTotalResponse>) => {
if (response.data.ok) { if (response.data.ok) {
this.total_amount = parseFloat(response.data.total_amount) || 0; total_amount.value = parseFloat(String(response.data.total_amount)) || 0;
this.discount = parseFloat(response.data.discount) || 0; discount.value = parseFloat(String(response.data.discount)) || 0;
this.total_amount_after_discount = parseFloat(response.data.total_amount_after_discount) || 0; total_amount_after_discount.value = parseFloat(String(response.data.total_amount_after_discount)) || 0;
// Try to update charge amount with complete pricing // Try to update charge amount with complete pricing
const updated = this.updateChargeAmount(); const updated = updateChargeAmount();
// Fallback only if pricing not loaded yet and calculation didn't run // Fallback only if pricing not loaded yet and calculation didn't run
if (!updated) { if (!updated) {
if (this.promo_active) { if (promo_active.value) {
this.chargeAmount = this.total_amount_after_discount; chargeAmount.value = total_amount_after_discount.value;
} else { } else {
this.chargeAmount = this.total_amount; chargeAmount.value = total_amount.value;
} }
} }
} }
@@ -385,9 +388,9 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
getPromo(promo_id: any) { const getPromo = (promo_id: number) => {
let path = import.meta.env.VITE_BASE_URL + "/promo/" + promo_id; let path = import.meta.env.VITE_BASE_URL + "/promo/" + promo_id;
axios({ axios({
method: "get", method: "get",
@@ -395,18 +398,18 @@ export default defineComponent({
withCredentials: true, withCredentials: true,
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: AxiosResponse<PromoResponse>) => {
if (response.data) { if (response.data) {
this.promo = response.data promo.value = response.data
this.promo_active = true promo_active.value = true
// Trigger a charge amount update if all data is available // Trigger a charge amount update if all data is available
this.updateChargeAmount(); updateChargeAmount();
} }
}) })
}, }
userStatus() { const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -414,24 +417,24 @@ export default defineComponent({
withCredentials: true, withCredentials: true,
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: AxiosResponse<WhoAmIResponse>) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
}, }
getOilPricing() { const getOilPricing = () => {
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/table"; let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/table";
axios({ axios({
method: "get", method: "get",
url: path, url: path,
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: AxiosResponse<OilPricingResponse>) => {
this.pricing = response.data; pricing.value = response.data;
// Try to update charge amount when pricing is loaded // Try to update charge amount when pricing is loaded
this.updateChargeAmount(); updateChargeAmount();
}) })
.catch(() => { .catch(() => {
notify({ notify({
@@ -440,29 +443,29 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
getOilOrder(delivery_id: any) { const getOilOrder = (delivery_id: number | string) => {
let path = import.meta.env.VITE_BASE_URL + "/delivery/order/" + delivery_id; let path = import.meta.env.VITE_BASE_URL + "/delivery/order/" + delivery_id;
axios({ axios({
method: "get", method: "get",
url: path, url: path,
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: AxiosResponse<DeliveryOrderResponse>) => {
if (response.data && response.data.ok) { if (response.data && response.data.ok) {
this.delivery = response.data.delivery; delivery.value = response.data.delivery as DeliveryFormData;
this.getCustomer(this.delivery.customer_id) getCustomer(delivery.value.customer_id)
this.getCreditCards(this.delivery.customer_id) getCreditCards(delivery.value.customer_id)
if (this.delivery.promo_id != null) { if (delivery.value.promo_id != null) {
this.getPromo(this.delivery.promo_id); getPromo(delivery.value.promo_id);
this.promo_active = true; promo_active.value = true;
} }
} else { } else {
console.error("API Error:", response.data.error || "Failed to fetch delivery data."); console.error("API Error:", response.data.error || "Failed to fetch delivery data.");
} }
}) })
.catch((error: any) => { .catch((error: Error) => {
console.error("API Error in getOilOrder:", error); console.error("API Error in getOilOrder:", error);
notify({ notify({
title: "Error", title: "Error",
@@ -470,163 +473,163 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
getCreditCards(user_id: any) { const getCreditCards = (user_id: number) => {
let path = import.meta.env.VITE_BASE_URL + '/payment/cards/' + user_id; let path = import.meta.env.VITE_BASE_URL + '/payment/cards/' + user_id;
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: AxiosResponse<CreditCardFormData[]>) => {
this.credit_cards = response.data credit_cards.value = response.data
}) })
}, }
getCustomer(userid: any) { const getCustomer = (userid: number) => {
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid; let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: AxiosResponse<CustomerFormData>) => {
this.customer = response.data customer.value = response.data
}) })
}, }
calculateSubtotal() { const calculateSubtotal = () => {
const gallons = this.delivery.gallons_ordered || 0 const gallons = delivery.value.gallons_ordered || 0
const pricePerGallon = this.delivery.customer_price || 0 const pricePerGallon = delivery.value.customer_price || 0
return (gallons * pricePerGallon).toFixed(2) return (gallons * pricePerGallon).toFixed(2)
}, }
calculateTotalAmount() { const calculateTotalAmount = () => {
if (this.total_amount_after_discount == null || this.total_amount_after_discount === undefined) { if (total_amount_after_discount.value == null || total_amount_after_discount.value === undefined) {
return '0.00'; return '0.00';
} }
let totalNum = Number(this.total_amount_after_discount); let totalNum = Number(total_amount_after_discount.value);
if (isNaN(totalNum)) { if (isNaN(totalNum)) {
return '0.00'; return '0.00';
} }
if (this.delivery && this.delivery.prime == 1 && this.pricing && this.pricing.price_prime) { if (delivery.value && delivery.value.prime == 1 && pricing.value && pricing.value.price_prime) {
totalNum += Number(this.pricing.price_prime) || 0; totalNum += Number(pricing.value.price_prime) || 0;
} }
if (this.delivery && this.delivery.same_day == 1 && this.pricing && this.pricing.price_same_day) { if (delivery.value && delivery.value.same_day == 1 && pricing.value && pricing.value.price_same_day) {
totalNum += Number(this.pricing.price_same_day) || 0; totalNum += Number(pricing.value.price_same_day) || 0;
} }
if (this.delivery && this.delivery.emergency == 1 && this.pricing && this.pricing.price_emergency) { if (delivery.value && delivery.value.emergency == 1 && pricing.value && pricing.value.price_emergency) {
totalNum += Number(this.pricing.price_emergency) || 0; totalNum += Number(pricing.value.price_emergency) || 0;
} }
return totalNum.toFixed(2); return totalNum.toFixed(2);
}, }
calculateTotalAsNumber() { const calculateTotalAsNumber = () => {
if (this.total_amount_after_discount == null || this.total_amount_after_discount === undefined) { if (total_amount_after_discount.value == null || total_amount_after_discount.value === undefined) {
return 0; return 0;
} }
let totalNum = Number(this.total_amount_after_discount); let totalNum = Number(total_amount_after_discount.value);
if (isNaN(totalNum)) { if (isNaN(totalNum)) {
return 0; return 0;
} }
if (this.delivery && this.delivery.prime == 1 && this.pricing && this.pricing.price_prime) { if (delivery.value && delivery.value.prime == 1 && pricing.value && pricing.value.price_prime) {
totalNum += Number(this.pricing.price_prime) || 0; totalNum += Number(pricing.value.price_prime) || 0;
} }
if (this.delivery && this.delivery.same_day == 1 && this.pricing && this.pricing.price_same_day) { if (delivery.value && delivery.value.same_day == 1 && pricing.value && pricing.value.price_same_day) {
totalNum += Number(this.pricing.price_same_day) || 0; totalNum += Number(pricing.value.price_same_day) || 0;
} }
if (this.delivery && this.delivery.emergency == 1 && this.pricing && this.pricing.price_emergency) { if (delivery.value && delivery.value.emergency == 1 && pricing.value && pricing.value.price_emergency) {
totalNum += Number(this.pricing.price_emergency) || 0; totalNum += Number(pricing.value.price_emergency) || 0;
} }
return totalNum; return totalNum;
}, }
async handlePreauthorize() { const handlePreauthorize = async () => {
await this.processPayment('preauthorize') await processPayment('preauthorize')
}, }
async handleChargeNow() { const handleChargeNow = async () => {
if (!this.selectedCard) { if (!selectedCard.value) {
this.error = 'No credit card found for this customer' error.value = 'No credit card found for this customer'
return return
} }
if (!this.chargeAmount || this.chargeAmount <= 0) { if (!chargeAmount.value || chargeAmount.value <= 0) {
this.error = 'Please enter a valid charge amount' error.value = 'Please enter a valid charge amount'
return return
} }
this.isChargeConfirmationModalVisible = true isChargeConfirmationModalVisible.value = true
}, }
async proceedWithCharge() { const proceedWithCharge = async () => {
this.isChargeConfirmationModalVisible = false isChargeConfirmationModalVisible.value = false
await this.processPayment('charge') await processPayment('charge')
}, }
cancelCharge() { const cancelCharge = () => {
this.isChargeConfirmationModalVisible = false isChargeConfirmationModalVisible.value = false
}, }
async processPayment(actionType: string) { const processPayment = async (actionType: string) => {
if (!this.selectedCard) { if (!selectedCard.value) {
this.error = 'No credit card found for this customer' error.value = 'No credit card found for this customer'
return return
} }
this.loading = true loading.value = true
this.action = actionType action.value = actionType
this.error = '' error.value = ''
this.success = '' success.value = ''
try { try {
// Step 2: If payment method is credit, perform the pre-authorization // Step 2: If payment method is credit, perform the pre-authorization
if (actionType === 'preauthorize') { if (actionType === 'preauthorize') {
if (!this.chargeAmount || this.chargeAmount <= 0) { if (!chargeAmount.value || chargeAmount.value <= 0) {
throw new Error("Pre-authorization amount must be greater than zero."); throw new Error("Pre-authorization amount must be greater than zero.");
} }
const authPayload = { const authPayload = {
card_id: (this.selectedCard as any).id, card_id: selectedCard.value!.id,
preauthorize_amount: this.chargeAmount.toFixed(2), preauthorize_amount: chargeAmount.value.toFixed(2),
delivery_id: this.delivery.id, delivery_id: delivery.value.id,
}; };
const authPath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/authorize/saved-card/${this.customer.id}`; const authPath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/authorize/saved-card/${customer.value.id}`;
const response = await axios.post(authPath, authPayload, { withCredentials: true, headers: authHeader() }); const response = await axios.post(authPath, authPayload, { withCredentials: true, headers: authHeader() });
// Update payment type to 11 after successful preauthorization // Update payment type to 11 after successful preauthorization
try { try {
await axios.put(`${import.meta.env.VITE_BASE_URL}/payment/authorize/${this.delivery.id}`, {}, { headers: authHeader() }); await axios.put(`${import.meta.env.VITE_BASE_URL}/payment/authorize/${delivery.value.id}`, {}, { headers: authHeader() });
} catch (updateError) { } catch (updateError) {
console.error('Failed to update payment type after preauthorization:', updateError); console.error('Failed to update payment type after preauthorization:', updateError);
} }
// On successful authorization, show success and redirect // On successful authorization, show success and redirect
this.success = `Preauthorization successful! Transaction ID: ${response.data?.auth_net_transaction_id || 'N/A'}`; success.value = `Preauthorization successful! Transaction ID: ${response.data?.auth_net_transaction_id || 'N/A'}`;
setTimeout(() => { setTimeout(() => {
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } }); router.push({ name: "customerProfile", params: { id: customer.value.id } });
}, 2000); }, 2000);
} else { // Handle 'charge' action } else { // Handle 'charge' action
if (!this.chargeAmount || this.chargeAmount <= 0) { if (!chargeAmount.value || chargeAmount.value <= 0) {
throw new Error("Charge amount must be greater than zero."); throw new Error("Charge amount must be greater than zero.");
} }
// Create a payload that matches the backend's TransactionCreateByCardID schema // Create a payload that matches the backend's TransactionCreateByCardID schema
const chargePayload = { const chargePayload = {
card_id: (this.selectedCard as any).id, card_id: selectedCard.value!.id,
charge_amount: this.chargeAmount.toFixed(2), charge_amount: chargeAmount.value.toFixed(2),
delivery_id: this.delivery.id, delivery_id: delivery.value.id,
service_id: this.delivery.service_id || null, service_id: delivery.value.service_id || null,
// You can add other fields here if your schema requires them // You can add other fields here if your schema requires them
}; };
// Use the correct endpoint for charging a saved card // Use the correct endpoint for charging a saved card
const chargePath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/charge/saved-card/${this.customer.id}`; const chargePath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/charge/saved-card/${customer.value.id}`;
console.log('=== DEBUG: Charge payload ==='); console.log('=== DEBUG: Charge payload ===');
console.log('Calling endpoint:', chargePath); console.log('Calling endpoint:', chargePath);
@@ -636,37 +639,36 @@ export default defineComponent({
// Update payment type to 11 after successful charge // Update payment type to 11 after successful charge
try { try {
await axios.put(`${import.meta.env.VITE_BASE_URL}/payment/authorize/${this.delivery.id}`, {}, { headers: authHeader() }); await axios.put(`${import.meta.env.VITE_BASE_URL}/payment/authorize/${delivery.value.id}`, {}, { headers: authHeader() });
} catch (updateError) { } catch (updateError) {
console.error('Failed to update payment type after charge:', updateError); console.error('Failed to update payment type after charge:', updateError);
} }
// Status codes: 0 = APPROVED, 1 = DECLINED (based on backend TransactionStatus enum) // Status codes: 0 = APPROVED, 1 = DECLINED (based on backend TransactionStatus enum)
if (response.data && response.data.status === 0) { // 0 = APPROVED if (response.data && response.data.status === 0) { // 0 = APPROVED
this.success = `Charge successful! Transaction ID: ${response.data.auth_net_transaction_id || 'N/A'}`; success.value = `Charge successful! Transaction ID: ${response.data.auth_net_transaction_id || 'N/A'}`;
setTimeout(() => { setTimeout(() => {
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } }); router.push({ name: "customerProfile", params: { id: customer.value.id } });
}, 2000); }, 2000);
} else { } else {
// The error message from your backend will be more specific now // The error message from your backend will be more specific now
throw new Error(`Payment charge failed: ${response.data?.rejection_reason || 'Unknown error'}`); throw new Error(`Payment charge failed: ${response.data?.rejection_reason || 'Unknown error'}`);
} }
} }
} catch (error: any) { } catch (err: unknown) {
console.log(error) const axiosErr = err as AxiosError<{ detail?: string }>;
this.error = error.response?.data?.detail || `Failed to ${actionType} payment` console.log(err)
error.value = axiosErr.response?.data?.detail || `Failed to ${actionType} payment`
notify({ notify({
title: "Error", title: "Error",
text: this.error, text: error.value,
type: "error", type: "error",
}) })
} finally { } finally {
this.loading = false loading.value = false
this.action = '' action.value = ''
} }
} }
},
})
</script> </script>
<style scoped></style> <style scoped></style>

View File

@@ -278,37 +278,44 @@
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted } from 'vue'
import axios from 'axios' import { useRoute, useRouter } from 'vue-router'
import axios, { AxiosResponse, AxiosError } from 'axios'
import authHeader from '../../../services/auth.header' import authHeader from '../../../services/auth.header'
import Header from '../../../layouts/headers/headerauth.vue' import Header from '../../../layouts/headers/headerauth.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue' import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue' import Footer from '../../../layouts/footers/footer.vue'
import { notify } from "@kyvg/vue3-notification" import { notify } from "@kyvg/vue3-notification"
import type {
CustomerFormData,
CreditCardFormData,
PricingData,
PromoData,
DeliveryOrderResponse,
DeliveryTotalResponse,
OilPricingResponse,
PromoResponse,
PaymentCardResponse,
AuthorizeNetTransactionResponse
} from '../../../types/models'
export default defineComponent({ const route = useRoute()
name: 'captureAuthorize', const router = useRouter()
components: { // Reactive data
Header, const loading = ref(false)
SideBar, const userCardfound = ref(false)
Footer, const gallonsDelivered = ref('')
}, const captureAmount = ref(0)
const preAuthAmount = ref(0)
const transaction = ref<AuthorizeNetTransactionResponse | null>(null)
const showPaymentModal = ref(false)
const modalStep = ref(0)
const modalCapturedAmount = ref(0)
data() { const userCard = ref<CreditCardFormData>({
return { id: 0,
loading: false,
userCardfound: false,
gallonsDelivered: '',
captureAmount: 0,
preAuthAmount: 0,
transaction: null as any,
showPaymentModal: false,
modalStep: 0,
modalCapturedAmount: 0,
userCard: {
date_added: '', date_added: '',
user_id: '', user_id: '',
card_number: '', card_number: '',
@@ -319,9 +326,9 @@ export default defineComponent({
type_of_card: '', type_of_card: '',
security_number: '', security_number: '',
accepted_or_declined: '', accepted_or_declined: '',
main_card: '', main_card: false,
}, })
customer: { const customer = ref<CustomerFormData>({
id: 0, id: 0,
user_id: 0, user_id: 0,
customer_address: '', customer_address: '',
@@ -333,8 +340,9 @@ export default defineComponent({
customer_apt: '', customer_apt: '',
customer_home_type: 0, customer_home_type: 0,
customer_phone_number: '', customer_phone_number: '',
}, account_number: '',
deliveryOrder: { })
const deliveryOrder = ref({
id: '', id: '',
customer_id: 0, customer_id: 0,
customer_name: '', customer_name: '',
@@ -366,8 +374,8 @@ export default defineComponent({
driver_first_name: '', driver_first_name: '',
driver_last_name: '', driver_last_name: '',
total_price: 0, total_price: 0,
}, })
pricing: { const pricing = ref<PricingData>({
price_from_supplier: 0, price_from_supplier: 0,
price_for_customer: 0, price_for_customer: 0,
price_for_employee: 0, price_for_employee: 0,
@@ -375,42 +383,41 @@ export default defineComponent({
price_prime: 0, price_prime: 0,
price_emergency: 0, price_emergency: 0,
date: "", date: "",
}, })
promo_active: false, const promo_active = ref(false)
promo: { const promo = ref<PromoData>({
name_of_promotion: '', name_of_promotion: '',
description: '', description: '',
money_off_delivery: 0, money_off_delivery: 0,
text_on_ticket: '' text_on_ticket: ''
}, })
total_amount: 0, const total_amount = ref(0)
discount: 0, const discount = ref(0)
total_amount_after_discount: 0, const total_amount_after_discount = ref(0)
}
},
mounted() { // Lifecycle
this.getOilOrder(this.$route.params.id) onMounted(() => {
this.getOilPricing() getOilOrder(route.params.id)
this.getTransaction() getOilPricing()
}, getTransaction()
})
methods: { // Functions
sumdelivery(delivery_id: any) { const sumdelivery = (delivery_id: number | string) => {
let path = import.meta.env.VITE_BASE_URL + "/delivery/total/" + delivery_id; let path = import.meta.env.VITE_BASE_URL + "/delivery/total/" + delivery_id;
axios({ axios({
method: "get", method: "get",
url: path, url: path,
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: AxiosResponse<DeliveryTotalResponse>) => {
if (response.data.ok) { if (response.data.ok) {
this.total_amount = parseFloat(response.data.total_amount) || 0; total_amount.value = parseFloat(String(response.data.total_amount)) || 0;
this.discount = parseFloat(response.data.discount) || 0; discount.value = parseFloat(String(response.data.discount)) || 0;
this.total_amount_after_discount = parseFloat(response.data.total_amount_after_discount) || 0; total_amount_after_discount.value = parseFloat(String(response.data.total_amount_after_discount)) || 0;
// Set capture amount to the calculated total including fees and discount // Set capture amount to the calculated total including fees and discount
this.captureAmount = this.calculateTotalAsNumber(); captureAmount.value = calculateTotalAsNumber();
} }
}) })
.catch(() => { .catch(() => {
@@ -420,9 +427,9 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
getPromo(promo_id: any) { const getPromo = (promo_id: number) => {
let path = import.meta.env.VITE_BASE_URL + "/promo/" + promo_id; let path = import.meta.env.VITE_BASE_URL + "/promo/" + promo_id;
axios({ axios({
method: "get", method: "get",
@@ -430,114 +437,114 @@ export default defineComponent({
withCredentials: true, withCredentials: true,
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: AxiosResponse<PromoResponse>) => {
if (response.data) { if (response.data) {
this.promo = response.data promo.value = response.data
this.promo_active = true promo_active.value = true
} }
}) })
}, }
getOilOrder(delivery_id: any) { const getOilOrder = (delivery_id: number | string) => {
const path = `${import.meta.env.VITE_BASE_URL}/delivery/order/${delivery_id}`; const path = `${import.meta.env.VITE_BASE_URL}/delivery/order/${delivery_id}`;
axios.get(path, { withCredentials: true, headers: authHeader() }) axios.get<DeliveryOrderResponse>(path, { withCredentials: true, headers: authHeader() })
.then((response: any) => { .then((response) => {
if (response.data && response.data.ok) { if (response.data && response.data.ok) {
this.deliveryOrder = response.data.delivery; deliveryOrder.value = response.data.delivery as typeof deliveryOrder.value;
this.gallonsDelivered = this.deliveryOrder.gallons_delivered; gallonsDelivered.value = deliveryOrder.value.gallons_delivered;
this.getCustomer(this.deliveryOrder.customer_id); getCustomer(deliveryOrder.value.customer_id);
this.sumdelivery(delivery_id); sumdelivery(delivery_id);
if ([1, 2, 3].includes(this.deliveryOrder.payment_type)) { if ([1, 2, 3].includes(deliveryOrder.value.payment_type)) {
this.getPaymentCard(this.deliveryOrder.payment_card_id); getPaymentCard(deliveryOrder.value.payment_card_id);
} }
if (this.deliveryOrder.promo_id != null) { if (deliveryOrder.value.promo_id != null) {
this.getPromo(this.deliveryOrder.promo_id); getPromo(deliveryOrder.value.promo_id);
this.promo_active = true; promo_active.value = true;
} }
} else { } else {
console.error("API Error:", response.data.error || "Failed to fetch delivery data."); console.error("API Error:", response.data.error || "Failed to fetch delivery data.");
} }
}) })
.catch((error: any) => console.error("Error fetching oil order:", error)); .catch((error: Error) => console.error("Error fetching oil order:", error));
}, }
getPaymentCard(card_id: any) { const getPaymentCard = (card_id: number | string) => {
const path = `${import.meta.env.VITE_BASE_URL}/payment/card/${card_id}`; const path = `${import.meta.env.VITE_BASE_URL}/payment/card/${card_id}`;
axios.get(path, { withCredentials: true }) axios.get<PaymentCardResponse>(path, { withCredentials: true })
.then((response: any) => { .then((response) => {
if (response.data.userCard && response.data.userCard.card_number !== '') { if (response.data.userCard && response.data.userCard.card_number !== '') {
this.userCard = response.data; userCard.value = response.data.userCard as CreditCardFormData;
this.userCardfound = true; userCardfound.value = true;
} }
}) })
.catch((error: any) => { .catch((error: Error) => {
this.userCardfound = false; userCardfound.value = false;
console.error("Error fetching payment card:", error); console.error("Error fetching payment card:", error);
}); });
}, }
getCustomer(user_id: any) { const getCustomer = (user_id: number) => {
const path = `${import.meta.env.VITE_BASE_URL}/customer/${user_id}`; const path = `${import.meta.env.VITE_BASE_URL}/customer/${user_id}`;
axios.get(path, { withCredentials: true }) axios.get<CustomerFormData>(path, { withCredentials: true })
.then((response: any) => { .then((response) => {
this.customer = response.data; customer.value = response.data;
}) })
.catch((error: any) => { .catch((error: Error) => {
notify({ title: "Error", text: "Could not find customer", type: "error" }); notify({ title: "Error", text: "Could not find customer", type: "error" });
console.error("Error fetching customer:", error); console.error("Error fetching customer:", error);
}); });
}, }
getOilPricing() { const getOilPricing = () => {
const path = `${import.meta.env.VITE_BASE_URL}/info/price/oil/table`; const path = `${import.meta.env.VITE_BASE_URL}/info/price/oil/table`;
axios.get(path, { withCredentials: true }) axios.get<OilPricingResponse>(path, { withCredentials: true })
.then((response: any) => { .then((response) => {
this.pricing = response.data; pricing.value = response.data;
// Calculate capture amount if delivery order is already loaded // Calculate capture amount if delivery order is already loaded
this.calculateCaptureAmount(); calculateCaptureAmount();
}) })
.catch((error: any) => { .catch((error: Error) => {
notify({ title: "Error", text: "Could not get oil pricing", type: "error" }); notify({ title: "Error", text: "Could not get oil pricing", type: "error" });
console.error("Error fetching oil pricing:", error); console.error("Error fetching oil pricing:", error);
}); });
}, }
getTransaction() { const getTransaction = () => {
const path = `${import.meta.env.VITE_AUTHORIZE_URL}/api/transaction/delivery/${this.$route.params.id}`; const path = `${import.meta.env.VITE_AUTHORIZE_URL}/api/transaction/delivery/${route.params.id}`;
axios.get(path, { withCredentials: true, headers: authHeader() }) axios.get<AuthorizeNetTransactionResponse>(path, { withCredentials: true, headers: authHeader() })
.then((response: any) => { .then((response) => {
this.transaction = response.data; transaction.value = response.data;
this.preAuthAmount = parseFloat(response.data.preauthorize_amount || 0); preAuthAmount.value = parseFloat(String(response.data.preauthorize_amount) || '0');
if (response.data.status !== 0) { // Not approved if (response.data.status !== 0) { // Not approved
this.preAuthAmount = 0; preAuthAmount.value = 0;
} }
}) })
.catch((error: any) => { .catch((error: AxiosError) => {
if (error.response && error.response.status === 404) { if (error.response && error.response.status === 404) {
notify({ title: "Info", text: "No pre-authorization found. Redirecting to customer profile to update payment method.", type: "info" }); notify({ title: "Info", text: "No pre-authorization found. Redirecting to customer profile to update payment method.", type: "info" });
console.log("No transaction found for delivery - redirecting to customer profile"); console.log("No transaction found for delivery - redirecting to customer profile");
this.$router.push({ name: 'customerProfile', params: { id: this.customer.id } }); router.push({ name: 'customerProfile', params: { id: customer.value.id } });
} else { } else {
notify({ title: "Error", text: "No pre-authorized transaction found", type: "error" }); notify({ title: "Error", text: "No pre-authorized transaction found", type: "error" });
this.$router.push({ name: 'finalizeTicket', params: { id: this.$route.params.id } }); router.push({ name: 'finalizeTicket', params: { id: route.params.id } });
} }
}); });
}, }
async capturePayment() { const capturePayment = async () => {
if (!this.transaction || !this.captureAmount) { if (!transaction.value || !captureAmount.value) {
notify({ title: "Error", text: "Invalid capture amount or transaction data", type: "error" }); notify({ title: "Error", text: "Invalid capture amount or transaction data", type: "error" });
return; return;
} }
this.loading = true; loading.value = true;
try { try {
const payload = { const payload = {
charge_amount: this.captureAmount, // FastAPI handles string/number conversion charge_amount: captureAmount.value, // FastAPI handles string/number conversion
auth_net_transaction_id: this.transaction.auth_net_transaction_id auth_net_transaction_id: transaction.value.auth_net_transaction_id
}; };
// ✅ FIX: Cleaned up URL, removing the unnecessary customer_id query parameter. // ✅ FIX: Cleaned up URL, removing the unnecessary customer_id query parameter.
@@ -552,10 +559,10 @@ export default defineComponent({
// ✅ FIX: Improved logic to handle both success and declines properly. // ✅ FIX: Improved logic to handle both success and declines properly.
if (response.data && response.data.status === 0) { if (response.data && response.data.status === 0) {
// This is the APPROVED case // This is the APPROVED case
this.modalCapturedAmount = this.captureAmount; modalCapturedAmount.value = captureAmount.value;
this.showPaymentModal = true; showPaymentModal.value = true;
setTimeout(() => { this.modalStep = 1 }, 2000); setTimeout(() => { modalStep.value = 1 }, 2000);
setTimeout(() => { this.showPaymentModal = false; this.$router.push({ name: 'deliveryOrder', params: { id: this.$route.params.id } }) }, 4000); setTimeout(() => { showPaymentModal.value = false; router.push({ name: 'deliveryOrder', params: { id: route.params.id } }) }, 4000);
} else if (response.data && response.data.status === 1) { } else if (response.data && response.data.status === 1) {
// This is the DECLINED case // This is the DECLINED case
@@ -571,8 +578,9 @@ export default defineComponent({
throw new Error("Invalid response from server during capture."); throw new Error("Invalid response from server during capture.");
} }
} catch (error: any) { } catch (err: unknown) {
// This 'catch' block now only handles network errors or server crashes (500 errors). // This 'catch' block now only handles network errors or server crashes (500 errors).
const error = err as AxiosError<{ detail?: string }>;
const detail = error.response?.data?.detail || "Failed to capture payment due to a server error."; const detail = error.response?.data?.detail || "Failed to capture payment due to a server error.";
notify({ notify({
title: "Error", title: "Error",
@@ -581,90 +589,90 @@ export default defineComponent({
}); });
console.error("Capture Payment Error:", error); console.error("Capture Payment Error:", error);
} finally { } finally {
this.loading = false; loading.value = false;
} }
}, }
calculateSubtotal() { const calculateSubtotal = () => {
const gallons = parseFloat(this.gallonsDelivered || '0') || 0; const gallons = parseFloat(gallonsDelivered.value || '0') || 0;
const pricePerGallon = typeof this.deliveryOrder.customer_price === 'string' ? parseFloat(this.deliveryOrder.customer_price) : Number(this.deliveryOrder.customer_price) || 0; const pricePerGallon = typeof deliveryOrder.value.customer_price === 'string' ? parseFloat(deliveryOrder.value.customer_price) : Number(deliveryOrder.value.customer_price) || 0;
return (gallons * pricePerGallon).toFixed(2); return (gallons * pricePerGallon).toFixed(2);
}, }
calculateTotalAmount() { const calculateTotalAmount = () => {
if (this.total_amount_after_discount == null || this.total_amount_after_discount === undefined) { if (total_amount_after_discount.value == null || total_amount_after_discount.value === undefined) {
return '0.00'; return '0.00';
} }
let totalNum = Number(this.total_amount_after_discount); let totalNum = Number(total_amount_after_discount.value);
if (isNaN(totalNum)) { if (isNaN(totalNum)) {
return '0.00'; return '0.00';
} }
if (this.deliveryOrder && this.deliveryOrder.prime == 1 && this.pricing && this.pricing.price_prime) { if (deliveryOrder.value && deliveryOrder.value.prime == 1 && pricing.value && pricing.value.price_prime) {
totalNum += Number(this.pricing.price_prime) || 0; totalNum += Number(pricing.value.price_prime) || 0;
} }
if (this.deliveryOrder && this.deliveryOrder.same_day == 1 && this.pricing && this.pricing.price_same_day) { if (deliveryOrder.value && deliveryOrder.value.same_day == 1 && pricing.value && pricing.value.price_same_day) {
totalNum += Number(this.pricing.price_same_day) || 0; totalNum += Number(pricing.value.price_same_day) || 0;
} }
if (this.deliveryOrder && this.deliveryOrder.emergency == 1 && this.pricing && this.pricing.price_emergency) { if (deliveryOrder.value && deliveryOrder.value.emergency == 1 && pricing.value && pricing.value.price_emergency) {
totalNum += Number(this.pricing.price_emergency) || 0; totalNum += Number(pricing.value.price_emergency) || 0;
} }
return totalNum.toFixed(2); return totalNum.toFixed(2);
}, }
calculateTotalAsNumber() { const calculateTotalAsNumber = () => {
if (this.total_amount_after_discount == null || this.total_amount_after_discount === undefined) { if (total_amount_after_discount.value == null || total_amount_after_discount.value === undefined) {
return 0; return 0;
} }
let totalNum = Number(this.total_amount_after_discount); let totalNum = Number(total_amount_after_discount.value);
if (isNaN(totalNum)) { if (isNaN(totalNum)) {
return 0; return 0;
} }
if (this.deliveryOrder && this.deliveryOrder.prime == 1 && this.pricing && this.pricing.price_prime) { if (deliveryOrder.value && deliveryOrder.value.prime == 1 && pricing.value && pricing.value.price_prime) {
totalNum += Number(this.pricing.price_prime) || 0; totalNum += Number(pricing.value.price_prime) || 0;
} }
if (this.deliveryOrder && this.deliveryOrder.same_day == 1 && this.pricing && this.pricing.price_same_day) { if (deliveryOrder.value && deliveryOrder.value.same_day == 1 && pricing.value && pricing.value.price_same_day) {
totalNum += Number(this.pricing.price_same_day) || 0; totalNum += Number(pricing.value.price_same_day) || 0;
} }
if (this.deliveryOrder && this.deliveryOrder.emergency == 1 && this.pricing && this.pricing.price_emergency) { if (deliveryOrder.value && deliveryOrder.value.emergency == 1 && pricing.value && pricing.value.price_emergency) {
totalNum += Number(this.pricing.price_emergency) || 0; totalNum += Number(pricing.value.price_emergency) || 0;
} }
return totalNum; return totalNum;
}, }
calculateCaptureAmount() { const calculateCaptureAmount = () => {
// Only calculate if we have both delivery order and pricing data // Only calculate if we have both delivery order and pricing data
if (this.deliveryOrder.id && this.pricing.price_for_customer) { if (deliveryOrder.value.id && pricing.value.price_for_customer) {
const gallons = typeof this.gallonsDelivered === 'string' ? parseFloat(this.gallonsDelivered) : Number(this.gallonsDelivered) || 0; const gallons = typeof gallonsDelivered.value === 'string' ? parseFloat(gallonsDelivered.value) : Number(gallonsDelivered.value) || 0;
const pricePerGallon = typeof this.deliveryOrder.customer_price === 'string' ? parseFloat(this.deliveryOrder.customer_price) : Number(this.deliveryOrder.customer_price) || 0; const pricePerGallon = typeof deliveryOrder.value.customer_price === 'string' ? parseFloat(deliveryOrder.value.customer_price) : Number(deliveryOrder.value.customer_price) || 0;
let total = gallons * pricePerGallon; let total = gallons * pricePerGallon;
// Add prime fee if applicable // Add prime fee if applicable
if (this.deliveryOrder.prime == 1) { if (deliveryOrder.value.prime == 1) {
const primeFee = typeof this.pricing.price_prime === 'string' ? parseFloat(this.pricing.price_prime) : Number(this.pricing.price_prime) || 0; const primeFee = typeof pricing.value.price_prime === 'string' ? parseFloat(pricing.value.price_prime) : Number(pricing.value.price_prime) || 0;
total += primeFee; total += primeFee;
} }
// Add same day fee if applicable // Add same day fee if applicable
if (this.deliveryOrder.same_day === 1) { if (deliveryOrder.value.same_day === 1) {
const sameDayFee = typeof this.pricing.price_same_day === 'string' ? parseFloat(this.pricing.price_same_day) : Number(this.pricing.price_same_day) || 0; const sameDayFee = typeof pricing.value.price_same_day === 'string' ? parseFloat(pricing.value.price_same_day) : Number(pricing.value.price_same_day) || 0;
total += sameDayFee; total += sameDayFee;
} }
this.captureAmount = total; captureAmount.value = total;
} }
}, }
cancelCapture() { const cancelCapture = () => {
this.$router.push({ name: 'finalizeTicket', params: { id: this.$route.params.id } }); router.push({ name: 'finalizeTicket', params: { id: route.params.id } });
}, }
getTypeColor(transactionType: number) { const getTypeColor = (transactionType: number) => {
switch (transactionType) { switch (transactionType) {
case 1: return 'text-blue-600'; // Auth case 1: return 'text-blue-600'; // Auth
case 0: return 'text-orange-600'; // Charge case 0: return 'text-orange-600'; // Charge
@@ -672,9 +680,7 @@ export default defineComponent({
case 3: return 'text-green-600'; // Delivery/Other case 3: return 'text-green-600'; // Delivery/Other
default: return 'text-gray-600'; default: return 'text-gray-600';
} }
}, }
},
})
</script> </script>
<style scoped></style> <style scoped></style>

View File

@@ -335,35 +335,44 @@
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted } from 'vue'
import axios from 'axios' import { useRoute, useRouter } from 'vue-router'
import axios, { AxiosResponse } from 'axios'
import authHeader from '../../../services/auth.header' import authHeader from '../../../services/auth.header'
import Header from '../../../layouts/headers/headerauth.vue' import Header from '../../../layouts/headers/headerauth.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue' import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue' import Footer from '../../../layouts/footers/footer.vue'
import type {
DeliveryFormData,
CustomerFormData,
CreditCardFormData,
PricingData,
PromoData,
DeliveryOrderResponse,
DeliveryTotalResponse,
OilPricingResponse,
PromoResponse,
WhoAmIResponse,
AuthorizeCheckResponse,
CreateAuthorizeAccountResponse,
CardsOnFileResponse,
UpdateStatusResponse
} from '../../../types/models'
import useValidate from "@vuelidate/core"; import { useVuelidate } from "@vuelidate/core";
import { notify } from "@kyvg/vue3-notification" import { notify } from "@kyvg/vue3-notification"
import { minLength, required } from "@vuelidate/validators"; import { minLength, required } from "@vuelidate/validators";
export default defineComponent({ const route = useRoute()
name: 'PayOil', const router = useRouter()
components: { // Reactive data
Header, const loaded = ref(false)
SideBar, const user = ref({
Footer,
},
data() {
return {
v$: useValidate(),
loaded: false,
user: {
user_id: 0, user_id: 0,
}, })
delivery: { const delivery = ref<DeliveryFormData>({
id: 0, id: 0,
customer_id: 0, customer_id: 0,
customer_name: '', customer_name: '',
@@ -396,9 +405,8 @@ export default defineComponent({
driver_last_name: '', driver_last_name: '',
pre_charge_amount: 0, pre_charge_amount: 0,
total_price: 0, total_price: 0,
})
}, const credit_cards = ref<CreditCardFormData[]>([
credit_cards: [
{ {
id: 0, id: 0,
name_on_card: '', name_on_card: '',
@@ -409,12 +417,11 @@ export default defineComponent({
last_four_digits: '', last_four_digits: '',
expiration_year: '', expiration_year: '',
security_number: '', security_number: '',
} }
], ])
stripe: null, const stripe = ref(null)
customer: { const customer = ref<CustomerFormData>({
id: 0, id: 0,
user_id: 0, user_id: 0,
customer_first_name: '', customer_first_name: '',
@@ -428,8 +435,8 @@ export default defineComponent({
customer_phone_number: '', customer_phone_number: '',
account_number: '', account_number: '',
auth_net_profile_id: null, auth_net_profile_id: null,
}, })
pricing: { const pricing = ref<PricingData>({
price_from_supplier: 0, price_from_supplier: 0,
price_for_customer: 0, price_for_customer: 0,
price_for_employee: 0, price_for_employee: 0,
@@ -437,87 +444,78 @@ export default defineComponent({
price_prime: 0, price_prime: 0,
price_emergency: 0, price_emergency: 0,
date: "", date: "",
}, })
promo_active: false, const promo_active = ref(false)
promo: { const promo = ref<PromoData>({
name_of_promotion: '', name_of_promotion: '',
description: '', description: '',
money_off_delivery: 0, money_off_delivery: 0,
text_on_ticket: '' text_on_ticket: ''
}, })
priceprime: 0, const priceprime = ref(0)
pricesameday: 0, const pricesameday = ref(0)
priceemergency: 0, const priceemergency = ref(0)
total_amount: 0, const total_amount = ref(0)
discount: 0, const discount = ref(0)
total_amount_after_discount: 0, const total_amount_after_discount = ref(0)
credit_cards_count: 0, const credit_cards_count = ref(0)
isLoadingAuthorize: true, const isLoadingAuthorize = ref(true)
authorizeCheck: { profile_exists: false, has_payment_methods: false, missing_components: [] as string[], valid_for_charging: false }, const authorizeCheck = ref({ profile_exists: false, has_payment_methods: false, missing_components: [] as string[], valid_for_charging: false })
isDeleteAccountModalVisible: false, const isDeleteAccountModalVisible = ref(false)
isCreateAccountModalVisible: false, const isCreateAccountModalVisible = ref(false)
isCreatingAccount: false, const isCreatingAccount = ref(false)
createdProfileId: '', const createdProfileId = ref('')
isDuplicateErrorModalVisible: false, const isDuplicateErrorModalVisible = ref(false)
}
}, // Validations
validations() { const validations = {
return {
CreateOilOrderForm: { CreateOilOrderForm: {
basicInfo: { basicInfo: {
gallons_ordered: { required, minLength: minLength(1) }, gallons_ordered: { required, minLength: minLength(1) },
expected_delivery_date: { required }, expected_delivery_date: { required },
}, },
}, },
}; }
},
created() {
this.userStatus()
}, const v$ = useVuelidate(validations)
watch: {
$route() {
}, // Lifecycle
}, onMounted(() => {
mounted() { userStatus()
this.getOilOrder(this.$route.params.id) getOilOrder(route.params.id)
this.sumdelivery(this.$route.params.id); sumdelivery(route.params.id);
this.getOilPricing() getOilPricing()
this.updatestatus(); updatestatus();
})
}, // Functions
const updatestatus = () => {
methods: {
updatestatus() {
let path = import.meta.env.VITE_BASE_URL + '/delivery/updatestatus'; let path = import.meta.env.VITE_BASE_URL + '/delivery/updatestatus';
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: AxiosResponse<UpdateStatusResponse>) => {
if (response.data.update) if (response.data.update)
console.log("Updated Status of Deliveries") console.log("Updated Status of Deliveries")
}) })
}, }
sumdelivery(delivery_id: any) {
const sumdelivery = (delivery_id: number | string) => {
let path = import.meta.env.VITE_BASE_URL + "/delivery/total/" + delivery_id; let path = import.meta.env.VITE_BASE_URL + "/delivery/total/" + delivery_id;
axios({ axios({
method: "get", method: "get",
url: path, url: path,
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: AxiosResponse<DeliveryTotalResponse>) => {
if (response.data.ok) { if (response.data.ok) {
priceprime.value = response.data.priceprime;
pricesameday.value = response.data.pricesameday;
this.priceprime = response.data.priceprime; priceemergency.value = response.data.priceemergency;
this.pricesameday = response.data.pricesameday; total_amount.value = response.data.total_amount;
this.priceemergency = response.data.priceemergency; discount.value = response.data.discount;
this.total_amount = response.data.total_amount; total_amount_after_discount.value = response.data.total_amount_after_discount;
this.discount = response.data.discount;
this.total_amount_after_discount = response.data.total_amount_after_discount;
} }
}) })
.catch(() => { .catch(() => {
@@ -527,8 +525,9 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
getPromo(promo_id: any) {
const getPromo = (promo_id: number) => {
let path = import.meta.env.VITE_BASE_URL + "/promo/" + promo_id; let path = import.meta.env.VITE_BASE_URL + "/promo/" + promo_id;
axios({ axios({
method: "get", method: "get",
@@ -536,14 +535,14 @@ export default defineComponent({
withCredentials: true, withCredentials: true,
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: AxiosResponse<PromoResponse>) => {
if (response.data) { if (response.data) {
this.promo = response.data promo.value = response.data
} }
}) })
}, }
userStatus() {
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -551,22 +550,22 @@ export default defineComponent({
withCredentials: true, withCredentials: true,
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: AxiosResponse<WhoAmIResponse>) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
}
}, const getOilPricing = () => {
getOilPricing() {
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/table"; let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/table";
axios({ axios({
method: "get", method: "get",
url: path, url: path,
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: AxiosResponse<OilPricingResponse>) => {
this.pricing = response.data; pricing.value = response.data;
}) })
.catch(() => { .catch(() => {
notify({ notify({
@@ -575,34 +574,35 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
getOilOrder(delivery_id: any) {
const getOilOrder = (delivery_id: number | string) => {
let path = import.meta.env.VITE_BASE_URL + "/delivery/order/" + delivery_id; let path = import.meta.env.VITE_BASE_URL + "/delivery/order/" + delivery_id;
axios({ axios({
method: "get", method: "get",
url: path, url: path,
withCredentials: true, withCredentials: true,
}) })
.then((response: any) => { .then((response: AxiosResponse<DeliveryOrderResponse>) => {
console.log("=== DEBUG: API Response:", response.data); console.log("=== DEBUG: API Response:", response.data);
if (response.data && response.data.ok) { if (response.data && response.data.ok) {
console.log("=== DEBUG: Delivery data from API:", response.data.delivery); console.log("=== DEBUG: Delivery data from API:", response.data.delivery);
console.log("=== DEBUG: Delivery ID from API:", response.data.delivery?.id); console.log("=== DEBUG: Delivery ID from API:", response.data.delivery?.id);
this.delivery = response.data.delivery; delivery.value = response.data.delivery as DeliveryFormData;
console.log("=== DEBUG: Delivery object after assignment:", this.delivery); console.log("=== DEBUG: Delivery object after assignment:", delivery.value);
console.log("=== DEBUG: Delivery ID after assignment:", this.delivery.id); console.log("=== DEBUG: Delivery ID after assignment:", delivery.value.id);
this.getCustomer(this.delivery.customer_id) getCustomer(delivery.value.customer_id)
this.getCreditCards(this.delivery.customer_id) getCreditCards(delivery.value.customer_id)
this.getCreditCardsCount(this.delivery.customer_id) getCreditCardsCount(delivery.value.customer_id)
if (this.delivery.promo_id != null) { if (delivery.value.promo_id != null) {
this.getPromo(this.delivery.promo_id); getPromo(delivery.value.promo_id);
this.promo_active = true; promo_active.value = true;
} }
} else { } else {
console.error("API Error:", response.data.error || "Failed to fetch delivery data."); console.error("API Error:", response.data.error || "Failed to fetch delivery data.");
} }
}) })
.catch((error: any) => { .catch((error: Error) => {
console.error("=== DEBUG: API Error in getOilOrder:", error); console.error("=== DEBUG: API Error in getOilOrder:", error);
notify({ notify({
title: "Error", title: "Error",
@@ -610,69 +610,72 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
getCreditCards(user_id: any) {
const getCreditCards = (user_id: number) => {
let path = import.meta.env.VITE_BASE_URL + '/payment/cards/' + user_id; let path = import.meta.env.VITE_BASE_URL + '/payment/cards/' + user_id;
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: AxiosResponse<CreditCardFormData[]>) => {
credit_cards.value = response.data
this.credit_cards = response.data
}) })
}, }
getCreditCardsCount(user_id: any) {
const getCreditCardsCount = (user_id: number) => {
let path = import.meta.env.VITE_BASE_URL + '/payment/cards/onfile/' + user_id; let path = import.meta.env.VITE_BASE_URL + '/payment/cards/onfile/' + user_id;
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: AxiosResponse<CardsOnFileResponse>) => {
this.credit_cards_count = response.data.cards credit_cards_count.value = response.data.cards
}) })
}, }
getCustomer(userid: any) {
const getCustomer = (userid: number) => {
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid; let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: AxiosResponse<CustomerFormData>) => {
this.customer = response.data customer.value = response.data
this.checkAuthorizeAccount(); checkAuthorizeAccount();
}) })
}, }
calculateTotalAmount() {
if (this.total_amount_after_discount == null || this.total_amount_after_discount === undefined) { const calculateTotalAmount = () => {
if (total_amount_after_discount.value == null || total_amount_after_discount.value === undefined) {
return '0.00'; return '0.00';
} }
let totalNum = Number(this.total_amount_after_discount); let totalNum = Number(total_amount_after_discount.value);
if (isNaN(totalNum)) { if (isNaN(totalNum)) {
return '0.00'; return '0.00';
} }
if (this.delivery && this.delivery.prime == 1 && this.pricing && this.pricing.price_prime) { if (delivery.value && delivery.value.prime == 1 && pricing.value && pricing.value.price_prime) {
totalNum += Number(this.pricing.price_prime) || 0; totalNum += Number(pricing.value.price_prime) || 0;
} }
if (this.delivery && this.delivery.same_day == 1 && this.pricing && this.pricing.price_same_day) { if (delivery.value && delivery.value.same_day == 1 && pricing.value && pricing.value.price_same_day) {
totalNum += Number(this.pricing.price_same_day) || 0; totalNum += Number(pricing.value.price_same_day) || 0;
} }
if (this.delivery && this.delivery.emergency == 1 && this.pricing && this.pricing.price_emergency) { if (delivery.value && delivery.value.emergency == 1 && pricing.value && pricing.value.price_emergency) {
totalNum += Number(this.pricing.price_emergency) || 0; totalNum += Number(pricing.value.price_emergency) || 0;
} }
return totalNum.toFixed(2); return totalNum.toFixed(2);
}, }
checkoutOilUpdatePayment(payment_type: number) { const checkoutOilUpdatePayment = (payment_type: number) => {
let path = import.meta.env.VITE_BASE_URL + "/delivery/cash/" + this.delivery.id + '/' + payment_type; let path = import.meta.env.VITE_BASE_URL + "/delivery/cash/" + delivery.value.id + '/' + payment_type;
axios({ axios({
method: "PUT", method: "PUT",
url: path, url: path,
}) })
.then((response: any) => { .then((response: AxiosResponse<{ ok: boolean }>) => {
if (response.data.ok) { if (response.data.ok) {
if (payment_type == 0) { if (payment_type == 0) {
notify({ notify({
@@ -710,7 +713,7 @@ export default defineComponent({
type: "success", type: "success",
}); });
} }
this.$router.push({ name: "deliveryOrder", params: { id: this.delivery.id } }); router.push({ name: "deliveryOrder", params: { id: delivery.value.id } });
} }
}) })
.catch(() => { .catch(() => {
@@ -720,67 +723,69 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
async checkAuthorizeAccount() {
if (!this.customer.id) return;
this.isLoadingAuthorize = true; const checkAuthorizeAccount = async () => {
if (!customer.value.id) return;
isLoadingAuthorize.value = true;
try { try {
const path = `${import.meta.env.VITE_AUTHORIZE_URL}/user/check-authorize-account/${this.customer.id}`; const path = `${import.meta.env.VITE_AUTHORIZE_URL}/user/check-authorize-account/${customer.value.id}`;
const response = await axios.get(path, { headers: authHeader() }); const response = await axios.get(path, { headers: authHeader() });
this.authorizeCheck = response.data; authorizeCheck.value = response.data;
// Check if the API returned an error in the response body // Check if the API returned an error in the response body
if (this.authorizeCheck.missing_components && this.authorizeCheck.missing_components.includes('api_error')) { if (authorizeCheck.value.missing_components && authorizeCheck.value.missing_components.includes('api_error')) {
console.log("API error detected in response, calling cleanup for customer:", this.customer.id); console.log("API error detected in response, calling cleanup for customer:", customer.value.id);
this.cleanupAuthorizeData(); cleanupAuthorizeData();
return; // Don't set loading to false yet, let cleanup handle it return; // Don't set loading to false yet, let cleanup handle it
} }
} catch (error) { } catch (error) {
console.error("Failed to check authorize account:", error); console.error("Failed to check authorize account:", error);
notify({ title: "Error", text: "Could not check payment account status.", type: "error" }); notify({ title: "Error", text: "Could not check payment account status.", type: "error" });
// Set default error state // Set default error state
this.authorizeCheck = { authorizeCheck.value = {
profile_exists: false, profile_exists: false,
has_payment_methods: false, has_payment_methods: false,
missing_components: ['api_error'], missing_components: ['api_error'],
valid_for_charging: false valid_for_charging: false
}; };
// Automatically cleanup the local Authorize.Net data on API error // Automatically cleanup the local Authorize.Net data on API error
console.log("Calling cleanupAuthorizedData for customer:", this.customer.id); console.log("Calling cleanupAuthorizedData for customer:", customer.value.id);
this.cleanupAuthorizeData(); cleanupAuthorizeData();
} finally { } finally {
this.isLoadingAuthorize = false; isLoadingAuthorize.value = false;
} }
}, }
async createAuthorizeAccount() {
const createAuthorizeAccount = async () => {
// Show the creating account modal // Show the creating account modal
this.isCreatingAccount = true; isCreatingAccount.value = true;
this.isCreateAccountModalVisible = true; isCreateAccountModalVisible.value = true;
try { try {
const path = `${import.meta.env.VITE_AUTHORIZE_URL}/user/create-account/${this.customer.id}`; const path = `${import.meta.env.VITE_AUTHORIZE_URL}/user/create-account/${customer.value.id}`;
const response = await axios.post(path, {}, { headers: authHeader() }); const response = await axios.post(path, {}, { headers: authHeader() });
if (response.data.success) { if (response.data.success) {
// Update local state // Update local state
this.customer.auth_net_profile_id = response.data.profile_id; customer.value.auth_net_profile_id = response.data.profile_id;
this.authorizeCheck.valid_for_charging = true; authorizeCheck.value.valid_for_charging = true;
// this.authorizeCheck.profile_exists = true; // authorizeCheck.value.profile_exists = true;
this.authorizeCheck.has_payment_methods = true; authorizeCheck.value.has_payment_methods = true;
this.authorizeCheck.missing_components = []; authorizeCheck.value.missing_components = [];
this.createdProfileId = response.data.profile_id; createdProfileId.value = response.data.profile_id;
// Refresh credit cards to get updated payment profile IDs // Refresh credit cards to get updated payment profile IDs
await this.getCreditCards(this.customer.id); await getCreditCards(customer.value.id);
// Switch modal to success view and close after delay // Switch modal to success view and close after delay
setTimeout(() => { setTimeout(() => {
this.isCreatingAccount = false; isCreatingAccount.value = false;
setTimeout(() => { setTimeout(() => {
this.isCreateAccountModalVisible = false; isCreateAccountModalVisible.value = false;
this.createdProfileId = ''; createdProfileId.value = '';
notify({ notify({
title: "Success", title: "Success",
@@ -792,7 +797,7 @@ export default defineComponent({
} else { } else {
// Hide modal on error // Hide modal on error
this.isCreateAccountModalVisible = false; isCreateAccountModalVisible.value = false;
// Check for E00039 duplicate error // Check for E00039 duplicate error
const errorMessage = response.data.message || response.data.error_detail || "Failed to create Authorize.net account"; const errorMessage = response.data.message || response.data.error_detail || "Failed to create Authorize.net account";
@@ -800,7 +805,7 @@ export default defineComponent({
if (response.data.is_duplicate || errorMessage.includes("E00039")) { if (response.data.is_duplicate || errorMessage.includes("E00039")) {
// Show duplicate account popup // Show duplicate account popup
setTimeout(() => { setTimeout(() => {
this.showDuplicateErrorModal(); showDuplicateErrorModal();
}, 300); }, 300);
return; return;
} else { } else {
@@ -812,10 +817,11 @@ export default defineComponent({
}); });
} }
} }
} catch (error: any) { } catch (err: unknown) {
const error = err as { response?: { data?: { error_detail?: string; detail?: string; message?: string; is_duplicate?: boolean } }; message?: string };
console.error("Failed to create account:", error); console.error("Failed to create account:", error);
this.isCreateAccountModalVisible = false; isCreateAccountModalVisible.value = false;
this.isCreatingAccount = false; isCreatingAccount.value = false;
// Check for E00039 duplicate error // Check for E00039 duplicate error
const errorMessage = error.response?.data?.error_detail || const errorMessage = error.response?.data?.error_detail ||
@@ -826,7 +832,7 @@ export default defineComponent({
if (error.response?.data?.is_duplicate || errorMessage.includes("E00039")) { if (error.response?.data?.is_duplicate || errorMessage.includes("E00039")) {
// Show duplicate account popup // Show duplicate account popup
setTimeout(() => { setTimeout(() => {
this.showDuplicateErrorModal(); showDuplicateErrorModal();
}, 300); }, 300);
return; return;
} }
@@ -838,36 +844,41 @@ export default defineComponent({
type: "error" type: "error"
}); });
} }
}, }
showDeleteAccountModal() {
this.isDeleteAccountModalVisible = true; const showDeleteAccountModal = () => {
}, isDeleteAccountModalVisible.value = true;
showDuplicateErrorModal() { }
this.isDuplicateErrorModalVisible = true;
}, const showDuplicateErrorModal = () => {
hideDuplicateErrorModal() { isDuplicateErrorModalVisible.value = true;
this.isDuplicateErrorModalVisible = false; }
},
addCreditCard() { const hideDuplicateErrorModal = () => {
isDuplicateErrorModalVisible.value = false;
}
const addCreditCard = () => {
// Redirect to add card page // Redirect to add card page
this.$router.push({ name: 'cardadd', params: { customerId: this.customer.id } }); router.push({ name: 'cardadd', params: { customerId: customer.value.id } });
}, }
async deleteAccount() {
this.isDeleteAccountModalVisible = false; const deleteAccount = async () => {
isDeleteAccountModalVisible.value = false;
try { try {
const path = `${import.meta.env.VITE_AUTHORIZE_URL}/user/delete-account/${this.customer.id}`; const path = `${import.meta.env.VITE_AUTHORIZE_URL}/user/delete-account/${customer.value.id}`;
const response = await axios.delete(path, { headers: authHeader() }); const response = await axios.delete(path, { headers: authHeader() });
if (response.data.success) { if (response.data.success) {
// Update local state // Update local state
this.customer.auth_net_profile_id = null; customer.value.auth_net_profile_id = null;
this.authorizeCheck.valid_for_charging = false; authorizeCheck.value.valid_for_charging = false;
this.authorizeCheck.profile_exists = false; authorizeCheck.value.profile_exists = false;
this.authorizeCheck.has_payment_methods = false; authorizeCheck.value.has_payment_methods = false;
// Refresh credit cards list (IDs should now be null) // Refresh credit cards list (IDs should now be null)
this.getCreditCards(this.customer.id); getCreditCards(customer.value.id);
notify({ notify({
title: "Success", title: "Success",
@@ -881,7 +892,7 @@ export default defineComponent({
type: "warning" type: "warning"
}); });
} }
} catch (error: any) { } catch (error: unknown) {
console.error("Failed to delete account:", error); console.error("Failed to delete account:", error);
notify({ notify({
title: "Error", title: "Error",
@@ -889,21 +900,22 @@ export default defineComponent({
type: "error" type: "error"
}); });
} }
}, }
async cleanupAuthorizeData() {
const cleanupAuthorizeData = async () => {
try { try {
const path = `${import.meta.env.VITE_BASE_URL}/payment/authorize/cleanup/${this.customer.id}`; const path = `${import.meta.env.VITE_BASE_URL}/payment/authorize/cleanup/${customer.value.id}`;
const response = await axios.post(path, {}, { headers: authHeader() }); const response = await axios.post(path, {}, { headers: authHeader() });
if (response.data.ok) { if (response.data.ok) {
// Update local state to reflect cleanup // Update local state to reflect cleanup
this.customer.auth_net_profile_id = null; customer.value.auth_net_profile_id = null;
this.authorizeCheck.valid_for_charging = false; authorizeCheck.value.valid_for_charging = false;
this.authorizeCheck.profile_exists = false; authorizeCheck.value.profile_exists = false;
this.authorizeCheck.has_payment_methods = false; authorizeCheck.value.has_payment_methods = false;
// Refresh credit cards to reflect null payment profile IDs // Refresh credit cards to reflect null payment profile IDs
this.getCreditCards(this.customer.id); getCreditCards(customer.value.id);
console.log("Successfully cleaned up Authorize.Net data:", response.data.message); console.log("Successfully cleaned up Authorize.Net data:", response.data.message);
} else { } else {
@@ -912,13 +924,14 @@ export default defineComponent({
} catch (error) { } catch (error) {
console.error("Error during cleanup:", error); console.error("Error during cleanup:", error);
} }
}, }
getAccountStatusMessage(): string {
if (!this.authorizeCheck || !this.authorizeCheck.missing_components) { const getAccountStatusMessage = (): string => {
if (!authorizeCheck.value || !authorizeCheck.value.missing_components) {
return 'Account setup incomplete'; return 'Account setup incomplete';
} }
const missing = this.authorizeCheck.missing_components; const missing = authorizeCheck.value.missing_components;
if (missing.includes('customer_not_found')) { if (missing.includes('customer_not_found')) {
return 'Customer not found in Authorize.net'; return 'Customer not found in Authorize.net';
} else if (missing.includes('authorize_net_profile')) { } else if (missing.includes('authorize_net_profile')) {
@@ -932,10 +945,7 @@ export default defineComponent({
} else { } else {
return 'Account requires setup'; return 'Account requires setup';
} }
} }
},
})
</script> </script>
<style scoped></style> <style scoped></style>

View File

@@ -1,13 +1,13 @@
import PayOil from './oil/pay_oil.vue'; const PayOil = () => import('./oil/pay_oil.vue');
import AuthorizePreauthCharge from './oil/authorize_preauthcharge.vue'; const AuthorizePreauthCharge = () => import('./oil/authorize_preauthcharge.vue');
import CaptureAuthorize from './oil/capture_authorize.vue'; const CaptureAuthorize = () => import('./oil/capture_authorize.vue');
import PayService from './service/pay_service.vue'; const PayService = () => import('./service/pay_service.vue');
import AuthorizeServicePreauthCharge from './service/authorize_preauthcharge.vue'; const AuthorizeServicePreauthCharge = () => import('./service/authorize_preauthcharge.vue');
import ChargeServiceAuthorize from './service/capture_authorize.vue'; const ChargeServiceAuthorize = () => import('./service/capture_authorize.vue');
import AuthorizePrechargeAutho from './auto/authorize_precharge_autho.vue'; const AuthorizePrechargeAutho = () => import('./auto/authorize_precharge_autho.vue');
import CaptureAuthorizeAutho from './auto/capture_authorize_autho.vue'; const CaptureAuthorizeAutho = () => import('./auto/capture_authorize_autho.vue');
const payRoutes = [ const payRoutes = [
// This is for oil delivery // This is for oil delivery

View File

@@ -154,29 +154,47 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, watch } from 'vue' import { ref, computed, onMounted, watch } from 'vue'
import axios from 'axios' import { useRoute, useRouter } from 'vue-router'
import axios, { AxiosResponse, AxiosError } from 'axios'
import authHeader from '../../../services/auth.header' import authHeader from '../../../services/auth.header'
import { notify } from "@kyvg/vue3-notification" import { notify } from "@kyvg/vue3-notification"
import type {
CustomerFormData,
CreditCardFormData,
WhoAmIResponse
} from '../../../types/models'
export default defineComponent({ const route = useRoute()
name: 'AuthorizeServicePreauthCharge', const router = useRouter()
data() { interface ServiceFormData {
return { id: number;
serviceId: this.$route.params.id as string, scheduled_date: string;
loaded: false, customer_id: number;
chargeAmount: 0, customer_name: string;
loading: false, customer_address: string;
action: '', // 'preauthorize' or 'charge' customer_town: string;
error: '', type_service_call: number;
success: '', description: string;
selectedCardId: null as number | null, // Track which card is selected service_cost: string;
user: { payment_card_id: number;
}
// Reactive data
const serviceId = ref(route.params.id as string)
const loaded = ref(false)
const chargeAmount = ref(0)
const loading = ref(false)
const action = ref('') // 'preauthorize' or 'charge'
const error = ref('')
const success = ref('')
const selectedCardId = ref(null as number | null) // Track which card is selected
const user = ref({
user_id: 0, user_id: 0,
}, })
service: { const service = ref<ServiceFormData>({
id: 0, id: 0,
scheduled_date: '', scheduled_date: '',
customer_id: 0, customer_id: 0,
@@ -187,8 +205,8 @@ export default defineComponent({
description: '', description: '',
service_cost: '', service_cost: '',
payment_card_id: 0, payment_card_id: 0,
}, })
credit_cards: [ const credit_cards = ref<CreditCardFormData[]>([
{ {
id: 0, id: 0,
name_on_card: '', name_on_card: '',
@@ -199,10 +217,9 @@ export default defineComponent({
last_four_digits: '', last_four_digits: '',
expiration_year: '', expiration_year: '',
security_number: '', security_number: '',
} }
], ])
customer: { const customer = ref<CustomerFormData>({
id: 0, id: 0,
user_id: 0, user_id: 0,
customer_first_name: '', customer_first_name: '',
@@ -215,66 +232,55 @@ export default defineComponent({
customer_home_type: 0, customer_home_type: 0,
customer_phone_number: '', customer_phone_number: '',
account_number: '', account_number: '',
}, })
} // Computed
}, const selectedCard = computed(() => {
computed: {
selectedCard(): any {
// If user has selected a card manually, use that // If user has selected a card manually, use that
if (this.selectedCardId) { if (selectedCardId.value) {
return this.credit_cards.find((card: any) => card.id === this.selectedCardId) return credit_cards.value.find((card) => card.id === selectedCardId.value)
} }
// Otherwise use automatic selection logic // Otherwise use automatic selection logic
// First try to find payment_card_id from service // First try to find payment_card_id from service
if (this.service.payment_card_id && this.service.payment_card_id > 0) { if (service.value.payment_card_id && service.value.payment_card_id > 0) {
return this.credit_cards.find((card: any) => card.id === this.service.payment_card_id) return credit_cards.value.find((card) => card.id === service.value.payment_card_id)
} }
// Otherwise return the primary card (main_card = true) // Otherwise return the primary card (main_card = true)
return this.credit_cards.find((card: any) => card.main_card === true) || return credit_cards.value.find((card) => card.main_card === true) ||
this.credit_cards.find((card: any) => card.id > 0) || // Any card if no primary credit_cards.value.find((card) => card.id > 0) || // Any card if no primary
null null
})
// Watchers
watch(() => route.params.id, (newId) => {
if (newId !== serviceId.value) {
resetState()
serviceId.value = newId as string
loadData(newId as string)
} }
}, })
mounted() { // Lifecycle
this.loadData(this.serviceId) onMounted(() => {
}, loadData(serviceId.value)
})
created() { // Functions
this.watchRoute() const resetState = () => {
}, loading.value = false
action.value = ''
error.value = ''
success.value = ''
chargeAmount.value = 0
serviceId.value = route.params.id as string
}
methods: { const loadData = (serviceId: string) => {
watchRoute() { userStatus()
watch( getServiceOrder(serviceId)
() => this.$route.params.id, }
(newId) => {
if (newId !== this.serviceId) {
this.resetState()
this.serviceId = newId as string
this.loadData(newId as string)
}
}
)
},
resetState() { const userStatus = () => {
this.loading = false
this.action = ''
this.error = ''
this.success = ''
this.chargeAmount = 0
this.serviceId = this.$route.params.id as string
},
loadData(serviceId: string) {
this.userStatus()
this.getServiceOrder(serviceId)
},
userStatus() {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -282,14 +288,14 @@ export default defineComponent({
withCredentials: true, withCredentials: true,
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: AxiosResponse<WhoAmIResponse>) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
}, }
getServiceOrder(serviceId: any) { const getServiceOrder = (serviceId: number | string) => {
let path = import.meta.env.VITE_BASE_URL + "/service/" + serviceId; let path = import.meta.env.VITE_BASE_URL + "/service/" + serviceId;
axios({ axios({
method: "get", method: "get",
@@ -297,21 +303,21 @@ export default defineComponent({
withCredentials: true, withCredentials: true,
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: AxiosResponse<{ ok?: boolean; service?: ServiceFormData } | ServiceFormData[] | ServiceFormData>) => {
let serviceData; let serviceData: ServiceFormData | undefined;
if (response.data) { if (response.data) {
// Handle different API response structures // Handle different API response structures
if (response.data.service) { if ('service' in response.data && response.data.service) {
// API returns {ok: true, service: {...}} structure // API returns {ok: true, service: {...}} structure
serviceData = response.data.service; serviceData = response.data.service;
} else if (Array.isArray(response.data)) { } else if (Array.isArray(response.data)) {
serviceData = response.data[0]; // Array response serviceData = response.data[0]; // Array response
} else { } else if ('id' in response.data) {
serviceData = response.data; // Direct object response serviceData = response.data as ServiceFormData; // Direct object response
} }
if (serviceData && serviceData.id) { if (serviceData && serviceData.id) {
this.service = { service.value = {
id: serviceData.id, id: serviceData.id,
scheduled_date: serviceData.scheduled_date, scheduled_date: serviceData.scheduled_date,
customer_id: serviceData.customer_id, customer_id: serviceData.customer_id,
@@ -325,8 +331,8 @@ export default defineComponent({
}; };
// Fetch related data // Fetch related data
this.getCustomer(this.service.customer_id); getCustomer(service.value.customer_id);
this.getCreditCards(this.service.customer_id); getCreditCards(service.value.customer_id);
} else { } else {
console.error("API Error: Invalid service data received:", serviceData); console.error("API Error: Invalid service data received:", serviceData);
notify({ notify({
@@ -344,114 +350,110 @@ export default defineComponent({
}); });
} }
}) })
.catch((error: any) => { .catch((err: Error) => {
console.error("API Error in getServiceOrder:", error); console.error("API Error in getServiceOrder:", err);
notify({ notify({
title: "Error", title: "Error",
text: "Could not get service data", text: "Could not get service data",
type: "error", type: "error",
}); });
}); });
}, }
const getCreditCards = (user_id: number) => {
getCreditCards(user_id: any) {
let path = import.meta.env.VITE_BASE_URL + '/payment/cards/' + user_id; let path = import.meta.env.VITE_BASE_URL + '/payment/cards/' + user_id;
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: AxiosResponse<CreditCardFormData[]>) => {
console.log('Credit cards loaded:', response.data?.length || 0, 'cards'); console.log('Credit cards loaded:', response.data?.length || 0, 'cards');
this.credit_cards = response.data || []; credit_cards.value = response.data || [];
}).catch((error: any) => { }).catch((err: Error) => {
console.error('Failed to load credit cards:', error); console.error('Failed to load credit cards:', err);
this.credit_cards = []; credit_cards.value = [];
}); });
}, }
getCustomer(userid: any) { const getCustomer = (userid: number) => {
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid; let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: AxiosResponse<CustomerFormData>) => {
this.customer = response.data customer.value = response.data
}) })
}, }
const handlePreauthorize = async () => {
await processPayment('preauthorize')
}
const handleChargeNow = async () => {
await processPayment('charge')
}
async handlePreauthorize() { const processPayment = async (actionType: string) => {
await this.processPayment('preauthorize') if (!selectedCard.value) {
}, error.value = 'No credit card found for this customer'
async handleChargeNow() {
await this.processPayment('charge')
},
async processPayment(actionType: string) {
if (!this.selectedCard) {
this.error = 'No credit card found for this customer'
return return
} }
this.loading = true loading.value = true
this.action = actionType action.value = actionType
this.error = '' error.value = ''
this.success = '' success.value = ''
try { try {
// Step 2: If payment method is credit, perform the pre-authorization // Step 2: If payment method is credit, perform the pre-authorization
if (actionType === 'preauthorize') { if (actionType === 'preauthorize') {
if (!this.chargeAmount || this.chargeAmount <= 0) { if (!chargeAmount.value || chargeAmount.value <= 0) {
throw new Error("Pre-authorization amount must be greater than zero."); throw new Error("Pre-authorization amount must be greater than zero.");
} }
const authPayload = { const authPayload = {
card_id: (this.selectedCard as any).id, card_id: selectedCard.value!.id,
preauthorize_amount: this.chargeAmount.toFixed(2), preauthorize_amount: chargeAmount.value.toFixed(2),
service_id: this.service.id, service_id: service.value.id,
delivery_id: null, // No delivery for services delivery_id: null, // No delivery for services
}; };
const authPath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/authorize/saved-card/${this.customer.id}`; const authPath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/authorize/saved-card/${customer.value.id}`;
const response = await axios.post(authPath, authPayload, { withCredentials: true, headers: authHeader() }); const response = await axios.post(authPath, authPayload, { withCredentials: true, headers: authHeader() });
// Update payment type to 11 after successful preauthorization // Update payment type to 11 after successful preauthorization
try { try {
await axios.put(`${import.meta.env.VITE_BASE_URL}/payment/authorize/service/${this.service.id}`, { await axios.put(`${import.meta.env.VITE_BASE_URL}/payment/authorize/service/${service.value.id}`, {
card_id: (this.selectedCard as any).id, card_id: selectedCard.value!.id,
status: actionType === 'preauthorize' ? 1 : 3 status: actionType === 'preauthorize' ? 1 : 3
}, { headers: authHeader() }); }, { headers: authHeader() });
} catch (updateError) { } catch (updateError) {
console.error('Failed to update payment type after preauthorization:', updateError); console.error('Failed to update payment type after preauthorization:', updateError);
} }
// On successful authorization, show success and redirect // On successful authorization, show success and redirect
this.success = `Preauthorization successful! Transaction ID: ${response.data?.auth_net_transaction_id || 'N/A'}`; success.value = `Preauthorization successful! Transaction ID: ${response.data?.auth_net_transaction_id || 'N/A'}`;
setTimeout(() => { setTimeout(() => {
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } }); router.push({ name: "customerProfile", params: { id: customer.value.id } });
}, 2000); }, 2000);
} else { // Handle 'charge' action } else { // Handle 'charge' action
if (!this.chargeAmount || this.chargeAmount <= 0) { if (!chargeAmount.value || chargeAmount.value <= 0) {
throw new Error("Charge amount must be greater than zero."); throw new Error("Charge amount must be greater than zero.");
} }
// Create a payload that matches the backend's TransactionCreateByCardID schema // Create a payload that matches the backend's TransactionCreateByCardID schema
const chargePayload = { const chargePayload = {
card_id: (this.selectedCard as any).id, card_id: selectedCard.value!.id,
charge_amount: this.chargeAmount.toFixed(2), charge_amount: chargeAmount.value.toFixed(2),
service_id: this.service.id, service_id: service.value.id,
delivery_id: null, // No delivery for services delivery_id: null, // No delivery for services
// You can add other fields here if your schema requires them // You can add other fields here if your schema requires them
}; };
// Use the correct endpoint for charging a saved card // Use the correct endpoint for charging a saved card
const chargePath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/charge/saved-card/${this.customer.id}`; const chargePath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/charge/saved-card/${customer.value.id}`;
console.log('=== DEBUG: Charge payload ==='); console.log('=== DEBUG: Charge payload ===');
console.log('Calling endpoint:', chargePath); console.log('Calling endpoint:', chargePath);
@@ -462,19 +464,19 @@ export default defineComponent({
// Update service cost to the charged amount using new dedicated API // Update service cost to the charged amount using new dedicated API
try { try {
await axios.put( await axios.put(
`${import.meta.env.VITE_BASE_URL}/service/update-cost/${this.service.id}`, `${import.meta.env.VITE_BASE_URL}/service/update-cost/${service.value.id}`,
{ service_cost: this.chargeAmount }, { service_cost: chargeAmount.value },
{ headers: authHeader(), withCredentials: true } { headers: authHeader(), withCredentials: true }
); );
console.log(`✅ Updated service cost to ${this.chargeAmount} for service ${this.service.id}`); console.log(`✅ Updated service cost to ${chargeAmount.value} for service ${service.value.id}`);
} catch (costError) { } catch (costError) {
console.error('❌ Failed to update service cost:', costError); console.error('❌ Failed to update service cost:', costError);
} }
// Update payment status after successful charge // Update payment status after successful charge
try { try {
await axios.put(`${import.meta.env.VITE_BASE_URL}/payment/capture/service/${this.service.id}`, { await axios.put(`${import.meta.env.VITE_BASE_URL}/payment/capture/service/${service.value.id}`, {
card_id: (this.selectedCard as any).id, card_id: selectedCard.value!.id,
status: 3 // Approved status status: 3 // Approved status
}, { }, {
headers: authHeader(), headers: authHeader(),
@@ -486,51 +488,48 @@ export default defineComponent({
// Status codes: 0 = APPROVED, 1 = DECLINED (based on backend TransactionStatus enum) // Status codes: 0 = APPROVED, 1 = DECLINED (based on backend TransactionStatus enum)
if (response.data && response.data.status === 0) { // 0 = APPROVED if (response.data && response.data.status === 0) { // 0 = APPROVED
this.success = `Charge successful! Transaction ID: ${response.data.auth_net_transaction_id || 'N/A'}`; success.value = `Charge successful! Transaction ID: ${response.data.auth_net_transaction_id || 'N/A'}`;
setTimeout(() => { setTimeout(() => {
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } }); router.push({ name: "customerProfile", params: { id: customer.value.id } });
}, 2000); }, 2000);
} else { } else {
// The error message from your backend will be more specific now // The error message from your backend will be more specific now
throw new Error(`Payment charge failed: ${response.data?.rejection_reason || 'Unknown error'}`); throw new Error(`Payment charge failed: ${response.data?.rejection_reason || 'Unknown error'}`);
} }
} }
} catch (error: any) { } catch (err: unknown) {
console.log(error) const axiosErr = err as AxiosError<{ detail?: string }>;
this.error = error.response?.data?.detail || `Failed to ${actionType} payment` console.log(err)
error.value = axiosErr.response?.data?.detail || `Failed to ${actionType} payment`
notify({ notify({
title: "Error", title: "Error",
text: this.error, text: error.value,
type: "error", type: "error",
}) })
} finally { } finally {
this.loading = false loading.value = false
this.action = '' action.value = ''
} }
}, }
getServiceTypeName(typeId: number): string { const getServiceTypeName = (typeId: number): string => {
const typeMap: { [key: number]: string } = { 0: 'Tune-up', 1: 'No Heat', 2: 'Fix', 3: 'Tank Install', 4: 'Other' }; const typeMap: { [key: number]: string } = { 0: 'Tune-up', 1: 'No Heat', 2: 'Fix', 3: 'Tank Install', 4: 'Other' };
return typeMap[typeId] || 'Unknown'; return typeMap[typeId] || 'Unknown';
}, }
getServiceTypeColor(typeId: number): string { const getServiceTypeColor = (typeId: number): string => {
const colorMap: { [key: number]: string } = { 0: 'primary', 1: 'error', 2: 'warning', 3: 'info', 4: 'neutral' }; const colorMap: { [key: number]: string } = { 0: 'primary', 1: 'error', 2: 'warning', 3: 'info', 4: 'neutral' };
return `badge-${colorMap[typeId] || 'neutral'}`; return `badge-${colorMap[typeId] || 'neutral'}`;
}, }
selectCard(cardId: number) { const selectCard = (cardId: number) => {
this.selectedCardId = cardId; selectedCardId.value = cardId;
}, }
const formatScheduledDate = (dateString: string): string => {
formatScheduledDate(dateString: string): string {
if (!dateString) return 'Not scheduled'; if (!dateString) return 'Not scheduled';
return dateString; // Could format with dayjs if needed return dateString; // Could format with dayjs if needed
} }
},
})
</script> </script>
<style scoped></style> <style scoped></style>

View File

@@ -264,7 +264,7 @@ const service = ref<Service | null>(null);
const preAuthCard = ref<UserCard | null>(null); const preAuthCard = ref<UserCard | null>(null);
const selectedCard = ref<UserCard | null>(null); const selectedCard = ref<UserCard | null>(null);
const chargeAmount = ref<number>(0); const chargeAmount = ref<number>(0);
const transaction = ref(null as any); const transaction = ref<ServiceTransaction | null>(null);
const preAuthAmount = ref<number>(0); const preAuthAmount = ref<number>(0);
const serviceTransactions = ref<ServiceTransaction[]>([]); const serviceTransactions = ref<ServiceTransaction[]>([]);
const showPaymentModal = ref(false); const showPaymentModal = ref(false);
@@ -356,7 +356,8 @@ const updateServiceCost = async (serviceId: number, newCost: number): Promise<bo
console.error(`❌ SERVICE COST UPDATE FAILED:`, response.data); console.error(`❌ SERVICE COST UPDATE FAILED:`, response.data);
return false; return false;
} }
} catch (error: any) { } catch (err: unknown) {
const error = err as { response?: { data?: unknown }; message?: string };
console.error(`💥 ERROR UPDATING SERVICE COST:`, error.response?.data || error.message); console.error(`💥 ERROR UPDATING SERVICE COST:`, error.response?.data || error.message);
return false; return false;
} }
@@ -459,7 +460,8 @@ const chargeService = async () => {
throw new Error("Invalid response from server during capture."); throw new Error("Invalid response from server during capture.");
} }
} catch (error: any) { } catch (err: unknown) {
const error = err as { response?: { data?: { detail?: string } }; message?: string };
const detail = error.response?.data?.detail || "Failed to process payment due to a server error."; const detail = error.response?.data?.detail || "Failed to process payment due to a server error.";
notify({ title: "Error", text: detail, type: "error" }); notify({ title: "Error", text: detail, type: "error" });
console.error("Charge/Capture Service Error:", error); console.error("Charge/Capture Service Error:", error);
@@ -489,7 +491,7 @@ const getTransaction = async () => {
selectedCard.value = cardResponse.data; selectedCard.value = cardResponse.data;
chargeAmount.value = preAuthAmount.value; chargeAmount.value = preAuthAmount.value;
} }
} catch (error: any) { } catch (error: unknown) {
console.error("No pre-authorized transaction found for service:", error); console.error("No pre-authorized transaction found for service:", error);
preAuthAmount.value = service.value?.service_cost || 0; preAuthAmount.value = service.value?.service_cost || 0;
} }

View File

@@ -271,10 +271,18 @@
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted } from 'vue'
import axios from 'axios' import { useRoute, useRouter } from 'vue-router'
import axios, { AxiosResponse, AxiosError } from 'axios'
import authHeader from '../../../services/auth.header' import authHeader from '../../../services/auth.header'
import { ServiceCall, ServicePart, CreditCard, Customer } from '../../../types/models'
import type {
WhoAmIResponse,
CardsOnFileResponse,
AuthorizeCheckResponse,
CreateAuthorizeAccountResponse
} from '../../../types/models'
import Header from '../../../layouts/headers/headerauth.vue' import Header from '../../../layouts/headers/headerauth.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue' import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue' import Footer from '../../../layouts/footers/footer.vue'
@@ -283,52 +291,35 @@ import useValidate from "@vuelidate/core";
import { notify } from "@kyvg/vue3-notification" import { notify } from "@kyvg/vue3-notification"
import { required } from "@vuelidate/validators"; import { required } from "@vuelidate/validators";
export default defineComponent({ // Reactive data
name: 'PayService', const route = useRoute()
const router = useRouter()
components: { const loaded = ref(false)
Header, const user = ref({
SideBar,
Footer,
},
data() {
return {
v$: useValidate(),
loaded: false,
user: {
user_id: 0, user_id: 0,
}, })
service: { const service = ref<ServiceCall>({
id: 0, id: 0,
scheduled_date: '',
customer_id: 0, customer_id: 0,
customer_name: '', customer_name: '',
customer_address: '', customer_address: '',
customer_town: '', customer_town: '',
customer_state: '',
customer_zip: '',
type_service_call: 0, type_service_call: 0,
when_ordered: '',
scheduled_date: '',
description: '', description: '',
service_cost: '', service_cost: '0',
payment_type: 0,
payment_card_id: 0, payment_card_id: 0,
}, payment_status: 0,
serviceParts: null as any, })
credit_cards: [ const serviceParts = ref<ServicePart[] | null>(null)
{ const credit_cards = ref<CreditCard[]>([])
id: 0,
name_on_card: '',
main_card: false,
card_number: '',
expiration_month: '',
type_of_card: '',
last_four_digits: '',
expiration_year: '',
security_number: '',
} const stripe = ref(null)
], const customer = ref({
stripe: null,
customer: {
id: 0, id: 0,
user_id: 0, user_id: 0,
customer_first_name: '', customer_first_name: '',
@@ -342,8 +333,8 @@ export default defineComponent({
customer_phone_number: '', customer_phone_number: '',
account_number: '', account_number: '',
auth_net_profile_id: null, auth_net_profile_id: null,
}, })
pricing: { const pricing = ref({
price_from_supplier: 0, price_from_supplier: 0,
price_for_customer: 0, price_for_customer: 0,
price_for_employee: 0, price_for_employee: 0,
@@ -351,57 +342,44 @@ export default defineComponent({
price_prime: 0, price_prime: 0,
price_emergency: 0, price_emergency: 0,
date: "", date: "",
}, })
promo_active: false, const promo_active = ref(false)
promo: { const promo = ref({
name_of_promotion: '', name_of_promotion: '',
description: '', description: '',
money_off_delivery: 0, money_off_delivery: 0,
text_on_ticket: '' text_on_ticket: ''
}, })
priceprime: 0, const priceprime = ref(0)
pricesameday: 0, const pricesameday = ref(0)
priceemergency: 0, const priceemergency = ref(0)
total_amount: 0, const total_amount = ref(0)
discount: 0, const discount = ref(0)
total_amount_after_discount: 0, const total_amount_after_discount = ref(0)
credit_cards_count: 0, const credit_cards_count = ref(0)
isLoadingAuthorize: true, const isLoadingAuthorize = ref(true)
authorizeCheck: { profile_exists: false, has_payment_methods: false, missing_components: [] as string[], valid_for_charging: false }, const authorizeCheck = ref({ profile_exists: false, has_payment_methods: false, missing_components: [] as string[], valid_for_charging: false })
isDeleteAccountModalVisible: false, const isDeleteAccountModalVisible = ref(false)
isCreateAccountModalVisible: false, const isCreateAccountModalVisible = ref(false)
isCreatingAccount: false, const isCreatingAccount = ref(false)
createdProfileId: '', const createdProfileId = ref('')
isDuplicateErrorModalVisible: false, const isDuplicateErrorModalVisible = ref(false)
}
}, // Validation rules
validations() { const rules = {
return {
CreateServiceOrderForm: { CreateServiceOrderForm: {
basicInfo: { basicInfo: {
description: { required }, description: { required },
service_cost: { required }, service_cost: { required },
}, },
}, },
}; }
},
created() {
this.userStatus()
}, // Vuelidate instance
watch: { const v$ = useValidate(rules, {})
$route() {
}, // Functions
}, const userStatus = () => {
mounted() {
this.getServiceOrder(this.$route.params.id)
this.getServicePartsForCustomer();
},
methods: {
userStatus() {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -409,14 +387,14 @@ export default defineComponent({
withCredentials: true, withCredentials: true,
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: AxiosResponse<WhoAmIResponse>) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
}
}, const getServiceOrder = (service_id: number | string) => {
getServiceOrder(service_id: any) {
let path = import.meta.env.VITE_BASE_URL + "/service/" + service_id; let path = import.meta.env.VITE_BASE_URL + "/service/" + service_id;
axios({ axios({
method: "get", method: "get",
@@ -424,21 +402,21 @@ export default defineComponent({
withCredentials: true, withCredentials: true,
headers: authHeader(), headers: authHeader(),
}) })
.then((response: any) => { .then((response: AxiosResponse<{ ok?: boolean; service?: ServiceCall } | ServiceCall[] | ServiceCall>) => {
let serviceData; let serviceData: ServiceCall | undefined;
if (response.data) { if (response.data) {
// Handle different API response structures // Handle different API response structures
if (response.data.service) { if ('service' in response.data && response.data.service) {
// API returns {ok: true, service: {...}} structure // API returns {ok: true, service: {...}} structure
serviceData = response.data.service; serviceData = response.data.service;
} else if (Array.isArray(response.data)) { } else if (Array.isArray(response.data)) {
serviceData = response.data[0]; // Array response serviceData = response.data[0]; // Array response
} else { } else if ('id' in response.data) {
serviceData = response.data; // Direct object response serviceData = response.data as ServiceCall; // Direct object response
} }
if (serviceData && serviceData.id) { if (serviceData && serviceData.id) {
this.service = { service.value = {
id: serviceData.id, id: serviceData.id,
scheduled_date: serviceData.scheduled_date, scheduled_date: serviceData.scheduled_date,
customer_id: serviceData.customer_id, customer_id: serviceData.customer_id,
@@ -452,10 +430,10 @@ export default defineComponent({
}; };
// Fetch related data // Fetch related data
this.getCustomer(this.service.customer_id); getCustomer(service.value.customer_id);
this.getCreditCards(this.service.customer_id); getCreditCards(service.value.customer_id);
this.getCreditCardsCount(this.service.customer_id); getCreditCardsCount(service.value.customer_id);
this.getServicePartsForCustomer(); getServicePartsForCustomer();
} else { } else {
console.error("API Error: Invalid service data received:", serviceData); console.error("API Error: Invalid service data received:", serviceData);
notify({ notify({
@@ -473,7 +451,7 @@ export default defineComponent({
}); });
} }
}) })
.catch((error: any) => { .catch((error: AxiosError) => {
console.error("API Error in getServiceOrder:", error); console.error("API Error in getServiceOrder:", error);
console.error("Error details:", error.response?.data || error.message); console.error("Error details:", error.response?.data || error.message);
notify({ notify({
@@ -482,59 +460,63 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
getServicePartsForCustomer() {
if (!this.service.customer_id) return;
let path = `${import.meta.env.VITE_BASE_URL}/service/parts/customer/${this.service.customer_id}`; const getServicePartsForCustomer = () => {
axios.get(path, { headers: authHeader() }) if (!service.value.customer_id) return;
.then((response: any) => {
this.serviceParts = response.data; let path = `${import.meta.env.VITE_BASE_URL}/service/parts/customer/${service.value.customer_id}`;
axios.get<ServicePart[]>(path, { headers: authHeader() })
.then((response) => {
serviceParts.value = response.data;
}) })
.catch((error: any) => { .catch((error: Error) => {
console.error("Failed to fetch service parts:", error); console.error("Failed to fetch service parts:", error);
this.serviceParts = null; serviceParts.value = null;
}); });
}, }
getCreditCards(user_id: any) {
const getCreditCards = (user_id: number) => {
let path = import.meta.env.VITE_BASE_URL + '/payment/cards/' + user_id; let path = import.meta.env.VITE_BASE_URL + '/payment/cards/' + user_id;
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: AxiosResponse<CreditCard[]>) => {
credit_cards.value = response.data
this.credit_cards = response.data
}) })
}, }
getCreditCardsCount(user_id: any) {
const getCreditCardsCount = (user_id: number) => {
let path = import.meta.env.VITE_BASE_URL + '/payment/cards/onfile/' + user_id; let path = import.meta.env.VITE_BASE_URL + '/payment/cards/onfile/' + user_id;
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: AxiosResponse<CardsOnFileResponse>) => {
this.credit_cards_count = response.data.cards credit_cards_count.value = response.data.cards
}) })
}, }
getCustomer(userid: any) {
const getCustomer = (userid: number) => {
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid; let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
axios({ axios({
method: 'get', method: 'get',
url: path, url: path,
headers: authHeader(), headers: authHeader(),
}).then((response: any) => { }).then((response: AxiosResponse<typeof customer.value>) => {
this.customer = response.data customer.value = response.data
this.checkAuthorizeAccount(); checkAuthorizeAccount();
}) })
}, }
processServicePayment(payment_type: number) {
let path = import.meta.env.VITE_BASE_URL + "/payment/service/payment/" + this.service.id + '/' + payment_type; const processServicePayment = (payment_type: number) => {
let path = import.meta.env.VITE_BASE_URL + "/payment/service/payment/" + service.value.id + '/' + payment_type;
axios({ axios({
method: "PUT", method: "PUT",
url: path, url: path,
}) })
.then((response: any) => { .then((response: AxiosResponse<{ ok: boolean }>) => {
if (response.data.ok) { if (response.data.ok) {
if (payment_type == 0) { if (payment_type == 0) {
notify({ notify({
@@ -564,7 +546,7 @@ export default defineComponent({
type: "success", type: "success",
}); });
} }
this.$router.push({ name: "ServiceHome" }); router.push({ name: "ServiceHome" });
} }
}) })
.catch(() => { .catch(() => {
@@ -574,67 +556,69 @@ export default defineComponent({
type: "error", type: "error",
}); });
}); });
}, }
async checkAuthorizeAccount() {
if (!this.customer.id) return;
this.isLoadingAuthorize = true; const checkAuthorizeAccount = async () => {
if (!customer.value.id) return;
isLoadingAuthorize.value = true;
try { try {
const path = `${import.meta.env.VITE_AUTHORIZE_URL}/user/check-authorize-account/${this.customer.id}`; const path = `${import.meta.env.VITE_AUTHORIZE_URL}/user/check-authorize-account/${customer.value.id}`;
const response = await axios.get(path, { headers: authHeader() }); const response = await axios.get(path, { headers: authHeader() });
this.authorizeCheck = response.data; authorizeCheck.value = response.data;
// Check if the API returned an error in the response body // Check if the API returned an error in the response body
if (this.authorizeCheck.missing_components && this.authorizeCheck.missing_components.includes('api_error')) { if (authorizeCheck.value.missing_components && authorizeCheck.value.missing_components.includes('api_error')) {
console.log("API error detected in response, calling cleanup for customer:", this.customer.id); console.log("API error detected in response, calling cleanup for customer:", customer.value.id);
this.cleanupAuthorizeData(); cleanupAuthorizeData();
return; // Don't set loading to false yet, let cleanup handle it return; // Don't set loading to false yet, let cleanup handle it
} }
} catch (error) { } catch (error) {
console.error("Failed to check authorize account:", error); console.error("Failed to check authorize account:", error);
notify({ title: "Error", text: "Could not check payment account status.", type: "error" }); notify({ title: "Error", text: "Could not check payment account status.", type: "error" });
// Set default error state // Set default error state
this.authorizeCheck = { authorizeCheck.value = {
profile_exists: false, profile_exists: false,
has_payment_methods: false, has_payment_methods: false,
missing_components: ['api_error'], missing_components: ['api_error'],
valid_for_charging: false valid_for_charging: false
}; };
// Automatically cleanup the local Authorize.Net data on API error // Automatically cleanup the local Authorize.Net data on API error
console.log("Calling cleanupAuthorizedData for customer:", this.customer.id); console.log("Calling cleanupAuthorizedData for customer:", customer.value.id);
this.cleanupAuthorizeData(); cleanupAuthorizeData();
} finally { } finally {
this.isLoadingAuthorize = false; isLoadingAuthorize.value = false;
} }
}, }
async createAuthorizeAccount() {
const createAuthorizeAccount = async () => {
// Show the creating account modal // Show the creating account modal
this.isCreatingAccount = true; isCreatingAccount.value = true;
this.isCreateAccountModalVisible = true; isCreateAccountModalVisible.value = true;
try { try {
const path = `${import.meta.env.VITE_AUTHORIZE_URL}/user/create-account/${this.customer.id}`; const path = `${import.meta.env.VITE_AUTHORIZE_URL}/user/create-account/${customer.value.id}`;
const response = await axios.post(path, {}, { headers: authHeader() }); const response = await axios.post(path, {}, { headers: authHeader() });
if (response.data.success) { if (response.data.success) {
// Update local state // Update local state
this.customer.auth_net_profile_id = response.data.profile_id; customer.value.auth_net_profile_id = response.data.profile_id;
this.authorizeCheck.valid_for_charging = true; authorizeCheck.value.valid_for_charging = true;
this.authorizeCheck.profile_exists = true; authorizeCheck.value.profile_exists = true;
this.authorizeCheck.has_payment_methods = true; authorizeCheck.value.has_payment_methods = true;
this.authorizeCheck.missing_components = []; authorizeCheck.value.missing_components = [];
this.createdProfileId = response.data.profile_id; createdProfileId.value = response.data.profile_id;
// Refresh credit cards to get updated payment profile IDs // Refresh credit cards to get updated payment profile IDs
await this.getCreditCards(this.customer.id); await getCreditCards(customer.value.id);
// Switch modal to success view and close after delay // Switch modal to success view and close after delay
setTimeout(() => { setTimeout(() => {
this.isCreatingAccount = false; isCreatingAccount.value = false;
setTimeout(() => { setTimeout(() => {
this.isCreateAccountModalVisible = false; isCreateAccountModalVisible.value = false;
this.createdProfileId = ''; createdProfileId.value = '';
notify({ notify({
title: "Success", title: "Success",
@@ -646,7 +630,7 @@ export default defineComponent({
} else { } else {
// Hide modal on error // Hide modal on error
this.isCreateAccountModalVisible = false; isCreateAccountModalVisible.value = false;
// Check for E00039 duplicate error // Check for E00039 duplicate error
const errorMessage = response.data.message || response.data.error_detail || "Failed to create Authorize.net account"; const errorMessage = response.data.message || response.data.error_detail || "Failed to create Authorize.net account";
@@ -654,7 +638,7 @@ export default defineComponent({
if (response.data.is_duplicate || errorMessage.includes("E00039")) { if (response.data.is_duplicate || errorMessage.includes("E00039")) {
// Show duplicate account popup // Show duplicate account popup
setTimeout(() => { setTimeout(() => {
this.showDuplicateErrorModal(); showDuplicateErrorModal();
}, 300); }, 300);
return; return;
} else { } else {
@@ -666,10 +650,11 @@ export default defineComponent({
}); });
} }
} }
} catch (error: any) { } catch (err: unknown) {
const error = err as AxiosError<{ error_detail?: string; detail?: string; message?: string; is_duplicate?: boolean }>;
console.error("Failed to create account:", error); console.error("Failed to create account:", error);
this.isCreateAccountModalVisible = false; isCreateAccountModalVisible.value = false;
this.isCreatingAccount = false; isCreatingAccount.value = false;
// Check for E00039 duplicate error // Check for E00039 duplicate error
const errorMessage = error.response?.data?.error_detail || const errorMessage = error.response?.data?.error_detail ||
@@ -680,7 +665,7 @@ export default defineComponent({
if (error.response?.data?.is_duplicate || errorMessage.includes("E00039")) { if (error.response?.data?.is_duplicate || errorMessage.includes("E00039")) {
// Show duplicate account popup // Show duplicate account popup
setTimeout(() => { setTimeout(() => {
this.showDuplicateErrorModal(); showDuplicateErrorModal();
}, 300); }, 300);
return; return;
} }
@@ -692,36 +677,41 @@ export default defineComponent({
type: "error" type: "error"
}); });
} }
}, }
showDeleteAccountModal() {
this.isDeleteAccountModalVisible = true; const showDeleteAccountModal = () => {
}, isDeleteAccountModalVisible.value = true;
showDuplicateErrorModal() { }
this.isDuplicateErrorModalVisible = true;
}, const showDuplicateErrorModal = () => {
hideDuplicateErrorModal() { isDuplicateErrorModalVisible.value = true;
this.isDuplicateErrorModalVisible = false; }
},
addCreditCard() { const hideDuplicateErrorModal = () => {
isDuplicateErrorModalVisible.value = false;
}
const addCreditCard = () => {
// Redirect to add card page // Redirect to add card page
this.$router.push({ name: 'cardadd', params: { customerId: this.customer.id } }); router.push({ name: 'cardadd', params: { customerId: customer.value.id } });
}, }
async deleteAccount() {
this.isDeleteAccountModalVisible = false; const deleteAccount = async () => {
isDeleteAccountModalVisible.value = false;
try { try {
const path = `${import.meta.env.VITE_AUTHORIZE_URL}/user/delete-account/${this.customer.id}`; const path = `${import.meta.env.VITE_AUTHORIZE_URL}/user/delete-account/${customer.value.id}`;
const response = await axios.delete(path, { headers: authHeader() }); const response = await axios.delete(path, { headers: authHeader() });
if (response.data.success) { if (response.data.success) {
// Update local state // Update local state
this.customer.auth_net_profile_id = null; customer.value.auth_net_profile_id = null;
this.authorizeCheck.valid_for_charging = false; authorizeCheck.value.valid_for_charging = false;
this.authorizeCheck.profile_exists = false; authorizeCheck.value.profile_exists = false;
this.authorizeCheck.has_payment_methods = false; authorizeCheck.value.has_payment_methods = false;
// Refresh credit cards list (IDs should now be null) // Refresh credit cards list (IDs should now be null)
this.getCreditCards(this.customer.id); getCreditCards(customer.value.id);
notify({ notify({
title: "Success", title: "Success",
@@ -735,7 +725,7 @@ export default defineComponent({
type: "warning" type: "warning"
}); });
} }
} catch (error: any) { } catch (error: unknown) {
console.error("Failed to delete account:", error); console.error("Failed to delete account:", error);
notify({ notify({
title: "Error", title: "Error",
@@ -743,21 +733,22 @@ export default defineComponent({
type: "error" type: "error"
}); });
} }
}, }
async cleanupAuthorizeData() {
const cleanupAuthorizeData = async () => {
try { try {
const path = `${import.meta.env.VITE_BASE_URL}/payment/authorize/cleanup/${this.customer.id}`; const path = `${import.meta.env.VITE_BASE_URL}/payment/authorize/cleanup/${customer.value.id}`;
const response = await axios.post(path, {}, { headers: authHeader() }); const response = await axios.post(path, {}, { headers: authHeader() });
if (response.data.ok) { if (response.data.ok) {
// Update local state to reflect cleanup // Update local state to reflect cleanup
this.customer.auth_net_profile_id = null; customer.value.auth_net_profile_id = null;
this.authorizeCheck.valid_for_charging = false; authorizeCheck.value.valid_for_charging = false;
this.authorizeCheck.profile_exists = false; authorizeCheck.value.profile_exists = false;
this.authorizeCheck.has_payment_methods = false; authorizeCheck.value.has_payment_methods = false;
// Refresh credit cards to reflect null payment profile IDs // Refresh credit cards to reflect null payment profile IDs
this.getCreditCards(this.customer.id); getCreditCards(customer.value.id);
console.log("Successfully cleaned up Authorize.Net data:", response.data.message); console.log("Successfully cleaned up Authorize.Net data:", response.data.message);
} else { } else {
@@ -766,13 +757,14 @@ export default defineComponent({
} catch (error) { } catch (error) {
console.error("Error during cleanup:", error); console.error("Error during cleanup:", error);
} }
}, }
getAccountStatusMessage(): string {
if (!this.authorizeCheck || !this.authorizeCheck.missing_components) { const getAccountStatusMessage = (): string => {
if (!authorizeCheck.value || !authorizeCheck.value.missing_components) {
return 'Account setup incomplete'; return 'Account setup incomplete';
} }
const missing = this.authorizeCheck.missing_components; const missing = authorizeCheck.value.missing_components;
if (missing.includes('customer_not_found')) { if (missing.includes('customer_not_found')) {
return 'Customer not found in Authorize.net'; return 'Customer not found in Authorize.net';
} else if (missing.includes('authorize_net_profile')) { } else if (missing.includes('authorize_net_profile')) {
@@ -786,22 +778,30 @@ export default defineComponent({
} else { } else {
return 'Account requires setup'; return 'Account requires setup';
} }
}, }
getServiceTypeName(typeId: number): string {
const getServiceTypeName = (typeId: number): string => {
const typeMap: { [key: number]: string } = { 0: 'Tune-up', 1: 'No Heat', 2: 'Fix', 3: 'Tank Install', 4: 'Other' }; const typeMap: { [key: number]: string } = { 0: 'Tune-up', 1: 'No Heat', 2: 'Fix', 3: 'Tank Install', 4: 'Other' };
return typeMap[typeId] || 'Unknown'; return typeMap[typeId] || 'Unknown';
}, }
getServiceTypeColor(typeId: number): string {
const getServiceTypeColor = (typeId: number): string => {
const colorMap: { [key: number]: string } = { 0: 'primary', 1: 'error', 2: 'warning', 3: 'info', 4: 'neutral' }; const colorMap: { [key: number]: string } = { 0: 'primary', 1: 'error', 2: 'warning', 3: 'info', 4: 'neutral' };
return `badge-${colorMap[typeId] || 'neutral'}`; return `badge-${colorMap[typeId] || 'neutral'}`;
}, }
formatScheduledDate(dateString: string): string {
const formatScheduledDate = (dateString: string): string => {
if (!dateString) return 'Not scheduled'; if (!dateString) return 'Not scheduled';
return dateString; // Could format with dayjs if needed return dateString; // Could format with dayjs if needed
} }
}, // Lifecycle
onMounted(() => {
userStatus()
getServiceOrder(route.params.id)
getServicePartsForCustomer();
}) })
</script> </script>
<style scoped></style> <style scoped></style>

View File

@@ -35,8 +35,8 @@
/> />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue'; import { ref, onMounted } from 'vue';
import Header from '../../layouts/headers/headerauth.vue'; import Header from '../../layouts/headers/headerauth.vue';
import SideBar from '../../layouts/sidebar/sidebar.vue'; import SideBar from '../../layouts/sidebar/sidebar.vue';
import Footer from '../../layouts/footers/footer.vue'; import Footer from '../../layouts/footers/footer.vue';
@@ -60,43 +60,18 @@ interface ServiceCall {
service_cost: string; service_cost: string;
} }
export default defineComponent({ // Reactive data
name: 'ServiceCalendar', const user = ref(null)
components: { Header, SideBar, Footer, FullCalendar, ServiceEditModal }, const selectedServiceForEdit = ref(null as Partial<ServiceCall> | null)
data() { const fullCalendar = ref()
return {
user: null,
selectedServiceForEdit: null as Partial<ServiceCall> | null,
calendarOptions: {
plugins: [dayGridPlugin, interactionPlugin],
initialView: 'dayGridMonth',
weekends: true,
// Instead of a static array, we use a function source.
// This is the standard way FullCalendar fetches events.
events: `${import.meta.env.VITE_BASE_URL}/service/all`,
eventClick: this.handleEventClick,
// Add headers for authentication if needed by your API
eventSourceSuccess: (content) => {
// This is where you could transform data if needed
return content;
},
eventSourceFailure: (error) => {
console.error("Failed to fetch calendar events:", error);
}
} as CalendarOptions,
};
},
created() {
this.userStatus();
// We no longer need to call fetchEvents() here because FullCalendar does it automatically.
},
methods: {
// We can remove the fetchEvents method as FullCalendar now handles it.
// async fetchEvents(): Promise<void> { ... }
handleEventClick(clickInfo: EventClickArg): void { // Functions
// We can remove the fetchEvents method as FullCalendar now handles it.
// async fetchEvents(): Promise<void> { ... }
const handleEventClick = (clickInfo: EventClickArg): void => {
// This logic remains the same, as it correctly pulls data from extendedProps // This logic remains the same, as it correctly pulls data from extendedProps
this.selectedServiceForEdit = { selectedServiceForEdit.value = {
id: parseInt(clickInfo.event.id), id: parseInt(clickInfo.event.id),
scheduled_date: clickInfo.event.startStr, scheduled_date: clickInfo.event.startStr,
customer_name: clickInfo.event.title.split(': ')[1] || 'Unknown Customer', customer_name: clickInfo.event.title.split(': ')[1] || 'Unknown Customer',
@@ -105,52 +80,77 @@ export default defineComponent({
description: clickInfo.event.extendedProps.description, description: clickInfo.event.extendedProps.description,
service_cost: clickInfo.event.extendedProps.service_cost, service_cost: clickInfo.event.extendedProps.service_cost,
}; };
}, }
closeEditModal() { // Calendar options
this.selectedServiceForEdit = null; const calendarOptions = ref({
plugins: [dayGridPlugin, interactionPlugin],
initialView: 'dayGridMonth',
weekends: true,
// Instead of a static array, we use a function source.
// This is the standard way FullCalendar fetches events.
events: `${import.meta.env.VITE_BASE_URL}/service/all`,
eventClick: handleEventClick,
// Add headers for authentication if needed by your API
eventSourceSuccess: (content) => {
// This is where you could transform data if needed
return content;
}, },
eventSourceFailure: (error) => {
console.error("Failed to fetch calendar events:", error);
}
} as CalendarOptions)
// =================== THIS IS THE CORRECTED SECTION =================== // Lifecycle
async handleSaveChanges(updatedService: ServiceCall) { onMounted(() => {
userStatus();
// We no longer need to call fetchEvents() here because FullCalendar does it automatically.
})
const closeEditModal = () => {
selectedServiceForEdit.value = null;
}
// =================== THIS IS THE CORRECTED SECTION ===================
const handleSaveChanges = async (updatedService: ServiceCall) => {
try { try {
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`; const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true }); await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
// Get the FullCalendar component instance from the ref // Get the FullCalendar component instance from the ref
const calendarApi = (this.$refs.fullCalendar as any).getApi(); const calendarApi = (fullCalendar.value as any).getApi();
if (calendarApi) { if (calendarApi) {
// Tell FullCalendar to re-fetch its events from the source. // Tell FullCalendar to re-fetch its events from the source.
// This is the most reliable way to refresh the view immediately. // This is the most reliable way to refresh the view immediately.
calendarApi.refetchEvents(); calendarApi.refetchEvents();
} }
this.closeEditModal(); closeEditModal();
} catch (error) { } catch (error) {
console.error("Failed to save changes:", error); console.error("Failed to save changes:", error);
alert("An error occurred while saving. Please check the console."); alert("An error occurred while saving. Please check the console.");
} }
}, }
// =================== END OF CORRECTED SECTION =================== // =================== END OF CORRECTED SECTION ===================
async handleDeleteService(serviceId: number) { const handleDeleteService = async (serviceId: number) => {
try { try {
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`; const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
await axios.delete(path, { withCredentials: true, headers: authHeader() }); await axios.delete(path, { withCredentials: true, headers: authHeader() });
// Also refresh the calendar after a delete // Also refresh the calendar after a delete
const calendarApi = (this.$refs.fullCalendar as any).getApi(); const calendarApi = (fullCalendar.value as any).getApi();
if (calendarApi) { if (calendarApi) {
calendarApi.refetchEvents(); calendarApi.refetchEvents();
} }
this.closeEditModal(); closeEditModal();
} catch (error) { } catch (error) {
console.error("Error deleting event:", error); console.error("Error deleting event:", error);
} }
}, }
userStatus() { const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -160,13 +160,11 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
.catch(() => { .catch(() => {
this.user = null user.value = null
}) })
}, }
},
});
</script> </script>

View File

@@ -99,8 +99,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, PropType } from 'vue'; import { ref, watch } from 'vue';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import axios from 'axios'; import axios from 'axios';
import authHeader from '../../services/auth.header'; import authHeader from '../../services/auth.header';
@@ -111,88 +111,94 @@ interface EditableService extends Omit<ServiceCall, 'scheduled_date'> { date: st
interface Customer { id: number; account_number: string; customer_first_name: string; customer_last_name: string; customer_address: string; customer_town: string; customer_state: number; customer_zip: string; customer_phone_number: string; } interface Customer { id: number; account_number: string; customer_first_name: string; customer_last_name: string; customer_address: string; customer_town: string; customer_state: number; customer_zip: string; customer_phone_number: string; }
interface ServiceParts { customer_id: number; oil_filter: string; oil_filter_2: string; oil_nozzle: string; oil_nozzle_2: string; } interface ServiceParts { customer_id: number; oil_filter: string; oil_filter_2: string; oil_nozzle: string; oil_nozzle_2: string; }
export default defineComponent({ // Props and Emits
name: 'ServiceEditModal', const props = defineProps<{
props: { service: { type: Object as PropType<Partial<ServiceCall>>, required: true } }, service: Partial<ServiceCall>
data() { }>()
return {
editableService: {} as Partial<EditableService>, const emit = defineEmits<{
customer: null as Customer | null, 'close-modal': []
serviceParts: null as ServiceParts | null, 'save-changes': [service: ServiceCall]
isLoadingParts: true, 'delete-service': [serviceId: number]
serviceOptions: [ }>()
// Reactive data
const editableService = ref({} as Partial<EditableService>)
const customer = ref(null as Customer | null)
const serviceParts = ref(null as ServiceParts | null)
const isLoadingParts = ref(true)
const serviceOptions = ref([
{ text: 'Tune-up', value: 0 }, { text: 'No Heat', value: 1 }, { text: 'Fix', value: 2 }, { text: 'Tune-up', value: 0 }, { text: 'No Heat', value: 1 }, { text: 'Fix', value: 2 },
{ text: 'Tank Install', value: 3 }, { text: 'Other', value: 4 }, { text: 'Tank Install', value: 3 }, { text: 'Other', value: 4 },
], ])
};
}, // Watchers
watch: { watch(() => props.service, (newVal) => {
service: {
handler(newVal) {
if (!newVal) return; if (!newVal) return;
const scheduled = dayjs(newVal.scheduled_date || new Date()); const scheduled = dayjs(newVal.scheduled_date || new Date());
this.editableService = { ...newVal, date: scheduled.format('YYYY-MM-DD'), time: scheduled.hour() }; editableService.value = { ...newVal, date: scheduled.format('YYYY-MM-DD'), time: scheduled.hour() };
if (newVal.customer_id) { if (newVal.customer_id) {
this.getCustomer(newVal.customer_id); getCustomer(newVal.customer_id);
this.getServiceParts(newVal.customer_id); getServiceParts(newVal.customer_id);
} }
}, }, { immediate: true, deep: true })
immediate: true,
deep: true, // Functions
}, const getCustomer = (customerId: number) => {
}, customer.value = null;
methods: {
getCustomer(customerId: number) {
this.customer = null;
let path = import.meta.env.VITE_BASE_URL + '/customer/' + customerId; let path = import.meta.env.VITE_BASE_URL + '/customer/' + customerId;
axios.get(path, { headers: authHeader() }) axios.get(path, { headers: authHeader() })
.then((response: any) => { this.customer = response.data; }) .then((response: any) => { customer.value = response.data; })
.catch((error: any) => { console.error("Failed to fetch customer details for modal:", error); }); .catch((error: any) => { console.error("Failed to fetch customer details for modal:", error); });
}, }
getServiceParts(customerId: number) {
this.isLoadingParts = true; const getServiceParts = (customerId: number) => {
this.serviceParts = null; isLoadingParts.value = true;
serviceParts.value = null;
let path = `${import.meta.env.VITE_BASE_URL}/service/parts/customer/${customerId}`; let path = `${import.meta.env.VITE_BASE_URL}/service/parts/customer/${customerId}`;
axios.get(path, { headers: authHeader() }) axios.get(path, { headers: authHeader() })
.then((response: any) => { this.serviceParts = response.data; }) .then((response: any) => { serviceParts.value = response.data; })
.catch((error: any) => { console.error("Failed to fetch service parts:", error); }) .catch((error: any) => { console.error("Failed to fetch service parts:", error); })
.finally(() => { this.isLoadingParts = false; }); .finally(() => { isLoadingParts.value = false; });
}, }
async saveChanges() {
const date = this.editableService.date; const saveChanges = async () => {
const time = this.editableService.time || 0; const date = editableService.value.date;
const time = editableService.value.time || 0;
const combinedDateTime = dayjs(`${date} ${time}:00`).format('YYYY-MM-DDTHH:mm:ss'); const combinedDateTime = dayjs(`${date} ${time}:00`).format('YYYY-MM-DDTHH:mm:ss');
const finalPayload = { ...this.service, ...this.editableService, scheduled_date: combinedDateTime }; const finalPayload = { ...props.service, ...editableService.value, scheduled_date: combinedDateTime };
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${finalPayload.id}`; const path = `${import.meta.env.VITE_BASE_URL}/service/update/${finalPayload.id}`;
try { try {
await axios.put(path, finalPayload, { headers: authHeader(), withCredentials: true }); await axios.put(path, finalPayload, { headers: authHeader(), withCredentials: true });
this.$emit('save-changes', finalPayload); emit('save-changes', finalPayload as ServiceCall);
} catch (error) { } catch (error) {
console.error("Failed to save changes:", error); console.error("Failed to save changes:", error);
alert("An error occurred while saving. Please check the console."); alert("An error occurred while saving. Please check the console.");
} }
}, }
confirmDelete() {
if (this.service.id && window.confirm(`Are you sure you want to delete this service call?`)) { const confirmDelete = () => {
this.$emit('delete-service', this.service.id); if (props.service.id && window.confirm(`Are you sure you want to delete this service call?`)) {
emit('delete-service', props.service.id);
} }
}, }
getServiceTypeName(typeId: number | undefined | null): string {
const getServiceTypeName = (typeId: number | undefined | null): string => {
if (typeId === undefined || typeId === null) return 'Unknown'; if (typeId === undefined || typeId === null) return 'Unknown';
const typeMap: { [key: number]: string } = { 0: 'Tune-up', 1: 'No Heat', 2: 'Fix', 3: 'Tank Install', 4: 'Other' }; const typeMap: { [key: number]: string } = { 0: 'Tune-up', 1: 'No Heat', 2: 'Fix', 3: 'Tank Install', 4: 'Other' };
return typeMap[typeId] || 'Unknown'; return typeMap[typeId] || 'Unknown';
}, }
getServiceTypeColor(typeId: number | undefined | null): string {
const getServiceTypeColor = (typeId: number | undefined | null): string => {
if (typeId === undefined || typeId === null) return 'gray'; if (typeId === undefined || typeId === null) return 'gray';
const colorMap: { [key: number]: string } = { 0: 'blue', 1: 'red', 2: 'green', 3: '#B58900', 4: 'black' }; const colorMap: { [key: number]: string } = { 0: 'blue', 1: 'red', 2: 'green', 3: '#B58900', 4: 'black' };
return colorMap[typeId] || 'gray'; return colorMap[typeId] || 'gray';
}, }
getStateAbbrev(stateId: number | undefined | null): string {
const getStateAbbrev = (stateId: number | undefined | null): string => {
if (stateId === undefined || stateId === null) return 'Unknown'; if (stateId === undefined || stateId === null) return 'Unknown';
const stateMap: { [key: number]: string } = { 0: 'MA', 1: 'RI', 2: 'NH', 3: 'ME', 4: 'VT', 5: 'CT', 6: 'NY' }; const stateMap: { [key: number]: string } = { 0: 'MA', 1: 'RI', 2: 'NH', 3: 'ME', 4: 'VT', 5: 'CT', 6: 'NY' };
return stateMap[stateId] || 'Unknown'; return stateMap[stateId] || 'Unknown';
} }
},
});
</script> </script>

View File

@@ -156,63 +156,48 @@
@delete-service="handleDeleteService" @delete-service="handleDeleteService"
/> />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted } from 'vue'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../services/auth.header' import authHeader from '../../services/auth.header'
import { ServiceCall } from '../../types/models'
import Footer from '../../layouts/footers/footer.vue' import Footer from '../../layouts/footers/footer.vue'
import ServiceEditModal from './ServiceEditModal.vue' import ServiceEditModal from './ServiceEditModal.vue'
import dayjs from 'dayjs'; import dayjs from 'dayjs';
interface ServiceCall { // Reactive data
id: number; const user = ref(null)
scheduled_date: string; const services = ref<ServiceCall[]>([])
customer_id: number; const isLoading = ref(true)
customer_name: string; const selectedServiceForEdit = ref<ServiceCall | null>(null)
customer_address: string; // --- ADDITIONS FOR TRUNCATION ---
customer_town: string; const wordLimit = ref(50)
type_service_call: number; const expandedIds = ref<number[]>([])
description: string;
service_cost: string;
payment_status?: number;
}
export default defineComponent({ // Lifecycle
name: 'ServiceHome', onMounted(() => {
components: { Footer, ServiceEditModal }, userStatus();
data() { fetchUpcomingServices();
return { })
user: null,
services: [] as ServiceCall[], // Functions
isLoading: true, const fetchUpcomingServices = async (): Promise<void> => {
selectedServiceForEdit: null as ServiceCall | null, isLoading.value = true;
// --- ADDITIONS FOR TRUNCATION ---
wordLimit: 50,
expandedIds: [] as number[],
}
},
created() {
this.userStatus();
this.fetchUpcomingServices();
},
methods: {
async fetchUpcomingServices(): Promise<void> {
this.isLoading = true;
try { try {
const path = import.meta.env.VITE_BASE_URL + '/service/upcoming'; const path = import.meta.env.VITE_BASE_URL + '/service/upcoming';
const response = await axios.get(path, { const response = await axios.get(path, {
headers: authHeader(), headers: authHeader(),
withCredentials: true, withCredentials: true,
}); });
this.services = response.data.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id); services.value = response.data.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
} catch (error) { } catch (error) {
console.error("Failed to fetch upcoming service calls:", error); console.error("Failed to fetch upcoming service calls:", error);
} finally { } finally {
this.isLoading = false; isLoading.value = false;
} }
}, }
userStatus() { const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -222,83 +207,88 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
.catch(() => { .catch(() => {
this.user = null user.value = null
}) })
}, }
openEditModal(service: ServiceCall) { const openEditModal = (service: ServiceCall) => {
this.selectedServiceForEdit = service; selectedServiceForEdit.value = service;
}, }
closeEditModal() { const closeEditModal = () => {
this.selectedServiceForEdit = null; selectedServiceForEdit.value = null;
}, }
isLongDescription(text: string): boolean {
const isLongDescription = (text: string): boolean => {
if (!text) return false; if (!text) return false;
return text.split(/\s+/).length > this.wordLimit; return text.split(/\s+/).length > wordLimit.value;
}, }
truncateDescription(text: string): string {
if (!this.isLongDescription(text)) return text; const truncateDescription = (text: string): string => {
if (!isLongDescription(text)) return text;
const words = text.split(/\s+/); const words = text.split(/\s+/);
return words.slice(0, this.wordLimit).join(' ') + '...'; return words.slice(0, wordLimit.value).join(' ') + '...';
}, }
isExpanded(id: number): boolean {
return this.expandedIds.includes(id); const isExpanded = (id: number): boolean => {
}, return expandedIds.value.includes(id);
toggleExpand(id: number): void { }
const index = this.expandedIds.indexOf(id);
const toggleExpand = (id: number): void => {
const index = expandedIds.value.indexOf(id);
if (index === -1) { if (index === -1) {
this.expandedIds.push(id); expandedIds.value.push(id);
} else { } else {
this.expandedIds.splice(index, 1); expandedIds.value.splice(index, 1);
} }
}, }
async handleSaveChanges(updatedService: ServiceCall) {
const handleSaveChanges = async (updatedService: ServiceCall) => {
try { try {
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`; const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
const response = await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true }); const response = await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
if (response.data.ok) { if (response.data.ok) {
const index = this.services.findIndex(s => s.id === updatedService.id); const index = services.value.findIndex(s => s.id === updatedService.id);
if (index !== -1) { if (index !== -1) {
this.services[index] = response.data.service; services.value[index] = response.data.service;
} }
this.closeEditModal(); closeEditModal();
} }
} catch (error) { } catch (error) {
console.error("Failed to save changes:", error); console.error("Failed to save changes:", error);
alert("An error occurred while saving. Please check the console."); alert("An error occurred while saving. Please check the console.");
} }
}, }
async handleDeleteService(serviceId: number) { const handleDeleteService = async (serviceId: number) => {
try { try {
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`; const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
const response = await axios.delete(path, { headers: authHeader(), withCredentials: true }); const response = await axios.delete(path, { headers: authHeader(), withCredentials: true });
if (response.data.ok) { if (response.data.ok) {
this.services = this.services.filter(s => s.id !== serviceId); services.value = services.value.filter(s => s.id !== serviceId);
this.closeEditModal(); closeEditModal();
} }
} catch (error) { } catch (error) {
console.error("Failed to delete service call:", error); console.error("Failed to delete service call:", error);
alert("An error occurred while deleting. Please check the console."); alert("An error occurred while deleting. Please check the console.");
} }
}, }
formatDate(dateString: string): string { const formatDate = (dateString: string): string => {
if (!dateString) return 'N/A'; if (!dateString) return 'N/A';
return dayjs(dateString).format('MMMM D, YYYY'); return dayjs(dateString).format('MMMM D, YYYY');
}, }
formatTime(dateString: string): string { const formatTime = (dateString: string): string => {
if (!dateString) return 'N/A'; if (!dateString) return 'N/A';
return dayjs(dateString).format('h:mm A'); return dayjs(dateString).format('h:mm A');
}, }
getServiceTypeName(typeId: number): string { const getServiceTypeName = (typeId: number): string => {
const typeMap: { [key: number]: string } = { const typeMap: { [key: number]: string } = {
0: 'Tune-up', 0: 'Tune-up',
1: 'No Heat', 1: 'No Heat',
@@ -307,9 +297,9 @@ export default defineComponent({
4: 'Other', 4: 'Other',
}; };
return typeMap[typeId] || 'Unknown Service'; return typeMap[typeId] || 'Unknown Service';
}, }
// --- ADD THIS METHOD ---
formatCurrency(value: string | number): string { const formatCurrency = (value: string | number): string => {
if (value === null || value === undefined || value === '') return '$0.00'; if (value === null || value === undefined || value === '') return '$0.00';
const numberValue = Number(value); const numberValue = Number(value);
if (isNaN(numberValue)) return '$0.00'; if (isNaN(numberValue)) return '$0.00';
@@ -318,8 +308,9 @@ export default defineComponent({
style: 'currency', style: 'currency',
currency: 'USD', currency: 'USD',
}).format(numberValue); }).format(numberValue);
}, }
getServiceTypeColor(typeId: number): string {
const getServiceTypeColor = (typeId: number): string => {
const colorMap: { [key: number]: string } = { const colorMap: { [key: number]: string } = {
0: 'blue', 0: 'blue',
1: 'red', 1: 'red',
@@ -328,15 +319,13 @@ export default defineComponent({
4: 'black', 4: 'black',
}; };
return colorMap[typeId] || 'gray'; return colorMap[typeId] || 'gray';
}, }
shouldShowChargeButton(service: any): boolean { const shouldShowChargeButton = (service: any): boolean => {
return service.payment_status === null || service.payment_status === undefined; return service.payment_status === null || service.payment_status === undefined;
}, }
shouldShowCaptureButton(service: any): boolean { const shouldShowCaptureButton = (service: any): boolean => {
return service.payment_status === 1; return service.payment_status === 1;
} }
},
})
</script> </script>

View File

@@ -163,86 +163,72 @@
/> />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted } from 'vue'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../services/auth.header' import authHeader from '../../services/auth.header'
import { ServiceCall } from '../../types/models'
import Footer from '../../layouts/footers/footer.vue' import Footer from '../../layouts/footers/footer.vue'
import ServiceEditModal from './ServiceEditModal.vue' import ServiceEditModal from './ServiceEditModal.vue'
import dayjs from 'dayjs'; import dayjs from 'dayjs';
interface ServiceCall { // Reactive data
id: number; const user = ref(null)
scheduled_date: string; const services = ref<ServiceCall[]>([])
customer_id: number; const isLoading = ref(true)
customer_name: string; const selectedServiceForEdit = ref<ServiceCall | null>(null)
customer_address: string; // --- ADDITIONS FOR TRUNCATION ---
customer_town: string; const wordLimit = ref(50)
type_service_call: number; const expandedIds = ref<number[]>([])
description: string;
service_cost: string; // Lifecycle
payment_status?: number; onMounted(() => {
userStatus();
fetchPastServices();
})
// Functions
const isLongDescription = (text: string): boolean => {
if (!text) return false;
return text.split(/\s+/).length > wordLimit.value;
} }
export default defineComponent({ const truncateDescription = (text: string): string => {
name: 'ServiceHPast', if (!isLongDescription(text)) return text;
components: { Footer, ServiceEditModal },
data() {
return {
user: null,
services: [] as ServiceCall[],
isLoading: true,
selectedServiceForEdit: null as ServiceCall | null,
// --- ADDITIONS FOR TRUNCATION ---
wordLimit: 50,
expandedIds: [] as number[],
}
},
created() {
this.userStatus();
this.fetchPastServices();
},
methods: {
// --- NEW METHODS FOR TRUNCATION ---
isLongDescription(text: string): boolean {
if (!text) return false;
return text.split(/\s+/).length > this.wordLimit;
},
truncateDescription(text: string): string {
if (!this.isLongDescription(text)) return text;
const words = text.split(/\s+/); const words = text.split(/\s+/);
return words.slice(0, this.wordLimit).join(' ') + '...'; return words.slice(0, wordLimit.value).join(' ') + '...';
}, }
isExpanded(id: number): boolean {
return this.expandedIds.includes(id);
},
toggleExpand(id: number): void {
const index = this.expandedIds.indexOf(id);
if (index === -1) {
this.expandedIds.push(id);
} else {
this.expandedIds.splice(index, 1);
}
},
// --- API and Data Handling Methods --- const isExpanded = (id: number): boolean => {
async fetchPastServices(): Promise<void> { return expandedIds.value.includes(id);
this.isLoading = true; }
const toggleExpand = (id: number): void => {
const index = expandedIds.value.indexOf(id);
if (index === -1) {
expandedIds.value.push(id);
} else {
expandedIds.value.splice(index, 1);
}
}
const fetchPastServices = async (): Promise<void> => {
isLoading.value = true;
try { try {
const path = import.meta.env.VITE_BASE_URL + '/service/past'; const path = import.meta.env.VITE_BASE_URL + '/service/past';
const response = await axios.get(path, { const response = await axios.get(path, {
headers: authHeader(), headers: authHeader(),
withCredentials: true, withCredentials: true,
}); });
this.services = response.data.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id); services.value = response.data.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
} catch (error) { } catch (error) {
console.error("Failed to fetch past service calls:", error); console.error("Failed to fetch past service calls:", error);
} finally { } finally {
this.isLoading = false; isLoading.value = false;
} }
}, }
userStatus() { const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -252,55 +238,54 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
.catch(() => { .catch(() => {
this.user = null user.value = null
}) })
}, }
openEditModal(service: ServiceCall) { const openEditModal = (service: ServiceCall) => {
this.selectedServiceForEdit = service; selectedServiceForEdit.value = service;
}, }
closeEditModal() { const closeEditModal = () => {
this.selectedServiceForEdit = null; selectedServiceForEdit.value = null;
}, }
async handleSaveChanges(updatedService: ServiceCall) { const handleSaveChanges = async (updatedService: ServiceCall) => {
try { try {
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`; const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
const response = await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true }); const response = await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
if (response.data.ok) { if (response.data.ok) {
const index = this.services.findIndex(s => s.id === updatedService.id); const index = services.value.findIndex(s => s.id === updatedService.id);
if (index !== -1) { if (index !== -1) {
this.services[index] = response.data.service; services.value[index] = response.data.service;
} }
this.closeEditModal(); closeEditModal();
} }
} catch (error) { } catch (error) {
console.error("Failed to save changes:", error); console.error("Failed to save changes:", error);
alert("An error occurred while saving. Please check the console."); alert("An error occurred while saving. Please check the console.");
} }
}, }
async handleDeleteService(serviceId: number) { const handleDeleteService = async (serviceId: number) => {
try { try {
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`; const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
const response = await axios.delete(path, { headers: authHeader(), withCredentials: true }); const response = await axios.delete(path, { headers: authHeader(), withCredentials: true });
if (response.data.ok) { if (response.data.ok) {
this.services = this.services.filter(s => s.id !== serviceId); services.value = services.value.filter(s => s.id !== serviceId);
this.closeEditModal(); closeEditModal();
} }
} catch (error) { } catch (error) {
console.error("Failed to delete service call:", error); console.error("Failed to delete service call:", error);
alert("An error occurred while deleting. Please check the console."); alert("An error occurred while deleting. Please check the console.");
} }
}, }
// --- Formatting and Display Methods --- const formatCurrency = (value: string | number): string => {
formatCurrency(value: string | number): string {
if (value === null || value === undefined || value === '') return '$0.00'; if (value === null || value === undefined || value === '') return '$0.00';
const numberValue = Number(value); const numberValue = Number(value);
if (isNaN(numberValue)) return '$0.00'; if (isNaN(numberValue)) return '$0.00';
@@ -309,19 +294,19 @@ export default defineComponent({
style: 'currency', style: 'currency',
currency: 'USD', currency: 'USD',
}).format(numberValue); }).format(numberValue);
}, }
formatDate(dateString: string): string { const formatDate = (dateString: string): string => {
if (!dateString) return 'N/A'; if (!dateString) return 'N/A';
return dayjs(dateString).format('MMMM D, YYYY'); return dayjs(dateString).format('MMMM D, YYYY');
}, }
formatTime(dateString: string): string { const formatTime = (dateString: string): string => {
if (!dateString) return 'N/A'; if (!dateString) return 'N/A';
return dayjs(dateString).format('h:mm A'); return dayjs(dateString).format('h:mm A');
}, }
getServiceTypeName(typeId: number): string { const getServiceTypeName = (typeId: number): string => {
const typeMap: { [key: number]: string } = { const typeMap: { [key: number]: string } = {
0: 'Tune-up', 0: 'Tune-up',
1: 'No Heat', 1: 'No Heat',
@@ -330,9 +315,9 @@ export default defineComponent({
4: 'Other', 4: 'Other',
}; };
return typeMap[typeId] || 'Unknown Service'; return typeMap[typeId] || 'Unknown Service';
}, }
getServiceTypeColor(typeId: number): string { const getServiceTypeColor = (typeId: number): string => {
const colorMap: { [key: number]: string } = { const colorMap: { [key: number]: string } = {
0: 'blue', 0: 'blue',
1: 'red', 1: 'red',
@@ -341,15 +326,13 @@ export default defineComponent({
4: 'black', 4: 'black',
}; };
return colorMap[typeId] || 'gray'; return colorMap[typeId] || 'gray';
}, }
shouldShowChargeButton(service: any): boolean { const shouldShowChargeButton = (service: any): boolean => {
return service.payment_status === null || service.payment_status === undefined; return service.payment_status === null || service.payment_status === undefined;
}, }
shouldShowCaptureButton(service: any): boolean { const shouldShowCaptureButton = (service: any): boolean => {
return service.payment_status === 1; return service.payment_status === 1;
} }
},
})
</script> </script>

View File

@@ -123,56 +123,43 @@
<Footer /> <Footer />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted } from 'vue'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../services/auth.header' import authHeader from '../../services/auth.header'
import { ServicePlan } from '../../types/models'
import Footer from '../../layouts/footers/footer.vue' import Footer from '../../layouts/footers/footer.vue'
import dayjs from 'dayjs'; import dayjs from 'dayjs';
interface ServicePlan { // Reactive data
id: number; const user = ref(null)
customer_id: number; const servicePlans = ref<ServicePlan[]>([])
customer_name: string; const isLoading = ref(true)
customer_address: string;
customer_town: string;
contract_plan: number;
contract_years: number;
contract_start_date: string;
}
export default defineComponent({ // Lifecycle
name: 'ServicePlans', onMounted(() => {
components: { Footer }, userStatus();
data() { fetchServicePlans();
return { })
user: null,
servicePlans: [] as ServicePlan[], // Functions
isLoading: true, const fetchServicePlans = async (): Promise<void> => {
} isLoading.value = true;
},
created() {
this.userStatus();
this.fetchServicePlans();
},
methods: {
async fetchServicePlans(): Promise<void> {
this.isLoading = true;
try { try {
const path = import.meta.env.VITE_BASE_URL + '/service/plans/active'; const path = import.meta.env.VITE_BASE_URL + '/service/plans/active';
const response = await axios.get(path, { const response = await axios.get(path, {
headers: authHeader(), headers: authHeader(),
withCredentials: true, withCredentials: true,
}); });
this.servicePlans = response.data; servicePlans.value = response.data;
} catch (error) { } catch (error) {
console.error("Failed to fetch service plans:", error); console.error("Failed to fetch service plans:", error);
} finally { } finally {
this.isLoading = false; isLoading.value = false;
} }
}, }
userStatus() { const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -182,41 +169,41 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
.catch(() => { .catch(() => {
this.user = null user.value = null
}) })
}, }
getPlanName(planType: number): string { const getPlanName = (planType: number): string => {
const planNames: { [key: number]: string } = { const planNames: { [key: number]: string } = {
1: 'Standard', 1: 'Standard',
2: 'Premium' 2: 'Premium'
}; };
return planNames[planType] || 'Unknown'; return planNames[planType] || 'Unknown';
}, }
getPlanColor(planType: number): string { const getPlanColor = (planType: number): string => {
const planColors: { [key: number]: string } = { const planColors: { [key: number]: string } = {
1: 'blue', 1: 'blue',
2: 'gold' 2: 'gold'
}; };
return planColors[planType] || 'gray'; return planColors[planType] || 'gray';
}, }
formatDate(dateString: string): string { const formatDate = (dateString: string): string => {
if (!dateString) return 'N/A'; if (!dateString) return 'N/A';
return dayjs(dateString).format('MMM D, YYYY'); return dayjs(dateString).format('MMM D, YYYY');
}, }
formatEndDate(startDate: string, years: number): string { const formatEndDate = (startDate: string, years: number): string => {
if (!startDate) return 'N/A'; if (!startDate) return 'N/A';
return dayjs(startDate).add(years, 'year').format('MMM D, YYYY'); return dayjs(startDate).add(years, 'year').format('MMM D, YYYY');
}, }
getStatusText(startDate: string, years: number): string { const getStatusText = (startDate: string, years: number): string => {
if (!startDate) return 'Unknown'; if (!startDate) return 'Unknown';
const endDate = dayjs(startDate).add(years, 'year'); const endDate = dayjs(startDate).add(years, 'year');
const now = dayjs(); const now = dayjs();
@@ -227,9 +214,9 @@ export default defineComponent({
} else { } else {
return 'Active'; return 'Active';
} }
}, }
getStatusBadge(startDate: string, years: number): string { const getStatusBadge = (startDate: string, years: number): string => {
if (!startDate) return 'badge-ghost'; if (!startDate) return 'badge-ghost';
const endDate = dayjs(startDate).add(years, 'year'); const endDate = dayjs(startDate).add(years, 'year');
const now = dayjs(); const now = dayjs();
@@ -240,7 +227,5 @@ export default defineComponent({
} else { } else {
return 'badge-success'; return 'badge-success';
} }
} }
},
})
</script> </script>

View File

@@ -163,86 +163,72 @@
/> />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { ref, onMounted } from 'vue'
import axios from 'axios' import axios from 'axios'
import authHeader from '../../services/auth.header' import authHeader from '../../services/auth.header'
import { ServiceCall } from '../../types/models'
import Footer from '../../layouts/footers/footer.vue' import Footer from '../../layouts/footers/footer.vue'
import ServiceEditModal from './ServiceEditModal.vue' import ServiceEditModal from './ServiceEditModal.vue'
import dayjs from 'dayjs'; import dayjs from 'dayjs';
interface ServiceCall { // Reactive data
id: number; const user = ref(null)
scheduled_date: string; const services = ref<ServiceCall[]>([])
customer_id: number; const isLoading = ref(true)
customer_name: string; const selectedServiceForEdit = ref<ServiceCall | null>(null)
customer_address: string; // --- ADDITIONS FOR TRUNCATION ---
customer_town: string; const wordLimit = ref(50)
type_service_call: number; const expandedIds = ref<number[]>([])
description: string;
service_cost: string; // Lifecycle
payment_status?: number; onMounted(() => {
userStatus();
fetchTodayServices();
})
// Functions
const isLongDescription = (text: string): boolean => {
if (!text) return false;
return text.split(/\s+/).length > wordLimit.value;
} }
export default defineComponent({ const truncateDescription = (text: string): string => {
name: 'ServiceToday', if (!isLongDescription(text)) return text;
components: { Footer, ServiceEditModal },
data() {
return {
user: null,
services: [] as ServiceCall[],
isLoading: true,
selectedServiceForEdit: null as ServiceCall | null,
// --- ADDITIONS FOR TRUNCATION ---
wordLimit: 50,
expandedIds: [] as number[],
}
},
created() {
this.userStatus();
this.fetchTodayServices();
},
methods: {
// --- NEW METHODS FOR TRUNCATION ---
isLongDescription(text: string): boolean {
if (!text) return false;
return text.split(/\s+/).length > this.wordLimit;
},
truncateDescription(text: string): string {
if (!this.isLongDescription(text)) return text;
const words = text.split(/\s+/); const words = text.split(/\s+/);
return words.slice(0, this.wordLimit).join(' ') + '...'; return words.slice(0, wordLimit.value).join(' ') + '...';
}, }
isExpanded(id: number): boolean {
return this.expandedIds.includes(id);
},
toggleExpand(id: number): void {
const index = this.expandedIds.indexOf(id);
if (index === -1) {
this.expandedIds.push(id);
} else {
this.expandedIds.splice(index, 1);
}
},
// --- API and Data Handling Methods --- const isExpanded = (id: number): boolean => {
async fetchTodayServices(): Promise<void> { return expandedIds.value.includes(id);
this.isLoading = true; }
const toggleExpand = (id: number): void => {
const index = expandedIds.value.indexOf(id);
if (index === -1) {
expandedIds.value.push(id);
} else {
expandedIds.value.splice(index, 1);
}
}
const fetchTodayServices = async (): Promise<void> => {
isLoading.value = true;
try { try {
const path = import.meta.env.VITE_BASE_URL + '/service/today'; const path = import.meta.env.VITE_BASE_URL + '/service/today';
const response = await axios.get(path, { const response = await axios.get(path, {
headers: authHeader(), headers: authHeader(),
withCredentials: true, withCredentials: true,
}); });
this.services = response.data.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id); services.value = response.data.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
} catch (error) { } catch (error) {
console.error("Failed to fetch today's service calls:", error); console.error("Failed to fetch today's service calls:", error);
} finally { } finally {
this.isLoading = false; isLoading.value = false;
} }
}, }
userStatus() { const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami'; let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({ axios({
method: 'get', method: 'get',
@@ -252,55 +238,54 @@ export default defineComponent({
}) })
.then((response: any) => { .then((response: any) => {
if (response.data.ok) { if (response.data.ok) {
this.user = response.data.user; user.value = response.data.user;
} }
}) })
.catch(() => { .catch(() => {
this.user = null user.value = null
}) })
}, }
openEditModal(service: ServiceCall) { const openEditModal = (service: ServiceCall) => {
this.selectedServiceForEdit = service; selectedServiceForEdit.value = service;
}, }
closeEditModal() { const closeEditModal = () => {
this.selectedServiceForEdit = null; selectedServiceForEdit.value = null;
}, }
async handleSaveChanges(updatedService: ServiceCall) { const handleSaveChanges = async (updatedService: ServiceCall) => {
try { try {
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`; const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
const response = await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true }); const response = await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
if (response.data.ok) { if (response.data.ok) {
const index = this.services.findIndex(s => s.id === updatedService.id); const index = services.value.findIndex(s => s.id === updatedService.id);
if (index !== -1) { if (index !== -1) {
this.services[index] = response.data.service; services.value[index] = response.data.service;
} }
this.closeEditModal(); closeEditModal();
} }
} catch (error) { } catch (error) {
console.error("Failed to save changes:", error); console.error("Failed to save changes:", error);
alert("An error occurred while saving. Please check the console."); alert("An error occurred while saving. Please check the console.");
} }
}, }
async handleDeleteService(serviceId: number) { const handleDeleteService = async (serviceId: number) => {
try { try {
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`; const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
const response = await axios.delete(path, { headers: authHeader(), withCredentials: true }); const response = await axios.delete(path, { headers: authHeader(), withCredentials: true });
if (response.data.ok) { if (response.data.ok) {
this.services = this.services.filter(s => s.id !== serviceId); services.value = services.value.filter(s => s.id !== serviceId);
this.closeEditModal(); closeEditModal();
} }
} catch (error) { } catch (error) {
console.error("Failed to delete service call:", error); console.error("Failed to delete service call:", error);
alert("An error occurred while deleting. Please check the console."); alert("An error occurred while deleting. Please check the console.");
} }
}, }
// --- Formatting and Display Methods --- const formatCurrency = (value: string | number): string => {
formatCurrency(value: string | number): string {
if (value === null || value === undefined || value === '') return '$0.00'; if (value === null || value === undefined || value === '') return '$0.00';
const numberValue = Number(value); const numberValue = Number(value);
if (isNaN(numberValue)) return '$0.00'; if (isNaN(numberValue)) return '$0.00';
@@ -309,19 +294,19 @@ export default defineComponent({
style: 'currency', style: 'currency',
currency: 'USD', currency: 'USD',
}).format(numberValue); }).format(numberValue);
}, }
formatDate(dateString: string): string { const formatDate = (dateString: string): string => {
if (!dateString) return 'N/A'; if (!dateString) return 'N/A';
return dayjs(dateString).format('MMMM D, YYYY'); return dayjs(dateString).format('MMMM D, YYYY');
}, }
formatTime(dateString: string): string { const formatTime = (dateString: string): string => {
if (!dateString) return 'N/A'; if (!dateString) return 'N/A';
return dayjs(dateString).format('h:mm A'); return dayjs(dateString).format('h:mm A');
}, }
getServiceTypeName(typeId: number): string { const getServiceTypeName = (typeId: number): string => {
const typeMap: { [key: number]: string } = { const typeMap: { [key: number]: string } = {
0: 'Tune-up', 0: 'Tune-up',
1: 'No Heat', 1: 'No Heat',
@@ -330,9 +315,9 @@ export default defineComponent({
4: 'Other', 4: 'Other',
}; };
return typeMap[typeId] || 'Unknown Service'; return typeMap[typeId] || 'Unknown Service';
}, }
getServiceTypeColor(typeId: number): string { const getServiceTypeColor = (typeId: number): string => {
const colorMap: { [key: number]: string } = { const colorMap: { [key: number]: string } = {
0: 'blue', 0: 'blue',
1: 'red', 1: 'red',
@@ -341,15 +326,13 @@ export default defineComponent({
4: 'black', 4: 'black',
}; };
return colorMap[typeId] || 'gray'; return colorMap[typeId] || 'gray';
}, }
shouldShowChargeButton(service: any): boolean { const shouldShowChargeButton = (service: any): boolean => {
return service.payment_status === null || service.payment_status === undefined; return service.payment_status === null || service.payment_status === undefined;
}, }
shouldShowCaptureButton(service: any): boolean { const shouldShowCaptureButton = (service: any): boolean => {
return service.payment_status === 1; return service.payment_status === 1;
} }
},
})
</script> </script>

View File

@@ -55,8 +55,9 @@
/> />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue'; import { ref, onMounted, watch } from 'vue';
import { useRoute } from 'vue-router';
import Header from '../../../layouts/headers/headerauth.vue'; import Header from '../../../layouts/headers/headerauth.vue';
import FullCalendar from '@fullcalendar/vue3'; import FullCalendar from '@fullcalendar/vue3';
import dayGridPlugin from '@fullcalendar/daygrid'; import dayGridPlugin from '@fullcalendar/daygrid';
@@ -70,45 +71,18 @@ import authHeader from '../../../services/auth.header';
interface ServiceCall { id: number; scheduled_date: string; customer_id: number; customer_name: string; customer_address: string; customer_town: string; type_service_call: number; description: string; service_cost: string;} interface ServiceCall { id: number; scheduled_date: string; customer_id: number; customer_name: string; customer_address: string; customer_town: string; type_service_call: number; description: string; service_cost: string;}
interface Customer { id: number; customer_last_name: string; customer_first_name: string; customer_town: string; customer_state: number; customer_zip: string; customer_phone_number: string; customer_address: string; customer_home_type: number; customer_apt: string; } interface Customer { id: number; customer_last_name: string; customer_first_name: string; customer_town: string; customer_state: number; customer_zip: string; customer_phone_number: string; customer_address: string; customer_home_type: number; customer_apt: string; }
export default defineComponent({ // Route
name: 'CalendarCustomer', const route = useRoute();
components: { Header, FullCalendar, EventSidebar, ServiceEditModal },
data() { // Functions declared first (needed for calendarOptions)
return { const handleEventClick = (clickInfo: EventClickArg): void => {
isLoading: false, const events = (calendarOptions.value.events as any[]) || [];
selectedServiceForEdit: null as Partial<ServiceCall> | null,
calendarOptions: {
plugins: [dayGridPlugin, interactionPlugin],
initialView: 'dayGridMonth',
weekends: true,
events: [] as any[],
eventClick: this.handleEventClick,
} as CalendarOptions,
customer: null as Customer | null,
};
},
watch: {
'$route.params.id': {
handler(newId) {
if (newId) this.getCustomer(newId as string);
},
immediate: true,
},
},
created() {
this.fetchEvents();
},
methods: {
// --- THIS IS THE FIX ---
// The logic from ServiceCalendar.vue is now correctly applied here.
handleEventClick(clickInfo: EventClickArg): void {
const events = (this.calendarOptions.events as any[]) || [];
const originalEvent = events.find(e => e.id == clickInfo.event.id); const originalEvent = events.find(e => e.id == clickInfo.event.id);
if (originalEvent) { if (originalEvent) {
// We "flatten" the nested object from the calendar into the simple, // We "flatten" the nested object from the calendar into the simple,
// flat structure that the modal expects, ensuring customer_id is included. // flat structure that the modal expects, ensuring customer_id is included.
this.selectedServiceForEdit = { selectedServiceForEdit.value = {
id: originalEvent.id, id: originalEvent.id,
scheduled_date: originalEvent.start, scheduled_date: originalEvent.start,
customer_id: originalEvent.customer_id, // This was the missing piece customer_id: originalEvent.customer_id, // This was the missing piece
@@ -120,91 +94,113 @@ export default defineComponent({
customer_town: '', customer_town: '',
}; };
} }
}, };
closeEditModal() { // Reactive data
this.selectedServiceForEdit = null; const isLoading = ref(false);
}, const selectedServiceForEdit = ref<Partial<ServiceCall> | null>(null);
const calendarOptions = ref<CalendarOptions>({
plugins: [dayGridPlugin, interactionPlugin],
initialView: 'dayGridMonth',
weekends: true,
events: [] as any[],
eventClick: handleEventClick,
});
const customer = ref<Customer | null>(null);
async handleSaveChanges(updatedService: ServiceCall) { // Watchers
watch(() => route.params.id, (newId) => {
if (newId) getCustomer(newId as string);
}, { immediate: true });
// Lifecycle
onMounted(() => {
fetchEvents();
});
// Functions
const closeEditModal = () => {
selectedServiceForEdit.value = null;
};
const handleSaveChanges = async (updatedService: ServiceCall) => {
try { try {
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`; const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true }); await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
await this.fetchEvents(); await fetchEvents();
this.closeEditModal(); closeEditModal();
} catch (error) { } catch (error) {
console.error("Failed to save changes:", error); console.error("Failed to save changes:", error);
alert("An error occurred while saving. Please check the console."); alert("An error occurred while saving. Please check the console.");
} }
}, };
async handleDeleteService(serviceId: number) { const handleDeleteService = async (serviceId: number) => {
try { try {
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`; const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
const response = await axios.delete(path, { withCredentials: true, headers: authHeader() }); const response = await axios.delete(path, { withCredentials: true, headers: authHeader() });
if (response.data.ok === true) { if (response.data.ok === true) {
await this.fetchEvents(); await fetchEvents();
this.closeEditModal(); closeEditModal();
} else { } else {
console.error("Failed to delete event:", response.data.error); console.error("Failed to delete event:", response.data.error);
} }
} catch (error) { } catch (error) {
console.error("Error deleting event:", error); console.error("Error deleting event:", error);
} }
}, };
async getCustomer(customerId: string): Promise<void> { const getCustomer = async (customerId: string): Promise<void> => {
this.isLoading = true; isLoading.value = true;
this.customer = null; customer.value = null;
try { try {
const path = `${import.meta.env.VITE_BASE_URL}/customer/${customerId}`; const path = `${import.meta.env.VITE_BASE_URL}/customer/${customerId}`;
const response = await axios.get(path, { withCredentials: true, headers: authHeader() }); const response = await axios.get(path, { withCredentials: true, headers: authHeader() });
if (response.data && response.data.id) { if (response.data && response.data.id) {
this.customer = response.data; customer.value = response.data;
} }
} catch (error) { } catch (error) {
console.error("API call to get customer FAILED:", error); console.error("API call to get customer FAILED:", error);
} finally { } finally {
this.isLoading = false; isLoading.value = false;
} }
}, };
async fetchEvents(): Promise<void> { const fetchEvents = async (): Promise<void> => {
try { try {
const path = `${import.meta.env.VITE_BASE_URL}/service/all`; const path = `${import.meta.env.VITE_BASE_URL}/service/all`;
const response = await axios.get(path, { headers: authHeader(), withCredentials: true }); const response = await axios.get(path, { headers: authHeader(), withCredentials: true });
this.calendarOptions.events = response.data; calendarOptions.value.events = response.data;
} catch (error) { } catch (error) {
console.error("Error fetching all calendar events:", error); console.error("Error fetching all calendar events:", error);
} }
}, };
async handleEventScheduled(eventData: any): Promise<void> { const handleEventScheduled = async (eventData: any): Promise<void> => {
if (!this.customer) { if (!customer.value) {
alert("Error: A customer must be loaded in the sidebar to create a new event."); alert("Error: A customer must be loaded in the sidebar to create a new event.");
return; return;
} }
try { try {
const payload = { const payload = {
expected_delivery_date: eventData.start, type_service_call: eventData.type_service_call, expected_delivery_date: eventData.start, type_service_call: eventData.type_service_call,
customer_id: this.customer.id, description: eventData.extendedProps.description, customer_id: customer.value.id, description: eventData.extendedProps.description,
}; };
const path = import.meta.env.VITE_BASE_URL + "/service/create"; const path = import.meta.env.VITE_BASE_URL + "/service/create";
const response = await axios.post(path, payload, { withCredentials: true, headers: authHeader() }); const response = await axios.post(path, payload, { withCredentials: true, headers: authHeader() });
if (response.data.ok === true) { if (response.data.ok === true) {
await this.fetchEvents(); await fetchEvents();
} else { } else {
console.error("Failed to create event:", response.data.error); console.error("Failed to create event:", response.data.error);
} }
} catch (error) { } catch (error) {
console.error("Error creating event:", error); console.error("Error creating event:", error);
} }
}, };
async handleEventDelete(eventId: string): Promise<void> { const handleEventDelete = async (eventId: string): Promise<void> => {
// This is a simple alias now, as handleDeleteService is more specific // This is a simple alias now, as handleDeleteService is more specific
await this.handleDeleteService(Number(eventId)); await handleDeleteService(Number(eventId));
}, };
},
});
</script> </script>

View File

@@ -77,8 +77,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, PropType } from 'vue'; import { ref, computed } from 'vue';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
interface Customer { interface Customer {
@@ -94,80 +94,79 @@ interface Customer {
customer_apt: string; customer_apt: string;
} }
export default defineComponent({ // Props
name: 'EventSidebar', const props = defineProps<{
props: { customer: Customer | null;
customer: { }>();
type: Object as PropType<Customer | null>,
required: true, // Emits
}, const emit = defineEmits<{
}, 'event-scheduled': [eventData: any];
data() { }>();
return {
selectedService: '' as string | number, // Reactive data
serviceOptions: [ const selectedService = ref<string | number>('');
const serviceOptions = ref([
{ text: 'Tune-up', value: 0 }, { text: 'Tune-up', value: 0 },
{ text: 'No Heat', value: 1 }, { text: 'No Heat', value: 1 },
{ text: 'Fix', value: 2 }, { text: 'Fix', value: 2 },
{ text: 'Tank Install', value: 3 }, { text: 'Tank Install', value: 3 },
{ text: 'Other', value: 4 }, { text: 'Other', value: 4 },
], ]);
event: { const event = ref({
title: '', title: '',
description: '', description: '',
date: dayjs().format('YYYY-MM-DD'), date: dayjs().format('YYYY-MM-DD'),
endDate: '', endDate: '',
time: 12, time: 12,
}, });
};
}, // Computed properties
computed: { const customerStateName = computed((): string => {
customerStateName(): string { if (!props.customer) return '';
if (!this.customer) return '';
const stateMap: { [key: number]: string } = { const stateMap: { [key: number]: string } = {
0: 'Massachusetts', 1: 'Rhode Island', 2: 'New Hampshire', 0: 'Massachusetts', 1: 'Rhode Island', 2: 'New Hampshire',
3: 'Maine', 4: 'Vermont', 5: 'Connecticut', 6: 'New York', 3: 'Maine', 4: 'Vermont', 5: 'Connecticut', 6: 'New York',
}; };
return stateMap[this.customer.customer_state] || 'Unknown'; return stateMap[props.customer.customer_state] || 'Unknown';
}, });
customerHomeType(): string {
if (!this.customer) return ''; const customerHomeType = computed((): string => {
if (!props.customer) return '';
const homeTypeMap: { [key: number]: string } = { const homeTypeMap: { [key: number]: string } = {
0: 'Residential', 1: 'Apartment', 2: 'Condo', 3: 'Commercial', 0: 'Residential', 1: 'Apartment', 2: 'Condo', 3: 'Commercial',
4: 'Business', 5: 'Construction', 6: 'Container', 4: 'Business', 5: 'Construction', 6: 'Container',
}; };
return homeTypeMap[this.customer.customer_home_type] || 'Unknown'; return homeTypeMap[props.customer.customer_home_type] || 'Unknown';
} });
},
methods: { // Functions
submitEvent() { const submitEvent = () => {
if (!this.customer) { if (!props.customer) {
alert("Cannot submit: No customer data is loaded."); alert("Cannot submit: No customer data is loaded.");
return; return;
} }
const startDateTime = dayjs(`${this.event.date} ${this.event.time}:00`).format('YYYY-MM-DDTHH:mm:ss'); const startDateTime = dayjs(`${event.value.date} ${event.value.time}:00`).format('YYYY-MM-DDTHH:mm:ss');
const endDateTime = this.event.endDate ? dayjs(this.event.endDate).add(1, 'day').format('YYYY-MM-DD') : undefined; const endDateTime = event.value.endDate ? dayjs(event.value.endDate).add(1, 'day').format('YYYY-MM-DD') : undefined;
const eventPayload = { const eventPayload = {
title: this.event.title, title: event.value.title,
start: startDateTime, start: startDateTime,
type_service_call: this.selectedService, type_service_call: selectedService.value,
end: endDateTime, end: endDateTime,
extendedProps: { extendedProps: {
description: this.event.description, description: event.value.description,
}, },
}; };
this.$emit('event-scheduled', eventPayload); emit('event-scheduled', eventPayload);
this.event.title = ''; event.value.title = '';
this.selectedService = ''; selectedService.value = '';
this.event.description = ''; event.value.description = '';
this.event.endDate = ''; event.value.endDate = '';
this.event.date = dayjs().format('YYYY-MM-DD'); event.value.date = dayjs().format('YYYY-MM-DD');
this.event.time = 12; event.value.time = 12;
}, };
},
});
</script> </script>

View File

@@ -1,10 +1,10 @@
// Import the new component at the top // Import the new component at the top
import ServiceHome from './ServiceHome.vue' const ServiceHome = () => import('./ServiceHome.vue')
import ServicePast from './ServicePast.vue' const ServicePast = () => import('./ServicePast.vue')
import CalendarCustomer from './calender/CalendarCustomer.vue' const CalendarCustomer = () => import('./calender/CalendarCustomer.vue')
import ServiceCalendar from './ServiceCalendar.vue' const ServiceCalendar = () => import('./ServiceCalendar.vue')
import ServiceToday from './ServiceToday.vue' const ServiceToday = () => import('./ServiceToday.vue')
import ServicePlans from './ServicePlans.vue' const ServicePlans = () => import('./ServicePlans.vue')
const serviceRoutes = [ const serviceRoutes = [
{ {

View File

@@ -1,8 +1,8 @@
import Ticket from "../ticket/ticket.vue"; const Ticket = () => import("../ticket/ticket.vue");
import TicketAuto from "../ticket/ticketauto.vue"; const TicketAuto = () => import("../ticket/ticketauto.vue");
const ticketRoutes = [ const ticketRoutes = [

View File

@@ -170,6 +170,7 @@ import authHeader from '../../../services/auth.header'
import Header from '../../../layouts/headers/headerauth.vue' import Header from '../../../layouts/headers/headerauth.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue' import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue' import Footer from '../../../layouts/footers/footer.vue'
import {AuthorizeTransaction} from '../../../types/models'
export default defineComponent({ export default defineComponent({
name: 'transactionsAuthorize', name: 'transactionsAuthorize',
@@ -182,7 +183,7 @@ export default defineComponent({
data() { data() {
return { return {
transactions: [] as any[], transactions: [] as AuthorizeTransaction[],
} }
}, },
@@ -208,7 +209,7 @@ export default defineComponent({
getStatusText(status: number) { getStatusText(status: number) {
return status === 0 ? 'Approved' : 'Declined' return status === 0 ? 'Approved' : 'Declined'
}, },
getSourceText(transaction: any) { getSourceText(transaction: AuthorizeTransaction) {
if (transaction.auto_id) { if (transaction.auto_id) {
return 'Automatic' return 'Automatic'
} else if (transaction.delivery_id) { } else if (transaction.delivery_id) {
@@ -222,7 +223,7 @@ export default defineComponent({
formatDate(dateStr: string) { formatDate(dateStr: string) {
return dateStr.split('T')[0]; // YYYY-MM-DD return dateStr.split('T')[0]; // YYYY-MM-DD
}, },
getCaptureRoute(transaction: any) { getCaptureRoute(transaction: AuthorizeTransaction) {
if (transaction.service_id) { if (transaction.service_id) {
return { name: 'chargeServiceAuthorize', params: { id: transaction.service_id } }; return { name: 'chargeServiceAuthorize', params: { id: transaction.service_id } };
} else if (transaction.delivery_id) { } else if (transaction.delivery_id) {
@@ -230,7 +231,7 @@ export default defineComponent({
} }
return {}; // fallback, though condition should prevent this return {}; // fallback, though condition should prevent this
}, },
getPreauthRoute(transaction: any) { getPreauthRoute(transaction: AuthorizeTransaction) {
if (transaction.service_id) { if (transaction.service_id) {
return { name: 'chargeServiceAuthorize', params: { id: transaction.service_id } }; return { name: 'chargeServiceAuthorize', params: { id: transaction.service_id } };
} else if (transaction.delivery_id) { } else if (transaction.delivery_id) {

View File

@@ -1,4 +1,4 @@
import AuthorizePage from './authorize/index.vue'; const AuthorizePage = () => import('./authorize/index.vue');
const transactionsRoutes = [ const transactionsRoutes = [
{ {

View File

@@ -0,0 +1,139 @@
import api from './api';
export const adminService = {
// Oil pricing
getOilPricing: () =>
api.get('/admin/oil/get'),
updateOilPricing: (data: any) =>
api.post('/admin/oil/create', data),
// VoIP
getVoipRouting: () =>
api.get('/admin/voip_routing'),
// Employee management
employees: {
create: (data: any) =>
api.post('/employee/create', data),
getById: (id: number) =>
api.get(`/employee/${id}`),
getByIdAlt: (id: number) =>
api.get(`/employee/byid/${id}`),
getByUserId: (id: number) =>
api.get(`/employee/userid/${id}`),
update: (id: number, data: any) =>
api.put(`/employee/edit/${id}`, data),
getAll: (page: number = 1) =>
api.get(`/employee/all/${page}`),
getDrivers: () =>
api.get('/employee/drivers'),
},
// Promotions
promos: {
getAll: () =>
api.get('/promo/all'),
getById: (id: number) =>
api.get(`/promo/${id}`),
getPrice: (id: number) =>
api.get(`/promo/promoprice/${id}`),
create: (data: any) =>
api.post('/promo/create', data),
update: (id: number, data: any) =>
api.put(`/promo/edit/${id}`, data),
delete: (id: number) =>
api.delete(`/promo/delete/${id}`),
enable: (id: number) =>
api.put(`/promo/on/${id}`),
disable: (id: number) =>
api.put(`/promo/off/${id}`),
},
// Statistics
stats: {
deliveryCountToday: () =>
api.get('/stats/delivery/count/today'),
deliveredCountToday: () =>
api.get('/stats/delivery/count/delivered/today'),
employeeDeliveryTotal: (employeeId: number) =>
api.get(`/stats/delivery/total/${employeeId}`),
employeeGallonsTotal: (employeeId: number) =>
api.get(`/stats/gallons/total/${employeeId}`),
weeklyGallons: () =>
api.get('/stats/gallons/week'),
customerGallonsTotal: (customerId: number) =>
api.get(`/stats/gallons/check/total/${customerId}`),
employeePrimesTotal: (employeeId: number) =>
api.get(`/stats/primes/total/${employeeId}`),
userLastDelivery: (userId: number) =>
api.get(`/stats/user/lastdelivery/${userId}`),
userStats: (userId: number) =>
api.get(`/stats/user/${userId}`),
serviceCallsToday: () =>
api.get('/stats/call/count/today'),
sidebarCounts: () =>
api.get('/deliverystatus/stats/sidebar-counts'),
todayTotals: () =>
api.get('/deliverystatus/today-totals'),
tomorrowTotals: () =>
api.get('/deliverystatus/tomorrow-totals'),
waitingTotals: () =>
api.get('/deliverystatus/waiting-totals'),
pendingStatus: () =>
api.get('/deliverystatus/pending'),
},
// Money & reporting
money: {
weeklyProfit: () =>
api.get('/money/profit/week'),
yearlyProfit: () =>
api.get('/money/profit/year'),
customerListReport: () =>
api.get('/report/customers/list'),
},
// Social/comments
social: {
getPosts: (customerId: number, page: number = 1) =>
api.get(`/social/posts/${customerId}/${page}`),
createPost: (customerId: number, data: any) =>
api.post(`/social/create/${customerId}`, data),
deletePost: (postId: number) =>
api.delete(`/social/delete/${postId}`),
},
};
export default adminService;

51
src/services/api.ts Normal file
View File

@@ -0,0 +1,51 @@
import axios from 'axios';
// Main Flask API
const api = axios.create({
baseURL: import.meta.env.VITE_BASE_URL,
withCredentials: true,
});
// Authorize.net FastAPI
const authorizeApi = axios.create({
baseURL: import.meta.env.VITE_AUTHORIZE_URL,
withCredentials: true,
});
// Automatic Delivery System
const autoApi = axios.create({
baseURL: import.meta.env.VITE_AUTO_URL,
withCredentials: true,
});
// Request interceptor - add auth token
function addAuthHeader(config: { headers: { Authorization?: string } }) {
const token = localStorage.getItem('auth_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
api.interceptors.request.use(addAuthHeader as any);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
authorizeApi.interceptors.request.use(addAuthHeader as any);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
autoApi.interceptors.request.use(addAuthHeader as any);
// Response error handler - handle 401 errors
function handleResponseError(error: unknown) {
const err = error as { response?: { status?: number } };
if (err?.response?.status === 401) {
localStorage.removeItem('auth_token');
}
return Promise.reject(error);
}
api.interceptors.response.use(undefined, handleResponseError);
authorizeApi.interceptors.response.use(undefined, handleResponseError);
autoApi.interceptors.response.use(undefined, handleResponseError);
export { api, authorizeApi, autoApi };
export default api;

View File

@@ -0,0 +1,40 @@
import api, { authorizeApi } from './api';
export const authService = {
// Authentication
login: (data: { username: string; password: string }) =>
api.post('/auth/login', data),
register: (data: any) =>
api.post('/auth/register', data),
logout: () =>
api.post('/auth/logout'),
whoami: () =>
api.get('/auth/whoami'),
// Password management
changePassword: (data: { old_password: string; new_password: string }) =>
api.put('/auth/change-password', data),
adminChangePassword: (data: { user_id: number; new_password: string }) =>
api.put('/auth/admin-change-password', data),
unlockAccount: (data: any) =>
api.post('/auth/unlock-account', data),
// Authorize.net account management
authorize: {
checkAccount: (customerId: number) =>
authorizeApi.get(`/user/check-authorize-account/${customerId}`),
createAccount: (customerId: number, data?: any) =>
authorizeApi.post(`/user/create-account/${customerId}`, data),
deleteAccount: (customerId: number) =>
authorizeApi.delete(`/user/delete-account/${customerId}`),
},
};
export default authService;

View File

@@ -0,0 +1,56 @@
import api from './api';
import {
Customer,
CustomerDescription,
TankInspection,
CustomerStats,
CreateCustomerRequest,
UpdateCustomerRequest,
CustomerListResponse,
ApiResponse
} from '../types/models';
export const customerService = {
// CRUD operations
getAll: (page: number = 1): Promise<CustomerListResponse> =>
api.get(`/customer/all/${page}`),
getById: (id: number): Promise<ApiResponse<Customer>> =>
api.get(`/customer/${id}`),
create: (data: CreateCustomerRequest): Promise<ApiResponse<Customer>> =>
api.post('/customer/create', data),
update: (id: number, data: UpdateCustomerRequest): Promise<ApiResponse<Customer>> =>
api.put(`/customer/edit/${id}`, data),
delete: (id: number): Promise<ApiResponse<void>> =>
api.delete(`/customer/delete/${id}`),
getCount: (): Promise<ApiResponse<{ count: number }>> =>
api.get('/customer/count'),
// Profile & details
getDescription: (id: number): Promise<ApiResponse<CustomerDescription>> =>
api.get(`/customer/description/${id}`),
// Tank information
getTank: (id: number): Promise<ApiResponse<TankInspection>> =>
api.get(`/customer/tank/${id}`),
updateTank: (id: number, data: Partial<TankInspection>): Promise<ApiResponse<void>> =>
api.put(`/customer/edit/tank/${id}`, data),
// Automatic delivery
getAutomaticStatus: (id: number): Promise<ApiResponse<{ status: number }>> =>
api.get(`/customer/automatic/status/${id}`),
assignAutomatic: (id: number, data: { status: number }): Promise<ApiResponse<{ status: number }>> =>
api.put(`/customer/automatic/assign/${id}`, data),
// Search
search: (query: string): Promise<ApiResponse<Customer[]>> =>
api.get(`/search/customer?q=${encodeURIComponent(query)}`),
};
export default customerService;

View File

@@ -0,0 +1,128 @@
import api, { autoApi } from './api';
import {
Delivery,
DeliveryNote,
AutoDelivery,
DeliveryListResponse,
CreateDeliveryRequest,
UpdateDeliveryRequest,
ApiResponse
} from '../types/models';
export const deliveryService = {
// CRUD operations
create: (customerId: number, data: CreateDeliveryRequest): Promise<ApiResponse<Delivery>> =>
api.post(`/delivery/create/${customerId}`, data),
getById: (id: number): Promise<ApiResponse<Delivery>> =>
api.get(`/delivery/${id}`),
getOrder: (id: number): Promise<ApiResponse<Delivery>> =>
api.get(`/delivery/order/${id}`),
update: (id: number, data: UpdateDeliveryRequest): Promise<ApiResponse<Delivery>> =>
api.put(`/delivery/edit/${id}`, data),
delete: (id: number): Promise<ApiResponse<void>> =>
api.delete(`/delivery/delete/${id}`),
cancel: (id: number): Promise<ApiResponse<void>> =>
api.put(`/delivery/cancel/${id}`),
markCancelled: (id: number): Promise<ApiResponse<void>> =>
api.put(`/delivery/cancelled/${id}`),
// List operations
getAll: (page: number = 1): Promise<DeliveryListResponse> =>
api.get(`/delivery/all/${page}`),
getByCustomer: (customerId: number, page: number = 1): Promise<DeliveryListResponse> =>
api.get(`/delivery/customer/${customerId}/${page}`),
getPast1: (customerId: number): Promise<ApiResponse<Delivery[]>> =>
api.get(`/delivery/past1/${customerId}`),
getPast2: (customerId: number): Promise<ApiResponse<Delivery[]>> =>
api.get(`/delivery/past2/${customerId}`),
// Status-based lists
getWaiting: (page: number = 1): Promise<DeliveryListResponse> =>
api.get(`/delivery/waiting/${page}`),
getTomorrow: (page: number = 1): Promise<DeliveryListResponse> =>
api.get(`/delivery/tommorrow/${page}`),
getOutForDelivery: (page: number = 1): Promise<DeliveryListResponse> =>
api.get(`/delivery/outfordelivery/${page}`),
getDelivered: (page: number = 1): Promise<DeliveryListResponse> =>
api.get(`/delivery/delivered/${page}`),
getFinalized: (page: number = 1): Promise<DeliveryListResponse> =>
api.get(`/delivery/finalized/${page}`),
getPending: (page: number = 1): Promise<DeliveryListResponse> =>
api.get(`/delivery/pending/${page}`),
getIssues: (page: number = 1): Promise<DeliveryListResponse> =>
api.get(`/delivery/issue/${page}`),
// Status & totals
updateStatus: (data: { id: number; status: number }): Promise<ApiResponse<void>> =>
api.put('/delivery/updatestatus', data),
getTotal: (id: number): Promise<ApiResponse<{ total: number }>> =>
api.get(`/delivery/total/${id}`),
// Cash handling
handleCash: (id: number, type: string): Promise<ApiResponse<{ amount: number }>> =>
api.get(`/delivery/cash/${id}/${type}`),
// Finalize
finalize: (id: number, data?: { final_price: number }): Promise<ApiResponse<void>> =>
api.put(`/deliverydata/finalize/${id}`, data),
// Auto system endpoints (VITE_AUTO_URL)
auto: {
getDelivery: (id: number) =>
autoApi.get(`/delivery/delivery/${id}`),
getProfileDeliveries: (id: number) =>
autoApi.get(`/delivery/all/profile/${id}`),
getAllCustomers: () =>
autoApi.get('/delivery/all/customers'),
getByCustomer: (id: number) =>
autoApi.get(`/delivery/auto/customer/${id}`),
getTicket: (id: number) =>
autoApi.get(`/delivery/autoticket/${id}`),
findDelivery: (id: number) =>
autoApi.get(`/delivery/finddelivery/${id}`),
updateStatus: (id: number, data: any) =>
autoApi.put(`/delivery/update_status/${id}`, data),
confirm: (data: any) =>
autoApi.post('/confirm/delivery', data),
createTicket: (id: number, data: any) =>
autoApi.post(`/confirm/auto/create/${id}`, data),
createTicketNoPreauth: (id: number, data: any) =>
autoApi.post(`/confirm/auto/create/nopreauth/${id}`, data),
closeTicket: (id: number, data?: any) =>
autoApi.put(`/confirm/auto/close_ticket/${id}`, data),
updateTicket: (id: number, data: any) =>
autoApi.put(`/confirm/auto/update/${id}`, data),
estimateGallons: (customerId: number) =>
autoApi.get(`/fixstuff_customer/estimate_gallons/customer/${customerId}`),
},
};
export default deliveryService;

View File

@@ -0,0 +1,111 @@
import api, { authorizeApi } from './api';
import {
PaymentTransaction,
CreditCard,
AuthorizeTransaction,
TransactionListResponse,
CardListResponse,
CreateCardRequest,
PaymentRequest,
ApiResponse,
TokenizeCardRequest,
UpdateTokenizedCardRequest,
ChargeSavedCardRequest,
ChargeDirectRequest,
CaptureRequest,
PreauthorizeSavedCardRequest,
AuthorizeNetTransactionResponse
} from '../types/models';
export const paymentService = {
// Card management (Main API)
getCard: (id: number): Promise<ApiResponse<CreditCard>> =>
api.get(`/payment/card/${id}`),
getCards: (customerId: number): Promise<ApiResponse<CreditCard[]>> =>
api.get(`/payment/cards/${customerId}`),
getCardsOnFile: (customerId: number): Promise<ApiResponse<CreditCard[]>> =>
api.get(`/payment/cards/onfile/${customerId}`),
getAllCards: (page: number = 1): Promise<CardListResponse> =>
api.get(`/payment/cards/all/${page}`),
createCard: (customerId: number, data: CreateCardRequest): Promise<ApiResponse<CreditCard>> =>
api.post(`/payment/card/create/${customerId}`, data),
updateCard: (id: number, data: Partial<CreditCard>): Promise<ApiResponse<CreditCard>> =>
api.put(`/payment/card/edit/${id}`, data),
removeCard: (id: number): Promise<ApiResponse<void>> =>
api.delete(`/payment/card/remove/${id}`),
removeCardAlt: (id: number): Promise<ApiResponse<void>> =>
api.delete(`/payment/cards/remove/${id}`),
updatePaymentProfile: (id: number, data: { profile_id: string }): Promise<ApiResponse<void>> =>
api.put(`/payment/card/update_payment_profile/${id}`, data),
// Authorization & capture (Main API)
authorizeDelivery: (id: number, data: PaymentRequest): Promise<ApiResponse<PaymentTransaction>> =>
api.put(`/payment/authorize/${id}`, data),
authorizeService: (id: number, data: PaymentRequest): Promise<ApiResponse<PaymentTransaction>> =>
api.put(`/payment/authorize/service/${id}`, data),
cleanupAuthorization: (id: number): Promise<ApiResponse<void>> =>
api.put(`/payment/authorize/cleanup/${id}`),
captureServicePayment: (id: number, data: { amount: number }): Promise<ApiResponse<PaymentTransaction>> =>
api.put(`/payment/capture/service/${id}`, data),
// Service payment
getServicePayment: (id: number, type: string): Promise<ApiResponse<{ amount: number }>> =>
api.get(`/payment/service/payment/${id}/${type}`),
// Transactions
getDeliveryTransaction: (id: number): Promise<ApiResponse<PaymentTransaction>> =>
api.get(`/payment/transaction/delivery/${id}`),
getAuthorizeTransactions: (): Promise<ApiResponse<AuthorizeTransaction[]>> =>
api.get('/payment/transactions/authorize/1'),
getCustomerTransactions: (customerId: number, page: number = 1): Promise<TransactionListResponse> =>
api.get(`/payment/transactions/customer/${customerId}/${page}`),
getServiceTransactions: (serviceId: number): Promise<ApiResponse<PaymentTransaction[]>> =>
api.get(`/payment/transactions/service/${serviceId}`),
// Authorize.net endpoints
authorize: {
tokenizeCard: (customerId: number, data: TokenizeCardRequest): Promise<ApiResponse<CreditCard>> =>
authorizeApi.post(`/api/payments/customers/${customerId}/cards`, data),
updateTokenizedCard: (customerId: number, cardId: number, data: UpdateTokenizedCardRequest): Promise<ApiResponse<CreditCard>> =>
authorizeApi.put(`/api/payments/customers/${customerId}/cards/${cardId}`, data),
authorizeSavedCard: (customerId: number, data: PreauthorizeSavedCardRequest): Promise<ApiResponse<AuthorizeNetTransactionResponse>> =>
authorizeApi.post(`/api/payments/authorize/saved-card/${customerId}`, data),
chargeSavedCard: (customerId: number, data: ChargeSavedCardRequest): Promise<ApiResponse<AuthorizeNetTransactionResponse>> =>
authorizeApi.post(`/api/payments/charge/saved-card/${customerId}`, data),
charge: (customerId: number, data: ChargeDirectRequest): Promise<ApiResponse<AuthorizeNetTransactionResponse>> =>
authorizeApi.post(`/api/charge/${customerId}`, data),
capture: (data: CaptureRequest): Promise<ApiResponse<AuthorizeNetTransactionResponse>> =>
authorizeApi.post('/api/capture/', data),
// Auto transaction endpoints
getAutoTransaction: (deliveryId: number): Promise<ApiResponse<AuthorizeNetTransactionResponse>> =>
authorizeApi.get(`/api/auto/transaction/delivery/${deliveryId}`),
updateAutoTransactionId: (deliveryId: number, newId: number, data?: Record<string, unknown>): Promise<ApiResponse<void>> =>
authorizeApi.put(`/api/auto/transaction/delivery/${deliveryId}/update/${newId}`, data ?? {}),
linkTransactionToAuto: (transactionId: number, autoId: number, data?: Record<string, unknown>): Promise<ApiResponse<void>> =>
authorizeApi.put(`/api/transaction/${transactionId}/update_auto_id/${autoId}`, data ?? {}),
},
};
export default paymentService;

View File

@@ -0,0 +1,31 @@
import api from './api';
export const queryService = {
// Customer types
getCustomerTypes: () =>
api.get('/query/customertype'),
// States
getStates: () =>
api.get('/query/states'),
// Employee types
getEmployeeTypes: () =>
api.get('/query/employeetype'),
// Delivery statuses
getDeliveryStatuses: () =>
api.get('/query/deliverystatus'),
// Oil pricing info
getOilPrice: () =>
api.get('/info/price/oil'),
getOilPriceTable: () =>
api.get('/info/price/oil/table'),
getOilPriceTiers: () =>
api.get('/info/price/oil/tiers'),
};
export default queryService;

View File

@@ -0,0 +1,63 @@
import api from './api';
export const serviceService = {
// CRUD operations
create: (data: any) =>
api.post('/service/create', data),
getById: (id: number) =>
api.get(`/service/${id}`),
update: (id: number, data: any) =>
api.put(`/service/update/${id}`, data),
delete: (id: number) =>
api.delete(`/service/delete/${id}`),
// List operations
getAll: () =>
api.get('/service/all'),
getToday: () =>
api.get('/service/today'),
getUpcoming: () =>
api.get('/service/upcoming'),
getPast: () =>
api.get('/service/past'),
getForCustomer: (customerId: number) =>
api.get(`/service/for-customer/${customerId}`),
// Cost management
updateCost: (id: number, data: any) =>
api.put(`/service/update-cost/${id}`, data),
// Parts
getPartsForCustomer: (customerId: number) =>
api.get(`/service/parts/customer/${customerId}`),
updateParts: (id: number, data: any) =>
api.put(`/service/parts/update/${id}`, data),
// Service plans
plans: {
getActive: () =>
api.get('/service/plans/active'),
getForCustomer: (customerId: number) =>
api.get(`/service/plans/customer/${customerId}`),
create: (data: any) =>
api.post('/service/plans/create', data),
update: (id: number, data: any) =>
api.put(`/service/plans/update/${id}`, data),
delete: (id: number) =>
api.delete(`/service/plans/delete/${id}`),
},
};
export default serviceService;

View File

@@ -3,7 +3,7 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import axios from 'axios' import axios from 'axios'
import authHeader from '../services/auth.header' // Adjust path if needed import { authService } from '../services/authService'
interface User { interface User {
user_name: string; user_name: string;
@@ -60,9 +60,8 @@ export const useAuthStore = defineStore('auth', () => {
return false // No token, definitely not authenticated return false // No token, definitely not authenticated
} }
try { try {
// Use your existing endpoint to verify the token // Use the centralized auth service
const path = `${import.meta.env.VITE_BASE_URL}/auth/whoami`; const response = await authService.whoami();
const response = await axios.get(path, { headers: authHeader() });
if (response.data && response.data.ok) { if (response.data && response.data.ok) {
user.value = response.data.user user.value = response.data.user
return true return true

View File

@@ -2,8 +2,7 @@
import { ref } from 'vue' import { ref } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import axios from 'axios' import { adminService } from '../services/adminService'
import authHeader from '../services/auth.header' // Adjust path if needed
export const useCountsStore = defineStore('counts', () => { export const useCountsStore = defineStore('counts', () => {
// --- STATE --- // --- STATE ---
@@ -21,8 +20,7 @@ export const useCountsStore = defineStore('counts', () => {
// A single action to fetch ALL counts from our new, efficient endpoint. // A single action to fetch ALL counts from our new, efficient endpoint.
async function fetchSidebarCounts() { async function fetchSidebarCounts() {
try { try {
const path = `${import.meta.env.VITE_BASE_URL}/deliverystatus/stats/sidebar-counts`; const response = await adminService.stats.sidebarCounts();
const response = await axios.get(path, { headers: authHeader() });
if (response.data && response.data.ok) { if (response.data && response.data.ok) {
const counts = response.data.counts; const counts = response.data.counts;

View File

@@ -2,7 +2,7 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import axios from 'axios' import { customerService } from '../services/customerService'
// Define a type for what a search result looks like. // Define a type for what a search result looks like.
interface CustomerSearchResult { interface CustomerSearchResult {
@@ -38,9 +38,7 @@ export const useSearchStore = defineStore('search', () => {
isLoading.value = true; isLoading.value = true;
try { try {
// NOTE: Make sure this URL is correct. You may need to add your VITE_BASE_URL const response = await customerService.search(searchTerm.value);
const path = `${import.meta.env.VITE_BASE_URL}/search/customer?q=${searchTerm.value}`;
const response = await axios.get(path);
searchResults.value = response.data; searchResults.value = response.data;
} catch { // No `error` parameter as requested } catch { // No `error` parameter as requested
searchResults.value = []; searchResults.value = [];

725
src/types/models.ts Normal file
View File

@@ -0,0 +1,725 @@
/**
* EAMCO Office Frontend API Response Models
*
* This file contains TypeScript interfaces for all API responses
* to replace 'any' types throughout the application.
*/
import {
DeliveryStatusType,
PaymentStatusType,
AutoStatusType,
TransactionStatusType,
CustomerAutomaticStatus
} from '../constants/status';
// Base interfaces
export interface BaseEntity {
id: number;
}
export interface ApiResponse<T> {
ok: boolean;
data?: T;
error?: string;
}
export interface PaginatedResponse<T> {
data: T[];
total: number;
page: number;
per_page: number;
}
// Customer interfaces
export interface Customer extends BaseEntity {
auth_net_profile_id?: string;
account_number: string;
customer_first_name: string;
customer_last_name: string;
customer_town: string;
customer_state: number;
customer_zip: string;
customer_first_call?: string;
customer_email?: string;
customer_automatic: CustomerAutomaticStatus;
customer_phone_number?: string;
customer_home_type: number;
customer_apt?: string;
customer_address: string;
company_id: number;
customer_latitude?: string;
customer_longitude?: string;
correct_address: boolean;
}
export interface CustomerDescription extends BaseEntity {
customer_id: number;
account_number: string;
company_id: number;
fill_location?: number;
description?: string;
}
export interface TankInspection extends BaseEntity {
customer_id: number;
last_tank_inspection?: string;
tank_status: boolean;
outside_or_inside: boolean;
tank_size: number;
tank_images?: number;
tank_image_upload_dates?: string[];
}
export interface CustomerStats extends BaseEntity {
customer_id: number;
total_calls: number;
service_calls_total: number;
service_calls_total_spent: number;
service_calls_total_profit: number;
oil_deliveries: number;
oil_total_gallons: number;
oil_total_spent: number;
oil_total_profit: number;
}
// Delivery interfaces
export interface Delivery extends BaseEntity {
customer_id: number;
customer_name: string;
customer_address: string;
customer_town: string;
customer_state: string;
customer_zip: number;
gallons_ordered: number;
customer_asked_for_fill: number;
gallons_delivered: number;
customer_filled: number;
delivery_status: DeliveryStatusType;
when_ordered?: string;
when_delivered?: string;
expected_delivery_date?: string;
automatic: number;
automatic_id?: number;
oil_id?: number;
supplier_price?: number;
customer_price?: number;
customer_temperature?: number;
dispatcher_notes?: string;
prime: number;
same_day: number;
emergency: number;
payment_type: number;
payment_card_id?: number;
cash_recieved?: number;
driver_employee_id?: number;
driver_first_name?: string;
driver_last_name?: string;
pre_charge_amount?: number;
total_price: number;
final_price: number;
check_number?: string;
promo_id?: number;
promo_money_discount?: number;
}
export interface DeliveryNote extends BaseEntity {
delivery_id: number;
driver_comments?: string;
time_added: string;
driver_id: number;
driver_name: string;
}
// Payment interfaces
export interface PaymentTransaction extends BaseEntity {
customer_id: number;
delivery_id?: number;
service_id?: number;
amount: number;
payment_status: PaymentStatusType;
transaction_id?: string;
card_id?: number;
transaction_type?: TransactionStatusType;
created_at?: string;
updated_at?: string;
}
export interface CreditCard extends BaseEntity {
user_id: number;
date_added?: string;
card_number: string;
last_four_digits: number;
name_on_card: string;
expiration_month: string;
expiration_year: string;
type_of_card: string;
security_number?: string;
accepted_or_declined?: number;
main_card: boolean;
zip_code?: string;
auth_net_payment_profile_id?: string;
}
export interface AuthorizeTransaction extends BaseEntity {
transaction_id: string;
amount: number;
status: TransactionStatusType;
response_code?: string;
auth_code?: string;
created_at: string;
auth_net_transaction_id?: string;
customer_name?: string;
preauthorize_amount: number | null;
charge_amount: number | null;
transaction_type?: number;
delivery_id?: number;
service_id?: number;
auto_id?: number;
customer_id?: number;
rejection_reason?: string;
}
// Service interfaces
export interface ServiceCall extends BaseEntity {
customer_id: number;
customer_name?: string;
customer_address?: string;
customer_town?: string;
customer_state?: string;
customer_zip?: string;
type_service_call: number;
when_ordered?: string;
scheduled_date: string;
description: string;
service_cost: string;
payment_type?: number;
payment_card_id?: number;
payment_status?: number;
}
export interface ServicePart extends BaseEntity {
service_call_id: number;
part_name: string;
part_number?: string;
quantity: number;
unit_cost: number;
total_cost: number;
}
export interface ServicePlan extends BaseEntity {
customer_id: number;
customer_name: string;
customer_address: string;
customer_town: string;
contract_plan: number;
contract_years: number;
contract_start_date: string;
}
// Employee interfaces
export interface Employee extends BaseEntity {
employee_first_name: string;
employee_last_name: string;
employee_email?: string;
employee_phone?: string;
employee_role: string;
is_active: boolean;
hire_date?: string;
admin_role: boolean;
employee_type?: number;
employee_town?: string;
employee_phone_number?: string;
user_id?: number;
}
export interface User {
user_admin?: number;
// Add other user properties as needed
}
// Auto delivery interfaces
export interface AutoDelivery extends BaseEntity {
customer_id: number;
customer_full_name: string;
account_number: string;
customer_town: string;
customer_state: number;
customer_zip: string;
customer_address: string;
last_fill?: string;
last_updated?: string;
estimated_gallons_left: number;
estimated_gallons_left_prev_day: number;
tank_height?: string;
tank_size: number;
house_factor: number;
auto_status: AutoStatusType;
days_since_last_fill: number;
hot_water_summer: number;
open_ticket_id?: number;
}
// Promo interfaces
export interface Promo extends BaseEntity {
promo_code: string;
promo_name: string;
discount_type: string;
discount_value: number;
is_active: boolean;
start_date?: string;
end_date?: string;
usage_limit?: number;
usage_count: number;
}
// Query/Lookup interfaces
export interface StateOption {
value: number;
text: string;
abbreviation?: string;
}
export interface HomeTypeOption {
value: number;
text: string;
}
export interface PaymentTypeOption {
id: number;
name: string;
}
export interface OilPrice {
id: number;
supplier_name: string;
price_per_gallon: number;
effective_date: string;
}
// Search interfaces
export interface SearchResult {
id: number;
type: 'customer' | 'delivery' | 'service';
title: string;
subtitle?: string;
data: any; // Keep as any for now since search results can be various types
}
// Stats interfaces
export interface DashboardStats {
total_customers: number;
active_deliveries: number;
pending_payments: number;
monthly_revenue: number;
todays_deliveries: number;
}
export interface CompanyStats {
total_revenue: number;
total_profit: number;
total_deliveries: number;
total_service_calls: number;
average_delivery_time: number;
}
// Request interfaces
export interface CreateCustomerRequest {
customer_last_name: string;
customer_first_name: string;
customer_town: string;
customer_state: number;
customer_zip: string;
customer_email?: string;
customer_home_type: number;
customer_phone_number?: string;
customer_address: string;
customer_apt?: string;
customer_description?: string;
}
export interface UpdateCustomerRequest {
customer_last_name?: string;
customer_first_name?: string;
customer_town?: string;
customer_state?: number;
customer_zip?: string;
customer_email?: string;
customer_home_type?: number;
customer_phone_number?: string;
customer_address?: string;
customer_apt?: string;
customer_automatic?: CustomerAutomaticStatus;
}
export interface CreateDeliveryRequest {
customer_id: number;
gallons_ordered: number;
expected_delivery_date?: string;
payment_type: number;
payment_card_id?: number;
promo_id?: number;
dispatcher_notes?: string;
}
export interface UpdateDeliveryRequest {
gallons_delivered?: number;
delivery_status?: DeliveryStatusType;
payment_type?: number;
total_price?: number;
final_price?: number;
driver_employee_id?: number;
dispatcher_notes?: string;
}
export interface CreateCardRequest {
customer_id: number;
card_number: string;
expiration_month: number;
expiration_year: number;
security_number: string;
cardholder_name: string;
}
export interface PaymentRequest {
customer_id: number;
amount: number;
card_id?: number;
delivery_id?: number;
service_id?: number;
}
// Calendar/Event interfaces
export interface CalendarEvent {
id: string;
title: string;
start: string;
end?: string;
backgroundColor?: string;
extendedProps?: {
customer_id: number;
delivery_id?: number;
service_id?: number;
type: 'delivery' | 'service' | 'maintenance';
status: string;
};
}
// Form interfaces
export interface LoginForm {
username: string;
password: string;
}
export interface RegisterForm {
username: string;
email: string;
password: string;
confirm_password: string;
}
export interface ChangePasswordForm {
current_password: string;
new_password: string;
confirm_password: string;
}
// ============================================
// Payment Request Interfaces (Authorize.net)
// ============================================
export interface TokenizeCardRequest {
card_number: string;
expiration_month: string;
expiration_year: string;
cvv: string;
cardholder_name: string;
zip_code?: string;
}
export interface UpdateTokenizedCardRequest {
card_number?: string;
expiration_month?: string;
expiration_year?: string;
cvv?: string;
cardholder_name?: string;
zip_code?: string;
}
export interface ChargeSavedCardRequest {
card_id: number;
charge_amount: string;
delivery_id?: number;
service_id?: number | null;
}
export interface PreauthorizeSavedCardRequest {
card_id: number;
preauthorize_amount: string;
delivery_id?: number;
auto_id?: number;
}
export interface ChargeDirectRequest {
card_number: string;
expiration_date: string;
cvv: string;
charge_amount: string;
preauthorize_amount?: string;
transaction_type: number;
delivery_id?: number;
service_id?: number | null;
card_id?: number;
}
export interface CaptureRequest {
charge_amount: number;
auth_net_transaction_id: string;
}
// ============================================
// API Response Interfaces
// ============================================
export interface AuthorizeNetTransactionResponse {
id: number;
auth_net_transaction_id: string;
preauthorize_amount: number | null;
charge_amount: number | null;
status: number; // 0 = approved, 1 = declined
transaction_type: number; // 0 = charge, 1 = auth, 2 = capture
created_at: string;
rejection_reason?: string;
customer_id?: number;
delivery_id?: number;
service_id?: number;
auto_id?: number;
}
export interface DeliveryOrderResponse {
ok: boolean;
delivery: Delivery;
error?: string;
}
export interface DeliveryTotalResponse {
ok: boolean;
priceprime: number;
pricesameday: number;
priceemergency: number;
total_amount: number;
discount: number;
total_amount_after_discount: number;
}
export interface OilPricingResponse {
price_from_supplier: number;
price_for_customer: number;
price_for_employee: number;
price_same_day: number;
price_prime: number;
price_emergency: number;
date: string;
}
export interface WhoAmIResponse {
ok: boolean;
user: {
user_id: number;
user_admin?: number;
};
}
export interface AuthorizeCheckResponse {
profile_exists: boolean;
has_payment_methods: boolean;
missing_components: string[];
valid_for_charging: boolean;
}
export interface CreateAuthorizeAccountResponse {
success: boolean;
profile_id?: string;
message?: string;
error_detail?: string;
is_duplicate?: boolean;
}
export interface PromoResponse {
id: number;
name_of_promotion: string;
description: string;
money_off_delivery: number;
text_on_ticket: string;
}
export interface CardsOnFileResponse {
cards: number;
}
export interface PaymentCardResponse {
userCard: CreditCard;
}
export interface UpdateStatusResponse {
update: boolean;
}
// ============================================
// Component Local State Interfaces
// ============================================
export interface DeliveryFormData {
id: number;
customer_id: number;
customer_name: string;
customer_address: string;
customer_town: string;
customer_state: number;
customer_zip: string;
gallons_ordered: number;
customer_asked_for_fill: number;
gallons_delivered: number;
customer_filled: number;
delivery_status: number;
when_ordered: string;
when_delivered: string;
expected_delivery_date: string;
automatic: number;
automatic_id?: number;
oil_id: number;
supplier_price: number;
customer_price: number;
customer_temperature: number;
dispatcher_notes: string;
prime: number;
promo_id: number | null;
emergency: number;
same_day: number;
payment_type: number;
payment_card_id: number;
driver_employee_id: number;
driver_first_name: string;
driver_last_name: string;
pre_charge_amount: number;
total_price: number;
final_price?: number;
service_id?: number | null;
}
export interface CustomerFormData {
id: number;
user_id?: number;
customer_first_name: string;
customer_last_name: string;
customer_town: string;
customer_address: string;
customer_state: number;
customer_zip: string;
customer_apt: string;
customer_home_type: number;
customer_phone_number: string;
account_number: string;
auth_net_profile_id?: string | null;
customer_email?: string;
}
export interface CreditCardFormData {
id: number;
name_on_card: string;
main_card: boolean;
card_number: string;
expiration_month: string;
type_of_card: string;
last_four_digits: string | number;
expiration_year: string;
security_number: string;
date_added?: string;
user_id?: number | string;
accepted_or_declined?: number | string;
auth_net_payment_profile_id?: string;
zip_code?: string;
}
export interface PricingData {
price_from_supplier: number;
price_for_customer: number;
price_for_employee: number;
price_same_day: number;
price_prime: number;
price_emergency: number;
date: string;
}
export interface PromoData {
name_of_promotion: string;
description: string;
money_off_delivery: number;
text_on_ticket: string;
}
export interface AutoTicketData {
id: number;
customer_id: number | string;
account_number: string;
customer_town: string;
customer_state: number | string;
customer_address: string;
customer_zip: string;
customer_full_name: string;
oil_prices_id: number | string;
fill_date: string;
gallons_delivered: string;
price_per_gallon: string;
total_amount_customer: string;
payment_type: number | string;
payment_card_id: number | string;
payment_status: number;
open_ticket_id: number | null;
}
export interface AutoDeliveryData {
id: number;
customer_id: number;
account_number: string;
customer_town: string;
customer_state: number;
customer_address: string;
customer_zip: string;
customer_full_name: string;
last_fill: string;
days_since_last_fill: number;
last_updated: string;
estimated_gallons_left: number;
estimated_gallons_left_prev_day: number;
tank_height: string;
tank_size: string | number;
house_factor: number;
auto_status: number;
open_ticket_id: number | null;
}
export interface CustomerDescriptionData {
customer_id: number;
account_number: string;
company_id: number;
fill_location: number;
description: string;
}
// ============================================
// Axios Response Type Helper
// ============================================
export interface AxiosApiResponse<T> {
data: T;
status: number;
statusText: string;
}
// Utility types
export type CustomerListResponse = PaginatedResponse<Customer>;
export type DeliveryListResponse = PaginatedResponse<Delivery>;
export type TransactionListResponse = PaginatedResponse<PaymentTransaction>;
export type CardListResponse = PaginatedResponse<CreditCard>;
export type ServiceListResponse = PaginatedResponse<ServiceCall>;
export type EmployeeListResponse = PaginatedResponse<Employee>;