Items summary #281

Merged
alexm merged 8 commits from :feature/ItemsSummary into dev 2024-04-25 12:01:08 +00:00
40 changed files with 2747 additions and 2846 deletions
Showing only changes of commit 4d418c9cb7 - Show all commits

View File

@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2418.01]
## [2416.01] - 2024-04-18 ## [2416.01] - 2024-04-18
### Added ### Added

View File

@ -1,6 +1,6 @@
{ {
"name": "salix-front", "name": "salix-front",
"version": "24.16.0", "version": "24.18.0",
"description": "Salix frontend", "description": "Salix frontend",
"productName": "Salix", "productName": "Salix",
"author": "Verdnatura", "author": "Verdnatura",

View File

@ -93,13 +93,11 @@ module.exports = configure(function (/* ctx */) {
[ [
VueI18nPlugin({ VueI18nPlugin({
runtimeOnly: false, runtimeOnly: false,
include: [
path.resolve(__dirname, './src/i18n/locale/**'),
path.resolve(__dirname, './src/pages/**/locale/**'),
],
}), }),
{
// if you want to use Vue I18n Legacy API, you need to set `compositionOnly: false`
// compositionOnly: false,
// you need to set i18n resource including paths !
include: path.resolve(__dirname, './src/i18n/**'),
},
], ],
], ],
}, },

View File

