718 lines
33 KiB
Vue
718 lines
33 KiB
Vue
<script setup>
|
|
import { onMounted, ref, computed, onUpdated } from 'vue';
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
import { useI18n } from 'vue-i18n';
|
|
import axios from 'axios';
|
|
import { dashIfEmpty, toDate, toCurrency } from 'src/filters';
|
|
import SkeletonSummary from 'components/ui/SkeletonSummary.vue';
|
|
import FetchData from 'components/FetchData.vue';
|
|
import FetchedTags from 'components/ui/FetchedTags.vue';
|
|
import InvoiceOutDescriptorProxy from 'pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue';
|
|
import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue';
|
|
|
|
onMounted(() => fetch());
|
|
onUpdated(() => fetch());
|
|
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
|
|
const { t } = useI18n();
|
|
|
|
const $props = defineProps({
|
|
id: {
|
|
type: Number,
|
|
required: false,
|
|
default: null,
|
|
},
|
|
});
|
|
|
|
const entityId = computed(() => $props.id || route.params.id);
|
|
|
|
const ticket = ref();
|
|
const salesLines = ref(null);
|
|
const editableStates = ref([]);
|
|
|
|
async function fetch() {
|
|
const { data } = await axios.get(`Tickets/${entityId.value}/summary`);
|
|
if (data) {
|
|
ticket.value = data;
|
|
salesLines.value = data.sales;
|
|
}
|
|
}
|
|
|
|
function stateColor(state) {
|
|
if (state.code === 'OK') return 'text-green';
|
|
if (state.code === 'FREE') return 'text-blue-3';
|
|
if (state.alertLevel === 1) return 'text-primary';
|
|
if (state.alertLevel === 0) return 'text-red';
|
|
}
|
|
|
|
function formattedAddress() {
|
|
if (!ticket.value) return '';
|
|
|
|
const address = this.ticket.address;
|
|
const postcode = address.postalCode;
|
|
const province = address.province ? `(${address.province.name})` : '';
|
|
|
|
return `${address.street} - ${postcode} - ${address.city} ${province}`;
|
|
}
|
|
|
|
function isEditable() {
|
|
try {
|
|
return !this.ticket.ticketState.state.alertLevel;
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
async function changeState(value) {
|
|
if (!this.ticket.id) return;
|
|
|
|
const formData = {
|
|
ticketFk: this.ticket.id,
|
|
code: value,
|
|
};
|
|
|
|
await axios.post(`TicketTrackings/changeState`, formData);
|
|
await router.go(route.fullPath);
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<FetchData
|
|
url="States/editableStates"
|
|
@on-fetch="(data) => (editableStates = data)"
|
|
auto-load
|
|
/>
|
|
<div class="summary container">
|
|
<QCard>
|
|
<SkeletonSummary v-if="!ticket" />
|
|
<template v-if="ticket">
|
|
<div class="header bg-primary q-pa-sm q-mb-md">
|
|
<span>
|
|
Ticket #{{ ticket.id }} - {{ ticket.client.name }} ({{
|
|
ticket.client.id
|
|
}}) -
|
|
{{ ticket.nickname }}
|
|
</span>
|
|
<QBtnDropdown
|
|
side
|
|
top
|
|
color="orange-11"
|
|
text-color="black"
|
|
:label="t('ticket.summary.changeState')"
|
|
:disable="!isEditable()"
|
|
>
|
|
<QList>
|
|
<QVirtualScroll
|
|
style="max-height: 300px"
|
|
:items="editableStates"
|
|
separator
|
|
v-slot="{ item, index }"
|
|
>
|
|
<QItem
|
|
:key="index"
|
|
dense
|
|
clickable
|
|
v-close-popup
|
|
@click="changeState(item.code)"
|
|
>
|
|
<QItemSection>
|
|
<QItemLabel>{{ item.name }}</QItemLabel>
|
|
</QItemSection>
|
|
</QItem>
|
|
</QVirtualScroll>
|
|
</QList>
|
|
</QBtnDropdown>
|
|
</div>
|
|
<div class="row q-pa-md q-col-gutter-md q-mb-md">
|
|
<div class="col">
|
|
<QList>
|
|
<QItem>
|
|
<QItemSection>
|
|
<QItemLabel caption>
|
|
{{ t('ticket.summary.state') }}
|
|
</QItemLabel>
|
|
<QItemLabel
|
|
:class="stateColor(ticket.ticketState.state)"
|
|
>
|
|
{{ ticket.ticketState.state.name }}
|
|
</QItemLabel>
|
|
</QItemSection>
|
|
</QItem>
|
|
<QItem>
|
|
<QItemSection>
|
|
<QItemLabel caption>{{
|
|
t('ticket.summary.salesPerson')
|
|
}}</QItemLabel>
|
|
<QItemLabel>
|
|
<span class="link">
|
|
{{ ticket.client.salesPersonUser.name }}
|
|
<WorkerDescriptorProxy
|
|
:id="ticket.client.salesPersonFk"
|
|
/>
|
|
</span>
|
|
</QItemLabel>
|
|
</QItemSection>
|
|
</QItem>
|
|
<QItem>
|
|
<QItemSection>
|
|
<QItemLabel caption>{{
|
|
t('ticket.summary.agency')
|
|
}}</QItemLabel>
|
|
<QItemLabel>{{ ticket.agencyMode.name }}</QItemLabel>
|
|
</QItemSection>
|
|
</QItem>
|
|
<QItem>
|
|
<QItemSection>
|
|
<QItemLabel caption>{{
|
|
t('ticket.summary.zone')
|
|
}}</QItemLabel>
|
|
<QItemLabel class="link">{{
|
|
ticket.routeFk
|
|
}}</QItemLabel>
|
|
</QItemSection>
|
|
</QItem>
|
|
<QItem>
|
|
<QItemSection>
|
|
<QItemLabel caption>{{
|
|
t('ticket.summary.warehouse')
|
|
}}</QItemLabel>
|
|
<QItemLabel>{{ ticket.warehouse.name }}</QItemLabel>
|
|
</QItemSection>
|
|
</QItem>
|
|
<QItem>
|
|
<QItemSection>
|
|
<QItemLabel caption>{{
|
|
t('ticket.summary.invoice')
|
|
}}</QItemLabel>
|
|
<QItemLabel v-if="ticket.refFk">
|
|
<span class="link">
|
|
{{ ticket.refFk }}
|
|
<InvoiceOutDescriptorProxy :id="ticket.id" />
|
|
</span>
|
|
</QItemLabel>
|
|
</QItemSection>
|
|
</QItem>
|
|
</QList>
|
|
</div>
|
|
<div class="col">
|
|
<QList>
|
|
<QItem>
|
|
<QItemSection>
|
|
<QItemLabel caption>{{
|
|
t('ticket.summary.shipped')
|
|
}}</QItemLabel>
|
|
<QItemLabel>{{ toDate(ticket.shipped) }}</QItemLabel>
|
|
</QItemSection>
|
|
</QItem>
|
|
<QItem>
|
|
<QItemSection>
|
|
<QItemLabel caption>{{
|
|
t('ticket.summary.landed')
|
|
}}</QItemLabel>
|
|
<QItemLabel>{{ toDate(ticket.landed) }}</QItemLabel>
|
|
</QItemSection>
|
|
</QItem>
|
|
<QItem>
|
|
<QItemSection>
|
|
<QItemLabel caption>{{
|
|
t('ticket.summary.packages')
|
|
}}</QItemLabel>
|
|
<QItemLabel>{{ ticket.packages }}</QItemLabel>
|
|
</QItemSection>
|
|
</QItem>
|
|
<QItem>
|
|
<QItemSection>
|
|
<QItemLabel caption>{{
|
|
t('ticket.summary.consigneePhone')
|
|
}}</QItemLabel>
|
|
<QItemLabel>{{ ticket.address.phone }}</QItemLabel>
|
|
</QItemSection>
|
|
</QItem>
|
|
<QItem>
|
|
<QItemSection>
|
|
<QItemLabel caption>{{
|
|
t('ticket.summary.consigneeMobile')
|
|
}}</QItemLabel>
|
|
<QItemLabel>{{ ticket.address.mobile }}</QItemLabel>
|
|
</QItemSection>
|
|
</QItem>
|
|
<QItem>
|
|
<QItemSection>
|
|
<QItemLabel caption>{{
|
|
t('ticket.summary.clientPhone')
|
|
}}</QItemLabel>
|
|
<QItemLabel>{{ ticket.client.phone }}</QItemLabel>
|
|
</QItemSection>
|
|
</QItem>
|
|
<QItem>
|
|
<QItemSection>
|
|
<QItemLabel caption>{{
|
|
t('ticket.summary.clientMobile')
|
|
}}</QItemLabel>
|
|
<QItemLabel>{{ ticket.client.mobile }}</QItemLabel>
|
|
</QItemSection>
|
|
</QItem>
|
|
<QItem>
|
|
<QItemSection>
|
|
<QItemLabel caption>{{
|
|
t('ticket.summary.consignee')
|
|
}}</QItemLabel>
|
|
<QItemLabel>{{ formattedAddress() }}</QItemLabel>
|
|
</QItemSection>
|
|
</QItem>
|
|
</QList>
|
|
</div>
|
|
<div class="col">
|
|
<QList>
|
|
<QItem v-for="note in ticket.notes" :key="note.id">
|
|
<QItemSection>
|
|
<QItemLabel caption>
|
|
{{ note.observationType.description }}
|
|
</QItemLabel>
|
|
<QItemLabel>
|
|
{{ note.description }}
|
|
</QItemLabel>
|
|
</QItemSection>
|
|
</QItem>
|
|
</QList>
|
|
</div>
|
|
<div class="col">
|
|
<QList class="taxes">
|
|
<QItem>
|
|
<QItemSection>
|
|
<QItemLabel caption>{{
|
|
t('ticket.summary.subtotal')
|
|
}}</QItemLabel>
|
|
<QItemLabel>{{
|
|
toCurrency(ticket.totalWithoutVat)
|
|
}}</QItemLabel>
|
|
</QItemSection>
|
|
</QItem>
|
|
<QItem>
|
|
<QItemSection>
|
|
<QItemLabel caption>{{
|
|
t('ticket.summary.vat')
|
|
}}</QItemLabel>
|
|
<QItemLabel>{{
|
|
toCurrency(
|
|
ticket.totalWithVat - ticket.totalWithoutVat
|
|
)
|
|
}}</QItemLabel>
|
|
</QItemSection>
|
|
</QItem>
|
|
<QItem>
|
|
<QItemSection>
|
|
<QItemLabel caption>{{
|
|
t('ticket.summary.total')
|
|
}}</QItemLabel>
|
|
<QItemLabel>{{
|
|
toCurrency(ticket.totalWithVat)
|
|
}}</QItemLabel>
|
|
</QItemSection>
|
|
</QItem>
|
|
</QList>
|
|
</div>
|
|
</div>
|
|
<div class="row q-pa-md" v-if="salesLines.length > 0">
|
|
<div class="col">
|
|
<QList>
|
|
<QItemLabel header class="text-h6">
|
|
{{ t('ticket.summary.saleLines') }}
|
|
<RouterLink
|
|
:to="{
|
|
name: 'TicketBasicData',
|
|
params: { id: entityId },
|
|
}"
|
|
target="_blank"
|
|
>
|
|
<QIcon name="open_in_new" />
|
|
</RouterLink>
|
|
</QItemLabel>
|
|
<QTable :rows="ticket.sales" flat>
|
|
<template #header="props">
|
|
<QTr :props="props">
|
|
<QTh auto-width></QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.item')
|
|
}}</QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.visible')
|
|
}}</QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.available')
|
|
}}</QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.quantity')
|
|
}}</QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.description')
|
|
}}</QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.price')
|
|
}}</QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.discount')
|
|
}}</QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.amount')
|
|
}}</QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.packing')
|
|
}}</QTh>
|
|
</QTr>
|
|
</template>
|
|
<template #body="props">
|
|
<QTr :props="props">
|
|
<QTd>
|
|
<QBtn
|
|
flat
|
|
round
|
|
size="xs"
|
|
icon="vn:claims"
|
|
v-if="props.row.claim"
|
|
color="primary"
|
|
:to="{
|
|
name: 'ClaimCard',
|
|
params: {
|
|
id: props.row.claim.claimFk,
|
|
},
|
|
}"
|
|
>
|
|
<QTooltip
|
|
>{{ t('ticket.summary.claim') }}:
|
|
{{
|
|
props.row.claim.claimFk
|
|
}}</QTooltip
|
|
>
|
|
</QBtn>
|
|
<QBtn
|
|
flat
|
|
round
|
|
size="xs"
|
|
icon="vn:claims"
|
|
v-if="props.row.claimBeginning"
|
|
color="primary"
|
|
:to="{
|
|
name: 'ClaimCard',
|
|
params: {
|
|
id: props.row.claimBeginning
|
|
.claimFk,
|
|
},
|
|
}"
|
|
>
|
|
<QTooltip
|
|
>{{ t('ticket.summary.claim') }}:
|
|
{{
|
|
props.row.claimBeginning.claimFk
|
|
}}</QTooltip
|
|
>
|
|
</QBtn>
|
|
<QIcon
|
|
name="warning"
|
|
v-show="props.row.visible < 0"
|
|
size="xs"
|
|
color="primary"
|
|
>
|
|
<QTooltip
|
|
>{{ t('ticket.summary.visible') }}:
|
|
{{ props.row.visible }}</QTooltip
|
|
>
|
|
</QIcon>
|
|
<QIcon
|
|
name="vn:reserva"
|
|
v-show="props.row.reserved"
|
|
size="xs"
|
|
color="primary"
|
|
>
|
|
<QTooltip>
|
|
{{ t('ticket.summary.reserved') }}
|
|
</QTooltip>
|
|
</QIcon>
|
|
<QIcon
|
|
name="vn:unavailable"
|
|
v-show="props.row.itemShortage"
|
|
size="xs"
|
|
color="primary"
|
|
>
|
|
<QTooltip>
|
|
{{ t('ticket.summary.itemShortage') }}
|
|
</QTooltip>
|
|
</QIcon>
|
|
<QIcon
|
|
name="vn:components"
|
|
v-show="props.row.hasComponentLack"
|
|
size="xs"
|
|
color="primary"
|
|
>
|
|
<QTooltip>
|
|
{{
|
|
t(
|
|
'ticket.summary.hasComponentLack'
|
|
)
|
|
}}
|
|
</QTooltip>
|
|
</QIcon>
|
|
</QTd>
|
|
<QTd class="link">{{ props.row.itemFk }}</QTd>
|
|
<QTd>{{ props.row.visible }}</QTd>
|
|
<QTd>{{ props.row.available }}</QTd>
|
|
<QTd>{{ props.row.quantity }}</QTd>
|
|
<QTd>
|
|
<div class="fetched-tags">
|
|
<span>{{ props.row.item.name }}</span>
|
|
<span
|
|
v-if="props.row.item.subName"
|
|
class="subName"
|
|
>{{ props.row.item.subName }}</span
|
|
>
|
|
</div>
|
|
<fetched-tags
|
|
:item="props.row.item"
|
|
:max-length="5"
|
|
></fetched-tags>
|
|
</QTd>
|
|
<QTd>{{ props.row.price }}</QTd>
|
|
<QTd>{{ props.row.discount }} %</QTd>
|
|
<QTd
|
|
>{{
|
|
toCurrency(
|
|
props.row.quantity *
|
|
props.row.price *
|
|
((100 - props.row.discount) / 100)
|
|
)
|
|
}}
|
|
</QTd>
|
|
<QTd>{{
|
|
dashIfEmpty(props.row.item.itemPackingTypeFk)
|
|
}}</QTd>
|
|
</QTr>
|
|
</template>
|
|
</QTable>
|
|
</QList>
|
|
</div>
|
|
</div>
|
|
<div
|
|
class="row q-pa-md"
|
|
v-if="ticket.packagings.length > 0 || ticket.services.length > 0"
|
|
>
|
|
<div class="col" v-if="ticket.packagings.length > 0">
|
|
<QList>
|
|
<QItemLabel header class="text-h6">
|
|
{{ t('ticket.summary.packages') }}
|
|
<QIcon name="open_in_new" />
|
|
</QItemLabel>
|
|
<QTable :rows="ticket.packagings" flat>
|
|
<template #header="props">
|
|
<QTr :props="props">
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.created')
|
|
}}</QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.package')
|
|
}}</QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.quantity')
|
|
}}</QTh>
|
|
</QTr>
|
|
</template>
|
|
<template #body="props">
|
|
<QTr :props="props">
|
|
<QTd>{{ toDate(props.row.created) }}</QTd>
|
|
<QTd>{{ props.row.packaging.item.name }}</QTd>
|
|
<QTd>{{ props.row.quantity }}</QTd>
|
|
</QTr>
|
|
</template>
|
|
</QTable>
|
|
</QList>
|
|
</div>
|
|
<div class="col" v-if="ticket.services.length > 0">
|
|
<QList>
|
|
<QItemLabel header class="text-h6">
|
|
{{ t('ticket.summary.services') }}
|
|
<QIcon name="open_in_new" />
|
|
</QItemLabel>
|
|
<QTable :rows="ticket.services" flat>
|
|
<template #header="props">
|
|
<QTr :props="props">
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.quantity')
|
|
}}</QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.description')
|
|
}}</QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.price')
|
|
}}</QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.taxClass')
|
|
}}</QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.amount')
|
|
}}</QTh>
|
|
</QTr>
|
|
</template>
|
|
<template #body="props">
|
|
<QTr :props="props">
|
|
<QTd>{{ props.row.quantity }}</QTd>
|
|
<QTd>{{ props.row.description }}</QTd>
|
|
<QTd>{{ toCurrency(props.row.price) }}</QTd>
|
|
<QTd>{{ props.row.taxClass.description }}</QTd>
|
|
<QTd>{{
|
|
toCurrency(
|
|
props.row.quantity * props.row.price
|
|
)
|
|
}}</QTd>
|
|
</QTr>
|
|
</template>
|
|
</QTable>
|
|
</QList>
|
|
</div>
|
|
</div>
|
|
<div class="row q-pa-md" v-if="ticket.requests.length > 0">
|
|
<div class="col">
|
|
<QList>
|
|
<QItemLabel header class="text-h6">
|
|
{{ t('ticket.summary.request') }}
|
|
<QIcon name="open_in_new" />
|
|
</QItemLabel>
|
|
<QTable :rows="ticket.requests" flat>
|
|
<template #header="props">
|
|
<QTr :props="props">
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.description')
|
|
}}</QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.created')
|
|
}}</QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.requester')
|
|
}}</QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.atender')
|
|
}}</QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.quantity')
|
|
}}</QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.price')
|
|
}}</QTh>
|
|
<QTh auto-width>{{
|
|
t('ticket.summary.item')
|
|
}}</QTh>
|
|
<QTh auto-width>Ok</QTh>
|
|
</QTr>
|
|
</template>
|
|
<template #body="props">
|
|
<QTr :props="props">
|
|
<QTd>{{ props.row.description }}</QTd>
|
|
<QTd>{{ toDate(props.row.created) }}</QTd>
|
|
<QTd>{{ props.row.requester.user.name }}</QTd>
|
|
<QTd>{{ props.row.atender.user.name }}</QTd>
|
|
<QTd>{{ props.row.quantity }}</QTd>
|
|
<QTd>{{ toCurrency(props.row.price) }}</QTd>
|
|
<QTd v-if="!props.row.sale">-</QTd>
|
|
<QTd v-if="props.row.sale" class="link">{{
|
|
props.row.sale.itemFk
|
|
}}</QTd>
|
|
<QTd
|
|
><QCheckbox
|
|
v-model="props.row.isOk"
|
|
:disable="true"
|
|
/></QTd>
|
|
</QTr>
|
|
</template>
|
|
</QTable>
|
|
</QList>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</QCard>
|
|
</div>
|
|
</template>
|
|
<style lang="scss" scoped>
|
|
.container {
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
|
|
.q-card {
|
|
width: 100%;
|
|
height: 100%;
|
|
max-width: 1200px;
|
|
}
|
|
|
|
.summary {
|
|
.q-list {
|
|
.q-item__label--header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
|
|
a {
|
|
color: $primary;
|
|
}
|
|
}
|
|
}
|
|
.fetched-tags {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
& span {
|
|
flex-basis: 50%;
|
|
}
|
|
& span.subName {
|
|
flex-basis: 50%;
|
|
color: $secondary;
|
|
text-transform: uppercase;
|
|
font-size: 0.75rem;
|
|
}
|
|
}
|
|
.q-table__container {
|
|
text-align: left;
|
|
.q-icon {
|
|
padding: 2%;
|
|
}
|
|
}
|
|
.taxes {
|
|
border: $border-thin-light;
|
|
text-align: right;
|
|
padding: 8px;
|
|
}
|
|
|
|
.row {
|
|
flex-wrap: wrap;
|
|
|
|
.col {
|
|
min-width: 250px;
|
|
padding-left: 1.5%;
|
|
padding-right: 1.5%;
|
|
}
|
|
}
|
|
|
|
.header {
|
|
font-size: 18px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
align-content: center;
|
|
margin: 0;
|
|
text-align: center;
|
|
|
|
span {
|
|
flex: 1;
|
|
}
|
|
|
|
.q-btn {
|
|
flex: none;
|
|
}
|
|
}
|
|
}
|
|
|
|
.q-dialog .summary {
|
|
max-width: 1200px;
|
|
}
|
|
</style>
|