Files
eamco_office_frontend/src/pages/pay/service/pay_service.vue
Edwin Eames 53086515ba fix: resolve TypeScript build errors for production builds
- Add local AxiosResponse/AxiosError interfaces to models.ts as workaround
  for bundler moduleResolution issues with axios types
- Update 7 payment Vue files to import axios types from local models
- Convert axios.get<T>() generic calls to typed .then() response callbacks
- Fix type narrowing in getTypeColor(), getEmployeeTypeName() functions
- Add Number() conversion for tank_size arithmetic in auto preauth
- Use 'as unknown as' for Delivery to DeliveryFormData type assertions
- Fix incorrect import paths for sidebar/footer in delivery/create.vue

Production build (npm run build) now completes successfully.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 15:33:13 -05:00

810 lines
29 KiB
Vue
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- src/pages/pay/service/pay_service.vue -->
<template>
<div class="flex">
<div class="w-full px-4 md:px-10 py-4">
<!-- Breadcrumbs & Title -->
<div class="text-sm breadcrumbs">
<ul>
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
<!-- Add a link to the customer's profile if the data is available -->
<li v-if="customer && customer.id">
<router-link :to="{ name: 'customerProfile', params: { id: customer.id } }">
{{ customer.customer_first_name }} {{ customer.customer_last_name }}
</router-link>
</li>
<li>Confirm Service Payment</li>
</ul>
</div>
<h1 class="text-3xl font-bold mt-4 border-b border-gray-600 pb-2">
Confirm Service Payment #{{ service.id }}
</h1>
<!-- Main Content Grid -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 my-6">
<!-- LEFT COLUMN: Customer and Delivery Details -->
<div class="space-y-6">
<!-- Customer Info Card -->
<div class="bg-neutral rounded-lg p-5">
<div class="flex justify-between items-center mb-4">
<div>
<div class="text-xl font-bold">{{ customer.customer_first_name }} {{ customer.customer_last_name }}</div>
<div class="text-sm text-gray-400">Account: {{ customer.account_number }}</div>
</div>
<router-link v-if="customer && customer.id" :to="{ name: 'customerProfile', params: { id: customer.id } }" class="btn btn-secondary btn-sm">
View Profile
</router-link>
</div>
<div class="space-y-1">
<div>{{ customer.customer_address }}</div>
<div v-if="customer.customer_apt && customer.customer_apt !== 'None'">Apt: {{ customer.customer_apt }}</div>
<div>{{ customer.customer_town }}, {{ customer.customer_state === 0 ? 'MA' : 'RI' }} {{ customer.customer_zip }}</div>
<div class="mt-2">{{ customer.customer_phone_number }}</div>
</div>
</div>
<!-- Service Details Card -->
<div class="bg-neutral rounded-lg p-5">
<h3 class="text-xl font-bold mb-4">Service Details</h3>
<div class="space-y-3">
<div>
<div class="font-bold text-sm">Service Type</div>
<div class="badge" :class="getServiceTypeColor(service.type_service_call)">
{{ getServiceTypeName(service.type_service_call) }}
</div>
</div>
<div>
<div class="font-bold text-sm">Scheduled Date</div>
<div>{{ service.scheduled_date ? formatScheduledDate(service.scheduled_date) : 'Not scheduled' }}</div>
</div>
<div>
<div class="font-bold text-sm">Description</div>
<div class="text-sm">{{ service.description || 'No description provided' }}</div>
</div>
<div>
<div class="font-bold text-sm">Total Cost</div>
<div>${{ service.service_cost || '0.00' }}</div>
</div>
</div>
</div>
</div>
<!-- RIGHT COLUMN: Payment and Pricing Details -->
<div class="space-y-6">
<!-- Authorize.net Account Status Box -->
<div v-if="customer.id" class="bg-base-100 rounded-lg p-4 border">
<div class="flex flex-col xl:flex-row xl:items-center xl:justify-between gap-3">
<div class="flex items-center gap-3 min-w-0 flex-1">
<svg class="w-5 h-5 text-blue-600 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v4a3 3 0 003 3z"/>
</svg>
<span v-if="isLoadingAuthorize" class="text-sm font-medium">
<span class="loading loading-dots loading-xs mr-2"></span>
Loading...
</span>
<span v-else-if="authorizeCheck.valid_for_charging" class="text-sm font-medium">
Authorize Account ID: {{ customer.auth_net_profile_id }}
</span>
<span v-else class="text-sm font-medium text-red-600">
{{ getAccountStatusMessage() }}
</span>
</div>
<div class="flex gap-2 flex-shrink-0" v-if="!isLoadingAuthorize">
<!-- CREATE ACCOUNT SECTION - Only show when account doesn't exist -->
<div v-if="!authorizeCheck.valid_for_charging" class="flex gap-2">
<button
v-if="credit_cards_count === 0"
@click="addCreditCard"
class="btn btn-primary btn-sm"
>
Add Card
</button>
<button
@click="createAuthorizeAccount"
:class="['btn btn-sm', credit_cards_count === 0 ? 'btn-disabled' : 'btn-primary']"
:disabled="credit_cards_count === 0"
v-if="credit_cards_count > 0"
>
Create Account
</button>
<button
v-else
@click="addCreditCard"
class="btn btn-secondary btn-sm"
>
Add Card First
</button>
</div>
<!-- DELETE ACCOUNT SECTION - Only show when account exists -->
<div v-if="authorizeCheck.valid_for_charging" class="flex gap-2">
<button
@click="showDeleteAccountModal"
class="btn btn-error btn-sm"
>
Delete Account
</button>
</div>
</div>
</div>
</div>
<!-- Service Payment Card -->
<div class="bg-neutral rounded-lg p-5">
<h3 class="text-xl font-bold mb-4">Service Payment</h3>
<div class="space-y-4">
<!-- Payment Method Selection -->
<div>
<div class="font-bold text-sm mb-2">Select Payment Method</div>
<div class="space-y-2">
<!-- Show the selected card if payment is by credit -->
<div v-for="card in credit_cards" :key="card.id">
<div v-if="card.id === service.payment_card_id" class="bg-base-100 p-3 rounded-md text-sm">
<div class="font-mono font-semibold">{{ card.type_of_card }} ending in {{ card.last_four_digits }}</div>
<div>{{ card.name_on_card }}</div>
<div>Expires: {{ card.expiration_month }}/{{ card.expiration_year }}</div>
</div>
</div>
</div>
</div>
<!-- Service Cost -->
<div class="pt-2">
<div class="divide-y divide-gray-300">
<div class="flex justify-between items-center py-3">
<span class="text-lg font-bold">Service Total</span>
<span class="text-2xl font-bold text-accent">
${{ service.service_cost || '0.00' }}
</span>
</div>
</div>
</div>
</div>
</div>
<!-- Actions Card -->
<div class="bg-neutral rounded-lg p-5">
<div class="flex flex-wrap gap-4 justify-between items-center">
<!-- Pay Authorize Button -->
<button class="btn btn-success" :class="{ 'btn-disabled': !authorizeCheck.valid_for_charging }" :disabled="!authorizeCheck.valid_for_charging" @click="$router.push({ name: 'authorizeServicePreauthCharge', params: { id: $route.params.id } })">
Pay Authorize.net
</button>
<!-- A single confirm button is cleaner -->
<button class="btn btn-warning" @click="processServicePayment(1)">
Pay Tiger
</button>
<!-- Edit Service button removed due to no route defined -->
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Delete Account Confirmation Modal -->
<div class="modal" :class="{ 'modal-open': isDeleteAccountModalVisible }">
<div class="modal-box">
<h3 class="font-bold text-lg">Confirm Account Deletion</h3>
<p class="py-4">This will permanently delete the Authorize.net account and remove all payment profiles. This action cannot be undone.</p>
<div class="modal-action">
<button @click="deleteAccount" class="btn btn-error">Delete Account</button>
<button @click="isDeleteAccountModalVisible = false" class="btn">Cancel</button>
</div>
</div>
</div>
<!-- Create Account Progress Modal -->
<div class="modal" :class="{ 'modal-open': isCreateAccountModalVisible }">
<div class="modal-box">
<h3 class="font-bold text-lg">Creating Authorize.net Account</h3>
<div class="py-4 flex flex-col items-center">
<div v-if="isCreatingAccount" class="text-center">
<span class="text-lg mb-3">Setting up your payment account...</span>
<div class="loading loading-spinner loading-lg text-primary mb-3"></div>
<p class="text-sm text-gray-600">Please wait while we create your Authorize.net customer profile.</p>
</div>
<div v-else class="text-center">
<div class="text-success mb-3">
<svg class="w-12 h-12 mx-auto mb-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
</svg>
</div>
<p class="text-lg font-semibold mb-2">Account Created Successfully!</p>
<div class="bg-base-200 p-3 rounded-lg mb-4">
<p class="text-sm mb-1">Authorize.net Profile ID:</p>
<p class="font-mono font-bold text-success">{{ createdProfileId }}</p>
</div>
<p class="text-sm text-gray-600">Your payment account is now ready for transactions.</p>
</div>
</div>
</div>
</div>
<!-- Duplicate Account Error Modal -->
<div class="modal" :class="{ 'modal-open': isDuplicateErrorModalVisible }">
<div class="modal-box">
<h3 class="font-bold text-lg text-error"> Duplicate Account Detected</h3>
<div class="py-4 space-y-4">
<div class="text-center">
<svg class="w-16 h-16 mx-auto mb-4 text-warning" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"/>
</svg>
<p class="text-lg font-semibold">Duplicate Account in Authorize.net</p>
<p class="text-sm text-gray-600 mt-2">
A duplicate account was found in your Authorize.net merchant account.
</p>
<p class="text-sm text-gray-600 mt-2">
Customer ID: <strong>{{ customer.id }}</strong>
</p>
</div>
<div class="bg-base-200 p-4 rounded-lg">
<h4 class="font-semibold mb-2 text-warning">Action Required:</h4>
<ul class="list-disc list-inside text-sm space-y-1">
<li>Manually check your Authorize.net merchant dashboard</li>
<li>Review existing customer profiles</li>
<li>Contact support for linkage if needed</li>
</ul>
<p class="text-xs text-gray-500 mt-2">
Inconsistency between your system and Authorize.net detected.
</p>
</div>
<div class="text-center pt-2">
<p class="text-xs text-gray-500">
This profile may have been created previously and needs manual linking.
</p>
</div>
</div>
<div class="modal-action">
<button class="btn btn-primary" @click="hideDuplicateErrorModal()">
Acknowledge
</button>
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import axios from 'axios'
import authHeader from '../../../services/auth.header'
import { ServiceCall, ServicePart, CreditCard, Customer } from '../../../types/models'
import type {
AxiosResponse,
AxiosError,
WhoAmIResponse,
CardsOnFileResponse,
AuthorizeCheckResponse,
CreateAuthorizeAccountResponse
} from '../../../types/models'
import Header from '../../../layouts/headers/headerauth.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core";
import { notify } from "@kyvg/vue3-notification"
import { required } from "@vuelidate/validators";
// Reactive data
const route = useRoute()
const router = useRouter()
const loaded = ref(false)
const user = ref({
user_id: 0,
})
const service = ref<ServiceCall>({
id: 0,
customer_id: 0,
customer_name: '',
customer_address: '',
customer_town: '',
customer_state: '',
customer_zip: '',
type_service_call: 0,
when_ordered: '',
scheduled_date: '',
description: '',
service_cost: '0',
payment_type: 0,
payment_card_id: 0,
payment_status: 0,
})
const serviceParts = ref<ServicePart[] | null>(null)
const credit_cards = ref<CreditCard[]>([])
const stripe = ref(null)
const customer = ref({
id: 0,
user_id: 0,
customer_first_name: '',
customer_last_name: '',
customer_town: '',
customer_address: '',
customer_state: 0,
customer_zip: '',
customer_apt: '',
customer_home_type: 0,
customer_phone_number: '',
account_number: '',
auth_net_profile_id: null,
})
const pricing = ref({
price_from_supplier: 0,
price_for_customer: 0,
price_for_employee: 0,
price_same_day: 0,
price_prime: 0,
price_emergency: 0,
date: "",
})
const promo_active = ref(false)
const promo = ref({
name_of_promotion: '',
description: '',
money_off_delivery: 0,
text_on_ticket: ''
})
const priceprime = ref(0)
const pricesameday = ref(0)
const priceemergency = ref(0)
const total_amount = ref(0)
const discount = ref(0)
const total_amount_after_discount = ref(0)
const credit_cards_count = ref(0)
const isLoadingAuthorize = ref(true)
const authorizeCheck = ref({ profile_exists: false, has_payment_methods: false, missing_components: [] as string[], valid_for_charging: false })
const isDeleteAccountModalVisible = ref(false)
const isCreateAccountModalVisible = ref(false)
const isCreatingAccount = ref(false)
const createdProfileId = ref('')
const isDuplicateErrorModalVisible = ref(false)
// Validation rules
const rules = {
CreateServiceOrderForm: {
basicInfo: {
description: { required },
service_cost: { required },
},
},
}
// Vuelidate instance
const v$ = useValidate(rules, {})
// Functions
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: 'get',
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: AxiosResponse<WhoAmIResponse>) => {
if (response.data.ok) {
user.value = response.data.user;
}
})
}
const getServiceOrder = (service_id: number | string) => {
let path = import.meta.env.VITE_BASE_URL + "/service/" + service_id;
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: AxiosResponse<{ ok?: boolean; service?: ServiceCall } | ServiceCall[] | ServiceCall>) => {
let serviceData: ServiceCall | undefined;
if (response.data) {
// Handle different API response structures
if ('service' in response.data && response.data.service) {
// API returns {ok: true, service: {...}} structure
serviceData = response.data.service;
} else if (Array.isArray(response.data)) {
serviceData = response.data[0]; // Array response
} else if ('id' in response.data) {
serviceData = response.data as ServiceCall; // Direct object response
}
if (serviceData && serviceData.id) {
service.value = {
id: serviceData.id,
scheduled_date: serviceData.scheduled_date,
customer_id: serviceData.customer_id,
customer_name: serviceData.customer_name,
customer_address: serviceData.customer_address,
customer_town: serviceData.customer_town,
type_service_call: serviceData.type_service_call,
description: serviceData.description,
service_cost: serviceData.service_cost,
payment_card_id: serviceData.payment_card_id || 0,
};
// Fetch related data
getCustomer(service.value.customer_id);
getCreditCards(service.value.customer_id);
getCreditCardsCount(service.value.customer_id);
getServicePartsForCustomer();
} else {
console.error("API Error: Invalid service data received:", serviceData);
notify({
title: "Error",
text: "Invalid service data received",
type: "error",
});
}
} else {
console.error("API Error: No response data received");
notify({
title: "Error",
text: "Could not get service data",
type: "error",
});
}
})
.catch((error: AxiosError) => {
console.error("API Error in getServiceOrder:", error);
console.error("Error details:", error.response?.data || error.message);
notify({
title: "Error",
text: "Could not get service data",
type: "error",
});
});
}
const getServicePartsForCustomer = () => {
if (!service.value.customer_id) return;
let path = `${import.meta.env.VITE_BASE_URL}/service/parts/customer/${service.value.customer_id}`;
axios.get(path, { headers: authHeader() })
.then((response: AxiosResponse<ServicePart[]>) => {
serviceParts.value = response.data;
})
.catch((error: Error) => {
console.error("Failed to fetch service parts:", error);
serviceParts.value = null;
});
}
const getCreditCards = (user_id: number) => {
let path = import.meta.env.VITE_BASE_URL + '/payment/cards/' + user_id;
axios({
method: 'get',
url: path,
headers: authHeader(),
}).then((response: AxiosResponse<CreditCard[]>) => {
credit_cards.value = response.data
})
}
const getCreditCardsCount = (user_id: number) => {
let path = import.meta.env.VITE_BASE_URL + '/payment/cards/onfile/' + user_id;
axios({
method: 'get',
url: path,
headers: authHeader(),
}).then((response: AxiosResponse<CardsOnFileResponse>) => {
credit_cards_count.value = response.data.cards
})
}
const getCustomer = (userid: number) => {
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
axios({
method: 'get',
url: path,
headers: authHeader(),
}).then((response: AxiosResponse<typeof customer.value>) => {
customer.value = response.data
checkAuthorizeAccount();
})
}
const processServicePayment = (payment_type: number) => {
let path = import.meta.env.VITE_BASE_URL + "/payment/service/payment/" + service.value.id + '/' + payment_type;
axios({
method: "PUT",
url: path,
})
.then((response: AxiosResponse<{ ok: boolean }>) => {
if (response.data.ok) {
if (payment_type == 0) {
notify({
title: "Success",
text: "Service marked as cash payment",
type: "success",
});
}
if (payment_type == 1) {
notify({
title: "Success",
text: "Service marked as credit card payment",
type: "success",
});
}
if (payment_type == 3) {
notify({
title: "Success",
text: "Service marked as check payment",
type: "success",
});
}
if (payment_type == 11) {
notify({
title: "Success",
text: "Service payment processed via Authorize.net",
type: "success",
});
}
router.push({ name: "ServiceHome" });
}
})
.catch(() => {
notify({
title: "Error",
text: "Could not process service payment",
type: "error",
});
});
}
const checkAuthorizeAccount = async () => {
if (!customer.value.id) return;
isLoadingAuthorize.value = true;
try {
const path = `${import.meta.env.VITE_AUTHORIZE_URL}/user/check-authorize-account/${customer.value.id}`;
const response = await axios.get(path, { headers: authHeader() });
authorizeCheck.value = response.data;
// Check if the API returned an error in the response body
if (authorizeCheck.value.missing_components && authorizeCheck.value.missing_components.includes('api_error')) {
console.log("API error detected in response, calling cleanup for customer:", customer.value.id);
cleanupAuthorizeData();
return; // Don't set loading to false yet, let cleanup handle it
}
} catch (error) {
console.error("Failed to check authorize account:", error);
notify({ title: "Error", text: "Could not check payment account status.", type: "error" });
// Set default error state
authorizeCheck.value = {
profile_exists: false,
has_payment_methods: false,
missing_components: ['api_error'],
valid_for_charging: false
};
// Automatically cleanup the local Authorize.Net data on API error
console.log("Calling cleanupAuthorizedData for customer:", customer.value.id);
cleanupAuthorizeData();
} finally {
isLoadingAuthorize.value = false;
}
}
const createAuthorizeAccount = async () => {
// Show the creating account modal
isCreatingAccount.value = true;
isCreateAccountModalVisible.value = true;
try {
const path = `${import.meta.env.VITE_AUTHORIZE_URL}/user/create-account/${customer.value.id}`;
const response = await axios.post(path, {}, { headers: authHeader() });
if (response.data.success) {
// Update local state
customer.value.auth_net_profile_id = response.data.profile_id;
authorizeCheck.value.valid_for_charging = true;
authorizeCheck.value.profile_exists = true;
authorizeCheck.value.has_payment_methods = true;
authorizeCheck.value.missing_components = [];
createdProfileId.value = response.data.profile_id;
// Refresh credit cards to get updated payment profile IDs
await getCreditCards(customer.value.id);
// Switch modal to success view and close after delay
setTimeout(() => {
isCreatingAccount.value = false;
setTimeout(() => {
isCreateAccountModalVisible.value = false;
createdProfileId.value = '';
notify({
title: "Success",
text: "Authorize.net account created successfully!",
type: "success"
});
}, 3000); // Show success message for 3 seconds
}, 1000); // Brief delay to show success animation
} else {
// Hide modal on error
isCreateAccountModalVisible.value = false;
// Check for E00039 duplicate error
const errorMessage = response.data.message || response.data.error_detail || "Failed to create Authorize.net account";
if (response.data.is_duplicate || errorMessage.includes("E00039")) {
// Show duplicate account popup
setTimeout(() => {
showDuplicateErrorModal();
}, 300);
return;
} else {
// Normal error notification
notify({
title: "Error",
text: errorMessage,
type: "error"
});
}
}
} 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);
isCreateAccountModalVisible.value = false;
isCreatingAccount.value = false;
// Check for E00039 duplicate error
const errorMessage = error.response?.data?.error_detail ||
error.response?.data?.detail ||
error.response?.data?.message ||
error.message || "Failed to create Authorize.net account";
if (error.response?.data?.is_duplicate || errorMessage.includes("E00039")) {
// Show duplicate account popup
setTimeout(() => {
showDuplicateErrorModal();
}, 300);
return;
}
// Normal error notification
notify({
title: "Error",
text: errorMessage,
type: "error"
});
}
}
const showDeleteAccountModal = () => {
isDeleteAccountModalVisible.value = true;
}
const showDuplicateErrorModal = () => {
isDuplicateErrorModalVisible.value = true;
}
const hideDuplicateErrorModal = () => {
isDuplicateErrorModalVisible.value = false;
}
const addCreditCard = () => {
// Redirect to add card page
router.push({ name: 'cardadd', params: { customerId: customer.value.id } });
}
const deleteAccount = async () => {
isDeleteAccountModalVisible.value = false;
try {
const path = `${import.meta.env.VITE_AUTHORIZE_URL}/user/delete-account/${customer.value.id}`;
const response = await axios.delete(path, { headers: authHeader() });
if (response.data.success) {
// Update local state
customer.value.auth_net_profile_id = null;
authorizeCheck.value.valid_for_charging = false;
authorizeCheck.value.profile_exists = false;
authorizeCheck.value.has_payment_methods = false;
// Refresh credit cards list (IDs should now be null)
getCreditCards(customer.value.id);
notify({
title: "Success",
text: "Authorize.net account deleted successfully",
type: "success"
});
} else {
notify({
title: "Warning",
text: response.data.message || "Account deletion completed with warnings",
type: "warning"
});
}
} catch (error: unknown) {
console.error("Failed to delete account:", error);
notify({
title: "Error",
text: "Failed to delete Authorize.net account",
type: "error"
});
}
}
const cleanupAuthorizeData = async () => {
try {
const path = `${import.meta.env.VITE_BASE_URL}/payment/authorize/cleanup/${customer.value.id}`;
const response = await axios.post(path, {}, { headers: authHeader() });
if (response.data.ok) {
// Update local state to reflect cleanup
customer.value.auth_net_profile_id = null;
authorizeCheck.value.valid_for_charging = false;
authorizeCheck.value.profile_exists = false;
authorizeCheck.value.has_payment_methods = false;
// Refresh credit cards to reflect null payment profile IDs
getCreditCards(customer.value.id);
console.log("Successfully cleaned up Authorize.Net data:", response.data.message);
} else {
console.error("Failed to cleanup Authorize.Net data:", response.data.error);
}
} catch (error) {
console.error("Error during cleanup:", error);
}
}
const getAccountStatusMessage = (): string => {
if (!authorizeCheck.value || !authorizeCheck.value.missing_components) {
return 'Account setup incomplete';
}
const missing = authorizeCheck.value.missing_components;
if (missing.includes('customer_not_found')) {
return 'Customer not found in Authorize.net';
} else if (missing.includes('authorize_net_profile')) {
return 'No Authorize.net profile configured';
} else if (missing.includes('authorize_net_profile_invalid')) {
return 'Authorize.net profile is invalid';
} else if (missing.includes('payment_method')) {
return 'No payment methods configured';
} else if (missing.includes('api_error')) {
return 'Error checking account status';
} else {
return 'Account requires setup';
}
}
const getServiceTypeName = (typeId: number): string => {
const typeMap: { [key: number]: string } = { 0: 'Tune-up', 1: 'No Heat', 2: 'Fix', 3: 'Tank Install', 4: 'Other' };
return typeMap[typeId] || 'Unknown';
}
const getServiceTypeColor = (typeId: number): string => {
const colorMap: { [key: number]: string } = { 0: 'primary', 1: 'error', 2: 'warning', 3: 'info', 4: 'neutral' };
return `badge-${colorMap[typeId] || 'neutral'}`;
}
const formatScheduledDate = (dateString: string): string => {
if (!dateString) return 'Not scheduled';
return dateString; // Could format with dayjs if needed
}
// Lifecycle
onMounted(() => {
userStatus()
getServiceOrder(route.params.id)
getServicePartsForCustomer();
})
</script>
<style scoped></style>