refs #6062 fix: migration errors
gitea/salix-front/pipeline/head There was a failure building this commit Details

This commit is contained in:
Alex Moreno 2023-12-13 14:19:42 +01:00
parent 055f10e6ff
commit 7557096af6
79 changed files with 5656 additions and 751 deletions

12
package-lock.json generated
View File

@ -1,14 +1,14 @@
{ {
"name": "salix-front", "name": "salix-front",
"version": "23.36.01", "version": "23.52.01",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "salix-front", "name": "salix-front",
"version": "0.0.1", "version": "23.52.01",
"dependencies": { "dependencies": {
"@quasar/cli": "^2.2.1", "@quasar/cli": "^2.3.0",
"@quasar/extras": "^1.16.4", "@quasar/extras": "^1.16.4",
"axios": "^1.4.0", "axios": "^1.4.0",
"chromium": "^3.0.3", "chromium": "^3.0.3",
@ -946,9 +946,9 @@
} }
}, },
"node_modules/@quasar/cli": { "node_modules/@quasar/cli": {
"version": "2.2.1", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/@quasar/cli/-/cli-2.2.1.tgz", "resolved": "https://registry.npmjs.org/@quasar/cli/-/cli-2.3.0.tgz",
"integrity": "sha512-PMwJ76IeeNRRBw+08hUMjhqGC6JKJ/t1zIb+IOiyR5D4rkBR26Ha/Z46OD3KfwUprq4Q8s4ieB1+d3VY8FhPKg==", "integrity": "sha512-DNFDemicj3jXe5+Ib+5w9Bwj1U3yoHQkqn0bU/qysIl/p0MmGA1yqOfUF0V4fw/5or1dfCvStIA/oZxUcC+2pQ==",
"dependencies": { "dependencies": {
"@quasar/ssl-certificate": "^1.0.0", "@quasar/ssl-certificate": "^1.0.0",
"ci-info": "^3.8.0", "ci-info": "^3.8.0",

View File

@ -15,4 +15,14 @@ export default boot(() => {
Date.vnNow = () => { Date.vnNow = () => {
return new Date(Date.vnUTC()).getTime(); return new Date(Date.vnUTC()).getTime();
}; };
Date.vnFirstDayOfMonth = () => {
const date = new Date(Date.vnUTC());
return new Date(date.getFullYear(), date.getMonth(), 1);
};
Date.vnLastDayOfMonth = () => {
const date = new Date(Date.vnUTC());
return new Date(date.getFullYear(), date.getMonth() + 1, 0);
};
}); });

View File

@ -6,6 +6,7 @@ import { useQuasar } from 'quasar';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useValidator } from 'src/composables/useValidator'; import { useValidator } from 'src/composables/useValidator';
import useNotify from 'src/composables/useNotify.js';
import SkeletonForm from 'components/ui/SkeletonForm.vue'; import SkeletonForm from 'components/ui/SkeletonForm.vue';
const quasar = useQuasar(); const quasar = useQuasar();
@ -13,6 +14,7 @@ const state = useState();
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const { validate } = useValidator(); const { validate } = useValidator();
const { notify } = useNotify();
const $props = defineProps({ const $props = defineProps({
url: { url: {
@ -31,10 +33,28 @@ const $props = defineProps({
type: String, type: String,
default: null, default: null,
}, },
urlCreate: {
type: String,
default: null,
},
defaultActions: { defaultActions: {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
autoLoad: {
type: Boolean,
default: false,
},
formInitialData: {
type: Object,
default: () => {},
},
observeFormChanges: {
type: Boolean,
default: true,
description:
'Esto se usa principalmente para permitir guardar sin hacer cambios (Útil para la feature de clonar ya que en este caso queremos poder guardar de primeras)',
},
}); });
const emit = defineEmits(['onFetch']); const emit = defineEmits(['onFetch']);
@ -43,44 +63,73 @@ defineExpose({
save, save,
}); });
onMounted(async () => await fetch()); onMounted(async () => {
// Podemos enviarle al form la estructura de data inicial sin necesidad de fetchearla
if ($props.formInitialData && !$props.autoLoad) {
state.set($props.model, $props.formInitialData);
} else {
await fetch();
}
// Disparamos el watcher del form después de que se haya cargado la data inicial, si así se desea
if ($props.observeFormChanges) {
startFormWatcher();
}
});
onUnmounted(() => { onUnmounted(() => {
state.unset($props.model); state.unset($props.model);
}); });
const isLoading = ref(false); const isLoading = ref(false);
const hasChanges = ref(false); // Si elegimos observar los cambios del form significa que inicialmente las actions estaran deshabilitadas
const originalData = ref(); const hasChanges = ref(!$props.observeFormChanges);
const originalData = ref({...$props.formInitialData});
const formData = computed(() => state.get($props.model)); const formData = computed(() => state.get($props.model));
const formUrl = computed(() => $props.url); const formUrl = computed(() => $props.url);
const startFormWatcher = () => {
watch(
() => formData.value,
(val) => {
if (val) hasChanges.value = true;
},
{ deep: true }
);
};
function tMobile(...args) { function tMobile(...args) {
if (!quasar.platform.is.mobile) return t(...args); if (!quasar.platform.is.mobile) return t(...args);
} }
async function fetch() { async function fetch() {
const { data } = await axios.get($props.url, { const { data } = await axios.get($props.url, {
params: { filter: $props.filter }, params: { filter: JSON.stringify($props.filter) },
}); });
state.set($props.model, data); state.set($props.model, data);
originalData.value = data && JSON.parse(JSON.stringify(data)); originalData.value = data && JSON.parse(JSON.stringify(data));
watch(formData.value, () => (hasChanges.value = true));
emit('onFetch', state.get($props.model)); emit('onFetch', state.get($props.model));
} }
async function save() { async function save() {
if (!hasChanges.value) { if (!hasChanges.value) {
return quasar.notify({ notify('globals.noChanges', 'negative');
type: 'negative', return;
message: t('globals.noChanges'),
});
} }
isLoading.value = true; isLoading.value = true;
await axios.patch($props.urlUpdate || $props.url, formData.value);
try {
if ($props.urlCreate) {
await axios.post($props.urlCreate, formData.value);
notify('globals.dataCreated', 'positive');
} else {
await axios.patch($props.urlUpdate || $props.url, formData.value);
}
} catch (err) {
notify('errors.create', 'negative');
}
originalData.value = JSON.parse(JSON.stringify(formData.value)); originalData.value = JSON.parse(JSON.stringify(formData.value));
hasChanges.value = false; hasChanges.value = false;
@ -91,11 +140,12 @@ function reset() {
state.set($props.model, originalData.value); state.set($props.model, originalData.value);
originalData.value = JSON.parse(JSON.stringify(originalData.value)); originalData.value = JSON.parse(JSON.stringify(originalData.value));
watch(formData.value, () => (hasChanges.value = true));
emit('onFetch', state.get($props.model)); emit('onFetch', state.get($props.model));
hasChanges.value = false; if ($props.observeFormChanges) {
hasChanges.value = false;
}
} }
// eslint-disable-next-line vue/no-dupe-keys // eslint-disable-next-line vue/no-dupe-keys
function filter(value, update, filterOptions) { function filter(value, update, filterOptions) {
update( update(
@ -118,7 +168,7 @@ watch(formUrl, async () => {
}); });
</script> </script>
<template> <template>
<QBanner v-if="hasChanges" class="text-white bg-warning"> <QBanner v-if="$props.observeFormChanges && hasChanges" class="text-white bg-warning">
<QIcon name="warning" size="md" class="q-mr-md" /> <QIcon name="warning" size="md" class="q-mr-md" />
<span>{{ t('globals.changesToSave') }}</span> <span>{{ t('globals.changesToSave') }}</span>
</QBanner> </QBanner>
@ -176,6 +226,7 @@ watch(formUrl, async () => {
max-width: 800px; max-width: 800px;
width: 100%; width: 100%;
} }
.q-card { .q-card {
padding: 32px; padding: 32px;
} }

View File

@ -0,0 +1,76 @@
<script setup>
import { computed, ref } from 'vue';
import { toDate } from 'src/filters';
const props = defineProps({
modelValue: {
type: String,
default: null,
},
readonly: {
type: Boolean,
default: false,
}
});
const emit = defineEmits(['update:modelValue']);
const value = computed({
get() {
return props.modelValue;
},
set(value) {
emit('update:modelValue', value ? new Date(value).toISOString() : null);
},
});
const isPopupOpen = ref(false);
const onDateUpdate = (date) => {
value.value = date;
isPopupOpen.value = false;
};
const padDate = (value) => value.toString().padStart(2, '0');
const formatDate = (dateString) => {
const date = new Date(dateString || '');
return `${date.getFullYear()}/${padDate(date.getMonth() + 1)}/${padDate(
date.getDate()
)}`;
};
</script>
<template>
<QInput
class="vn-input-date"
rounded
readonly
:model-value="toDate(value)"
v-bind="$attrs"
>
<template #append>
<QIcon name="event" class="cursor-pointer">
<QPopupProxy
v-model="isPopupOpen"
cover
transition-show="scale"
transition-hide="scale"
:no-parent-event="props.readonly"
>
<QDate
:model-value="formatDate(value)"
@update:model-value="onDateUpdate"
/>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</template>
<style lang="scss">
.vn-input-date.q-field--standard.q-field--readonly .q-field__control:before {
border-bottom-style: solid;
}
.vn-input-date.q-field--outlined.q-field--readonly .q-field__control:before {
border-style: solid;
}
</style>

View File

@ -25,6 +25,14 @@ const props = defineProps({
type: String, type: String,
default: null, default: null,
}, },
url: {
type: String,
default: null,
},
mapper: {
type: Function,
default: null,
},
}); });
const filter = { const filter = {
@ -155,7 +163,7 @@ function parseProps(propNames, locale, vals, olds) {
return props; return props;
} }
function getLogs(data) { function getLogTree(data) {
const logs = []; const logs = [];
let originLog = null; let originLog = null;
let userLog = null; let userLog = null;
@ -226,10 +234,8 @@ async function openPointRecord(id, modelLog) {
} }
async function setLogTree() { async function setLogTree() {
filter.where = { and: [{ originFk: route.params.id }] }; filter.where = { and: [{ originFk: route.params.id }] };
const { data } = await axios.get(`${props.model}Logs`, { const { data } = await getLogs(filter);
params: { filter: JSON.stringify(filter) }, logTree.value = getLogTree(data);
});
logTree.value = getLogs(data);
} }
function filterByRecord(modelLog) { function filterByRecord(modelLog) {
@ -257,11 +263,15 @@ async function applyFilter() {
filter.where.and.push(selectedFilters.value); filter.where.and.push(selectedFilters.value);
} }
const { data } = await axios.get(`${props.model}Logs`, { const { data } = await getLogs(filter);
logTree.value = getLogTree(data);
}
async function getLogs(filter) {
return axios.get(props.url ?? `${props.model}Logs`, {
params: { filter: JSON.stringify(filter) }, params: { filter: JSON.stringify(filter) },
}); });
logTree.value = getLogs(data);
} }
function setDate(type) { function setDate(type) {
@ -467,7 +477,7 @@ setLogTree();
</QItemSection> </QItemSection>
<QItemSection> <QItemSection>
<QCard <QCard
class="changes-log q-py-none" class="changes-log q-py-none q-mb-xs"
v-for="(log, logIndex) in modelLog.logs" v-for="(log, logIndex) in modelLog.logs"
:key="logIndex" :key="logIndex"
> >

View File

@ -98,7 +98,12 @@ const value = computed({
ref="vnSelectRef" ref="vnSelectRef"
> >
<template v-if="isClearable" #append> <template v-if="isClearable" #append>
<QIcon name="close" @click.stop="value = null" class="cursor-pointer" /> <QIcon
name="close"
@click.stop="value = null"
class="cursor-pointer"
size="18px"
/>
</template> </template>
<template v-for="(_, slotName) in $slots" #[slotName]="slotData"> <template v-for="(_, slotName) in $slots" #[slotName]="slotData">
<slot :name="slotName" v-bind="slotData ?? {}" /> <slot :name="slotName" v-bind="slotData ?? {}" />

View File

@ -63,6 +63,7 @@ async function getData() {
skip: 0, skip: 0,
}); });
const { data } = await arrayData.fetch({ append: false }); const { data } = await arrayData.fetch({ append: false });
entity.value = data;
emit('onFetch', data); emit('onFetch', data);
} }
const emit = defineEmits(['onFetch']); const emit = defineEmits(['onFetch']);
@ -98,13 +99,13 @@ function viewSummary(id) {
</QBtn> </QBtn>
<RouterLink :to="{ name: `${module}Summary`, params: { id: entity.id } }"> <RouterLink :to="{ name: `${module}Summary`, params: { id: entity.id } }">
<QBtn <QBtn
round
flat
dense
size="md"
icon="launch"
color="white"
class="link" class="link"
color="white"
dense
flat
icon="launch"
round
size="md"
> >
<QTooltip> <QTooltip>
{{ t('components.cardDescriptor.summary') }} {{ t('components.cardDescriptor.summary') }}
@ -112,13 +113,13 @@ function viewSummary(id) {
</QBtn> </QBtn>
</RouterLink> </RouterLink>
<QBtn <QBtn
v-if="slots.menu"
size="md"
icon="more_vert"
color="white" color="white"
round
flat
dense dense
flat
icon="more_vert"
round
size="md"
v-if="slots.menu"
> >
<QTooltip> <QTooltip>
{{ t('components.cardDescriptor.moreOptions') }} {{ t('components.cardDescriptor.moreOptions') }}

View File

@ -1,31 +1,69 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const $props = defineProps({ const $props = defineProps({
element: { type: Object, default: null },
id: { type: Number, default: null }, id: { type: Number, default: null },
isSelected: { type: Boolean, default: false },
title: { type: String, default: null }, title: { type: String, default: null },
showCheckbox: { type: Boolean, default: false },
}); });
const emit = defineEmits(['toggleCardCheck']);
const toggleCardCheck = (item) => {
emit('toggleCardCheck', item);
};
</script> </script>
<template> <template>
<QCard class="card q-mb-md cursor-pointer q-hoverable bg-white-7 q-pa-lg"> <QCard class="card q-mb-md cursor-pointer q-hoverable bg-white-7 q-pa-lg">
<div> <div>
<slot name="title"> <slot name="title">
<div class="title text-primary text-weight-bold text-h5"> <div class="flex justify-between">
{{ $props.title ?? `#${$props.id}` }} <div class="flex items-center">
<div class="title text-primary text-weight-bold text-h5">
{{ $props.title }}
</div>
<QChip class="q-chip-color" outline size="sm">
{{ t('ID') }}: {{ $props.id }}
</QChip>
</div>
<QCheckbox
v-if="showCheckbox"
:model-value="isSelected"
@click="toggleCardCheck($props.element)"
/>
</div> </div>
</slot> </slot>
<div class="card-list-body row"> <div class="card-list-body">
<div class="list-items row flex-wrap-wrap q-mt-md"> <div class="list-items row flex-wrap-wrap">
<slot name="list-items" /> <slot name="list-items" />
</div> </div>
<div class="actions column justify-center"> <div class="actions">
<slot name="actions" /> <slot name="actions" />
</div> </div>
</div> </div>
</div> </div>
</QCard> </QCard>
</template> </template>
<style lang="scss"> <style lang="scss">
.title {
margin-right: 25px;
}
.q-chip-color {
color: var(--vn-label);
}
.card-list-body { .card-list-body {
display: flex;
justify-content: space-between;
margin-top: 10px;
.vn-label-value { .vn-label-value {
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
@ -39,28 +77,33 @@ const $props = defineProps({
white-space: nowrap; white-space: nowrap;
} }
.value { .value {
width: 60%; width: 65%;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
} }
.actions { .actions {
.q-btn { display: flex;
width: 30px; flex-direction: column;
} justify-content: center;
.q-icon { width: 25%;
color: $primary;
font-size: 25px;
}
} }
} }
@media (max-width: $breakpoint-xs) { @media (max-width: $breakpoint-xs) {
.card-list-body { .card-list-body {
flex-wrap: wrap;
justify-content: center;
.vn-label-value { .vn-label-value {
width: 100%; width: 100%;
} }
.actions {
width: 100%;
margin-top: 15px;
padding: 0 15%;
justify-content: center;
}
} }
} }
</style> </style>
@ -73,11 +116,11 @@ const $props = defineProps({
background-color: var(--vn-gray); background-color: var(--vn-gray);
} }
.list-items { .list-items {
width: 90%; width: 75%;
}
@media (max-width: $breakpoint-xs) {
.list-items {
width: 85%;
}
} }
</style> </style>
<i18n>
es:
ID: ID
</i18n>

View File

@ -27,7 +27,7 @@ defineExpose({
async function fetch() { async function fetch() {
const params = {}; const params = {};
if (props.filter) params.filter = props.filter; if (props.filter) params.filter = JSON.stringify(props.filter);
const { data } = await axios.get(props.url, { params }); const { data } = await axios.get(props.url, { params });
entity.value = data; entity.value = data;
@ -73,6 +73,7 @@ watch(props, async () => {
.cardSummary { .cardSummary {
width: 100%; width: 100%;
.summaryHeader { .summaryHeader {
text-align: center; text-align: center;
font-size: 20px; font-size: 20px;
@ -86,6 +87,7 @@ watch(props, async () => {
justify-content: space-evenly; justify-content: space-evenly;
gap: 15px; gap: 15px;
padding: 15px; padding: 15px;
background-color: var(--vn-gray);
> .q-card.vn-one { > .q-card.vn-one {
width: 350px; width: 350px;

View File

@ -26,7 +26,7 @@ const props = defineProps({
}, },
}); });
const emit = defineEmits(['refresh', 'clear']); const emit = defineEmits(['refresh', 'clear', 'search']);
const arrayData = useArrayData(props.dataKey); const arrayData = useArrayData(props.dataKey);
const store = arrayData.store; const store = arrayData.store;
@ -49,6 +49,7 @@ async function search() {
if (!props.showAll && !Object.values(params).length) store.data = []; if (!props.showAll && !Object.values(params).length) store.data = [];
isLoading.value = false; isLoading.value = false;
emit('search');
} }
async function reload() { async function reload() {
@ -102,6 +103,7 @@ function formatValue(value) {
return `"${value}"`; return `"${value}"`;
} }
</script> </script>
<template> <template>
<QForm @submit="search"> <QForm @submit="search">
<QList dense> <QList dense>
@ -115,32 +117,32 @@ function formatValue(value) {
<div class="q-gutter-xs"> <div class="q-gutter-xs">
<QBtn <QBtn
@click="clearFilters" @click="clearFilters"
icon="filter_list_off"
color="primary" color="primary"
size="sm" dense
flat
icon="filter_list_off"
padding="none" padding="none"
round round
flat size="sm"
dense
> >
<QTooltip>{{ t('Remove filters') }}</QTooltip> <QTooltip>{{ t('Remove filters') }}</QTooltip>
</QBtn> </QBtn>
<QBtn <QBtn
@click="reload" @click="reload"
icon="refresh"
color="primary" color="primary"
size="sm" dense
flat
icon="refresh"
padding="none" padding="none"
round round
flat size="sm"
dense
> >
<QTooltip>{{ t('Refresh') }}</QTooltip> <QTooltip>{{ t('Refresh') }}</QTooltip>
</QBtn> </QBtn>
</div> </div>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem class="q-mb-sm">
<div <div
v-if="tags.length === 0" v-if="tags.length === 0"
class="text-grey font-xs text-center full-width" class="text-grey font-xs text-center full-width"
@ -149,14 +151,14 @@ function formatValue(value) {
</div> </div>
<div> <div>
<QChip <QChip
v-for="chip of tags"
:key="chip.label" :key="chip.label"
@remove="remove(chip.label)" @remove="remove(chip.label)"
icon="label"
color="primary"
class="text-dark" class="text-dark"
size="sm" color="primary"
icon="label"
removable removable
size="sm"
v-for="chip of tags"
> >
<slot name="tags" :tag="chip" :format-fn="formatValue"> <slot name="tags" :tag="chip" :format-fn="formatValue">
<div class="q-gutter-x-xs"> <div class="q-gutter-x-xs">
@ -168,29 +170,29 @@ function formatValue(value) {
</div> </div>
</QItem> </QItem>
<QSeparator /> <QSeparator />
<template v-if="props.searchButton">
<QItem>
<QItemSection class="q-py-sm">
<QBtn
:label="t('Search')"
type="submit"
color="primary"
class="full-width"
icon="search"
unelevated
rounded
dense
/>
</QItemSection>
</QItem>
<QSeparator />
</template>
</QList> </QList>
<slot name="body" :params="userParams" :search-fn="search"></slot> <slot name="body" :params="userParams" :search-fn="search"></slot>
<template v-if="props.searchButton">
<QItem>
<QItemSection class="q-py-sm">
<QBtn
:label="t('Search')"
class="full-width"
color="primary"
dense
icon="search"
rounded
type="submit"
unelevated
/>
</QItemSection>
</QItem>
<QSeparator />
</template>
</QForm> </QForm>
<QInnerLoading <QInnerLoading
:showing="isLoading"
:label="t('globals.pleaseWait')" :label="t('globals.pleaseWait')"
:showing="isLoading"
color="primary" color="primary"
/> />
</template> </template>

View File

@ -149,13 +149,13 @@ export function useArrayData(key, userOptions) {
return { filter, params }; return { filter, params };
} }
function sanitizerParams(params) { function sanitizerParams(params, exprBuilder) {
for (const param in params) { for (const param in params) {
if (params[param] === '' || params[param] === null) { if (params[param] === '' || params[param] === null) {
delete store.userParams[param]; delete store.userParams[param];
delete params[param]; delete params[param];
if (store.filter?.where) { if (store.filter?.where) {
delete store.filter.where[Object.keys(store?.exprBuilder(param))[0]]; delete store.filter.where[Object.keys(exprBuilder ? exprBuilder(param) : param)[0]];
if (Object.keys(store.filter.where).length === 0) { if (Object.keys(store.filter.where).length === 0) {
delete store.filter.where; delete store.filter.where;
} }

View File

@ -0,0 +1,22 @@
import { Notify } from 'quasar';
import { i18n } from 'src/boot/i18n';
export default function useNotify() {
const notify = (message, type, icon) => {
const defaultIcons = {
warning: 'warning',
negative: 'error',
positive: 'check',
};
Notify.create({
message: i18n.global.t(message),
type: type,
icon: icon ? icon : defaultIcons[type],
});
};
return {
notify,
};
}

View File

@ -8,6 +8,7 @@ const user = ref({
nickname: '', nickname: '',
lang: '', lang: '',
darkMode: null, darkMode: null,
companyFk: null,
}); });
const roles = ref([]); const roles = ref([]);
@ -23,6 +24,7 @@ export function useState() {
nickname: user.value.nickname, nickname: user.value.nickname,
lang: user.value.lang, lang: user.value.lang,
darkMode: user.value.darkMode, darkMode: user.value.darkMode,
companyFk: user.value.companyFk,
}; };
}); });
} }
@ -34,6 +36,7 @@ export function useState() {
nickname: data.nickname, nickname: data.nickname,
lang: data.lang, lang: data.lang,
darkMode: data.darkMode, darkMode: data.darkMode,
companyFk: data.companyFk,
}; };
} }
@ -59,7 +62,6 @@ export function useState() {
delete state.value[name]; delete state.value[name];
} }
return { return {
getUser, getUser,
setUser, setUser,
@ -69,6 +71,6 @@ export function useState() {
get, get,
unset, unset,
drawer, drawer,
headerMounted headerMounted,
}; };
} }

View File

@ -1,14 +1,24 @@
import axios from 'axios'; import axios from 'axios';
import { useState } from './useState'; import { useState } from './useState';
import useNotify from './useNotify';
export function useUserConfig() { export function useUserConfig() {
const state = useState(); const state = useState();
const { notify } = useNotify();
async function fetch() { async function fetch() {
const { data } = await axios.get('UserConfigs/getUserConfig'); try {
const user = state.getUser().value; const { data } = await axios.get('UserConfigs/getUserConfig');
user.darkMode = data.darkMode; const user = state.getUser().value;
state.setUser(user); user.darkMode = data.darkMode;
user.companyFk = data.companyFk;
state.setUser(user);
return data;
} catch (error) {
notify('globals.errors.userConfig', 'negative');
console.error('Error fetching user config:', error);
}
} }
return { return {

View File

@ -21,6 +21,7 @@ export default {
dataDeleted: 'Data deleted', dataDeleted: 'Data deleted',
search: 'Search', search: 'Search',
changes: 'Changes', changes: 'Changes',
dataCreated: 'Data created',
add: 'Add', add: 'Add',
create: 'Create', create: 'Create',
save: 'Save', save: 'Save',
@ -47,12 +48,29 @@ export default {
dateFormat: 'en-GB', dateFormat: 'en-GB',
microsip: 'Open in MicroSIP', microsip: 'Open in MicroSIP',
noSelectedRows: `You don't have any line selected`, noSelectedRows: `You don't have any line selected`,
downloadCSVSuccess: 'CSV downloaded successfully',
// labels compartidos entre vistas
reference: 'Reference',
agency: 'Agency',
wareHouseOut: 'Warehouse Out',
wareHouseIn: 'Warehouse In',
landed: 'Landed',
shipped: 'Shipped',
totalEntries: 'Total entries',
amount: 'Amount',
packages: 'Packages',
download: 'Download',
selectRows: 'Select all { numberRows } row(s)',
allRows: 'All { numberRows } row(s)',
markAll: 'Mark all',
}, },
errors: { errors: {
statusUnauthorized: 'Access denied', statusUnauthorized: 'Access denied',
statusInternalServerError: 'An internal server error has ocurred', statusInternalServerError: 'An internal server error has ocurred',
statusBadGateway: 'It seems that the server has fall down', statusBadGateway: 'It seems that the server has fall down',
statusGatewayTimeout: 'Could not contact the server', statusGatewayTimeout: 'Could not contact the server',
userConfig: 'Error fetching user config',
create: 'Error during creation',
}, },
login: { login: {
title: 'Login', title: 'Login',
@ -235,7 +253,6 @@ export default {
invoice: 'Invoice', invoice: 'Invoice',
shipped: 'Shipped', shipped: 'Shipped',
landed: 'Landed', landed: 'Landed',
packages: 'Packages',
consigneePhone: 'Consignee phone', consigneePhone: 'Consignee phone',
consigneeMobile: 'Consignee mobile', consigneeMobile: 'Consignee mobile',
clientPhone: 'Client phone', clientPhone: 'Client phone',
@ -252,7 +269,6 @@ export default {
description: 'Description', description: 'Description',
price: 'Price', price: 'Price',
discount: 'Discount', discount: 'Discount',
amount: 'Amount',
packing: 'Packing', packing: 'Packing',
hasComponentLack: 'Component lack', hasComponentLack: 'Component lack',
itemShortage: 'Not visible', itemShortage: 'Not visible',
@ -345,7 +361,6 @@ export default {
assignedTo: 'Assigned', assignedTo: 'Assigned',
created: 'Created', created: 'Created',
state: 'State', state: 'State',
packages: 'Packages',
picked: 'Picked', picked: 'Picked',
returnOfMaterial: 'Return of material authorization (RMA)', returnOfMaterial: 'Return of material authorization (RMA)',
}, },
@ -359,6 +374,8 @@ export default {
pageTitles: { pageTitles: {
invoiceOuts: 'Create invoice', invoiceOuts: 'Create invoice',
list: 'List', list: 'List',
negativeBases: 'Negative Bases',
globalInvoicing: 'Global invoicing',
createInvoiceOut: 'Create invoice out', createInvoiceOut: 'Create invoice out',
summary: 'Summary', summary: 'Summary',
basicData: 'Basic Data', basicData: 'Basic Data',
@ -367,7 +384,6 @@ export default {
ref: 'Reference', ref: 'Reference',
issued: 'Issued', issued: 'Issued',
shortIssued: 'Issued', shortIssued: 'Issued',
amount: 'Amount',
client: 'Client', client: 'Client',
created: 'Created', created: 'Created',
shortCreated: 'Created', shortCreated: 'Created',
@ -377,7 +393,6 @@ export default {
}, },
card: { card: {
issued: 'Issued', issued: 'Issued',
amount: 'Amount',
client: 'Client', client: 'Client',
company: 'Company', company: 'Company',
customerCard: 'Customer card', customerCard: 'Customer card',
@ -400,6 +415,73 @@ export default {
shipped: 'Shipped', shipped: 'Shipped',
totalWithVat: 'Amount', totalWithVat: 'Amount',
}, },
globalInvoices: {
errors: {
chooseValidClient: 'Choose a valid client',
chooseValidCompany: 'Choose a valid company',
chooseValidPrinter: 'Choose a valid printer',
fillDates: 'Invoice date and the max date should be filled',
invoiceDateLessThanMaxDate: 'Invoice date can not be less than max date',
invoiceWithFutureDate: 'Exists an invoice with a future date',
noTicketsToInvoice: 'There are not clients to invoice',
criticalInvoiceError: 'Critical invoicing error, process stopped',
},
table: {
client: 'Client',
addressId: 'Address id',
streetAddress: 'Street',
},
statusCard: {
percentageText: '{getPercentage}% {getAddressNumber} of {getNAddresses}',
pdfsNumberText: '{nPdfs} of {totalPdfs} PDFs',
},
},
negativeBases: {
from: 'From',
to: 'To',
company: 'Company',
country: 'Country',
clientId: 'Client Id',
client: 'Client',
amount: 'Amount',
base: 'Base',
ticketId: 'Ticket Id',
active: 'Active',
hasToInvoice: 'Has to Invoice',
verifiedData: 'Verified Data',
comercial: 'Comercial',
errors: {
downloadCsvFailed: 'CSV download failed',
},
},
},
shelving: {
pageTitles: {
shelving: 'Shelving',
shelvingList: 'Shelving List',
create: 'Create',
summary: 'Summary',
basicData: 'Basic Data',
log: 'Logs',
},
list: {
parking: 'Parking',
priority: 'Priority',
newShelving: 'New Shelving',
},
summary: {
code: 'Code',
parking: 'Parking',
priority: 'Priority',
worker: 'Worker',
recyclable: 'Recyclable',
},
basicData: {
code: 'Code',
parking: 'Parking',
priority: 'Priority',
recyclable: 'Recyclable',
},
}, },
invoiceIn: { invoiceIn: {
pageTitles: { pageTitles: {
@ -581,6 +663,81 @@ export default {
}, },
}, },
}, },
supplier: {
pageTitles: {
suppliers: 'Suppliers',
supplier: 'Supplier',
list: 'List',
create: 'Create',
summary: 'Summary',
},
list: {
payMethod: 'Pay method',
payDeadline: 'Pay deadline',
payDay: 'Pay day',
account: 'Account',
newSupplier: 'New supplier',
},
summary: {
responsible: 'Responsible',
notes: 'Notes',
verified: 'Verified',
isActive: 'Is active',
billingData: 'Billing data',
payMethod: 'Pay method',
payDeadline: 'Pay deadline',
payDay: 'Día de pago',
account: 'Account',
fiscalData: 'Fiscal data',
sageTaxType: 'Sage tax type',
sageTransactionType: 'Sage transaction type',
sageWithholding: 'Sage withholding',
supplierActivity: 'Supplier activity',
healthRegister: 'Healt register',
fiscalAddress: 'Fiscal address',
socialName: 'Social name',
taxNumber: 'Tax number',
street: 'Street',
city: 'City',
postCode: 'Postcode',
province: 'Province',
country: 'Country',
},
create: {
supplierName: 'Supplier name',
},
},
travel: {
pageTitles: {
travel: 'Travels',
list: 'List',
create: 'Create',
summary: 'Summary',
extraCommunity: 'Extra community',
},
summary: {
confirmed: 'Confirmed',
entryId: 'Entry Id',
freight: 'Freight',
package: 'Package',
delivered: 'Delivered',
received: 'Received',
entries: 'Entries',
cloneShipping: 'Clone travel',
CloneTravelAndEntries: 'Clone travel and his entries',
AddEntry: 'Add entry',
},
variables: {
search: 'Id/Reference',
agencyModeFk: 'Agency',
warehouseInFk: ' Warehouse In',
warehouseOutFk: 'Warehouse Out',
landedFrom: 'Landed from',
landedTo: 'Landed to',
continent: 'Continent out',
totalEntries: 'Total entries',
},
},
components: { components: {
topbar: {}, topbar: {},
userPanel: { userPanel: {
@ -588,10 +745,11 @@ export default {
logOut: 'Log Out', logOut: 'Log Out',
}, },
smartCard: { smartCard: {
openCard: 'View card',
openSummary: 'Open summary',
viewDescription: 'View description',
downloadFile: 'Download file', downloadFile: 'Download file',
clone: 'Clone',
openCard: 'View',
openSummary: 'Summary',
viewDescription: 'Description',
}, },
cardDescriptor: { cardDescriptor: {
mainList: 'Main list', mainList: 'Main list',

View File

@ -21,6 +21,7 @@ export default {
dataDeleted: 'Datos eliminados', dataDeleted: 'Datos eliminados',
search: 'Buscar', search: 'Buscar',
changes: 'Cambios', changes: 'Cambios',
dataCreated: 'Datos creados',
add: 'Añadir', add: 'Añadir',
create: 'Crear', create: 'Crear',
save: 'Guardar', save: 'Guardar',
@ -47,12 +48,28 @@ export default {
dateFormat: 'es-ES', dateFormat: 'es-ES',
noSelectedRows: `No tienes ninguna línea seleccionada`, noSelectedRows: `No tienes ninguna línea seleccionada`,
microsip: 'Abrir en MicroSIP', microsip: 'Abrir en MicroSIP',
downloadCSVSuccess: 'Descarga de CSV exitosa',
reference: 'Referencia',
agency: 'Agencia',
wareHouseOut: 'Alm. salida',
wareHouseIn: 'Alm. entrada',
landed: 'F. entrega',
shipped: 'F. envío',
totalEntries: 'Ent. totales',
amount: 'Importe',
packages: 'Bultos',
download: 'Descargar',
selectRows: 'Seleccionar las { numberRows } filas(s)',
allRows: 'Todo { numberRows } filas(s)',
markAll: 'Marcar todo',
}, },
errors: { errors: {
statusUnauthorized: 'Acceso denegado', statusUnauthorized: 'Acceso denegado',
statusInternalServerError: 'Ha ocurrido un error interno del servidor', statusInternalServerError: 'Ha ocurrido un error interno del servidor',
statusBadGateway: 'Parece ser que el servidor ha caído', statusBadGateway: 'Parece ser que el servidor ha caído',
statusGatewayTimeout: 'No se ha podido contactar con el servidor', statusGatewayTimeout: 'No se ha podido contactar con el servidor',
userConfig: 'Error al obtener configuración de usuario',
create: 'Error al crear',
}, },
login: { login: {
title: 'Inicio de sesión', title: 'Inicio de sesión',
@ -234,7 +251,6 @@ export default {
invoice: 'Factura', invoice: 'Factura',
shipped: 'Enviado', shipped: 'Enviado',
landed: 'Entregado', landed: 'Entregado',
packages: 'Bultos',
consigneePhone: 'Tel. consignatario', consigneePhone: 'Tel. consignatario',
consigneeMobile: 'Móv. consignatario', consigneeMobile: 'Móv. consignatario',
clientPhone: 'Tel. cliente', clientPhone: 'Tel. cliente',
@ -251,7 +267,6 @@ export default {
description: 'Descripción', description: 'Descripción',
price: 'Precio', price: 'Precio',
discount: 'Descuento', discount: 'Descuento',
amount: 'Importe',
packing: 'Encajado', packing: 'Encajado',
hasComponentLack: 'Faltan componentes', hasComponentLack: 'Faltan componentes',
itemShortage: 'No visible', itemShortage: 'No visible',
@ -344,7 +359,6 @@ export default {
assignedTo: 'Asignada a', assignedTo: 'Asignada a',
created: 'Creada', created: 'Creada',
state: 'Estado', state: 'Estado',
packages: 'Bultos',
picked: 'Recogida', picked: 'Recogida',
returnOfMaterial: 'Autorización de retorno de materiales (RMA)', returnOfMaterial: 'Autorización de retorno de materiales (RMA)',
}, },
@ -359,6 +373,8 @@ export default {
pageTitles: { pageTitles: {
invoiceOuts: 'Crear factura', invoiceOuts: 'Crear factura',
list: 'Listado', list: 'Listado',
negativeBases: 'Bases Negativas',
globalInvoicing: 'Facturación global',
createInvoiceOut: 'Crear fact. emitida', createInvoiceOut: 'Crear fact. emitida',
summary: 'Resumen', summary: 'Resumen',
basicData: 'Datos básicos', basicData: 'Datos básicos',
@ -367,7 +383,6 @@ export default {
ref: 'Referencia', ref: 'Referencia',
issued: 'Fecha emisión', issued: 'Fecha emisión',
shortIssued: 'F. emisión', shortIssued: 'F. emisión',
amount: 'Importe',
client: 'Cliente', client: 'Cliente',
created: 'Fecha creación', created: 'Fecha creación',
shortCreated: 'F. creación', shortCreated: 'F. creación',
@ -377,7 +392,6 @@ export default {
}, },
card: { card: {
issued: 'Fecha emisión', issued: 'Fecha emisión',
amount: 'Importe',
client: 'Cliente', client: 'Cliente',
company: 'Empresa', company: 'Empresa',
customerCard: 'Ficha del cliente', customerCard: 'Ficha del cliente',
@ -400,6 +414,75 @@ export default {
shipped: 'F. envío', shipped: 'F. envío',
totalWithVat: 'Importe', totalWithVat: 'Importe',
}, },
globalInvoices: {
errors: {
chooseValidClient: 'Selecciona un cliente válido',
chooseValidCompany: 'Selecciona una empresa válida',
chooseValidPrinter: 'Selecciona una impresora válida',
fillDates:
'La fecha de la factura y la fecha máxima deben estar completas',
invoiceDateLessThanMaxDate:
'La fecha de la factura no puede ser menor que la fecha máxima',
invoiceWithFutureDate: 'Existe una factura con una fecha futura',
noTicketsToInvoice: 'No hay clientes para facturar',
criticalInvoiceError: 'Error crítico en la facturación, proceso detenido',
},
table: {
client: 'Cliente',
addressId: 'Id dirección',
streetAddress: 'Dirección fiscal',
},
statusCard: {
percentageText: '{getPercentage}% {getAddressNumber} de {getNAddresses}',
pdfsNumberText: '{nPdfs} de {totalPdfs} PDFs',
},
},
negativeBases: {
from: 'Desde',
to: 'Hasta',
company: 'Empresa',
country: 'País',
clientId: 'Id cliente',
client: 'Cliente',
amount: 'Importe',
base: 'Base',
ticketId: 'Id ticket',
active: 'Activo',
hasToInvoice: 'Facturar',
verifiedData: 'Datos comprobados',
comercial: 'Comercial',
errors: {
downloadCsvFailed: 'Error al descargar CSV',
},
},
},
shelving: {
pageTitles: {
shelving: 'Carros',
shelvingList: 'Listado de carros',
create: 'Crear',
summary: 'Resumen',
basicData: 'Datos básicos',
log: 'Registros de auditoría',
},
list: {
parking: 'Parking',
priority: 'Prioridad',
newShelving: 'Nuevo Carro',
},
summary: {
code: 'Código',
parking: 'Parking',
priority: 'Prioridad',
worker: 'Trabajador',
recyclable: 'Reciclable',
},
basicData: {
code: 'Código',
parking: 'Parking',
priority: 'Prioridad',
recyclable: 'Reciclable',
},
}, },
invoiceIn: { invoiceIn: {
pageTitles: { pageTitles: {
@ -579,6 +662,81 @@ export default {
}, },
}, },
}, },
supplier: {
pageTitles: {
suppliers: 'Proveedores',
supplier: 'Proveedor',
list: 'Listado',
create: 'Crear',
summary: 'Resumen',
},
list: {
payMethod: 'Método de pago',
payDeadline: 'Plazo de pago',
payDay: 'Día de pago',
account: 'Cuenta',
newSupplier: 'Nuevo proveedor',
},
summary: {
responsible: 'Responsable',
notes: 'Notas',
verified: 'Verificado',
isActive: 'Está activo',
billingData: 'Forma de pago',
payMethod: 'Método de pago',
payDeadline: 'Plazo de pago',
payDay: 'Día de pago',
account: 'Cuenta',
fiscalData: 'Data fiscal',
sageTaxType: 'Tipo de impuesto Sage',
sageTransactionType: 'Tipo de transacción Sage',
sageWithholding: 'Retención sage',
supplierActivity: 'Actividad proveedor',
healthRegister: 'Pasaporte sanitario',
fiscalAddress: 'Dirección fiscal',
socialName: 'Razón social',
taxNumber: 'NIF/CIF',
street: 'Dirección',
city: 'Población',
postCode: 'Código postal',
province: 'Provincia',
country: 'País',
},
create: {
supplierName: 'Nombre del proveedor',
},
},
travel: {
pageTitles: {
travel: 'Envíos',
list: 'Listado',
create: 'Crear',
summary: 'Resumen',
extraCommunity: 'Extra comunitarios',
},
summary: {
confirmed: 'Confirmado',
entryId: 'Id entrada',
freight: 'Porte',
package: 'Embalaje',
delivered: 'Enviada',
received: 'Recibida',
entries: 'Entradas',
cloneShipping: 'Clonar envío',
CloneTravelAndEntries: 'Clonar travel y sus entradas',
AddEntry: 'Añadir entrada',
},
variables: {
search: 'Id/Referencia',
agencyModeFk: 'Agencia',
warehouseInFk: 'Alm. entrada',
warehouseOutFk: ' Alm. salida',
landedFrom: 'Llegada desde',
landedTo: 'Llegada hasta',
continent: 'Cont. Salida',
totalEntries: 'Ent. totales',
},
},
components: { components: {
topbar: {}, topbar: {},
userPanel: { userPanel: {
@ -586,10 +744,11 @@ export default {
logOut: 'Cerrar sesión', logOut: 'Cerrar sesión',
}, },
smartCard: { smartCard: {
openCard: 'Ver ficha',
openSummary: 'Abrir detalles',
viewDescription: 'Ver descripción',
downloadFile: 'Descargar archivo', downloadFile: 'Descargar archivo',
clone: 'Clonar',
openCard: 'Ficha',
openSummary: 'Detalles',
viewDescription: 'Descripción',
}, },
cardDescriptor: { cardDescriptor: {
mainList: 'Listado principal', mainList: 'Listado principal',

View File

@ -7,6 +7,7 @@ import { useSession } from 'src/composables/useSession';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnInputDate from "components/common/VnInputDate.vue";
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@ -96,6 +97,7 @@ const statesFilter = {
:url-update="`Claims/updateClaim/${route.params.id}`" :url-update="`Claims/updateClaim/${route.params.id}`"
:filter="claimFilter" :filter="claimFilter"
model="claim" model="claim"
auto-load
> >
<template #form="{ data, validate, filter }"> <template #form="{ data, validate, filter }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
@ -107,33 +109,7 @@ const statesFilter = {
/> />
</div> </div>
<div class="col"> <div class="col">
<QInput <VnInputDate v-model="data.created" :label="t('claim.basicData.created')" />
v-model="data.created"
mask="####-##-##"
fill-mask="_"
autofocus
>
<template #append>
<QIcon name="event" class="cursor-pointer">
<QPopupProxy
cover
transition-show="scale"
transition-hide="scale"
>
<QDate v-model="data.created" mask="YYYY-MM-DD">
<div class="row items-center justify-end">
<QBtn
v-close-popup
label="Close"
color="primary"
flat
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</div> </div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
@ -183,7 +159,7 @@ const statesFilter = {
<div class="col"> <div class="col">
<QInput <QInput
v-model.number="data.packages" v-model.number="data.packages"
:label="t('claim.basicData.packages')" :label="t('globals.packages')"
:rules="validate('claim.packages')" :rules="validate('claim.packages')"
type="number" type="number"
/> />

View File

@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue'; import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@ -150,33 +151,7 @@ const states = ref();
</QItem> --> </QItem> -->
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInputDate v-model="params.created" :label="t('Created')" />
v-model="params.created"
:label="t('Created')"
autofocus
readonly
>
<template #append>
<QIcon name="event" class="cursor-pointer">
<QPopupProxy
cover
transition-show="scale"
transition-hide="scale"
>
<QDate v-model="params.created">
<div class="row items-center justify-end">
<QBtn
v-close-popup
label="Close"
color="primary"
flat
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
</QExpansionItem> </QExpansionItem>

View File

@ -89,10 +89,11 @@ function viewDescriptor(id) {
> >
<template #body="{ rows }"> <template #body="{ rows }">
<CardList <CardList
v-for="row of rows" :id="row.id"
:key="row.id" :key="row.id"
:title="row.clientName" :title="row.clientName"
@click="navigate(row.id)" @click="navigate(row.id)"
v-for="row of rows"
> >
<template #list-items> <template #list-items>
<VnLv label="ID" :value="row.id" /> <VnLv label="ID" :value="row.id" />
@ -118,11 +119,7 @@ function viewDescriptor(id) {
/> />
<VnLv :label="t('claim.list.state')"> <VnLv :label="t('claim.list.state')">
<template #value> <template #value>
<QBadge <QBadge :color="stateColor(row.stateCode)" dense>
:color="stateColor(row.stateCode)"
class="q-ma-none"
dense
>
{{ row.stateDescription }} {{ row.stateDescription }}
</QBadge> </QBadge>
</template> </template>
@ -130,19 +127,26 @@ function viewDescriptor(id) {
</template> </template>
<template #actions> <template #actions>
<QBtn <QBtn
flat :label="t('components.smartCard.openCard')"
icon="arrow_circle_right"
@click.stop="navigate(row.id)" @click.stop="navigate(row.id)"
class="bg-vn-dark"
outline
/>
<QBtn
:label="t('components.smartCard.viewDescription')"
@click.stop
class="bg-vn-dark"
outline
style="margin-top: 15px"
> >
<QTooltip> <CustomerDescriptorProxy :id="row.clientFk" />
{{ t('components.smartCard.openCard') }}
</QTooltip>
</QBtn>
<QBtn flat icon="preview" @click.stop="viewSummary(row.id)">
<QTooltip>
{{ t('components.smartCard.openSummary') }}
</QTooltip>
</QBtn> </QBtn>
<QBtn
:label="t('components.smartCard.openSummary')"
@click.stop="viewSummary(row.id)"
color="primary"
style="margin-top: 15px"
/>
</template> </template>
</CardList> </CardList>
</template> </template>

View File

@ -60,7 +60,7 @@ const filterOptions = {
auto-load auto-load
/> />
<FormModel :url="`Clients/${route.params.id}`" model="customer"> <FormModel :url="`Clients/${route.params.id}`" model="customer" auto-load>
<template #form="{ data, validate, filter }"> <template #form="{ data, validate, filter }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">

View File

@ -73,10 +73,10 @@ function viewSummary(id) {
v-for="row of rows" v-for="row of rows"
:key="row.id" :key="row.id"
:title="row.name" :title="row.name"
:id="row.id"
@click="navigate(row.id)" @click="navigate(row.id)"
> >
<template #list-items> <template #list-items>
<VnLv label="ID" :value="row.id" />
<VnLv :label="t('customer.list.email')" :value="row.email" /> <VnLv :label="t('customer.list.email')" :value="row.email" />
<VnLv :value="row.phone"> <VnLv :value="row.phone">
<template #label> <template #label>
@ -87,25 +87,17 @@ function viewSummary(id) {
</template> </template>
<template #actions> <template #actions>
<QBtn <QBtn
flat :label="t('components.smartCard.openCard')"
color="primary"
icon="arrow_circle_right"
@click.stop="navigate(row.id)" @click.stop="navigate(row.id)"
> class="bg-vn-dark"
<QTooltip> outline
{{ t('components.smartCard.openCard') }} />
</QTooltip>
</QBtn>
<QBtn <QBtn
flat :label="t('components.smartCard.openSummary')"
color="grey-7"
icon="preview"
@click.stop="viewSummary(row.id)" @click.stop="viewSummary(row.id)"
> color="primary"
<QTooltip> style="margin-top: 15px"
{{ t('components.smartCard.openSummary') }} />
</QTooltip>
</QBtn>
</template> </template>
</CardList> </CardList>
</template> </template>

View File

@ -1,6 +1,7 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@ -76,78 +77,16 @@ function isValidNumber(value) {
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInputDate
v-model="params.from" v-model="params.from"
:label="t('From')" :label="t('From')"
mask="date" />
placeholder="yyyy/mm/dd"
>
<template #append>
<QIcon name="event" class="cursor-pointer">
<QPopupProxy
cover
transition-show="scale"
transition-hide="scale"
>
<QDate v-model="params.from" landscape>
<div
class="row items-center justify-end q-gutter-sm"
>
<QBtn
:label="t('globals.cancel')"
color="primary"
flat
v-close-popup
/>
<QBtn
:label="t('globals.confirm')"
color="primary"
flat
v-close-popup
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</QItemSection> </QItemSection>
<QItemSection> <QItemSection>
<QInput <VnInputDate
v-model="params.to" v-model="params.to"
:label="t('To')" :label="t('To')"
mask="date" />
placeholder="yyyy/mm/dd"
>
<template #append>
<QIcon name="event" class="cursor-pointer">
<QPopupProxy
cover
transition-show="scale"
transition-hide="scale"
>
<QDate v-model="params.to" landscape>
<div
class="row items-center justify-end q-gutter-sm"
>
<QBtn
:label="t('globals.cancel')"
color="primary"
flat
v-close-popup
/>
<QBtn
:label="t('globals.confirm')"
color="primary"
flat
v-close-popup
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
</QList> </QList>

View File

@ -7,6 +7,7 @@ import CardDescriptor from 'components/ui/CardDescriptor.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 useCardDescription from 'src/composables/useCardDescription'; import useCardDescription from 'src/composables/useCardDescription';
import InvoiceOutDescriptorMenu from './InvoiceOutDescriptorMenu.vue';
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -59,12 +60,12 @@ const setData = (entity) => (data.value = useCardDescription(entity.ref, entity.
@on-fetch="setData" @on-fetch="setData"
data-key="invoiceOutData" data-key="invoiceOutData"
> >
<template #menu="{ entity }">
<InvoiceOutDescriptorMenu :invoice-out="entity" />
</template>
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('invoiceOut.card.issued')" :value="toDate(entity.issued)" /> <VnLv :label="t('invoiceOut.card.issued')" :value="toDate(entity.issued)" />
<VnLv <VnLv :label="t('globals.amount')" :value="toCurrency(entity.amount)" />
:label="t('invoiceOut.card.amount')"
:value="toCurrency(entity.amount)"
/>
<VnLv v-if="entity.client" :label="t('invoiceOut.card.client')"> <VnLv v-if="entity.client" :label="t('invoiceOut.card.client')">
<template #value> <template #value>
<span class="link"> <span class="link">

View File

@ -0,0 +1,40 @@
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>
<template>
<QItem v-ripple clickable>
<QItemSection>{{ t('Transfer invoice to') }}</QItemSection>
</QItem>
<QItem v-ripple clickable>
<QItemSection>{{ t('See invoice') }}</QItemSection>
</QItem>
<QItem v-ripple clickable>
<QItemSection>{{ t('Send invoice') }}</QItemSection>
</QItem>
<QItem v-ripple clickable>
<QItemSection>{{ t('Delete invoice') }}</QItemSection>
</QItem>
<QItem v-ripple clickable>
<QItemSection>{{ t('Post invoice') }}</QItemSection>
</QItem>
<QItem v-ripple clickable>
<QItemSection>{{ t('Regenerate invoice PDF') }}</QItemSection>
</QItem>
<QItem v-ripple clickable>
<QItemSection>{{ t('Pass') }}</QItemSection>
</QItem>
</template>
<i18n>
es:
Transfer invoice to: Transferir factura a
See invoice: Ver factura
Send invoice: Enviar factura
Delete invoice: Eliminar factura
Post invoice: Asentar factura
Regenerate invoice PDF: Regenerar PDF factura
Pass: Abono
</i18n>

View File

@ -3,6 +3,7 @@ import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@ -43,66 +44,72 @@ function setWorkers(data) {
<QItemSection> <QItemSection>
<QInput <QInput
:label="t('Customer ID')" :label="t('Customer ID')"
v-model="params.clientFk" class="q-mt-sm"
dense
lazy-rules lazy-rules
> outlined
<template #prepend> rounded
<QIcon name="vn:client" size="sm"></QIcon> v-model="params.clientFk"
</template> />
</QInput>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QInput :label="t('FI')" v-model="params.fi" lazy-rules>
<template #prepend>
<QIcon name="badge" size="sm"></QIcon>
</template>
</QInput>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QInput :label="t('Amount')" v-model="params.amount" lazy-rules>
<template #prepend>
<QIcon name="euro" size="sm"></QIcon>
</template>
</QInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection>
<QInput
:label="t('FI')"
class="q-mt-sm"
dense
lazy-rules
outlined
rounded
v-model="params.fi"
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QInput
:label="t('Amount')"
class="q-mt-sm"
dense
lazy-rules
outlined
rounded
v-model="params.amount"
/>
</QItemSection>
</QItem>
<QItem class="q-mt-sm">
<QItemSection> <QItemSection>
<QInput <QInput
:label="t('Min')" :label="t('Min')"
dense
lazy-rules
outlined
rounded
type="number" type="number"
v-model.number="params.min" v-model.number="params.min"
lazy-rules />
>
<template #prepend>
<QIcon name="euro" size="sm"></QIcon>
</template>
</QInput>
</QItemSection> </QItemSection>
<QItemSection> <QItemSection>
<QInput <QInput
:label="t('Max')" :label="t('Max')"
dense
lazy-rules
outlined
rounded
type="number" type="number"
v-model.number="params.max" v-model.number="params.max"
lazy-rules />
>
<template #prepend>
<QIcon name="euro" size="sm"></QIcon>
</template>
</QInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-md"> <QItem class="q-mb-md">
<QItemSection> <QItemSection>
<QCheckbox <QCheckbox
v-model="params.hasPdf"
@update:model-value="searchFn()"
:label="t('Has PDF')" :label="t('Has PDF')"
@update:model-value="searchFn()"
toggle-indeterminate toggle-indeterminate
v-model="params.hasPdf"
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -110,115 +117,35 @@ function setWorkers(data) {
<QExpansionItem :label="t('More options')" expand-separator> <QExpansionItem :label="t('More options')" expand-separator>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInputDate
:label="t('Issued')"
v-model="params.issued" v-model="params.issued"
mask="date" :label="t('Issued')"
> dense
<template #append> outlined
<QIcon name="event" class="cursor-pointer"> rounded
<QPopupProxy />
cover
transition-show="scale"
transition-hide="scale"
>
<QDate v-model="params.issued" landscape>
<div
class="row items-center justify-end q-gutter-sm"
>
<QBtn
:label="t('globals.cancel')"
color="primary"
flat
v-close-popup
/>
<QBtn
:label="t('globals.confirm')"
color="primary"
flat
@click="save"
v-close-popup
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInputDate
:label="t('Created')"
v-model="params.created" v-model="params.created"
mask="date" :label="t('Created')"
> dense
<template #append> outlined
<QIcon name="event" class="cursor-pointer"> rounded
<QPopupProxy />
cover
transition-show="scale"
transition-hide="scale"
>
<QDate v-model="params.created" landscape>
<div
class="row items-center justify-end q-gutter-sm"
>
<QBtn
:label="t('globals.cancel')"
color="primary"
flat
v-close-popup
/>
<QBtn
:label="t('globals.confirm')"
color="primary"
flat
@click="save"
v-close-popup
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput :label="t('Dued')" v-model="params.dued" mask="date"> <VnInputDate
<template #append> v-model="params.dued"
<QIcon name="event" class="cursor-pointer"> :label="t('Dued')"
<QPopupProxy dense
cover outlined
transition-show="scale" rounded
transition-hide="scale" />
>
<QDate v-model="params.dued" landscape>
<div
class="row items-center justify-end q-gutter-sm"
>
<QBtn
:label="t('globals.cancel')"
color="primary"
flat
v-close-popup
/>
<QBtn
:label="t('globals.confirm')"
color="primary"
flat
@click="save"
v-close-popup
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
</QExpansionItem> </QExpansionItem>

View File

@ -0,0 +1,198 @@
<script setup>
import { onMounted, onUnmounted, computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore';
import InvoiceOutGlobalForm from './InvoiceOutGlobalForm.vue';
import { useInvoiceOutGlobalStore } from 'src/stores/invoiceOutGlobal.js';
import { storeToRefs } from 'pinia';
import { QBadge, QBtn } from 'quasar';
import CustomerDescriptor from 'src/pages/Customer/Card/CustomerDescriptor.vue';
const stateStore = useStateStore();
const { t } = useI18n();
const invoiceOutGlobalStore = useInvoiceOutGlobalStore();
// invoiceOutGlobalStore state and getters
const {
status,
getPercentage,
getAddressNumber,
getNAddresses,
nPdfs,
totalPdfs,
errors,
} = storeToRefs(invoiceOutGlobalStore);
const selectedCustomerId = ref(null);
const tableColumnComponents = {
clientId: {
component: QBtn,
props: { flat: true, color: 'blue' },
event: (prop) => selectCustomerId(prop.value),
},
clientName: {
component: 'span',
props: {},
},
id: {
component: 'span',
props: {},
},
nickname: {
component: 'span',
props: {},
},
message: {
component: QBadge,
props: { color: 'red' },
},
};
const columns = computed(() => {
return [
{ label: 'Id', field: 'clientId', name: 'clientId', align: 'left' },
{
label: t('invoiceOut.globalInvoices.table.client'),
field: 'clientName',
name: 'clientName',
align: 'left',
},
{
label: t('invoiceOut.globalInvoices.table.addressId'),
field: 'id',
name: 'id',
align: 'left',
},
{
label: t('invoiceOut.globalInvoices.table.streetAddress'),
field: 'nickname',
name: 'nickname',
align: 'left',
},
{ label: 'Error', field: 'message', name: 'message', align: 'left' },
];
});
const rows = computed(() => {
if (!errors && !errors.length > 0) return [];
return errors.value.map((row) => {
return {
...row.client,
message: row.message,
};
});
});
const selectCustomerId = (id) => {
selectedCustomerId.value = id;
};
onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => {
stateStore.rightDrawer = false;
invoiceOutGlobalStore.$reset();
});
</script>
<template>
<QDrawer v-model="stateStore.rightDrawer" :width="256" side="right" show-if-above>
<QScrollArea class="fit text-grey-8">
<InvoiceOutGlobalForm />
</QScrollArea>
</QDrawer>
<QPage class="column items-center q-pa-md">
<QCard v-if="status" class="card">
<QCardSection class="card-section">
<span class="text">{{ t(`status.${status}`) }}</span>
<span class="text">{{
t('invoiceOut.globalInvoices.statusCard.percentageText', {
getPercentage: getPercentage,
getAddressNumber: getAddressNumber,
getNAddresses: getNAddresses,
})
}}</span>
<span class="text">{{
t('invoiceOut.globalInvoices.statusCard.pdfsNumberText', {
nPdfs: nPdfs,
totalPdfs: totalPdfs,
})
}}</span>
</QCardSection>
</QCard>
<QTable
v-if="rows.length > 0"
:rows="rows"
:columns="columns"
hide-bottom
row-key="id"
:pagination="{ rowsPerPage: 0 }"
class="full-width q-mt-md"
>
<template #body-cell="props">
<QTd :props="props">
<component
:is="tableColumnComponents[props.col.name].component"
v-bind="tableColumnComponents[props.col.name].props"
@click="tableColumnComponents[props.col.name].event(props)"
class="col-content"
>
{{ props.value }}
<QPopupProxy>
<CustomerDescriptor
v-if="selectedCustomerId === props.value"
:id="selectedCustomerId"
/>
</QPopupProxy>
</component>
</QTd>
</template>
</QTable>
</QPage>
</template>
<style lang="scss" scoped>
.card {
display: flex;
justify-content: center;
width: 100%;
background-color: var(--vn-dark);
padding: 16px;
.card-section {
display: flex;
flex-direction: column;
padding: 0px;
}
.text {
font-size: 14px;
color: var(--vn-text);
}
}
.col-content {
border-radius: 4px;
padding: 6px 6px 6px 6px;
}
</style>
<i18n>
en:
status:
packageInvoicing: Build packaging tickets
invoicing: Invoicing client
stopping: Stopping process
done: Ended process
of: of
es:
status:
packageInvoicing: Generación de tickets de empaque
invoicing: Facturando a cliente
stopping: Deteniendo proceso
done: Proceso detenido
of: de
</i18n>

View File

@ -0,0 +1,201 @@
<script setup>
import { onMounted, ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useInvoiceOutGlobalStore } from 'src/stores/invoiceOutGlobal.js';
import { storeToRefs } from 'pinia';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import FetchData from 'components/FetchData.vue';
import VnInputDate from "components/common/VnInputDate.vue";
const { t } = useI18n();
const invoiceOutGlobalStore = useInvoiceOutGlobalStore();
// invoiceOutGlobalStore state and getters
const {
initialDataLoading,
formInitialData,
invoicing,
status,
} = storeToRefs(invoiceOutGlobalStore);
// invoiceOutGlobalStore actions
const { makeInvoice, setStatusValue } = invoiceOutGlobalStore;
const clientsToInvoice = ref('all');
const companiesOptions = ref([]);
const printersOptions = ref([]);
const clientsOptions = ref([]);
const formData = ref({
companyFk: null,
invoiceDate: null,
maxShipped: null,
clientId: null,
printer: null,
});
const optionsInitialData = computed(() => {
return (
companiesOptions.value.length > 0 &&
printersOptions.value.length > 0 &&
clientsOptions.value.length > 0
);
});
const getStatus = computed({
get() {
return status.value;
},
set(value) {
setStatusValue(value.value);
},
});
const onFetchCompanies = (companies) => {
companiesOptions.value = [...companies];
};
const onFetchPrinters = (printers) => {
printersOptions.value = [...printers];
};
const onFetchClients = (clients) => {
clientsOptions.value = [...clients];
};
onMounted(async () => {
await invoiceOutGlobalStore.init();
formData.value = { ...formInitialData.value };
});
</script>
<template>
<FetchData url="Companies" @on-fetch="(data) => onFetchCompanies(data)" auto-load />
<FetchData url="Printers" @on-fetch="(data) => onFetchPrinters(data)" auto-load />
<FetchData url="Clients" @on-fetch="(data) => onFetchClients(data)" auto-load />
<QForm
v-if="!initialDataLoading && optionsInitialData"
@submit="makeInvoice(formData, clientsToInvoice)"
class="form-container q-pa-md"
style="max-width: 256px"
>
<div class="column q-gutter-y-md">
<QRadio
v-model="clientsToInvoice"
dense
val="all"
:label="t('allClients')"
:dark="true"
/>
<QRadio
v-model="clientsToInvoice"
dense
val="one"
:label="t('oneClient')"
:dark="true"
/>
<VnSelectFilter
v-if="clientsToInvoice === 'one'"
:label="t('client')"
v-model="formData.clientId"
:options="clientsOptions"
option-value="id"
option-label="name"
hide-selected
dense
outlined
rounded
/>
<VnInputDate
v-model="formData.invoiceDate"
:label="t('invoiceDate')"
dense
outlined
rounded />
<VnInputDate
v-model="formData.maxShipped"
:label="t('maxShipped')"
dense
outlined
rounded />
<VnSelectFilter
:label="t('company')"
v-model="formData.companyFk"
:options="companiesOptions"
option-value="id"
option-label="code"
hide-selected
dense
outlined
rounded
/>
<VnSelectFilter
:label="t('printer')"
v-model="formData.printer"
:options="printersOptions"
option-value="id"
option-label="name"
hide-selected
dense
outlined
rounded
/>
</div>
<QBtn
v-if="!invoicing"
:label="t('invoiceOut')"
type="submit"
color="primary"
class="q-mt-md full-width"
unelevated
rounded
dense
/>
<QBtn
v-if="invoicing"
:label="t('stop')"
color="primary"
class="q-mt-md full-width"
unelevated
rounded
dense
@click="getStatus = 'stopping'"
/>
</QForm>
</template>
<style scoped>
.form-container * {
max-width: 100%;
}
</style>
<i18n>
en:
invoiceDate: Invoice date
maxShipped: Max date
allClients: All clients
oneClient: One client
company: Company
printer: Printer
invoiceOut: Invoice out
client: Client
stop: Stop
es:
invoiceDate: Fecha de factura
maxShipped: Fecha límite
allClients: Todos los clientes
oneClient: Un solo cliente
company: Empresa
printer: Impresora
invoiceOut: Facturar
client: Cliente
stop: Parar
</i18n>

View File

@ -1,8 +1,8 @@
<script setup> <script setup>
import { onMounted, onUnmounted } from 'vue'; import { onMounted, onUnmounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useQuasar } from 'quasar'; import { exportFile, useQuasar } from 'quasar';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import VnPaginate from 'src/components/ui/VnPaginate.vue'; import VnPaginate from 'src/components/ui/VnPaginate.vue';
import InvoiceOutSummaryDialog from './Card/InvoiceOutSummaryDialog.vue'; import InvoiceOutSummaryDialog from './Card/InvoiceOutSummaryDialog.vue';
@ -12,10 +12,11 @@ import InvoiceOutFilter from './InvoiceOutFilter.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import CardList from 'src/components/ui/CardList.vue'; import CardList from 'src/components/ui/CardList.vue';
const stateStore = useStateStore();
const router = useRouter();
const quasar = useQuasar();
const { t } = useI18n(); const { t } = useI18n();
const selectedCards = ref(new Map());
const quasar = useQuasar();
const router = useRouter();
const stateStore = useStateStore();
onMounted(() => (stateStore.rightDrawer = true)); onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false)); onUnmounted(() => (stateStore.rightDrawer = false));
@ -32,25 +33,81 @@ function viewSummary(id) {
}, },
}); });
} }
const toggleIndividualCard = (cardData) => {
if (selectedCards.value.has(cardData.id)) {
selectedCards.value.delete(cardData.id);
return;
}
selectedCards.value.set(cardData.id, cardData);
};
const toggleAllCards = (cardsData) => {
const allSelected = selectedCards.value.size === cardsData.length;
if (!allSelected) {
// Si no todas las tarjetas están seleccionadas, selecciónalas todas
cardsData.forEach((data) => {
if (!selectedCards.value.has(data.id)) {
selectedCards.value.set(data.id, data);
}
});
} else {
// Si todas las tarjetas están seleccionadas, deselecciónalas todas
selectedCards.value.clear();
}
};
const downloadCsv = () => {
if (selectedCards.value.size === 0) return;
const selectedCardsArray = Array.from(selectedCards.value.values());
let file;
for (var i = 0; i < selectedCardsArray.length; i++) {
if (i == 0) file += Object.keys(selectedCardsArray[i]).join(';') + '\n';
file +=
Object.keys(selectedCardsArray[i])
.map(function (key) {
return selectedCardsArray[i][key];
})
.join(';') + '\n';
}
const status = exportFile('file.csv', file, {
encoding: 'windows-1252',
mimeType: 'text/csv;charset=windows-1252;',
});
if (status === true) {
quasar.notify({
message: t('fileAllowed'),
color: 'positive',
icon: 'check',
});
} else {
quasar.notify({
message: t('fileDenied'),
color: 'negative',
icon: 'warning',
});
}
};
</script> </script>
<template> <template>
<template v-if="stateStore.isHeaderMounted()"> <template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar"> <Teleport to="#searchbar">
<VnSearchbar <VnSearchbar
:info="t('youCanSearchByInvoiceReference')"
:label="t('searchInvoice')"
data-key="InvoiceOutList" data-key="InvoiceOutList"
:label="t('Search invoice')"
:info="t('You can search by invoice reference')"
/> />
</Teleport> </Teleport>
<Teleport to="#actions-append"> <Teleport to="#actions-append">
<div class="row q-gutter-x-sm"> <div class="row q-gutter-x-sm">
<QBtn <QBtn
flat
@click="stateStore.toggleRightDrawer()" @click="stateStore.toggleRightDrawer()"
round
dense dense
flat
icon="menu" icon="menu"
round
> >
<QTooltip bottom anchor="bottom right"> <QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }} {{ t('globals.collapseMenu') }}
@ -64,71 +121,128 @@ function viewSummary(id) {
<InvoiceOutFilter data-key="InvoiceOutList" /> <InvoiceOutFilter data-key="InvoiceOutList" />
</QScrollArea> </QScrollArea>
</QDrawer> </QDrawer>
<QPage class="column items-center q-pa-md"> <QPage>
<div class="card-list"> <VnPaginate
<VnPaginate auto-load
data-key="InvoiceOutList" data-key="InvoiceOutList"
url="InvoiceOuts/filter" order="issued DESC, id DESC"
order="issued DESC, id DESC" url="InvoiceOuts/filter"
auto-load >
> <template #body="{ rows }">
<template #body="{ rows }"> <QToolbar class="bg-vn-dark justify-end">
<CardList <div id="st-actions">
v-for="row of rows" <QBtn
:key="row.id" @click="downloadCsv()"
:title="row.ref" class="q-mr-xl"
@click="navigate(row.id)" color="primary"
> :disable="selectedCards.size === 0"
<template #list-items> :label="t('globals.download')"
<VnLv label="ID" :value="row.id" /> />
<VnLv <!-- <QBtnDropdown
:label="t('invoiceOut.list.shortIssued')" class="q-mr-xl"
:title-label="t('invoiceOut.list.issued')" color="primary"
:value="toDate(row.issued)" :disable="!manageCheckboxes && arrayElements.length < 1"
/> :label="t('globals.download')"
<VnLv v-else
:label="t('invoiceOut.list.amount')" >
:value="toCurrency(row.amount)" <QList>
/> <QItem clickable v-close-popup @click="downloadCsv(rows)">
<VnLv <QItemSection>
:label="t('invoiceOut.list.client')" <QItemLabel>
:value="row.clientSocialName" {{
/> t('globals.allRows', {
<VnLv numberRows: rows.length,
:label="t('invoiceOut.list.shortCreated')" })
:title-label="t('invoiceOut.list.created')" }}
:value="toDate(row.created)" </QItemLabel>
/> </QItemSection>
<VnLv </QItem>
:label="t('invoiceOut.list.company')"
:value="row.companyCode" <QItem clickable v-close-popup @click="downloadCsv(rows)">
/> <QItemSection>
<VnLv <QItemLabel>
:label="t('invoiceOut.list.shortDued')" {{
:title-label="t('invoiceOut.list.dued')" t('globals.selectRows', {
:value="toDate(row.dued)" numberRows: rows.length,
/> })
</template> }}
<template #actions> </QItemLabel>
<QBtn </QItemSection>
flat </QItem>
icon="arrow_circle_right" </QList>
@click.stop="navigate(row.id)" </QBtnDropdown> -->
> <QCheckbox
<QTooltip> left-label
{{ t('components.smartCard.openCard') }} :label="t('globals.markAll')"
</QTooltip> @click="toggleAllCards(rows)"
</QBtn> :model-value="selectedCards.size === rows.length"
<QBtn flat icon="preview" @click.stop="viewSummary(row.id)"> class="q-mr-md"
<QTooltip> />
{{ t('components.smartCard.openSummary') }} </div>
</QTooltip> </QToolbar>
</QBtn> <div class="flex flex-center q-pa-md">
</template> <div class="card-list">
</CardList> <CardList
</template> :element="row"
</VnPaginate> :id="row.id"
</div> :show-checkbox="true"
:is-selected="selectedCards.has(row.id)"
:key="row.id"
:title="row.ref"
@click="navigate(row.id)"
@toggle-card-check="toggleIndividualCard(row)"
v-for="row of rows"
>
<template #list-items>
<VnLv
:label="t('invoiceOut.list.shortIssued')"
:title-label="t('invoiceOut.list.issued')"
:value="toDate(row.issued)"
/>
<VnLv
:label="t('invoiceOut.list.amount')"
:value="toCurrency(row.amount)"
/>
<VnLv
:label="t('invoiceOut.list.client')"
:value="row.clientSocialName"
/>
<VnLv
:label="t('invoiceOut.list.shortCreated')"
:title-label="t('invoiceOut.list.created')"
:value="toDate(row.created)"
/>
<VnLv
:label="t('invoiceOut.list.company')"
:value="row.companyCode"
/>
<VnLv
:label="t('invoiceOut.list.shortDued')"
:title-label="t('invoiceOut.list.dued')"
:value="toDate(row.dued)"
/>
</template>
<template #actions>
<QBtn
:label="t('components.smartCard.openCard')"
@click.stop="navigate(row.id)"
class="bg-vn-dark"
outline
type="reset"
/>
<QBtn
:label="t('components.smartCard.openSummary')"
@click.stop="viewSummary(row.id)"
color="primary"
style="margin-top: 15px"
type="submit"
/>
</template>
</CardList>
</div>
</div>
</template>
</VnPaginate>
</QPage> </QPage>
</template> </template>
@ -140,7 +254,14 @@ function viewSummary(id) {
</style> </style>
<i18n> <i18n>
en:
searchInvoice: Search issued invoice
fileDenied: Browser denied file download...
fileAllowed: Successful download of CSV file
youCanSearchByInvoiceReference: You can search by invoice reference
es: es:
Search invoice: Buscar factura emitida searchInvoice: Buscar factura emitida
You can search by invoice reference: Puedes buscar por referencia de la factura fileDenied: El navegador denegó la descarga de archivos...
fileAllowed: Descarga exitosa de archivo CSV
youCanSearchByInvoiceReference: Puedes buscar por referencia de la factura
</i18n> </i18n>

View File

@ -0,0 +1,356 @@
<script setup>
import { onMounted, ref, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import invoiceOutService from 'src/services/invoiceOut.service';
import { toCurrency } from 'src/filters';
import { QCheckbox, QBtn } from 'quasar';
import { useInvoiceOutGlobalStore } from 'src/stores/invoiceOutGlobal.js';
import VnInputDate from 'components/common/VnInputDate.vue';
const invoiceOutGlobalStore = useInvoiceOutGlobalStore();
const rows = ref([]);
const { t } = useI18n();
const dateRange = reactive({
from: Date.vnFirstDayOfMonth().toISOString(),
to: Date.vnLastDayOfMonth().toISOString(),
});
const selectedCustomerId = ref(0);
const selectedWorkerId = ref(0);
const filter = ref({
company: null,
country: null,
clientId: null,
client: null,
amount: null,
base: null,
ticketId: null,
active: null,
hasToInvoice: null,
verifiedData: null,
comercial: null,
});
const tableColumnComponents = {
company: {
component: 'span',
props: () => {},
event: () => {},
},
country: {
component: 'span',
props: () => {},
event: () => {},
},
clientId: {
component: QBtn,
props: () => ({ flat: true, color: 'blue' }),
event: (prop) => selectCustomerId(prop.value),
},
client: {
component: 'span',
props: () => {},
event: () => {},
},
amount: {
component: 'span',
props: () => {},
event: () => {},
},
base: {
component: 'span',
props: () => {},
event: () => {},
},
ticketId: {
component: 'span',
props: () => {},
event: () => {},
},
active: {
component: QCheckbox,
props: (prop) => ({
disable: true,
'model-value': Boolean(prop.value),
}),
event: () => {},
},
hasToInvoice: {
component: QCheckbox,
props: (prop) => ({
disable: true,
'model-value': Boolean(prop.value),
}),
event: () => {},
},
verifiedData: {
component: QCheckbox,
props: (prop) => ({
disable: true,
'model-value': Boolean(prop.value),
}),
event: () => {},
},
comercial: {
component: QBtn,
props: () => ({ flat: true, color: 'blue' }),
event: (prop) => selectWorkerId(prop.row.comercialId),
},
};
const columns = ref([
{
label: 'company',
field: 'company',
name: 'company',
align: 'left',
},
{
label: 'country',
field: 'country',
name: 'country',
align: 'left',
},
{
label: 'clientId',
field: 'clientId',
name: 'clientId',
align: 'left',
},
{
label: 'client',
field: 'clientSocialName',
name: 'client',
align: 'left',
},
{
label: 'amount',
field: 'amount',
name: 'amount',
align: 'left',
format: (value) => toCurrency(value),
},
{
label: 'base',
field: 'taxableBase',
name: 'base',
align: 'left',
},
{
label: 'ticketId',
field: 'ticketFk',
name: 'ticketId',
align: 'left',
},
{
label: 'active',
field: 'isActive',
name: 'active',
align: 'left',
},
{
label: 'hasToInvoice',
field: 'hasToInvoice',
name: 'hasToInvoice',
align: 'left',
},
{
label: 'verifiedData',
field: 'isTaxDataChecked',
name: 'verifiedData',
align: 'left',
},
{
label: 'comercial',
field: 'comercialName',
name: 'comercial',
align: 'left',
},
]);
const downloadCSV = async () => {
await invoiceOutGlobalStore.getNegativeBasesCsv(dateRange.from, dateRange.to);
};
const search = async () => {
const and = [];
Object.keys(filter.value).forEach((key) => {
if (filter.value[key]) {
and.push({
[key]: filter.value[key],
});
}
});
const searchFilter = {
limit: 20
}
if (and.length) {
searchFilter.where = {
and
}
}
const params = {
...dateRange,
filter: JSON.stringify(searchFilter),
};
rows.value = await invoiceOutService.getNegativeBases(params);
};
const refresh = () => {
dateRange.from = Date.vnFirstDayOfMonth().toISOString();
dateRange.to = Date.vnLastDayOfMonth().toISOString();
filter.value = {
company: null,
country: null,
clientId: null,
client: null,
amount: null,
base: null,
ticketId: null,
active: null,
hasToInvoice: null,
verifiedData: null,
comercial: null,
};
search();
};
const selectCustomerId = (id) => {
selectedCustomerId.value = id;
};
const selectWorkerId = (id) => {
selectedWorkerId.value = id;
};
onMounted(async () => {
refresh();
});
</script>
<template>
<QPage class="column items-center q-pa-md">
<QTable
:rows="rows"
:columns="columns"
hide-bottom
row-key="clientId"
:pagination="{ rowsPerPage: 0 }"
class="full-width q-mt-md"
>
<template #top-left>
<div class="row justify-start items-end">
<VnInputDate
v-model="dateRange.from"
:label="t('invoiceOut.negativeBases.from')"
class="q-mr-md"
dense
lazy-rules
outlined
rounded
/>
<VnInputDate
v-model="dateRange.to"
:label="t('invoiceOut.negativeBases.to')"
class="q-mr-md"
dense
lazy-rules
outlined
rounded
/>
<QBtn
color="primary"
icon-right="archive"
no-caps
@click="downloadCSV()"
/>
</div>
</template>
<template #top-right>
<div class="row justify-start items-center">
<span class="q-mr-md text-results">
{{ rows.length }} {{ t('results') }}
</span>
<QBtn
color="primary"
icon-right="search"
no-caps
class="q-mr-sm"
@click="search()"
/>
<QBtn color="primary" icon-right="refresh" no-caps @click="refresh" />
</div>
</template>
<template #header="props">
<QTr :props="props" class="full-height">
<QTh v-for="col in props.cols" :key="col.name" :props="props">
<div class="column justify-start items-start full-height">
{{ t(`invoiceOut.negativeBases.${col.label}`) }}
<QInput
:class="{
invisible:
col.field === 'isActive' ||
col.field === 'hasToInvoice' ||
col.field === 'isTaxDataChecked',
}"
dense
outlined
rounded
v-model="filter[col.field]"
type="text"
@keyup.enter="search()"
/>
</div>
</QTh>
</QTr>
</template>
<template #body-cell="props">
<QTd :props="props">
<component
:is="tableColumnComponents[props.col.name].component"
class="col-content"
v-bind="tableColumnComponents[props.col.name].props(props)"
@click="tableColumnComponents[props.col.name].event(props)"
>
<template
v-if="
props.col.name !== 'active' &&
props.col.name !== 'hasToInvoice' &&
props.col.name !== 'verifiedData'
"
>{{ props.value }}
</template>
<CustomerDescriptorProxy
v-if="props.col.name === 'clientId'"
:id="selectedCustomerId"
/>
<WorkerDescriptorProxy
v-if="props.col.name === 'comercial'"
:id="selectedWorkerId"
/>
</component>
</QTd>
</template>
</QTable>
</QPage>
</template>
<style lang="scss" scoped>
.col-content {
border-radius: 4px;
padding: 6px 6px 6px 6px;
}
.text-results {
color: var(--vn-label);
}
</style>
<i18n></i18n>

View File

@ -3,6 +3,7 @@ import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInputDate from "components/common/VnInputDate.vue";
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@ -111,35 +112,7 @@ const countries = ref();
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInputDate v-model="params.shipped" :label="t('route.cmr.list.shipped')" />
:label="t('route.cmr.list.shipped')"
v-model="params.shipped"
mask="date"
>
<template #append>
<QIcon name="event" class="cursor-pointer">
<QPopupProxy
cover
transition-show="rotate"
transition-hide="rotate"
>
<QDate v-model="params.shipped" minimal>
<div
class="row items-center justify-end q-gutter-sm"
>
<QBtn
:label="t('globals.close')"
color="primary"
flat
@click="save"
v-close-popup
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
</QList> </QList>

View File

@ -0,0 +1,21 @@
<script setup>
import { useStateStore } from 'stores/useStateStore';
import LeftMenu from 'components/LeftMenu.vue';
import ShelvingDescriptor from 'pages/Shelving/Card/ShelvingDescriptor.vue';
const stateStore = useStateStore();
</script>
<template>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit">
<ShelvingDescriptor />
<QSeparator />
<LeftMenu source="card" />
</QScrollArea>
</QDrawer>
<QPageContainer>
<QPage>
<RouterView></RouterView>
</QPage>
</QPageContainer>
</template>

View File

@ -0,0 +1,71 @@
<script setup>
import { ref, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'components/ui/VnLv.vue';
import useCardDescription from 'composables/useCardDescription';
import WorkerDescriptorProxy from "pages/Worker/Card/WorkerDescriptorProxy.vue";
import ShelvingDescriptorMenu from "pages/Shelving/Card/ShelvingDescriptorMenu.vue";
const $props = defineProps({
id: {
type: Number,
required: false,
default: null,
},
});
const route = useRoute();
const { t } = useI18n();
const entityId = computed(() => {
return $props.id || route.params.id;
});
const filter = {
include: [
{
relation: 'worker',
scope: {
fields: ['id'],
include: {
relation: 'user',
scope: { fields: ['nickname'] },
},
},
},
{ relation: 'parking' },
],
};
const data = ref(useCardDescription());
const setData = (entity) => (data.value = useCardDescription(entity.code, entity.id));
</script>
<template>
<CardDescriptor
module="Shelving"
:url="`Shelvings/${entityId}`"
:filter="filter"
:title="data.title"
:subtitle="data.subtitle"
data-key="Shelvings"
@on-fetch="setData"
>
<template #body="{ entity }">
<VnLv :label="t('shelving.summary.code')" :value="entity.code" />
<VnLv :label="t('shelving.summary.parking')" :value="entity.parking?.code" />
<VnLv v-if="entity.worker" :label="t('shelving.summary.worker')">
<template #value>
<span class="link">
{{ entity.worker?.user?.nickname }}
<WorkerDescriptorProxy :id="entity.worker?.id" />
</span>
</template>
</VnLv>
</template>
<template #menu="{entity}">
<ShelvingDescriptorMenu :shelving="entity" />
</template>
</CardDescriptor>
</template>

View File

@ -0,0 +1,61 @@
<script setup>
import axios from 'axios';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import VnConfirm from 'components/ui/VnConfirm.vue';
const $props = defineProps({
shelving: {
type: Object,
required: true,
},
});
const router = useRouter();
const quasar = useQuasar();
const { t } = useI18n();
function confirmRemove() {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('confirmDeletion'),
message: t('confirmDeletionMessage'),
promise: remove,
},
})
.onOk(async () => await router.push({ name: 'ShelvingList' }));
}
async function remove() {
if (!$props.shelving.value.id) {
return;
}
await axios.delete(`Shelvings/${$props.shelving.value.id}`);
quasar.notify({
message: t('globals.dataDeleted'),
type: 'positive',
});
}
</script>
<template>
<QItem @click="confirmRemove()" v-ripple clickable>
<QItemSection avatar>
<QIcon name="delete" />
</QItemSection>
<QItemSection>{{ t('deleteShelving') }}</QItemSection>
</QItem>
</template>
<i18n>
{
"en": {
"deleteShelving": "Delete Shelving"
},
"es": {
"deleteShelving": "Eliminar carro"
}
}
</i18n>

View File

@ -0,0 +1,15 @@
<script setup>
import ShelvingDescriptor from "pages/Shelving/Card/ShelvingDescriptor.vue";
const $props = defineProps({
id: {
type: Number,
required: true,
},
});
</script>
<template>
<QPopupProxy>
<ShelvingDescriptor v-if="$props.id" :id="$props.id" />
</QPopupProxy>
</template>

View File

@ -0,0 +1,120 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'components/ui/VnFilterPanel.vue';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const emit = defineEmits(['search']);
const workers = ref();
const parkings = ref();
function setWorkers(data) {
workers.value = data;
}
function setParkings(data) {
parkings.value = data;
}
</script>
<template>
<FetchData
url="Parkings"
:filter="{ fields: ['id', 'code'] }"
sort-by="code ASC"
@on-fetch="setParkings"
auto-load
/>
<FetchData
url="Workers/activeWithInheritedRole"
:filter="{ where: { role: 'salesPerson' } }"
sort-by="firstName ASC"
@on-fetch="setWorkers"
auto-load
/>
<VnFilterPanel :data-key="props.dataKey" :search-button="true" @search="emit('search')">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params }">
<QList dense>
<QItem class="q-my-sm">
<QItemSection v-if="!parkings">
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="parkings">
<QSelect
dense
outlined
rounded
:label="t('params.parkingFk')"
v-model="params.parkingFk"
:options="parkings"
option-value="id"
option-label="code"
emit-value
map-options
use-input
:input-debounce="0"
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection v-if="!workers">
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="workers">
<QSelect
dense
outlined
rounded
:label="t('params.userFk')"
v-model="params.userFk"
:options="workers"
option-value="id"
option-label="name"
emit-value
map-options
use-input
:input-debounce="0"
/>
</QItemSection>
</QItem>
<QItem class="q-mb-md">
<QItemSection>
<QCheckbox
v-model="params.isRecyclable"
:label="t('params.isRecyclable')"
toggle-indeterminate
/>
</QItemSection>
</QItem>
</QList>
</template>
</VnFilterPanel>
</template>
<i18n>
en:
params:
parkingFk: Parking
userFk: Worker
isRecyclable: Recyclable
es:
params:
parkingFk: Parking
userFk: Trabajador
isRecyclable: Reciclable
</i18n>

View File

@ -0,0 +1,126 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import VnRow from 'components/ui/VnRow.vue';
import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue';
const { t } = useI18n();
const route = useRoute();
const shelvingId = route.params?.id || null;
const isNew = Boolean(!shelvingId);
const defaultInitialData = {
parkingFk: null,
priority: 0,
code: null,
isRecyclable: false,
}
const parkingFilter = { fields: ['id', 'code'] };
const parkingList = ref([]);
const parkingListCopy = ref([]);
const setParkingList = (data) => {
parkingList.value = data;
parkingListCopy.value = data;
};
const parkingSelectFilter = {
options: parkingList,
filterFn: (options, value) => {
const search = value.trim().toLowerCase();
if (!search || search === '') {
return parkingListCopy.value;
}
return options.value.filter((option) =>
option.code.toLowerCase().startsWith(search)
);
},
};
const shelvingFilter = {
include: [
{
relation: 'worker',
scope: {
fields: ['id'],
include: {
relation: 'user',
scope: { fields: ['nickname'] },
},
},
},
{ relation: 'parking' },
],
};
</script>
<template>
<QToolbar class="bg-vn-dark justify-end">
<div id="st-data"></div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<FetchData
url="Parkings"
:filter="parkingFilter"
@on-fetch="setParkingList"
auto-load
/>
<FormModel
:url="isNew ? null : `Shelvings/${shelvingId}`"
:url-create="isNew ? 'Shelvings' : null"
:observe-form-changes="!isNew"
:filter="shelvingFilter"
model="shelving"
:auto-load="!isNew"
:form-initial-data="defaultInitialData"
>
<template #form="{ data, validate, filter }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QInput
v-model="data.code"
:label="t('shelving.basicData.code')"
:rules="validate('Shelving.code')"
/>
</div>
<div class="col">
<QSelect
v-model="data.parkingFk"
:options="parkingList"
option-value="id"
option-label="code"
emit-value
:label="t('shelving.basicData.parking')"
map-options
use-input
@filter="
(value, update) => filter(value, update, parkingSelectFilter)
"
:rules="validate('Shelving.parkingFk')"
:input-debounce="0"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QInput
v-model="data.priority"
:label="t('shelving.basicData.priority')"
:rules="validate('Shelving.priority')"
/>
</div>
<div class="col">
<QCheckbox
v-model="data.isRecyclable"
:label="t('shelving.basicData.recyclable')"
:rules="validate('Shelving.isRecyclable')"
/>
</div>
</VnRow>
</template>
</FormModel>
</template>

View File

@ -0,0 +1,6 @@
<script setup>
import VnLog from 'src/components/common/VnLog.vue';
</script>
<template>
<VnLog model="Shelving" url="/ShelvingLogs"></VnLog>
</template>

View File

@ -0,0 +1,20 @@
<script setup>
import VnSearchbar from 'components/ui/VnSearchbar.vue';
import {useI18n} from "vue-i18n";
const { t } = useI18n();
</script>
<template>
<VnSearchbar
data-key="ShelvingList"
:label="t('Search shelving')"
:info="t('You can search by search reference')"
/>
</template>
<style scoped lang="scss"></style>
<i18n>
es:
Search shelving: Buscar carros
You can search by shelving reference: Puedes buscar por referencia del carro
</i18n>

View File

@ -0,0 +1,110 @@
<script setup>
import { computed, onMounted, onUnmounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore';
import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'components/ui/VnLv.vue';
import ShelvingFilter from 'pages/Shelving/Card/ShelvingFilter.vue';
const $props = defineProps({
id: {
type: Number,
default: 0,
},
});
const route = useRoute();
const stateStore = useStateStore();
const router = useRouter();
const { t } = useI18n();
const entityId = computed(() => $props.id || route.params.id);
const isDialog = Boolean($props.id);
const hideRightDrawer = () => {
if (!isDialog) {
stateStore.rightDrawer = false;
}
}
onMounted(hideRightDrawer);
onUnmounted(hideRightDrawer);
const filter = {
include: [
{
relation: 'worker',
scope: {
fields: ['id'],
include: {
relation: 'user',
scope: { fields: ['nickname'] },
},
},
},
{ relation: 'parking' },
],
};
</script>
<template>
<template v-if="!isDialog && stateStore.isHeaderMounted()">
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
</template>
<div class="q-pa-md">
<CardSummary ref="summary" :url="`Shelvings/${entityId}`" :filter="filter">
<template #header="{ entity }">
<div>{{ entity.code }}</div>
</template>
<template #body="{ entity }">
<QCard class="vn-one">
<div class="header">
{{ t('shelving.pageTitles.basicData') }}
</div>
<VnLv :label="t('shelving.summary.code')" :value="entity.code" />
<VnLv
:label="t('shelving.summary.parking')"
:value="entity.parking?.code"
/>
<VnLv
:label="t('shelving.summary.priority')"
:value="entity.priority"
/>
<VnLv
:label="t('shelving.summary.worker')"
:value="entity.worker?.user?.nickname"
/>
<VnLv
:label="t('shelving.summary.recyclable')"
:value="entity.isRecyclable"
/>
</QCard>
</template>
</CardSummary>
</div>
<QDrawer
v-if="!isDialog"
v-model="stateStore.rightDrawer"
side="right"
:width="256"
show-if-above
>
<QScrollArea class="fit text-grey-8">
<ShelvingFilter
data-key="ShelvingList"
@search="router.push({ name: 'ShelvingList' })"
/>
</QScrollArea>
</QDrawer>
</template>

View File

@ -0,0 +1,29 @@
<script setup>
import { useDialogPluginComponent } from 'quasar';
import ShelvingSummary from "pages/Shelving/Card/ShelvingSummary.vue";
const $props = defineProps({
id: {
type: Number,
required: true,
},
});
defineEmits([...useDialogPluginComponent.emits]);
const { dialogRef, onDialogHide } = useDialogPluginComponent();
</script>
<template>
<QDialog ref="dialogRef" @hide="onDialogHide">
<ShelvingSummary v-if="$props.id" :id="$props.id" />
</QDialog>
</template>
<style lang="scss">
.q-dialog .summary .header {
position: sticky;
z-index: $z-max;
top: 0;
}
</style>

View File

@ -0,0 +1,138 @@
<script setup>
import VnPaginate from 'components/ui/VnPaginate.vue';
import { useStateStore } from 'stores/useStateStore';
import { useI18n } from 'vue-i18n';
import { onMounted, onUnmounted } from 'vue';
import CardList from 'components/ui/CardList.vue';
import VnLv from 'components/ui/VnLv.vue';
import { useQuasar } from 'quasar';
import { useRouter } from 'vue-router';
import ShelvingFilter from 'pages/Shelving/Card/ShelvingFilter.vue';
import ShelvingSummaryDialog from 'pages/Shelving/Card/ShelvingSummaryDialog.vue';
import ShelvingSearchbar from 'pages/Shelving/Card/ShelvingSearchbar.vue';
const stateStore = useStateStore();
const router = useRouter();
const quasar = useQuasar();
const { t } = useI18n();
const filter = {
include: [{ relation: 'parking' }],
};
onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false));
function navigate(id) {
router.push({ path: `/shelving/${id}` });
}
function viewSummary(id) {
quasar.dialog({
component: ShelvingSummaryDialog,
componentProps: {
id,
},
});
}
function exprBuilder(param, value) {
switch (param) {
case 'search':
return { code: { like: `%${value}%` } };
case 'parkingFk':
case 'userFk':
case 'isRecyclable':
return { [param]: value };
}
}
</script>
<template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<ShelvingSearchbar />
</Teleport>
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
</template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<ShelvingFilter data-key="ShelvingList" />
</QScrollArea>
</QDrawer>
<QPage class="column items-center q-pa-md">
<div class="card-list">
<VnPaginate
data-key="ShelvingList"
url="Shelvings"
:filter="filter"
:expr-builder="exprBuilder"
auto-load
>
<template #body="{ rows }">
<CardList
v-for="row of rows"
:key="row.id"
:id="row.id"
:title="row.code"
@click="navigate(row.id)"
>
<template #list-items>
<VnLv
:label="t('shelving.list.parking')"
:title-label="t('shelving.list.parking')"
:value="row.parking?.code"
/>
<VnLv
:label="t('shelving.list.priority')"
:value="row?.priority"
/>
</template>
<template #actions>
<QBtn
:label="t('components.smartCard.openCard')"
@click.stop="navigate(row.id)"
class="bg-vn-dark"
outline
/>
<QBtn
:label="t('components.smartCard.openSummary')"
@click.stop="viewSummary(row.id)"
color="primary"
style="margin-top: 15px"
/>
</template>
</CardList>
</template>
</VnPaginate>
</div>
<QPageSticky :offset="[20, 20]">
<RouterLink :to="{ name: 'ShelvingCreate' }">
<QBtn fab icon="add" color="primary" />
<QTooltip>
{{ t('shelving.list.newShelving') }}
</QTooltip>
</RouterLink>
</QPageSticky>
</QPage>
</template>
<style lang="scss" scoped>
.card-list {
width: 100%;
max-width: 60em;
}
</style>

View File

@ -0,0 +1,17 @@
<script setup>
import { useStateStore } from 'stores/useStateStore';
import LeftMenu from 'src/components/LeftMenu.vue';
const stateStore = useStateStore();
</script>
<template>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit text-grey-8">
<LeftMenu />
</QScrollArea>
</QDrawer>
<QPageContainer>
<RouterView></RouterView>
</QPageContainer>
</template>

View File

@ -0,0 +1,63 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
const stateStore = useStateStore();
const { t } = useI18n();
</script>
<template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<VnSearchbar
data-key="SuppliersList"
:limit="20"
:label="t('Search suppliers')"
/>
</Teleport>
</template>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit">
<!-- Aca iría left menu y descriptor -->
</QScrollArea>
</QDrawer>
<QPageContainer>
<QPage>
<QToolbar class="bg-vn-dark justify-end">
<div id="st-data"></div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<div class="q-pa-md"><RouterView></RouterView></div>
</QPage>
</QPageContainer>
</template>
<style lang="scss">
.q-scrollarea__content {
max-width: 100%;
}
</style>
<style lang="scss" scoped>
.descriptor {
max-width: 256px;
h5 {
margin: 0 15px;
}
.header {
display: flex;
justify-content: space-between;
}
.q-card__actions {
justify-content: center;
}
#descriptor-skeleton .q-card__actions {
justify-content: space-between;
}
}
</style>

View File

@ -0,0 +1,94 @@
<script setup>
import { ref, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import useCardDescription from 'src/composables/useCardDescription';
const $props = defineProps({
id: {
type: Number,
required: false,
default: null,
},
});
const route = useRoute();
const { t } = useI18n();
const filter = {
fields: [
'id',
'name',
'nickname',
'nif',
'payMethodFk',
'payDemFk',
'payDay',
'isActive',
'isSerious',
'isTrucker',
'account',
],
include: [
{
relation: 'payMethod',
scope: {
fields: ['id', 'name'],
},
},
{
relation: 'payDem',
scope: {
fields: ['id', 'payDem'],
},
},
{
relation: 'client',
scope: {
fields: ['id', 'fi'],
},
},
],
};
const entityId = computed(() => {
return $props.id || route.params.id;
});
const data = ref(useCardDescription());
const setData = (entity) => {
data.value = useCardDescription(entity.ref, entity.id);
};
</script>
<template>
<CardDescriptor
module="Supplier"
:url="`Suppliers/${entityId}`"
:title="data.title"
:subtitle="data.subtitle"
:filter="filter"
@on-fetch="setData"
data-key="Supplier"
>
<template #body="{ entity }">
<VnLv :label="t('supplier.summary.taxNumber')" :value="entity.nif" />
<VnLv label="Alias" :value="entity.nickname" />
<VnLv
:label="t('supplier.summary.payMethod')"
:value="entity.payMethod.name"
/>
<VnLv
:label="t('supplier.summary.payDeadline')"
:value="entity.payDem.payDem"
/>
<VnLv :label="t('supplier.summary.payDay')" :value="entity.payDay" />
<VnLv :label="t('supplier.summary.account')" :value="entity.account" />
</template>
</CardDescriptor>
</template>
<i18n>
</i18n>

View File

@ -0,0 +1,16 @@
<script setup>
import SupplierDescriptor from './SupplierDescriptor.vue';
const $props = defineProps({
id: {
type: Number,
required: true,
},
});
</script>
<template>
<QPopupProxy>
<SupplierDescriptor v-if="$props.id" :id="$props.id" />
</QPopupProxy>
</template>

View File

@ -0,0 +1,188 @@
<script setup>
import { onMounted, ref, computed, onUpdated } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CardSummary from 'components/ui/CardSummary.vue';
import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import { getUrl } from 'src/composables/getUrl';
import { useRole } from 'src/composables/useRole';
import { dashIfEmpty } from 'src/filters';
onUpdated(() => summaryRef.value.fetch());
const route = useRoute();
const roleState = useRole();
const { t } = useI18n();
const $props = defineProps({
id: {
type: Number,
required: false,
default: null,
},
});
const entityId = computed(() => $props.id || route.params.id);
const summaryRef = ref();
const supplier = ref();
const supplierUrl = ref();
onMounted(async () => {
await roleState.fetch();
supplierUrl.value = (await getUrl('supplier/')) + entityId.value;
});
async function setData(data) {
if (data) {
supplier.value = data;
}
}
const isAdministrative = computed(() => {
return roleState.hasAny(['administrative']);
});
</script>
<template>
<CardSummary
ref="summaryRef"
:url="`Suppliers/${entityId}/getSummary`"
@on-fetch="(data) => setData(data)"
>
<template #header-left>
<a v-if="isAdministrative" class="header link" :href="supplierUrl">
<QIcon name="open_in_new" color="white" size="25px" />
</a>
</template>
<template #header>
<span>{{ supplier.name }} - {{ supplier.id }}</span>
</template>
<template #body>
<QCard class="vn-one">
<a v-if="isAdministrative" class="header link" :href="supplierUrl">
{{ t('globals.summary.basicData') }}
<QIcon name="open_in_new" color="primary" />
</a>
<span v-else> {{ t('globals.summary.basicData') }}</span>
<VnLv label="Id" :value="supplier.id" />
<VnLv label="Alias" :value="supplier.nickname" />
<VnLv :label="t('supplier.summary.responsible')">
<template #value>
<span class="link">
{{ dashIfEmpty(supplier.worker?.user?.nickname) }}
<WorkerDescriptorProxy
v-if="supplier.worker?.user?.id"
:id="supplier.worker?.user?.id"
/>
</span>
</template>
</VnLv>
<VnLv :label="t('supplier.summary.notes')" class="q-mb-xs">
<template #value>
<span> {{ dashIfEmpty(supplier.note) }} </span>
</template>
</VnLv>
<VnLv :label="t('supplier.summary.verified')" class="q-mb-xs">
<template #value>
<QCheckbox
v-model="supplier.isSerious"
dense
disable
class="full-width q-mb-xs"
/>
</template>
</VnLv>
<VnLv :label="t('supplier.summary.isActive')" class="q-mb-xs">
<template #value>
<QCheckbox
v-model="supplier.isActive"
dense
disable
class="full-width q-mb-xs"
/>
</template>
</VnLv>
</QCard>
<QCard class="vn-one">
<a v-if="isAdministrative" class="header link" :href="supplierUrl">
{{ t('supplier.summary.billingData') }}
<QIcon name="open_in_new" color="primary" />
</a>
<span v-else> {{ t('supplier.summary.billingData') }}</span>
<VnLv
:label="t('supplier.summary.payMethod')"
:value="supplier.payMethod?.name"
dash
/>
<VnLv
:label="t('supplier.summary.payDeadline')"
:value="supplier.payDem?.payDem"
dash
/>
<VnLv :label="t('supplier.summary.payDay')" :value="supplier.payDay" />
<VnLv :label="t('supplier.summary.account')" :value="supplier.account" />
</QCard>
<QCard class="vn-one">
<a v-if="isAdministrative" class="header link" :href="supplierUrl">
{{ t('supplier.summary.fiscalData') }}
<QIcon name="open_in_new" color="primary" />
</a>
<span v-else> {{ t('supplier.summary.fiscalData') }}</span>
<VnLv
:label="t('supplier.summary.sageTaxType')"
:value="supplier.sageTaxType?.vat"
dash
/>
<VnLv
:label="t('supplier.summary.sageTransactionType')"
:value="supplier.sageTransactionType?.transaction"
dash
/>
<VnLv
:label="t('supplier.summary.sageWithholding')"
:value="supplier.sageWithholding?.withholding"
dash
/>
<VnLv
:label="t('supplier.summary.supplierActivity')"
:value="supplier.supplierActivity?.name"
dash
/>
<VnLv
:label="t('supplier.summary.healthRegister')"
:value="supplier.healthRegister"
/>
</QCard>
<QCard class="vn-one">
<a v-if="isAdministrative" class="header link" :href="supplierUrl">
{{ t('supplier.summary.fiscalAddress') }}
<QIcon name="open_in_new" color="primary" />
</a>
<span v-else> {{ t('supplier.summary.fiscalAddress') }}</span>
<VnLv :label="t('supplier.summary.socialName')" :value="supplier.name" />
<VnLv :label="t('supplier.summary.taxNumber')" :value="supplier.nif" />
<VnLv :label="t('supplier.summary.street')" :value="supplier.street" />
<VnLv :label="t('supplier.summary.city')" :value="supplier.city" />
<VnLv
:label="t('supplier.summary.postCode')"
:value="supplier.postCode"
/>
<VnLv
:label="t('supplier.summary.province')"
:value="supplier.province?.name"
dash
/>
<VnLv
:label="t('supplier.summary.country')"
:value="supplier.country?.country"
dash
/>
</QCard>
</template>
</CardSummary>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,29 @@
<script setup>
import { useDialogPluginComponent } from 'quasar';
import SupplierSummary from './SupplierSummary.vue';
const $props = defineProps({
id: {
type: Number,
required: true,
},
});
defineEmits([...useDialogPluginComponent.emits]);
const { dialogRef, onDialogHide } = useDialogPluginComponent();
</script>
<template>
<QDialog ref="dialogRef" @hide="onDialogHide">
<SupplierSummary v-if="$props.id" :id="$props.id" />
</QDialog>
</template>
<style lang="scss">
.q-dialog .summary .header {
position: sticky;
z-index: $z-max;
top: 0;
}
</style>

View File

@ -0,0 +1,61 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { reactive } from 'vue';
import { useStateStore } from 'stores/useStateStore';
import VnSearchbar from 'components/ui/VnSearchbar.vue';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
const { t } = useI18n();
const stateStore = useStateStore();
const newSupplierForm = reactive({
name: null,
});
</script>
<template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<VnSearchbar
data-key="SuppliersList"
:limit="20"
:label="t('Search suppliers')"
/>
</Teleport>
</template>
<QPage>
<QToolbar class="bg-vn-dark justify-end">
<div id="st-data"></div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<FormModel
url-create="Suppliers/newSupplier"
model="supplier"
:form-initial-data="newSupplierForm"
>
<template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QInput
v-model="data.name"
:label="t('supplier.create.supplierName')"
/>
</div>
</VnRow>
</template>
</FormModel>
</QPage>
</template>
<style lang="scss" scoped>
.card {
display: flex;
justify-content: center;
width: 100%;
background-color: var(--vn-dark);
padding: 40px;
}
</style>

View File

@ -0,0 +1,111 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore';
import { useRouter } from 'vue-router';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import CardList from 'src/components/ui/CardList.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import { useQuasar } from 'quasar';
import SupplierSummaryDialog from './Card/SupplierSummaryDialog.vue';
const stateStore = useStateStore();
const router = useRouter();
const quasar = useQuasar();
const { t } = useI18n();
function navigate(id) {
router.push({ path: `/supplier/${id}` });
}
const redirectToCreateView = () => {
router.push({ name: 'SupplierCreate' });
};
const viewSummary = (id) => {
quasar.dialog({
component: SupplierSummaryDialog,
componentProps: {
id,
},
});
};
</script>
<template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<VnSearchbar
data-key="SuppliersList"
:limit="20"
:label="t('Search suppliers')"
/>
</Teleport>
</template>
<QPage class="column items-center q-pa-md">
<div class="card-list">
<VnPaginate data-key="SuppliersList" url="Suppliers/filter" auto-load>
<template #body="{ rows }">
<CardList
v-for="row of rows"
:key="row.id"
:title="row.socialName"
:id="row.id"
@click="navigate(row.id)"
>
<template #list-items>
<VnLv label="NIF/CIF" :value="row.nif" />
<VnLv label="Alias" :value="row.nickname" />
<VnLv
:label="t('supplier.list.payMethod')"
:value="row.payMethod"
/>
<VnLv
:label="t('supplier.list.payDeadline')"
:title-label="t('invoiceOut.list.created')"
:value="row.payDem"
/>
<VnLv
:label="t('supplier.list.payDay')"
:value="row.payDay"
/>
<VnLv
:label="t('supplier.list.account')"
:value="row.account"
/>
</template>
<template #actions>
<QBtn
:label="t('components.smartCard.openSummary')"
@click.stop="viewSummary(row.id)"
color="primary"
/>
</template>
</CardList>
</template>
</VnPaginate>
</div>
<QPageSticky :offset="[20, 20]">
<QBtn fab icon="add" color="primary" @click="redirectToCreateView()" />
<QTooltip>
{{ t('supplier.list.newSupplier') }}
</QTooltip>
</QPageSticky>
</QPage>
</template>
<style lang="scss" scoped>
.card-list {
width: 100%;
max-width: 60em;
}
</style>
<i18n>
en:
Search suppliers: Search suppliers
es:
Search suppliers: Buscar proveedores
</i18n>

View File

@ -0,0 +1,17 @@
<script setup>
import { useStateStore } from 'stores/useStateStore';
import LeftMenu from 'src/components/LeftMenu.vue';
const stateStore = useStateStore();
</script>
<template>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit text-grey-8">
<LeftMenu />
</QScrollArea>
</QDrawer>
<QPageContainer>
<RouterView></RouterView>
</QPageContainer>
</template>

View File

@ -208,7 +208,7 @@ async function changeState(value) {
:label="t('ticket.summary.landed')" :label="t('ticket.summary.landed')"
:value="toDate(ticket.landed)" :value="toDate(ticket.landed)"
/> />
<VnLv :label="t('ticket.summary.packages')" :value="ticket.packages" /> <VnLv :label="t('global.packages')" :value="ticket.packages" />
<VnLv :value="ticket.address.phone"> <VnLv :value="ticket.address.phone">
<template #label> <template #label>
{{ t('ticket.summary.consigneePhone') }} {{ t('ticket.summary.consigneePhone') }}
@ -276,7 +276,7 @@ async function changeState(value) {
<QTh auto-width>{{ t('ticket.summary.description') }}</QTh> <QTh auto-width>{{ t('ticket.summary.description') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.price') }}</QTh> <QTh auto-width>{{ t('ticket.summary.price') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.discount') }}</QTh> <QTh auto-width>{{ t('ticket.summary.discount') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.amount') }}</QTh> <QTh auto-width>{{ t('globals.amount') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.packing') }}</QTh> <QTh auto-width>{{ t('ticket.summary.packing') }}</QTh>
</QTr> </QTr>
</template> </template>
@ -400,7 +400,7 @@ async function changeState(value) {
v-if="ticket.packagings.length > 0 || ticket.services.length > 0" v-if="ticket.packagings.length > 0 || ticket.services.length > 0"
> >
<a class="header link" :href="ticketUrl + 'package'"> <a class="header link" :href="ticketUrl + 'package'">
{{ t('ticket.summary.packages') }} {{ t('globals.packages') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" color="primary" />
</a> </a>
<QTable :rows="ticket.packagings" flat> <QTable :rows="ticket.packagings" flat>
@ -431,7 +431,7 @@ async function changeState(value) {
<QTh auto-width>{{ t('ticket.summary.description') }}</QTh> <QTh auto-width>{{ t('ticket.summary.description') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.price') }}</QTh> <QTh auto-width>{{ t('ticket.summary.price') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.taxClass') }}</QTh> <QTh auto-width>{{ t('ticket.summary.taxClass') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.amount') }}</QTh> <QTh auto-width>{{ t('globals.amount') }}</QTh>
</QTr> </QTr>
</template> </template>
<template #body="props"> <template #body="props">

View File

@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import toDateString from 'filters/toDateString'; import toDateString from 'filters/toDateString';
import VnInputDate from "components/common/VnInputDate.vue";
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@ -71,69 +72,10 @@ const warehouses = ref();
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput v-model="params.from" :label="t('From')" mask="date"> <VnInputDate v-model="params.from" :label="t('From')" />
<template #append>
<QIcon name="event" class="cursor-pointer">
<QPopupProxy
cover
transition-show="scale"
transition-hide="scale"
>
<QDate v-model="params.from" landscape>
<div
class="row items-center justify-end q-gutter-sm"
>
<QBtn
:label="t('globals.cancel')"
color="primary"
flat
v-close-popup
/>
<QBtn
:label="t('globals.confirm')"
color="primary"
flat
v-close-popup
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</QItemSection> </QItemSection>
<QItemSection> <QItemSection>
<QInput v-model="params.to" :label="t('To')" mask="date"> <VnInputDate v-model="params.to" :label="t('To')" />
<template #append>
<QIcon name="event" class="cursor-pointer">
<QPopupProxy
cover
transition-show="scale"
transition-hide="scale"
>
<QDate v-model="params.to" landscape>
<div
class="row items-center justify-end q-gutter-sm"
>
<QBtn
:label="t('globals.cancel')"
color="primary"
flat
v-close-popup
/>
<QBtn
:label="t('globals.confirm')"
color="primary"
flat
@click="save"
v-close-popup
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>

View File

@ -87,6 +87,7 @@ function viewSummary(id) {
v-for="row of rows" v-for="row of rows"
:key="row.id" :key="row.id"
:id="row.id" :id="row.id"
:title="`${row.nickname} (${row.id})`"
@click="navigate(row.id)" @click="navigate(row.id)"
> >
<template #list-items> <template #list-items>
@ -120,11 +121,11 @@ function viewSummary(id) {
/> />
</template> </template>
<template #actions> <template #actions>
<QBtn flat icon="preview" @click.stop="viewSummary(row.id)"> <QBtn
<QTooltip> :label="t('components.smartCard.openSummary')"
{{ t('components.smartCard.openSummary') }} @click.stop="viewSummary(row.id)"
</QTooltip> color="primary"
</QBtn> />
</template> </template>
</CardList> </CardList>
</template> </template>

View File

@ -0,0 +1,51 @@
<script setup>
import { useStateStore } from 'stores/useStateStore';
const stateStore = useStateStore();
</script>
<template>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit">
<!-- Aca iría left menu y descriptor -->
</QScrollArea>
</QDrawer>
<QPageContainer>
<QPage>
<QToolbar class="bg-vn-dark justify-end">
<div id="st-data"></div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<div class="q-pa-md"><RouterView></RouterView></div>
</QPage>
</QPageContainer>
</template>
<style lang="scss">
.q-scrollarea__content {
max-width: 100%;
}
</style>
<style lang="scss" scoped>
.descriptor {
max-width: 256px;
h5 {
margin: 0 15px;
}
.header {
display: flex;
justify-content: space-between;
}
.q-card__actions {
justify-content: center;
}
#descriptor-skeleton .q-card__actions {
justify-content: space-between;
}
}
</style>

View File

@ -0,0 +1,80 @@
<script setup>
import { ref, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { toDate } from 'src/filters';
import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import useCardDescription from 'src/composables/useCardDescription';
const $props = defineProps({
id: {
type: Number,
required: false,
default: null,
},
});
const route = useRoute();
const { t } = useI18n();
const filter = {
fields: [
'id',
'ref',
'shipped',
'landed',
'totalEntries',
'warehouseInFk',
'warehouseOutFk',
'cargoSupplierFk',
],
include: [
{
relation: 'warehouseIn',
scope: {
fields: ['name'],
},
},
{
relation: 'warehouseOut',
scope: {
fields: ['name'],
},
},
],
};
const entityId = computed(() => {
return $props.id || route.params.id;
});
const data = ref(useCardDescription());
const setData = (entity) => {
data.value = useCardDescription(entity.ref, entity.id);
};
</script>
<template>
<CardDescriptor
module="Travel"
:url="`Travels/${entityId}`"
:title="data.title"
:subtitle="data.subtitle"
:filter="filter"
@on-fetch="setData"
data-key="travelData"
>
<template #body="{ entity }">
<VnLv :label="t('globals.wareHouseIn')" :value="entity.warehouseIn.name" />
<VnLv :label="t('globals.wareHouseOut')" :value="entity.warehouseOut.name" />
<VnLv :label="t('globals.shipped')" :value="toDate(entity.shipped)" />
<VnLv :label="t('globals.landed')" :value="toDate(entity.landed)" />
<VnLv :label="t('globals.totalEntries')" :value="entity.totalEntries" />
</template>
</CardDescriptor>
</template>
<i18n>
</i18n>

View File

@ -0,0 +1,16 @@
<script setup>
import TravelDescriptor from './TravelDescriptor.vue';
const $props = defineProps({
id: {
type: Number,
required: true,
},
});
</script>
<template>
<QPopupProxy>
<TravelDescriptor v-if="$props.id" :id="$props.id" />
</QPopupProxy>
</template>

View File

@ -0,0 +1,323 @@
<script setup>
import { onMounted, ref, computed, onUpdated } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import { getUrl } from 'src/composables/getUrl';
import { toDate } from 'src/filters';
import travelService from 'src/services/travel.service';
import { QCheckbox, QIcon } from 'quasar';
import { toCurrency } from 'filters/index';
import VnRow from 'components/ui/VnRow.vue';
onUpdated(() => summaryRef.value.fetch());
const route = useRoute();
const { t } = useI18n();
const $props = defineProps({
id: {
type: Number,
required: false,
default: null,
},
});
const router = useRouter();
const entityId = computed(() => $props.id || route.params.id);
const entries = ref([]);
const summaryRef = ref();
const travel = ref();
const travelUrl = ref();
onMounted(async () => {
travelUrl.value = (await getUrl('travel/')) + entityId.value;
});
const cloneTravel = () => {
const stringifiedTravelData = JSON.stringify(travel.value);
redirectToCreateView(stringifiedTravelData);
};
const headerMenuOptions = [
{ name: t('travel.summary.cloneShipping'), action: cloneTravel },
{ name: t('travel.summary.CloneTravelAndEntries'), action: null },
{ name: t('travel.summary.AddEntry'), action: null },
];
const tableColumnComponents = {
isConfirmed: {
component: () => QCheckbox,
props: (prop) => ({
disable: true,
'model-value': Boolean(prop.value),
}),
},
id: {
component: () => 'span',
props: () => {},
event: () => openEntryDescriptor(),
},
supplierName: {
component: () => 'span',
props: () => {},
event: () => {},
},
reference: {
component: () => 'span',
props: () => {},
event: () => {},
},
freightValue: {
component: () => 'span',
props: () => {},
event: () => {},
},
packageValue: {
component: () => 'span',
props: () => {},
event: () => {},
},
cc: {
component: () => 'span',
props: () => {},
event: () => {},
},
pallet: {
component: () => 'span',
props: () => {},
event: () => {},
},
m3: {
component: () => 'span',
props: () => {},
event: () => {},
},
observation: {
component: (props) => (props.value ? QIcon : null),
props: () => ({ name: 'insert_drive_file', color: 'primary', size: '25px' }),
},
};
const entriesTableColumns = computed(() => {
return [
{
label: t('travel.summary.confirmed'),
field: 'isConfirmed',
name: 'isConfirmed',
align: 'left',
},
{
label: t('travel.summary.entryId'),
field: 'id',
name: 'id',
align: 'left',
},
{
label: t('supplier.pageTitles.supplier'),
field: 'supplierName',
name: 'supplierName',
align: 'left',
},
{
label: t('globals.reference'),
field: 'reference',
name: 'reference',
align: 'left',
},
{
label: t('travel.summary.freight'),
field: 'freightValue',
name: 'freightValue',
align: 'left',
format: (val) => {
return toCurrency(val);
},
},
{
label: t('travel.summary.package'),
field: 'packageValue',
name: 'packageValue',
align: 'left',
format: (val) => {
return toCurrency(val);
},
},
{ label: 'CC', field: 'cc', name: 'cc', align: 'left' },
{ label: 'Pallet', field: 'pallet', name: 'pallet', align: 'left' },
{ label: 'm³', field: 'm3', name: 'm3', align: 'left' },
{
label: '',
field: 'observation',
name: 'observation',
align: 'left',
toolTip: 'Observation three',
},
];
});
const entriesTableRows = computed(() => {
if (!entries.value && !entries.value.length > 0) return [];
return entries.value;
});
async function setTravelData(data) {
if (data) {
travel.value = data;
const entriesResponse = await travelService.getTravelEntries(travel.value.id);
if (entriesResponse.data) entries.value = entriesResponse.data;
}
}
const redirectToCreateView = (queryParams) => {
router.push({ name: 'TravelCreate', query: { travelData: queryParams } });
};
const openEntryDescriptor = () => {};
</script>
<template>
<CardSummary
ref="summaryRef"
:url="`Travels/${entityId}/getTravel`"
@on-fetch="(data) => setTravelData(data)"
>
<template #header-left>
<a class="header link" :href="travelUrl">
<QIcon name="open_in_new" color="white" size="25px" />
</a>
</template>
<template #header>
<span>{{ travel.ref }} - {{ travel.id }}</span>
</template>
<template #header-right>
<QBtn color="white" dense flat icon="more_vert" round size="md">
<QTooltip>
{{ t('components.cardDescriptor.moreOptions') }}
</QTooltip>
<QMenu>
<QList dense v-for="option in headerMenuOptions" :key="option">
<QItem v-ripple clickable @click="option.action">
{{ option.name }}
</QItem>
</QList>
</QMenu>
</QBtn>
</template>
<template #body>
<QCard class="vn-one row justify-around" style="min-width: 100%">
<VnRow>
<div class="col">
<VnLv
:label="t('globals.shipped')"
:value="toDate(travel.shipped)"
/>
</div>
<div class="col">
<VnLv
:label="t('globals.wareHouseOut')"
:value="travel.warehouseOut?.name"
/>
</div>
<div class="col">
<VnLv :label="t('travel.summary.delivered')" class="q-mb-xs">
<template #value>
<QCheckbox
v-model="travel.isDelivered"
disable
dense
class="full-width q-my-xs"
/>
</template>
</VnLv>
</div>
</VnRow>
<VnRow>
<div class="col">
<VnLv
:label="t('globals.landed')"
:value="toDate(travel.landed)"
/>
</div>
<div class="col">
<VnLv
:label="t('globals.wareHouseIn')"
:value="travel.warehouseIn?.name"
/>
</div>
<div class="col">
<VnLv :label="t('travel.summary.received')" class="q-mb-xs">
<template #value>
<QCheckbox
v-model="travel.isReceived"
disable
dense
class="full-width q-mb-xs"
/>
</template>
</VnLv>
</div>
</VnRow>
<VnRow>
<div class="col">
<VnLv :label="t('globals.agency')" :value="travel.agency?.name" />
</div>
<div class="col">
<VnLv :label="t('globals.reference')" :value="travel.ref" />
</div>
<div class="col">
<VnLv label="m³" :value="travel.m3" />
</div>
<div class="col">
<VnLv :label="t('globals.totalEntries')" :value="travel.m3" />
</div>
</VnRow>
</QCard>
<QCard class="vn-two" v-if="entriesTableRows.length > 0">
<a class="header" :href="travelUrl + 'entry'">
{{ t('travel.summary.entries') }}
<QIcon name="open_in_new" color="primary" />
</a>
<QTable
:rows="entriesTableRows"
:columns="entriesTableColumns"
hide-bottom
row-key="id"
class="full-width q-mt-md"
>
<template #body-cell="props">
<QTd :props="props">
<component
:is="
tableColumnComponents[props.col.name].component(props)
"
v-bind="
tableColumnComponents[props.col.name].props(props)
"
@click="
tableColumnComponents[props.col.name].event(props)
"
class="col-content"
>
<template
v-if="
props.col.name !== 'observation' &&
props.col.name !== 'isConfirmed'
"
>{{ props.value }}</template
>
<QTooltip v-if="props.col.toolTip">{{
props.col.toolTip
}}</QTooltip>
</component>
</QTd>
</template>
</QTable>
</QCard>
</template>
</CardSummary>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,29 @@
<script setup>
import { useDialogPluginComponent } from 'quasar';
import TravelSummary from './TravelSummary.vue';
const $props = defineProps({
id: {
type: Number,
required: true,
},
});
defineEmits([...useDialogPluginComponent.emits]);
const { dialogRef, onDialogHide } = useDialogPluginComponent();
</script>
<template>
<QDialog ref="dialogRef" @hide="onDialogHide">
<TravelSummary v-if="$props.id" :id="$props.id" />
</QDialog>
</template>
<style lang="scss">
.q-dialog .summary .header {
position: sticky;
z-index: $z-max;
top: 0;
}
</style>

View File

@ -0,0 +1,419 @@
<script setup>
import { onMounted, ref, computed } from 'vue';
import { QBtn, QField, QPopupEdit } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import ExtraCommunityFilter from './ExtraCommunityFilter.vue';
import { useStateStore } from 'stores/useStateStore';
import { toCurrency } from 'src/filters';
import { useArrayData } from 'composables/useArrayData';
import { toDate } from 'src/filters';
import { usePrintService } from 'composables/usePrintService';
import travelService from 'src/services/travel.service';
const router = useRouter();
const stateStore = useStateStore();
const { t } = useI18n();
const { openReport } = usePrintService();
const shippedFrom = ref(Date.vnNew());
const landedTo = ref(Date.vnNew());
const arrayData = useArrayData('ExtraCommunity', {
url: 'Travels/extraCommunityFilter',
limit: 0,
order: [
'landed ASC',
'shipped ASC',
'travelFk',
'loadPriority',
'agencyModeFk',
'supplierName',
'evaNotes',
],
userParams: {
continent: 'AM',
shippedFrom: shippedFrom.value,
landedTo: landedTo.value,
},
});
const rows = computed(() => arrayData.store.data || []);
const tableColumnComponents = {
id: {
component: QBtn,
attrs: () => ({ flat: true, color: 'blue', class: 'col-content' }),
},
cargoSupplierNickname: {
component: QBtn,
attrs: () => ({ flat: true, color: 'blue', class: 'col-content' }),
},
agencyModeName: {
component: 'span',
attrs: () => ({ class: 'col-content' }),
},
invoiceAmount: {
component: 'span',
attrs: () => ({
class: 'col-content',
}),
},
ref: {
component: QField,
attrs: () => ({ readonly: true, dense: true }),
},
stickers: {
component: 'span',
attrs: () => ({ class: 'col-content' }),
},
kg: {
component: 'span',
attrs: () => ({ class: 'col-content' }),
},
loadedKg: {
component: 'span',
attrs: () => ({ class: 'col-content' }),
},
volumeKg: {
component: 'span',
attrs: () => ({ class: 'col-content' }),
},
warehouseOutName: {
component: 'span',
attrs: () => ({ class: 'col-content' }),
},
shipped: {
component: 'span',
attrs: () => ({ class: 'col-content' }),
},
warehouseInName: {
component: 'span',
attrs: () => ({ class: 'col-content' }),
},
landed: {
component: 'span',
attrs: () => ({ class: 'col-content' }),
},
};
const columns = computed(() => {
return [
{
label: 'id',
field: 'id',
name: 'id',
align: 'left',
showValue: true,
},
{
label: t('supplier.pageTitles.supplier'),
field: 'cargoSupplierNickname',
name: 'cargoSupplierNickname',
align: 'left',
showValue: true,
},
{
label: t('globals.agency'),
field: 'agencyModeName',
name: 'agencyModeName',
align: 'left',
showValue: true,
},
{
label: t('globals.amount'),
name: 'invoiceAmount',
field: 'entries',
align: 'left',
showValue: true,
format: (value) =>
toCurrency(
value
? value.reduce((sum, entry) => {
return sum + (entry.invoiceAmount || 0);
}, 0)
: 0
),
},
{
label: t('globals.reference'),
field: 'ref',
name: 'ref',
align: 'left',
showValue: false,
},
{
label: t('globals.packages'),
field: 'stickers',
name: 'stickers',
align: 'left',
showValue: true,
},
{
label: t('kg'),
field: 'kg',
name: 'kg',
align: 'left',
showValue: true,
},
{
label: t('physicKg'),
field: 'loadedKg',
name: 'loadedKg',
align: 'left',
showValue: true,
},
{
label: 'KG Vol.',
field: 'volumeKg',
name: 'volumeKg',
align: 'left',
showValue: true,
},
{
label: t('globals.wareHouseOut'),
field: 'warehouseOutName',
name: 'warehouseOutName',
align: 'left',
showValue: true,
},
{
label: t('shipped'),
field: 'shipped',
name: 'shipped',
align: 'left',
format: (value) => toDate(value.substring(0, 10)),
showValue: true,
},
{
label: t('globals.wareHouseIn'),
field: 'warehouseInName',
name: 'warehouseInName',
align: 'left',
showValue: true,
},
{
label: t('landed'),
field: 'landed',
name: 'landed',
align: 'left',
format: (value) => toDate(value.substring(0, 10)),
showValue: true,
},
];
});
async function getData() {
await arrayData.fetch({ append: false });
}
const openReportPdf = () => {
const params = {
...arrayData.store.userParams,
limit: arrayData.store.limit,
};
openReport('Travels/extra-community-pdf', params);
};
const saveFieldValue = async (val, field, index) => {
const id = rows.value[index].id;
const params = { [field]: val };
await travelService.updateTravel(id, params);
};
const navigateToTravelId = (id) => {
router.push({ path: `/travel/${id}` });
};
const stopEventPropagation = (event, col) => {
if (!['ref', 'id', 'supplier'].includes(col.name)) return;
event.preventDefault();
event.stopPropagation();
};
onMounted(async () => {
stateStore.rightDrawer = true;
shippedFrom.value.setDate(shippedFrom.value.getDate() - 2);
shippedFrom.value.setHours(0, 0, 0, 0);
landedTo.value.setDate(landedTo.value.getDate() + 7);
landedTo.value.setHours(23, 59, 59, 59);
await getData();
});
</script>
<template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<VnSearchbar
data-key="ExtraCommunity"
:limit="20"
:label="t('searchExtraCommunity')"
/>
</Teleport>
</template>
<QToolbar class="bg-vn-dark justify-end">
<div id="st-data"></div>
<QSpace />
<div id="st-actions">
<QBtn color="primary" icon-right="archive" no-caps @click="openReportPdf()" />
</div>
</QToolbar>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<ExtraCommunityFilter data-key="ExtraCommunity" />
</QScrollArea>
</QDrawer>
<QPage class="column items-center q-pa-md">
<QTable
:rows="rows"
:columns="columns"
hide-bottom
row-key="clientId"
:pagination="{ rowsPerPage: 0 }"
class="full-width q-mt-md"
>
<template #body="props">
<QTr
:props="props"
@click="navigateToTravelId(props.row.id)"
class="cursor-pointer"
>
<QTd
v-for="col in props.cols"
:key="col.name"
:props="props"
@click="stopEventPropagation($event, col)"
>
<component
:is="tableColumnComponents[col.name].component"
class="col-content"
v-bind="tableColumnComponents[col.name].attrs(props)"
>
<!-- Editable 'ref' and 'kg' QField slot -->
<template
v-if="col.name === 'ref' || col.name === 'kg'"
#control
>
<div
class="self-center full-width no-outline"
tabindex="0"
>
{{ col.value }}
</div>
<QPopupEdit
:key="col.name"
v-model="col.value"
label-set="Save"
label-cancel="Close"
>
<QInput
v-model="rows[props.pageIndex][col.field]"
dense
autofocus
@keyup.enter="
saveFieldValue(
rows[props.pageIndex][col.field],
col.field,
props.rowIndex
)
"
/>
</QPopupEdit>
</template>
<template v-if="col.showValue">
{{ col.value }}
</template>
<!-- Main Row Descriptors -->
<TravelDescriptorProxy
v-if="col.name === 'id'"
:id="props.row.id"
/>
<SupplierDescriptorProxy
v-if="col.name === 'cargoSupplierNickname'"
:id="props.row.cargoSupplierFk"
/>
</component>
</QTd>
</QTr>
<QTr
v-for="entry in props.row.entries"
:key="entry.id"
:props="props"
class="secondary-row"
>
<QTd
><QBtn flat color="blue" class="col-content">{{ entry.id }}</QBtn>
<!-- Cuando se cree el modulo relacionado a entries, crear su descriptor y colocarlo acá -->
<!-- <EntryDescriptorProxy :id="entry.id"/> -->
</QTd>
<QTd
><QBtn flat color="blue" class="col-content">{{
entry.supplierName
}}</QBtn>
<SupplierDescriptorProxy :id="entry.supplierFk" />
</QTd>
<QTd></QTd>
<QTd
><span class="col-content">{{
toCurrency(entry.invoiceAmount)
}}</span></QTd
>
<QTd
><span class="col-content">{{ entry.reference }}</span></QTd
>
<QTd
><span class="col-content">{{ entry.stickers }}</span></QTd
>
<QTd></QTd>
<QTd
><span class="col-content">{{ entry.loadedkg }}</span></QTd
>
<QTd
><span class="col-content">{{ entry.volumeKg }}</span></QTd
>
<QTd></QTd>
<QTd></QTd>
<QTd></QTd>
<QTd></QTd>
</QTr>
</template>
</QTable>
</QPage>
</template>
<style lang="scss" scoped>
.col-content {
border-radius: 4px;
padding: 6px 6px 6px 6px;
}
.secondary-row {
background-color: var(--vn-gray);
}
</style>
<i18n>
en:
searchExtraCommunity: Search for extra community shipping
kg: BI. KG
physicKg: Phy. KG
shipped: W. shipped
landed: W. landed
es:
searchExtraCommunity: Buscar por envío extra comunitario
kg: KG Bloq.
physicKg: KG físico
shipped: F. envío
landed: F. llegada
</i18n>

View File

@ -0,0 +1,263 @@
<script setup>
import { reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import FetchData from 'components/FetchData.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const filtersOptions = reactive({
warehouses: [],
continents: [],
agencies: [],
suppliers: [],
});
const updateFilterOptions = (data, optionKey) => {
filtersOptions[optionKey] = [...data];
};
const add = (paramsObj, key) => {
if (paramsObj[key] === undefined) {
paramsObj[key] = 1;
} else {
paramsObj[key]++;
}
};
const decrement = (paramsObj, key) => {
if (paramsObj[key] === 0) return;
paramsObj[key]--;
};
</script>
<template>
<FetchData
url="Warehouses"
@on-fetch="(data) => updateFilterOptions(data, 'warehouses')"
auto-load
/>
<FetchData
url="Continents"
@on-fetch="(data) => updateFilterOptions(data, 'continents')"
auto-load
/>
<FetchData
url="AgencyModes"
@on-fetch="(data) => updateFilterOptions(data, 'agencies')"
auto-load
/>
<FetchData
url="Suppliers"
@on-fetch="(data) => updateFilterOptions(data, 'suppliers')"
auto-load
/>
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params }">
<QList dense style="max-width: 256px" class="list">
<QItem class="q-my-sm">
<QItemSection>
<QInput label="id" dense outlined rounded v-model="params.id" />
</QItemSection>
</QItem>
<QItem class="q-my-sm">
<QItemSection>
<QInput
:label="t('params.ref')"
dense
outlined
rounded
v-model="params.reference"
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<QInput
v-model="params.totalEntries"
type="number"
:label="t('params.totalEntries')"
dense
outlined
rounded
min="0"
class="input-number"
>
<template #append>
<QBtn
icon="add"
flat
dense
size="12px"
@click="add(params, 'totalEntries')"
/>
<QBtn
icon="remove"
flat
dense
size="12px"
@click="decrement(params, 'totalEntries')"
/>
</template>
</QInput>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnSelectFilter
:label="t('params.agencyModeFk')"
v-model="params.agencyModeFk"
:options="filtersOptions.agencies"
option-value="agencyFk"
option-label="name"
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnInputDate
v-model="params.shippedFrom"
:label="t('params.shippedFrom')"
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnInputDate
v-model="params.landedTo"
:label="t('params.landedTo')"
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnSelectFilter
:label="t('params.warehouseOutFk')"
v-model="params.warehouseOutFk"
:options="filtersOptions.warehouses"
option-value="id"
option-label="name"
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnSelectFilter
:label="t('params.warehouseInFk')"
v-model="params.warehouseInFk"
:options="filtersOptions.warehouses"
option-value="id"
option-label="name"
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnSelectFilter
:label="t('supplier.pageTitles.supplier')"
v-model="params.cargoSupplierFk"
:options="filtersOptions.suppliers"
option-value="id"
option-label="name"
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnSelectFilter
:label="t('params.continent')"
v-model="params.continent"
:options="filtersOptions.continents"
option-value="code"
option-label="name"
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
</QList>
</template>
</VnFilterPanel>
</template>
<style scoped>
.list * {
max-width: 100%;
}
.input-number >>> input[type='number'] {
-moz-appearance: textfield;
}
.input-number >>> input::-webkit-outer-spin-button,
.input-number >>> input::-webkit-inner-spin-button {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
}
</style>
<i18n>
en:
params:
ref: Reference
totalEntries: Total entries
agencyModeFk: Agency
warehouseInFk: Warehouse In
warehouseOutFk: Warehouse Out
shippedFrom: Shipped from
landedTo: Landed to
cargoSupplierFk: Supplier
continent: Continent out
es:
params:
ref: Referencia
totalEntries: Ent. totales
agencyModeFk: Agencia
warehouseInFk: Alm. entrada
warehouseOutFk: Alm. salida
shippedFrom: Llegada desde
landedTo: Llegada hasta
cargoSupplierFk: Proveedor
continent: Cont. Salida
</i18n>

View File

@ -0,0 +1,179 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { reactive, ref } from 'vue';
import FetchData from 'components/FetchData.vue';
import { useRoute } from 'vue-router';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import { toDate } from 'src/filters';
import { onBeforeMount } from 'vue';
const { t } = useI18n();
const route = useRoute();
const newTravelForm = reactive({
ref: null,
agencyModeFk: null,
shipped: null,
landed: null,
warehouseOutFk: null,
warehouseInFk: null,
});
const agenciesOptions = ref([]);
const viewAction = ref();
const warehousesOptions = ref([]);
onBeforeMount(() => {
// Esto nos permite decirle a FormModel si queremos observar los cambios o no
// Ya que si queremos clonar queremos que nos permita guardar inmediatamente sin realizar cambios en el form
viewAction.value = route.query.travelData ? 'clone' : 'create';
if (route.query.travelData) {
const travelData = JSON.parse(route.query.travelData);
for (let key in newTravelForm) {
if (key === 'landed' || key === 'shipped') {
newTravelForm[key] = travelData[key].substring(0, 10);
} else {
newTravelForm[key] = travelData[key];
}
}
}
});
const onFetchAgencies = (agencies) => {
agenciesOptions.value = [...agencies];
};
const onFetchWarehouses = (warehouses) => {
warehousesOptions.value = [...warehouses];
};
</script>
<template>
<FetchData url="AgencyModes" @on-fetch="(data) => onFetchAgencies(data)" auto-load />
<FetchData url="Warehouses" @on-fetch="(data) => onFetchWarehouses(data)" auto-load />
<QPage>
<QToolbar class="bg-vn-dark">
<div id="st-data"></div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<FormModel
url-update="Travels"
model="travel"
:form-initial-data="newTravelForm"
:observe-form-changes="viewAction === 'create'"
>
<template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QInput v-model="data.ref" :label="t('globals.reference')" />
</div>
<div class="col">
<VnSelectFilter
:label="t('globals.agency')"
v-model="data.agencyModeFk"
:options="agenciesOptions"
option-value="agencyFk"
option-label="name"
hide-selected
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QInput
rounded
placeholder="dd-mm-aaa"
:label="t('globals.shipped')"
:model-value="toDate(data.shipped)"
>
<template #append>
<QIcon name="event" class="cursor-pointer">
<QPopupProxy
cover
transition-show="scale"
transition-hide="scale"
>
<QDate v-model="data.shipped">
<div class="row items-center justify-end">
<QBtn
v-close-popup
:label="t('globals.close')"
color="primary"
flat
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</div>
<div class="col">
<QInput
rounded
placeholder="dd-mm-aaa"
:label="t('globals.landed')"
:model-value="toDate(data.landed)"
>
<template #append>
<QIcon name="event" class="cursor-pointer">
<QPopupProxy
cover
transition-show="scale"
transition-hide="scale"
>
<QDate v-model="data.landed">
<div class="row items-center justify-end">
<QBtn
v-close-popup
:label="t('globals.close')"
color="primary"
flat
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('globals.wareHouseOut')"
v-model="data.warehouseOutFk"
:options="warehousesOptions"
option-value="id"
option-label="name"
hide-selected
/>
</div>
<div class="col">
<VnSelectFilter
:label="t('globals.wareHouseIn')"
v-model="data.warehouseInFk"
:options="warehousesOptions"
option-value="id"
option-label="name"
hide-selected
/>
</div>
</VnRow>
</template>
</FormModel>
</QPage>
</template>
<style lang="scss" scoped>
.card {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
grid-gap: 20px;
}
</style>

View File

@ -0,0 +1,311 @@
<script setup>
import { reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import FetchData from 'components/FetchData.vue';
import { toDate } from 'src/filters';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const filtersOptions = reactive({
warehouses: [],
continents: [],
agencies: [],
});
const updateFilterOptions = (data, optionKey) => {
filtersOptions[optionKey] = [...data];
};
const add = (paramsObj, key) => {
if (paramsObj[key] === undefined) {
paramsObj[key] = 1;
} else {
paramsObj[key]++;
}
};
const decrement = (paramsObj, key) => {
if (paramsObj[key] === 0) return;
paramsObj[key]--;
};
</script>
<template>
<FetchData
url="Warehouses"
@on-fetch="(data) => updateFilterOptions(data, 'warehouses')"
auto-load
/>
<FetchData
url="Continents"
@on-fetch="(data) => updateFilterOptions(data, 'continents')"
auto-load
/>
<FetchData
url="AgencyModes"
@on-fetch="(data) => updateFilterOptions(data, 'agencies')"
auto-load
/>
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params }">
<QList dense style="max-width: 256px" class="list">
<QItem class="q-my-sm">
<QItemSection>
<QInput
:label="t('params.search')"
dense
outlined
rounded
v-model="params.search"
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnSelectFilter
:label="t('params.agencyModeFk')"
v-model="params.agencyModeFk"
:options="filtersOptions.agencies"
option-value="agencyFk"
option-label="name"
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnSelectFilter
:label="t('params.warehouseOutFk')"
v-model="params.warehouseOutFk"
:options="filtersOptions.warehouses"
option-value="id"
option-label="name"
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnSelectFilter
:label="t('params.warehouseInFk')"
v-model="params.warehouseInFk"
:options="filtersOptions.warehouses"
option-value="id"
option-label="name"
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<QInput
v-model="params.scopeDays"
type="number"
:label="t('params.scopeDays')"
dense
outlined
rounded
class="input-number"
>
<template #append>
<QBtn
icon="add"
flat
dense
size="12px"
@click="add(params, 'scopeDays')"
/>
<QBtn
icon="remove"
flat
dense
size="12px"
@click="decrement(params, 'scopeDays')"
/>
</template>
</QInput>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<QInput
dense
outlined
rounded
placeholder="dd-mm-aaa"
:label="t('params.landedFrom')"
:model-value="toDate(params.landedFrom)"
>
<template #append>
<QIcon name="event" class="cursor-pointer">
<QPopupProxy
cover
transition-show="scale"
transition-hide="scale"
>
<QDate v-model="params.landedFrom">
<div class="row items-center justify-end">
<QBtn
v-close-popup
:label="t('globals.close')"
color="primary"
flat
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<QInput
dense
outlined
rounded
placeholder="dd-mm-aaa"
:model-value="toDate(params.landedTo)"
:label="t('params.landedTo')"
>
<template #append>
<QIcon name="event" class="cursor-pointer">
<QPopupProxy
cover
transition-show="scale"
transition-hide="scale"
>
<QDate v-model="params.landedTo">
<div class="row items-center justify-end">
<QBtn
v-close-popup
:label="t('globals.close')"
color="primary"
flat
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnSelectFilter
:label="t('params.continent')"
v-model="params.continent"
:options="filtersOptions.continents"
option-value="code"
option-label="name"
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<QInput
v-model="params.totalEntries"
type="number"
:label="t('params.totalEntries')"
dense
outlined
rounded
min="0"
class="input-number"
>
<template #append>
<QBtn
icon="add"
flat
dense
size="12px"
@click="add(params, 'totalEntries')"
/>
<QBtn
icon="remove"
flat
dense
size="12px"
@click="decrement(params, 'totalEntries')"
/>
</template>
</QInput>
</QItemSection>
</QItem>
</QList>
</template>
</VnFilterPanel>
</template>
<style scoped>
.list * {
max-width: 100%;
}
.input-number >>> input[type='number'] {
-moz-appearance: textfield;
}
.input-number >>> input::-webkit-outer-spin-button,
.input-number >>> input::-webkit-inner-spin-button {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
}
</style>
<i18n>
en:
params:
search: Id/Reference
agencyModeFk: Agency
warehouseInFk: Warehouse In
warehouseOutFk: Warehouse Out
scopeDays: Days onward
landedFrom: Landed from
landedTo: Landed to
continent: Continent out
totalEntries: Total entries
es:
params:
search: Id/Referencia
agencyModeFk: Agencia
warehouseInFk: Alm. entrada
warehouseOutFk: Alm. salida
scopeDays: Días adelante
landedFrom: Llegada desde
landedTo: Llegada hasta
continent: Cont. Salida
totalEntries: Ent. totales
</i18n>

View File

@ -0,0 +1,147 @@
<script setup>
import { onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { useQuasar } from 'quasar';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import CardList from 'src/components/ui/CardList.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import TravelSummaryDialog from './Card/TravelSummaryDialog.vue';
import { useStateStore } from 'stores/useStateStore';
import TravelFilter from './TravelFilter.vue';
import { toDate } from 'src/filters/index';
const router = useRouter();
const quasar = useQuasar();
const { t } = useI18n();
const stateStore = useStateStore();
const navigateToTravelId = (id) => {
router.push({ path: `/travel/${id}` });
};
const cloneTravel = (travelData) => {
const stringifiedTravelData = JSON.stringify(travelData);
redirectToCreateView(stringifiedTravelData);
};
const redirectToCreateView = (queryParams) => {
router.push({ name: 'TravelCreate', query: { travelData: queryParams } });
};
const viewSummary = (id) => {
quasar.dialog({
component: TravelSummaryDialog,
componentProps: {
id,
},
});
};
onMounted(async () => {
stateStore.rightDrawer = true;
});
</script>
<template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<TravelFilter data-key="TravelList" />
</QScrollArea>
</QDrawer>
<QPage class="column items-center q-pa-md">
<div class="card-list">
<VnPaginate
data-key="TravelList"
url="Travels/filter"
auto-load
order="shipped DESC, landed DESC"
>
<template #body="{ rows }">
<CardList
v-for="row of rows"
:key="row.id"
:title="row.ref"
:id="row.id"
@click="navigateToTravelId(row.id)"
>
<template #list-items>
<VnLv
:label="t('globals.agency')"
:value="row.agencyModeName"
/>
<VnLv
:label="t('globals.wareHouseOut')"
:value="row.warehouseOutFk"
/>
<VnLv
:label="t('globals.shipped')"
:value="toDate(row.shipped)"
/>
<VnLv
:label="t('globals.landed')"
:value="toDate(row.landed)"
/>
<VnLv
:label="t('globals.wareHouseIn')"
:value="row.warehouseInFk"
/>
<VnLv
:label="t('globals.totalEntries')"
:value="row.totalEntries"
/>
</template>
<template #actions>
<QBtn
:label="t('components.smartCard.clone')"
@click.stop="cloneTravel(row)"
class="bg-vn-dark"
outline
/>
<QBtn
:label="t('addEntry')"
@click.stop="viewSummary(row.id)"
class="bg-vn-dark"
outline
style="margin-top: 15px"
/>
<QBtn
:label="t('components.smartCard.openSummary')"
@click.stop="viewSummary(row.id)"
color="primary"
style="margin-top: 15px"
/>
</template>
</CardList>
</template>
</VnPaginate>
</div>
<QPageSticky :offset="[20, 20]">
<QBtn fab icon="add" color="primary" @click="redirectToCreateView()" />
<QTooltip>
{{ t('supplier.list.newSupplier') }}
</QTooltip>
</QPageSticky>
</QPage>
</template>
<style lang="scss" scoped>
.card-list {
width: 100%;
max-width: 60em;
}
</style>
<i18n>
en:
addEntry: Add entry
es:
addEntry: Añadir entrada
</i18n>

View File

@ -0,0 +1,17 @@
<script setup>
import { useStateStore } from 'stores/useStateStore';
import LeftMenu from 'src/components/LeftMenu.vue';
const stateStore = useStateStore();
</script>
<template>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit text-grey-8">
<LeftMenu />
</QScrollArea>
</QDrawer>
<QPageContainer>
<RouterView></RouterView>
</QPageContainer>
</template>

View File

@ -239,8 +239,8 @@ function exceedMaxHeight(pos) {
<template> <template>
<QPage class="q-pa-sm q-mx-xl"> <QPage class="q-pa-sm q-mx-xl">
<QCard class="q-pa-sm"> <QForm @submit="onSubmit()" @reset="onReset()" class="q-pa-sm">
<QForm @submit="onSubmit()" @reset="onReset()" class="q-pa-md"> <QCard class="q-pa-md">
<QInput <QInput
filled filled
v-model="name" v-model="name"
@ -248,7 +248,7 @@ function exceedMaxHeight(pos) {
:rules="[(val) => !!val || t('wagon.warnings.nameNotEmpty')]" :rules="[(val) => !!val || t('wagon.warnings.nameNotEmpty')]"
/> />
<QCheckbox class="q-mb-sm" v-model="divisible" label="Divisible" /> <QCheckbox class="q-mb-sm" v-model="divisible" label="Divisible" />
<div class="wagon-tray q-mx-xl" v-for="tray in wagon" :key="tray.id"> <div class="wagon-tray q-mx-lg" v-for="tray in wagon" :key="tray.id">
<div class="position"> <div class="position">
<QInput <QInput
autofocus autofocus
@ -309,16 +309,6 @@ function exceedMaxHeight(pos) {
<QIcon color="grey-6" name="trip_origin" size="3rem" /> <QIcon color="grey-6" name="trip_origin" size="3rem" />
<QIcon color="grey-6" name="trip_origin" size="3rem" /> <QIcon color="grey-6" name="trip_origin" size="3rem" />
</div> </div>
<div>
<QBtn :label="t('wagon.type.submit')" type="submit" color="primary" />
<QBtn
:label="t('wagon.type.reset')"
type="reset"
color="primary"
flat
class="q-ml-sm"
/>
</div>
<QDialog <QDialog
v-model="colorPickerActive" v-model="colorPickerActive"
position="right" position="right"
@ -346,8 +336,18 @@ function exceedMaxHeight(pos) {
</QCardSection> </QCardSection>
</QCard> </QCard>
</QDialog> </QDialog>
</QForm> </QCard>
</QCard> <div class="q-mt-md">
<QBtn :label="t('wagon.type.submit')" type="submit" color="primary" />
<QBtn
:label="t('wagon.type.reset')"
type="reset"
color="primary"
flat
class="q-ml-sm"
/>
</div>
</QForm>
</QPage> </QPage>
</template> </template>
@ -357,45 +357,55 @@ function exceedMaxHeight(pos) {
justify-content: center; justify-content: center;
align-items: flex-start; align-items: flex-start;
} }
.q-card {
.q-form {
width: 70%; width: 70%;
} }
.q-dialog { .q-dialog {
.q-card { .q-card {
width: 100%; width: 100%;
} }
} }
.wheels { .wheels {
margin-left: 5%; margin-left: 5%;
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
} }
.wagon-tray { .wagon-tray {
display: flex; display: flex;
height: 6rem; height: 6rem;
.position { .position {
width: 15%; width: 20%;
border-right: 1rem solid gray; border-right: 1rem solid gray;
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
justify-content: flex-end; justify-content: flex-end;
padding-right: 1rem; padding-right: 1rem;
} }
.shelving { .shelving {
display: flex; display: flex;
width: 75%; width: 75%;
.shelving-half { .shelving-half {
width: 50%; width: 50%;
height: 100%; height: 100%;
.shelving-up { .shelving-up {
height: 80%; height: 80%;
width: 100%; width: 100%;
} }
.shelving-down { .shelving-down {
height: 20%; height: 20%;
width: 100%; width: 100%;
} }
} }
.shelving-divisible { .shelving-divisible {
width: 1%; width: 1%;
height: 100%; height: 100%;
@ -403,6 +413,7 @@ function exceedMaxHeight(pos) {
border-right: 0.5rem dashed grey; border-right: 0.5rem dashed grey;
} }
} }
.action-button { .action-button {
width: 10%; width: 10%;
border-left: 1rem solid gray; border-left: 1rem solid gray;

View File

@ -5,6 +5,8 @@ import VnPaginate from 'src/components/ui/VnPaginate.vue';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import CardList from 'components/ui/CardList.vue';
import VnLv from 'components/ui/VnLv.vue';
const quasar = useQuasar(); const quasar = useQuasar();
const arrayData = useArrayData('WagonTypeList'); const arrayData = useArrayData('WagonTypeList');
@ -48,43 +50,28 @@ async function remove(row) {
auto-load auto-load
> >
<template #body="{ rows }"> <template #body="{ rows }">
<QCard class="card q-mb-md" v-for="row of rows" :key="row.id"> <CardList
<QItem v-for="row of rows"
class="q-pa-none items-start cursor-pointer q-hoverable" :key="row.id"
v-ripple :title="(row.name || '').toString()"
clickable :id="row.id"
> @click="navigate(row.id)"
<QItemSection class="q-pa-md" @click="navigate(row.id)"> >
<div class="text-h6">{{ row.name }}</div> <template #actions>
<QItem-label caption>#{{ row.id }}</QItem-label> <QBtn
</QItemSection> :label="t('components.smartCard.openCard')"
<QSeparator vertical /> @click.stop="navigate(row.id)"
<QCardActions vertical class="justify-between"> class="bg-vn-dark"
<QBtn outline
flat />
round <QBtn
color="primary" :label="t('wagon.list.remove')"
icon="arrow_circle_right" @click.stop="remove(row)"
@click="navigate(row.id)" color="primary"
> style="margin-top: 15px"
<QTooltip> />
{{ t('components.smartCard.openCard') }} </template>
</QTooltip> </CardList>
</QBtn>
<QBtn
flat
round
color="primary"
icon="delete"
@click="remove(row)"
>
<QTooltip>
{{ t('wagon.list.remove') }}
</QTooltip>
</QBtn>
</QCardActions>
</QItem>
</QCard>
</template> </template>
</VnPaginate> </VnPaginate>
</div> </div>

View File

@ -86,9 +86,9 @@ function filterType(val, update) {
<template> <template>
<QPage class="q-pa-sm q-mx-xl"> <QPage class="q-pa-sm q-mx-xl">
<QCard class="q-pa-sm"> <QForm @submit="onSubmit()" @reset="onReset()" class="q-pa-sm">
<QForm @submit="onSubmit()" @reset="onReset()" class="q-pa-md"> <QCard class="q-pa-md">
<div class="row q-col-gutter-md q-mb-md"> <div class="row q-col-gutter-md">
<div class="col"> <div class="col">
<QInput <QInput
filled filled
@ -108,7 +108,7 @@ function filterType(val, update) {
/> />
</div> </div>
</div> </div>
<div class="row q-col-gutter-md q-mb-md"> <div class="row q-col-gutter-md">
<div class="col"> <div class="col">
<QInput <QInput
filled filled
@ -155,18 +155,18 @@ function filterType(val, update) {
</QSelect> </QSelect>
</div> </div>
</div> </div>
<div> </QCard>
<QBtn :label="t('wagon.type.submit')" type="submit" color="primary" /> <div class="q-mt-md">
<QBtn <QBtn :label="t('wagon.type.submit')" type="submit" color="primary" />
:label="t('wagon.type.reset')" <QBtn
type="reset" :label="t('wagon.type.reset')"
color="primary" type="reset"
flat color="primary"
class="q-ml-sm" flat
/> class="q-ml-sm"
</div> />
</QForm> </div>
</QCard> </QForm>
</QPage> </QPage>
</template> </template>
@ -176,7 +176,8 @@ function filterType(val, update) {
justify-content: center; justify-content: center;
align-items: flex-start; align-items: flex-start;
} }
.q-card {
.q-form {
width: 70%; width: 70%;
} }
</style> </style>

View File

@ -5,6 +5,8 @@ import VnPaginate from 'src/components/ui/VnPaginate.vue';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import CardList from 'components/ui/CardList.vue';
import VnLv from 'components/ui/VnLv.vue';
const quasar = useQuasar(); const quasar = useQuasar();
const arrayData = useArrayData('WagonList'); const arrayData = useArrayData('WagonList');
@ -55,69 +57,40 @@ async function remove(row) {
auto-load auto-load
> >
<template #body="{ rows }"> <template #body="{ rows }">
<QCard class="card q-mb-md" v-for="row of rows" :key="row.id"> <CardList
<QItem v-for="row of rows"
class="q-pa-none items-start cursor-pointer q-hoverable" :key="row.id"
v-ripple :title="(row.label || '').toString()"
clickable :id="row.id"
> @click="navigate(row.id)"
<QItemSection class="q-pa-md" @click="navigate(row.id)"> >
<div class="text-h6">{{ row.label }}</div> <template #list-items>
<QItemLabel caption>#{{ row.id }}</QItemLabel> <VnLv
<QList> :label="t('wagon.list.plate')"
<QItem class="q-pa-none"> :title-label="t('wagon.list.plate')"
<QItemSection> :value="row.plate"
<QItemLabel caption> />
{{ t('wagon.list.plate') }} <VnLv :label="t('wagon.list.volume')" :value="row?.volume" />
</QItemLabel> <VnLv
<QItemLabel>{{ row.plate }}</QItemLabel> :label="t('wagon.list.type')"
</QItemSection> :value="row?.type?.name"
</QItem> />
<QItem class="q-pa-none"> </template>
<QItemSection> <template #actions>
<QItemLabel caption> <QBtn
{{ t('wagon.list.volume') }} :label="t('components.smartCard.openCard')"
</QItemLabel> @click.stop="navigate(row.id)"
<QItemLabel>{{ row.volume }}</QItemLabel> class="bg-vn-dark"
</QItemSection> outline
</QItem> />
<QItem class="q-pa-none"> <QBtn
<QItemSection> :label="t('wagon.list.remove')"
<QItemLabel caption> @click.stop="remove(row)"
{{ t('wagon.list.type') }} color="primary"
</QItemLabel> style="margin-top: 15px"
<QItemLabel>{{ row.type.name }}</QItemLabel> />
</QItemSection> </template>
</QItem> </CardList>
</QList>
</QItemSection>
<QSeparator vertical />
<QCardActions vertical class="justify-between">
<QBtn
flat
round
color="primary"
icon="arrow_circle_right"
@click="navigate(row.id)"
>
<QTooltip>
{{ t('components.smartCard.openCard') }}
</QTooltip>
</QBtn>
<QBtn
flat
round
color="primary"
icon="delete"
@click="remove(row)"
>
<QTooltip>
{{ t('wagon.list.remove') }}
</QTooltip>
</QBtn>
</QCardActions>
</QItem>
</QCard>
</template> </template>
</VnPaginate> </VnPaginate>
</div> </div>

View File

@ -71,11 +71,11 @@ function viewSummary(id) {
<CardList <CardList
v-for="row of rows" v-for="row of rows"
:key="row.id" :key="row.id"
@click="navigate(row.id)" :id="row.id"
:title="row.nickname" :title="row.nickname"
@click="navigate(row.id)"
> >
<template #list-items> <template #list-items>
<VnLv label="ID" :value="row.id" />
<VnLv :label="t('worker.list.name')" :value="row.userName" /> <VnLv :label="t('worker.list.name')" :value="row.userName" />
<VnLv :label="t('worker.list.email')" :value="row.email" /> <VnLv :label="t('worker.list.email')" :value="row.email" />
<VnLv <VnLv
@ -85,19 +85,17 @@ function viewSummary(id) {
</template> </template>
<template #actions> <template #actions>
<QBtn <QBtn
flat :label="t('components.smartCard.openCard')"
icon="arrow_circle_right"
@click.stop="navigate(row.id)" @click.stop="navigate(row.id)"
> class="bg-vn-dark"
<QTooltip> outline
{{ t('components.smartCard.openCard') }} />
</QTooltip> <QBtn
</QBtn> :label="t('components.smartCard.openSummary')"
<QBtn flat icon="preview" @click.stop="viewSummary(row.id)"> @click.stop="viewSummary(row.id)"
<QTooltip> color="primary"
{{ t('components.smartCard.openSummary') }} style="margin-top: 15px"
</QTooltip> />
</QBtn>
</template> </template>
</CardList> </CardList>
</template> </template>

View File

@ -0,0 +1,61 @@
import { RouterView } from 'vue-router';
export default {
path: '/supplier',
name: 'Supplier',
meta: {
title: 'suppliers',
icon: 'vn:supplier',
},
component: RouterView,
redirect: { name: 'SupplierMain' },
menus: {
main: ['SupplierList'],
card: [],
},
children: [
{
path: '',
name: 'SupplierMain',
component: () => import('src/pages/Supplier/SupplierMain.vue'),
redirect: { name: 'SupplierList' },
children: [
{
path: 'list',
name: 'SupplierList',
meta: {
title: 'list',
icon: 'view_list',
},
component: () => import('src/pages/Supplier/SupplierList.vue'),
},
{
path: 'create',
name: 'SupplierCreate',
meta: {
title: 'create',
},
component: () => import('src/pages/Supplier/SupplierCreate.vue'),
},
],
},
{
name: 'SupplierCard',
path: ':id',
component: () => import('src/pages/Supplier/Card/SupplierCard.vue'),
redirect: { name: 'SupplierSummary' },
children: [
{
name: 'SupplierSummary',
path: 'summary',
meta: {
title: 'summary',
icon: 'launch',
},
component: () =>
import('src/pages/Supplier/Card/SupplierSummary.vue'),
},
],
},
],
};

View File

@ -4,7 +4,22 @@ import Claim from './claim';
import InvoiceOut from './invoiceOut'; import InvoiceOut from './invoiceOut';
import invoiceIn from './invoiceIn'; import invoiceIn from './invoiceIn';
import Worker from './worker'; import Worker from './worker';
import Shelving from './shelving';
import Wagon from './wagon'; import Wagon from './wagon';
import Route from './route'; import Route from './route';
import Supplier from './Supplier';
import Travel from './travel';
export default [Customer, Ticket, Claim, InvoiceOut, invoiceIn, Worker, Wagon, Route]; export default [
Customer,
Ticket,
Claim,
InvoiceOut,
Worker,
Shelving,
Wagon,
Route,
Supplier,
Travel,
invoiceIn,
];

View File

@ -5,12 +5,12 @@ export default {
name: 'InvoiceOut', name: 'InvoiceOut',
meta: { meta: {
title: 'invoiceOuts', title: 'invoiceOuts',
icon: 'vn:invoice-out' icon: 'vn:invoice-out',
}, },
component: RouterView, component: RouterView,
redirect: { name: 'InvoiceOutMain' }, redirect: { name: 'InvoiceOutMain' },
menus: { menus: {
main: ['InvoiceOutList'], main: ['InvoiceOutList', 'InvoiceOutGlobal', 'InvoiceOutNegativeBases'],
card: [], card: [],
}, },
children: [ children: [
@ -28,8 +28,27 @@ export default {
icon: 'view_list', icon: 'view_list',
}, },
component: () => import('src/pages/InvoiceOut/InvoiceOutList.vue'), component: () => import('src/pages/InvoiceOut/InvoiceOutList.vue'),
} },
] {
path: 'global-invoicing',
name: 'InvoiceOutGlobal',
meta: {
title: 'globalInvoicing',
icon: 'contact_support',
},
component: () => import('src/pages/InvoiceOut/InvoiceOutGlobal.vue'),
},
{
path: 'negative-bases',
name: 'InvoiceOutNegativeBases',
meta: {
title: 'negativeBases',
icon: 'view_list',
},
component: () =>
import('src/pages/InvoiceOut/InvoiceOutNegativeBases.vue'),
},
],
}, },
{ {
name: 'InvoiceOutCard', name: 'InvoiceOutCard',
@ -41,11 +60,12 @@ export default {
name: 'InvoiceOutSummary', name: 'InvoiceOutSummary',
path: 'summary', path: 'summary',
meta: { meta: {
title: 'summary' title: 'summary',
}, },
component: () => import('src/pages/InvoiceOut/Card/InvoiceOutSummary.vue'), component: () =>
} import('src/pages/InvoiceOut/Card/InvoiceOutSummary.vue'),
] },
],
}, },
] ],
}; };

View File

@ -0,0 +1,80 @@
import {RouterView} from "vue-router";
export default {
path: '/shelving',
name: 'Shelving',
meta: {
title: 'shelving',
icon: 'vn:inventory'
},
component: RouterView,
redirect: { name: 'ShelvingMain' },
menus: {
main: ['ShelvingList'],
card: ['ShelvingBasicData', 'ShelvingLog']
},
children: [
{
path: '',
name: 'ShelvingMain',
component: () => import('src/pages/Shelving/ShelvingMain.vue'),
redirect: { name: 'ShelvingList' },
children: [
{
path: 'list',
name: 'ShelvingList',
meta: {
title: 'shelvingList',
icon: 'view_list',
},
component: () => import('src/pages/Shelving/ShelvingList.vue'),
},
{
path: 'create',
name: 'ShelvingCreate',
meta: {
title: 'create',
},
component: () => import('src/pages/Shelving/Card/ShelvingForm.vue'),
},
],
},
{
name: 'ShelvingLayout',
path: ':id',
component: () => import('pages/Shelving/Card/ShelvingCard.vue'),
redirect: { name: 'ShelvingSummary' },
children: [
{
name: 'ShelvingSummary',
path: 'summary',
meta: {
title: 'summary',
},
component: () =>
import('pages/Shelving/Card/ShelvingSummary.vue'),
},
{
name: 'ShelvingBasicData',
path: 'basic-data',
meta: {
title: 'basicData',
icon: 'vn:settings',
roles: ['salesPerson'],
},
component: () => import('pages/Shelving/Card/ShelvingForm.vue'),
},
{
name: 'ShelvingLog',
path: 'log',
meta: {
title: 'log',
icon: 'history',
},
component: () => import('src/pages/Shelving/Card/ShelvingLog.vue'),
},
],
},
]
};

View File

@ -0,0 +1,69 @@
import { RouterView } from 'vue-router';
export default {
path: '/travel',
name: 'Travel',
meta: {
title: 'travel',
icon: 'local_airport',
},
component: RouterView,
redirect: { name: 'TravelMain' },
menus: {
main: ['TravelList', 'ExtraCommunity'],
card: [],
},
children: [
{
path: '',
name: 'TravelMain',
component: () => import('src/pages/Travel/TravelMain.vue'),
redirect: { name: 'TravelList' },
children: [
{
path: 'list',
name: 'TravelList',
meta: {
title: 'list',
icon: 'view_list',
},
component: () => import('src/pages/Travel/TravelList.vue'),
},
{
path: 'extra-community',
name: 'ExtraCommunity',
meta: {
title: 'extraCommunity',
icon: 'vn:shipment-01',
},
component: () => import('src/pages/Travel/ExtraCommunity.vue'),
},
{
path: 'create',
name: 'TravelCreate',
meta: {
title: 'extraCommunity',
icon: '',
},
component: () => import('src/pages/Travel/TravelCreate.vue'),
},
],
},
{
name: 'TravelCard',
path: ':id',
component: () => import('src/pages/Travel/Card/TravelCard.vue'),
redirect: { name: 'TravelSummary' },
children: [
{
name: 'TravelSummary',
path: 'summary',
meta: {
title: 'summary',
},
component: () => import('src/pages/Travel/Card/TravelSummary.vue'),
},
],
},
],
};

View File

@ -5,7 +5,10 @@ import worker from './modules/worker';
import invoiceOut from './modules/invoiceOut'; import invoiceOut from './modules/invoiceOut';
import invoiceIn from './modules/invoiceIn'; import invoiceIn from './modules/invoiceIn';
import wagon from './modules/wagon'; import wagon from './modules/wagon';
import supplier from './modules/Supplier';
import route from './modules/route'; import route from './modules/route';
import travel from './modules/travel';
import shelving from 'src/router/modules/shelving';
const routes = [ const routes = [
{ {
@ -49,15 +52,18 @@ const routes = [
ticket, ticket,
claim, claim,
worker, worker,
shelving,
invoiceOut, invoiceOut,
invoiceIn, invoiceIn,
wagon,
route,
supplier,
travel,
{ {
path: '/:catchAll(.*)*', path: '/:catchAll(.*)*',
name: 'NotFound', name: 'NotFound',
component: () => import('../pages/NotFound.vue'), component: () => import('../pages/NotFound.vue'),
}, },
wagon,
route,
], ],
}, },
]; ];

View File

@ -0,0 +1,49 @@
import axios from 'axios';
const request = async (method, url, params = {}) => {
try {
let response;
if (method === 'GET') {
response = await axios.get(url, { params });
} else if (method === 'POST') {
response = await axios.post(url, params);
}
return response.data;
} catch (err) {
console.error(`Error with ${method} request to ${url}`, err);
return err.response;
}
};
const invoiceOutService = {
getNegativeBases: async (params) => {
return await request('GET', 'InvoiceOuts/negativeBases', params);
},
getNegativeBasesCsv: async (params) => {
return await request('GET', 'InvoiceOuts/negativeBasesCsv', params);
},
getInvoiceDate: async (params) => {
return await request('GET', 'InvoiceOuts/getInvoiceDate', params);
},
getFindOne: async (params) => {
return await request('GET', 'InvoiceOutConfigs/findOne', params);
},
getClientsToInvoice: async (params) => {
return await request('POST', 'InvoiceOuts/clientsToInvoice', params);
},
invoiceClient: async (params) => {
return await request('POST', 'InvoiceOuts/invoiceClient', params);
},
makePdfAndNotify: async (invoiceId, params) => {
return await request('POST', `InvoiceOuts/${invoiceId}/makePdfAndNotify`, params);
},
};
export default invoiceOutService;

View File

@ -0,0 +1,23 @@
import axios from 'axios';
const travelService = {
getTravelEntries: async (param) => {
try {
return await axios.get(`Travels/${param}/getEntries`);
} catch (err) {
console.error(`Error fetching travel entries`, err);
return err.response;
}
},
updateTravel: async (id, params) => {
try {
return await axios.patch(`Travels/${id}`, params);
} catch (err) {
console.error(`Error updating travel`, err);
return err.response;
}
},
};
export default travelService;

View File

@ -0,0 +1,264 @@
import { defineStore } from 'pinia';
import { useUserConfig } from 'src/composables/useUserConfig';
import invoiceOutService from 'src/services/invoiceOut.service';
import useNotify from 'src/composables/useNotify.js';
import { exportFile } from 'quasar';
const { notify } = useNotify();
export const useInvoiceOutGlobalStore = defineStore({
id: 'invoiceOutGlobal',
state: () => ({
initialDataLoading: true,
formInitialData: {
companyFk: null,
invoiceDate: null,
maxShipped: null,
clientId: null,
printer: null,
},
addresses: [],
minInvoicingDate: null,
parallelism: null,
invoicing: false,
isInvoicing: false,
status: null,
addressIndex: 0,
errors: [],
printer: null,
nRequests: 0,
nPdfs: 0,
totalPdfs: 0,
}),
actions: {
async init() {
await this.fetchAllData();
},
async fetchAllData() {
try {
const userInfo = await useUserConfig().fetch();
const date = Date.vnNew();
this.formInitialData.maxShipped = new Date(
date.getFullYear(),
date.getMonth(),
0
)
.toISOString()
.substring(0, 10);
await Promise.all([
this.fetchParallelism(),
this.fetchInvoiceOutConfig(userInfo.companyFk),
]);
this.initialDataLoading = false;
} catch (err) {
console.error('Error fetching invoice out global initial data');
}
},
async fetchInvoiceOutConfig(companyFk) {
this.formInitialData.companyFk = companyFk;
const params = { companyFk: companyFk };
const { issued } = await invoiceOutService.getInvoiceDate(params);
const stringDate = issued.substring(0, 10);
this.minInvoicingDate = stringDate;
this.formInitialData.invoiceDate = stringDate;
},
async fetchParallelism() {
const filter = { fields: ['parallelism'] };
const { parallelism } = await invoiceOutService.getFindOne(filter);
this.parallelism = parallelism;
},
async makeInvoice(formData, clientsToInvoice) {
this.invoicing = true;
this.status = 'packageInvoicing';
try {
this.printer = formData.printer;
const params = {
invoiceDate: new Date(formData.invoiceDate),
maxShipped: new Date(formData.maxShipped),
clientId: formData.clientId ? formData.clientId : null,
companyFk: formData.companyFk,
};
this.validateMakeInvoceParams(params, clientsToInvoice);
if (clientsToInvoice == 'all') params.clientId = undefined;
const addressesResponse = await invoiceOutService.getClientsToInvoice(
params
);
this.addresses = addressesResponse;
if (!this.addresses || !this.addresses.length > 0) {
notify(
'invoiceOut.globalInvoices.errors.noTicketsToInvoice',
'negative'
);
throw new Error("There aren't addresses to invoice");
}
this.addresses.forEach(async (address) => {
await this.invoiceClient(address, formData);
});
} catch (err) {
this.handleError(err);
}
},
validateMakeInvoceParams(params, clientsToInvoice) {
if (clientsToInvoice === 'one' && !params.clientId) {
notify('invoiceOut.globalInvoices.errors.chooseValidClient', 'negative');
throw new Error('Invalid client');
}
if (!params.invoiceDate || !params.maxShipped) {
notify('invoiceOut.globalInvoices.errors.fillDates', 'negative');
throw new Error('Missing dates');
}
if (params.invoiceDate < params.maxShipped) {
notify(
'invoiceOut.globalInvoices.errors.invoiceDateLessThanMaxDate',
'negative'
);
throw new Error('Invalid date range');
}
const invoiceDateTime = new Date(params.invoiceDate).getTime();
const minInvoiceDateTime = new Date(this.minInvoicingDate).getTime();
if (this.minInvoicingDate && invoiceDateTime < minInvoiceDateTime) {
notify(
'invoiceOut.globalInvoices.errors.invoiceWithFutureDate',
'negative'
);
throw new Error('Invoice date in the future');
}
if (!params.companyFk) {
notify('invoiceOut.globalInvoices.errors.chooseValidCompany', 'negative');
throw new Error('Invalid company');
}
if (!this.printer) {
notify('invoiceOut.globalInvoices.errors.chooseValidPrinter', 'negative');
throw new Error('Invalid printer');
}
},
async invoiceClient(address, formData) {
if (this.nRequests === this.parallelism || this.isInvoicing) return;
if (this.status === 'stopping') {
if (this.nRequests) return;
this.invoicing = false;
this.status = 'done';
return;
}
const params = {
clientId: address.clientId,
addressId: address.id,
invoiceDate: new Date(formData.invoiceDate),
maxShipped: new Date(formData.maxShipped),
companyFk: formData.companyFk,
};
this.status = 'invoicing';
this.invoicing = true;
const invoiceResponse = await invoiceOutService.invoiceClient(params);
if (invoiceResponse.data.error) {
if (invoiceResponse.status >= 400 && invoiceResponse.status < 500) {
this.invoiceClientError(address, invoiceResponse);
this.addressIndex++;
return;
} else {
this.invoicing = false;
this.status = 'done';
notify(
'invoiceOut.globalInvoices.errors.criticalInvoiceError',
'negative'
);
throw new Error('Critical invoicing error, process stopped');
}
} else {
this.isInvoicing = false;
if (invoiceResponse.data) {
this.makePdfAndNotify(invoiceResponse.data, address);
}
}
},
async makePdfAndNotify(invoiceId, client) {
try {
this.nRequests++;
this.totalPdfs++;
const params = { printerFk: this.printer };
await invoiceOutService.makePdfAndNotify(invoiceId, params);
this.nPdfs++;
this.nRequests--;
} catch (err) {
this.invoiceClientError(client, err, true);
}
},
invoiceClientError(client, response, isWarning) {
const message = response.data?.error?.message || response.message;
this.errors.unshift({ client, message, isWarning });
},
handleError(err) {
this.invoicing = false;
this.status = null;
throw err;
},
async getNegativeBasesCsv(from, to) {
try {
const params = { from: from, to: to };
const CSVResponse = await invoiceOutService.getNegativeBasesCsv(params);
if (CSVResponse.data && CSVResponse.data.error) throw new Error();
const status = exportFile('negativeBases.csv', CSVResponse, {
encoding: 'windows-1252',
mimeType: 'text/csv;charset=windows-1252;',
});
if (status) {
notify('globals.downloadCSVSuccess', 'positive');
}
} catch (err) {
notify('invoiceOut.negativeBases.errors.downloadCsvFailed', 'negative');
}
},
// State mutations actions
setStatusValue(status) {
this.status = status;
},
},
getters: {
getNAddresses(state) {
return state.addresses.length;
},
getPercentage(state) {
if (this.getNAdresses <= 0 || !state.addressIndex) {
return 0;
}
let porcentaje = (state.addressIndex / this.getNAddresses) * 100;
return porcentaje;
},
getAddressNumber(state) {
return state.addressIndex;
},
},
});

View File

@ -11,10 +11,13 @@ export const useNavigationStore = defineStore('navigationStore', () => {
'claim', 'claim',
'ticket', 'ticket',
'invoiceOut', 'invoiceOut',
'invoiceIn',
'worker', 'worker',
'shelving',
'wagon', 'wagon',
'route', 'route',
'supplier',
'travel',
'invoiceIn',
]; ];
const pinnedModules = ref([]); const pinnedModules = ref([]);
const role = useRole(); const role = useRole();