Modulo de rutas #195
|
@ -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
|
||||
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>
|
|
@ -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() {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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>
|
|
@ -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: m³
|
||||
from: From
|
||||
to: To
|
||||
date: Date
|
||||
agencyFk: Agency agreement
|
||||
packages: Packages
|
||||
price: Price
|
||||
invoiceInFk: Received
|
||||
supplierFk: Autonomous
|
||||
es:
|
||||
params:
|
||||
agencyModeFk: Agencia ruta
|
||||
m3: m³
|
||||
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>
|
|
@ -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}`"
|
||||
jsegarra
commented
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>
|
||||
|
|
|
@ -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
jgallego
commented
en salix es city. en salix es city.
"Population" se refiere al número total de individuos que habitan en un área geográfica específica
kmartinez
commented
En Salix hay una incongruencia, en el listado de tickets se muestra "city", pero en el modal muestra "population". Se cambio a "City". 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>
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
jsegarra
commented
Segundos? Segundos?
jsegarra
commented
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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
jsegarra
commented
Hecho en falta el autofocus cuando es nuevo registro Hecho en falta el autofocus cuando es nuevo registro
kmartinez
commented
Corregido Corregido 7974725da0.
jsegarra
commented
Uff...un ejemplo de, mala practica, tener código duplicado. Has añadido autofocus 1 vez pero ese formulario lo tenemos duplicado. 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
kmartinez
commented
Aca se crea un nuevo componente para el formulario 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
jsegarra
commented
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
kmartinez
commented
Corregido 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>
|
|
@ -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
jsegarra
commented
segundos? segundos?
jsegarra
commented
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
jsegarra
commented
Segundos? Segundos?
jsegarra
commented
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>
|
|
@ -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>
|
|
@ -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
jsegarra
commented
Creo que no estamos mostrando correctamente el valor que toca Creo que no estamos mostrando correctamente el valor que toca
kmartinez
commented
Corregido 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
jsegarra
commented
❌ 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
jsegarra
commented
Revisamos porque me añade una hora a la que he seleccionado Revisamos porque me añade una hora a la que he seleccionado
Si, selecciono 13:00 me aparece 14:00
kmartinez
commented
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
jsegarra
commented
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
kmartinez
commented
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>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<script setup>
|
||||
import VnLog from 'src/components/common/VnLog.vue';
|
||||
</script>
|
||||
<template>
|
||||
<VnLog model="Route" url="/RouteLogs"></VnLog>
|
||||
</template>
|
|
@ -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>
|
|
@ -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
jgallego
commented
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
kmartinez
commented
Corregido 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
jsegarra
commented
font-variation-settings: 'FILL' 1; font-variation-settings: 'FILL' 1;
kmartinez
commented
Corregido 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
jsegarra
commented
añadir: font-variation-settings: 'FILL' 1; añadir: font-variation-settings: 'FILL' 1;
kmartinez
commented
Corregido 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
|
||||
jgallego
commented
usar el verbo quitar en lugar de remover usar el verbo quitar en lugar de remover
kmartinez
commented
Corregido Corregido 77e29a2b876988253cc867d29168ee44471ea5f0.
|
||||
Street: Dirección fiscal
|
||||
jgallego
commented
usar el verbo quitar en lugar de remover usar el verbo quitar en lugar de remover
kmartinez
commented
Corregido 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>
|
|
@ -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
|
||||
];
|
||||
|
|
|
@ -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'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
|
@ -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'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
@ -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(.*)*',
|
||||
|
|
|
@ -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');
|
||||
}
|
Teniendo arriba:
Hace falta estos ifs?
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