454 lines
15 KiB
Vue
454 lines
15 KiB
Vue
<script setup>
|
|
import VnPaginate from 'components/ui/VnPaginate.vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { computed, ref } from 'vue';
|
|
import { dashIfEmpty } from 'src/filters';
|
|
import VnInputDate from 'components/common/VnInputDate.vue';
|
|
import VnInput from 'components/common/VnInput.vue';
|
|
import axios from 'axios';
|
|
import { QIcon, useQuasar } from 'quasar';
|
|
import RouteListTicketsDialog from 'pages/Route/Card/RouteListTicketsDialog.vue';
|
|
import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue';
|
|
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
|
|
import { useRoute } from 'vue-router';
|
|
import VnConfirm from 'components/ui/VnConfirm.vue';
|
|
import FetchData from 'components/FetchData.vue';
|
|
import { openBuscaman } from 'src/utils/buscaman';
|
|
import SendSmsDialog from 'components/common/SendSmsDialog.vue';
|
|
import RouteSearchbar from "pages/Route/Card/RouteSearchbar.vue";
|
|
import {useStateStore} from "stores/useStateStore";
|
|
|
|
const { t } = useI18n();
|
|
const quasar = useQuasar();
|
|
const route = useRoute();
|
|
|
|
const stateStore = useStateStore();
|
|
const selectedRows = ref([]);
|
|
const columns = computed(() => [
|
|
{
|
|
name: 'order',
|
|
label: t('Order'),
|
|
field: (row) => dashIfEmpty(row?.priority),
|
|
sortable: false,
|
|
align: 'center',
|
|
},
|
|
{
|
|
name: 'street',
|
|
label: t('Street'),
|
|
field: (row) => row?.street,
|
|
sortable: false,
|
|
align: 'left',
|
|
},
|
|
{
|
|
name: 'city',
|
|
label: t('City'),
|
|
field: (row) => row?.city,
|
|
sortable: false,
|
|
align: 'left',
|
|
},
|
|
{
|
|
name: 'pc',
|
|
label: t('PC'),
|
|
field: (row) => row?.postalCode,
|
|
sortable: false,
|
|
align: 'center',
|
|
},
|
|
{
|
|
name: 'client',
|
|
label: t('Client'),
|
|
field: (row) => row?.nickname,
|
|
sortable: false,
|
|
align: 'left',
|
|
},
|
|
{
|
|
name: 'warehouse',
|
|
label: t('Warehouse'),
|
|
field: (row) => row?.warehouseName,
|
|
sortable: false,
|
|
align: 'left',
|
|
},
|
|
{
|
|
name: 'packages',
|
|
label: t('Packages'),
|
|
field: (row) => row?.packages,
|
|
sortable: false,
|
|
align: 'center',
|
|
},
|
|
{
|
|
name: 'volume',
|
|
label: 'm³',
|
|
field: (row) => row?.volume,
|
|
sortable: false,
|
|
align: 'center',
|
|
},
|
|
{
|
|
name: 'packaging',
|
|
label: t('Packaging'),
|
|
field: (row) => row?.ipt,
|
|
sortable: false,
|
|
align: 'center',
|
|
},
|
|
{
|
|
name: 'ticket',
|
|
label: t('Ticket'),
|
|
field: (row) => row?.id,
|
|
sortable: false,
|
|
align: 'center',
|
|
},
|
|
{
|
|
name: 'observations',
|
|
label: '',
|
|
field: (row) => row?.ticketObservation,
|
|
sortable: false,
|
|
align: 'left',
|
|
},
|
|
]);
|
|
|
|
const refreshKey = ref(0);
|
|
const confirmationDialog = ref(false);
|
|
const startingDate = ref(null);
|
|
const routeEntity = ref(null);
|
|
const ticketList = ref([]);
|
|
|
|
const cloneRoutes = () => {
|
|
axios.post('Routes/clone', {
|
|
created: startingDate.value,
|
|
ids: selectedRows.value.map((row) => row?.id),
|
|
});
|
|
refreshKey.value++;
|
|
startingDate.value = null;
|
|
};
|
|
|
|
const deletePriorities = async () => {
|
|
try {
|
|
await Promise.all(
|
|
selectedRows.value.map((ticket) =>
|
|
axios.patch(`Tickets/${ticket?.id}/`, { priority: null })
|
|
)
|
|
);
|
|
} finally {
|
|
refreshKey.value++;
|
|
}
|
|
};
|
|
|
|
const setOrderedPriority = async () => {
|
|
try {
|
|
await Promise.all(
|
|
ticketList.value.map((ticket, index) =>
|
|
axios.patch(`Tickets/${ticket?.id}/`, { priority: index + 1 })
|
|
)
|
|
);
|
|
} finally {
|
|
refreshKey.value++;
|
|
}
|
|
};
|
|
|
|
const sortRoutes = async () => {
|
|
await axios.get(`Routes/${route.params?.id}/guessPriority/`);
|
|
refreshKey.value++;
|
|
};
|
|
|
|
const updatePriority = async (ticket, priority = null) => {
|
|
return axios.patch(`Tickets/${ticket?.id}/`, {
|
|
priority: priority || ticket?.priority,
|
|
});
|
|
};
|
|
|
|
const setHighestPriority = async (ticket, ticketList) => {
|
|
const highestPriority = Math.max(...ticketList.map((item) => item.priority)) + 1;
|
|
if (highestPriority - 1 !== ticket.priority) {
|
|
const response = await updatePriority(ticket, highestPriority);
|
|
ticket.priority = response.data.priority;
|
|
}
|
|
};
|
|
|
|
const goToBuscaman = async (ticket = null) => {
|
|
await openBuscaman(
|
|
routeEntity.value?.vehicleFk,
|
|
ticket ? [ticket] : selectedRows.value
|
|
);
|
|
};
|
|
|
|
const openTicketsDialog = () => {
|
|
quasar
|
|
.dialog({
|
|
component: RouteListTicketsDialog,
|
|
componentProps: {
|
|
id: route.params.id,
|
|
},
|
|
})
|
|
.onOk(() => refreshKey.value++);
|
|
};
|
|
|
|
const removeTicket = async (ticket) => {
|
|
await axios.patch(`Tickets/${ticket?.id}/`, { routeFk: null });
|
|
await axios.post(`Routes/${route?.params?.id}/updateVolume`);
|
|
refreshKey.value++;
|
|
};
|
|
|
|
const confirmRemove = (ticket) => {
|
|
quasar
|
|
.dialog({
|
|
component: VnConfirm,
|
|
componentProps: {
|
|
title: t('Confirm removal from route'),
|
|
message: t('Are you sure you want to remove this ticket from the route?'),
|
|
promise: () => removeTicket(ticket),
|
|
},
|
|
})
|
|
.onOk(() => refreshKey.value++);
|
|
};
|
|
|
|
const openSmsDialog = async () => {
|
|
const clientsId = [];
|
|
const clientsPhone = [];
|
|
|
|
for (let ticket of selectedRows.value) {
|
|
clientsId.push(ticket?.clientFk);
|
|
const { data: client } = await axios.get(`Clients/${ticket?.clientFk}`);
|
|
clientsPhone.push(client.phone);
|
|
}
|
|
|
|
quasar.dialog({
|
|
component: SendSmsDialog,
|
|
componentProps: {
|
|
title: t('Send SMS to the selected tickets'),
|
|
url: 'Routes/sendSms',
|
|
destinationFk: clientsId.toString(),
|
|
destination: clientsPhone.toString(),
|
|
},
|
|
});
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<template v-if="stateStore.isHeaderMounted()">
|
|
<Teleport to="#searchbar">
|
|
<RouteSearchbar />
|
|
</Teleport>
|
|
</template>
|
|
<FetchData
|
|
@on-fetch="(data) => (routeEntity = data)"
|
|
auto-load
|
|
:url="`Routes/${route.params?.id}`"
|
|
:filter="{
|
|
fields: ['vehicleFk'],
|
|
}"
|
|
/>
|
|
<QDialog v-model="confirmationDialog">
|
|
<QCard style="min-width: 350px">
|
|
<QCardSection>
|
|
<p class="text-h6 q-ma-none">{{ t('Select the starting date') }}</p>
|
|
</QCardSection>
|
|
|
|
<QCardSection class="q-pt-none">
|
|
<VnInputDate
|
|
:label="t('Stating date')"
|
|
v-model="startingDate"
|
|
autofocus
|
|
/>
|
|
</QCardSection>
|
|
<QCardActions align="right">
|
|
<QBtn flat :label="t('Cancel')" v-close-popup class="text-primary" />
|
|
<QBtn color="primary" v-close-popup @click="cloneRoutes">
|
|
{{ t('Clone') }}
|
|
</QBtn>
|
|
</QCardActions>
|
|
</QCard>
|
|
</QDialog>
|
|
<QPage class="column items-center">
|
|
<QToolbar class="bg-vn-dark justify-end">
|
|
<div id="st-actions" class="q-pa-sm">
|
|
<QBtn icon="vn:wand" color="primary" class="q-mr-sm" @click="sortRoutes">
|
|
<QTooltip>{{ t('Sort routes') }}</QTooltip>
|
|
</QBtn>
|
|
<QBtn
|
|
icon="vn:buscaman"
|
|
color="primary"
|
|
class="q-mr-sm"
|
|
:disable="!selectedRows?.length"
|
|
@click="goToBuscaman()"
|
|
>
|
|
<QTooltip>{{ t('Open buscaman') }}</QTooltip>
|
|
</QBtn>
|
|
<QBtn
|
|
icon="filter_alt"
|
|
color="primary"
|
|
class="q-mr-sm filled-icon"
|
|
:disable="!selectedRows?.length"
|
|
@click="deletePriorities"
|
|
>
|
|
<QTooltip>{{ t('Delete priority') }}</QTooltip>
|
|
</QBtn>
|
|
<QBtn
|
|
icon="format_list_numbered"
|
|
color="primary"
|
|
class="q-mr-sm"
|
|
@click="setOrderedPriority"
|
|
>
|
|
<QTooltip
|
|
>{{
|
|
t('Renumber all tickets in the order you see on the screen')
|
|
}}
|
|
</QTooltip>
|
|
</QBtn>
|
|
<QBtn
|
|
icon="sms"
|
|
color="primary"
|
|
class="q-mr-sm filled-icon"
|
|
:disable="!selectedRows?.length"
|
|
@click="openSmsDialog"
|
|
>
|
|
<QTooltip>{{ t('Send SMS to all clients') }}</QTooltip>
|
|
</QBtn>
|
|
</div>
|
|
</QToolbar>
|
|
<div class="route-list">
|
|
<VnPaginate
|
|
:key="refreshKey"
|
|
data-key="RouteTicketList"
|
|
url="Routes/getTickets"
|
|
:filter="{ id: route.params.id }"
|
|
:order="['priority ASC']"
|
|
auto-load
|
|
@on-fetch="(data) => (ticketList = data)"
|
|
>
|
|
<template #body="{ rows }">
|
|
<div class="q-pa-md">
|
|
<QTable
|
|
v-model:selected="selectedRows"
|
|
:columns="columns"
|
|
:rows="rows"
|
|
:rows-per-page-options="[0]"
|
|
row-key="id"
|
|
flat
|
|
hide-pagination
|
|
selection="multiple"
|
|
>
|
|
<template #body-cell-order="{ row }">
|
|
<QTd class="order-field">
|
|
<div class="flex no-wrap items-center">
|
|
<QIcon
|
|
name="low_priority"
|
|
size="xs"
|
|
class="q-mr-md cursor-pointer"
|
|
color="primary"
|
|
@click="setHighestPriority(row, rows)"
|
|
>
|
|
<QTooltip>
|
|
{{ t('Assign highest priority') }}
|
|
</QTooltip>
|
|
</QIcon>
|
|
<VnInput
|
|
v-model="row.priority"
|
|
dense
|
|
@blur="updatePriority(row)"
|
|
/>
|
|
</div>
|
|
</QTd>
|
|
</template>
|
|
<template #body-cell-city="{ value, row }">
|
|
<QTd auto-width>
|
|
<span
|
|
class="text-primary cursor-pointer"
|
|
@click="goToBuscaman(row)"
|
|
>
|
|
{{ value }}
|
|
<QTooltip>{{ t('Open buscaman') }}</QTooltip>
|
|
</span>
|
|
</QTd>
|
|
</template>
|
|
<template #body-cell-client="{ value, row }">
|
|
<QTd auto-width>
|
|
<span class="text-primary cursor-pointer">
|
|
{{ value }}
|
|
<CustomerDescriptorProxy :id="row?.clientFk" />
|
|
</span>
|
|
</QTd>
|
|
</template>
|
|
<template #body-cell-ticket="{ value, row }">
|
|
<QTd auto-width class="text-center">
|
|
<span class="text-primary cursor-pointer">
|
|
{{ value }}
|
|
<TicketDescriptorProxy :id="row?.id" />
|
|
</span>
|
|
</QTd>
|
|
</template>
|
|
<template #body-cell-observations="{ value, row }">
|
|
<QTd auto-width>
|
|
<div class="flex items-center no-wrap table-actions">
|
|
<QIcon
|
|
name="delete"
|
|
color="primary"
|
|
class="cursor-pointer"
|
|
size="xs"
|
|
@click="confirmRemove(row)"
|
|
>
|
|
<QTooltip>{{ t('globals.remove') }}</QTooltip>
|
|
</QIcon>
|
|
<QIcon
|
|
v-if="value"
|
|
name="vn:notes"
|
|
color="primary"
|
|
class="cursor-pointer"
|
|
>
|
|
<QTooltip>{{ value }}</QTooltip>
|
|
</QIcon>
|
|
</div>
|
|
</QTd>
|
|
</template>
|
|
</QTable>
|
|
</div>
|
|
</template>
|
|
</VnPaginate>
|
|
</div>
|
|
<QPageSticky :offset="[20, 20]">
|
|
<QBtn fab icon="add" color="primary" @click="openTicketsDialog">
|
|
<QTooltip>
|
|
{{ t('Add ticket') }}
|
|
</QTooltip>
|
|
</QBtn>
|
|
</QPageSticky>
|
|
</QPage>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.route-list {
|
|
width: 100%;
|
|
}
|
|
|
|
.order-field {
|
|
max-width: 140px;
|
|
min-width: 120px;
|
|
}
|
|
|
|
.table-actions {
|
|
gap: 12px;
|
|
}
|
|
|
|
.filled-icon {
|
|
font-variation-settings: 'FILL' 1;
|
|
}
|
|
</style>
|
|
<i18n>
|
|
es:
|
|
Order: Orden
|
|
Street: Dirección fiscal
|
|
City: Población
|
|
PC: CP
|
|
Client: Cliente
|
|
Warehouse: Almacén
|
|
Packages: Bultos
|
|
Packaging: Encajado
|
|
Confirm removal from route: Quitar de la ruta
|
|
Are you sure you want to remove this ticket from the route?: ¿Seguro que quieres quitar este ticket de la ruta?
|
|
Sort routes: Ordenar rutas
|
|
Open buscaman: Abrir buscaman
|
|
Delete priority: Borrar orden
|
|
Renumber all tickets in the order you see on the screen: Renumerar todos los tickets con el orden que ves por pantalla
|
|
Assign highest priority: Asignar máxima prioridad
|
|
Send SMS to all clients: Mandar sms a todos los clientes de las rutas
|
|
Send SMS to the selected tickets: Enviar SMS a los tickets seleccionados
|
|
Add ticket: Añadir ticket
|
|
</i18n>
|