0
0
Fork 0

Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 6911-saveOnEnter

This commit is contained in:
Jorge Penadés 2024-02-28 08:36:59 +01:00
commit f8d1b3d935
26 changed files with 697 additions and 96 deletions

View File

@ -176,8 +176,8 @@ async function remove(data) {
.dialog({ .dialog({
component: VnConfirm, component: VnConfirm,
componentProps: { componentProps: {
title: t('confirmDeletion'), title: t('globals.confirmDeletion'),
message: t('confirmDeletionMessage'), message: t('globals.confirmDeletionMessage'),
newData, newData,
ids, ids,
}, },
@ -317,16 +317,3 @@ watch(formUrl, async () => {
color="primary" color="primary"
/> />
</template> </template>
<i18n>
{
"en": {
"confirmDeletion": "Confirm deletion",
"confirmDeletionMessage": "Are you sure you want to delete this?"
},
"es": {
"confirmDeletion": "Confirmar eliminación",
"confirmDeletionMessage": "Seguro que quieres eliminar?"
}
}
</i18n>

View File

@ -276,7 +276,7 @@ const makeRequest = async () => {
class="cursor-pointer q-mr-sm" class="cursor-pointer q-mr-sm"
@click="openInputFile()" @click="openInputFile()"
> >
<!-- <QTooltip>{{ t('Select a file') }}</QTooltip> --> <!-- <QTooltip>{{ t('globals.selectFile') }}</QTooltip> -->
</QIcon> </QIcon>
<QIcon name="info" class="cursor-pointer"> <QIcon name="info" class="cursor-pointer">
<QTooltip>{{ <QTooltip>{{

View File

@ -59,11 +59,4 @@ async function fetch(fetchFilter = {}) {
// //
} }
} }
const render = () => {
return h('div', []);
};
</script> </script>
<template>
<render />
</template>

View File

@ -1,6 +1,7 @@
<script setup> <script setup>
import axios from 'axios'; import axios from 'axios';
import { onMounted, onUnmounted, computed, ref, watch, nextTick } from 'vue'; import { onMounted, onUnmounted, computed, ref, watch, nextTick } from 'vue';
import { onBeforeRouteLeave } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
@ -8,6 +9,7 @@ import { useStateStore } from 'stores/useStateStore';
import { useValidator } from 'src/composables/useValidator'; import { useValidator } from 'src/composables/useValidator';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import SkeletonForm from 'components/ui/SkeletonForm.vue'; import SkeletonForm from 'components/ui/SkeletonForm.vue';
import VnConfirm from './ui/VnConfirm.vue';
const quasar = useQuasar(); const quasar = useQuasar();
const state = useState(); const state = useState();
@ -59,6 +61,10 @@ const $props = defineProps({
type: Function, type: Function,
default: null, default: null,
}, },
saveFn: {
type: Function,
default: null,
},
}); });
const emit = defineEmits(['onFetch', 'onDataSaved']); const emit = defineEmits(['onFetch', 'onDataSaved']);
@ -75,9 +81,8 @@ onMounted(async () => {
}); });
// Podemos enviarle al form la estructura de data inicial sin necesidad de fetchearla // Podemos enviarle al form la estructura de data inicial sin necesidad de fetchearla
if ($props.formInitialData && !$props.autoLoad) { state.set($props.model, $props.formInitialData ?? {});
state.set($props.model, $props.formInitialData); if ($props.autoLoad && !$props.formInitialData) {
} else {
await fetch(); await fetch();
} }
@ -90,6 +95,19 @@ onMounted(async () => {
} }
}); });
onBeforeRouteLeave((to, from, next) => {
if (!hasChanges.value) next();
quasar.dialog({
component: VnConfirm,
componentProps: {
title: t('Unsaved changes will be lost'),
message: t('Are you sure exit without saving?'),
promise: () => next(),
},
});
});
onUnmounted(() => { onUnmounted(() => {
state.unset($props.model); state.unset($props.model);
}); });
@ -138,17 +156,20 @@ async function save() {
try { try {
const body = $props.mapper ? $props.mapper(formData.value) : formData.value; const body = $props.mapper ? $props.mapper(formData.value) : formData.value;
let response; let response;
if ($props.urlCreate) { if ($props.saveFn) response = await $props.saveFn(body);
response = await axios.post($props.urlCreate, body); else
notify('globals.dataCreated', 'positive'); response = await axios[$props.urlCreate ? 'post' : 'patch'](
} else { $props.urlCreate || $props.urlUpdate || $props.url,
response = await axios.patch($props.urlUpdate || $props.url, body); body
} );
if ($props.urlCreate) notify('globals.dataCreated', 'positive');
emit('onDataSaved', formData.value, response?.data); emit('onDataSaved', formData.value, response?.data);
originalData.value = JSON.parse(JSON.stringify(formData.value)); originalData.value = JSON.parse(JSON.stringify(formData.value));
hasChanges.value = false; hasChanges.value = false;
} catch (err) { } catch (err) {
notify('errors.create', 'negative'); console.error(err);
notify('errors.writeRequest', 'negative');
} }
isLoading.value = false; isLoading.value = false;
} }
@ -250,3 +271,8 @@ watch(formUrl, async () => {
padding: 32px; padding: 32px;
} }
</style> </style>
<i18n>
es:
Unsaved changes will be lost: Los cambios que no haya guardado se perderán
Are you sure exit without saving?: ¿Seguro que quiere salir sin guardar?
</i18n>

View File

