Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8717-reviewAndFixAgencySection
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details

This commit is contained in:
Jose Antonio Tubau 2025-03-24 09:22:14 +01:00
commit e28f30d7e9
46 changed files with 939 additions and 660 deletions

2
Jenkinsfile vendored
View File

@ -126,7 +126,7 @@ pipeline {
sh "docker-compose ${env.COMPOSE_PARAMS} up -d" sh "docker-compose ${env.COMPOSE_PARAMS} up -d"
image.inside("--network ${env.COMPOSE_PROJECT}_default -e CI -e TZ --init") { image.inside("--network ${env.COMPOSE_PROJECT}_default -e CI -e TZ --init") {
sh 'sh test/cypress/cypressParallel.sh 2' sh 'sh test/cypress/cypressParallel.sh 1'
} }
} }
} }

View File

@ -633,6 +633,7 @@ const rowCtrlClickFunction = computed(() => {
:data-key="$attrs['data-key']" :data-key="$attrs['data-key']"
:columns="columns" :columns="columns"
:redirect="redirect" :redirect="redirect"
v-bind="$attrs?.['table-filter']"
> >
<template <template
v-for="(_, slotName) in $slots" v-for="(_, slotName) in $slots"

View File

@ -1,12 +1,15 @@
<script setup> <script setup>
import { onBeforeMount } from 'vue'; import { onBeforeMount, computed } from 'vue';
import { useRouter, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'; import { useRoute, useRouter, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import useCardSize from 'src/composables/useCardSize'; import useCardSize from 'src/composables/useCardSize';
import VnSubToolbar from '../ui/VnSubToolbar.vue'; import VnSubToolbar from '../ui/VnSubToolbar.vue';
const emit = defineEmits(['onFetch']);
const props = defineProps({ const props = defineProps({
id: { type: Number, required: false, default: null },
dataKey: { type: String, required: true }, dataKey: { type: String, required: true },
url: { type: String, default: undefined }, url: { type: String, default: undefined },
idInWhere: { type: Boolean, default: false }, idInWhere: { type: Boolean, default: false },
@ -16,10 +19,13 @@ const props = defineProps({
searchDataKey: { type: String, default: undefined }, searchDataKey: { type: String, default: undefined },
searchbarProps: { type: Object, default: undefined }, searchbarProps: { type: Object, default: undefined },
redirectOnError: { type: Boolean, default: false }, redirectOnError: { type: Boolean, default: false },
visual: { type: Boolean, default: true },
}); });
const route = useRoute();
const stateStore = useStateStore(); const stateStore = useStateStore();
const router = useRouter(); const router = useRouter();
const entityId = computed(() => props.id || route?.params?.id);
const arrayData = useArrayData(props.dataKey, { const arrayData = useArrayData(props.dataKey, {
url: props.url, url: props.url,
userFilter: props.filter, userFilter: props.filter,
@ -35,7 +41,7 @@ onBeforeMount(async () => {
const route = router.currentRoute.value; const route = router.currentRoute.value;
try { try {
await fetch(route.params.id); await fetch(entityId.value);
} catch { } catch {
const { matched: matches } = route; const { matched: matches } = route;
const { path } = matches.at(-1); const { path } = matches.at(-1);
@ -51,8 +57,7 @@ onBeforeRouteUpdate(async (to, from) => {
router.push({ name, params: to.params }); router.push({ name, params: to.params });
} }
} }
const id = to.params.id; if (entityId.value !== to.params.id) await fetch(to.params.id, true);
if (id !== from.params.id) await fetch(id, true);
}); });
async function fetch(id, append = false) { async function fetch(id, append = false) {
@ -61,14 +66,17 @@ async function fetch(id, append = false) {
else if (!regex.test(props.url)) arrayData.store.url = `${props.url}/${id}`; else if (!regex.test(props.url)) arrayData.store.url = `${props.url}/${id}`;
else arrayData.store.url = props.url.replace(regex, `/${id}`); else arrayData.store.url = props.url.replace(regex, `/${id}`);
await arrayData.fetch({ append, updateRouter: false }); await arrayData.fetch({ append, updateRouter: false });
emit('onFetch', arrayData.store.data);
} }
function hasRouteParam(params, valueToCheck = ':addressId') { function hasRouteParam(params, valueToCheck = ':addressId') {
return Object.values(params).includes(valueToCheck); return Object.values(params).includes(valueToCheck);
} }
</script> </script>
<template> <template>
<VnSubToolbar /> <template v-if="visual">
<div :class="[useCardSize(), $attrs.class]"> <VnSubToolbar />
<RouterView :key="$route.path" /> <div :class="[useCardSize(), $attrs.class]">
</div> <RouterView :key="$route.path" />
</div>
</template>
</template> </template>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, toRefs, computed, watch, onMounted, useAttrs } from 'vue'; import { ref, toRefs, computed, watch, onMounted, useAttrs, nextTick } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { useRequired } from 'src/composables/useRequired'; import { useRequired } from 'src/composables/useRequired';
@ -247,6 +247,7 @@ async function fetchFilter(val) {
} }
async function filterHandler(val, update) { async function filterHandler(val, update) {
if (isLoading.value) return update();
if (!val && lastVal.value === val) { if (!val && lastVal.value === val) {
lastVal.value = val; lastVal.value = val;
return update(); return update();
@ -294,6 +295,7 @@ async function onScroll({ to, direction, from, index }) {
await arrayData.loadMore(); await arrayData.loadMore();
setOptions(arrayData.store.data); setOptions(arrayData.store.data);
vnSelectRef.value.scrollTo(lastIndex); vnSelectRef.value.scrollTo(lastIndex);
await nextTick();
isLoading.value = false; isLoading.value = false;
} }
} }

View File

@ -4,12 +4,15 @@ import { vi, afterEach, beforeAll, describe, expect, it } from 'vitest';
describe('VnDmsList', () => { describe('VnDmsList', () => {
let vm; let vm;
const dms = { const dms = {
userFk: 1, userFk: 1,
name: 'DMS 1' name: 'DMS 1',
}; };
beforeAll(() => { beforeAll(() => {
vi.mock('src/composables/getUrl', () => ({
getUrl: vi.fn().mockResolvedValue(''),
}));
vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); vi.spyOn(axios, 'get').mockResolvedValue({ data: [] });
vm = createWrapper(VnDmsList, { vm = createWrapper(VnDmsList, {
props: { props: {
@ -18,8 +21,8 @@ describe('VnDmsList', () => {
filter: 'wd.workerFk', filter: 'wd.workerFk',
updateModel: 'Workers', updateModel: 'Workers',
deleteModel: 'WorkerDms', deleteModel: 'WorkerDms',
downloadModel: 'WorkerDms' downloadModel: 'WorkerDms',
} },
}).vm; }).vm;
}); });
@ -29,46 +32,45 @@ describe('VnDmsList', () => {
describe('setData()', () => { describe('setData()', () => {
const data = [ const data = [
{ {
userFk: 1, userFk: 1,
name: 'Jessica', name: 'Jessica',
lastName: 'Jones', lastName: 'Jones',
file: '4.jpg', file: '4.jpg',
created: '2021-07-28 21:00:00' created: '2021-07-28 21:00:00',
}, },
{ {
userFk: 2, userFk: 2,
name: 'Bruce', name: 'Bruce',
lastName: 'Banner', lastName: 'Banner',
created: '2022-07-28 21:00:00', created: '2022-07-28 21:00:00',
dms: { dms: {
userFk: 2, userFk: 2,
name: 'Bruce', name: 'Bruce',
lastName: 'BannerDMS', lastName: 'BannerDMS',
created: '2022-07-28 21:00:00', created: '2022-07-28 21:00:00',
file: '4.jpg', file: '4.jpg',
} },
}, },
{ {
userFk: 3, userFk: 3,
name: 'Natasha', name: 'Natasha',
lastName: 'Romanoff', lastName: 'Romanoff',
file: '4.jpg', file: '4.jpg',
created: '2021-10-28 21:00:00' created: '2021-10-28 21:00:00',
} },
] ];
it('Should replace objects that contain the "dms" property with the value of the same and sort by creation date', () => { it('Should replace objects that contain the "dms" property with the value of the same and sort by creation date', () => {
vm.setData(data); vm.setData(data);
expect([vm.rows][0][0].lastName).toEqual('BannerDMS'); expect([vm.rows][0][0].lastName).toEqual('BannerDMS');
expect([vm.rows][0][1].lastName).toEqual('Romanoff'); expect([vm.rows][0][1].lastName).toEqual('Romanoff');
}); });
}); });
describe('parseDms()', () => { describe('parseDms()', () => {
const resultDms = { ...dms, userId:1}; const resultDms = { ...dms, userId: 1 };
it('Should add properties that end with "Fk" by changing the suffix to "Id"', () => { it('Should add properties that end with "Fk" by changing the suffix to "Id"', () => {
const parsedDms = vm.parseDms(dms); const parsedDms = vm.parseDms(dms);
expect(parsedDms).toEqual(resultDms); expect(parsedDms).toEqual(resultDms);
@ -76,12 +78,12 @@ describe('VnDmsList', () => {
}); });
describe('showFormDialog()', () => { describe('showFormDialog()', () => {
const resultDms = { ...dms, userId:1}; const resultDms = { ...dms, userId: 1 };
it('should call fn parseDms() and set show true if dms is defined', () => { it('should call fn parseDms() and set show true if dms is defined', () => {
vm.showFormDialog(dms); vm.showFormDialog(dms);
expect(vm.formDialog.show).toEqual(true); expect(vm.formDialog.show).toEqual(true);
expect(vm.formDialog.dms).toEqual(resultDms); expect(vm.formDialog.dms).toEqual(resultDms);
}); });
}); });
}); });

View File

@ -1,367 +1,38 @@
<script setup> <script setup>
import { onBeforeMount, watch, computed, ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import VnDescriptor from './VnDescriptor.vue';
import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue';
import { useArrayData } from 'composables/useArrayData';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { useState } from 'src/composables/useState';
import { useRoute, useRouter } from 'vue-router';
import { useClipboard } from 'src/composables/useClipboard';
import VnMoreOptions from './VnMoreOptions.vue';
const $props = defineProps({ const $props = defineProps({
url: { id: {
type: String,
default: '',
},
filter: {
type: Object,
default: null,
},
title: {
type: String,
default: '',
},
subtitle: {
type: Number, type: Number,
default: null, default: false,
}, },
dataKey: { card: {
type: String,
default: null,
},
summary: {
type: Object, type: Object,
default: null, default: null,
}, },
width: {
type: String,
default: 'md-width',
},
}); });
const state = useState();
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
const { copyText } = useClipboard();
const { viewSummary } = useSummaryDialog();
let arrayData;
let store;
let entity;
const isLoading = ref(false);
const isSameDataKey = computed(() => $props.dataKey === route.meta.moduleName);
const DESCRIPTOR_PROXY = 'DescriptorProxy';
const moduleName = ref();
const isSameModuleName = route.matched[1].meta.moduleName !== moduleName.value;
defineExpose({ getData });
onBeforeMount(async () => {
arrayData = useArrayData($props.dataKey, {
url: $props.url,
userFilter: $props.filter,
skip: 0,
oneRecord: true,
});
store = arrayData.store;
entity = computed(() => {
const data = store.data ?? {};
if (data) emit('onFetch', data);
return data;
});
// It enables to load data only once if the module is the same as the dataKey
if (!isSameDataKey.value || !route.params.id) await getData();
watch(
() => [$props.url, $props.filter],
async () => {
if (!isSameDataKey.value) await getData();
},
);
});
function getName() {
let name = $props.dataKey;
if ($props.dataKey.includes(DESCRIPTOR_PROXY)) {
name = name.split(DESCRIPTOR_PROXY)[0];
}
return name;
}
const routeName = computed(() => {
let routeName = getName();
return `${routeName}Summary`;
});
async function getData() {
store.url = $props.url;
store.filter = $props.filter ?? {};
isLoading.value = true;
try {
const { data } = await arrayData.fetch({ append: false, updateRouter: false });
state.set($props.dataKey, data);
emit('onFetch', data);
} finally {
isLoading.value = false;
}
}
function getValueFromPath(path) {
if (!path) return;
const keys = path.toString().split('.');
let current = entity.value;
for (const key of keys) {
if (current[key] === undefined) return undefined;
else current = current[key];
}
return current;
}
function copyIdText(id) {
copyText(id, {
component: {
copyValue: id,
},
});
}
const emit = defineEmits(['onFetch']); const emit = defineEmits(['onFetch']);
const entity = ref();
const iconModule = computed(() => {
moduleName.value = getName();
if (isSameModuleName) {
return router.options.routes[1].children.find((r) => r.name === moduleName.value)
?.meta?.icon;
} else {
return route.matched[1].meta.icon;
}
});
const toModule = computed(() => {
moduleName.value = getName();
if (isSameModuleName) {
return router.options.routes[1].children.find((r) => r.name === moduleName.value)
?.children[0]?.redirect;
} else {
return route.matched[1].path.split('/').length > 2
? route.matched[1].redirect
: route.matched[1].children[0].redirect;
}
});
</script> </script>
<template> <template>
<div class="descriptor" data-cy="cardDescriptor"> <component
<template v-if="entity && !isLoading"> :is="card"
<div class="header bg-primary q-pa-sm justify-between"> :id
<slot name="header-extra-action"> :visual="false"
<QBtn v-bind="$attrs"
round @on-fetch="
flat (data) => {
dense entity = data;
size="md" emit('onFetch', data);
:icon="iconModule"
color="white"
class="link"
:to="toModule"
>
<QTooltip>
{{ t('globals.goToModuleIndex') }}
</QTooltip>
</QBtn>
</slot>
<QBtn
@click.stop="viewSummary(entity.id, $props.summary, $props.width)"
round
flat
dense
size="md"
icon="preview"
color="white"
class="link"
v-if="summary"
data-cy="openSummaryBtn"
>
<QTooltip>
{{ t('components.smartCard.openSummary') }}
</QTooltip>
</QBtn>
<RouterLink :to="{ name: routeName, params: { id: entity.id } }">
<QBtn
class="link"
color="white"
dense
flat
icon="launch"
round
size="md"
data-cy="goToSummaryBtn"
>
<QTooltip>
{{ t('components.cardDescriptor.summary') }}
</QTooltip>
</QBtn>
</RouterLink>
<VnMoreOptions v-if="$slots.menu">
<template #menu="{ menuRef }">
<slot name="menu" :entity="entity" :menu-ref="menuRef" />
</template>
</VnMoreOptions>
</div>
<slot name="before" />
<div class="body q-py-sm">
<QList dense>
<QItemLabel header class="ellipsis text-h5" :lines="1">
<div class="title">
<span
v-if="$props.title"
:title="getValueFromPath(title)"
:data-cy="`${$attrs['data-cy'] ?? 'cardDescriptor'}_title`"
>
{{ getValueFromPath(title) ?? $props.title }}
</span>
<slot v-else name="description" :entity="entity">
<span
:title="entity.name"
:data-cy="`${$attrs['data-cy'] ?? 'cardDescriptor'}_description`"
v-text="entity.name"
/>
</slot>
</div>
</QItemLabel>
<QItem>
<QItemLabel
class="subtitle"
:data-cy="`${$attrs['data-cy'] ?? 'cardDescriptor'}_subtitle`"
>
#{{ getValueFromPath(subtitle) ?? entity.id }}
</QItemLabel>
<QBtn
round
flat
dense
size="sm"
icon="content_copy"
color="primary"
@click.stop="copyIdText(entity.id)"
>
<QTooltip>
{{ t('globals.copyId') }}
</QTooltip>
</QBtn>
</QItem>
</QList>
<div
class="list-box q-mt-xs"
:data-cy="`${$attrs['data-cy'] ?? 'cardDescriptor'}_listbox`"
>
<slot name="body" :entity="entity" />
</div>
</div>
<div class="icons">
<slot name="icons" :entity="entity" />
</div>
<div class="actions justify-center" data-cy="descriptor_actions">
<slot name="actions" :entity="entity" />
</div>
<slot name="after" />
</template>
<SkeletonDescriptor v-if="!entity || isLoading" />
</div>
<QInnerLoading
:label="t('globals.pleaseWait')"
:showing="isLoading"
color="primary"
/>
</template>
<style lang="scss">
.body {
background-color: var(--vn-section-color);
.text-h5 {
font-size: 20px;
padding-top: 5px;
padding-bottom: 0px;
}
.q-item {
min-height: 20px;
.link {
margin-left: 10px;
}
}
.vn-label-value {
display: flex;
padding: 0px 16px;
.label {
color: var(--vn-label-color);
font-size: 14px;
&:not(:has(a))::after {
content: ':';
} }
} "
.value { />
color: var(--vn-text-color); <VnDescriptor v-model="entity" v-bind="$attrs">
font-size: 14px; <template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName">
margin-left: 4px; <slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" />
overflow: hidden; </template>
text-overflow: ellipsis; </VnDescriptor>
white-space: nowrap; </template>
text-align: left;
}
.info {
margin-left: 5px;
}
}
}
</style>
<style lang="scss" scoped>
.title {
overflow: hidden;
text-overflow: ellipsis;
span {
color: var(--vn-text-color);
font-weight: bold;
}
}
.subtitle {
color: var(--vn-text-color);
font-size: 16px;
margin-bottom: 2px;
}
.list-box {
.q-item__label {
color: var(--vn-label-color);
padding-bottom: 0%;
}
}
.descriptor {
width: 256px;
.header {
display: flex;
align-items: center;
}
.icons {
margin: 0 10px;
display: flex;
justify-content: center;
.q-icon {
margin-right: 5px;
}
}
.actions {
margin: 0 5px;
justify-content: center !important;
}
}
</style>
<i18n>
en:
globals:
copyId: Copy ID
es:
globals:
copyId: Copiar ID
</i18n>

View File

@ -0,0 +1,78 @@
<script setup>
import { onBeforeMount, watch, computed, ref } from 'vue';
import { useArrayData } from 'composables/useArrayData';
import { useState } from 'src/composables/useState';
import { useRoute } from 'vue-router';
import VnDescriptor from './VnDescriptor.vue';
const $props = defineProps({
url: {
type: String,
default: '',
},
filter: {
type: Object,
default: null,
},
dataKey: {
type: String,
default: null,
},
});
const state = useState();
const route = useRoute();
let arrayData;
let store;
let entity;
const isLoading = ref(false);
const isSameDataKey = computed(() => $props.dataKey === route.meta.moduleName);
defineExpose({ getData });
onBeforeMount(async () => {
arrayData = useArrayData($props.dataKey, {
url: $props.url,
userFilter: $props.filter,
skip: 0,
oneRecord: true,
});
store = arrayData.store;
entity = computed(() => {
const data = store.data ?? {};
if (data) emit('onFetch', data);
return data;
});
// It enables to load data only once if the module is the same as the dataKey
if (!isSameDataKey.value || !route.params.id) await getData();
watch(
() => [$props.url, $props.filter],
async () => {
if (!isSameDataKey.value) await getData();
},
);
});
async function getData() {
store.url = $props.url;
store.filter = $props.filter ?? {};
isLoading.value = true;
try {
const { data } = await arrayData.fetch({ append: false, updateRouter: false });
state.set($props.dataKey, data);
emit('onFetch', data);
} finally {
isLoading.value = false;
}
}
const emit = defineEmits(['onFetch']);
</script>
<template>
<VnDescriptor v-model="entity" v-bind="$attrs" :module="dataKey">
<template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName">
<slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" />
</template>
</VnDescriptor>
</template>

View File

@ -0,0 +1,318 @@
<script setup>
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { useRoute, useRouter } from 'vue-router';
import { useClipboard } from 'src/composables/useClipboard';
import VnMoreOptions from './VnMoreOptions.vue';
const entity = defineModel({ type: Object, default: null });
const $props = defineProps({
title: {
type: String,
default: '',
},
subtitle: {
type: Number,
default: null,
},
summary: {
type: Object,
default: null,
},
width: {
type: String,
default: 'md-width',
},
module: {
type: String,
default: null,
},
toModule: {
type: String,
default: null,
},
});
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
const { copyText } = useClipboard();
const { viewSummary } = useSummaryDialog();
const DESCRIPTOR_PROXY = 'DescriptorProxy';
const moduleName = ref();
const isSameModuleName = route.matched[1].meta.moduleName !== moduleName.value;
function getName() {
let name = $props.module;
if ($props.module.includes(DESCRIPTOR_PROXY)) {
name = name.split(DESCRIPTOR_PROXY)[0];
}
return name;
}
const routeName = computed(() => {
let routeName = getName();
return `${routeName}Summary`;
});
function getValueFromPath(path) {
if (!path) return;
const keys = path.toString().split('.');
let current = entity.value;
for (const key of keys) {
if (current[key] === undefined) return undefined;
else current = current[key];
}
return current;
}
function copyIdText(id) {
copyText(id, {
component: {
copyValue: id,
},
});
}
const emit = defineEmits(['onFetch']);
const iconModule = computed(() => {
moduleName.value = getName();
if ($props.toModule) {
return router.getRoutes().find((r) => r.name === $props.toModule.name).meta.icon;
}
if (isSameModuleName) {
return router.options.routes[1].children.find((r) => r.name === moduleName.value)
?.meta?.icon;
} else {
return route.matched[1].meta.icon;
}
});
const toModule = computed(() => {
moduleName.value = getName();
if ($props.toModule) return $props.toModule;
if (isSameModuleName) {
return router.options.routes[1].children.find((r) => r.name === moduleName.value)
?.redirect;
} else {
return route.matched[1].path.split('/').length > 2
? route.matched[1].redirect
: route.matched[1].children[0].redirect;
}
});
</script>
<template>
<div class="descriptor" data-cy="vnDescriptor">
<template v-if="entity && entity?.id">
<div class="header bg-primary q-pa-sm justify-between">
<slot name="header-extra-action">
<QBtn
round
flat
dense
size="md"
:icon="iconModule"
color="white"
class="link"
:to="toModule"
>
<QTooltip>
{{ t('globals.goToModuleIndex') }}
</QTooltip>
</QBtn>
</slot>
<QBtn
@click.stop="viewSummary(entity.id, summary, width)"
round
flat
dense
size="md"
icon="preview"
color="white"
class="link"
v-if="summary"
data-cy="openSummaryBtn"
>
<QTooltip>
{{ t('components.smartCard.openSummary') }}
</QTooltip>
</QBtn>
<RouterLink :to="{ name: routeName, params: { id: entity.id } }">
<QBtn
class="link"
color="white"
dense
flat
icon="launch"
round
size="md"
data-cy="goToSummaryBtn"
>
<QTooltip>
{{ t('components.vnDescriptor.summary') }}
</QTooltip>
</QBtn>
</RouterLink>
<VnMoreOptions v-if="$slots.menu">
<template #menu="{ menuRef }">
<slot name="menu" :entity="entity" :menu-ref="menuRef" />
</template>
</VnMoreOptions>
</div>
<slot name="before" />
<div class="body q-py-sm">
<QList dense>
<QItemLabel header class="ellipsis text-h5" :lines="1">
<div class="title">
<span
v-if="title"
:title="getValueFromPath(title)"
:data-cy="`${$attrs['data-cy'] ?? 'vnDescriptor'}_title`"
>
{{ getValueFromPath(title) ?? title }}
</span>
<slot v-else name="description" :entity="entity">
<span
:title="entity.name"
:data-cy="`${$attrs['data-cy'] ?? 'vnDescriptor'}_description`"
v-text="entity.name"
/>
</slot>
</div>
</QItemLabel>
<QItem>
<QItemLabel
class="subtitle"
:data-cy="`${$attrs['data-cy'] ?? 'vnDescriptor'}_subtitle`"
>
#{{ getValueFromPath(subtitle) ?? entity.id }}
</QItemLabel>
<QBtn
round
flat
dense
size="sm"
icon="content_copy"
color="primary"
@click.stop="copyIdText(entity.id)"
>
<QTooltip>
{{ t('globals.copyId') }}
</QTooltip>
</QBtn>
</QItem>
</QList>
<div
class="list-box q-mt-xs"
:data-cy="`${$attrs['data-cy'] ?? 'vnDescriptor'}_listbox`"
>
<slot name="body" :entity="entity" />
</div>
</div>
<div class="icons">
<slot name="icons" :entity="entity" />
</div>
<div class="actions justify-center" data-cy="descriptor_actions">
<slot name="actions" :entity="entity" />
</div>
<slot name="after" />
</template>
<SkeletonDescriptor v-if="!entity" />
</div>
<QInnerLoading :label="t('globals.pleaseWait')" :showing="!entity" color="primary" />
</template>
<style lang="scss">
.body {
background-color: var(--vn-section-color);
.text-h5 {
font-size: 20px;
padding-top: 5px;
padding-bottom: 0px;
}
.q-item {
min-height: 20px;
.link {
margin-left: 10px;
}
}
.vn-label-value {
display: flex;
padding: 0px 16px;
.label {
color: var(--vn-label-color);
font-size: 14px;
&:not(:has(a))::after {
content: ':';
}
}
.value {
color: var(--vn-text-color);
font-size: 14px;
margin-left: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: left;
}
.info {
margin-left: 5px;
}
}
}
</style>
<style lang="scss" scoped>
.title {
overflow: hidden;
text-overflow: ellipsis;
span {
color: var(--vn-text-color);
font-weight: bold;
}
}
.subtitle {
color: var(--vn-text-color);
font-size: 16px;
margin-bottom: 2px;
}
.list-box {
.q-item__label {
color: var(--vn-label-color);
padding-bottom: 0%;
}
}
.descriptor {
width: 256px;
.header {
display: flex;
align-items: center;
}
.icons {
margin: 0 10px;
display: flex;
justify-content: center;
.q-icon {
margin-right: 5px;
}
}
.actions {
margin: 0 5px;
justify-content: center !important;
}
}
</style>
<i18n>
en:
globals:
copyId: Copy ID
es:
globals:
copyId: Copiar ID
</i18n>

View File

@ -6,10 +6,12 @@ const session = useSession();
const token = session.getToken(); const token = session.getToken();
describe('downloadFile', () => { describe('downloadFile', () => {
const baseUrl = 'http://localhost:9000';
let defaulCreateObjectURL; let defaulCreateObjectURL;
beforeAll(() => { beforeAll(() => {
vi.mock('src/composables/getUrl', () => ({
getUrl: vi.fn().mockResolvedValue(''),
}));
defaulCreateObjectURL = window.URL.createObjectURL; defaulCreateObjectURL = window.URL.createObjectURL;
window.URL.createObjectURL = vi.fn(() => 'blob:http://localhost:9000/blob-id'); window.URL.createObjectURL = vi.fn(() => 'blob:http://localhost:9000/blob-id');
}); });
@ -22,15 +24,14 @@ describe('downloadFile', () => {
headers: { 'content-disposition': 'attachment; filename="test-file.txt"' }, headers: { 'content-disposition': 'attachment; filename="test-file.txt"' },
}; };
vi.spyOn(axios, 'get').mockImplementation((url) => { vi.spyOn(axios, 'get').mockImplementation((url) => {
if (url == 'Urls/getUrl') return Promise.resolve({ data: baseUrl }); if (url.includes('downloadFile')) return Promise.resolve(res);
else if (url.includes('downloadFile')) return Promise.resolve(res);
}); });
await downloadFile(1); await downloadFile(1);
expect(axios.get).toHaveBeenCalledWith( expect(axios.get).toHaveBeenCalledWith(
`${baseUrl}/api/dms/1/downloadFile?access_token=${token}`, `/api/dms/1/downloadFile?access_token=${token}`,
{ responseType: 'blob' } { responseType: 'blob' },
); );
}); });
}); });

View File

@ -5,20 +5,30 @@ import { exportFile } from 'quasar';
const { getTokenMultimedia } = useSession(); const { getTokenMultimedia } = useSession();
const token = getTokenMultimedia(); const token = getTokenMultimedia();
const appUrl = (await getUrl('', 'lilium')).replace('/#/', '');
export async function downloadFile(id, model = 'dms', urlPath = '/downloadFile', url) { export async function downloadFile(id, model = 'dms', urlPath = '/downloadFile', url) {
const appUrl = (await getUrl('', 'lilium')).replace('/#/', '');
const response = await axios.get( const response = await axios.get(
url ?? `${appUrl}/api/${model}/${id}${urlPath}?access_token=${token}`, url ?? `${appUrl}/api/${model}/${id}${urlPath}?access_token=${token}`,
{ responseType: 'blob' } { responseType: 'blob' }
); );
download(response);
}
export async function downloadDocuware(url, params) {
const response = await axios.get(`${appUrl}/api/` + url, {
responseType: 'blob',
params,
});
download(response);
}
function download(response) {
const contentDisposition = response.headers['content-disposition']; const contentDisposition = response.headers['content-disposition'];
const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(contentDisposition); const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(contentDisposition);
const filename = const filename = matches?.[1] ? matches[1].replace(/['"]/g, '') : 'downloaded-file';
matches != null && matches[1]
? matches[1].replace(/['"]/g, '')
: 'downloaded-file';
exportFile(filename, response.data); exportFile(filename, response.data);
} }

View File

@ -646,6 +646,7 @@ worker:
model: Model model: Model
serialNumber: Serial number serialNumber: Serial number
removePDA: Deallocate PDA removePDA: Deallocate PDA
sendToTablet: Send to tablet
create: create:
lastName: Last name lastName: Last name
birth: Birth birth: Birth
@ -892,6 +893,8 @@ components:
VnLv: VnLv:
copyText: '{copyValue} has been copied to the clipboard' copyText: '{copyValue} has been copied to the clipboard'
iban_tooltip: 'IBAN: ES21 1234 5678 90 0123456789' iban_tooltip: 'IBAN: ES21 1234 5678 90 0123456789'
VnNotes:
clientWithoutPhone: 'The following clients do not have a phone number and the message will not be sent to them: {clientWithoutPhone}'
weekdays: weekdays:
sun: Sunday sun: Sunday
mon: Monday mon: Monday

View File

@ -731,6 +731,7 @@ worker:
model: Modelo model: Modelo
serialNumber: Número de serie serialNumber: Número de serie
removePDA: Desasignar PDA removePDA: Desasignar PDA
sendToTablet: Enviar a la tablet
create: create:
lastName: Apellido lastName: Apellido
birth: Fecha de nacimiento birth: Fecha de nacimiento
@ -976,6 +977,8 @@ components:
VnLv: VnLv:
copyText: '{copyValue} se ha copiado al portapepeles' copyText: '{copyValue} se ha copiado al portapepeles'
iban_tooltip: 'IBAN: ES21 1234 5678 90 0123456789' iban_tooltip: 'IBAN: ES21 1234 5678 90 0123456789'
VnNotes:
clientWithoutPhone: 'Estos clientes no tienen asociado número de télefono y el sms no les será enviado: {clientWithoutPhone}'
weekdays: weekdays:
sun: Domingo sun: Domingo
mon: Lunes mon: Lunes

View File

@ -4,7 +4,7 @@ import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import axios from 'axios'; import axios from 'axios';
@ -48,11 +48,12 @@ const removeAlias = () => {
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
ref="descriptor" ref="descriptor"
:url="`MailAliases/${entityId}`" :url="`MailAliases/${entityId}`"
data-key="Alias" data-key="Alias"
title="alias" title="alias"
:to-module="{ name: 'AccountAlias' }"
> >
<template #menu> <template #menu>
<QItem v-ripple clickable @click="removeAlias()"> <QItem v-ripple clickable @click="removeAlias()">
@ -62,7 +63,7 @@ const removeAlias = () => {
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('role.description')" :value="entity.description" /> <VnLv :label="t('role.description')" :value="entity.description" />
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<i18n> <i18n>

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import AccountDescriptorMenu from './AccountDescriptorMenu.vue'; import AccountDescriptorMenu from './AccountDescriptorMenu.vue';
import VnImg from 'src/components/ui/VnImg.vue'; import VnImg from 'src/components/ui/VnImg.vue';
@ -20,7 +20,7 @@ onMounted(async () => {
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
ref="descriptor" ref="descriptor"
:url="`VnUsers/preview`" :url="`VnUsers/preview`"
:filter="{ ...filter, where: { id: entityId } }" :filter="{ ...filter, where: { id: entityId } }"
@ -78,7 +78,7 @@ onMounted(async () => {
</QIcon> </QIcon>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<style scoped> <style scoped>
.q-item__label { .q-item__label {

View File

@ -2,7 +2,7 @@
import { computed } from 'vue'; 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 CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import axios from 'axios'; import axios from 'axios';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
@ -32,11 +32,12 @@ const removeRole = async () => {
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
url="VnRoles" url="VnRoles"
:filter="{ where: { id: entityId } }" :filter="{ where: { id: entityId } }"
data-key="Role" data-key="Role"
:summary="$props.summary" :summary="$props.summary"
:to-module="{ name: 'AccountRoles' }"
> >
<template #menu> <template #menu>
<QItem v-ripple clickable @click="removeRole()"> <QItem v-ripple clickable @click="removeRole()">
@ -46,7 +47,7 @@ const removeRole = async () => {
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('role.description')" :value="entity.description" /> <VnLv :label="t('role.description')" :value="entity.description" />
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<style scoped> <style scoped>
.q-item__label { .q-item__label {

View File

@ -6,7 +6,7 @@ import { toDateHourMinSec, toPercentage } from 'src/filters';
import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.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 DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue';
import { getUrl } from 'src/composables/getUrl'; import { getUrl } from 'src/composables/getUrl';
@ -44,7 +44,7 @@ onMounted(async () => {
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
:url="`Claims/${entityId}`" :url="`Claims/${entityId}`"
:filter="filter" :filter="filter"
title="client.name" title="client.name"
@ -147,7 +147,7 @@ onMounted(async () => {
</QBtn> </QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<style scoped> <style scoped>
.q-item__label { .q-item__label {

View File

@ -7,7 +7,7 @@ import { toCurrency, toDate } from 'src/filters';
import useCardDescription from 'src/composables/useCardDescription'; import useCardDescription from 'src/composables/useCardDescription';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import CustomerDescriptorMenu from './CustomerDescriptorMenu.vue'; import CustomerDescriptorMenu from './CustomerDescriptorMenu.vue';
import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue';
@ -54,7 +54,7 @@ const debtWarning = computed(() => {
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
:url="`Clients/${entityId}/getCard`" :url="`Clients/${entityId}/getCard`"
:summary="$props.summary" :summary="$props.summary"
data-key="Customer" data-key="Customer"
@ -232,7 +232,7 @@ const debtWarning = computed(() => {
</QBtn> </QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<i18n> <i18n>

View File

@ -6,7 +6,7 @@ import { toDate } from 'src/filters';
import { getUrl } from 'src/composables/getUrl'; import { getUrl } from 'src/composables/getUrl';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { usePrintService } from 'composables/usePrintService'; import { usePrintService } from 'composables/usePrintService';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue'; import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue';
import axios from 'axios'; import axios from 'axios';
@ -145,7 +145,7 @@ async function deleteEntry() {
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
:url="`Entries/${entityId}`" :url="`Entries/${entityId}`"
:filter="entryFilter" :filter="entryFilter"
title="supplier.nickname" title="supplier.nickname"
@ -264,7 +264,7 @@ async function deleteEntry() {
</QBtn> </QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<i18n> <i18n>
es: es:

View File

@ -5,7 +5,7 @@ import { useI18n } from 'vue-i18n';
import axios from 'axios'; import axios from 'axios';
import { toCurrency, toDate } from 'src/filters'; import { toCurrency, toDate } from 'src/filters';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
import filter from './InvoiceInFilter.js'; import filter from './InvoiceInFilter.js';
import InvoiceInDescriptorMenu from './InvoiceInDescriptorMenu.vue'; import InvoiceInDescriptorMenu from './InvoiceInDescriptorMenu.vue';
@ -84,7 +84,7 @@ async function setInvoiceCorrection(id) {
} }
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
ref="cardDescriptorRef" ref="cardDescriptorRef"
data-key="InvoiceIn" data-key="InvoiceIn"
:url="`InvoiceIns/${entityId}`" :url="`InvoiceIns/${entityId}`"
@ -159,7 +159,7 @@ async function setInvoiceCorrection(id) {
</QBtn> </QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.q-dialog { .q-dialog {

View File

@ -3,7 +3,7 @@ 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 CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import InvoiceOutDescriptorMenu from './InvoiceOutDescriptorMenu.vue'; import InvoiceOutDescriptorMenu from './InvoiceOutDescriptorMenu.vue';
@ -34,7 +34,7 @@ function ticketFilter(invoice) {
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
ref="descriptor" ref="descriptor"
:url="`InvoiceOuts/${entityId}`" :url="`InvoiceOuts/${entityId}`"
:filter="filter" :filter="filter"
@ -93,5 +93,5 @@ function ticketFilter(invoice) {
</QBtn> </QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>

View File

@ -3,7 +3,7 @@ import { computed, ref, onMounted } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import CardDescriptor from 'src/components/ui/CardDescriptor.vue'; import EntityDescriptor from 'src/components/ui/EntityDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import ItemDescriptorImage from 'src/pages/Item/Card/ItemDescriptorImage.vue'; import ItemDescriptorImage from 'src/pages/Item/Card/ItemDescriptorImage.vue';
@ -90,7 +90,7 @@ const updateStock = async () => {
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
data-key="Item" data-key="Item"
:summary="$props.summary" :summary="$props.summary"
:url="`Items/${entityId}/getCard`" :url="`Items/${entityId}/getCard`"
@ -162,7 +162,7 @@ const updateStock = async () => {
</QBtn> </QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<i18n> <i18n>

View File

@ -2,7 +2,7 @@
import { computed } from 'vue'; import { computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import filter from './ItemTypeFilter.js'; import filter from './ItemTypeFilter.js';
@ -25,11 +25,12 @@ const entityId = computed(() => {
}); });
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
:url="`ItemTypes/${entityId}`" :url="`ItemTypes/${entityId}`"
:filter="filter" :filter="filter"
title="code" title="code"
data-key="ItemType" data-key="ItemType"
:to-module="{ name: 'ItemTypeList' }"
> >
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="$t('itemType.shared.code')" :value="entity.code" /> <VnLv :label="$t('itemType.shared.code')" :value="entity.code" />
@ -45,5 +46,5 @@ const entityId = computed(() => {
:value="entity.category?.name" :value="entity.category?.name"
/> />
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>

View File

@ -118,8 +118,6 @@ const getLocale = (label) => {
rounded rounded
:label="t('globals.params.departmentFk')" :label="t('globals.params.departmentFk')"
v-model="params.departmentFk" v-model="params.departmentFk"
option-value="id"
option-label="name"
url="Departments" url="Departments"
/> />
</QItemSection> </QItemSection>
@ -209,20 +207,6 @@ const getLocale = (label) => {
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem>
<QItemSection>
<VnSelect
outlined
dense
rounded
:label="t('globals.params.departmentFk')"
v-model="params.department"
option-label="name"
option-value="name"
url="Departments"
/>
</QItemSection>
</QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnSelect <VnSelect

View File

@ -6,9 +6,11 @@ import filter from './OrderFilter.js';
<template> <template>
<VnCard <VnCard
data-key="Order" :data-key="$attrs['data-key'] ?? 'Order'"
url="Orders" url="Orders"
:filter="filter" :filter="filter"
:descriptor="OrderDescriptor" :descriptor="OrderDescriptor"
v-bind="$attrs"
v-on="$attrs"
/> />
</template> </template>

View File

@ -4,10 +4,10 @@ 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 { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import filter from './OrderFilter.js';
import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import OrderCard from './OrderCard.vue';
import CardDescriptor from 'src/components/ui/CardDescriptor.vue';
import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue';
const DEFAULT_ITEMS = 0; const DEFAULT_ITEMS = 0;
@ -24,11 +24,14 @@ const route = useRoute();
const state = useState(); const state = useState();
const { t } = useI18n(); const { t } = useI18n();
const getTotalRef = ref(); const getTotalRef = ref();
const total = ref(0);
const entityId = computed(() => { const entityId = computed(() => {
return $props.id || route.params.id; return $props.id || route.params.id;
}); });
const orderTotal = computed(() => state.get('orderTotal') ?? 0);
const setData = (entity) => { const setData = (entity) => {
if (!entity) return; if (!entity) return;
getTotalRef.value && getTotalRef.value.fetch(); getTotalRef.value && getTotalRef.value.fetch();
@ -38,9 +41,6 @@ const setData = (entity) => {
const getConfirmationValue = (isConfirmed) => { const getConfirmationValue = (isConfirmed) => {
return t(isConfirmed ? 'globals.confirmed' : 'order.summary.notConfirmed'); return t(isConfirmed ? 'globals.confirmed' : 'order.summary.notConfirmed');
}; };
const orderTotal = computed(() => state.get('orderTotal') ?? 0);
const total = ref(0);
</script> </script>
<template> <template>
@ -54,12 +54,12 @@ const total = ref(0);
" "
/> />
<CardDescriptor <CardDescriptor
ref="descriptor" v-bind="$attrs"
:url="`Orders/${entityId}`" :id="entityId"
:filter="filter" :card="OrderCard"
title="client.name" title="client.name"
@on-fetch="setData" @on-fetch="setData"
data-key="Order" module="Order"
> >
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv <VnLv

View File

@ -12,6 +12,11 @@ const $props = defineProps({
<template> <template>
<QPopupProxy> <QPopupProxy>
<OrderDescriptor v-if="$props.id" :id="$props.id" :summary="OrderSummary" /> <OrderDescriptor
v-if="$props.id"
:id="$props.id"
:summary="OrderSummary"
data-key="OrderDescriptor"
/>
</QPopupProxy> </QPopupProxy>
</template> </template>

View File

@ -3,7 +3,7 @@ import { computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import VnLv from 'components/ui/VnLv.vue'; import VnLv from 'components/ui/VnLv.vue';
const props = defineProps({ const props = defineProps({
@ -21,14 +21,15 @@ const { store } = useArrayData();
const card = computed(() => store.data); const card = computed(() => store.data);
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
data-key="Agency" data-key="Agency"
:url="`Agencies/${entityId}`" :url="`Agencies/${entityId}`"
:title="card?.name" :title="card?.name"
:subtitle="props.id" :subtitle="props.id"
:to-module="{ name: 'RouteAgency' }"
> >
<template #body="{ entity: agency }"> <template #body="{ entity: agency }">
<VnLv :label="t('globals.name')" :value="agency.name" /> <VnLv :label="t('globals.name')" :value="agency.name" />
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import useCardDescription from 'composables/useCardDescription'; import useCardDescription from 'composables/useCardDescription';
import VnLv from 'components/ui/VnLv.vue'; import VnLv from 'components/ui/VnLv.vue';
import { dashIfEmpty, toDate } from 'src/filters'; import { dashIfEmpty, toDate } from 'src/filters';
@ -41,13 +41,12 @@ const getZone = async () => {
zone.value = zoneData.name; zone.value = zoneData.name;
}; };
const data = ref(useCardDescription()); const data = ref(useCardDescription());
const setData = (entity) => (data.value = useCardDescription(entity.code, entity.id));
onMounted(async () => { onMounted(async () => {
getZone(); getZone();
}); });
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
:url="`Routes/${entityId}`" :url="`Routes/${entityId}`"
:filter="filter" :filter="filter"
:title="null" :title="null"
@ -69,7 +68,7 @@ onMounted(async () => {
<template #menu="{ entity }"> <template #menu="{ entity }">
<RouteDescriptorMenu :route="entity" /> <RouteDescriptorMenu :route="entity" />
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<i18n> <i18n>
es: es:

View File

@ -2,7 +2,7 @@
import { computed } from 'vue'; 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 CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import VnLv from 'components/ui/VnLv.vue'; import VnLv from 'components/ui/VnLv.vue';
import { dashIfEmpty, toDateHourMin } from 'src/filters'; import { dashIfEmpty, toDateHourMin } from 'src/filters';
import SupplierDescriptorProxy from 'pages/Supplier/Card/SupplierDescriptorProxy.vue'; import SupplierDescriptorProxy from 'pages/Supplier/Card/SupplierDescriptorProxy.vue';
@ -30,11 +30,12 @@ const entityId = computed(() => {
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
:url="`Roadmaps/${entityId}`" :url="`Roadmaps/${entityId}`"
:filter="filter" :filter="filter"
data-key="Roadmap" data-key="Roadmap"
:summary="summary" :summary="summary"
:to-module="{ name: 'RouteRoadmap' }"
> >
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('Roadmap')" :value="entity?.name" /> <VnLv :label="t('Roadmap')" :value="entity?.name" />
@ -51,7 +52,7 @@ const entityId = computed(() => {
<template #menu="{ entity }"> <template #menu="{ entity }">
<RoadmapDescriptorMenu :route="entity" /> <RoadmapDescriptorMenu :route="entity" />
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<i18n> <i18n>
es: es:

View File

@ -199,12 +199,22 @@ const confirmRemove = (ticket) => {
const openSmsDialog = async () => { const openSmsDialog = async () => {
const clientsId = []; const clientsId = [];
const clientsPhone = []; const clientsPhone = [];
const clientWithoutPhone = [];
for (let ticket of selectedRows.value) { for (let ticket of selectedRows.value) {
clientsId.push(ticket?.clientFk); clientsId.push(ticket?.clientFk);
const { data: client } = await axios.get(`Clients/${ticket?.clientFk}`); const { data: client } = await axios.get(`Clients/${ticket?.clientFk}`);
if (!client.phone) {
clientWithoutPhone.push(ticket?.clientFk);
continue;
}
clientsPhone.push(client.phone); clientsPhone.push(client.phone);
} }
if (clientWithoutPhone.length) {
quasar.notify({
type: 'warning',
message: t('components.VnNotes.clientWithoutPhone', { clientWithoutPhone }),
});
}
quasar.dialog({ quasar.dialog({
component: SendSmsDialog, component: SendSmsDialog,

View File

@ -2,7 +2,7 @@
import { computed } from 'vue'; import { computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import axios from 'axios'; import axios from 'axios';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
@ -20,10 +20,11 @@ const route = useRoute();
const entityId = computed(() => props.id || route.params.id); const entityId = computed(() => props.id || route.params.id);
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
:url="`Vehicles/${entityId}`" :url="`Vehicles/${entityId}`"
data-key="Vehicle" data-key="Vehicle"
title="numberPlate" title="numberPlate"
:to-module="{ name: 'RouteVehicle' }"
> >
<template #menu="{ entity }"> <template #menu="{ entity }">
<QItem <QItem
@ -53,7 +54,7 @@ const entityId = computed(() => props.id || route.params.id);
<VnLv :label="$t('globals.model')" :value="entity.model" /> <VnLv :label="$t('globals.model')" :value="entity.model" />
<VnLv :label="$t('globals.country')" :value="entity.countryCodeFk" /> <VnLv :label="$t('globals.country')" :value="entity.countryCodeFk" />
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<i18n> <i18n>
es: es:

View File

@ -2,7 +2,7 @@
import { computed } from 'vue'; 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 CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import VnLv from 'components/ui/VnLv.vue'; import VnLv from 'components/ui/VnLv.vue';
import ShelvingDescriptorMenu from 'pages/Shelving/Card/ShelvingDescriptorMenu.vue'; import ShelvingDescriptorMenu from 'pages/Shelving/Card/ShelvingDescriptorMenu.vue';
import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue';
@ -24,7 +24,7 @@ const entityId = computed(() => {
}); });
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
:url="`Shelvings/${entityId}`" :url="`Shelvings/${entityId}`"
:filter="filter" :filter="filter"
title="code" title="code"
@ -45,5 +45,5 @@ const entityId = computed(() => {
<template #menu="{ entity }"> <template #menu="{ entity }">
<ShelvingDescriptorMenu :shelving="entity" /> <ShelvingDescriptorMenu :shelving="entity" />
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { computed } from 'vue'; import { computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import VnLv from 'components/ui/VnLv.vue'; import VnLv from 'components/ui/VnLv.vue';
import filter from './ParkingFilter.js'; import filter from './ParkingFilter.js';
const props = defineProps({ const props = defineProps({
@ -16,17 +16,17 @@ const route = useRoute();
const entityId = computed(() => props.id || route.params.id); const entityId = computed(() => props.id || route.params.id);
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
data-key="Parking" data-key="Parking"
:url="`Parkings/${entityId}`" :url="`Parkings/${entityId}`"
title="code" title="code"
:filter="filter" :filter="filter"
:to-module="{ name: 'ParkingList' }" :to-module="{ name: 'ParkingMain' }"
> >
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="$t('globals.code')" :value="entity.code" /> <VnLv :label="$t('globals.code')" :value="entity.code" />
<VnLv :label="$t('parking.pickingOrder')" :value="entity.pickingOrder" /> <VnLv :label="$t('parking.pickingOrder')" :value="entity.pickingOrder" />
<VnLv :label="$t('parking.sector')" :value="entity.sector?.description" /> <VnLv :label="$t('parking.sector')" :value="entity.sector?.description" />
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>

View File

@ -3,7 +3,7 @@ import { ref, computed, onMounted } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import { toDateString } from 'src/filters'; import { toDateString } from 'src/filters';
@ -61,7 +61,7 @@ const getEntryQueryParams = (supplier) => {
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
:url="`Suppliers/${entityId}`" :url="`Suppliers/${entityId}`"
:filter="filter" :filter="filter"
data-key="Supplier" data-key="Supplier"
@ -136,7 +136,7 @@ const getEntryQueryParams = (supplier) => {
</QBtn> </QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<i18n> <i18n>

View File

@ -4,7 +4,7 @@ import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
import DepartmentDescriptorProxy from 'pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; import DepartmentDescriptorProxy from 'pages/Worker/Department/Card/DepartmentDescriptorProxy.vue';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import TicketDescriptorMenu from './TicketDescriptorMenu.vue'; import TicketDescriptorMenu from './TicketDescriptorMenu.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import { toDateTimeFormat } from 'src/filters/date'; import { toDateTimeFormat } from 'src/filters/date';
@ -57,7 +57,7 @@ function getInfo() {
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
:url="`Tickets/${entityId}`" :url="`Tickets/${entityId}`"
:filter="filter" :filter="filter"
data-key="Ticket" data-key="Ticket"
@ -155,7 +155,7 @@ function getInfo() {
</QBtn> </QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<i18n> <i18n>

View File

@ -3,7 +3,7 @@ import { computed, ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import useCardDescription from 'src/composables/useCardDescription'; import useCardDescription from 'src/composables/useCardDescription';
import TravelDescriptorMenuItems from './TravelDescriptorMenuItems.vue'; import TravelDescriptorMenuItems from './TravelDescriptorMenuItems.vue';
@ -31,7 +31,7 @@ const setData = (entity) => (data.value = useCardDescription(entity.ref, entity.
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
:url="`Travels/${entityId}`" :url="`Travels/${entityId}`"
:title="data.title" :title="data.title"
:subtitle="data.subtitle" :subtitle="data.subtitle"
@ -79,7 +79,7 @@ const setData = (entity) => (data.value = useCardDescription(entity.ref, entity.
</QBtn> </QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<i18n> <i18n>

View File

@ -2,7 +2,7 @@
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import CardDescriptor from 'src/components/ui/CardDescriptor.vue'; import EntityDescriptor from 'src/components/ui/EntityDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue';
import VnChangePassword from 'src/components/common/VnChangePassword.vue'; import VnChangePassword from 'src/components/common/VnChangePassword.vue';
@ -52,7 +52,7 @@ const handlePhotoUpdated = (evt = false) => {
}; };
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
ref="cardDescriptorRef" ref="cardDescriptorRef"
:data-key="dataKey" :data-key="dataKey"
:summary="$props.summary" :summary="$props.summary"
@ -167,7 +167,7 @@ const handlePhotoUpdated = (evt = false) => {
</QBtn> </QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </EntityDescriptor>
<VnChangePassword <VnChangePassword
ref="changePassRef" ref="changePassRef"
:submit-fn=" :submit-fn="
@ -190,9 +190,3 @@ const handlePhotoUpdated = (evt = false) => {
white-space: nowrap; white-space: nowrap;
} }
</style> </style>
<i18n>
es:
Click to allow the user to be disabled: Marcar para deshabilitar
Click to exclude the user from getting disabled: Marcar para no deshabilitar
</i18n>

View File

@ -63,3 +63,8 @@ const showChangePasswordDialog = () => {
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>
<i18n>
es:
Click to allow the user to be disabled: Marcar para deshabilitar
Click to exclude the user from getting disabled: Marcar para no deshabilitar
</i18n>

View File

@ -5,24 +5,25 @@ import { ref, computed } from 'vue';
import axios from 'axios'; import axios from 'axios';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import { useVnConfirm } from 'composables/useVnConfirm';
import { useArrayData } from 'src/composables/useArrayData';
import { downloadDocuware } from 'src/composables/downloadFile';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import FormModelPopup from 'src/components/FormModelPopup.vue'; import FormModelPopup from 'src/components/FormModelPopup.vue';
import { useVnConfirm } from 'composables/useVnConfirm';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnTable from 'src/components/VnTable/VnTable.vue';
const { t } = useI18n(); const { t } = useI18n();
const { notify } = useNotify(); const { notify } = useNotify();
const loadingDocuware = ref(true);
const paginate = ref(); const tableRef = ref();
const dialog = ref(); const dialog = ref();
const route = useRoute(); const route = useRoute();
const { openConfirmationModal } = useVnConfirm(); const { openConfirmationModal } = useVnConfirm();
const routeId = computed(() => route.params.id); const routeId = computed(() => route.params.id);
const worker = computed(() => useArrayData('Worker').store.data);
const initialData = computed(() => { const initialData = computed(() => {
return { return {
userFk: routeId.value, userFk: routeId.value,
@ -31,154 +32,268 @@ const initialData = computed(() => {
}; };
}); });
const deallocatePDA = async (deviceProductionFk) => { const columns = computed(() => [
await axios.post(`Workers/${route.params.id}/deallocatePDA`, { {
pda: deviceProductionFk, align: 'center',
}); label: t('globals.state'),
notify(t('PDA deallocated'), 'positive'); name: 'state',
format: (row) => row?.docuware?.state,
paginate.value.fetch(); columnFilter: false,
}; chip: {
condition: (_, row) => !!row.docuware,
color: (row) => (isSigned(row) ? 'bg-positive' : 'bg-warning'),
},
visible: false,
},
{
align: 'right',
label: t('worker.pda.currentPDA'),
name: 'deviceProductionFk',
columnClass: 'shrink',
cardVisible: true,
},
{
align: 'left',
label: t('Model'),
name: 'modelFk',
format: ({ deviceProduction }) => deviceProduction.modelFk,
cardVisible: true,
},
{
align: 'right',
label: t('Serial number'),
name: 'serialNumber',
format: ({ deviceProduction }) => deviceProduction.serialNumber,
cardVisible: true,
},
{
align: 'left',
label: t('Current SIM'),
name: 'simFk',
cardVisible: true,
},
{
align: 'right',
name: 'actions',
columnFilter: false,
cardVisible: true,
},
]);
function reloadData() { function reloadData() {
initialData.value.deviceProductionFk = null; initialData.value.deviceProductionFk = null;
initialData.value.simFk = null; initialData.value.simFk = null;
paginate.value.fetch(); tableRef.value.reload();
}
async function fetchDocuware() {
loadingDocuware.value = true;
const id = `${worker.value?.lastName} ${worker.value?.firstName}`;
const rows = tableRef.value.CrudModelRef.formData;
const promises = rows.map(async (row) => {
const { data } = await axios.post(`Docuwares/${id}/checkFile`, {
fileCabinet: 'hr',
signed: false,
mergeFilter: [
{
DBName: 'TIPO_DOCUMENTO',
Value: ['PDA'],
},
{
DBName: 'FILENAME',
Value: [`${row.deviceProductionFk}-pda`],
},
],
});
row.docuware = data;
});
await Promise.allSettled(promises);
loadingDocuware.value = false;
}
async function sendToTablet(rows) {
const promises = rows.map(async (row) => {
await axios.post(`Docuwares/upload-pda-pdf`, {
ids: [row.deviceProductionFk],
});
row.docuware = true;
});
await Promise.allSettled(promises);
notify(t('PDF sended to signed'), 'positive');
tableRef.value.reload();
}
async function deallocatePDA(deviceProductionFk) {
await axios.post(`Workers/${route.params.id}/deallocatePDA`, {
pda: deviceProductionFk,
});
const index = tableRef.value.CrudModelRef.formData.findIndex(
(data) => data?.deviceProductionFk == deviceProductionFk,
);
delete tableRef.value.CrudModelRef.formData[index];
notify(t('PDA deallocated'), 'positive');
}
function isSigned(row) {
return row.docuware?.state === 'Firmado';
} }
</script> </script>
<template> <template>
<QPage class="column items-center q-pa-md centerCard"> <FetchData
<FetchData url="workers/getAvailablePda"
url="workers/getAvailablePda" @on-fetch="(data) => (deviceProductions = data)"
@on-fetch="(data) => (deviceProductions = data)" auto-load
auto-load />
/> <VnTable
<VnPaginate ref="tableRef"
ref="paginate" data-key="WorkerPda"
data-key="WorkerPda" url="DeviceProductionUsers"
url="DeviceProductionUsers" :user-filter="{ order: 'id' }"
:user-filter="{ where: { userFk: routeId } }" :filter="{ where: { userFk: routeId } }"
order="id" search-url="pda"
search-url="pda" auto-load
auto-load :columns="columns"
> @onFetch="fetchDocuware"
<template #body="{ rows }"> :hasSubToolbar="true"
<QCard :default-remove="false"
flat :default-reset="false"
bordered :default-save="false"
:key="row.id" :table="{
v-for="row of rows" 'row-key': 'deviceProductionFk',
class="card q-px-md q-mb-sm container" selection: 'multiple',
> }"
<VnRow> :table-filter="{ hiddenTags: ['userFk'] }"
<VnInput >
:label="t('worker.pda.currentPDA')" <template #moreBeforeActions>
:model-value="row?.deviceProductionFk"
disable
/>
<VnInput
:label="t('Model')"
:model-value="row?.deviceProduction?.modelFk"
disable
/>
<VnInput
:label="t('Serial number')"
:model-value="row?.deviceProduction?.serialNumber"
disable
/>
<VnInput
:label="t('Current SIM')"
:model-value="row?.simFk"
disable
/>
<QBtn
flat
icon="delete"
color="primary"
class="btn-delete"
@click="
openConfirmationModal(
t(`Remove PDA`),
t('Do you want to remove this PDA?'),
() => deallocatePDA(row.deviceProductionFk),
)
"
>
<QTooltip>
{{ t('worker.pda.removePDA') }}
</QTooltip>
</QBtn>
</VnRow>
</QCard>
</template>
</VnPaginate>
<QPageSticky :offset="[18, 18]">
<QBtn <QBtn
@click.stop="dialog.show()" :label="t('globals.refresh')"
icon="refresh"
@click="tableRef.reload()"
/>
<QBtn
:disable="!tableRef?.selected?.length"
:label="t('globals.send')"
icon="install_mobile"
@click="sendToTablet(tableRef?.selected)"
class="bg-primary"
/>
</template>
<template #column-actions="{ row }">
<QBtn
flat
icon="delete"
color="primary" color="primary"
fab @click="
icon="add" openConfirmationModal(
v-shortcut="'+'" t(`Remove PDA`),
t('Do you want to remove this PDA?'),
() => deallocatePDA(row.deviceProductionFk),
)
"
data-cy="workerPda-remove"
> >
<QDialog ref="dialog"> <QTooltip>
<FormModelPopup {{ t('worker.pda.removePDA') }}
:title="t('Add new device')" </QTooltip>
url-create="DeviceProductionUsers"
model="DeviceProductionUser"
:form-initial-data="initialData"
@on-data-saved="reloadData()"
>
<template #form-inputs="{ data }">
<VnRow>
<VnSelect
:label="t('worker.pda.newPDA')"
v-model="data.deviceProductionFk"
:options="deviceProductions"
option-label="id"
option-value="id"
id="deviceProductionFk"
hide-selected
data-cy="pda-dialog-select"
:required="true"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel
>ID: {{ scope.opt?.id }}</QItemLabel
>
<QItemLabel caption>
{{ scope.opt?.modelFk }},
{{ scope.opt?.serialNumber }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
<VnInput
v-model="data.simFk"
:label="t('SIM serial number')"
id="simSerialNumber"
use-input
/>
</VnRow>
</template>
</FormModelPopup>
</QDialog>
</QBtn> </QBtn>
<QTooltip> <QBtn
{{ t('globals.new') }} v-if="!isSigned(row)"
</QTooltip> :loading="loadingDocuware"
</QPageSticky> icon="install_mobile"
</QPage> flat
color="primary"
@click="
openConfirmationModal(
t('Sign PDA'),
t('Are you sure you want to send it?'),
() => sendToTablet([row]),
)
"
data-cy="workerPda-send"
>
<QTooltip>
{{ t('worker.pda.sendToTablet') }}
</QTooltip>
</QBtn>
<QBtn
v-if="isSigned(row)"
:loading="loadingDocuware"
icon="cloud_download"
flat
color="primary"
@click="
downloadDocuware('Docuwares/download-pda-pdf', {
file: row.deviceProductionFk + '-pda',
worker: worker?.lastName + ' ' + worker?.firstName,
})
"
data-cy="workerPda-download"
>
<QTooltip>
{{ t('worker.pda.download') }}
</QTooltip>
</QBtn>
</template>
</VnTable>
<QPageSticky :offset="[18, 18]">
<QBtn @click.stop="dialog.show()" color="primary" fab icon="add" v-shortcut="'+'">
<QDialog ref="dialog">
<FormModelPopup
:title="t('Add new device')"
url-create="DeviceProductionUsers"
model="DeviceProductionUser"
:form-initial-data="initialData"
@on-data-saved="reloadData()"
>
<template #form-inputs="{ data }">
<VnRow>
<VnSelect
:label="t('PDA')"
v-model="data.deviceProductionFk"
:options="deviceProductions"
option-label="modelFk"
option-value="id"
id="deviceProductionFk"
hide-selected
data-cy="pda-dialog-select"
:required="true"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel
>ID: {{ scope.opt?.id }}</QItemLabel
>
<QItemLabel caption>
{{ scope.opt?.modelFk }},
{{ scope.opt?.serialNumber }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
<VnSelect
url="Sims"
option-label="line"
option-value="code"
v-model="data.simFk"
:label="t('SIM serial number')"
id="simSerialNumber"
/>
</VnRow>
</template>
</FormModelPopup>
</QDialog>
</QBtn>
<QTooltip>
{{ t('globals.new') }}
</QTooltip>
</QPageSticky>
</template> </template>
<style lang="scss" scoped>
.btn-delete {
max-width: 4%;
margin-top: 30px;
}
</style>
<i18n> <i18n>
es: es:
Model: Modelo Model: Modelo
@ -190,4 +305,6 @@ es:
Do you want to remove this PDA?: ¿Desea eliminar este PDA? Do you want to remove this PDA?: ¿Desea eliminar este PDA?
You can only have one PDA: Solo puedes tener un PDA si no eres autonomo You can only have one PDA: Solo puedes tener un PDA si no eres autonomo
This PDA is already assigned to another user: Este PDA ya está asignado a otro usuario This PDA is already assigned to another user: Este PDA ya está asignado a otro usuario
Are you sure you want to send it?: ¿Seguro que quieres enviarlo?
Sign PDA: Firmar PDA
</i18n> </i18n>

View File

@ -4,7 +4,7 @@ import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useVnConfirm } from 'composables/useVnConfirm'; import { useVnConfirm } from 'composables/useVnConfirm';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import CardDescriptor from 'src/components/ui/CardDescriptor.vue'; import EntityDescriptor from 'src/components/ui/EntityDescriptor.vue';
import axios from 'axios'; import axios from 'axios';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
@ -40,7 +40,7 @@ const removeDepartment = async () => {
const { openConfirmationModal } = useVnConfirm(); const { openConfirmationModal } = useVnConfirm();
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
ref="DepartmentDescriptorRef" ref="DepartmentDescriptorRef"
:url="`Departments/${entityId}`" :url="`Departments/${entityId}`"
:summary="$props.summary" :summary="$props.summary"
@ -95,7 +95,7 @@ const { openConfirmationModal } = useVnConfirm();
</QBtn> </QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<i18n> <i18n>

View File

@ -16,6 +16,7 @@ const $props = defineProps({
v-if="$props.id" v-if="$props.id"
:id="$props.id" :id="$props.id"
:summary="DepartmentSummary" :summary="DepartmentSummary"
data-key="DepartmentDescriptorProxy"
/> />
</QPopupProxy> </QPopupProxy>
</template> </template>

View File

@ -2,7 +2,7 @@
import { computed } from 'vue'; import { computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import { toTimeFormat } from 'src/filters/date'; import { toTimeFormat } from 'src/filters/date';
import { toCurrency } from 'filters/index'; import { toCurrency } from 'filters/index';
@ -25,7 +25,7 @@ const entityId = computed(() => {
</script> </script>
<template> <template>
<CardDescriptor :url="`Zones/${entityId}`" :filter="filter" data-key="Zone"> <EntityDescriptor :url="`Zones/${entityId}`" :filter="filter" data-key="Zone">
<template #menu="{ entity }"> <template #menu="{ entity }">
<ZoneDescriptorMenuItems :zone="entity" /> <ZoneDescriptorMenuItems :zone="entity" />
</template> </template>
@ -36,5 +36,5 @@ const entityId = computed(() => {
<VnLv :label="$t('list.price')" :value="toCurrency(entity.price)" /> <VnLv :label="$t('list.price')" :value="toCurrency(entity.price)" />
<VnLv :label="$t('zone.bonus')" :value="toCurrency(entity.bonus)" /> <VnLv :label="$t('zone.bonus')" :value="toCurrency(entity.bonus)" />
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>

View File

@ -271,12 +271,14 @@ export default {
path: 'department', path: 'department',
name: 'Department', name: 'Department',
redirect: { name: 'WorkerDepartment' }, redirect: { name: 'WorkerDepartment' },
component: () => import('src/pages/Worker/WorkerDepartment.vue'), meta: { title: 'department', icon: 'vn:greuge' },
children: [ children: [
{ {
component: () =>
import('src/pages/Worker/WorkerDepartment.vue'),
meta: { title: 'department', icon: 'vn:greuge' },
name: 'WorkerDepartment', name: 'WorkerDepartment',
path: 'list', path: 'list',
meta: { title: 'department', icon: 'vn:greuge' },
}, },
departmentCard, departmentCard,
], ],

View File

@ -25,7 +25,7 @@ describe('VnLog', () => {
it('should show claimDescriptor', () => { it('should show claimDescriptor', () => {
cy.dataCy('iconLaunch-claimFk').first().click(); cy.dataCy('iconLaunch-claimFk').first().click();
cy.dataCy('cardDescriptor_subtitle').contains('1'); cy.dataCy('vnDescriptor_subtitle').contains('1');
cy.dataCy('iconLaunch-claimFk').first().click(); cy.dataCy('iconLaunch-claimFk').first().click();
}); });
}); });

View File

@ -1,23 +1,80 @@
describe('WorkerPda', () => { describe('WorkerPda', () => {
const select = '[data-cy="pda-dialog-select"]'; const deviceId = 4;
beforeEach(() => { beforeEach(() => {
cy.viewport(1920, 1080);
cy.login('developer'); cy.login('developer');
cy.visit(`/#/worker/1110/pda`); cy.visit(`/#/worker/1110/pda`);
}); });
it('assign pda', () => { it('assign and delete pda', () => {
cy.addBtnClick(); creatNewPDA();
cy.get(select).click(); cy.checkNotification('Data created');
cy.get(select).type('{downArrow}{enter}'); cy.visit(`/#/worker/1110/pda`);
cy.get('.q-notification__message').should('have.text', 'Data created'); removeNewPDA();
cy.checkNotification('PDA deallocated');
}); });
it('delete pda', () => { it('send and download pdf to docuware', () => {
cy.get('.btn-delete').click(); //Send
cy.get( cy.intercept('POST', '/api/Docuwares/upload-pda-pdf', (req) => {
'.q-card__actions > .q-btn--unelevated > .q-btn__content > .block' req.reply({
).click(); statusCode: 200,
cy.get('.q-notification__message').should('have.text', 'PDA deallocated'); body: {},
});
});
creatNewPDA();
cy.dataCy('workerPda-send').click();
cy.clickConfirm();
cy.checkNotification('PDF sended to signed');
//Download
cy.intercept('POST', /\/api\/Docuwares\/Jones%20Jessica\/checkFile/, (req) => {
req.reply({
statusCode: 200,
body: {
id: deviceId,
state: 'Firmado',
},
});
});
cy.get('#st-actions').contains('refresh').click();
cy.intercept('GET', '/api/Docuwares/download-pda-pdf**', (req) => {
req.reply({
statusCode: 200,
body: {},
});
});
cy.dataCy('workerPda-download').click();
removeNewPDA();
}); });
it('send 2 pdfs to docuware', () => {
cy.intercept('POST', '/api/Docuwares/upload-pda-pdf', (req) => {
req.reply({
statusCode: 200,
body: {},
});
});
creatNewPDA();
creatNewPDA(2);
cy.selectRows([1, 2]);
cy.get('#st-actions').contains('Send').click();
cy.checkNotification('PDF sended to signed');
removeNewPDA();
});
function creatNewPDA(id = deviceId) {
cy.addBtnClick();
cy.selectOption('[data-cy="pda-dialog-select"]', id);
cy.saveCard();
}
function removeNewPDA() {
cy.dataCy('workerPda-remove').first().click();
cy.clickConfirm();
}
}); });

View File

@ -371,7 +371,7 @@ Cypress.Commands.add('validateContent', (selector, expectedValue) => {
}); });
Cypress.Commands.add('openActionsDescriptor', () => { Cypress.Commands.add('openActionsDescriptor', () => {
cy.get('[data-cy="cardDescriptor"] [data-cy="descriptor-more-opts"]').click(); cy.get('[data-cy="vnDescriptor"] [data-cy="descriptor-more-opts"]').click();
}); });
Cypress.Commands.add('openUserPanel', () => { Cypress.Commands.add('openUserPanel', () => {
@ -466,16 +466,16 @@ Cypress.Commands.add('validateDescriptor', (toCheck = {}) => {
const popupSelector = popup ? '[role="menu"] ' : ''; const popupSelector = popup ? '[role="menu"] ' : '';
if (title) cy.get(`${popupSelector}[data-cy="cardDescriptor_title"]`).contains(title); if (title) cy.get(`${popupSelector}[data-cy="vnDescriptor_title"]`).contains(title);
if (description) if (description)
cy.get(`${popupSelector}[data-cy="cardDescriptor_description"]`).contains( cy.get(`${popupSelector}[data-cy="vnDescriptor_description"]`).contains(
description, description,
); );
if (subtitle) if (subtitle)
cy.get(`${popupSelector}[data-cy="cardDescriptor_subtitle"]`).contains(subtitle); cy.get(`${popupSelector}[data-cy="vnDescriptor_subtitle"]`).contains(subtitle);
for (const index in listbox) for (const index in listbox)
cy.get(`${popupSelector}[data-cy="cardDescriptor_listbox"] > *`) cy.get(`${popupSelector}[data-cy="vnDescriptor_listbox"] > *`)
.eq(index) .eq(index)
.should('contain.text', listbox[index]); .should('contain.text', listbox[index]);
}); });