Modulo de rutas #195

Merged
jsegarra merged 49 commits from :feature/route-module into dev 2024-03-14 12:44:43 +00:00
32 changed files with 3126 additions and 243 deletions

View File

@ -0,0 +1,156 @@
<script setup>
import {useDialogPluginComponent} from 'quasar';
import {useI18n} from 'vue-i18n';
import {computed, ref} from 'vue';
import VnInput from 'components/common/VnInput.vue';
import axios from 'axios';
import useNotify from "composables/useNotify";
const MESSAGE_MAX_LENGTH = 160;
const {t} = useI18n();
const {notify} = useNotify();
const props = defineProps({
title: {
type: String,
required: true,
},
url: {
type: String,
required: true,
},
destination: {
type: String,
required: true,
},
destinationFk: {
type: String,
required: true,
},
data: {
type: Object,
default: () => ({}),
},
});
const emit = defineEmits([...useDialogPluginComponent.emits, 'sent']);
const {dialogRef, onDialogHide} = useDialogPluginComponent();
const smsRules = [
(val) => (val && val.length > 0) || t("The message can't be empty"),
(val) =>
(val && new Blob([val]).size <= MESSAGE_MAX_LENGTH) ||
t("The message it's too long"),
];
const message = ref('');
const charactersRemaining = computed(
() => MESSAGE_MAX_LENGTH - new Blob([message.value]).size
);
const charactersChipColor = computed(() => {
if (charactersRemaining.value < 0) {
return 'negative';
}
if (charactersRemaining.value <= 25) {
return 'warning';
}
return 'primary';
});
const onSubmit = async () => {
if (!props.destination) {
alexm marked this conversation as resolved
Review

Teniendo arriba:

    destination: {
        type: String,
        required: true,
    }

Hace falta estos ifs?

Teniendo arriba: ``` destination: { type: String, required: true, } ``` Hace falta estos ifs?
Review

Mmm...es por si se modifica a posteriori. De todas maneras si borras el campo sin el clearable, te deja el valor del campo a "", por lo que la validación no entraría, no?
Quizás falta integrar lo del hover_clearable o cambiar la validación.
@kevin, tus has podido mostrar el throw de la validación?
Gracias

Mmm...es por si se modifica a posteriori. De todas maneras si borras el campo sin el clearable, te deja el valor del campo a "", por lo que la validación no entraría, no? Quizás falta integrar lo del hover_clearable o cambiar la validación. @kevin, tus has podido mostrar el throw de la validación? Gracias
throw new Error(`The destination can't be empty`);
}
if (!message.value) {
throw new Error(`The message can't be empty`);
}
if (charactersRemaining.value < 0) {
throw new Error(`The message it's too long`);
}
const response = await axios.post(props.url, {
destination: props.destination,
destinationFk: props.destinationFk,
message: message.value,
...props.data,
});
if (response.data) {
emit('sent', response.data);
notify('globals.smsSent', 'positive');
}
emit('ok', response.data);
emit('hide', response.data);
};
</script>
<template>
<QDialog ref="dialogRef" @hide="onDialogHide">
<QCard class="full-width dialog">
<QCardSection class="row">
<span v-if="title" class="text-h6">{{ title }}</span>
<QSpace />
<QBtn icon="close" flat round dense v-close-popup />
</QCardSection>
<QForm @submit="onSubmit">
<QCardSection>
<VnInput
v-model="message"
type="textarea"
:rules="smsRules"
:label="t('Message')"
:placeholder="t('Message')"
:rows="5"
required
clearable
no-error-icon
>
<template #append>
<QIcon name="info">
<QTooltip>
{{
t(
'Special characters like accents counts as a multiple'
)
}}
</QTooltip>
</QIcon>
</template>
</VnInput>
<p class="q-mb-none q-mt-md">
{{ t('Characters remaining') }}:
<QChip :color="charactersChipColor">
{{ charactersRemaining }}
</QChip>
</p>
</QCardSection>
<QCardActions align="right">
<QBtn type="button" flat v-close-popup class="text-primary">
{{ t('globals.cancel') }}
</QBtn>
<QBtn type="submit" color="primary">{{ t('Send') }}</QBtn>
</QCardActions>
</QForm>
</QCard>
</QDialog>
</template>
<style lang="scss" scoped>
.dialog {
max-width: 450px;
}
</style>
<i18n>
es:
Message: Mensaje
Send: Enviar
Characters remaining: Carácteres restantes
Special characters like accents counts as a multiple: Carácteres especiales como los acentos cuentan como varios
The destination can't be empty: El destinatario no puede estar vacio
The message can't be empty: El mensaje no puede estar vacio
The message it's too long: El mensaje es demasiado largo
</i18n>

View File