@ -0,0 +1,201 @@
<script setup>
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FormModelPopup from 'components/FormModelPopup.vue';
const route = useRoute();
const { t } = useI18n();
const emit = defineEmits(['onDataSaved']);
const $props = defineProps({
model: {
type: String,
required: true,
},
defaultDmsCode: {
type: String,
default: null,
},
formInitialData: {
type: Object,
default: null,
},
});
const warehouses = ref();
const companies = ref();
const dmsTypes = ref();
const allowedContentTypes = ref();
const inputFileRef = ref();
const dms = ref({});
onMounted(() => {
defaultData();
if (!$props.formInitialData)
dms.value.description = t($props.model + 'Description', dms.value);
});
function onFileChange(files) {
dms.value.hasFileAttached = !!files;
dms.value.file = files?.name;
}
function mapperDms(data) {
const formData = new FormData();
const { files } = data;
if (files) formData.append(files?.name, files);
delete data.files;
const dms = {
hasFile: !!data.hasFile,
hasFileAttached: data.hasFileAttached,
reference: data.reference,
warehouseId: data.warehouseFk,
companyId: data.companyFk,
dmsTypeId: data.dmsTypeFk,
description: data.description,
};
return [formData, { params: dms }];
}
function getUrl() {
if ($props.formInitialData) return 'dms/' + $props.formInitialData.id + '/updateFile';
return `${$props.model}/${route.params.id}/uploadFile`;
}
async function save() {
const body = mapperDms(dms.value);
await axios.post(getUrl(), body[0], body[1]);
emit('onDataSaved', body[1].params);
}
function defaultData() {
if ($props.formInitialData) return (dms.value = $props.formInitialData);
return addDefaultData({
reference: route.params.id,
});
}
function setDmsTypes(data) {
dmsTypes.value = data;
if (!$props.formInitialData && $props.defaultDmsCode) {
const { id } = data.find((dmsType) => dmsType.code == $props.defaultDmsCode);
addDefaultData({ dmsTypeFk: id });
}
}
function addDefaultData(data) {
Object.assign(dms.value, data);
}
</script>
<template>
<FetchData url="Warehouses" @on-fetch="(data) => (warehouses = data)" auto-load />
<FetchData url="Companies" @on-fetch="(data) => (companies = data)" auto-load />
<FetchData url="DmsTypes" @on-fetch="setDmsTypes" auto-load />
<FetchData
url="DmsContainers/allowedContentTypes"
@on-fetch="(data) => (allowedContentTypes = data.join(','))"
auto-load
/>
<FetchData
url="UserConfigs/getUserConfig"
@on-fetch="addDefaultData"
:auto-load="!$props.formInitialData"
/>
<FormModelPopup
:title="formInitialData ? t('globals.edit') : t('globals.create')"
model="dms"
:form-initial-data="formInitialData"
:save-fn="save"
>
<template #form-inputs>
<div class="q-gutter-y-ms">
<VnRow>
<VnInput :label="t('globals.reference')" v-model="dms.reference" />
<VnSelectFilter
:label="t('globals.company')"
v-model="dms.companyFk"
:options="companies"
option-value="id"
option-label="code"
input-debounce="0"
/>
</VnRow>
<VnRow>
<VnSelectFilter
:label="t('globals.warehouse')"
v-model="dms.warehouseFk"
:options="warehouses"
option-value="id"
option-label="name"
input-debounce="0"
/>
<VnSelectFilter
:label="t('globals.type')"
v-model="dms.dmsTypeFk"
:options="dmsTypes"
option-value="id"
option-label="name"
input-debounce="0"
/>
</VnRow>
<QInput
:label="t('globals.description')"
v-model="dms.description"
type="textarea"
/>
<QFile
ref="inputFileRef"
:label="t('entry.buys.file')"
v-model="dms.files"
:multiple="false"
:accept="allowedContentTypes"
@update:model-value="onFileChange(dms.files)"
class="required"
:display-value="dms.file"
>
<template #append>
<QIcon
name="vn:attach"
class="cursor-pointer"
@click="inputFileRef.pickFiles()"
>
<QTooltip>{{ t('globals.selectFile') }}</QTooltip>
</QIcon>
<QIcon name="info" class="cursor-pointer">
<QTooltip>{{
t('contentTypesInfo', { allowedContentTypes })
}}</QTooltip>
</QIcon>
</template>
</QFile>
<QCheckbox
v-model="dms.hasFile"
:label="t('Generate identifier for original file')"
/>
</div>
</template>
</FormModelPopup>
</template>
<style scoped>
.q-gutter-y-ms {
display: grid;
row-gap: 20px;
}
</style>
<i18n>
en:
contentTypesInfo: Allowed file types {allowedContentTypes}
EntryDmsDescription: Reference {reference}
es:
Generate identifier for original file: Generar identificador para archivo original
contentTypesInfo: Tipos de archivo permitidos {allowedContentTypes}
EntryDmsDescription: Referencia {reference}
</i18n>

View File

@ -0,0 +1,316 @@
<script setup>
import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { useQuasar, QCheckbox, QBtn, QInput } from 'quasar';
import axios from 'axios';
import FetchData from 'components/FetchData.vue';
import VnDms from 'src/components/common/VnDms.vue';
import VnConfirm from 'components/ui/VnConfirm.vue';
import { downloadFile } from 'src/composables/downloadFile';
const route = useRoute();
const quasar = useQuasar();
const { t } = useI18n();
const rows = ref();
const dmsRef = ref();
const formDialog = ref({});
const $props = defineProps({
model: {
type: String,
required: true,
},
updateModel: {
type: String,
default: null,
},
defaultDmsCode: {
type: String,
required: true,
},
filter: {
type: String,
required: true,
},
});
const dmsFilter = {
include: {
relation: 'dms',
scope: {
fields: [
'dmsTypeFk',
'reference',
'hardCopyNumber',
'workerFk',
'description',
'hasFile',
'file',
'created',
'companyFk',
'warehouseFk',
],
include: [
{
relation: 'dmsType',
scope: {
fields: ['name'],
},
},
{
relation: 'worker',
scope: {
fields: ['id'],
include: {
relation: 'user',
scope: {
fields: ['name'],
},
},
},
},
],
},
},
order: ['dmsFk DESC'],
};
const columns = computed(() => [
{
align: 'left',
field: 'id',
label: t('globals.id'),
name: 'id',
component: 'span',
},
{
align: 'left',
field: 'type',
label: t('globals.type'),
name: 'type',
component: QInput,
props: (prop) => ({
readonly: true,
borderless: true,
'model-value': prop.row.dmsType.name,
}),
},
{
align: 'left',
field: 'order',
label: t('globals.order'),
name: 'order',
component: 'span',
},
{
align: 'left',
field: 'reference',
label: t('globals.reference'),
name: 'reference',
component: 'span',
},
{
align: 'left',
field: 'description',
label: t('globals.description'),
name: 'description',
component: 'span',
},
{
align: 'left',
field: 'hasFile',
label: t('globals.original'),
name: 'hasFile',
component: QCheckbox,
props: (prop) => ({
disable: true,
'model-value': Boolean(prop.value),
}),
},
{
align: 'left',
field: 'file',
label: t('globals.file'),
name: 'file',
component: 'span',
},
{
field: 'options',
name: 'options',
components: [
{
component: QBtn,
props: () => ({
icon: 'cloud_download',
flat: true,
color: 'primary',
}),
click: (prop) => downloadFile(prop.row.id),
},
{
component: QBtn,
props: () => ({
icon: 'edit',
flat: true,
color: 'primary',
}),
click: (prop) => showFormDialog(prop.row),
},
{
component: QBtn,
props: () => ({
icon: 'delete',
flat: true,
color: 'primary',
}),
click: (prop) => deleteDms(prop.row.id),
},
],
},
]);
function setData(data) {
const newData = data.map((value) => value.dms);
rows.value = newData;
}
function deleteDms(dmsFk) {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('globals.confirmDeletion'),
message: t('globals.confirmDeletionMessage'),
},
})
.onOk(async () => {
await axios.post(`${$props.model}/${dmsFk}/removeFile`);
const index = rows.value.findIndex((row) => row.id == dmsFk);
rows.value.splice(index, 1);
});
}
function showFormDialog(dms) {
if (dms) dms = parseDms(dms);
formDialog.value = {
show: true,
dms,
};
}
function parseDms(data) {
for (let prop in data) {
if (prop.endsWith('Fk')) data[prop.replace('Fk', 'Id')] = data[prop];
}
return data;
}
</script>
<template>
<FetchData
ref="dmsRef"
:url="$props.model"
:filter="dmsFilter"
:where="{ [$props.filter]: route.params.id }"
@on-fetch="setData"
auto-load
/>
<QTable
:columns="columns"
:pagination="{ rowsPerPage: 0 }"
:rows="rows"
class="full-width q-mt-md"
hide-bottom
row-key="clientFk"
:grid="$q.screen.lt.sm"
>
<template #body-cell="props">
<QTd :props="props">
<QTr :props="props">
<component
v-if="props.col.component"
:is="props.col.component"
v-bind="props.col.props && props.col.props(props)"
>
<span
v-if="props.col.component == 'span'"
style="white-space: wrap"
>{{ props.value }}</span
>
</component>
</QTr>
<div class="flex justify-center" v-if="props.col.name == 'options'">
<div v-for="button of props.col.components" :key="button.id">
<component
:is="button.component"
v-bind="button.props(props)"
@click="button.click(props)"
/>
</div>
</div>
</QTd>
</template>
<template #item="props">
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
<QCard
bordered
flat
@keyup.ctrl.enter.stop="claimDevelopmentForm?.saveChanges()"
>
<QSeparator />
<QList dense>
<QItem v-for="col in props.cols" :key="col.name">
<div v-if="col.name != 'options'" class="row">
<span class="labelColor">{{ col.label }}:</span>
<span>{{ col.value }}</span>
</div>
<div v-if="col.name == 'options'" class="row">
<div
v-for="button of col.components"
:key="button.id"
class="row"
>
<component
:is="button.component"
v-bind="button.props(col)"
@click="button.click(col)"
/>
</div>
</div>
</QItem>
</QList>
</QCard>
</div>
</template>
</QTable>
<QDialog v-model="formDialog.show">
<VnDms
:model="updateModel ?? model"
:default-dms-code="defaultDmsCode"
:form-initial-data="formDialog.dms"
@on-data-saved="dmsRef.fetch()"
:description="$props.description"
/>
</QDialog>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn fab color="primary" icon="add" @click="showFormDialog()" />
</QPageSticky>
</template>
<style scoped>
.q-gutter-y-ms {
display: grid;
row-gap: 20px;
}
.labelColor {
color: var(--vn-label);
}
</style>
<i18n>
en:
contentTypesInfo: Allowed file types {allowedContentTypes}
es:
contentTypesInfo: Tipos de archivo permitidos {allowedContentTypes}
Generate identifier for original file: Generar identificador para archivo original
</i18n>

View File

