Add POS order synchronization components, including sync columns, tables, pagination, and dynamic filtering options
This commit is contained in:
@@ -116,6 +116,10 @@ const props = defineProps({
|
||||
*/
|
||||
]
|
||||
})
|
||||
},
|
||||
is_button_hover_effect: {
|
||||
type: Boolean,
|
||||
default: false // If true, the button will have a hover effect
|
||||
}
|
||||
});
|
||||
|
||||
@@ -201,7 +205,7 @@ const label_shortened = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="color-indicator" @click="onClick" @mouseover="onHover" @mouseleave="onLeave">
|
||||
<div class="color-indicator" @click="onClick" @mouseover="onHover" @mouseleave="onLeave" v-bind:class="{ 'is-button-hover-effect': props.is_button_hover_effect }">
|
||||
<span class="icon-text is-flex-wrap-nowrap">
|
||||
<span class="icon" v-show="visibility.icon">
|
||||
<!-- Loading -->
|
||||
@@ -346,4 +350,12 @@ const label_shortened = computed(() => {
|
||||
.no-underline-text {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.is-button-hover-effect {
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
.is-button-hover-effect:hover {
|
||||
background-color: var(--button-hover-bg-color, #f5f5f5);
|
||||
color: var(--button-hover-text-color, #363636);
|
||||
}
|
||||
</style>
|
||||
@@ -338,7 +338,7 @@ const selectAllInvoiceCollections = () => {
|
||||
|
||||
const getUniqueInvoiceCollections = () => {
|
||||
// Get a list of unique invoice collections from the orders
|
||||
return [...new Set(props.orders.map(order => order.invoice_collection_id))];
|
||||
return [...new Set(props.objects.map(order => order.invoice_collection_id))];
|
||||
};
|
||||
|
||||
const invoiceCollectionQueueInProgress = ref([]);
|
||||
@@ -414,7 +414,7 @@ const processNextInvoiceCollection = () => {
|
||||
|
||||
const getFirstInvoiceWithCollectionId = (invoiceCollectionId) => {
|
||||
// Get the first invoice with the given collection id
|
||||
return props.orders.find(order => order.invoice_collection_id === invoiceCollectionId);
|
||||
return props.objects.find(order => order.invoice_collection_id === invoiceCollectionId);
|
||||
};
|
||||
|
||||
const processInvoiceCollection = (invoiceCollectionId) => {
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<script setup lang="ts">
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import WhiteBox from "@/components/displays/boxes/WhiteBox.vue";
|
||||
import PosOrdersSyncColumn from "@/components/displays/department/pos/sync/displays/PosOrdersSyncColumn.vue";
|
||||
import ColorIndicator from "@/components/displays/buttons/ColorIndicator.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="columns is-vcentered">
|
||||
<!-- Potential duplicates -->
|
||||
<PosOrdersSyncColumn
|
||||
:title="SessionUser.objects.global.language.warnings"
|
||||
icon="fas fa-exclamation-triangle"
|
||||
iconColor="has-text-warning"
|
||||
>
|
||||
<template #default>
|
||||
Test
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="card-footer-item has-text-centered">
|
||||
<color-indicator
|
||||
v-bind:label="{ text: SessionUser.objects.global.language.warnings, classes: [], max_length: 20 }"
|
||||
v-bind:visibility="{ dropdown: false, icon: true }"
|
||||
v-bind:color_class="'has-text-warning'"
|
||||
v-bind:is_button_hover_effect="true"
|
||||
style="width: 100%; text-align: center;"
|
||||
class="p-2 is-clickable"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</PosOrdersSyncColumn>
|
||||
<!-- Successful Syncs -->
|
||||
<PosOrdersSyncColumn
|
||||
:title="SessionUser.objects.global.language.successful_syncs"
|
||||
icon="fas fa-check-circle"
|
||||
iconColor="has-text-success"
|
||||
>
|
||||
<template #default>
|
||||
Test
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="card-footer-item has-text-centered">
|
||||
<color-indicator
|
||||
v-bind:label="{ text: SessionUser.objects.orders.meta.title, classes: [], max_length: 20 }"
|
||||
v-bind:visibility="{ dropdown: false, icon: true }"
|
||||
v-bind:color_class="'has-text-success'"
|
||||
v-bind:is_button_hover_effect="true"
|
||||
style="width: 100%; text-align: center;"
|
||||
class="p-2 is-clickable"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</PosOrdersSyncColumn>
|
||||
<!-- Customers applicable -->
|
||||
<PosOrdersSyncColumn
|
||||
:title="SessionUser.objects.global.language.customers"
|
||||
icon="fas fa-users"
|
||||
iconColor="has-text-info"
|
||||
>
|
||||
<template #default>
|
||||
Test
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="card-footer-item has-text-centered">
|
||||
<color-indicator
|
||||
v-bind:label="{ text: SessionUser.objects.global.language.customers, classes: [], max_length: 20 }"
|
||||
v-bind:visibility="{ dropdown: false, icon: true }"
|
||||
v-bind:color_class="'has-text-info'"
|
||||
v-bind:is_button_hover_effect="true"
|
||||
style="width: 100%; text-align: center;"
|
||||
class="p-2 is-clickable"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</PosOrdersSyncColumn>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,63 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits } from "vue";
|
||||
import WhiteBox from "@/components/displays/boxes/WhiteBox.vue";
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: "Unnamed"
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: "fas fa-box"
|
||||
},
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: "has-text-info"
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="column">
|
||||
<WhiteBox class="card p-0">
|
||||
<div class="card-header">
|
||||
<div class="card-header-icon">
|
||||
<span class="icon" :class="props.iconColor">
|
||||
<i :class="props.icon"></i>
|
||||
</span>
|
||||
</div>
|
||||
<h2 class="card-header-title">
|
||||
{{ props.title }}
|
||||
</h2>
|
||||
<button class="card-header-icon" aria-label="more options">
|
||||
<span class="icon">
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<slot name="footer"></slot>
|
||||
<!--
|
||||
<span class="card-footer-item has-text-centered">
|
||||
<strong>Footer</strong>
|
||||
</span>
|
||||
<span class="card-footer-item has-text-centered">
|
||||
<a href="#" class="has-text-info">Link</a>
|
||||
</span>
|
||||
<span class="card-footer-item has-text-centered">
|
||||
<a href="#" class="has-text-info">Another link</a>
|
||||
</span>
|
||||
-->
|
||||
</footer>
|
||||
</WhiteBox>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,181 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
objects: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
allowSelectMultiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
import {ref, watch} from 'vue';
|
||||
import { departments, getDepartments, isLoading, getDepartmentName} from "@/components/pagination/departmentTabs.vue";
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import {orderBy, orderDirection, setOrder, loadList} from "@/components/pagination/paginatedList.vue";
|
||||
|
||||
|
||||
const redirectDepartmentOrderPage = (orderId, departmentId) => {
|
||||
// Send the user to the order page (In a new tab)
|
||||
// `/admin/${departmentId}/modules/pos/orders/${orderId}`
|
||||
window.open(`/admin/${departmentId}/modules/pos/orders/${orderId}`, '_blank');
|
||||
};
|
||||
|
||||
const redirectSuperUserInvoiceCollectionPage = (invoiceCollectionId) => {
|
||||
// Send the user to the invoice collection page (In a new tab)
|
||||
window.open(`/superuser/invoices/${invoiceCollectionId}`, '_blank');
|
||||
};
|
||||
|
||||
// Get the departments (If the departments are not already loaded)
|
||||
if (departments.value.length === 0) {
|
||||
getDepartments();
|
||||
}
|
||||
|
||||
const clickUser = (customer_id) => {
|
||||
if (!SessionUser.canAccessSuperUser()) {
|
||||
return;
|
||||
}
|
||||
// Send the user to the user page
|
||||
SessionUser.adminUser.customers.fromCustomerNumber.getUserId(customer_id).then(response => window.open('/superuser/users/' + response.data.data.user_id, '_blank'));
|
||||
};
|
||||
|
||||
const isColumnCurrentlyBeingSortedBy = (column) => {
|
||||
// Check if the column is currently being sorted by
|
||||
return orderBy.value === column;
|
||||
};
|
||||
|
||||
const isCurrentSortDirectionAscending = () => {
|
||||
// Check if the current sort direction is ascending
|
||||
return orderDirection.value === 'asc';
|
||||
};
|
||||
|
||||
const isCurrentSortDirectionDescending = () => {
|
||||
// Check if the current sort direction is descending
|
||||
return orderDirection.value === 'desc';
|
||||
};
|
||||
|
||||
const onTableHeaderClick = (column) => {
|
||||
// Check if the column is already sorted
|
||||
if (orderBy.value === column) {
|
||||
// If the column is already sorted, toggle the direction
|
||||
const direction = orderDirection.value === 'asc' ? 'desc' : 'asc';
|
||||
setOrder(
|
||||
column,
|
||||
direction
|
||||
);
|
||||
} else {
|
||||
// If the column is not sorted, set the order to the new column and default to ascending
|
||||
const direction = 'asc';
|
||||
setOrder(
|
||||
column,
|
||||
direction
|
||||
);
|
||||
}
|
||||
// Load the list
|
||||
loadList();
|
||||
};
|
||||
|
||||
const tableHeaders = ref([
|
||||
{
|
||||
name: 'id',
|
||||
title: 'Id',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'department',
|
||||
title: 'Afdeling',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'customer_number',
|
||||
title: SessionUser.objects.global.language.customer,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'order_id',
|
||||
title: SessionUser.functions.ucFirst(SessionUser.objects.orders.meta.labels.single),
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'created_at',
|
||||
title: 'Dato',
|
||||
sortable: true,
|
||||
},
|
||||
]);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table class="table is-fullwidth is-hoverable">
|
||||
<thead>
|
||||
<tr>
|
||||
<template v-for="tableHeaders in tableHeaders" :key="tableHeaders">
|
||||
<th
|
||||
v-if="tableHeaders.sortable"
|
||||
:class="{
|
||||
'is-clickable': tableHeaders.sortable,
|
||||
'has-text-centered': tableHeaders.name === 'id'
|
||||
}"
|
||||
@click="onTableHeaderClick(tableHeaders.name)"
|
||||
>
|
||||
<span class="is-flex-wrap-nowrap">
|
||||
<span class="has-text-weight-bold">{{ tableHeaders.title }}</span>
|
||||
<span
|
||||
v-if="isColumnCurrentlyBeingSortedBy(tableHeaders.name)"
|
||||
:class="{
|
||||
'fas fa-sort-up': isCurrentSortDirectionAscending(),
|
||||
'fas fa-sort-down': isCurrentSortDirectionDescending()
|
||||
}"
|
||||
class="icon is-small"
|
||||
/>
|
||||
</span>
|
||||
</th>
|
||||
<th
|
||||
v-else
|
||||
:class="{
|
||||
'has-text-centered': tableHeaders.name === 'id'
|
||||
}"
|
||||
>
|
||||
<span class="is-flex-wrap-nowrap">
|
||||
<span class="has-text-weight-bold">{{ tableHeaders.title }}</span>
|
||||
</span>
|
||||
</th>
|
||||
</template>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="object in objects" :key="object.id">
|
||||
<tr>
|
||||
<!-- Id -->
|
||||
<td class="has-text-centered">
|
||||
{{object.id}}
|
||||
</td>
|
||||
<!-- Department -->
|
||||
<td>
|
||||
{{ getDepartmentName(object.department) }}
|
||||
</td>
|
||||
<!-- Customer Number -->
|
||||
<td>
|
||||
{{ object.customer_number }}
|
||||
</td>
|
||||
<!-- Order Id -->
|
||||
<td>
|
||||
{{ object.order_id }}
|
||||
</td>
|
||||
<!-- Created At -->
|
||||
<td>
|
||||
{{ object.created_at }}
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="100%">Viser {{ objects.length }} transaktioner</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,929 @@
|
||||
<script setup>
|
||||
|
||||
import ActionSettingsWheelButton from "@/components/displays/buttons/ActionSettingsWheelButton.vue";
|
||||
|
||||
const props = defineProps({
|
||||
orders: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
invoiceView: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
allowSelectMultiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isCustomerView: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
});
|
||||
import {ref, watch} from 'vue';
|
||||
import { departments, getDepartments, isLoading, getDepartmentName} from "@/components/pagination/departmentTabs.vue";
|
||||
import {showPopper, removePopperIfOpen, popperBox} from "@/components/displays/PopperDefault.vue";
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import {orderBy, orderDirection, setOrder, loadList} from "@/components/pagination/paginatedList.vue";
|
||||
import ActionSettingsWheelItem from "@/components/displays/buttons/ActionSettingsWheelItem.vue";
|
||||
import OrderContentTable from "@/components/displays/superuser/tables/OrderContentTable.vue";
|
||||
import InvoiceMultipleCollectionsModal from "@/components/displays/modals/InvoiceMultipleCollectionsModal.vue";
|
||||
import Swal from "sweetalert2";
|
||||
import ColorIndicator from "@/components/displays/buttons/ColorIndicator.vue";
|
||||
|
||||
|
||||
const redirectDepartmentOrderPage = (orderId, departmentId) => {
|
||||
// Send the user to the order page (In a new tab)
|
||||
// `/admin/${departmentId}/modules/pos/orders/${orderId}`
|
||||
window.open(`/admin/${departmentId}/modules/pos/orders/${orderId}`, '_blank');
|
||||
};
|
||||
|
||||
const redirectSuperUserInvoiceCollectionPage = (invoiceCollectionId) => {
|
||||
// Send the user to the invoice collection page (In a new tab)
|
||||
window.open(`/superuser/invoices/${invoiceCollectionId}`, '_blank');
|
||||
};
|
||||
|
||||
// Get the departments (If the departments are not already loaded)
|
||||
if (departments.value.length === 0) {
|
||||
getDepartments();
|
||||
}
|
||||
|
||||
const isOrderInvoicedWithEconomic = (order) => {
|
||||
return order.economic_invoice_module.invoice_id !== null || order.economic_invoice_module.invoice_draft_id !== null;
|
||||
};
|
||||
|
||||
const isOrderEconomicInvoiceBooked = (order) => {
|
||||
return order.economic_invoice_module.invoice_id !== null;
|
||||
};
|
||||
|
||||
const isOrderInvoicedWithStripe = (order) => {
|
||||
return order.stripe_invoice_module !== undefined;
|
||||
};
|
||||
|
||||
const isOrderInvoiced = (order) => {
|
||||
return isOrderInvoicedWithEconomic(order) || isOrderInvoicedWithStripe(order);
|
||||
};
|
||||
|
||||
const doesOrderHaveInvoiceCollection = (order) => {
|
||||
return order.invoice_collection !== undefined && order.invoice_collection !== null;
|
||||
};
|
||||
|
||||
const isOrderInvoiceCollectionClosed = (order) => {
|
||||
return order.invoice_collection.closed_at !== null;
|
||||
};
|
||||
|
||||
const isOrderInvoiceCollectionBooked = (order) => {
|
||||
return order.invoice_collection.booked_invoice_id !== null;
|
||||
};
|
||||
|
||||
const isOrderStripeInvoicePaid = (order) => {
|
||||
if (order.stripe_invoice_module) {
|
||||
return order.stripe_invoice_module.paid;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const doesOrderHaveErrorMessage = (order) => {
|
||||
return order.error_message !== null && order.error_message !== undefined;
|
||||
};
|
||||
|
||||
const getOrderInvoiceStatusBarColor = (order) => {
|
||||
if (order.economic_invoice_module) {
|
||||
// Check the invoice status
|
||||
var color_class = 'has-text-danger';
|
||||
if (isOrderInvoicedWithStripe(order)) {
|
||||
isOrderStripeInvoicePaid(order)
|
||||
? color_class = 'has-text-success'
|
||||
: color_class = 'has-text-warning';
|
||||
}
|
||||
if (isOrderInvoicedWithEconomic(order)) {
|
||||
isOrderEconomicInvoiceBooked(order)
|
||||
? color_class = 'has-text-success'
|
||||
: color_class = 'has-text-warning';
|
||||
}
|
||||
if (doesOrderHaveInvoiceCollection(order)) {
|
||||
color_class = 'has-text-grey';
|
||||
// Check if the invoice collection is closed
|
||||
if (isOrderInvoiceCollectionClosed(order)) {
|
||||
color_class = 'has-text-warning';
|
||||
// Check if the invoice collection is booked
|
||||
if (isOrderInvoiceCollectionBooked(order)) {
|
||||
color_class = 'has-text-success';
|
||||
}
|
||||
}
|
||||
}
|
||||
if (doesOrderHaveErrorMessage(order)) {
|
||||
color_class = 'has-text-danger';
|
||||
}
|
||||
// Return the color class
|
||||
return color_class;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const getOrderInvoiceStatusContent = (order) => {
|
||||
const content = [];
|
||||
if (isOrderInvoiced(order)) {
|
||||
// If the order is invoiced with e-conomic, show the invoice id
|
||||
if (isOrderInvoicedWithEconomic(order)) {
|
||||
isOrderEconomicInvoiceBooked(order)
|
||||
? content.push('Bogført i e-conomic')
|
||||
: content.push('Gemt som kladde i e-conomic');
|
||||
isOrderEconomicInvoiceBooked(order)
|
||||
? content.push('Faktura ID: <strong>' + order.economic_invoice_module.invoice_id + '</strong>')
|
||||
: content.push('Faktura Kladde ID: <strong>' + order.economic_invoice_module.invoice_draft_id + '</strong>');
|
||||
}
|
||||
// If the order is invoiced with stripe, show the invoice id
|
||||
if (isOrderInvoicedWithStripe(order)) {
|
||||
isOrderStripeInvoicePaid(order)
|
||||
? content.push('Fakturaen er <strong>betalt</strong>')
|
||||
: content.push('Fakturaen er <strong>ikke betalt</strong>');
|
||||
content.push('Faktura ID: <strong>' + order.stripe_invoice_module.id + '</strong>');
|
||||
}
|
||||
content.push(''); // Add a line break
|
||||
isOrderInvoicedWithEconomic(order)
|
||||
? content.push('<span class="has-text-grey">Faktureret med e-conomic</span>')
|
||||
: content.push('<span class="has-text-grey">Faktureret med stripe</span>');
|
||||
} else {
|
||||
// Check if the order has an invoice collection
|
||||
if (doesOrderHaveInvoiceCollection(order)) {
|
||||
// Check if the invoice collection is closed
|
||||
if (isOrderInvoiceCollectionClosed(order)) {
|
||||
content.push('Faktura samlingen er <strong>lukket</strong>');
|
||||
// Check if the invoice collection is booked
|
||||
if (isOrderInvoiceCollectionBooked(order)) {
|
||||
content.push('Faktura samlingen er <strong>bogført</strong>');
|
||||
}
|
||||
}
|
||||
content.push(''); // Add a line break
|
||||
content.push('Faktura samling ID: <strong>' + order.invoice_collection.id + '</strong>');
|
||||
// Add the Error message if it exists
|
||||
if (doesOrderHaveErrorMessage(order)) {
|
||||
content.push('Fejl: <strong class="has-text-danger">' + order.error_message + '</strong>');
|
||||
}
|
||||
} else {
|
||||
content.push(''); // Add a line break
|
||||
content.push('<strong class="has-text-grey">Transaktionen er ikke faktureret</strong>');
|
||||
}
|
||||
}
|
||||
// Return the list of content as a string, with line breaks between each item
|
||||
return content.join('<br>');
|
||||
};
|
||||
|
||||
const getOrderInvoiceStatusTitle = (order) => {
|
||||
return isOrderInvoiced(order) ? 'Faktura status' : 'Transaktion status';
|
||||
};
|
||||
|
||||
const clickUser = (customer_id) => {
|
||||
if (!SessionUser.canAccessSuperUser()) {
|
||||
return;
|
||||
}
|
||||
// Send the user to the user page
|
||||
SessionUser.adminUser.customers.fromCustomerNumber.getUserId(customer_id).then(response => window.open('/superuser/users/' + response.data.data.user_id, '_blank'));
|
||||
};
|
||||
|
||||
const isColumnCurrentlyBeingSortedBy = (column) => {
|
||||
// Check if the column is currently being sorted by
|
||||
return orderBy.value === column;
|
||||
};
|
||||
|
||||
const isCurrentSortDirectionAscending = () => {
|
||||
// Check if the current sort direction is ascending
|
||||
return orderDirection.value === 'asc';
|
||||
};
|
||||
|
||||
const isCurrentSortDirectionDescending = () => {
|
||||
// Check if the current sort direction is descending
|
||||
return orderDirection.value === 'desc';
|
||||
};
|
||||
|
||||
const onTableHeaderClick = (column) => {
|
||||
// Check if the column is already sorted
|
||||
if (orderBy.value === column) {
|
||||
// If the column is already sorted, toggle the direction
|
||||
const direction = orderDirection.value === 'asc' ? 'desc' : 'asc';
|
||||
setOrder(
|
||||
column,
|
||||
direction
|
||||
);
|
||||
} else {
|
||||
// If the column is not sorted, set the order to the new column and default to ascending
|
||||
const direction = 'asc';
|
||||
setOrder(
|
||||
column,
|
||||
direction
|
||||
);
|
||||
}
|
||||
// Load the list
|
||||
loadList();
|
||||
};
|
||||
|
||||
const tableHeaders = ref([
|
||||
{
|
||||
name: 'id',
|
||||
title: 'Id',
|
||||
sortable: true,
|
||||
},
|
||||
...(props.isCustomerView) ? [] : [{
|
||||
name: 'customer_id',
|
||||
title: 'Kunde',
|
||||
sortable: true,
|
||||
}],
|
||||
...(props.isCustomerView) ? [] : [{
|
||||
name: 'cashier_id',
|
||||
title: 'Kassør',
|
||||
sortable: true,
|
||||
}],
|
||||
{
|
||||
name: 'department_id',
|
||||
title: 'Afdeling',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'reg_1',
|
||||
title: 'Reg. 1',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'reg_2',
|
||||
title: 'Reg. 2',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'reg_3',
|
||||
title: 'Reg. 3',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'reference',
|
||||
title: 'Reference',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'notes',
|
||||
title: 'Noter',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'created_at',
|
||||
title: 'Dato',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'total_net_amount',
|
||||
title: 'Total',
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
name: 'completed_at',
|
||||
title: 'Gennemført',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
title: 'Actions',
|
||||
sortable: false,
|
||||
},
|
||||
]);
|
||||
|
||||
const active_dropdown_object_ids = ref([]);
|
||||
const toggleDropdown = (object) => {
|
||||
// Check if the object is already in the active dropdown list
|
||||
if (active_dropdown_object_ids.value.includes(object.id)) {
|
||||
// If it is, remove it from the list
|
||||
active_dropdown_object_ids.value = active_dropdown_object_ids.value.filter(id => id !== object.id);
|
||||
} else {
|
||||
// If it is not, add it to the list
|
||||
active_dropdown_object_ids.value.push(object.id);
|
||||
}
|
||||
};
|
||||
const isDropdownActive = (object) => {
|
||||
// Check if the object is in the active dropdown list
|
||||
return active_dropdown_object_ids.value.includes(object.id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Invoice collection selection
|
||||
*/
|
||||
const selectedInvoiceCollections = ref([]);
|
||||
const toggleInvoiceCollectionSelection = (invoiceCollectionId) => {
|
||||
// Check if the invoice collection is already selected
|
||||
if (selectedInvoiceCollections.value.includes(invoiceCollectionId)) {
|
||||
// If it is, remove it from the list
|
||||
selectedInvoiceCollections.value = selectedInvoiceCollections.value.filter(id => id !== invoiceCollectionId);
|
||||
} else {
|
||||
// If it is not, add it to the list
|
||||
selectedInvoiceCollections.value.push(invoiceCollectionId);
|
||||
}
|
||||
};
|
||||
|
||||
const isInvoiceCollectionSelected = (invoiceCollectionId) => {
|
||||
// Check if the invoice collection is in the selected list
|
||||
return selectedInvoiceCollections.value.includes(invoiceCollectionId);
|
||||
};
|
||||
const isInvoiceCollectionSelectedAll = () => {
|
||||
// Check if all invoice collections are selected
|
||||
return selectedInvoiceCollections.value.length === getUniqueInvoiceCollections().length;
|
||||
};
|
||||
const selectAllInvoiceCollections = () => {
|
||||
// Check if all invoice collections are selected
|
||||
if (isInvoiceCollectionSelectedAll()) {
|
||||
// If they are, deselect all
|
||||
selectedInvoiceCollections.value = [];
|
||||
} else {
|
||||
// If they are not, select all
|
||||
selectedInvoiceCollections.value = getUniqueInvoiceCollections();
|
||||
}
|
||||
};
|
||||
|
||||
const getUniqueInvoiceCollections = () => {
|
||||
// Get a list of unique invoice collections from the orders
|
||||
return [...new Set(props.objects.map(order => order.invoice_collection_id))];
|
||||
};
|
||||
|
||||
const invoiceCollectionQueueInProgress = ref([]);
|
||||
const invoiceCollectionQueueFailed = ref([]);
|
||||
const invoiceCollectionQueueSuccess = ref([]);
|
||||
const invoiceCollectionQueue = ref([]);
|
||||
const invoiceCollectionMaxConnections = 2;
|
||||
const isInvoiceMultipleCollectionsModalOpen = ref(false);
|
||||
|
||||
const invoiceSelectedCollections = () => {
|
||||
// Open the invoice multiple collections modal
|
||||
isInvoiceMultipleCollectionsModalOpen.value = true;
|
||||
// Check if any invoice collections are selected
|
||||
if (selectedInvoiceCollections.value.length > 0) {
|
||||
// If they are, show a message
|
||||
console.log('Selected invoice collections: ' + selectedInvoiceCollections.value);
|
||||
for (const invoiceCollectionId of selectedInvoiceCollections.value) {
|
||||
// Log the invoice collection id
|
||||
console.log('Invoice collection id: ', invoiceCollectionId);
|
||||
// Add the invoice collection to the queue
|
||||
addInvoiceCollectionToQueue(invoiceCollectionId);
|
||||
}
|
||||
// Process the invoice collection queue
|
||||
processInvoiceCollectionQueue();
|
||||
} else {
|
||||
// If they are not, show a message
|
||||
console.log('No invoice collections selected');
|
||||
}
|
||||
};
|
||||
|
||||
const addInvoiceCollectionToQueue = (invoiceCollectionId) => {
|
||||
// Check if the invoice collection is already in the queue
|
||||
if (invoiceCollectionQueue.value.includes(invoiceCollectionId)) {
|
||||
// If it is, remove it from the queue
|
||||
invoiceCollectionQueue.value = invoiceCollectionQueue.value.filter(id => id !== invoiceCollectionId);
|
||||
} else {
|
||||
// If it is not, add it to the queue
|
||||
invoiceCollectionQueue.value.push(invoiceCollectionId);
|
||||
}
|
||||
};
|
||||
|
||||
const processInvoiceCollectionQueue = () => {
|
||||
// Check if the queue is empty
|
||||
if (invoiceCollectionQueue.value.length === 0) {
|
||||
// If it is, show a message
|
||||
console.log('Invoice collection queue is empty');
|
||||
return;
|
||||
}
|
||||
// Check if the max connections are reached
|
||||
if (invoiceCollectionQueueInProgress.value.length >= invoiceCollectionMaxConnections) {
|
||||
// If they are, show a message
|
||||
console.log('Max connections reached');
|
||||
return;
|
||||
}
|
||||
while (invoiceCollectionQueueInProgress.value.length < invoiceCollectionMaxConnections && invoiceCollectionQueue.value.length > 0) {
|
||||
// Process the next invoice collection
|
||||
processNextInvoiceCollection();
|
||||
}
|
||||
};
|
||||
|
||||
const processNextInvoiceCollection = () => {
|
||||
// Check if the queue is empty
|
||||
if (invoiceCollectionQueue.value.length === 0) {
|
||||
// If it is, show a message
|
||||
console.log('Invoice collection queue is empty');
|
||||
return;
|
||||
}
|
||||
// Get the next invoice collection from the queue
|
||||
const invoiceCollectionId = invoiceCollectionQueue.value.shift();
|
||||
// Process the invoice collection
|
||||
processInvoiceCollection(invoiceCollectionId);
|
||||
};
|
||||
|
||||
const getFirstInvoiceWithCollectionId = (invoiceCollectionId) => {
|
||||
// Get the first invoice with the given collection id
|
||||
return props.objects.find(order => order.invoice_collection_id === invoiceCollectionId);
|
||||
};
|
||||
|
||||
const processInvoiceCollection = (invoiceCollectionId) => {
|
||||
// Check if the invoice collection is already in progress
|
||||
if (invoiceCollectionQueueInProgress.value.includes(invoiceCollectionId)) {
|
||||
// If it is, remove it from the in progress list
|
||||
invoiceCollectionQueueInProgress.value = invoiceCollectionQueueInProgress.value.filter(id => id !== invoiceCollectionId);
|
||||
} else {
|
||||
// If it is not, add it to the in progress list
|
||||
invoiceCollectionQueueInProgress.value.push(invoiceCollectionId);
|
||||
}
|
||||
// Process the invoice collection
|
||||
console.log('Processing invoice collection: ' + invoiceCollectionId);
|
||||
// Send the invoice collection request to the server
|
||||
// Send the request
|
||||
SessionUser.request(
|
||||
'/collected-invoices/economic',
|
||||
'POST',
|
||||
{
|
||||
id: parseInt(invoiceCollectionId)
|
||||
}
|
||||
).then((response) => {
|
||||
if (response.data && response.data.data && response.data.success) {
|
||||
// If the request was successful, add the invoice collection to the success list
|
||||
console.log('Invoice collection processed successfully: ' + invoiceCollectionId, response);
|
||||
addInvoiceCollectionToSuccess(invoiceCollectionId);
|
||||
addCollectionQueueLog(
|
||||
true, // Success state
|
||||
invoiceCollectionId, // Invoice collection id
|
||||
'Invoice collection processed successfully', // Error message
|
||||
);
|
||||
} else {
|
||||
// If the request failed, add the invoice collection to the failed list
|
||||
console.log('Invoice collection processing failed: ' + invoiceCollectionId, response);
|
||||
invoiceCollectionQueueFailed.value.push(invoiceCollectionId);
|
||||
addCollectionQueueLog(
|
||||
false, // Success state
|
||||
invoiceCollectionId, // Invoice collection id
|
||||
'Unknown error', // Error message
|
||||
);
|
||||
}
|
||||
}
|
||||
).catch((error) => {
|
||||
// If the request failed, add the invoice collection to the failed list
|
||||
console.log('Invoice collection processing failed 2: ' + invoiceCollectionId, error);
|
||||
invoiceCollectionQueueFailed.value.push(invoiceCollectionId);
|
||||
let errorMessage = error.response.data.data.message;
|
||||
addCollectionQueueLog(
|
||||
false, // Success state
|
||||
invoiceCollectionId, // Invoice collection id
|
||||
errorMessage, // Error message
|
||||
);
|
||||
}).finally(() => {
|
||||
// Remove the invoice collection from the in progress list
|
||||
invoiceCollectionQueueInProgress.value = invoiceCollectionQueueInProgress.value.filter(id => id !== invoiceCollectionId);
|
||||
// Process the next invoice collection in the queue
|
||||
processInvoiceCollectionQueue();
|
||||
});
|
||||
};
|
||||
|
||||
const collectionQueueLog = ref([]);
|
||||
const addCollectionQueueLog = (success, invoiceCollectionId, errorMessage) => {
|
||||
// Add the log to the collection queue log
|
||||
collectionQueueLog.value.push({
|
||||
success: success,
|
||||
invoiceCollectionId: invoiceCollectionId,
|
||||
errorMessage: errorMessage
|
||||
});
|
||||
};
|
||||
|
||||
const addInvoiceCollectionToSuccess = (invoiceCollectionId) => {
|
||||
// Check if the invoice collection is already in the success list
|
||||
if (invoiceCollectionQueueSuccess.value.includes(invoiceCollectionId)) {
|
||||
// If it is, remove it from the success list
|
||||
invoiceCollectionQueueSuccess.value = invoiceCollectionQueueSuccess.value.filter(id => id !== invoiceCollectionId);
|
||||
} else {
|
||||
// If it is not, add it to the success list
|
||||
invoiceCollectionQueueSuccess.value.push(invoiceCollectionId);
|
||||
}
|
||||
};
|
||||
|
||||
const confirmCloseModal = () => {
|
||||
Swal.fire(
|
||||
{
|
||||
title: 'Er du sikker på at du vil lukke vinduet?',
|
||||
text: "Du vil ikke kunne få den visualiseret igen!",
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Ja, luk vinduet!'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
isInvoiceMultipleCollectionsModalOpen.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table class="table is-fullwidth is-hoverable">
|
||||
<thead>
|
||||
<tr v-if="props.invoiceView && props.allowSelectMultiple">
|
||||
<td colspan="100%">
|
||||
<div class="buttons">
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="invoiceSelectedCollections()"
|
||||
>
|
||||
{{SessionUser.objects.global.language.invoice}} {{SessionUser.objects.global.language.selected_multiple}} ({{selectedInvoiceCollections.length}})
|
||||
</button>
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="selectAllInvoiceCollections()"
|
||||
:class="{
|
||||
'is-light': !isInvoiceCollectionSelectedAll(),
|
||||
'is-dark': isInvoiceCollectionSelectedAll()
|
||||
}"
|
||||
>
|
||||
{{ isInvoiceCollectionSelectedAll() ? SessionUser.objects.global.language.unselect : SessionUser.objects.global.language.select }} {{ SessionUser.objects.global.language.all.toLowerCase() }}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="is-narrow" v-if="props.invoiceView && props.allowSelectMultiple"></th>
|
||||
<th class="is-narrow" v-if="props.invoiceView"></th>
|
||||
<template v-for="tableHeaders in tableHeaders" :key="tableHeaders">
|
||||
<th
|
||||
v-if="tableHeaders.sortable"
|
||||
:class="{
|
||||
'is-clickable': tableHeaders.sortable,
|
||||
'has-text-centered': tableHeaders.name === 'id'
|
||||
}"
|
||||
@click="onTableHeaderClick(tableHeaders.name)"
|
||||
>
|
||||
<span class="is-flex-wrap-nowrap">
|
||||
<span class="has-text-weight-bold">{{ tableHeaders.title }}</span>
|
||||
<span
|
||||
v-if="isColumnCurrentlyBeingSortedBy(tableHeaders.name)"
|
||||
:class="{
|
||||
'fas fa-sort-up': isCurrentSortDirectionAscending(),
|
||||
'fas fa-sort-down': isCurrentSortDirectionDescending()
|
||||
}"
|
||||
class="icon is-small"
|
||||
/>
|
||||
</span>
|
||||
</th>
|
||||
<th
|
||||
v-else
|
||||
:class="{
|
||||
'has-text-centered': tableHeaders.name === 'id'
|
||||
}"
|
||||
>
|
||||
<span class="is-flex-wrap-nowrap">
|
||||
<span class="has-text-weight-bold">{{ tableHeaders.title }}</span>
|
||||
</span>
|
||||
</th>
|
||||
</template>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="order in orders" :key="order.id">
|
||||
<tr>
|
||||
<template v-if="props.invoiceView && props.allowSelectMultiple">
|
||||
<td class="is-narrow">
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="toggleInvoiceCollectionSelection(order.invoice_collection_id)"
|
||||
:class="{
|
||||
'is-light': !isInvoiceCollectionSelected(parseInt(order.invoice_collection_id)),
|
||||
'is-dark': isInvoiceCollectionSelected(parseInt(order.invoice_collection_id))
|
||||
}"
|
||||
>
|
||||
{{ isInvoiceCollectionSelected(parseInt(order.invoice_collection_id)) ? SessionUser.objects.global.language.unselect : SessionUser.objects.global.language.select }}
|
||||
</button>
|
||||
</td>
|
||||
</template>
|
||||
<template v-if="props.invoiceView">
|
||||
<td class="is-narrow">
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="toggleDropdown(order)"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
:class="isDropdownActive(order) ? 'has-text-link' : ''"
|
||||
:style="isDropdownActive(order) ? 'transform: rotate(90deg);' : ''"
|
||||
>
|
||||
<!-- Dropdown icon -->
|
||||
<i class="fas fa-angle-right"></i>
|
||||
</span>
|
||||
</button>
|
||||
</td>
|
||||
</template>
|
||||
<td>
|
||||
<ColorIndicator
|
||||
v-bind:color_class="getOrderInvoiceStatusBarColor(order)"
|
||||
v-bind:is_narrow="true"
|
||||
v-bind:label="{
|
||||
text: order.id,
|
||||
classes: [],
|
||||
max_length: 7,
|
||||
}"
|
||||
v-bind:visibility="{
|
||||
icon: true,
|
||||
dropdown: true,
|
||||
}"
|
||||
v-bind:dropdown_content="{
|
||||
title: null,
|
||||
buttons_title: null,
|
||||
content: [
|
||||
// Transaction
|
||||
{
|
||||
text: SessionUser.functions.ucFirst(SessionUser.objects.orders.meta.labels.single),
|
||||
action: () => {
|
||||
redirectDepartmentOrderPage(order.id, order.department_id);
|
||||
},
|
||||
...(SessionUser.canAccessSuperUser() ? {} : { disabled: true }),
|
||||
button_classes: ['no-underline-text', 'is-text'],
|
||||
button: true,
|
||||
button_text: order.id,
|
||||
v_centered: true,
|
||||
},
|
||||
// Invoice collection
|
||||
{
|
||||
text: SessionUser.objects.global.language.invoice + ' ' + SessionUser.objects.global.language.collection.toLowerCase(),
|
||||
action: () => {
|
||||
redirectSuperUserInvoiceCollectionPage(order.invoice_collection_id);
|
||||
},
|
||||
...(SessionUser.canAccessSuperUser() ? {} : { disabled: true }),
|
||||
button_classes: ['no-underline-text', 'is-text'],
|
||||
button: true,
|
||||
button_text: order.invoice_collection_id,
|
||||
v_centered: true,
|
||||
},
|
||||
// Completed status
|
||||
{
|
||||
text: SessionUser.objects.orders.columns.completed_at.label,
|
||||
action: () => {
|
||||
redirectDepartmentOrderPage(order.id, order.department_id);
|
||||
},
|
||||
...(SessionUser.canAccessSuperUser() ? {} : { disabled: true }),
|
||||
button_classes: ['no-underline-text', 'is-text', ...(order.completed_at !== null ? [] : ['has-text-warning'])],
|
||||
button: true,
|
||||
button_text: (order.completed_at !== null ? SessionUser.objects.global.language.yes : SessionUser.objects.global.language.no),
|
||||
v_centered: true,
|
||||
},
|
||||
],
|
||||
buttons: [],
|
||||
}"
|
||||
/>
|
||||
</td>
|
||||
<td v-if="!props.isCustomerView">
|
||||
<ColorIndicator
|
||||
v-bind:label="{
|
||||
text: order.customer_name,
|
||||
classes: [],
|
||||
max_length: 7,
|
||||
}"
|
||||
v-bind:visibility="{
|
||||
icon: false,
|
||||
dropdown: true
|
||||
}"
|
||||
v-bind:dropdown_content="{
|
||||
title: null,
|
||||
buttons_title: null,
|
||||
content: [
|
||||
{
|
||||
text: order.customer_name,
|
||||
action: () => {
|
||||
clickUser(order.customer_id);
|
||||
},
|
||||
...(SessionUser.canAccessSuperUser() ? {} : { disabled: true }),
|
||||
button: true,
|
||||
button_text: 'Vis kunde',
|
||||
}
|
||||
],
|
||||
buttons: [],
|
||||
}"
|
||||
/>
|
||||
</td>
|
||||
<!--
|
||||
<td>
|
||||
<span
|
||||
@mouseover="showPopper(
|
||||
popperBox(
|
||||
'Customer ID',
|
||||
order.customer_id,
|
||||
),
|
||||
$event.target
|
||||
)"
|
||||
@mouseleave="removePopperIfOpen()"
|
||||
@click="clickUser(order.customer_id)"
|
||||
:class="{ 'is-clickable': SessionUser.canAccessSuperUser() }"
|
||||
>
|
||||
{{ order.customer_name }}
|
||||
</span>
|
||||
</td> -->
|
||||
<td v-if="!props.isCustomerView">{{ order.cashier_id }}</td>
|
||||
<td>{{ getDepartmentName(order.department_id) }}</td>
|
||||
<td>
|
||||
<ColorIndicator
|
||||
v-bind:label="{
|
||||
text: order.reg_1,
|
||||
classes: [],
|
||||
max_length: 7,
|
||||
}"
|
||||
v-bind:visibility="{
|
||||
icon: false,
|
||||
dropdown: (order.reg_1.length > 7),
|
||||
}"
|
||||
v-bind:dropdown_content="{
|
||||
title: null,
|
||||
buttons_title: null,
|
||||
content: [
|
||||
{
|
||||
text: order.reg_1,
|
||||
action: () => {},
|
||||
}
|
||||
],
|
||||
buttons: [],
|
||||
}"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<ColorIndicator
|
||||
v-show="order.reg_2 !== ''"
|
||||
v-bind:label="{
|
||||
text: order.reg_2,
|
||||
classes: [],
|
||||
max_length: 7,
|
||||
}"
|
||||
v-bind:visibility="{
|
||||
icon: false,
|
||||
dropdown: (order.reg_2.length > 7),
|
||||
}"
|
||||
v-bind:dropdown_content="{
|
||||
title: null,
|
||||
buttons_title: null,
|
||||
content: [
|
||||
{
|
||||
text: order.reg_2,
|
||||
action: () => {},
|
||||
}
|
||||
],
|
||||
buttons: [],
|
||||
}"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<ColorIndicator
|
||||
v-show="order.reg_3 !== ''"
|
||||
v-bind:label="{
|
||||
text: order.reg_3,
|
||||
classes: [],
|
||||
max_length: 7,
|
||||
}"
|
||||
v-bind:visibility="{
|
||||
icon: false,
|
||||
dropdown: (order.reg_3.length > 7),
|
||||
}"
|
||||
v-bind:dropdown_content="{
|
||||
title: null,
|
||||
buttons_title: null,
|
||||
content: [
|
||||
{
|
||||
text: order.reg_3,
|
||||
action: () => {},
|
||||
}
|
||||
],
|
||||
buttons: [],
|
||||
}"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<ColorIndicator
|
||||
v-show="order.reference !== ''"
|
||||
v-bind:label="{
|
||||
text: order.reference,
|
||||
classes: [],
|
||||
max_length: 7,
|
||||
}"
|
||||
v-bind:visibility="{
|
||||
icon: false,
|
||||
dropdown: (order.reference.length > 7),
|
||||
}"
|
||||
v-bind:dropdown_content="{
|
||||
title: null,
|
||||
buttons_title: null,
|
||||
content: [
|
||||
{
|
||||
text: order.reference,
|
||||
action: () => {},
|
||||
}
|
||||
],
|
||||
buttons: [],
|
||||
}"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<ColorIndicator
|
||||
v-show="order.notes !== ''"
|
||||
v-bind:label="{
|
||||
text: order.notes,
|
||||
classes: [],
|
||||
max_length: 7,
|
||||
}"
|
||||
v-bind:visibility="{
|
||||
icon: false,
|
||||
dropdown: (order.notes.length > 7),
|
||||
}"
|
||||
v-bind:dropdown_content="{
|
||||
title: null,
|
||||
buttons_title: null,
|
||||
content: [
|
||||
{
|
||||
text: order.notes,
|
||||
action: () => {},
|
||||
}
|
||||
],
|
||||
buttons: [],
|
||||
}"
|
||||
/>
|
||||
</td>
|
||||
<td>{{ order.created_at }}</td>
|
||||
<td>{{ SessionUser.functions.currency.toLocal(order.total_net_amount) }}</td>
|
||||
<td>{{ order.completed_at }}</td>
|
||||
<td>
|
||||
<!-- Dropdown -->
|
||||
<ActionSettingsWheelButton
|
||||
v-bind:user_id="order.user_id"
|
||||
v-bind:order_id="order.id"
|
||||
v-bind:invoice_collection_id="order.invoice_collection_id"
|
||||
v-bind:reg_1="order.reg_1"
|
||||
>
|
||||
<template v-slot:actions>
|
||||
<!--
|
||||
Go to the order page, in a new tab
|
||||
<action-settings-wheel-item
|
||||
label="Se transaktion i ny fane"
|
||||
icon="fas fa-external-link-alt"
|
||||
:click-action="() => redirectDepartmentOrderPage(order.id, order.department_id)"
|
||||
:disabled="false"
|
||||
/>
|
||||
Show the invoice collection, in a new tab
|
||||
<action-settings-wheel-item
|
||||
label="Vis faktura samling i ny fane"
|
||||
icon="fas fa-file-invoice-dollar"
|
||||
:click-action="() => redirectSuperUserInvoiceCollectionPage(order.invoice_collection.id)"
|
||||
:disabled="!doesOrderHaveInvoiceCollection(order)"
|
||||
/>
|
||||
-->
|
||||
</template>
|
||||
</ActionSettingsWheelButton>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Dropdown content (if the invoice view is enabled,and the dropdown is active) -->
|
||||
<tr v-if="props.invoiceView && isDropdownActive(order)">
|
||||
<!-- Order line items -->
|
||||
<td colspan="100%">
|
||||
<!-- Content -->
|
||||
<div class="content">
|
||||
<OrderContentTable
|
||||
:order-id="order.id"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="100%">Viser {{ orders.length }} transaktioner</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<InvoiceMultipleCollectionsModal
|
||||
v-if="isInvoiceMultipleCollectionsModalOpen"
|
||||
v-bind:in-progress="invoiceCollectionQueueInProgress"
|
||||
v-bind:failed="invoiceCollectionQueueFailed"
|
||||
v-bind:succeeded="invoiceCollectionQueueSuccess"
|
||||
v-bind:queued="invoiceCollectionQueue"
|
||||
v-bind:log="collectionQueueLog"
|
||||
@closeModal="confirmCloseModal()"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.status-bar-table-header {
|
||||
max-width: 5px;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
.status-bar-table-row {
|
||||
border-radius: 5px;
|
||||
width: 5px;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
|
||||
}
|
||||
.status-bar.has-text-danger {
|
||||
border-color: #ff3860;
|
||||
}
|
||||
.status-bar.has-text-warning {
|
||||
border-color: #ffdd57;
|
||||
}
|
||||
.status-bar.has-text-success {
|
||||
border-color: #48c774;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,126 @@
|
||||
<script setup>
|
||||
import OrdersSyncTable from "@/components/displays/department/pos/sync/ordersSyncTable.vue";
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
const props = defineProps({
|
||||
onlyFromInvoiceCollection: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
autoLoad: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
setCustomerFilter: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
hideSearch: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
import { defineProps } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import {
|
||||
isLoaded,
|
||||
isLoading,
|
||||
list,
|
||||
loadList,
|
||||
metaCurrentPage,
|
||||
metaItemsPerPage,
|
||||
metaTotalItems,
|
||||
setEndpoint,
|
||||
setMetaItemsPerPage,
|
||||
setPage,
|
||||
metaSearch,
|
||||
search,
|
||||
setFilter,
|
||||
setOrder,
|
||||
hideSearchField,
|
||||
setHideSearchField,
|
||||
} from "@/components/pagination/paginatedList.vue";
|
||||
import PaginationNavigation from "@/components/displays/pagination/PaginationNavigation.vue";
|
||||
|
||||
import LoadButtonWhileAwait from "@/components/request/LoadButtonWhileAwait.vue";
|
||||
import PaginationDisplay from "@/components/displays/pagination/PaginationDisplay.vue";
|
||||
import OrdersTable from "@/components/displays/department/pos/orders/ordersTable.vue";
|
||||
import ColorIndicator from "@/components/displays/buttons/ColorIndicator.vue";
|
||||
const router = useRouter();
|
||||
setEndpoint("/orders", false);
|
||||
// If the customer filter is set, filter the orders by the customer number
|
||||
if (props.setCustomerFilter > 0) {
|
||||
setFilter("customer_id", props.setCustomerFilter);
|
||||
console.log("Setting customer filter to: " + props.setCustomerFilter);
|
||||
}
|
||||
|
||||
setFilter("cashier_id", 2285, false);
|
||||
|
||||
|
||||
// Hide the search field
|
||||
if (props.hideSearch) {
|
||||
setHideSearchField(true);
|
||||
console.log("Hiding search field");
|
||||
} else {
|
||||
setHideSearchField(false);
|
||||
}
|
||||
|
||||
// If the departmentId is set, in the route, filter the orders by the departmentId
|
||||
if (router.currentRoute.value.params.departmentId) {
|
||||
setOrder("created_at", "desc");
|
||||
setFilter("department_id", router.currentRoute.value.params.departmentId, false);
|
||||
} else {
|
||||
setOrder("created_at", "desc");
|
||||
}
|
||||
|
||||
// If the onlyFromInvoiceCollection prop is set, filter the orders by the invoice collection id
|
||||
if (props.onlyFromInvoiceCollection > 0) {
|
||||
setFilter("invoice_collection_id", props.onlyFromInvoiceCollection, false);
|
||||
}
|
||||
|
||||
// Load the list automatically if the autoLoad prop is set
|
||||
if (props.autoLoad) {
|
||||
loadList();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="level mb-3">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<h1 class="title is-4">{{SessionUser.objects.orders.meta.title}} ({{SessionUser.objects.global.language.synchronized}})</h1>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<h2 class="subtitle is-6">{{SessionUser.objects.global.language.showing}} {{ list.length }} {{SessionUser.objects.global.language.showing_of_separator}} {{ metaTotalItems }} {{SessionUser.objects.orders.meta.title.toLowerCase()}}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
<LoadButtonWhileAwait class="is-dark" :isLoading="isLoading" :loadFunction="loadList" icon="fas fa-sync-alt">{{SessionUser.objects.global.language.reload}}</LoadButtonWhileAwait>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input @input="search($event.target.value)" class="input" type="text" placeholder="Søg i transaktioner" v-if="!hideSearchField"/>
|
||||
<PaginationDisplay :metaItemsPerPage="metaItemsPerPage" :loadFunction="loadList" :isLoading="isLoading" :setMetaItemsPerPage="setMetaItemsPerPage">
|
||||
<template #paginationColumns>
|
||||
<!-- Sort by created_at -->
|
||||
<div class="column is-narrow my-3">
|
||||
<label class="label">Order direction</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select @change="setOrder('created_at', $event.target.value); loadList();">
|
||||
<option value="asc">Ascending (Oldest first)</option>
|
||||
<option value="desc" selected>Descending (Newest first)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</PaginationDisplay>
|
||||
<OrdersSyncTable :orders="list" />
|
||||
<PaginationNavigation :currentPage="metaCurrentPage" :totalPages="Math.ceil(metaTotalItems / metaItemsPerPage)" :loadFunction="loadList" :setPage="setPage" :isLoading="isLoading" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
<script setup>
|
||||
import OrdersSyncTable from "@/components/displays/department/pos/sync/ordersSyncTable.vue";
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
const props = defineProps({
|
||||
autoLoad: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
setCustomerFilter: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
hideSearch: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
import { defineProps } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import {
|
||||
isLoaded,
|
||||
isLoading,
|
||||
list,
|
||||
loadList,
|
||||
metaCurrentPage,
|
||||
metaItemsPerPage,
|
||||
metaTotalItems,
|
||||
setEndpoint,
|
||||
setMetaItemsPerPage,
|
||||
setPage,
|
||||
metaSearch,
|
||||
search,
|
||||
setFilter,
|
||||
setOrder,
|
||||
hideSearchField,
|
||||
setHideSearchField,
|
||||
} from "@/components/pagination/paginatedList.vue";
|
||||
import PaginationNavigation from "@/components/displays/pagination/PaginationNavigation.vue";
|
||||
|
||||
import LoadButtonWhileAwait from "@/components/request/LoadButtonWhileAwait.vue";
|
||||
import PaginationDisplay from "@/components/displays/pagination/PaginationDisplay.vue";
|
||||
import OrdersTable from "@/components/displays/department/pos/orders/ordersTable.vue";
|
||||
import ColorIndicator from "@/components/displays/buttons/ColorIndicator.vue";
|
||||
import OrdersSyncPossibleDuplicateTable
|
||||
from "@/components/displays/department/pos/sync/ordersSyncPossibleDuplicateTable.vue";
|
||||
const router = useRouter();
|
||||
setEndpoint("/orders/sync/potential-matches", false);
|
||||
// If the customer filter is set, filter the orders by the customer number
|
||||
if (props.setCustomerFilter > 0) {
|
||||
setFilter("customer_number", props.setCustomerFilter);
|
||||
console.log("Setting customer filter to: " + props.setCustomerFilter);
|
||||
}
|
||||
|
||||
// Hide the search field
|
||||
if (props.hideSearch) {
|
||||
setHideSearchField(true);
|
||||
console.log("Hiding search field");
|
||||
} else {
|
||||
setHideSearchField(false);
|
||||
}
|
||||
|
||||
// If the departmentId is set, in the route, filter the orders by the departmentId
|
||||
if (router.currentRoute.value.params.departmentId) {
|
||||
setOrder("created_at", "desc");
|
||||
setFilter("department", router.currentRoute.value.params.departmentId, false);
|
||||
} else {
|
||||
setOrder("created_at", "desc");
|
||||
}
|
||||
// Load the list automatically if the autoLoad prop is set
|
||||
if (props.autoLoad) {
|
||||
loadList();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="level mb-3">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<h1 class="title is-4">{{SessionUser.objects.orders.meta.title}} ({{SessionUser.objects.global.language.synchronized}})</h1>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<h2 class="subtitle is-6">{{SessionUser.objects.global.language.showing}} {{ list.length }} {{SessionUser.objects.global.language.showing_of_separator}} {{ metaTotalItems }} {{SessionUser.objects.orders.meta.title.toLowerCase()}}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
<LoadButtonWhileAwait class="is-dark" :isLoading="isLoading" :loadFunction="loadList" icon="fas fa-sync-alt">{{SessionUser.objects.global.language.reload}}</LoadButtonWhileAwait>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input @input="search($event.target.value)" class="input" type="text" placeholder="Søg i transaktioner" v-if="!hideSearchField"/>
|
||||
<PaginationDisplay :metaItemsPerPage="metaItemsPerPage" :loadFunction="loadList" :isLoading="isLoading" :setMetaItemsPerPage="setMetaItemsPerPage">
|
||||
<template #paginationColumns>
|
||||
<!-- Sort by created_at -->
|
||||
<div class="column is-narrow my-3">
|
||||
<label class="label">Order direction</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select @change="setOrder('created_at', $event.target.value); loadList();">
|
||||
<option value="asc">Ascending (Oldest first)</option>
|
||||
<option value="desc" selected>Descending (Newest first)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</PaginationDisplay>
|
||||
<OrdersSyncPossibleDuplicateTable :objects="list" />
|
||||
<PaginationNavigation :currentPage="metaCurrentPage" :totalPages="Math.ceil(metaTotalItems / metaItemsPerPage)" :loadFunction="loadList" :setPage="setPage" :isLoading="isLoading" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -161,7 +161,7 @@ const getOrders = (object) => {
|
||||
for (let i = 0; i < invoices.length; i++) {
|
||||
let invoice = invoices[i];
|
||||
// Get the orders for the invoice
|
||||
let invoice_orders = invoice.orders;
|
||||
let invoice_orders = invoice.objects;
|
||||
// For each order, add the customer_number to the order
|
||||
for (let j = 0; j < invoice_orders.length; j++) {
|
||||
let order = invoice_orders[j];
|
||||
@@ -180,7 +180,7 @@ const getOrders = (object) => {
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
console.log('orders', orders);
|
||||
console.log('objects', orders);
|
||||
return orders;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ const getOrders = (object) => {
|
||||
let orders = [];
|
||||
for (let i = 0; i < invoices.length; i++) {
|
||||
let invoice = invoices[i];
|
||||
let invoice_orders = invoice.orders;
|
||||
let invoice_orders = invoice.objects;
|
||||
orders = orders.concat(invoice_orders);
|
||||
}
|
||||
return orders;
|
||||
@@ -106,7 +106,7 @@ getDepartments();
|
||||
<!-- Active invoice count -->
|
||||
<td>{{ object.active_invoices.length }}</td>
|
||||
<!-- Active invoice count -->
|
||||
<td>{{ object.orders }}</td>
|
||||
<td>{{ object.objects }}</td>
|
||||
<!-- Status -->
|
||||
<!-- Total net amount -->
|
||||
<td>{{ SessionUser.functions.currency.toLocal(object.total_net_amount) }}</td>
|
||||
|
||||
@@ -73,7 +73,7 @@ const onSelect = (id) => {
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="object in props.objects" :key="object.id">
|
||||
<tr v-if="object.orders > 0 || props.showEmpty">
|
||||
<tr v-if="object.objects > 0 || props.showEmpty">
|
||||
<td>{{ object.id }}</td>
|
||||
<!-- Customer name -->
|
||||
<EditableTableColumn
|
||||
@@ -146,7 +146,10 @@ const onSelect = (id) => {
|
||||
</tr>
|
||||
<!-- Show a count of the total number of objects -->
|
||||
<tr>
|
||||
<td colspan="9">Viser {{ objects.length - objects.filter(object => object.orders === 0).length }} af {{objects.length}} {{ SessionUser.objects.collectedOrderInvoices.meta.labels.multiple }}<template v-if="!props.showEmpty">, skjuler {{ objects.filter(object => object.orders === 0).length }} {{ SessionUser.objects.collectedOrderInvoices.meta.labels.multiple }} uden ordre</template></td>
|
||||
<td colspan="9">Viser {{ objects.length - objects.filter(object => object.objects === 0).length }} af
|
||||
{{ objects.length }} {{ SessionUser.objects.collectedOrderInvoices.meta.labels.multiple }}<template v-if="!props.showEmpty">,
|
||||
skjuler {{ objects.filter(object => object.objects === 0).length }}
|
||||
{{ SessionUser.objects.collectedOrderInvoices.meta.labels.multiple }} uden ordre</template></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -17,7 +17,7 @@ const su_object = SessionUser.objects.collectedOrderInvoices;
|
||||
|
||||
const isInvoiceCancelled = (object) => {
|
||||
// Check if the invoice has been canceled (There's no orders attached to it)
|
||||
if (object.orders.length === 0) {
|
||||
if (object.objects.length === 0) {
|
||||
return true
|
||||
}
|
||||
};
|
||||
@@ -96,7 +96,7 @@ const dropdown_content = (object) => {
|
||||
button: true,
|
||||
button_classes: ['has-text-grey', 'is-text'],
|
||||
v_centered: true,
|
||||
button_text: object.orders.length > 0 ? object.orders.length : SessionUser.objects.global.language.no_data,
|
||||
button_text: object.objects.length > 0 ? object.objects.length : SessionUser.objects.global.language.no_data,
|
||||
},
|
||||
],
|
||||
buttons: []
|
||||
|
||||
@@ -108,6 +108,16 @@ const menu_items = ref([
|
||||
'list_orders'
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Synkronisering',
|
||||
route: '/admin/' + SessionUser.functions.getDepartmentIdFromUrl() + '/modules/pos/sync',
|
||||
icon: 'fas fa-exchange-alt',
|
||||
children: [],
|
||||
hidden: (SessionUser.functions.getDepartmentIdFromUrl() === undefined),
|
||||
permissions: [
|
||||
'department_pos_sync'
|
||||
],
|
||||
},
|
||||
],
|
||||
hidden: (SessionUser.functions.getDepartmentIdFromUrl() === undefined)
|
||||
},
|
||||
|
||||
@@ -19,8 +19,13 @@ export const ObjectsGlobal = {
|
||||
return `Rediger ${object.columns[column].label.toLowerCase()}`;
|
||||
},
|
||||
collection: "Samling",
|
||||
showing_of_separator: "af",
|
||||
split: "Opdel",
|
||||
successful_syncs: "Succesfulde synkroniseringer",
|
||||
save: "Gem",
|
||||
synchronized: "Synkroniseret",
|
||||
warnings: "Advarsler",
|
||||
warning: "Advarsel",
|
||||
tank_cleaning: "Tankcleaning",
|
||||
default: "Standard",
|
||||
required: "Påkrævet",
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<script setup>
|
||||
|
||||
import DepartmentDashboardHero from "@/views/dashboards/departmentDashboard/DepartmentDashboardHero.vue";
|
||||
import DepartmentPosNavigation from "@/views/dashboards/departmentDashboard/modules/Pos/DepartmentPosNavigation.vue";
|
||||
import OrdersPagination from "@/components/displays/pagination/models/DepartmentPos/OrdersPagination.vue";
|
||||
import RestrictedPageWrapper from "@/components/page/wrappers/RestrictedPageWrapper.vue";
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import DepartmentDashboardPageWrapper from "@/views/dashboards/departmentDashboard/DepartmentDashboardPageWrapper.vue";
|
||||
import NotFoundFallBackPageWrapper from "@/components/page/wrappers/NotFoundFallBackPageWrapper.vue";
|
||||
import OrdersSyncPagination from "@/components/displays/pagination/models/DepartmentPos/OrdersSyncPagination.vue";
|
||||
import PosOrdersSync from "@/components/displays/department/pos/sync/displays/PosOrdersSync.vue";
|
||||
import OrdersSyncPossibleDuplicatesPagination
|
||||
from "@/components/displays/pagination/models/DepartmentPos/OrdersSyncPossibleDuplicatesPagination.vue";
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RestrictedPageWrapper :hasPermission="SessionUser.canAccessAdmin()">
|
||||
<DepartmentDashboardPageWrapper title="Kassesystem" subtitle="Point of Sale">
|
||||
<NotFoundFallBackPageWrapper :exists="SessionUser.functions.getDepartmentIdFromUrl() && SessionUser.canAccessDepartment(SessionUser.functions.getDepartmentIdFromUrl())" error="Please select a department, that you have access to. If you believe this is an error, please contact support.">
|
||||
<template #default>
|
||||
<DepartmentDashboardHero />
|
||||
<PosOrdersSync />
|
||||
<OrdersSyncPossibleDuplicatesPagination />
|
||||
<!--
|
||||
Successful Orders Syncs
|
||||
<OrdersSyncPagination />
|
||||
-->
|
||||
</template>
|
||||
</NotFoundFallBackPageWrapper>
|
||||
</DepartmentDashboardPageWrapper>
|
||||
</RestrictedPageWrapper>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
+1
-1
@@ -43,7 +43,7 @@ const getCollectedOrderInvoice = async () => {
|
||||
SessionUser.objects.collectedOrderInvoices.get.single(collectedOrderInvoiceId).then((response) => {
|
||||
const raw_data = response;
|
||||
console.log(raw_data);
|
||||
orders.value = raw_data.orders || [];
|
||||
orders.value = raw_data.objects || [];
|
||||
user.value = raw_data.user;
|
||||
collectedOrderInvoice.value = raw_data;
|
||||
isLoading.value = false;
|
||||
|
||||
Reference in New Issue
Block a user