@ -27,6 +27,10 @@ const $props = defineProps({
type: Object,
default: null,
},
url: {
type: String,
default: null,
},
});
const warehouses = ref();
@ -65,14 +69,15 @@ function mapperDms(data) {
}
function getUrl() {
if ($props.url) return $props.url;
if ($props.formInitialData) return 'dms/' + $props.formInitialData.id + '/updateFile';
return `${$props.model}/${route.params.id}/uploadFile`;
}
async function save() {
const body = mapperDms(dms.value);
await axios.post(getUrl(), body[0], body[1]);
emit('onDataSaved', body[1].params);
const response = await axios.post(getUrl(), body[0], body[1]);
emit('onDataSaved', body[1].params, response);
}
function defaultData() {

View File

@ -44,7 +44,7 @@ const props = defineProps({
},
offset: {
type: Number,
default: 500,
default: 0,
},
skeleton: {
type: Boolean,
@ -54,6 +54,10 @@ const props = defineProps({
type: Function,
default: null,
},
disableInfiniteScroll: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['onFetch', 'onPaginate']);
@ -122,10 +126,11 @@ async function paginate() {
emit('onPaginate');
}
async function onLoad(...params) {
if (!store.data) return;
async function onLoad(index, done) {
if (!store.data) {
return done();
}
const done = params[1];
if (store.data.length === 0 || !props.url) return done(false);
pagination.value.page = pagination.value.page + 1;
@ -174,7 +179,8 @@ async function onLoad(...params) {
v-if="store.data"
@load="onLoad"
:offset="offset"
class="full-width full-height"
:disable="disableInfiniteScroll || !arrayData.hasMoreData.value"
class="full-width"
v-bind="$attrs"
>
<slot name="body" :rows="store.data"></slot>

View File

@ -74,6 +74,7 @@ export default {
company: 'Company',
fieldRequired: 'Field required',
allowedFilesText: 'Allowed file types: { allowedContentTypes }',
smsSent: 'SMS sent',
confirmDeletion: 'Confirm deletion',
confirmDeletionMessage: 'Are you sure you want to delete this?',
description: 'Description',
@ -948,6 +949,22 @@ export default {
uncompleteTrays: 'There are incomplete trays',
},
},
'route/roadmap': {
pageTitles: {
roadmap: 'Roadmap',
summary: 'Summary',
basicData: 'Basic Data',
stops: 'Stops'
},
},
roadmap: {
pageTitles: {
roadmap: 'Roadmap',
summary: 'Summary',
basicData: 'Basic Data',
stops: 'Stops'
},
},
route: {
pageTitles: {
routes: 'Routes',
@ -956,6 +973,11 @@ export default {
create: 'Create',
basicData: 'Basic Data',
summary: 'Summary',
RouteRoadmap: 'Roadmaps',
RouteRoadmapCreate: 'Create roadmap',
tickets: 'Tickets',
log: 'Log',
autonomous: 'Autonomous',
},
cmr: {
list: {

View File

@ -74,6 +74,7 @@ export default {
company: 'Empresa',
fieldRequired: 'Campo requerido',
allowedFilesText: 'Tipos de archivo permitidos: { allowedContentTypes }',
smsSent: 'SMS enviado',
confirmDeletion: 'Confirmar eliminación',
confirmDeletionMessage: '¿Seguro que quieres eliminar?',
description: 'Descripción',
@ -948,6 +949,22 @@ export default {
uncompleteTrays: 'Hay bandejas sin completar',
},
},
'route/roadmap': {
pageTitles: {
roadmap: 'Troncales',
summary: 'Resumen',
basicData: 'Datos básicos',
stops: 'Paradas'
},
},
roadmap: {
pageTitles: {
roadmap: 'Troncales',
summary: 'Resumen',
basicData: 'Datos básicos',
stops: 'Paradas'
},
},
route: {
pageTitles: {
routes: 'Rutas',
@ -955,7 +972,12 @@ export default {
RouteList: 'Listado',
create: 'Crear',
basicData: 'Datos básicos',
summary: 'Summary',
summary: 'Resumen',
RouteRoadmap: 'Troncales',
RouteRoadmapCreate: 'Crear troncal',
tickets: 'Tickets',
log: 'Historial',
autonomous: 'Autónomos',
},
cmr: {
list: {

View File

@ -0,0 +1,15 @@
<script setup>
import InvoiceInDescriptor from "pages/InvoiceIn/Card/InvoiceInDescriptor.vue";
const $props = defineProps({
id: {
type: Number,
required: true,
},
});
</script>
<template>
<QPopupProxy>
<InvoiceInDescriptor v-if="$props.id" :id="$props.id" />
</QPopupProxy>
</template>

View File

@ -0,0 +1,237 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'components/common/VnInput.vue';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const emit = defineEmits(['search']);
const agencyList = ref([]);
const agencyAgreementList = ref([]);
const supplierList = ref([]);
const exprBuilder = (param, value) => {
switch (param) {
case 'agencyModeFk':
return { 'a.agencyModeFk': value };
case 'supplierFk':
return { 'a.supplierName': value };
case 'routeFk':
return { 'a.routeFk': value };
case 'created':
case 'agencyFk':
case 'packages':
case 'm3':
case 'kmTotal':
case 'price':
case 'invoiceInFk':
return { [`a.${param}`]: value };
}
};
</script>
<template>
<FetchData
url="AgencyModes"
:filter="{ fields: ['id', 'name'] }"
sort-by="name ASC"
limit="30"
@on-fetch="(data) => (agencyList = data)"
auto-load
/>
<FetchData
url="Agencies"
:filter="{ fields: ['id', 'name'] }"
sort-by="name ASC"
limit="30"
@on-fetch="(data) => (agencyAgreementList = data)"
auto-load
/>
<FetchData
url="Suppliers"
:filter="{ fields: ['name'] }"
sort-by="name ASC"
limit="30"
@on-fetch="(data) => (supplierList = data)"
auto-load
/>
<VnFilterPanel
:data-key="props.dataKey"
:expr-builder="exprBuilder"
search-button
@search="emit('search')"
>
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params }">
<QList dense>
<QItem class="q-my-sm">
<QItemSection>
<VnInput v-model="params.routeFk" :label="t('ID')" is-outlined />
</QItemSection>
</QItem>
<QItem class="q-my-sm" v-if="agencyList">
<QItemSection>
<VnSelectFilter
:label="t('Agency route')"
v-model="params.agencyModeFk"
:options="agencyList"
option-value="id"
option-label="name"
dense
outlined
rounded
emit-value
map-options
use-input
is-clearable
:input-debounce="0"
/>
</QItemSection>
</QItem>
<QItem class="q-my-sm" v-if="agencyAgreementList">
<QItemSection>
<VnSelectFilter
:label="t('Agency agreement')"
v-model="params.agencyFk"
:options="agencyAgreementList"
option-value="id"
option-label="name"
dense
outlined
rounded
emit-value
map-options
use-input
is-clearable
:input-debounce="0"
/>
</QItemSection>
</QItem>
<QItem class="q-my-sm" v-if="supplierList">
<QItemSection>
<VnSelectFilter
:label="t('Autonomous')"
v-model="params.supplierFk"
:options="supplierList"
option-value="name"
option-label="name"
dense
outlined
rounded
emit-value
map-options
use-input
is-clearable
:input-debounce="0"
/>
</QItemSection>
</QItem>
<QItem class="q-my-sm">
<QItemSection>
<VnInputDate
v-model="params.created"
:label="t('Date')"
is-outlined
/>
</QItemSection>
</QItem>
<QItem class="q-my-sm">
<QItemSection>
<VnInputDate
v-model="params.from"
:label="t('From')"
is-outlined
/>
</QItemSection>
</QItem>
<QItem class="q-my-sm">
<QItemSection>
<VnInputDate
v-model="params.to"
:label="t('To')"
is-outlined
is-clearable
/>
</QItemSection>
</QItem>
<QItem class="q-my-sm">
<QItemSection>
<VnInput v-model="params.packages" :label="t('Packages')" is-outlined />
</QItemSection>
</QItem>
<QItem class="q-my-sm">
<QItemSection>
<VnInput v-model="params.m3" :label="t('m3')" is-outlined />
</QItemSection>
</QItem>
<QItem class="q-my-sm">
<QItemSection>
<VnInput v-model="params.kmTotal" :label="t('Km')" is-outlined />
</QItemSection>
</QItem>
<QItem class="q-my-sm">
<QItemSection>
<VnInput v-model="params.price" :label="t('Price')" is-outlined />
</QItemSection>
</QItem>
<QItem class="q-my-sm">
<QItemSection>
<VnInput v-model="params.invoiceInFk" :label="t('Received')" is-outlined />
</QItemSection>
</QItem>
</QList>
</template>
</VnFilterPanel>
</template>
<i18n>
en:
params:
agencyModeFk: Agency route
m3:
from: From
to: To
date: Date
agencyFk: Agency agreement
packages: Packages
price: Price
invoiceInFk: Received
supplierFk: Autonomous
es:
params:
agencyModeFk: Agencia ruta
m3:
from: Desde
to: Hasta
date: Fecha
agencyFk: Agencia Acuerdo
packages: Bultos
price: Precio
invoiceInFk: Recibida
supplierFk: Autónomos
From: Desde
To: Hasta
Date: Fecha
Agency route: Agencia Ruta
Agency agreement: Agencia Acuerdo
Packages: Bultos
Price: Precio
Received: Recibida
Autonomous: Autónomos
</i18n>

View File

@ -11,12 +11,15 @@ import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'components/common/VnInput.vue';
import axios from 'axios';
import VnInputTime from 'components/common/VnInputTime.vue';
import RouteSearchbar from "pages/Route/Card/RouteSearchbar.vue";
import {useStateStore} from "stores/useStateStore";
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const shelvingId = route.params?.id || null;
const isNew = Boolean(!shelvingId);
const stateStore = useStateStore();
const shelvingId = ref(route.params?.id || null);
const isNew = Boolean(!shelvingId.value);
const defaultInitialData = {
agencyModeFk: null,
created: null,
@ -78,6 +81,11 @@ const onSave = (data, response) => {
</script>
<template>
<VnSubToolbar />
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<RouteSearchbar />
</Teleport>
</template>
<FetchData
url="Workers/search"
:filter="{ fields: ['id', 'nickname'] }"
@ -89,7 +97,7 @@ const onSave = (data, response) => {
<FetchData
url="AgencyModes/isActive"
:filter="{ fields: ['id', 'name'] }"
sort-by="name ASC"
sort-by="name"
limit="30"
@on-fetch="(data) => (agencyList = data)"
auto-load
@ -103,13 +111,13 @@ const onSave = (data, response) => {
auto-load
/>
<FormModel
:url="isNew ? null : `Routes/${shelvingId}`"
:url="isNew ? null : `Routes/${route.params?.id}`"
Review

Agencia y creado están al revés, pero bueno, creo que no es relevante por eso lo pongo como comentario

Agencia y creado están al revés, pero bueno, creo que no es relevante por eso lo pongo como comentario
:url-create="isNew ? 'Routes' : null"
:observe-form-changes="!isNew"
:filter="routeFilter"
model="route"
:auto-load="!isNew"
:form-initial-data="defaultInitialData"
:form-initial-data="isNew ? defaultInitialData : null"
@on-data-saved="onSave"
>
<template #form="{ data }">
@ -131,7 +139,7 @@ const onSave = (data, response) => {
<QItemSection>
<QItemLabel>{{ opt.name }}</QItemLabel>
<QItemLabel caption>
{{ opt.nickname }},{{ opt.code }}
{{ opt.nickname }}, {{ opt.code }}
</QItemLabel>
</QItemSection>
</QItem>
@ -212,6 +220,8 @@ const onSave = (data, response) => {
<VnInput
v-model="data.description"
:label="t('Description')"
type="textarea"
:rows="3"
clearable
/>
</div>
@ -230,4 +240,5 @@ es:
Hour finished: Hora fin
Description: Descripción
Is served: Se ha servido
Created: Creado
</i18n>

View File

@ -0,0 +1,241 @@
<script setup>
import { useDialogPluginComponent } from 'quasar';
import { useI18n } from 'vue-i18n';
import { ref } from 'vue';
import FetchData from 'components/FetchData.vue';
import axios from 'axios';
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue';
const { t } = useI18n();
const $props = defineProps({
id: {
type: Number,
required: true,
},
});
const emit = defineEmits([...useDialogPluginComponent.emits]);
const { dialogRef, onDialogHide } = useDialogPluginComponent();
const columns = ref([
{
name: 'ticket',
label: t('Ticket'),
field: (row) => row.id,
sortable: true,
align: 'left',
},
{
name: 'client',
label: t('Client'),
field: (row) => row.nickname,
sortable: true,
align: 'left',
},
{
name: 'province',
label: t('Province'),
field: (row) => row?.address?.province?.name,
sortable: true,
align: 'left',
},
{
name: 'city',
label: t('City'),
jgallego marked this conversation as resolved Outdated

en salix es city.
"Population" se refiere al número total de individuos que habitan en un área geográfica específica

en salix es city. "Population" se refiere al número total de individuos que habitan en un área geográfica específica

En Salix hay una incongruencia, en el listado de tickets se muestra "city", pero en el modal muestra "population".

Se cambio a "City".

e4553659cf

En Salix hay una incongruencia, en el listado de tickets se muestra "city", pero en el modal muestra "population". Se cambio a "City". e4553659cf1ef6c245569557901bcd3a0a680d51
field: (row) => row?.address?.city,
sortable: true,
align: 'left',
},
{
name: 'pc',
label: t('PC'),
field: (row) => row?.address?.postalCode,
sortable: true,
align: 'left',
},
{
name: 'address',
label: t('Address'),
field: (row) => row?.address?.street,
sortable: true,
align: 'left',
},
{
name: 'zone',
label: t('Zone'),
field: (row) => row?.zone?.name,
sortable: true,
align: 'left',
},
{
name: 'actions',
label: '',
sortable: false,
align: 'right',
},
]);
const refreshKey = ref(0);
const selectedRows = ref([]);
const isLoading = ref(true);
const ticketList = ref([]);
const onDataFetched = (data) => {
ticketList.value = data;
isLoading.value = false;
};
const selectedTicket = ref(null);
const unlinkZone = async (ticket) => {
await axios.post('Routes/unlink', {
agencyModeId: ticket?.agencyModeFk,
zoneId: ticket.zoneFk,
});
selectedTicket.value = null;
refreshKey.value++;
};
const setTicketsRoute = async () => {
if (!selectedRows.value?.length) {
return;
}
await Promise.all(
(selectedRows.value || [])
.filter((ticket) => ticket?.id)
.map((ticket) =>
axios.patch(`Routes/${$props.id}/insertTicket`, { ticketId: ticket.id })
)
);
await axios.post(`Routes/${$props.id}/updateVolume`);
emit('ok');
emit('hide');
};
</script>
<template>
<FetchData
:key="refreshKey"
:url="`Routes/${$props.id}/getSuggestedTickets`"
@on-fetch="onDataFetched"
auto-load
/>
<QDialog
:model-value="Boolean(selectedTicket)"
@update:model-value="(value) => (!value ? (selectedTicket = null) : null)"
class="confirmation-dialog"
>
<QCard style="min-width: 350px">
<QCardSection>
<p class="text-h6 q-ma-none">{{ t('Unlink selected zone?') }}</p>
</QCardSection>
<QCardActions align="right">
<QBtn
flat
:label="t('globals.cancel')"
v-close-popup
class="text-primary"
/>
<QBtn color="primary" v-close-popup @click="unlinkZone(selectedTicket)">
{{ t('Unlink') }}
</QBtn>
</QCardActions>
</QCard>
</QDialog>
<QDialog ref="dialogRef" @hide="onDialogHide">
<QCard class="full-width dialog">
<QCardSection class="row items-center q-pb-none">
<div class="text-h6">{{ t('Tickets to add') }}</div>
<QSpace />
<QBtn icon="close" flat round dense v-close-popup />
</QCardSection>
<QCardSection>
<QTable
v-model:selected="selectedRows"
:columns="columns"
:loading="isLoading"
:rows="ticketList"
flat
row-key="id"
selection="multiple"
:rows-per-page-options="[0]"
hide-pagination
>
<template #body-cell-ticket="props">
<QTd :props="props">
<span class="link">
{{ props.value }}
<TicketDescriptorProxy :id="props?.row?.id" />
</span>
</QTd>
</template>
<template #body-cell-client="props">
<QTd :props="props">
<span class="link">
{{ props.value }}
<CustomerDescriptorProxy :id="props?.row?.clientFk" />
</span>
</QTd>
</template>
<template #body-cell-actions="props">
<QTd :props="props">
<QIcon
name="link_off"
size="xs"
color="primary"
class="cursor-pointer"
@click="selectedTicket = props?.row"
>
<QTooltip
>{{
t('Unlink zone', {
zone: props?.row?.zone?.name,
agency: props?.row?.agencyMode?.name,
})
}}
</QTooltip>
</QIcon>
</QTd>
</template>
</QTable>
</QCardSection>
<QCardActions align="right">
<QBtn flat v-close-popup class="text-primary"
>{{ t('globals.cancel') }}
</QBtn>
<QBtn
color="primary"
:disable="!selectedRows?.length"
@click="setTicketsRoute"
>{{ t('globals.add') }}
</QBtn>
</QCardActions>
</QCard>
</QDialog>
</template>
<style lang="scss" scoped>
.confirmation-dialog {
z-index: 6001 !important;
}
.dialog {
max-width: 1280px;
}
</style>
<i18n>
en:
Unlink zone: Unlink zone {zone} from agency {agency}
es:
Tickets to add: Tickets a añadir
Client: Cliente
Province: Provincia
City: Población
PC: CP
Address: Dirección
Zone: Zona
Unlink zone: Desvincular zona {zone} de agencia {agency}
Unlink: Desvincular
</i18n>

View File

@ -9,6 +9,7 @@ const { t } = useI18n();
data-key="RouteList"
:label="t('Search route')"
:info="t('You can search by route reference')"
custom-route-redirect-name="RouteList"
/>
</template>

View File

@ -8,9 +8,10 @@ import VnLv from 'components/ui/VnLv.vue';
import { QIcon } from 'quasar';
import { dashIfEmpty, toCurrency, toDate, toHour } from 'src/filters';
import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue';
import axios from 'axios';
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue';
import RouteSearchbar from 'pages/Route/Card/RouteSearchbar.vue';
import { openBuscaman } from 'src/utils/buscaman';
const $props = defineProps({
id: {
@ -104,7 +105,7 @@ const ticketColumns = ref([
label: t('route.summary.ticket'),
field: (row) => row?.id,
sortable: false,
align: 'right',
align: 'center',
},
{
name: 'observations',
@ -114,24 +115,15 @@ const ticketColumns = ref([
align: 'left',
},
]);
const openBuscaman = async (route, ticket) => {
if (!route.vehicleFk) throw new Error(`The route doesn't have a vehicle`);
const response = await axios.get(`Routes/${route.vehicleFk}/getDeliveryPoint`);
if (!response.data)
throw new Error(`The route's vehicle doesn't have a delivery point`);
const address = `${response.data}+to:${ticket.postalCode} ${ticket.city} ${ticket.street}`;
window.open(
'https://gps.buscalia.com/usuario/localizar.aspx?bmi=true&addr=' +
encodeURI(address),
'_blank'
);
};
</script>
<template>
<div class="q-pa-md">
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<RouteSearchbar />
</Teleport>
</template>
<div class="q-pa-md full-width">
<CardSummary
ref="summary"
:url="`Routes/${entityId}/summary`"
@ -206,9 +198,10 @@ const openBuscaman = async (route, ticket) => {
</QCard>
<QCard class="vn-max">
<div class="header">
<a class="header" :href="`#/route/${entityId}/tickets`">
{{ t('route.summary.tickets') }}
</div>
<QIcon name="open_in_new" color="primary" />
</a>
<QTable
:columns="ticketColumns"
:rows="entity?.tickets"
@ -221,7 +214,7 @@ const openBuscaman = async (route, ticket) => {
<QTd auto-width>
<span
class="text-primary cursor-pointer"
@click="openBuscaman(entity?.route, row)"
@click="openBuscaman(entity?.route?.vehicleFk, [row])"
>
{{ value }}
</span>
@ -236,7 +229,7 @@ const openBuscaman = async (route, ticket) => {
</QTd>
</template>
<template #body-cell-ticket="{ value, row }">
<QTd auto-width>
<QTd auto-width class="text-center">
<span class="text-primary cursor-pointer">
{{ value }}
<TicketDescriptorProxy :id="row?.id" />

View File

@ -0,0 +1,88 @@
<script setup>
import { useDialogPluginComponent } from 'quasar';
import { useI18n } from 'vue-i18n';
import { reactive, ref } from 'vue';
import axios from 'axios';
import FetchData from 'components/FetchData.vue';
import RoadmapAddStopForm from 'pages/Route/Roadmap/RoadmapAddStopForm.vue';
const emit = defineEmits([...useDialogPluginComponent.emits]);
const { dialogRef, onDialogHide } = useDialogPluginComponent();
const { t } = useI18n();
const props = defineProps({
roadmapFk: {
type: Number,
required: true,
},
});
const isLoading = ref(false);
const roadmapStopForm = reactive({
roadmapFk: Number(props.roadmapFk) || 0,
warehouseFk: null,
eta: new Date().toISOString(),
description: '',
});
const updateDefaultStop = (data) => {
const eta = new Date(data.etd);
eta.setDate(eta.getDate() + 1);
roadmapStopForm.eta = eta.toISOString();
};
const onSave = async () => {
isLoading.value = true;
try {
await axios.post('ExpeditionTrucks', { ...roadmapStopForm });
emit('ok');
} finally {
isLoading.value = false;
emit('hide');
}
};
</script>
<template>
<FetchData
:url="`Roadmaps/${roadmapFk}`"
auto-load
:filter="{ fields: ['etd'] }"
@on-fetch="updateDefaultStop"
/>
<QDialog ref="dialogRef" @hide="onDialogHide">
<QCard class="q-pa-lg">
<QCardSection class="row absolute absolute-top z-fullscreen">
<QSpace />
<QBtn icon="close" flat round dense v-close-popup />
</QCardSection>
<RoadmapAddStopForm
:roadmap-fk="roadmapFk"
:form-data="roadmapStopForm"
layout="dialog"
/>
<QCardActions align="right">
<QBtn :label="t('globals.cancel')" color="primary" flat v-close-popup />
<QBtn
:label="t('globals.confirm')"
color="primary"
:loading="isLoading"
@click="onSave"
unelevated
/>
</QCardActions>
</QCard>
</QDialog>
</template>
<style lang="scss" scoped>
.card-section {
gap: 16px;
}
</style>
<i18n>
es:
Warehouse: Almacén
ETA date: Fecha ETA
ETA time: Hora ETA
Description: Descripción
</i18n>

View File

@ -0,0 +1,104 @@
<script setup>
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInputTime from 'components/common/VnInputTime.vue';
import VnInput from 'components/common/VnInput.vue';
const props = defineProps({
formData: {
type: Object,
default: () => ({}),
},
roadmapFk: {
type: [Number, String],
required: true,
},
layout: {
type: String,
default: 'row',
},
});
const { t } = useI18n();
const warehouseList = ref([]);
const form = computed(() => props.formData);
const isDialog = computed(() => props.layout === 'dialog');
</script>
<template>
<FetchData
url="Warehouses"
auto-load
:filter="{ fields: ['id', 'name'] }"
sort-by="name"
limit="30"
@on-fetch="(data) => (warehouseList = data)"
/>
<div :class="[isDialog ? 'column' : 'form-gap', 'full-width flex']">
<QCardSection class="flex-grow q-px-none flex-1">
<VnSelectFilter
v-model.number="form.warehouseFk"
class="full-width"
:label="t('Warehouse')"
:options="warehouseList"
option-value="id"
option-label="name"
emit-value
map-options
use-input
hide-selected
autofocus
:input-debounce="0"
/>
</QCardSection>
<div :class="[!isDialog ? 'flex-2' : null, 'form-gap flex no-wrap']">
<QCardSection class="row q-px-none flex-1">
<VnInputDate
v-model="form.eta"
class="full-width"
:label="t('ETA date')"
/>
</QCardSection>
<QCardSection class="row q-px-none flex-1">
<VnInputTime
v-model="form.eta"
class="full-width"
:label="t('ETA hour')"
/>
</QCardSection>
</div>
<QCardSection class="q-px-none flex-1">
<VnInput
v-model="form.description"
class="full-width"
:label="t('Description')"
type="textarea"
:rows="1"
/>
</QCardSection>
</div>
</template>
<style lang="scss" scoped>
.form-gap {
gap: 16px;
}
.flex-1 {
flex: 1;
}
.flex-2 {
flex: 2;
}
</style>
<i18n>
es:
Warehouse: Almacén
ETA date: Fecha ETA
ETA hour: Hora ETA
Description: Descripción
Remove stop: Eliminar parada
Add stop: Añadir parada
</i18n>

View File

@ -0,0 +1,143 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import VnRow from 'components/ui/VnRow.vue';
import FormModel from 'components/FormModel.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'components/common/VnInput.vue';
import VnInputTime from 'components/common/VnInputTime.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import FetchData from 'components/FetchData.vue';
import {ref} from "vue";
const { t } = useI18n();
const router = useRouter();
const route = useRoute();
const supplierList = ref([]);
const filter = { include: [{ relation: 'supplier' }] };
const onSave = (data, response) => {
router.push({ name: 'RoadmapSummary', params: { id: response?.id } });
};
</script>
<template>
<VnSubToolbar />
<FetchData
url="Suppliers"
auto-load
:filter="{ fields: ['id', 'nickname'] }"
sort-by="nickname"
limit="30"
@on-fetch="(data) => (supplierList = data)"
/>
<FormModel
:url="`Roadmaps/${route.params?.id}`"
observe-form-changes
:filter="filter"
model="roadmap"
auto-load
@on-data-saved="onSave"
>
<template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput v-model="data.name" :label="t('Roadmap')" clearable />
</div>
<div class="col">
<VnInputDate v-model="data.etd" :label="t('ETD date')" />
</div>
<div class="col">
<VnInputTime v-model="data.etd" :label="t('ETD hour')" />
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput
v-model="data.tractorPlate"
:label="t('Tractor plate')"
clearable
/>
</div>
<div class="col">
<VnInput
v-model="data.trailerPlate"
:label="t('Trailer plate')"
clearable
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('Carrier')"
v-model="data.supplierFk"
:options="supplierList"
option-value="id"
option-label="nickname"
emit-value
map-options
use-input
:input-debounce="0"
>
<template #option="{ itemProps, opt }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel
>{{ opt.id }} - {{ opt.nickname }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</div>
<div class="col">
<VnInput
v-model="data.price"
:label="t('Price')"
type="number"
clearable
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput
v-model="data.driverName"
:label="t('Driver name')"
clearable
/>
</div>
<div class="col">
<VnInput
v-model="data.phone"
:label="t('Phone')"
clearable
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput
v-model="data.observations"
:label="t('Observations')"
clearable
/>
</div>
</VnRow>
</template>
</FormModel>
</template>
<i18n>
es:
Roadmap: Troncal
ETD date: Fecha ETD
ETD hour: Hora ETD
Tractor plate: Matrícula tractor
Trailer plate: Matrícula trailer
Carrier: Transportista
Price: Precio
Driver name: Nombre del conductor
Phone: Teléfono
Observations: Observaciones
</i18n>

View File

@ -0,0 +1,21 @@
<script setup>
import { useStateStore } from 'stores/useStateStore';
import LeftMenu from 'components/LeftMenu.vue';
import RoadmapDescriptor from "pages/Route/Roadmap/RoadmapDescriptor.vue";
const stateStore = useStateStore();
</script>
<template>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit">
<RoadmapDescriptor />
<QSeparator />
<LeftMenu source="card" />
</QScrollArea>
</QDrawer>
<QPageContainer>
<QPage>
<RouterView></RouterView>
</QPage>
</QPageContainer>
</template>

View File

@ -0,0 +1,56 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import VnRow from 'components/ui/VnRow.vue';
import FormModel from 'components/FormModel.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'components/common/VnInput.vue';
import VnInputTime from 'components/common/VnInputTime.vue';
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const defaultInitialData = {
etd: Date.vnNew().toISOString(),
};
const onSave = (data, response) => {
router.push({ name: 'RoadmapSummary', params: { id: response?.id } });
};
</script>
<template>
<VnSubToolbar />
<FormModel
:url="null"
url-create="Roadmaps"
model="roadmap"
:observe-form-changes="false"
:auto-load="false"
:form-initial-data="defaultInitialData"
@on-data-saved="onSave"
>
<template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput
v-model="data.name"
:label="t('Roadmap')"
clearable
/>
</div>
<div class="col">
<VnInputDate v-model="data.etd" :label="t('ETD date')" />
</div>
<div class="col">
<VnInputTime v-model="data.etd" :label="t('ETD hour')" />
</div>
</VnRow>
</template>
</FormModel>
</template>
<i18n>
es:
Roadmap: Troncal
ETD date: Fecha ETD
ETD hour: Hora ETD
</i18n>

View File

@ -0,0 +1,63 @@
<script setup>
import { ref, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'components/ui/VnLv.vue';
import useCardDescription from 'composables/useCardDescription';
import {dashIfEmpty, toDateHour} from 'src/filters';
import SupplierDescriptorProxy from 'pages/Supplier/Card/SupplierDescriptorProxy.vue';
import RoadmapDescriptorMenu from "pages/Route/Roadmap/RoadmapDescriptorMenu.vue";
const $props = defineProps({
id: {
type: Number,
required: false,
default: null,
},
});
const route = useRoute();
const { t } = useI18n();
const entityId = computed(() => {
return $props.id || route.params.id;
});
const filter = { include: [{ relation: 'supplier' }] };
const data = ref(useCardDescription());
const setData = (entity) => (data.value = useCardDescription(entity.code, entity.id));
</script>
<template>
<CardDescriptor
module="Roadmap"
:url="`Roadmaps/${entityId}`"
:filter="filter"
:title="data.title"
:subtitle="data.subtitle"
data-key="Roadmap"
@on-fetch="setData"
>
<template #body="{ entity }">
<VnLv :label="t('Roadmap')" :value="entity?.name" />
<VnLv :label="t('ETD')" :value="toDateHour(entity?.etd)" />
jsegarra marked this conversation as resolved
Review

Segundos?

Segundos?
Review

Se resuelve en una PR aparte

Se resuelve en una PR aparte
<VnLv :label="t('Carrier')">
<template #value>
<span class="link" v-if="entity?.supplier?.id">
{{ dashIfEmpty(entity?.supplier?.nickname) }}
<SupplierDescriptorProxy :id="entity?.supplier?.id" />
</span>
</template>
</VnLv>
</template>
<template #menu="{ entity }">
<RoadmapDescriptorMenu :route="entity" />
</template>
</CardDescriptor>
</template>
<i18n>
es:
Roadmap: Troncal
Carrier: Transportista
</i18n>

View File

@ -0,0 +1,57 @@
<script setup>
import axios from 'axios';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import VnConfirm from 'components/ui/VnConfirm.vue';
const props = defineProps({
route: {
type: Object,
required: true,
},
});
const router = useRouter();
const quasar = useQuasar();
const { t } = useI18n();
function confirmRemove() {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('Confirm deletion'),
message: t('Are you sure you want to delete this roadmap?'),
promise: remove,
},
})
.onOk(async () => await router.push({ name: 'RouteRoadmap' }));
}
async function remove() {
if (!props.route.id) {
return;
}
await axios.delete(`Roadmaps/${props.route.id}`);
quasar.notify({
message: t('globals.dataDeleted'),
type: 'positive',
});
}
</script>
<template>
<QItem @click="confirmRemove" v-ripple clickable>
<QItemSection avatar>
<QIcon name="delete" />
</QItemSection>
<QItemSection>{{ t('Delete roadmap') }}</QItemSection>
</QItem>
</template>
<i18n>
es:
Confirm deletion: Confirmar eliminación
Are you sure you want to delete this roadmap?: ¿Estás seguro de que quieres eliminar esta troncal?
Delete roadmap: Eliminar troncal
</i18n>

View File

@ -0,0 +1,15 @@
<script setup>
import RoadmapDescriptor from 'pages/Route/Roadmap/RoadmapDescriptor.vue';
const $props = defineProps({
id: {
type: Number,
required: true,
},
});
</script>
<template>
<QPopupProxy>
<RoadmapDescriptor v-if="$props.id" :id="$props.id" />
</QPopupProxy>
</template>

View File

@ -0,0 +1,183 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'components/common/VnInput.vue';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const emit = defineEmits(['search']);
const supplierList = ref([]);
const exprBuilder = (param, value) => {
switch (param) {
case 'tractorPlate':
case 'trailerPlate':
case 'driverName':
case 'phone':
return { [param]: { like: `%${value}%` } };
case 'supplierFk':
case 'price':
return { [param]: value };
case 'from':
return { etd: { gte: value } };
case 'to':
return { etd: { lte: value } };
}
};
</script>
<template>
<FetchData
url="Suppliers"
:filter="{ fields: ['id', 'nickname'] }"
sort-by="nickname"
limit="30"
@on-fetch="(data) => (supplierList = data)"
auto-load
/>
<VnFilterPanel
:data-key="props.dataKey"
:search-button="true"
:expr-builder="exprBuilder"
@search="emit('search')"
>
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params }">
<QItem class="q-my-sm">
<QItemSection>
<VnInputDate v-model="params.from" :label="t('From')" is-outlined />
</QItemSection>
</QItem>
<QItem class="q-my-sm">
<QItemSection>
<VnInputDate v-model="params.to" :label="t('To')" is-outlined />
</QItemSection>
</QItem>
<QItem class="q-my-sm">
<QItemSection>
<VnInput
v-model="params.tractorPlate"
:label="t('Tractor Plate')"
is-outlined
clearable
/>
</QItemSection>
</QItem>
<QItem class="q-my-sm">
<QItemSection>
<VnInput
v-model="params.trailerPlate"
:label="t('Trailer Plate')"
is-outlined
clearable
/>
</QItemSection>
</QItem>
<QItem v-if="supplierList" class="q-my-sm">
<QItemSection>
<VnSelectFilter
:label="t('Carrier')"
v-model="params.supplierFk"
:options="supplierList"
option-value="id"
option-label="nickname"
dense
outlined
rounded
emit-value
map-options
use-input
:input-debounce="0"
>
<template #option="{ itemProps, opt }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel
>{{ opt.id }} - {{ opt.nickname }}</QItemLabel
>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</QItemSection>
</QItem>
<QItem class="q-my-sm">
<QItemSection>
<VnInput
v-model="params.price"
:label="t('Price')"
type="number"
is-outlined
clearable
/>
</QItemSection>
</QItem>
<QItem class="q-my-sm">
<QItemSection>
<VnInput
v-model="params.driverName"
:label="t('Driver name')"
is-outlined
clearable
/>
</QItemSection>
</QItem>
<QItem class="q-my-sm">
<QItemSection>
<VnInput
v-model="params.phone"
:label="t('Phone')"
is-outlined
clearable
/>
</QItemSection>
</QItem>
</template>
</VnFilterPanel>
</template>
<i18n>
en:
params:
tractorPlate: Tractor Plate
trailerPlate: Trailer Plate
supplierFk: Carrier
price: Price
driverName: Driver name
phone: Phone
from: From
to: To
es:
params:
tractorPlate: Matrícula del tractor
trailerPlate: Matrícula del trailer
supplierFk: Transportista
price: Precio
driverName: Nombre del conductor
phone: Teléfono
from: Desde
to: Hasta
From: Desde
To: Hasta
Tractor Plate: Matrícula del tractor
Trailer Plate: Matrícula del trailer
Carrier: Transportista
Price: Precio
Driver name: Nombre del conductor
Phone: Teléfono
</i18n>

View File

@ -0,0 +1,95 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import FetchData from 'components/FetchData.vue';
import { onMounted, ref } from 'vue';
import CrudModel from 'components/CrudModel.vue';
import RoadmapAddStopForm from "pages/Route/Roadmap/RoadmapAddStopForm.vue";
const { t } = useI18n();
const route = useRoute();
const roadmapStopsCrudRef = ref(null);
const defaultStop = ref({
roadmapFk: Number(route.params?.id) || 0,
eta: new Date().toISOString(),
});
const updateDefaultStop = (data) => {
const eta = new Date(data.etd);
eta.setDate(eta.getDate() + 1);
defaultStop.value.eta = eta.toISOString();
};
onMounted(() => {
if (roadmapStopsCrudRef.value) roadmapStopsCrudRef.value.reload();
});
</script>
<template>
<VnSubToolbar />
<FetchData
:url="`Roadmaps/${route.params?.id}`"
auto-load
:filter="{ fields: ['etd'] }"
@on-fetch="updateDefaultStop"
/>
<div class="q-pa-lg">
<CrudModel
ref="roadmapStopsCrudRef"
data-key="ExpeditionTrucks"
url="ExpeditionTrucks"
model="ExpeditionTrucks"
:filter="{ where: { roadmapFk: route.params?.id } }"
:default-remove="false"
:data-required="defaultStop"
>
<template #body="{ rows }">
<QCard class="q-pa-md">
<QCardSection
v-for="(row, index) in rows"
:key="index"
class="row no-wrap"
>
<RoadmapAddStopForm :roadmap-fk="route.params?.id" :form-data="row" />
<div class="col-1 row justify-center items-center">
<QIcon
name="delete"
size="sm"
class="cursor-pointer"
color="primary"
@click="roadmapStopsCrudRef.remove([row])"
>
<QTooltip>
{{ t('Remove stop') }}
</QTooltip>
</QIcon>
</div>
</QCardSection>
<QCardSection>
jsegarra marked this conversation as resolved Outdated

Hecho en falta el autofocus cuando es nuevo registro

Hecho en falta el autofocus cuando es nuevo registro

Corregido 7974725da0.

Corregido 7974725da0.

Uff...un ejemplo de, mala practica, tener código duplicado. Has añadido autofocus 1 vez pero ese formulario lo tenemos duplicado.
Lo del formulario duplicado lo comentamos en post-daily, pero entiendo que no te ha dado tiempo a todo

Uff...un ejemplo de, mala practica, tener código duplicado. Has añadido autofocus 1 vez pero ese formulario lo tenemos duplicado. Lo del formulario duplicado lo comentamos en post-daily, pero entiendo que no te ha dado tiempo a todo

Aca se crea un nuevo componente para el formulario 1ab9bfefed.

Aca se crea un nuevo componente para el formulario 1ab9bfefed.
<QIcon
name="add"
size="sm"
class="cursor-pointer"
color="primary"
@click="roadmapStopsCrudRef.insert()"
>
<QTooltip>
{{ t('Add stop') }}
</QTooltip>
</QIcon>
</QCardSection>
</QCard>
</template>
</CrudModel>
</div>
jsegarra marked this conversation as resolved
Review

Tenemos definida una traducción para ETA hour pero no la estamos usando

Tenemos definida una traducción para ETA hour pero no la estamos usando
Review

Corregido 7974725da0.

Corregido 7974725da0.
</template>
<i18n>
es:
Warehouse: Almacén
ETA date: Fecha ETA
ETA hour: Hora ETA
Description: Descripción
Remove stop: Eliminar parada
Add stop: Añadir parada
</i18n>

View File

@ -0,0 +1,172 @@
<script setup>
import { computed, onMounted, onUnmounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { QIcon, useQuasar } from 'quasar';
import { dashIfEmpty, toDateHour } from 'src/filters';
import { useStateStore } from 'stores/useStateStore';
import VnLv from 'components/ui/VnLv.vue';
import CardSummary from 'components/ui/CardSummary.vue';
import SupplierDescriptorProxy from 'pages/Supplier/Card/SupplierDescriptorProxy.vue';
import VnLinkPhone from 'components/ui/VnLinkPhone.vue';
import RoadmapAddStopDialog from 'pages/Route/Roadmap/RoadmapAddStopDialog.vue';
const $props = defineProps({
id: {
type: Number,
default: 0,
},
});
const route = useRoute();
const stateStore = useStateStore();
const { t } = useI18n();
const quasar = useQuasar();
const summary = ref(null);
const entityId = computed(() => $props.id || route.params.id);
const isDialog = Boolean($props.id);
const hideRightDrawer = () => {
if (!isDialog) {
stateStore.rightDrawer = false;
}
};
onMounted(hideRightDrawer);
onUnmounted(hideRightDrawer);
const columns = ref([
{
name: 'warehouse',
label: t('Warehouse'),
field: (row) => dashIfEmpty(row?.warehouse?.name),
sortable: true,
align: 'left',
},
{
name: 'ETA',
label: t('ETA'),
field: (row) => toDateHour(row?.eta),
jsegarra marked this conversation as resolved
Review

segundos?

segundos?
Review

Se resuelve en una PR aparte

Se resuelve en una PR aparte
sortable: false,
align: 'left',
},
]);
const filter = {
include: [
{ relation: 'supplier' },
{ relation: 'worker' },
{
relation: 'expeditionTruck',
scope: { include: [{ relation: 'warehouse' }] },
},
],
};
const openAddStopDialog = () => {
quasar
.dialog({
component: RoadmapAddStopDialog,
componentProps: { roadmapFk: entityId.value },
})
.onOk(() => summary.value.fetch());
};
</script>
<template>
<div class="q-pa-md full-width">
<CardSummary ref="summary" :url="`Roadmaps/${entityId}`" :filter="filter">
<template #header-left>
<RouterLink :to="{ name: `RoadmapSummary`, params: { id: entityId } }">
<QIcon name="open_in_new" color="white" size="sm" />
</RouterLink>
</template>
<template #header="{ entity }">
<span>{{ `${entity?.id} - ${entity?.name}` }}</span>
</template>
<template #body="{ entity }">
<QCard class="vn-one">
<VnLv :label="t('Carrier')">
<template #value>
<span class="link" v-if="entity?.supplier?.id">
{{ dashIfEmpty(entity?.supplier?.nickname) }}
<SupplierDescriptorProxy :id="entity?.supplier?.id" />
</span>
</template>
</VnLv>
<VnLv :label="t('ETD')" :value="toDateHour(entity?.etd)" />
jsegarra marked this conversation as resolved
Review

Segundos?

Segundos?
Review

Se resuelve en una PR aparte

Se resuelve en una PR aparte
<VnLv
:label="t('Tractor Plate')"
:value="dashIfEmpty(entity?.tractorPlate)"
/>
<VnLv
:label="t('Trailer Plate')"
:value="dashIfEmpty(entity?.trailerPlate)"
/>
</QCard>
<QCard class="vn-one">
<VnLv :label="t('Phone')" :value="dashIfEmpty(entity?.phone)">
<template #value>
<span>
{{ dashIfEmpty(entity?.phone) }}
<VnLinkPhone :phone-number="entity?.phone" />
</span>
</template>
</VnLv>
<VnLv
:label="t('Worker')"
:value="
entity?.worker?.firstName
? `${entity?.worker?.firstName} ${entity?.worker?.lastName}`
: '-'
"
/>
<VnLv
:label="t('Observations')"
:value="dashIfEmpty(entity?.observations)"
/>
</QCard>
<QCard class="vn-max">
<div class="flex items-center">
<a class="header" :href="`#/route/roadmap/${entityId}/stops`">
{{ t('Stops') }}
<QIcon name="open_in_new" color="primary">
<QTooltip>
{{ t('Go to stops') }}
</QTooltip>
</QIcon>
</a>
<QIcon
name="add_circle"
color="primary"
class="q-ml-lg header cursor-pointer"
@click.stop="openAddStopDialog"
>
<QTooltip>
{{ t('Add stop') }}
</QTooltip>
</QIcon>
</div>
<QTable
:columns="columns"
:rows="entity?.expeditionTruck"
:rows-per-page-options="[0]"
row-key="id"
flat
hide-pagination
/>
</QCard>
</template>
</CardSummary>
</div>
</template>
<i18n>
es:
Carrier: Transportista
Tractor Plate: Matrícula tractor
Trailer Plate: Matrícula trailer
Phone: Teléfono
Worker: Trabajador
Observations: Observaciones
Stops: Paradas
Warehouse: Almacén
Go to stops: Ir a paradas
Add stop: Añadir parada
</i18n>

View File

@ -0,0 +1,344 @@
<script setup>
import VnPaginate from 'components/ui/VnPaginate.vue';
import { useStateStore } from 'stores/useStateStore';
import { useI18n } from 'vue-i18n';
import { computed, onMounted, onUnmounted, ref } from 'vue';
import { dashIfEmpty, toCurrency, toDate } from 'src/filters';
import VnSearchbar from 'components/ui/VnSearchbar.vue';
import RouteDescriptorProxy from 'pages/Route/Card/RouteDescriptorProxy.vue';
import InvoiceInDescriptorProxy from 'pages/InvoiceIn/Card/InvoiceInDescriptorProxy.vue';
import SupplierDescriptorProxy from 'pages/Supplier/Card/SupplierDescriptorProxy.vue';
import VnLv from 'components/ui/VnLv.vue';
import useNotify from 'composables/useNotify';
import RouteAutonomousFilter from 'pages/Route/Card/RouteAutonomousFilter.vue';
import { useRouter } from 'vue-router';
import RouteSummary from 'pages/Route/Card/RouteSummary.vue';
import { useSummaryDialog } from 'composables/useSummaryDialog';
import VnDms from 'components/common/VnDms.vue';
import { useState } from 'composables/useState';
import axios from 'axios';
const stateStore = useStateStore();
const { t } = useI18n();
const { notify } = useNotify();
const router = useRouter();
const { viewSummary } = useSummaryDialog();
onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false));
const state = useState();
const user = state.getUser();
const dmsDialog = ref({
show: false,
initialForm: {},
rowsToCreateInvoiceIn: [],
});
const selectedRows = ref([]);
const columns = computed(() => [
{
name: 'ID',
label: t('ID'),
field: (row) => row.routeFk,
sortable: true,
align: 'left',
},
{
name: 'date',
label: t('Date'),
field: (row) => toDate(row.created),
sortable: true,
align: 'left',
},
{
name: 'agency-route',
label: t('Agency route'),
field: (row) => row?.agencyModeName,
sortable: true,
align: 'left',
},
{
name: 'agency-agreement',
label: t('Agency agreement'),
field: (row) => row?.agencyAgreement,
sortable: true,
align: 'left',
},
{
name: 'packages',
label: t('Packages'),
field: (row) => dashIfEmpty(row.packages),
sortable: true,
align: 'left',
},
{
name: 'volume',
label: 'm³',
field: (row) => dashIfEmpty(row.m3),
sortable: true,
align: 'left',
},
{
name: 'km',
label: t('Km'),
field: (row) => dashIfEmpty(row?.kmTotal),
sortable: true,
align: 'left',
},
{
name: 'price',
label: t('Price'),
field: (row) => (row?.price ? toCurrency(row.price) : '-'),
sortable: true,
align: 'left',
},
{
name: 'received',
label: t('Received'),
field: (row) => row?.invoiceInFk,
sortable: true,
align: 'left',
},
{
name: 'autonomous',
label: t('Autonomous'),
field: (row) => row?.supplierName,
sortable: true,
align: 'left',
},
{
name: 'actions',
label: '',
sortable: false,
align: 'right',
},
]);
const refreshKey = ref(0);
const total = computed(() => selectedRows.value.reduce((item) => item?.price || 0, 0));
const openDmsUploadDialog = async () => {
dmsDialog.value.rowsToCreateInvoiceIn = selectedRows.value
.filter(
(agencyTerm) => agencyTerm.supplierFk === selectedRows.value?.[0].supplierFk
)
.map((agencyTerm) => ({
routeFk: agencyTerm.routeFk,
supplierFk: agencyTerm.supplierFk,
created: agencyTerm.created,
totalPrice: total.value,
}));
if (dmsDialog.value.rowsToCreateInvoiceIn.length !== selectedRows.value.length) {
dmsDialog.value.rowsToCreateInvoiceIn = [];
dmsDialog.value.initialForm = null;
return notify(t('Two autonomous cannot be counted at the same time'), 'negative');
}
const dmsType = await axios
.get('DmsTypes/findOne', {
filter: {
where: { code: 'invoiceIn' },
},
})
.then((res) => res.data);
dmsDialog.value.initialForm = {
description: selectedRows.value?.[0].supplierName,
companyFk: user.value.companyFk,
warehouseFk: user.value.warehouseFk,
dmsTypeFk: dmsType?.id,
};
dmsDialog.value.show = true;
};
const onDmsSaved = async (dms, response) => {
if (response) {
await axios.post('AgencyTerms/createInvoiceIn', {
rows: dmsDialog.value.rowsToCreateInvoiceIn,
dms: response.data,
});
}
dmsDialog.value.show = false;
dmsDialog.value.initialForm = null;
dmsDialog.value.rowsToCreateInvoiceIn = [];
refreshKey.value++;
};
function navigateToRouteSummary(event, row) {
router.push({ name: 'RouteSummary', params: { id: row.routeFk } });
}
</script>
<template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<VnSearchbar
data-key="RouteAutonomousList"
:label="t('Search autonomous')"
:info="t('You can search by autonomous reference')"
/>
</Teleport>
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
</template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<RouteAutonomousFilter data-key="RouteAutonomousList" />
</QScrollArea>
</QDrawer>
<QPage class="column items-center">
<div class="route-list">
<div class="q-pa-md">
<QCard class="vn-one q-py-sm q-px-lg">
<VnLv class="flex">
<template #label>
<span class="text-h6">{{ t('Total') }}</span>
</template>
<template #value>
<span class="text-h6 q-ml-md">{{ toCurrency(total) }}</span>
</template>
</VnLv>
</QCard>
</div>
<VnPaginate
:key="refreshKey"
data-key="RouteAutonomousList"
url="AgencyTerms/filter"
:limit="20"
auto-load
>
<template #body="{ rows }">
<div class="q-pa-md">
<QTable
v-model:selected="selectedRows"
:columns="columns"
:rows="rows"
flat
row-key="routeFk"
selection="multiple"
:rows-per-page-options="[0]"
hide-pagination
@row-click="navigateToRouteSummary"
>
<template #body-cell-ID="props">
<QTd :props="props">
<span class="link" @click.stop>
{{ props.value }}
<RouteDescriptorProxy :id="props.value" />
</span>
</QTd>
</template>
<template #body-cell-received="props">
<QTd :props="props">
<span :class="props.value && 'link'" @click.stop>
{{ dashIfEmpty(props.value) }}
<InvoiceInDescriptorProxy
v-if="props.value"
:id="props.value"
/>
</span>
</QTd>
</template>
<template #body-cell-autonomous="props">
<QTd :props="props">
<span class="link" @click.stop>
{{ props.value }}
<SupplierDescriptorProxy
v-if="props.row?.supplierFk"
:id="props.row?.supplierFk"
/>
</span>
</QTd>
</template>
<template #body-cell-actions="props">
<QTd :props="props">
<div class="table-actions">
<QIcon
name="preview"
size="xs"
color="primary"
@click.stop="
viewSummary(
props?.row?.routeFk,
RouteSummary
)
"
>
<QTooltip>{{ t('Preview') }}</QTooltip>
</QIcon>
</div>
</QTd>
</template>
</QTable>
</div>
</template>
</VnPaginate>
</div>
<QPageSticky :offset="[20, 20]" v-if="selectedRows?.length">
<QBtn
fab
icon="vn:invoice-in-create"
color="primary"
@click="openDmsUploadDialog"
>
<QTooltip>
{{ t('Create invoiceIn') }}
</QTooltip>
</QBtn>
</QPageSticky>
</QPage>
<QDialog v-model="dmsDialog.show">
<VnDms
url="dms/uploadFile"
model="AgencyTerms"
:form-initial-data="dmsDialog.initialForm"
@on-data-saved="onDmsSaved"
/>
</QDialog>
</template>
<style lang="scss" scoped>
.route-list {
width: 100%;
}
.table-actions {
display: flex;
align-items: center;
gap: 12px;
i {
cursor: pointer;
}
}
</style>
<i18n>
es:
Search autonomous: Buscar autónomos
You can search by autonomous reference: Puedes buscar por referencia del autónomo
Create invoiceIn: Crear factura recibida
Two autonomous cannot be counted at the same time: Dos autonónomos no pueden ser contabilizados al mismo tiempo
Date: Fecha
Agency route: Agencia Ruta
Agency agreement: Agencia Acuerdo
Packages: Bultos
Price: Precio
Received: Recibida
Autonomous: Autónomos
Preview: Vista previa
</i18n>

View File

@ -2,9 +2,8 @@
import VnPaginate from 'components/ui/VnPaginate.vue';
import { useStateStore } from 'stores/useStateStore';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import { computed, onMounted, onUnmounted, ref } from 'vue';
import { dashIfEmpty, toDate, toHour } from 'src/filters';
import { dashIfEmpty, toHour } from 'src/filters';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import FetchData from 'components/FetchData.vue';
import { useValidator } from 'composables/useValidator';
@ -16,12 +15,15 @@ import RouteSearchbar from 'pages/Route/Card/RouteSearchbar.vue';
import RouteFilter from 'pages/Route/Card/RouteFilter.vue';
import RouteSummary from 'pages/Route/Card/RouteSummary.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import {useSession} from "composables/useSession";
import { useSession } from 'composables/useSession';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import RouteListTicketsDialog from 'pages/Route/Card/RouteListTicketsDialog.vue';
import { useQuasar } from 'quasar';
const stateStore = useStateStore();
const { t } = useI18n();
const { validate } = useValidator();
const quasar = useQuasar();
const session = useSession();
const { viewSummary } = useSummaryDialog();
@ -35,7 +37,7 @@ const columns = computed(() => [
label: t('ID'),
field: (row) => row.id,
sortable: true,
align: 'left',
align: 'center',
},
{
name: 'worker',
jsegarra marked this conversation as resolved
Review

Creo que no estamos mostrando correctamente el valor que toca

Creo que no estamos mostrando correctamente el valor que toca
Review

Corregido e8e2bc8403.

Corregido e8e2bc8403.
@ -70,7 +72,7 @@ const columns = computed(() => [
label: 'm³',
field: (row) => dashIfEmpty(row.m3),
sortable: true,
align: 'left',
align: 'center',
},
{
name: 'description',
@ -113,24 +115,6 @@ const updateRoute = async (route) => {
}
};
const updateVehicle = (row, vehicle) => {
row.vehicleFk = vehicle.id;
row.vehiclePlateNumber = vehicle.numberPlate;
updateRoute(row);
};
const updateAgency = (row, agency) => {
row.agencyModeFk = agency.id;
row.agencyName = agency.name;
updateRoute(row);
};
const updateWorker = (row, worker) => {
row.workerFk = worker.id;
row.workerUserName = worker.name;
updateRoute(row);
};
const confirmationDialog = ref(false);
const startingDate = ref(null);
@ -144,8 +128,8 @@ const cloneRoutes = () => {
};
const showRouteReport = () => {
const ids = selectedRows.value.map(row => row?.id)
const idString = ids.join(',')
const ids = selectedRows.value.map((row) => row?.id);
const idString = ids.join(',');
let url;
if (selectedRows.value.length <= 1) {
@ -153,12 +137,12 @@ const showRouteReport = () => {
} else {
const params = new URLSearchParams({
access_token: session.getToken(),
id: idString
})
id: idString,
});
url = `api/Routes/downloadZip?${params.toString()}`;
}
window.open(url, '_blank');
}
};
const markAsServed = () => {
selectedRows.value.forEach((row) => {
@ -169,6 +153,20 @@ const markAsServed = () => {
refreshKey.value++;
startingDate.value = null;
};
const openTicketsDialog = (id) => {
if (!id) {
return;
}
quasar
.dialog({
component: RouteListTicketsDialog,
componentProps: {
id,
},
})
.onOk(() => refreshKey.value++);
};
</script>
<template>
@ -278,199 +276,136 @@ const markAsServed = () => {
:rows-per-page-options="[0]"
hide-pagination
:pagination="{ sortBy: 'ID', descending: true }"
:no-data-label="t('globals.noResults')"
jsegarra marked this conversation as resolved Outdated

Revisamos porque not working para español

❌ Revisamos porque not working para español
>
<template #body-cell-worker="props">
<QTd :props="props">
{{ props.row?.workerUserName }}
<QPopupEdit
:model-value="props.row.workerFk"
v-slot="scope"
buttons
@update:model-value="
(worker) => updateWorker(props.row, worker)
"
<template #body-cell-worker="{ row }">
<QTd>
<VnSelectFilter
:label="t('Worker')"
v-model="row.workerFk"
:options="workers"
option-value="id"
option-label="nickname"
hide-selected
dense
:emit-value="false"
:rules="validate('Route.workerFk')"
:is-clearable="false"
@update:model-value="updateRoute(row)"
>
<VnSelectFilter
:label="t('Worker')"
v-model="scope.value"
:options="workers"
option-value="id"
option-label="name"
hide-selected
autofocus
:emit-value="false"
:rules="validate('Route.workerFk')"
:is-clearable="false"
@keyup.enter="scope.set"
@focus="($event) => $event.target.select()"
>
<template #option="{ opt, itemProps }">
<QItem
v-bind="itemProps"
class="q-pa-xs row items-center"
<template #option="{ opt, itemProps }">
<QItem
v-bind="itemProps"
class="q-pa-xs row items-center"
>
<QItemSection
class="col-9 justify-center"
>
<QItemSection
class="col-9 justify-center"
>
<span>{{ opt.name }}</span>
<span class="text-grey">{{
opt.nickname
}}</span>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</QPopupEdit>
<span>{{ opt.name }}</span>
<span class="text-grey">{{
opt.nickname
}}</span>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</QTd>
</template>
<template #body-cell-agency="props">
<QTd :props="props">
{{ props.row?.agencyName }}
<QPopupEdit
:model-value="props.row.agencyModeFk"
v-slot="scope"
buttons
@update:model-value="
(agency) => updateAgency(props.row, agency)
"
>
<VnSelectFilter
:label="t('Agency')"
v-model="scope.value"
:options="agencyList"
option-value="id"
option-label="name"
hide-selected
autofocus
:emit-value="false"
:rules="validate('route.agencyFk')"
:is-clearable="false"
@keyup.enter="scope.set"
@focus="($event) => $event.target.select()"
/>
</QPopupEdit>
<template #body-cell-agency="{ row }">
<QTd>
<VnSelectFilter
:label="t('Agency')"
v-model="row.agencyModeFk"
:options="agencyList"
option-value="id"
option-label="name"
hide-selected
dense
:emit-value="false"
:rules="validate('route.agencyFk')"
:is-clearable="false"
@update:model-value="updateRoute(row)"
/>
</QTd>
</template>
<template #body-cell-vehicle="props">
<QTd :props="props">
{{ props.row?.vehiclePlateNumber }}
<QPopupEdit
:model-value="props.row.vehicleFk"
v-slot="scope"
buttons
@update:model-value="
(vehicle) => updateVehicle(props.row, vehicle)
"
>
<VnSelectFilter
:label="t('Vehicle')"
v-model="scope.value"
:options="vehicleList"
option-value="id"
option-label="numberPlate"
hide-selected
autofocus
:emit-value="false"
:rules="validate('route.vehicleFk')"
:is-clearable="false"
@keyup.enter="scope.set"
@focus="($event) => $event.target.select()"
/>
</QPopupEdit>
<template #body-cell-vehicle="{ row }">
<QTd>
<VnSelectFilter
:label="t('Vehicle')"
v-model="row.vehicleFk"
:options="vehicleList"
option-value="id"
option-label="numberPlate"
hide-selected
dense
:emit-value="false"
:rules="validate('route.vehicleFk')"
:is-clearable="false"
@update:model-value="updateRoute(row)"
/>
</QTd>
</template>
<template #body-cell-date="props">
<QTd :props="props">
{{ toDate(props.row?.created) }}
<QPopupEdit
v-model="props.row.created"
v-slot="scope"
@update:model-value="updateRoute(props.row)"
buttons
>
<VnInputDate
v-model="scope.value"
autofocus
:label="t('Date')"
:rules="validate('route.created')"
:is-clearable="false"
@keyup.enter="scope.set"
@focus="($event) => $event.target.select()"
/>
</QPopupEdit>
<template #body-cell-date="{ row }">
<QTd class="table-input-cell">
<VnInputDate
v-model="row.created"
hide-bottom-space
dense
:label="t('Date')"
:rules="validate('route.created')"
:is-clearable="false"
@update:model-value="updateRoute(row)"
/>
</QTd>
</template>
<template #body-cell-description="props">
<QTd :props="props">
{{ props.row?.description }}
<QPopupEdit
v-model="props.row.description"
v-slot="scope"
@update:model-value="updateRoute(props.row)"
buttons
>
<VnInput
v-model="scope.value"
autofocus
:label="t('Description')"
:rules="validate('route.description')"
:is-clearable="false"
@keyup.enter="scope.set"
@focus="($event) => $event.target.select()"
/>
</QPopupEdit>
<template #body-cell-description="{ row }">
<QTd class="table-input-cell">
<VnInput
v-model="row.description"
:label="t('Description')"
:rules="validate('route.description')"
:is-clearable="false"
dense
@update:model-value="updateRoute(row)"
/>
</QTd>
</template>
<template #body-cell-started="props">
<QTd :props="props">
{{ toHour(props.row.started) }}
<QPopupEdit
v-model="props.row.started"
v-slot="scope"
buttons
@update:model-value="updateRoute(props.row)"
>
<VnInputTime
v-model="scope.value"
autofocus
:label="t('Hour started')"
:rules="validate('route.started')"
:is-clearable="false"
@keyup.enter="scope.set"
@focus="($event) => $event.target.select()"
/>
</QPopupEdit>
<template #body-cell-started="{ row }">
<QTd class="table-input-cell">
<VnInputTime
jsegarra marked this conversation as resolved
Review

Revisamos porque me añade una hora a la que he seleccionado
Si, selecciono 13:00 me aparece 14:00

Revisamos porque me añade una hora a la que he seleccionado Si, selecciono 13:00 me aparece 14:00
Review

Este error tiene que ver con el formato de las fechas.

Se soluciona en otro PR #223

Este error tiene que ver con el formato de las fechas. Se soluciona en otro PR https://gitea.verdnatura.es/verdnatura/salix-front/pulls/223
v-model="row.started"
jsegarra marked this conversation as resolved
Review

Si selecciono 12:00 o 00:00, que es lo mismo que ya aparece me dice invalid Date

Si selecciono 12:00 o 00:00, que es lo mismo que ya aparece me dice invalid Date
Review

Este error tiene que ver con el formato de las fechas.

Se soluciona en otro PR #223

Este error tiene que ver con el formato de las fechas. Se soluciona en otro PR https://gitea.verdnatura.es/verdnatura/salix-front/pulls/223
:label="t('Hour started')"
:rules="validate('route.started')"
:is-clearable="false"
hide-bottom-space
dense
@update:model-value="updateRoute(row)"
/>
</QTd>
</template>
<template #body-cell-finished="props">
<QTd :props="props">
{{ toHour(props.row.finished) }}
<QPopupEdit
v-model="props.row.finished"
v-slot="scope"
buttons
@update:model-value="updateRoute(props.row)"
>
<VnInputTime
v-model="scope.value"
autofocus
:label="t('Hour finished')"
:rules="validate('route.finished')"
:is-clearable="false"
@keyup.enter="scope.set"
@focus="($event) => $event.target.select()"
/>
</QPopupEdit>
<template #body-cell-finished="{ row }">
<QTd class="table-input-cell">
<VnInputTime
v-model="row.finished"
autofocus
:label="t('Hour finished')"
:rules="validate('route.finished')"
:is-clearable="false"
hide-bottom-space
dense
@update:model-value="updateRoute(row)"
/>
</QTd>
</template>
<template #body-cell-actions="props">
<QTd :props="props">
<div class="flex items-center table-actions">
<div class="flex items-center no-wrap table-actions">
<QIcon
name="vn:ticketAdd"
size="xs"
color="primary"
class="cursor-pointer"
@click="openTicketsDialog(props?.row?.id)"
>
<QTooltip>{{ t('Add ticket') }}</QTooltip>
</QIcon>
@ -485,6 +420,20 @@ const markAsServed = () => {
>
<QTooltip>{{ t('Preview') }}</QTooltip>
</QIcon>
<RouterLink
:to="{
name: 'RouteSummary',
params: { id: props?.row?.id },
}"
>
<QIcon
name="vn:eye"
size="xs"
color="primary"
>
<QTooltip>{{ t('Summary') }}</QTooltip>
</QIcon>
</RouterLink>
</div>
</QTd>
</template>
@ -505,8 +454,13 @@ const markAsServed = () => {
</template>
<style lang="scss" scoped>
.table-input-cell {
min-width: 150px;
}
.route-list {
width: 100%;
max-height: 100%;
}
.table-actions {
@ -535,4 +489,5 @@ es:
Download selected routes as PDF: Descargar rutas seleccionadas como PDF
Add ticket: Añadir tickets
Preview: Vista previa
Summary: Resumen
</i18n>

View File

@ -0,0 +1,6 @@
<script setup>
import VnLog from 'src/components/common/VnLog.vue';
</script>
<template>
<VnLog model="Route" url="/RouteLogs"></VnLog>
</template>

View File

@ -0,0 +1,296 @@
<script setup>
import VnPaginate from 'components/ui/VnPaginate.vue';
import { useStateStore } from 'stores/useStateStore';
import { useI18n } from 'vue-i18n';
import { computed, onMounted, onUnmounted, ref } from 'vue';
import { dashIfEmpty, toDateHour } from 'src/filters';
import { useQuasar } from 'quasar';
import toCurrency from 'filters/toCurrency';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import SupplierDescriptorProxy from 'pages/Supplier/Card/SupplierDescriptorProxy.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue';
import RoadmapFilter from 'pages/Route/Roadmap/RoadmapFilter.vue';
import VnConfirm from 'components/ui/VnConfirm.vue';
import axios from 'axios';
import VnInputDate from 'components/common/VnInputDate.vue';
import {useSummaryDialog} from "composables/useSummaryDialog";
import RoadmapSummary from "pages/Route/Roadmap/RoadmapSummary.vue";
import {useRouter} from "vue-router";
const stateStore = useStateStore();
const { t } = useI18n();
const quasar = useQuasar();
const router = useRouter();
const { viewSummary } = useSummaryDialog();
const to = Date.vnNew();
to.setDate(to.getDate() + 1);
to.setHours(0, 0, 0, 0);
const from = Date.vnNew();
from.setDate(from.getDate());
from.setHours(0, 0, 0, 0);
onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false));
const selectedRows = ref([]);
const columns = computed(() => [
{
name: 'roadmap',
label: t('Roadmap'),
field: (row) => row.name,
sortable: true,
align: 'left',
},
{
name: 'ETD',
label: t('ETD'),
field: (row) => toDateHour(row.etd),
sortable: true,
align: 'left',
},
{
name: 'carrier',
label: t('Carrier'),
field: (row) => row.supplier?.nickname,
sortable: true,
align: 'left',
},
{
name: 'plate',
label: t('Plate'),
field: (row) => row.tractorPlate,
sortable: true,
align: 'left',
},
{
name: 'price',
label: t('Price'),
field: (row) => toCurrency(row.price),
sortable: true,
align: 'left',
},
{
name: 'observations',
label: t('Observations'),
field: (row) => dashIfEmpty(row.observations),
sortable: true,
align: 'left',
},
{
name: 'actions',
label: '',
sortable: false,
align: 'right',
},
]);
const refreshKey = ref(0);
const isCloneDialogOpen = ref(false);
const etdDate = ref(null);
const filter = {
include: { relation: 'supplier', scope: { fields: ['nickname'] } },
where: {
and: [{ etd: { gte: from } }, { etd: { lte: to } }],
},
};
const cloneSelection = async () => {
await axios.post('Roadmaps/clone', {
etd: etdDate.value,
ids: selectedRows.value.map((row) => row?.id),
});
refreshKey.value++;
etdDate.value = null;
};
const removeSelection = async () => {
await Promise.all(
selectedRows.value.map((roadmap) => {
axios.delete(`Roadmaps/${roadmap.id}`);
})
);
};
function confirmRemove() {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('Selected roadmaps will be removed'),
message: t('Are you sure you want to continue?'),
promise: removeSelection,
},
})
.onOk(() => refreshKey.value++);
}
function navigateToRoadmapSummary(event, row) {
router.push({ name: 'RoadmapSummary', params: { id: row.id } })
}
</script>
<template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<VnSearchbar
data-key="RoadmapList"
:label="t('Search roadmaps')"
:info="t('You can search by roadmap reference')"
/>
</Teleport>
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
</template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<RoadmapFilter data-key="RoadmapList" />
</QScrollArea>
</QDrawer>
<QDialog v-model="isCloneDialogOpen">
<QCard style="min-width: 350px">
<QCardSection>
<p class="text-h6 q-ma-none">
{{ t('Select the estimated date of departure (ETD)') }}
</p>
</QCardSection>
<QCardSection class="q-pt-none">
<VnInputDate :label="t('ETD')" v-model="etdDate" autofocus />
</QCardSection>
<QCardActions align="right">
<QBtn flat :label="t('Cancel')" v-close-popup class="text-primary" />
<QBtn color="primary" v-close-popup @click="cloneSelection">
{{ t('Clone') }}
</QBtn>
</QCardActions>
</QCard>
</QDialog>
<QPage class="column items-center">
<VnSubToolbar class="bg-vn-dark justify-end">
<template #st-actions>
<QBtn
icon="vn:clone"
color="primary"
class="q-mr-sm"
:disable="!selectedRows?.length"
@click="isCloneDialogOpen = true"
>
<QTooltip>{{ t('Clone Selected Routes') }}</QTooltip>
</QBtn>
<QBtn
icon="delete"
color="primary"
class="q-mr-sm"
:disable="!selectedRows?.length"
@click="confirmRemove"
>
<QTooltip>{{ t('Delete roadmap(s)') }}</QTooltip>
</QBtn>
</template>
</VnSubToolbar>
<div class="route-list">
<VnPaginate
:key="refreshKey"
data-key="RoadmapList"
url="Roadmaps"
:limit="20"
:filter="filter"
auto-load
>
<template #body="{ rows }">
<div class="q-pa-md">
<QTable
v-model:selected="selectedRows"
:columns="columns"
:rows="rows"
flat
row-key="id"
selection="multiple"
:rows-per-page-options="[0]"
hide-pagination
:pagination="{ sortBy: 'ID', descending: true }"
@row-click="navigateToRoadmapSummary"
>
<template #body-cell-carrier="props">
<QTd :props="props">
<span v-if="props.value" class="link" @click.stop>
{{ props.value }}
<SupplierDescriptorProxy
:id="props.row?.supplier?.id"
/>
</span>
</QTd>
</template>
<template #body-cell-actions="props">
<QTd :props="props">
<div class="flex items-center table-actions">
<QIcon
name="preview"
size="xs"
color="primary"
@click.stop="viewSummary(props?.row?.id, RoadmapSummary)"
class="cursor-pointer"
>
<QTooltip>{{ t('Preview') }}</QTooltip>
</QIcon>
</div>
</QTd>
</template>
</QTable>
</div>
</template>
</VnPaginate>
</div>
<QPageSticky :offset="[20, 20]">
<RouterLink :to="{ name: 'RouteRoadmapCreate' }">
<QBtn fab icon="add" color="primary" />
<QTooltip>
{{ t('Create roadmap') }}
</QTooltip>
</RouterLink>
</QPageSticky>
</QPage>
</template>
<style lang="scss" scoped>
.route-list {
width: 100%;
}
.table-actions {
gap: 12px;
}
</style>
<i18n>
es:
Search roadmaps: Buscar troncales
You can search by roadmap reference: Puedes buscar por referencia de la troncal
Delete roadmap(s): Eliminar troncal(es)
Selected roadmaps will be removed: Las troncales seleccionadas serán eliminadas
Are you sure you want to continue?: ¿Seguro que quieres continuar?
The date can't be empty: La fecha no puede estar vacía
Clone Selected Routes: Clonar rutas seleccionadas
Create roadmap: Crear troncal
Roadmap: Troncal
Carrier: Transportista
Plate: Matrícula
Price: Precio
Observations: Observaciones
Preview: Vista previa
</i18n>

View File

@ -0,0 +1,453 @@
<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';
jgallego marked this conversation as resolved
Review

Al cargar esta seccion desaparece el buscardor de la parte superior, en basic-data sí aparece

Al cargar esta seccion desaparece el buscardor de la parte superior, en basic-data sí aparece
Review

Corregido 77e29a2b87.

Corregido 77e29a2b876988253cc867d29168ee44471ea5f0.
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"
jsegarra marked this conversation as resolved Outdated

font-variation-settings: 'FILL' 1;

font-variation-settings: 'FILL' 1;

Corregido 7974725da0.

Corregido 7974725da0.
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"
>
jsegarra marked this conversation as resolved
Review

añadir: font-variation-settings: 'FILL' 1;

añadir: font-variation-settings: 'FILL' 1;
Review

Corregido 7974725da0.

Corregido 7974725da0.
<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

usar el verbo quitar en lugar de remover

usar el verbo quitar en lugar de remover

Corregido 77e29a2b87.

Corregido 77e29a2b876988253cc867d29168ee44471ea5f0.
Street: Dirección fiscal

usar el verbo quitar en lugar de remover

usar el verbo quitar en lugar de remover

Corregido 77e29a2b87.

Corregido 77e29a2b876988253cc867d29168ee44471ea5f0.
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>

View File

@ -13,6 +13,7 @@ import Travel from './travel';
import Order from './order';
import Department from './department';
import Entry from './entry';
import roadmap from "./roadmap";
export default [
Item,
@ -30,4 +31,5 @@ export default [
invoiceIn,
Department,
Entry,
roadmap
];

View File

@ -0,0 +1,52 @@
import { RouterView } from 'vue-router';
export default {
path: '/route/roadmap',
name: 'Roadmap',
meta: {
title: 'roadmap',
icon: 'vn:troncales',
},
component: RouterView,
redirect: { name: 'RouteMain' },
menus: {
card: ['RoadmapBasicData', 'RoadmapStops'],
},
children: [
{
name: 'RouteRoadmapCard',
path: ':id',
component: () => import('src/pages/Route/Roadmap/RoadmapCard.vue'),
redirect: { name: 'RoadmapSummary' },
children: [
{
name: 'RoadmapSummary',
path: 'summary',
meta: {
title: 'summary',
icon: 'open_in_new',
},
component: () => import('pages/Route/Roadmap/RoadmapSummary.vue'),
},
{
name: 'RoadmapBasicData',
path: 'basic-data',
meta: {
title: 'basicData',
icon: 'vn:settings',
},
component: () => import('pages/Route/Roadmap/RoadmapBasicData.vue'),
},
{
name: 'RoadmapStops',
path: 'stops',
meta: {
title: 'stops',
icon: 'vn:lines',
},
component: () => import('pages/Route/Roadmap/RoadmapStops.vue'),
},
],
},
],
};

View File

@ -11,8 +11,8 @@ export default {
component: RouterView,
redirect: { name: 'RouteMain' },
menus: {
main: ['RouteList', 'CmrList'],
card: ['RouteBasicData'],
main: ['RouteList', 'RouteAutonomous', 'RouteRoadmap', 'CmrList'],
card: ['RouteBasicData', 'RouteTickets', 'RouteLog'],
},
children: [
{
@ -21,15 +21,6 @@ export default {
component: () => import('src/pages/Route/RouteMain.vue'),
redirect: { name: 'RouteList' },
children: [
{
path: 'cmr',
name: 'CmrList',
meta: {
title: 'cmrsList',
icon: 'fact_check',
},
component: () => import('src/pages/Route/Cmr/CmrList.vue'),
},
{
path: 'list',
name: 'RouteList',
@ -47,6 +38,42 @@ export default {
},
component: () => import('src/pages/Route/Card/RouteForm.vue'),
},
{
path: 'agency-term',
name: 'RouteAutonomous',
meta: {
title: 'autonomous',
icon: 'vn:agency-term',
},
component: () => import('src/pages/Route/RouteAutonomous.vue'),
},
{
path: 'roadmap',
name: 'RouteRoadmap',
meta: {
title: 'RouteRoadmap',
icon: 'vn:troncales',
},
component: () => import('src/pages/Route/RouteRoadmap.vue'),
},
{
path: 'roadmap/create',
name: 'RouteRoadmapCreate',
meta: {
title: 'RouteRoadmapCreate',
icon: 'vn:troncales',
},
component: () => import('src/pages/Route/Roadmap/RoadmapCreate.vue'),
},
{
path: 'cmr',
name: 'CmrList',
meta: {
title: 'cmrsList',
icon: 'fact_check',
},
component: () => import('src/pages/Route/Cmr/CmrList.vue'),
},
],
},
{
@ -73,6 +100,24 @@ export default {
},
component: () => import('pages/Route/Card/RouteSummary.vue'),
},
{
path: 'tickets',
name: 'RouteTickets',
meta: {
title: 'tickets',
icon: 'vn:ticket',
},
component: () => import('src/pages/Route/RouteTickets.vue'),
},
{
path: 'log',
name: 'RouteLog',
meta: {
title: 'log',
icon: 'vn:History',
},
component: () => import('src/pages/Route/RouteLog.vue'),
},
],
},
],

View File

@ -13,6 +13,7 @@ import department from './modules/department';
import shelving from 'src/router/modules/shelving';
import order from 'src/router/modules/order';
import entry from 'src/router/modules/entry';
import roadmap from "src/router/modules/roadmap";
const routes = [
{
@ -66,6 +67,7 @@ const routes = [
supplier,
travel,
department,
roadmap,
entry,
{
path: '/:catchAll(.*)*',

22
src/utils/buscaman.js Normal file
View File

@ -0,0 +1,22 @@
import axios from 'axios';
const BUSCAMAN_URL = 'https://gps.buscalia.com/usuario/localizar.aspx?bmi=true&addr=';
export async function openBuscaman(vehicleId, tickets) {
if (!vehicleId) throw new Error(`The route doesn't have a vehicle`);
const response = await axios.get(`Routes/${vehicleId}/getDeliveryPoint`);
if (!response.data) {
throw new Error(`The route's vehicle doesn't have a delivery point`);
}
let addresses = response.data;
tickets.forEach((ticket, index) => {
const previousLine = tickets[index - 1] ? tickets[index - 1].street : null;
if (previousLine !== tickets.street) {
addresses += `+to:${ticket.postalCode} ${ticket.city} ${ticket.street}`;
}
});
window.open(BUSCAMAN_URL + encodeURI(addresses), '_blank');
}