Add Stripe payment integration and improve POS features
This commit introduces the ability to process payments via Stripe, including creating, retrieving, and deleting payment intents, and integrating this functionality into the department POS system. New UI components and logic were added to handle Stripe readers, tax computation, and payment flows. Additionally, enhancements were made to allow subscription handling in the product table and force editing of orders by superusers.
This commit is contained in:
@@ -122,6 +122,12 @@ const attributes = ref([
|
||||
prop: 'invoiceWithStripe',
|
||||
description: 'Når denne er sat, faktureres ordren med Stripe',
|
||||
icon: 'fas fa-cogs'
|
||||
},
|
||||
{
|
||||
name: 'Only tank cleaning',
|
||||
prop: 'onlyTankCleaning',
|
||||
description: 'Når denne er sat, bliver kunden kategoriseret som "Tank cleaning" kunde.',
|
||||
icon: 'fas fa-cogs'
|
||||
}
|
||||
]);
|
||||
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
<script setup>
|
||||
import { ref, defineProps } from 'vue';
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import { StripeModule } from "@/components/stripe/StripeModule.vue";
|
||||
|
||||
const props = defineProps({
|
||||
departmentId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: 'Næste',
|
||||
},
|
||||
order_id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
import { Colors } from "@/ThemeConfig.vue";
|
||||
|
||||
// Readers variables
|
||||
const readers = ref([]);
|
||||
const isLoading = ref(false);
|
||||
const error = ref(null);
|
||||
const isReady = ref(false);
|
||||
const selectedReader = ref(null);
|
||||
const isReaderSelected = ref(false);
|
||||
const isReadersLoading = ref(false);
|
||||
|
||||
const tax_rates = ref([
|
||||
{ id: 1, display_name: 'Dansk kunde', percentage: 25 },
|
||||
{ id: 2, display_name: 'EU kunde', percentage: 0 },
|
||||
]);
|
||||
|
||||
const selectedTaxRate = ref(tax_rates.value[0].id);
|
||||
|
||||
const getTaxRatePercentage = (taxRateId) => {
|
||||
const taxRate = tax_rates.value.find(rate => rate.id === taxRateId);
|
||||
return taxRate ? taxRate.percentage : 0;
|
||||
}
|
||||
|
||||
const isReaderConnected = (reader) => {
|
||||
return reader.status === 'online';
|
||||
};
|
||||
const isReaderAvailable = (reader) => {
|
||||
return (reader.action === null || reader.action === undefined || reader.action === "") && isReaderConnected(reader);
|
||||
};
|
||||
const getAvailableReaders = () => {
|
||||
return readers.value.filter(isReaderAvailable);
|
||||
}
|
||||
const attemptAutomaticReaderSelection = (readers) => {
|
||||
const availableReaders = readers.filter(isReaderAvailable);
|
||||
if (availableReaders.length > 0) {
|
||||
selectedReader.value = availableReaders[0];
|
||||
isReaderSelected.value = true;
|
||||
} else {
|
||||
selectedReader.value = null;
|
||||
isReaderSelected.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const getStripeReaders = async () => {
|
||||
isReadersLoading.value = true;
|
||||
SessionUser.request(
|
||||
'/modules/stripe/department/terminal/readers',
|
||||
'GET',
|
||||
{
|
||||
id: props.departmentId,
|
||||
},
|
||||
).then((response) => {
|
||||
console.log('Response from Stripe:', response.data.data.data);
|
||||
if (response.status === 200) {
|
||||
isReadersLoading.value = false;
|
||||
isReady.value = true;
|
||||
readers.value = response.data.data.data;
|
||||
// Attempt to automatically select a reader
|
||||
attemptAutomaticReaderSelection(readers.value);
|
||||
return response;
|
||||
} else {
|
||||
isReadersLoading.value = false;
|
||||
error.value = response.data.data.message;
|
||||
console.error('Error fetching readers:', response);
|
||||
}
|
||||
}).catch((error) => {
|
||||
isReadersLoading.value = false;
|
||||
error.value = error.message;
|
||||
console.error('Error fetching readers:', error);
|
||||
})
|
||||
}
|
||||
|
||||
const onClick = () => {
|
||||
console.log('Stripe payment initiated', selectedReader.value);
|
||||
isLoading.value = true;
|
||||
// Start the payment process with the selected reader
|
||||
StripeModule.paymentIntents.createPaymentIntent(selectedReader.value, props.order_id, getTaxRatePercentage(selectedTaxRate.value))
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
console.log('Payment intent created successfully:', response.data.data);
|
||||
// Load the payment intent
|
||||
StripeModule.paymentIntents.getPaymentIntent(props.order_id);
|
||||
} else {
|
||||
// Handle error
|
||||
error.value = response.data.message;
|
||||
console.error('Error creating payment intent:', response);
|
||||
}
|
||||
isLoading.value = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error creating payment intent:', error);
|
||||
// Handle error
|
||||
error.value = error.message;
|
||||
isLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
const isAnyReadersUnavailable = () => {
|
||||
return readers.value.some(reader => !isReaderAvailable(reader));
|
||||
}
|
||||
|
||||
const isAnyReadersAvailable = () => {
|
||||
return readers.value.some(reader => isReaderAvailable(reader));
|
||||
}
|
||||
|
||||
// This will return "Available", "Disconnected", "Busy", or "Unknown"
|
||||
const getReaderStatus = (reader) => {
|
||||
if (reader.status === 'online') {
|
||||
return 'Available';
|
||||
} else if (reader.status === 'offline') {
|
||||
return 'Disconnected';
|
||||
} else if (reader.status === 'busy') {
|
||||
return 'Busy';
|
||||
} else {
|
||||
return 'Unknown ( ' + reader.status + ' )';
|
||||
}
|
||||
}
|
||||
|
||||
getStripeReaders();
|
||||
StripeModule.paymentIntents.getPaymentIntent(props.order_id);
|
||||
// Check if the readers are available every 5 seconds
|
||||
setInterval(() => {
|
||||
if (isReady.value) {
|
||||
getStripeReaders();
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
// Reload the payment intent every 5 seconds
|
||||
setInterval(() => {
|
||||
if (isReady.value) {
|
||||
StripeModule.paymentIntents.getPaymentIntent(props.order_id);
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
const loadingDeleteButton = ref(false);
|
||||
|
||||
const setDeleteButtonLoading = (isLoading) => {
|
||||
console.log('Loading delete button:', isLoading);
|
||||
loadingDeleteButton.value = !!isLoading;
|
||||
}
|
||||
|
||||
const onClickCancelPaymentIntent = () => {
|
||||
console.log('Delete payment intent');
|
||||
loadingDeleteButton.value = true;
|
||||
StripeModule.paymentIntents.deletePaymentIntent(props.order_id)
|
||||
.then(async (response) => {
|
||||
if (response.status === 200) {
|
||||
console.log('Payment intent deleted successfully:', response.data.data);
|
||||
// Unset the payment intent
|
||||
StripeModule.paymentIntents.paymentIntent.value = null;
|
||||
// Reload the payment intent
|
||||
StripeModule.paymentIntents.getPaymentIntent(props.order_id).finally(() => {
|
||||
getStripeReaders().finally(() => {
|
||||
setDeleteButtonLoading(false);
|
||||
})
|
||||
});
|
||||
} else {
|
||||
console.error('Error deleting payment intent:', response);
|
||||
setDeleteButtonLoading(false);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error deleting payment intent:', error);
|
||||
setDeleteButtonLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<!-- Error state -->
|
||||
<template v-if="error">
|
||||
<button class="button is-danger" @click="getAvailableReaders">
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
</span>
|
||||
<span>{{ error }}</span>
|
||||
</button>
|
||||
</template>
|
||||
<!-- Ready state, no readers -->
|
||||
<template v-else-if="readers.length === 0 && isReady">
|
||||
<button class="button is-warning" @click="getAvailableReaders">
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
</span>
|
||||
<span>No readers available</span>
|
||||
</button>
|
||||
</template>
|
||||
<!-- Ready state, readers available -->
|
||||
<template v-else-if="readers.length > 0 && isReady && !isLoading && StripeModule.paymentIntents.paymentIntent.value === null">
|
||||
<button
|
||||
class="button"
|
||||
@click="onClick"
|
||||
:disabled="!isReaderSelected"
|
||||
:style="{ 'background-color': Colors.buttons.success.backgroundColor, 'color': Colors.buttons.success.textColor }"
|
||||
>
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-credit-card"></i>
|
||||
</span>
|
||||
<span>{{ props.label }}</span>
|
||||
</button>
|
||||
</template>
|
||||
<!-- Currently processing payment -->
|
||||
<template v-else-if="StripeModule.paymentIntents.paymentIntent.value !== null">
|
||||
<button class="button is-info" :class="{ 'is-loading': isLoading }">
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-credit-card"></i>
|
||||
</span>
|
||||
<span>Processing payment...</span>
|
||||
</button>
|
||||
</template>
|
||||
<!-- Default state, loading -->
|
||||
<template v-else-if="isLoading">
|
||||
<button class="button is-loading">
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-spinner"></i>
|
||||
</span>
|
||||
<span>Loading...</span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Reader selection -->
|
||||
<div class="column">
|
||||
<template v-if="isReady && readers.length > 0 && StripeModule.paymentIntents.paymentIntent.value === null">
|
||||
<div class="select">
|
||||
<select v-model="selectedReader">
|
||||
<option disabled value="">
|
||||
<template v-if="isAnyReadersAvailable()">Select a reader</template>
|
||||
<template v-else>No available readers</template>
|
||||
</option>
|
||||
<template v-for="reader of getAvailableReaders()" :key="reader.id">
|
||||
<option :value="reader" v-if="isReaderAvailable(reader)">
|
||||
{{ reader.label }} ({{ getReaderStatus(reader) }})
|
||||
</option>
|
||||
</template>
|
||||
<!-- Unavailable readers -->
|
||||
<template v-if="isAnyReadersUnavailable()">
|
||||
<option disabled>Unavailable Readers</option>
|
||||
<template v-for="reader of readers" :key="reader.id">
|
||||
<option :value="reader" v-if="!isReaderAvailable(reader)" :disabled="true">
|
||||
{{ reader.label }} ({{ getReaderStatus(reader) }})
|
||||
</option>
|
||||
</template>
|
||||
</template>
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
<!-- Currently processing payment -->
|
||||
<template v-else-if="StripeModule.paymentIntents.paymentIntent.value !== null">
|
||||
</template>
|
||||
<!-- No readers available -->
|
||||
<template v-else-if="readers.length === 0 && isReady">
|
||||
<div class="select">
|
||||
<select disabled>
|
||||
<option>No readers available</option>
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Select tax percentage -->
|
||||
<template v-if="StripeModule.paymentIntents.paymentIntent.value === null">
|
||||
<div class="column">
|
||||
<div class="select">
|
||||
<select v-model="selectedTaxRate">
|
||||
<template v-for="taxRate in tax_rates" :key="taxRate.id">
|
||||
<option :value="taxRate.id">
|
||||
{{ taxRate.display_name }} ({{ taxRate.percentage }}% moms)
|
||||
</option>
|
||||
</template>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- If the payment intent exists, show the delete button -->
|
||||
<div class="column">
|
||||
<template v-if="StripeModule.paymentIntents.paymentIntent.value !== null">
|
||||
<button
|
||||
class="button is-danger"
|
||||
:class="{ 'is-loading': loadingDeleteButton }"
|
||||
@click="onClickCancelPaymentIntent()"
|
||||
>
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-trash"></i>
|
||||
</span>
|
||||
<span>Delete Payment Intent</span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
{{ error }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -5,7 +5,7 @@ import PosLastScannedLicensePlates from "@/components/displays/department/pos/Po
|
||||
import NextStep from "@/components/forms/department/pos/buttons/NextStep.vue";
|
||||
import SelectProductsFormPOS from "@/components/forms/department/pos/SelectProductsFormPOS.vue";
|
||||
import NextStepError from "@/components/forms/department/pos/error/NextStepError.vue";
|
||||
import { order_items, order_id, loadOrderItems, isCustomerSelected, doesOrderContainMaterial } from "@/components/shop/POSDepartmentProcess.vue";
|
||||
import { order_items, order_id, loadOrderItems, isCustomerSelected, doesOrderContainMaterial, customer_id, department_id } from "@/components/shop/POSDepartmentProcess.vue";
|
||||
import PosOrderItemsCurrent from "@/components/displays/department/pos/PosOrderItemsCurrent.vue";
|
||||
import PosSelectedCustomer from "@/components/displays/department/pos/PosSelectedCustomer.vue";
|
||||
import Cancel from "@/components/forms/department/pos/buttons/Cancel.vue";
|
||||
@@ -13,6 +13,7 @@ import WhiteBox from "@/components/displays/boxes/WhiteBox.vue";
|
||||
import ButtonsBox from "@/components/displays/boxes/ButtonsBox.vue";
|
||||
import PreviousStep from "@/components/forms/department/pos/buttons/PreviousStep.vue";
|
||||
import PosOrderLicensePlates from "@/components/displays/department/pos/order/PosOrderLicensePlates.vue";
|
||||
import PayWithStripeButton from "@/components/displays/department/pos/displays/PayWithStripeButton.vue";
|
||||
|
||||
</script>
|
||||
|
||||
@@ -45,11 +46,23 @@ import PosOrderLicensePlates from "@/components/displays/department/pos/order/Po
|
||||
class="is-fullwidth"
|
||||
label="Annuller"
|
||||
/>
|
||||
<NextStep
|
||||
style="width: 25%;"
|
||||
class="is-fullwidth"
|
||||
label="Gennemfør"
|
||||
/>
|
||||
<template v-if="customer_id !== 999">
|
||||
<NextStep
|
||||
style="width: 25%;"
|
||||
class="is-fullwidth"
|
||||
label="Gennemfør"
|
||||
/>
|
||||
</template>
|
||||
<!-- Stripe customer -->
|
||||
<template v-else>
|
||||
<PayWithStripeButton
|
||||
style="width: 25%;"
|
||||
class="is-fullwidth"
|
||||
label="Betal med Stripe"
|
||||
:departmentId="department_id"
|
||||
:order_id="order_id"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</ButtonsBox>
|
||||
</div>
|
||||
|
||||
@@ -102,6 +102,7 @@ const getDisplayProductAddons = (product) => {
|
||||
<th>{{ SessionUser.objects.products.columns.name.label }}</th>
|
||||
<th>{{ SessionUser.objects.products.columns.description.label }}</th>
|
||||
<th>{{ SessionUser.objects.products.columns.price.label }}</th>
|
||||
<th>{{ SessionUser.objects.products.columns.subscription_allowed.label }}</th>
|
||||
<th>{{ SessionUser.objects.products.columns.category.label }}</th>
|
||||
<th>{{ SessionUser.objects.products.columns.piktogram.label }}</th>
|
||||
<th>{{ SessionUser.objects.products.columns.economic_product_id.label }}</th>
|
||||
@@ -135,6 +136,14 @@ const getDisplayProductAddons = (product) => {
|
||||
:editFunction="SessionUser.objects.products.showEditObjectFieldForm"
|
||||
column="price"
|
||||
/>
|
||||
<!-- Subscription allowed -->
|
||||
<EditableTableColumn
|
||||
:object="product"
|
||||
:loadList="loadList"
|
||||
:editFunction="SessionUser.objects.products.showEditObjectFieldForm"
|
||||
column="subscription_allowed"
|
||||
:parseFunction="value => value ? SessionUser.objects.global.language.yes : SessionUser.objects.global.language.no"
|
||||
/>
|
||||
<!-- Category -->
|
||||
<EditableTableColumn
|
||||
:object="product"
|
||||
|
||||
@@ -74,6 +74,14 @@ export const Products = {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
subscription_allowed: {
|
||||
label: "Abonnement tilladt",
|
||||
type: "boolean",
|
||||
sortable: true,
|
||||
creation: {
|
||||
required: false
|
||||
}
|
||||
},
|
||||
category: {
|
||||
label: "Kategori",
|
||||
type: "select",
|
||||
@@ -156,6 +164,14 @@ export const Products = {
|
||||
ObjectsGlobal.parse.number(price)
|
||||
)
|
||||
},
|
||||
subscription_allowed: async (id, subscription_allowed) => {
|
||||
return ObjectsGlobal.set.column(
|
||||
Products.meta.endpoint,
|
||||
id,
|
||||
"subscription_allowed",
|
||||
ObjectsGlobal.parse.boolean(subscription_allowed)
|
||||
)
|
||||
},
|
||||
category: async (id, category) => {
|
||||
return ObjectsGlobal.set.column(
|
||||
Products.meta.endpoint,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import { StripePaymentIntent } from "@/components/stripe/endpoints/PaymentIntents.vue";
|
||||
|
||||
export const StripeModule = {
|
||||
paymentIntents: StripePaymentIntent,
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,86 @@
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import { StripeModule } from "@/components/stripe/StripeModule.vue";
|
||||
|
||||
const paymentIntent = ref(null);
|
||||
|
||||
// Create a payment intent
|
||||
const createPaymentIntent = async (reader, order_id, tax_percentage = 0) => {
|
||||
SessionUser.request(
|
||||
'/orders/module/stripe/payment_intent',
|
||||
'POST',
|
||||
{
|
||||
id: order_id,
|
||||
reader: reader.id,
|
||||
tax_percentage: tax_percentage,
|
||||
},
|
||||
).then((response) => {
|
||||
console.log('Response from Stripe:', response.data.data);
|
||||
if (response.status === 200) {
|
||||
// Get the payment intent
|
||||
StripeModule.paymentIntents.getPaymentIntent(order_id);
|
||||
return response;
|
||||
} else {
|
||||
console.error('Error creating payment intent:', response);
|
||||
return null;
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error('Error creating payment intent:', error);
|
||||
})
|
||||
}
|
||||
|
||||
// Get the payment intent
|
||||
const getPaymentIntent = async (order_id) => {
|
||||
SessionUser.request(
|
||||
'/orders/module/stripe/payment_intent',
|
||||
'GET',
|
||||
{
|
||||
id: order_id,
|
||||
},
|
||||
).then((response) => {
|
||||
console.log('Response from Stripe:', response.data.data);
|
||||
if (response.status === 200) {
|
||||
paymentIntent.value = response.data.data;
|
||||
return response;
|
||||
} else {
|
||||
console.error('Error getting payment intent:', response);
|
||||
return null;
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error('Error getting payment intent:', error);
|
||||
// Unset the payment intent if an error occurs
|
||||
paymentIntent.value = null;
|
||||
})
|
||||
}
|
||||
|
||||
// Delete the payment intent
|
||||
const deletePaymentIntent = async (order_id) => {
|
||||
SessionUser.request(
|
||||
'/orders/module/stripe/payment_intent',
|
||||
'DELETE',
|
||||
{
|
||||
id: order_id,
|
||||
},
|
||||
).then((response) => {
|
||||
console.log('Response from Stripe:', response.data.data);
|
||||
if (response.status === 200) {
|
||||
paymentIntent.value = null;
|
||||
return response;
|
||||
} else {
|
||||
console.error('Error deleting payment intent:', response);
|
||||
return null;
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error('Error deleting payment intent:', error);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export const StripePaymentIntent = {
|
||||
createPaymentIntent,
|
||||
getPaymentIntent,
|
||||
deletePaymentIntent,
|
||||
paymentIntent,
|
||||
}
|
||||
</script>
|
||||
@@ -133,8 +133,13 @@ const isStripeInvoicePaid = () => {
|
||||
// Load the order, when the page is loaded
|
||||
loadOrder();
|
||||
|
||||
const forceAllowEdit = ref(false);
|
||||
|
||||
// Make sure the order isn't invoiced, and the user has permission to edit the order items
|
||||
const isEditPossible = () => {
|
||||
if (forceAllowEdit.value) {
|
||||
return true;
|
||||
}
|
||||
return !isInvoicedWithEconomic() && !isInvoicedWithStripe() && SessionUser.hasPermission('edit_order_items') && !closed_at.value;
|
||||
};
|
||||
|
||||
@@ -271,12 +276,18 @@ const isCompleted = () => {
|
||||
<!-- Other slot -->
|
||||
<template #other>
|
||||
<!-- Super user actions -->
|
||||
<span v-if="SessionUser.canAccessSuperUser()">
|
||||
<span v-if="SessionUser.canAccessSuperUser()" class="buttons">
|
||||
<!-- Delete order button -->
|
||||
<button class="button is-danger is-inverted" @click="showDeleteOrderDialog" v-if="(!isInvoiced() || SessionUser.canAccessSuperUser())">
|
||||
<span class="icon"><i class="fas fa-trash"></i></span>
|
||||
<span>Slet ordre</span>
|
||||
</button>
|
||||
<!-- Force allow edit button -->
|
||||
<button class="button is-danger is-inverted" @click="forceAllowEdit = !forceAllowEdit" v-if="!isInvoiced()">
|
||||
<span class="icon"><i class="fas fa-lock-open"></i></span>
|
||||
<span v-if="!isEditPossible()">Gennemtving redigeringsret</span>
|
||||
<span v-else>Fjern tvungen redigeringsret</span>
|
||||
</button>
|
||||
</span>
|
||||
</template>
|
||||
<!-- Details slot -->
|
||||
|
||||
+4
-1
@@ -19,6 +19,8 @@ import UserOtherVaskeabonnement
|
||||
import OrdersPagination from "@/components/displays/pagination/models/DepartmentPos/OrdersPagination.vue";
|
||||
import CollectedOrderInvoiceCustomerNotes
|
||||
from "@/views/dashboards/superUserDashboard/collectedOrderInvoice/displays/collectedOrderInvoiceCustomerNotes.vue";
|
||||
import InvoiceOrdersPagination
|
||||
from "@/components/displays/pagination/models/SuperUserDashboard/InvoiceOrdersPagination.vue";
|
||||
|
||||
// Get the id from the URL
|
||||
const router = useRouter();
|
||||
@@ -89,7 +91,8 @@ getCollectedOrderInvoice();
|
||||
v-bind:collectedOrderInvoice="collectedOrderInvoice"
|
||||
/>
|
||||
<!-- Orders -->
|
||||
<OrdersPagination :auto-load="true" :onlyFromInvoiceCollection="collectedOrderInvoiceId"/>
|
||||
<InvoiceOrdersPagination :auto-load="true" :onlyFromInvoiceCollection="collectedOrderInvoiceId" :invoice-view="true"/>
|
||||
<!-- <OrdersPagination :auto-load="true" :onlyFromInvoiceCollection="collectedOrderInvoiceId" /> -->
|
||||
</template>
|
||||
</NotFoundFallBackPageWrapper>
|
||||
</RestrictedPageWrapper>
|
||||
|
||||
Reference in New Issue
Block a user