@ -1,6 +1,7 @@
import { boot } from 'quasar/wrappers'; import { boot } from 'quasar/wrappers';
import { createI18n } from 'vue-i18n'; import { createI18n } from 'vue-i18n';
import messages from 'src/i18n'; import messages from 'src/i18n';
import { locales } from 'src/i18n/handle';
const i18n = createI18n({ const i18n = createI18n({
locale: navigator.language || navigator.userLanguage, locale: navigator.language || navigator.userLanguage,
@ -12,8 +13,9 @@ const i18n = createI18n({
legacy: false, legacy: false,
}); });
export default boot(({ app }) => { export default boot(async ({ app }) => {
// Set i18n instance on app // Set i18n instance on app
await locales();
app.use(i18n); app.use(i18n);
}); });

View File

@ -78,6 +78,7 @@ defineExpose({
<div class="q-mt-lg row justify-end"> <div class="q-mt-lg row justify-end">
<QBtn <QBtn
:label="t('globals.save')" :label="t('globals.save')"
:title="t('globals.save')"
type="submit" type="submit"
color="primary" color="primary"
:disabled="isLoading" :disabled="isLoading"
@ -85,6 +86,7 @@ defineExpose({
/> />
<QBtn <QBtn
:label="t('globals.cancel')" :label="t('globals.cancel')"
:title="t('globals.cancel')"
type="reset" type="reset"
color="primary" color="primary"
flat flat

View File

@ -198,11 +198,13 @@ function addDefaultData(data) {
en: en:
contentTypesInfo: Allowed file types {allowedContentTypes} contentTypesInfo: Allowed file types {allowedContentTypes}
EntryDmsDescription: Reference {reference} EntryDmsDescription: Reference {reference}
WorkersDescription: Working of employee id {reference}
SupplierDmsDescription: Reference {reference} SupplierDmsDescription: Reference {reference}
es: es:
Generate identifier for original file: Generar identificador para archivo original Generate identifier for original file: Generar identificador para archivo original
contentTypesInfo: Tipos de archivo permitidos {allowedContentTypes} contentTypesInfo: Tipos de archivo permitidos {allowedContentTypes}
EntryDmsDescription: Referencia {reference} EntryDmsDescription: Referencia {reference}
WorkersDescription: Laboral del empleado {reference}
SupplierDmsDescription: Referencia {reference} SupplierDmsDescription: Referencia {reference}
</i18n> </i18n>

View File

@ -5,9 +5,11 @@ import { useRoute } from 'vue-router';
import { useQuasar, QCheckbox, QBtn, QInput } from 'quasar'; import { useQuasar, QCheckbox, QBtn, QInput } from 'quasar';
import axios from 'axios'; import axios from 'axios';
import FetchData from 'components/FetchData.vue'; import VnPaginate from 'components/ui/VnPaginate.vue';
import VnDms from 'src/components/common/VnDms.vue'; import VnDms from 'src/components/common/VnDms.vue';
import VnConfirm from 'components/ui/VnConfirm.vue'; import VnConfirm from 'components/ui/VnConfirm.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnUserLink from '../ui/VnUserLink.vue';
import { downloadFile } from 'src/composables/downloadFile'; import { downloadFile } from 'src/composables/downloadFile';
const route = useRoute(); const route = useRoute();
@ -26,6 +28,15 @@ const $props = defineProps({
type: String, type: String,
default: null, default: null,
}, },
deleteModel: {
type: String,
default: null,
},
downloadModel: {
type: String,
required: false,
default: null,
},
defaultDmsCode: { defaultDmsCode: {
type: String, type: String,
required: true, required: true,
@ -74,7 +85,7 @@ const dmsFilter = {
], ],
}, },
}, },
order: ['dmsFk DESC'], where: { [$props.filter]: route.params.id },
}; };
const columns = computed(() => [ const columns = computed(() => [
@ -94,12 +105,12 @@ const columns = computed(() => [
props: (prop) => ({ props: (prop) => ({
readonly: true, readonly: true,
borderless: true, borderless: true,
'model-value': prop.row.dmsType.name, 'model-value': prop.row.dmsType?.name,
}), }),
}, },
{ {
align: 'left', align: 'left',
field: 'order', field: 'hardCopyNumber',
label: t('globals.order'), label: t('globals.order'),
name: 'order', name: 'order',
component: 'span', component: 'span',
@ -117,6 +128,7 @@ const columns = computed(() => [
label: t('globals.description'), label: t('globals.description'),
name: 'description', name: 'description',
component: 'span', component: 'span',
props: (prop) => ({ value: prop.value?.toUpperCase() }),
}, },
{ {
align: 'left', align: 'left',
@ -136,21 +148,53 @@ const columns = computed(() => [
name: 'file', name: 'file',
component: 'span', component: 'span',
}, },
{
align: 'left',
field: 'worker',
label: t('globals.worker'),
name: 'worker',
component: VnUserLink,
props: (prop) => ({
name: prop.row.worker?.user?.name.toLowerCase(),
workerId: prop.row.worker?.id,
}),
},
{
align: 'left',
field: 'created',
label: t('globals.created'),
name: 'created',
component: VnInputDate,
props: (prop) => ({
disable: true,
'model-value': prop.row.created,
}),
},
{ {
field: 'options', field: 'options',
name: 'options', name: 'options',
components: [ components: [
{ {
component: QBtn, component: QBtn,
name: 'download',
isDocuware: true,
props: () => ({ props: () => ({
icon: 'cloud_download', icon: 'cloud_download',
flat: true, flat: true,
color: 'primary', color: 'primary',
}), }),
click: (prop) => downloadFile(prop.row.id), click: (prop) =>
downloadFile(
prop.row.id,
$props.downloadModel,
null,
prop.row.download
),
}, },
{ {
component: QBtn, component: QBtn,
name: 'edit',
external: false,
props: () => ({ props: () => ({
icon: 'edit', icon: 'edit',
flat: true, flat: true,
@ -160,6 +204,8 @@ const columns = computed(() => [
}, },
{ {
component: QBtn, component: QBtn,
name: 'delete',
external: false,
props: () => ({ props: () => ({
icon: 'delete', icon: 'delete',
flat: true, flat: true,
@ -167,12 +213,24 @@ const columns = computed(() => [
}), }),
click: (prop) => deleteDms(prop.row.id), click: (prop) => deleteDms(prop.row.id),
}, },
{
component: QBtn,
name: 'open',
external: true,
props: () => ({
icon: 'open_in_new',
flat: true,
color: 'primary',
}),
click: (prop) => open(prop.row.url),
},
], ],
}, },
]); ]);
function setData(data) { function setData(data) {
const newData = data.map((value) => value.dms); const newData = data.map((value) => value.dms || value);
newData.sort((a, b) => new Date(b.created) - new Date(a.created));
rows.value = newData; rows.value = newData;
} }
@ -186,7 +244,7 @@ function deleteDms(dmsFk) {
}, },
}) })
.onOk(async () => { .onOk(async () => {
await axios.post(`${$props.model}/${dmsFk}/removeFile`); await axios.post(`${$props.deleteModel ?? $props.model}/${dmsFk}/removeFile`);
const index = rows.value.findIndex((row) => row.id == dmsFk); const index = rows.value.findIndex((row) => row.id == dmsFk);
rows.value.splice(index, 1); rows.value.splice(index, 1);
}); });
@ -206,84 +264,106 @@ function parseDms(data) {
} }
return data; return data;
} }
async function open(url) {
window.open(url).focus();
}
function shouldRenderButton(button, isExternal = false) {
if (button.name == 'download') return true;
return button.external === isExternal;
}
</script> </script>
<template> <template>
<FetchData <VnPaginate
ref="dmsRef" ref="dmsRef"
:data-key="$props.model"
:url="$props.model" :url="$props.model"
:filter="dmsFilter" :filter="dmsFilter"
:where="{ [$props.filter]: route.params.id }" :order="['dmsFk DESC']"
:auto-load="true"
@on-fetch="setData" @on-fetch="setData"
auto-load
/>
<QTable
:columns="columns"
:rows="rows"
class="full-width q-mt-md"
hide-bottom
row-key="clientFk"
:grid="$q.screen.lt.sm"
> >
<template #body-cell="props"> <template #body>
<QTd :props="props"> <QTable
<QTr :props="props"> :columns="columns"
<component :rows="rows"
v-if="props.col.component" class="full-width q-mt-md"
:is="props.col.component" hide-bottom
v-bind="props.col.props && props.col.props(props)" row-key="clientFk"
> :grid="$q.screen.lt.sm"
<span >
v-if="props.col.component == 'span'" <template #body-cell="props">
style="white-space: wrap" <QTd :props="props">
>{{ props.value }}</span <QTr :props="props">
> <component
</component> v-if="props.col.component"
</QTr> :is="props.col.component"
v-bind="props.col.props && props.col.props(props)"
<div class="flex justify-center" v-if="props.col.name == 'options'"> >
<div v-for="button of props.col.components" :key="button.id"> <span
<component v-if="props.col.component == 'span'"
:is="button.component" style="white-space: wrap"
v-bind="button.props(props)" >{{ props.value }}</span
@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 </component>
:is="button.component" </QTr>
v-bind="button.props(col)"
@click="button.click(col)" <div class="row no-wrap" v-if="props.col.name == 'options'">
/> <div v-for="button of props.col.components" :key="button.id">
</div> <component
v-if="
shouldRenderButton(button, props.row.isDocuware)
"
:is="button.component"
v-bind="button.props(props)"
@click="button.click(props)"
/>
</div> </div>
</QItem> </div>
</QList> </QTd>
</QCard> </template>
</div> <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
v-if="
shouldRenderButton(
button.name,
props.row.isDocuware
)
"
:is="button.component"
v-bind="button.props(col)"
@click="button.click(col)"
/>
</div>
</div>
</QItem>
</QList>
</QCard>
</div>
</template>
</QTable>
</template> </template>
</QTable> </VnPaginate>
<QDialog v-model="formDialog.show"> <QDialog v-model="formDialog.show">
<VnDms <VnDms
:model="updateModel ?? model" :model="updateModel ?? model"

View File

@ -77,6 +77,7 @@ const arrayData = useArrayData(props.dataKey, {
userParams: props.userParams, userParams: props.userParams,
exprBuilder: props.exprBuilder, exprBuilder: props.exprBuilder,
}); });
const hasMoreData = ref();
const store = arrayData.store; const store = arrayData.store;
onMounted(() => { onMounted(() => {
@ -109,11 +110,10 @@ async function paginate() {
isLoading.value = true; isLoading.value = true;
await arrayData.loadMore(); await arrayData.loadMore();
if (!arrayData.hasMoreData.value) { if (!arrayData.hasMoreData.value) {
if (store.userParamsChanged) arrayData.hasMoreData.value = true; if (store.userParamsChanged) arrayData.hasMoreData.value = true;
store.userParamsChanged = false; store.userParamsChanged = false;
isLoading.value = false; endPagination();
return; return;
} }
@ -123,12 +123,15 @@ async function paginate() {
pagination.value.sortBy = sortBy; pagination.value.sortBy = sortBy;
pagination.value.descending = descending; pagination.value.descending = descending;
isLoading.value = false; endPagination();
}
function endPagination() {
hasMoreData.value = arrayData.hasMoreData.value;
isLoading.value = false;
emit('onFetch', store.data); emit('onFetch', store.data);
emit('onPaginate'); emit('onPaginate');
} }
async function onLoad(index, done) { async function onLoad(index, done) {
if (!store.data) { if (!store.data) {
return done(); return done();
@ -193,6 +196,9 @@ defineExpose({ fetch, addFilter });
<QSpinner color="orange" size="md" /> <QSpinner color="orange" size="md" />
</div> </div>
</QInfiniteScroll> </QInfiniteScroll>
<div v-if="!isLoading && hasMoreData" class="w-full flex justify-center q-mt-md">
<QBtn color="primary" :label="t('Load more data')" @click="paginate()" />
</div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -209,4 +215,5 @@ defineExpose({ fetch, addFilter });
es: es:
No data to display: Sin datos que mostrar No data to display: Sin datos que mostrar
No results found: No se han encontrado resultados No results found: No se han encontrado resultados
Load more data: Cargar más resultados
</i18n> </i18n>

View File

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

View File

@ -76,7 +76,7 @@ const removeNode = (node) => {
notify(t('department.departmentRemoved'), 'positive'); notify(t('department.departmentRemoved'), 'positive');
await fetchNodeLeaves(parentFk); await fetchNodeLeaves(parentFk);
} catch (err) { } catch (err) {
console.log('Error removing department'); console.error('Error removing department');
} }
}); });
}; };

View File

@ -1,5 +1,6 @@
<script setup> <script setup>
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
const $props = defineProps({ const $props = defineProps({

View File

@ -1,11 +1,11 @@
import { useSession } from 'src/composables/useSession'; import { useSession } from 'src/composables/useSession';
import { getUrl } from './getUrl'; import { getUrl } from './getUrl';
const {getTokenMultimedia} = useSession(); const { getTokenMultimedia } = useSession();
const token = getTokenMultimedia(); const token = getTokenMultimedia();
export async function downloadFile(dmsId) { export async function downloadFile(id, model = 'dms', urlPath = '/downloadFile', url) {
let appUrl = await getUrl('', 'lilium'); let appUrl = await getUrl('', 'lilium');
appUrl = appUrl.replace('/#/', ''); appUrl = appUrl.replace('/#/', '');
window.open(`${appUrl}/api/dms/${dmsId}/downloadFile?access_token=${token}`); window.open(url ?? `${appUrl}/api/${model}/${id}${urlPath}?access_token=${token}`);
} }

View File

@ -97,7 +97,7 @@ export function useArrayData(key, userOptions) {
const { limit } = filter; const { limit } = filter;
hasMoreData.value = response.data.length === limit; hasMoreData.value = response.data.length >= limit;
if (append) { if (append) {
if (!store.data) store.data = []; if (!store.data) store.data = [];

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

17
src/i18n/handle.js Normal file
View File

@ -0,0 +1,17 @@
const modules = import.meta.glob(`../pages/**/locale/**.yml`);
import translations from './index';
const LOCALE_EXTENSION = '.yml';
export async function locales() {
for await (const module of Object.keys(modules)) {
const splittedFile = module.split('/');
const lang = splittedFile.pop().split(LOCALE_EXTENSION)[0];
const moduleFiles = splittedFile.join('/') + '/' + lang + LOCALE_EXTENSION;
import(moduleFiles).then((t) => {
Object.assign(translations[lang], t.default);
});
}
return translations;
}
export default translations;

View File

@ -1,9 +1,15 @@
import en from './en'; const files = import.meta.glob(`./locale/*.yml`);
import es from './es'; const translations = {};
export const localeEquivalence = {
'en':'en-GB' for (const file in files) {
const lang = file.split('/').at(2).split('.')[0];
import(file).then((t) => {
translations[lang] = t.default;
});
} }
export default {
en: en, export const localeEquivalence = {
es: es, en: 'en-GB',
}; };
export default translations;

1156
src/i18n/locale/en.yml Normal file

File diff suppressed because it is too large Load Diff

1153
src/i18n/locale/es.yml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@ import ClaimNotes from 'src/pages/Claim/Card/ClaimNotes.vue';
import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import VnTitle from 'src/components/common/VnTitle.vue'; import VnTitle from 'src/components/common/VnTitle.vue';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@ -177,7 +178,7 @@ function openDialog(dmsId) {
@on-fetch="getClaimDms" @on-fetch="getClaimDms"
> >
<template #header="{ entity: { claim } }"> <template #header="{ entity: { claim } }">
{{ claim.id }} - {{ claim.client.name }} {{ claim.id }} - {{ claim.client.name }} ({{ claim.client.id }})
</template> </template>
<template #body="{ entity: { claim, salesClaimed, developments } }"> <template #body="{ entity: { claim, salesClaimed, developments } }">
<QCard class="vn-one"> <QCard class="vn-one">
@ -214,10 +215,10 @@ function openDialog(dmsId) {
</VnLv> </VnLv>
<VnLv :label="t('claim.summary.customer')"> <VnLv :label="t('claim.summary.customer')">
<template #value> <template #value>
<VnUserLink <span class="link cursor-pointer">
:name="claim.client?.name" {{ claim.client?.name }}
:worker-id="claim.client?.id" <CustomerDescriptorProxy :id="claim.clientFk" />
/> </span>
</template> </template>
</VnLv> </VnLv>
<VnLv <VnLv

View File

@ -48,13 +48,17 @@ const zones = ref();
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">
<QItemSection> <QItemSection>
<VnInput :label="t('Name')" v-model="params.name" is-outlined /> <VnInput
:label="t('customerFilter.filter.name')"
v-model="params.name"
is-outlined
/>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">
<QItemSection> <QItemSection>
<VnInput <VnInput
:label="t('Social Name')" :label="t('customerFilter.filter.socialName')"
v-model="params.socialName" v-model="params.socialName"
is-outlined is-outlined
/> />
@ -170,8 +174,6 @@ en:
params: params:
search: Contains search: Contains
fi: FI fi: FI
name: Name
socialName: Social Name
salesPersonFk: Salesperson salesPersonFk: Salesperson
provinceFk: Province provinceFk: Province
city: City city: City
@ -183,8 +185,6 @@ es:
params: params:
search: Contiene search: Contiene
fi: NIF fi: NIF
name: Nombre
socialName: Razón Social
salesPersonFk: Comercial salesPersonFk: Comercial
provinceFk: Provincia provinceFk: Provincia
city: Ciudad city: Ciudad
@ -193,8 +193,6 @@ es:
zoneFk: Zona zoneFk: Zona
postcode: CP postcode: CP
FI: NIF FI: NIF
Name: Nombre
Social Name: Razón social
Salesperson: Comercial Salesperson: Comercial
Province: Provincia Province: Provincia
City: Ciudad City: Ciudad

View File

@ -0,0 +1,4 @@
customerFilter:
filter:
name: Name
socialName: Social name

View File

@ -0,0 +1,4 @@
customerFilter:
filter:
name: Nombre
socialName: Razón Social

View File

@ -63,7 +63,7 @@ const removeDepartment = () => {
router.push({ name: 'WorkerDepartment' }); router.push({ name: 'WorkerDepartment' });
notify('department.departmentRemoved', 'positive'); notify('department.departmentRemoved', 'positive');
} catch (err) { } catch (err) {
console.log('Error removing department'); console.error('Error removing department');
} }
}); });
}; };

View File

@ -1,19 +1,19 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue'; 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 FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import CrudModel from 'components/CrudModel.vue'; import CrudModel from 'components/CrudModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
const route = useRoute(); const { params } = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const entryObservationsRef = ref(null); const entryObservationsRef = ref(null);
const entryObservationsOptions = ref([]); const entryObservationsOptions = ref([]);
const selected = ref([]);
const sortEntryObservationOptions = (data) => { const sortEntryObservationOptions = (data) => {
entryObservationsOptions.value = [...data].sort((a, b) => entryObservationsOptions.value = [...data].sort((a, b) =>
@ -24,6 +24,29 @@ const sortEntryObservationOptions = (data) => {
onMounted(() => { onMounted(() => {
if (entryObservationsRef.value) entryObservationsRef.value.reload(); if (entryObservationsRef.value) entryObservationsRef.value.reload();
}); });
const columns = computed(() => [
{
name: 'observationType',
label: t('entry.notes.observationType'),
field: (row) => row.observationTypeFk,
sortable: true,
options: entryObservationsOptions.value,
required: true,
model: 'observationTypeFk',
optionValue: 'id',
optionLabel: 'description',
tabIndex: 1,
align: 'left',
},
{
name: 'description',
label: t('globals.description'),
field: (row) => row.description,
tabIndex: 2,
align: 'left',
},
]);
</script> </script>
<template> <template>
<FetchData <FetchData
@ -37,65 +60,90 @@ onMounted(() => {
model="EntryAccount" model="EntryAccount"
:filter="{ :filter="{
fields: ['id', 'entryFk', 'observationTypeFk', 'description'], fields: ['id', 'entryFk', 'observationTypeFk', 'description'],
where: { entryFk: route.params.id }, where: { entryFk: params.id },
}" }"
ref="entryObservationsRef" ref="entryObservationsRef"
:default-remove="false" :data-required="{ entryFk: params.id }"
:data-required="{ entryFk: route.params.id }" v-model:selected="selected"
> >
<template #body="{ rows, validate }"> <template #body="{ rows, validate }">
<QCard class="q-pa-md"> <QTable
<VnRow v-model:selected="selected"
v-for="(row, index) in rows" :columns="columns"
:key="index" :rows="rows"
class="row q-gutter-md q-mb-md" :pagination="{ rowsPerPage: 0 }"
> row-key="$index"
<VnSelectFilter selection="multiple"
:label="t('entry.notes.observationType')" hide-pagination
v-model="row.observationTypeFk" :grid="$q.screen.lt.md"
:options="entryObservationsOptions" table-header-class="text-left"
:disable="!!row.id" >
option-label="description" <template #body-cell-observationType="{ row, col }">
option-value="id" <QTd>
hide-selected <VnSelectFilter
/> v-model="row[col.model]"
:options="col.options"
<VnInput :option-value="col.optionValue"
:label="t('globals.description')" :option-label="col.optionLabel"
v-model="row.description" :autofocus="col.tabIndex == 1"
:rules="validate('EntryObservation.description')" input-debounce="0"
/> hide-selected
:required="true"
<div class="row justify-center items-center"> />
<QIcon </QTd>
name="delete" </template>
size="sm" <template #body-cell-description="{ row, col }">
class="cursor-pointer" <QTd>
color="primary" <VnInput
@click="entryObservationsRef.remove([row])" :label="t('globals.description')"
> v-model="row[col.name]"
<QTooltip> :rules="validate('EntryObservation.description')"
{{ t('Remove note') }} />
</QTooltip> </QTd>
</QIcon> </template>
<template #item="props">
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
<QCard bordered flat>
<QCardSection>
<QCheckbox v-model="props.selected" dense />
</QCardSection>
<QSeparator />
<QList dense>
<QItem>
<QItemSection>
<VnSelectFilter
v-model="props.row.observationTypeFk"
:options="entryObservationsOptions"
option-value="id"
option-label="description"
input-debounce="0"
hide-selected
:required="true"
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
:label="t('globals.description')"
v-model="props.row.description"
:rules="
validate('EntryObservation.description')
"
/>
</QItemSection>
</QItem>
</QList>
</QCard>
</div> </div>
</VnRow> </template>
<QIcon </QTable>
name="add"
size="sm"
class="cursor-pointer"
color="primary"
@click="entryObservationsRef.insert()"
>
<QTooltip>
{{ t('Add note') }}
</QTooltip>
</QIcon>
</QCard>
</template> </template>
</CrudModel> </CrudModel>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn fab color="primary" icon="add" @click="entryObservationsRef.insert()" />
</QPageSticky>
</template> </template>
<i18n> <i18n>
es: es:
Add note: Añadir nota Add note: Añadir nota

View File

@ -1,6 +1,8 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { onMounted } from 'vue';
import { useStateStore } from 'stores/useStateStore';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
@ -19,6 +21,11 @@ const props = defineProps({
const currenciesOptions = ref([]); const currenciesOptions = ref([]);
const companiesOptions = ref([]); const companiesOptions = ref([]);
const suppliersOptions = ref([]); const suppliersOptions = ref([]);
const stateStore = useStateStore();
onMounted(async () => {
stateStore.rightDrawer = true;
});
</script> </script>
<template> <template>
@ -58,7 +65,7 @@ const suppliersOptions = ref([]);
<QItemSection> <QItemSection>
<VnInput <VnInput
v-model="params.search" v-model="params.search"
:label="t('params.search')" :label="t('entryFilter.filter.search')"
is-outlined is-outlined
/> />
</QItemSection> </QItemSection>
@ -67,7 +74,7 @@ const suppliersOptions = ref([]);
<QItemSection> <QItemSection>
<VnInput <VnInput
v-model="params.reference" v-model="params.reference"
:label="t('params.reference')" :label="t('entryFilter.filter.reference')"
is-outlined is-outlined
/> />
</QItemSection> </QItemSection>
@ -210,8 +217,7 @@ const suppliersOptions = ref([]);
<i18n> <i18n>
en: en:
params: params:
search: General search
reference: Reference
invoiceNumber: Invoice number invoiceNumber: Invoice number
travelFk: Travel travelFk: Travel
companyFk: Company companyFk: Company
@ -225,8 +231,7 @@ en:
isOrdered: Ordered isOrdered: Ordered
es: es:
params: params:
search: Búsqueda general
reference: Referencia
invoiceNumber: Núm. factura invoiceNumber: Núm. factura
travelFk: Envío travelFk: Envío
companyFk: Empresa companyFk: Empresa

View File

@ -0,0 +1,8 @@
entryList:
list:
inventoryEntry: 'Inventory entry'
virtualEntry: 'Virtual entry'
entryFilter:
filter:
search: 'General search'
reference: 'Reference'

View File

@ -0,0 +1,8 @@
entryList:
list:
inventoryEntry: 'Es inventario'
virtualEntry: 'Es una redada'
entryFilter:
filter:
search: 'Búsqueda general'
reference: 'Referencia'

View File

@ -61,7 +61,7 @@ const data = ref(useCardDescription());
const setData = (entity) => { const setData = (entity) => {
if (!entity) return; if (!entity) return;
data.value = useCardDescription(entity.client.name, entity.id); data.value = useCardDescription(entity.client.name, entity.id);
state.set('ClaimDescriptor', entity); state.set('OrderDescriptor', entity);
}; };
const getConfirmationValue = (isConfirmed) => { const getConfirmationValue = (isConfirmed) => {

View File

@ -4,7 +4,6 @@ import { useI18n } from 'vue-i18n';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
@ -63,7 +62,7 @@ const decrement = (paramsObj, key) => {
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelectFilter
:label="t('params.agencyModeFk')" :label="t('travelFilter.filter.agencyModeFk')"
v-model="params.agencyModeFk" v-model="params.agencyModeFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"
:options="agenciesOptions" :options="agenciesOptions"
@ -79,7 +78,7 @@ const decrement = (paramsObj, key) => {
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelectFilter
:label="t('params.warehouseOutFk')" :label="t('travelFilter.filter.warehouseOutFk')"
v-model="params.warehouseOutFk" v-model="params.warehouseOutFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"
:options="warehousesOptions" :options="warehousesOptions"
@ -95,7 +94,7 @@ const decrement = (paramsObj, key) => {
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelectFilter
:label="t('params.warehouseInFk')" :label="t('travelFilter.filter.warehouseInFk')"
v-model="params.warehouseInFk" v-model="params.warehouseInFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"
:options="warehousesOptions" :options="warehousesOptions"
@ -113,7 +112,7 @@ const decrement = (paramsObj, key) => {
<QInput <QInput
v-model="params.scopeDays" v-model="params.scopeDays"
type="number" type="number"
:label="t('params.scopeDays')" :label="t('travelFilter.filter.scopeDays')"
dense dense
outlined outlined
rounded rounded
@ -211,10 +210,6 @@ const decrement = (paramsObj, key) => {
en: en:
params: params:
search: Id/Reference search: Id/Reference
agencyModeFk: Agency
warehouseInFk: Warehouse In
warehouseOutFk: Warehouse Out
scopeDays: Days onward
landedFrom: Landed from landedFrom: Landed from
landedTo: Landed to landedTo: Landed to
continent: Continent out continent: Continent out
@ -222,10 +217,6 @@ en:
es: es:
params: params:
search: Id/Referencia search: Id/Referencia
agencyModeFk: Agencia
warehouseInFk: Alm. entrada
warehouseOutFk: Alm. salida
scopeDays: Días adelante
landedFrom: Llegada desde landedFrom: Llegada desde
landedTo: Llegada hasta landedTo: Llegada hasta
continent: Cont. Salida continent: Cont. Salida

View File

@ -0,0 +1,6 @@
travelFilter:
filter:
warehouseOutFk: 'Warehouse Out'
warehouseInFk: 'Warehouse In'
agencyModeFk: 'Agency'
scopeDays: 'Days onward'

View File

@ -0,0 +1,6 @@
travelFilter:
filter:
warehouseInFk: 'Alm. entrada'
warehouseOutFk: 'Alm. salida'
agencyModeFk: 'Agencia'
scopeDays: 'Días adelante'

View File

@ -0,0 +1,15 @@
<script setup>
import VnDmsList from 'src/components/common/VnDmsList.vue';
import { useRoute } from 'vue-router';
const route = useRoute();
</script>
<template>
<VnDmsList
:model="`WorkerDms/${route.params.id}/filter`"
update-model="Workers"
delete-model="WorkerDms"
download-model="WorkerDms"
default-dms-code="hhrrData"
filter="worker"
/>
</template>

View File

@ -19,6 +19,7 @@ export default {
'WorkerNotificationsManager', 'WorkerNotificationsManager',
'WorkerPBX', 'WorkerPBX',
'WorkerLog', 'WorkerLog',
'WorkerDms',
], ],
departmentCard: ['BasicData'], departmentCard: ['BasicData'],
}, },
@ -127,6 +128,15 @@ export default {
}, },
component: () => import('src/pages/Worker/Card/WorkerPBX.vue'), component: () => import('src/pages/Worker/Card/WorkerPBX.vue'),
}, },
{
name: 'WorkerDms',
path: 'dms',
meta: {
title: 'dms',
icon: 'cloud_upload',
},
component: () => import('src/pages/Worker/Card/WorkerDms.vue'),
},
{ {
name: 'WorkerLog', name: 'WorkerLog',
path: 'log', path: 'log',

View File

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

View File

@ -37,6 +37,7 @@ describe('InvoiceInVat', () => {
it('should throw an error if there are fields undefined', () => { it('should throw an error if there are fields undefined', () => {
cy.get(inputBtns).eq(0).click(); cy.get(inputBtns).eq(0).click();
cy.get(':nth-child(1) > .q-td.q-table--col-auto-width > .q-field > .q-field__inner > .q-field__control > :nth-child(2) > .default-icon').click();
cy.get(dialogBtns).eq(2).click(); cy.get(dialogBtns).eq(2).click();
cy.get('.q-notification__message').should('have.text', "The code can't be empty"); cy.get('.q-notification__message').should('have.text', "The code can't be empty");
}); });
@ -44,7 +45,7 @@ describe('InvoiceInVat', () => {
it('should correctly handle expense addition', () => { it('should correctly handle expense addition', () => {
cy.get(inputBtns).eq(0).click(); cy.get(inputBtns).eq(0).click();
cy.get(dialogInputs).eq(0).click(); cy.get(':nth-child(1) > .q-td.q-table--col-auto-width > .q-field > .q-field__inner > .q-field__control > :nth-child(2) > .default-icon').click();
cy.get(dialogInputs).eq(0).type(randomInt); cy.get(dialogInputs).eq(0).type(randomInt);
cy.get(dialogInputs).eq(1).click(); cy.get(dialogInputs).eq(1).click();
cy.get(dialogInputs).eq(1).type('This is a dummy expense'); cy.get(dialogInputs).eq(1).type('This is a dummy expense');

View File

@ -8,9 +8,9 @@ describe('WorkerList', () => {
}); });
it('should load workers', () => { it('should load workers', () => {
cy.get(workerFieldNames).eq(0).should('have.text', 'JessicaJones'); cy.get(workerFieldNames).eq(0).should('have.text', 'jessicajones');
cy.get(workerFieldNames).eq(1).should('have.text', 'BruceBanner'); cy.get(workerFieldNames).eq(1).should('have.text', 'brucebanner');
cy.get(workerFieldNames).eq(2).should('have.text', 'CharlesXavier'); cy.get(workerFieldNames).eq(2).should('have.text', 'charlesxavier');
}); });
it('should open the worker summary', () => { it('should open the worker summary', () => {

View File

@ -60,7 +60,6 @@ describe('WorkerNotificationsManager', () => {
it('should active a notification if you are their boss', () => { it('should active a notification if you are their boss', () => {
cy.login('salesBoss'); cy.login('salesBoss');
cy.visit(`/#/worker/${salesPersonId}/notifications`); cy.visit(`/#/worker/${salesPersonId}/notifications`);
cy.waitForElement(activeList);
cy.waitForElement(availableList); cy.waitForElement(availableList);
cy.get(activeList) cy.get(activeList)

View File

@ -6,7 +6,8 @@ describe('WorkerSummary', () => {
}); });
it('should load worker summary', () => { it('should load worker summary', () => {
cy.get('.summaryHeader > div').should('have.text', '19 - salesBoss salesBoss'); cy.waitForElement('.summaryHeader');
cy.get('.summaryHeader > div').should('have.text', '19 - salesboss salesboss');
cy.get(':nth-child(1) > :nth-child(2) > .value > span').should( cy.get(':nth-child(1) > :nth-child(2) > .value > span').should(
'have.text', 'have.text',
'salesBossNick' 'salesBossNick'

View File

@ -19,7 +19,7 @@ export default defineConfig({
plugins: [ plugins: [
vue({ vue({
template: { template: {
transformAssetUrls transformAssetUrls,
}, },
}), }),
quasar({ quasar({