fixes #5056 Nuevo sistema de sacado de pedidos: Gestión de vagones #43
|
@ -29,7 +29,7 @@ module.exports = configure(function (/* ctx */) {
|
||||||
// app boot file (/src/boot)
|
// app boot file (/src/boot)
|
||||||
// --> boot files are part of "main.js"
|
// --> boot files are part of "main.js"
|
||||||
// https://v2.quasar.dev/quasar-cli/boot-files
|
// https://v2.quasar.dev/quasar-cli/boot-files
|
||||||
boot: ['i18n', 'axios'],
|
boot: ['i18n', 'axios', 'vnDate'],
|
||||||
|
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
||||||
css: ['app.scss'],
|
css: ['app.scss'],
|
||||||
|
|
65
src/App.vue
65
src/App.vue
|
@ -1,16 +1,10 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted } from 'vue';
|
import { onMounted } from 'vue';
|
||||||
import axios from 'axios';
|
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
import { useSession } from 'src/composables/useSession';
|
|
||||||
|
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
const router = useRouter();
|
const { availableLocales, locale, fallbackLocale } = useI18n();
|
||||||
const session = useSession();
|
|
||||||
const { t, availableLocales, locale, fallbackLocale } = useI18n();
|
|
||||||
const { isLoggedIn } = session;
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
let userLang = window.navigator.language;
|
let userLang = window.navigator.language;
|
||||||
|
@ -39,63 +33,6 @@ quasar.iconMapFn = (iconName) => {
|
||||||
content: iconName,
|
content: iconName,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function responseError(error) {
|
|
||||||
let message = error.message;
|
|
||||||
let logOut = false;
|
|
||||||
|
|
||||||
switch (error.response?.status) {
|
|
||||||
case 401:
|
|
||||||
message = 'login.loginError';
|
|
||||||
|
|
||||||
if (isLoggedIn()) {
|
|
||||||
message = 'errors.statusUnauthorized';
|
|
||||||
logOut = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 403:
|
|
||||||
message = 'errors.statusUnauthorized';
|
|
||||||
break;
|
|
||||||
case 500:
|
|
||||||
message = 'errors.statusInternalServerError';
|
|
||||||
break;
|
|
||||||
case 502:
|
|
||||||
message = 'errors.statusBadGateway';
|
|
||||||
break;
|
|
||||||
case 504:
|
|
||||||
message = 'errors.statusGatewayTimeout';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let translatedMessage = t(message);
|
|
||||||
if (!translatedMessage) translatedMessage = message;
|
|
||||||
|
|
||||||
quasar.notify({
|
|
||||||
message: translatedMessage,
|
|
||||||
type: 'negative',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (logOut) {
|
|
||||||
session.destroy();
|
|
||||||
router.push({ path: '/login' });
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
axios.interceptors.response.use((response) => {
|
|
||||||
const { method } = response.config;
|
|
||||||
|
|
||||||
const isSaveRequest = method === 'patch';
|
|
||||||
if (isSaveRequest) {
|
|
||||||
quasar.notify({
|
|
||||||
message: t('globals.dataSaved'),
|
|
||||||
type: 'positive',
|
|
||||||
icon: 'check',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}, responseError);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,24 +1,80 @@
|
||||||
import { boot } from 'quasar/wrappers';
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { Notify } from 'quasar';
|
||||||
import { useSession } from 'src/composables/useSession';
|
import { useSession } from 'src/composables/useSession';
|
||||||
|
import { Router } from 'src/router';
|
||||||
|
import { i18n } from './i18n';
|
||||||
|
|
||||||
export default boot(() => {
|
const session = useSession();
|
||||||
const { getToken } = useSession();
|
const { t } = i18n.global;
|
||||||
|
|
||||||
axios.defaults.baseURL = '/api/';
|
axios.defaults.baseURL = '/api/';
|
||||||
|
|
||||||
axios.interceptors.request.use(
|
const onRequest = (config) => {
|
||||||
function (context) {
|
const token = session.getToken();
|
||||||
const token = getToken();
|
if (token.length && config.headers) {
|
||||||
|
config.headers.Authorization = token;
|
||||||
if (token.length && context.headers) {
|
|
||||||
context.headers.Authorization = token;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return context;
|
return config;
|
||||||
},
|
};
|
||||||
function (error) {
|
|
||||||
|
const onRequestError = (error) => {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onResponse = (response) => {
|
||||||
|
const { method } = response.config;
|
||||||
|
|
||||||
|
const isSaveRequest = method === 'patch';
|
||||||
|
if (isSaveRequest) {
|
||||||
|
Notify.create({
|
||||||
|
message: t('globals.dataSaved'),
|
||||||
|
type: 'positive',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
|
||||||
});
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onResponseError = (error) => {
|
||||||
|
let message = '';
|
||||||
|
|
||||||
|
const response = error.response;
|
||||||
|
const responseData = response && response.data;
|
||||||
|
const responseError = responseData && response.data.error;
|
||||||
|
if (responseError) {
|
||||||
|
message = responseError.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (response.status) {
|
||||||
|
case 500:
|
||||||
|
message = 'errors.statusInternalServerError';
|
||||||
|
break;
|
||||||
|
case 502:
|
||||||
|
message = 'errors.statusBadGateway';
|
||||||
|
break;
|
||||||
|
case 504:
|
||||||
|
message = 'errors.statusGatewayTimeout';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.isLoggedIn && response.status === 401) {
|
||||||
|
session.destroy();
|
||||||
|
Router.push({ path: '/login' });
|
||||||
|
}
|
||||||
|
|
||||||
|
Notify.create({
|
||||||
|
message: t(message),
|
||||||
|
type: 'negative',
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
};
|
||||||
|
|
||||||
|
axios.interceptors.request.use(onRequest, onRequestError);
|
||||||
|
axios.interceptors.response.use(onResponse, onResponseError);
|
||||||
|
|
||||||
|
export {
|
||||||
|
onRequest,
|
||||||
|
onResponseError
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { boot } from 'quasar/wrappers';
|
||||||
|
|
||||||
|
export default boot(() => {
|
||||||
|
Date.vnUTC = () => {
|
||||||
|
const env = process.env.NODE_ENV;
|
||||||
|
if (!env || env === 'development') return new Date(Date.UTC(2001, 0, 1, 11));
|
||||||
|
|
||||||
|
return new Date();
|
||||||
|
};
|
||||||
|
|
||||||
|
Date.vnNew = () => {
|
||||||
|
return new Date(Date.vnUTC());
|
||||||
|
};
|
||||||
|
|
||||||
|
Date.vnNow = () => {
|
||||||
|
return new Date(Date.vnUTC()).getTime();
|
||||||
|
};
|
||||||
|
});
|
|
@ -14,14 +14,14 @@ const props = defineProps({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
url: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
data: {
|
data: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
filter: {
|
filter: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null,
|
default: null,
|
||||||
|
@ -38,6 +38,10 @@ const props = defineProps({
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 10,
|
default: 10,
|
||||||
},
|
},
|
||||||
|
userParams: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
offset: {
|
offset: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 500,
|
default: 500,
|
||||||
|
@ -58,6 +62,7 @@ const arrayData = useArrayData(props.dataKey, {
|
||||||
where: props.where,
|
where: props.where,
|
||||||
limit: props.limit,
|
limit: props.limit,
|
||||||
order: props.order,
|
order: props.order,
|
||||||
|
userParams: props.userParams,
|
||||||
});
|
});
|
||||||
const store = arrayData.store;
|
const store = arrayData.store;
|
||||||
|
|
||||||
|
@ -123,12 +128,20 @@ async function onLoad(...params) {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<div
|
||||||
|
v-if="!props.autoLoad && !store.data && !isLoading"
|
||||||
|
class="info-row q-pa-md text-center"
|
||||||
|
>
|
||||||
|
<h5>
|
||||||
|
{{ t('No data to display') }}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="store.data && store.data.length === 0 && !isLoading"
|
v-if="store.data && store.data.length === 0 && !isLoading"
|
||||||
class="info-row q-pa-md text-center"
|
class="info-row q-pa-md text-center"
|
||||||
>
|
>
|
||||||
<h5>
|
<h5>
|
||||||
{{ t('components.smartCard.noData') }}
|
{{ t('No results found') }}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="props.autoLoad && !store.data" class="card-list q-gutter-y-md">
|
<div v-if="props.autoLoad && !store.data" class="card-list q-gutter-y-md">
|
||||||
|
@ -160,9 +173,6 @@ async function onLoad(...params) {
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
// .q-infinite-scroll {
|
|
||||||
// width: 100%;
|
|
||||||
// }
|
|
||||||
.info-row {
|
.info-row {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
@ -171,3 +181,9 @@ async function onLoad(...params) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
No data to display: Sin datos que mostrar
|
||||||
|
No results found: No se han encontrado resultados
|
||||||
|
</i18n>
|
||||||
|
|
|
@ -3,12 +3,13 @@ import { ref } from 'vue';
|
||||||
import { useDialogPluginComponent } from 'quasar';
|
import { useDialogPluginComponent } from 'quasar';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
const $props = defineProps({
|
const props = defineProps({
|
||||||
address: {
|
data: {
|
||||||
type: String,
|
type: Object,
|
||||||
default: '',
|
requied: true,
|
||||||
|
default: null,
|
||||||
},
|
},
|
||||||
send: {
|
promise: {
|
||||||
type: Function,
|
type: Function,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
@ -19,24 +20,30 @@ defineEmits(['confirm', ...useDialogPluginComponent.emits]);
|
||||||
const { dialogRef, onDialogOK } = useDialogPluginComponent();
|
const { dialogRef, onDialogOK } = useDialogPluginComponent();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const address = ref($props.address);
|
const address = ref(props.data.address);
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
|
|
||||||
async function confirm() {
|
async function confirm() {
|
||||||
isLoading.value = true;
|
const response = { address };
|
||||||
await $props.send(address.value);
|
|
||||||
isLoading.value = false;
|
|
||||||
|
|
||||||
onDialogOK();
|
if (props.promise) {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
Object.assign(response, props.data);
|
||||||
|
await props.promise(response);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDialogOK(response);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-dialog ref="dialogRef" persistent>
|
<q-dialog ref="dialogRef" persistent>
|
||||||
<q-card class="q-pa-sm">
|
<q-card class="q-pa-sm">
|
||||||
<q-card-section class="row items-center q-pb-none">
|
<q-card-section class="row items-center q-pb-none">
|
||||||
<span class="text-h6 text-grey">{{
|
<span class="text-h6 text-grey">{{ t('Send email notification') }}</span>
|
||||||
t('Send email notification: Send email notification')
|
|
||||||
}}</span>
|
|
||||||
<q-space />
|
<q-space />
|
||||||
<q-btn icon="close" flat round dense v-close-popup />
|
<q-btn icon="close" flat round dense v-close-popup />
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
@ -53,6 +60,7 @@ async function confirm() {
|
||||||
color="primary"
|
color="primary"
|
||||||
:loading="isLoading"
|
:loading="isLoading"
|
||||||
@click="confirm"
|
@click="confirm"
|
||||||
|
unelevated
|
||||||
/>
|
/>
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
@ -67,6 +75,6 @@ async function confirm() {
|
||||||
|
|
||||||
<i18n>
|
<i18n>
|
||||||
es:
|
es:
|
||||||
Send email notification: Enviar notificación por correo,
|
Send email notification: Enviar notificación por correo
|
||||||
The notification will be sent to the following address: La notificación se enviará a la siguiente dirección
|
The notification will be sent to the following address: La notificación se enviará a la siguiente dirección
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|
|
@ -0,0 +1,261 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { useDialogPluginComponent } from 'quasar';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
const { dialogRef, onDialogOK } = useDialogPluginComponent();
|
||||||
|
const { t, availableLocales } = useI18n();
|
||||||
|
|
||||||
|
defineEmits(['confirm', ...useDialogPluginComponent.emits]);
|
||||||
|
const props = defineProps({
|
||||||
|
subject: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'Verdnatura',
|
||||||
|
},
|
||||||
|
phone: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
template: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
locale: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'es',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
promise: {
|
||||||
|
type: Function,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const maxLength = 160;
|
||||||
|
const locale = ref(props.locale);
|
||||||
|
const subject = ref(props.subject);
|
||||||
|
const phone = ref(props.phone);
|
||||||
|
const message = ref('');
|
||||||
|
|
||||||
|
updateMessage();
|
||||||
|
|
||||||
|
function updateMessage() {
|
||||||
|
const params = props.data;
|
||||||
|
const key = `templates['${props.template}']`;
|
||||||
|
|
||||||
|
message.value = t(key, params, { locale: locale.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalLength = computed(() => message.value.length);
|
||||||
|
const color = computed(() => {
|
||||||
|
if (totalLength.value == maxLength) return 'negative';
|
||||||
|
if ((totalLength.value / maxLength) * 100 > 90) return 'warning';
|
||||||
|
return 'positive';
|
||||||
|
});
|
||||||
|
|
||||||
|
const languages = availableLocales.map((locale) => ({ label: t(locale), value: locale }));
|
||||||
|
|
||||||
|
const isLoading = ref(false);
|
||||||
|
async function send() {
|
||||||
|
const response = {
|
||||||
|
destination: phone.value,
|
||||||
|
message: message.value,
|
||||||
|
};
|
||||||
|
if (props.promise) {
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Object.assign(response, props.data);
|
||||||
|
await props.promise(response);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDialogOK(response);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-dialog ref="dialogRef" persistent>
|
||||||
|
<q-card class="q-pa-sm">
|
||||||
|
<q-card-section class="row items-center q-pb-none">
|
||||||
|
<span class="text-h6 text-grey">
|
||||||
|
{{ t('Send SMS') }}
|
||||||
|
</span>
|
||||||
|
<q-space />
|
||||||
|
<q-btn icon="close" :disable="isLoading" flat round dense v-close-popup />
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section v-if="props.locale">
|
||||||
|
<q-banner class="bg-amber text-white" rounded dense>
|
||||||
|
<template #avatar>
|
||||||
|
<q-icon name="warning" />
|
||||||
|
</template>
|
||||||
|
<span
|
||||||
|
v-html="t('CustomerDefaultLanguage', { locale: t(props.locale) })"
|
||||||
|
></span>
|
||||||
|
</q-banner>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section class="q-pb-xs">
|
||||||
|
<q-select
|
||||||
|
:label="t('Language')"
|
||||||
|
:options="languages"
|
||||||
|
v-model="locale"
|
||||||
|
@update:model-value="updateMessage()"
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
:input-debounce="0"
|
||||||
|
rounded
|
||||||
|
outlined
|
||||||
|
dense
|
||||||
|
/>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section class="q-pb-xs">
|
||||||
|
<q-input
|
||||||
|
:label="t('Phone')"
|
||||||
|
v-model="phone"
|
||||||
|
rounded
|
||||||
|
outlined
|
||||||
|
autofocus
|
||||||
|
dense
|
||||||
|
/>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section class="q-pb-xs">
|
||||||
|
<q-input
|
||||||
|
:label="t('Subject')"
|
||||||
|
v-model="subject"
|
||||||
|
rounded
|
||||||
|
outlined
|
||||||
|
autofocus
|
||||||
|
dense
|
||||||
|
/>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section class="q-mb-md" q-input>
|
||||||
|
<q-input
|
||||||
|
:label="t('Message')"
|
||||||
|
v-model="message"
|
||||||
|
type="textarea"
|
||||||
|
:maxlength="maxLength"
|
||||||
|
:counter="true"
|
||||||
|
:autogrow="true"
|
||||||
|
:bottom-slots="true"
|
||||||
|
:rules="[(value) => value.length < maxLength || 'Error!']"
|
||||||
|
stack-label
|
||||||
|
outlined
|
||||||
|
autofocus
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<q-icon
|
||||||
|
v-if="message !== ''"
|
||||||
|
name="close"
|
||||||
|
@click="message = ''"
|
||||||
|
class="cursor-pointer"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #counter>
|
||||||
|
<q-chip :color="color" dense>
|
||||||
|
{{ totalLength }}/{{ maxLength }}
|
||||||
|
</q-chip>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-actions align="right">
|
||||||
|
<q-btn
|
||||||
|
:label="t('globals.cancel')"
|
||||||
|
color="primary"
|
||||||
|
:disable="isLoading"
|
||||||
|
flat
|
||||||
|
v-close-popup
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
:label="t('globals.confirm')"
|
||||||
|
@click="send()"
|
||||||
|
:loading="isLoading"
|
||||||
|
color="primary"
|
||||||
|
unelevated
|
||||||
|
/>
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.q-chip {
|
||||||
|
transition: background 0.36s;
|
||||||
|
}
|
||||||
|
.q-card {
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
en:
|
||||||
|
CustomerDefaultLanguage: This customer uses <strong>{locale}</strong> as their default language
|
||||||
|
templates:
|
||||||
|
pendingPayment: 'Your order is pending of payment.
|
||||||
|
Please, enter the website and make the payment with a credit card. Thank you.'
|
||||||
|
minAmount: 'A minimum amount of 50€ (VAT excluded) is required for your order
|
||||||
|
{ orderId } of { shipped } to receive it without additional shipping costs.'
|
||||||
|
orderChanges: 'Order {orderId} of { shipped }: { changes }'
|
||||||
|
en: English
|
||||||
|
es: Spanish
|
||||||
|
fr: French
|
||||||
|
pt: Portuguese
|
||||||
|
es:
|
||||||
|
Send SMS: Enviar SMS
|
||||||
|
CustomerDefaultLanguage: Este cliente utiliza <strong>{locale}</strong> como idioma por defecto
|
||||||
|
Language: Idioma
|
||||||
|
Phone: Móvil
|
||||||
|
Subject: Asunto
|
||||||
|
Message: Mensaje
|
||||||
|
templates:
|
||||||
|
pendingPayment: 'Su pedido está pendiente de pago.
|
||||||
|
Por favor, entre en la página web y efectue el pago con tarjeta. Muchas gracias.'
|
||||||
|
minAmount: 'Es necesario un importe mínimo de 50€ (Sin IVA) en su pedido
|
||||||
|
{ orderId } del día { shipped } para recibirlo sin portes adicionales.'
|
||||||
|
orderChanges: 'Pedido {orderId} día { shipped }: { changes }'
|
||||||
|
en: Inglés
|
||||||
|
es: Español
|
||||||
|
fr: Francés
|
||||||
|
pt: Portugués
|
||||||
|
fr:
|
||||||
|
Send SMS: Envoyer SMS
|
||||||
|
CustomerDefaultLanguage: Ce client utilise l'{locale} comme langue par défaut
|
||||||
|
Language: Langage
|
||||||
|
Phone: Mobile
|
||||||
|
Subject: Affaire
|
||||||
|
Message: Message
|
||||||
|
templates:
|
||||||
|
pendingPayment: 'Votre commande est en attente de paiement.
|
||||||
|
Veuillez vous connecter sur le site web et effectuer le paiement par carte. Merci beaucoup.'
|
||||||
|
minAmount: 'Un montant minimum de 50€ (TVA non incluse) est requis pour votre commande
|
||||||
|
{ orderId } du { shipped } afin de la recevoir sans frais de port supplémentaires.'
|
||||||
|
orderChanges: 'Commande { orderId } du { shipped }: { changes }'
|
||||||
|
en: Anglais
|
||||||
|
es: Espagnol
|
||||||
|
fr: Français
|
||||||
|
pt: Portugais
|
||||||
|
pt:
|
||||||
|
Send SMS: Enviar SMS
|
||||||
|
CustomerDefaultLanguage: Este cliente utiliza o <strong>{locale}</strong> como seu idioma padrão
|
||||||
|
Language: Linguagem
|
||||||
|
Phone: Móvel
|
||||||
|
Subject: Assunto
|
||||||
|
Message: Mensagem
|
||||||
|
templates:
|
||||||
|
pendingPayment: 'Seu pedido está pendente de pagamento.
|
||||||
|
Por favor, acesse o site e faça o pagamento com cartão. Muito obrigado.'
|
||||||
|
minAmount: 'É necessário um valor mínimo de 50€ (sem IVA) em seu pedido
|
||||||
|
{ orderId } do dia { shipped } para recebê-lo sem custos de envio adicionais.'
|
||||||
|
orderChanges: 'Pedido { orderId } dia { shipped }: { changes }'
|
||||||
|
en: Inglês
|
||||||
|
es: Espanhol
|
||||||
|
fr: Francês
|
||||||
|
pt: Português
|
||||||
|
</i18n>
|
|
@ -59,9 +59,9 @@ watch(props, async () => {
|
||||||
:to="{ name: `${module}Summary`, params: { id: entity.id } }"
|
:to="{ name: `${module}Summary`, params: { id: entity.id } }"
|
||||||
>
|
>
|
||||||
<q-btn round flat dense size="md" icon="launch" color="white">
|
<q-btn round flat dense size="md" icon="launch" color="white">
|
||||||
<q-tooltip>{{
|
<q-tooltip>
|
||||||
t('components.cardDescriptor.summary')
|
{{ t('components.cardDescriptor.summary') }}
|
||||||
}}</q-tooltip>
|
</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ const props = defineProps({
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
question: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
@ -18,15 +18,37 @@ const props = defineProps({
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
promise: {
|
||||||
|
type: Function,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
defineEmits(['confirm', ...useDialogPluginComponent.emits]);
|
defineEmits(['confirm', ...useDialogPluginComponent.emits]);
|
||||||
|
|
||||||
const { dialogRef, onDialogOK } = useDialogPluginComponent();
|
const { dialogRef, onDialogOK } = useDialogPluginComponent();
|
||||||
|
|
||||||
const question = props.question || t('question');
|
const title = props.title || t('Confirm');
|
||||||
const message = props.message || t('message');
|
const message = props.message || t('Are you sure you want to continue?');
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
|
|
||||||
|
async function confirm() {
|
||||||
|
isLoading.value = true;
|
||||||
|
if (props.promise) {
|
||||||
|
try {
|
||||||
|
await props.promise(props.data);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onDialogOK(props.data);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-dialog ref="dialogRef" persistent>
|
<q-dialog ref="dialogRef" persistent>
|
||||||
|
@ -39,20 +61,28 @@ const isLoading = ref(false);
|
||||||
size="xl"
|
size="xl"
|
||||||
v-if="icon"
|
v-if="icon"
|
||||||
/>
|
/>
|
||||||
<span class="text-h6 text-grey">{{ message }}</span>
|
<span class="text-h6 text-grey">{{ title }}</span>
|
||||||
<q-space />
|
<q-space />
|
||||||
<q-btn icon="close" flat round dense v-close-popup />
|
<q-btn icon="close" :disable="isLoading" flat round dense v-close-popup />
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section class="row items-center">
|
<q-card-section class="row items-center">
|
||||||
{{ question }}
|
{{ message }}
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-actions align="right">
|
<q-card-actions align="right">
|
||||||
<q-btn :label="t('globals.cancel')" color="primary" flat v-close-popup />
|
<q-btn
|
||||||
|
:label="t('globals.cancel')"
|
||||||
|
color="primary"
|
||||||
|
:disable="isLoading"
|
||||||
|
flat
|
||||||
|
v-close-popup
|
||||||
|
/>
|
||||||
<q-btn
|
<q-btn
|
||||||
:label="t('globals.confirm')"
|
:label="t('globals.confirm')"
|
||||||
color="primary"
|
color="primary"
|
||||||
:loading="isLoading"
|
:loading="isLoading"
|
||||||
@click="onDialogOK()"
|
@click="confirm()"
|
||||||
|
unelevated
|
||||||
|
autofocus
|
||||||
/>
|
/>
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
@ -66,13 +96,7 @@ const isLoading = ref(false);
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<i18n>
|
<i18n>
|
||||||
"en": {
|
es:
|
||||||
"question": "Are you sure you want to continue?",
|
Confirm: Confirmar
|
||||||
"message": "Confirm"
|
Are you sure you want to continue?: ¿Seguro que quieres continuar?
|
||||||
}
|
|
||||||
|
|
||||||
"es": {
|
|
||||||
"question": "¿Seguro que quieres continuar?",
|
|
||||||
"message": "Confirmar"
|
|
||||||
}
|
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|
|
@ -15,6 +15,11 @@ const props = defineProps({
|
||||||
required: false,
|
required: false,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
params: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['refresh', 'clear']);
|
const emit = defineEmits(['refresh', 'clear']);
|
||||||
|
@ -24,8 +29,9 @@ const store = arrayData.store;
|
||||||
const userParams = ref({});
|
const userParams = ref({});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
if (props.params) userParams.value = props.params;
|
||||||
const params = store.userParams;
|
const params = store.userParams;
|
||||||
if (params) {
|
if (Object.keys(params).length > 0) {
|
||||||
userParams.value = Object.assign({}, params);
|
userParams.value = Object.assign({}, params);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,11 +23,42 @@ const props = defineProps({
|
||||||
required: false,
|
required: false,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
filter: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: Number,
|
||||||
|
default: 10,
|
||||||
|
},
|
||||||
|
userParams: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const arrayData = useArrayData(props.dataKey);
|
const arrayData = useArrayData(props.dataKey, {
|
||||||
|
url: props.url,
|
||||||
|
filter: props.filter,
|
||||||
|
where: props.where,
|
||||||
|
limit: props.limit,
|
||||||
|
order: props.order,
|
||||||
|
userParams: props.userParams,
|
||||||
|
});
|
||||||
const store = arrayData.store;
|
const store = arrayData.store;
|
||||||
const searchText = ref('');
|
const searchText = ref('');
|
||||||
|
|
||||||
|
|
|
@ -20,20 +20,27 @@ export function useArrayData(key, userOptions) {
|
||||||
|
|
||||||
const page = ref(1);
|
const page = ref(1);
|
||||||
|
|
||||||
if (typeof userOptions === 'object') {
|
|
||||||
if (userOptions.filter) store.filter = userOptions.filter;
|
|
||||||
if (userOptions.url) store.url = userOptions.url;
|
|
||||||
if (userOptions.limit) store.limit = userOptions.limit;
|
|
||||||
if (userOptions.order) store.order = userOptions.order;
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
setOptions();
|
||||||
|
|
||||||
const query = route.query;
|
const query = route.query;
|
||||||
if (query.params) {
|
if (query.params) {
|
||||||
store.userParams = JSON.parse(query.params);
|
store.userParams = JSON.parse(query.params);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function setOptions() {
|
||||||
|
if (typeof userOptions === 'object') {
|
||||||
|
for (const option in userOptions) {
|
||||||
|
if (userOptions[option] == null) continue;
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(store, option)) {
|
||||||
|
store[option] = userOptions[option];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function fetch({ append = false }) {
|
async function fetch({ append = false }) {
|
||||||
if (!store.url) return;
|
if (!store.url) return;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import toLowerCase from './toLowerCase';
|
import toLowerCase from './toLowerCase';
|
||||||
import toDate from './toDate';
|
import toDate from './toDate';
|
||||||
|
import toDateString from './toDateString';
|
||||||
import toCurrency from './toCurrency';
|
import toCurrency from './toCurrency';
|
||||||
import toPercentage from './toPercentage';
|
import toPercentage from './toPercentage';
|
||||||
import toLowerCamel from './toLowerCamel';
|
import toLowerCamel from './toLowerCamel';
|
||||||
|
@ -9,6 +10,7 @@ export {
|
||||||
toLowerCase,
|
toLowerCase,
|
||||||
toLowerCamel,
|
toLowerCamel,
|
||||||
toDate,
|
toDate,
|
||||||
|
toDateString,
|
||||||
toCurrency,
|
toCurrency,
|
||||||
toPercentage,
|
toPercentage,
|
||||||
dashIfEmpty,
|
dashIfEmpty,
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
export default function toDateString(date) {
|
||||||
|
let day = date.getDate();
|
||||||
|
let month = date.getMonth() + 1;
|
||||||
|
let year = date.getFullYear();
|
||||||
|
|
||||||
|
if (day < 10) day = `0${day}`;
|
||||||
|
if (month < 10) month = `0${month}`;
|
||||||
|
|
||||||
|
return `${year}-${month}-${day}`
|
||||||
|
}
|
|
@ -434,7 +434,6 @@ export default {
|
||||||
logOut: 'Log Out',
|
logOut: 'Log Out',
|
||||||
},
|
},
|
||||||
smartCard: {
|
smartCard: {
|
||||||
noData: 'No data to display',
|
|
||||||
openCard: 'View card',
|
openCard: 'View card',
|
||||||
openSummary: 'Open summary',
|
openSummary: 'Open summary',
|
||||||
viewDescription: 'View description',
|
viewDescription: 'View description',
|
||||||
|
|
|
@ -433,7 +433,6 @@ export default {
|
||||||
logOut: 'Cerrar sesión',
|
logOut: 'Cerrar sesión',
|
||||||
},
|
},
|
||||||
smartCard: {
|
smartCard: {
|
||||||
noData: 'Sin datos que mostrar',
|
|
||||||
openCard: 'Ver ficha',
|
openCard: 'Ver ficha',
|
||||||
openSummary: 'Abrir detalles',
|
openSummary: 'Abrir detalles',
|
||||||
viewDescription: 'Ver descripción',
|
viewDescription: 'Ver descripción',
|
||||||
|
|
|
@ -12,6 +12,7 @@ const { t } = useI18n();
|
||||||
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">
|
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">
|
||||||
<VnSearchbar
|
<VnSearchbar
|
||||||
data-key="ClaimList"
|
data-key="ClaimList"
|
||||||
|
url="Claims/filter"
|
||||||
:label="t('Search claim')"
|
:label="t('Search claim')"
|
||||||
:info="t('You can search by claim id or customer name')"
|
:info="t('You can search by claim id or customer name')"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { toDate } from 'src/filters';
|
import { toDate } from 'src/filters';
|
||||||
|
|
||||||
import TicketDescriptorPopover from 'pages/Ticket/Card/TicketDescriptorPopover.vue';
|
import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue';
|
||||||
import ClaimDescriptorMenu from 'pages/Claim/Card/ClaimDescriptorMenu.vue';
|
import ClaimDescriptorMenu from 'pages/Claim/Card/ClaimDescriptorMenu.vue';
|
||||||
import CardDescriptor from 'components/ui/CardDescriptor.vue';
|
import CardDescriptor from 'components/ui/CardDescriptor.vue';
|
||||||
|
|
||||||
|
@ -86,9 +86,8 @@ function stateColor(code) {
|
||||||
<q-item-label>
|
<q-item-label>
|
||||||
<span class="link">
|
<span class="link">
|
||||||
{{ entity.ticketFk }}
|
{{ entity.ticketFk }}
|
||||||
<q-popup-proxy>
|
|
||||||
<ticket-descriptor-popover :id="entity.ticketFk" />
|
<TicketDescriptorProxy :id="entity.ticketFk" />
|
||||||
</q-popup-proxy>
|
|
||||||
</span>
|
</span>
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { useI18n } from 'vue-i18n';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { usePrintService } from 'composables/usePrintService';
|
import { usePrintService } from 'composables/usePrintService';
|
||||||
import SendEmailDialog from 'components/common/SendEmailDialog.vue';
|
import SendEmailDialog from 'components/common/SendEmailDialog.vue';
|
||||||
|
import VnConfirm from 'components/ui/VnConfirm.vue';
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
claim: {
|
claim: {
|
||||||
|
@ -33,13 +34,15 @@ function confirmPickupOrder() {
|
||||||
quasar.dialog({
|
quasar.dialog({
|
||||||
component: SendEmailDialog,
|
component: SendEmailDialog,
|
||||||
componentProps: {
|
componentProps: {
|
||||||
|
data: {
|
||||||
address: customer.email,
|
address: customer.email,
|
||||||
|
},
|
||||||
send: sendPickupOrder,
|
send: sendPickupOrder,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendPickupOrder(address) {
|
function sendPickupOrder({ address }) {
|
||||||
const id = claim.value.id;
|
const id = claim.value.id;
|
||||||
const customer = claim.value.client;
|
const customer = claim.value.client;
|
||||||
return sendEmail(`Claims/${id}/claim-pickup-email`, {
|
return sendEmail(`Claims/${id}/claim-pickup-email`, {
|
||||||
|
@ -48,16 +51,26 @@ function sendPickupOrder(address) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const showConfirmDialog = ref(false);
|
function confirmRemove() {
|
||||||
async function deleteClaim() {
|
quasar
|
||||||
|
.dialog({
|
||||||
|
component: VnConfirm,
|
||||||
|
componentProps: {
|
||||||
|
title: t('confirmDeletion'),
|
||||||
|
message: t('confirmDeletionMessage'),
|
||||||
|
promise: remove,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.onOk(async () => await router.push({ name: 'ClaimList' }));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function remove() {
|
||||||
const id = claim.value.id;
|
const id = claim.value.id;
|
||||||
await axios.delete(`Claims/${id}`);
|
await axios.delete(`Claims/${id}`);
|
||||||
quasar.notify({
|
quasar.notify({
|
||||||
message: t('globals.dataDeleted'),
|
message: t('globals.dataDeleted'),
|
||||||
type: 'positive',
|
type: 'positive'
|
||||||
icon: 'check',
|
|
||||||
});
|
});
|
||||||
await router.push({ name: 'ClaimList' });
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
@ -87,27 +100,12 @@ async function deleteClaim() {
|
||||||
</q-menu>
|
</q-menu>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-separator />
|
<q-separator />
|
||||||
<q-item @click="showConfirmDialog = true" v-ripple clickable>
|
<q-item @click="confirmRemove()" v-ripple clickable>
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
<q-icon name="delete" />
|
<q-icon name="delete" />
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>{{ t('deleteClaim') }}</q-item-section>
|
<q-item-section>{{ t('deleteClaim') }}</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
|
|
||||||
<q-dialog v-model="showConfirmDialog">
|
|
||||||
<q-card class="q-pa-sm">
|
|
||||||
<q-card-section class="row items-center q-pb-none">
|
|
||||||
<span class="text-h6 text-grey">{{ t('confirmDeletion') }}</span>
|
|
||||||
<q-space />
|
|
||||||
<q-btn icon="close" flat round dense v-close-popup />
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section class="row items-center">{{ t('confirmDeletionMessage') }}</q-card-section>
|
|
||||||
<q-card-actions align="right">
|
|
||||||
<q-btn :label="t('globals.cancel')" color="primary" flat v-close-popup />
|
|
||||||
<q-btn :label="t('globals.confirm')" color="primary" @click="deleteClaim" />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<i18n>
|
<i18n>
|
||||||
|
|
|
@ -158,6 +158,12 @@ en:
|
||||||
hasToPickUp: Has to pick Up
|
hasToPickUp: Has to pick Up
|
||||||
dmsFk: Document ID
|
dmsFk: Document ID
|
||||||
text: Description
|
text: Description
|
||||||
|
claimStateFk: Claim State
|
||||||
|
workerFk: Worker
|
||||||
|
clientFk: Customer
|
||||||
|
rma: RMA
|
||||||
|
responsibility: Responsibility
|
||||||
|
packages: Packages
|
||||||
es:
|
es:
|
||||||
Audit logs: Registros de auditoría
|
Audit logs: Registros de auditoría
|
||||||
Property: Propiedad
|
Property: Propiedad
|
||||||
|
@ -186,4 +192,10 @@ es:
|
||||||
hasToPickUp: Se debe recoger
|
hasToPickUp: Se debe recoger
|
||||||
dmsFk: ID documento
|
dmsFk: ID documento
|
||||||
text: Descripción
|
text: Descripción
|
||||||
|
claimStateFk: Estado de la reclamación
|
||||||
|
workerFk: Trabajador
|
||||||
|
clientFk: Cliente
|
||||||
|
rma: RMA
|
||||||
|
responsibility: Responsabilidad
|
||||||
|
packages: Bultos
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|
|
@ -64,23 +64,23 @@ function openDialog(dmsId) {
|
||||||
multimediaDialog.value = true;
|
multimediaDialog.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function viewDeleteDms(dmsId) {
|
function viewDeleteDms(index) {
|
||||||
quasar
|
quasar
|
||||||
.dialog({
|
.dialog({
|
||||||
component: VnConfirm,
|
component: VnConfirm,
|
||||||
componentProps: {
|
componentProps: {
|
||||||
message: t('This file will be deleted'),
|
title: t('This file will be deleted'),
|
||||||
icon: 'delete',
|
icon: 'delete',
|
||||||
|
data: { index },
|
||||||
|
promise: deleteDms,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.onOk(() => deleteDms(dmsId));
|
.onOk(() => claimDms.value.splice(index, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteDms(index) {
|
async function deleteDms({ index }) {
|
||||||
const dmsId = claimDms.value[index].dmsFk;
|
const dmsId = claimDms.value[index].dmsFk;
|
||||||
await axios.post(`ClaimDms/${dmsId}/removeFile`);
|
await axios.post(`ClaimDms/${dmsId}/removeFile`);
|
||||||
|
|
||||||
claimDms.value.splice(index, 1);
|
|
||||||
quasar.notify({
|
quasar.notify({
|
||||||
message: t('globals.dataDeleted'),
|
message: t('globals.dataDeleted'),
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
|
|
|
@ -69,18 +69,19 @@ function confirmRemove(id) {
|
||||||
quasar
|
quasar
|
||||||
.dialog({
|
.dialog({
|
||||||
component: VnConfirm,
|
component: VnConfirm,
|
||||||
|
componentProps: {
|
||||||
|
data: { id },
|
||||||
|
promise: remove,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.onOk(() => remove(id));
|
.onOk(async () => await arrayData.refresh());
|
||||||
}
|
}
|
||||||
|
|
||||||
async function remove(id) {
|
async function remove({ id }) {
|
||||||
await axios.delete(`ClaimRmas/${id}`);
|
await axios.delete(`ClaimRmas/${id}`);
|
||||||
await arrayData.refresh();
|
|
||||||
|
|
||||||
quasar.notify({
|
quasar.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
message: t('globals.rowRemoved'),
|
message: t('globals.rowRemoved'),
|
||||||
icon: 'check',
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { toDate, toCurrency } from 'src/filters';
|
import { toDate, toCurrency } from 'src/filters';
|
||||||
import CardSummary from 'components/ui/CardSummary.vue';
|
import CardSummary from 'components/ui/CardSummary.vue';
|
||||||
|
import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -109,22 +110,30 @@ function stateColor(code) {
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section v-if="claim.worker && claim.worker.user">
|
<q-item-section v-if="claim.worker && claim.worker.user">
|
||||||
<q-item-label caption>{{
|
<q-item-label caption>
|
||||||
t('claim.summary.assignedTo')
|
{{ t('claim.summary.assignedTo') }}
|
||||||
}}</q-item-label>
|
</q-item-label>
|
||||||
<q-item-label>{{
|
<q-item-label>
|
||||||
claim.worker.user.nickname
|
<span class="link">
|
||||||
}}</q-item-label>
|
{{ claim.worker.user.nickname }}
|
||||||
|
<WorkerDescriptorProxy :id="claim.workerFk" />
|
||||||
|
</span>
|
||||||
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section
|
<q-item-section
|
||||||
v-if="claim.client && claim.client.salesPersonUser"
|
v-if="claim.client && claim.client.salesPersonUser"
|
||||||
>
|
>
|
||||||
<q-item-label caption>{{
|
<q-item-label caption>
|
||||||
t('claim.summary.attendedBy')
|
{{ t('claim.summary.attendedBy') }}
|
||||||
}}</q-item-label>
|
</q-item-label>
|
||||||
<q-item-label>{{
|
<q-item-label>
|
||||||
claim.client.salesPersonUser.name
|
<span class="link">
|
||||||
}}</q-item-label>
|
{{ claim.client.salesPersonUser.name }}
|
||||||
|
<WorkerDescriptorProxy
|
||||||
|
:id="claim.client.salesPersonFk"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { useStateStore } from 'stores/useStateStore';
|
||||||
import { toDate } from 'filters/index';
|
import { toDate } from 'filters/index';
|
||||||
import Paginate from 'components/PaginateData.vue';
|
import Paginate from 'components/PaginateData.vue';
|
||||||
import ClaimSummaryDialog from './Card/ClaimSummaryDialog.vue';
|
import ClaimSummaryDialog from './Card/ClaimSummaryDialog.vue';
|
||||||
import CustomerDescriptorPopover from 'pages/Customer/Card/CustomerDescriptorPopover.vue';
|
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
|
||||||
import VnSearchbar from 'components/ui/VnSearchbar.vue';
|
import VnSearchbar from 'components/ui/VnSearchbar.vue';
|
||||||
import ClaimFilter from './ClaimFilter.vue';
|
import ClaimFilter from './ClaimFilter.vue';
|
||||||
|
|
||||||
|
@ -173,9 +173,8 @@ function viewSummary(id) {
|
||||||
<q-tooltip>
|
<q-tooltip>
|
||||||
{{ t('components.smartCard.viewDescription') }}
|
{{ t('components.smartCard.viewDescription') }}
|
||||||
</q-tooltip>
|
</q-tooltip>
|
||||||
<q-popup-proxy>
|
|
||||||
<CustomerDescriptorPopover :id="row.clientFk" />
|
<CustomerDescriptorProxy :id="row.clientFk" />
|
||||||
</q-popup-proxy>
|
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-item>
|
</q-item>
|
||||||
|
|
|
@ -16,7 +16,7 @@ const input = ref();
|
||||||
|
|
||||||
const newRma = ref({
|
const newRma = ref({
|
||||||
code: '',
|
code: '',
|
||||||
crated: new Date(),
|
crated: Date.vnNew(),
|
||||||
});
|
});
|
||||||
|
|
||||||
function onInputUpdate(value) {
|
function onInputUpdate(value) {
|
||||||
|
@ -35,7 +35,7 @@ async function submit() {
|
||||||
|
|
||||||
newRma.value = {
|
newRma.value = {
|
||||||
code: '',
|
code: '',
|
||||||
created: new Date(),
|
created: Date.vnNew(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,23 +43,25 @@ function confirm(id) {
|
||||||
quasar
|
quasar
|
||||||
.dialog({
|
.dialog({
|
||||||
component: VnConfirm,
|
component: VnConfirm,
|
||||||
|
componentProps: {
|
||||||
|
data: { id },
|
||||||
|
promise: remove,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.onOk(() => remove(id));
|
.onOk(async () => await arrayData.refresh());
|
||||||
}
|
}
|
||||||
|
|
||||||
async function remove(id) {
|
async function remove({ id }) {
|
||||||
await axios.delete(`ClaimRmas/${id}`);
|
await axios.delete(`ClaimRmas/${id}`);
|
||||||
await arrayData.refresh();
|
|
||||||
quasar.notify({
|
quasar.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
message: t('globals.rowRemoved'),
|
message: t('globals.rowRemoved'),
|
||||||
icon: 'check',
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-page class="q-pa-md sticky">
|
<q-page class="column items-center q-pa-md sticky">
|
||||||
<q-page-sticky expand position="top" :offset="[16, 16]">
|
<q-page-sticky expand position="top" :offset="[16, 16]">
|
||||||
<q-card class="card q-pa-md">
|
<q-card class="card q-pa-md">
|
||||||
<q-form @submit="submit">
|
<q-form @submit="submit">
|
||||||
|
@ -79,6 +81,7 @@ async function remove(id) {
|
||||||
</q-form>
|
</q-form>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-page-sticky>
|
</q-page-sticky>
|
||||||
|
<div class="card-list">
|
||||||
<paginate
|
<paginate
|
||||||
data-key="ClaimRmaList"
|
data-key="ClaimRmaList"
|
||||||
url="ClaimRmas"
|
url="ClaimRmas"
|
||||||
|
@ -105,7 +108,11 @@ async function remove(id) {
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-card-actions vertical class="justify-between">
|
<q-card-actions vertical class="justify-between">
|
||||||
<q-skeleton type="circle" class="q-mb-md" size="40px" />
|
<q-skeleton
|
||||||
|
type="circle"
|
||||||
|
class="q-mb-md"
|
||||||
|
size="40px"
|
||||||
|
/>
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-separator />
|
<q-separator />
|
||||||
|
@ -119,7 +126,9 @@ async function remove(id) {
|
||||||
<q-item-label caption>{{
|
<q-item-label caption>{{
|
||||||
t('claim.rmaList.code')
|
t('claim.rmaList.code')
|
||||||
}}</q-item-label>
|
}}</q-item-label>
|
||||||
<q-item-label>{{ row.code }}</q-item-label>
|
<q-item-label>{{
|
||||||
|
row.code
|
||||||
|
}}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
|
@ -141,6 +150,7 @@ async function remove(id) {
|
||||||
</q-card>
|
</q-card>
|
||||||
</template>
|
</template>
|
||||||
</paginate>
|
</paginate>
|
||||||
|
</div>
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -149,7 +159,7 @@ async function remove(id) {
|
||||||
padding-top: 156px;
|
padding-top: 156px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card-list, .card {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 60em;
|
max-width: 60em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ const { t } = useI18n();
|
||||||
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">
|
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">
|
||||||
<VnSearchbar
|
<VnSearchbar
|
||||||
data-key="CustomerList"
|
data-key="CustomerList"
|
||||||
|
url="Clients/filter"
|
||||||
:label="t('Search customer')"
|
:label="t('Search customer')"
|
||||||
:info="t('You can search by customer id or name')"
|
:info="t('You can search by customer id or name')"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -9,7 +9,7 @@ const $props = defineProps({
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-card>
|
<q-popup-proxy>
|
||||||
<customer-descriptor v-if="$props.id" :id="$props.id" />
|
<CustomerDescriptor v-if="$props.id" :id="$props.id" />
|
||||||
</q-card>
|
</q-popup-proxy>
|
||||||
</template>
|
</template>
|
|
@ -12,6 +12,7 @@ const { t } = useI18n();
|
||||||
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">
|
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">
|
||||||
<VnSearchbar
|
<VnSearchbar
|
||||||
data-key="InvoiceOutList"
|
data-key="InvoiceOutList"
|
||||||
|
url="InvoiceOuts/filter"
|
||||||
:label="t('Search invoice')"
|
:label="t('Search invoice')"
|
||||||
:info="t('You can search by invoice reference')"
|
:info="t('You can search by invoice reference')"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -3,8 +3,8 @@ import { ref, computed } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { toCurrency, toDate } from 'src/filters';
|
import { toCurrency, toDate } from 'src/filters';
|
||||||
import CardDescriptor from 'src/components/ui/CardDescriptor.vue';
|
import CardDescriptor from 'components/ui/CardDescriptor.vue';
|
||||||
import CustomerDescriptorPopover from 'src/pages/Customer/Card/CustomerDescriptorPopover.vue';
|
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
id: {
|
id: {
|
||||||
|
@ -80,9 +80,7 @@ function ticketFilter(invoice) {
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
<q-item-label class="link">
|
<q-item-label class="link">
|
||||||
{{ entity.client.name }}
|
{{ entity.client.name }}
|
||||||
<q-popup-proxy>
|
<CustomerDescriptorProxy :id="entity.client.id" />
|
||||||
<customer-descriptor-popover :id="entity.client.id" />
|
|
||||||
</q-popup-proxy>
|
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section v-if="entity.company">
|
<q-item-section v-if="entity.company">
|
||||||
|
|
|
@ -9,7 +9,7 @@ const $props = defineProps({
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-card>
|
<q-popup-proxy>
|
||||||
<invoiceOut-descriptor v-if="$props.id" :id="$props.id" />
|
<InvoiceOutDescriptor v-if="$props.id" :id="$props.id" />
|
||||||
</q-card>
|
</q-popup-proxy>
|
||||||
</template>
|
</template>
|
|
@ -48,7 +48,6 @@ const password = ref('');
|
||||||
const keepLogin = ref(true);
|
const keepLogin = ref(true);
|
||||||
|
|
||||||
async function onSubmit() {
|
async function onSubmit() {
|
||||||
try {
|
|
||||||
const { data } = await axios.post('Accounts/login', {
|
const { data } = await axios.post('Accounts/login', {
|
||||||
user: username.value,
|
user: username.value,
|
||||||
password: password.value,
|
password: password.value,
|
||||||
|
@ -69,9 +68,6 @@ async function onSubmit() {
|
||||||
} else {
|
} else {
|
||||||
router.push({ name: 'Dashboard' });
|
router.push({ name: 'Dashboard' });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -92,10 +88,20 @@ async function onSubmit() {
|
||||||
>
|
>
|
||||||
<q-menu auto-close>
|
<q-menu auto-close>
|
||||||
<q-list dense>
|
<q-list dense>
|
||||||
<q-item @click="userLocale = 'en'" :active="userLocale == 'en'" v-ripple clickable>
|
<q-item
|
||||||
|
@click="userLocale = 'en'"
|
||||||
|
:active="userLocale == 'en'"
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
>
|
||||||
{{ t('globals.lang.en') }}
|
{{ t('globals.lang.en') }}
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item @click="userLocale = 'es'" :active="userLocale == 'es'" v-ripple clickable>
|
<q-item
|
||||||
|
@click="userLocale = 'es'"
|
||||||
|
:active="userLocale == 'es'"
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
>
|
||||||
{{ t('globals.lang.es') }}
|
{{ t('globals.lang.es') }}
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
|
@ -104,30 +110,48 @@ async function onSubmit() {
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t(`globals.darkMode`) }}</q-item-label>
|
<q-item-label caption>{{
|
||||||
|
t(`globals.darkMode`)
|
||||||
|
}}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-toggle v-model="darkMode" checked-icon="dark_mode" unchecked-icon="light_mode" />
|
<q-toggle
|
||||||
|
v-model="darkMode"
|
||||||
|
checked-icon="dark_mode"
|
||||||
|
unchecked-icon="light_mode"
|
||||||
|
/>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-toolbar>
|
</q-toolbar>
|
||||||
</q-page-sticky>
|
</q-page-sticky>
|
||||||
<div class="login-form q-pa-xl">
|
<div class="login-form q-pa-xl">
|
||||||
<q-img src="~/assets/logo.svg" alt="Logo" fit="contain" :ratio="16 / 9" class="q-mb-md" />
|
<q-img
|
||||||
|
src="~/assets/logo.svg"
|
||||||
|
alt="Logo"
|
||||||
|
fit="contain"
|
||||||
|
:ratio="16 / 9"
|
||||||
|
class="q-mb-md"
|
||||||
|
/>
|
||||||
<q-form @submit="onSubmit" class="q-gutter-md">
|
<q-form @submit="onSubmit" class="q-gutter-md">
|
||||||
<q-input
|
<q-input
|
||||||
v-model="username"
|
v-model="username"
|
||||||
:label="t('login.username')"
|
:label="t('login.username')"
|
||||||
lazy-rules
|
lazy-rules
|
||||||
:rules="[(val) => (val && val.length > 0) || t('login.fieldRequired')]"
|
:rules="[
|
||||||
|
(val) =>
|
||||||
|
(val && val.length > 0) || t('login.fieldRequired'),
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
<q-input
|
<q-input
|
||||||
type="password"
|
type="password"
|
||||||
v-model="password"
|
v-model="password"
|
||||||
:label="t('login.password')"
|
:label="t('login.password')"
|
||||||
lazy-rules
|
lazy-rules
|
||||||
:rules="[(val) => (val && val.length > 0) || t('login.fieldRequired')]"
|
:rules="[
|
||||||
|
(val) =>
|
||||||
|
(val && val.length > 0) || t('login.fieldRequired'),
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
<q-toggle v-model="keepLogin" :label="t('login.keepLogin')" />
|
<q-toggle v-model="keepLogin" :label="t('login.keepLogin')" />
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ const { t } = useI18n();
|
||||||
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">
|
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">
|
||||||
<VnSearchbar
|
<VnSearchbar
|
||||||
data-key="TicketList"
|
data-key="TicketList"
|
||||||
|
url="Tickets/filter"
|
||||||
:label="t('Search ticket')"
|
:label="t('Search ticket')"
|
||||||
:info="t('You can search by ticket id or alias')"
|
:info="t('You can search by ticket id or alias')"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -3,8 +3,9 @@ import { computed } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { toDate } from 'src/filters';
|
import { toDate } from 'src/filters';
|
||||||
import CustomerDescriptorPopover from 'src/pages/Customer/Card/CustomerDescriptorPopover.vue';
|
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
|
||||||
import CardDescriptor from 'components/ui/CardDescriptor.vue';
|
import CardDescriptor from 'components/ui/CardDescriptor.vue';
|
||||||
|
import TicketDescriptorMenu from './TicketDescriptorMenu.vue';
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
id: {
|
id: {
|
||||||
|
@ -23,11 +24,25 @@ const entityId = computed(() => {
|
||||||
|
|
||||||
const filter = {
|
const filter = {
|
||||||
include: [
|
include: [
|
||||||
|
{
|
||||||
|
relation: 'address',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'name', 'mobile', 'phone'],
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
relation: 'client',
|
relation: 'client',
|
||||||
scope: {
|
scope: {
|
||||||
fields: ['id', 'name', 'salesPersonFk'],
|
fields: ['id', 'name', 'salesPersonFk', 'phone', 'mobile', 'email'],
|
||||||
include: { relation: 'salesPersonUser' },
|
include: [
|
||||||
|
{
|
||||||
|
relation: 'user',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'lang'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ relation: 'salesPersonUser' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -61,6 +76,9 @@ function stateColor(state) {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<card-descriptor module="Ticket" :url="`Tickets/${entityId}`" :filter="filter">
|
<card-descriptor module="Ticket" :url="`Tickets/${entityId}`" :filter="filter">
|
||||||
|
<template #menu="{ entity }">
|
||||||
|
<TicketDescriptorMenu :ticket="entity" />
|
||||||
|
</template>
|
||||||
<template #description="{ entity }">
|
<template #description="{ entity }">
|
||||||
<span>
|
<span>
|
||||||
{{ entity.client.name }}
|
{{ entity.client.name }}
|
||||||
|
@ -91,9 +109,7 @@ function stateColor(state) {
|
||||||
<q-item-label>
|
<q-item-label>
|
||||||
<span class="link">
|
<span class="link">
|
||||||
{{ entity.clientFk }}
|
{{ entity.clientFk }}
|
||||||
<q-popup-proxy>
|
<CustomerDescriptorProxy :id="entity.client.id" />
|
||||||
<customer-descriptor-popover :id="entity.client.id" />
|
|
||||||
</q-popup-proxy>
|
|
||||||
</span>
|
</span>
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
@ -121,6 +137,16 @@ function stateColor(state) {
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
|
<q-card-actions class="q-gutter-md">
|
||||||
|
<q-icon
|
||||||
|
v-if="entity.isDeleted == true"
|
||||||
|
name="vn:deletedTicket"
|
||||||
|
size="xs"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
<q-tooltip>{{ t('This ticket is deleted') }}</q-tooltip>
|
||||||
|
</q-icon>
|
||||||
|
</q-card-actions>
|
||||||
|
|
||||||
<q-card-actions>
|
<q-card-actions>
|
||||||
<q-btn
|
<q-btn
|
||||||
|
@ -135,3 +161,8 @@ function stateColor(state) {
|
||||||
</template>
|
</template>
|
||||||
</card-descriptor>
|
</card-descriptor>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
This ticket is deleted: Este ticket está eliminado
|
||||||
|
</i18n>
|
||||||
|
|
|
@ -0,0 +1,256 @@
|
||||||
|
<script setup>
|
||||||
|
import axios from 'axios';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
|
import { usePrintService } from 'composables/usePrintService';
|
||||||
|
import SendEmailDialog from 'components/common/SendEmailDialog.vue';
|
||||||
|
import VnConfirm from 'components/ui/VnConfirm.vue';
|
||||||
|
import VnSmsDialog from 'components/common/VnSmsDialog.vue';
|
||||||
|
import toDate from 'filters/toDate';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
ticket: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
const quasar = useQuasar();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { openReport, sendEmail } = usePrintService();
|
||||||
|
|
||||||
|
const ticket = ref(props.ticket);
|
||||||
|
|
||||||
|
function openDeliveryNote(type = 'deliveryNote', documentType = 'pdf') {
|
||||||
|
const path = `Tickets/${ticket.value.id}/delivery-note-${documentType}`;
|
||||||
|
openReport(path, {
|
||||||
|
recipientId: ticket.value.clientFk,
|
||||||
|
type: type,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendDeliveryNoteConfirmation(type = 'deliveryNote', documentType = 'pdf') {
|
||||||
|
const customer = ticket.value.client;
|
||||||
|
quasar.dialog({
|
||||||
|
component: SendEmailDialog,
|
||||||
|
componentProps: {
|
||||||
|
data: {
|
||||||
|
address: customer.email,
|
||||||
|
type: type,
|
||||||
|
documentType: documentType,
|
||||||
|
},
|
||||||
|
promise: sendDeliveryNote,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendDeliveryNote({ address, type, documentType }) {
|
||||||
|
const id = ticket.value.id;
|
||||||
|
const customer = ticket.value.client;
|
||||||
|
let pathName = 'delivery-note-email';
|
||||||
|
if (documentType == 'csv') pathName = 'delivery-note-csv-email';
|
||||||
|
|
||||||
|
const path = `Tickets/${id}/${pathName}`;
|
||||||
|
return sendEmail(path, {
|
||||||
|
recipientId: customer.id,
|
||||||
|
recipient: address,
|
||||||
|
type: type,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const shipped = toDate(ticket.value.shipped);
|
||||||
|
function showSmsDialog(template, customData) {
|
||||||
|
const address = ticket.value.address;
|
||||||
|
const client = ticket.value.client;
|
||||||
|
const phone =
|
||||||
|
route.params.phone ||
|
||||||
|
address.mobile ||
|
||||||
|
address.phone ||
|
||||||
|
client.mobile ||
|
||||||
|
client.phone;
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
orderId: ticket.value.id,
|
||||||
|
shipped: shipped,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof customData === 'object') {
|
||||||
|
Object.assign(data, customData);
|
||||||
|
}
|
||||||
|
|
||||||
|
quasar.dialog({
|
||||||
|
component: VnSmsDialog,
|
||||||
|
componentProps: {
|
||||||
|
phone: phone,
|
||||||
|
template: template,
|
||||||
|
locale: client.user.lang,
|
||||||
|
data: data,
|
||||||
|
promise: sendSms,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showSmsDialogWithChanges() {
|
||||||
|
const query = `TicketLogs/${route.params.id}/getChanges`;
|
||||||
|
const response = await axios.get(query);
|
||||||
|
|
||||||
|
showSmsDialog('orderChanges', { changes: response.data });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendSms(body) {
|
||||||
|
await axios.post(`Tickets/${route.params.id}/sendSms`, body);
|
||||||
|
quasar.notify({
|
||||||
|
message: 'Notification sent',
|
||||||
|
type: 'positive',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmDelete() {
|
||||||
|
quasar
|
||||||
|
.dialog({
|
||||||
|
component: VnConfirm,
|
||||||
|
componentProps: {
|
||||||
|
promise: remove,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.onOk(async () => await router.push({ name: 'TicketList' }));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function remove() {
|
||||||
|
const id = route.params.id;
|
||||||
|
await axios.post(`Tickets/${id}/setDeleted`);
|
||||||
|
|
||||||
|
quasar.notify({
|
||||||
|
message: t('Ticket deleted'),
|
||||||
|
type: 'positive',
|
||||||
|
});
|
||||||
|
quasar.notify({
|
||||||
|
message: t('You can undo this action within the first hour'),
|
||||||
|
icon: 'info',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<q-item v-ripple clickable>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="picture_as_pdf" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>{{ t('Open Delivery Note...') }}</q-item-section>
|
||||||
|
<q-item-section side>
|
||||||
|
<q-icon name="keyboard_arrow_right" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-menu anchor="top end" self="top start" auto-close bordered>
|
||||||
|
<q-list>
|
||||||
|
<q-item @click="openDeliveryNote('deliveryNote')" v-ripple clickable>
|
||||||
|
<q-item-section>{{ t('With prices') }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item @click="openDeliveryNote('withoutPrices')" v-ripple clickable>
|
||||||
|
<q-item-section>{{ t('Without prices') }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item
|
||||||
|
@click="openDeliveryNote('deliveryNote', 'csv')"
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
>
|
||||||
|
<q-item-section>{{ t('As CSV') }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-menu>
|
||||||
|
</q-item>
|
||||||
|
<q-item v-ripple clickable>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="send" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>{{ t('Send Delivery Note...') }}</q-item-section>
|
||||||
|
<q-item-section side>
|
||||||
|
<q-icon name="keyboard_arrow_right" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-menu anchor="top end" self="top start" auto-close>
|
||||||
|
<q-list>
|
||||||
|
<q-item
|
||||||
|
@click="sendDeliveryNoteConfirmation('deliveryNote')"
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
>
|
||||||
|
<q-item-section>{{ t('With prices') }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item
|
||||||
|
@click="sendDeliveryNoteConfirmation('withoutPrices')"
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
>
|
||||||
|
<q-item-section>{{ t('Without prices') }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item
|
||||||
|
@click="sendDeliveryNoteConfirmation('deliveryNote', 'csv')"
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
>
|
||||||
|
<q-item-section>{{ t('As CSV') }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-menu>
|
||||||
|
</q-item>
|
||||||
|
<q-item @click="openDeliveryNote('proforma')" v-ripple clickable>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="receipt" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>{{ t('Open Proforma Invoice') }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item v-ripple clickable>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="sms" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>{{ t('Send SMS...') }}</q-item-section>
|
||||||
|
<q-item-section side>
|
||||||
|
<q-icon name="keyboard_arrow_right" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-menu anchor="top end" self="top start" auto-close>
|
||||||
|
<q-list>
|
||||||
|
<q-item @click="showSmsDialog('pendingPayment')" v-ripple clickable>
|
||||||
|
<q-item-section>{{ t('Pending payment') }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item @click="showSmsDialog('minAmount')" v-ripple clickable>
|
||||||
|
<q-item-section>{{ t('Minimum amount') }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item
|
||||||
|
@click="showSmsDialogWithChanges('orderChanges')"
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
>
|
||||||
|
<q-item-section>{{ t('Order changes') }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-menu>
|
||||||
|
</q-item>
|
||||||
|
<template v-if="!ticket.isDeleted">
|
||||||
|
<q-separator />
|
||||||
|
<q-item @click="confirmDelete()" v-ripple clickable>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="delete" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>{{ t('Delete ticket') }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
Open Delivery Note...: Abrir albarán...
|
||||||
|
Send Delivery Note...: Enviar albarán...
|
||||||
|
With prices: Con precios
|
||||||
|
Without prices: Sin precios
|
||||||
|
As CSV: Como CSV
|
||||||
|
Open Proforma Invoice: Abrir factura proforma
|
||||||
|
Delete ticket: Eliminar ticket
|
||||||
|
Send SMS...: Enviar SMS
|
||||||
|
Pending payment: Pago pendiente
|
||||||
|
Minimum amount: Importe mínimo
|
||||||
|
Order changes: Cambios del pedido
|
||||||
|
Ticket deleted: Ticket eliminado
|
||||||
|
You can undo this action within the first hour: Puedes deshacer esta acción dentro de la primera hora
|
||||||
|
</i18n>
|
|
@ -9,7 +9,7 @@ const $props = defineProps({
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-card>
|
<q-popup-proxy>
|
||||||
<ticket-descriptor v-if="$props.id" :id="$props.id" />
|
<TicketDescriptor v-if="$props.id" :id="$props.id" />
|
||||||
</q-card>
|
</q-popup-proxy>
|
||||||
</template>
|
</template>
|
|
@ -7,6 +7,8 @@ import { dashIfEmpty, toDate, toCurrency } from 'src/filters';
|
||||||
import SkeletonSummary from 'components/ui/SkeletonSummary.vue';
|
import SkeletonSummary from 'components/ui/SkeletonSummary.vue';
|
||||||
import FetchData from 'components/FetchData.vue';
|
import FetchData from 'components/FetchData.vue';
|
||||||
import FetchedTags from 'components/ui/FetchedTags.vue';
|
import FetchedTags from 'components/ui/FetchedTags.vue';
|
||||||
|
import InvoiceOutDescriptorProxy from 'pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue';
|
||||||
|
import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||||
|
|
||||||
onMounted(() => fetch());
|
onMounted(() => fetch());
|
||||||
onUpdated(() => fetch());
|
onUpdated(() => fetch());
|
||||||
|
@ -79,14 +81,20 @@ async function changeState(value) {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<fetch-data url="States/editableStates" @on-fetch="(data) => (editableStates = data)" auto-load />
|
<fetch-data
|
||||||
|
url="States/editableStates"
|
||||||
|
@on-fetch="(data) => (editableStates = data)"
|
||||||
|
auto-load
|
||||||
|
/>
|
||||||
<div class="summary container">
|
<div class="summary container">
|
||||||
<q-card>
|
<q-card>
|
||||||
<skeleton-summary v-if="!ticket" />
|
<skeleton-summary v-if="!ticket" />
|
||||||
<template v-if="ticket">
|
<template v-if="ticket">
|
||||||
<div class="header bg-primary q-pa-sm q-mb-md">
|
<div class="header bg-primary q-pa-sm q-mb-md">
|
||||||
<span>
|
<span>
|
||||||
Ticket #{{ ticket.id }} - {{ ticket.client.name }} ({{ ticket.client.id }}) -
|
Ticket #{{ ticket.id }} - {{ ticket.client.name }} ({{
|
||||||
|
ticket.client.id
|
||||||
|
}}) -
|
||||||
{{ ticket.nickname }}
|
{{ ticket.nickname }}
|
||||||
</span>
|
</span>
|
||||||
<q-btn-dropdown
|
<q-btn-dropdown
|
||||||
|
@ -104,7 +112,13 @@ async function changeState(value) {
|
||||||
separator
|
separator
|
||||||
v-slot="{ item, index }"
|
v-slot="{ item, index }"
|
||||||
>
|
>
|
||||||
<q-item :key="index" dense clickable v-close-popup @click="changeState(item.code)">
|
<q-item
|
||||||
|
:key="index"
|
||||||
|
dense
|
||||||
|
clickable
|
||||||
|
v-close-popup
|
||||||
|
@click="changeState(item.code)"
|
||||||
|
>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label>{{ item.name }}</q-item-label>
|
<q-item-label>{{ item.name }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
@ -118,40 +132,72 @@ async function changeState(value) {
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('ticket.summary.state') }}</q-item-label>
|
<q-item-label caption>{{
|
||||||
<q-item-label :class="stateColor(ticket.ticketState.state)">
|
t('ticket.summary.state')
|
||||||
|
}}</q-item-label>
|
||||||
|
<q-item-label
|
||||||
|
:class="stateColor(ticket.ticketState.state)"
|
||||||
|
>
|
||||||
{{ ticket.ticketState.state.name }}
|
{{ ticket.ticketState.state.name }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('ticket.summary.salesPerson') }}</q-item-label>
|
<q-item-label caption>{{
|
||||||
<q-item-label class="link">{{ ticket.client.salesPersonUser.name }}</q-item-label>
|
t('ticket.summary.salesPerson')
|
||||||
|
}}</q-item-label>
|
||||||
|
<q-item-label>
|
||||||
|
<span class="link">
|
||||||
|
{{ ticket.client.salesPersonUser.name }}
|
||||||
|
<WorkerDescriptorProxy
|
||||||
|
:id="ticket.client.salesPersonFk"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('ticket.summary.agency') }}</q-item-label>
|
<q-item-label caption>{{
|
||||||
<q-item-label>{{ ticket.agencyMode.name }}</q-item-label>
|
t('ticket.summary.agency')
|
||||||
|
}}</q-item-label>
|
||||||
|
<q-item-label>{{
|
||||||
|
ticket.agencyMode.name
|
||||||
|
}}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('ticket.summary.zone') }}</q-item-label>
|
<q-item-label caption>{{
|
||||||
<q-item-label class="link">{{ ticket.routeFk }}</q-item-label>
|
t('ticket.summary.zone')
|
||||||
|
}}</q-item-label>
|
||||||
|
<q-item-label class="link">{{
|
||||||
|
ticket.routeFk
|
||||||
|
}}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('ticket.summary.warehouse') }}</q-item-label>
|
<q-item-label caption>{{
|
||||||
<q-item-label>{{ ticket.warehouse.name }}</q-item-label>
|
t('ticket.summary.warehouse')
|
||||||
|
}}</q-item-label>
|
||||||
|
<q-item-label>{{
|
||||||
|
ticket.warehouse.name
|
||||||
|
}}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('ticket.summary.invoice') }}</q-item-label>
|
<q-item-label caption>{{
|
||||||
<q-item-label v-if="ticket.refFk" class="link">{{ ticket.refFk }}</q-item-label>
|
t('ticket.summary.invoice')
|
||||||
|
}}</q-item-label>
|
||||||
|
<q-item-label v-if="ticket.refFk">
|
||||||
|
<span class="link">
|
||||||
|
{{ ticket.refFk }}
|
||||||
|
<InvoiceOutDescriptorProxy :id="ticket.id" />
|
||||||
|
</span>
|
||||||
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
|
@ -160,49 +206,75 @@ async function changeState(value) {
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('ticket.summary.shipped') }}</q-item-label>
|
<q-item-label caption>{{
|
||||||
<q-item-label>{{ toDate(ticket.shipped) }}</q-item-label>
|
t('ticket.summary.shipped')
|
||||||
|
}}</q-item-label>
|
||||||
|
<q-item-label>{{
|
||||||
|
toDate(ticket.shipped)
|
||||||
|
}}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('ticket.summary.landed') }}</q-item-label>
|
<q-item-label caption>{{
|
||||||
<q-item-label>{{ toDate(ticket.landed) }}</q-item-label>
|
t('ticket.summary.landed')
|
||||||
|
}}</q-item-label>
|
||||||
|
<q-item-label>{{
|
||||||
|
toDate(ticket.landed)
|
||||||
|
}}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('ticket.summary.packages') }}</q-item-label>
|
<q-item-label caption>{{
|
||||||
|
t('ticket.summary.packages')
|
||||||
|
}}</q-item-label>
|
||||||
<q-item-label>{{ ticket.packages }}</q-item-label>
|
<q-item-label>{{ ticket.packages }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('ticket.summary.consigneePhone') }}</q-item-label>
|
<q-item-label caption>{{
|
||||||
<q-item-label>{{ ticket.address.phone }}</q-item-label>
|
t('ticket.summary.consigneePhone')
|
||||||
|
}}</q-item-label>
|
||||||
|
<q-item-label>{{
|
||||||
|
ticket.address.phone
|
||||||
|
}}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('ticket.summary.consigneeMobile') }}</q-item-label>
|
<q-item-label caption>{{
|
||||||
<q-item-label>{{ ticket.address.mobile }}</q-item-label>
|
t('ticket.summary.consigneeMobile')
|
||||||
|
}}</q-item-label>
|
||||||
|
<q-item-label>{{
|
||||||
|
ticket.address.mobile
|
||||||
|
}}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('ticket.summary.clientPhone') }}</q-item-label>
|
<q-item-label caption>{{
|
||||||
|
t('ticket.summary.clientPhone')
|
||||||
|
}}</q-item-label>
|
||||||
<q-item-label>{{ ticket.client.phone }}</q-item-label>
|
<q-item-label>{{ ticket.client.phone }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('ticket.summary.clientMobile') }}</q-item-label>
|
<q-item-label caption>{{
|
||||||
<q-item-label>{{ ticket.client.mobile }}</q-item-label>
|
t('ticket.summary.clientMobile')
|
||||||
|
}}</q-item-label>
|
||||||
|
<q-item-label>{{
|
||||||
|
ticket.client.mobile
|
||||||
|
}}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('ticket.summary.consignee') }}</q-item-label>
|
<q-item-label caption>{{
|
||||||
|
t('ticket.summary.consignee')
|
||||||
|
}}</q-item-label>
|
||||||
<q-item-label>{{ formattedAddress() }}</q-item-label>
|
<q-item-label>{{ formattedAddress() }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
|
@ -226,22 +298,34 @@ async function changeState(value) {
|
||||||
<q-list class="taxes">
|
<q-list class="taxes">
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('ticket.summary.subtotal') }}</q-item-label>
|
<q-item-label caption>{{
|
||||||
<q-item-label>{{ toCurrency(ticket.totalWithoutVat) }}</q-item-label>
|
t('ticket.summary.subtotal')
|
||||||
</q-item-section>
|
}}</q-item-label>
|
||||||
</q-item>
|
|
||||||
<q-item>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label caption>{{ t('ticket.summary.vat') }}</q-item-label>
|
|
||||||
<q-item-label>{{
|
<q-item-label>{{
|
||||||
toCurrency(ticket.totalWithVat - ticket.totalWithoutVat)
|
toCurrency(ticket.totalWithoutVat)
|
||||||
}}</q-item-label>
|
}}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('ticket.summary.total') }}</q-item-label>
|
<q-item-label caption>{{
|
||||||
<q-item-label>{{ toCurrency(ticket.totalWithVat) }}</q-item-label>
|
t('ticket.summary.vat')
|
||||||
|
}}</q-item-label>
|
||||||
|
<q-item-label>{{
|
||||||
|
toCurrency(
|
||||||
|
ticket.totalWithVat - ticket.totalWithoutVat
|
||||||
|
)
|
||||||
|
}}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{
|
||||||
|
t('ticket.summary.total')
|
||||||
|
}}</q-item-label>
|
||||||
|
<q-item-label>{{
|
||||||
|
toCurrency(ticket.totalWithVat)
|
||||||
|
}}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
|
@ -253,7 +337,10 @@ async function changeState(value) {
|
||||||
<q-item-label header class="text-h6">
|
<q-item-label header class="text-h6">
|
||||||
{{ t('ticket.summary.saleLines') }}
|
{{ t('ticket.summary.saleLines') }}
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'TicketBasicData', params: { id: entityId } }"
|
:to="{
|
||||||
|
name: 'TicketBasicData',
|
||||||
|
params: { id: entityId },
|
||||||
|
}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<q-icon name="open_in_new" />
|
<q-icon name="open_in_new" />
|
||||||
|
@ -263,15 +350,33 @@ async function changeState(value) {
|
||||||
<template #header="props">
|
<template #header="props">
|
||||||
<q-tr :props="props">
|
<q-tr :props="props">
|
||||||
<q-th auto-width></q-th>
|
<q-th auto-width></q-th>
|
||||||
<q-th auto-width>{{ t('ticket.summary.item') }}</q-th>
|
<q-th auto-width>{{
|
||||||
<q-th auto-width>{{ t('ticket.summary.visible') }}</q-th>
|
t('ticket.summary.item')
|
||||||
<q-th auto-width>{{ t('ticket.summary.available') }}</q-th>
|
}}</q-th>
|
||||||
<q-th auto-width>{{ t('ticket.summary.quantity') }}</q-th>
|
<q-th auto-width>{{
|
||||||
<q-th auto-width>{{ t('ticket.summary.description') }}</q-th>
|
t('ticket.summary.visible')
|
||||||
<q-th auto-width>{{ t('ticket.summary.price') }}</q-th>
|
}}</q-th>
|
||||||
<q-th auto-width>{{ t('ticket.summary.discount') }}</q-th>
|
<q-th auto-width>{{
|
||||||
<q-th auto-width>{{ t('ticket.summary.amount') }}</q-th>
|
t('ticket.summary.available')
|
||||||
<q-th auto-width>{{ t('ticket.summary.packing') }}</q-th>
|
}}</q-th>
|
||||||
|
<q-th auto-width>{{
|
||||||
|
t('ticket.summary.quantity')
|
||||||
|
}}</q-th>
|
||||||
|
<q-th auto-width>{{
|
||||||
|
t('ticket.summary.description')
|
||||||
|
}}</q-th>
|
||||||
|
<q-th auto-width>{{
|
||||||
|
t('ticket.summary.price')
|
||||||
|
}}</q-th>
|
||||||
|
<q-th auto-width>{{
|
||||||
|
t('ticket.summary.discount')
|
||||||
|
}}</q-th>
|
||||||
|
<q-th auto-width>{{
|
||||||
|
t('ticket.summary.amount')
|
||||||
|
}}</q-th>
|
||||||
|
<q-th auto-width>{{
|
||||||
|
t('ticket.summary.packing')
|
||||||
|
}}</q-th>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
<template #body="props">
|
<template #body="props">
|
||||||
|
@ -284,11 +389,18 @@ async function changeState(value) {
|
||||||
icon="vn:claims"
|
icon="vn:claims"
|
||||||
v-if="props.row.claim"
|
v-if="props.row.claim"
|
||||||
color="primary"
|
color="primary"
|
||||||
:to="{ name: 'ClaimCard', params: { id: props.row.claim.claimFk } }"
|
:to="{
|
||||||
|
name: 'ClaimCard',
|
||||||
|
params: {
|
||||||
|
id: props.row.claim.claimFk,
|
||||||
|
},
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<q-tooltip
|
<q-tooltip
|
||||||
>{{ t('ticket.summary.claim') }}:
|
>{{ t('ticket.summary.claim') }}:
|
||||||
{{ props.row.claim.claimFk }}</q-tooltip
|
{{
|
||||||
|
props.row.claim.claimFk
|
||||||
|
}}</q-tooltip
|
||||||
>
|
>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn
|
<q-btn
|
||||||
|
@ -300,12 +412,17 @@ async function changeState(value) {
|
||||||
color="primary"
|
color="primary"
|
||||||
:to="{
|
:to="{
|
||||||
name: 'ClaimCard',
|
name: 'ClaimCard',
|
||||||
params: { id: props.row.claimBeginning.claimFk },
|
params: {
|
||||||
|
id: props.row.claimBeginning
|
||||||
|
.claimFk,
|
||||||
|
},
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<q-tooltip
|
<q-tooltip
|
||||||
>{{ t('ticket.summary.claim') }}:
|
>{{ t('ticket.summary.claim') }}:
|
||||||
{{ props.row.claimBeginning.claimFk }}</q-tooltip
|
{{
|
||||||
|
props.row.claimBeginning.claimFk
|
||||||
|
}}</q-tooltip
|
||||||
>
|
>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-icon
|
<q-icon
|
||||||
|
@ -325,7 +442,9 @@ async function changeState(value) {
|
||||||
size="xs"
|
size="xs"
|
||||||
color="primary"
|
color="primary"
|
||||||
>
|
>
|
||||||
<q-tooltip>{{ t('ticket.summary.reserved') }}</q-tooltip>
|
<q-tooltip>{{
|
||||||
|
t('ticket.summary.reserved')
|
||||||
|
}}</q-tooltip>
|
||||||
</q-icon>
|
</q-icon>
|
||||||
<q-icon
|
<q-icon
|
||||||
name="vn:unavailable"
|
name="vn:unavailable"
|
||||||
|
@ -333,7 +452,9 @@ async function changeState(value) {
|
||||||
size="xs"
|
size="xs"
|
||||||
color="primary"
|
color="primary"
|
||||||
>
|
>
|
||||||
<q-tooltip>{{ t('ticket.summary.itemShortage') }}</q-tooltip>
|
<q-tooltip>{{
|
||||||
|
t('ticket.summary.itemShortage')
|
||||||
|
}}</q-tooltip>
|
||||||
</q-icon>
|
</q-icon>
|
||||||
<q-icon
|
<q-icon
|
||||||
name="vn:components"
|
name="vn:components"
|
||||||
|
@ -341,7 +462,9 @@ async function changeState(value) {
|
||||||
size="xs"
|
size="xs"
|
||||||
color="primary"
|
color="primary"
|
||||||
>
|
>
|
||||||
<q-tooltip>{{ t('ticket.summary.hasComponentLack') }}</q-tooltip>
|
<q-tooltip>{{
|
||||||
|
t('ticket.summary.hasComponentLack')
|
||||||
|
}}</q-tooltip>
|
||||||
</q-icon>
|
</q-icon>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td class="link">{{ props.row.itemFk }}</q-td>
|
<q-td class="link">{{ props.row.itemFk }}</q-td>
|
||||||
|
@ -351,11 +474,16 @@ async function changeState(value) {
|
||||||
<q-td>
|
<q-td>
|
||||||
<div class="fetched-tags">
|
<div class="fetched-tags">
|
||||||
<span>{{ props.row.item.name }}</span>
|
<span>{{ props.row.item.name }}</span>
|
||||||
<span v-if="props.row.item.subName" class="subName">{{
|
<span
|
||||||
props.row.item.subName
|
v-if="props.row.item.subName"
|
||||||
}}</span>
|
class="subName"
|
||||||
|
>{{ props.row.item.subName }}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<fetched-tags :item="props.row.item" :max-length="5"></fetched-tags>
|
<fetched-tags
|
||||||
|
:item="props.row.item"
|
||||||
|
:max-length="5"
|
||||||
|
></fetched-tags>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td>{{ props.row.price }}</q-td>
|
<q-td>{{ props.row.price }}</q-td>
|
||||||
<q-td>{{ props.row.discount }} %</q-td>
|
<q-td>{{ props.row.discount }} %</q-td>
|
||||||
|
@ -368,14 +496,19 @@ async function changeState(value) {
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td>{{ dashIfEmpty(props.row.item.itemPackingTypeFk) }}</q-td>
|
<q-td>{{
|
||||||
|
dashIfEmpty(props.row.item.itemPackingTypeFk)
|
||||||
|
}}</q-td>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
</q-table>
|
</q-table>
|
||||||
</q-list>
|
</q-list>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-pa-md" v-if="ticket.packagings.length > 0 || ticket.services.length > 0">
|
<div
|
||||||
|
class="row q-pa-md"
|
||||||
|
v-if="ticket.packagings.length > 0 || ticket.services.length > 0"
|
||||||
|
>
|
||||||
<div class="col" v-if="ticket.packagings.length > 0">
|
<div class="col" v-if="ticket.packagings.length > 0">
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item-label header class="text-h6">
|
<q-item-label header class="text-h6">
|
||||||
|
@ -385,9 +518,15 @@ async function changeState(value) {
|
||||||
<q-table :rows="ticket.packagings" flat>
|
<q-table :rows="ticket.packagings" flat>
|
||||||
<template #header="props">
|
<template #header="props">
|
||||||
<q-tr :props="props">
|
<q-tr :props="props">
|
||||||
<q-th auto-width>{{ t('ticket.summary.created') }}</q-th>
|
<q-th auto-width>{{
|
||||||
<q-th auto-width>{{ t('ticket.summary.package') }}</q-th>
|
t('ticket.summary.created')
|
||||||
<q-th auto-width>{{ t('ticket.summary.quantity') }}</q-th>
|
}}</q-th>
|
||||||
|
<q-th auto-width>{{
|
||||||
|
t('ticket.summary.package')
|
||||||
|
}}</q-th>
|
||||||
|
<q-th auto-width>{{
|
||||||
|
t('ticket.summary.quantity')
|
||||||
|
}}</q-th>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
<template #body="props">
|
<template #body="props">
|
||||||
|
@ -409,11 +548,21 @@ async function changeState(value) {
|
||||||
<q-table :rows="ticket.services" flat>
|
<q-table :rows="ticket.services" flat>
|
||||||
<template #header="props">
|
<template #header="props">
|
||||||
<q-tr :props="props">
|
<q-tr :props="props">
|
||||||
<q-th auto-width>{{ t('ticket.summary.quantity') }}</q-th>
|
<q-th auto-width>{{
|
||||||
<q-th auto-width>{{ t('ticket.summary.description') }}</q-th>
|
t('ticket.summary.quantity')
|
||||||
<q-th auto-width>{{ t('ticket.summary.price') }}</q-th>
|
}}</q-th>
|
||||||
<q-th auto-width>{{ t('ticket.summary.taxClass') }}</q-th>
|
<q-th auto-width>{{
|
||||||
<q-th auto-width>{{ t('ticket.summary.amount') }}</q-th>
|
t('ticket.summary.description')
|
||||||
|
}}</q-th>
|
||||||
|
<q-th auto-width>{{
|
||||||
|
t('ticket.summary.price')
|
||||||
|
}}</q-th>
|
||||||
|
<q-th auto-width>{{
|
||||||
|
t('ticket.summary.taxClass')
|
||||||
|
}}</q-th>
|
||||||
|
<q-th auto-width>{{
|
||||||
|
t('ticket.summary.amount')
|
||||||
|
}}</q-th>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
<template #body="props">
|
<template #body="props">
|
||||||
|
@ -422,7 +571,11 @@ async function changeState(value) {
|
||||||
<q-td>{{ props.row.description }}</q-td>
|
<q-td>{{ props.row.description }}</q-td>
|
||||||
<q-td>{{ toCurrency(props.row.price) }}</q-td>
|
<q-td>{{ toCurrency(props.row.price) }}</q-td>
|
||||||
<q-td>{{ props.row.taxClass.description }}</q-td>
|
<q-td>{{ props.row.taxClass.description }}</q-td>
|
||||||
<q-td>{{ toCurrency(props.row.quantity * props.row.price) }}</q-td>
|
<q-td>{{
|
||||||
|
toCurrency(
|
||||||
|
props.row.quantity * props.row.price
|
||||||
|
)
|
||||||
|
}}</q-td>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
</q-table>
|
</q-table>
|
||||||
|
@ -439,13 +592,27 @@ async function changeState(value) {
|
||||||
<q-table :rows="ticket.requests" flat>
|
<q-table :rows="ticket.requests" flat>
|
||||||
<template #header="props">
|
<template #header="props">
|
||||||
<q-tr :props="props">
|
<q-tr :props="props">
|
||||||
<q-th auto-width>{{ t('ticket.summary.description') }}</q-th>
|
<q-th auto-width>{{
|
||||||
<q-th auto-width>{{ t('ticket.summary.created') }}</q-th>
|
t('ticket.summary.description')
|
||||||
<q-th auto-width>{{ t('ticket.summary.requester') }}</q-th>
|
}}</q-th>
|
||||||
<q-th auto-width>{{ t('ticket.summary.atender') }}</q-th>
|
<q-th auto-width>{{
|
||||||
<q-th auto-width>{{ t('ticket.summary.quantity') }}</q-th>
|
t('ticket.summary.created')
|
||||||
<q-th auto-width>{{ t('ticket.summary.price') }}</q-th>
|
}}</q-th>
|
||||||
<q-th auto-width>{{ t('ticket.summary.item') }}</q-th>
|
<q-th auto-width>{{
|
||||||
|
t('ticket.summary.requester')
|
||||||
|
}}</q-th>
|
||||||
|
<q-th auto-width>{{
|
||||||
|
t('ticket.summary.atender')
|
||||||
|
}}</q-th>
|
||||||
|
<q-th auto-width>{{
|
||||||
|
t('ticket.summary.quantity')
|
||||||
|
}}</q-th>
|
||||||
|
<q-th auto-width>{{
|
||||||
|
t('ticket.summary.price')
|
||||||
|
}}</q-th>
|
||||||
|
<q-th auto-width>{{
|
||||||
|
t('ticket.summary.item')
|
||||||
|
}}</q-th>
|
||||||
<q-th auto-width>Ok</q-th>
|
<q-th auto-width>Ok</q-th>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
|
@ -458,8 +625,14 @@ async function changeState(value) {
|
||||||
<q-td>{{ props.row.quantity }}</q-td>
|
<q-td>{{ props.row.quantity }}</q-td>
|
||||||
<q-td>{{ toCurrency(props.row.price) }}</q-td>
|
<q-td>{{ toCurrency(props.row.price) }}</q-td>
|
||||||
<q-td v-if="!props.row.sale">-</q-td>
|
<q-td v-if="!props.row.sale">-</q-td>
|
||||||
<q-td v-if="props.row.sale" class="link">{{ props.row.sale.itemFk }}</q-td>
|
<q-td v-if="props.row.sale" class="link">{{
|
||||||
<q-td><q-checkbox v-model="props.row.isOk" :disable="true" /></q-td>
|
props.row.sale.itemFk
|
||||||
|
}}</q-td>
|
||||||
|
<q-td
|
||||||
|
><q-checkbox
|
||||||
|
v-model="props.row.isOk"
|
||||||
|
:disable="true"
|
||||||
|
/></q-td>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
</q-table>
|
</q-table>
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import FetchData from 'components/FetchData.vue';
|
import FetchData from 'components/FetchData.vue';
|
||||||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||||
|
import toDateString from 'filters/toDateString';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
@ -12,6 +13,15 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const from = Date.vnNew()
|
||||||
|
const to = Date.vnNew();
|
||||||
|
to.setDate(to.getDate() + 1)
|
||||||
|
|
||||||
|
const defaultParams = {
|
||||||
|
from: toDateString(from),
|
||||||
|
to: toDateString(to),
|
||||||
|
};
|
||||||
|
|
||||||
const workers = ref();
|
const workers = ref();
|
||||||
const provinces = ref();
|
const provinces = ref();
|
||||||
const states = ref();
|
const states = ref();
|
||||||
|
@ -30,7 +40,11 @@ const warehouses = ref();
|
||||||
@on-fetch="(data) => (workers = data)"
|
@on-fetch="(data) => (workers = data)"
|
||||||
auto-load
|
auto-load
|
||||||
/>
|
/>
|
||||||
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
|
<VnFilterPanel
|
||||||
|
:data-key="props.dataKey"
|
||||||
|
:params="defaultParams"
|
||||||
|
:search-button="true"
|
||||||
|
>
|
||||||
<template #tags="{ tag, formatFn }">
|
<template #tags="{ tag, formatFn }">
|
||||||
<div class="q-gutter-x-xs">
|
<div class="q-gutter-x-xs">
|
||||||
<strong>{{ t(`params.${tag.label}`) }}: </strong>
|
<strong>{{ t(`params.${tag.label}`) }}: </strong>
|
||||||
|
@ -57,7 +71,7 @@ const warehouses = ref();
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-input v-model="params.dateFrom" :label="t('From')" mask="date">
|
<q-input v-model="params.from" :label="t('From')" mask="date">
|
||||||
<template #append>
|
<template #append>
|
||||||
<q-icon name="event" class="cursor-pointer">
|
<q-icon name="event" class="cursor-pointer">
|
||||||
<q-popup-proxy
|
<q-popup-proxy
|
||||||
|
@ -65,7 +79,7 @@ const warehouses = ref();
|
||||||
transition-show="scale"
|
transition-show="scale"
|
||||||
transition-hide="scale"
|
transition-hide="scale"
|
||||||
>
|
>
|
||||||
<q-date v-model="params.dateFrom" landscape>
|
<q-date v-model="params.from" landscape>
|
||||||
<div
|
<div
|
||||||
class="row items-center justify-end q-gutter-sm"
|
class="row items-center justify-end q-gutter-sm"
|
||||||
>
|
>
|
||||||
|
@ -79,7 +93,6 @@ const warehouses = ref();
|
||||||
:label="t('globals.confirm')"
|
:label="t('globals.confirm')"
|
||||||
color="primary"
|
color="primary"
|
||||||
flat
|
flat
|
||||||
@click="save"
|
|
||||||
v-close-popup
|
v-close-popup
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -90,7 +103,7 @@ const warehouses = ref();
|
||||||
</q-input>
|
</q-input>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-input v-model="params.dateTo" :label="t('To')" mask="date">
|
<q-input v-model="params.to" :label="t('To')" mask="date">
|
||||||
<template #append>
|
<template #append>
|
||||||
<q-icon name="event" class="cursor-pointer">
|
<q-icon name="event" class="cursor-pointer">
|
||||||
<q-popup-proxy
|
<q-popup-proxy
|
||||||
|
@ -98,7 +111,7 @@ const warehouses = ref();
|
||||||
transition-show="scale"
|
transition-show="scale"
|
||||||
transition-hide="scale"
|
transition-hide="scale"
|
||||||
>
|
>
|
||||||
<q-date v-model="params.dateTo" landscape>
|
<q-date v-model="params.to" landscape>
|
||||||
<div
|
<div
|
||||||
class="row items-center justify-end q-gutter-sm"
|
class="row items-center justify-end q-gutter-sm"
|
||||||
>
|
>
|
||||||
|
@ -278,8 +291,8 @@ en:
|
||||||
search: Contains
|
search: Contains
|
||||||
clientFk: Customer
|
clientFk: Customer
|
||||||
orderFK: Order
|
orderFK: Order
|
||||||
dateFrom: From
|
from: From
|
||||||
dateTo: To
|
to: To
|
||||||
salesPersonFk: Salesperson
|
salesPersonFk: Salesperson
|
||||||
stateFk: State
|
stateFk: State
|
||||||
refFk: Invoice Ref.
|
refFk: Invoice Ref.
|
||||||
|
@ -295,8 +308,8 @@ es:
|
||||||
search: Contiene
|
search: Contiene
|
||||||
clientFk: Cliente
|
clientFk: Cliente
|
||||||
orderFK: Pedido
|
orderFK: Pedido
|
||||||
dateFrom: Desde
|
from: Desde
|
||||||
dateTo: Hasta
|
to: Hasta
|
||||||
salesPersonFk: Comercial
|
salesPersonFk: Comercial
|
||||||
stateFk: Estado
|
stateFk: Estado
|
||||||
refFk: Ref. Factura
|
refFk: Ref. Factura
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { useQuasar } from 'quasar';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
import Paginate from 'src/components/PaginateData.vue';
|
import Paginate from 'src/components/PaginateData.vue';
|
||||||
import { toDate, toCurrency } from 'src/filters/index';
|
import { toDate, toDateString, toCurrency } from 'src/filters/index';
|
||||||
import TicketSummaryDialog from './Card/TicketSummaryDialog.vue';
|
import TicketSummaryDialog from './Card/TicketSummaryDialog.vue';
|
||||||
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
|
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
|
||||||
import TicketFilter from './TicketFilter.vue';
|
import TicketFilter from './TicketFilter.vue';
|
||||||
|
@ -46,6 +46,15 @@ const filter = {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const from = Date.vnNew();
|
||||||
|
const to = Date.vnNew();
|
||||||
|
to.setDate(to.getDate() + 1);
|
||||||
|
|
||||||
|
const userParams = {
|
||||||
|
from: toDateString(from),
|
||||||
|
to: toDateString(to),
|
||||||
|
};
|
||||||
|
|
||||||
function stateColor(row) {
|
function stateColor(row) {
|
||||||
if (row.alertLevelCode === 'OK') return 'green';
|
if (row.alertLevelCode === 'OK') return 'green';
|
||||||
if (row.alertLevelCode === 'FREE') return 'blue-3';
|
if (row.alertLevelCode === 'FREE') return 'blue-3';
|
||||||
|
@ -104,6 +113,7 @@ function viewSummary(id) {
|
||||||
data-key="TicketList"
|
data-key="TicketList"
|
||||||
url="Tickets/filter"
|
url="Tickets/filter"
|
||||||
:filter="filter"
|
:filter="filter"
|
||||||
|
:user-params="userParams"
|
||||||
order="id DESC"
|
order="id DESC"
|
||||||
auto-load
|
auto-load
|
||||||
>
|
>
|
||||||
|
@ -123,9 +133,9 @@ function viewSummary(id) {
|
||||||
<q-item-label caption>
|
<q-item-label caption>
|
||||||
{{ t('ticket.list.nickname') }}
|
{{ t('ticket.list.nickname') }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
<q-item-label>{{
|
<q-item-label>
|
||||||
row.nickname
|
{{ row.nickname }}
|
||||||
}}</q-item-label>
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>
|
<q-item-label caption>
|
||||||
|
|
|
@ -12,6 +12,7 @@ const { t } = useI18n();
|
||||||
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">
|
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">
|
||||||
<VnSearchbar
|
<VnSearchbar
|
||||||
data-key="WorkerList"
|
data-key="WorkerList"
|
||||||
|
url="Workers/filter"
|
||||||
:label="t('Search worker')"
|
:label="t('Search worker')"
|
||||||
:info="t('You can search by worker id or name')"
|
:info="t('You can search by worker id or name')"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import { route } from 'quasar/wrappers';
|
import { route } from 'quasar/wrappers';
|
||||||
import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router';
|
import {
|
||||||
|
createRouter,
|
||||||
|
createMemoryHistory,
|
||||||
|
createWebHistory,
|
||||||
|
createWebHashHistory,
|
||||||
|
} from 'vue-router';
|
||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
import { i18n } from 'src/boot/i18n';
|
import { i18n } from 'src/boot/i18n';
|
||||||
import { useState } from 'src/composables/useState';
|
import { useState } from 'src/composables/useState';
|
||||||
|
@ -12,6 +17,22 @@ const session = useSession();
|
||||||
const role = useRole();
|
const role = useRole();
|
||||||
const { t } = i18n.global;
|
const { t } = i18n.global;
|
||||||
|
|
||||||
|
const createHistory = process.env.SERVER
|
||||||
|
? createMemoryHistory
|
||||||
|
: process.env.VUE_ROUTER_MODE === 'history'
|
||||||
|
? createWebHistory
|
||||||
|
: createWebHashHistory;
|
||||||
|
|
||||||
|
const Router = createRouter({
|
||||||
|
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||||
|
routes,
|
||||||
|
|
||||||
|
// Leave this as is and make changes in quasar.conf.js instead!
|
||||||
|
// quasar.conf.js -> build -> vueRouterMode
|
||||||
|
// quasar.conf.js -> build -> publicPath
|
||||||
|
history: createHistory(process.env.VUE_ROUTER_BASE),
|
||||||
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If not building with SSR mode, you can
|
* If not building with SSR mode, you can
|
||||||
* directly export the Router instantiation;
|
* directly export the Router instantiation;
|
||||||
|
@ -20,24 +41,8 @@ const { t } = i18n.global;
|
||||||
* async/await or return a Promise which resolves
|
* async/await or return a Promise which resolves
|
||||||
* with the Router instance.
|
* with the Router instance.
|
||||||
*/
|
*/
|
||||||
|
export { Router };
|
||||||
export default route(function (/* { store, ssrContext } */) {
|
export default route(function (/* { store, ssrContext } */) {
|
||||||
const createHistory = process.env.SERVER
|
|
||||||
? createMemoryHistory
|
|
||||||
: process.env.VUE_ROUTER_MODE === 'history'
|
|
||||||
? createWebHistory
|
|
||||||
: createWebHashHistory;
|
|
||||||
|
|
||||||
const Router = createRouter({
|
|
||||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
|
||||||
routes,
|
|
||||||
|
|
||||||
// Leave this as is and make changes in quasar.conf.js instead!
|
|
||||||
// quasar.conf.js -> build -> vueRouterMode
|
|
||||||
// quasar.conf.js -> build -> publicPath
|
|
||||||
history: createHistory(process.env.VUE_ROUTER_BASE),
|
|
||||||
});
|
|
||||||
|
|
||||||
Router.beforeEach(async (to, from, next) => {
|
Router.beforeEach(async (to, from, next) => {
|
||||||
const { isLoggedIn } = session;
|
const { isLoggedIn } = session;
|
||||||
|
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
import { vi, describe, expect, it, beforeAll } from 'vitest';
|
|
||||||
import { createWrapper } from 'app/test/vitest/helper';
|
|
||||||
import App from 'src/App.vue';
|
|
||||||
import { useSession } from 'src/composables/useSession';
|
|
||||||
|
|
||||||
const mockLoggedIn = vi.fn();
|
|
||||||
const mockDestroy = vi.fn();
|
|
||||||
const session = useSession();
|
|
||||||
|
|
||||||
vi.mock('src/composables/useSession', () => ({
|
|
||||||
useSession: () => ({
|
|
||||||
isLoggedIn: mockLoggedIn,
|
|
||||||
destroy: mockDestroy,
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('App', () => {
|
|
||||||
let vm;
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
const options = {
|
|
||||||
global: {
|
|
||||||
stubs: ['router-view'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
vm = createWrapper(App, options).vm;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a login error message', async () => {
|
|
||||||
vi.spyOn(vm.quasar, 'notify');
|
|
||||||
|
|
||||||
session.isLoggedIn.mockReturnValue(false);
|
|
||||||
|
|
||||||
const response = {
|
|
||||||
response: {
|
|
||||||
status: 401,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(vm.responseError(response)).rejects.toEqual(expect.objectContaining(response));
|
|
||||||
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
message: 'Invalid username or password',
|
|
||||||
type: 'negative',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return an unauthorized error message', async () => {
|
|
||||||
vi.spyOn(vm.quasar, 'notify');
|
|
||||||
|
|
||||||
session.isLoggedIn.mockReturnValue(true);
|
|
||||||
|
|
||||||
const response = {
|
|
||||||
response: {
|
|
||||||
status: 401,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(vm.responseError(response)).rejects.toEqual(expect.objectContaining(response));
|
|
||||||
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
message: 'Access denied',
|
|
||||||
type: 'negative',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(session.destroy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { vi, describe, expect, it } from 'vitest';
|
||||||
|
import { onRequest, onResponseError } from 'src/boot/axios';
|
||||||
|
import { Notify } from 'quasar'
|
||||||
|
|
||||||
|
vi.mock('src/composables/useSession', () => ({
|
||||||
|
useSession: () => ({
|
||||||
|
getToken: () => 'DEFAULT_TOKEN'
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('src/router', () => ({}));
|
||||||
|
|
||||||
|
describe('Axios boot', () => {
|
||||||
|
|
||||||
|
describe('onRequest()', async () => {
|
||||||
|
it('should set the "Authorization" property on the headers', async () => {
|
||||||
|
const config = { headers: {} };
|
||||||
|
|
||||||
|
const resultConfig = onRequest(config);
|
||||||
|
|
||||||
|
expect(resultConfig).toEqual(expect.objectContaining({
|
||||||
|
headers: {
|
||||||
|
Authorization: 'DEFAULT_TOKEN'
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('onResponseError()', async () => {
|
||||||
|
it('should call to the Notify plugin with a message error for an status code "500"', async () => {
|
||||||
|
Notify.create = vi.fn()
|
||||||
|
|
||||||
|
const error = {
|
||||||
|
response: {
|
||||||
|
status: 500
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = onResponseError(error);
|
||||||
|
|
||||||
|
|
||||||
|
expect(result).rejects.toEqual(
|
||||||
|
expect.objectContaining(error)
|
||||||
|
);
|
||||||
|
expect(Notify.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
message: 'An internal server error has ocurred',
|
||||||
|
type: 'negative',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call to the Notify plugin with a message from the response property', async () => {
|
||||||
|
Notify.create = vi.fn()
|
||||||
|
|
||||||
|
const error = {
|
||||||
|
response: {
|
||||||
|
status: 401,
|
||||||
|
data: {
|
||||||
|
error: {
|
||||||
|
message: 'Invalid user or password'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = onResponseError(error);
|
||||||
|
|
||||||
|
|
||||||
|
expect(result).rejects.toEqual(
|
||||||
|
expect.objectContaining(error)
|
||||||
|
);
|
||||||
|
expect(Notify.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
message: 'Invalid user or password',
|
||||||
|
type: 'negative',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
|
@ -18,12 +18,12 @@ describe('ClaimDescriptorMenu', () => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('deleteClaim()', () => {
|
describe('remove()', () => {
|
||||||
it('should delete the claim', async () => {
|
it('should delete the claim', async () => {
|
||||||
vi.spyOn(axios, 'delete').mockResolvedValue({ data: true });
|
vi.spyOn(axios, 'delete').mockResolvedValue({ data: true });
|
||||||
vi.spyOn(vm.quasar, 'notify');
|
vi.spyOn(vm.quasar, 'notify');
|
||||||
|
|
||||||
await vm.deleteClaim();
|
await vm.remove();
|
||||||
|
|
||||||
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({ type: 'positive' })
|
expect.objectContaining({ type: 'positive' })
|
||||||
|
|
|
@ -21,7 +21,7 @@ describe('ClaimPhoto', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
vm = createWrapper(ClaimPhoto, {
|
vm = createWrapper(ClaimPhoto, {
|
||||||
global: {
|
global: {
|
||||||
stubs: ['FetchData', 'TeleportSlot', 'vue-i18n'],
|
stubs: ['FetchData', 'vue-i18n'],
|
||||||
mocks: {
|
mocks: {
|
||||||
fetch: vi.fn(),
|
fetch: vi.fn(),
|
||||||
},
|
},
|
||||||
|
@ -38,7 +38,7 @@ describe('ClaimPhoto', () => {
|
||||||
vi.spyOn(axios, 'post').mockResolvedValue({ data: true });
|
vi.spyOn(axios, 'post').mockResolvedValue({ data: true });
|
||||||
vi.spyOn(vm.quasar, 'notify');
|
vi.spyOn(vm.quasar, 'notify');
|
||||||
|
|
||||||
await vm.deleteDms(0);
|
await vm.deleteDms({ index: 0 });
|
||||||
|
|
||||||
expect(axios.post).toHaveBeenCalledWith(
|
expect(axios.post).toHaveBeenCalledWith(
|
||||||
`ClaimDms/${claimMock.claimDms[0].dmsFk}/removeFile`
|
`ClaimDms/${claimMock.claimDms[0].dmsFk}/removeFile`
|
||||||
|
@ -46,7 +46,6 @@ describe('ClaimPhoto', () => {
|
||||||
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({ type: 'positive' })
|
expect.objectContaining({ type: 'positive' })
|
||||||
);
|
);
|
||||||
expect(vm.claimDms).toEqual([]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -59,8 +58,10 @@ describe('ClaimPhoto', () => {
|
||||||
expect(vm.quasar.dialog).toHaveBeenCalledWith(
|
expect(vm.quasar.dialog).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
componentProps: {
|
componentProps: {
|
||||||
message: 'This file will be deleted',
|
title: 'This file will be deleted',
|
||||||
icon: 'delete',
|
icon: 'delete',
|
||||||
|
data: { index: 1 },
|
||||||
|
promise: vm.deleteDms
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -23,7 +23,9 @@ describe('Login', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
vi.spyOn(axios, 'post').mockResolvedValue({ data: { token: 'token' } });
|
vi.spyOn(axios, 'post').mockResolvedValue({ data: { token: 'token' } });
|
||||||
vi.spyOn(axios, 'get').mockResolvedValue({ data: { roles: [], user: expectedUser } });
|
vi.spyOn(axios, 'get').mockResolvedValue({
|
||||||
|
data: { roles: [], user: expectedUser },
|
||||||
|
});
|
||||||
vi.spyOn(vm.quasar, 'notify');
|
vi.spyOn(vm.quasar, 'notify');
|
||||||
|
|
||||||
expect(vm.session.getToken()).toEqual('');
|
expect(vm.session.getToken()).toEqual('');
|
||||||
|
@ -31,7 +33,9 @@ describe('Login', () => {
|
||||||
await vm.onSubmit();
|
await vm.onSubmit();
|
||||||
|
|
||||||
expect(vm.session.getToken()).toEqual('token');
|
expect(vm.session.getToken()).toEqual('token');
|
||||||
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining({ type: 'positive' }));
|
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ type: 'positive' })
|
||||||
|
);
|
||||||
vm.session.destroy();
|
vm.session.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue