working auto
This commit is contained in:
@@ -1,8 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import AutomaticHome from './home.vue';
|
import AutomaticHome from './home.vue';
|
||||||
|
import AutomaticView from './view.vue';
|
||||||
|
|
||||||
|
|
||||||
const autoRoutes = [
|
const autoRoutes = [
|
||||||
{
|
{
|
||||||
@@ -10,6 +9,11 @@ const autoRoutes = [
|
|||||||
name: 'auto',
|
name: 'auto',
|
||||||
component: AutomaticHome,
|
component: AutomaticHome,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/automatic/:id',
|
||||||
|
name: 'automaticView',
|
||||||
|
component: AutomaticView,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export default autoRoutes
|
export default autoRoutes
|
||||||
|
|||||||
448
src/pages/automatic/view.vue
Normal file
448
src/pages/automatic/view.vue
Normal file
@@ -0,0 +1,448 @@
|
|||||||
|
<!-- src/pages/automatic/view.vue -->
|
||||||
|
<template>
|
||||||
|
<div class="flex">
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="w-full px-4 md:px-10 py-4">
|
||||||
|
<!-- Breadcrumbs & Title -->
|
||||||
|
<div class="text-sm breadcrumbs">
|
||||||
|
<ul>
|
||||||
|
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||||
|
<li><router-link :to="{ name: 'customer' }">Customers</router-link></li>
|
||||||
|
<li v-if="customer && customer.id">
|
||||||
|
<router-link :to="{ name: 'customerProfile', params: { id: customer.id } }">
|
||||||
|
{{ customer.customer_first_name }} {{ customer.customer_last_name }}
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
<li>Automatic Delivery #{{ autoTicket.id }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<h1 class="text-3xl font-bold mt-4 border-b border-gray-600 pb-2">
|
||||||
|
Automatic Delivery #{{ autoTicket.id }}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<!-- TOP SECTION: Customer & Payment Status Info -->
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 my-6">
|
||||||
|
<!-- Customer Info Card -->
|
||||||
|
<div class="bg-neutral rounded-lg p-5">
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<div class="text-xl font-bold">{{ autoTicket.customer_full_name }}</div>
|
||||||
|
<div class="text-sm text-gray-400">Account: {{ autoTicket.account_number }}</div>
|
||||||
|
</div>
|
||||||
|
<router-link v-if="customer && customer.id" :to="{ name: 'customerProfile', params: { id: customer.id } }" class="btn btn-secondary btn-sm">
|
||||||
|
View Profile
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>{{ autoTicket.customer_address }}</div>
|
||||||
|
<div v-if="autoTicket.customer_apt && autoTicket.customer_apt !== 'None'">Apt: {{ autoTicket.customer_apt }}</div>
|
||||||
|
<div>
|
||||||
|
{{ autoTicket.customer_town }},
|
||||||
|
<span v-if="autoTicket.customer_state == 0">Massachusetts</span>
|
||||||
|
<span v-else-if="autoTicket.customer_state == 1">Rhode Island</span>
|
||||||
|
<span v-else-if="autoTicket.customer_state == 2">New Hampshire</span>
|
||||||
|
<span v-else-if="autoTicket.customer_state == 3">Maine</span>
|
||||||
|
<span v-else-if="autoTicket.customer_state == 4">Vermont</span>
|
||||||
|
<span v-else-if="autoTicket.customer_state == 5">Connecticut</span>
|
||||||
|
<span v-else-if="autoTicket.customer_state == 6">New York</span>
|
||||||
|
<span v-else>Unknown state</span>
|
||||||
|
{{ autoTicket.customer_zip }}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-400 mt-1">
|
||||||
|
Auto Delivery
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
|
<div>
|
||||||
|
<div class="font-bold text-sm">Fill Date</div>
|
||||||
|
<div>{{ autoTicket.fill_date }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-2">
|
||||||
|
<div class="font-bold text-sm">Response Time</div>
|
||||||
|
<div>Instant</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- BOTTOM SECTION: Auto Delivery & Financial Details -->
|
||||||
|
<div class="bg-neutral rounded-lg p-6">
|
||||||
|
<div class="grid grid-cols-1 xl:grid-cols-2 gap-8">
|
||||||
|
<!-- Left Column: Pricing, Gallons, Transaction -->
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- Gallons Delivered -->
|
||||||
|
<div class="p-4 border rounded-md">
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="label-text font-bold">Gallons Delivered</label>
|
||||||
|
<div class="text-lg mt-1">
|
||||||
|
<span>{{ autoTicket.gallons_delivered }} gallons</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pricing & Totals -->
|
||||||
|
<div class="p-4 border rounded-md space-y-2">
|
||||||
|
<label class="label-text font-bold">Financial Summary</label>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<span>Total Amount</span>
|
||||||
|
<span>${{ autoTicket.total_amount_customer }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<span class="text-lg font-bold">
|
||||||
|
Estimated Charge Amount
|
||||||
|
</span>
|
||||||
|
<span class="text-2xl font-bold text-success">
|
||||||
|
${{ autoTicket.total_amount_customer }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Transaction Summary -->
|
||||||
|
<div v-if="autoTicket.payment_type == 11" class="p-4 border rounded-md">
|
||||||
|
<label class="label-text font-bold">Authorize.net Transaction Details</label>
|
||||||
|
<div v-if="transaction" class="mt-2 space-y-2">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="font-bold">Transaction ID:</span>
|
||||||
|
<span class="font-mono">{{ transaction.auth_net_transaction_id || 'N/A' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>Pre-Auth Amount:</span>
|
||||||
|
<span>${{ transaction.preauthorize_amount || '0.00' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>Charge Amount:</span>
|
||||||
|
<span>${{ transaction.charge_amount || '0.00' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>Type:</span>
|
||||||
|
<span :class="getTypeColor(transaction.transaction_type)">
|
||||||
|
{{ transaction.transaction_type === 0 ? 'Charge' : transaction.transaction_type === 1 ? 'Auth' : transaction.transaction_type === 2 ? 'Capture' : 'Other' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>Date:</span>
|
||||||
|
<span>{{ format_date(transaction.created_at) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>Status:</span>
|
||||||
|
<span :class="transaction.status === 0 ? 'text-success' : 'text-error'">
|
||||||
|
{{ transaction.status === 0 ? 'Approved' : 'Declined' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="mt-2 text-gray-500">
|
||||||
|
No authorize.net transaction data available
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Simple Payment Display -->
|
||||||
|
<div v-else class="p-4 border rounded-md">
|
||||||
|
<label class="label-text font-bold">Payment Method</label>
|
||||||
|
<div class="mt-2">
|
||||||
|
<span class="text-info font-semibold">{{ getPaymentMethodName(autoTicket.payment_type) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Delivery Summary -->
|
||||||
|
<div class="p-4 border rounded-md">
|
||||||
|
<label class="label-text font-bold">Delivery Summary</label>
|
||||||
|
<div class="space-y-2 mt-2">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>Gallons Delivered:</span>
|
||||||
|
<span class="font-semibold">{{ autoTicket.gallons_delivered }} gallons</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>Price per Gallon:</span>
|
||||||
|
<span class="font-semibold">${{ autoTicket.price_per_gallon }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between font-bold">
|
||||||
|
<span>Total:</span>
|
||||||
|
<span>${{ autoTicket.total_amount_customer }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Column -->
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- Payment Method -->
|
||||||
|
<div class="p-4 border rounded-md">
|
||||||
|
<label class="label-text font-bold">Payment Method</label>
|
||||||
|
<div class="mt-1">
|
||||||
|
<div class="text-lg">
|
||||||
|
{{ getPaymentMethodName(autoTicket.payment_type) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Card display -->
|
||||||
|
<div v-if="userCardfound && [1, 2, 3].includes(autoTicket.payment_type)"
|
||||||
|
class="p-4 rounded-lg border mt-2"
|
||||||
|
:class="userCard.main_card ? 'bg-primary/10 border-primary' : 'bg-base-200 border-base-300'">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div>
|
||||||
|
<div class="font-bold">{{ userCard.name_on_card }}</div>
|
||||||
|
<div class="text-xs opacity-70">{{ userCard.type_of_card }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="userCard.main_card" class="badge badge-primary badge-sm">Primary</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 text-sm font-mono tracking-wider">
|
||||||
|
<p>{{ userCard.card_number }}</p>
|
||||||
|
<p>
|
||||||
|
Exp:
|
||||||
|
<span v-if="Number(userCard.expiration_month) < 10">0</span>{{ userCard.expiration_month }} / {{ userCard.expiration_year }}
|
||||||
|
</p>
|
||||||
|
<p>CVV: {{ userCard.security_number }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Notes -->
|
||||||
|
<div class="p-4 border rounded-md">
|
||||||
|
<label class="label-text font-bold">Notes</label>
|
||||||
|
<div class="prose prose-sm mt-4 max-w-none">
|
||||||
|
<blockquote class="text-gray-400">Auto delivery processed automatically based on tank levels.</blockquote>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Footer />
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
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 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';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'automaticDeliveryView',
|
||||||
|
components: {
|
||||||
|
Header,
|
||||||
|
SideBar,
|
||||||
|
Footer,
|
||||||
|
},
|
||||||
|
|
||||||
|
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,
|
||||||
|
userCardfound: false,
|
||||||
|
userCard: {} as UserCard,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.getAutoTicket(this.$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-purple-600'; // Capture
|
||||||
|
default: return 'text-gray-600';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getPaymentMethodName(paymentType: number): string {
|
||||||
|
switch (paymentType) {
|
||||||
|
case 0: return 'Cash';
|
||||||
|
case 1: return 'Credit Card';
|
||||||
|
case 11: return 'Authorize.net PCI Card API';
|
||||||
|
default: return 'Other';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getCustomer(customerId: number) {
|
||||||
|
if (!customerId) return;
|
||||||
|
const path = `${import.meta.env.VITE_BASE_URL}/customer/${customerId}`;
|
||||||
|
axios.get(path, { withCredentials: true })
|
||||||
|
.then((response: any) => {
|
||||||
|
this.customer = response.data;
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.error("Error fetching customer:", error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getPaymentCard(cardId: any) {
|
||||||
|
if (!cardId) {
|
||||||
|
this.userCardfound = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const path = `${import.meta.env.VITE_BASE_URL}/payment/card/${cardId}`;
|
||||||
|
axios.get(path, { withCredentials: true })
|
||||||
|
.then((response: any) => {
|
||||||
|
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) => {
|
||||||
|
this.userCard = {} as UserCard;
|
||||||
|
this.userCardfound = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getAutoTicket(autoTicketId: any) {
|
||||||
|
if (!autoTicketId) return;
|
||||||
|
const path = `${import.meta.env.VITE_AUTO_URL}/delivery/autoticket/${autoTicketId}`;
|
||||||
|
axios.get(path, { withCredentials: true })
|
||||||
|
.then((response: any) => {
|
||||||
|
this.autoTicket = response.data;
|
||||||
|
this.getCustomer(this.autoTicket.customer_id);
|
||||||
|
if ([1, 2, 3].includes(this.autoTicket.payment_type)) {
|
||||||
|
this.getPaymentCard(this.autoTicket.payment_card_id);
|
||||||
|
}
|
||||||
|
if (this.autoTicket.payment_type == 11) {
|
||||||
|
this.getTransaction(autoTicketId);
|
||||||
|
}
|
||||||
|
this.getAutoDelivery(autoTicketId);
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
notify({
|
||||||
|
title: "Error",
|
||||||
|
text: "Could not get automatic ticket",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getAutoDelivery(ticketId: any) {
|
||||||
|
const path = `${import.meta.env.VITE_AUTO_URL}/delivery/finddelivery/${ticketId}`;
|
||||||
|
axios.get(path, { withCredentials: true })
|
||||||
|
.then((response: any) => {
|
||||||
|
this.autoDelivery = response.data;
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.error("Error fetching auto delivery:", error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getTransaction(deliveryId: any) {
|
||||||
|
const path = `${import.meta.env.VITE_AUTHORIZE_URL}/api/auto/transaction/delivery/${deliveryId}`;
|
||||||
|
console.log(path)
|
||||||
|
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||||
|
.then((response: any) => {
|
||||||
|
this.transaction = response.data;
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.error("No transaction found for delivery:", error);
|
||||||
|
this.transaction = null;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Gallons</th>
|
<th>Gallons</th>
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
|
<th>View</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -20,6 +21,11 @@
|
|||||||
<td>{{ auto.customer_full_name }}</td>
|
<td>{{ auto.customer_full_name }}</td>
|
||||||
<td>{{ auto.gallons_delivered }}</td>
|
<td>{{ auto.gallons_delivered }}</td>
|
||||||
<td>{{ auto.fill_date }}</td>
|
<td>{{ auto.fill_date }}</td>
|
||||||
|
<td>
|
||||||
|
<router-link :to="{ name: 'automaticView', params: { id: auto.id } }">
|
||||||
|
<button class="btn btn-xs btn-primary">View</button>
|
||||||
|
</router-link>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -44,4 +50,4 @@ interface Props {
|
|||||||
|
|
||||||
// 3. Use the typed defineProps
|
// 3. Use the typed defineProps
|
||||||
defineProps<Props>();
|
defineProps<Props>();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ interface Transaction {
|
|||||||
rejection_reason: string | null;
|
rejection_reason: string | null;
|
||||||
delivery_id: number | null;
|
delivery_id: number | null;
|
||||||
service_id: number | null;
|
service_id: number | null;
|
||||||
|
auto_id: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -125,7 +126,9 @@ defineProps<Props>();
|
|||||||
const getStatusClass = (status: number) => status === 0 ? 'badge-success' : 'badge-error';
|
const getStatusClass = (status: number) => status === 0 ? 'badge-success' : 'badge-error';
|
||||||
const getStatusText = (status: number) => status === 0 ? 'Approved' : 'Declined';
|
const getStatusText = (status: number) => status === 0 ? 'Approved' : 'Declined';
|
||||||
const getSourceText = (transaction: Transaction) => {
|
const getSourceText = (transaction: Transaction) => {
|
||||||
if (transaction.delivery_id) {
|
if (transaction.auto_id) {
|
||||||
|
return 'Automatic';
|
||||||
|
} else if (transaction.delivery_id) {
|
||||||
return 'Delivery - ' + transaction.delivery_id;
|
return 'Delivery - ' + transaction.delivery_id;
|
||||||
} else if (transaction.service_id) {
|
} else if (transaction.service_id) {
|
||||||
return 'Service - ' + transaction.service_id;
|
return 'Service - ' + transaction.service_id;
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pt-2">
|
<div class="pt-2">
|
||||||
<button type="submit" class="btn btn-secondary btn-sm">Finalize Delivery</button>
|
<button type="submit" class="btn btn-success btn-sm">Finalize Delivery</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -497,6 +497,19 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
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: {
|
UpdateDeliveredAuto(payload: {
|
||||||
gallons_delivered: string,
|
gallons_delivered: string,
|
||||||
}) {
|
}) {
|
||||||
@@ -516,7 +529,7 @@
|
|||||||
text: "Auto Updated",
|
text: "Auto Updated",
|
||||||
type: "success",
|
type: "success",
|
||||||
});
|
});
|
||||||
this.$router.push({ name: "payAutoCapture", params: { id: this.autoTicket.id } });
|
// Removed redirect from here, will handle in onSubmit
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -554,12 +567,20 @@
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
|
|
||||||
let payload = {
|
let payload = {
|
||||||
gallons_delivered: this.FinalizeOilOrderForm.gallons_delivered,
|
gallons_delivered: this.FinalizeOilOrderForm.gallons_delivered,
|
||||||
};
|
};
|
||||||
this.UpdateDeliveredAuto(payload);
|
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" });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,48 +19,94 @@
|
|||||||
<div class="max-w-6xl">
|
<div class="max-w-6xl">
|
||||||
<h1 class="text-3xl font-bold mb-8">Payment Authorization Authorize.net</h1>
|
<h1 class="text-3xl font-bold mb-8">Payment Authorization Authorize.net</h1>
|
||||||
|
|
||||||
<!-- Top Row: Charge Breakdown and Payment Method -->
|
<!-- Top Row: Customer Info/Payment and Charge Breakdown/Pricing -->
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
||||||
<!-- Charge Breakdown -->
|
<!-- Left Column: Customer Info and Payment Method -->
|
||||||
<div class="bg-base-100 rounded-lg p-6">
|
<div class="space-y-4">
|
||||||
<h3 class="text-lg font-semibold mb-4">Charge Breakdown</h3>
|
<!-- Customer Info Card -->
|
||||||
<div class="space-y-2">
|
<div class="bg-neutral rounded-lg p-5">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between items-center mb-4">
|
||||||
<span>Gallons to Fill:</span>
|
<div>
|
||||||
<span>{{ calculateGallonsToFill() }} gallons</span>
|
<div class="text-xl font-bold">{{ customer.customer_first_name }} {{ customer.customer_last_name }}</div>
|
||||||
|
<div class="text-sm text-gray-400">Account: {{ customer.account_number }}</div>
|
||||||
|
</div>
|
||||||
|
<router-link v-if="customer && customer.id" :to="{ name: 'customerProfile', params: { id: customer.id } }" class="btn btn-secondary btn-sm">
|
||||||
|
View Profile
|
||||||
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div>
|
||||||
<span>Price per Gallon:</span>
|
<div>{{ customer.customer_address }}</div>
|
||||||
<span>${{ pricing.price_for_customer || currentOilPrice }}</span>
|
<div v-if="customer.customer_apt && customer.customer_apt !== 'None'">{{ customer.customer_apt }}</div>
|
||||||
</div>
|
<div>{{ customer.customer_town }}, {{ customerStateName }} {{ customer.customer_zip }}</div>
|
||||||
<div class="flex justify-between font-semibold">
|
<div class="mt-2">{{ customer.customer_phone_number }}</div>
|
||||||
<span>Subtotal:</span>
|
|
||||||
<span>${{ calculateSubtotal() }}</span>
|
|
||||||
</div>
|
|
||||||
<hr class="my-3">
|
|
||||||
<div class="flex justify-between font-bold text-lg">
|
|
||||||
<span>Estimated Total:</span>
|
|
||||||
<span>${{ calculateTotalAmount() }}</span>
|
|
||||||
</div>
|
|
||||||
</div> <!-- close space-y-2 -->
|
|
||||||
</div> <!-- close bg-base-100 -->
|
|
||||||
|
|
||||||
<!-- Credit Card Display -->
|
|
||||||
<div class="bg-base-100 rounded-lg p-6">
|
|
||||||
<h3 class="text-lg font-semibold mb-4">Payment Method</h3>
|
|
||||||
<div v-if="selectedCard" class="bg-base-200 p-4 rounded-md">
|
|
||||||
<div class="flex justify-between items-center mb-2">
|
|
||||||
<div class="font-semibold">{{ selectedCard.type_of_card }}</div>
|
|
||||||
<div v-if="selectedCard.main_card" class="badge badge-primary">Primary</div>
|
|
||||||
</div>
|
|
||||||
<div class="font-mono text-sm">
|
|
||||||
<div> {{ selectedCard.card_number }}</div>
|
|
||||||
<div>{{ selectedCard.name_on_card }}</div>
|
|
||||||
<div>Expires: {{ selectedCard.expiration_month }}/{{ selectedCard.expiration_year }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="text-gray-500 p-4">
|
|
||||||
No payment method selected
|
<!-- Credit Card Display -->
|
||||||
|
<div class="bg-base-100 rounded-lg p-6">
|
||||||
|
<h3 class="text-lg font-semibold mb-4">Payment Method</h3>
|
||||||
|
<div v-if="selectedCard" class="bg-base-200 p-4 rounded-md">
|
||||||
|
<div class="flex justify-between items-center mb-2">
|
||||||
|
<div class="font-semibold">{{ selectedCard.type_of_card }}</div>
|
||||||
|
<div v-if="selectedCard.main_card" class="badge badge-primary">Primary</div>
|
||||||
|
</div>
|
||||||
|
<div class="font-mono text-sm">
|
||||||
|
<div> {{ selectedCard.card_number }}</div>
|
||||||
|
<div>{{ selectedCard.name_on_card }}</div>
|
||||||
|
<div>Expires: {{ selectedCard.expiration_month }}/{{ selectedCard.expiration_year }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-gray-500 p-4">
|
||||||
|
No payment method selected
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Column: Charge Breakdown and Pricing -->
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- Charge Breakdown -->
|
||||||
|
<div class="bg-base-100 rounded-lg p-6">
|
||||||
|
<h3 class="text-lg font-semibold mb-4">Charge Breakdown</h3>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>Gallons to Fill:</span>
|
||||||
|
<span>{{ calculateGallonsToFill() }} gallons</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>Price per Gallon:</span>
|
||||||
|
<span>${{ pricing.price_for_customer || currentOilPrice }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between font-semibold">
|
||||||
|
<span>Subtotal:</span>
|
||||||
|
<span>${{ calculateSubtotal() }}</span>
|
||||||
|
</div>
|
||||||
|
<hr class="my-3">
|
||||||
|
<div class="flex justify-between font-bold text-lg">
|
||||||
|
<span>Estimated Total:</span>
|
||||||
|
<span>${{ calculateTotalAmount() }}</span>
|
||||||
|
</div>
|
||||||
|
</div> <!-- close space-y-2 -->
|
||||||
|
</div> <!-- close bg-base-100 -->
|
||||||
|
|
||||||
|
<!-- Pricing Chart Card -->
|
||||||
|
<div class="bg-neutral rounded-lg p-5">
|
||||||
|
<h3 class="text-xl font-bold mb-4">Today's Price Per Gallon</h3>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="table table-sm w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Gallons</th>
|
||||||
|
<th>Total Price</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="tier in pricingTiers" :key="tier.gallons" :class="isPricingTierSelected(tier.gallons) ? 'bg-blue-600 text-white hover:bg-blue-600' : 'hover'">
|
||||||
|
<td>{{ tier.gallons }}</td>
|
||||||
|
<td>${{ tier.price.toFixed(2) }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -92,6 +138,14 @@
|
|||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex flex-wrap gap-2 mt-2">
|
||||||
|
<button v-for="amount in quickGallonAmounts"
|
||||||
|
:key="amount"
|
||||||
|
@click.prevent="setGallons(amount)"
|
||||||
|
:class="['btn', 'btn-xs', selectedGallonAmount == amount ? 'bg-blue-600 text-white border-blue-600' : 'btn-outline']">
|
||||||
|
{{ amount }} gal
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Action Buttons -->
|
<!-- Action Buttons -->
|
||||||
@@ -162,6 +216,7 @@ export default defineComponent({
|
|||||||
deliveryId: this.$route.params.id as string,
|
deliveryId: this.$route.params.id as string,
|
||||||
loaded: false,
|
loaded: false,
|
||||||
chargeAmount: 0,
|
chargeAmount: 0,
|
||||||
|
quickGallonAmounts: [100, 125, 150, 175, 200, 220],
|
||||||
loading: false,
|
loading: false,
|
||||||
action: '', // 'preauthorize' or 'charge'
|
action: '', // 'preauthorize' or 'charge'
|
||||||
error: '',
|
error: '',
|
||||||
@@ -222,17 +277,26 @@ export default defineComponent({
|
|||||||
date: "",
|
date: "",
|
||||||
},
|
},
|
||||||
currentOilPrice: 0,
|
currentOilPrice: 0,
|
||||||
|
pricingTiers: [] as { gallons: number; price: number }[],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
selectedCard(): any {
|
selectedCard(): any {
|
||||||
return this.credit_cards.find((card: any) => card.main_card) || this.credit_cards[0]
|
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() {
|
mounted() {
|
||||||
this.loadData(this.deliveryId)
|
this.loadData(this.deliveryId)
|
||||||
|
this.getPricingTiers()
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
@@ -376,6 +440,55 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
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() {
|
calculateGallonsToFill() {
|
||||||
return this.autoDelivery.tank_size - this.autoDelivery.estimated_gallons_left
|
return this.autoDelivery.tank_size - this.autoDelivery.estimated_gallons_left
|
||||||
},
|
},
|
||||||
@@ -514,8 +627,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
// Create Tickets_Auto_Delivery after successful charge
|
// Create Tickets_Auto_Delivery after successful charge
|
||||||
const ticketPayload = {
|
const ticketPayload = {
|
||||||
gallons_delivered: this.calculateGallonsToFill(),
|
gallons_delivered: 0,
|
||||||
payment_type: 1, // 1 for charge, 11 for preauthorize
|
payment_type: 11, // 11 for Authorize charge
|
||||||
payment_card_id: this.selectedCard.id,
|
payment_card_id: this.selectedCard.id,
|
||||||
payment_status: response.data.status // 0 = APPROVED
|
payment_status: response.data.status // 0 = APPROVED
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -217,6 +217,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Payment Capture Success Modal -->
|
||||||
|
<div class="modal" :class="{ 'modal-open': showPaymentModal }">
|
||||||
|
<div class="modal-box">
|
||||||
|
<h3 class="font-bold text-lg">Payment Captured Successfully</h3>
|
||||||
|
<div class="py-4">
|
||||||
|
<div v-if="modalStep === 0" class="text-center">
|
||||||
|
<span class="text-lg mb-3">Transaction ID: {{ transaction?.auth_net_transaction_id }}</span>
|
||||||
|
<div class="loading loading-spinner loading-lg text-success mb-3"></div>
|
||||||
|
<p class="text-sm text-gray-600">Processing payment capture...</p>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="modalStep === 1" class="text-center">
|
||||||
|
<span class="text-lg mb-3">Transaction ID: {{ transaction?.auth_net_transaction_id }}</span>
|
||||||
|
<p class="text-lg">Captured Amount: ${{ modalCapturedAmount }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -236,6 +254,9 @@ export default defineComponent({
|
|||||||
captureAmount: 0,
|
captureAmount: 0,
|
||||||
preAuthAmount: 0,
|
preAuthAmount: 0,
|
||||||
transaction: null as any,
|
transaction: null as any,
|
||||||
|
showPaymentModal: false,
|
||||||
|
modalStep: 0,
|
||||||
|
modalCapturedAmount: 0,
|
||||||
|
|
||||||
userCard: {
|
userCard: {
|
||||||
date_added: '',
|
date_added: '',
|
||||||
@@ -252,10 +273,10 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
customerDescription: {
|
customerDescription: {
|
||||||
customer_id: 0,
|
customer_id: 0,
|
||||||
account_number: '',
|
account_number: '',
|
||||||
company_id: 0,
|
company_id: 0,
|
||||||
fill_location: 0,
|
fill_location: 0,
|
||||||
description: '',
|
description: '',
|
||||||
},
|
},
|
||||||
customer: {
|
customer: {
|
||||||
id: 0,
|
id: 0,
|
||||||
@@ -324,7 +345,7 @@ export default defineComponent({
|
|||||||
price_emergency: 0,
|
price_emergency: 0,
|
||||||
date: "",
|
date: "",
|
||||||
},
|
},
|
||||||
|
|
||||||
total_amount: 0,
|
total_amount: 0,
|
||||||
discount: 0,
|
discount: 0,
|
||||||
total_amount_after_discount: 0,
|
total_amount_after_discount: 0,
|
||||||
@@ -356,7 +377,7 @@ export default defineComponent({
|
|||||||
if (this.autoTicket.payment_card_id) {
|
if (this.autoTicket.payment_card_id) {
|
||||||
this.getPaymentCard(this.autoTicket.payment_card_id)
|
this.getPaymentCard(this.autoTicket.payment_card_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
notify({
|
notify({
|
||||||
@@ -366,7 +387,7 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getAutoDelivery(delivery_id: any) {
|
getAutoDelivery(delivery_id: any) {
|
||||||
let path = import.meta.env.VITE_AUTO_URL + "/delivery/finddelivery/" + delivery_id;
|
let path = import.meta.env.VITE_AUTO_URL + "/delivery/finddelivery/" + delivery_id;
|
||||||
axios({
|
axios({
|
||||||
@@ -379,7 +400,7 @@ export default defineComponent({
|
|||||||
this.autoDelivery = response.data;
|
this.autoDelivery = response.data;
|
||||||
this.getCustomer(this.autoDelivery.customer_id)
|
this.getCustomer(this.autoDelivery.customer_id)
|
||||||
this.getCustomerDescription(this.autoDelivery.customer_id)
|
this.getCustomerDescription(this.autoDelivery.customer_id)
|
||||||
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
notify({
|
notify({
|
||||||
@@ -490,14 +511,12 @@ export default defineComponent({
|
|||||||
|
|
||||||
if (response.data && response.data.status === 0) {
|
if (response.data && response.data.status === 0) {
|
||||||
this.autoTicket.payment_status = 3; // Update local status immediately
|
this.autoTicket.payment_status = 3; // Update local status immediately
|
||||||
notify({
|
this.modalCapturedAmount = this.captureAmount;
|
||||||
title: "Success",
|
this.showPaymentModal = true;
|
||||||
text: "Payment captured successfully!",
|
|
||||||
type: "success",
|
|
||||||
});
|
|
||||||
// Close the ticket and unassign from delivery
|
// Close the ticket and unassign from delivery
|
||||||
this.closeTicket(this.autoTicket.id);
|
this.closeTicket(this.autoTicket.id);
|
||||||
this.$router.push({ name: 'auto' });
|
setTimeout(() => { this.modalStep = 1 }, 2000);
|
||||||
|
setTimeout(() => { this.showPaymentModal = false; this.$router.push({ name: 'auto' }) }, 4000);
|
||||||
} else if (response.data && response.data.status === 1) {
|
} else if (response.data && response.data.status === 1) {
|
||||||
const reason = response.data.rejection_reason || "The payment was declined by the gateway.";
|
const reason = response.data.rejection_reason || "The payment was declined by the gateway.";
|
||||||
notify({
|
notify({
|
||||||
|
|||||||
@@ -134,7 +134,42 @@
|
|||||||
<div class="font-bold">When Delivered</div>
|
<div class="font-bold">When Delivered</div>
|
||||||
<div class="opacity-80">{{ deliveryOrder.when_delivered }}</div>
|
<div class="opacity-80">{{ deliveryOrder.when_delivered }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Transaction Summary -->
|
||||||
|
<div v-if="transaction && transaction.auth_net_transaction_id" class="bg-neutral rounded-lg p-5">
|
||||||
|
<h3 class="text-xl font-bold mb-4">Transaction Summary</h3>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div class="grid grid-cols-2 gap-x-4 gap-y-3 text-sm">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="font-bold">Transaction ID:</span>
|
||||||
|
<span class="font-mono">{{ transaction.auth_net_transaction_id }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="font-bold">Pre-Auth Amount:</span>
|
||||||
|
<span>${{ transaction.preauthorize_amount || '0.00' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="font-bold">Charge Amount:</span>
|
||||||
|
<span>${{ transaction.charge_amount || '0.00' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="font-bold">Type:</span>
|
||||||
|
<span :class="getTypeColor(transaction.transaction_type)">{{ transaction.transaction_type === 0 ? 'Charge' : transaction.transaction_type === 1 ? 'Auth' : 'Capture' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="font-bold">Date:</span>
|
||||||
|
<span>{{ transaction.created_at }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="font-bold">Status:</span>
|
||||||
|
<span :class="transaction.status === 0 ? 'text-success' : 'text-error'">
|
||||||
|
{{ transaction.status === 0 ? 'Approved' : 'Declined' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -173,6 +208,9 @@
|
|||||||
<!-- Capture Payment Form -->
|
<!-- Capture Payment Form -->
|
||||||
<div class="bg-base-100 rounded-lg p-5">
|
<div class="bg-base-100 rounded-lg p-5">
|
||||||
<h2 class="text-2xl font-bold mb-4">Capture Payment</h2>
|
<h2 class="text-2xl font-bold mb-4">Capture Payment</h2>
|
||||||
|
<div v-if="preAuthAmount > 0" class="mb-2 text-sm text-orange-600">
|
||||||
|
Max Capture amount: ${{ preAuthAmount.toFixed(2) }}
|
||||||
|
</div>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
@@ -219,6 +257,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Payment Capture Success Modal -->
|
||||||
|
<div class="modal" :class="{ 'modal-open': showPaymentModal }">
|
||||||
|
<div class="modal-box">
|
||||||
|
<h3 class="font-bold text-lg">Payment Captured Successfully</h3>
|
||||||
|
<div class="py-4">
|
||||||
|
<div v-if="modalStep === 0" class="text-center">
|
||||||
|
<span class="text-lg mb-3">Transaction ID: {{ transaction?.auth_net_transaction_id }}</span>
|
||||||
|
<div class="loading loading-spinner loading-lg text-success mb-3"></div>
|
||||||
|
<p class="text-sm text-gray-600">Processing payment capture...</p>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="modalStep === 1" class="text-center">
|
||||||
|
<span class="text-lg mb-3">Transaction ID: {{ transaction?.auth_net_transaction_id }}</span>
|
||||||
|
<p class="text-lg">Captured Amount: ${{ modalCapturedAmount }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<Footer />
|
<Footer />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -248,6 +304,9 @@ export default defineComponent({
|
|||||||
captureAmount: 0,
|
captureAmount: 0,
|
||||||
preAuthAmount: 0,
|
preAuthAmount: 0,
|
||||||
transaction: null as any,
|
transaction: null as any,
|
||||||
|
showPaymentModal: false,
|
||||||
|
modalStep: 0,
|
||||||
|
modalCapturedAmount: 0,
|
||||||
|
|
||||||
userCard: {
|
userCard: {
|
||||||
date_added: '',
|
date_added: '',
|
||||||
@@ -493,13 +552,11 @@ export default defineComponent({
|
|||||||
// ✅ FIX: Improved logic to handle both success and declines properly.
|
// ✅ FIX: Improved logic to handle both success and declines properly.
|
||||||
if (response.data && response.data.status === 0) {
|
if (response.data && response.data.status === 0) {
|
||||||
// This is the APPROVED case
|
// This is the APPROVED case
|
||||||
notify({
|
this.modalCapturedAmount = this.captureAmount;
|
||||||
title: "Success",
|
this.showPaymentModal = true;
|
||||||
text: "Payment captured successfully!",
|
setTimeout(() => { this.modalStep = 1 }, 2000);
|
||||||
type: "success",
|
setTimeout(() => { this.showPaymentModal = false; this.$router.push({ name: 'deliveryOrder', params: { id: this.$route.params.id } }) }, 4000);
|
||||||
});
|
|
||||||
this.$router.push({ name: 'deliveryOrder', params: { id: this.$route.params.id } });
|
|
||||||
|
|
||||||
} else if (response.data && response.data.status === 1) {
|
} else if (response.data && response.data.status === 1) {
|
||||||
// This is the DECLINED case
|
// This is the DECLINED case
|
||||||
const reason = response.data.rejection_reason || "The payment was declined by the gateway.";
|
const reason = response.data.rejection_reason || "The payment was declined by the gateway.";
|
||||||
@@ -606,6 +663,16 @@ export default defineComponent({
|
|||||||
cancelCapture() {
|
cancelCapture() {
|
||||||
this.$router.push({ name: 'finalizeTicket', params: { id: this.$route.params.id } });
|
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';
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -183,6 +183,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Payment Capture Success Modal -->
|
||||||
|
<div class="modal" :class="{ 'modal-open': showPaymentModal }">
|
||||||
|
<div class="modal-box">
|
||||||
|
<h3 class="font-bold text-lg">Payment Captured Successfully</h3>
|
||||||
|
<div class="py-4">
|
||||||
|
<div v-if="modalStep === 0" class="text-center">
|
||||||
|
<span class="text-lg mb-3">Transaction ID: {{ transaction?.auth_net_transaction_id }}</span>
|
||||||
|
<div class="loading loading-spinner loading-lg text-success mb-3"></div>
|
||||||
|
<p class="text-sm text-gray-600">Processing payment capture...</p>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="modalStep === 1" class="text-center">
|
||||||
|
<span class="text-lg mb-3">Transaction ID: {{ transaction?.auth_net_transaction_id }}</span>
|
||||||
|
<p class="text-lg">Captured Amount: ${{ modalCapturedAmount }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<Footer />
|
<Footer />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -248,6 +266,9 @@ const chargeAmount = ref<number>(0);
|
|||||||
const transaction = ref(null as any);
|
const transaction = ref(null as any);
|
||||||
const preAuthAmount = ref<number>(0);
|
const preAuthAmount = ref<number>(0);
|
||||||
const serviceTransactions = ref<ServiceTransaction[]>([]);
|
const serviceTransactions = ref<ServiceTransaction[]>([]);
|
||||||
|
const showPaymentModal = ref(false);
|
||||||
|
const modalStep = ref(0);
|
||||||
|
const modalCapturedAmount = ref(0);
|
||||||
|
|
||||||
// --- Computed Properties for Cleaner Template ---
|
// --- Computed Properties for Cleaner Template ---
|
||||||
const stateName = computed(() => {
|
const stateName = computed(() => {
|
||||||
@@ -399,8 +420,10 @@ const chargeService = async () => {
|
|||||||
{ withCredentials: true, headers: authHeader() }
|
{ withCredentials: true, headers: authHeader() }
|
||||||
);
|
);
|
||||||
|
|
||||||
notify({ title: "Success", text: "Service charged successfully!", type: "success" });
|
modalCapturedAmount.value = chargeAmount.value;
|
||||||
router.push({ name: 'customerProfile', params: { id: service.value!.customer_id } });
|
showPaymentModal.value = true;
|
||||||
|
setTimeout(() => { modalStep.value = 1 }, 2000);
|
||||||
|
setTimeout(() => { showPaymentModal.value = false; router.push({ name: 'customerProfile', params: { id: service.value!.customer_id } }) }, 4000);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
const reason = response.data?.rejection_reason || "The charge was declined.";
|
const reason = response.data?.rejection_reason || "The charge was declined.";
|
||||||
@@ -435,8 +458,10 @@ const chargeService = async () => {
|
|||||||
{ withCredentials: true, headers: authHeader() }
|
{ withCredentials: true, headers: authHeader() }
|
||||||
);
|
);
|
||||||
|
|
||||||
notify({ title: "Success", text: "Payment captured successfully!", type: "success" });
|
modalCapturedAmount.value = chargeAmount.value;
|
||||||
router.push({ name: 'customerProfile', params: { id: service.value!.customer_id } });
|
showPaymentModal.value = true;
|
||||||
|
setTimeout(() => { modalStep.value = 1 }, 2000);
|
||||||
|
setTimeout(() => { showPaymentModal.value = false; router.push({ name: 'customerProfile', params: { id: service.value!.customer_id } }) }, 4000);
|
||||||
} else if (captureResponse.data?.status === 1) {
|
} else if (captureResponse.data?.status === 1) {
|
||||||
const reason = captureResponse.data.rejection_reason || "The payment was declined by the gateway.";
|
const reason = captureResponse.data.rejection_reason || "The payment was declined by the gateway.";
|
||||||
notify({ title: "Payment Declined", text: reason, type: "warn" });
|
notify({ title: "Payment Declined", text: reason, type: "warn" });
|
||||||
|
|||||||
@@ -71,6 +71,9 @@
|
|||||||
<router-link v-if="transaction.delivery_id" :to="{ name: 'deliveryOrder', params: { id: transaction.delivery_id } }" class="btn btn-xs btn-info">
|
<router-link v-if="transaction.delivery_id" :to="{ name: 'deliveryOrder', params: { id: transaction.delivery_id } }" class="btn btn-xs btn-info">
|
||||||
View
|
View
|
||||||
</router-link>
|
</router-link>
|
||||||
|
<router-link v-if="transaction.auto_id" :to="{ name: 'automaticView', params: { id: transaction.auto_id } }" class="btn btn-xs btn-primary">
|
||||||
|
View
|
||||||
|
</router-link>
|
||||||
<template v-if="(Number(transaction.preauthorize_amount) >= 0 && Number(transaction.charge_amount) === 0 && (transaction.delivery_id || transaction.service_id))">
|
<template v-if="(Number(transaction.preauthorize_amount) >= 0 && Number(transaction.charge_amount) === 0 && (transaction.delivery_id || transaction.service_id))">
|
||||||
<router-link v-if="!transaction.auth_net_transaction_id" :to="getPreauthRoute(transaction)" class="btn btn-xs btn-warning">
|
<router-link v-if="!transaction.auth_net_transaction_id" :to="getPreauthRoute(transaction)" class="btn btn-xs btn-warning">
|
||||||
Preauth/Charge
|
Preauth/Charge
|
||||||
@@ -138,6 +141,9 @@
|
|||||||
<router-link v-if="transaction.delivery_id" :to="{ name: 'deliveryOrder', params: { id: transaction.delivery_id } }" class="btn btn-xs btn-info btn-block">
|
<router-link v-if="transaction.delivery_id" :to="{ name: 'deliveryOrder', params: { id: transaction.delivery_id } }" class="btn btn-xs btn-info btn-block">
|
||||||
View Delivery
|
View Delivery
|
||||||
</router-link>
|
</router-link>
|
||||||
|
<router-link v-if="transaction.auto_id" :to="{ name: 'automaticView', params: { id: transaction.auto_id } }" class="btn btn-xs btn-primary btn-block">
|
||||||
|
View Automatic
|
||||||
|
</router-link>
|
||||||
<template v-if="(Number(transaction.preauthorize_amount) >= 0 && Number(transaction.charge_amount) === 0 && (transaction.delivery_id || transaction.service_id))">
|
<template v-if="(Number(transaction.preauthorize_amount) >= 0 && Number(transaction.charge_amount) === 0 && (transaction.delivery_id || transaction.service_id))">
|
||||||
<router-link v-if="!transaction.auth_net_transaction_id" :to="getPreauthRoute(transaction)" class="btn btn-xs btn-warning btn-block">
|
<router-link v-if="!transaction.auth_net_transaction_id" :to="getPreauthRoute(transaction)" class="btn btn-xs btn-warning btn-block">
|
||||||
Preauth/Charge
|
Preauth/Charge
|
||||||
@@ -203,7 +209,9 @@ export default defineComponent({
|
|||||||
return status === 0 ? 'Approved' : 'Declined'
|
return status === 0 ? 'Approved' : 'Declined'
|
||||||
},
|
},
|
||||||
getSourceText(transaction: any) {
|
getSourceText(transaction: any) {
|
||||||
if (transaction.delivery_id) {
|
if (transaction.auto_id) {
|
||||||
|
return 'Automatic'
|
||||||
|
} else if (transaction.delivery_id) {
|
||||||
return 'Delivery - ' + transaction.delivery_id
|
return 'Delivery - ' + transaction.delivery_id
|
||||||
} else if (transaction.service_id) {
|
} else if (transaction.service_id) {
|
||||||
return 'Service - ' + transaction.service_id
|
return 'Service - ' + transaction.service_id
|
||||||
|
|||||||
Reference in New Issue
Block a user