@ -94,16 +94,6 @@ async function send() {
<QSpace /> <QSpace />
<QBtn icon="close" :disable="isLoading" flat round dense v-close-popup /> <QBtn icon="close" :disable="isLoading" flat round dense v-close-popup />
</QCardSection> </QCardSection>
<QCardSection v-if="props.locale">
<QBanner class="bg-amber text-white" rounded dense>
<template #avatar>
<QIcon name="warning" />
</template>
<span
v-html="t('CustomerDefaultLanguage', { locale: t(props.locale) })"
></span>
</QBanner>
</QCardSection>
<QCardSection class="q-pb-xs"> <QCardSection class="q-pb-xs">
<QSelect <QSelect
:label="t('Language')" :label="t('Language')"
@ -184,11 +174,10 @@ async function send() {
<i18n> <i18n>
en: en:
CustomerDefaultLanguage: This customer uses <strong>{locale}</strong> as their default language
templates: templates:
pendingPayment: 'Your order is pending of payment. pendingPayment: 'Your order is pending of payment.
Please, enter the website and make the payment with a credit card. Thank you.' 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 minAmount: 'A minimum amount of 50 (VAT excluded) is required for your order
{ orderId } of { shipped } to receive it without additional shipping costs.' { orderId } of { shipped } to receive it without additional shipping costs.'
orderChanges: 'Order {orderId} of { shipped }: { changes }' orderChanges: 'Order {orderId} of { shipped }: { changes }'
en: English en: English
@ -197,7 +186,6 @@ en:
pt: Portuguese pt: Portuguese
es: es:
Send SMS: Enviar SMS Send SMS: Enviar SMS
CustomerDefaultLanguage: Este cliente utiliza <strong>{locale}</strong> como idioma por defecto
Language: Idioma Language: Idioma
Phone: Móvil Phone: Móvil
Subject: Asunto Subject: Asunto
@ -205,7 +193,7 @@ es:
templates: templates:
pendingPayment: 'Su pedido está pendiente de pago. pendingPayment: 'Su pedido está pendiente de pago.
Por favor, entre en la página web y efectue el pago con tarjeta. Muchas gracias.' 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 minAmount: 'Es necesario un importe mínimo de 50 (Sin IVA) en su pedido
{ orderId } del día { shipped } para recibirlo sin portes adicionales.' { orderId } del día { shipped } para recibirlo sin portes adicionales.'
orderChanges: 'Pedido {orderId} día { shipped }: { changes }' orderChanges: 'Pedido {orderId} día { shipped }: { changes }'
en: Inglés en: Inglés
@ -222,7 +210,7 @@ fr:
templates: templates:
pendingPayment: 'Votre commande est en attente de paiement. pendingPayment: 'Votre commande est en attente de paiement.
Veuillez vous connecter sur le site web et effectuer le paiement par carte. Merci beaucoup.' 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 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.' { orderId } du { shipped } afin de la recevoir sans frais de port supplémentaires.'
orderChanges: 'Commande { orderId } du { shipped }: { changes }' orderChanges: 'Commande { orderId } du { shipped }: { changes }'
en: Anglais en: Anglais
@ -239,7 +227,7 @@ pt:
templates: templates:
pendingPayment: 'Seu pedido está pendente de pagamento. pendingPayment: 'Seu pedido está pendente de pagamento.
Por favor, acesse o site e faça o pagamento com cartão. Muito obrigado.' 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 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.' { orderId } do dia { shipped } para recebê-lo sem custos de envio adicionais.'
orderChanges: 'Pedido { orderId } dia { shipped }: { changes }' orderChanges: 'Pedido { orderId } dia { shipped }: { changes }'
en: Inglês en: Inglês

View File

@ -1,12 +1,16 @@
<template> <template>
<div id="row"> <div id="row" class="q-gutter-md">
<slot></slot> <slot></slot>
</div> </div>
</template> </template>
<style lang="scss" scopped> <style lang="scss" scopped>
#row {
display: grid;
grid-template-columns: 1fr 1fr;
}
@media screen and (max-width: 800px) { @media screen and (max-width: 800px) {
#row { #row {
flex-direction: column; grid-template-columns: 1fr;
} }
} }
</style> </style>

View File

@ -24,6 +24,7 @@ export default {
dataCreated: 'Data created', dataCreated: 'Data created',
add: 'Add', add: 'Add',
create: 'Create', create: 'Create',
edit: 'Edit',
save: 'Save', save: 'Save',
remove: 'Remove', remove: 'Remove',
reset: 'Reset', reset: 'Reset',
@ -64,12 +65,22 @@ export default {
markAll: 'Mark all', markAll: 'Mark all',
requiredField: 'Required field', requiredField: 'Required field',
class: 'clase', class: 'clase',
type: 'type', type: 'Type',
reason: 'reason', reason: 'reason',
noResults: 'No results', noResults: 'No results',
system: 'System', system: 'System',
warehouse: 'Warehouse',
company: 'Company',
fieldRequired: 'Field required', fieldRequired: 'Field required',
allowedFilesText: 'Allowed file types: { allowedContentTypes }', allowedFilesText: 'Allowed file types: { allowedContentTypes }',
confirmDeletion: 'Confirm deletion',
confirmDeletionMessage: 'Are you sure you want to delete this?',
description: 'Description',
id: 'Id',
order: 'Order',
original: 'Original',
file: 'File',
selectFile: 'Select a file',
copyClipboard: 'Copy on clipboard', copyClipboard: 'Copy on clipboard',
}, },
errors: { errors: {
@ -78,7 +89,7 @@ export default {
statusBadGateway: 'It seems that the server has fall down', statusBadGateway: 'It seems that the server has fall down',
statusGatewayTimeout: 'Could not contact the server', statusGatewayTimeout: 'Could not contact the server',
userConfig: 'Error fetching user config', userConfig: 'Error fetching user config',
create: 'Error during creation', writeRequest: 'The requested operation could not be completed',
}, },
login: { login: {
title: 'Login', title: 'Login',
@ -276,6 +287,7 @@ export default {
basicData: 'Basic data', basicData: 'Basic data',
buys: 'Buys', buys: 'Buys',
notes: 'Notes', notes: 'Notes',
dms: 'File management',
log: 'Log', log: 'Log',
create: 'Create', create: 'Create',
latestBuys: 'Latest buys', latestBuys: 'Latest buys',
@ -347,7 +359,6 @@ export default {
reference: 'Reference', reference: 'Reference',
observations: 'Observations', observations: 'Observations',
item: 'Item', item: 'Item',
description: 'Description',
size: 'Size', size: 'Size',
packing: 'Packing', packing: 'Packing',
grouping: 'Grouping', grouping: 'Grouping',
@ -362,7 +373,6 @@ export default {
}, },
notes: { notes: {
observationType: 'Observation type', observationType: 'Observation type',
description: 'Description',
}, },
descriptor: { descriptor: {
agency: 'Agency', agency: 'Agency',
@ -375,7 +385,6 @@ export default {
packing: 'Packing', packing: 'Packing',
grouping: 'Grouping', grouping: 'Grouping',
quantity: 'Quantity', quantity: 'Quantity',
description: 'Description',
size: 'Size', size: 'Size',
tags: 'Tags', tags: 'Tags',
type: 'Type', type: 'Type',
@ -461,7 +470,6 @@ export default {
visible: 'Visible', visible: 'Visible',
available: 'Available', available: 'Available',
quantity: 'Quantity', quantity: 'Quantity',
description: 'Description',
price: 'Price', price: 'Price',
discount: 'Discount', discount: 'Discount',
packing: 'Packing', packing: 'Packing',
@ -534,7 +542,6 @@ export default {
landed: 'Landed', landed: 'Landed',
quantity: 'Quantity', quantity: 'Quantity',
claimed: 'Claimed', claimed: 'Claimed',
description: 'Description',
price: 'Price', price: 'Price',
discount: 'Discount', discount: 'Discount',
total: 'Total', total: 'Total',
@ -796,7 +803,6 @@ export default {
orderTicketList: 'Order Ticket List', orderTicketList: 'Order Ticket List',
details: 'Details', details: 'Details',
item: 'Item', item: 'Item',
description: 'Description',
quantity: 'Quantity', quantity: 'Quantity',
price: 'Price', price: 'Price',
amount: 'Amount', amount: 'Amount',
@ -1141,7 +1147,6 @@ export default {
warehouse: 'Warehouse', warehouse: 'Warehouse',
travelFileDescription: 'Travel id { travelId }', travelFileDescription: 'Travel id { travelId }',
file: 'File', file: 'File',
description: 'Description',
}, },
}, },
item: { item: {
@ -1175,7 +1180,6 @@ export default {
clone: 'Clone', clone: 'Clone',
openCard: 'View', openCard: 'View',
openSummary: 'Summary', openSummary: 'Summary',
viewDescription: 'Description',
}, },
cardDescriptor: { cardDescriptor: {
mainList: 'Main list', mainList: 'Main list',

View File

@ -24,6 +24,7 @@ export default {
dataCreated: 'Datos creados', dataCreated: 'Datos creados',
add: 'Añadir', add: 'Añadir',
create: 'Crear', create: 'Crear',
edit: 'Modificar',
save: 'Guardar', save: 'Guardar',
remove: 'Eliminar', remove: 'Eliminar',
reset: 'Restaurar', reset: 'Restaurar',
@ -64,12 +65,22 @@ export default {
markAll: 'Marcar todo', markAll: 'Marcar todo',
requiredField: 'Campo obligatorio', requiredField: 'Campo obligatorio',
class: 'clase', class: 'clase',
type: 'tipo', type: 'Tipo',
reason: 'motivo', reason: 'motivo',
noResults: 'Sin resultados', noResults: 'Sin resultados',
system: 'Sistema', system: 'Sistema',
warehouse: 'Almacén',
company: 'Empresa',
fieldRequired: 'Campo requerido', fieldRequired: 'Campo requerido',
allowedFilesText: 'Tipos de archivo permitidos: { allowedContentTypes }', allowedFilesText: 'Tipos de archivo permitidos: { allowedContentTypes }',
confirmDeletion: 'Confirmar eliminación',
confirmDeletionMessage: '¿Seguro que quieres eliminar?',
description: 'Descripción',
id: 'Id',
order: 'Orden',
original: 'Original',
file: 'Fichero',
selectFile: 'Seleccione un fichero',
copyClipboard: 'Copiar en portapapeles', copyClipboard: 'Copiar en portapapeles',
}, },
errors: { errors: {
@ -78,7 +89,7 @@ export default {
statusBadGateway: 'Parece ser que el servidor ha caído', statusBadGateway: 'Parece ser que el servidor ha caído',
statusGatewayTimeout: 'No se ha podido contactar con el servidor', statusGatewayTimeout: 'No se ha podido contactar con el servidor',
userConfig: 'Error al obtener configuración de usuario', userConfig: 'Error al obtener configuración de usuario',
create: 'Error al crear', writeRequest: 'No se pudo completar la operación solicitada',
}, },
login: { login: {
title: 'Inicio de sesión', title: 'Inicio de sesión',
@ -275,6 +286,7 @@ export default {
basicData: 'Datos básicos', basicData: 'Datos básicos',
buys: 'Compras', buys: 'Compras',
notes: 'Notas', notes: 'Notas',
dms: 'Gestión documental',
log: 'Historial', log: 'Historial',
create: 'Crear', create: 'Crear',
latestBuys: 'Últimas compras', latestBuys: 'Últimas compras',
@ -346,7 +358,6 @@ export default {
reference: 'Referencia', reference: 'Referencia',
observations: 'Observaciónes', observations: 'Observaciónes',
item: 'Artículo', item: 'Artículo',
description: 'Descripción',
size: 'Medida', size: 'Medida',
packing: 'Packing', packing: 'Packing',
grouping: 'Grouping', grouping: 'Grouping',
@ -361,7 +372,6 @@ export default {
}, },
notes: { notes: {
observationType: 'Tipo de observación', observationType: 'Tipo de observación',
description: 'Descripción',
}, },
descriptor: { descriptor: {
agency: 'Agencia', agency: 'Agencia',
@ -374,7 +384,6 @@ export default {
packing: 'Packing', packing: 'Packing',
grouping: 'Grouping', grouping: 'Grouping',
quantity: 'Cantidad', quantity: 'Cantidad',
description: 'Descripción',
size: 'Medida', size: 'Medida',
tags: 'Etiquetas', tags: 'Etiquetas',
type: 'Tipo', type: 'Tipo',
@ -460,7 +469,6 @@ export default {
visible: 'Visible', visible: 'Visible',
available: 'Disponible', available: 'Disponible',
quantity: 'Cantidad', quantity: 'Cantidad',
description: 'Descripción',
price: 'Precio', price: 'Precio',
discount: 'Descuento', discount: 'Descuento',
packing: 'Encajado', packing: 'Encajado',
@ -533,7 +541,6 @@ export default {
landed: 'Entregado', landed: 'Entregado',
quantity: 'Cantidad', quantity: 'Cantidad',
claimed: 'Reclamado', claimed: 'Reclamado',
description: 'Descripción',
price: 'Precio', price: 'Precio',
discount: 'Descuento', discount: 'Descuento',
total: 'Total', total: 'Total',
@ -704,7 +711,6 @@ export default {
orderTicketList: 'Tickets del pedido', orderTicketList: 'Tickets del pedido',
details: 'Detalles', details: 'Detalles',
item: 'Item', item: 'Item',
description: 'Descripción',
quantity: 'Cantidad', quantity: 'Cantidad',
price: 'Precio', price: 'Precio',
amount: 'Monto', amount: 'Monto',
@ -1141,7 +1147,6 @@ export default {
warehouse: 'Almacén', warehouse: 'Almacén',
travelFileDescription: 'Id envío { travelId }', travelFileDescription: 'Id envío { travelId }',
file: 'Fichero', file: 'Fichero',
description: 'Descripción',
}, },
}, },
item: { item: {
@ -1175,7 +1180,6 @@ export default {
clone: 'Clonar', clone: 'Clonar',
openCard: 'Ficha', openCard: 'Ficha',
openSummary: 'Detalles', openSummary: 'Detalles',
viewDescription: 'Descripción',
}, },
cardDescriptor: { cardDescriptor: {
mainList: 'Listado principal', mainList: 'Listado principal',

View File

@ -116,7 +116,13 @@ function navigate(event, id) {
</template> </template>
<template #actions> <template #actions>
<QBtn <QBtn
:label="t('components.smartCard.viewDescription')" :label="t('components.smartCard.openCard')"
@click.stop="navigate(row.id)"
class="bg-vn-dark"
outline
/>
<QBtn
:label="t('globals.description')"
@click.stop @click.stop
class="bg-vn-dark" class="bg-vn-dark"
outline outline

View File

@ -44,7 +44,7 @@ const columns = computed(() => [
align: 'left', align: 'left',
}, },
{ {
label: t('entry.buys.description'), label: t('globals.description'),
name: 'description', name: 'description',
field: 'description', field: 'description',
align: 'left', align: 'left',
@ -214,7 +214,7 @@ const redirectToBuysView = () => {
class="cursor-pointer" class="cursor-pointer"
@click="inputFileRef.pickFiles()" @click="inputFileRef.pickFiles()"
> >
<QTooltip>{{ t('Select a file') }}</QTooltip> <QTooltip>{{ t('globals.selectFile') }}</QTooltip>
</QIcon> </QIcon>
</template> </template>
</QFile> </QFile>
@ -292,6 +292,6 @@ const redirectToBuysView = () => {
<i18n> <i18n>
es: es:
Select a file: Selecciona un fichero globals.selectFile: Selecciona un fichero
Some of the imported buys does not have an item: Algunas de las compras importadas no tienen un artículo Some of the imported buys does not have an item: Algunas de las compras importadas no tienen un artículo
</i18n> </i18n>

View File

@ -0,0 +1,11 @@
<script setup>
import VnDmsList from 'src/components/common/VnDmsList.vue';
</script>
<template>
<VnDmsList
model="EntryDms"
update-model="EntryDms"
default-dms-code="entry"
filter="entryFk"
/>
</template>

View File

@ -63,7 +63,7 @@ onMounted(() => {
</div> </div>
<div class="col"> <div class="col">
<VnInput <VnInput
:label="t('entry.notes.description')" :label="t('globals.description')"
v-model="row.description" v-model="row.description"
:rules="validate('EntryObservation.description')" :rules="validate('EntryObservation.description')"
/> />

View File

@ -59,7 +59,7 @@ const columns = computed(() => [
align: 'left', align: 'left',
}, },
{ {
label: t('entry.latestBuys.description'), label: t('globals.description'),
field: 'description', field: 'description',
name: 'description', name: 'description',
align: 'left', align: 'left',
@ -214,7 +214,7 @@ const editTableCellFormFieldsOptions = [
{ field: 'grouping', label: t('entry.latestBuys.grouping') }, { field: 'grouping', label: t('entry.latestBuys.grouping') },
{ field: 'packageValue', label: t('entry.latestBuys.packageValue') }, { field: 'packageValue', label: t('entry.latestBuys.packageValue') },
{ field: 'weight', label: t('entry.latestBuys.weight') }, { field: 'weight', label: t('entry.latestBuys.weight') },
{ field: 'description', label: t('entry.latestBuys.description') }, { field: 'description', label: t('globals.description') },
{ field: 'size', label: t('entry.latestBuys.size') }, { field: 'size', label: t('entry.latestBuys.size') },
{ field: 'weightByPiece', label: t('entry.latestBuys.weightByPiece') }, { field: 'weightByPiece', label: t('entry.latestBuys.weightByPiece') },
{ field: 'packingOut', label: t('entry.latestBuys.packingOut') }, { field: 'packingOut', label: t('entry.latestBuys.packingOut') },

View File

@ -174,7 +174,12 @@ async function upsert() {
@on-fetch="(data) => (userConfig = data)" @on-fetch="(data) => (userConfig = data)"
auto-load auto-load
/> />
<FormModel v-if="invoiceIn" :url="`InvoiceIns/${route.params.id}`" model="invoiceIn"> <FormModel
v-if="invoiceIn"
:url="`InvoiceIns/${route.params.id}`"
model="invoiceIn"
:auto-load="true"
>
<template #form="{ data }"> <template #form="{ data }">
<div class="row q-gutter-md q-mb-md"> <div class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">
@ -521,7 +526,7 @@ async function upsert() {
@click="inputFileRef.pickFiles()" @click="inputFileRef.pickFiles()"
> >
<QTooltip> <QTooltip>
{{ t('Select a file') }} {{ t('globals.selectFile') }}
</QTooltip> </QTooltip>
</QBtn> </QBtn>
<QBtn icon="info" flat round padding="xs"> <QBtn icon="info" flat round padding="xs">
@ -630,7 +635,7 @@ async function upsert() {
@click="inputFileRef.pickFiles()" @click="inputFileRef.pickFiles()"
> >
<QTooltip> <QTooltip>
{{ t('Select a file') }} {{ t('globals.selectFile') }}
</QTooltip> </QTooltip>
</QBtn> </QBtn>
<QBtn icon="info" flat round padding="xs"> <QBtn icon="info" flat round padding="xs">
@ -699,7 +704,6 @@ async function upsert() {
Generate identifier for original file: Generar identificador para archivo original Generate identifier for original file: Generar identificador para archivo original
File: Fichero File: Fichero
Create document: Crear documento Create document: Crear documento
Select a file: Seleccione un fichero
Allowed content types: Tipos de archivo permitidos Allowed content types: Tipos de archivo permitidos
The company can't be empty: La empresa no puede estar vacía The company can't be empty: La empresa no puede estar vacía
The warehouse can't be empty: El almacén no puede estar vacío The warehouse can't be empty: El almacén no puede estar vacío

View File

@ -31,7 +31,7 @@ const detailsColumns = ref([
}, },
{ {
name: 'description', name: 'description',
label: t('order.summary.description'), label: t('globals.description'),
field: (row) => row?.item?.name, field: (row) => row?.item?.name,
}, },
{ {
@ -167,7 +167,7 @@ const detailsColumns = ref([
<template #header="props"> <template #header="props">
<QTr :props="props"> <QTr :props="props">
<QTh auto-width>{{ t('order.summary.item') }}</QTh> <QTh auto-width>{{ t('order.summary.item') }}</QTh>
<QTh>{{ t('order.summary.description') }}</QTh> <QTh>{{ t('globals.description') }}</QTh>
<QTh auto-width>{{ t('order.summary.quantity') }}</QTh> <QTh auto-width>{{ t('order.summary.quantity') }}</QTh>
<QTh auto-width>{{ t('order.summary.price') }}</QTh> <QTh auto-width>{{ t('order.summary.price') }}</QTh>
<QTh auto-width>{{ t('order.summary.amount') }}</QTh> <QTh auto-width>{{ t('order.summary.amount') }}</QTh>

View File

@ -199,7 +199,7 @@ const openBuscaman = async (route, ticket) => {
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<div class="header"> <div class="header">
{{ t('route.summary.description') }} {{ t('globals.description') }}
</div> </div>
<p> <p>
{{ dashIfEmpty(entity?.route?.description) }} {{ dashIfEmpty(entity?.route?.description) }}

View File

@ -270,7 +270,7 @@ async function changeState(value) {
<QTh auto-width>{{ t('ticket.summary.visible') }}</QTh> <QTh auto-width>{{ t('ticket.summary.visible') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.available') }}</QTh> <QTh auto-width>{{ t('ticket.summary.available') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.quantity') }}</QTh> <QTh auto-width>{{ t('ticket.summary.quantity') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.description') }}</QTh> <QTh auto-width>{{ t('globals.description') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.price') }}</QTh> <QTh auto-width>{{ t('ticket.summary.price') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.discount') }}</QTh> <QTh auto-width>{{ t('ticket.summary.discount') }}</QTh>
<QTh auto-width>{{ t('globals.amount') }}</QTh> <QTh auto-width>{{ t('globals.amount') }}</QTh>
@ -425,7 +425,7 @@ async function changeState(value) {
<template #header="props"> <template #header="props">
<QTr :props="props"> <QTr :props="props">
<QTh auto-width>{{ t('ticket.summary.quantity') }}</QTh> <QTh auto-width>{{ t('ticket.summary.quantity') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.description') }}</QTh> <QTh auto-width>{{ t('globals.description') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.price') }}</QTh> <QTh auto-width>{{ t('ticket.summary.price') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.taxClass') }}</QTh> <QTh auto-width>{{ t('ticket.summary.taxClass') }}</QTh>
<QTh auto-width>{{ t('globals.amount') }}</QTh> <QTh auto-width>{{ t('globals.amount') }}</QTh>

View File

@ -301,7 +301,7 @@ const onThermographCreated = async (data) => {
<VnRow v-if="viewAction === 'edit'" class="row q-gutter-md q-mb-md"> <VnRow v-if="viewAction === 'edit'" class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">
<QInput <QInput
:label="t('travel.thermographs.description')" :label="t('globals.description')"
type="textarea" type="textarea"
v-model="thermographForm.description" v-model="thermographForm.description"
fill-input fill-input

View File

@ -11,7 +11,7 @@ export default {
redirect: { name: 'EntryMain' }, redirect: { name: 'EntryMain' },
menus: { menus: {
main: ['EntryList', 'EntryLatestBuys'], main: ['EntryList', 'EntryLatestBuys'],
card: ['EntryBasicData', 'EntryBuys', 'EntryNotes', 'EntryLog'], card: ['EntryBasicData', 'EntryBuys', 'EntryNotes', 'EntryDms', 'EntryLog'],
}, },
children: [ children: [
{ {
@ -95,6 +95,15 @@ export default {
}, },
component: () => import('src/pages/Entry/Card/EntryNotes.vue'), component: () => import('src/pages/Entry/Card/EntryNotes.vue'),
}, },
{
path: 'dms',
name: 'EntryDms',
meta: {
title: 'dms',
icon: 'smb_share',
},
component: () => import('src/pages/Entry/Card/EntryDms.vue'),
},
{ {
path: 'log', path: 'log',
name: 'EntryLog', name: 'EntryLog',

View File

@ -13,7 +13,7 @@ describe('ClaimDevelopment', () => {
it('should reset line', () => { it('should reset line', () => {
cy.selectOption(firstLineReason, 'Novato'); cy.selectOption(firstLineReason, 'Novato');
cy.resetCard(); cy.resetCard();
cy.getValue(firstLineReason).should('have.value', 'Prisas'); cy.getValue(firstLineReason).should('equal', 'Prisas');
}); });
it('should edit line', () => { it('should edit line', () => {
@ -23,7 +23,7 @@ describe('ClaimDevelopment', () => {
cy.login('developer'); cy.login('developer');
cy.visit(`/#/claim/${claimId}/development`); cy.visit(`/#/claim/${claimId}/development`);
cy.getValue(firstLineReason).should('have.value', 'Novato'); cy.getValue(firstLineReason).should('equal', 'Novato');
//Restart data //Restart data
cy.selectOption(firstLineReason, 'Prisas'); cy.selectOption(firstLineReason, 'Prisas');

View File

@ -0,0 +1,41 @@
describe('WagonTypeCreate', () => {
const entryId = 1;
beforeEach(() => {
cy.viewport(1920, 1080);
cy.login('developer');
cy.visit(`/#/entry/${entryId}/dms`);
});
it('should create edit and remove new dms', () => {
cy.addRow();
cy.get('.icon-attach').click()
cy.get('.q-file').selectFile('test/cypress/fixtures/image.jpg', {
force: true,
});
cy.get("tbody > tr").then((value) => {
//Create and check if exist new row
let newFileTd = Cypress.$(value).length;
cy.get('.q-btn--standard > .q-btn__content > .block').click();
expect(value).to.have.length(newFileTd++);
const newRowSelector = `tbody > :nth-child(${newFileTd})`
cy.waitForElement(newRowSelector);
//Edit new dms
const u = undefined;
cy.validateRow(newRowSelector, [u,u,u,u,'ENTRADA ID 1'])
cy.get(`tbody :nth-child(${newFileTd}) > .text-right > .flex > :nth-child(2) > .q-btn > .q-btn__content > .q-icon`).click();
})
// cy.log('newFileTd', newFileTd)
// //Create and check if exist new row
// cy.log('newFileTd:', newFileTd);
// cy.get(`tbody :nth-child(${newFileTd}) > .text-right > .flex > :nth-child(2) > .q-btn > .q-btn__content > .q-icon`).click()
// cy.get(`tbody :nth-child(${newFileTd}) > :nth-child(5) > .q-tr > :nth-child(1) > span`).then((value) => {
// cy.log(value)
// });
});
});

View File

@ -18,7 +18,7 @@ describe('InvoiceInIntrastat', () => {
cy.visit(`/#/invoice-in/1/intrastat`); cy.visit(`/#/invoice-in/1/intrastat`);
cy.getValue(firstLineCode).should( cy.getValue(firstLineCode).should(
'have.value', 'equal',
'Plantas vivas: Esqueje/injerto, Vid' 'Plantas vivas: Esqueje/injerto, Vid'
); );
}); });

View File

@ -21,7 +21,7 @@ describe('InvoiceInVat', () => {
cy.saveCard(); cy.saveCard();
cy.visit(`/#/invoice-in/1/vat`); cy.visit(`/#/invoice-in/1/vat`);
cy.getValue(firstLineVat).should('have.value', 'H.P. IVA 21% CEE'); cy.getValue(firstLineVat).should('equal', 'H.P. IVA 21% CEE');
}); });
it('should add a new row', () => { it('should add a new row', () => {

View File

@ -42,7 +42,7 @@ Cypress.Commands.add('login', (user) => {
}); });
Cypress.Commands.add('waitForElement', (element) => { Cypress.Commands.add('waitForElement', (element) => {
cy.get(element, { timeout: 2000 }).should('be.visible'); cy.get(element, { timeout: 5000 }).should('be.visible');
}); });
Cypress.Commands.add('getValue', (selector) => { Cypress.Commands.add('getValue', (selector) => {
@ -55,7 +55,13 @@ Cypress.Commands.add('getValue', (selector) => {
return cy.get( return cy.get(
selector + selector +
'> .q-field > .q-field__inner > .q-field__control > .q-field__control-container > .q-field__native > input' '> .q-field > .q-field__inner > .q-field__control > .q-field__control-container > .q-field__native > input'
); ).invoke('val')
}
// Si es un QSelect
if ($el.find('span').length) {
return cy.get(
selector + ' span'
).then(($span) => { return $span[0].innerText })
} }
// Puedes añadir un log o lanzar un error si el elemento no es reconocido // Puedes añadir un log o lanzar un error si el elemento no es reconocido
cy.log('Elemento no soportado'); cy.log('Elemento no soportado');
@ -126,12 +132,13 @@ Cypress.Commands.add('validateRow', (rowSelector, expectedValues) => {
cy.get(rowSelector).within(() => { cy.get(rowSelector).within(() => {
for (const [index, value] of expectedValues.entries()) { for (const [index, value] of expectedValues.entries()) {
cy.log('CHECKING ', index, value); cy.log('CHECKING ', index, value);
if(value === undefined) continue
if (typeof value == 'boolean') { if (typeof value == 'boolean') {
const prefix = value ? '' : 'not.'; const prefix = value ? '' : 'not.';
cy.getValue(`:nth-child(${index + 1})`).should(`${prefix}be.checked`); cy.getValue(`:nth-child(${index + 1})`).should(`${prefix}be.checked`);
continue; continue;
} }
cy.getValue(`:nth-child(${index + 1})`).should('have.value', value); cy.getValue(`:nth-child(${index + 1})`).should('equal', value)
} }
}); });
}); });