5387-descriptor_sms #42
|
@ -44,6 +44,11 @@ function responseError(error) {
|
|||
let message = error.message;
|
||||
let logOut = false;
|
||||
|
||||
const response = error.response;
|
||||
if (response && response.data.error) {
|
||||
message = response.data.error.message;
|
||||
}
|
||||
|
||||
switch (error.response?.status) {
|
||||
case 401:
|
||||
message = 'login.loginError';
|
||||
|
|
|
@ -3,12 +3,13 @@ import { ref } from 'vue';
|
|||
import { useDialogPluginComponent } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const $props = defineProps({
|
||||
address: {
|
||||
type: String,
|
||||
default: '',
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
requied: true,
|
||||
default: null,
|
||||
},
|
||||
send: {
|
||||
promise: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
|
@ -19,24 +20,30 @@ defineEmits(['confirm', ...useDialogPluginComponent.emits]);
|
|||
const { dialogRef, onDialogOK } = useDialogPluginComponent();
|
||||
const { t } = useI18n();
|
||||
|
||||
const address = ref($props.address);
|
||||
const address = ref(props.data.address);
|
||||
const isLoading = ref(false);
|
||||
|
||||
async function confirm() {
|
||||
isLoading.value = true;
|
||||
await $props.send(address.value);
|
||||
isLoading.value = false;
|
||||
const response = { address };
|
||||
|
||||
onDialogOK();
|
||||
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 email notification: Send email notification')
|
||||
}}</span>
|
||||
<span class="text-h6 text-grey">{{ t('Send email notification') }}</span>
|
||||
<q-space />
|
||||
<q-btn icon="close" flat round dense v-close-popup />
|
||||
</q-card-section>
|
||||
|
@ -53,6 +60,7 @@ async function confirm() {
|
|||
color="primary"
|
||||
:loading="isLoading"
|
||||
@click="confirm"
|
||||
unelevated
|
||||
/>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
|
@ -67,6 +75,6 @@ async function confirm() {
|
|||
|
||||
<i18n>
|
||||
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
|
||||
</i18n>
|
||||
|
|
|
@ -0,0 +1,243 @@
|
|||
<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>
|
||||
<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
|
||||
en: English
|
||||
es: Spanish
|
||||
fr: French
|
||||
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 }\r
|
||||
{ changes }'
|
||||
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
|
||||
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 }\r { changes }'
|
||||
en: Anglais
|
||||
es: Espagnol
|
||||
fr: Français
|
||||
</i18n>
|
|
@ -59,9 +59,9 @@ watch(props, async () => {
|
|||
:to="{ name: `${module}Summary`, params: { id: entity.id } }"
|
||||
>
|
||||
<q-btn round flat dense size="md" icon="launch" color="white">
|
||||
<q-tooltip>{{
|
||||
t('components.cardDescriptor.summary')
|
||||
}}</q-tooltip>
|
||||
<q-tooltip>
|
||||
{{ t('components.cardDescriptor.summary') }}
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</router-link>
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ const props = defineProps({
|
|||
type: String,
|
||||
default: null,
|
||||
},
|
||||
question: {
|
||||
title: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
|
@ -18,15 +18,37 @@ const props = defineProps({
|
|||
type: String,
|
||||
default: null,
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
promise: {
|
||||
type: Function,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
defineEmits(['confirm', ...useDialogPluginComponent.emits]);
|
||||
|
||||
const { dialogRef, onDialogOK } = useDialogPluginComponent();
|
||||
|
||||
const question = props.question || t('question');
|
||||
const message = props.message || t('message');
|
||||
const title = props.title || t('Confirm');
|
||||
const message = props.message || t('Are you sure you want to continue?');
|
||||
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>
|
||||
<template>
|
||||
<q-dialog ref="dialogRef" persistent>
|
||||
|
@ -39,20 +61,28 @@ const isLoading = ref(false);
|
|||
size="xl"
|
||||
v-if="icon"
|
||||
/>
|
||||
<span class="text-h6 text-grey">{{ message }}</span>
|
||||
<span class="text-h6 text-grey">{{ title }}</span>
|
||||
<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 class="row items-center">
|
||||
{{ question }}
|
||||
{{ message }}
|
||||
</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.cancel')"
|
||||
color="primary"
|
||||
:disable="isLoading"
|
||||
flat
|
||||
v-close-popup
|
||||
/>
|
||||
<q-btn
|
||||
:label="t('globals.confirm')"
|
||||
color="primary"
|
||||
:loading="isLoading"
|
||||
@click="onDialogOK()"
|
||||
@click="confirm()"
|
||||
unelevated
|
||||
autofocus
|
||||
/>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
|
@ -66,13 +96,7 @@ const isLoading = ref(false);
|
|||
</style>
|
||||
|
||||
<i18n>
|
||||
"en": {
|
||||
"question": "Are you sure you want to continue?",
|
||||
"message": "Confirm"
|
||||
}
|
||||
|
||||
"es": {
|
||||
"question": "¿Seguro que quieres continuar?",
|
||||
"message": "Confirmar"
|
||||
}
|
||||
es:
|
||||
Confirm: Confirmar
|
||||
Are you sure you want to continue?: ¿Seguro que quieres continuar?
|
||||
</i18n>
|
||||
|
|
|
@ -6,6 +6,7 @@ import { useI18n } from 'vue-i18n';
|
|||
import { useRouter } from 'vue-router';
|
||||
import { usePrintService } from 'composables/usePrintService';
|
||||
import SendEmailDialog from 'components/common/SendEmailDialog.vue';
|
||||
import VnConfirm from 'components/ui/VnConfirm.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
claim: {
|
||||
|
@ -33,13 +34,15 @@ function confirmPickupOrder() {
|
|||
quasar.dialog({
|
||||
component: SendEmailDialog,
|
||||
componentProps: {
|
||||
data: {
|
||||
address: customer.email,
|
||||
},
|
||||
send: sendPickupOrder,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function sendPickupOrder(address) {
|
||||
function sendPickupOrder({ address }) {
|
||||
const id = claim.value.id;
|
||||
const customer = claim.value.client;
|
||||
return sendEmail(`Claims/${id}/claim-pickup-email`, {
|
||||
|
@ -48,16 +51,26 @@ function sendPickupOrder(address) {
|
|||
});
|
||||
}
|
||||
|
||||
const showConfirmDialog = ref(false);
|
||||
async function deleteClaim() {
|
||||
function confirmRemove() {
|
||||
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;
|
||||
await axios.delete(`Claims/${id}`);
|
||||
quasar.notify({
|
||||
message: t('globals.dataDeleted'),
|
||||
type: 'positive',
|
||||
icon: 'check',
|
||||
type: 'positive'
|
||||
});
|
||||
await router.push({ name: 'ClaimList' });
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
|
@ -87,27 +100,12 @@ async function deleteClaim() {
|
|||
</q-menu>
|
||||
</q-item>
|
||||
<q-separator />
|
||||
<q-item @click="showConfirmDialog = true" v-ripple clickable>
|
||||
<q-item @click="confirmRemove()" v-ripple clickable>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="delete" />
|
||||
</q-item-section>
|
||||
<q-item-section>{{ t('deleteClaim') }}</q-item-section>
|
||||
</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>
|
||||
|
||||
<i18n>
|
||||
|
|
|
@ -64,23 +64,23 @@ function openDialog(dmsId) {
|
|||
multimediaDialog.value = true;
|
||||
}
|
||||
|
||||
function viewDeleteDms(dmsId) {
|
||||
function viewDeleteDms(index) {
|
||||
quasar
|
||||
.dialog({
|
||||
component: VnConfirm,
|
||||
componentProps: {
|
||||
message: t('This file will be deleted'),
|
||||
title: t('This file will be deleted'),
|
||||
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;
|
||||
await axios.post(`ClaimDms/${dmsId}/removeFile`);
|
||||
|
||||
claimDms.value.splice(index, 1);
|
||||
quasar.notify({
|
||||
message: t('globals.dataDeleted'),
|
||||
type: 'positive',
|
||||
|
|
|
@ -69,18 +69,19 @@ function confirmRemove(id) {
|
|||
quasar
|
||||
.dialog({
|
||||
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 arrayData.refresh();
|
||||
|
||||
quasar.notify({
|
||||
type: 'positive',
|
||||
message: t('globals.rowRemoved'),
|
||||
icon: 'check',
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -43,23 +43,25 @@ function confirm(id) {
|
|||
quasar
|
||||
.dialog({
|
||||
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 arrayData.refresh();
|
||||
quasar.notify({
|
||||
type: 'positive',
|
||||
message: t('globals.rowRemoved'),
|
||||
icon: 'check',
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<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-card class="card q-pa-md">
|
||||
<q-form @submit="submit">
|
||||
|
@ -79,6 +81,7 @@ async function remove(id) {
|
|||
</q-form>
|
||||
</q-card>
|
||||
</q-page-sticky>
|
||||
<div class="card-list">
|
||||
<paginate
|
||||
data-key="ClaimRmaList"
|
||||
url="ClaimRmas"
|
||||
|
@ -105,7 +108,11 @@ async function remove(id) {
|
|||
</q-list>
|
||||
</q-item-section>
|
||||
<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-item>
|
||||
<q-separator />
|
||||
|
@ -119,7 +126,9 @@ async function remove(id) {
|
|||
<q-item-label caption>{{
|
||||
t('claim.rmaList.code')
|
||||
}}</q-item-label>
|
||||
<q-item-label>{{ row.code }}</q-item-label>
|
||||
<q-item-label>{{
|
||||
row.code
|
||||
}}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
|
@ -141,6 +150,7 @@ async function remove(id) {
|
|||
</q-card>
|
||||
</template>
|
||||
</paginate>
|
||||
</div>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
|
@ -149,7 +159,7 @@ async function remove(id) {
|
|||
padding-top: 156px;
|
||||
}
|
||||
|
||||
.card {
|
||||
.card-list, .card {
|
||||
width: 100%;
|
||||
max-width: 60em;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n';
|
|||
import { toDate } from 'src/filters';
|
||||
import CustomerDescriptorPopover from 'src/pages/Customer/Card/CustomerDescriptorPopover.vue';
|
||||
import CardDescriptor from 'components/ui/CardDescriptor.vue';
|
||||
import TicketDescriptorMenu from './TicketDescriptorMenu.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
|
@ -23,11 +24,25 @@ const entityId = computed(() => {
|
|||
|
||||
const filter = {
|
||||
include: [
|
||||
{
|
||||
relation: 'address',
|
||||
scope: {
|
||||
fields: ['id', 'name', 'mobile', 'phone'],
|
||||
},
|
||||
},
|
||||
{
|
||||
relation: 'client',
|
||||
scope: {
|
||||
fields: ['id', 'name', 'salesPersonFk'],
|
||||
include: { relation: 'salesPersonUser' },
|
||||
fields: ['id', 'name', 'salesPersonFk', 'phone', 'mobile', 'email'],
|
||||
include: [
|
||||
{
|
||||
relation: 'user',
|
||||
scope: {
|
||||
fields: ['id', 'lang'],
|
||||
},
|
||||
},
|
||||
{ relation: 'salesPersonUser' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -61,6 +76,9 @@ function stateColor(state) {
|
|||
|
||||
<template>
|
||||
<card-descriptor module="Ticket" :url="`Tickets/${entityId}`" :filter="filter">
|
||||
<template #menu="{ entity }">
|
||||
<TicketDescriptorMenu :ticket="entity" />
|
||||
</template>
|
||||
<template #description="{ entity }">
|
||||
<span>
|
||||
{{ entity.client.name }}
|
||||
|
@ -121,6 +139,16 @@ function stateColor(state) {
|
|||
</q-item-section>
|
||||
</q-item>
|
||||
</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-btn
|
||||
|
@ -135,3 +163,8 @@ function stateColor(state) {
|
|||
</template>
|
||||
</card-descriptor>
|
||||
</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>
|
|
@ -34,10 +34,17 @@ describe('App', () => {
|
|||
const response = {
|
||||
response: {
|
||||
status: 401,
|
||||
data: {
|
||||
error: {
|
||||
message: 'Invalid username or password',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(vm.responseError(response)).rejects.toEqual(expect.objectContaining(response));
|
||||
expect(vm.responseError(response)).rejects.toEqual(
|
||||
expect.objectContaining(response)
|
||||
);
|
||||
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: 'Invalid username or password',
|
||||
|
@ -54,10 +61,17 @@ describe('App', () => {
|
|||
const response = {
|
||||
response: {
|
||||
status: 401,
|
||||
data: {
|
||||
error: {
|
||||
message: 'Access denied',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(vm.responseError(response)).rejects.toEqual(expect.objectContaining(response));
|
||||
expect(vm.responseError(response)).rejects.toEqual(
|
||||
expect.objectContaining(response)
|
||||
);
|
||||
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: 'Access denied',
|
||||
|
|
|
@ -18,12 +18,12 @@ describe('ClaimDescriptorMenu', () => {
|
|||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('deleteClaim()', () => {
|
||||
describe('remove()', () => {
|
||||
it('should delete the claim', async () => {
|
||||
vi.spyOn(axios, 'delete').mockResolvedValue({ data: true });
|
||||
vi.spyOn(vm.quasar, 'notify');
|
||||
|
||||
await vm.deleteClaim();
|
||||
await vm.remove();
|
||||
|
||||
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ type: 'positive' })
|
||||
|
|
|
@ -21,7 +21,7 @@ describe('ClaimPhoto', () => {
|
|||
beforeAll(() => {
|
||||
vm = createWrapper(ClaimPhoto, {
|
||||
global: {
|
||||
stubs: ['FetchData', 'TeleportSlot', 'vue-i18n'],
|
||||
stubs: ['FetchData', 'vue-i18n'],
|
||||
mocks: {
|
||||
fetch: vi.fn(),
|
||||
},
|
||||
|
@ -38,7 +38,7 @@ describe('ClaimPhoto', () => {
|
|||
vi.spyOn(axios, 'post').mockResolvedValue({ data: true });
|
||||
vi.spyOn(vm.quasar, 'notify');
|
||||
|
||||
await vm.deleteDms(0);
|
||||
await vm.deleteDms({ index: 0 });
|
||||
|
||||
expect(axios.post).toHaveBeenCalledWith(
|
||||
`ClaimDms/${claimMock.claimDms[0].dmsFk}/removeFile`
|
||||
|
@ -46,7 +46,6 @@ describe('ClaimPhoto', () => {
|
|||
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ type: 'positive' })
|
||||
);
|
||||
expect(vm.claimDms).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -59,8 +58,10 @@ describe('ClaimPhoto', () => {
|
|||
expect(vm.quasar.dialog).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
componentProps: {
|
||||
message: 'This file will be deleted',
|
||||
title: 'This file will be deleted',
|
||||
icon: 'delete',
|
||||
data: { index: 1 },
|
||||
promise: vm.deleteDms
|
||||
},
|
||||
})
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue