major claude changes
This commit is contained in:
@@ -110,200 +110,185 @@
|
||||
</div>
|
||||
<Footer />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../services/auth.header'
|
||||
import Header from '../layouts/headers/headerauth.vue'
|
||||
import SideBar from '../layouts/sidebar/sidebar.vue'
|
||||
import Footer from '../layouts/footers/footer.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Home',
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
clickCount?: number
|
||||
}>()
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
props: {
|
||||
clickCount: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
token: null,
|
||||
call_count:0,
|
||||
delivery_count: 0,
|
||||
delivery_count_delivered: 0,
|
||||
price_from_supplier: 0,
|
||||
today_oil_price: 0,
|
||||
price_for_employee: 0,
|
||||
price_same_day: 0,
|
||||
price_prime: 0,
|
||||
price_emergency: 0,
|
||||
user: {
|
||||
user_id: 0,
|
||||
user_name: '',
|
||||
},
|
||||
employee: {
|
||||
id: '',
|
||||
user_id: '',
|
||||
employee_last_name: "",
|
||||
employee_first_name: "",
|
||||
employee_town: "",
|
||||
employee_address: "",
|
||||
employee_apt: "",
|
||||
employee_zip: "",
|
||||
employee_birthday: "",
|
||||
employee_phone_number: "",
|
||||
employee_start_date: "",
|
||||
employee_end_date: "",
|
||||
employee_type: '',
|
||||
employee_state: '',
|
||||
},
|
||||
total_gallons_past_week: 0,
|
||||
total_profit_past_week: 0,
|
||||
total_deliveries: 0,
|
||||
// Router
|
||||
const router = useRouter()
|
||||
|
||||
|
||||
loaded: false,
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
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';
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
this.employeeStatus()
|
||||
} else {
|
||||
|
||||
localStorage.removeItem('user');
|
||||
this.$router.push('/login');
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
totalgallonsweek() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/stats/gallons/week';
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.total_gallons_past_week = response.data.total;
|
||||
|
||||
})
|
||||
},
|
||||
totalprofitweek() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/money/profit/week';
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.total_profit_past_week = response.data.total_profit;
|
||||
this.total_deliveries = response.data.total_deliveries;
|
||||
|
||||
})
|
||||
},
|
||||
employeeStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/employee/userid/' + this.user.user_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.employee = response.data;
|
||||
this.loaded = true;
|
||||
|
||||
})
|
||||
},
|
||||
total_calls() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/stats/call/count/today'
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.call_count = response.data.data;
|
||||
})
|
||||
},
|
||||
today_delivery_count() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/stats/delivery/count/today'
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.delivery_count = response.data.data;
|
||||
})
|
||||
},
|
||||
today_delivery_delivered() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/stats/delivery/count/delivered/today'
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
console.log(response.data)
|
||||
this.delivery_count_delivered = response.data.data;
|
||||
})
|
||||
},
|
||||
today_price_oil() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/info/price/oil'
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.price_from_supplier = response.data.price_from_supplier;
|
||||
this.today_oil_price = response.data.price_for_customer;
|
||||
this.price_for_employee = response.data.price_for_employee;
|
||||
this.price_same_day = response.data.price_same_day;
|
||||
this.price_prime = response.data.price_prime;
|
||||
this.price_emergency = response.data.price_emergency;
|
||||
})
|
||||
},
|
||||
|
||||
},
|
||||
// Reactive data
|
||||
const token = ref(null)
|
||||
const call_count = ref(0)
|
||||
const delivery_count = ref(0)
|
||||
const delivery_count_delivered = ref(0)
|
||||
const price_from_supplier = ref(0)
|
||||
const today_oil_price = ref(0)
|
||||
const price_for_employee = ref(0)
|
||||
const price_same_day = ref(0)
|
||||
const price_prime = ref(0)
|
||||
const price_emergency = ref(0)
|
||||
const user = ref({
|
||||
user_id: 0,
|
||||
user_name: '',
|
||||
})
|
||||
</script>
|
||||
const employee = ref({
|
||||
id: '',
|
||||
user_id: '',
|
||||
employee_last_name: "",
|
||||
employee_first_name: "",
|
||||
employee_town: "",
|
||||
employee_address: "",
|
||||
employee_apt: "",
|
||||
employee_zip: "",
|
||||
employee_birthday: "",
|
||||
employee_phone_number: "",
|
||||
employee_start_date: "",
|
||||
employee_end_date: "",
|
||||
employee_type: '',
|
||||
employee_state: '',
|
||||
})
|
||||
const total_gallons_past_week = ref(0)
|
||||
const total_profit_past_week = ref(0)
|
||||
const total_deliveries = ref(0)
|
||||
const loaded = ref(false)
|
||||
|
||||
<style scoped></style>
|
||||
<script setup lang="ts">
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus()
|
||||
today_delivery_count()
|
||||
today_delivery_delivered()
|
||||
today_price_oil()
|
||||
totalgallonsweek()
|
||||
totalprofitweek()
|
||||
})
|
||||
|
||||
// Functions
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
employeeStatus()
|
||||
} else {
|
||||
localStorage.removeItem('user');
|
||||
router.push('/login');
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const totalgallonsweek = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/stats/gallons/week';
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
total_gallons_past_week.value = response.data.total;
|
||||
})
|
||||
}
|
||||
|
||||
const totalprofitweek = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/money/profit/week';
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
total_profit_past_week.value = response.data.total_profit;
|
||||
total_deliveries.value = response.data.total_deliveries;
|
||||
})
|
||||
}
|
||||
|
||||
const employeeStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/employee/userid/' + user.value.user_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
employee.value = response.data;
|
||||
loaded.value = true;
|
||||
})
|
||||
}
|
||||
|
||||
const total_calls = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/stats/call/count/today'
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
call_count.value = response.data.data;
|
||||
})
|
||||
}
|
||||
|
||||
const today_delivery_count = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/stats/delivery/count/today'
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
delivery_count.value = response.data.data;
|
||||
})
|
||||
}
|
||||
|
||||
const today_delivery_delivered = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/stats/delivery/count/delivered/today'
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
console.log(response.data)
|
||||
delivery_count_delivered.value = response.data.data;
|
||||
})
|
||||
}
|
||||
|
||||
const today_price_oil = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/info/price/oil'
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
price_from_supplier.value = response.data.price_from_supplier;
|
||||
today_oil_price.value = response.data.price_for_customer;
|
||||
price_for_employee.value = response.data.price_for_employee;
|
||||
price_same_day.value = response.data.price_same_day;
|
||||
price_prime.value = response.data.price_prime;
|
||||
price_emergency.value = response.data.price_emergency;
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
|
||||
|
||||
import OilPrice from '../admin/oilprice.vue';
|
||||
const OilPrice = () => import('../admin/oilprice.vue');
|
||||
|
||||
import Promo from '../admin/promo/promo.vue';
|
||||
import PromoCreate from '../admin/promo/create.vue';
|
||||
import PromoEdit from '../admin/promo/edit.vue';
|
||||
const Promo = () => import('../admin/promo/promo.vue');
|
||||
const PromoCreate = () => import('../admin/promo/create.vue');
|
||||
const PromoEdit = () => import('../admin/promo/edit.vue');
|
||||
|
||||
|
||||
const adminRoutes = [
|
||||
@@ -35,5 +35,3 @@ const adminRoutes = [
|
||||
|
||||
export default adminRoutes
|
||||
//sourceMappingURL=index.ts.map
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Login from '../auth/login.vue';
|
||||
import Register from '../auth/register.vue';
|
||||
import changePassword from '../auth/changepassword.vue';
|
||||
import lostPassword from '../auth/lostpassword.vue';
|
||||
const Login = () => import('../auth/login.vue');
|
||||
const Register = () => import('../auth/register.vue');
|
||||
const changePassword = () => import('../auth/changepassword.vue');
|
||||
const lostPassword = () => import('../auth/lostpassword.vue');
|
||||
|
||||
const authRoutes = [
|
||||
{
|
||||
@@ -37,4 +37,4 @@ const authRoutes = [
|
||||
},
|
||||
]
|
||||
|
||||
export default authRoutes;
|
||||
export default authRoutes;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- src/pages/authorize/PaymentForm.vue -->
|
||||
<!-- <template>
|
||||
<template>
|
||||
<div class="container mx-auto p-4">
|
||||
<h1 class="text-2xl font-bold mb-4">HVAC Payment</h1>
|
||||
|
||||
@@ -87,89 +87,91 @@
|
||||
</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>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { AuthorizeTransaction } from '../../types/models'
|
||||
|
||||
const API_URL = 'http://localhost:8000/api'; // Your FastAPI backend URL
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
directCharge: {
|
||||
cardNumber: '',
|
||||
expirationDate: '',
|
||||
cvv: '',
|
||||
amount: 0,
|
||||
},
|
||||
authorization: {
|
||||
cardNumber: '',
|
||||
expirationDate: '',
|
||||
cvv: '',
|
||||
amount: 0,
|
||||
},
|
||||
capture: {
|
||||
amount: 0,
|
||||
},
|
||||
authorizedTransactionId: null,
|
||||
transactionResult: null,
|
||||
customerId: 1 // Assuming a customer with ID 1 exists for this example
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async submitDirectCharge() {
|
||||
try {
|
||||
const response = await axios.post(`${API_URL}/charge/?customer_id=${this.customerId}`, {
|
||||
card_number: this.directCharge.cardNumber,
|
||||
expiration_date: this.directCharge.expirationDate,
|
||||
cvv: this.directCharge.cvv,
|
||||
amount: this.directCharge.amount,
|
||||
transaction_type: 'charge'
|
||||
});
|
||||
this.transactionResult = response.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
this.transactionResult = { status: 'Error processing transaction' };
|
||||
}
|
||||
},
|
||||
async submitAuthorization() {
|
||||
try {
|
||||
const response = await axios.post(`${API_URL}/authorize/?customer_id=${this.customerId}`, {
|
||||
card_number: this.authorization.cardNumber,
|
||||
expiration_date: this.authorization.expirationDate,
|
||||
cvv: this.authorization.cvv,
|
||||
amount: this.authorization.amount,
|
||||
transaction_type: 'auth'
|
||||
});
|
||||
this.transactionResult = response.data;
|
||||
if (response.data.status === 'authorized') {
|
||||
this.authorizedTransactionId = response.data.auth_net_transaction_id;
|
||||
this.capture.amount = this.authorization.amount; // Pre-fill capture amount
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
this.transactionResult = { status: 'Error processing authorization' };
|
||||
}
|
||||
},
|
||||
async submitCapture() {
|
||||
try {
|
||||
const response = await axios.post(`${API_URL}/capture/`, {
|
||||
amount: this.capture.amount,
|
||||
auth_net_transaction_id: this.authorizedTransactionId
|
||||
});
|
||||
this.transactionResult = response.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
this.transactionResult = { status: 'Error processing capture' };
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script> -->
|
||||
// Reactive data
|
||||
const directCharge = ref({
|
||||
cardNumber: '',
|
||||
expirationDate: '',
|
||||
cvv: '',
|
||||
amount: 0,
|
||||
})
|
||||
|
||||
const authorization = ref({
|
||||
cardNumber: '',
|
||||
expirationDate: '',
|
||||
cvv: '',
|
||||
amount: 0,
|
||||
})
|
||||
|
||||
const capture = ref({
|
||||
amount: 0,
|
||||
})
|
||||
|
||||
const authorizedTransactionId = ref<string | null>(null)
|
||||
const transactionResult = ref<AuthorizeTransaction | null>(null)
|
||||
const customerId = ref(1) // Assuming a customer with ID 1 exists for this example
|
||||
|
||||
// Functions
|
||||
const submitDirectCharge = async () => {
|
||||
try {
|
||||
const response = await axios.post(`${API_URL}/charge/?customer_id=${customerId.value}`, {
|
||||
card_number: directCharge.value.cardNumber,
|
||||
expiration_date: directCharge.value.expirationDate,
|
||||
cvv: directCharge.value.cvv,
|
||||
amount: directCharge.value.amount,
|
||||
transaction_type: 'charge'
|
||||
});
|
||||
transactionResult.value = response.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
transactionResult.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
const submitAuthorization = async () => {
|
||||
try {
|
||||
const response = await axios.post(`${API_URL}/authorize/?customer_id=${customerId.value}`, {
|
||||
card_number: authorization.value.cardNumber,
|
||||
expiration_date: authorization.value.expirationDate,
|
||||
cvv: authorization.value.cvv,
|
||||
amount: authorization.value.amount,
|
||||
transaction_type: 'auth'
|
||||
});
|
||||
transactionResult.value = response.data;
|
||||
if (response.data.status === 'authorized') {
|
||||
authorizedTransactionId.value = response.data.auth_net_transaction_id;
|
||||
capture.value.amount = authorization.value.amount; // Pre-fill capture amount
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
transactionResult.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
const submitCapture = async () => {
|
||||
try {
|
||||
const response = await axios.post(`${API_URL}/capture/`, {
|
||||
amount: capture.value.amount,
|
||||
auth_net_transaction_id: authorizedTransactionId.value
|
||||
});
|
||||
transactionResult.value = response.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
transactionResult.value = null;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -177,138 +177,118 @@
|
||||
</div>
|
||||
<Footer />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../services/auth.header'
|
||||
import { AutoDelivery } from '../../types/models'
|
||||
import Footer from '../../layouts/footers/footer.vue'
|
||||
|
||||
// Define a type for the delivery object for better code quality
|
||||
interface AutoDelivery {
|
||||
id: number;
|
||||
customer_id: number;
|
||||
last_fill: string | null;
|
||||
estimated_gallons_left: number;
|
||||
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;
|
||||
// Reactive data
|
||||
const user = ref(null)
|
||||
const deliveries = ref<AutoDelivery[]>([])
|
||||
// --- NEW: Data properties for sorting ---
|
||||
const sortKey = ref<'tank_level_percent' | 'hot_water_summer' | keyof AutoDelivery>('tank_level_percent')
|
||||
const sortAsc = ref(true)
|
||||
|
||||
// Computed properties
|
||||
// --- NEW: Computed property to handle sorting ---
|
||||
const sortedDeliveries = computed((): AutoDelivery[] => {
|
||||
// Create a copy to avoid mutating the original array
|
||||
const sorted = [...deliveries.value];
|
||||
|
||||
sorted.sort((a, b) => {
|
||||
// First, prioritize auto_status = 3 to be at the top
|
||||
if (a.auto_status === 3 && b.auto_status !== 3) {
|
||||
return -1;
|
||||
}
|
||||
if (a.auto_status !== 3 && b.auto_status === 3) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Then sort by the selected key
|
||||
let valA: any;
|
||||
let valB: any;
|
||||
|
||||
// Special case for our calculated percentage
|
||||
if (sortKey.value === 'tank_level_percent') {
|
||||
valA = getTankLevelPercentage(a);
|
||||
valB = getTankLevelPercentage(b);
|
||||
} else {
|
||||
valA = a[sortKey.value as keyof AutoDelivery];
|
||||
valB = b[sortKey.value as keyof AutoDelivery];
|
||||
// Special handling for hot_water_summer to ensure it's number
|
||||
if (sortKey.value === 'hot_water_summer') {
|
||||
valA = valA || 0;
|
||||
valB = valB || 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle nulls or different types if necessary
|
||||
if (valA === null) return 1;
|
||||
if (valB === null) return -1;
|
||||
|
||||
// Comparison logic
|
||||
if (valA < valB) {
|
||||
return sortAsc.value ? -1 : 1;
|
||||
}
|
||||
if (valA > valB) {
|
||||
return sortAsc.value ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return sorted;
|
||||
})
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus();
|
||||
get_oil_orders();
|
||||
})
|
||||
|
||||
// Functions
|
||||
// --- 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
|
||||
sortAsc.value = !sortAsc.value;
|
||||
} else {
|
||||
// If clicking a new key, set it and default to ascending
|
||||
sortKey.value = key;
|
||||
sortAsc.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AutomaticHome',
|
||||
components: {
|
||||
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
|
||||
const sorted = [...this.deliveries];
|
||||
// --- NEW: Helper method for percentage calculation ---
|
||||
const getTankLevelPercentage = (oil: AutoDelivery): number => {
|
||||
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 (oil.estimated_gallons_left / oil.tank_size) * 100;
|
||||
}
|
||||
|
||||
sorted.sort((a, b) => {
|
||||
// First, prioritize auto_status = 3 to be at the top
|
||||
if (a.auto_status === 3 && b.auto_status !== 3) {
|
||||
return -1;
|
||||
}
|
||||
if (a.auto_status !== 3 && b.auto_status === 3) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Then sort by the selected key
|
||||
let valA: any;
|
||||
let valB: any;
|
||||
|
||||
// Special case for our calculated percentage
|
||||
if (this.sortKey === 'tank_level_percent') {
|
||||
valA = this.getTankLevelPercentage(a);
|
||||
valB = this.getTankLevelPercentage(b);
|
||||
} else {
|
||||
valA = a[this.sortKey as keyof AutoDelivery];
|
||||
valB = b[this.sortKey as keyof AutoDelivery];
|
||||
// Special handling for hot_water_summer to ensure it's number
|
||||
if (this.sortKey === 'hot_water_summer') {
|
||||
valA = valA || 0;
|
||||
valB = valB || 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle nulls or different types if necessary
|
||||
if (valA === null) return 1;
|
||||
if (valB === null) return -1;
|
||||
|
||||
// Comparison logic
|
||||
if (valA < valB) {
|
||||
return this.sortAsc ? -1 : 1;
|
||||
}
|
||||
if (valA > valB) {
|
||||
return this.sortAsc ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return sorted;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.userStatus();
|
||||
this.get_oil_orders();
|
||||
},
|
||||
methods: {
|
||||
// --- NEW: Method to handle sorting ---
|
||||
sortBy(key: keyof AutoDelivery | 'tank_level_percent' | 'hot_water_summer') {
|
||||
if (this.sortKey === key) {
|
||||
// If clicking the same key, reverse the direction
|
||||
this.sortAsc = !this.sortAsc;
|
||||
} else {
|
||||
// If clicking a new key, set it and default to ascending
|
||||
this.sortKey = key;
|
||||
this.sortAsc = true;
|
||||
const userStatus = () => {
|
||||
const path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
}
|
||||
},
|
||||
// --- NEW: Helper method for percentage calculation ---
|
||||
getTankLevelPercentage(oil: AutoDelivery): number {
|
||||
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 (oil.estimated_gallons_left / oil.tank_size) * 100;
|
||||
},
|
||||
userStatus() {
|
||||
const path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null;
|
||||
});
|
||||
},
|
||||
get_oil_orders() {
|
||||
})
|
||||
.catch(() => {
|
||||
user.value = null;
|
||||
});
|
||||
}
|
||||
|
||||
const get_oil_orders = () => {
|
||||
const path = import.meta.env.VITE_AUTO_URL + '/delivery/all/customers';
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
this.deliveries = response.data;
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error("Failed to fetch automatic deliveries:", error);
|
||||
});
|
||||
},
|
||||
},
|
||||
})
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
deliveries.value = response.data;
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error("Failed to fetch automatic deliveries:", error);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
|
||||
import AutomaticHome from './home.vue';
|
||||
import AutomaticView from './view.vue';
|
||||
const AutomaticHome = () => import('./home.vue');
|
||||
const AutomaticView = () => import('./view.vue');
|
||||
|
||||
const autoRoutes = [
|
||||
{
|
||||
|
||||
@@ -54,26 +54,26 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Payment Status Card -->
|
||||
<div class="bg-neutral rounded-lg p-5">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="col-span-2">
|
||||
<div class="font-bold text-sm">Payment Status</div>
|
||||
<div class="badge badge-lg"
|
||||
:class="{
|
||||
'badge-success': autoTicket.payment_status == 3,
|
||||
'badge-info': autoTicket.payment_status == 1,
|
||||
'badge-error': autoTicket.payment_status == 0 || autoTicket.payment_status == null,
|
||||
'badge-warning': [2, 4].includes(autoTicket.payment_status)
|
||||
}">
|
||||
<span v-if="autoTicket.payment_status == 0 || autoTicket.payment_status == null">Unpaid</span>
|
||||
<span v-else-if="autoTicket.payment_status == 1">Pre-authorized</span>
|
||||
<span v-else-if="autoTicket.payment_status == 2">Processing</span>
|
||||
<span v-else-if="autoTicket.payment_status == 3">Paid</span>
|
||||
<span v-else-if="autoTicket.payment_status == 4">Failed</span>
|
||||
<span v-else>Unknown</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Payment Status Card -->
|
||||
<div class="bg-neutral rounded-lg p-5">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="col-span-2">
|
||||
<div class="font-bold text-sm">Payment Status</div>
|
||||
<div class="badge badge-lg"
|
||||
:class="{
|
||||
'badge-success': autoTicket.payment_status === PAYMENT_STATUS.PAID,
|
||||
'badge-info': autoTicket.payment_status === PAYMENT_STATUS.PRE_AUTHORIZED,
|
||||
'badge-error': autoTicket.payment_status === PAYMENT_STATUS.UNPAID || autoTicket.payment_status == null,
|
||||
'badge-warning': [PAYMENT_STATUS.PROCESSING, PAYMENT_STATUS.FAILED].includes(autoTicket.payment_status)
|
||||
}">
|
||||
<span v-if="autoTicket.payment_status === PAYMENT_STATUS.UNPAID || autoTicket.payment_status == null">Unpaid</span>
|
||||
<span v-else-if="autoTicket.payment_status === PAYMENT_STATUS.PRE_AUTHORIZED">Pre-authorized</span>
|
||||
<span v-else-if="autoTicket.payment_status === PAYMENT_STATUS.PROCESSING">Processing</span>
|
||||
<span v-else-if="autoTicket.payment_status === PAYMENT_STATUS.PAID">Paid</span>
|
||||
<span v-else-if="autoTicket.payment_status === PAYMENT_STATUS.FAILED">Failed</span>
|
||||
<span v-else>Unknown</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-bold text-sm">Fill Date</div>
|
||||
<div>{{ autoTicket.fill_date }}</div>
|
||||
@@ -150,8 +150,8 @@
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>Status:</span>
|
||||
<span :class="transaction.status === 0 ? 'text-success' : 'text-error'">
|
||||
{{ transaction.status === 0 ? 'Approved' : 'Declined' }}
|
||||
<span :class="transaction.status === TRANSACTION_STATUS.APPROVED ? 'text-success' : 'text-error'">
|
||||
{{ transaction.status === TRANSACTION_STATUS.APPROVED ? 'Approved' : 'Declined' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -242,25 +242,21 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../services/auth.header'
|
||||
|
||||
interface UserCard {
|
||||
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 {
|
||||
PAYMENT_STATUS,
|
||||
AUTO_STATUS,
|
||||
TRANSACTION_STATUS,
|
||||
getPaymentStatusLabel,
|
||||
getTransactionStatusLabel
|
||||
} from '../../constants/status';
|
||||
|
||||
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 moment from 'moment';
|
||||
import dayjs from 'dayjs';
|
||||
import {AutoDelivery, Customer, AuthorizeTransaction, CreditCard} from '../../types/models';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'automaticDeliveryView',
|
||||
@@ -273,62 +269,25 @@ export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
v$: useValidate(),
|
||||
autoTicket: {
|
||||
id: 0,
|
||||
customer_id: 0,
|
||||
account_number: '',
|
||||
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,
|
||||
autoTicket: {} as any,
|
||||
autoDelivery: {} as AutoDelivery,
|
||||
customer: {} as Customer,
|
||||
transaction: {} as AuthorizeTransaction,
|
||||
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: {
|
||||
format_date(value: string) {
|
||||
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) {
|
||||
case 1: return 'text-blue-600'; // Auth
|
||||
case 0: return 'text-orange-600'; // Charge
|
||||
@@ -360,7 +320,7 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
|
||||
getCustomer(customerId: number) {
|
||||
getCustomer(customerId: number | undefined) {
|
||||
if (!customerId) return;
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/${customerId}`;
|
||||
axios.get(path, { withCredentials: true })
|
||||
@@ -384,12 +344,12 @@ export default defineComponent({
|
||||
this.userCard = response.data;
|
||||
this.userCardfound = true;
|
||||
} else {
|
||||
this.userCard = {} as UserCard;
|
||||
this.userCard = {} as CreditCard;
|
||||
this.userCardfound = false;
|
||||
}
|
||||
})
|
||||
.catch((_error: any) => {
|
||||
this.userCard = {} as UserCard;
|
||||
this.userCard = {} as CreditCard;
|
||||
this.userCardfound = false;
|
||||
});
|
||||
},
|
||||
@@ -438,7 +398,7 @@ export default defineComponent({
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error("No transaction found for delivery:", error);
|
||||
this.transaction = null;
|
||||
this.transaction = {} as AuthorizeTransaction;
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
|
||||
import CardHome from '../card/home.vue';
|
||||
import AddCardCreate from '../card/addcard.vue';
|
||||
import EditCard from "./editcard.vue";
|
||||
const CardHome = () => import('../card/home.vue');
|
||||
const AddCardCreate = () => import('../card/addcard.vue');
|
||||
const EditCard = () => import("./editcard.vue");
|
||||
|
||||
|
||||
const cardRoutes = [
|
||||
|
||||
@@ -120,193 +120,189 @@
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../services/auth.header'
|
||||
import { ServicePlan, Customer } from '../../types/models'
|
||||
import Footer from '../../layouts/footers/footer.vue'
|
||||
import { notify } from "@kyvg/vue3-notification";
|
||||
|
||||
interface ServicePlan {
|
||||
id: number;
|
||||
customer_id: number;
|
||||
contract_plan: number;
|
||||
contract_years: number;
|
||||
contract_start_date: string;
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
// Reactive data
|
||||
const customerId = ref(route.params.id as string)
|
||||
const customerName = ref('')
|
||||
const servicePlan = ref(null as ServicePlan | null)
|
||||
const formData = ref({
|
||||
contract_plan: 0,
|
||||
contract_years: 1,
|
||||
contract_start_date: '',
|
||||
})
|
||||
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];
|
||||
}
|
||||
})
|
||||
|
||||
// Functions
|
||||
const loadCustomer = async () => {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/${customerId.value}`;
|
||||
const response = await axios.get(path, { headers: authHeader() });
|
||||
const customer: Customer = response.data;
|
||||
customerName.value = `${customer.customer_first_name} ${customer.customer_last_name}`;
|
||||
} catch (error) {
|
||||
console.error('Failed to load customer:', error);
|
||||
notify({ title: "Error", text: "Failed to load customer information.", type: "error" });
|
||||
}
|
||||
}
|
||||
|
||||
interface Customer {
|
||||
id: number;
|
||||
customer_first_name: string;
|
||||
customer_last_name: string;
|
||||
const loadServicePlan = async () => {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/plans/customer/${customerId.value}`;
|
||||
const response = await axios.get(path, { headers: authHeader() });
|
||||
|
||||
if (response.data && response.data.contract_plan !== undefined) {
|
||||
servicePlan.value = response.data;
|
||||
formData.value = {
|
||||
contract_plan: response.data.contract_plan,
|
||||
contract_years: response.data.contract_years,
|
||||
contract_start_date: response.data.contract_start_date,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
// Plan doesn't exist yet, that's okay
|
||||
console.log('No existing service plan found');
|
||||
}
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ServicePlanEdit',
|
||||
components: { Footer },
|
||||
data() {
|
||||
return {
|
||||
customerId: this.$route.params.id as string,
|
||||
customerName: '',
|
||||
servicePlan: null as ServicePlan | null,
|
||||
formData: {
|
||||
const onSubmit = async () => {
|
||||
try {
|
||||
const payload = {
|
||||
customer_id: parseInt(customerId.value),
|
||||
...formData.value
|
||||
};
|
||||
|
||||
let response;
|
||||
if (servicePlan.value) {
|
||||
// Update existing plan
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/plans/update/${customerId.value}`;
|
||||
response = await axios.put(path, payload, { headers: authHeader() });
|
||||
} else {
|
||||
// Create new plan
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/plans/create`;
|
||||
response = await axios.post(path, payload, { headers: authHeader() });
|
||||
}
|
||||
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: `Service plan ${servicePlan.value ? 'updated' : 'created'} successfully!`,
|
||||
type: "success"
|
||||
});
|
||||
// Redirect to profile page after successful submission
|
||||
router.push({ name: 'customerProfile', params: { id: customerId.value } });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to save service plan:', error);
|
||||
notify({ title: "Error", text: "Failed to save service plan.", type: "error" });
|
||||
}
|
||||
}
|
||||
|
||||
const deletePlan = async () => {
|
||||
if (!confirm('Are you sure you want to delete this service plan?')) return;
|
||||
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/plans/delete/${customerId.value}`;
|
||||
const response = await axios.delete(path, { headers: authHeader() });
|
||||
|
||||
if (response.data.ok) {
|
||||
notify({ title: "Success", text: "Service plan deleted successfully!", type: "success" });
|
||||
servicePlan.value = null;
|
||||
formData.value = {
|
||||
contract_plan: 0,
|
||||
contract_years: 1,
|
||||
contract_start_date: '',
|
||||
},
|
||||
renewalDate: '',
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadCustomer();
|
||||
this.loadServicePlan();
|
||||
},
|
||||
methods: {
|
||||
async loadCustomer() {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/${this.customerId}`;
|
||||
const response = await axios.get(path, { headers: authHeader() });
|
||||
const customer: Customer = response.data;
|
||||
this.customerName = `${customer.customer_first_name} ${customer.customer_last_name}`;
|
||||
} catch (error) {
|
||||
console.error('Failed to load customer:', error);
|
||||
notify({ title: "Error", text: "Failed to load customer information.", type: "error" });
|
||||
}
|
||||
},
|
||||
|
||||
async loadServicePlan() {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/plans/customer/${this.customerId}`;
|
||||
const response = await axios.get(path, { headers: authHeader() });
|
||||
|
||||
if (response.data && response.data.contract_plan !== undefined) {
|
||||
this.servicePlan = response.data;
|
||||
this.formData = {
|
||||
contract_plan: response.data.contract_plan,
|
||||
contract_years: response.data.contract_years,
|
||||
contract_start_date: response.data.contract_start_date,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
// Plan doesn't exist yet, that's okay
|
||||
console.log('No existing service plan found');
|
||||
}
|
||||
},
|
||||
|
||||
async onSubmit() {
|
||||
try {
|
||||
const payload = {
|
||||
customer_id: parseInt(this.customerId),
|
||||
...this.formData
|
||||
};
|
||||
|
||||
let response;
|
||||
if (this.servicePlan) {
|
||||
// Update existing plan
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/plans/update/${this.customerId}`;
|
||||
response = await axios.put(path, payload, { headers: authHeader() });
|
||||
} else {
|
||||
// Create new plan
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/plans/create`;
|
||||
response = await axios.post(path, payload, { headers: authHeader() });
|
||||
}
|
||||
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: `Service plan ${this.servicePlan ? 'updated' : 'created'} successfully!`,
|
||||
type: "success"
|
||||
});
|
||||
// Redirect to profile page after successful submission
|
||||
this.$router.push({ name: 'customerProfile', params: { id: this.customerId } });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to save service plan:', error);
|
||||
notify({ title: "Error", text: "Failed to save service plan.", type: "error" });
|
||||
}
|
||||
},
|
||||
|
||||
async deletePlan() {
|
||||
if (!confirm('Are you sure you want to delete this service plan?')) return;
|
||||
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/plans/delete/${this.customerId}`;
|
||||
const response = await axios.delete(path, { headers: authHeader() });
|
||||
|
||||
if (response.data.ok) {
|
||||
notify({ title: "Success", text: "Service plan deleted successfully!", type: "success" });
|
||||
this.servicePlan = null;
|
||||
this.formData = {
|
||||
contract_plan: 0,
|
||||
contract_years: 1,
|
||||
contract_start_date: '',
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to delete service plan:', error);
|
||||
notify({ title: "Error", text: "Failed to delete service plan.", type: "error" });
|
||||
}
|
||||
},
|
||||
|
||||
renewContract() {
|
||||
if (!this.renewalDate) {
|
||||
notify({ title: "Error", text: "Please select a renewal date.", type: "error" });
|
||||
return;
|
||||
}
|
||||
|
||||
this.formData.contract_years += 1;
|
||||
this.formData.contract_start_date = this.renewalDate;
|
||||
this.renewalDate = '';
|
||||
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "Contract renewed! Years increased by 1 and start date updated.",
|
||||
type: "success"
|
||||
});
|
||||
},
|
||||
|
||||
getPlanName(planType: number): string {
|
||||
const planNames: { [key: number]: string } = {
|
||||
1: 'Standard Plan',
|
||||
2: 'Premium Plan'
|
||||
};
|
||||
return planNames[planType] || 'No Plan';
|
||||
},
|
||||
|
||||
formatEndDate(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];
|
||||
},
|
||||
|
||||
getStatusText(startDate: string, years: number): string {
|
||||
if (!startDate) return 'Unknown';
|
||||
const endDate = new Date(startDate);
|
||||
endDate.setFullYear(endDate.getFullYear() + years);
|
||||
const now = new Date();
|
||||
if (now > endDate) {
|
||||
return 'Expired';
|
||||
} else if (now > new Date(endDate.getTime() - 30 * 24 * 60 * 60 * 1000)) {
|
||||
return 'Expiring Soon';
|
||||
} else {
|
||||
return 'Active';
|
||||
}
|
||||
},
|
||||
|
||||
getStatusBadge(startDate: string, years: number): string {
|
||||
if (!startDate) return 'badge-ghost';
|
||||
const endDate = new Date(startDate);
|
||||
endDate.setFullYear(endDate.getFullYear() + years);
|
||||
const now = new Date();
|
||||
if (now > endDate) {
|
||||
return 'badge-error';
|
||||
} else if (now > new Date(endDate.getTime() - 30 * 24 * 60 * 60 * 1000)) {
|
||||
return 'badge-warning';
|
||||
} else {
|
||||
return 'badge-success';
|
||||
}
|
||||
}
|
||||
},
|
||||
} catch (error) {
|
||||
console.error('Failed to delete service plan:', error);
|
||||
notify({ title: "Error", text: "Failed to delete service plan.", type: "error" });
|
||||
}
|
||||
}
|
||||
|
||||
const renewContract = () => {
|
||||
if (!renewalDate.value) {
|
||||
notify({ title: "Error", text: "Please select a renewal date.", type: "error" });
|
||||
return;
|
||||
}
|
||||
|
||||
formData.value.contract_years += 1;
|
||||
formData.value.contract_start_date = renewalDate.value;
|
||||
renewalDate.value = '';
|
||||
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "Contract renewed! Years increased by 1 and start date updated.",
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
|
||||
const getPlanName = (planType: number): string => {
|
||||
const planNames: { [key: number]: string } = {
|
||||
1: 'Standard Plan',
|
||||
2: 'Premium Plan'
|
||||
};
|
||||
return planNames[planType] || 'No Plan';
|
||||
}
|
||||
|
||||
const formatEndDate = (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];
|
||||
}
|
||||
|
||||
const getStatusText = (startDate: string, years: number): string => {
|
||||
if (!startDate) return 'Unknown';
|
||||
const endDate = new Date(startDate);
|
||||
endDate.setFullYear(endDate.getFullYear() + years);
|
||||
const now = new Date();
|
||||
if (now > endDate) {
|
||||
return 'Expired';
|
||||
} else if (now > new Date(endDate.getTime() - 30 * 24 * 60 * 60 * 1000)) {
|
||||
return 'Expiring Soon';
|
||||
} else {
|
||||
return 'Active';
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusBadge = (startDate: string, years: number): string => {
|
||||
if (!startDate) return 'badge-ghost';
|
||||
const endDate = new Date(startDate);
|
||||
endDate.setFullYear(endDate.getFullYear() + years);
|
||||
const now = new Date();
|
||||
if (now > endDate) {
|
||||
return 'badge-error';
|
||||
} else if (now > new Date(endDate.getTime() - 30 * 24 * 60 * 60 * 1000)) {
|
||||
return 'badge-warning';
|
||||
} else {
|
||||
return 'badge-success';
|
||||
}
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
loadCustomer();
|
||||
loadServicePlan();
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -140,120 +140,114 @@
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../services/auth.header'
|
||||
import { StateOption, HomeTypeOption } from '../../types/models'
|
||||
import Footer from '../../layouts/footers/footer.vue'
|
||||
import useValidate from "@vuelidate/core";
|
||||
import { email, minLength, required } from "@vuelidate/validators";
|
||||
import { notify } from "@kyvg/vue3-notification";
|
||||
|
||||
interface SelectOption {
|
||||
text: string;
|
||||
value: number;
|
||||
// Reactive data
|
||||
const router = useRouter()
|
||||
const user = ref(null)
|
||||
const stateList = ref<StateOption[]>([])
|
||||
const custList = ref<HomeTypeOption[]>([])
|
||||
|
||||
// Form object
|
||||
const CreateCustomerForm = ref({
|
||||
customer_last_name: "",
|
||||
customer_first_name: "",
|
||||
customer_town: "",
|
||||
customer_address: "",
|
||||
customer_apt: "",
|
||||
customer_zip: "",
|
||||
customer_email: "",
|
||||
customer_phone_number: "",
|
||||
customer_description: "",
|
||||
customer_home_type: 0,
|
||||
customer_state: 0,
|
||||
})
|
||||
|
||||
// Validation rules
|
||||
const rules = {
|
||||
CreateCustomerForm: {
|
||||
customer_last_name: { required, minLength: minLength(1) },
|
||||
customer_first_name: { required, minLength: minLength(1) },
|
||||
customer_town: { required, minLength: minLength(1) },
|
||||
customer_zip: { required, minLength: minLength(5) },
|
||||
customer_email: { email },
|
||||
customer_phone_number: { required },
|
||||
customer_home_type: { required },
|
||||
customer_state: { required },
|
||||
customer_address: { required },
|
||||
},
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CustomerCreate',
|
||||
components: {
|
||||
Footer,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
v$: useValidate(),
|
||||
user: null,
|
||||
stateList: [] as SelectOption[],
|
||||
custList: [] as SelectOption[],
|
||||
// --- REFACTORED: Simplified, flat form object ---
|
||||
CreateCustomerForm: {
|
||||
customer_last_name: "",
|
||||
customer_first_name: "",
|
||||
customer_town: "",
|
||||
customer_address: "",
|
||||
customer_apt: "",
|
||||
customer_zip: "",
|
||||
customer_email: "",
|
||||
customer_phone_number: "",
|
||||
customer_description: "",
|
||||
// --- FIX: Initialized as numbers for proper v-model binding ---
|
||||
customer_home_type: 0,
|
||||
customer_state: 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
validations() {
|
||||
return {
|
||||
// --- REFACTORED: Validation rules point to the flat form object ---
|
||||
CreateCustomerForm: {
|
||||
customer_last_name: { required, minLength: minLength(1) },
|
||||
customer_first_name: { required, minLength: minLength(1) },
|
||||
customer_town: { required, minLength: minLength(1) },
|
||||
customer_zip: { required, minLength: minLength(5) },
|
||||
customer_email: { email }, // Optional, so only validate format
|
||||
customer_phone_number: { required },
|
||||
customer_home_type: { required },
|
||||
customer_state: { required },
|
||||
customer_address: { required },
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.userStatus();
|
||||
},
|
||||
mounted() {
|
||||
this.getCustomerTypeList();
|
||||
this.getStatesList();
|
||||
},
|
||||
methods: {
|
||||
acceptNumber() {
|
||||
const x = this.CreateCustomerForm.customer_phone_number.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
|
||||
if (x) {
|
||||
this.CreateCustomerForm.customer_phone_number = !x[2] ? x[1] : `(${x[1]}) ${x[2]}${x[3] ? `-${x[3]}` : ''}`;
|
||||
}
|
||||
},
|
||||
userStatus() {
|
||||
const path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) { this.user = response.data.user; }
|
||||
})
|
||||
.catch(() => { this.user = null; });
|
||||
},
|
||||
// Vuelidate instance
|
||||
const v$ = useValidate(rules, { CreateCustomerForm })
|
||||
|
||||
getCustomerTypeList() {
|
||||
const path = import.meta.env.VITE_BASE_URL + "/query/customertype";
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => { this.custList = response.data; });
|
||||
},
|
||||
getStatesList() {
|
||||
const path = import.meta.env.VITE_BASE_URL + "/query/states";
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => { this.stateList = response.data; });
|
||||
},
|
||||
CreateCustomer(payload: any) {
|
||||
const path = import.meta.env.VITE_BASE_URL + "/customer/create";
|
||||
axios.post(path, payload, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
const new_user_id = response.data.user.user_id;
|
||||
this.$router.push({ name: 'customerProfile', params: { id: new_user_id } });
|
||||
} else {
|
||||
notify({ title: "Error", text: response.data.error || "Failed to create customer.", type: "error" });
|
||||
}
|
||||
});
|
||||
},
|
||||
onSubmit() {
|
||||
this.v$.$validate(); // Trigger validation
|
||||
if (!this.v$.$error) {
|
||||
// If validation passes, submit the form
|
||||
this.CreateCustomer(this.CreateCustomerForm);
|
||||
// Functions
|
||||
const acceptNumber = () => {
|
||||
const x = CreateCustomerForm.value.customer_phone_number.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
|
||||
if (x) {
|
||||
CreateCustomerForm.value.customer_phone_number = !x[2] ? x[1] : `(${x[1]}) ${x[2]}${x[3] ? `-${x[3]}` : ''}`;
|
||||
}
|
||||
}
|
||||
|
||||
const userStatus = () => {
|
||||
const path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) { user.value = response.data.user; }
|
||||
})
|
||||
.catch(() => { user.value = null; });
|
||||
}
|
||||
|
||||
const getCustomerTypeList = () => {
|
||||
const path = import.meta.env.VITE_BASE_URL + "/query/customertype";
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => { custList.value = response.data; });
|
||||
}
|
||||
|
||||
const getStatesList = () => {
|
||||
const path = import.meta.env.VITE_BASE_URL + "/query/states";
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => { stateList.value = response.data; });
|
||||
}
|
||||
|
||||
const CreateCustomer = (payload: any) => {
|
||||
const path = import.meta.env.VITE_BASE_URL + "/customer/create";
|
||||
axios.post(path, payload, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
const new_user_id = response.data.user.user_id;
|
||||
router.push({ name: 'customerProfile', params: { id: new_user_id } });
|
||||
} else {
|
||||
// If validation fails, show a single notification
|
||||
notify({ title: "Validation Error", text: "Please fill out all required fields correctly.", type: "error" });
|
||||
console.log("Form validation failed.");
|
||||
notify({ title: "Error", text: response.data.error || "Failed to create customer.", type: "error" });
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
});
|
||||
}
|
||||
|
||||
const onSubmit = () => {
|
||||
v$.value.$validate(); // Trigger validation
|
||||
if (!v$.value.$error) {
|
||||
// If validation passes, submit the form
|
||||
CreateCustomer(CreateCustomerForm.value);
|
||||
} else {
|
||||
// If validation fails, show a single notification
|
||||
notify({ title: "Validation Error", text: "Please fill out all required fields correctly.", type: "error" });
|
||||
console.log("Form validation failed.");
|
||||
}
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus();
|
||||
getCustomerTypeList();
|
||||
getStatesList();
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -162,235 +162,227 @@
|
||||
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<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 { StateOption, HomeTypeOption } from '../../types/models'
|
||||
import Footer from '../../layouts/footers/footer.vue'
|
||||
import useValidate from "@vuelidate/core";
|
||||
import { email, minLength, required } from "@vuelidate/validators";
|
||||
|
||||
// --- NEW: Interface for select options for better type safety ---
|
||||
interface SelectOption {
|
||||
text: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CustomerEdit',
|
||||
|
||||
components: {
|
||||
// Removed unused Header and SideBar
|
||||
Footer,
|
||||
// Reactive data
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const user = ref(null)
|
||||
const stateList = ref<StateOption[]>([])
|
||||
const custList = ref<HomeTypeOption[]>([])
|
||||
const customer = ref({
|
||||
id: 0,
|
||||
user_id: 0,
|
||||
customer_first_name: '',
|
||||
customer_last_name: '',
|
||||
customer_town: '',
|
||||
customer_state: 0,
|
||||
customer_zip: '',
|
||||
customer_apt: '',
|
||||
customer_address: '',
|
||||
customer_home_type: 0,
|
||||
customer_phone_number: '',
|
||||
account_number: '',
|
||||
})
|
||||
const customerDescription = ref({
|
||||
id: 0,
|
||||
customer_id: 0,
|
||||
account_number: '',
|
||||
company_id: '',
|
||||
fill_location: 0,
|
||||
description: '',
|
||||
})
|
||||
const CreateCustomerForm = ref({
|
||||
basicInfo: {
|
||||
customer_last_name: "",
|
||||
customer_first_name: "",
|
||||
customer_town: "",
|
||||
customer_apt: "",
|
||||
customer_home_type: 0,
|
||||
customer_zip: "",
|
||||
customer_automatic: false,
|
||||
customer_email: "",
|
||||
customer_phone_number: "",
|
||||
customer_state: 0,
|
||||
customer_address: "",
|
||||
customer_description: "",
|
||||
customer_fill_location: 0,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
v$: useValidate(),
|
||||
user: null,
|
||||
|
||||
stateList: [] as SelectOption[],
|
||||
custList: [] as SelectOption[],
|
||||
customer: {
|
||||
id: 0,
|
||||
user_id: 0,
|
||||
customer_first_name: '',
|
||||
customer_last_name: '',
|
||||
customer_town: '',
|
||||
customer_state: 0,
|
||||
customer_zip: '',
|
||||
customer_apt: '',
|
||||
customer_address: '',
|
||||
customer_home_type: 0,
|
||||
customer_phone_number: '',
|
||||
account_number: '',
|
||||
},
|
||||
customerDescription: {
|
||||
id: 0,
|
||||
customer_id: 0,
|
||||
account_number: '',
|
||||
company_id: '',
|
||||
fill_location: 0,
|
||||
description: '',
|
||||
},
|
||||
CreateCustomerForm: {
|
||||
basicInfo: {
|
||||
customer_last_name: "",
|
||||
customer_first_name: "",
|
||||
customer_town: "",
|
||||
customer_apt: "",
|
||||
// --- FIX: Initialized as a number ---
|
||||
customer_home_type: 0,
|
||||
customer_zip: "",
|
||||
customer_automatic: false,
|
||||
customer_email: "",
|
||||
customer_phone_number: "",
|
||||
// --- FIX: Initialized as a number ---
|
||||
customer_state: 0,
|
||||
customer_address: "",
|
||||
customer_description: "",
|
||||
customer_fill_location: 0,
|
||||
},
|
||||
servicePlan: {
|
||||
contract_plan: 0,
|
||||
contract_years: 1,
|
||||
contract_start_date: "",
|
||||
},
|
||||
},
|
||||
renewalDate: "",
|
||||
}
|
||||
},
|
||||
validations() {
|
||||
return {
|
||||
CreateCustomerForm: {
|
||||
basicInfo: {
|
||||
customer_last_name: { required, minLength: minLength(1) },
|
||||
customer_first_name: { required, minLength: minLength(1) },
|
||||
customer_town: { required, minLength: minLength(1) },
|
||||
customer_home_type: { required },
|
||||
customer_zip: { required, minLength: minLength(5) },
|
||||
customer_email: { email }, // Removed required to match template label "Optional"
|
||||
customer_phone_number: { required },
|
||||
customer_state: { required },
|
||||
customer_address: { required },
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.userStatus()
|
||||
this.getCustomer(this.$route.params.id)
|
||||
},
|
||||
mounted() {
|
||||
this.getCustomerTypeList();
|
||||
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) {
|
||||
this.CreateCustomerForm.basicInfo.customer_phone_number = !x[2] ? x[1] : '(' + x[1] + ') ' + x[2] + (x[3] ? '-' + x[3] : '');
|
||||
} else {
|
||||
this.CreateCustomerForm.basicInfo.customer_phone_number = '';
|
||||
}
|
||||
},
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
},
|
||||
getCustomerDescription(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/description/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.customerDescription = response.data
|
||||
this.CreateCustomerForm.basicInfo.customer_description = this.customerDescription.description;
|
||||
this.CreateCustomerForm.basicInfo.customer_fill_location = this.customerDescription.fill_location
|
||||
})
|
||||
},
|
||||
getCustomer(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/" + userid;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
this.customer = response.data;
|
||||
this.getCustomerDescription(this.customer.id);
|
||||
this.CreateCustomerForm.basicInfo.customer_last_name = response.data.customer_last_name;
|
||||
this.CreateCustomerForm.basicInfo.customer_first_name = response.data.customer_first_name;
|
||||
this.CreateCustomerForm.basicInfo.customer_town = response.data.customer_town;
|
||||
this.CreateCustomerForm.basicInfo.customer_state = response.data.customer_state;
|
||||
this.CreateCustomerForm.basicInfo.customer_zip = response.data.customer_zip;
|
||||
this.CreateCustomerForm.basicInfo.customer_phone_number = response.data.customer_phone_number;
|
||||
this.CreateCustomerForm.basicInfo.customer_home_type = response.data.customer_home_type;
|
||||
this.CreateCustomerForm.basicInfo.customer_apt = response.data.customer_apt;
|
||||
this.CreateCustomerForm.basicInfo.customer_email = response.data.customer_email;
|
||||
this.CreateCustomerForm.basicInfo.customer_address = response.data.customer_address;
|
||||
|
||||
if (response.data.customer_automatic === 1) {
|
||||
this.CreateCustomerForm.basicInfo.customer_automatic = true
|
||||
}
|
||||
if (response.data.customer_automatic === 0) {
|
||||
this.CreateCustomerForm.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;
|
||||
axios({
|
||||
method: "put",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.$router.push({ name: "customerProfile", params: { id: this.customer.id }, query: { success: 'true' } });
|
||||
} else if (response.data.error) {
|
||||
// Handle specific errors if needed
|
||||
this.$router.push("/");
|
||||
}
|
||||
})
|
||||
},
|
||||
onSubmit() {
|
||||
// Create payload with both basic info and service plan data
|
||||
const payload = {
|
||||
...this.CreateCustomerForm.basicInfo,
|
||||
service_plan: this.CreateCustomerForm.servicePlan
|
||||
};
|
||||
this.editItem(payload);
|
||||
},
|
||||
getCustomerTypeList() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/query/customertype";
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => {
|
||||
this.custList = response.data;
|
||||
});
|
||||
},
|
||||
getStatesList() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/query/states";
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => {
|
||||
this.stateList = response.data;
|
||||
});
|
||||
},
|
||||
formatEndDate(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];
|
||||
},
|
||||
renewContract() {
|
||||
if (!this.renewalDate) {
|
||||
alert('Please select a renewal date');
|
||||
return;
|
||||
}
|
||||
this.CreateCustomerForm.servicePlan.contract_years += 1;
|
||||
this.CreateCustomerForm.servicePlan.contract_start_date = this.renewalDate;
|
||||
this.renewalDate = '';
|
||||
alert('Contract renewed! Years increased by 1 and start date updated.');
|
||||
},
|
||||
servicePlan: {
|
||||
contract_plan: 0,
|
||||
contract_years: 1,
|
||||
contract_start_date: "",
|
||||
},
|
||||
})
|
||||
const renewalDate = ref("")
|
||||
|
||||
// Validation rules
|
||||
const rules = {
|
||||
CreateCustomerForm: {
|
||||
basicInfo: {
|
||||
customer_last_name: { required, minLength: minLength(1) },
|
||||
customer_first_name: { required, minLength: minLength(1) },
|
||||
customer_town: { required, minLength: minLength(1) },
|
||||
customer_home_type: { required },
|
||||
customer_zip: { required, minLength: minLength(5) },
|
||||
customer_email: { email },
|
||||
customer_phone_number: { required },
|
||||
customer_state: { required },
|
||||
customer_address: { required },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Vuelidate instance
|
||||
const v$ = useValidate(rules, { CreateCustomerForm })
|
||||
|
||||
// Functions
|
||||
const acceptNumber = () => {
|
||||
let x = CreateCustomerForm.value.basicInfo.customer_phone_number.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
|
||||
if (x) {
|
||||
CreateCustomerForm.value.basicInfo.customer_phone_number = !x[2] ? x[1] : '(' + x[1] + ') ' + x[2] + (x[3] ? '-' + x[3] : '');
|
||||
} else {
|
||||
CreateCustomerForm.value.basicInfo.customer_phone_number = '';
|
||||
}
|
||||
}
|
||||
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
user.value = null
|
||||
})
|
||||
}
|
||||
|
||||
const getCustomerDescription = (userid: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/description/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
customerDescription.value = response.data
|
||||
CreateCustomerForm.value.basicInfo.customer_description = customerDescription.value.description;
|
||||
CreateCustomerForm.value.basicInfo.customer_fill_location = customerDescription.value.fill_location
|
||||
})
|
||||
}
|
||||
|
||||
const getCustomer = (userid: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/" + userid;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
customer.value = response.data;
|
||||
getCustomerDescription(customer.value.id);
|
||||
CreateCustomerForm.value.basicInfo.customer_last_name = response.data.customer_last_name;
|
||||
CreateCustomerForm.value.basicInfo.customer_first_name = response.data.customer_first_name;
|
||||
CreateCustomerForm.value.basicInfo.customer_town = response.data.customer_town;
|
||||
CreateCustomerForm.value.basicInfo.customer_state = response.data.customer_state;
|
||||
CreateCustomerForm.value.basicInfo.customer_zip = response.data.customer_zip;
|
||||
CreateCustomerForm.value.basicInfo.customer_phone_number = response.data.customer_phone_number;
|
||||
CreateCustomerForm.value.basicInfo.customer_home_type = response.data.customer_home_type;
|
||||
CreateCustomerForm.value.basicInfo.customer_apt = response.data.customer_apt;
|
||||
CreateCustomerForm.value.basicInfo.customer_email = response.data.customer_email;
|
||||
CreateCustomerForm.value.basicInfo.customer_address = response.data.customer_address;
|
||||
|
||||
if (response.data.customer_automatic === 1) {
|
||||
CreateCustomerForm.value.basicInfo.customer_automatic = true
|
||||
}
|
||||
if (response.data.customer_automatic === 0) {
|
||||
CreateCustomerForm.value.basicInfo.customer_automatic = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const editItem = (payload: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/edit/" + customer.value.id;
|
||||
axios({
|
||||
method: "put",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
router.push({ name: "customerProfile", params: { id: customer.value.id }, query: { success: 'true' } });
|
||||
} else if (response.data.error) {
|
||||
router.push("/");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const onSubmit = () => {
|
||||
const payload = {
|
||||
...CreateCustomerForm.value.basicInfo,
|
||||
service_plan: CreateCustomerForm.value.servicePlan
|
||||
};
|
||||
editItem(payload);
|
||||
}
|
||||
|
||||
const getCustomerTypeList = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/query/customertype";
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => {
|
||||
custList.value = response.data;
|
||||
});
|
||||
}
|
||||
|
||||
const getStatesList = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/query/states";
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => {
|
||||
stateList.value = response.data;
|
||||
});
|
||||
}
|
||||
|
||||
const formatEndDate = (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];
|
||||
}
|
||||
|
||||
const renewContract = () => {
|
||||
if (!renewalDate.value) {
|
||||
alert('Please select a renewal date');
|
||||
return;
|
||||
}
|
||||
CreateCustomerForm.value.servicePlan.contract_years += 1;
|
||||
CreateCustomerForm.value.servicePlan.contract_start_date = renewalDate.value;
|
||||
renewalDate.value = '';
|
||||
alert('Contract renewed! Years increased by 1 and start date updated.');
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus()
|
||||
getCustomer(route.params.id)
|
||||
getCustomerTypeList();
|
||||
getStatesList();
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -105,107 +105,92 @@
|
||||
</div>
|
||||
<Footer />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
|
||||
import authHeader from '../../services/auth.header'
|
||||
import { customerService } from '../../services/customerService'
|
||||
import { Customer } from '../../types/models'
|
||||
import Header from '../../layouts/headers/headerauth.vue'
|
||||
import PaginationComp from '../../components/pagination.vue'
|
||||
import SideBar from '../../layouts/sidebar/sidebar.vue'
|
||||
import Footer from '../../layouts/footers/footer.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CustomerHome',
|
||||
// Reactive data
|
||||
const token = ref(null)
|
||||
const user = ref(null)
|
||||
const customers = ref<Customer[]>([])
|
||||
const customer_count = ref(0)
|
||||
const page = ref(1)
|
||||
const perPage = ref(50)
|
||||
const recordsLength = ref(0)
|
||||
const options = ref({
|
||||
edgeNavigation: false,
|
||||
format: false,
|
||||
template: PaginationComp
|
||||
})
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
// Functions
|
||||
const getPage = (pageVal: any) => {
|
||||
customers.value = [];
|
||||
get_customers(pageVal)
|
||||
get_customer_count()
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
token: null,
|
||||
user: null,
|
||||
customers: [] as any[],
|
||||
customer_count: 0,
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
|
||||
options: {
|
||||
edgeNavigation: false,
|
||||
format: false,
|
||||
template: PaginationComp
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
user.value = null
|
||||
})
|
||||
}
|
||||
|
||||
const get_customers = async (pageVal: number) => {
|
||||
try {
|
||||
const response = await customerService.getAll(pageVal)
|
||||
customers.value = response.data || []
|
||||
} catch (error) {
|
||||
console.error('Error fetching customers:', error)
|
||||
customers.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const get_customer_count = async () => {
|
||||
try {
|
||||
const response = await customerService.getCount()
|
||||
if (response.data) {
|
||||
customer_count.value = response.data.count
|
||||
}
|
||||
},
|
||||
} catch (error) {
|
||||
console.error('Error fetching customer count:', error)
|
||||
}
|
||||
}
|
||||
|
||||
created() {
|
||||
this.userStatus()
|
||||
},
|
||||
mounted() {
|
||||
this.getPage(this.page)
|
||||
const deleteCustomer = (user_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/delete/' + user_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then(() => {
|
||||
get_customers(1)
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
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';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
},
|
||||
get_customers(page: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/all/' + page;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.customers = response.data
|
||||
})
|
||||
},
|
||||
get_customer_count() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/count';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.customer_count = response.data.count
|
||||
})
|
||||
},
|
||||
deleteCustomer(user_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/delete/' + user_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then(() => {
|
||||
|
||||
this.get_customers(1)
|
||||
})
|
||||
},
|
||||
},
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus()
|
||||
getPage(page.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
<tbody>
|
||||
<tr v-for="customer in customers" :key="customer.account_number">
|
||||
<td>{{ customer.account_number }}</td>
|
||||
<td>{{ customer.first_name }}</td>
|
||||
<td>{{ customer.last_name }}</td>
|
||||
<td>{{ customer.address }}</td>
|
||||
<td>{{ customer.town }}</td>
|
||||
<td>{{ customer.phone_number }}</td>
|
||||
<td>{{ customer.customer_first_name }}</td>
|
||||
<td>{{ customer.customer_last_name }}</td>
|
||||
<td>{{ customer.customer_address }}</td>
|
||||
<td>{{ customer.customer_town }}</td>
|
||||
<td>{{ customer.customer_phone_number }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -28,50 +28,38 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import axios from 'axios';
|
||||
import authHeader from '../../services/auth.header';
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../services/auth.header'
|
||||
import { Customer } from '../../types/models'
|
||||
|
||||
interface Customer {
|
||||
account_number: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
address: string;
|
||||
town: string;
|
||||
phone_number: string;
|
||||
// Reactive data
|
||||
const customers = ref<Customer[]>([])
|
||||
|
||||
// Functions
|
||||
const fetchCustomers = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/report/customers/list';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
customers.value = response.data.customers;
|
||||
}
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
console.error('Error fetching customer data:', error);
|
||||
});
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CustomerList',
|
||||
data() {
|
||||
return {
|
||||
customers: [] as Customer[]
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.fetchCustomers();
|
||||
},
|
||||
methods: {
|
||||
fetchCustomers() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/report/customers/list';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.customers = response.data.customers;
|
||||
}
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
console.error('Error fetching customer data:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
fetchCustomers();
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -76,8 +76,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
import dayjs from 'dayjs'
|
||||
@@ -101,158 +101,150 @@ interface FuelEstimation {
|
||||
last_fill?: string | null;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TankEstimation',
|
||||
props: {
|
||||
customerId: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
estimation: null as FuelEstimation | null,
|
||||
loading: true,
|
||||
error: null as string | null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchEstimation()
|
||||
},
|
||||
watch: {
|
||||
customerId: {
|
||||
handler(newId, oldId) {
|
||||
if (newId !== oldId) {
|
||||
console.log('Customer ID changed from', oldId, 'to', newId)
|
||||
this.fetchEstimation()
|
||||
}
|
||||
},
|
||||
immediate: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async fetchEstimation() {
|
||||
this.loading = true
|
||||
this.error = null
|
||||
this.estimation = null // Clear previous data
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
customerId: number
|
||||
}>()
|
||||
|
||||
try {
|
||||
console.log('Fetching estimation for customer ID:', this.customerId)
|
||||
// Reactive data
|
||||
const estimation = ref<FuelEstimation | null>(null)
|
||||
const loading = ref(true)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
// First check if customer is automatic
|
||||
const customerPath = `${import.meta.env.VITE_BASE_URL}/customer/${this.customerId}`
|
||||
console.log('Checking customer type:', customerPath)
|
||||
const customerResponse = await axios.get(customerPath, { headers: authHeader() })
|
||||
const isAutomatic = customerResponse.data.customer_automatic === 1
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
fetchEstimation()
|
||||
})
|
||||
|
||||
console.log('Customer automatic status:', isAutomatic, customerResponse.data)
|
||||
|
||||
let path: string
|
||||
if (isAutomatic) {
|
||||
// Fetch from automatic delivery API
|
||||
path = `${import.meta.env.VITE_AUTO_URL}/delivery/auto/customer/${this.customerId}`
|
||||
console.log('Fetching automatic data from:', path)
|
||||
} else {
|
||||
// Fetch from customer estimation API
|
||||
path = `${import.meta.env.VITE_AUTO_URL}/fixstuff_customer/estimate_gallons/customer/${this.customerId}`
|
||||
console.log('Fetching customer data from:', path)
|
||||
}
|
||||
|
||||
const response = await axios.get(path, { headers: authHeader() })
|
||||
console.log('API Response:', response.data)
|
||||
|
||||
if (response.data.error) {
|
||||
this.error = response.data.error
|
||||
console.error('API returned error:', response.data.error)
|
||||
} else {
|
||||
if (isAutomatic) {
|
||||
// Transform automatic delivery data to match our interface
|
||||
if (response.data && response.data.id) {
|
||||
const autoData = response.data
|
||||
console.log('Processing automatic data:', autoData)
|
||||
this.estimation = {
|
||||
id: autoData.id,
|
||||
customer_id: autoData.customer_id,
|
||||
total_deliveries: 0, // Not available in auto data
|
||||
customer_full_name: autoData.customer_full_name,
|
||||
account_number: autoData.account_number,
|
||||
address: autoData.customer_address,
|
||||
estimated_gallons: autoData.estimated_gallons_left,
|
||||
tank_size: autoData.tank_size,
|
||||
scaling_factor: autoData.house_factor,
|
||||
last_5_deliveries: [],
|
||||
last_fill: autoData.last_fill
|
||||
}
|
||||
console.log('Set automatic estimation:', this.estimation)
|
||||
} else {
|
||||
console.warn('No automatic delivery data found for customer', this.customerId)
|
||||
this.error = 'No automatic delivery data available'
|
||||
}
|
||||
} else {
|
||||
console.log('Setting customer estimation:', response.data)
|
||||
this.estimation = response.data
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Failed to fetch fuel estimation:', error)
|
||||
if (error.response?.status === 404) {
|
||||
this.error = 'Customer data not found'
|
||||
} else if (error.response?.data?.error) {
|
||||
this.error = error.response.data.error
|
||||
} else {
|
||||
this.error = 'Failed to load fuel estimation data'
|
||||
}
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
getTankLevelPercentage(): number {
|
||||
if (!this.estimation || !this.estimation.tank_size || this.estimation.tank_size === 0) {
|
||||
return 0
|
||||
}
|
||||
return (this.estimation.estimated_gallons / this.estimation.tank_size) * 100
|
||||
},
|
||||
|
||||
calculateDailyUsage(): string {
|
||||
if (!this.estimation || !this.estimation.scaling_factor) {
|
||||
return 'N/A'
|
||||
}
|
||||
// For a typical day with ~20 degree days (moderate winter day)
|
||||
const typicalDegreeDays = 20
|
||||
const dailyUsage = this.estimation.scaling_factor * typicalDegreeDays
|
||||
return dailyUsage.toFixed(1)
|
||||
},
|
||||
|
||||
formatScalingFactor(scalingFactor: number | null): string {
|
||||
if (scalingFactor === null || scalingFactor === undefined) {
|
||||
return 'N/A'
|
||||
}
|
||||
return scalingFactor.toFixed(2)
|
||||
},
|
||||
|
||||
getScalingFactorCategory(scalingFactor: number | null): string {
|
||||
if (scalingFactor === null || scalingFactor === undefined) {
|
||||
return 'N/A'
|
||||
}
|
||||
|
||||
if (scalingFactor < 0.05) {
|
||||
return 'Very Low'
|
||||
} else if (scalingFactor < 0.1) {
|
||||
return 'Low'
|
||||
} else if (scalingFactor < 0.2) {
|
||||
return 'Normal'
|
||||
} else if (scalingFactor < 0.5) {
|
||||
return 'High'
|
||||
} else {
|
||||
return 'Very High'
|
||||
}
|
||||
},
|
||||
|
||||
formatDate(dateString: string): string {
|
||||
if (!dateString) return 'N/A'
|
||||
return dayjs(dateString).format('MMM D, YYYY')
|
||||
}
|
||||
// Watchers
|
||||
watch(() => props.customerId, (newId, oldId) => {
|
||||
if (newId !== oldId) {
|
||||
console.log('Customer ID changed from', oldId, 'to', newId)
|
||||
fetchEstimation()
|
||||
}
|
||||
})
|
||||
|
||||
// Functions
|
||||
const fetchEstimation = async () => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
estimation.value = null // Clear previous data
|
||||
|
||||
try {
|
||||
console.log('Fetching estimation for customer ID:', props.customerId)
|
||||
|
||||
// First check if customer is automatic
|
||||
const customerPath = `${import.meta.env.VITE_BASE_URL}/customer/${props.customerId}`
|
||||
console.log('Checking customer type:', customerPath)
|
||||
const customerResponse = await axios.get(customerPath, { headers: authHeader() })
|
||||
const isAutomatic = customerResponse.data.customer_automatic === 1
|
||||
|
||||
console.log('Customer automatic status:', isAutomatic, customerResponse.data)
|
||||
|
||||
let path: string
|
||||
if (isAutomatic) {
|
||||
// Fetch from automatic delivery API
|
||||
path = `${import.meta.env.VITE_AUTO_URL}/delivery/auto/customer/${props.customerId}`
|
||||
console.log('Fetching automatic data from:', path)
|
||||
} else {
|
||||
// Fetch from customer estimation API
|
||||
path = `${import.meta.env.VITE_AUTO_URL}/fixstuff_customer/estimate_gallons/customer/${props.customerId}`
|
||||
console.log('Fetching customer data from:', path)
|
||||
}
|
||||
|
||||
const response = await axios.get(path, { headers: authHeader() })
|
||||
console.log('API Response:', response.data)
|
||||
|
||||
if (response.data.error) {
|
||||
error.value = response.data.error
|
||||
console.error('API returned error:', response.data.error)
|
||||
} else {
|
||||
if (isAutomatic) {
|
||||
// Transform automatic delivery data to match our interface
|
||||
if (response.data && response.data.id) {
|
||||
const autoData = response.data
|
||||
console.log('Processing automatic data:', autoData)
|
||||
estimation.value = {
|
||||
id: autoData.id,
|
||||
customer_id: autoData.customer_id,
|
||||
total_deliveries: 0, // Not available in auto data
|
||||
customer_full_name: autoData.customer_full_name,
|
||||
account_number: autoData.account_number,
|
||||
address: autoData.customer_address,
|
||||
estimated_gallons: autoData.estimated_gallons_left,
|
||||
tank_size: autoData.tank_size,
|
||||
scaling_factor: autoData.house_factor,
|
||||
last_5_deliveries: [],
|
||||
last_fill: autoData.last_fill
|
||||
}
|
||||
console.log('Set automatic estimation:', estimation.value)
|
||||
} else {
|
||||
console.warn('No automatic delivery data found for customer', props.customerId)
|
||||
error.value = 'No automatic delivery data available'
|
||||
}
|
||||
} else {
|
||||
console.log('Setting customer estimation:', response.data)
|
||||
estimation.value = response.data
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Failed to fetch fuel estimation:', error)
|
||||
if (error.response?.status === 404) {
|
||||
error.value = 'Customer data not found'
|
||||
} else if (error.response?.data?.error) {
|
||||
error.value = error.response.data.error
|
||||
} else {
|
||||
error.value = 'Failed to load fuel estimation data'
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const getTankLevelPercentage = (): number => {
|
||||
if (!estimation.value || !estimation.value.tank_size || estimation.value.tank_size === 0) {
|
||||
return 0
|
||||
}
|
||||
return (estimation.value.estimated_gallons / estimation.value.tank_size) * 100
|
||||
}
|
||||
|
||||
const calculateDailyUsage = (): string => {
|
||||
if (!estimation.value || !estimation.value.scaling_factor) {
|
||||
return 'N/A'
|
||||
}
|
||||
// For a typical day with ~20 degree days (moderate winter day)
|
||||
const typicalDegreeDays = 20
|
||||
const dailyUsage = estimation.value.scaling_factor * typicalDegreeDays
|
||||
return dailyUsage.toFixed(1)
|
||||
}
|
||||
|
||||
const formatScalingFactor = (scalingFactor: number | null): string => {
|
||||
if (scalingFactor === null || scalingFactor === undefined) {
|
||||
return 'N/A'
|
||||
}
|
||||
return scalingFactor.toFixed(2)
|
||||
}
|
||||
|
||||
const getScalingFactorCategory = (scalingFactor: number | null): string => {
|
||||
if (scalingFactor === null || scalingFactor === undefined) {
|
||||
return 'N/A'
|
||||
}
|
||||
|
||||
if (scalingFactor < 0.05) {
|
||||
return 'Very Low'
|
||||
} else if (scalingFactor < 0.1) {
|
||||
return 'Low'
|
||||
} else if (scalingFactor < 0.2) {
|
||||
return 'Normal'
|
||||
} else if (scalingFactor < 0.5) {
|
||||
return 'High'
|
||||
} else {
|
||||
return 'Very High'
|
||||
}
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string): string => {
|
||||
if (!dateString) return 'N/A'
|
||||
return dayjs(dateString).format('MMM D, YYYY')
|
||||
}
|
||||
</script>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -33,6 +33,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { AuthorizeTransaction } from '../../../../types/models';
|
||||
import DeliveriesTable from './DeliveriesTable.vue';
|
||||
import ServiceCallsTable from './ServiceCallsTable.vue';
|
||||
import TransactionsTable from './TransactionsTable.vue';
|
||||
@@ -69,26 +70,12 @@ interface ServiceCall {
|
||||
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
|
||||
interface Props {
|
||||
deliveries: Delivery[];
|
||||
autodeliveries: AutomaticDelivery[];
|
||||
serviceCalls: ServiceCall[];
|
||||
transactions: Transaction[];
|
||||
transactions: AuthorizeTransaction[];
|
||||
}
|
||||
|
||||
// 3. Use the typed defineProps
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<div><strong>Pre-Auth:</strong> ${{ transaction.preauthorize_amount || '0.00' }}</div>
|
||||
<div><strong>Charge:</strong> ${{ transaction.charge_amount || '0.00' }}</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>
|
||||
</td>
|
||||
<td>
|
||||
@@ -78,7 +78,7 @@
|
||||
<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>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>
|
||||
<!-- 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">
|
||||
@@ -103,29 +103,17 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
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;
|
||||
}
|
||||
import { AuthorizeTransaction } from '../../../../types/models';
|
||||
|
||||
interface Props {
|
||||
transactions: Transaction[];
|
||||
transactions: AuthorizeTransaction[];
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
const getStatusClass = (status: number) => status === 0 ? 'badge-success' : 'badge-error';
|
||||
const getStatusText = (status: number) => status === 0 ? 'Approved' : 'Declined';
|
||||
const getSourceText = (transaction: Transaction) => {
|
||||
const getSourceText = (transaction: AuthorizeTransaction) => {
|
||||
if (transaction.auto_id) {
|
||||
return 'Automatic';
|
||||
} else if (transaction.delivery_id) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
|
||||
|
||||
import CustomerHome from '../customer/home.vue';
|
||||
import CustomerCreate from '../customer/create.vue';
|
||||
import CustomerEdit from "../customer/edit.vue";
|
||||
import CustomerProfile from "./profile/profile.vue"
|
||||
import TankEdit from "./tank/edit.vue"
|
||||
const CustomerHome = () => import('../customer/home.vue');
|
||||
const CustomerCreate = () => import('../customer/create.vue');
|
||||
const CustomerEdit = () => import("../customer/edit.vue");
|
||||
const CustomerProfile = () => import("./profile/profile.vue")
|
||||
const TankEdit = () => import("./tank/edit.vue")
|
||||
|
||||
|
||||
const customerRoutes = [
|
||||
|
||||
@@ -96,8 +96,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
// Interfaces remain the same
|
||||
interface ServiceParts {
|
||||
@@ -116,92 +116,78 @@ interface NozzleParts {
|
||||
part3: string;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PartsEditModal',
|
||||
props: {
|
||||
existingParts: {
|
||||
type: Object as PropType<ServiceParts>,
|
||||
required: true
|
||||
},
|
||||
customerId: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['close-modal', 'save-parts'],
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
existingParts: ServiceParts
|
||||
customerId: number
|
||||
}>()
|
||||
|
||||
data() {
|
||||
return {
|
||||
// All reactive state goes inside the data object
|
||||
editableParts: {} as Partial<ServiceParts>,
|
||||
nozzle1: { part1: '', part2: '', part3: '' } as NozzleParts,
|
||||
nozzle2: { part1: '', part2: '', part3: '' } as NozzleParts,
|
||||
|
||||
// ======================================================
|
||||
// ============== THE UPDATED OIL FILTER LIST ==============
|
||||
// ======================================================
|
||||
oilFilterOptions: [
|
||||
'RF-1',
|
||||
'RF-4',
|
||||
'88CR',
|
||||
'Big Guy f80-24',
|
||||
'Garber Model R',
|
||||
'Garber Model M',
|
||||
'PurePro f100-10W'
|
||||
],
|
||||
|
||||
nozzlePart1Options: ['a', 'b', 'w'],
|
||||
nozzlePart2Options: 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()),
|
||||
};
|
||||
},
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'close-modal': []
|
||||
'save-parts': [parts: Partial<ServiceParts>]
|
||||
}>()
|
||||
|
||||
watch: {
|
||||
// Watch for changes to the `existingParts` prop
|
||||
existingParts: {
|
||||
handler(newVal) {
|
||||
if (!newVal) return;
|
||||
// Update the component's internal data when the prop changes
|
||||
this.editableParts = { ...newVal, customer_id: this.customerId };
|
||||
this.nozzle1 = this.splitNozzle(newVal.oil_nozzle);
|
||||
this.nozzle2 = this.splitNozzle(newVal.oil_nozzle_2);
|
||||
},
|
||||
immediate: true, // Run the handler immediately when the component is created
|
||||
deep: true, // Watch for nested changes within the object
|
||||
},
|
||||
},
|
||||
// Reactive data
|
||||
const editableParts = ref({} as Partial<ServiceParts>)
|
||||
const nozzle1 = ref({ part1: '', part2: '', part3: '' } as NozzleParts)
|
||||
const nozzle2 = ref({ part1: '', part2: '', part3: '' } as NozzleParts)
|
||||
|
||||
methods: {
|
||||
// Function to split a combined nozzle string (e.g., "a 55 75") into its parts
|
||||
splitNozzle(nozzleStr: string | undefined): NozzleParts {
|
||||
if (!nozzleStr || typeof nozzleStr !== 'string' || nozzleStr.trim() === '') {
|
||||
return { part1: '', part2: '', part3: '' };
|
||||
}
|
||||
const parts = nozzleStr.split(' ');
|
||||
return {
|
||||
part1: parts[0] || '',
|
||||
part2: parts[1] || '',
|
||||
part3: parts[2] || '',
|
||||
};
|
||||
},
|
||||
// ======================================================
|
||||
// ============== THE UPDATED OIL FILTER LIST ==============
|
||||
// ======================================================
|
||||
const oilFilterOptions = ref([
|
||||
'RF-1',
|
||||
'RF-4',
|
||||
'88CR',
|
||||
'Big Guy f80-24',
|
||||
'Garber Model R',
|
||||
'Garber Model M',
|
||||
'PurePro f100-10W'
|
||||
])
|
||||
|
||||
// Function to combine nozzle parts into a single string
|
||||
combineNozzle(parts: NozzleParts): string {
|
||||
if (parts.part1 && parts.part2 && parts.part3) {
|
||||
return `${parts.part1} ${parts.part2} ${parts.part3}`;
|
||||
}
|
||||
return ''; // Return empty if any part is missing
|
||||
},
|
||||
const nozzlePart1Options = ref(['a', 'b', 'w'])
|
||||
const nozzlePart2Options = ref(Array.from({ length: (200 - 45) / 5 + 1 }, (_, i) => (45 + i * 5).toString()))
|
||||
const nozzlePart3Options = ref(Array.from({ length: (100 - 45) / 5 + 1 }, (_, i) => (45 + i * 5).toString()))
|
||||
|
||||
// Form submission handler
|
||||
saveChanges() {
|
||||
// Before emitting, combine the nozzle parts back into a single string
|
||||
this.editableParts.oil_nozzle = this.combineNozzle(this.nozzle1);
|
||||
this.editableParts.oil_nozzle_2 = this.combineNozzle(this.nozzle2);
|
||||
|
||||
// Emit the save event with the final payload
|
||||
this.$emit('save-parts', this.editableParts);
|
||||
},
|
||||
},
|
||||
});
|
||||
// Watchers
|
||||
watch(() => props.existingParts, (newVal) => {
|
||||
if (!newVal) return;
|
||||
// Update the component's internal data when the prop changes
|
||||
editableParts.value = { ...newVal, customer_id: props.customerId };
|
||||
nozzle1.value = splitNozzle(newVal.oil_nozzle);
|
||||
nozzle2.value = splitNozzle(newVal.oil_nozzle_2);
|
||||
}, { immediate: true, deep: true })
|
||||
|
||||
// Functions
|
||||
// Function to split a combined nozzle string (e.g., "a 55 75") into its parts
|
||||
const splitNozzle = (nozzleStr: string | undefined): NozzleParts => {
|
||||
if (!nozzleStr || typeof nozzleStr !== 'string' || nozzleStr.trim() === '') {
|
||||
return { part1: '', part2: '', part3: '' };
|
||||
}
|
||||
const parts = nozzleStr.split(' ');
|
||||
return {
|
||||
part1: parts[0] || '',
|
||||
part2: parts[1] || '',
|
||||
part3: parts[2] || '',
|
||||
};
|
||||
}
|
||||
|
||||
// Function to combine nozzle parts into a single string
|
||||
const combineNozzle = (parts: NozzleParts): string => {
|
||||
if (parts.part1 && parts.part2 && parts.part3) {
|
||||
return `${parts.part1} ${parts.part2} ${parts.part3}`;
|
||||
}
|
||||
return ''; // Return empty if any part is missing
|
||||
}
|
||||
|
||||
// Form submission handler
|
||||
const saveChanges = () => {
|
||||
// Before emitting, combine the nozzle parts back into a single string
|
||||
editableParts.value.oil_nozzle = combineNozzle(nozzle1.value);
|
||||
editableParts.value.oil_nozzle_2 = combineNozzle(nozzle2.value);
|
||||
|
||||
// Emit the save event with the final payload
|
||||
emit('save-parts', editableParts.value);
|
||||
}
|
||||
</script>```
|
||||
|
||||
@@ -85,8 +85,9 @@
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<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 Footer from '../../../layouts/footers/footer.vue'
|
||||
@@ -100,88 +101,89 @@ interface TankFormData {
|
||||
fill_location: string;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TankEdit',
|
||||
components: {
|
||||
Footer,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
user: null as any,
|
||||
customer: {} as any,
|
||||
// --- REFACTORED: Simplified, flat form object ---
|
||||
TankForm: {
|
||||
last_tank_inspection: null,
|
||||
tank_status: true,
|
||||
outside_or_inside: true,
|
||||
tank_size: 0,
|
||||
fill_location: '',
|
||||
} as TankFormData,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
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';
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => { this.user = null; });
|
||||
},
|
||||
getCustomer(userid: any) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/${userid}`;
|
||||
axios.get(path, { headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
this.customer = response.data;
|
||||
});
|
||||
},
|
||||
getCustomerDescription(userid: any) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/description/${userid}`;
|
||||
axios.get(path, { headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
// Only update fill_location if the response has it
|
||||
if (response.data && response.data.fill_location) {
|
||||
this.TankForm.fill_location = response.data.fill_location;
|
||||
}
|
||||
});
|
||||
},
|
||||
getTank(customer_id: any) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/tank/${customer_id}`;
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
// Update the form model with data from the tank endpoint
|
||||
this.TankForm.last_tank_inspection = response.data.last_tank_inspection;
|
||||
this.TankForm.tank_status = response.data.tank_status;
|
||||
this.TankForm.outside_or_inside = response.data.outside_or_inside;
|
||||
this.TankForm.tank_size = response.data.tank_size;
|
||||
}
|
||||
});
|
||||
},
|
||||
editTank(payload: TankFormData) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/edit/tank/${this.$route.params.id}`;
|
||||
axios.put(path, payload, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } });
|
||||
} else {
|
||||
console.error("Failed to edit tank:", response.data.error);
|
||||
}
|
||||
});
|
||||
},
|
||||
onSubmit() {
|
||||
// The payload is simply the entire form object now
|
||||
this.editTank(this.TankForm);
|
||||
},
|
||||
},
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
// Reactive data
|
||||
const user = ref(null as any)
|
||||
const customer = ref({} as any)
|
||||
// --- REFACTORED: Simplified, flat form object ---
|
||||
const TankForm = ref({
|
||||
last_tank_inspection: null,
|
||||
tank_status: true,
|
||||
outside_or_inside: true,
|
||||
tank_size: 0,
|
||||
fill_location: '',
|
||||
} as TankFormData)
|
||||
|
||||
// Functions
|
||||
const userStatus = () => {
|
||||
const path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => { user.value = null; });
|
||||
}
|
||||
|
||||
const getCustomer = (userid: any) => {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/${userid}`;
|
||||
axios.get(path, { headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
customer.value = response.data;
|
||||
});
|
||||
}
|
||||
|
||||
const getCustomerDescription = (userid: any) => {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/description/${userid}`;
|
||||
axios.get(path, { headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
// Only update fill_location if the response has it
|
||||
if (response.data && response.data.fill_location) {
|
||||
TankForm.value.fill_location = response.data.fill_location;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const getTank = (customer_id: any) => {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/tank/${customer_id}`;
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
// Update the form model with data from the tank endpoint
|
||||
TankForm.value.last_tank_inspection = response.data.last_tank_inspection;
|
||||
TankForm.value.tank_status = response.data.tank_status;
|
||||
TankForm.value.outside_or_inside = response.data.outside_or_inside;
|
||||
TankForm.value.tank_size = response.data.tank_size;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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() })
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
router.push({ name: "customerProfile", params: { id: customer.value.id } });
|
||||
} else {
|
||||
console.error("Failed to edit tank:", response.data.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const onSubmit = () => {
|
||||
// The payload is simply the entire form object now
|
||||
editTank(TankForm.value);
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus();
|
||||
const customerId = route.params.id;
|
||||
getCustomer(customerId);
|
||||
getCustomerDescription(customerId);
|
||||
getTank(customerId);
|
||||
})
|
||||
</script>
|
||||
@@ -229,7 +229,7 @@
|
||||
<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.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>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 mt-2">
|
||||
@@ -305,43 +305,21 @@
|
||||
|
||||
<Footer />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, watch, nextTick } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../services/auth.header'
|
||||
import { Customer, CreditCard, CreateCardRequest } 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 SideBar from '../../../layouts/sidebar/sidebar.vue'
|
||||
import Footer from '../../../layouts/footers/footer.vue'
|
||||
import { useVuelidate } from "@vuelidate/core";
|
||||
import { notify } from "@kyvg/vue3-notification"
|
||||
import { minLength, required, requiredIf } from "@vuelidate/validators";
|
||||
|
||||
// --- TYPE DEFINITIONS (MODIFIED) ---
|
||||
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 Driver { id: number; employee_first_name: string; employee_last_name: string; }
|
||||
interface PricingTier { gallons: number | string; price: number | string; }
|
||||
@@ -370,363 +348,384 @@ interface CardFormData {
|
||||
type_of_card: string;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'deliveryCreate',
|
||||
components: { Header, SideBar, Footer },
|
||||
data() {
|
||||
return {
|
||||
v$: useValidate(),
|
||||
user: null as any,
|
||||
customer: {} as any,
|
||||
isLoading: false, // For main form submission
|
||||
isCardSaving: false, // For quick add card form
|
||||
quickGallonAmounts: [100, 125, 150, 175, 200, 220],
|
||||
userCards: [] as UserCard[],
|
||||
promos: [] as Promo[],
|
||||
truckDriversList: [] as Driver[],
|
||||
pricingTiers: [] as PricingTier[],
|
||||
isLoadingAuthorize: true,
|
||||
authorizeCheck: { profile_exists: false, has_payment_methods: false, missing_components: [] as string[], valid_for_charging: false },
|
||||
isConfirmationModalVisible: false,
|
||||
formDelivery: {
|
||||
gallons_ordered: '',
|
||||
customer_asked_for_fill: false,
|
||||
expected_delivery_date: '',
|
||||
dispatcher_notes_taken: '',
|
||||
prime: false,
|
||||
emergency: false,
|
||||
same_day: false,
|
||||
credit: false,
|
||||
cash: false,
|
||||
check: false,
|
||||
other: false,
|
||||
credit_card_id: 0,
|
||||
promo_id: 0,
|
||||
driver_employee_id: 0,
|
||||
} as DeliveryFormData,
|
||||
// Simplified formCard data
|
||||
formCard: {
|
||||
card_number: '',
|
||||
expiration_month: '',
|
||||
expiration_year: '',
|
||||
security_number: '',
|
||||
card_name: '',
|
||||
type_of_card: '',
|
||||
} as CardFormData,
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
}
|
||||
// Reactive data
|
||||
const user = ref(null as any)
|
||||
const customer = ref({} as any)
|
||||
const isLoading = ref(false) // For main form submission
|
||||
const isCardSaving = ref(false) // For quick add card form
|
||||
const quickGallonAmounts = ref([100, 125, 150, 175, 200, 220])
|
||||
const userCards = ref<CreditCard[]>([])
|
||||
const promos = ref([] as Promo[])
|
||||
const truckDriversList = ref([] as Driver[])
|
||||
const pricingTiers = ref([] as PricingTier[])
|
||||
const isLoadingAuthorize = ref(true)
|
||||
const authorizeCheck = ref({ profile_exists: false, has_payment_methods: false, missing_components: [] as string[], valid_for_charging: false })
|
||||
const isConfirmationModalVisible = ref(false)
|
||||
const formDelivery = ref({
|
||||
gallons_ordered: '',
|
||||
customer_asked_for_fill: false,
|
||||
expected_delivery_date: '',
|
||||
dispatcher_notes_taken: '',
|
||||
prime: false,
|
||||
emergency: false,
|
||||
same_day: false,
|
||||
credit: false,
|
||||
cash: false,
|
||||
check: false,
|
||||
other: false,
|
||||
credit_card_id: 0,
|
||||
promo_id: 0,
|
||||
driver_employee_id: 0,
|
||||
} as DeliveryFormData)
|
||||
// Simplified formCard data
|
||||
const formCard = ref({
|
||||
card_number: '',
|
||||
expiration_month: '',
|
||||
expiration_year: '',
|
||||
security_number: '',
|
||||
card_name: '',
|
||||
type_of_card: '',
|
||||
} as CardFormData)
|
||||
|
||||
// Computed
|
||||
const preAuthAmount = computed((): number => {
|
||||
if (!formDelivery.value.credit || formDelivery.value.customer_asked_for_fill) return 0;
|
||||
const gallons = Number(formDelivery.value.gallons_ordered);
|
||||
if (isNaN(gallons) || gallons <= 0 || pricingTiers.value.length === 0) return 0;
|
||||
|
||||
// Find the correct price tier. Assumes tiers are for total price, not price/gallon.
|
||||
let priceForGallons = 0;
|
||||
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
|
||||
let applicableTier = sortedTiers.filter(t => gallons >= Number(t.gallons)).pop();
|
||||
|
||||
if (applicableTier) {
|
||||
const pricePerGallon = Number(applicableTier.price) / Number(applicableTier.gallons);
|
||||
priceForGallons = gallons * pricePerGallon;
|
||||
} else if (sortedTiers.length > 0) {
|
||||
// Fallback to the lowest tier's price/gallon if no tier is met (e.g., ordering 50 gallons when lowest tier is 100)
|
||||
const lowestTier = sortedTiers[0];
|
||||
const pricePerGallon = Number(lowestTier.price) / Number(lowestTier.gallons);
|
||||
priceForGallons = gallons * pricePerGallon;
|
||||
}
|
||||
|
||||
return priceForGallons;
|
||||
})
|
||||
|
||||
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' };
|
||||
return states[customer.value.customer_state] || 'Unknown state';
|
||||
})
|
||||
|
||||
const customerHomeTypeName = computed((): string => {
|
||||
const types: Record<number, string> = { 0: 'Residential', 1: 'apartment', 2: 'condo', 3: 'commercial', 4: 'business', 5: 'construction', 6: 'container' };
|
||||
return types[customer.value.customer_home_type] || 'Unknown type';
|
||||
})
|
||||
|
||||
const isAnyPaymentMethodSelected = computed((): boolean => {
|
||||
return !!(formDelivery.value?.credit || formDelivery.value?.cash || formDelivery.value?.check || formDelivery.value?.other);
|
||||
})
|
||||
|
||||
const selectedGallonsAmount = computed((): number => {
|
||||
const value = formDelivery.value.gallons_ordered ?? '';
|
||||
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;
|
||||
}
|
||||
},
|
||||
expected_delivery_date: { required },
|
||||
driver_employee_id: { required },
|
||||
credit_card_id: {
|
||||
creditCardRequired: (value: number) => {
|
||||
if (formDelivery.value.credit) { return value !== 0; }
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},
|
||||
validations() {
|
||||
return {
|
||||
formDelivery: {
|
||||
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 },
|
||||
},
|
||||
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 },
|
||||
},
|
||||
}
|
||||
|
||||
const v$ = useVuelidate(validations, { formDelivery, formCard, isAnyPaymentMethodSelected })
|
||||
|
||||
// Functions
|
||||
const setGallons = (amount: number) => {
|
||||
formDelivery.value.gallons_ordered = String(amount);
|
||||
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;
|
||||
const tierNum = Number(tierGallons);
|
||||
if (isNaN(tierNum)) return false;
|
||||
return selectedGallons === tierNum;
|
||||
}
|
||||
|
||||
const getPricingTiers = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/tiers";
|
||||
axios({ method: "get", url: path, withCredentials: true, headers: authHeader() })
|
||||
.then((response: SimpleResponse<{ [key: string]: string }>) => {
|
||||
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" }));
|
||||
}
|
||||
|
||||
const getCustomer = (user_id: string | number | string[]) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/" + user_id;
|
||||
axios({ method: "get", url: path, withCredentials: true })
|
||||
.then((response: SimpleResponse<Customer>) => { customer.value = response.data; })
|
||||
.catch(() => notify({ title: "Error", text: "Could not find customer", type: "error" }));
|
||||
}
|
||||
|
||||
const getPaymentCards = (user_id: string | number | string[]) => {
|
||||
// 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}`;
|
||||
return axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: SimpleResponse<CreditCard[]>) => { userCards.value = response.data; })
|
||||
.catch(() => { userCards.value = []; }); // Clear cards on error
|
||||
}
|
||||
|
||||
const getPromos = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/promo/all";
|
||||
axios({ method: "get", url: path, withCredentials: true })
|
||||
.then((response: SimpleResponse<Promo[]>) => {
|
||||
promos.value = response.data;
|
||||
})
|
||||
.catch(() => { /* empty */ });
|
||||
}
|
||||
|
||||
const getDriversList = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/employee/drivers";
|
||||
axios({ method: "get", url: path, withCredentials: true, headers: authHeader() })
|
||||
.then((response: SimpleResponse<Driver[]>) => {
|
||||
truckDriversList.value = response.data;
|
||||
})
|
||||
.catch(() => { /* empty */ });
|
||||
}
|
||||
|
||||
const editCard = (card_id: number) => {
|
||||
router.push({ name: "cardedit", params: { id: card_id } });
|
||||
}
|
||||
|
||||
const removeCard = (card_id: number) => {
|
||||
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}
|
||||
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() })
|
||||
.then(() => {
|
||||
notify({ title: "Card Removed", type: "success" });
|
||||
getPaymentCards(customer.value.id);
|
||||
})
|
||||
.catch(() => {
|
||||
notify({ title: "Error", text: "Could not remove card.", type: "error" });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const onDeliverySubmit = async () => {
|
||||
const isFormValid = await v$.value.formDelivery.$validate();
|
||||
if (!isFormValid) {
|
||||
notify({ title: "Form Incomplete", text: "Please review the fields marked in red.", type: "warn" });
|
||||
return;
|
||||
}
|
||||
if (formDelivery.value.cash || formDelivery.value.check) {
|
||||
isConfirmationModalVisible.value = true;
|
||||
} else {
|
||||
proceedWithSubmission();
|
||||
}
|
||||
}
|
||||
|
||||
const proceedWithSubmission = async () => {
|
||||
isConfirmationModalVisible.value = false;
|
||||
isLoading.value = true;
|
||||
|
||||
// Step 1: Create the delivery order record
|
||||
const createDeliveryPath = `${import.meta.env.VITE_BASE_URL}/delivery/create/${customer.value.id}`;
|
||||
|
||||
try {
|
||||
const deliveryResponse = await axios.post(createDeliveryPath, formDelivery.value, { withCredentials: true, headers: authHeader() });
|
||||
const deliveryData = deliveryResponse.data;
|
||||
|
||||
if (!deliveryData.ok || !deliveryData.delivery_id) {
|
||||
throw new Error(deliveryData.error || "Failed to create delivery record.");
|
||||
}
|
||||
|
||||
// Delivery created successfully - redirect to payment page
|
||||
notify({
|
||||
title: "Success!",
|
||||
text: `Delivery #${deliveryData.delivery_id} created. ${
|
||||
formDelivery.value.credit
|
||||
? "Redirecting to payment page."
|
||||
: ""
|
||||
}`,
|
||||
type: "success"
|
||||
});
|
||||
router.push({ name: "payOil", params: { id: deliveryData.delivery_id } });
|
||||
|
||||
} catch (error: any) {
|
||||
const errorMessage = error.response?.data?.detail || "An error occurred during submission.";
|
||||
notify({ title: "Submission Error", text: errorMessage, type: "error" });
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
} 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
|
||||
};
|
||||
},
|
||||
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;
|
||||
} finally {
|
||||
isLoadingAuthorize.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the correct price tier. Assumes tiers are for total price, not price/gallon.
|
||||
let priceForGallons = 0;
|
||||
const sortedTiers = [...this.pricingTiers].sort((a, b) => Number(a.gallons) - Number(b.gallons));
|
||||
|
||||
// Find the highest tier that is less than or equal to the gallons ordered
|
||||
let applicableTier = sortedTiers.filter(t => gallons >= Number(t.gallons)).pop();
|
||||
|
||||
if (applicableTier) {
|
||||
const pricePerGallon = Number(applicableTier.price) / Number(applicableTier.gallons);
|
||||
priceForGallons = gallons * pricePerGallon;
|
||||
} else if (sortedTiers.length > 0) {
|
||||
// Fallback to the lowest tier's price/gallon if no tier is met (e.g., ordering 50 gallons when lowest tier is 100)
|
||||
const lowestTier = sortedTiers[0];
|
||||
const pricePerGallon = Number(lowestTier.price) / Number(lowestTier.gallons);
|
||||
priceForGallons = gallons * pricePerGallon;
|
||||
}
|
||||
const onCardSubmit = async () => {
|
||||
v$.value.formCard.$validate();
|
||||
if (v$.value.formCard.$error) {
|
||||
notify({ title: "Validation Error", text: "Please fill out all required fields.", type: "error" });
|
||||
return;
|
||||
}
|
||||
isCardSaving.value = true;
|
||||
|
||||
return priceForGallons;
|
||||
},
|
||||
customerStateName(): string {
|
||||
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';
|
||||
},
|
||||
customerHomeTypeName(): string {
|
||||
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';
|
||||
},
|
||||
isAnyPaymentMethodSelected(): boolean {
|
||||
return !!(this.formDelivery?.credit || this.formDelivery?.cash || this.formDelivery?.check || this.formDelivery?.other);
|
||||
},
|
||||
selectedGallonsAmount(): number {
|
||||
const value = this.formDelivery.gallons_ordered ?? '';
|
||||
return Number(value);
|
||||
// --- STEP 1: PREPARE PAYLOADS FOR BOTH SERVICES ---
|
||||
// Payload for Flask backend (it takes all the raw details for your DB)
|
||||
const flaskPayload = {
|
||||
card_number: formCard.value.card_number,
|
||||
expiration_month: formCard.value.expiration_month,
|
||||
expiration_year: formCard.value.expiration_year,
|
||||
type_of_card: formCard.value.type_of_card,
|
||||
security_number: formCard.value.security_number, // Flask expects 'security_number'
|
||||
main_card: false,
|
||||
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 ---
|
||||
try {
|
||||
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);
|
||||
const flaskResponse = await axios.post(flaskPath, flaskPayload, { withCredentials: true, headers: authHeader() });
|
||||
|
||||
if (!flaskResponse.data.ok) {
|
||||
// If the primary save fails, stop everything and show an error.
|
||||
throw new Error(flaskResponse.data.error || "Failed to save card.");
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getDriversList()
|
||||
this.getPromos()
|
||||
this.getPricingTiers()
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
const customerId = this.$route.params.id;
|
||||
this.getCustomer(customerId);
|
||||
this.getPaymentCards(customerId);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const customerId = this.$route.params.id;
|
||||
this.getCustomer(customerId)
|
||||
this.getPaymentCards(customerId);
|
||||
},
|
||||
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; },
|
||||
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]; },
|
||||
isPricingTierSelected(tierGallons: number | string): boolean {
|
||||
if (!this.formDelivery.gallons_ordered) return false;
|
||||
const selectedGallons = Number(this.formDelivery.gallons_ordered);
|
||||
if (isNaN(selectedGallons)) return false;
|
||||
const tierNum = Number(tierGallons);
|
||||
if (isNaN(tierNum)) return false;
|
||||
return selectedGallons === tierNum;
|
||||
},
|
||||
getPricingTiers() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/tiers";
|
||||
axios({ method: "get", url: path, withCredentials: true, headers: authHeader() })
|
||||
.then((response: SimpleResponse<{ [key: string]: string }>) => {
|
||||
this.pricingTiers = 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" }));
|
||||
},
|
||||
getCustomer(user_id: string | number | string[]) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/" + user_id;
|
||||
axios({ method: "get", url: path, withCredentials: true })
|
||||
.then((response: SimpleResponse<Customer>) => { this.customer = response.data; })
|
||||
.catch(() => notify({ title: "Error", text: "Could not find customer", type: "error" }));
|
||||
},
|
||||
getPaymentCards(user_id: string | number | string[]) {
|
||||
// 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}`;
|
||||
return axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: SimpleResponse<UserCard[]>) => { this.userCards = response.data; })
|
||||
.catch(() => { this.userCards = []; }); // Clear cards on error
|
||||
},
|
||||
getPromos() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/promo/all";
|
||||
axios({ method: "get", url: path, withCredentials: true })
|
||||
.then((response: SimpleResponse<Promo[]>) => {
|
||||
this.promos = response.data;
|
||||
})
|
||||
.catch(() => { /* empty */ });
|
||||
},
|
||||
getDriversList() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/employee/drivers";
|
||||
axios({ method: "get", url: path, withCredentials: true, headers: authHeader() })
|
||||
.then((response: SimpleResponse<Driver[]>) => {
|
||||
this.truckDriversList = response.data;
|
||||
})
|
||||
.catch(() => { /* empty */ });
|
||||
},
|
||||
console.log("Card successfully saved to local database via Flask.");
|
||||
|
||||
editCard(card_id: number) {
|
||||
this.$router.push({ name: "cardedit", params: { id: card_id } });
|
||||
},
|
||||
} catch (error: any) {
|
||||
const errorMessage = error.response?.data?.error || "A critical error occurred while saving the card.";
|
||||
notify({ title: "Error", text: errorMessage, type: "error" });
|
||||
isCardSaving.value = false; // Stop loading spinner
|
||||
return; // End the function here
|
||||
}
|
||||
|
||||
removeCard(card_id: number) {
|
||||
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}
|
||||
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() })
|
||||
.then(() => {
|
||||
notify({ title: "Card Removed", type: "success" });
|
||||
this.getPaymentCards(this.customer.id);
|
||||
})
|
||||
.catch(() => {
|
||||
notify({ title: "Error", text: "Could not remove card.", type: "error" });
|
||||
});
|
||||
}
|
||||
},
|
||||
// --- STEP 3: BEST-EFFORT CALL - TOKENIZE CARD VIA FASTAPI ---
|
||||
if (authorizeCheck.value.profile_exists) {
|
||||
// Payload for FastAPI backend (it only needs the essentials for Authorize.Net)
|
||||
const fastapiPayload = {
|
||||
card_number: formCard.value.card_number.replace(/\s/g, ''),
|
||||
expiration_date: `${formCard.value.expiration_year}-${formCard.value.expiration_month}`,
|
||||
cvv: formCard.value.security_number, // Map security_number to cvv for FastAPI
|
||||
main_card: false, // Send this to FastAPI as well
|
||||
};
|
||||
|
||||
async onDeliverySubmit() {
|
||||
const isFormValid = await this.v$.formDelivery.$validate();
|
||||
if (!isFormValid) {
|
||||
notify({ title: "Form Incomplete", text: "Please review the fields marked in red.", type: "warn" });
|
||||
return;
|
||||
}
|
||||
if (this.formDelivery.cash || this.formDelivery.check) {
|
||||
this.isConfirmationModalVisible = true;
|
||||
} else {
|
||||
this.proceedWithSubmission();
|
||||
}
|
||||
},
|
||||
|
||||
async proceedWithSubmission() {
|
||||
this.isConfirmationModalVisible = false;
|
||||
this.isLoading = true;
|
||||
try {
|
||||
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);
|
||||
await axios.post(fastapiPath, fastapiPayload, { withCredentials: true, headers: authHeader() });
|
||||
console.log("Card successfully tokenized with Authorize.Net via FastAPI.");
|
||||
} catch (error: any) {
|
||||
// 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);
|
||||
}
|
||||
}else{
|
||||
console.log("Skipping Authorize.Net tokenization as no profile exists for customer.");
|
||||
}
|
||||
// --- STEP 4: ALWAYS SHOW SUCCESS, REFRESH CARDS, STAY ON PAGE ---
|
||||
// This code runs as long as the first (Flask) call was successful.
|
||||
notify({ type: 'success', title: 'Card Saved!' });
|
||||
|
||||
// Step 1: Create the delivery order record
|
||||
const createDeliveryPath = `${import.meta.env.VITE_BASE_URL}/delivery/create/${this.customer.id}`;
|
||||
|
||||
try {
|
||||
const deliveryResponse = await axios.post(createDeliveryPath, this.formDelivery, { withCredentials: true, headers: authHeader() });
|
||||
const deliveryData = deliveryResponse.data;
|
||||
// Refresh the card list and try to auto-select if possible
|
||||
await getPaymentCards(customer.value.id);
|
||||
if (userCards.value.length > 0) {
|
||||
formDelivery.value.credit = true;
|
||||
formDelivery.value.credit_card_id = userCards.value[userCards.value.length - 1].id;
|
||||
}
|
||||
|
||||
if (!deliveryData.ok || !deliveryData.delivery_id) {
|
||||
throw new Error(deliveryData.error || "Failed to create delivery record.");
|
||||
}
|
||||
|
||||
// Delivery created successfully - redirect to payment page
|
||||
notify({
|
||||
title: "Success!",
|
||||
text: `Delivery #${deliveryData.delivery_id} created. ${
|
||||
this.formDelivery.credit
|
||||
? "Redirecting to payment page."
|
||||
: ""
|
||||
}`,
|
||||
type: "success"
|
||||
});
|
||||
this.$router.push({ name: "payOil", params: { id: deliveryData.delivery_id } });
|
||||
// Reset the quick add form
|
||||
Object.assign(formCard.value, { card_number: '', expiration_month: '', expiration_year: '', security_number: '', card_name: '', type_of_card: '' });
|
||||
v$.value.formCard.$reset();
|
||||
isCardSaving.value = false;
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
const errorMessage = error.response?.data?.detail || "An error occurred during submission.";
|
||||
notify({ title: "Submission Error", text: errorMessage, type: "error" });
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
async checkAuthorizeAccount() {
|
||||
if (!this.customer.id) return;
|
||||
// Watchers
|
||||
watch(route, () => {
|
||||
const customerId = route.params.id;
|
||||
getCustomer(customerId);
|
||||
getPaymentCards(customerId);
|
||||
})
|
||||
|
||||
this.isLoadingAuthorize = true;
|
||||
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_AUTHORIZE_URL}/user/check-authorize-account/${this.customer.id}`;
|
||||
const response = await axios.get(path, { headers: authHeader() });
|
||||
this.authorizeCheck = response.data;
|
||||
} 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
|
||||
this.authorizeCheck = {
|
||||
profile_exists: false,
|
||||
has_payment_methods: false,
|
||||
missing_components: ['api_error'],
|
||||
valid_for_charging: false
|
||||
};
|
||||
} finally {
|
||||
this.isLoadingAuthorize = false;
|
||||
}
|
||||
},
|
||||
async onCardSubmit() {
|
||||
this.v$.formCard.$validate();
|
||||
if (this.v$.formCard.$error) {
|
||||
notify({ title: "Validation Error", text: "Please fill out all required fields.", type: "error" });
|
||||
return;
|
||||
}
|
||||
this.isCardSaving = true;
|
||||
|
||||
// --- STEP 1: PREPARE PAYLOADS FOR BOTH SERVICES ---
|
||||
// Payload for Flask backend (it takes all the raw details for your DB)
|
||||
const flaskPayload = {
|
||||
card_number: this.formCard.card_number,
|
||||
expiration_month: this.formCard.expiration_month,
|
||||
expiration_year: this.formCard.expiration_year,
|
||||
type_of_card: this.formCard.type_of_card,
|
||||
security_number: this.formCard.security_number, // Flask expects 'security_number'
|
||||
main_card: false,
|
||||
name_on_card: this.formCard.card_name, // Map card_name to name_on_card for Flask
|
||||
};
|
||||
|
||||
|
||||
|
||||
// --- STEP 2: CRITICAL CALL - SAVE CARD TO LOCAL DATABASE VIA FLASK ---
|
||||
try {
|
||||
const flaskPath = `${import.meta.env.VITE_BASE_URL}/payment/card/create/${this.customer.id}`;
|
||||
console.log("Attempting to save card to local DB via Flask:", flaskPath);
|
||||
const flaskResponse = await axios.post(flaskPath, flaskPayload, { withCredentials: true, headers: authHeader() });
|
||||
|
||||
if (!flaskResponse.data.ok) {
|
||||
// If the primary save fails, stop everything and show an error.
|
||||
throw new Error(flaskResponse.data.error || "Failed to save card.");
|
||||
}
|
||||
console.log("Card successfully saved to local database via Flask.");
|
||||
|
||||
} catch (error: any) {
|
||||
const errorMessage = error.response?.data?.error || "A critical error occurred while saving the card.";
|
||||
notify({ title: "Error", text: errorMessage, type: "error" });
|
||||
this.isCardSaving = false; // Stop loading spinner
|
||||
return; // End the function here
|
||||
}
|
||||
|
||||
// --- STEP 3: BEST-EFFORT CALL - TOKENIZE CARD VIA FASTAPI ---
|
||||
if (this.authorizeCheck.profile_exists) {
|
||||
// Payload for FastAPI backend (it only needs the essentials for Authorize.Net)
|
||||
const fastapiPayload = {
|
||||
card_number: this.formCard.card_number.replace(/\s/g, ''),
|
||||
expiration_date: `${this.formCard.expiration_year}-${this.formCard.expiration_month}`,
|
||||
cvv: this.formCard.security_number, // Map security_number to cvv for FastAPI
|
||||
main_card: false, // Send this to FastAPI as well
|
||||
};
|
||||
|
||||
try {
|
||||
const fastapiPath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/customers/${this.customer.id}/cards`;
|
||||
console.log("Attempting to tokenize card with Authorize.Net via FastAPI:", fastapiPath);
|
||||
await axios.post(fastapiPath, fastapiPayload, { withCredentials: true, headers: authHeader() });
|
||||
console.log("Card successfully tokenized with Authorize.Net via FastAPI.");
|
||||
} catch (error: any) {
|
||||
// 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);
|
||||
}
|
||||
}else{
|
||||
console.log("Skipping Authorize.Net tokenization as no profile exists for customer.");
|
||||
}
|
||||
// --- STEP 4: ALWAYS SHOW SUCCESS, REFRESH CARDS, STAY ON PAGE ---
|
||||
// This code runs as long as the first (Flask) call was successful.
|
||||
notify({ type: 'success', title: 'Card Saved!' });
|
||||
|
||||
// Refresh the card list and try to auto-select if possible
|
||||
await this.getPaymentCards(this.customer.id);
|
||||
if (this.userCards.length > 0) {
|
||||
this.formDelivery.credit = true;
|
||||
this.formDelivery.credit_card_id = this.userCards[this.userCards.length - 1].id;
|
||||
}
|
||||
|
||||
// Reset the quick add form
|
||||
Object.assign(this.formCard, { card_number: '', expiration_month: '', expiration_year: '', security_number: '', card_name: '', type_of_card: '' });
|
||||
this.v$.formCard.$reset();
|
||||
this.isCardSaving = false;
|
||||
},
|
||||
},
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
getDriversList()
|
||||
getPromos()
|
||||
getPricingTiers()
|
||||
const customerId = route.params.id;
|
||||
getCustomer(customerId)
|
||||
getPaymentCards(customerId);
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -223,7 +223,7 @@
|
||||
</div>
|
||||
<div class="mt-2 text-sm font-mono tracking-wider">
|
||||
<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>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 mt-2">
|
||||
@@ -240,302 +240,308 @@
|
||||
</div>
|
||||
<Footer />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../services/auth.header'
|
||||
import { Customer, CreditCard } 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 { useVuelidate } from "@vuelidate/core";
|
||||
import { required, requiredIf } from "@vuelidate/validators";
|
||||
import { notify } from "@kyvg/vue3-notification";
|
||||
|
||||
// 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 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; }
|
||||
|
||||
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' };
|
||||
|
||||
export default defineComponent({
|
||||
name: 'deliveryEdit',
|
||||
components: { Header, SideBar, Footer },
|
||||
data() {
|
||||
return {
|
||||
v$: useValidate(),
|
||||
quickGallonAmounts: [100, 125, 150, 175, 200, 220],
|
||||
deliveryStatus: [] as any[],
|
||||
truckDriversList: [] as any[],
|
||||
userCards: [] as UserCard[],
|
||||
promos: [] as any[],
|
||||
pricingTiers: [] as PricingTier[],
|
||||
customer: {} as Customer,
|
||||
deliveryOrder: {} as DeliveryOrder,
|
||||
userCard: {} as UserCard,
|
||||
// RESTORED: Add all form fields to the data model
|
||||
CreateOilOrderForm: {
|
||||
basicInfo: {
|
||||
gallons_ordered: '',
|
||||
customer_asked_for_fill: false,
|
||||
created_delivery_date: '',
|
||||
expected_delivery_date: '',
|
||||
prime: false,
|
||||
emergency: false,
|
||||
same_day: false,
|
||||
delivery_status: 0,
|
||||
driver_employee_id: 0,
|
||||
dispatcher_notes_taken: '',
|
||||
promo_id: 0,
|
||||
payment_type: 0,
|
||||
credit_card_id: 0,
|
||||
credit: false,
|
||||
cash: false,
|
||||
check: false,
|
||||
other: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
validations() {
|
||||
return {
|
||||
CreateOilOrderForm: {
|
||||
basicInfo: {
|
||||
gallons_ordered: {
|
||||
required: requiredIf(function(this: any) {
|
||||
return !this.CreateOilOrderForm.basicInfo.customer_asked_for_fill;
|
||||
}),
|
||||
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
|
||||
const num = parseInt(value, 10);
|
||||
return num >= 1;
|
||||
}
|
||||
},
|
||||
expected_delivery_date: { required },
|
||||
credit_card_id: {
|
||||
creditCardRequired: function(this: any, value: number) {
|
||||
if (this.CreateOilOrderForm.basicInfo.credit) {
|
||||
return value !== 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
isAnyPaymentMethodSelected: {
|
||||
mustBeTrue: (value: boolean) => value === true,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
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);
|
||||
if (isNaN(tierNum)) return false;
|
||||
|
||||
return selectedGallons === tierNum;
|
||||
};
|
||||
},
|
||||
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() })
|
||||
.then((response: any) => {
|
||||
// FIX: Check for the 'ok' flag and access the nested 'delivery' object
|
||||
if (response.data && response.data.ok) {
|
||||
this.deliveryOrder = response.data.delivery; // <-- THIS IS THE CRITICAL CHANGE
|
||||
|
||||
// RESTORED: Populate all form fields from the API response
|
||||
const paymentType = this.deliveryOrder.payment_type;
|
||||
this.CreateOilOrderForm.basicInfo = {
|
||||
gallons_ordered: String(this.deliveryOrder.gallons_ordered),
|
||||
customer_asked_for_fill: !!this.deliveryOrder.customer_asked_for_fill,
|
||||
created_delivery_date: this.deliveryOrder.when_ordered,
|
||||
expected_delivery_date: this.deliveryOrder.expected_delivery_date,
|
||||
prime: !!this.deliveryOrder.prime,
|
||||
emergency: !!this.deliveryOrder.emergency,
|
||||
same_day: !!this.deliveryOrder.same_day,
|
||||
delivery_status: this.deliveryOrder.delivery_status,
|
||||
driver_employee_id: this.deliveryOrder.driver_employee_id || 0,
|
||||
dispatcher_notes_taken: this.deliveryOrder.dispatcher_notes,
|
||||
promo_id: this.deliveryOrder.promo_id || 0,
|
||||
payment_type: paymentType,
|
||||
credit_card_id: this.deliveryOrder.payment_card_id || 0,
|
||||
// Set the correct payment method checkbox based on payment_type
|
||||
credit: paymentType === 1,
|
||||
cash: paymentType === 0,
|
||||
check: paymentType === 3,
|
||||
other: paymentType === 4,
|
||||
};
|
||||
|
||||
this.getCustomer(this.deliveryOrder.customer_id);
|
||||
} else {
|
||||
console.error("API Error:", response.data.error || "Failed to fetch delivery data.");
|
||||
}
|
||||
})
|
||||
.catch((error: any) => console.error("Error fetching delivery order:", error));
|
||||
},
|
||||
|
||||
getCustomer(customerId: number) {
|
||||
axios.get(`${import.meta.env.VITE_BASE_URL}/customer/${customerId}`, { withCredentials: true })
|
||||
.then((response: any) => {
|
||||
this.customer = response.data;
|
||||
this.getPaymentCards(customerId);
|
||||
if (this.deliveryOrder.payment_type === 1 && this.deliveryOrder.payment_card_id) {
|
||||
this.getPaymentCard(this.deliveryOrder.payment_card_id);
|
||||
}
|
||||
})
|
||||
.catch((error: any) => console.error("Error fetching customer:", error));
|
||||
},
|
||||
|
||||
getPaymentCards(customerId: number) {
|
||||
axios.get(`${import.meta.env.VITE_BASE_URL}/payment/cards/${customerId}`, { withCredentials: true })
|
||||
.then((response: any) => { this.userCards = response.data; })
|
||||
.catch((error: any) => console.error("Error fetching payment cards:", error));
|
||||
},
|
||||
|
||||
getPaymentCard(cardId: number) {
|
||||
axios.get(`${import.meta.env.VITE_BASE_URL}/payment/card/${cardId}`, { withCredentials: true })
|
||||
.then((response: any) => { this.userCard = response.data; })
|
||||
.catch((error: any) => console.error("Error fetching specific payment card:", error));
|
||||
},
|
||||
|
||||
getPromos() {
|
||||
axios.get(`${import.meta.env.VITE_BASE_URL}/promo/all`, { withCredentials: true })
|
||||
.then((response: any) => { this.promos = response.data; });
|
||||
},
|
||||
getDriversList() {
|
||||
axios.get(`${import.meta.env.VITE_BASE_URL}/employee/drivers`, { headers: authHeader(), withCredentials: true })
|
||||
.then((response: any) => { this.truckDriversList = response.data; });
|
||||
},
|
||||
getDeliveryStatusList() {
|
||||
axios.get(`${import.meta.env.VITE_BASE_URL}/query/deliverystatus`, { withCredentials: true })
|
||||
.then((response: any) => { this.deliveryStatus = response.data; });
|
||||
},
|
||||
getPricingTiers() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/tiers";
|
||||
axios({ method: "get", url: path, withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
const tiersObject = response.data;
|
||||
this.pricingTiers = Object.entries(tiersObject).map(([gallons, price]) => ({
|
||||
gallons: parseInt(gallons, 10),
|
||||
price: price as string | number,
|
||||
}));
|
||||
})
|
||||
.catch(() => {
|
||||
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 } });
|
||||
},
|
||||
removeCard(card_id: number) {
|
||||
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}`;
|
||||
axios.delete(path, { headers: authHeader() })
|
||||
.then(() => {
|
||||
notify({ title: "Card Removed", type: "success" });
|
||||
this.getPaymentCards(this.customer.id);
|
||||
})
|
||||
.catch(() => {
|
||||
notify({ title: "Error", text: "Could not remove card.", type: "error" });
|
||||
});
|
||||
}
|
||||
},
|
||||
selectCreditCard(cardId: number) {
|
||||
this.CreateOilOrderForm.basicInfo.credit_card_id = cardId;
|
||||
},
|
||||
setGallons(amount: number) {
|
||||
this.CreateOilOrderForm.basicInfo.gallons_ordered = String(amount);
|
||||
this.CreateOilOrderForm.basicInfo.customer_asked_for_fill = false;
|
||||
},
|
||||
setDeliveryDate(days: number) {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + days);
|
||||
this.CreateOilOrderForm.basicInfo.expected_delivery_date = date.toISOString().split('T')[0];
|
||||
},
|
||||
isDeliveryDateSelected(days: number): boolean {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + days);
|
||||
return this.CreateOilOrderForm.basicInfo.expected_delivery_date === date.toISOString().split('T')[0];
|
||||
},
|
||||
async onSubmit() {
|
||||
const isFormValid = await this.v$.CreateOilOrderForm.$validate();
|
||||
const isPaymentValid = await this.v$.isAnyPaymentMethodSelected.$validate();
|
||||
|
||||
if (!isFormValid || !isPaymentValid) {
|
||||
notify({ title: "Form Incomplete", text: "Please review the fields marked in red.", type: "warn" });
|
||||
return;
|
||||
}
|
||||
|
||||
const formInfo = this.CreateOilOrderForm.basicInfo;
|
||||
// Convert checkboxes back to payment_type number for API
|
||||
let paymentType = 0; // Default to cash
|
||||
if (formInfo.credit) paymentType = 1;
|
||||
else if (formInfo.cash) paymentType = 0;
|
||||
else if (formInfo.other) paymentType = 4;
|
||||
else if (formInfo.check) paymentType = 3;
|
||||
|
||||
// The payload now automatically includes all the restored fields
|
||||
const payload = {
|
||||
...formInfo,
|
||||
payment_type: paymentType,
|
||||
cash: formInfo.cash,
|
||||
credit: formInfo.credit,
|
||||
check: formInfo.check,
|
||||
other: formInfo.other,
|
||||
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() })
|
||||
.then(() => {
|
||||
notify({ type: 'success', title: 'Success!', text: 'Delivery updated.' });
|
||||
if (paymentType === 1) {
|
||||
this.$router.push({ name: 'payOil', params: { id: this.deliveryOrder.id } });
|
||||
} else {
|
||||
this.$router.push({ name: 'deliveryOrder', params: { id: this.deliveryOrder.id } });
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error("Error submitting form:", error);
|
||||
notify({ type: 'error', title: 'Update Failed', text: 'Could not save changes.' });
|
||||
});
|
||||
},
|
||||
// Reactive data
|
||||
const quickGallonAmounts = ref([100, 125, 150, 175, 200, 220])
|
||||
const deliveryStatus = ref([] as any[])
|
||||
const truckDriversList = ref([] as any[])
|
||||
const userCards = ref<CreditCard[]>([])
|
||||
const promos = ref([] as any[])
|
||||
const pricingTiers = ref([] as PricingTier[])
|
||||
const customer = ref({} as Customer)
|
||||
const deliveryOrder = ref({} as DeliveryOrder)
|
||||
const userCard = ref({} as CreditCard)
|
||||
// RESTORED: Add all form fields to the data model
|
||||
const CreateOilOrderForm = ref({
|
||||
basicInfo: {
|
||||
gallons_ordered: '',
|
||||
customer_asked_for_fill: false,
|
||||
created_delivery_date: '',
|
||||
expected_delivery_date: '',
|
||||
prime: false,
|
||||
emergency: false,
|
||||
same_day: false,
|
||||
delivery_status: 0,
|
||||
driver_employee_id: 0,
|
||||
dispatcher_notes_taken: '',
|
||||
promo_id: 0,
|
||||
payment_type: 0,
|
||||
credit_card_id: 0,
|
||||
credit: false,
|
||||
cash: false,
|
||||
check: false,
|
||||
other: false,
|
||||
},
|
||||
})
|
||||
|
||||
// Computed
|
||||
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: {
|
||||
basicInfo: {
|
||||
gallons_ordered: {
|
||||
required: requiredIf(() => !CreateOilOrderForm.value.basicInfo.customer_asked_for_fill),
|
||||
minValue: (value: string) => {
|
||||
if (CreateOilOrderForm.value.basicInfo.customer_asked_for_fill) return true;
|
||||
if (!value) return true; // if empty, required will catch it
|
||||
const num = parseInt(value, 10);
|
||||
return num >= 1;
|
||||
}
|
||||
},
|
||||
expected_delivery_date: { required },
|
||||
credit_card_id: {
|
||||
creditCardRequired: (value: number) => {
|
||||
if (CreateOilOrderForm.value.basicInfo.credit) {
|
||||
return value !== 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
isAnyPaymentMethodSelected: {
|
||||
mustBeTrue: (value: boolean) => value === true,
|
||||
},
|
||||
}
|
||||
|
||||
const v$ = useVuelidate(validations, { CreateOilOrderForm, isAnyPaymentMethodSelected })
|
||||
|
||||
// Functions
|
||||
const fetchInitialData = () => {
|
||||
const deliveryId = route.params.id as string;
|
||||
getPromos();
|
||||
getDriversList();
|
||||
getDeliveryStatusList();
|
||||
getPricingTiers();
|
||||
getDeliveryOrder(deliveryId);
|
||||
}
|
||||
|
||||
const getDeliveryOrder = (deliveryId: string) => {
|
||||
axios.get(`${import.meta.env.VITE_BASE_URL}/delivery/order/${deliveryId}`, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
// FIX: Check for the 'ok' flag and access the nested 'delivery' object
|
||||
if (response.data && response.data.ok) {
|
||||
deliveryOrder.value = response.data.delivery; // <-- THIS IS THE CRITICAL CHANGE
|
||||
|
||||
// RESTORED: Populate all form fields from the API response
|
||||
const paymentType = deliveryOrder.value.payment_type;
|
||||
CreateOilOrderForm.value.basicInfo = {
|
||||
gallons_ordered: String(deliveryOrder.value.gallons_ordered),
|
||||
customer_asked_for_fill: !!deliveryOrder.value.customer_asked_for_fill,
|
||||
created_delivery_date: deliveryOrder.value.when_ordered,
|
||||
expected_delivery_date: deliveryOrder.value.expected_delivery_date,
|
||||
prime: !!deliveryOrder.value.prime,
|
||||
emergency: !!deliveryOrder.value.emergency,
|
||||
same_day: !!deliveryOrder.value.same_day,
|
||||
delivery_status: deliveryOrder.value.delivery_status,
|
||||
driver_employee_id: deliveryOrder.value.driver_employee_id || 0,
|
||||
dispatcher_notes_taken: deliveryOrder.value.dispatcher_notes,
|
||||
promo_id: deliveryOrder.value.promo_id || 0,
|
||||
payment_type: paymentType,
|
||||
credit_card_id: deliveryOrder.value.payment_card_id || 0,
|
||||
// Set the correct payment method checkbox based on payment_type
|
||||
credit: paymentType === 1,
|
||||
cash: paymentType === 0,
|
||||
check: paymentType === 3,
|
||||
other: paymentType === 4,
|
||||
};
|
||||
|
||||
getCustomer(deliveryOrder.value.customer_id);
|
||||
} else {
|
||||
console.error("API Error:", response.data.error || "Failed to fetch delivery data.");
|
||||
}
|
||||
})
|
||||
.catch((error: any) => console.error("Error fetching delivery order:", error));
|
||||
}
|
||||
|
||||
const getCustomer = (customerId: number) => {
|
||||
axios.get(`${import.meta.env.VITE_BASE_URL}/customer/${customerId}`, { withCredentials: true })
|
||||
.then((response: any) => {
|
||||
customer.value = response.data;
|
||||
getPaymentCards(customerId);
|
||||
if (deliveryOrder.value.payment_type === 1 && deliveryOrder.value.payment_card_id) {
|
||||
getPaymentCard(deliveryOrder.value.payment_card_id);
|
||||
}
|
||||
})
|
||||
.catch((error: any) => console.error("Error fetching customer:", error));
|
||||
}
|
||||
|
||||
const getPaymentCards = (customerId: number) => {
|
||||
axios.get(`${import.meta.env.VITE_BASE_URL}/payment/cards/${customerId}`, { withCredentials: true })
|
||||
.then((response: any) => { userCards.value = response.data; })
|
||||
.catch((error: any) => console.error("Error fetching payment cards:", error));
|
||||
}
|
||||
|
||||
const getPaymentCard = (cardId: number) => {
|
||||
axios.get(`${import.meta.env.VITE_BASE_URL}/payment/card/${cardId}`, { withCredentials: true })
|
||||
.then((response: any) => { userCard.value = response.data; })
|
||||
.catch((error: any) => console.error("Error fetching specific payment card:", error));
|
||||
}
|
||||
|
||||
const getPromos = () => {
|
||||
axios.get(`${import.meta.env.VITE_BASE_URL}/promo/all`, { withCredentials: true })
|
||||
.then((response: any) => { promos.value = response.data; });
|
||||
}
|
||||
|
||||
const getDriversList = () => {
|
||||
axios.get(`${import.meta.env.VITE_BASE_URL}/employee/drivers`, { headers: authHeader(), withCredentials: true })
|
||||
.then((response: any) => { truckDriversList.value = response.data; });
|
||||
}
|
||||
|
||||
const getDeliveryStatusList = () => {
|
||||
axios.get(`${import.meta.env.VITE_BASE_URL}/query/deliverystatus`, { withCredentials: true })
|
||||
.then((response: any) => { deliveryStatus.value = response.data; });
|
||||
}
|
||||
|
||||
const getPricingTiers = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/tiers";
|
||||
axios({ method: "get", url: path, withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
const tiersObject = response.data;
|
||||
pricingTiers.value = Object.entries(tiersObject).map(([gallons, price]) => ({
|
||||
gallons: parseInt(gallons, 10),
|
||||
price: price as string | number,
|
||||
}));
|
||||
})
|
||||
.catch(() => {
|
||||
notify({ title: "Pricing Error", text: "Could not retrieve today's pricing.", type: "error" });
|
||||
});
|
||||
}
|
||||
|
||||
const editCard = (card_id: number) => {
|
||||
router.push({ name: "cardedit", params: { id: card_id } });
|
||||
}
|
||||
|
||||
const removeCard = (card_id: number) => {
|
||||
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}`;
|
||||
axios.delete(path, { headers: authHeader() })
|
||||
.then(() => {
|
||||
notify({ title: "Card Removed", type: "success" });
|
||||
getPaymentCards(customer.value.id);
|
||||
})
|
||||
.catch(() => {
|
||||
notify({ title: "Error", text: "Could not remove card.", type: "error" });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const selectCreditCard = (cardId: number) => {
|
||||
CreateOilOrderForm.value.basicInfo.credit_card_id = cardId;
|
||||
}
|
||||
|
||||
const setGallons = (amount: number) => {
|
||||
CreateOilOrderForm.value.basicInfo.gallons_ordered = String(amount);
|
||||
CreateOilOrderForm.value.basicInfo.customer_asked_for_fill = false;
|
||||
}
|
||||
|
||||
const setDeliveryDate = (days: number) => {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + days);
|
||||
CreateOilOrderForm.value.basicInfo.expected_delivery_date = date.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
const isDeliveryDateSelected = (days: number): boolean => {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + days);
|
||||
return CreateOilOrderForm.value.basicInfo.expected_delivery_date === date.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
const onSubmit = async () => {
|
||||
const isFormValid = await v$.value.CreateOilOrderForm.$validate();
|
||||
const isPaymentValid = await v$.value.isAnyPaymentMethodSelected.$validate();
|
||||
|
||||
if (!isFormValid || !isPaymentValid) {
|
||||
notify({ title: "Form Incomplete", text: "Please review the fields marked in red.", type: "warn" });
|
||||
return;
|
||||
}
|
||||
|
||||
const formInfo = CreateOilOrderForm.value.basicInfo;
|
||||
// Convert checkboxes back to payment_type number for API
|
||||
let paymentType = 0; // Default to cash
|
||||
if (formInfo.credit) paymentType = 1;
|
||||
else if (formInfo.cash) paymentType = 0;
|
||||
else if (formInfo.other) paymentType = 4;
|
||||
else if (formInfo.check) paymentType = 3;
|
||||
|
||||
// The payload now automatically includes all the restored fields
|
||||
const payload = {
|
||||
...formInfo,
|
||||
payment_type: paymentType,
|
||||
cash: formInfo.cash,
|
||||
credit: formInfo.credit,
|
||||
check: formInfo.check,
|
||||
other: formInfo.other,
|
||||
credit_card_id: formInfo.credit ? formInfo.credit_card_id : null,
|
||||
};
|
||||
|
||||
axios.post(`${import.meta.env.VITE_BASE_URL}/delivery/edit/${deliveryOrder.value.id}`, payload, { withCredentials: true, headers: authHeader() })
|
||||
.then(() => {
|
||||
notify({ type: 'success', title: 'Success!', text: 'Delivery updated.' });
|
||||
if (paymentType === 1) {
|
||||
router.push({ name: 'payOil', params: { id: deliveryOrder.value.id } });
|
||||
} else {
|
||||
router.push({ name: 'deliveryOrder', params: { id: deliveryOrder.value.id } });
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error("Error submitting form:", error);
|
||||
notify({ type: 'error', title: 'Update Failed', text: 'Could not save changes.' });
|
||||
});
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
fetchInitialData();
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -55,18 +55,18 @@
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-sm" :class="{
|
||||
'badge-warning': oil.delivery_status == 0,
|
||||
'badge-success': [1, 10].includes(oil.delivery_status),
|
||||
'badge-info': oil.delivery_status == 2,
|
||||
'badge-error': [3, 5].includes(oil.delivery_status),
|
||||
'badge-warning': oil.delivery_status === computedDELIVERY_STATUS.WAITING,
|
||||
'badge-success': [computedDELIVERY_STATUS.DELIVERED, computedDELIVERY_STATUS.FINALIZED].includes(oil.delivery_status as any),
|
||||
'badge-info': oil.delivery_status === computedDELIVERY_STATUS.OUT_FOR_DELIVERY,
|
||||
'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-else-if="oil.delivery_status == 1">Cancelled</span>
|
||||
<span v-else-if="oil.delivery_status == 2">Today</span>
|
||||
<span v-else-if="oil.delivery_status == 3">Tomorrow_Delivery</span>
|
||||
<span v-else-if="oil.delivery_status == 4">Partial_Delivery</span>
|
||||
<span v-else-if="oil.delivery_status == 5">Issue</span>
|
||||
<span v-else-if="oil.delivery_status == 10">Finalized</span>
|
||||
<span v-if="oil.delivery_status === computedDELIVERY_STATUS.WAITING">Waiting</span>
|
||||
<span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.CANCELLED">Cancelled</span>
|
||||
<span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.OUT_FOR_DELIVERY">Today</span>
|
||||
<span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.TOMORROW">Tomorrow_Delivery</span>
|
||||
<span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.PARTIAL_DELIVERY">Partial_Delivery</span>
|
||||
<span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.ISSUE">Issue</span>
|
||||
<span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.FINALIZED">Finalized</span>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@@ -96,7 +96,7 @@
|
||||
<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: '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>
|
||||
</div>
|
||||
</td>
|
||||
@@ -115,18 +115,18 @@
|
||||
<p class="text-xs text-gray-400">Delivery #{{ oil.id }}</p>
|
||||
</div>
|
||||
<div class="badge" :class="{
|
||||
'badge-warning': oil.delivery_status == 0,
|
||||
'badge-success': [1, 10].includes(oil.delivery_status),
|
||||
'badge-info': oil.delivery_status == 2,
|
||||
'badge-error': [3, 5].includes(oil.delivery_status),
|
||||
'badge-warning': oil.delivery_status === computedDELIVERY_STATUS.WAITING,
|
||||
'badge-success': [computedDELIVERY_STATUS.DELIVERED, computedDELIVERY_STATUS.FINALIZED].includes(oil.delivery_status as any),
|
||||
'badge-info': oil.delivery_status === computedDELIVERY_STATUS.OUT_FOR_DELIVERY,
|
||||
'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-else-if="oil.delivery_status == 1">Delivered</span>
|
||||
<span v-else-if="oil.delivery_status == 2">Today_Delivery</span>
|
||||
<span v-else-if="oil.delivery_status == 3">Tommorrow_Delivery</span>
|
||||
<span v-else-if="oil.delivery_status == 4">Partial Delivery</span>
|
||||
<span v-else-if="oil.delivery_status == 5">Issue</span>
|
||||
<span v-else-if="oil.delivery_status == 10">Finalized</span>
|
||||
<span v-if="oil.delivery_status === computedDELIVERY_STATUS.WAITING">Waiting</span>
|
||||
<span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.DELIVERED">Delivered</span>
|
||||
<span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.OUT_FOR_DELIVERY">Today_Delivery</span>
|
||||
<span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.TOMORROW">Tommorrow_Delivery</span>
|
||||
<span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.PARTIAL_DELIVERY">Partial Delivery</span>
|
||||
<span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.ISSUE">Issue</span>
|
||||
<span v-else-if="oil.delivery_status === computedDELIVERY_STATUS.FINALIZED">Finalized</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
<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: '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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -169,136 +169,135 @@
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import axios from 'axios'
|
||||
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 PaginationComp from '../../components/pagination.vue'
|
||||
import SideBar from '../../layouts/sidebar/sidebar.vue'
|
||||
import Footer from '../../layouts/footers/footer.vue'
|
||||
import { notify } from "@kyvg/vue3-notification";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'deliveryHome',
|
||||
// Reactive data
|
||||
const delivery_count = ref(0)
|
||||
const delivery_count_delivered = ref(0)
|
||||
const token = ref(null)
|
||||
const user = ref(null)
|
||||
const deliveries = ref<Delivery[]>([])
|
||||
const page = ref(1)
|
||||
const perPage = ref(50)
|
||||
const recordsLength = ref(0)
|
||||
const options = ref({
|
||||
edgeNavigation: false,
|
||||
format: false,
|
||||
template: PaginationComp
|
||||
})
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
// Computed
|
||||
const computedDELIVERY_STATUS = computed(() => DELIVERY_STATUS)
|
||||
|
||||
data() {
|
||||
return {
|
||||
delivery_count: 0,
|
||||
delivery_count_delivered: 0,
|
||||
token: null,
|
||||
user: null,
|
||||
deliveries: [] as any[],
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
options: {
|
||||
edgeNavigation: false,
|
||||
format: false,
|
||||
template: PaginationComp
|
||||
// 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';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
user.value = null
|
||||
})
|
||||
}
|
||||
|
||||
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) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "deleted delivery",
|
||||
type: "success",
|
||||
});
|
||||
getPage(page.value)
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error deleting delivery",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
created() {
|
||||
this.userStatus()
|
||||
},
|
||||
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)
|
||||
},
|
||||
const today_delivery_count = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/stats/delivery/count/today'
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
delivery_count.value = response.data.data;
|
||||
})
|
||||
}
|
||||
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = 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 today_delivery_delivered = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/stats/delivery/count/delivered/today'
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
delivery_count_delivered.value = response.data.data;
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
deleteCall(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "deleted delivery",
|
||||
type: "success",
|
||||
});
|
||||
this.getPage(this.page)
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error deleting delivery",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
today_delivery_count() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/stats/delivery/count/today'
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.delivery_count = response.data.data;
|
||||
})
|
||||
},
|
||||
today_delivery_delivered() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/stats/delivery/count/delivered/today'
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.delivery_count_delivered = response.data.data;
|
||||
})
|
||||
},
|
||||
},
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus()
|
||||
getPage(page.value);
|
||||
today_delivery_count();
|
||||
today_delivery_delivered();
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import DeliveryHome from './home.vue';
|
||||
import DeliveryCreate from "./create.vue";
|
||||
import DeliveryEdit from './edit.vue';
|
||||
import DeliveryOrder from './view.vue';
|
||||
import deliveryTicketsMissing from './update_tickets/missing_data_home.vue';
|
||||
const DeliveryHome = () => import('./home.vue');
|
||||
const DeliveryCreate = () => import("./create.vue");
|
||||
const DeliveryEdit = () => import('./edit.vue');
|
||||
const DeliveryOrder = () => import('./view.vue');
|
||||
const deliveryTicketsMissing = () => import('./update_tickets/missing_data_home.vue');
|
||||
|
||||
import deliveryPending from './viewstatus/pending.vue';
|
||||
import deliveryCancelled from './viewstatus/cancelled.vue';
|
||||
import deliveryIssue from './viewstatus/issue.vue';
|
||||
import deliveryDelivered from './viewstatus/delivered.vue';
|
||||
import deliveryOutForDelivery from './viewstatus/todaysdeliveries.vue';
|
||||
import deliveryWaiting from './viewstatus/waiting.vue';
|
||||
import deliveryFinalized from './viewstatus/finalized.vue'
|
||||
import deliveryTommorrow from './viewstatus/tommorrow.vue'
|
||||
import finalizeTicket from './update_tickets/finalize_ticket.vue';
|
||||
import finalizeTicketAuto from './update_tickets/finalize_ticket_auto.vue';
|
||||
import finalizeTicketAutoNocc from './update_tickets/finalize_ticket_auto_nocc.vue';
|
||||
const deliveryPending = () => import('./viewstatus/pending.vue');
|
||||
const deliveryCancelled = () => import('./viewstatus/cancelled.vue');
|
||||
const deliveryIssue = () => import('./viewstatus/issue.vue');
|
||||
const deliveryDelivered = () => import('./viewstatus/delivered.vue');
|
||||
const deliveryOutForDelivery = () => import('./viewstatus/todaysdeliveries.vue');
|
||||
const deliveryWaiting = () => import('./viewstatus/waiting.vue');
|
||||
const deliveryFinalized = () => import('./viewstatus/finalized.vue')
|
||||
const deliveryTommorrow = () => import('./viewstatus/tommorrow.vue')
|
||||
const finalizeTicket = () => import('./update_tickets/finalize_ticket.vue');
|
||||
const finalizeTicketAuto = () => import('./update_tickets/finalize_ticket_auto.vue');
|
||||
const finalizeTicketAutoNocc = () => import('./update_tickets/finalize_ticket_auto_nocc.vue');
|
||||
|
||||
const deliveryRoutes = [
|
||||
{
|
||||
|
||||
@@ -252,8 +252,9 @@
|
||||
</div>
|
||||
<Footer />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
import Header from '../../../layouts/headers/headerauth.vue'
|
||||
@@ -272,389 +273,395 @@ interface UserCard {
|
||||
security_number: string;
|
||||
}
|
||||
|
||||
// Route and router
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
export default defineComponent({
|
||||
name: 'finalizeTicket',
|
||||
components: { Header, SideBar, Footer },
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
user: { id: 0 },
|
||||
userCardfound: false,
|
||||
preChargeTotal: 0,
|
||||
FinalizeOilOrderForm: {
|
||||
cash_recieved: '',
|
||||
fill_location: '',
|
||||
check_number: '',
|
||||
gallons_delivered: '',
|
||||
},
|
||||
userCard: {} as UserCard,
|
||||
customer: {
|
||||
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: '',
|
||||
},
|
||||
customerDescription: { fill_location: '' },
|
||||
deliveryOrder: {
|
||||
id: '',
|
||||
customer_id: 0,
|
||||
gallons_ordered: 0,
|
||||
customer_asked_for_fill: 0,
|
||||
gallons_delivered: '',
|
||||
delivery_status: 0,
|
||||
when_ordered: '',
|
||||
when_delivered: '',
|
||||
expected_delivery_date: '',
|
||||
customer_price: '',
|
||||
prime: 0,
|
||||
same_day: 0,
|
||||
payment_type: 0,
|
||||
payment_card_id: '',
|
||||
promo_id: null,
|
||||
},
|
||||
pricing: {
|
||||
price_prime: 0,
|
||||
price_same_day: 0,
|
||||
},
|
||||
promo_active: false,
|
||||
promo: {
|
||||
name_of_promotion: '',
|
||||
description: '',
|
||||
money_off_delivery: 0,
|
||||
text_on_ticket: ''
|
||||
},
|
||||
total_amount: 0,
|
||||
discount: 0,
|
||||
total_amount_after_discount: 0,
|
||||
transaction: null as any,
|
||||
// Reactive data
|
||||
const isLoading = ref(false)
|
||||
const user = ref({ id: 0 })
|
||||
const userCardfound = ref(false)
|
||||
const preChargeTotal = ref(0)
|
||||
const FinalizeOilOrderForm = ref({
|
||||
cash_recieved: '',
|
||||
fill_location: '',
|
||||
check_number: '',
|
||||
gallons_delivered: '',
|
||||
})
|
||||
const userCard = ref({} as UserCard)
|
||||
const customer = ref({
|
||||
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: '',
|
||||
})
|
||||
const customerDescription = ref({ fill_location: '' })
|
||||
const deliveryOrder = ref({
|
||||
id: '',
|
||||
customer_id: 0,
|
||||
gallons_ordered: 0,
|
||||
customer_asked_for_fill: 0,
|
||||
gallons_delivered: '',
|
||||
delivery_status: 0,
|
||||
when_ordered: '',
|
||||
when_delivered: '',
|
||||
expected_delivery_date: '',
|
||||
customer_price: '',
|
||||
prime: 0,
|
||||
same_day: 0,
|
||||
payment_type: 0,
|
||||
payment_card_id: '',
|
||||
promo_id: null,
|
||||
})
|
||||
const pricing = ref({
|
||||
price_prime: 0,
|
||||
price_same_day: 0,
|
||||
})
|
||||
const promo_active = ref(false)
|
||||
const promo = ref({
|
||||
name_of_promotion: '',
|
||||
description: '',
|
||||
money_off_delivery: 0,
|
||||
text_on_ticket: ''
|
||||
})
|
||||
const total_amount = ref(0)
|
||||
const discount = ref(0)
|
||||
const total_amount_after_discount = ref(0)
|
||||
const transaction = ref(null as any)
|
||||
|
||||
// Computed properties
|
||||
const finalChargeAmount = computed((): number => {
|
||||
// If promo is active, use server-calculated totals with fees added
|
||||
if (promo_active.value && total_amount_after_discount.value > 0) {
|
||||
let total = total_amount_after_discount.value;
|
||||
if (deliveryOrder.value.prime === 1) total += Number(pricing.value.price_prime);
|
||||
if (deliveryOrder.value.same_day === 1) total += Number(pricing.value.price_same_day);
|
||||
return total;
|
||||
}
|
||||
|
||||
// Otherwise, calculate locally
|
||||
const gallons = Number(FinalizeOilOrderForm.value.gallons_delivered);
|
||||
const pricePerGallon = Number(deliveryOrder.value.customer_price);
|
||||
if (isNaN(gallons) || isNaN(pricePerGallon) || gallons <= 0) {
|
||||
return 0;
|
||||
}
|
||||
let total = gallons * pricePerGallon;
|
||||
if (deliveryOrder.value.prime === 1) total += Number(pricing.value.price_prime);
|
||||
if (deliveryOrder.value.same_day === 1) total += Number(pricing.value.price_same_day);
|
||||
return total;
|
||||
})
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
const deliveryId = route.params.id;
|
||||
// --- DEBUGGING STEP 1 ---
|
||||
console.log(`[DEBUG] Component Mounted. Fetching data for delivery ID: ${deliveryId}`);
|
||||
getOilOrder(deliveryId);
|
||||
getOilPricing();
|
||||
})
|
||||
|
||||
// Functions
|
||||
const getOilOrder = async (delivery_id: any) => {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/delivery/order/${delivery_id}`;
|
||||
// --- DEBUGGING STEP 2 ---
|
||||
console.log(`[DEBUG] Calling getOilOrder API at: ${path}`);
|
||||
|
||||
try {
|
||||
const response = await axios.get(path, { withCredentials: true, headers: authHeader() });
|
||||
// --- DEBUGGING STEP 3 ---
|
||||
console.log('[DEBUG] Received RAW response from getOilOrder:', response.data);
|
||||
|
||||
if (response.data && response.data.ok) {
|
||||
console.log('[DEBUG] Response is OK. Processing data...');
|
||||
deliveryOrder.value = response.data.delivery;
|
||||
|
||||
// --- DEBUGGING STEP 4 ---
|
||||
console.log(`[DEBUG] Value of response.data.total_amount is:`, response.data.total_amount);
|
||||
|
||||
total_amount.value = response.data.delivery.total_amount || 0;
|
||||
preChargeTotal.value = response.data.delivery.total_amount || 0;
|
||||
|
||||
|
||||
await getCustomer(deliveryOrder.value.customer_id);
|
||||
|
||||
if ([1, 2, 11].includes(deliveryOrder.value.payment_type) && deliveryOrder.value.payment_card_id) {
|
||||
getPaymentCard(deliveryOrder.value.payment_card_id);
|
||||
}
|
||||
|
||||
if (deliveryOrder.value.promo_id != null) {
|
||||
getPromo(deliveryOrder.value.promo_id);
|
||||
}
|
||||
|
||||
// Fetch calculated totals including discounts
|
||||
sumdelivery(delivery_id);
|
||||
|
||||
// Call transaction fetch after customer is loaded
|
||||
setTimeout(() => getTransaction(delivery_id), 500);
|
||||
} else {
|
||||
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" });
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
finalChargeAmount(): number {
|
||||
// If promo is active, use server-calculated totals with fees added
|
||||
if (this.promo_active && this.total_amount_after_discount > 0) {
|
||||
let total = this.total_amount_after_discount;
|
||||
if (this.deliveryOrder.prime === 1) total += Number(this.pricing.price_prime);
|
||||
if (this.deliveryOrder.same_day === 1) total += Number(this.pricing.price_same_day);
|
||||
return total;
|
||||
} catch (error) {
|
||||
// --- DEBUGGING STEP 5 ---
|
||||
console.error("[DEBUG] The getOilOrder API call FAILED. Error object:", error);
|
||||
notify({ title: "Network Error", text: "Could not fetch delivery order.", type: "error" });
|
||||
}
|
||||
}
|
||||
|
||||
const getPaymentCard = async (card_id: any) => {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/payment/card/${card_id}`;
|
||||
try {
|
||||
const response = await axios.get(path, { withCredentials: true, headers: authHeader() });
|
||||
userCard.value = response.data;
|
||||
userCardfound.value = true;
|
||||
} catch (error) {
|
||||
userCardfound.value = false;
|
||||
console.error(`[DEBUG] Error fetching payment card ${card_id}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
const getCustomer = async (user_id: any) => {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/${user_id}`;
|
||||
try {
|
||||
const response = await axios.get(path, { withCredentials: true });
|
||||
customer.value = response.data;
|
||||
await getCustomerDescription(deliveryOrder.value.customer_id);
|
||||
} catch (error) { console.error("[DEBUG] Error fetching customer:", error); }
|
||||
}
|
||||
|
||||
const getCustomerDescription = async (user_id: any) => {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/description/${user_id}`;
|
||||
try {
|
||||
const response = await axios.get(path, { withCredentials: true });
|
||||
customerDescription.value = response.data;
|
||||
FinalizeOilOrderForm.value.fill_location = customerDescription.value.fill_location;
|
||||
} catch (error) { console.error("[DEBUG] Error fetching customer description:", error); }
|
||||
}
|
||||
|
||||
const getOilPricing = () => {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/info/price/oil/table`;
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => { pricing.value = response.data; })
|
||||
.catch((error: any) => { console.error("[DEBUG] Error fetching oil pricing:", error); });
|
||||
}
|
||||
|
||||
const getPromo = (promo_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/promo/" + promo_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
promo.value = response.data
|
||||
promo_active.value = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Otherwise, calculate locally
|
||||
const gallons = Number(this.FinalizeOilOrderForm.gallons_delivered);
|
||||
const pricePerGallon = Number(this.deliveryOrder.customer_price);
|
||||
if (isNaN(gallons) || isNaN(pricePerGallon) || gallons <= 0) {
|
||||
return 0;
|
||||
const sumdelivery = (delivery_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/delivery/total/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
total_amount.value = parseFloat(response.data.total_amount) || 0;
|
||||
discount.value = parseFloat(response.data.discount) || 0;
|
||||
total_amount_after_discount.value = parseFloat(response.data.total_amount_after_discount) || 0;
|
||||
}
|
||||
let total = gallons * pricePerGallon;
|
||||
if (this.deliveryOrder.prime === 1) total += Number(this.pricing.price_prime);
|
||||
if (this.deliveryOrder.same_day === 1) total += Number(this.pricing.price_same_day);
|
||||
return total;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const deliveryId = this.$route.params.id;
|
||||
// --- DEBUGGING STEP 1 ---
|
||||
console.log(`[DEBUG] Component Mounted. Fetching data for delivery ID: ${deliveryId}`);
|
||||
this.getOilOrder(deliveryId);
|
||||
this.getOilPricing();
|
||||
},
|
||||
methods: {
|
||||
async getOilOrder(delivery_id: any) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/delivery/order/${delivery_id}`;
|
||||
// --- DEBUGGING STEP 2 ---
|
||||
console.log(`[DEBUG] Calling getOilOrder API at: ${path}`);
|
||||
|
||||
try {
|
||||
const response = await axios.get(path, { withCredentials: true, headers: authHeader() });
|
||||
// --- DEBUGGING STEP 3 ---
|
||||
console.log('[DEBUG] Received RAW response from getOilOrder:', response.data);
|
||||
|
||||
if (response.data && response.data.ok) {
|
||||
console.log('[DEBUG] Response is OK. Processing data...');
|
||||
this.deliveryOrder = response.data.delivery;
|
||||
|
||||
// --- DEBUGGING STEP 4 ---
|
||||
console.log(`[DEBUG] Value of response.data.total_amount is:`, response.data.total_amount);
|
||||
|
||||
this.total_amount = response.data.delivery.total_amount || 0;
|
||||
this.preChargeTotal = response.data.delivery.total_amount || 0;
|
||||
|
||||
|
||||
await this.getCustomer(this.deliveryOrder.customer_id);
|
||||
|
||||
if ([1, 2, 11].includes(this.deliveryOrder.payment_type) && this.deliveryOrder.payment_card_id) {
|
||||
this.getPaymentCard(this.deliveryOrder.payment_card_id);
|
||||
}
|
||||
|
||||
if (this.deliveryOrder.promo_id != null) {
|
||||
this.getPromo(this.deliveryOrder.promo_id);
|
||||
}
|
||||
|
||||
// Fetch calculated totals including discounts
|
||||
this.sumdelivery(delivery_id);
|
||||
|
||||
// Call transaction fetch after customer is loaded
|
||||
setTimeout(() => this.getTransaction(delivery_id), 500);
|
||||
} else {
|
||||
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" });
|
||||
}
|
||||
} catch (error) {
|
||||
// --- DEBUGGING STEP 5 ---
|
||||
console.error("[DEBUG] The getOilOrder API call FAILED. Error object:", error);
|
||||
notify({ title: "Network Error", text: "Could not fetch delivery order.", type: "error" });
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
async getPaymentCard(card_id: any) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/payment/card/${card_id}`;
|
||||
try {
|
||||
const response = await axios.get(path, { withCredentials: true, headers: authHeader() });
|
||||
this.userCard = response.data;
|
||||
this.userCardfound = true;
|
||||
} catch (error) {
|
||||
this.userCardfound = false;
|
||||
console.error(`[DEBUG] Error fetching payment card ${card_id}:`, error);
|
||||
}
|
||||
},
|
||||
async getCustomer(user_id: any) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/${user_id}`;
|
||||
try {
|
||||
const response = await axios.get(path, { withCredentials: true });
|
||||
this.customer = response.data;
|
||||
await this.getCustomerDescription(this.deliveryOrder.customer_id);
|
||||
} catch (error) { console.error("[DEBUG] Error fetching customer:", error); }
|
||||
},
|
||||
async getCustomerDescription(user_id: any) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/description/${user_id}`;
|
||||
try {
|
||||
const response = await axios.get(path, { withCredentials: true });
|
||||
this.customerDescription = response.data;
|
||||
this.FinalizeOilOrderForm.fill_location = this.customerDescription.fill_location;
|
||||
} catch (error) { console.error("[DEBUG] Error fetching customer description:", error); }
|
||||
},
|
||||
getOilPricing() {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/info/price/oil/table`;
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => { this.pricing = response.data; })
|
||||
.catch((error: any) => { console.error("[DEBUG] Error fetching oil pricing:", error); });
|
||||
},
|
||||
getPromo(promo_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/promo/" + promo_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
this.promo = response.data
|
||||
this.promo_active = true
|
||||
}
|
||||
})
|
||||
},
|
||||
sumdelivery(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/delivery/total/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.total_amount = parseFloat(response.data.total_amount) || 0;
|
||||
this.discount = parseFloat(response.data.discount) || 0;
|
||||
this.total_amount_after_discount = parseFloat(response.data.total_amount_after_discount) || 0;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get oil pricing",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
},
|
||||
getTransaction(delivery_id: any) {
|
||||
// Add guard to prevent undefined customer ID API calls
|
||||
if (!delivery_id || !this.customer || !this.customer.id) {
|
||||
console.log("Skipping transaction fetch - delivery or customer data not available");
|
||||
return;
|
||||
}
|
||||
|
||||
// Consistent with delivery/view.vue - use customer transaction endpoint
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/payment/transactions/customer/${this.customer.id}/1`;
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
console.log("Transaction API response:", response.data);
|
||||
// Handle both single transaction object and array responses
|
||||
if (response.data && Array.isArray(response.data) && response.data.length > 0) {
|
||||
// Find the transaction for this specific delivery
|
||||
const deliveryTransaction = response.data.find((txn: any) =>
|
||||
txn.delivery_id === parseInt(delivery_id) ||
|
||||
txn.transaction_id === delivery_id ||
|
||||
txn.delivery_number === delivery_id
|
||||
);
|
||||
this.transaction = deliveryTransaction || null;
|
||||
} else if (response.data && !Array.isArray(response.data)) {
|
||||
// If single transaction, check if it's for this delivery
|
||||
const txn = response.data;
|
||||
if (txn.delivery_id === parseInt(delivery_id) ||
|
||||
txn.transaction_id === delivery_id ||
|
||||
txn.delivery_number === delivery_id) {
|
||||
this.transaction = txn;
|
||||
} else {
|
||||
this.transaction = null;
|
||||
}
|
||||
} else {
|
||||
this.transaction = null;
|
||||
}
|
||||
|
||||
if (!this.transaction) {
|
||||
console.log(`No transaction found for delivery ${delivery_id} among customer transactions`);
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
// Handle various error responses gracefully
|
||||
if (error.response && error.response.status === 404) {
|
||||
console.log(`No transactions found for customer ${this.customer.id}`);
|
||||
this.transaction = null;
|
||||
} else if (error.response && error.response.status === 400) {
|
||||
console.log(`Bad request for customer transactions: ${error.response.data?.detail || error.message}`);
|
||||
this.transaction = null;
|
||||
} else {
|
||||
console.error("Error fetching transaction:", error);
|
||||
this.transaction = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
getTypeColor(transactionType: number) {
|
||||
switch (transactionType) {
|
||||
case 1: return 'text-blue-600'; // Auth
|
||||
case 0: return 'text-orange-600'; // Charge
|
||||
case 2: return 'text-purple-600'; // Capture
|
||||
case 3: return 'text-green-600'; // Delivery/Other
|
||||
default: return 'text-gray-600';
|
||||
}
|
||||
},
|
||||
CreateTransaction() {
|
||||
const path = `${import.meta.env.VITE_MONEY_URL}/delivery/add/${this.deliveryOrder.id}`;
|
||||
axios.post(path, {}, { withCredentials: true, headers: authHeader() })
|
||||
.then(() => notify({ title: "Success", text: "Accounting record created.", type: "success" }))
|
||||
.catch(() => notify({ title: "Warning", text: "Could not create accounting record.", type: "warn" }));
|
||||
},
|
||||
async onSubmit() {
|
||||
if (Number(this.FinalizeOilOrderForm.gallons_delivered) <= 0) {
|
||||
notify({ title: "Validation Error", text: "Gallons delivered must be greater than zero.", type: "error" });
|
||||
return;
|
||||
}
|
||||
this.isLoading = true;
|
||||
const finalizePayload = {
|
||||
gallons_delivered: this.FinalizeOilOrderForm.gallons_delivered,
|
||||
fill_location: this.FinalizeOilOrderForm.fill_location,
|
||||
cash_recieved: this.FinalizeOilOrderForm.cash_recieved,
|
||||
check_number: this.FinalizeOilOrderForm.check_number,
|
||||
};
|
||||
const finalizePath = `${import.meta.env.VITE_BASE_URL}/deliverydata/finalize/${this.deliveryOrder.id}`;
|
||||
try {
|
||||
const finalizeResponse = await axios.put(finalizePath, finalizePayload, { withCredentials: true, headers: authHeader() });
|
||||
if (!finalizeResponse.data.ok) {
|
||||
throw new Error(finalizeResponse.data.error || "Failed to update delivery details.");
|
||||
}
|
||||
this.CreateTransaction();
|
||||
notify({ title: "Success", text: "Ticket has been finalized.", type: "success" });
|
||||
|
||||
// FIX: Wait for customer data to be loaded before redirecting
|
||||
await this.waitForCustomerData(this.deliveryOrder.customer_id);
|
||||
|
||||
// Updated redirect logic based on your requirements
|
||||
await this.handleRedirect();
|
||||
|
||||
} catch (error: any) {
|
||||
const errorMessage = error.response?.data?.detail || "An error occurred during finalization.";
|
||||
notify({ title: "Error", text: errorMessage, type: "error" });
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// NEW: Wait for customer data to be loaded before redirecting
|
||||
async waitForCustomerData(customerId: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const checkCustomer = () => {
|
||||
if (this.customer && this.customer.id && this.customer.id === customerId) {
|
||||
resolve();
|
||||
} else {
|
||||
setTimeout(checkCustomer, 100);
|
||||
}
|
||||
};
|
||||
checkCustomer();
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get oil pricing",
|
||||
type: "error",
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// NEW: Updated redirect logic based on payment type and transaction status
|
||||
async handleRedirect() {
|
||||
console.log('[DEBUG] Starting redirect logic...');
|
||||
console.log('[DEBUG] payment_type:', this.deliveryOrder.payment_type);
|
||||
console.log('[DEBUG] transaction:', this.transaction);
|
||||
console.log('[DEBUG] customer:', this.customer);
|
||||
|
||||
if (this.deliveryOrder.payment_type === 1) {
|
||||
// payment_type 1: Manual charging - already charged, just redirect to profile
|
||||
console.log('[DEBUG] payment_type 1 - redirecting to customer profile');
|
||||
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } });
|
||||
|
||||
} else if (this.deliveryOrder.payment_type === 11) {
|
||||
// payment_type 11: API charging - check transaction type
|
||||
console.log('[DEBUG] payment_type 11 - checking transaction type');
|
||||
|
||||
if (this.transaction) {
|
||||
console.log('[DEBUG] Transaction found, type:', this.transaction.transaction_type);
|
||||
|
||||
if (this.transaction.transaction_type === 0) {
|
||||
// Already charged (transaction_type = 0) - redirect to profile
|
||||
console.log('[DEBUG] Already charged - redirecting to customer profile');
|
||||
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } });
|
||||
|
||||
} else if (this.transaction.transaction_type === 1) {
|
||||
// Auth only (transaction_type = 1) - redirect to capture page
|
||||
console.log('[DEBUG] Auth only - redirecting to capture page');
|
||||
this.$router.push({ name: "captureAuthorize", params: { id: this.deliveryOrder.id } });
|
||||
|
||||
} else {
|
||||
// Unknown transaction type - default to customer profile
|
||||
console.log('[DEBUG] Unknown transaction type - redirecting to customer profile');
|
||||
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } });
|
||||
}
|
||||
const getTransaction = (delivery_id: any) => {
|
||||
// Add guard to prevent undefined customer ID API calls
|
||||
if (!delivery_id || !customer.value || !customer.value.id) {
|
||||
console.log("Skipping transaction fetch - delivery or customer data not available");
|
||||
return;
|
||||
}
|
||||
|
||||
// Consistent with delivery/view.vue - use customer transaction endpoint
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/payment/transactions/customer/${customer.value.id}/1`;
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
console.log("Transaction API response:", response.data);
|
||||
// Handle both single transaction object and array responses
|
||||
if (response.data && Array.isArray(response.data) && response.data.length > 0) {
|
||||
// Find the transaction for this specific delivery
|
||||
const deliveryTransaction = response.data.find((txn: any) =>
|
||||
txn.delivery_id === parseInt(delivery_id) ||
|
||||
txn.transaction_id === delivery_id ||
|
||||
txn.delivery_number === delivery_id
|
||||
);
|
||||
transaction.value = deliveryTransaction || null;
|
||||
} else if (response.data && !Array.isArray(response.data)) {
|
||||
// If single transaction, check if it's for this delivery
|
||||
const txn = response.data;
|
||||
if (txn.delivery_id === parseInt(delivery_id) ||
|
||||
txn.transaction_id === delivery_id ||
|
||||
txn.delivery_number === delivery_id) {
|
||||
transaction.value = txn;
|
||||
} else {
|
||||
// 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');
|
||||
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } });
|
||||
transaction.value = null;
|
||||
}
|
||||
} else {
|
||||
transaction.value = null;
|
||||
}
|
||||
|
||||
} else if ([2].includes(this.deliveryOrder.payment_type)) {
|
||||
// payment_type 2: Credit Card & Cash - go to capture page for any remaining payment
|
||||
console.log('[DEBUG] payment_type 2 - redirecting to capture page');
|
||||
this.$router.push({ name: "captureAuthorize", params: { id: this.deliveryOrder.id } });
|
||||
if (!transaction.value) {
|
||||
console.log(`No transaction found for delivery ${delivery_id} among customer transactions`);
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
// Handle various error responses gracefully
|
||||
if (error.response && error.response.status === 404) {
|
||||
console.log(`No transactions found for customer ${customer.value.id}`);
|
||||
transaction.value = null;
|
||||
} else if (error.response && error.response.status === 400) {
|
||||
console.log(`Bad request for customer transactions: ${error.response.data?.detail || error.message}`);
|
||||
transaction.value = null;
|
||||
} else {
|
||||
console.error("Error fetching transaction:", error);
|
||||
transaction.value = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const getTypeColor = (transactionType: number) => {
|
||||
switch (transactionType) {
|
||||
case 1: return 'text-blue-600'; // Auth
|
||||
case 0: return 'text-orange-600'; // Charge
|
||||
case 2: return 'text-purple-600'; // Capture
|
||||
case 3: return 'text-green-600'; // Delivery/Other
|
||||
default: return 'text-gray-600';
|
||||
}
|
||||
}
|
||||
|
||||
const CreateTransaction = () => {
|
||||
const path = `${import.meta.env.VITE_MONEY_URL}/delivery/add/${deliveryOrder.value.id}`;
|
||||
axios.post(path, {}, { withCredentials: true, headers: authHeader() })
|
||||
.then(() => notify({ title: "Success", text: "Accounting record created.", type: "success" }))
|
||||
.catch(() => notify({ title: "Warning", text: "Could not create accounting record.", type: "warn" }));
|
||||
}
|
||||
|
||||
const onSubmit = async () => {
|
||||
if (Number(FinalizeOilOrderForm.value.gallons_delivered) <= 0) {
|
||||
notify({ title: "Validation Error", text: "Gallons delivered must be greater than zero.", type: "error" });
|
||||
return;
|
||||
}
|
||||
isLoading.value = true;
|
||||
const finalizePayload = {
|
||||
gallons_delivered: FinalizeOilOrderForm.value.gallons_delivered,
|
||||
fill_location: FinalizeOilOrderForm.value.fill_location,
|
||||
cash_recieved: FinalizeOilOrderForm.value.cash_recieved,
|
||||
check_number: FinalizeOilOrderForm.value.check_number,
|
||||
};
|
||||
const finalizePath = `${import.meta.env.VITE_BASE_URL}/deliverydata/finalize/${deliveryOrder.value.id}`;
|
||||
try {
|
||||
const finalizeResponse = await axios.put(finalizePath, finalizePayload, { withCredentials: true, headers: authHeader() });
|
||||
if (!finalizeResponse.data.ok) {
|
||||
throw new Error(finalizeResponse.data.error || "Failed to update delivery details.");
|
||||
}
|
||||
CreateTransaction();
|
||||
notify({ title: "Success", text: "Ticket has been finalized.", type: "success" });
|
||||
|
||||
// FIX: Wait for customer data to be loaded before redirecting
|
||||
await waitForCustomerData(deliveryOrder.value.customer_id);
|
||||
|
||||
// Updated redirect logic based on your requirements
|
||||
await handleRedirect();
|
||||
|
||||
} catch (error: any) {
|
||||
const errorMessage = error.response?.data?.detail || "An error occurred during finalization.";
|
||||
notify({ title: "Error", text: errorMessage, type: "error" });
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// NEW: Wait for customer data to be loaded before redirecting
|
||||
const waitForCustomerData = async (customerId: number): Promise<void> => {
|
||||
return new Promise((resolve) => {
|
||||
const checkCustomer = () => {
|
||||
if (customer.value && customer.value.id && customer.value.id === customerId) {
|
||||
resolve();
|
||||
} else {
|
||||
setTimeout(checkCustomer, 100);
|
||||
}
|
||||
};
|
||||
checkCustomer();
|
||||
});
|
||||
}
|
||||
|
||||
// NEW: Updated redirect logic based on payment type and transaction status
|
||||
const handleRedirect = async () => {
|
||||
console.log('[DEBUG] Starting redirect logic...');
|
||||
console.log('[DEBUG] payment_type:', deliveryOrder.value.payment_type);
|
||||
console.log('[DEBUG] transaction:', transaction.value);
|
||||
console.log('[DEBUG] customer:', customer.value);
|
||||
|
||||
if (deliveryOrder.value.payment_type === 1) {
|
||||
// payment_type 1: Manual charging - already charged, just redirect to profile
|
||||
console.log('[DEBUG] payment_type 1 - redirecting to customer profile');
|
||||
router.push({ name: "customerProfile", params: { id: customer.value.id } });
|
||||
|
||||
} else if (deliveryOrder.value.payment_type === 11) {
|
||||
// payment_type 11: API charging - check transaction type
|
||||
console.log('[DEBUG] payment_type 11 - checking transaction type');
|
||||
|
||||
if (transaction.value) {
|
||||
console.log('[DEBUG] Transaction found, type:', transaction.value.transaction_type);
|
||||
|
||||
if (transaction.value.transaction_type === 0) {
|
||||
// Already charged (transaction_type = 0) - redirect to profile
|
||||
console.log('[DEBUG] Already charged - redirecting to customer profile');
|
||||
router.push({ name: "customerProfile", params: { id: customer.value.id } });
|
||||
|
||||
} else if (transaction.value.transaction_type === 1) {
|
||||
// Auth only (transaction_type = 1) - redirect to capture page
|
||||
console.log('[DEBUG] Auth only - redirecting to capture page');
|
||||
router.push({ name: "captureAuthorize", params: { id: deliveryOrder.value.id } });
|
||||
|
||||
} else {
|
||||
// Default case (cash, check, etc.) - redirect to customer profile
|
||||
console.log('[DEBUG] Default payment type - redirecting to customer profile');
|
||||
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } });
|
||||
// Unknown transaction type - default to customer profile
|
||||
console.log('[DEBUG] Unknown transaction type - redirecting to customer profile');
|
||||
router.push({ name: "customerProfile", params: { id: customer.value.id } });
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
} else {
|
||||
// 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');
|
||||
router.push({ name: "customerProfile", params: { id: customer.value.id } });
|
||||
}
|
||||
|
||||
} else if ([2].includes(deliveryOrder.value.payment_type)) {
|
||||
// payment_type 2: Credit Card & Cash - go to capture page for any remaining payment
|
||||
console.log('[DEBUG] payment_type 2 - redirecting to capture page');
|
||||
router.push({ name: "captureAuthorize", params: { id: deliveryOrder.value.id } });
|
||||
|
||||
} else {
|
||||
// Default case (cash, check, etc.) - redirect to customer profile
|
||||
console.log('[DEBUG] Default payment type - redirecting to customer profile');
|
||||
router.push({ name: "customerProfile", params: { id: customer.value.id } });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
<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="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="text-xs opacity-70">{{ userCard.type_of_card }}</div>
|
||||
<div class="mt-1 text-sm font-mono tracking-wider">
|
||||
@@ -119,471 +119,458 @@
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
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"
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name: 'finalizeTicketAuto',
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
v$: useValidate(),
|
||||
loaded: false,
|
||||
user: {
|
||||
id: 0
|
||||
},
|
||||
userCardfound: false,
|
||||
deliveryStatus: [],
|
||||
userCards: [],
|
||||
deliveryNotesDriver: [],
|
||||
today_oil_price: 0,
|
||||
|
||||
FinalizeOilOrderForm: {
|
||||
fill_location: 0,
|
||||
check_number: 0,
|
||||
delivery_status: 10,
|
||||
userCards: [],
|
||||
credit_card_id: 0,
|
||||
driver: 0,
|
||||
gallons_delivered: '',
|
||||
customer_filled: false,
|
||||
prime: false,
|
||||
same_day: false,
|
||||
emergency: false,
|
||||
},
|
||||
CreateOilOrderForm: {
|
||||
basicInfo: {
|
||||
gallons_delivered: '',
|
||||
userCards: []
|
||||
},
|
||||
},
|
||||
userCard: {
|
||||
date_added: '',
|
||||
user_id: '',
|
||||
card_number: '',
|
||||
last_four_digits: '',
|
||||
name_on_card: '',
|
||||
expiration_month: '',
|
||||
expiration_year: '',
|
||||
type_of_card: '',
|
||||
security_number: '',
|
||||
accepted_or_declined: '',
|
||||
main_card: '',
|
||||
},
|
||||
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: '',
|
||||
},
|
||||
customerDescription: {
|
||||
customer_id: 0,
|
||||
account_number: '',
|
||||
company_id: 0,
|
||||
fill_location: 0,
|
||||
description: '',
|
||||
},
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
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"
|
||||
|
||||
// Route and router
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
autoTicket: {
|
||||
id: 0,
|
||||
customer_id: '',
|
||||
account_number: '',
|
||||
// Reactive data
|
||||
const v$ = useValidate()
|
||||
const loaded = ref(false)
|
||||
const user = ref({
|
||||
id: 0
|
||||
})
|
||||
const userCardfound = ref(false)
|
||||
const deliveryStatus = ref([])
|
||||
const userCards = ref([])
|
||||
const deliveryNotesDriver = ref([])
|
||||
const today_oil_price = ref(0)
|
||||
|
||||
customer_town : '',
|
||||
customer_state : '',
|
||||
customer_address : '',
|
||||
customer_zip: '',
|
||||
customer_full_name : '',
|
||||
const FinalizeOilOrderForm = ref({
|
||||
fill_location: 0,
|
||||
check_number: 0,
|
||||
delivery_status: 10,
|
||||
userCards: [],
|
||||
credit_card_id: 0,
|
||||
driver: 0,
|
||||
gallons_delivered: '',
|
||||
customer_filled: false,
|
||||
prime: false,
|
||||
same_day: false,
|
||||
emergency: false,
|
||||
})
|
||||
const CreateOilOrderForm = ref({
|
||||
basicInfo: {
|
||||
gallons_delivered: '',
|
||||
userCards: []
|
||||
},
|
||||
})
|
||||
const userCard = ref<{
|
||||
date_added: string;
|
||||
user_id: string;
|
||||
card_number: string;
|
||||
last_four_digits: string;
|
||||
name_on_card: string;
|
||||
expiration_month: string;
|
||||
expiration_year: string;
|
||||
type_of_card: string;
|
||||
security_number: string;
|
||||
accepted_or_declined: string;
|
||||
main_card: string;
|
||||
} | null>(null)
|
||||
const customer = ref({
|
||||
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: '',
|
||||
})
|
||||
const customerDescription = ref({
|
||||
customer_id: 0,
|
||||
account_number: '',
|
||||
company_id: 0,
|
||||
fill_location: 0,
|
||||
description: '',
|
||||
})
|
||||
|
||||
oil_prices_id : '',
|
||||
fill_date : '',
|
||||
gallons_delivered : '',
|
||||
price_per_gallon : '',
|
||||
const autoTicket = ref({
|
||||
id: 0,
|
||||
customer_id: '',
|
||||
account_number: '',
|
||||
|
||||
total_amount_customer : '',
|
||||
customer_town : '',
|
||||
customer_state : '',
|
||||
customer_address : '',
|
||||
customer_zip: '',
|
||||
customer_full_name : '',
|
||||
|
||||
payment_type : '',
|
||||
payment_card_id : '',
|
||||
payment_status : '',
|
||||
open_ticket_id: 0
|
||||
oil_prices_id : '',
|
||||
fill_date : '',
|
||||
gallons_delivered : '',
|
||||
price_per_gallon : '',
|
||||
|
||||
},
|
||||
total_amount_customer : '',
|
||||
|
||||
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,
|
||||
},
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.userStatus()
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.today_price_oil();
|
||||
this.getAutoTicket(this.$route.params.id);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.today_price_oil();
|
||||
this.getAutoTicket(this.$route.params.id);
|
||||
payment_type : '',
|
||||
payment_card_id : '',
|
||||
payment_status : '',
|
||||
open_ticket_id: 0
|
||||
|
||||
|
||||
},
|
||||
|
||||
methods: {
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
this.user.id = response.data.user_id;
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
getPaymentCard(card_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/payment/card/" + card_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
|
||||
if (response.data.userCard.card_number === ''){
|
||||
this.userCard === null;
|
||||
this.userCardfound = false;
|
||||
}
|
||||
else{
|
||||
this.userCard = response.data;
|
||||
this.userCardfound = true;
|
||||
}
|
||||
this.FinalizeOilOrderForm.userCards = response.data.id
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
},
|
||||
getPaymentCards(user_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/payment/cards/" + user_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.userCards = response.data;
|
||||
if (this.userCards && this.userCards.length > 0) {
|
||||
this.userCardfound = true;
|
||||
this.userCard = this.userCards.find((card: any) => card.main_card) || this.userCards[0];
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
},
|
||||
getCustomer(user_id: any) {
|
||||
if (!user_id || user_id === 'undefined') return;
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/" + user_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.customer = response.data;
|
||||
if (this.customer.id > 0) {
|
||||
this.getPaymentCards(this.customer.user_id || this.customer.id);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not find customer",
|
||||
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: '' };
|
||||
});
|
||||
},
|
||||
getCustomerDescription(user_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/description/" + user_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.customerDescription = response.data;
|
||||
this.loaded = true
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not find customer",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
},
|
||||
getAutoTicket(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/delivery/autoticket/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.autoTicket = response.data;
|
||||
this.getCustomer(this.autoTicket.customer_id)
|
||||
})
|
||||
|
||||
this.getAutoDelivery(this.autoTicket.id)
|
||||
this.getCustomerDescription(this.autoTicket.customer_id)
|
||||
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get automatic",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getAutoDelivery(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/delivery/finddelivery/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
const autoDelivery = ref({
|
||||
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,
|
||||
})
|
||||
|
||||
this.autoDelivery = response.data;
|
||||
this.getCustomer(this.autoDelivery.customer_id)
|
||||
this.getCustomerDescription(this.autoDelivery.customer_id)
|
||||
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get automatic",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
},
|
||||
today_price_oil() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/info/price/oil'
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.today_oil_price = response.data.price_for_customer;
|
||||
})
|
||||
},
|
||||
UpdateAuto(payload: {
|
||||
gallons: string,
|
||||
delivery_id: string,
|
||||
}) {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/confirm/delivery"
|
||||
axios({
|
||||
method: "put",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
text: 'Update',
|
||||
type: 'postive',
|
||||
title: 'top'
|
||||
})
|
||||
this.$router.push({ name: "auto" });
|
||||
}
|
||||
else {
|
||||
notify({
|
||||
text: 'Auto Failure',
|
||||
type: 'negative',
|
||||
title: 'Update'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
// Watchers
|
||||
watch(() => route.params, () => {
|
||||
today_price_oil()
|
||||
getAutoTicket(route.params.id)
|
||||
}, { immediate: false })
|
||||
|
||||
|
||||
ConfirmAuto(payload: {
|
||||
gallons_delivered: string,
|
||||
}) {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/confirm/auto/create/" + this.autoDelivery.id;
|
||||
axios({
|
||||
method: "post",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus()
|
||||
today_price_oil()
|
||||
getAutoTicket(route.params.id)
|
||||
})
|
||||
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "Auto Delivered",
|
||||
type: "success",
|
||||
});
|
||||
this.CreateTransaction(response.data['0']['auto_ticket_id'])
|
||||
this.updateTransactionDelivery(this.autoDelivery.id, response.data['0']['auto_ticket_id'])
|
||||
this.$router.push({ name: "payAutoCapture", params: { id: response.data['0']['auto_ticket_id'] } });
|
||||
|
||||
}
|
||||
if (response.data.error) {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not finalize auto",
|
||||
type: "error",
|
||||
});
|
||||
this.$router.push("auto");
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
closeTicket(ticketId: number) {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/confirm/auto/close_ticket/" + ticketId;
|
||||
axios({
|
||||
method: "put",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then(() => {
|
||||
// Ticket closed successfully
|
||||
})
|
||||
},
|
||||
|
||||
UpdateDeliveredAuto(payload: {
|
||||
gallons_delivered: string,
|
||||
}) {
|
||||
console.log(this.autoDelivery)
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/confirm/auto/update/" + this.autoDelivery.id;
|
||||
axios({
|
||||
method: "put",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "Auto Updated",
|
||||
type: "success",
|
||||
});
|
||||
// Removed redirect from here, will handle in onSubmit
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
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}`;
|
||||
axios.put(path, {}, { withCredentials: true, headers: authHeader() })
|
||||
.then(() => console.log("Transaction auto_id updated"))
|
||||
.catch(() => console.error("Error updating transaction auto_id"));
|
||||
},
|
||||
|
||||
CreateTransaction(auto_ticket_id: string,) {
|
||||
let path = import.meta.env.VITE_MONEY_URL + "/delivery/add/auto/" + auto_ticket_id;
|
||||
axios({
|
||||
method: "post",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.status == 201) {
|
||||
notify({
|
||||
message: 'Confirmed Transaction',
|
||||
type: 'positive',
|
||||
position: 'top'
|
||||
})
|
||||
}
|
||||
else {
|
||||
notify({
|
||||
message: 'Form Error',
|
||||
type: 'negative',
|
||||
position: 'top'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
onSubmit() {
|
||||
let payload = {
|
||||
gallons_delivered: this.FinalizeOilOrderForm.gallons_delivered,
|
||||
};
|
||||
this.UpdateDeliveredAuto(payload);
|
||||
if (this.autoTicket.payment_status == '1') {
|
||||
// Pre-authorized: redirect to capture page
|
||||
this.$router.push({ name: "payAutoCapture", params: { id: this.autoTicket.id } });
|
||||
} else {
|
||||
// Fully charged: close ticket
|
||||
if (this.autoDelivery.open_ticket_id) {
|
||||
this.closeTicket(this.autoDelivery.open_ticket_id);
|
||||
}
|
||||
this.$router.push({ name: "auto" });
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
// Functions
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
user.value.id = response.data.user_id;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getPaymentCard = (card_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/payment/card/" + card_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
|
||||
if (response.data.userCard.card_number === ''){
|
||||
userCard.value = null;
|
||||
userCardfound.value = false;
|
||||
}
|
||||
else{
|
||||
userCard.value = response.data;
|
||||
userCardfound.value = true;
|
||||
}
|
||||
FinalizeOilOrderForm.value.userCards = response.data.id
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
}
|
||||
|
||||
const getPaymentCards = (user_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/payment/cards/" + user_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
userCards.value = response.data;
|
||||
if (userCards.value && userCards.value.length > 0) {
|
||||
userCardfound.value = true;
|
||||
userCard.value = userCards.value.find((card: any) => card.main_card) || userCards.value[0];
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
}
|
||||
|
||||
const getCustomer = (user_id: any) => {
|
||||
if (!user_id || user_id === 'undefined') return;
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/" + user_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
customer.value = response.data;
|
||||
if (customer.value.id > 0) {
|
||||
getPaymentCards(customer.value.user_id || customer.value.id);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not find customer",
|
||||
type: "error",
|
||||
});
|
||||
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: '' };
|
||||
});
|
||||
}
|
||||
|
||||
const getCustomerDescription = (user_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/description/" + user_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
customerDescription.value = response.data;
|
||||
loaded.value = true
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not find customer",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const getAutoTicket = (delivery_id: any) => {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/delivery/autoticket/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
autoTicket.value = response.data;
|
||||
getCustomer(autoTicket.value.customer_id)
|
||||
|
||||
getAutoDelivery(autoTicket.value.id)
|
||||
getCustomerDescription(autoTicket.value.customer_id)
|
||||
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get automatic",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const getAutoDelivery = (delivery_id: any) => {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/delivery/finddelivery/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
|
||||
autoDelivery.value = response.data;
|
||||
getCustomer(autoDelivery.value.customer_id)
|
||||
getCustomerDescription(autoDelivery.value.customer_id)
|
||||
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get automatic",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const today_price_oil = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/info/price/oil'
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
today_oil_price.value = response.data.price_for_customer;
|
||||
})
|
||||
}
|
||||
|
||||
const UpdateAuto = (payload: {
|
||||
gallons: string,
|
||||
delivery_id: string,
|
||||
}) => {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/confirm/delivery"
|
||||
axios({
|
||||
method: "put",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
text: 'Update',
|
||||
type: 'postive',
|
||||
title: 'top'
|
||||
})
|
||||
router.push({ name: "auto" });
|
||||
}
|
||||
else {
|
||||
notify({
|
||||
text: 'Auto Failure',
|
||||
type: 'negative',
|
||||
title: 'Update'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const ConfirmAuto = (payload: {
|
||||
gallons_delivered: string,
|
||||
}) => {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/confirm/auto/create/" + autoDelivery.value.id;
|
||||
axios({
|
||||
method: "post",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "Auto Delivered",
|
||||
type: "success",
|
||||
});
|
||||
CreateTransaction(response.data['0']['auto_ticket_id'])
|
||||
updateTransactionDelivery(autoDelivery.value.id, response.data['0']['auto_ticket_id'])
|
||||
router.push({ name: "payAutoCapture", params: { id: response.data['0']['auto_ticket_id'] } });
|
||||
|
||||
}
|
||||
if (response.data.error) {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not finalize auto",
|
||||
type: "error",
|
||||
});
|
||||
router.push("auto");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const closeTicket = (ticketId: number) => {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/confirm/auto/close_ticket/" + ticketId;
|
||||
axios({
|
||||
method: "put",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then(() => {
|
||||
// Ticket closed successfully
|
||||
})
|
||||
}
|
||||
|
||||
const UpdateDeliveredAuto = (payload: {
|
||||
gallons_delivered: string,
|
||||
}) => {
|
||||
console.log(autoDelivery.value)
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/confirm/auto/update/" + autoDelivery.value.id;
|
||||
axios({
|
||||
method: "put",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "Auto Updated",
|
||||
type: "success",
|
||||
});
|
||||
// Removed redirect from here, will handle in onSubmit
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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}`;
|
||||
axios.put(path, {}, { withCredentials: true, headers: authHeader() })
|
||||
.then(() => console.log("Transaction auto_id updated"))
|
||||
.catch(() => console.error("Error updating transaction auto_id"));
|
||||
}
|
||||
|
||||
const CreateTransaction = (auto_ticket_id: string) => {
|
||||
let path = import.meta.env.VITE_MONEY_URL + "/delivery/add/auto/" + auto_ticket_id;
|
||||
axios({
|
||||
method: "post",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.status == 201) {
|
||||
notify({
|
||||
message: 'Confirmed Transaction',
|
||||
type: 'positive',
|
||||
position: 'top'
|
||||
})
|
||||
}
|
||||
else {
|
||||
notify({
|
||||
message: 'Form Error',
|
||||
type: 'negative',
|
||||
position: 'top'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const onSubmit = () => {
|
||||
let payload = {
|
||||
gallons_delivered: FinalizeOilOrderForm.value.gallons_delivered,
|
||||
};
|
||||
UpdateDeliveredAuto(payload);
|
||||
if (autoTicket.value.payment_status == '1') {
|
||||
// Pre-authorized: redirect to capture page
|
||||
router.push({ name: "payAutoCapture", params: { id: autoTicket.value.id } });
|
||||
} else {
|
||||
// Fully charged: close ticket
|
||||
if (autoDelivery.value.open_ticket_id) {
|
||||
closeTicket(autoDelivery.value.open_ticket_id);
|
||||
}
|
||||
router.push({ name: "auto" });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
<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="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="text-xs opacity-70">{{ userCard.type_of_card }}</div>
|
||||
<div class="mt-1 text-sm font-mono tracking-wider">
|
||||
@@ -121,8 +121,9 @@
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
import Header from '../../../layouts/headers/headerauth.vue'
|
||||
@@ -131,408 +132,397 @@ import Footer from '../../../layouts/footers/footer.vue'
|
||||
import useValidate from "@vuelidate/core";
|
||||
import { notify } from "@kyvg/vue3-notification"
|
||||
|
||||
// Route and router
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
export default defineComponent({
|
||||
name: 'finalizeTicketAuto',
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
v$: useValidate(),
|
||||
loaded: false,
|
||||
user: {
|
||||
id: 0
|
||||
},
|
||||
userCardfound: false,
|
||||
deliveryStatus: [],
|
||||
userCards: [],
|
||||
deliveryNotesDriver: [],
|
||||
today_oil_price: 0,
|
||||
|
||||
FinalizeOilOrderForm: {
|
||||
fill_location: 0,
|
||||
check_number: 0,
|
||||
delivery_status: 10,
|
||||
userCards: [],
|
||||
credit_card_id: 0,
|
||||
driver: 0,
|
||||
gallons_delivered: '',
|
||||
customer_filled: false,
|
||||
prime: false,
|
||||
same_day: false,
|
||||
emergency: false,
|
||||
},
|
||||
CreateOilOrderForm: {
|
||||
basicInfo: {
|
||||
gallons_delivered: '',
|
||||
userCards: []
|
||||
},
|
||||
},
|
||||
userCard: {
|
||||
date_added: '',
|
||||
user_id: '',
|
||||
card_number: '',
|
||||
last_four_digits: '',
|
||||
name_on_card: '',
|
||||
expiration_month: '',
|
||||
expiration_year: '',
|
||||
type_of_card: '',
|
||||
security_number: '',
|
||||
accepted_or_declined: '',
|
||||
main_card: '',
|
||||
},
|
||||
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: '',
|
||||
},
|
||||
customerDescription: {
|
||||
customer_id: 0,
|
||||
account_number: '',
|
||||
company_id: 0,
|
||||
fill_location: 0,
|
||||
description: '',
|
||||
},
|
||||
|
||||
|
||||
autoTicket: {
|
||||
id: 0,
|
||||
customer_id: '',
|
||||
account_number: '',
|
||||
|
||||
customer_town: '',
|
||||
customer_state: '',
|
||||
customer_address: '',
|
||||
customer_zip: '',
|
||||
customer_full_name: '',
|
||||
|
||||
oil_prices_id: '',
|
||||
fill_date: '',
|
||||
gallons_delivered: '',
|
||||
price_per_gallon: '',
|
||||
|
||||
total_amount_customer: '',
|
||||
|
||||
payment_type: '',
|
||||
payment_card_id: '',
|
||||
payment_status: '',
|
||||
open_ticket_id: 0
|
||||
|
||||
},
|
||||
|
||||
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,
|
||||
},
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.userStatus()
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.today_price_oil();
|
||||
this.getAutoTicket(this.$route.params.id);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.today_price_oil();
|
||||
this.getAutoDelivery(this.$route.params.id);
|
||||
|
||||
|
||||
},
|
||||
|
||||
methods: {
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
this.user.id = response.data.user_id;
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
getPaymentCard(card_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/payment/card/" + card_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
|
||||
if (response.data.userCard.card_number === '') {
|
||||
this.userCard === null;
|
||||
this.userCardfound = false;
|
||||
}
|
||||
else {
|
||||
this.userCard = response.data;
|
||||
this.userCardfound = true;
|
||||
}
|
||||
this.FinalizeOilOrderForm.userCards = response.data.id
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
},
|
||||
getPaymentCards(user_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/payment/cards/" + user_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.userCards = response.data;
|
||||
if (this.userCards && this.userCards.length > 0) {
|
||||
this.userCardfound = true;
|
||||
this.userCard = this.userCards.find((card: any) => card.main_card) || this.userCards[0];
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
},
|
||||
getCustomer(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.customer = response.data
|
||||
})
|
||||
},
|
||||
getCreditCards(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/payment/cards/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.userCards = response.data;
|
||||
if (this.userCards && this.userCards.length > 0) {
|
||||
this.userCardfound = true;
|
||||
this.userCard = this.userCards.find((card: any) => card.main_card) || this.userCards[0];
|
||||
}
|
||||
})
|
||||
},
|
||||
getCustomerDescription(user_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/description/" + user_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.customerDescription = response.data;
|
||||
this.loaded = true
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not find customer",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
},
|
||||
getAutoTicket(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/delivery/autoticket/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.autoTicket = response.data;
|
||||
this.getCustomer(this.autoTicket.customer_id)
|
||||
|
||||
this.getAutoDelivery(this.autoTicket.id)
|
||||
this.getCustomerDescription(this.autoTicket.customer_id)
|
||||
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get automatic",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getAutoDelivery(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/delivery/delivery/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data && response.data.customer_id) {
|
||||
this.autoDelivery = response.data;
|
||||
this.getCustomer(this.autoDelivery.customer_id)
|
||||
this.getCreditCards(this.autoDelivery.customer_id)
|
||||
} else {
|
||||
console.error("API Error:", response.data.error || "Failed to fetch auto delivery data.");
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error("API Error in getAutoDelivery:", error);
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get automatic delivery",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
},
|
||||
today_price_oil() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/info/price/oil'
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.today_oil_price = response.data.price_for_customer;
|
||||
})
|
||||
},
|
||||
UpdateAuto(payload: {
|
||||
gallons: string,
|
||||
delivery_id: string,
|
||||
}) {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/confirm/delivery"
|
||||
axios({
|
||||
method: "put",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
text: 'Update',
|
||||
type: 'postive',
|
||||
title: 'top'
|
||||
})
|
||||
this.$router.push({ name: "auto" });
|
||||
}
|
||||
else {
|
||||
notify({
|
||||
text: 'Auto Failure',
|
||||
type: 'negative',
|
||||
title: 'Update'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
CreateTransaction(auto_ticket_id: string,) {
|
||||
let path = import.meta.env.VITE_MONEY_URL + "/delivery/add/auto/" + auto_ticket_id;
|
||||
axios({
|
||||
method: "post",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.status == 201) {
|
||||
notify({
|
||||
message: 'Confirmed Transaction',
|
||||
type: 'positive',
|
||||
position: 'top'
|
||||
})
|
||||
}
|
||||
else {
|
||||
notify({
|
||||
message: 'Form Error',
|
||||
type: 'negative',
|
||||
position: 'top'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
ConfirmAuto(payload: {
|
||||
gallons_delivered: string,
|
||||
}) {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/confirm/auto/create/nopreauth/" + this.autoDelivery.id;
|
||||
axios({
|
||||
method: "post",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "Auto Delivered",
|
||||
type: "success",
|
||||
});
|
||||
this.CreateTransaction(response.data['0']['auto_ticket_id'])
|
||||
}
|
||||
if (response.data.error) {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not finalize auto",
|
||||
type: "error",
|
||||
});
|
||||
this.$router.push("auto");
|
||||
}
|
||||
})
|
||||
},
|
||||
onSubmit() {
|
||||
let payload = {
|
||||
gallons_delivered: this.FinalizeOilOrderForm.gallons_delivered,
|
||||
};
|
||||
|
||||
this.ConfirmAuto(payload)
|
||||
this.$router.push({ name: "auto" });
|
||||
|
||||
},
|
||||
// Reactive data
|
||||
const v$ = useValidate()
|
||||
const loaded = ref(false)
|
||||
const user = ref({
|
||||
id: 0
|
||||
})
|
||||
const userCardfound = ref(false)
|
||||
const deliveryStatus = ref([])
|
||||
const userCards = ref([])
|
||||
const deliveryNotesDriver = ref([])
|
||||
const today_oil_price = ref(0)
|
||||
|
||||
const FinalizeOilOrderForm = ref({
|
||||
fill_location: 0,
|
||||
check_number: 0,
|
||||
delivery_status: 10,
|
||||
userCards: [],
|
||||
credit_card_id: 0,
|
||||
driver: 0,
|
||||
gallons_delivered: '',
|
||||
customer_filled: false,
|
||||
prime: false,
|
||||
same_day: false,
|
||||
emergency: false,
|
||||
})
|
||||
const CreateOilOrderForm = ref({
|
||||
basicInfo: {
|
||||
gallons_delivered: '',
|
||||
userCards: []
|
||||
},
|
||||
})
|
||||
const userCard = ref<{
|
||||
date_added: string;
|
||||
user_id: string;
|
||||
card_number: string;
|
||||
last_four_digits: string;
|
||||
name_on_card: string;
|
||||
expiration_month: string;
|
||||
expiration_year: string;
|
||||
type_of_card: string;
|
||||
security_number: string;
|
||||
accepted_or_declined: string;
|
||||
main_card: string;
|
||||
} | null>(null)
|
||||
const customer = ref({
|
||||
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: '',
|
||||
})
|
||||
const customerDescription = ref({
|
||||
customer_id: 0,
|
||||
account_number: '',
|
||||
company_id: 0,
|
||||
fill_location: 0,
|
||||
description: '',
|
||||
})
|
||||
|
||||
const autoTicket = ref({
|
||||
id: 0,
|
||||
customer_id: '',
|
||||
account_number: '',
|
||||
|
||||
customer_town: '',
|
||||
customer_state: '',
|
||||
customer_address: '',
|
||||
customer_zip: '',
|
||||
customer_full_name: '',
|
||||
|
||||
oil_prices_id: '',
|
||||
fill_date: '',
|
||||
gallons_delivered: '',
|
||||
price_per_gallon: '',
|
||||
|
||||
total_amount_customer: '',
|
||||
|
||||
payment_type: '',
|
||||
payment_card_id: '',
|
||||
payment_status: '',
|
||||
open_ticket_id: 0
|
||||
|
||||
})
|
||||
|
||||
const autoDelivery = ref({
|
||||
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,
|
||||
})
|
||||
|
||||
// Watchers
|
||||
watch(() => route.params, () => {
|
||||
today_price_oil()
|
||||
getAutoTicket(route.params.id)
|
||||
}, { immediate: false })
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus()
|
||||
today_price_oil()
|
||||
getAutoDelivery(route.params.id)
|
||||
})
|
||||
|
||||
// Functions
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
user.value.id = response.data.user_id;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getPaymentCard = (card_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/payment/card/" + card_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
|
||||
if (response.data.userCard.card_number === '') {
|
||||
userCard.value = null;
|
||||
userCardfound.value = false;
|
||||
}
|
||||
else {
|
||||
userCard.value = response.data;
|
||||
userCardfound.value = true;
|
||||
}
|
||||
FinalizeOilOrderForm.value.userCards = response.data.id
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
}
|
||||
|
||||
const getPaymentCards = (user_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/payment/cards/" + user_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
userCards.value = response.data;
|
||||
if (userCards.value && userCards.value.length > 0) {
|
||||
userCardfound.value = true;
|
||||
userCard.value = userCards.value.find((card: any) => card.main_card) || userCards.value[0];
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
}
|
||||
|
||||
const getCustomer = (userid: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
customer.value = response.data
|
||||
})
|
||||
}
|
||||
|
||||
const getCreditCards = (userid: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/payment/cards/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
userCards.value = response.data;
|
||||
if (userCards.value && userCards.value.length > 0) {
|
||||
userCardfound.value = true;
|
||||
userCard.value = userCards.value.find((card: any) => card.main_card) || userCards.value[0];
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getCustomerDescription = (user_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/description/" + user_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
customerDescription.value = response.data;
|
||||
loaded.value = true
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not find customer",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const getAutoTicket = (delivery_id: any) => {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/delivery/autoticket/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
autoTicket.value = response.data;
|
||||
getCustomer(autoTicket.value.customer_id)
|
||||
|
||||
getAutoDelivery(autoTicket.value.id)
|
||||
getCustomerDescription(autoTicket.value.customer_id)
|
||||
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get automatic",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const getAutoDelivery = (delivery_id: any) => {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/delivery/delivery/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data && response.data.customer_id) {
|
||||
autoDelivery.value = response.data;
|
||||
getCustomer(autoDelivery.value.customer_id)
|
||||
getCreditCards(autoDelivery.value.customer_id)
|
||||
} else {
|
||||
console.error("API Error:", response.data.error || "Failed to fetch auto delivery data.");
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error("API Error in getAutoDelivery:", error);
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get automatic delivery",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const today_price_oil = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/info/price/oil'
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
today_oil_price.value = response.data.price_for_customer;
|
||||
})
|
||||
}
|
||||
|
||||
const UpdateAuto = (payload: {
|
||||
gallons: string,
|
||||
delivery_id: string,
|
||||
}) => {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/confirm/delivery"
|
||||
axios({
|
||||
method: "put",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
text: 'Update',
|
||||
type: 'postive',
|
||||
title: 'top'
|
||||
})
|
||||
router.push({ name: "auto" });
|
||||
}
|
||||
else {
|
||||
notify({
|
||||
text: 'Auto Failure',
|
||||
type: 'negative',
|
||||
title: 'Update'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const CreateTransaction = (auto_ticket_id: string) => {
|
||||
let path = import.meta.env.VITE_MONEY_URL + "/delivery/add/auto/" + auto_ticket_id;
|
||||
axios({
|
||||
method: "post",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.status == 201) {
|
||||
notify({
|
||||
message: 'Confirmed Transaction',
|
||||
type: 'positive',
|
||||
position: 'top'
|
||||
})
|
||||
}
|
||||
else {
|
||||
notify({
|
||||
message: 'Form Error',
|
||||
type: 'negative',
|
||||
position: 'top'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const ConfirmAuto = (payload: {
|
||||
gallons_delivered: string,
|
||||
}) => {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/confirm/auto/create/nopreauth/" + autoDelivery.value.id;
|
||||
axios({
|
||||
method: "post",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "Auto Delivered",
|
||||
type: "success",
|
||||
});
|
||||
CreateTransaction(response.data['0']['auto_ticket_id'])
|
||||
}
|
||||
if (response.data.error) {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not finalize auto",
|
||||
type: "error",
|
||||
});
|
||||
router.push("auto");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const onSubmit = () => {
|
||||
let payload = {
|
||||
gallons_delivered: FinalizeOilOrderForm.value.gallons_delivered,
|
||||
};
|
||||
|
||||
ConfirmAuto(payload)
|
||||
router.push({ name: "auto" });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -71,72 +71,54 @@
|
||||
<Footer/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent} from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
import Header from '../../../layouts/headers/headerauth.vue'
|
||||
|
||||
import SideBar from '../../../layouts/sidebar/sidebar.vue'
|
||||
import Footer from '../../../layouts/footers/footer.vue'
|
||||
|
||||
// Reactive data
|
||||
const token = ref(null)
|
||||
const user = ref(null)
|
||||
const deliveries = ref([])
|
||||
|
||||
export default defineComponent({
|
||||
name: 'deliveryTicketsMissing',
|
||||
|
||||
components: {
|
||||
Header,
|
||||
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';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
},
|
||||
get_oil_orders() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/pending';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.deliveries = response.data
|
||||
})
|
||||
},
|
||||
|
||||
},
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus()
|
||||
get_oil_orders()
|
||||
})
|
||||
|
||||
// Functions
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
user.value = null
|
||||
})
|
||||
}
|
||||
|
||||
const get_oil_orders = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/pending';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
deliveries.value = response.data
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -67,20 +67,20 @@
|
||||
<div class="font-bold text-sm">Current Status</div>
|
||||
<div class="badge badge-lg"
|
||||
:class="{
|
||||
'badge-success': [1, 10].includes(deliveryOrder.delivery_status),
|
||||
'badge-info': deliveryOrder.delivery_status == 2,
|
||||
'badge-error': deliveryOrder.delivery_status == 5,
|
||||
'badge-warning': ![1, 10, 2, 5].includes(deliveryOrder.delivery_status)
|
||||
'badge-success': [DELIVERY_STATUS.DELIVERED, DELIVERY_STATUS.FINALIZED].includes(deliveryOrder.delivery_status as any),
|
||||
'badge-info': deliveryOrder.delivery_status == DELIVERY_STATUS.OUT_FOR_DELIVERY,
|
||||
'badge-error': deliveryOrder.delivery_status == DELIVERY_STATUS.ISSUE,
|
||||
'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-else-if="deliveryOrder.delivery_status == 1">Delivered</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == 2">Out_for_Delivery</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == 3">Tomorrow</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == 4">Partial Delivery</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == 5">Misdelivery</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == 6">Unknown</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == 9">Pending</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == 10">Finalized</span>
|
||||
<span v-if="deliveryOrder.delivery_status == DELIVERY_STATUS.WAITING">Waiting</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == DELIVERY_STATUS.DELIVERED">Delivered</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == DELIVERY_STATUS.OUT_FOR_DELIVERY">Out_for_Delivery</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == DELIVERY_STATUS.TOMORROW">Tomorrow</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == DELIVERY_STATUS.PARTIAL_DELIVERY">Partial Delivery</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == DELIVERY_STATUS.ISSUE">Misdelivery</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == DELIVERY_STATUS.UNKNOWN">Unknown</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == DELIVERY_STATUS.PENDING_PAYMENT">Pending</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == DELIVERY_STATUS.FINALIZED">Finalized</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -354,488 +354,480 @@
|
||||
</div>
|
||||
<Footer />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../services/auth.header'
|
||||
|
||||
interface UserCard {
|
||||
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 { DELIVERY_STATUS, PAYMENT_STATUS, TRANSACTION_STATUS } from '../../constants/status'
|
||||
import { CreditCard } 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 moment from 'moment';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'deliveryOrder',
|
||||
const route = useRoute()
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
// Reactive data
|
||||
const v$ = useValidate()
|
||||
const user = ref({
|
||||
user_id: 0
|
||||
})
|
||||
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 deliveryNotesDriver = ref([])
|
||||
const userCardfound = ref(false)
|
||||
const userCard = ref({} as CreditCard)
|
||||
const customer = ref({
|
||||
account_number: '',
|
||||
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: '',
|
||||
})
|
||||
|
||||
data() {
|
||||
return {
|
||||
v$: useValidate(),
|
||||
user: {
|
||||
user_id: 0
|
||||
},
|
||||
priceprime: 0,
|
||||
pricesameday: 0,
|
||||
priceemergency: 0,
|
||||
total_amount: 0,
|
||||
discount: 0,
|
||||
total_amount_after_discount: 0,
|
||||
deliveryNotesDriver: [],
|
||||
userCardfound: false,
|
||||
userCard: {} as UserCard,
|
||||
customer: {
|
||||
account_number: '',
|
||||
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: '',
|
||||
},
|
||||
const deliveryMoney = ref({
|
||||
time_added: '',
|
||||
total_amount_oil: '',
|
||||
total_amount_emergency: '',
|
||||
total_amount_same_day: '',
|
||||
total_amount_prime: '',
|
||||
total_amount_fee: '',
|
||||
total_discount_amount: '',
|
||||
total_discount_total: '',
|
||||
total_amount: '',
|
||||
})
|
||||
const promo = ref({
|
||||
id: 0,
|
||||
name_of_promotion: '',
|
||||
description: '',
|
||||
money_off_delivery: '',
|
||||
text_on_ticket: ''
|
||||
})
|
||||
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 deliveryOrder = ref({
|
||||
id: '',
|
||||
customer_id: 0,
|
||||
customer_name: '',
|
||||
customer_address: '',
|
||||
customer_town: '',
|
||||
customer_state: 0,
|
||||
customer_zip: '',
|
||||
gallons_ordered: 0,
|
||||
customer_asked_for_fill: 0,
|
||||
gallons_delivered: '',
|
||||
customer_filled: 0,
|
||||
delivery_status: 0,
|
||||
when_ordered: '',
|
||||
when_delivered: '',
|
||||
expected_delivery_date: '',
|
||||
automatic: 0,
|
||||
oil_id: 0,
|
||||
supplier_price: '',
|
||||
customer_price: '',
|
||||
customer_temperature: '',
|
||||
dispatcher_notes: '',
|
||||
prime: 0,
|
||||
same_day: 0,
|
||||
emergency: 0,
|
||||
payment_type: 0,
|
||||
payment_card_id: '',
|
||||
driver_employee_id: 0,
|
||||
driver_first_name: '',
|
||||
driver_last_name: '',
|
||||
promo_id: 0,
|
||||
})
|
||||
const transaction = ref(null as any)
|
||||
|
||||
deliveryMoney: {
|
||||
time_added: '',
|
||||
total_amount_oil: '',
|
||||
total_amount_emergency: '',
|
||||
total_amount_same_day: '',
|
||||
total_amount_prime: '',
|
||||
total_amount_fee: '',
|
||||
total_discount_amount: '',
|
||||
total_discount_total: '',
|
||||
total_amount: '',
|
||||
},
|
||||
promo: {
|
||||
id: 0,
|
||||
name_of_promotion: '',
|
||||
description: '',
|
||||
money_off_delivery: '',
|
||||
text_on_ticket: ''
|
||||
},
|
||||
pricing: {
|
||||
price_from_supplier: 0,
|
||||
price_for_customer: 0,
|
||||
price_for_employee: 0,
|
||||
price_same_day: 0,
|
||||
price_prime: 0,
|
||||
price_emergency: 0,
|
||||
date: "",
|
||||
},
|
||||
deliveryOrder: {
|
||||
id: '',
|
||||
customer_id: 0,
|
||||
customer_name: '',
|
||||
customer_address: '',
|
||||
customer_town: '',
|
||||
customer_state: 0,
|
||||
customer_zip: '',
|
||||
gallons_ordered: 0,
|
||||
customer_asked_for_fill: 0,
|
||||
gallons_delivered: '',
|
||||
customer_filled: 0,
|
||||
delivery_status: 0,
|
||||
when_ordered: '',
|
||||
when_delivered: '',
|
||||
expected_delivery_date: '',
|
||||
automatic: 0,
|
||||
oil_id: 0,
|
||||
supplier_price: '',
|
||||
customer_price: '',
|
||||
customer_temperature: '',
|
||||
dispatcher_notes: '',
|
||||
prime: 0,
|
||||
same_day: 0,
|
||||
emergency: 0,
|
||||
payment_type: 0,
|
||||
payment_card_id: '',
|
||||
driver_employee_id: 0,
|
||||
driver_first_name: '',
|
||||
driver_last_name: '',
|
||||
promo_id: 0,
|
||||
},
|
||||
transaction: null as any,
|
||||
}
|
||||
},
|
||||
// Computed
|
||||
const computedDELIVERY_STATUS = computed(() => DELIVERY_STATUS)
|
||||
const computedPAYMENT_STATUS = computed(() => PAYMENT_STATUS)
|
||||
const computedTRANSACTION_STATUS = computed(() => TRANSACTION_STATUS)
|
||||
|
||||
created() {
|
||||
this.userStatus()
|
||||
},
|
||||
watch: {
|
||||
$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();
|
||||
},
|
||||
// Watchers
|
||||
watch(route, () => {
|
||||
getOilOrder(route.params.id);
|
||||
getOilOrderMoney(route.params.id);
|
||||
sumdelivery(route.params.id);
|
||||
})
|
||||
|
||||
methods: {
|
||||
format_date(value: string) {
|
||||
if (value) {
|
||||
return moment(String(value)).format('LLLL')
|
||||
}
|
||||
},
|
||||
getTypeColor(transactionType: number) {
|
||||
switch (transactionType) {
|
||||
case 1: return 'text-blue-600'; // Auth
|
||||
case 0: return 'text-orange-600'; // Charge
|
||||
case 2: return 'text-green-600'; // Capture
|
||||
case 3: return 'text-purple-600'; // Delivery/Other
|
||||
default: return 'text-gray-600';
|
||||
}
|
||||
},
|
||||
deleteCall(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "deleted delivery",
|
||||
type: "success",
|
||||
});
|
||||
this.$router.push({ name: "customerProfile", params: { id: this.customer.user_id } });
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error deleting delivery",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
cancelDelivery() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/cancel/' + this.deliveryOrder.id;
|
||||
axios({
|
||||
method: 'post',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "Delivery cancelled",
|
||||
type: "success",
|
||||
});
|
||||
// Refresh the delivery data
|
||||
this.getOilOrder(this.deliveryOrder.id);
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "Failed to cancel delivery",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}).catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Error cancelling delivery",
|
||||
type: "error",
|
||||
});
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus()
|
||||
getOilOrder(route.params.id);
|
||||
getOilOrderMoney(route.params.id);
|
||||
sumdelivery(route.params.id);
|
||||
getOilPricing();
|
||||
})
|
||||
|
||||
// Functions
|
||||
const format_date = (value: string) => {
|
||||
if (value) {
|
||||
return dayjs(String(value)).format('LLLL')
|
||||
}
|
||||
}
|
||||
|
||||
const getTypeColor = (transactionType: number) => {
|
||||
switch (transactionType) {
|
||||
case 1: return 'text-blue-600'; // Auth
|
||||
case 0: return 'text-orange-600'; // Charge
|
||||
case 2: return 'text-green-600'; // Capture
|
||||
case 3: return 'text-purple-600'; // Delivery/Other
|
||||
default: return 'text-gray-600';
|
||||
}
|
||||
}
|
||||
|
||||
const deleteCall = (delivery_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "deleted delivery",
|
||||
type: "success",
|
||||
});
|
||||
},
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
|
||||
}
|
||||
})
|
||||
},
|
||||
getOilPricing() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/table";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.pricing = response.data;
|
||||
})
|
||||
.catch((_error: any) => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get oil pricing",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
},
|
||||
getCustomer(user_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/" + user_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.customer = response.data;
|
||||
})
|
||||
.catch((_error: any) => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not find customer",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getPaymentCard(card_id: any) {
|
||||
if (card_id) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/payment/card/" + card_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
// Check if we have valid card data
|
||||
if (response.data && response.data.card_number && response.data.card_number !== '') {
|
||||
this.userCard = response.data;
|
||||
this.userCardfound = true;
|
||||
} else {
|
||||
this.userCard = {} as UserCard;
|
||||
this.userCardfound = false;
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error("Error fetching payment card:", error);
|
||||
this.userCard = {} as UserCard;
|
||||
this.userCardfound = false;
|
||||
});
|
||||
} else {
|
||||
this.userCardfound = false;
|
||||
}
|
||||
},
|
||||
// Note: router.push would need to be imported and used
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error deleting delivery",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const cancelDelivery = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/cancel/' + deliveryOrder.value.id;
|
||||
axios({
|
||||
method: 'post',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "Delivery cancelled",
|
||||
type: "success",
|
||||
});
|
||||
// Refresh the delivery data
|
||||
getOilOrder(deliveryOrder.value.id);
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "Failed to cancel delivery",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}).catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Error cancelling delivery",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
|
||||
getOilOrder(delivery_id: any) {
|
||||
if (!delivery_id) { // Add a guard to prevent calls with an undefined ID
|
||||
console.error("getOilOrder called with no ID.");
|
||||
return;
|
||||
}
|
||||
let path = import.meta.env.VITE_BASE_URL + "/delivery/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const getOilPricing = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/table";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
pricing.value = response.data;
|
||||
})
|
||||
.catch((_error: any) => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get oil pricing",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const getCustomer = (user_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/" + user_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
customer.value = response.data;
|
||||
})
|
||||
.catch((_error: any) => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not find customer",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const getPaymentCard = (card_id: any) => {
|
||||
if (card_id) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/payment/card/" + card_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
// FIX: Check for the 'ok' flag and access the nested 'delivery' object
|
||||
if (response.data && response.data.ok) {
|
||||
this.deliveryOrder = response.data.delivery; // <-- THIS IS THE CRITICAL CHANGE
|
||||
|
||||
// Now that this.deliveryOrder is the correct object, the rest of the logic will work.
|
||||
this.getCustomer(this.deliveryOrder.customer_id);
|
||||
|
||||
if ([1, 2, 3].includes(this.deliveryOrder.payment_type)) {
|
||||
this.getPaymentCard(this.deliveryOrder.payment_card_id);
|
||||
}
|
||||
if (this.deliveryOrder.promo_id != null) {
|
||||
this.getPromo(this.deliveryOrder.promo_id);
|
||||
}
|
||||
|
||||
// Only fetch transactions for Authorize.net payments
|
||||
if (this.deliveryOrder.payment_type == 11) {
|
||||
this.getTransaction(delivery_id);
|
||||
}
|
||||
|
||||
// Check if we have valid card data
|
||||
if (response.data && response.data.card_number && response.data.card_number !== '') {
|
||||
userCard.value = response.data;
|
||||
userCardfound.value = true;
|
||||
} else {
|
||||
console.error("API Error:", response.data.error || "Failed to fetch delivery data.");
|
||||
notify({ title: "Error", text: "Could not load delivery details.", type: "error" });
|
||||
userCard.value = {} as CreditCard;
|
||||
userCardfound.value = false;
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error("Error fetching delivery order:", error);
|
||||
console.error("Error fetching payment card:", error);
|
||||
userCard.value = {} as CreditCard;
|
||||
userCardfound.value = false;
|
||||
});
|
||||
},
|
||||
getOilOrderMoney(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_MONEY_URL + "/delivery/order/money/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
this.deliveryMoney = response.data
|
||||
}
|
||||
})
|
||||
},
|
||||
sumdelivery(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/delivery/total/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data && response.data.ok) {
|
||||
this.priceprime = response.data.priceprime || 0;
|
||||
this.pricesameday = response.data.pricesameday || 0;
|
||||
this.priceemergency = response.data.priceemergency || 0;
|
||||
this.total_amount = parseFloat(response.data.total_amount) || 0;
|
||||
this.discount = parseFloat(response.data.discount) || 0;
|
||||
this.total_amount_after_discount = parseFloat(response.data.total_amount_after_discount) || 0;
|
||||
} else {
|
||||
// Fallback calculation if API doesn't return expected data
|
||||
this.calculateFallbackTotal();
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error("Error fetching delivery totals:", error);
|
||||
// Fallback calculation on error
|
||||
this.calculateFallbackTotal();
|
||||
notify({
|
||||
title: "Warning",
|
||||
text: "Could not get delivery totals, using estimated calculation",
|
||||
type: "warn",
|
||||
});
|
||||
});
|
||||
},
|
||||
} else {
|
||||
userCardfound.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
calculateFallbackTotal() {
|
||||
// Fallback calculation using available data
|
||||
if (this.deliveryOrder.gallons_ordered && this.pricing.price_for_customer) {
|
||||
const gallons = Number(this.deliveryOrder.gallons_ordered);
|
||||
const pricePerGallon = Number(this.pricing.price_for_customer);
|
||||
let total = gallons * pricePerGallon;
|
||||
const getOilOrder = (delivery_id: any) => {
|
||||
if (!delivery_id) { // Add a guard to prevent calls with an undefined ID
|
||||
console.error("getOilOrder called with no ID.");
|
||||
return;
|
||||
}
|
||||
let path = import.meta.env.VITE_BASE_URL + "/delivery/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
// FIX: Check for the 'ok' flag and access the nested 'delivery' object
|
||||
if (response.data && response.data.ok) {
|
||||
deliveryOrder.value = response.data.delivery; // <-- THIS IS THE CRITICAL CHANGE
|
||||
|
||||
if (this.deliveryOrder.prime == 1) {
|
||||
total += Number(this.pricing.price_prime) || 0;
|
||||
}
|
||||
if (this.deliveryOrder.same_day == 1) {
|
||||
total += Number(this.pricing.price_same_day) || 0;
|
||||
}
|
||||
if (this.deliveryOrder.emergency == 1) {
|
||||
total += Number(this.pricing.price_emergency) || 0;
|
||||
}
|
||||
// Now that deliveryOrder is the correct object, the rest of the logic will work.
|
||||
getCustomer(deliveryOrder.value.customer_id);
|
||||
|
||||
this.total_amount = total;
|
||||
this.total_amount_after_discount = total; // No discount info available
|
||||
this.discount = 0;
|
||||
if ([1, 2, 3].includes(deliveryOrder.value.payment_type)) {
|
||||
getPaymentCard(deliveryOrder.value.payment_card_id);
|
||||
}
|
||||
},
|
||||
|
||||
getPromo(promo_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/promo/" + promo_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
this.promo = response.data;
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error('Error fetching promo:', error);
|
||||
})
|
||||
},
|
||||
|
||||
calculateDeliveryTotal() {
|
||||
if (!this.deliveryOrder.gallons_delivered || !this.pricing.price_for_customer) {
|
||||
return '0.00';
|
||||
if (deliveryOrder.value.promo_id != null) {
|
||||
getPromo(deliveryOrder.value.promo_id);
|
||||
}
|
||||
|
||||
const gallons = Number(this.deliveryOrder.gallons_delivered);
|
||||
const pricePerGallon = Number(this.pricing.price_for_customer);
|
||||
let total = gallons * pricePerGallon;
|
||||
|
||||
if (this.deliveryOrder.prime == 1) {
|
||||
total += Number(this.pricing.price_prime) || 0;
|
||||
}
|
||||
if (this.deliveryOrder.same_day == 1) {
|
||||
total += Number(this.pricing.price_same_day) || 0;
|
||||
}
|
||||
if (this.deliveryOrder.emergency == 1) {
|
||||
total += Number(this.pricing.price_emergency) || 0;
|
||||
// Only fetch transactions for Authorize.net payments
|
||||
if (deliveryOrder.value.payment_type == 11) {
|
||||
getTransaction(delivery_id);
|
||||
}
|
||||
|
||||
return total.toFixed(2);
|
||||
},
|
||||
calculateEstimatedTotal() {
|
||||
if (!this.deliveryOrder.gallons_ordered || !this.pricing.price_for_customer) {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
console.error("API Error:", response.data.error || "Failed to fetch delivery data.");
|
||||
notify({ title: "Error", text: "Could not load delivery details.", type: "error" });
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error("Error fetching delivery order:", error);
|
||||
});
|
||||
}
|
||||
|
||||
const gallons = Number(this.deliveryOrder.gallons_ordered);
|
||||
const pricePerGallon = Number(this.pricing.price_for_customer);
|
||||
let total = gallons * pricePerGallon;
|
||||
|
||||
if (this.deliveryOrder.prime == 1) {
|
||||
total += Number(this.pricing.price_prime) || 0;
|
||||
}
|
||||
if (this.deliveryOrder.same_day == 1) {
|
||||
total += Number(this.pricing.price_same_day) || 0;
|
||||
}
|
||||
if (this.deliveryOrder.emergency == 1) {
|
||||
total += Number(this.pricing.price_emergency) || 0;
|
||||
const getOilOrderMoney = (delivery_id: any) => {
|
||||
let path = import.meta.env.VITE_MONEY_URL + "/delivery/order/money/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
deliveryMoney.value = response.data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return total;
|
||||
},
|
||||
|
||||
getTransaction(delivery_id: any) {
|
||||
// Simple endpoint to get transaction directly by delivery_id
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/payment/transaction/delivery/${delivery_id}`;
|
||||
axios.get(path, {
|
||||
withCredentials: true,
|
||||
headers: authHeader()
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.transaction = response.data.transaction;
|
||||
console.log("Transaction loaded:", this.transaction);
|
||||
} else {
|
||||
console.log("No transaction found for delivery:", delivery_id);
|
||||
this.transaction = null;
|
||||
}
|
||||
}).catch((error: any) => {
|
||||
console.error("Error fetching transaction:", error);
|
||||
this.transaction = null;
|
||||
const sumdelivery = (delivery_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/delivery/total/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data && response.data.ok) {
|
||||
priceprime.value = response.data.priceprime || 0;
|
||||
pricesameday.value = response.data.pricesameday || 0;
|
||||
priceemergency.value = response.data.priceemergency || 0;
|
||||
total_amount.value = parseFloat(response.data.total_amount) || 0;
|
||||
discount.value = parseFloat(response.data.discount) || 0;
|
||||
total_amount_after_discount.value = parseFloat(response.data.total_amount_after_discount) || 0;
|
||||
} else {
|
||||
// Fallback calculation if API doesn't return expected data
|
||||
calculateFallbackTotal();
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error("Error fetching delivery totals:", error);
|
||||
// Fallback calculation on error
|
||||
calculateFallbackTotal();
|
||||
notify({
|
||||
title: "Warning",
|
||||
text: "Could not get delivery totals, using estimated calculation",
|
||||
type: "warn",
|
||||
});
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const calculateFallbackTotal = () => {
|
||||
// Fallback calculation using available data
|
||||
if (deliveryOrder.value.gallons_ordered && pricing.value.price_for_customer) {
|
||||
const gallons = Number(deliveryOrder.value.gallons_ordered);
|
||||
const pricePerGallon = Number(pricing.value.price_for_customer);
|
||||
let total = gallons * pricePerGallon;
|
||||
|
||||
if (deliveryOrder.value.prime == 1) {
|
||||
total += Number(pricing.value.price_prime) || 0;
|
||||
}
|
||||
if (deliveryOrder.value.same_day == 1) {
|
||||
total += Number(pricing.value.price_same_day) || 0;
|
||||
}
|
||||
if (deliveryOrder.value.emergency == 1) {
|
||||
total += Number(pricing.value.price_emergency) || 0;
|
||||
}
|
||||
|
||||
total_amount.value = total;
|
||||
total_amount_after_discount.value = total; // No discount info available
|
||||
discount.value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const getPromo = (promo_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/promo/" + promo_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
promo.value = response.data;
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error('Error fetching promo:', error);
|
||||
})
|
||||
}
|
||||
|
||||
const calculateDeliveryTotal = () => {
|
||||
if (!deliveryOrder.value.gallons_delivered || !pricing.value.price_for_customer) {
|
||||
return '0.00';
|
||||
}
|
||||
|
||||
const gallons = Number(deliveryOrder.value.gallons_delivered);
|
||||
const pricePerGallon = Number(pricing.value.price_for_customer);
|
||||
let total = gallons * pricePerGallon;
|
||||
|
||||
if (deliveryOrder.value.prime == 1) {
|
||||
total += Number(pricing.value.price_prime) || 0;
|
||||
}
|
||||
if (deliveryOrder.value.same_day == 1) {
|
||||
total += Number(pricing.value.price_same_day) || 0;
|
||||
}
|
||||
if (deliveryOrder.value.emergency == 1) {
|
||||
total += Number(pricing.value.price_emergency) || 0;
|
||||
}
|
||||
|
||||
return total.toFixed(2);
|
||||
}
|
||||
|
||||
const calculateEstimatedTotal = () => {
|
||||
if (!deliveryOrder.value.gallons_ordered || !pricing.value.price_for_customer) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const gallons = Number(deliveryOrder.value.gallons_ordered);
|
||||
const pricePerGallon = Number(pricing.value.price_for_customer);
|
||||
let total = gallons * pricePerGallon;
|
||||
|
||||
if (deliveryOrder.value.prime == 1) {
|
||||
total += Number(pricing.value.price_prime) || 0;
|
||||
}
|
||||
if (deliveryOrder.value.same_day == 1) {
|
||||
total += Number(pricing.value.price_same_day) || 0;
|
||||
}
|
||||
if (deliveryOrder.value.emergency == 1) {
|
||||
total += Number(pricing.value.price_emergency) || 0;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
const getTransaction = (delivery_id: any) => {
|
||||
// Simple endpoint to get transaction directly by delivery_id
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/payment/transaction/delivery/${delivery_id}`;
|
||||
axios.get(path, {
|
||||
withCredentials: true,
|
||||
headers: authHeader()
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
transaction.value = response.data.transaction;
|
||||
console.log("Transaction loaded:", transaction.value);
|
||||
} else {
|
||||
console.log("No transaction found for delivery:", delivery_id);
|
||||
transaction.value = null;
|
||||
}
|
||||
}).catch((error: any) => {
|
||||
console.error("Error fetching transaction:", error);
|
||||
transaction.value = null;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -121,104 +121,93 @@
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent} from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
import { deliveryService } from '../../../services/deliveryService'
|
||||
import { Delivery } from '../../../types/models'
|
||||
import Header from '../../../layouts/headers/headerauth.vue'
|
||||
import PaginationComp from '../../../components/pagination.vue'
|
||||
import SideBar from '../../../layouts/sidebar/sidebar.vue'
|
||||
import Footer from '../../../layouts/footers/footer.vue'
|
||||
import {notify} from "@kyvg/vue3-notification";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'deliveryCancelled',
|
||||
// Reactive data
|
||||
const token = ref(null)
|
||||
const user = ref(null)
|
||||
const deliveries = ref<Delivery[]>([])
|
||||
const page = ref(1)
|
||||
const perPage = ref(50)
|
||||
const recordsLength = ref(0)
|
||||
const options = ref({
|
||||
edgeNavigation: false,
|
||||
format: false,
|
||||
template: PaginationComp
|
||||
})
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
// Functions
|
||||
const getPage = (pageVal: any) => {
|
||||
deliveries.value = [];
|
||||
get_oil_orders(pageVal)
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
token: null,
|
||||
user: null,
|
||||
deliveries: [] as any[],
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
options: {
|
||||
edgeNavigation: false,
|
||||
format: false,
|
||||
template: PaginationComp
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.userStatus()
|
||||
},
|
||||
mounted() {
|
||||
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() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
},
|
||||
get_oil_orders(page: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/issue/' + 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/cancelled/' + delivery_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "deleted delivery",
|
||||
type: "success",
|
||||
});
|
||||
this.getPage(this.page)
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error deleting delivery",
|
||||
type: "success",
|
||||
});
|
||||
user.value = response.data.user;
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
.catch(() => {
|
||||
user.value = null
|
||||
})
|
||||
}
|
||||
|
||||
const get_oil_orders = async (pageVal: number) => {
|
||||
try {
|
||||
const response = await deliveryService.getIssues(pageVal)
|
||||
deliveries.value = response.data || []
|
||||
} catch (error) {
|
||||
console.error('Error fetching issue deliveries:', error)
|
||||
deliveries.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const deleteCall = (delivery_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/cancelled/' + delivery_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "deleted delivery",
|
||||
type: "success",
|
||||
});
|
||||
getPage(page.value)
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error deleting delivery",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus()
|
||||
getPage(page.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -121,105 +121,93 @@
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent} from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
import { deliveryService } from '../../../services/deliveryService'
|
||||
import { Delivery } from '../../../types/models'
|
||||
import Header from '../../../layouts/headers/headerauth.vue'
|
||||
import PaginationComp from '../../../components/pagination.vue'
|
||||
import SideBar from '../../../layouts/sidebar/sidebar.vue'
|
||||
import Footer from '../../../layouts/footers/footer.vue'
|
||||
import {notify} from "@kyvg/vue3-notification";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'deliveryDelivered',
|
||||
// Reactive data
|
||||
const token = ref(null)
|
||||
const user = ref(null)
|
||||
const deliveries = ref<Delivery[]>([])
|
||||
const page = ref(1)
|
||||
const perPage = ref(50)
|
||||
const recordsLength = ref(0)
|
||||
const options = ref({
|
||||
edgeNavigation: false,
|
||||
format: false,
|
||||
template: PaginationComp
|
||||
})
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
// Functions
|
||||
const getPage = (pageVal: any) => {
|
||||
deliveries.value = [];
|
||||
get_oil_orders(pageVal)
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
token: null,
|
||||
user: null,
|
||||
deliveries: [] as any[],
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
options: {
|
||||
edgeNavigation: false,
|
||||
format: false,
|
||||
template: PaginationComp
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.userStatus()
|
||||
},
|
||||
mounted() {
|
||||
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() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
},
|
||||
|
||||
get_oil_orders(page: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/delivered/' + 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;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "deleted delivery",
|
||||
type: "success",
|
||||
});
|
||||
this.getPage(this.page)
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error deleting delivery",
|
||||
type: "success",
|
||||
});
|
||||
user.value = response.data.user;
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
.catch(() => {
|
||||
user.value = null
|
||||
})
|
||||
}
|
||||
|
||||
const get_oil_orders = async (pageVal: number) => {
|
||||
try {
|
||||
const response = await deliveryService.getDelivered(pageVal)
|
||||
deliveries.value = response.data || []
|
||||
} catch (error) {
|
||||
console.error('Error fetching delivered deliveries:', error)
|
||||
deliveries.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const deleteCall = (delivery_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "deleted delivery",
|
||||
type: "success",
|
||||
});
|
||||
getPage(page.value)
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error deleting delivery",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus()
|
||||
getPage(page.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -121,109 +121,95 @@
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent} from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
import Header from '../../../layouts/headers/headerauth.vue'
|
||||
import PaginationComp from '../../../components/pagination.vue'
|
||||
import SideBar from '../../../layouts/sidebar/sidebar.vue'
|
||||
import Footer from '../../../layouts/footers/footer.vue'
|
||||
import {notify} from "@kyvg/vue3-notification";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'deliveryFinalized',
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
token: null,
|
||||
user: null,
|
||||
deliveries: [] as any[],
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
options: {
|
||||
edgeNavigation: false,
|
||||
format: false,
|
||||
template: PaginationComp
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.userStatus()
|
||||
},
|
||||
mounted() {
|
||||
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)
|
||||
},
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
import { deliveryService } from '../../../services/deliveryService'
|
||||
import { Delivery } from '../../../types/models'
|
||||
import Header from '../../../layouts/headers/headerauth.vue'
|
||||
import PaginationComp from '../../../components/pagination.vue'
|
||||
import SideBar from '../../../layouts/sidebar/sidebar.vue'
|
||||
import Footer from '../../../layouts/footers/footer.vue'
|
||||
import {notify} from "@kyvg/vue3-notification";
|
||||
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
},
|
||||
// Reactive data
|
||||
const token = ref(null)
|
||||
const user = ref(null)
|
||||
const deliveries = ref<Delivery[]>([])
|
||||
const page = ref(1)
|
||||
const perPage = ref(50)
|
||||
const recordsLength = ref(0)
|
||||
const options = ref({
|
||||
edgeNavigation: false,
|
||||
format: false,
|
||||
template: PaginationComp
|
||||
})
|
||||
|
||||
get_oil_orders(page: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/finalized/' + page;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.deliveries = response.data
|
||||
})
|
||||
},
|
||||
// Functions
|
||||
const getPage = (pageVal: any) => {
|
||||
deliveries.value = [];
|
||||
get_oil_orders(pageVal)
|
||||
}
|
||||
|
||||
deleteCall(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "deleted delivery",
|
||||
type: "success",
|
||||
});
|
||||
this.getPage(this.page)
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error deleting delivery",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
</script>
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
user.value = null
|
||||
})
|
||||
}
|
||||
|
||||
const get_oil_orders = async (pageVal: number) => {
|
||||
try {
|
||||
const response = await deliveryService.getFinalized(pageVal)
|
||||
deliveries.value = response.data || []
|
||||
} catch (error) {
|
||||
console.error('Error fetching finalized deliveries:', error)
|
||||
deliveries.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const deleteCall = (delivery_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "deleted delivery",
|
||||
type: "success",
|
||||
});
|
||||
getPage(page.value)
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error deleting delivery",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus()
|
||||
getPage(page.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
|
||||
@@ -122,104 +122,93 @@
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent} from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
import { Delivery } from '../../../types/models'
|
||||
import Header from '../../../layouts/headers/headerauth.vue'
|
||||
import PaginationComp from '../../../components/pagination.vue'
|
||||
import SideBar from '../../../layouts/sidebar/sidebar.vue'
|
||||
import Footer from '../../../layouts/footers/footer.vue'
|
||||
import {notify} from "@kyvg/vue3-notification";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'deliveryIssue',
|
||||
// Reactive data
|
||||
const token = ref(null)
|
||||
const user = ref(null)
|
||||
const deliveries = ref<Delivery[]>([])
|
||||
const page = ref(1)
|
||||
const perPage = ref(50)
|
||||
const recordsLength = ref(0)
|
||||
const options = ref({
|
||||
edgeNavigation: false,
|
||||
format: false,
|
||||
template: PaginationComp
|
||||
})
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
// Functions
|
||||
const getPage = (pageVal: any) => {
|
||||
deliveries.value = [];
|
||||
get_oil_orders(pageVal)
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
token: null,
|
||||
user: null,
|
||||
deliveries: [] as any[],
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
options: {
|
||||
edgeNavigation: false,
|
||||
format: false,
|
||||
template: PaginationComp
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.userStatus()
|
||||
},
|
||||
mounted() {
|
||||
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() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
},
|
||||
get_oil_orders(page: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/issue/' + 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;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "deleted delivery",
|
||||
type: "success",
|
||||
});
|
||||
this.getPage(this.page)
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error deleting delivery",
|
||||
type: "success",
|
||||
});
|
||||
user.value = response.data.user;
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
.catch(() => {
|
||||
user.value = null
|
||||
})
|
||||
}
|
||||
|
||||
const get_oil_orders = (pageVal: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/issue/' + pageVal;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
deliveries.value = response.data
|
||||
})
|
||||
}
|
||||
|
||||
const deleteCall = (delivery_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "deleted delivery",
|
||||
type: "success",
|
||||
});
|
||||
getPage(page.value)
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error deleting delivery",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus()
|
||||
getPage(page.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -157,108 +157,94 @@
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
import Header from '../../../layouts/headers/headerauth.vue'
|
||||
import PaginationComp from '../../../components/pagination.vue'
|
||||
import SideBar from '../../../layouts/sidebar/sidebar.vue'
|
||||
import Footer from '../../../layouts/footers/footer.vue'
|
||||
import { notify } from "@kyvg/vue3-notification";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'deliveryPending',
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
token: null,
|
||||
user: null,
|
||||
deliveries: [] as any[],
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
options: {
|
||||
edgeNavigation: false,
|
||||
format: false,
|
||||
template: PaginationComp
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.userStatus()
|
||||
},
|
||||
mounted() {
|
||||
this.getPage(this.page)
|
||||
},
|
||||
methods: {
|
||||
getPage: function (page: any) {
|
||||
this.deliveries = [];
|
||||
this.get_oil_orders(page)
|
||||
},
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
},
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
import { deliveryService } from '../../../services/deliveryService'
|
||||
import { Delivery } from '../../../types/models'
|
||||
import Header from '../../../layouts/headers/headerauth.vue'
|
||||
import PaginationComp from '../../../components/pagination.vue'
|
||||
import SideBar from '../../../layouts/sidebar/sidebar.vue'
|
||||
import Footer from '../../../layouts/footers/footer.vue'
|
||||
import { notify } from "@kyvg/vue3-notification";
|
||||
|
||||
|
||||
get_oil_orders(page: 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;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "deleted delivery",
|
||||
type: "success",
|
||||
});
|
||||
this.getPage(this.page)
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error deleting delivery",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// Reactive data
|
||||
const token = ref(null)
|
||||
const user = ref(null)
|
||||
const deliveries = ref<Delivery[]>([])
|
||||
const page = ref(1)
|
||||
const perPage = ref(50)
|
||||
const recordsLength = ref(0)
|
||||
const options = ref({
|
||||
edgeNavigation: false,
|
||||
format: false,
|
||||
template: PaginationComp
|
||||
})
|
||||
|
||||
},
|
||||
// Functions
|
||||
const getPage = (pageVal: any) => {
|
||||
deliveries.value = [];
|
||||
get_oil_orders(pageVal)
|
||||
}
|
||||
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
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 = []
|
||||
}
|
||||
}
|
||||
|
||||
const deleteCall = (delivery_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "deleted delivery",
|
||||
type: "success",
|
||||
});
|
||||
getPage(page.value)
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error deleting delivery",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus()
|
||||
getPage(page.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -164,174 +164,116 @@
|
||||
</div>
|
||||
<Footer />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
import { deliveryService } from '../../../services/deliveryService'
|
||||
import { Delivery } from '../../../types/models'
|
||||
import Header from '../../../layouts/headers/headerauth.vue'
|
||||
import PaginationComp from '../../../components/pagination.vue'
|
||||
import SideBar from '../../../layouts/sidebar/sidebar.vue'
|
||||
import Footer from '../../../layouts/footers/footer.vue'
|
||||
import { notify } from "@kyvg/vue3-notification";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'deliveryOutForDelivery',
|
||||
// Reactive data
|
||||
const token = ref(null)
|
||||
const user = ref(null)
|
||||
const deliveries = ref<Delivery[]>([])
|
||||
const totals = ref<{ town: string; gallons: number }[]>([])
|
||||
const grand_total = ref(0)
|
||||
const page = ref(1)
|
||||
const perPage = ref(50)
|
||||
const recordsLength = ref(0)
|
||||
const options = ref({
|
||||
edgeNavigation: false,
|
||||
format: false,
|
||||
template: PaginationComp
|
||||
})
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
// Functions
|
||||
const getPage = (pageVal: any) => {
|
||||
deliveries.value = [];
|
||||
get_oil_orders(pageVal)
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
token: null,
|
||||
user: null,
|
||||
deliveries: [] as any[],
|
||||
totals: [] as any[],
|
||||
grand_total: 0,
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
options: {
|
||||
edgeNavigation: false,
|
||||
format: false,
|
||||
template: PaginationComp
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
user.value = null
|
||||
})
|
||||
}
|
||||
|
||||
const mod = (date: any) => new Date(date).getTime()
|
||||
|
||||
const get_oil_orders = async (pageVal: number) => {
|
||||
try {
|
||||
const response = await deliveryService.getOutForDelivery(pageVal)
|
||||
deliveries.value = response.data || []
|
||||
// Sort deliveries by Delivery # (id) in descending order
|
||||
deliveries.value.sort((a, b) => b.id - a.id);
|
||||
} catch (error) {
|
||||
console.error('Error fetching out for delivery:', error)
|
||||
deliveries.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const get_totals = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/today-totals';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
totals.value = response.data.totals || []
|
||||
grand_total.value = response.data.grand_total || 0
|
||||
}).catch((error: any) => {
|
||||
console.error('Error fetching totals:', error);
|
||||
totals.value = []
|
||||
grand_total.value = 0
|
||||
})
|
||||
}
|
||||
|
||||
const deleteCall = (delivery_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "deleted delivery",
|
||||
type: "success",
|
||||
});
|
||||
getPage(page.value)
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error deleting delivery",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.userStatus()
|
||||
},
|
||||
mounted() {
|
||||
this.getPage(this.page)
|
||||
this.get_totals()
|
||||
},
|
||||
methods: {
|
||||
getPage: function (page: any) {
|
||||
this.deliveries = [];
|
||||
this.get_oil_orders(page)
|
||||
},
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
mod: (date: any) => new Date (date).getTime(),
|
||||
get_oil_orders(page: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/outfordelivery/' + page;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.deliveries = response.data
|
||||
// Sort deliveries by Delivery # (id) in descending order
|
||||
this.deliveries.sort((a, b) => b.id - a.id);
|
||||
})
|
||||
},
|
||||
|
||||
get_totals() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/today-totals';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.totals = response.data.totals || []
|
||||
this.grand_total = response.data.grand_total || 0
|
||||
}).catch((error: any) => {
|
||||
console.error('Error fetching totals:', error);
|
||||
this.totals = []
|
||||
this.grand_total = 0
|
||||
})
|
||||
},
|
||||
|
||||
deleteCall(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "deleted delivery",
|
||||
type: "success",
|
||||
});
|
||||
this.getPage(this.page)
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error deleting delivery",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// printtTicketAll() {
|
||||
// let path = import.meta.env.VITE_PRINT_URL + '/command/printticket/print_today';
|
||||
// axios({
|
||||
// method: 'get',
|
||||
// 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",
|
||||
// });
|
||||
// }
|
||||
// })
|
||||
// },
|
||||
// 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",
|
||||
// });
|
||||
// }
|
||||
// })
|
||||
// },
|
||||
},
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus()
|
||||
getPage(page.value)
|
||||
get_totals()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -157,173 +157,164 @@
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
import { Delivery } from '../../../types/models'
|
||||
import Header from '../../../layouts/headers/headerauth.vue'
|
||||
import PaginationComp from '../../../components/pagination.vue'
|
||||
import SideBar from '../../../layouts/sidebar/sidebar.vue'
|
||||
import Footer from '../../../layouts/footers/footer.vue'
|
||||
import { notify } from "@kyvg/vue3-notification";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'deliveryOutForDelivery',
|
||||
interface TownTotal {
|
||||
town: string;
|
||||
gallons: number;
|
||||
}
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
// Reactive data
|
||||
const token = ref(null)
|
||||
const user = ref(null)
|
||||
const deliveries = ref<Delivery[]>([])
|
||||
const totals = ref<TownTotal[]>([])
|
||||
const grand_total = ref(0)
|
||||
const page = ref(1)
|
||||
const perPage = ref(50)
|
||||
const recordsLength = ref(0)
|
||||
const options = ref({
|
||||
edgeNavigation: false,
|
||||
format: false,
|
||||
template: PaginationComp
|
||||
})
|
||||
|
||||
data() {
|
||||
return {
|
||||
token: null,
|
||||
user: null,
|
||||
deliveries: [] as any[],
|
||||
totals: [] as any[],
|
||||
grand_total: 0,
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
options: {
|
||||
edgeNavigation: false,
|
||||
format: false,
|
||||
template: PaginationComp
|
||||
// Functions
|
||||
const getPage = (pageVal: any) => {
|
||||
deliveries.value = [];
|
||||
get_oil_orders(pageVal)
|
||||
}
|
||||
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
user.value = null
|
||||
})
|
||||
}
|
||||
|
||||
const get_oil_orders = (pageVal: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/tommorrow/' + pageVal;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
deliveries.value = response.data
|
||||
})
|
||||
}
|
||||
|
||||
const get_totals = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/tomorrow-totals';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
totals.value = response.data.totals || []
|
||||
grand_total.value = response.data.grand_total || 0
|
||||
}).catch((error: any) => {
|
||||
console.error('Error fetching totals:', error);
|
||||
totals.value = []
|
||||
grand_total.value = 0
|
||||
})
|
||||
}
|
||||
|
||||
const deleteCall = (delivery_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "deleted delivery",
|
||||
type: "success",
|
||||
});
|
||||
getPage(page.value)
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error deleting delivery",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.userStatus()
|
||||
},
|
||||
mounted() {
|
||||
this.getPage(this.page)
|
||||
this.get_totals()
|
||||
},
|
||||
methods: {
|
||||
getPage: function (page: any) {
|
||||
this.deliveries = [];
|
||||
this.get_oil_orders(page)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
},
|
||||
get_oil_orders(page: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/tommorrow/' + page;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.deliveries = response.data
|
||||
})
|
||||
},
|
||||
|
||||
get_totals() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/tomorrow-totals';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.totals = response.data.totals || []
|
||||
this.grand_total = response.data.grand_total || 0
|
||||
}).catch((error: any) => {
|
||||
console.error('Error fetching totals:', error);
|
||||
this.totals = []
|
||||
this.grand_total = 0
|
||||
})
|
||||
},
|
||||
|
||||
deleteCall(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "deleted delivery",
|
||||
type: "success",
|
||||
});
|
||||
this.getPage(this.page)
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error deleting delivery",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
printtTicketAll() {
|
||||
let path = import.meta.env.VITE_PRINT_URL + '/command/printticket/print_tommorrow';
|
||||
axios({
|
||||
method: 'delete',
|
||||
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",
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
printTicket(delivery_id: number) {
|
||||
let path = import.meta.env.VITE_PRINT_URL + '/command/printticket/' + delivery_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
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",
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
const printtTicketAll = () => {
|
||||
let path = import.meta.env.VITE_PRINT_URL + '/command/printticket/print_tommorrow';
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "Sent to Printer",
|
||||
type: "success",
|
||||
});
|
||||
getPage(page.value)
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error printing",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const printTicket = (delivery_id: number) => {
|
||||
let path = import.meta.env.VITE_PRINT_URL + '/command/printticket/' + delivery_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "Sent to Printer",
|
||||
type: "success",
|
||||
});
|
||||
getPage(page.value)
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error printing",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus()
|
||||
getPage(page.value)
|
||||
get_totals()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -137,127 +137,112 @@
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
import { deliveryService } from '../../../services/deliveryService'
|
||||
import { Delivery } from '../../../types/models'
|
||||
import Header from '../../../layouts/headers/headerauth.vue'
|
||||
import PaginationComp from '../../../components/pagination.vue'
|
||||
import SideBar from '../../../layouts/sidebar/sidebar.vue'
|
||||
import Footer from '../../../layouts/footers/footer.vue'
|
||||
import { notify } from "@kyvg/vue3-notification";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'deliveryWaiting',
|
||||
// Reactive data
|
||||
const token = ref(null)
|
||||
const user = ref(null)
|
||||
const deliveries = ref<Delivery[]>([])
|
||||
const totals = ref<{ town: string; gallons: number }[]>([])
|
||||
const grand_total = ref(0)
|
||||
const page = ref(1)
|
||||
const perPage = ref(50)
|
||||
const recordsLength = ref(0)
|
||||
const options = ref({
|
||||
edgeNavigation: false,
|
||||
format: false,
|
||||
template: PaginationComp
|
||||
})
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
// Functions
|
||||
const getPage = (pageVal: any) => {
|
||||
deliveries.value = [];
|
||||
get_oil_orders(pageVal)
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
token: null,
|
||||
user: null,
|
||||
deliveries: [] as any[],
|
||||
totals: [] as any[],
|
||||
grand_total: 0,
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
options: {
|
||||
edgeNavigation: false,
|
||||
format: false,
|
||||
template: PaginationComp
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
user.value = null
|
||||
})
|
||||
}
|
||||
|
||||
const get_oil_orders = async (pageVal: number) => {
|
||||
try {
|
||||
const response = await deliveryService.getWaiting(pageVal)
|
||||
deliveries.value = response.data || []
|
||||
} catch (error) {
|
||||
console.error('Error fetching waiting deliveries:', error)
|
||||
deliveries.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const get_totals = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/waiting-totals';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
totals.value = response.data.totals || []
|
||||
grand_total.value = response.data.grand_total || 0
|
||||
}).catch((error: any) => {
|
||||
console.error('Error fetching totals:', error);
|
||||
totals.value = []
|
||||
grand_total.value = 0
|
||||
})
|
||||
}
|
||||
|
||||
const deleteCall = (delivery_id: any) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "deleted delivery",
|
||||
type: "success",
|
||||
});
|
||||
getPage(page.value)
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error deleting delivery",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
created() {
|
||||
this.userStatus()
|
||||
},
|
||||
mounted() {
|
||||
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() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
},
|
||||
get_oil_orders(page: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/waiting/' + page;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.deliveries = response.data
|
||||
})
|
||||
},
|
||||
get_totals() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/waiting-totals';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.totals = response.data.totals || []
|
||||
this.grand_total = response.data.grand_total || 0
|
||||
}).catch((error: any) => {
|
||||
console.error('Error fetching totals:', error);
|
||||
this.totals = []
|
||||
this.grand_total = 0
|
||||
})
|
||||
},
|
||||
|
||||
deleteCall(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "deleted delivery",
|
||||
type: "success",
|
||||
});
|
||||
this.getPage(this.page)
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "error deleting delivery",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
|
||||
},
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus()
|
||||
getPage(page.value)
|
||||
get_totals()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ import useValidate from "@vuelidate/core";
|
||||
import { required, minLength, helpers } from "@vuelidate/validators";
|
||||
import Header from "../../layouts/headers/headerauth.vue";
|
||||
import authHeader from "../../services/auth.header";
|
||||
import {Employee} from '../../types/models';
|
||||
|
||||
export default defineComponent({
|
||||
name: "EmployeeChangePassword",
|
||||
@@ -74,7 +75,7 @@ export default defineComponent({
|
||||
user: null,
|
||||
user_admin: 0,
|
||||
loaded: false,
|
||||
employee: null as any,
|
||||
employee: {} as Employee,
|
||||
ChangePasswordForm: {
|
||||
new_password: "",
|
||||
password_confirm: "",
|
||||
|
||||
@@ -45,7 +45,8 @@
|
||||
<td>{{ person.employee_phone_number }}</td>
|
||||
<td class="text-right">
|
||||
<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 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>
|
||||
@@ -73,7 +74,7 @@
|
||||
<p>{{ person.employee_phone_number }}</p>
|
||||
</div>
|
||||
<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 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>
|
||||
@@ -97,6 +98,7 @@ import axios from 'axios'
|
||||
import authHeader from '../../services/auth.header'
|
||||
import PaginationComp from '../../components/pagination.vue'
|
||||
import Footer from '../../layouts/footers/footer.vue'
|
||||
import {Employee, User} from '../../types/models'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EmployeeHome',
|
||||
@@ -105,8 +107,8 @@ export default defineComponent({
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
user: {} as any,
|
||||
employees: [] as any[],
|
||||
user: {} as User,
|
||||
employees: [] as Employee[],
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
@@ -136,7 +138,7 @@ export default defineComponent({
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null;
|
||||
this.user = {} as User;
|
||||
});
|
||||
},
|
||||
// --- METHOD CORRECTED TO MATCH YOUR SIMPLE ARRAY API RESPONSE ---
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
|
||||
|
||||
import EmployeeHome from '../employee/home.vue';
|
||||
import EmployeeCreate from "../employee/create.vue";
|
||||
import EmployeeEdit from "../employee/edit.vue";
|
||||
import EmployeeProfile from "../employee/profile/home.vue";
|
||||
import EmployeeChangePassword from "../employee/changepassword.vue";
|
||||
const EmployeeHome = () => import('../employee/home.vue');
|
||||
const EmployeeCreate = () => import("../employee/create.vue");
|
||||
const EmployeeEdit = () => import("../employee/edit.vue");
|
||||
const EmployeeProfile = () => import("../employee/profile/home.vue");
|
||||
const EmployeeChangePassword = () => import("../employee/changepassword.vue");
|
||||
|
||||
const employeeRoutes = [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
|
||||
import MoneyYear from '../money/profit_year.vue';
|
||||
const MoneyYear = () => import('../money/profit_year.vue');
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -202,463 +202,468 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, watch } from 'vue'
|
||||
import axios from 'axios'
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import axios, { AxiosResponse, AxiosError } from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
import { notify } from "@kyvg/vue3-notification"
|
||||
import type {
|
||||
CustomerFormData,
|
||||
CreditCardFormData,
|
||||
PricingData,
|
||||
OilPricingResponse,
|
||||
WhoAmIResponse,
|
||||
AutoDeliveryData
|
||||
} from '../../../types/models'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AuthorizePrechargeAutho',
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
data() {
|
||||
return {
|
||||
deliveryId: this.$route.params.id as string,
|
||||
loaded: false,
|
||||
chargeAmount: 0,
|
||||
quickGallonAmounts: [100, 125, 150, 175, 200, 220],
|
||||
loading: false,
|
||||
action: '', // 'preauthorize' or 'charge'
|
||||
error: '',
|
||||
success: '',
|
||||
isChargeConfirmationModalVisible: false,
|
||||
transactionId: 0,
|
||||
user: {
|
||||
user_id: 0,
|
||||
},
|
||||
autoDelivery: {
|
||||
id: 0,
|
||||
customer_id: 0,
|
||||
customer_full_name: '',
|
||||
customer_address: '',
|
||||
customer_town: '',
|
||||
customer_state: 0,
|
||||
customer_zip: '',
|
||||
tank_size: 0,
|
||||
estimated_gallons_left: 0,
|
||||
house_factor: 0,
|
||||
auto_status: 0,
|
||||
},
|
||||
credit_cards: [
|
||||
{
|
||||
id: 0,
|
||||
name_on_card: '',
|
||||
main_card: false,
|
||||
card_number: '',
|
||||
expiration_month: '',
|
||||
type_of_card: '',
|
||||
last_four_digits: '',
|
||||
expiration_year: '',
|
||||
security_number: '',
|
||||
|
||||
}
|
||||
],
|
||||
customer: {
|
||||
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: '',
|
||||
},
|
||||
pricing: {
|
||||
price_from_supplier: 0,
|
||||
price_for_customer: 0,
|
||||
price_for_employee: 0,
|
||||
price_same_day: 0,
|
||||
price_prime: 0,
|
||||
price_emergency: 0,
|
||||
date: "",
|
||||
},
|
||||
currentOilPrice: 0,
|
||||
pricingTiers: [] as { gallons: number; price: number }[],
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
selectedCard(): any {
|
||||
return this.credit_cards.find((card: any) => card.main_card) || this.credit_cards[0]
|
||||
},
|
||||
customerStateName(): string {
|
||||
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';
|
||||
},
|
||||
selectedGallonAmount(): number | null {
|
||||
return this.quickGallonAmounts.find(gal => this.calculatePriceForGallons(gal).toFixed(2) === this.chargeAmount.toFixed(2)) || null;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.loadData(this.deliveryId)
|
||||
this.getPricingTiers()
|
||||
},
|
||||
|
||||
created() {
|
||||
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
|
||||
},
|
||||
|
||||
loadData(deliveryId: string) {
|
||||
this.userStatus()
|
||||
this.getAutoDelivery(deliveryId)
|
||||
this.getOilPricing()
|
||||
this.getCurrentOilPrice()
|
||||
},
|
||||
|
||||
getCurrentOilPrice() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/info/price/oil'
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.currentOilPrice = response.data.price_for_customer;
|
||||
this.calculateDefaultChargeAmount()
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get oil pricing",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
calculateDefaultChargeAmount() {
|
||||
this.chargeAmount = this.calculateTotalAsNumber()
|
||||
},
|
||||
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getOilPricing() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/table";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.pricing = response.data;
|
||||
this.calculateDefaultChargeAmount()
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get oil pricing",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getAutoDelivery(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/delivery/delivery/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data && response.data.customer_id) {
|
||||
this.autoDelivery = response.data;
|
||||
this.getCustomer(this.autoDelivery.customer_id)
|
||||
this.getCreditCards(this.autoDelivery.customer_id)
|
||||
} else {
|
||||
console.error("API Error:", response.data.error || "Failed to fetch auto delivery data.");
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error("API Error in getAutoDelivery:", error);
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get automatic delivery",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getCreditCards(user_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/payment/cards/' + user_id;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.credit_cards = response.data
|
||||
})
|
||||
},
|
||||
|
||||
getCustomer(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.customer = response.data
|
||||
})
|
||||
},
|
||||
|
||||
getPricingTiers() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/tiers";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader()
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.pricingTiers = Object.entries(response.data).map(([gallons, price]) => ({ gallons: parseInt(gallons, 10), price: Number(price) }));
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Pricing Error",
|
||||
text: "Could not retrieve today's pricing.",
|
||||
type: "error"
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
isPricingTierSelected(tierGallons: number | string): boolean {
|
||||
const calculated = this.calculateGallonsToFill()
|
||||
return calculated == Number(tierGallons)
|
||||
},
|
||||
|
||||
calculatePriceForGallons(gallons: number): number {
|
||||
let priceForGallons = 0;
|
||||
const sortedTiers = [...this.pricingTiers].sort((a, b) => Number(a.gallons) - Number(b.gallons));
|
||||
|
||||
// Find the highest tier that is less than or equal to the gallons ordered
|
||||
let applicableTier = sortedTiers.filter(t => gallons >= Number(t.gallons)).pop();
|
||||
|
||||
if (applicableTier) {
|
||||
const pricePerGallon = Number(applicableTier.price) / Number(applicableTier.gallons);
|
||||
priceForGallons = gallons * pricePerGallon;
|
||||
} else if (sortedTiers.length > 0) {
|
||||
// Fallback to the lowest tier's price/gallon if no tier is met (e.g., ordering 50 gallons when lowest tier is 100)
|
||||
const lowestTier = sortedTiers[0];
|
||||
const pricePerGallon = Number(lowestTier.price) / Number(lowestTier.gallons);
|
||||
priceForGallons = gallons * pricePerGallon;
|
||||
}
|
||||
|
||||
return priceForGallons;
|
||||
},
|
||||
|
||||
setGallons(gallons: number) {
|
||||
this.chargeAmount = this.calculatePriceForGallons(gallons);
|
||||
},
|
||||
|
||||
calculateGallonsToFill() {
|
||||
return this.autoDelivery.tank_size - this.autoDelivery.estimated_gallons_left
|
||||
},
|
||||
|
||||
calculateSubtotal() {
|
||||
const gallons = this.calculateGallonsToFill()
|
||||
const pricePerGallon = this.pricing.price_for_customer || this.currentOilPrice
|
||||
return (gallons * pricePerGallon).toFixed(2)
|
||||
},
|
||||
|
||||
calculateTotalAmount() {
|
||||
const subtotal = parseFloat(this.calculateSubtotal())
|
||||
let total = subtotal
|
||||
|
||||
// No additional fees for auto preauthorization
|
||||
|
||||
return total.toFixed(2)
|
||||
},
|
||||
|
||||
calculateTotalAsNumber() {
|
||||
return parseFloat(this.calculateTotalAmount())
|
||||
},
|
||||
|
||||
async handlePreauthorize() {
|
||||
await this.processPayment('preauthorize')
|
||||
},
|
||||
|
||||
async handleChargeNow() {
|
||||
this.loading = true
|
||||
this.isChargeConfirmationModalVisible = true
|
||||
},
|
||||
|
||||
async proceedWithCharge() {
|
||||
this.isChargeConfirmationModalVisible = false
|
||||
await this.processPayment('charge')
|
||||
},
|
||||
|
||||
cancelCharge() {
|
||||
this.isChargeConfirmationModalVisible = false
|
||||
},
|
||||
|
||||
async processPayment(actionType: string) {
|
||||
if (!this.selectedCard) {
|
||||
this.error = 'No credit card found for this customer'
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
this.action = actionType
|
||||
this.error = ''
|
||||
this.success = ''
|
||||
|
||||
try {
|
||||
// Step 2: If payment method is credit, perform the pre-authorization
|
||||
if (actionType === 'preauthorize') {
|
||||
if (!this.chargeAmount || this.chargeAmount <= 0) {
|
||||
throw new Error("Pre-authorization amount must be greater than zero.");
|
||||
}
|
||||
|
||||
const authPayload = {
|
||||
card_id: (this.selectedCard as any).id,
|
||||
preauthorize_amount: this.chargeAmount.toFixed(2),
|
||||
delivery_id: null,
|
||||
auto_id: this.deliveryId,
|
||||
};
|
||||
|
||||
const authPath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/authorize/saved-card/${this.customer.id}`;
|
||||
|
||||
const response = await axios.post(authPath, authPayload, { withCredentials: true, headers: authHeader() });
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
this.transactionId = response.data.id;
|
||||
|
||||
// Update auto_delivery status to 3
|
||||
await axios.put(`${import.meta.env.VITE_AUTO_URL}/delivery/update_status/${this.deliveryId}`, {}, { withCredentials: true, headers: authHeader() });
|
||||
|
||||
// Create Tickets_Auto_Delivery after successful preauthorize
|
||||
const ticketPayload = {
|
||||
gallons_delivered: 0,
|
||||
payment_type: 11, // 11 for preauthorize, 1 for charge
|
||||
payment_card_id: this.selectedCard.id,
|
||||
payment_status: 1 // Pre-authorized status (ready for capture)
|
||||
};
|
||||
const ticketUrl = `${import.meta.env.VITE_AUTO_URL}/confirm/auto/create/${this.deliveryId}`;
|
||||
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');
|
||||
|
||||
// Update transaction auto_id to ticket ID
|
||||
if (this.transactionId && ticketResponse.data) {
|
||||
const data = Array.isArray(ticketResponse.data) ? ticketResponse.data[0] : ticketResponse.data;
|
||||
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() });
|
||||
} else {
|
||||
console.error('auto_ticket_id is undefined in ticket response');
|
||||
}
|
||||
}
|
||||
|
||||
// On successful authorization, show success and redirect
|
||||
this.success = `Preauthorization successful! Transaction ID: ${response.data.auth_net_transaction_id}`;
|
||||
setTimeout(() => {
|
||||
this.$router.push({ name: "auto" });
|
||||
}, 2000);
|
||||
}
|
||||
else { // Handle 'charge' action
|
||||
if (!this.chargeAmount || this.chargeAmount <= 0) {
|
||||
throw new Error("Charge amount must be greater than zero.");
|
||||
}
|
||||
|
||||
const chargePayload = {
|
||||
card_id: (this.selectedCard as any).id,
|
||||
charge_amount: this.chargeAmount.toFixed(2),
|
||||
delivery_id: this.deliveryId,
|
||||
};
|
||||
|
||||
const chargePath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/charge/saved-card/${this.customer.id}`;
|
||||
|
||||
console.log('=== DEBUG: Charge payload ===');
|
||||
console.log('Calling endpoint:', chargePath);
|
||||
console.log('Final payload being sent:', chargePayload);
|
||||
|
||||
const response = await axios.post(chargePath, chargePayload, { withCredentials: true, headers: authHeader() });
|
||||
|
||||
// Update auto_delivery status to 3
|
||||
await axios.put(`${import.meta.env.VITE_AUTO_URL}/delivery/update_status/${this.deliveryId}`, {}, { withCredentials: true, headers: authHeader() });
|
||||
|
||||
// Status codes: 0 = APPROVED, 1 = DECLINED (based on backend TransactionStatus enum)
|
||||
if (response.data && response.data.status === 0) { // 0 = APPROVED
|
||||
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");
|
||||
}
|
||||
this.transactionId = response.data.id;
|
||||
this.success = `Charge successful! Transaction ID: ${response.data.auth_net_transaction_id}`;
|
||||
|
||||
// Create Tickets_Auto_Delivery after successful charge
|
||||
const ticketPayload = {
|
||||
gallons_delivered: 0,
|
||||
payment_type: 11, // 11 for Authorize charge
|
||||
payment_card_id: this.selectedCard.id,
|
||||
payment_status: response.data.status // 0 = APPROVED
|
||||
};
|
||||
const ticketUrl = `${import.meta.env.VITE_AUTO_URL}/confirm/auto/create/${this.deliveryId}`;
|
||||
console.log("POOOPP!")
|
||||
await axios.post(ticketUrl, ticketPayload, { withCredentials: true, headers: authHeader() });
|
||||
|
||||
setTimeout(() => {
|
||||
this.$router.push({ name: "auto" });
|
||||
}, 2000);
|
||||
} else {
|
||||
// The error message from your backend will be more specific now
|
||||
throw new Error(`Payment charge failed: ${response.data?.rejection_reason || 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log(error)
|
||||
this.error = error.response?.data?.detail || `Failed to ${actionType} payment`
|
||||
notify({
|
||||
title: "Error",
|
||||
text: this.error,
|
||||
type: "error",
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
this.action = ''
|
||||
}
|
||||
}
|
||||
},
|
||||
// Reactive data
|
||||
const deliveryId = ref(route.params.id as string)
|
||||
const loaded = ref(false)
|
||||
const chargeAmount = ref(0)
|
||||
const quickGallonAmounts = ref([100, 125, 150, 175, 200, 220])
|
||||
const loading = ref(false)
|
||||
const action = ref('') // 'preauthorize' or 'charge'
|
||||
const error = ref('')
|
||||
const success = ref('')
|
||||
const isChargeConfirmationModalVisible = ref(false)
|
||||
const transactionId = ref(0)
|
||||
const user = ref({
|
||||
user_id: 0,
|
||||
})
|
||||
const autoDelivery = ref<AutoDeliveryData>({
|
||||
id: 0,
|
||||
customer_id: 0,
|
||||
customer_full_name: '',
|
||||
customer_address: '',
|
||||
customer_town: '',
|
||||
customer_state: 0,
|
||||
customer_zip: '',
|
||||
tank_size: 0,
|
||||
estimated_gallons_left: 0,
|
||||
estimated_gallons_left_prev_day: 0,
|
||||
house_factor: 0,
|
||||
auto_status: 0,
|
||||
account_number: '',
|
||||
last_fill: '',
|
||||
days_since_last_fill: 0,
|
||||
last_updated: '',
|
||||
tank_height: '',
|
||||
open_ticket_id: null,
|
||||
})
|
||||
const credit_cards = ref<CreditCardFormData[]>([
|
||||
{
|
||||
id: 0,
|
||||
name_on_card: '',
|
||||
main_card: false,
|
||||
card_number: '',
|
||||
expiration_month: '',
|
||||
type_of_card: '',
|
||||
last_four_digits: '',
|
||||
expiration_year: '',
|
||||
security_number: '',
|
||||
}
|
||||
])
|
||||
const customer = ref<CustomerFormData>({
|
||||
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: '',
|
||||
})
|
||||
const pricing = ref<PricingData>({
|
||||
price_from_supplier: 0,
|
||||
price_for_customer: 0,
|
||||
price_for_employee: 0,
|
||||
price_same_day: 0,
|
||||
price_prime: 0,
|
||||
price_emergency: 0,
|
||||
date: "",
|
||||
})
|
||||
const currentOilPrice = ref(0)
|
||||
const pricingTiers = ref([] as { gallons: number; price: number }[])
|
||||
|
||||
// Computed
|
||||
const selectedCard = computed(() => {
|
||||
return credit_cards.value.find((card) => card.main_card) || credit_cards.value[0]
|
||||
})
|
||||
|
||||
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' };
|
||||
return states[customer.value.customer_state] || 'Unknown state';
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
loadData(deliveryId.value)
|
||||
getPricingTiers()
|
||||
})
|
||||
|
||||
// Functions
|
||||
const resetState = () => {
|
||||
loading.value = false
|
||||
action.value = ''
|
||||
error.value = ''
|
||||
success.value = ''
|
||||
chargeAmount.value = 0
|
||||
}
|
||||
|
||||
const loadData = (deliveryId: string) => {
|
||||
userStatus()
|
||||
getAutoDelivery(deliveryId)
|
||||
getOilPricing()
|
||||
getCurrentOilPrice()
|
||||
}
|
||||
|
||||
const getCurrentOilPrice = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/info/price/oil'
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: AxiosResponse<OilPricingResponse>) => {
|
||||
currentOilPrice.value = response.data.price_for_customer;
|
||||
calculateDefaultChargeAmount()
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get oil pricing",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const calculateDefaultChargeAmount = () => {
|
||||
chargeAmount.value = calculateTotalAsNumber()
|
||||
}
|
||||
|
||||
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 getOilPricing = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/table";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: AxiosResponse<OilPricingResponse>) => {
|
||||
pricing.value = response.data;
|
||||
calculateDefaultChargeAmount()
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get oil pricing",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const getAutoDelivery = (delivery_id: number | string) => {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/delivery/delivery/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: AxiosResponse<AutoDeliveryData>) => {
|
||||
if (response.data && response.data.customer_id) {
|
||||
autoDelivery.value = response.data;
|
||||
getCustomer(autoDelivery.value.customer_id)
|
||||
getCreditCards(autoDelivery.value.customer_id)
|
||||
} else {
|
||||
console.error("API Error: Failed to fetch auto delivery data.");
|
||||
}
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
console.error("API Error in getAutoDelivery:", err);
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get automatic delivery",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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<CreditCardFormData[]>) => {
|
||||
credit_cards.value = response.data
|
||||
})
|
||||
}
|
||||
|
||||
const getCustomer = (userid: number) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: AxiosResponse<CustomerFormData>) => {
|
||||
customer.value = response.data
|
||||
})
|
||||
}
|
||||
|
||||
const getPricingTiers = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/tiers";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader()
|
||||
})
|
||||
.then((response: AxiosResponse<Record<string, number>>) => {
|
||||
pricingTiers.value = Object.entries(response.data).map(([gallons, price]) => ({ gallons: parseInt(gallons, 10), price: Number(price) }));
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Pricing Error",
|
||||
text: "Could not retrieve today's pricing.",
|
||||
type: "error"
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const isPricingTierSelected = (tierGallons: number | string): boolean => {
|
||||
const calculated = calculateGallonsToFill()
|
||||
return calculated == Number(tierGallons)
|
||||
}
|
||||
|
||||
const calculatePriceForGallons = (gallons: number): number => {
|
||||
let priceForGallons = 0;
|
||||
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
|
||||
let applicableTier = sortedTiers.filter(t => gallons >= Number(t.gallons)).pop();
|
||||
|
||||
if (applicableTier) {
|
||||
const pricePerGallon = Number(applicableTier.price) / Number(applicableTier.gallons);
|
||||
priceForGallons = gallons * pricePerGallon;
|
||||
} else if (sortedTiers.length > 0) {
|
||||
// Fallback to the lowest tier's price/gallon if no tier is met (e.g., ordering 50 gallons when lowest tier is 100)
|
||||
const lowestTier = sortedTiers[0];
|
||||
const pricePerGallon = Number(lowestTier.price) / Number(lowestTier.gallons);
|
||||
priceForGallons = gallons * pricePerGallon;
|
||||
}
|
||||
|
||||
return priceForGallons;
|
||||
}
|
||||
|
||||
const setGallons = (gallons: number) => {
|
||||
chargeAmount.value = calculatePriceForGallons(gallons);
|
||||
}
|
||||
|
||||
const calculateGallonsToFill = () => {
|
||||
return autoDelivery.value.tank_size - autoDelivery.value.estimated_gallons_left
|
||||
}
|
||||
|
||||
const calculateSubtotal = () => {
|
||||
const gallons = calculateGallonsToFill()
|
||||
const pricePerGallon = pricing.value.price_for_customer || currentOilPrice.value
|
||||
return (gallons * pricePerGallon).toFixed(2)
|
||||
}
|
||||
|
||||
const calculateTotalAmount = () => {
|
||||
const subtotal = parseFloat(calculateSubtotal())
|
||||
let total = subtotal
|
||||
|
||||
// No additional fees for auto preauthorization
|
||||
|
||||
return total.toFixed(2)
|
||||
}
|
||||
|
||||
const calculateTotalAsNumber = () => {
|
||||
return parseFloat(calculateTotalAmount())
|
||||
}
|
||||
|
||||
const handlePreauthorize = async () => {
|
||||
await processPayment('preauthorize')
|
||||
}
|
||||
|
||||
const handleChargeNow = async () => {
|
||||
loading.value = true
|
||||
isChargeConfirmationModalVisible.value = true
|
||||
}
|
||||
|
||||
const proceedWithCharge = async () => {
|
||||
isChargeConfirmationModalVisible.value = false
|
||||
await processPayment('charge')
|
||||
}
|
||||
|
||||
const cancelCharge = () => {
|
||||
isChargeConfirmationModalVisible.value = false
|
||||
}
|
||||
|
||||
const processPayment = async (actionType: string) => {
|
||||
if (!selectedCard.value) {
|
||||
error.value = 'No credit card found for this customer'
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
action.value = actionType
|
||||
error.value = ''
|
||||
success.value = ''
|
||||
|
||||
try {
|
||||
// Step 2: If payment method is credit, perform the pre-authorization
|
||||
if (actionType === 'preauthorize') {
|
||||
if (!chargeAmount.value || chargeAmount.value <= 0) {
|
||||
throw new Error("Pre-authorization amount must be greater than zero.");
|
||||
}
|
||||
|
||||
const authPayload = {
|
||||
card_id: selectedCard.value!.id,
|
||||
preauthorize_amount: chargeAmount.value.toFixed(2),
|
||||
delivery_id: null,
|
||||
auto_id: deliveryId.value,
|
||||
};
|
||||
|
||||
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() });
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
transactionId.value = response.data.id;
|
||||
|
||||
// Update auto_delivery status to 3
|
||||
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
|
||||
const ticketPayload = {
|
||||
gallons_delivered: 0,
|
||||
payment_type: 11, // 11 for preauthorize, 1 for charge
|
||||
payment_card_id: selectedCard.value!.id,
|
||||
payment_status: 1 // Pre-authorized status (ready for capture)
|
||||
};
|
||||
const ticketUrl = `${import.meta.env.VITE_AUTO_URL}/confirm/auto/create/${deliveryId.value}`;
|
||||
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');
|
||||
|
||||
// Update transaction auto_id to ticket ID
|
||||
if (transactionId.value && ticketResponse.data) {
|
||||
const data = Array.isArray(ticketResponse.data) ? ticketResponse.data[0] : ticketResponse.data;
|
||||
if (data && data.auto_ticket_id !== undefined) {
|
||||
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 {
|
||||
console.error('auto_ticket_id is undefined in ticket response');
|
||||
}
|
||||
}
|
||||
|
||||
// On successful authorization, show success and redirect
|
||||
success.value = `Preauthorization successful! Transaction ID: ${response.data.auth_net_transaction_id}`;
|
||||
setTimeout(() => {
|
||||
router.push({ name: "auto" });
|
||||
}, 2000);
|
||||
}
|
||||
else { // Handle 'charge' action
|
||||
if (!chargeAmount.value || chargeAmount.value <= 0) {
|
||||
throw new Error("Charge amount must be greater than zero.");
|
||||
}
|
||||
|
||||
const chargePayload = {
|
||||
card_id: selectedCard.value!.id,
|
||||
charge_amount: chargeAmount.value.toFixed(2),
|
||||
delivery_id: deliveryId.value,
|
||||
};
|
||||
|
||||
const chargePath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/charge/saved-card/${customer.value.id}`;
|
||||
|
||||
console.log('=== DEBUG: Charge payload ===');
|
||||
console.log('Calling endpoint:', chargePath);
|
||||
console.log('Final payload being sent:', chargePayload);
|
||||
|
||||
const response = await axios.post(chargePath, chargePayload, { withCredentials: true, headers: authHeader() });
|
||||
|
||||
// Update auto_delivery status to 3
|
||||
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)
|
||||
if (response.data && response.data.status === 0) { // 0 = APPROVED
|
||||
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");
|
||||
}
|
||||
transactionId.value = response.data.id;
|
||||
success.value = `Charge successful! Transaction ID: ${response.data.auth_net_transaction_id}`;
|
||||
|
||||
// Create Tickets_Auto_Delivery after successful charge
|
||||
const ticketPayload = {
|
||||
gallons_delivered: 0,
|
||||
payment_type: 11, // 11 for Authorize charge
|
||||
payment_card_id: selectedCard.value!.id,
|
||||
payment_status: response.data.status // 0 = APPROVED
|
||||
};
|
||||
const ticketUrl = `${import.meta.env.VITE_AUTO_URL}/confirm/auto/create/${deliveryId.value}`;
|
||||
console.log("POOOPP!")
|
||||
await axios.post(ticketUrl, ticketPayload, { withCredentials: true, headers: authHeader() });
|
||||
|
||||
setTimeout(() => {
|
||||
router.push({ name: "auto" });
|
||||
}, 2000);
|
||||
} else {
|
||||
// The error message from your backend will be more specific now
|
||||
throw new Error(`Payment charge failed: ${response.data?.rejection_reason || 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
const axiosErr = err as AxiosError<{ detail?: string }>;
|
||||
console.log(err)
|
||||
error.value = axiosErr.response?.data?.detail || `Failed to ${actionType} payment`
|
||||
notify({
|
||||
title: "Error",
|
||||
text: error.value,
|
||||
type: "error",
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
action.value = ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -237,341 +237,349 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import axios from 'axios'
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import axios, { AxiosResponse, AxiosError } from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
import { notify } from "@kyvg/vue3-notification"
|
||||
import type {
|
||||
CustomerFormData,
|
||||
CreditCardFormData,
|
||||
PricingData,
|
||||
CustomerDescriptionData,
|
||||
AutoTicketData,
|
||||
AutoDeliveryData,
|
||||
PaymentCardResponse,
|
||||
AuthorizeNetTransactionResponse
|
||||
} from '../../../types/models'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'captureAuthorizeAuto',
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
userCardfound: false,
|
||||
gallonsDelivered: '',
|
||||
captureAmount: 0,
|
||||
preAuthAmount: 0,
|
||||
transaction: null as any,
|
||||
showPaymentModal: false,
|
||||
modalStep: 0,
|
||||
modalCapturedAmount: 0,
|
||||
// Reactive data
|
||||
const loading = ref(false)
|
||||
const userCardfound = ref(false)
|
||||
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)
|
||||
|
||||
userCard: {
|
||||
date_added: '',
|
||||
user_id: '',
|
||||
card_number: '',
|
||||
last_four_digits: '',
|
||||
name_on_card: '',
|
||||
expiration_month: '',
|
||||
expiration_year: '',
|
||||
type_of_card: '',
|
||||
security_number: '',
|
||||
accepted_or_declined: '',
|
||||
main_card: '',
|
||||
},
|
||||
customerDescription: {
|
||||
customer_id: 0,
|
||||
account_number: '',
|
||||
company_id: 0,
|
||||
fill_location: 0,
|
||||
description: '',
|
||||
},
|
||||
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: '',
|
||||
},
|
||||
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,
|
||||
},
|
||||
autoTicket: {
|
||||
id: 0,
|
||||
customer_id: '',
|
||||
account_number: '',
|
||||
|
||||
customer_town : '',
|
||||
customer_state : '',
|
||||
customer_address : '',
|
||||
customer_zip: '',
|
||||
customer_full_name : '',
|
||||
|
||||
oil_prices_id : '',
|
||||
fill_date : '',
|
||||
gallons_delivered : '',
|
||||
price_per_gallon : '',
|
||||
|
||||
total_amount_customer : '',
|
||||
|
||||
payment_type : '',
|
||||
payment_card_id : '',
|
||||
payment_status : 0,
|
||||
open_ticket_id: 0
|
||||
|
||||
},
|
||||
|
||||
pricing: {
|
||||
price_from_supplier: 0,
|
||||
price_for_customer: 0,
|
||||
price_for_employee: 0,
|
||||
price_same_day: 0,
|
||||
price_prime: 0,
|
||||
price_emergency: 0,
|
||||
date: "",
|
||||
},
|
||||
|
||||
total_amount: 0,
|
||||
discount: 0,
|
||||
total_amount_after_discount: 0,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.getAutoTicket(this.$route.params.id)
|
||||
this.getTransaction()
|
||||
},
|
||||
|
||||
methods: {
|
||||
getAutoTicket(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/delivery/autoticket/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.autoTicket = response.data;
|
||||
console.log(this.autoTicket)
|
||||
this.gallonsDelivered = this.autoTicket.gallons_delivered;
|
||||
this.captureAmount = parseFloat(this.autoTicket.total_amount_customer || '0');
|
||||
this.getCustomer(this.autoTicket.customer_id)
|
||||
|
||||
this.getAutoDelivery(this.autoTicket.id)
|
||||
this.getCustomerDescription(this.autoTicket.customer_id)
|
||||
if (this.autoTicket.payment_card_id) {
|
||||
this.getPaymentCard(this.autoTicket.payment_card_id)
|
||||
}
|
||||
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get automatic",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getAutoDelivery(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/delivery/finddelivery/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
|
||||
this.autoDelivery = response.data;
|
||||
this.getCustomer(this.autoDelivery.customer_id)
|
||||
this.getCustomerDescription(this.autoDelivery.customer_id)
|
||||
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get automatic",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getPaymentCard(card_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/payment/card/" + card_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.userCard.card_number === ''){
|
||||
this.userCardfound = false;
|
||||
}
|
||||
else{
|
||||
this.userCard = response.data;
|
||||
this.userCardfound = true;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
},
|
||||
getCustomer(user_id: any) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/${user_id}`;
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => {
|
||||
this.customer = response.data;
|
||||
})
|
||||
.catch((error: any) => {
|
||||
notify({ title: "Error", text: "Could not find customer", type: "error" });
|
||||
console.error("Error fetching customer:", error);
|
||||
});
|
||||
},
|
||||
getCustomerDescription(user_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/description/" + user_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.customerDescription = response.data;
|
||||
this.loading = false
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not find customer",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
},
|
||||
getTransaction() {
|
||||
const path = `${import.meta.env.VITE_AUTHORIZE_URL}/api/auto/transaction/delivery/${this.$route.params.id}`;
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
this.transaction = response.data;
|
||||
this.preAuthAmount = parseFloat(response.data.preauthorize_amount || 0);
|
||||
if (response.data.status !== 0) { // Not approved
|
||||
this.preAuthAmount = 0;
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
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" });
|
||||
console.log("No transaction found for Automatic - redirecting to customer profile");
|
||||
this.$router.push({ name: 'customerProfile', params: { id: this.customer.id } });
|
||||
} else {
|
||||
notify({ title: "Error", text: "No pre-authorized transaction found", type: "error" });
|
||||
this.$router.push({ name: 'finalizeTicketAuto', params: { id: this.$route.params.id } });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async capturePayment() {
|
||||
if (this.autoTicket.payment_status !== 1) {
|
||||
notify({ title: "Error", text: "Payment already captured or not ready for capture", type: "error" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.transaction || !this.captureAmount) {
|
||||
notify({ title: "Error", text: "Invalid capture amount or transaction data", type: "error" });
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
charge_amount: this.captureAmount,
|
||||
auth_net_transaction_id: this.transaction.auth_net_transaction_id
|
||||
};
|
||||
|
||||
const url = `${import.meta.env.VITE_AUTHORIZE_URL}/api/capture/`;
|
||||
|
||||
const response = await axios.post(
|
||||
url,
|
||||
payload,
|
||||
{ withCredentials: true, headers: authHeader() }
|
||||
);
|
||||
|
||||
if (response.data && response.data.status === 0) {
|
||||
this.autoTicket.payment_status = 3; // Update local status immediately
|
||||
this.modalCapturedAmount = this.captureAmount;
|
||||
this.showPaymentModal = true;
|
||||
// Close the ticket and unassign from delivery
|
||||
this.closeTicket(this.autoTicket.id);
|
||||
setTimeout(() => { this.modalStep = 1 }, 2000);
|
||||
setTimeout(() => { this.showPaymentModal = false; this.$router.push({ name: 'auto' }) }, 4000);
|
||||
} else if (response.data && response.data.status === 1) {
|
||||
const reason = response.data.rejection_reason || "The payment was declined by the gateway.";
|
||||
notify({
|
||||
title: "Payment Declined",
|
||||
text: reason,
|
||||
type: "warn",
|
||||
});
|
||||
} else {
|
||||
throw new Error("Invalid response from server during capture.");
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
const detail = error.response?.data?.detail || "Failed to capture payment due to a server error.";
|
||||
notify({
|
||||
title: "Error",
|
||||
text: detail,
|
||||
type: "error",
|
||||
});
|
||||
console.error("Capture Payment Error:", error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
cancelCapture() {
|
||||
this.$router.push({ name: 'finalizeTicketAuto', params: { id: this.$route.params.id } });
|
||||
},
|
||||
|
||||
async closeTicket(ticket_id: number) {
|
||||
const path = `${import.meta.env.VITE_AUTO_URL}/confirm/auto/close_ticket/${ticket_id}`;
|
||||
axios.put(path, {}, { withCredentials: true })
|
||||
.then(() => {
|
||||
console.log("Ticket closed successfully");
|
||||
})
|
||||
.catch((error: any) => {
|
||||
notify({
|
||||
title: "Warning",
|
||||
text: "Payment captured, but failed to close ticket. Check manually.",
|
||||
type: "warn",
|
||||
});
|
||||
console.error("Error closing ticket:", error);
|
||||
});
|
||||
},
|
||||
|
||||
getTypeColor(transactionType: number) {
|
||||
switch (transactionType) {
|
||||
case 1: return 'text-blue-600'; // Auth
|
||||
case 0: return 'text-orange-600'; // Charge
|
||||
case 2: return 'text-purple-600'; // Capture
|
||||
case 3: return 'text-green-600'; // Delivery/Other
|
||||
default: return 'text-gray-600';
|
||||
}
|
||||
},
|
||||
},
|
||||
const userCard = ref<CreditCardFormData>({
|
||||
id: 0,
|
||||
date_added: '',
|
||||
user_id: '',
|
||||
card_number: '',
|
||||
last_four_digits: '',
|
||||
name_on_card: '',
|
||||
expiration_month: '',
|
||||
expiration_year: '',
|
||||
type_of_card: '',
|
||||
security_number: '',
|
||||
accepted_or_declined: '',
|
||||
main_card: false,
|
||||
})
|
||||
const customerDescription = ref<CustomerDescriptionData>({
|
||||
customer_id: 0,
|
||||
account_number: '',
|
||||
company_id: 0,
|
||||
fill_location: 0,
|
||||
description: '',
|
||||
})
|
||||
const customer = ref<CustomerFormData>({
|
||||
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: '',
|
||||
account_number: '',
|
||||
})
|
||||
const autoDelivery = ref<AutoDeliveryData>({
|
||||
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,
|
||||
})
|
||||
const autoTicket = ref<AutoTicketData>({
|
||||
id: 0,
|
||||
customer_id: '',
|
||||
account_number: '',
|
||||
customer_town: '',
|
||||
customer_state: '',
|
||||
customer_address: '',
|
||||
customer_zip: '',
|
||||
customer_full_name: '',
|
||||
oil_prices_id: '',
|
||||
fill_date: '',
|
||||
gallons_delivered: '',
|
||||
price_per_gallon: '',
|
||||
total_amount_customer: '',
|
||||
payment_type: '',
|
||||
payment_card_id: '',
|
||||
payment_status: 0,
|
||||
open_ticket_id: 0
|
||||
})
|
||||
|
||||
const pricing = ref<PricingData>({
|
||||
price_from_supplier: 0,
|
||||
price_for_customer: 0,
|
||||
price_for_employee: 0,
|
||||
price_same_day: 0,
|
||||
price_prime: 0,
|
||||
price_emergency: 0,
|
||||
date: "",
|
||||
})
|
||||
|
||||
const total_amount = ref(0)
|
||||
const discount = ref(0)
|
||||
const total_amount_after_discount = ref(0)
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
getAutoTicket(route.params.id)
|
||||
getTransaction()
|
||||
})
|
||||
|
||||
// Functions
|
||||
const getAutoTicket = (delivery_id: number | string) => {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/delivery/autoticket/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: AxiosResponse<AutoTicketData>) => {
|
||||
autoTicket.value = response.data;
|
||||
console.log(autoTicket.value)
|
||||
gallonsDelivered.value = autoTicket.value.gallons_delivered;
|
||||
captureAmount.value = parseFloat(autoTicket.value.total_amount_customer || '0');
|
||||
getCustomer(autoTicket.value.customer_id)
|
||||
|
||||
getAutoDelivery(autoTicket.value.id)
|
||||
getCustomerDescription(autoTicket.value.customer_id)
|
||||
if (autoTicket.value.payment_card_id) {
|
||||
getPaymentCard(autoTicket.value.payment_card_id)
|
||||
}
|
||||
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get automatic",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const getAutoDelivery = (delivery_id: number) => {
|
||||
let path = import.meta.env.VITE_AUTO_URL + "/delivery/finddelivery/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: AxiosResponse<AutoDeliveryData>) => {
|
||||
|
||||
autoDelivery.value = response.data;
|
||||
getCustomer(autoDelivery.value.customer_id)
|
||||
getCustomerDescription(autoDelivery.value.customer_id)
|
||||
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get automatic",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const getPaymentCard = (card_id: number | string) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/payment/card/" + card_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: AxiosResponse<PaymentCardResponse>) => {
|
||||
if (response.data.userCard.card_number === ''){
|
||||
userCardfound.value = false;
|
||||
}
|
||||
else{
|
||||
userCard.value = response.data.userCard as CreditCardFormData;
|
||||
userCardfound.value = true;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
}
|
||||
|
||||
const getCustomer = (user_id: number | string) => {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/${user_id}`;
|
||||
axios.get<CustomerFormData>(path, { withCredentials: true })
|
||||
.then((response) => {
|
||||
customer.value = response.data;
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
notify({ title: "Error", text: "Could not find customer", type: "error" });
|
||||
console.error("Error fetching customer:", error);
|
||||
});
|
||||
}
|
||||
|
||||
const getCustomerDescription = (user_id: number | string) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/description/" + user_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: AxiosResponse<CustomerDescriptionData>) => {
|
||||
customerDescription.value = response.data;
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not find customer",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const getTransaction = () => {
|
||||
const path = `${import.meta.env.VITE_AUTHORIZE_URL}/api/auto/transaction/delivery/${route.params.id}`;
|
||||
axios.get<AuthorizeNetTransactionResponse>(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response) => {
|
||||
transaction.value = response.data;
|
||||
preAuthAmount.value = parseFloat(String(response.data.preauthorize_amount) || '0');
|
||||
if (response.data.status !== 0) { // Not approved
|
||||
preAuthAmount.value = 0;
|
||||
}
|
||||
})
|
||||
.catch((error: AxiosError) => {
|
||||
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" });
|
||||
console.log("No transaction found for Automatic - redirecting to customer profile");
|
||||
router.push({ name: 'customerProfile', params: { id: customer.value.id } });
|
||||
} else {
|
||||
notify({ title: "Error", text: "No pre-authorized transaction found", type: "error" });
|
||||
router.push({ name: 'finalizeTicketAuto', params: { id: route.params.id } });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const capturePayment = async () => {
|
||||
if (autoTicket.value.payment_status !== 1) {
|
||||
notify({ title: "Error", text: "Payment already captured or not ready for capture", type: "error" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!transaction.value || !captureAmount.value) {
|
||||
notify({ title: "Error", text: "Invalid capture amount or transaction data", type: "error" });
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
charge_amount: captureAmount.value,
|
||||
auth_net_transaction_id: transaction.value.auth_net_transaction_id
|
||||
};
|
||||
|
||||
const url = `${import.meta.env.VITE_AUTHORIZE_URL}/api/capture/`;
|
||||
|
||||
const response = await axios.post(
|
||||
url,
|
||||
payload,
|
||||
{ withCredentials: true, headers: authHeader() }
|
||||
);
|
||||
|
||||
if (response.data && response.data.status === 0) {
|
||||
autoTicket.value.payment_status = 3; // Update local status immediately
|
||||
modalCapturedAmount.value = captureAmount.value;
|
||||
showPaymentModal.value = true;
|
||||
// Close the ticket and unassign from delivery
|
||||
closeTicket(autoTicket.value.id);
|
||||
setTimeout(() => { modalStep.value = 1 }, 2000);
|
||||
setTimeout(() => { showPaymentModal.value = false; router.push({ name: 'auto' }) }, 4000);
|
||||
} else if (response.data && response.data.status === 1) {
|
||||
const reason = response.data.rejection_reason || "The payment was declined by the gateway.";
|
||||
notify({
|
||||
title: "Payment Declined",
|
||||
text: reason,
|
||||
type: "warn",
|
||||
});
|
||||
} else {
|
||||
throw new Error("Invalid response from server during capture.");
|
||||
}
|
||||
|
||||
} 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.";
|
||||
notify({
|
||||
title: "Error",
|
||||
text: detail,
|
||||
type: "error",
|
||||
});
|
||||
console.error("Capture Payment Error:", error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const cancelCapture = () => {
|
||||
router.push({ name: 'finalizeTicketAuto', params: { id: route.params.id } });
|
||||
}
|
||||
|
||||
const closeTicket = async (ticket_id: number) => {
|
||||
const path = `${import.meta.env.VITE_AUTO_URL}/confirm/auto/close_ticket/${ticket_id}`;
|
||||
axios.put(path, {}, { withCredentials: true })
|
||||
.then(() => {
|
||||
console.log("Ticket closed successfully");
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
notify({
|
||||
title: "Warning",
|
||||
text: "Payment captured, but failed to close ticket. Check manually.",
|
||||
type: "warn",
|
||||
});
|
||||
console.error("Error closing ticket:", error);
|
||||
});
|
||||
}
|
||||
|
||||
const getTypeColor = (transactionType: number) => {
|
||||
switch (transactionType) {
|
||||
case 1: return 'text-blue-600'; // Auth
|
||||
case 0: return 'text-orange-600'; // Charge
|
||||
case 2: return 'text-purple-600'; // Capture
|
||||
case 3: return 'text-green-600'; // Delivery/Other
|
||||
default: return 'text-gray-600';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -172,501 +172,503 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, watch } from 'vue'
|
||||
import axios from 'axios'
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import axios, { AxiosResponse, AxiosError } from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
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({
|
||||
name: 'AuthorizePreauthCharge',
|
||||
// Router and route
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
data() {
|
||||
return {
|
||||
deliveryId: this.$route.params.id as string,
|
||||
loaded: false,
|
||||
chargeAmount: 0,
|
||||
loading: false,
|
||||
action: '', // 'preauthorize' or 'charge'
|
||||
error: '',
|
||||
success: '',
|
||||
isChargeConfirmationModalVisible: false,
|
||||
user: {
|
||||
user_id: 0,
|
||||
},
|
||||
delivery: {
|
||||
id: 0,
|
||||
customer_id: 0,
|
||||
customer_name: '',
|
||||
customer_address: '',
|
||||
customer_town: '',
|
||||
customer_state: 0,
|
||||
customer_zip: '',
|
||||
gallons_ordered: 0,
|
||||
customer_asked_for_fill: 0,
|
||||
gallons_delivered: 0,
|
||||
customer_filled: 0,
|
||||
delivery_status: 0,
|
||||
when_ordered: '',
|
||||
when_delivered: '',
|
||||
expected_delivery_date: '',
|
||||
automatic: 0,
|
||||
oil_id: 0,
|
||||
supplier_price: 0,
|
||||
customer_price: 0,
|
||||
customer_temperature: 0,
|
||||
dispatcher_notes: '',
|
||||
prime: 0,
|
||||
promo_id: 0,
|
||||
emergency: 0,
|
||||
same_day: 0,
|
||||
payment_type: 0,
|
||||
payment_card_id: 0,
|
||||
driver_employee_id: 0,
|
||||
driver_first_name: '',
|
||||
driver_last_name: '',
|
||||
pre_charge_amount: 0,
|
||||
total_price: 0,
|
||||
service_id: null,
|
||||
},
|
||||
credit_cards: [
|
||||
{
|
||||
id: 0,
|
||||
name_on_card: '',
|
||||
main_card: false,
|
||||
card_number: '',
|
||||
expiration_month: '',
|
||||
type_of_card: '',
|
||||
last_four_digits: '',
|
||||
expiration_year: '',
|
||||
security_number: '',
|
||||
|
||||
}
|
||||
],
|
||||
customer: {
|
||||
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: '',
|
||||
},
|
||||
pricing: {
|
||||
price_from_supplier: 0,
|
||||
price_for_customer: 0,
|
||||
price_for_employee: 0,
|
||||
price_same_day: 0,
|
||||
price_prime: 0,
|
||||
price_emergency: 0,
|
||||
date: "",
|
||||
},
|
||||
promo_active: false,
|
||||
promo: {
|
||||
name_of_promotion: '',
|
||||
description: '',
|
||||
money_off_delivery: 0,
|
||||
text_on_ticket: ''
|
||||
},
|
||||
total_amount: 0,
|
||||
discount: 0,
|
||||
total_amount_after_discount: 0,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
selectedCard(): any {
|
||||
return this.credit_cards.find((card: any) => card.id === this.delivery.payment_card_id)
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.loadData(this.deliveryId)
|
||||
},
|
||||
|
||||
created() {
|
||||
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';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.update)
|
||||
console.log("Updated Status of Deliveries")
|
||||
})
|
||||
},
|
||||
|
||||
updateChargeAmount() {
|
||||
// Only update if we have all necessary data
|
||||
if (this.total_amount_after_discount > 0 &&
|
||||
this.pricing.price_prime !== undefined &&
|
||||
this.pricing.price_same_day !== undefined &&
|
||||
this.pricing.price_emergency !== undefined) {
|
||||
this.chargeAmount = this.calculateTotalAsNumber();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
sumdelivery(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/delivery/total/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.total_amount = parseFloat(response.data.total_amount) || 0;
|
||||
this.discount = parseFloat(response.data.discount) || 0;
|
||||
this.total_amount_after_discount = parseFloat(response.data.total_amount_after_discount) || 0;
|
||||
|
||||
// Try to update charge amount with complete pricing
|
||||
const updated = this.updateChargeAmount();
|
||||
|
||||
// Fallback only if pricing not loaded yet and calculation didn't run
|
||||
if (!updated) {
|
||||
if (this.promo_active) {
|
||||
this.chargeAmount = this.total_amount_after_discount;
|
||||
} else {
|
||||
this.chargeAmount = this.total_amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get oil pricing",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getPromo(promo_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/promo/" + promo_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
this.promo = response.data
|
||||
this.promo_active = true
|
||||
|
||||
// Trigger a charge amount update if all data is available
|
||||
this.updateChargeAmount();
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getOilPricing() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/table";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.pricing = response.data;
|
||||
// Try to update charge amount when pricing is loaded
|
||||
this.updateChargeAmount();
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get oil pricing",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getOilOrder(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/delivery/order/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data && response.data.ok) {
|
||||
this.delivery = response.data.delivery;
|
||||
this.getCustomer(this.delivery.customer_id)
|
||||
this.getCreditCards(this.delivery.customer_id)
|
||||
if (this.delivery.promo_id != null) {
|
||||
this.getPromo(this.delivery.promo_id);
|
||||
this.promo_active = true;
|
||||
}
|
||||
} else {
|
||||
console.error("API Error:", response.data.error || "Failed to fetch delivery data.");
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error("API Error in getOilOrder:", error);
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get delivery",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getCreditCards(user_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/payment/cards/' + user_id;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.credit_cards = response.data
|
||||
})
|
||||
},
|
||||
|
||||
getCustomer(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.customer = response.data
|
||||
})
|
||||
},
|
||||
|
||||
calculateSubtotal() {
|
||||
const gallons = this.delivery.gallons_ordered || 0
|
||||
const pricePerGallon = this.delivery.customer_price || 0
|
||||
return (gallons * pricePerGallon).toFixed(2)
|
||||
},
|
||||
|
||||
calculateTotalAmount() {
|
||||
if (this.total_amount_after_discount == null || this.total_amount_after_discount === undefined) {
|
||||
return '0.00';
|
||||
}
|
||||
|
||||
let totalNum = Number(this.total_amount_after_discount);
|
||||
if (isNaN(totalNum)) {
|
||||
return '0.00';
|
||||
}
|
||||
|
||||
if (this.delivery && this.delivery.prime == 1 && this.pricing && this.pricing.price_prime) {
|
||||
totalNum += Number(this.pricing.price_prime) || 0;
|
||||
}
|
||||
if (this.delivery && this.delivery.same_day == 1 && this.pricing && this.pricing.price_same_day) {
|
||||
totalNum += Number(this.pricing.price_same_day) || 0;
|
||||
}
|
||||
if (this.delivery && this.delivery.emergency == 1 && this.pricing && this.pricing.price_emergency) {
|
||||
totalNum += Number(this.pricing.price_emergency) || 0;
|
||||
}
|
||||
|
||||
return totalNum.toFixed(2);
|
||||
},
|
||||
|
||||
calculateTotalAsNumber() {
|
||||
if (this.total_amount_after_discount == null || this.total_amount_after_discount === undefined) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let totalNum = Number(this.total_amount_after_discount);
|
||||
if (isNaN(totalNum)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this.delivery && this.delivery.prime == 1 && this.pricing && this.pricing.price_prime) {
|
||||
totalNum += Number(this.pricing.price_prime) || 0;
|
||||
}
|
||||
if (this.delivery && this.delivery.same_day == 1 && this.pricing && this.pricing.price_same_day) {
|
||||
totalNum += Number(this.pricing.price_same_day) || 0;
|
||||
}
|
||||
if (this.delivery && this.delivery.emergency == 1 && this.pricing && this.pricing.price_emergency) {
|
||||
totalNum += Number(this.pricing.price_emergency) || 0;
|
||||
}
|
||||
|
||||
return totalNum;
|
||||
},
|
||||
|
||||
async handlePreauthorize() {
|
||||
await this.processPayment('preauthorize')
|
||||
},
|
||||
|
||||
async handleChargeNow() {
|
||||
if (!this.selectedCard) {
|
||||
this.error = 'No credit card found for this customer'
|
||||
return
|
||||
}
|
||||
if (!this.chargeAmount || this.chargeAmount <= 0) {
|
||||
this.error = 'Please enter a valid charge amount'
|
||||
return
|
||||
}
|
||||
this.isChargeConfirmationModalVisible = true
|
||||
},
|
||||
|
||||
async proceedWithCharge() {
|
||||
this.isChargeConfirmationModalVisible = false
|
||||
await this.processPayment('charge')
|
||||
},
|
||||
|
||||
cancelCharge() {
|
||||
this.isChargeConfirmationModalVisible = false
|
||||
},
|
||||
|
||||
async processPayment(actionType: string) {
|
||||
if (!this.selectedCard) {
|
||||
this.error = 'No credit card found for this customer'
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
this.action = actionType
|
||||
this.error = ''
|
||||
this.success = ''
|
||||
|
||||
try {
|
||||
// Step 2: If payment method is credit, perform the pre-authorization
|
||||
if (actionType === 'preauthorize') {
|
||||
if (!this.chargeAmount || this.chargeAmount <= 0) {
|
||||
throw new Error("Pre-authorization amount must be greater than zero.");
|
||||
}
|
||||
|
||||
const authPayload = {
|
||||
card_id: (this.selectedCard as any).id,
|
||||
preauthorize_amount: this.chargeAmount.toFixed(2),
|
||||
delivery_id: this.delivery.id,
|
||||
};
|
||||
|
||||
const authPath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/authorize/saved-card/${this.customer.id}`;
|
||||
|
||||
const response = await axios.post(authPath, authPayload, { withCredentials: true, headers: authHeader() });
|
||||
|
||||
// Update payment type to 11 after successful preauthorization
|
||||
try {
|
||||
await axios.put(`${import.meta.env.VITE_BASE_URL}/payment/authorize/${this.delivery.id}`, {}, { headers: authHeader() });
|
||||
} catch (updateError) {
|
||||
console.error('Failed to update payment type after preauthorization:', updateError);
|
||||
}
|
||||
|
||||
// On successful authorization, show success and redirect
|
||||
this.success = `Preauthorization successful! Transaction ID: ${response.data?.auth_net_transaction_id || 'N/A'}`;
|
||||
setTimeout(() => {
|
||||
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } });
|
||||
}, 2000);
|
||||
} else { // Handle 'charge' action
|
||||
if (!this.chargeAmount || this.chargeAmount <= 0) {
|
||||
throw new Error("Charge amount must be greater than zero.");
|
||||
}
|
||||
|
||||
// Create a payload that matches the backend's TransactionCreateByCardID schema
|
||||
const chargePayload = {
|
||||
card_id: (this.selectedCard as any).id,
|
||||
charge_amount: this.chargeAmount.toFixed(2),
|
||||
delivery_id: this.delivery.id,
|
||||
service_id: this.delivery.service_id || null,
|
||||
// You can add other fields here if your schema requires them
|
||||
};
|
||||
|
||||
// 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}`;
|
||||
|
||||
console.log('=== DEBUG: Charge payload ===');
|
||||
console.log('Calling endpoint:', chargePath);
|
||||
console.log('Final payload being sent:', chargePayload);
|
||||
|
||||
const response = await axios.post(chargePath, chargePayload, { withCredentials: true, headers: authHeader() });
|
||||
|
||||
// Update payment type to 11 after successful charge
|
||||
try {
|
||||
await axios.put(`${import.meta.env.VITE_BASE_URL}/payment/authorize/${this.delivery.id}`, {}, { headers: authHeader() });
|
||||
} catch (updateError) {
|
||||
console.error('Failed to update payment type after charge:', updateError);
|
||||
}
|
||||
|
||||
// Status codes: 0 = APPROVED, 1 = DECLINED (based on backend TransactionStatus enum)
|
||||
if (response.data && response.data.status === 0) { // 0 = APPROVED
|
||||
this.success = `Charge successful! Transaction ID: ${response.data.auth_net_transaction_id || 'N/A'}`;
|
||||
setTimeout(() => {
|
||||
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } });
|
||||
}, 2000);
|
||||
} else {
|
||||
// The error message from your backend will be more specific now
|
||||
throw new Error(`Payment charge failed: ${response.data?.rejection_reason || 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log(error)
|
||||
this.error = error.response?.data?.detail || `Failed to ${actionType} payment`
|
||||
notify({
|
||||
title: "Error",
|
||||
text: this.error,
|
||||
type: "error",
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
this.action = ''
|
||||
}
|
||||
}
|
||||
},
|
||||
// Reactive data
|
||||
const deliveryId = 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 isChargeConfirmationModalVisible = ref(false)
|
||||
const user = ref({
|
||||
user_id: 0,
|
||||
})
|
||||
const delivery = ref<DeliveryFormData>({
|
||||
id: 0,
|
||||
customer_id: 0,
|
||||
customer_name: '',
|
||||
customer_address: '',
|
||||
customer_town: '',
|
||||
customer_state: 0,
|
||||
customer_zip: '',
|
||||
gallons_ordered: 0,
|
||||
customer_asked_for_fill: 0,
|
||||
gallons_delivered: 0,
|
||||
customer_filled: 0,
|
||||
delivery_status: 0,
|
||||
when_ordered: '',
|
||||
when_delivered: '',
|
||||
expected_delivery_date: '',
|
||||
automatic: 0,
|
||||
oil_id: 0,
|
||||
supplier_price: 0,
|
||||
customer_price: 0,
|
||||
customer_temperature: 0,
|
||||
dispatcher_notes: '',
|
||||
prime: 0,
|
||||
promo_id: 0,
|
||||
emergency: 0,
|
||||
same_day: 0,
|
||||
payment_type: 0,
|
||||
payment_card_id: 0,
|
||||
driver_employee_id: 0,
|
||||
driver_first_name: '',
|
||||
driver_last_name: '',
|
||||
pre_charge_amount: 0,
|
||||
total_price: 0,
|
||||
service_id: null,
|
||||
})
|
||||
const credit_cards = ref<CreditCardFormData[]>([
|
||||
{
|
||||
id: 0,
|
||||
name_on_card: '',
|
||||
main_card: false,
|
||||
card_number: '',
|
||||
expiration_month: '',
|
||||
type_of_card: '',
|
||||
last_four_digits: '',
|
||||
expiration_year: '',
|
||||
security_number: '',
|
||||
}
|
||||
])
|
||||
const customer = ref<CustomerFormData>({
|
||||
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: '',
|
||||
})
|
||||
const pricing = ref<PricingData>({
|
||||
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<PromoData>({
|
||||
name_of_promotion: '',
|
||||
description: '',
|
||||
money_off_delivery: 0,
|
||||
text_on_ticket: ''
|
||||
})
|
||||
const total_amount = ref(0)
|
||||
const discount = ref(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)
|
||||
}
|
||||
})
|
||||
|
||||
// Functions
|
||||
const resetState = () => {
|
||||
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
|
||||
}
|
||||
|
||||
const loadData = (deliveryId: string) => {
|
||||
userStatus()
|
||||
getOilOrder(deliveryId)
|
||||
sumdelivery(deliveryId)
|
||||
getOilPricing()
|
||||
updatestatus()
|
||||
}
|
||||
|
||||
const updatestatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/updatestatus';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: AxiosResponse<UpdateStatusResponse>) => {
|
||||
if (response.data.update)
|
||||
console.log("Updated Status of Deliveries")
|
||||
})
|
||||
}
|
||||
|
||||
const updateChargeAmount = () => {
|
||||
// Only update if we have all necessary data
|
||||
if (total_amount_after_discount.value > 0 &&
|
||||
pricing.value.price_prime !== undefined &&
|
||||
pricing.value.price_same_day !== undefined &&
|
||||
pricing.value.price_emergency !== undefined) {
|
||||
chargeAmount.value = calculateTotalAsNumber();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const sumdelivery = (delivery_id: number | string) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/delivery/total/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: AxiosResponse<DeliveryTotalResponse>) => {
|
||||
if (response.data.ok) {
|
||||
total_amount.value = parseFloat(String(response.data.total_amount)) || 0;
|
||||
discount.value = parseFloat(String(response.data.discount)) || 0;
|
||||
total_amount_after_discount.value = parseFloat(String(response.data.total_amount_after_discount)) || 0;
|
||||
|
||||
// Try to update charge amount with complete pricing
|
||||
const updated = updateChargeAmount();
|
||||
|
||||
// Fallback only if pricing not loaded yet and calculation didn't run
|
||||
if (!updated) {
|
||||
if (promo_active.value) {
|
||||
chargeAmount.value = total_amount_after_discount.value;
|
||||
} else {
|
||||
chargeAmount.value = total_amount.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get oil pricing",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const getPromo = (promo_id: number) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/promo/" + promo_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: AxiosResponse<PromoResponse>) => {
|
||||
if (response.data) {
|
||||
promo.value = response.data
|
||||
promo_active.value = true
|
||||
|
||||
// Trigger a charge amount update if all data is available
|
||||
updateChargeAmount();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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 getOilPricing = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/table";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: AxiosResponse<OilPricingResponse>) => {
|
||||
pricing.value = response.data;
|
||||
// Try to update charge amount when pricing is loaded
|
||||
updateChargeAmount();
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get oil pricing",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const getOilOrder = (delivery_id: number | string) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/delivery/order/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: AxiosResponse<DeliveryOrderResponse>) => {
|
||||
if (response.data && response.data.ok) {
|
||||
delivery.value = response.data.delivery as DeliveryFormData;
|
||||
getCustomer(delivery.value.customer_id)
|
||||
getCreditCards(delivery.value.customer_id)
|
||||
if (delivery.value.promo_id != null) {
|
||||
getPromo(delivery.value.promo_id);
|
||||
promo_active.value = true;
|
||||
}
|
||||
} else {
|
||||
console.error("API Error:", response.data.error || "Failed to fetch delivery data.");
|
||||
}
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
console.error("API Error in getOilOrder:", error);
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get delivery",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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<CreditCardFormData[]>) => {
|
||||
credit_cards.value = response.data
|
||||
})
|
||||
}
|
||||
|
||||
const getCustomer = (userid: number) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: AxiosResponse<CustomerFormData>) => {
|
||||
customer.value = response.data
|
||||
})
|
||||
}
|
||||
|
||||
const calculateSubtotal = () => {
|
||||
const gallons = delivery.value.gallons_ordered || 0
|
||||
const pricePerGallon = delivery.value.customer_price || 0
|
||||
return (gallons * pricePerGallon).toFixed(2)
|
||||
}
|
||||
|
||||
const calculateTotalAmount = () => {
|
||||
if (total_amount_after_discount.value == null || total_amount_after_discount.value === undefined) {
|
||||
return '0.00';
|
||||
}
|
||||
|
||||
let totalNum = Number(total_amount_after_discount.value);
|
||||
if (isNaN(totalNum)) {
|
||||
return '0.00';
|
||||
}
|
||||
|
||||
if (delivery.value && delivery.value.prime == 1 && pricing.value && pricing.value.price_prime) {
|
||||
totalNum += Number(pricing.value.price_prime) || 0;
|
||||
}
|
||||
if (delivery.value && delivery.value.same_day == 1 && pricing.value && pricing.value.price_same_day) {
|
||||
totalNum += Number(pricing.value.price_same_day) || 0;
|
||||
}
|
||||
if (delivery.value && delivery.value.emergency == 1 && pricing.value && pricing.value.price_emergency) {
|
||||
totalNum += Number(pricing.value.price_emergency) || 0;
|
||||
}
|
||||
|
||||
return totalNum.toFixed(2);
|
||||
}
|
||||
|
||||
const calculateTotalAsNumber = () => {
|
||||
if (total_amount_after_discount.value == null || total_amount_after_discount.value === undefined) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let totalNum = Number(total_amount_after_discount.value);
|
||||
if (isNaN(totalNum)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (delivery.value && delivery.value.prime == 1 && pricing.value && pricing.value.price_prime) {
|
||||
totalNum += Number(pricing.value.price_prime) || 0;
|
||||
}
|
||||
if (delivery.value && delivery.value.same_day == 1 && pricing.value && pricing.value.price_same_day) {
|
||||
totalNum += Number(pricing.value.price_same_day) || 0;
|
||||
}
|
||||
if (delivery.value && delivery.value.emergency == 1 && pricing.value && pricing.value.price_emergency) {
|
||||
totalNum += Number(pricing.value.price_emergency) || 0;
|
||||
}
|
||||
|
||||
return totalNum;
|
||||
}
|
||||
|
||||
const handlePreauthorize = async () => {
|
||||
await processPayment('preauthorize')
|
||||
}
|
||||
|
||||
const handleChargeNow = async () => {
|
||||
if (!selectedCard.value) {
|
||||
error.value = 'No credit card found for this customer'
|
||||
return
|
||||
}
|
||||
if (!chargeAmount.value || chargeAmount.value <= 0) {
|
||||
error.value = 'Please enter a valid charge amount'
|
||||
return
|
||||
}
|
||||
isChargeConfirmationModalVisible.value = true
|
||||
}
|
||||
|
||||
const proceedWithCharge = async () => {
|
||||
isChargeConfirmationModalVisible.value = false
|
||||
await processPayment('charge')
|
||||
}
|
||||
|
||||
const cancelCharge = () => {
|
||||
isChargeConfirmationModalVisible.value = false
|
||||
}
|
||||
|
||||
const processPayment = async (actionType: string) => {
|
||||
if (!selectedCard.value) {
|
||||
error.value = 'No credit card found for this customer'
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
action.value = actionType
|
||||
error.value = ''
|
||||
success.value = ''
|
||||
|
||||
try {
|
||||
// Step 2: If payment method is credit, perform the pre-authorization
|
||||
if (actionType === 'preauthorize') {
|
||||
if (!chargeAmount.value || chargeAmount.value <= 0) {
|
||||
throw new Error("Pre-authorization amount must be greater than zero.");
|
||||
}
|
||||
|
||||
const authPayload = {
|
||||
card_id: selectedCard.value!.id,
|
||||
preauthorize_amount: chargeAmount.value.toFixed(2),
|
||||
delivery_id: delivery.value.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() });
|
||||
|
||||
// Update payment type to 11 after successful preauthorization
|
||||
try {
|
||||
await axios.put(`${import.meta.env.VITE_BASE_URL}/payment/authorize/${delivery.value.id}`, {}, { headers: authHeader() });
|
||||
} catch (updateError) {
|
||||
console.error('Failed to update payment type after preauthorization:', updateError);
|
||||
}
|
||||
|
||||
// On successful authorization, show success and redirect
|
||||
success.value = `Preauthorization successful! Transaction ID: ${response.data?.auth_net_transaction_id || 'N/A'}`;
|
||||
setTimeout(() => {
|
||||
router.push({ name: "customerProfile", params: { id: customer.value.id } });
|
||||
}, 2000);
|
||||
} else { // Handle 'charge' action
|
||||
if (!chargeAmount.value || chargeAmount.value <= 0) {
|
||||
throw new Error("Charge amount must be greater than zero.");
|
||||
}
|
||||
|
||||
// Create a payload that matches the backend's TransactionCreateByCardID schema
|
||||
const chargePayload = {
|
||||
card_id: selectedCard.value!.id,
|
||||
charge_amount: chargeAmount.value.toFixed(2),
|
||||
delivery_id: delivery.value.id,
|
||||
service_id: delivery.value.service_id || null,
|
||||
// You can add other fields here if your schema requires them
|
||||
};
|
||||
|
||||
// Use the correct endpoint for charging a saved card
|
||||
const chargePath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/charge/saved-card/${customer.value.id}`;
|
||||
|
||||
console.log('=== DEBUG: Charge payload ===');
|
||||
console.log('Calling endpoint:', chargePath);
|
||||
console.log('Final payload being sent:', chargePayload);
|
||||
|
||||
const response = await axios.post(chargePath, chargePayload, { withCredentials: true, headers: authHeader() });
|
||||
|
||||
// Update payment type to 11 after successful charge
|
||||
try {
|
||||
await axios.put(`${import.meta.env.VITE_BASE_URL}/payment/authorize/${delivery.value.id}`, {}, { headers: authHeader() });
|
||||
} catch (updateError) {
|
||||
console.error('Failed to update payment type after charge:', updateError);
|
||||
}
|
||||
|
||||
// Status codes: 0 = APPROVED, 1 = DECLINED (based on backend TransactionStatus enum)
|
||||
if (response.data && response.data.status === 0) { // 0 = APPROVED
|
||||
success.value = `Charge successful! Transaction ID: ${response.data.auth_net_transaction_id || 'N/A'}`;
|
||||
setTimeout(() => {
|
||||
router.push({ name: "customerProfile", params: { id: customer.value.id } });
|
||||
}, 2000);
|
||||
} else {
|
||||
// The error message from your backend will be more specific now
|
||||
throw new Error(`Payment charge failed: ${response.data?.rejection_reason || 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
const axiosErr = err as AxiosError<{ detail?: string }>;
|
||||
console.log(err)
|
||||
error.value = axiosErr.response?.data?.detail || `Failed to ${actionType} payment`
|
||||
notify({
|
||||
title: "Error",
|
||||
text: error.value,
|
||||
type: "error",
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
action.value = ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -278,403 +278,409 @@
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import axios from 'axios'
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import axios, { AxiosResponse, AxiosError } from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
import Header from '../../../layouts/headers/headerauth.vue'
|
||||
import SideBar from '../../../layouts/sidebar/sidebar.vue'
|
||||
import Footer from '../../../layouts/footers/footer.vue'
|
||||
import { notify } from "@kyvg/vue3-notification"
|
||||
import type {
|
||||
CustomerFormData,
|
||||
CreditCardFormData,
|
||||
PricingData,
|
||||
PromoData,
|
||||
DeliveryOrderResponse,
|
||||
DeliveryTotalResponse,
|
||||
OilPricingResponse,
|
||||
PromoResponse,
|
||||
PaymentCardResponse,
|
||||
AuthorizeNetTransactionResponse
|
||||
} from '../../../types/models'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'captureAuthorize',
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
// Reactive data
|
||||
const loading = ref(false)
|
||||
const userCardfound = ref(false)
|
||||
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() {
|
||||
return {
|
||||
loading: false,
|
||||
userCardfound: false,
|
||||
gallonsDelivered: '',
|
||||
captureAmount: 0,
|
||||
preAuthAmount: 0,
|
||||
transaction: null as any,
|
||||
showPaymentModal: false,
|
||||
modalStep: 0,
|
||||
modalCapturedAmount: 0,
|
||||
|
||||
userCard: {
|
||||
date_added: '',
|
||||
user_id: '',
|
||||
card_number: '',
|
||||
last_four_digits: '',
|
||||
name_on_card: '',
|
||||
expiration_month: '',
|
||||
expiration_year: '',
|
||||
type_of_card: '',
|
||||
security_number: '',
|
||||
accepted_or_declined: '',
|
||||
main_card: '',
|
||||
},
|
||||
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: '',
|
||||
},
|
||||
deliveryOrder: {
|
||||
id: '',
|
||||
customer_id: 0,
|
||||
customer_name: '',
|
||||
customer_address: '',
|
||||
customer_town: '',
|
||||
customer_state: 0,
|
||||
customer_zip: '',
|
||||
gallons_ordered: 0,
|
||||
customer_asked_for_fill: 0,
|
||||
gallons_delivered: '',
|
||||
customer_filled: 0,
|
||||
delivery_status: 0,
|
||||
when_ordered: '',
|
||||
when_delivered: '',
|
||||
expected_delivery_date: '',
|
||||
automatic: 0,
|
||||
oil_id: 0,
|
||||
supplier_price: '',
|
||||
customer_price: '',
|
||||
customer_temperature: '',
|
||||
dispatcher_notes: '',
|
||||
prime: 0,
|
||||
same_day: 0,
|
||||
emergency: 0,
|
||||
promo_id: 0,
|
||||
payment_type: 0,
|
||||
payment_card_id: '',
|
||||
driver_employee_id: 0,
|
||||
driver_first_name: '',
|
||||
driver_last_name: '',
|
||||
total_price: 0,
|
||||
},
|
||||
pricing: {
|
||||
price_from_supplier: 0,
|
||||
price_for_customer: 0,
|
||||
price_for_employee: 0,
|
||||
price_same_day: 0,
|
||||
price_prime: 0,
|
||||
price_emergency: 0,
|
||||
date: "",
|
||||
},
|
||||
promo_active: false,
|
||||
promo: {
|
||||
name_of_promotion: '',
|
||||
description: '',
|
||||
money_off_delivery: 0,
|
||||
text_on_ticket: ''
|
||||
},
|
||||
total_amount: 0,
|
||||
discount: 0,
|
||||
total_amount_after_discount: 0,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.getOilOrder(this.$route.params.id)
|
||||
this.getOilPricing()
|
||||
this.getTransaction()
|
||||
},
|
||||
|
||||
methods: {
|
||||
sumdelivery(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/delivery/total/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.total_amount = parseFloat(response.data.total_amount) || 0;
|
||||
this.discount = parseFloat(response.data.discount) || 0;
|
||||
this.total_amount_after_discount = parseFloat(response.data.total_amount_after_discount) || 0;
|
||||
|
||||
// Set capture amount to the calculated total including fees and discount
|
||||
this.captureAmount = this.calculateTotalAsNumber();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get totals",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getPromo(promo_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/promo/" + promo_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
this.promo = response.data
|
||||
this.promo_active = true
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getOilOrder(delivery_id: any) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/delivery/order/${delivery_id}`;
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data && response.data.ok) {
|
||||
this.deliveryOrder = response.data.delivery;
|
||||
this.gallonsDelivered = this.deliveryOrder.gallons_delivered;
|
||||
this.getCustomer(this.deliveryOrder.customer_id);
|
||||
this.sumdelivery(delivery_id);
|
||||
|
||||
if ([1, 2, 3].includes(this.deliveryOrder.payment_type)) {
|
||||
this.getPaymentCard(this.deliveryOrder.payment_card_id);
|
||||
}
|
||||
|
||||
if (this.deliveryOrder.promo_id != null) {
|
||||
this.getPromo(this.deliveryOrder.promo_id);
|
||||
this.promo_active = true;
|
||||
}
|
||||
} else {
|
||||
console.error("API Error:", response.data.error || "Failed to fetch delivery data.");
|
||||
}
|
||||
})
|
||||
.catch((error: any) => console.error("Error fetching oil order:", error));
|
||||
},
|
||||
|
||||
getPaymentCard(card_id: any) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/payment/card/${card_id}`;
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => {
|
||||
if (response.data.userCard && response.data.userCard.card_number !== '') {
|
||||
this.userCard = response.data;
|
||||
this.userCardfound = true;
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
this.userCardfound = false;
|
||||
console.error("Error fetching payment card:", error);
|
||||
});
|
||||
},
|
||||
|
||||
getCustomer(user_id: any) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/${user_id}`;
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => {
|
||||
this.customer = response.data;
|
||||
})
|
||||
.catch((error: any) => {
|
||||
notify({ title: "Error", text: "Could not find customer", type: "error" });
|
||||
console.error("Error fetching customer:", error);
|
||||
});
|
||||
},
|
||||
|
||||
getOilPricing() {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/info/price/oil/table`;
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => {
|
||||
this.pricing = response.data;
|
||||
// Calculate capture amount if delivery order is already loaded
|
||||
this.calculateCaptureAmount();
|
||||
})
|
||||
.catch((error: any) => {
|
||||
notify({ title: "Error", text: "Could not get oil pricing", type: "error" });
|
||||
console.error("Error fetching oil pricing:", error);
|
||||
});
|
||||
},
|
||||
|
||||
getTransaction() {
|
||||
const path = `${import.meta.env.VITE_AUTHORIZE_URL}/api/transaction/delivery/${this.$route.params.id}`;
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
this.transaction = response.data;
|
||||
this.preAuthAmount = parseFloat(response.data.preauthorize_amount || 0);
|
||||
if (response.data.status !== 0) { // Not approved
|
||||
this.preAuthAmount = 0;
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
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" });
|
||||
console.log("No transaction found for delivery - redirecting to customer profile");
|
||||
this.$router.push({ name: 'customerProfile', params: { id: this.customer.id } });
|
||||
} else {
|
||||
notify({ title: "Error", text: "No pre-authorized transaction found", type: "error" });
|
||||
this.$router.push({ name: 'finalizeTicket', params: { id: this.$route.params.id } });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async capturePayment() {
|
||||
if (!this.transaction || !this.captureAmount) {
|
||||
notify({ title: "Error", text: "Invalid capture amount or transaction data", type: "error" });
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
charge_amount: this.captureAmount, // FastAPI handles string/number conversion
|
||||
auth_net_transaction_id: this.transaction.auth_net_transaction_id
|
||||
};
|
||||
|
||||
// ✅ FIX: Cleaned up URL, removing the unnecessary customer_id query parameter.
|
||||
const url = `${import.meta.env.VITE_AUTHORIZE_URL}/api/capture/`;
|
||||
|
||||
const response = await axios.post(
|
||||
url,
|
||||
payload,
|
||||
{ withCredentials: true, headers: authHeader() }
|
||||
);
|
||||
|
||||
// ✅ FIX: Improved logic to handle both success and declines properly.
|
||||
if (response.data && response.data.status === 0) {
|
||||
// This is the APPROVED case
|
||||
this.modalCapturedAmount = this.captureAmount;
|
||||
this.showPaymentModal = true;
|
||||
setTimeout(() => { this.modalStep = 1 }, 2000);
|
||||
setTimeout(() => { this.showPaymentModal = false; this.$router.push({ name: 'deliveryOrder', params: { id: this.$route.params.id } }) }, 4000);
|
||||
|
||||
} else if (response.data && response.data.status === 1) {
|
||||
// This is the DECLINED case
|
||||
const reason = response.data.rejection_reason || "The payment was declined by the gateway.";
|
||||
notify({
|
||||
title: "Payment Declined",
|
||||
text: reason,
|
||||
type: "warn", // Use 'warn' for declines instead of 'error'
|
||||
});
|
||||
|
||||
} else {
|
||||
// This handles unexpected responses from the backend.
|
||||
throw new Error("Invalid response from server during capture.");
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
// This 'catch' block now only handles network errors or server crashes (500 errors).
|
||||
const detail = error.response?.data?.detail || "Failed to capture payment due to a server error.";
|
||||
notify({
|
||||
title: "Error",
|
||||
text: detail,
|
||||
type: "error",
|
||||
});
|
||||
console.error("Capture Payment Error:", error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
calculateSubtotal() {
|
||||
const gallons = parseFloat(this.gallonsDelivered || '0') || 0;
|
||||
const pricePerGallon = typeof this.deliveryOrder.customer_price === 'string' ? parseFloat(this.deliveryOrder.customer_price) : Number(this.deliveryOrder.customer_price) || 0;
|
||||
return (gallons * pricePerGallon).toFixed(2);
|
||||
},
|
||||
|
||||
calculateTotalAmount() {
|
||||
if (this.total_amount_after_discount == null || this.total_amount_after_discount === undefined) {
|
||||
return '0.00';
|
||||
}
|
||||
|
||||
let totalNum = Number(this.total_amount_after_discount);
|
||||
if (isNaN(totalNum)) {
|
||||
return '0.00';
|
||||
}
|
||||
|
||||
if (this.deliveryOrder && this.deliveryOrder.prime == 1 && this.pricing && this.pricing.price_prime) {
|
||||
totalNum += Number(this.pricing.price_prime) || 0;
|
||||
}
|
||||
if (this.deliveryOrder && this.deliveryOrder.same_day == 1 && this.pricing && this.pricing.price_same_day) {
|
||||
totalNum += Number(this.pricing.price_same_day) || 0;
|
||||
}
|
||||
if (this.deliveryOrder && this.deliveryOrder.emergency == 1 && this.pricing && this.pricing.price_emergency) {
|
||||
totalNum += Number(this.pricing.price_emergency) || 0;
|
||||
}
|
||||
|
||||
return totalNum.toFixed(2);
|
||||
},
|
||||
|
||||
calculateTotalAsNumber() {
|
||||
if (this.total_amount_after_discount == null || this.total_amount_after_discount === undefined) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let totalNum = Number(this.total_amount_after_discount);
|
||||
if (isNaN(totalNum)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this.deliveryOrder && this.deliveryOrder.prime == 1 && this.pricing && this.pricing.price_prime) {
|
||||
totalNum += Number(this.pricing.price_prime) || 0;
|
||||
}
|
||||
if (this.deliveryOrder && this.deliveryOrder.same_day == 1 && this.pricing && this.pricing.price_same_day) {
|
||||
totalNum += Number(this.pricing.price_same_day) || 0;
|
||||
}
|
||||
if (this.deliveryOrder && this.deliveryOrder.emergency == 1 && this.pricing && this.pricing.price_emergency) {
|
||||
totalNum += Number(this.pricing.price_emergency) || 0;
|
||||
}
|
||||
|
||||
return totalNum;
|
||||
},
|
||||
|
||||
calculateCaptureAmount() {
|
||||
// Only calculate if we have both delivery order and pricing data
|
||||
if (this.deliveryOrder.id && this.pricing.price_for_customer) {
|
||||
const gallons = typeof this.gallonsDelivered === 'string' ? parseFloat(this.gallonsDelivered) : Number(this.gallonsDelivered) || 0;
|
||||
const pricePerGallon = typeof this.deliveryOrder.customer_price === 'string' ? parseFloat(this.deliveryOrder.customer_price) : Number(this.deliveryOrder.customer_price) || 0;
|
||||
let total = gallons * pricePerGallon;
|
||||
|
||||
// Add prime fee if applicable
|
||||
if (this.deliveryOrder.prime == 1) {
|
||||
const primeFee = typeof this.pricing.price_prime === 'string' ? parseFloat(this.pricing.price_prime) : Number(this.pricing.price_prime) || 0;
|
||||
total += primeFee;
|
||||
}
|
||||
|
||||
// Add same day fee if applicable
|
||||
if (this.deliveryOrder.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;
|
||||
total += sameDayFee;
|
||||
}
|
||||
|
||||
this.captureAmount = total;
|
||||
}
|
||||
},
|
||||
|
||||
cancelCapture() {
|
||||
this.$router.push({ name: 'finalizeTicket', params: { id: this.$route.params.id } });
|
||||
},
|
||||
|
||||
getTypeColor(transactionType: number) {
|
||||
switch (transactionType) {
|
||||
case 1: return 'text-blue-600'; // Auth
|
||||
case 0: return 'text-orange-600'; // Charge
|
||||
case 2: return 'text-purple-600'; // Capture
|
||||
case 3: return 'text-green-600'; // Delivery/Other
|
||||
default: return 'text-gray-600';
|
||||
}
|
||||
},
|
||||
},
|
||||
const userCard = ref<CreditCardFormData>({
|
||||
id: 0,
|
||||
date_added: '',
|
||||
user_id: '',
|
||||
card_number: '',
|
||||
last_four_digits: '',
|
||||
name_on_card: '',
|
||||
expiration_month: '',
|
||||
expiration_year: '',
|
||||
type_of_card: '',
|
||||
security_number: '',
|
||||
accepted_or_declined: '',
|
||||
main_card: false,
|
||||
})
|
||||
const customer = ref<CustomerFormData>({
|
||||
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: '',
|
||||
account_number: '',
|
||||
})
|
||||
const deliveryOrder = ref({
|
||||
id: '',
|
||||
customer_id: 0,
|
||||
customer_name: '',
|
||||
customer_address: '',
|
||||
customer_town: '',
|
||||
customer_state: 0,
|
||||
customer_zip: '',
|
||||
gallons_ordered: 0,
|
||||
customer_asked_for_fill: 0,
|
||||
gallons_delivered: '',
|
||||
customer_filled: 0,
|
||||
delivery_status: 0,
|
||||
when_ordered: '',
|
||||
when_delivered: '',
|
||||
expected_delivery_date: '',
|
||||
automatic: 0,
|
||||
oil_id: 0,
|
||||
supplier_price: '',
|
||||
customer_price: '',
|
||||
customer_temperature: '',
|
||||
dispatcher_notes: '',
|
||||
prime: 0,
|
||||
same_day: 0,
|
||||
emergency: 0,
|
||||
promo_id: 0,
|
||||
payment_type: 0,
|
||||
payment_card_id: '',
|
||||
driver_employee_id: 0,
|
||||
driver_first_name: '',
|
||||
driver_last_name: '',
|
||||
total_price: 0,
|
||||
})
|
||||
const pricing = ref<PricingData>({
|
||||
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<PromoData>({
|
||||
name_of_promotion: '',
|
||||
description: '',
|
||||
money_off_delivery: 0,
|
||||
text_on_ticket: ''
|
||||
})
|
||||
const total_amount = ref(0)
|
||||
const discount = ref(0)
|
||||
const total_amount_after_discount = ref(0)
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
getOilOrder(route.params.id)
|
||||
getOilPricing()
|
||||
getTransaction()
|
||||
})
|
||||
|
||||
// Functions
|
||||
const sumdelivery = (delivery_id: number | string) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/delivery/total/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: AxiosResponse<DeliveryTotalResponse>) => {
|
||||
if (response.data.ok) {
|
||||
total_amount.value = parseFloat(String(response.data.total_amount)) || 0;
|
||||
discount.value = parseFloat(String(response.data.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
|
||||
captureAmount.value = calculateTotalAsNumber();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get totals",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const getPromo = (promo_id: number) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/promo/" + promo_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: AxiosResponse<PromoResponse>) => {
|
||||
if (response.data) {
|
||||
promo.value = response.data
|
||||
promo_active.value = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getOilOrder = (delivery_id: number | string) => {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/delivery/order/${delivery_id}`;
|
||||
axios.get<DeliveryOrderResponse>(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response) => {
|
||||
if (response.data && response.data.ok) {
|
||||
deliveryOrder.value = response.data.delivery as typeof deliveryOrder.value;
|
||||
gallonsDelivered.value = deliveryOrder.value.gallons_delivered;
|
||||
getCustomer(deliveryOrder.value.customer_id);
|
||||
sumdelivery(delivery_id);
|
||||
|
||||
if ([1, 2, 3].includes(deliveryOrder.value.payment_type)) {
|
||||
getPaymentCard(deliveryOrder.value.payment_card_id);
|
||||
}
|
||||
|
||||
if (deliveryOrder.value.promo_id != null) {
|
||||
getPromo(deliveryOrder.value.promo_id);
|
||||
promo_active.value = true;
|
||||
}
|
||||
} else {
|
||||
console.error("API Error:", response.data.error || "Failed to fetch delivery data.");
|
||||
}
|
||||
})
|
||||
.catch((error: Error) => console.error("Error fetching oil order:", error));
|
||||
}
|
||||
|
||||
const getPaymentCard = (card_id: number | string) => {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/payment/card/${card_id}`;
|
||||
axios.get<PaymentCardResponse>(path, { withCredentials: true })
|
||||
.then((response) => {
|
||||
if (response.data.userCard && response.data.userCard.card_number !== '') {
|
||||
userCard.value = response.data.userCard as CreditCardFormData;
|
||||
userCardfound.value = true;
|
||||
}
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
userCardfound.value = false;
|
||||
console.error("Error fetching payment card:", error);
|
||||
});
|
||||
}
|
||||
|
||||
const getCustomer = (user_id: number) => {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/${user_id}`;
|
||||
axios.get<CustomerFormData>(path, { withCredentials: true })
|
||||
.then((response) => {
|
||||
customer.value = response.data;
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
notify({ title: "Error", text: "Could not find customer", type: "error" });
|
||||
console.error("Error fetching customer:", error);
|
||||
});
|
||||
}
|
||||
|
||||
const getOilPricing = () => {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/info/price/oil/table`;
|
||||
axios.get<OilPricingResponse>(path, { withCredentials: true })
|
||||
.then((response) => {
|
||||
pricing.value = response.data;
|
||||
// Calculate capture amount if delivery order is already loaded
|
||||
calculateCaptureAmount();
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
notify({ title: "Error", text: "Could not get oil pricing", type: "error" });
|
||||
console.error("Error fetching oil pricing:", error);
|
||||
});
|
||||
}
|
||||
|
||||
const getTransaction = () => {
|
||||
const path = `${import.meta.env.VITE_AUTHORIZE_URL}/api/transaction/delivery/${route.params.id}`;
|
||||
axios.get<AuthorizeNetTransactionResponse>(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response) => {
|
||||
transaction.value = response.data;
|
||||
preAuthAmount.value = parseFloat(String(response.data.preauthorize_amount) || '0');
|
||||
if (response.data.status !== 0) { // Not approved
|
||||
preAuthAmount.value = 0;
|
||||
}
|
||||
})
|
||||
.catch((error: AxiosError) => {
|
||||
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" });
|
||||
console.log("No transaction found for delivery - redirecting to customer profile");
|
||||
router.push({ name: 'customerProfile', params: { id: customer.value.id } });
|
||||
} else {
|
||||
notify({ title: "Error", text: "No pre-authorized transaction found", type: "error" });
|
||||
router.push({ name: 'finalizeTicket', params: { id: route.params.id } });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const capturePayment = async () => {
|
||||
if (!transaction.value || !captureAmount.value) {
|
||||
notify({ title: "Error", text: "Invalid capture amount or transaction data", type: "error" });
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
charge_amount: captureAmount.value, // FastAPI handles string/number conversion
|
||||
auth_net_transaction_id: transaction.value.auth_net_transaction_id
|
||||
};
|
||||
|
||||
// ✅ FIX: Cleaned up URL, removing the unnecessary customer_id query parameter.
|
||||
const url = `${import.meta.env.VITE_AUTHORIZE_URL}/api/capture/`;
|
||||
|
||||
const response = await axios.post(
|
||||
url,
|
||||
payload,
|
||||
{ withCredentials: true, headers: authHeader() }
|
||||
);
|
||||
|
||||
// ✅ FIX: Improved logic to handle both success and declines properly.
|
||||
if (response.data && response.data.status === 0) {
|
||||
// This is the APPROVED case
|
||||
modalCapturedAmount.value = captureAmount.value;
|
||||
showPaymentModal.value = true;
|
||||
setTimeout(() => { modalStep.value = 1 }, 2000);
|
||||
setTimeout(() => { showPaymentModal.value = false; router.push({ name: 'deliveryOrder', params: { id: route.params.id } }) }, 4000);
|
||||
|
||||
} else if (response.data && response.data.status === 1) {
|
||||
// This is the DECLINED case
|
||||
const reason = response.data.rejection_reason || "The payment was declined by the gateway.";
|
||||
notify({
|
||||
title: "Payment Declined",
|
||||
text: reason,
|
||||
type: "warn", // Use 'warn' for declines instead of 'error'
|
||||
});
|
||||
|
||||
} else {
|
||||
// This handles unexpected responses from the backend.
|
||||
throw new Error("Invalid response from server during capture.");
|
||||
}
|
||||
|
||||
} catch (err: unknown) {
|
||||
// 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.";
|
||||
notify({
|
||||
title: "Error",
|
||||
text: detail,
|
||||
type: "error",
|
||||
});
|
||||
console.error("Capture Payment Error:", error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const calculateSubtotal = () => {
|
||||
const gallons = parseFloat(gallonsDelivered.value || '0') || 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);
|
||||
}
|
||||
|
||||
const calculateTotalAmount = () => {
|
||||
if (total_amount_after_discount.value == null || total_amount_after_discount.value === undefined) {
|
||||
return '0.00';
|
||||
}
|
||||
|
||||
let totalNum = Number(total_amount_after_discount.value);
|
||||
if (isNaN(totalNum)) {
|
||||
return '0.00';
|
||||
}
|
||||
|
||||
if (deliveryOrder.value && deliveryOrder.value.prime == 1 && pricing.value && pricing.value.price_prime) {
|
||||
totalNum += Number(pricing.value.price_prime) || 0;
|
||||
}
|
||||
if (deliveryOrder.value && deliveryOrder.value.same_day == 1 && pricing.value && pricing.value.price_same_day) {
|
||||
totalNum += Number(pricing.value.price_same_day) || 0;
|
||||
}
|
||||
if (deliveryOrder.value && deliveryOrder.value.emergency == 1 && pricing.value && pricing.value.price_emergency) {
|
||||
totalNum += Number(pricing.value.price_emergency) || 0;
|
||||
}
|
||||
|
||||
return totalNum.toFixed(2);
|
||||
}
|
||||
|
||||
const calculateTotalAsNumber = () => {
|
||||
if (total_amount_after_discount.value == null || total_amount_after_discount.value === undefined) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let totalNum = Number(total_amount_after_discount.value);
|
||||
if (isNaN(totalNum)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (deliveryOrder.value && deliveryOrder.value.prime == 1 && pricing.value && pricing.value.price_prime) {
|
||||
totalNum += Number(pricing.value.price_prime) || 0;
|
||||
}
|
||||
if (deliveryOrder.value && deliveryOrder.value.same_day == 1 && pricing.value && pricing.value.price_same_day) {
|
||||
totalNum += Number(pricing.value.price_same_day) || 0;
|
||||
}
|
||||
if (deliveryOrder.value && deliveryOrder.value.emergency == 1 && pricing.value && pricing.value.price_emergency) {
|
||||
totalNum += Number(pricing.value.price_emergency) || 0;
|
||||
}
|
||||
|
||||
return totalNum;
|
||||
}
|
||||
|
||||
const calculateCaptureAmount = () => {
|
||||
// Only calculate if we have both delivery order and pricing data
|
||||
if (deliveryOrder.value.id && pricing.value.price_for_customer) {
|
||||
const gallons = typeof gallonsDelivered.value === 'string' ? parseFloat(gallonsDelivered.value) : Number(gallonsDelivered.value) || 0;
|
||||
const pricePerGallon = typeof deliveryOrder.value.customer_price === 'string' ? parseFloat(deliveryOrder.value.customer_price) : Number(deliveryOrder.value.customer_price) || 0;
|
||||
let total = gallons * pricePerGallon;
|
||||
|
||||
// Add prime fee if applicable
|
||||
if (deliveryOrder.value.prime == 1) {
|
||||
const primeFee = typeof pricing.value.price_prime === 'string' ? parseFloat(pricing.value.price_prime) : Number(pricing.value.price_prime) || 0;
|
||||
total += primeFee;
|
||||
}
|
||||
|
||||
// Add same day fee if applicable
|
||||
if (deliveryOrder.value.same_day === 1) {
|
||||
const sameDayFee = typeof pricing.value.price_same_day === 'string' ? parseFloat(pricing.value.price_same_day) : Number(pricing.value.price_same_day) || 0;
|
||||
total += sameDayFee;
|
||||
}
|
||||
|
||||
captureAmount.value = total;
|
||||
}
|
||||
}
|
||||
|
||||
const cancelCapture = () => {
|
||||
router.push({ name: 'finalizeTicket', params: { id: route.params.id } });
|
||||
}
|
||||
|
||||
const getTypeColor = (transactionType: number) => {
|
||||
switch (transactionType) {
|
||||
case 1: return 'text-blue-600'; // Auth
|
||||
case 0: return 'text-orange-600'; // Charge
|
||||
case 2: return 'text-purple-600'; // Capture
|
||||
case 3: return 'text-green-600'; // Delivery/Other
|
||||
default: return 'text-gray-600';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,13 @@
|
||||
|
||||
|
||||
import PayOil from './oil/pay_oil.vue';
|
||||
import AuthorizePreauthCharge from './oil/authorize_preauthcharge.vue';
|
||||
import CaptureAuthorize from './oil/capture_authorize.vue';
|
||||
import PayService from './service/pay_service.vue';
|
||||
import AuthorizeServicePreauthCharge from './service/authorize_preauthcharge.vue';
|
||||
import ChargeServiceAuthorize from './service/capture_authorize.vue';
|
||||
import AuthorizePrechargeAutho from './auto/authorize_precharge_autho.vue';
|
||||
import CaptureAuthorizeAutho from './auto/capture_authorize_autho.vue';
|
||||
const PayOil = () => import('./oil/pay_oil.vue');
|
||||
const AuthorizePreauthCharge = () => import('./oil/authorize_preauthcharge.vue');
|
||||
const CaptureAuthorize = () => import('./oil/capture_authorize.vue');
|
||||
const PayService = () => import('./service/pay_service.vue');
|
||||
const AuthorizeServicePreauthCharge = () => import('./service/authorize_preauthcharge.vue');
|
||||
const ChargeServiceAuthorize = () => import('./service/capture_authorize.vue');
|
||||
const AuthorizePrechargeAutho = () => import('./auto/authorize_precharge_autho.vue');
|
||||
const CaptureAuthorizeAutho = () => import('./auto/capture_authorize_autho.vue');
|
||||
|
||||
const payRoutes = [
|
||||
// This is for oil delivery
|
||||
|
||||
@@ -154,383 +154,382 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, watch } from 'vue'
|
||||
import axios from 'axios'
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import axios, { AxiosResponse, AxiosError } from 'axios'
|
||||
import authHeader from '../../../services/auth.header'
|
||||
import { notify } from "@kyvg/vue3-notification"
|
||||
import type {
|
||||
CustomerFormData,
|
||||
CreditCardFormData,
|
||||
WhoAmIResponse
|
||||
} from '../../../types/models'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AuthorizeServicePreauthCharge',
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
data() {
|
||||
return {
|
||||
serviceId: this.$route.params.id as string,
|
||||
loaded: false,
|
||||
chargeAmount: 0,
|
||||
loading: false,
|
||||
action: '', // 'preauthorize' or 'charge'
|
||||
error: '',
|
||||
success: '',
|
||||
selectedCardId: null as number | null, // Track which card is selected
|
||||
user: {
|
||||
user_id: 0,
|
||||
},
|
||||
service: {
|
||||
id: 0,
|
||||
scheduled_date: '',
|
||||
customer_id: 0,
|
||||
customer_name: '',
|
||||
customer_address: '',
|
||||
customer_town: '',
|
||||
type_service_call: 0,
|
||||
description: '',
|
||||
service_cost: '',
|
||||
payment_card_id: 0,
|
||||
},
|
||||
credit_cards: [
|
||||
{
|
||||
id: 0,
|
||||
name_on_card: '',
|
||||
main_card: false,
|
||||
card_number: '',
|
||||
expiration_month: '',
|
||||
type_of_card: '',
|
||||
last_four_digits: '',
|
||||
expiration_year: '',
|
||||
security_number: '',
|
||||
interface ServiceFormData {
|
||||
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;
|
||||
payment_card_id: number;
|
||||
}
|
||||
|
||||
}
|
||||
],
|
||||
customer: {
|
||||
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: '',
|
||||
},
|
||||
// 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,
|
||||
})
|
||||
const service = ref<ServiceFormData>({
|
||||
id: 0,
|
||||
scheduled_date: '',
|
||||
customer_id: 0,
|
||||
customer_name: '',
|
||||
customer_address: '',
|
||||
customer_town: '',
|
||||
type_service_call: 0,
|
||||
description: '',
|
||||
service_cost: '',
|
||||
payment_card_id: 0,
|
||||
})
|
||||
const credit_cards = ref<CreditCardFormData[]>([
|
||||
{
|
||||
id: 0,
|
||||
name_on_card: '',
|
||||
main_card: false,
|
||||
card_number: '',
|
||||
expiration_month: '',
|
||||
type_of_card: '',
|
||||
last_four_digits: '',
|
||||
expiration_year: '',
|
||||
security_number: '',
|
||||
}
|
||||
])
|
||||
const customer = ref<CustomerFormData>({
|
||||
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: '',
|
||||
})
|
||||
|
||||
}
|
||||
},
|
||||
// Computed
|
||||
const selectedCard = computed(() => {
|
||||
// If user has selected a card manually, use that
|
||||
if (selectedCardId.value) {
|
||||
return credit_cards.value.find((card) => card.id === selectedCardId.value)
|
||||
}
|
||||
// Otherwise use automatic selection logic
|
||||
// First try to find payment_card_id from service
|
||||
if (service.value.payment_card_id && service.value.payment_card_id > 0) {
|
||||
return credit_cards.value.find((card) => card.id === service.value.payment_card_id)
|
||||
}
|
||||
// Otherwise return the primary card (main_card = true)
|
||||
return credit_cards.value.find((card) => card.main_card === true) ||
|
||||
credit_cards.value.find((card) => card.id > 0) || // Any card if no primary
|
||||
null
|
||||
})
|
||||
|
||||
computed: {
|
||||
selectedCard(): any {
|
||||
// If user has selected a card manually, use that
|
||||
if (this.selectedCardId) {
|
||||
return this.credit_cards.find((card: any) => card.id === this.selectedCardId)
|
||||
// Watchers
|
||||
watch(() => route.params.id, (newId) => {
|
||||
if (newId !== serviceId.value) {
|
||||
resetState()
|
||||
serviceId.value = newId as string
|
||||
loadData(newId as string)
|
||||
}
|
||||
})
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
loadData(serviceId.value)
|
||||
})
|
||||
|
||||
// Functions
|
||||
const resetState = () => {
|
||||
loading.value = false
|
||||
action.value = ''
|
||||
error.value = ''
|
||||
success.value = ''
|
||||
chargeAmount.value = 0
|
||||
serviceId.value = route.params.id as string
|
||||
}
|
||||
|
||||
const loadData = (serviceId: string) => {
|
||||
userStatus()
|
||||
getServiceOrder(serviceId)
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
// Otherwise use automatic selection logic
|
||||
// First try to find payment_card_id from service
|
||||
if (this.service.payment_card_id && this.service.payment_card_id > 0) {
|
||||
return this.credit_cards.find((card: any) => card.id === this.service.payment_card_id)
|
||||
}
|
||||
// Otherwise return the primary card (main_card = true)
|
||||
return this.credit_cards.find((card: any) => card.main_card === true) ||
|
||||
this.credit_cards.find((card: any) => card.id > 0) || // Any card if no primary
|
||||
null
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
mounted() {
|
||||
this.loadData(this.serviceId)
|
||||
},
|
||||
|
||||
created() {
|
||||
this.watchRoute()
|
||||
},
|
||||
|
||||
methods: {
|
||||
watchRoute() {
|
||||
watch(
|
||||
() => this.$route.params.id,
|
||||
(newId) => {
|
||||
if (newId !== this.serviceId) {
|
||||
this.resetState()
|
||||
this.serviceId = newId as string
|
||||
this.loadData(newId as string)
|
||||
}
|
||||
const getServiceOrder = (serviceId: number | string) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/service/" + serviceId;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: AxiosResponse<{ ok?: boolean; service?: ServiceFormData } | ServiceFormData[] | ServiceFormData>) => {
|
||||
let serviceData: ServiceFormData | 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 ServiceFormData; // Direct object response
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
resetState() {
|
||||
this.loading = false
|
||||
this.action = ''
|
||||
this.error = ''
|
||||
this.success = ''
|
||||
this.chargeAmount = 0
|
||||
this.serviceId = this.$route.params.id as string
|
||||
},
|
||||
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,
|
||||
};
|
||||
|
||||
loadData(serviceId: string) {
|
||||
this.userStatus()
|
||||
this.getServiceOrder(serviceId)
|
||||
},
|
||||
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getServiceOrder(serviceId: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/service/" + serviceId;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
let serviceData;
|
||||
if (response.data) {
|
||||
// Handle different API response structures
|
||||
if (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 {
|
||||
serviceData = response.data; // Direct object response
|
||||
}
|
||||
|
||||
if (serviceData && serviceData.id) {
|
||||
this.service = {
|
||||
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
|
||||
this.getCustomer(this.service.customer_id);
|
||||
this.getCreditCards(this.service.customer_id);
|
||||
} 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: any) => {
|
||||
console.error("API Error in getServiceOrder:", error);
|
||||
// Fetch related data
|
||||
getCustomer(service.value.customer_id);
|
||||
getCreditCards(service.value.customer_id);
|
||||
} else {
|
||||
console.error("API Error: Invalid service data received:", serviceData);
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get service data",
|
||||
text: "Invalid service data received",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
|
||||
getCreditCards(user_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/payment/cards/' + user_id;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
console.log('Credit cards loaded:', response.data?.length || 0, 'cards');
|
||||
this.credit_cards = response.data || [];
|
||||
}).catch((error: any) => {
|
||||
console.error('Failed to load credit cards:', error);
|
||||
this.credit_cards = [];
|
||||
});
|
||||
},
|
||||
|
||||
getCustomer(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.customer = response.data
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
|
||||
async handlePreauthorize() {
|
||||
await this.processPayment('preauthorize')
|
||||
},
|
||||
|
||||
async handleChargeNow() {
|
||||
await this.processPayment('charge')
|
||||
},
|
||||
|
||||
async processPayment(actionType: string) {
|
||||
if (!this.selectedCard) {
|
||||
this.error = 'No credit card found for this customer'
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
this.action = actionType
|
||||
this.error = ''
|
||||
this.success = ''
|
||||
|
||||
try {
|
||||
// Step 2: If payment method is credit, perform the pre-authorization
|
||||
if (actionType === 'preauthorize') {
|
||||
if (!this.chargeAmount || this.chargeAmount <= 0) {
|
||||
throw new Error("Pre-authorization amount must be greater than zero.");
|
||||
}
|
||||
|
||||
const authPayload = {
|
||||
card_id: (this.selectedCard as any).id,
|
||||
preauthorize_amount: this.chargeAmount.toFixed(2),
|
||||
service_id: this.service.id,
|
||||
delivery_id: null, // No delivery for services
|
||||
};
|
||||
|
||||
const authPath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/authorize/saved-card/${this.customer.id}`;
|
||||
|
||||
const response = await axios.post(authPath, authPayload, { withCredentials: true, headers: authHeader() });
|
||||
|
||||
// Update payment type to 11 after successful preauthorization
|
||||
try {
|
||||
await axios.put(`${import.meta.env.VITE_BASE_URL}/payment/authorize/service/${this.service.id}`, {
|
||||
card_id: (this.selectedCard as any).id,
|
||||
status: actionType === 'preauthorize' ? 1 : 3
|
||||
}, { headers: authHeader() });
|
||||
} catch (updateError) {
|
||||
console.error('Failed to update payment type after preauthorization:', updateError);
|
||||
}
|
||||
|
||||
// On successful authorization, show success and redirect
|
||||
this.success = `Preauthorization successful! Transaction ID: ${response.data?.auth_net_transaction_id || 'N/A'}`;
|
||||
setTimeout(() => {
|
||||
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } });
|
||||
}, 2000);
|
||||
} else { // Handle 'charge' action
|
||||
if (!this.chargeAmount || this.chargeAmount <= 0) {
|
||||
throw new Error("Charge amount must be greater than zero.");
|
||||
}
|
||||
|
||||
// Create a payload that matches the backend's TransactionCreateByCardID schema
|
||||
const chargePayload = {
|
||||
card_id: (this.selectedCard as any).id,
|
||||
charge_amount: this.chargeAmount.toFixed(2),
|
||||
service_id: this.service.id,
|
||||
delivery_id: null, // No delivery for services
|
||||
// You can add other fields here if your schema requires them
|
||||
};
|
||||
|
||||
// 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}`;
|
||||
|
||||
console.log('=== DEBUG: Charge payload ===');
|
||||
console.log('Calling endpoint:', chargePath);
|
||||
console.log('Final payload being sent:', chargePayload);
|
||||
|
||||
const response = await axios.post(chargePath, chargePayload, { withCredentials: true, headers: authHeader() });
|
||||
|
||||
// Update service cost to the charged amount using new dedicated API
|
||||
try {
|
||||
await axios.put(
|
||||
`${import.meta.env.VITE_BASE_URL}/service/update-cost/${this.service.id}`,
|
||||
{ service_cost: this.chargeAmount },
|
||||
{ headers: authHeader(), withCredentials: true }
|
||||
);
|
||||
console.log(`✅ Updated service cost to ${this.chargeAmount} for service ${this.service.id}`);
|
||||
} catch (costError) {
|
||||
console.error('❌ Failed to update service cost:', costError);
|
||||
}
|
||||
|
||||
// Update payment status after successful charge
|
||||
try {
|
||||
await axios.put(`${import.meta.env.VITE_BASE_URL}/payment/capture/service/${this.service.id}`, {
|
||||
card_id: (this.selectedCard as any).id,
|
||||
status: 3 // Approved status
|
||||
}, {
|
||||
headers: authHeader(),
|
||||
withCredentials: true
|
||||
});
|
||||
} catch (updateError) {
|
||||
console.error('Failed to update payment status after charge:', updateError);
|
||||
}
|
||||
|
||||
// Status codes: 0 = APPROVED, 1 = DECLINED (based on backend TransactionStatus enum)
|
||||
if (response.data && response.data.status === 0) { // 0 = APPROVED
|
||||
this.success = `Charge successful! Transaction ID: ${response.data.auth_net_transaction_id || 'N/A'}`;
|
||||
setTimeout(() => {
|
||||
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } });
|
||||
}, 2000);
|
||||
} else {
|
||||
// The error message from your backend will be more specific now
|
||||
throw new Error(`Payment charge failed: ${response.data?.rejection_reason || 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log(error)
|
||||
this.error = error.response?.data?.detail || `Failed to ${actionType} payment`
|
||||
} else {
|
||||
console.error("API Error: No response data received");
|
||||
notify({
|
||||
title: "Error",
|
||||
text: this.error,
|
||||
text: "Could not get service data",
|
||||
type: "error",
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
this.action = ''
|
||||
});
|
||||
}
|
||||
},
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
console.error("API Error in getServiceOrder:", err);
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not get service data",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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 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<CreditCardFormData[]>) => {
|
||||
console.log('Credit cards loaded:', response.data?.length || 0, 'cards');
|
||||
credit_cards.value = response.data || [];
|
||||
}).catch((err: Error) => {
|
||||
console.error('Failed to load credit cards:', err);
|
||||
credit_cards.value = [];
|
||||
});
|
||||
}
|
||||
|
||||
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 getCustomer = (userid: number) => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: AxiosResponse<CustomerFormData>) => {
|
||||
customer.value = response.data
|
||||
})
|
||||
}
|
||||
|
||||
selectCard(cardId: number) {
|
||||
this.selectedCardId = cardId;
|
||||
},
|
||||
const handlePreauthorize = async () => {
|
||||
await processPayment('preauthorize')
|
||||
}
|
||||
|
||||
const handleChargeNow = async () => {
|
||||
await processPayment('charge')
|
||||
}
|
||||
|
||||
const processPayment = async (actionType: string) => {
|
||||
if (!selectedCard.value) {
|
||||
error.value = 'No credit card found for this customer'
|
||||
return
|
||||
}
|
||||
|
||||
formatScheduledDate(dateString: string): string {
|
||||
if (!dateString) return 'Not scheduled';
|
||||
return dateString; // Could format with dayjs if needed
|
||||
loading.value = true
|
||||
action.value = actionType
|
||||
error.value = ''
|
||||
success.value = ''
|
||||
|
||||
try {
|
||||
// Step 2: If payment method is credit, perform the pre-authorization
|
||||
if (actionType === 'preauthorize') {
|
||||
if (!chargeAmount.value || chargeAmount.value <= 0) {
|
||||
throw new Error("Pre-authorization amount must be greater than zero.");
|
||||
}
|
||||
|
||||
const authPayload = {
|
||||
card_id: selectedCard.value!.id,
|
||||
preauthorize_amount: chargeAmount.value.toFixed(2),
|
||||
service_id: service.value.id,
|
||||
delivery_id: null, // No delivery for services
|
||||
};
|
||||
|
||||
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() });
|
||||
|
||||
// Update payment type to 11 after successful preauthorization
|
||||
try {
|
||||
await axios.put(`${import.meta.env.VITE_BASE_URL}/payment/authorize/service/${service.value.id}`, {
|
||||
card_id: selectedCard.value!.id,
|
||||
status: actionType === 'preauthorize' ? 1 : 3
|
||||
}, { headers: authHeader() });
|
||||
} catch (updateError) {
|
||||
console.error('Failed to update payment type after preauthorization:', updateError);
|
||||
}
|
||||
|
||||
// On successful authorization, show success and redirect
|
||||
success.value = `Preauthorization successful! Transaction ID: ${response.data?.auth_net_transaction_id || 'N/A'}`;
|
||||
setTimeout(() => {
|
||||
router.push({ name: "customerProfile", params: { id: customer.value.id } });
|
||||
}, 2000);
|
||||
} else { // Handle 'charge' action
|
||||
if (!chargeAmount.value || chargeAmount.value <= 0) {
|
||||
throw new Error("Charge amount must be greater than zero.");
|
||||
}
|
||||
|
||||
// Create a payload that matches the backend's TransactionCreateByCardID schema
|
||||
const chargePayload = {
|
||||
card_id: selectedCard.value!.id,
|
||||
charge_amount: chargeAmount.value.toFixed(2),
|
||||
service_id: service.value.id,
|
||||
delivery_id: null, // No delivery for services
|
||||
// You can add other fields here if your schema requires them
|
||||
};
|
||||
|
||||
// Use the correct endpoint for charging a saved card
|
||||
const chargePath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/charge/saved-card/${customer.value.id}`;
|
||||
|
||||
console.log('=== DEBUG: Charge payload ===');
|
||||
console.log('Calling endpoint:', chargePath);
|
||||
console.log('Final payload being sent:', chargePayload);
|
||||
|
||||
const response = await axios.post(chargePath, chargePayload, { withCredentials: true, headers: authHeader() });
|
||||
|
||||
// Update service cost to the charged amount using new dedicated API
|
||||
try {
|
||||
await axios.put(
|
||||
`${import.meta.env.VITE_BASE_URL}/service/update-cost/${service.value.id}`,
|
||||
{ service_cost: chargeAmount.value },
|
||||
{ headers: authHeader(), withCredentials: true }
|
||||
);
|
||||
console.log(`✅ Updated service cost to ${chargeAmount.value} for service ${service.value.id}`);
|
||||
} catch (costError) {
|
||||
console.error('❌ Failed to update service cost:', costError);
|
||||
}
|
||||
|
||||
// Update payment status after successful charge
|
||||
try {
|
||||
await axios.put(`${import.meta.env.VITE_BASE_URL}/payment/capture/service/${service.value.id}`, {
|
||||
card_id: selectedCard.value!.id,
|
||||
status: 3 // Approved status
|
||||
}, {
|
||||
headers: authHeader(),
|
||||
withCredentials: true
|
||||
});
|
||||
} catch (updateError) {
|
||||
console.error('Failed to update payment status after charge:', updateError);
|
||||
}
|
||||
|
||||
// Status codes: 0 = APPROVED, 1 = DECLINED (based on backend TransactionStatus enum)
|
||||
if (response.data && response.data.status === 0) { // 0 = APPROVED
|
||||
success.value = `Charge successful! Transaction ID: ${response.data.auth_net_transaction_id || 'N/A'}`;
|
||||
setTimeout(() => {
|
||||
router.push({ name: "customerProfile", params: { id: customer.value.id } });
|
||||
}, 2000);
|
||||
} else {
|
||||
// The error message from your backend will be more specific now
|
||||
throw new Error(`Payment charge failed: ${response.data?.rejection_reason || 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
} catch (err: unknown) {
|
||||
const axiosErr = err as AxiosError<{ detail?: string }>;
|
||||
console.log(err)
|
||||
error.value = axiosErr.response?.data?.detail || `Failed to ${actionType} payment`
|
||||
notify({
|
||||
title: "Error",
|
||||
text: error.value,
|
||||
type: "error",
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
action.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
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 selectCard = (cardId: number) => {
|
||||
selectedCardId.value = cardId;
|
||||
}
|
||||
|
||||
const formatScheduledDate = (dateString: string): string => {
|
||||
if (!dateString) return 'Not scheduled';
|
||||
return dateString; // Could format with dayjs if needed
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -264,7 +264,7 @@ const service = ref<Service | null>(null);
|
||||
const preAuthCard = ref<UserCard | null>(null);
|
||||
const selectedCard = ref<UserCard | null>(null);
|
||||
const chargeAmount = ref<number>(0);
|
||||
const transaction = ref(null as any);
|
||||
const transaction = ref<ServiceTransaction | null>(null);
|
||||
const preAuthAmount = ref<number>(0);
|
||||
const serviceTransactions = ref<ServiceTransaction[]>([]);
|
||||
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);
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
@@ -459,7 +460,8 @@ const chargeService = async () => {
|
||||
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.";
|
||||
notify({ title: "Error", text: detail, type: "error" });
|
||||
console.error("Charge/Capture Service Error:", error);
|
||||
@@ -489,7 +491,7 @@ const getTransaction = async () => {
|
||||
selectedCard.value = cardResponse.data;
|
||||
chargeAmount.value = preAuthAmount.value;
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error("No pre-authorized transaction found for service:", error);
|
||||
preAuthAmount.value = service.value?.service_cost || 0;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,8 +35,8 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import Header from '../../layouts/headers/headerauth.vue';
|
||||
import SideBar from '../../layouts/sidebar/sidebar.vue';
|
||||
import Footer from '../../layouts/footers/footer.vue';
|
||||
@@ -44,7 +44,7 @@ import FullCalendar from '@fullcalendar/vue3';
|
||||
import dayGridPlugin from '@fullcalendar/daygrid';
|
||||
import interactionPlugin from '@fullcalendar/interaction';
|
||||
import { CalendarOptions, EventClickArg } from '@fullcalendar/core';
|
||||
import ServiceEditModal from './ServiceEditModal.vue';
|
||||
import ServiceEditModal from './ServiceEditModal.vue';
|
||||
import axios from 'axios';
|
||||
import authHeader from '../../services/auth.header';
|
||||
|
||||
@@ -60,113 +60,111 @@ interface ServiceCall {
|
||||
service_cost: string;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ServiceCalendar',
|
||||
components: { Header, SideBar, Footer, FullCalendar, ServiceEditModal },
|
||||
data() {
|
||||
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,
|
||||
};
|
||||
// Reactive data
|
||||
const user = ref(null)
|
||||
const selectedServiceForEdit = ref(null as Partial<ServiceCall> | null)
|
||||
const fullCalendar = ref()
|
||||
|
||||
// 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
|
||||
selectedServiceForEdit.value = {
|
||||
id: parseInt(clickInfo.event.id),
|
||||
scheduled_date: clickInfo.event.startStr,
|
||||
customer_name: clickInfo.event.title.split(': ')[1] || 'Unknown Customer',
|
||||
customer_id: clickInfo.event.extendedProps.customer_id,
|
||||
type_service_call: clickInfo.event.extendedProps.type_service_call,
|
||||
description: clickInfo.event.extendedProps.description,
|
||||
service_cost: clickInfo.event.extendedProps.service_cost,
|
||||
};
|
||||
}
|
||||
|
||||
// Calendar options
|
||||
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;
|
||||
},
|
||||
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> { ... }
|
||||
eventSourceFailure: (error) => {
|
||||
console.error("Failed to fetch calendar events:", error);
|
||||
}
|
||||
} as CalendarOptions)
|
||||
|
||||
handleEventClick(clickInfo: EventClickArg): void {
|
||||
// This logic remains the same, as it correctly pulls data from extendedProps
|
||||
this.selectedServiceForEdit = {
|
||||
id: parseInt(clickInfo.event.id),
|
||||
scheduled_date: clickInfo.event.startStr,
|
||||
customer_name: clickInfo.event.title.split(': ')[1] || 'Unknown Customer',
|
||||
customer_id: clickInfo.event.extendedProps.customer_id,
|
||||
type_service_call: clickInfo.event.extendedProps.type_service_call,
|
||||
description: clickInfo.event.extendedProps.description,
|
||||
service_cost: clickInfo.event.extendedProps.service_cost,
|
||||
};
|
||||
},
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus();
|
||||
// We no longer need to call fetchEvents() here because FullCalendar does it automatically.
|
||||
})
|
||||
|
||||
closeEditModal() {
|
||||
this.selectedServiceForEdit = null;
|
||||
},
|
||||
const closeEditModal = () => {
|
||||
selectedServiceForEdit.value = null;
|
||||
}
|
||||
|
||||
// =================== THIS IS THE CORRECTED SECTION ===================
|
||||
async handleSaveChanges(updatedService: ServiceCall) {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
|
||||
await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
|
||||
// =================== THIS IS THE CORRECTED SECTION ===================
|
||||
const handleSaveChanges = async (updatedService: ServiceCall) => {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
|
||||
await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
|
||||
|
||||
// Get the FullCalendar component instance from the ref
|
||||
const calendarApi = (this.$refs.fullCalendar as any).getApi();
|
||||
if (calendarApi) {
|
||||
// Tell FullCalendar to re-fetch its events from the source.
|
||||
// This is the most reliable way to refresh the view immediately.
|
||||
calendarApi.refetchEvents();
|
||||
}
|
||||
|
||||
this.closeEditModal();
|
||||
} catch (error) {
|
||||
console.error("Failed to save changes:", error);
|
||||
alert("An error occurred while saving. Please check the console.");
|
||||
// Get the FullCalendar component instance from the ref
|
||||
const calendarApi = (fullCalendar.value as any).getApi();
|
||||
if (calendarApi) {
|
||||
// Tell FullCalendar to re-fetch its events from the source.
|
||||
// This is the most reliable way to refresh the view immediately.
|
||||
calendarApi.refetchEvents();
|
||||
}
|
||||
|
||||
closeEditModal();
|
||||
} catch (error) {
|
||||
console.error("Failed to save changes:", error);
|
||||
alert("An error occurred while saving. Please check the console.");
|
||||
}
|
||||
}
|
||||
// =================== END OF CORRECTED SECTION ===================
|
||||
|
||||
const handleDeleteService = async (serviceId: number) => {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
|
||||
await axios.delete(path, { withCredentials: true, headers: authHeader() });
|
||||
|
||||
// Also refresh the calendar after a delete
|
||||
const calendarApi = (fullCalendar.value as any).getApi();
|
||||
if (calendarApi) {
|
||||
calendarApi.refetchEvents();
|
||||
}
|
||||
|
||||
closeEditModal();
|
||||
} catch (error) {
|
||||
console.error("Error deleting event:", error);
|
||||
}
|
||||
}
|
||||
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
}
|
||||
},
|
||||
// =================== END OF CORRECTED SECTION ===================
|
||||
|
||||
async handleDeleteService(serviceId: number) {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
|
||||
await axios.delete(path, { withCredentials: true, headers: authHeader() });
|
||||
|
||||
// Also refresh the calendar after a delete
|
||||
const calendarApi = (this.$refs.fullCalendar as any).getApi();
|
||||
if (calendarApi) {
|
||||
calendarApi.refetchEvents();
|
||||
}
|
||||
|
||||
this.closeEditModal();
|
||||
} catch (error) {
|
||||
console.error("Error deleting event:", error);
|
||||
}
|
||||
},
|
||||
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
user.value = null
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -99,8 +99,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import axios from 'axios';
|
||||
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 ServiceParts { customer_id: number; oil_filter: string; oil_filter_2: string; oil_nozzle: string; oil_nozzle_2: string; }
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ServiceEditModal',
|
||||
props: { service: { type: Object as PropType<Partial<ServiceCall>>, required: true } },
|
||||
data() {
|
||||
return {
|
||||
editableService: {} as Partial<EditableService>,
|
||||
customer: null as Customer | null,
|
||||
serviceParts: null as ServiceParts | null,
|
||||
isLoadingParts: true,
|
||||
serviceOptions: [
|
||||
{ text: 'Tune-up', value: 0 }, { text: 'No Heat', value: 1 }, { text: 'Fix', value: 2 },
|
||||
{ text: 'Tank Install', value: 3 }, { text: 'Other', value: 4 },
|
||||
],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
service: {
|
||||
handler(newVal) {
|
||||
if (!newVal) return;
|
||||
const scheduled = dayjs(newVal.scheduled_date || new Date());
|
||||
this.editableService = { ...newVal, date: scheduled.format('YYYY-MM-DD'), time: scheduled.hour() };
|
||||
if (newVal.customer_id) {
|
||||
this.getCustomer(newVal.customer_id);
|
||||
this.getServiceParts(newVal.customer_id);
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getCustomer(customerId: number) {
|
||||
this.customer = null;
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/' + customerId;
|
||||
axios.get(path, { headers: authHeader() })
|
||||
.then((response: any) => { this.customer = response.data; })
|
||||
.catch((error: any) => { console.error("Failed to fetch customer details for modal:", error); });
|
||||
},
|
||||
getServiceParts(customerId: number) {
|
||||
this.isLoadingParts = true;
|
||||
this.serviceParts = null;
|
||||
let path = `${import.meta.env.VITE_BASE_URL}/service/parts/customer/${customerId}`;
|
||||
axios.get(path, { headers: authHeader() })
|
||||
.then((response: any) => { this.serviceParts = response.data; })
|
||||
.catch((error: any) => { console.error("Failed to fetch service parts:", error); })
|
||||
.finally(() => { this.isLoadingParts = false; });
|
||||
},
|
||||
async saveChanges() {
|
||||
const date = this.editableService.date;
|
||||
const time = this.editableService.time || 0;
|
||||
const combinedDateTime = dayjs(`${date} ${time}:00`).format('YYYY-MM-DDTHH:mm:ss');
|
||||
const finalPayload = { ...this.service, ...this.editableService, scheduled_date: combinedDateTime };
|
||||
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${finalPayload.id}`;
|
||||
try {
|
||||
await axios.put(path, finalPayload, { headers: authHeader(), withCredentials: true });
|
||||
this.$emit('save-changes', finalPayload);
|
||||
} catch (error) {
|
||||
console.error("Failed to save changes:", error);
|
||||
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?`)) {
|
||||
this.$emit('delete-service', this.service.id);
|
||||
}
|
||||
},
|
||||
getServiceTypeName(typeId: number | undefined | null): string {
|
||||
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' };
|
||||
return typeMap[typeId] || 'Unknown';
|
||||
},
|
||||
getServiceTypeColor(typeId: number | undefined | null): string {
|
||||
if (typeId === undefined || typeId === null) return 'gray';
|
||||
const colorMap: { [key: number]: string } = { 0: 'blue', 1: 'red', 2: 'green', 3: '#B58900', 4: 'black' };
|
||||
return colorMap[typeId] || 'gray';
|
||||
},
|
||||
getStateAbbrev(stateId: number | undefined | null): string {
|
||||
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' };
|
||||
return stateMap[stateId] || 'Unknown';
|
||||
}
|
||||
},
|
||||
});
|
||||
// Props and Emits
|
||||
const props = defineProps<{
|
||||
service: Partial<ServiceCall>
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'close-modal': []
|
||||
'save-changes': [service: ServiceCall]
|
||||
'delete-service': [serviceId: number]
|
||||
}>()
|
||||
|
||||
// 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: 'Tank Install', value: 3 }, { text: 'Other', value: 4 },
|
||||
])
|
||||
|
||||
// Watchers
|
||||
watch(() => props.service, (newVal) => {
|
||||
if (!newVal) return;
|
||||
const scheduled = dayjs(newVal.scheduled_date || new Date());
|
||||
editableService.value = { ...newVal, date: scheduled.format('YYYY-MM-DD'), time: scheduled.hour() };
|
||||
if (newVal.customer_id) {
|
||||
getCustomer(newVal.customer_id);
|
||||
getServiceParts(newVal.customer_id);
|
||||
}
|
||||
}, { immediate: true, deep: true })
|
||||
|
||||
// Functions
|
||||
const getCustomer = (customerId: number) => {
|
||||
customer.value = null;
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/' + customerId;
|
||||
axios.get(path, { headers: authHeader() })
|
||||
.then((response: any) => { customer.value = response.data; })
|
||||
.catch((error: any) => { console.error("Failed to fetch customer details for modal:", error); });
|
||||
}
|
||||
|
||||
const getServiceParts = (customerId: number) => {
|
||||
isLoadingParts.value = true;
|
||||
serviceParts.value = null;
|
||||
let path = `${import.meta.env.VITE_BASE_URL}/service/parts/customer/${customerId}`;
|
||||
axios.get(path, { headers: authHeader() })
|
||||
.then((response: any) => { serviceParts.value = response.data; })
|
||||
.catch((error: any) => { console.error("Failed to fetch service parts:", error); })
|
||||
.finally(() => { isLoadingParts.value = false; });
|
||||
}
|
||||
|
||||
const saveChanges = async () => {
|
||||
const date = editableService.value.date;
|
||||
const time = editableService.value.time || 0;
|
||||
const combinedDateTime = dayjs(`${date} ${time}:00`).format('YYYY-MM-DDTHH:mm:ss');
|
||||
const finalPayload = { ...props.service, ...editableService.value, scheduled_date: combinedDateTime };
|
||||
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${finalPayload.id}`;
|
||||
try {
|
||||
await axios.put(path, finalPayload, { headers: authHeader(), withCredentials: true });
|
||||
emit('save-changes', finalPayload as ServiceCall);
|
||||
} catch (error) {
|
||||
console.error("Failed to save changes:", error);
|
||||
alert("An error occurred while saving. Please check the console.");
|
||||
}
|
||||
}
|
||||
|
||||
const confirmDelete = () => {
|
||||
if (props.service.id && window.confirm(`Are you sure you want to delete this service call?`)) {
|
||||
emit('delete-service', props.service.id);
|
||||
}
|
||||
}
|
||||
|
||||
const getServiceTypeName = (typeId: number | undefined | null): string => {
|
||||
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' };
|
||||
return typeMap[typeId] || 'Unknown';
|
||||
}
|
||||
|
||||
const getServiceTypeColor = (typeId: number | undefined | null): string => {
|
||||
if (typeId === undefined || typeId === null) return 'gray';
|
||||
const colorMap: { [key: number]: string } = { 0: 'blue', 1: 'red', 2: 'green', 3: '#B58900', 4: 'black' };
|
||||
return colorMap[typeId] || 'gray';
|
||||
}
|
||||
|
||||
const getStateAbbrev = (stateId: number | undefined | null): string => {
|
||||
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' };
|
||||
return stateMap[stateId] || 'Unknown';
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -156,187 +156,176 @@
|
||||
@delete-service="handleDeleteService"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../services/auth.header'
|
||||
import { ServiceCall } from '../../types/models'
|
||||
import Footer from '../../layouts/footers/footer.vue'
|
||||
import ServiceEditModal from './ServiceEditModal.vue'
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
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;
|
||||
payment_status?: number;
|
||||
// Reactive data
|
||||
const user = ref(null)
|
||||
const services = ref<ServiceCall[]>([])
|
||||
const isLoading = ref(true)
|
||||
const selectedServiceForEdit = ref<ServiceCall | null>(null)
|
||||
// --- ADDITIONS FOR TRUNCATION ---
|
||||
const wordLimit = ref(50)
|
||||
const expandedIds = ref<number[]>([])
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus();
|
||||
fetchUpcomingServices();
|
||||
})
|
||||
|
||||
// Functions
|
||||
const fetchUpcomingServices = async (): Promise<void> => {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const path = import.meta.env.VITE_BASE_URL + '/service/upcoming';
|
||||
const response = await axios.get(path, {
|
||||
headers: authHeader(),
|
||||
withCredentials: true,
|
||||
});
|
||||
services.value = response.data.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch upcoming service calls:", error);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ServiceHome',
|
||||
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[],
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
user.value = null
|
||||
})
|
||||
}
|
||||
|
||||
const openEditModal = (service: ServiceCall) => {
|
||||
selectedServiceForEdit.value = service;
|
||||
}
|
||||
|
||||
const closeEditModal = () => {
|
||||
selectedServiceForEdit.value = null;
|
||||
}
|
||||
|
||||
const isLongDescription = (text: string): boolean => {
|
||||
if (!text) return false;
|
||||
return text.split(/\s+/).length > wordLimit.value;
|
||||
}
|
||||
|
||||
const truncateDescription = (text: string): string => {
|
||||
if (!isLongDescription(text)) return text;
|
||||
const words = text.split(/\s+/);
|
||||
return words.slice(0, wordLimit.value).join(' ') + '...';
|
||||
}
|
||||
|
||||
const isExpanded = (id: number): boolean => {
|
||||
return expandedIds.value.includes(id);
|
||||
}
|
||||
|
||||
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 handleSaveChanges = async (updatedService: ServiceCall) => {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
|
||||
const response = await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
|
||||
if (response.data.ok) {
|
||||
const index = services.value.findIndex(s => s.id === updatedService.id);
|
||||
if (index !== -1) {
|
||||
services.value[index] = response.data.service;
|
||||
}
|
||||
closeEditModal();
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.userStatus();
|
||||
this.fetchUpcomingServices();
|
||||
},
|
||||
methods: {
|
||||
async fetchUpcomingServices(): Promise<void> {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
const path = import.meta.env.VITE_BASE_URL + '/service/upcoming';
|
||||
const response = await axios.get(path, {
|
||||
headers: authHeader(),
|
||||
withCredentials: true,
|
||||
});
|
||||
this.services = response.data.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch upcoming service calls:", error);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
},
|
||||
} catch (error) {
|
||||
console.error("Failed to save changes:", error);
|
||||
alert("An error occurred while saving. Please check the console.");
|
||||
}
|
||||
}
|
||||
|
||||
openEditModal(service: ServiceCall) {
|
||||
this.selectedServiceForEdit = service;
|
||||
},
|
||||
|
||||
closeEditModal() {
|
||||
this.selectedServiceForEdit = null;
|
||||
},
|
||||
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+/);
|
||||
return words.slice(0, this.wordLimit).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);
|
||||
}
|
||||
},
|
||||
async handleSaveChanges(updatedService: ServiceCall) {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
|
||||
const response = await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
|
||||
if (response.data.ok) {
|
||||
const index = this.services.findIndex(s => s.id === updatedService.id);
|
||||
if (index !== -1) {
|
||||
this.services[index] = response.data.service;
|
||||
}
|
||||
this.closeEditModal();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save changes:", error);
|
||||
alert("An error occurred while saving. Please check the console.");
|
||||
}
|
||||
},
|
||||
|
||||
async handleDeleteService(serviceId: number) {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
|
||||
const response = await axios.delete(path, { headers: authHeader(), withCredentials: true });
|
||||
if (response.data.ok) {
|
||||
this.services = this.services.filter(s => s.id !== serviceId);
|
||||
this.closeEditModal();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete service call:", error);
|
||||
alert("An error occurred while deleting. Please check the console.");
|
||||
}
|
||||
},
|
||||
|
||||
formatDate(dateString: string): string {
|
||||
if (!dateString) return 'N/A';
|
||||
return dayjs(dateString).format('MMMM D, YYYY');
|
||||
},
|
||||
|
||||
formatTime(dateString: string): string {
|
||||
if (!dateString) return 'N/A';
|
||||
return dayjs(dateString).format('h:mm A');
|
||||
},
|
||||
|
||||
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 Service';
|
||||
},
|
||||
// --- ADD THIS METHOD ---
|
||||
formatCurrency(value: string | number): string {
|
||||
if (value === null || value === undefined || value === '') return '$0.00';
|
||||
const numberValue = Number(value);
|
||||
if (isNaN(numberValue)) return '$0.00';
|
||||
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
}).format(numberValue);
|
||||
},
|
||||
getServiceTypeColor(typeId: number): string {
|
||||
const colorMap: { [key: number]: string } = {
|
||||
0: 'blue',
|
||||
1: 'red',
|
||||
2: 'green',
|
||||
3: '#B58900',
|
||||
4: 'black',
|
||||
};
|
||||
return colorMap[typeId] || 'gray';
|
||||
},
|
||||
|
||||
shouldShowChargeButton(service: any): boolean {
|
||||
return service.payment_status === null || service.payment_status === undefined;
|
||||
},
|
||||
|
||||
shouldShowCaptureButton(service: any): boolean {
|
||||
return service.payment_status === 1;
|
||||
const handleDeleteService = async (serviceId: number) => {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
|
||||
const response = await axios.delete(path, { headers: authHeader(), withCredentials: true });
|
||||
if (response.data.ok) {
|
||||
services.value = services.value.filter(s => s.id !== serviceId);
|
||||
closeEditModal();
|
||||
}
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Failed to delete service call:", error);
|
||||
alert("An error occurred while deleting. Please check the console.");
|
||||
}
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string): string => {
|
||||
if (!dateString) return 'N/A';
|
||||
return dayjs(dateString).format('MMMM D, YYYY');
|
||||
}
|
||||
|
||||
const formatTime = (dateString: string): string => {
|
||||
if (!dateString) return 'N/A';
|
||||
return dayjs(dateString).format('h:mm A');
|
||||
}
|
||||
|
||||
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 Service';
|
||||
}
|
||||
|
||||
const formatCurrency = (value: string | number): string => {
|
||||
if (value === null || value === undefined || value === '') return '$0.00';
|
||||
const numberValue = Number(value);
|
||||
if (isNaN(numberValue)) return '$0.00';
|
||||
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
}).format(numberValue);
|
||||
}
|
||||
|
||||
const getServiceTypeColor = (typeId: number): string => {
|
||||
const colorMap: { [key: number]: string } = {
|
||||
0: 'blue',
|
||||
1: 'red',
|
||||
2: 'green',
|
||||
3: '#B58900',
|
||||
4: 'black',
|
||||
};
|
||||
return colorMap[typeId] || 'gray';
|
||||
}
|
||||
|
||||
const shouldShowChargeButton = (service: any): boolean => {
|
||||
return service.payment_status === null || service.payment_status === undefined;
|
||||
}
|
||||
|
||||
const shouldShowCaptureButton = (service: any): boolean => {
|
||||
return service.payment_status === 1;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -163,193 +163,176 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../services/auth.header'
|
||||
import { ServiceCall } from '../../types/models'
|
||||
import Footer from '../../layouts/footers/footer.vue'
|
||||
import ServiceEditModal from './ServiceEditModal.vue'
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
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;
|
||||
payment_status?: number;
|
||||
// Reactive data
|
||||
const user = ref(null)
|
||||
const services = ref<ServiceCall[]>([])
|
||||
const isLoading = ref(true)
|
||||
const selectedServiceForEdit = ref<ServiceCall | null>(null)
|
||||
// --- ADDITIONS FOR TRUNCATION ---
|
||||
const wordLimit = ref(50)
|
||||
const expandedIds = ref<number[]>([])
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus();
|
||||
fetchPastServices();
|
||||
})
|
||||
|
||||
// Functions
|
||||
const isLongDescription = (text: string): boolean => {
|
||||
if (!text) return false;
|
||||
return text.split(/\s+/).length > wordLimit.value;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ServiceHPast',
|
||||
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[],
|
||||
const truncateDescription = (text: string): string => {
|
||||
if (!isLongDescription(text)) return text;
|
||||
const words = text.split(/\s+/);
|
||||
return words.slice(0, wordLimit.value).join(' ') + '...';
|
||||
}
|
||||
|
||||
const isExpanded = (id: number): boolean => {
|
||||
return expandedIds.value.includes(id);
|
||||
}
|
||||
|
||||
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 {
|
||||
const path = import.meta.env.VITE_BASE_URL + '/service/past';
|
||||
const response = await axios.get(path, {
|
||||
headers: authHeader(),
|
||||
withCredentials: true,
|
||||
});
|
||||
services.value = response.data.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch past service calls:", error);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
user.value = null
|
||||
})
|
||||
}
|
||||
|
||||
const openEditModal = (service: ServiceCall) => {
|
||||
selectedServiceForEdit.value = service;
|
||||
}
|
||||
|
||||
const closeEditModal = () => {
|
||||
selectedServiceForEdit.value = null;
|
||||
}
|
||||
|
||||
const handleSaveChanges = async (updatedService: ServiceCall) => {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
|
||||
const response = await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
|
||||
if (response.data.ok) {
|
||||
const index = services.value.findIndex(s => s.id === updatedService.id);
|
||||
if (index !== -1) {
|
||||
services.value[index] = response.data.service;
|
||||
}
|
||||
closeEditModal();
|
||||
}
|
||||
},
|
||||
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+/);
|
||||
return words.slice(0, this.wordLimit).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);
|
||||
}
|
||||
},
|
||||
} catch (error) {
|
||||
console.error("Failed to save changes:", error);
|
||||
alert("An error occurred while saving. Please check the console.");
|
||||
}
|
||||
}
|
||||
|
||||
// --- API and Data Handling Methods ---
|
||||
async fetchPastServices(): Promise<void> {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
const path = import.meta.env.VITE_BASE_URL + '/service/past';
|
||||
const response = await axios.get(path, {
|
||||
headers: authHeader(),
|
||||
withCredentials: true,
|
||||
});
|
||||
this.services = response.data.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch past service calls:", error);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
},
|
||||
|
||||
openEditModal(service: ServiceCall) {
|
||||
this.selectedServiceForEdit = service;
|
||||
},
|
||||
|
||||
closeEditModal() {
|
||||
this.selectedServiceForEdit = null;
|
||||
},
|
||||
|
||||
async handleSaveChanges(updatedService: ServiceCall) {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
|
||||
const response = await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
|
||||
if (response.data.ok) {
|
||||
const index = this.services.findIndex(s => s.id === updatedService.id);
|
||||
if (index !== -1) {
|
||||
this.services[index] = response.data.service;
|
||||
}
|
||||
this.closeEditModal();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save changes:", error);
|
||||
alert("An error occurred while saving. Please check the console.");
|
||||
}
|
||||
},
|
||||
|
||||
async handleDeleteService(serviceId: number) {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
|
||||
const response = await axios.delete(path, { headers: authHeader(), withCredentials: true });
|
||||
if (response.data.ok) {
|
||||
this.services = this.services.filter(s => s.id !== serviceId);
|
||||
this.closeEditModal();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete service call:", error);
|
||||
alert("An error occurred while deleting. Please check the console.");
|
||||
}
|
||||
},
|
||||
|
||||
// --- Formatting and Display Methods ---
|
||||
formatCurrency(value: string | number): string {
|
||||
if (value === null || value === undefined || value === '') return '$0.00';
|
||||
const numberValue = Number(value);
|
||||
if (isNaN(numberValue)) return '$0.00';
|
||||
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
}).format(numberValue);
|
||||
},
|
||||
|
||||
formatDate(dateString: string): string {
|
||||
if (!dateString) return 'N/A';
|
||||
return dayjs(dateString).format('MMMM D, YYYY');
|
||||
},
|
||||
|
||||
formatTime(dateString: string): string {
|
||||
if (!dateString) return 'N/A';
|
||||
return dayjs(dateString).format('h:mm A');
|
||||
},
|
||||
|
||||
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 Service';
|
||||
},
|
||||
|
||||
getServiceTypeColor(typeId: number): string {
|
||||
const colorMap: { [key: number]: string } = {
|
||||
0: 'blue',
|
||||
1: 'red',
|
||||
2: 'green',
|
||||
3: '#B58900',
|
||||
4: 'black',
|
||||
};
|
||||
return colorMap[typeId] || 'gray';
|
||||
},
|
||||
|
||||
shouldShowChargeButton(service: any): boolean {
|
||||
return service.payment_status === null || service.payment_status === undefined;
|
||||
},
|
||||
|
||||
shouldShowCaptureButton(service: any): boolean {
|
||||
return service.payment_status === 1;
|
||||
const handleDeleteService = async (serviceId: number) => {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
|
||||
const response = await axios.delete(path, { headers: authHeader(), withCredentials: true });
|
||||
if (response.data.ok) {
|
||||
services.value = services.value.filter(s => s.id !== serviceId);
|
||||
closeEditModal();
|
||||
}
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Failed to delete service call:", error);
|
||||
alert("An error occurred while deleting. Please check the console.");
|
||||
}
|
||||
}
|
||||
|
||||
const formatCurrency = (value: string | number): string => {
|
||||
if (value === null || value === undefined || value === '') return '$0.00';
|
||||
const numberValue = Number(value);
|
||||
if (isNaN(numberValue)) return '$0.00';
|
||||
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
}).format(numberValue);
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string): string => {
|
||||
if (!dateString) return 'N/A';
|
||||
return dayjs(dateString).format('MMMM D, YYYY');
|
||||
}
|
||||
|
||||
const formatTime = (dateString: string): string => {
|
||||
if (!dateString) return 'N/A';
|
||||
return dayjs(dateString).format('h:mm A');
|
||||
}
|
||||
|
||||
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 Service';
|
||||
}
|
||||
|
||||
const getServiceTypeColor = (typeId: number): string => {
|
||||
const colorMap: { [key: number]: string } = {
|
||||
0: 'blue',
|
||||
1: 'red',
|
||||
2: 'green',
|
||||
3: '#B58900',
|
||||
4: 'black',
|
||||
};
|
||||
return colorMap[typeId] || 'gray';
|
||||
}
|
||||
|
||||
const shouldShowChargeButton = (service: any): boolean => {
|
||||
return service.payment_status === null || service.payment_status === undefined;
|
||||
}
|
||||
|
||||
const shouldShowCaptureButton = (service: any): boolean => {
|
||||
return service.payment_status === 1;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -123,124 +123,109 @@
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../services/auth.header'
|
||||
import { ServicePlan } from '../../types/models'
|
||||
import Footer from '../../layouts/footers/footer.vue'
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
interface ServicePlan {
|
||||
id: number;
|
||||
customer_id: number;
|
||||
customer_name: string;
|
||||
customer_address: string;
|
||||
customer_town: string;
|
||||
contract_plan: number;
|
||||
contract_years: number;
|
||||
contract_start_date: string;
|
||||
// Reactive data
|
||||
const user = ref(null)
|
||||
const servicePlans = ref<ServicePlan[]>([])
|
||||
const isLoading = ref(true)
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus();
|
||||
fetchServicePlans();
|
||||
})
|
||||
|
||||
// Functions
|
||||
const fetchServicePlans = async (): Promise<void> => {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const path = import.meta.env.VITE_BASE_URL + '/service/plans/active';
|
||||
const response = await axios.get(path, {
|
||||
headers: authHeader(),
|
||||
withCredentials: true,
|
||||
});
|
||||
servicePlans.value = response.data;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch service plans:", error);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ServicePlans',
|
||||
components: { Footer },
|
||||
data() {
|
||||
return {
|
||||
user: null,
|
||||
servicePlans: [] as ServicePlan[],
|
||||
isLoading: true,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.userStatus();
|
||||
this.fetchServicePlans();
|
||||
},
|
||||
methods: {
|
||||
async fetchServicePlans(): Promise<void> {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
const path = import.meta.env.VITE_BASE_URL + '/service/plans/active';
|
||||
const response = await axios.get(path, {
|
||||
headers: authHeader(),
|
||||
withCredentials: true,
|
||||
});
|
||||
this.servicePlans = response.data;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch service plans:", error);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
}
|
||||
},
|
||||
})
|
||||
.catch(() => {
|
||||
user.value = null
|
||||
})
|
||||
}
|
||||
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
},
|
||||
const getPlanName = (planType: number): string => {
|
||||
const planNames: { [key: number]: string } = {
|
||||
1: 'Standard',
|
||||
2: 'Premium'
|
||||
};
|
||||
return planNames[planType] || 'Unknown';
|
||||
}
|
||||
|
||||
getPlanName(planType: number): string {
|
||||
const planNames: { [key: number]: string } = {
|
||||
1: 'Standard',
|
||||
2: 'Premium'
|
||||
};
|
||||
return planNames[planType] || 'Unknown';
|
||||
},
|
||||
const getPlanColor = (planType: number): string => {
|
||||
const planColors: { [key: number]: string } = {
|
||||
1: 'blue',
|
||||
2: 'gold'
|
||||
};
|
||||
return planColors[planType] || 'gray';
|
||||
}
|
||||
|
||||
getPlanColor(planType: number): string {
|
||||
const planColors: { [key: number]: string } = {
|
||||
1: 'blue',
|
||||
2: 'gold'
|
||||
};
|
||||
return planColors[planType] || 'gray';
|
||||
},
|
||||
const formatDate = (dateString: string): string => {
|
||||
if (!dateString) return 'N/A';
|
||||
return dayjs(dateString).format('MMM D, YYYY');
|
||||
}
|
||||
|
||||
formatDate(dateString: string): string {
|
||||
if (!dateString) return 'N/A';
|
||||
return dayjs(dateString).format('MMM D, YYYY');
|
||||
},
|
||||
const formatEndDate = (startDate: string, years: number): string => {
|
||||
if (!startDate) return 'N/A';
|
||||
return dayjs(startDate).add(years, 'year').format('MMM D, YYYY');
|
||||
}
|
||||
|
||||
formatEndDate(startDate: string, years: number): string {
|
||||
if (!startDate) return 'N/A';
|
||||
return dayjs(startDate).add(years, 'year').format('MMM D, YYYY');
|
||||
},
|
||||
const getStatusText = (startDate: string, years: number): string => {
|
||||
if (!startDate) return 'Unknown';
|
||||
const endDate = dayjs(startDate).add(years, 'year');
|
||||
const now = dayjs();
|
||||
if (now.isAfter(endDate)) {
|
||||
return 'Expired';
|
||||
} else if (now.isAfter(endDate.subtract(30, 'day'))) {
|
||||
return 'Expiring Soon';
|
||||
} else {
|
||||
return 'Active';
|
||||
}
|
||||
}
|
||||
|
||||
getStatusText(startDate: string, years: number): string {
|
||||
if (!startDate) return 'Unknown';
|
||||
const endDate = dayjs(startDate).add(years, 'year');
|
||||
const now = dayjs();
|
||||
if (now.isAfter(endDate)) {
|
||||
return 'Expired';
|
||||
} else if (now.isAfter(endDate.subtract(30, 'day'))) {
|
||||
return 'Expiring Soon';
|
||||
} else {
|
||||
return 'Active';
|
||||
}
|
||||
},
|
||||
|
||||
getStatusBadge(startDate: string, years: number): string {
|
||||
if (!startDate) return 'badge-ghost';
|
||||
const endDate = dayjs(startDate).add(years, 'year');
|
||||
const now = dayjs();
|
||||
if (now.isAfter(endDate)) {
|
||||
return 'badge-error';
|
||||
} else if (now.isAfter(endDate.subtract(30, 'day'))) {
|
||||
return 'badge-warning';
|
||||
} else {
|
||||
return 'badge-success';
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
const getStatusBadge = (startDate: string, years: number): string => {
|
||||
if (!startDate) return 'badge-ghost';
|
||||
const endDate = dayjs(startDate).add(years, 'year');
|
||||
const now = dayjs();
|
||||
if (now.isAfter(endDate)) {
|
||||
return 'badge-error';
|
||||
} else if (now.isAfter(endDate.subtract(30, 'day'))) {
|
||||
return 'badge-warning';
|
||||
} else {
|
||||
return 'badge-success';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -163,193 +163,176 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../services/auth.header'
|
||||
import { ServiceCall } from '../../types/models'
|
||||
import Footer from '../../layouts/footers/footer.vue'
|
||||
import ServiceEditModal from './ServiceEditModal.vue'
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
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;
|
||||
payment_status?: number;
|
||||
// Reactive data
|
||||
const user = ref(null)
|
||||
const services = ref<ServiceCall[]>([])
|
||||
const isLoading = ref(true)
|
||||
const selectedServiceForEdit = ref<ServiceCall | null>(null)
|
||||
// --- ADDITIONS FOR TRUNCATION ---
|
||||
const wordLimit = ref(50)
|
||||
const expandedIds = ref<number[]>([])
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus();
|
||||
fetchTodayServices();
|
||||
})
|
||||
|
||||
// Functions
|
||||
const isLongDescription = (text: string): boolean => {
|
||||
if (!text) return false;
|
||||
return text.split(/\s+/).length > wordLimit.value;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ServiceToday',
|
||||
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[],
|
||||
const truncateDescription = (text: string): string => {
|
||||
if (!isLongDescription(text)) return text;
|
||||
const words = text.split(/\s+/);
|
||||
return words.slice(0, wordLimit.value).join(' ') + '...';
|
||||
}
|
||||
|
||||
const isExpanded = (id: number): boolean => {
|
||||
return expandedIds.value.includes(id);
|
||||
}
|
||||
|
||||
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 {
|
||||
const path = import.meta.env.VITE_BASE_URL + '/service/today';
|
||||
const response = await axios.get(path, {
|
||||
headers: authHeader(),
|
||||
withCredentials: true,
|
||||
});
|
||||
services.value = response.data.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch today's service calls:", error);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
user.value = null
|
||||
})
|
||||
}
|
||||
|
||||
const openEditModal = (service: ServiceCall) => {
|
||||
selectedServiceForEdit.value = service;
|
||||
}
|
||||
|
||||
const closeEditModal = () => {
|
||||
selectedServiceForEdit.value = null;
|
||||
}
|
||||
|
||||
const handleSaveChanges = async (updatedService: ServiceCall) => {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
|
||||
const response = await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
|
||||
if (response.data.ok) {
|
||||
const index = services.value.findIndex(s => s.id === updatedService.id);
|
||||
if (index !== -1) {
|
||||
services.value[index] = response.data.service;
|
||||
}
|
||||
closeEditModal();
|
||||
}
|
||||
},
|
||||
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+/);
|
||||
return words.slice(0, this.wordLimit).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);
|
||||
}
|
||||
},
|
||||
} catch (error) {
|
||||
console.error("Failed to save changes:", error);
|
||||
alert("An error occurred while saving. Please check the console.");
|
||||
}
|
||||
}
|
||||
|
||||
// --- API and Data Handling Methods ---
|
||||
async fetchTodayServices(): Promise<void> {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
const path = import.meta.env.VITE_BASE_URL + '/service/today';
|
||||
const response = await axios.get(path, {
|
||||
headers: authHeader(),
|
||||
withCredentials: true,
|
||||
});
|
||||
this.services = response.data.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch today's service calls:", error);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
},
|
||||
|
||||
openEditModal(service: ServiceCall) {
|
||||
this.selectedServiceForEdit = service;
|
||||
},
|
||||
|
||||
closeEditModal() {
|
||||
this.selectedServiceForEdit = null;
|
||||
},
|
||||
|
||||
async handleSaveChanges(updatedService: ServiceCall) {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
|
||||
const response = await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
|
||||
if (response.data.ok) {
|
||||
const index = this.services.findIndex(s => s.id === updatedService.id);
|
||||
if (index !== -1) {
|
||||
this.services[index] = response.data.service;
|
||||
}
|
||||
this.closeEditModal();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save changes:", error);
|
||||
alert("An error occurred while saving. Please check the console.");
|
||||
}
|
||||
},
|
||||
|
||||
async handleDeleteService(serviceId: number) {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
|
||||
const response = await axios.delete(path, { headers: authHeader(), withCredentials: true });
|
||||
if (response.data.ok) {
|
||||
this.services = this.services.filter(s => s.id !== serviceId);
|
||||
this.closeEditModal();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete service call:", error);
|
||||
alert("An error occurred while deleting. Please check the console.");
|
||||
}
|
||||
},
|
||||
|
||||
// --- Formatting and Display Methods ---
|
||||
formatCurrency(value: string | number): string {
|
||||
if (value === null || value === undefined || value === '') return '$0.00';
|
||||
const numberValue = Number(value);
|
||||
if (isNaN(numberValue)) return '$0.00';
|
||||
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
}).format(numberValue);
|
||||
},
|
||||
|
||||
formatDate(dateString: string): string {
|
||||
if (!dateString) return 'N/A';
|
||||
return dayjs(dateString).format('MMMM D, YYYY');
|
||||
},
|
||||
|
||||
formatTime(dateString: string): string {
|
||||
if (!dateString) return 'N/A';
|
||||
return dayjs(dateString).format('h:mm A');
|
||||
},
|
||||
|
||||
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 Service';
|
||||
},
|
||||
|
||||
getServiceTypeColor(typeId: number): string {
|
||||
const colorMap: { [key: number]: string } = {
|
||||
0: 'blue',
|
||||
1: 'red',
|
||||
2: 'green',
|
||||
3: '#B58900',
|
||||
4: 'black',
|
||||
};
|
||||
return colorMap[typeId] || 'gray';
|
||||
},
|
||||
|
||||
shouldShowChargeButton(service: any): boolean {
|
||||
return service.payment_status === null || service.payment_status === undefined;
|
||||
},
|
||||
|
||||
shouldShowCaptureButton(service: any): boolean {
|
||||
return service.payment_status === 1;
|
||||
const handleDeleteService = async (serviceId: number) => {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
|
||||
const response = await axios.delete(path, { headers: authHeader(), withCredentials: true });
|
||||
if (response.data.ok) {
|
||||
services.value = services.value.filter(s => s.id !== serviceId);
|
||||
closeEditModal();
|
||||
}
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Failed to delete service call:", error);
|
||||
alert("An error occurred while deleting. Please check the console.");
|
||||
}
|
||||
}
|
||||
|
||||
const formatCurrency = (value: string | number): string => {
|
||||
if (value === null || value === undefined || value === '') return '$0.00';
|
||||
const numberValue = Number(value);
|
||||
if (isNaN(numberValue)) return '$0.00';
|
||||
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
}).format(numberValue);
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string): string => {
|
||||
if (!dateString) return 'N/A';
|
||||
return dayjs(dateString).format('MMMM D, YYYY');
|
||||
}
|
||||
|
||||
const formatTime = (dateString: string): string => {
|
||||
if (!dateString) return 'N/A';
|
||||
return dayjs(dateString).format('h:mm A');
|
||||
}
|
||||
|
||||
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 Service';
|
||||
}
|
||||
|
||||
const getServiceTypeColor = (typeId: number): string => {
|
||||
const colorMap: { [key: number]: string } = {
|
||||
0: 'blue',
|
||||
1: 'red',
|
||||
2: 'green',
|
||||
3: '#B58900',
|
||||
4: 'black',
|
||||
};
|
||||
return colorMap[typeId] || 'gray';
|
||||
}
|
||||
|
||||
const shouldShowChargeButton = (service: any): boolean => {
|
||||
return service.payment_status === null || service.payment_status === undefined;
|
||||
}
|
||||
|
||||
const shouldShowCaptureButton = (service: any): boolean => {
|
||||
return service.payment_status === 1;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -55,156 +55,152 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import Header from '../../../layouts/headers/headerauth.vue';
|
||||
import FullCalendar from '@fullcalendar/vue3';
|
||||
import dayGridPlugin from '@fullcalendar/daygrid';
|
||||
import interactionPlugin from '@fullcalendar/interaction';
|
||||
import { CalendarOptions, EventClickArg } from '@fullcalendar/core';
|
||||
import EventSidebar from './EventSidebar.vue';
|
||||
import ServiceEditModal from '../../service/ServiceEditModal.vue';
|
||||
import ServiceEditModal from '../../service/ServiceEditModal.vue';
|
||||
import axios from 'axios';
|
||||
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 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({
|
||||
name: 'CalendarCustomer',
|
||||
components: { Header, FullCalendar, EventSidebar, ServiceEditModal },
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
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);
|
||||
// Route
|
||||
const route = useRoute();
|
||||
|
||||
if (originalEvent) {
|
||||
// We "flatten" the nested object from the calendar into the simple,
|
||||
// flat structure that the modal expects, ensuring customer_id is included.
|
||||
this.selectedServiceForEdit = {
|
||||
id: originalEvent.id,
|
||||
scheduled_date: originalEvent.start,
|
||||
customer_id: originalEvent.customer_id, // This was the missing piece
|
||||
customer_name: originalEvent.title.split(': ')[1] || 'Unknown Customer',
|
||||
type_service_call: originalEvent.extendedProps.type_service_call,
|
||||
description: originalEvent.extendedProps.description,
|
||||
service_cost: originalEvent.extendedProps.description,
|
||||
customer_address: '',
|
||||
customer_town: '',
|
||||
};
|
||||
}
|
||||
},
|
||||
// Functions declared first (needed for calendarOptions)
|
||||
const handleEventClick = (clickInfo: EventClickArg): void => {
|
||||
const events = (calendarOptions.value.events as any[]) || [];
|
||||
const originalEvent = events.find(e => e.id == clickInfo.event.id);
|
||||
|
||||
closeEditModal() {
|
||||
this.selectedServiceForEdit = null;
|
||||
},
|
||||
if (originalEvent) {
|
||||
// We "flatten" the nested object from the calendar into the simple,
|
||||
// flat structure that the modal expects, ensuring customer_id is included.
|
||||
selectedServiceForEdit.value = {
|
||||
id: originalEvent.id,
|
||||
scheduled_date: originalEvent.start,
|
||||
customer_id: originalEvent.customer_id, // This was the missing piece
|
||||
customer_name: originalEvent.title.split(': ')[1] || 'Unknown Customer',
|
||||
type_service_call: originalEvent.extendedProps.type_service_call,
|
||||
description: originalEvent.extendedProps.description,
|
||||
service_cost: originalEvent.extendedProps.description,
|
||||
customer_address: '',
|
||||
customer_town: '',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
async handleSaveChanges(updatedService: ServiceCall) {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
|
||||
await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
|
||||
await this.fetchEvents();
|
||||
this.closeEditModal();
|
||||
} catch (error) {
|
||||
console.error("Failed to save changes:", error);
|
||||
alert("An error occurred while saving. Please check the console.");
|
||||
}
|
||||
},
|
||||
|
||||
async handleDeleteService(serviceId: number) {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
|
||||
const response = await axios.delete(path, { withCredentials: true, headers: authHeader() });
|
||||
if (response.data.ok === true) {
|
||||
await this.fetchEvents();
|
||||
this.closeEditModal();
|
||||
} else {
|
||||
console.error("Failed to delete event:", response.data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error deleting event:", error);
|
||||
}
|
||||
},
|
||||
|
||||
async getCustomer(customerId: string): Promise<void> {
|
||||
this.isLoading = true;
|
||||
this.customer = null;
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/${customerId}`;
|
||||
const response = await axios.get(path, { withCredentials: true, headers: authHeader() });
|
||||
if (response.data && response.data.id) {
|
||||
this.customer = response.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("API call to get customer FAILED:", error);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async fetchEvents(): Promise<void> {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/all`;
|
||||
const response = await axios.get(path, { headers: authHeader(), withCredentials: true });
|
||||
this.calendarOptions.events = response.data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching all calendar events:", error);
|
||||
}
|
||||
},
|
||||
|
||||
async handleEventScheduled(eventData: any): Promise<void> {
|
||||
if (!this.customer) {
|
||||
alert("Error: A customer must be loaded in the sidebar to create a new event.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const payload = {
|
||||
expected_delivery_date: eventData.start, type_service_call: eventData.type_service_call,
|
||||
customer_id: this.customer.id, description: eventData.extendedProps.description,
|
||||
};
|
||||
const path = import.meta.env.VITE_BASE_URL + "/service/create";
|
||||
const response = await axios.post(path, payload, { withCredentials: true, headers: authHeader() });
|
||||
if (response.data.ok === true) {
|
||||
await this.fetchEvents();
|
||||
} else {
|
||||
console.error("Failed to create event:", response.data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error creating event:", error);
|
||||
}
|
||||
},
|
||||
|
||||
async handleEventDelete(eventId: string): Promise<void> {
|
||||
// This is a simple alias now, as handleDeleteService is more specific
|
||||
await this.handleDeleteService(Number(eventId));
|
||||
},
|
||||
},
|
||||
// Reactive data
|
||||
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);
|
||||
|
||||
// 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 {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
|
||||
await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
|
||||
await fetchEvents();
|
||||
closeEditModal();
|
||||
} catch (error) {
|
||||
console.error("Failed to save changes:", error);
|
||||
alert("An error occurred while saving. Please check the console.");
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteService = async (serviceId: number) => {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
|
||||
const response = await axios.delete(path, { withCredentials: true, headers: authHeader() });
|
||||
if (response.data.ok === true) {
|
||||
await fetchEvents();
|
||||
closeEditModal();
|
||||
} else {
|
||||
console.error("Failed to delete event:", response.data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error deleting event:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const getCustomer = async (customerId: string): Promise<void> => {
|
||||
isLoading.value = true;
|
||||
customer.value = null;
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/${customerId}`;
|
||||
const response = await axios.get(path, { withCredentials: true, headers: authHeader() });
|
||||
if (response.data && response.data.id) {
|
||||
customer.value = response.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("API call to get customer FAILED:", error);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const fetchEvents = async (): Promise<void> => {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/all`;
|
||||
const response = await axios.get(path, { headers: authHeader(), withCredentials: true });
|
||||
calendarOptions.value.events = response.data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching all calendar events:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEventScheduled = async (eventData: any): Promise<void> => {
|
||||
if (!customer.value) {
|
||||
alert("Error: A customer must be loaded in the sidebar to create a new event.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const payload = {
|
||||
expected_delivery_date: eventData.start, type_service_call: eventData.type_service_call,
|
||||
customer_id: customer.value.id, description: eventData.extendedProps.description,
|
||||
};
|
||||
const path = import.meta.env.VITE_BASE_URL + "/service/create";
|
||||
const response = await axios.post(path, payload, { withCredentials: true, headers: authHeader() });
|
||||
if (response.data.ok === true) {
|
||||
await fetchEvents();
|
||||
} else {
|
||||
console.error("Failed to create event:", response.data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error creating event:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEventDelete = async (eventId: string): Promise<void> => {
|
||||
// This is a simple alias now, as handleDeleteService is more specific
|
||||
await handleDeleteService(Number(eventId));
|
||||
};
|
||||
</script>
|
||||
@@ -77,8 +77,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
interface Customer {
|
||||
@@ -94,80 +94,79 @@ interface Customer {
|
||||
customer_apt: string;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EventSidebar',
|
||||
props: {
|
||||
customer: {
|
||||
type: Object as PropType<Customer | null>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedService: '' as string | number,
|
||||
serviceOptions: [
|
||||
{ text: 'Tune-up', value: 0 },
|
||||
{ text: 'No Heat', value: 1 },
|
||||
{ text: 'Fix', value: 2 },
|
||||
{ text: 'Tank Install', value: 3 },
|
||||
{ text: 'Other', value: 4 },
|
||||
],
|
||||
event: {
|
||||
title: '',
|
||||
description: '',
|
||||
date: dayjs().format('YYYY-MM-DD'),
|
||||
endDate: '',
|
||||
time: 12,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
customerStateName(): string {
|
||||
if (!this.customer) return '';
|
||||
const stateMap: { [key: number]: string } = {
|
||||
0: 'Massachusetts', 1: 'Rhode Island', 2: 'New Hampshire',
|
||||
3: 'Maine', 4: 'Vermont', 5: 'Connecticut', 6: 'New York',
|
||||
};
|
||||
return stateMap[this.customer.customer_state] || 'Unknown';
|
||||
},
|
||||
customerHomeType(): string {
|
||||
if (!this.customer) return '';
|
||||
const homeTypeMap: { [key: number]: string } = {
|
||||
0: 'Residential', 1: 'Apartment', 2: 'Condo', 3: 'Commercial',
|
||||
4: 'Business', 5: 'Construction', 6: 'Container',
|
||||
};
|
||||
return homeTypeMap[this.customer.customer_home_type] || 'Unknown';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submitEvent() {
|
||||
if (!this.customer) {
|
||||
alert("Cannot submit: No customer data is loaded.");
|
||||
return;
|
||||
}
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
customer: Customer | null;
|
||||
}>();
|
||||
|
||||
const startDateTime = dayjs(`${this.event.date} ${this.event.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;
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'event-scheduled': [eventData: any];
|
||||
}>();
|
||||
|
||||
const eventPayload = {
|
||||
title: this.event.title,
|
||||
start: startDateTime,
|
||||
type_service_call: this.selectedService,
|
||||
end: endDateTime,
|
||||
extendedProps: {
|
||||
description: this.event.description,
|
||||
},
|
||||
};
|
||||
|
||||
this.$emit('event-scheduled', eventPayload);
|
||||
|
||||
this.event.title = '';
|
||||
this.selectedService = '';
|
||||
this.event.description = '';
|
||||
this.event.endDate = '';
|
||||
this.event.date = dayjs().format('YYYY-MM-DD');
|
||||
this.event.time = 12;
|
||||
},
|
||||
},
|
||||
// Reactive data
|
||||
const selectedService = ref<string | number>('');
|
||||
const serviceOptions = ref([
|
||||
{ text: 'Tune-up', value: 0 },
|
||||
{ text: 'No Heat', value: 1 },
|
||||
{ text: 'Fix', value: 2 },
|
||||
{ text: 'Tank Install', value: 3 },
|
||||
{ text: 'Other', value: 4 },
|
||||
]);
|
||||
const event = ref({
|
||||
title: '',
|
||||
description: '',
|
||||
date: dayjs().format('YYYY-MM-DD'),
|
||||
endDate: '',
|
||||
time: 12,
|
||||
});
|
||||
|
||||
// Computed properties
|
||||
const customerStateName = computed((): string => {
|
||||
if (!props.customer) return '';
|
||||
const stateMap: { [key: number]: string } = {
|
||||
0: 'Massachusetts', 1: 'Rhode Island', 2: 'New Hampshire',
|
||||
3: 'Maine', 4: 'Vermont', 5: 'Connecticut', 6: 'New York',
|
||||
};
|
||||
return stateMap[props.customer.customer_state] || 'Unknown';
|
||||
});
|
||||
|
||||
const customerHomeType = computed((): string => {
|
||||
if (!props.customer) return '';
|
||||
const homeTypeMap: { [key: number]: string } = {
|
||||
0: 'Residential', 1: 'Apartment', 2: 'Condo', 3: 'Commercial',
|
||||
4: 'Business', 5: 'Construction', 6: 'Container',
|
||||
};
|
||||
return homeTypeMap[props.customer.customer_home_type] || 'Unknown';
|
||||
});
|
||||
|
||||
// Functions
|
||||
const submitEvent = () => {
|
||||
if (!props.customer) {
|
||||
alert("Cannot submit: No customer data is loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
const startDateTime = dayjs(`${event.value.date} ${event.value.time}:00`).format('YYYY-MM-DDTHH:mm:ss');
|
||||
const endDateTime = event.value.endDate ? dayjs(event.value.endDate).add(1, 'day').format('YYYY-MM-DD') : undefined;
|
||||
|
||||
const eventPayload = {
|
||||
title: event.value.title,
|
||||
start: startDateTime,
|
||||
type_service_call: selectedService.value,
|
||||
end: endDateTime,
|
||||
extendedProps: {
|
||||
description: event.value.description,
|
||||
},
|
||||
};
|
||||
|
||||
emit('event-scheduled', eventPayload);
|
||||
|
||||
event.value.title = '';
|
||||
selectedService.value = '';
|
||||
event.value.description = '';
|
||||
event.value.endDate = '';
|
||||
event.value.date = dayjs().format('YYYY-MM-DD');
|
||||
event.value.time = 12;
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Import the new component at the top
|
||||
import ServiceHome from './ServiceHome.vue'
|
||||
import ServicePast from './ServicePast.vue'
|
||||
import CalendarCustomer from './calender/CalendarCustomer.vue'
|
||||
import ServiceCalendar from './ServiceCalendar.vue'
|
||||
import ServiceToday from './ServiceToday.vue'
|
||||
import ServicePlans from './ServicePlans.vue'
|
||||
const ServiceHome = () => import('./ServiceHome.vue')
|
||||
const ServicePast = () => import('./ServicePast.vue')
|
||||
const CalendarCustomer = () => import('./calender/CalendarCustomer.vue')
|
||||
const ServiceCalendar = () => import('./ServiceCalendar.vue')
|
||||
const ServiceToday = () => import('./ServiceToday.vue')
|
||||
const ServicePlans = () => import('./ServicePlans.vue')
|
||||
|
||||
const serviceRoutes = [
|
||||
{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
|
||||
|
||||
import Ticket from "../ticket/ticket.vue";
|
||||
import TicketAuto from "../ticket/ticketauto.vue";
|
||||
const Ticket = () => import("../ticket/ticket.vue");
|
||||
const TicketAuto = () => import("../ticket/ticketauto.vue");
|
||||
|
||||
|
||||
const ticketRoutes = [
|
||||
|
||||
@@ -170,6 +170,7 @@ import authHeader from '../../../services/auth.header'
|
||||
import Header from '../../../layouts/headers/headerauth.vue'
|
||||
import SideBar from '../../../layouts/sidebar/sidebar.vue'
|
||||
import Footer from '../../../layouts/footers/footer.vue'
|
||||
import {AuthorizeTransaction} from '../../../types/models'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'transactionsAuthorize',
|
||||
@@ -182,7 +183,7 @@ export default defineComponent({
|
||||
|
||||
data() {
|
||||
return {
|
||||
transactions: [] as any[],
|
||||
transactions: [] as AuthorizeTransaction[],
|
||||
}
|
||||
},
|
||||
|
||||
@@ -208,7 +209,7 @@ export default defineComponent({
|
||||
getStatusText(status: number) {
|
||||
return status === 0 ? 'Approved' : 'Declined'
|
||||
},
|
||||
getSourceText(transaction: any) {
|
||||
getSourceText(transaction: AuthorizeTransaction) {
|
||||
if (transaction.auto_id) {
|
||||
return 'Automatic'
|
||||
} else if (transaction.delivery_id) {
|
||||
@@ -222,7 +223,7 @@ export default defineComponent({
|
||||
formatDate(dateStr: string) {
|
||||
return dateStr.split('T')[0]; // YYYY-MM-DD
|
||||
},
|
||||
getCaptureRoute(transaction: any) {
|
||||
getCaptureRoute(transaction: AuthorizeTransaction) {
|
||||
if (transaction.service_id) {
|
||||
return { name: 'chargeServiceAuthorize', params: { id: transaction.service_id } };
|
||||
} else if (transaction.delivery_id) {
|
||||
@@ -230,7 +231,7 @@ export default defineComponent({
|
||||
}
|
||||
return {}; // fallback, though condition should prevent this
|
||||
},
|
||||
getPreauthRoute(transaction: any) {
|
||||
getPreauthRoute(transaction: AuthorizeTransaction) {
|
||||
if (transaction.service_id) {
|
||||
return { name: 'chargeServiceAuthorize', params: { id: transaction.service_id } };
|
||||
} else if (transaction.delivery_id) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import AuthorizePage from './authorize/index.vue';
|
||||
const AuthorizePage = () => import('./authorize/index.vue');
|
||||
|
||||
const transactionsRoutes = [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user