Merge branch 'dev' into 7826_fix

This commit is contained in:
Javier Segarra 2025-01-26 03:08:54 +01:00
commit 94d471e20f
107 changed files with 2519 additions and 2257 deletions

4
.gitignore vendored
View File

@ -29,5 +29,5 @@ yarn-error.log*
*.sln *.sln
# Cypress directories and files # Cypress directories and files
/tests/cypress/videos /test/cypress/videos
/tests/cypress/screenshots /test/cypress/screenshots

View File

@ -1,6 +1,6 @@
<script setup> <script setup>
import axios from 'axios'; import axios from 'axios';
import { computed, ref, watch } from 'vue'; import { computed, ref, useAttrs, watch } from 'vue';
import { useRouter, onBeforeRouteLeave } from 'vue-router'; import { useRouter, onBeforeRouteLeave } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
@ -17,6 +17,7 @@ const quasar = useQuasar();
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const { validate } = useValidator(); const { validate } = useValidator();
const $attrs = useAttrs();
const $props = defineProps({ const $props = defineProps({
model: { model: {
@ -113,9 +114,11 @@ onBeforeRouteLeave((to, from, next) => {
}); });
async function fetch(data) { async function fetch(data) {
resetData(data); const keyData = $attrs['key-data'];
emit('onFetch', data); const rows = keyData ? data[keyData] : data;
return data; resetData(rows);
emit('onFetch', rows);
return rows;
} }
function resetData(data) { function resetData(data) {

View File

@ -61,6 +61,7 @@ onMounted(() => stateStore.setMounted());
/> />
<QSpace /> <QSpace />
<div id="searchbar" class="searchbar"></div> <div id="searchbar" class="searchbar"></div>
<div id="searchbar-after"></div>
<QSpace /> <QSpace />
<div class="q-pl-sm q-gutter-sm row items-center no-wrap"> <div class="q-pl-sm q-gutter-sm row items-center no-wrap">
{{ state }} {{ state }}

View File

@ -181,7 +181,7 @@ onMounted(() => {
watch( watch(
() => $props.columns, () => $props.columns,
(value) => splitColumns(value), (value) => splitColumns(value),
{ immediate: true } { immediate: true },
); );
const isTableMode = computed(() => mode.value == TABLE_MODE); const isTableMode = computed(() => mode.value == TABLE_MODE);
@ -212,7 +212,7 @@ function splitColumns(columns) {
// Status column // Status column
if (splittedColumns.value.chips.length) { if (splittedColumns.value.chips.length) {
splittedColumns.value.columnChips = splittedColumns.value.chips.filter( splittedColumns.value.columnChips = splittedColumns.value.chips.filter(
(c) => !c.isId (c) => !c.isId,
); );
if (splittedColumns.value.columnChips.length) if (splittedColumns.value.columnChips.length)
splittedColumns.value.columns.unshift({ splittedColumns.value.columns.unshift({
@ -484,7 +484,9 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
btn.isPrimary ? 'text-primary-light' : 'color-vn-text ' btn.isPrimary ? 'text-primary-light' : 'color-vn-text '
" "
:style="`visibility: ${ :style="`visibility: ${
(btn.show && btn.show(row)) ?? true ? 'visible' : 'hidden' ((btn.show && btn.show(row)) ?? true)
? 'visible'
: 'hidden'
}`" }`"
@click="btn.action(row)" @click="btn.action(row)"
/> />

View File

@ -0,0 +1,53 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore';
import { useHasContent } from 'src/composables/useHasContent';
import { watch } from 'vue';
const { t } = useI18n();
const stateStore = useStateStore();
const hasContent = useHasContent('#advanced-menu');
const $props = defineProps({
isMainSection: {
type: Boolean,
default: false,
},
});
watch(
() => $props.isMainSection,
(val) => {
if (stateStore) stateStore.rightAdvancedDrawer = val;
},
{ immediate: true }
);
</script>
<template>
<Teleport to="#searchbar-after" v-if="stateStore.isHeaderMounted()">
<QBtn
v-if="hasContent || $slots['advanced-menu']"
flat
@click="stateStore.toggleRightAdvancedDrawer()"
round
dense
icon="tune"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.advancedMenu') }}
</QTooltip>
</QBtn>
</Teleport>
<QDrawer
v-model="stateStore.rightAdvancedDrawer"
side="right"
:width="256"
:overlay="!isMainSection"
v-bind="$attrs"
>
<QScrollArea class="fit">
<div id="advanced-menu"></div>
<slot v-if="!hasContent" name="advanced-menu" />
</QScrollArea>
</QDrawer>
</template>

View File

@ -17,7 +17,7 @@ onMounted(() => {
}); });
</script> </script>
<template> <template>
<Teleport to="#actions-append" v-if="stateStore.isHeaderMounted()"> <Teleport to="#actions-prepend" v-if="stateStore.isHeaderMounted()">
<div class="row q-gutter-x-sm"> <div class="row q-gutter-x-sm">
<QBtn <QBtn
v-if="hasContent || $slots['right-panel']" v-if="hasContent || $slots['right-panel']"

View File

@ -12,6 +12,7 @@ const props = defineProps({
baseUrl: { type: String, default: undefined }, baseUrl: { type: String, default: undefined },
customUrl: { type: String, default: undefined }, customUrl: { type: String, default: undefined },
filter: { type: Object, default: () => {} }, filter: { type: Object, default: () => {} },
userFilter: { type: Object, default: () => {} },
descriptor: { type: Object, required: true }, descriptor: { type: Object, required: true },
filterPanel: { type: Object, default: undefined }, filterPanel: { type: Object, default: undefined },
searchDataKey: { type: String, default: undefined }, searchDataKey: { type: String, default: undefined },
@ -32,6 +33,7 @@ const url = computed(() => {
const arrayData = useArrayData(props.dataKey, { const arrayData = useArrayData(props.dataKey, {
url: url.value, url: url.value,
filter: props.filter, filter: props.filter,
userFilter: props.userFilter,
}); });
onBeforeMount(async () => { onBeforeMount(async () => {

View File

@ -15,6 +15,7 @@ import FetchData from '../FetchData.vue';
import VnSelect from './VnSelect.vue'; import VnSelect from './VnSelect.vue';
import VnUserLink from '../ui/VnUserLink.vue'; import VnUserLink from '../ui/VnUserLink.vue';
import VnPaginate from '../ui/VnPaginate.vue'; import VnPaginate from '../ui/VnPaginate.vue';
import RightMenu from './RightMenu.vue';
const stateStore = useStateStore(); const stateStore = useStateStore();
const validationsStore = useValidator(); const validationsStore = useValidator();
@ -130,7 +131,7 @@ const actionsIcon = {
}; };
const validDate = new RegExp( const validDate = new RegExp(
/^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])/.source + /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])/.source +
/T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/.source /T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/.source,
); );
function castJsonValue(value) { function castJsonValue(value) {
@ -192,7 +193,7 @@ function getLogTree(data) {
user: log.user, user: log.user,
userFk: log.userFk, userFk: log.userFk,
logs: [], logs: [],
}) }),
); );
} }
// Model // Model
@ -210,7 +211,7 @@ function getLogTree(data) {
id: log.changedModelId, id: log.changedModelId,
showValue: log.changedModelValue, showValue: log.changedModelValue,
logs: [], logs: [],
}) }),
); );
nLogs = 0; nLogs = 0;
} }
@ -282,7 +283,7 @@ function setDate(type) {
to = date.adjustDate( to = date.adjustDate(
to, to,
{ hour: 21, minute: 59, second: 59, millisecond: 999 }, { hour: 21, minute: 59, second: 59, millisecond: 999 },
true true,
); );
switch (type) { switch (type) {
@ -365,7 +366,7 @@ async function clearFilter() {
dateTo.value = undefined; dateTo.value = undefined;
userRadio.value = undefined; userRadio.value = undefined;
Object.keys(checkboxOptions.value).forEach( Object.keys(checkboxOptions.value).forEach(
(opt) => (checkboxOptions.value[opt].selected = false) (opt) => (checkboxOptions.value[opt].selected = false),
); );
await applyFilter(); await applyFilter();
} }
@ -378,7 +379,7 @@ watch(
() => router.currentRoute.value.params.id, () => router.currentRoute.value.params.id,
() => { () => {
applyFilter(); applyFilter();
} },
); );
</script> </script>
<template> <template>
@ -391,7 +392,7 @@ watch(
const changedModel = item.changedModel; const changedModel = item.changedModel;
return { return {
locale: useCapitalize( locale: useCapitalize(
validations[changedModel]?.locale?.name ?? changedModel validations[changedModel]?.locale?.name ?? changedModel,
), ),
value: changedModel, value: changedModel,
}; };
@ -507,7 +508,7 @@ watch(
:title=" :title="
date.formatDate( date.formatDate(
log.creationDate, log.creationDate,
'DD/MM/YYYY hh:mm:ss' 'DD/MM/YYYY hh:mm:ss',
) ?? `date:'dd/MM/yyyy HH:mm:ss'` ) ?? `date:'dd/MM/yyyy HH:mm:ss'`
" "
> >
@ -577,7 +578,7 @@ watch(
t( t(
`actions.${ `actions.${
actionsText[log.action] actionsText[log.action]
}` }`,
) )
" "
/> />
@ -677,139 +678,144 @@ watch(
</div> </div>
</template> </template>
</VnPaginate> </VnPaginate>
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> <RightMenu>
<QList dense> <template #right-panel>
<QSeparator /> <QList dense>
<QItem class="q-mt-sm"> <QSeparator />
<QInput <QItem class="q-mt-sm">
:label="t('globals.search')" <QInput
v-model="searchInput" :label="t('globals.search')"
class="full-width" v-model="searchInput"
clearable class="full-width"
clear-icon="close" clearable
@keyup.enter="() => selectFilter('search')" clear-icon="close"
@focusout="() => selectFilter('search')" @keyup.enter="() => selectFilter('search')"
@clear="() => selectFilter('search')" @focusout="() => selectFilter('search')"
> @clear="() => selectFilter('search')"
<template #append> >
<QIcon name="info" class="cursor-pointer"> <template #append>
<QTooltip>{{ t('tooltips.search') }}</QTooltip> <QIcon name="info" class="cursor-pointer">
</QIcon> <QTooltip>{{ t('tooltips.search') }}</QTooltip>
</template> </QIcon>
</QInput> </template>
</QItem> </QInput>
<QItem> </QItem>
<VnSelect <QItem>
class="full-width"
:label="t('globals.entity')"
v-model="selectedFilters.changedModel"
option-label="locale"
option-value="value"
:options="actions"
@update:model-value="selectFilter('action')"
hide-selected
/>
</QItem>
<QItem class="q-mt-sm">
<QOptionGroup
size="sm"
v-model="userRadio"
:options="userTypes"
color="primary"
@update:model-value="selectFilter('userRadio')"
right-label
>
<template #label="{ label }">
{{ t(`Users.${label}`) }}
</template>
</QOptionGroup>
</QItem>
<QItem class="q-mt-sm">
<QItemSection v-if="userRadio !== null">
<VnSelect <VnSelect
class="full-width" class="full-width"
:label="t('globals.user')" :label="t('globals.entity')"
v-model="userSelect" v-model="selectedFilters.changedModel"
option-label="name" option-label="locale"
option-value="id" option-value="value"
:url="`${model}Logs/${$route.params.id}/editors`" :options="actions"
:fields="['id', 'nickname', 'name', 'image']" @update:model-value="selectFilter('action')"
sort-by="nickname"
@update:model-value="selectFilter('userSelect')"
hide-selected hide-selected
/>
</QItem>
<QItem class="q-mt-sm">
<QOptionGroup
size="sm"
v-model="userRadio"
:options="userTypes"
color="primary"
@update:model-value="selectFilter('userRadio')"
right-label
> >
<template #option="{ opt, itemProps }"> <template #label="{ label }">
<QItem v-bind="itemProps" class="q-pa-xs row items-center"> {{ t(`Users.${label}`) }}
<QItemSection class="col-3 items-center">
<VnAvatar :worker-id="opt.id" />
</QItemSection>
<QItemSection class="col-9 justify-center">
<span>{{ opt.name }}</span>
<span class="text-grey">{{ opt.nickname }}</span>
</QItemSection>
</QItem>
</template> </template>
</VnSelect> </QOptionGroup>
</QItemSection> </QItem>
</QItem> <QItem class="q-mt-sm">
<QItem class="q-mt-sm"> <QItemSection v-if="userRadio !== null">
<QInput <VnSelect
:label="t('globals.changes')" class="full-width"
v-model="changeInput" :label="t('globals.user')"
class="full-width" v-model="userSelect"
clearable option-label="name"
clear-icon="close" option-value="id"
@keyup.enter="selectFilter('change')" :url="`${model}Logs/${route.params.id}/editors`"
@focusout="selectFilter('change')" :fields="['id', 'nickname', 'name', 'image']"
@clear="selectFilter('change')" sort-by="nickname"
@update:model-value="selectFilter('userSelect')"
hide-selected
>
<template #option="{ opt, itemProps }">
<QItem
v-bind="itemProps"
class="q-pa-xs row items-center"
>
<QItemSection class="col-3 items-center">
<VnAvatar :worker-id="opt.id" />
</QItemSection>
<QItemSection class="col-9 justify-center">
<span>{{ opt.name }}</span>
<span class="text-grey">{{ opt.nickname }}</span>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QItemSection>
</QItem>
<QItem class="q-mt-sm">
<QInput
:label="t('globals.changes')"
v-model="changeInput"
class="full-width"
clearable
clear-icon="close"
@keyup.enter="selectFilter('change')"
@focusout="selectFilter('change')"
@clear="selectFilter('change')"
>
<template #append>
<QIcon name="info" class="cursor-pointer">
<QTooltip max-width="250px">{{
t('tooltips.changes')
}}</QTooltip>
</QIcon>
</template>
</QInput>
</QItem>
<QItem
:class="index == 'create' ? 'q-mt-md' : 'q-mt-xs'"
v-for="(checkboxOption, index) in checkboxOptions"
:key="index"
> >
<template #append> <QCheckbox
<QIcon name="info" class="cursor-pointer"> size="sm"
<QTooltip max-width="250px">{{ v-model="checkboxOption.selected"
t('tooltips.changes') :label="t(`actions.${checkboxOption.label}`)"
}}</QTooltip> @update:model-value="selectFilter"
</QIcon> />
</template> </QItem>
</QInput> <QItem class="q-mt-sm">
</QItem> <QInput
<QItem class="full-width"
:class="index == 'create' ? 'q-mt-md' : 'q-mt-xs'" :label="t('globals.date')"
v-for="(checkboxOption, index) in checkboxOptions" @click="dateFromDialog = true"
:key="index" @focus="(evt) => evt.target.blur()"
> @clear="selectFilter('date', 'to')"
<QCheckbox v-model="dateFrom"
size="sm" clearable
v-model="checkboxOption.selected" clear-icon="close"
:label="t(`actions.${checkboxOption.label}`)" />
@update:model-value="selectFilter" </QItem>
/> <QItem class="q-mt-sm">
</QItem> <QInput
<QItem class="q-mt-sm"> class="full-width"
<QInput :label="t('to')"
class="full-width" @click="dateToDialog = true"
:label="t('globals.date')" @focus="(evt) => evt.target.blur()"
@click="dateFromDialog = true" @clear="selectFilter('date', 'from')"
@focus="(evt) => evt.target.blur()" v-model="dateTo"
@clear="selectFilter('date', 'to')" clearable
v-model="dateFrom" clear-icon="close"
clearable />
clear-icon="close" </QItem>
/> </QList>
</QItem> </template>
<QItem class="q-mt-sm"> </RightMenu>
<QInput
class="full-width"
:label="t('to')"
@click="dateToDialog = true"
@focus="(evt) => evt.target.blur()"
@clear="selectFilter('date', 'from')"
v-model="dateTo"
clearable
clear-icon="close"
/>
</QItem>
</QList>
</Teleport>
<QDialog v-model="dateFromDialog"> <QDialog v-model="dateFromDialog">
<QDate <QDate
:years-in-month-view="false" :years-in-month-view="false"

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import RightMenu from './RightMenu.vue'; import RightAdvancedMenu from './RightAdvancedMenu.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue';
import VnTableFilter from '../VnTable/VnTableFilter.vue'; import VnTableFilter from '../VnTable/VnTableFilter.vue';
import { onBeforeMount, onMounted, onUnmounted, computed, ref } from 'vue'; import { onBeforeMount, onMounted, onUnmounted, computed, ref } from 'vue';
@ -54,6 +54,7 @@ const sectionValue = computed(() => $props.section ?? $props.dataKey);
const isMainSection = ref(false); const isMainSection = ref(false);
const searchbarId = 'section-searchbar'; const searchbarId = 'section-searchbar';
const advancedMenuSlot = 'advanced-menu';
const hasContent = useHasContent(`#${searchbarId}`); const hasContent = useHasContent(`#${searchbarId}`);
onBeforeMount(() => { onBeforeMount(() => {
@ -93,9 +94,9 @@ function checkIsMain() {
/> />
<div :id="searchbarId"></div> <div :id="searchbarId"></div>
</slot> </slot>
<RightMenu> <RightAdvancedMenu :is-main-section="isMainSection">
<template #right-panel v-if="$slots['rightMenu'] || rightFilter"> <template #advanced-menu v-if="$slots[advancedMenuSlot] || rightFilter">
<slot name="rightMenu"> <slot :name="advancedMenuSlot">
<VnTableFilter <VnTableFilter
v-if="rightFilter && columns" v-if="rightFilter && columns"
:data-key="dataKey" :data-key="dataKey"
@ -104,7 +105,7 @@ function checkIsMain() {
/> />
</slot> </slot>
</template> </template>
</RightMenu> </RightAdvancedMenu>
<slot name="body" v-if="isMainSection" /> <slot name="body" v-if="isMainSection" />
<RouterView v-else /> <RouterView v-else />
</template> </template>

View File

@ -0,0 +1,146 @@
import { createWrapper, axios } from 'app/test/vitest/helper';
import { vi, afterEach, beforeEach, beforeAll, describe, expect, it } from 'vitest';
import VnDms from 'src/components/common/VnDms.vue';
class MockFormData {
constructor() {
this.entries = {};
}
append(key, value) {
if (!key) {
throw new Error('Key is required for FormData.append');
}
this.entries[key] = value;
}
get(key) {
return this.entries[key] || null;
}
getAll() {
return this.entries;
}
}
global.FormData = MockFormData;
describe('VnDms', () => {
let wrapper;
let vm;
let postMock;
const postResponseMock = { data: { success: true } };
const data = {
hasFile: true,
hasFileAttached: true,
reference: 'DMS-test',
warehouseFk: 1,
companyFk: 2,
dmsTypeFk: 3,
description: 'This is a test description',
files: { name: 'example.txt', content: new Blob(['file content'], { type: 'text/plain' })},
};
const expectedBody = {
hasFile: true,
hasFileAttached: true,
reference: 'DMS-test',
warehouseId: 1,
companyId: 2,
dmsTypeId: 3,
description: 'This is a test description',
};
beforeAll(() => {
wrapper = createWrapper(VnDms, {
propsData: {
url: '/test',
formInitialData: { id: 1, reference: 'test' },
model: 'Worker',
}
});
wrapper = wrapper.wrapper;
vm = wrapper.vm;
vi.spyOn(vm, '$emit');
});
beforeEach(() => {
postMock = vi.spyOn(axios, 'post').mockResolvedValue(postResponseMock);
vm.dms = data;
});
afterEach(() => {
vi.clearAllMocks();
});
describe('mapperDms', () => {
it('should map DMS data correctly and add file to FormData', () => {
const [formData, params] = vm.mapperDms(data);
expect(formData.get('example.txt')).toBe(data.files);
expect(expectedBody).toEqual(params.params);
});
it('should map DMS data correctly without file', () => {
delete data.files;
const [formData, params] = vm.mapperDms(data);
expect(formData.getAll()).toEqual({});
expect(expectedBody).toEqual(params.params);
});
});
describe('getUrl', () => {
it('should returns prop url when is set', async () => {
expect(vm.getUrl()).toBe('/test');
});
it('should returns url dms/"props.formInitialData.id"/updateFile when prop url is null', async () => {
await wrapper.setProps({ url: null });
expect(vm.getUrl()).toBe('dms/1/updateFile');
});
it('should returns url "props.model"/"route.params.id"/uploadFile when formInitialData is null', async () => {
await wrapper.setProps({ formInitialData: null });
vm.route.params.id = '123';
expect(vm.getUrl()).toBe('Worker/123/uploadFile');
});
});
describe('save', () => {
it('should save data correctly', async () => {
await vm.save();
expect(postMock).toHaveBeenCalledWith(vm.getUrl(), expect.any(FormData), { params: expectedBody });
expect(wrapper.emitted('onDataSaved')).toBeTruthy();
});
});
describe('defaultData', () => {
it('should set dms with formInitialData', async () => {
const testData = {
hasFile: false,
hasFileAttached: false,
reference: 'defaultData-test',
warehouseFk: 2,
companyFk: 3,
dmsTypeFk: 2,
description: 'This is a test description'
}
await wrapper.setProps({ formInitialData: testData });
vm.defaultData();
expect(vm.dms).toEqual(testData);
});
it('should add reference with "route.params.id" to dms if formInitialData is null', async () => {
await wrapper.setProps({ formInitialData: null });
vm.route.params.id= '111';
vm.defaultData();
expect(vm.dms.reference).toBe('111');
});
});
});

View File

@ -41,7 +41,7 @@ const card = toRef(props, 'item');
</div> </div>
</div> </div>
<div class="content"> <div class="content">
<span class="link"> <span class="link" @click.stop>
{{ card.name }} {{ card.name }}
<ItemDescriptorProxy :id="card.id" /> <ItemDescriptorProxy :id="card.id" />
</span> </span>

View File

@ -190,7 +190,10 @@ const getLocale = (label) => {
const globalLocale = `globals.params.${param}`; const globalLocale = `globals.params.${param}`;
if (te(globalLocale)) return t(globalLocale); if (te(globalLocale)) return t(globalLocale);
else if (te(t(`params.${param}`))); else if (te(t(`params.${param}`)));
else return t(`${route.meta.moduleName.toLowerCase()}.params.${param}`); else {
const camelCaseModuleName = route.meta.moduleName.charAt(0).toLowerCase() + route.meta.moduleName.slice(1);
return t(`${camelCaseModuleName}.params.${param}`);
}
}; };
</script> </script>

View File

@ -78,6 +78,10 @@ const props = defineProps({
type: String, type: String,
default: '', default: '',
}, },
keyData: {
type: String,
default: undefined,
},
}); });
const emit = defineEmits(['onFetch', 'onPaginate', 'onChange']); const emit = defineEmits(['onFetch', 'onPaginate', 'onChange']);
@ -255,7 +259,7 @@ defineExpose({
:disable="disableInfiniteScroll || !store.hasMoreData" :disable="disableInfiniteScroll || !store.hasMoreData"
v-bind="$attrs" v-bind="$attrs"
> >
<slot name="body" :rows="store.data"></slot> <slot name="body" :rows="keyData ? store.data[keyData] : store.data"></slot>
<div v-if="isLoading" class="spinner info-row q-pa-md text-center"> <div v-if="isLoading" class="spinner info-row q-pa-md text-center">
<QSpinner color="primary" size="md" /> <QSpinner color="primary" size="md" />
</div> </div>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { defineProps, ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
const { t } = useI18n(); const { t } = useI18n();

View File

@ -170,10 +170,9 @@ export function useArrayData(key, userOptions) {
async function addOrder(field, direction = 'ASC') { async function addOrder(field, direction = 'ASC') {
const newOrder = field + ' ' + direction; const newOrder = field + ' ' + direction;
let order = store.order || []; const order = toArray(store.order);
if (typeof order == 'string') order = [order];
let index = order.findIndex((o) => o.split(' ')[0] === field); let index = getOrderIndex(order, field);
if (index > -1) { if (index > -1) {
order[index] = newOrder; order[index] = newOrder;
} else { } else {
@ -190,16 +189,24 @@ export function useArrayData(key, userOptions) {
} }
async function deleteOrder(field) { async function deleteOrder(field) {
let order = store.order ?? []; const order = toArray(store.order);
if (typeof order == 'string') order = [order]; const index = getOrderIndex(order, field);
const index = order.findIndex((o) => o.split(' ')[0] === field);
if (index > -1) order.splice(index, 1); if (index > -1) order.splice(index, 1);
store.order = order; store.order = order;
fetch({}); fetch({});
} }
function getOrderIndex(order, field) {
return order.findIndex((o) => o.split(' ')[0] === field);
}
function toArray(str = []) {
if (!str) return [];
if (Array.isArray(str)) return str;
if (typeof str === 'string') return str.split(',').map((item) => item.trim());
}
function sanitizerParams(params, exprBuilder) { 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) {
@ -290,8 +297,7 @@ export function useArrayData(key, userOptions) {
Object.assign(params, store.userParams); Object.assign(params, store.userParams);
if (params.filter) params.filter.skip = store.skip; if (params.filter) params.filter.skip = store.skip;
if (store?.order && typeof store?.order == 'string') store.order = [store.order]; if (store.order) params.filter.order = toArray(store.order);
if (store.order?.length) params.filter.order = [...store.order];
else delete params.filter.order; else delete params.filter.order;
return { filter, params, limit: filter.limit }; return { filter, params, limit: filter.limit };

View File

@ -5,7 +5,7 @@ export function useHasContent(selector) {
const hasContent = ref(); const hasContent = ref();
onMounted(() => { onMounted(() => {
container.value = document.querySelector(selector); container.value = document?.querySelector(selector);
if (!container.value) return; if (!container.value) return;
const observer = new MutationObserver(() => { const observer = new MutationObserver(() => {

View File

@ -312,11 +312,11 @@ input::-webkit-inner-spin-button {
} }
.q-item > .q-item__section:has(.q-checkbox) { .q-item > .q-item__section:has(.q-checkbox) {
max-width: min-content; max-width: fit-content;
} }
.row > .column:has(.q-checkbox) { .row > .column:has(.q-checkbox) {
max-width: min-content; max-width: fit-content;
} }
.q-field__inner { .q-field__inner {
.q-field__control { .q-field__control {

View File

@ -1,13 +1,15 @@
globals: globals:
lang: lang:
es: Spanish es: Spanish
en: English en: English
quantity: Quantity
language: Language language: Language
quantity: Quantity
entity: Entity entity: Entity
preview: Preview
user: User user: User
details: Details details: Details
collapseMenu: Collapse left menu collapseMenu: Collapse lateral menu
advancedMenu: Advanced menu
backToDashboard: Return to dashboard backToDashboard: Return to dashboard
notifications: Notifications notifications: Notifications
userPanel: User panel userPanel: User panel
@ -35,7 +37,6 @@ globals:
confirm: Confirm confirm: Confirm
assign: Assign assign: Assign
back: Back back: Back
downloadPdf: Download PDF
yes: 'Yes' yes: 'Yes'
no: 'No' no: 'No'
noChanges: No changes to save noChanges: No changes to save
@ -59,6 +60,7 @@ globals:
downloadCSVSuccess: CSV downloaded successfully downloadCSVSuccess: CSV downloaded successfully
reference: Reference reference: Reference
agency: Agency agency: Agency
entry: Entry
warehouseOut: Warehouse Out warehouseOut: Warehouse Out
warehouseIn: Warehouse In warehouseIn: Warehouse In
landed: Landed landed: Landed
@ -67,11 +69,11 @@ globals:
amount: Amount amount: Amount
packages: Packages packages: Packages
download: Download download: Download
downloadPdf: Download PDF
selectRows: 'Select all { numberRows } row(s)' selectRows: 'Select all { numberRows } row(s)'
allRows: 'All { numberRows } row(s)' allRows: 'All { numberRows } row(s)'
markAll: Mark all markAll: Mark all
requiredField: Required field requiredField: Required field
valueCantBeEmpty: Value cannot be empty
class: clase class: clase
type: Type type: Type
reason: reason reason: reason
@ -81,6 +83,9 @@ globals:
warehouse: Warehouse warehouse: Warehouse
company: Company company: Company
fieldRequired: Field required fieldRequired: Field required
valueCantBeEmpty: Value cannot be empty
Value can't be blank: Value cannot be blank
Value can't be null: Value cannot be null
allowedFilesText: 'Allowed file types: { allowedContentTypes }' allowedFilesText: 'Allowed file types: { allowedContentTypes }'
smsSent: SMS sent smsSent: SMS sent
confirmDeletion: Confirm deletion confirmDeletion: Confirm deletion
@ -130,6 +135,26 @@ globals:
medium: Medium medium: Medium
big: Big big: Big
email: Email email: Email
supplier: Supplier
ticketList: Ticket List
created: Created
worker: Worker
now: Now
name: Name
new: New
comment: Comment
observations: Observations
goToModuleIndex: Go to module index
createInvoiceIn: Create invoice in
myAccount: My account
noOne: No one
maxTemperature: Max
minTemperature: Min
changePass: Change password
deleteConfirmTitle: Delete selected elements
changeState: Change state
raid: 'Raid {daysInForward} days'
isVies: Vies
pageTitles: pageTitles:
logIn: Login logIn: Login
addressEdit: Update address addressEdit: Update address
@ -151,13 +176,14 @@ globals:
subRoles: Subroles subRoles: Subroles
inheritedRoles: Inherited Roles inheritedRoles: Inherited Roles
customers: Customers customers: Customers
customerCreate: New customer
createCustomer: Create customer
createOrder: New order
list: List list: List
webPayments: Web Payments webPayments: Web Payments
extendedList: Extended list extendedList: Extended list
notifications: Notifications notifications: Notifications
defaulter: Defaulter defaulter: Defaulter
customerCreate: New customer
createOrder: New order
fiscalData: Fiscal data fiscalData: Fiscal data
billingData: Billing data billingData: Billing data
consignees: Consignees consignees: Consignees
@ -193,27 +219,28 @@ globals:
claims: Claims claims: Claims
claimCreate: New claim claimCreate: New claim
lines: Lines lines: Lines
photos: Photos
development: Development development: Development
photos: Photos
action: Action action: Action
invoiceOuts: Invoice out invoiceOuts: Invoice out
negativeBases: Negative Bases negativeBases: Negative Bases
globalInvoicing: Global invoicing globalInvoicing: Global invoicing
invoiceOutCreate: Create invoice out invoiceOutCreate: Create invoice out
order: Orders
orderList: List
orderCreate: New order
catalog: Catalog
volume: Volume
shelving: Shelving shelving: Shelving
shelvingList: Shelving List shelvingList: Shelving List
shelvingCreate: New shelving shelvingCreate: New shelving
invoiceIns: Invoices In invoiceIns: Invoices In
invoiceInCreate: Create invoice in invoiceInCreate: Create invoice in
vat: VAT vat: VAT
labeler: Labeler
dueDay: Due day dueDay: Due day
intrastat: Intrastat intrastat: Intrastat
corrective: Corrective corrective: Corrective
order: Orders
orderList: List
orderCreate: New order
catalog: Catalog
volume: Volume
workers: Workers workers: Workers
workerCreate: New worker workerCreate: New worker
department: Department department: Department
@ -226,10 +253,10 @@ globals:
wagonsList: Wagons List wagonsList: Wagons List
wagonCreate: Create wagon wagonCreate: Create wagon
wagonEdit: Edit wagon wagonEdit: Edit wagon
wagonCounter: Trolley counter
typesList: Types List typesList: Types List
typeCreate: Create type typeCreate: Create type
typeEdit: Edit type typeEdit: Edit type
wagonCounter: Trolley counter
roadmap: Roadmap roadmap: Roadmap
stops: Stops stops: Stops
routes: Routes routes: Routes
@ -238,21 +265,16 @@ globals:
routeCreate: New route routeCreate: New route
RouteRoadmap: Roadmaps RouteRoadmap: Roadmaps
RouteRoadmapCreate: Create roadmap RouteRoadmapCreate: Create roadmap
RouteExtendedList: Router
autonomous: Autonomous autonomous: Autonomous
suppliers: Suppliers suppliers: Suppliers
supplier: Supplier supplier: Supplier
expedition: Expedition
services: Service
components: Components
pictures: Pictures
packages: Packages
tracking: Tracking
labeler: Labeler
supplierCreate: New supplier supplierCreate: New supplier
accounts: Accounts accounts: Accounts
addresses: Addresses addresses: Addresses
agencyTerm: Agency agreement agencyTerm: Agency agreement
travel: Travels travel: Travels
create: Create
extraCommunity: Extra community extraCommunity: Extra community
travelCreate: New travel travelCreate: New travel
history: Log history: Log
@ -260,14 +282,13 @@ globals:
items: Items items: Items
diary: Diary diary: Diary
tags: Tags tags: Tags
create: Create
buyRequest: Buy requests
fixedPrice: Fixed prices fixedPrice: Fixed prices
buyRequest: Buy requests
wasteBreakdown: Waste breakdown wasteBreakdown: Waste breakdown
itemCreate: New item itemCreate: New item
barcode: Barcodes
tax: Tax tax: Tax
botanical: Botanical botanical: Botanical
barcode: Barcodes
itemTypeCreate: New item type itemTypeCreate: New item type
family: Item Type family: Item Type
lastEntries: Last entries lastEntries: Last entries
@ -283,13 +304,20 @@ globals:
formation: Formation formation: Formation
locations: Locations locations: Locations
warehouses: Warehouses warehouses: Warehouses
saleTracking: Sale tracking
roles: Roles roles: Roles
connections: Connections connections: Connections
acls: ACLs acls: ACLs
mailForwarding: Mail forwarding mailForwarding: Mail forwarding
mailAlias: Mail alias mailAlias: Mail alias
privileges: Privileges privileges: Privileges
observation: Notes
expedition: Expedition
saleTracking: Sale tracking
services: Service
tracking: Tracking
components: Components
pictures: Pictures
packages: Packages
ldap: LDAP ldap: LDAP
samba: Samba samba: Samba
twoFactor: Two factor twoFactor: Two factor
@ -300,27 +328,12 @@ globals:
serial: Serial serial: Serial
medical: Mutual medical: Mutual
pit: IRPF pit: IRPF
RouteExtendedList: Router
wasteRecalc: Waste recaclulate wasteRecalc: Waste recaclulate
operator: Operator operator: Operator
parking: Parking parking: Parking
supplier: Supplier
created: Created
worker: Worker
now: Now
name: Name
new: New
comment: Comment
observations: Observations
goToModuleIndex: Go to module index
unsavedPopup: unsavedPopup:
title: Unsaved changes will be lost title: Unsaved changes will be lost
subtitle: Are you sure exit without saving? subtitle: Are you sure exit without saving?
createInvoiceIn: Create invoice in
myAccount: My account
noOne: No one
maxTemperature: Max
minTemperature: Min
params: params:
clientFk: Client id clientFk: Client id
salesPersonFk: Sales person salesPersonFk: Sales person
@ -338,19 +351,13 @@ globals:
supplierFk: Supplier supplierFk: Supplier
supplierRef: Supplier ref supplierRef: Supplier ref
serial: Serial serial: Serial
amount: Importe amount: Amount
awbCode: AWB awbCode: AWB
correctedFk: Rectified correctedFk: Rectified
correctingFk: Rectificative correctingFk: Rectificative
daysOnward: Days onward daysOnward: Days onward
countryFk: Country countryFk: Country
companyFk: Company companyFk: Company
changePass: Change password
setPass: Set password
deleteConfirmTitle: Delete selected elements
changeState: Change state
raid: 'Raid {daysInForward} days'
isVies: Vies
errors: errors:
statusUnauthorized: Access denied statusUnauthorized: Access denied
statusInternalServerError: An internal server error has ocurred statusInternalServerError: An internal server error has ocurred
@ -490,21 +497,6 @@ invoiceOut:
comercial: Comercial comercial: Comercial
errors: errors:
downloadCsvFailed: CSV download failed downloadCsvFailed: CSV download failed
shelving:
list:
parking: Parking
priority: Priority
newShelving: New Shelving
summary:
recyclable: Recyclable
parking:
pickingOrder: Picking order
sector: Sector
row: Row
column: Column
searchBar:
info: You can search by parking code
label: Search parking...
department: department:
chat: Chat chat: Chat
bossDepartment: Boss Department bossDepartment: Boss Department
@ -696,6 +688,9 @@ supplier:
consumption: consumption:
entry: Entry entry: Entry
travel: travel:
search: Search travel
searchInfo: You can search by travel id or name
id: Id
travelList: travelList:
tableVisibleColumns: tableVisibleColumns:
ref: Reference ref: Reference
@ -726,62 +721,6 @@ travel:
destination: Destination destination: Destination
thermograph: Thermograph thermograph: Thermograph
travelFileDescription: 'Travel id { travelId }' travelFileDescription: 'Travel id { travelId }'
item:
descriptor:
buyer: Buyer
color: Color
category: Category
available: Available
warehouseText: 'Calculated on the warehouse of { warehouseName }'
itemDiary: Item diary
list:
id: Identifier
stems: Stems
category: Category
typeName: Type
isActive: Active
userName: Buyer
weightByPiece: Weight/Piece
stemMultiplier: Multiplier
fixedPrice:
itemFk: Item ID
groupingPrice: Grouping price
packingPrice: Packing price
hasMinPrice: Has min price
minPrice: Min price
started: Started
ended: Ended
create:
priority: Priority
buyRequest:
requester: Requester
requested: Requested
attender: Atender
achieved: Achieved
concept: Concept
summary:
otherData: Other data
tax: Tax
botanical: Botanical
barcode: Barcode
completeName: Complete name
family: Familiy
stems: Stems
multiplier: Multiplier
buyer: Buyer
doPhoto: Do photo
intrastatCode: Intrastat code
ref: Reference
relevance: Relevance
weight: Weight (gram)/stem
units: Units/box
expense: Expense
generic: Generic
recycledPlastic: Recycled plastic
nonRecycledPlastic: Non recycled plastic
minSalesQuantity: Min sales quantity
genus: Genus
specie: Specie
components: components:
topbar: {} topbar: {}
itemsFilterPanel: itemsFilterPanel:

View File

@ -5,9 +5,11 @@ globals:
language: Idioma language: Idioma
quantity: Cantidad quantity: Cantidad
entity: Entidad entity: Entidad
preview: Vista previa
user: Usuario user: Usuario
details: Detalles details: Detalles
collapseMenu: Contraer menú lateral collapseMenu: Contraer menú lateral
advancedMenu: Menú avanzado
backToDashboard: Volver al tablón backToDashboard: Volver al tablón
notifications: Notificaciones notifications: Notificaciones
userPanel: Panel de usuario userPanel: Panel de usuario
@ -53,11 +55,12 @@ globals:
today: Hoy today: Hoy
yesterday: Ayer yesterday: Ayer
dateFormat: es-ES dateFormat: es-ES
noSelectedRows: No tienes ninguna línea seleccionada
microsip: Abrir en MicroSIP microsip: Abrir en MicroSIP
noSelectedRows: No tienes ninguna línea seleccionada
downloadCSVSuccess: Descarga de CSV exitosa downloadCSVSuccess: Descarga de CSV exitosa
reference: Referencia reference: Referencia
agency: Agencia agency: Agencia
entry: Entrada
warehouseOut: Alm. salida warehouseOut: Alm. salida
warehouseIn: Alm. entrada warehouseIn: Alm. entrada
landed: F. entrega landed: F. entrega
@ -132,6 +135,26 @@ globals:
medium: Mediano/a medium: Mediano/a
big: Grande big: Grande
email: Correo email: Correo
supplier: Proveedor
ticketList: Listado de tickets
created: Fecha creación
worker: Trabajador
now: Ahora
name: Nombre
new: Nuevo
comment: Comentario
observations: Observaciones
goToModuleIndex: Ir al índice del módulo
createInvoiceIn: Crear factura recibida
myAccount: Mi cuenta
noOne: Nadie
maxTemperature: Máx
minTemperature: Mín
changePass: Cambiar contraseña
deleteConfirmTitle: Eliminar los elementos seleccionados
changeState: Cambiar estado
raid: 'Redada {daysInForward} días'
isVies: Vies
pageTitles: pageTitles:
logIn: Inicio de sesión logIn: Inicio de sesión
addressEdit: Modificar consignatario addressEdit: Modificar consignatario
@ -154,17 +177,17 @@ globals:
inheritedRoles: Roles heredados inheritedRoles: Roles heredados
customers: Clientes customers: Clientes
customerCreate: Nuevo cliente customerCreate: Nuevo cliente
createCustomer: Crear cliente
createOrder: Nuevo pedido createOrder: Nuevo pedido
list: Listado list: Listado
webPayments: Pagos Web webPayments: Pagos Web
extendedList: Listado extendido extendedList: Listado extendido
notifications: Notificaciones notifications: Notificaciones
defaulter: Morosos defaulter: Morosos
createCustomer: Crear cliente
fiscalData: Datos fiscales fiscalData: Datos fiscales
billingData: Forma de pago billingData: Forma de pago
consignees: Consignatarios consignees: Consignatarios
'address-create': Nuevo consignatario address-create: Nuevo consignatario
notes: Notas notes: Notas
credits: Créditos credits: Créditos
greuges: Greuges greuges: Greuges
@ -230,10 +253,10 @@ globals:
wagonsList: Listado vagones wagonsList: Listado vagones
wagonCreate: Crear tipo wagonCreate: Crear tipo
wagonEdit: Editar tipo wagonEdit: Editar tipo
wagonCounter: Contador de carros
typesList: Listado tipos typesList: Listado tipos
typeCreate: Crear tipo typeCreate: Crear tipo
typeEdit: Editar tipo typeEdit: Editar tipo
wagonCounter: Contador de carros
roadmap: Troncales roadmap: Troncales
stops: Paradas stops: Paradas
routes: Rutas routes: Rutas
@ -242,8 +265,8 @@ globals:
routeCreate: Nueva ruta routeCreate: Nueva ruta
RouteRoadmap: Troncales RouteRoadmap: Troncales
RouteRoadmapCreate: Crear troncal RouteRoadmapCreate: Crear troncal
autonomous: Autónomos
RouteExtendedList: Enrutador RouteExtendedList: Enrutador
autonomous: Autónomos
suppliers: Proveedores suppliers: Proveedores
supplier: Proveedor supplier: Proveedor
supplierCreate: Nuevo proveedor supplierCreate: Nuevo proveedor
@ -308,23 +331,9 @@ globals:
wasteRecalc: Recalcular mermas wasteRecalc: Recalcular mermas
operator: Operario operator: Operario
parking: Parking parking: Parking
supplier: Proveedor
created: Fecha creación
worker: Trabajador
now: Ahora
name: Nombre
new: Nuevo
comment: Comentario
observations: Observaciones
goToModuleIndex: Ir al índice del módulo
unsavedPopup: unsavedPopup:
title: Los cambios que no haya guardado se perderán title: Los cambios que no haya guardado se perderán
subtitle: ¿Seguro que quiere salir sin guardar? subtitle: ¿Seguro que quiere salir sin guardar?
createInvoiceIn: Crear factura recibida
myAccount: Mi cuenta
noOne: Nadie
maxTemperature: Máx
minTemperature: Mín
params: params:
clientFk: Id cliente clientFk: Id cliente
salesPersonFk: Comercial salesPersonFk: Comercial
@ -347,12 +356,6 @@ globals:
packing: ITP packing: ITP
countryFk: País countryFk: País
companyFk: Empresa companyFk: Empresa
changePass: Cambiar contraseña
setPass: Establecer contraseña
deleteConfirmTitle: Eliminar los elementos seleccionados
changeState: Cambiar estado
raid: 'Redada {daysInForward} días'
isVies: Vies
errors: errors:
statusUnauthorized: Acceso denegado statusUnauthorized: Acceso denegado
statusInternalServerError: Ha ocurrido un error interno del servidor statusInternalServerError: Ha ocurrido un error interno del servidor
@ -447,11 +450,15 @@ ticket:
attender: Consignatario attender: Consignatario
create: create:
address: Dirección address: Dirección
invoiceOut: order:
card: field:
issued: Fecha emisión salesPersonFk: Comercial
customerCard: Ficha del cliente form:
ticketList: Listado de tickets clientFk: Cliente
addressFk: Dirección
agencyModeFk: Agencia
list:
newOrder: Nuevo Pedido
summary: summary:
issued: Fecha issued: Fecha
dued: Fecha límite dued: Fecha límite
@ -462,47 +469,6 @@ invoiceOut:
fee: Cuota fee: Cuota
tickets: Tickets tickets: Tickets
totalWithVat: Importe totalWithVat: Importe
globalInvoices:
errors:
chooseValidClient: Selecciona un cliente válido
chooseValidCompany: Selecciona una empresa válida
chooseValidPrinter: Selecciona una impresora válida
chooseValidSerialType: Selecciona una tipo de serie 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 existen tickets para facturar
criticalInvoiceError: Error crítico en la facturación proceso detenido
invalidSerialTypeForAll: El tipo de serie debe ser global cuando se facturan todos los clientes
table:
addressId: Id dirección
streetAddress: Dirección fiscal
statusCard:
percentageText: '{getPercentage}% {getAddressNumber} de {getNAddresses}'
pdfsNumberText: '{nPdfs} de {totalPdfs} PDFs'
negativeBases:
clientId: Id cliente
base: Base
active: Activo
hasToInvoice: Facturar
verifiedData: Datos comprobados
comercial: Comercial
errors:
downloadCsvFailed: Error al descargar CSV
shelving:
list:
parking: Parking
priority: Prioridad
newShelving: Nuevo Carro
summary:
recyclable: Reciclable
parking:
pickingOrder: Orden de recogida
row: Fila
column: Columna
searchBar:
info: Puedes buscar por código de parking
label: Buscar parking...
department: department:
chat: Chat chat: Chat
bossDepartment: Jefe de departamento bossDepartment: Jefe de departamento
@ -692,6 +658,9 @@ supplier:
consumption: consumption:
entry: Entrada entry: Entrada
travel: travel:
search: Buscar envío
searchInfo: Buscar envío por id o nombre
id: Id
travelList: travelList:
tableVisibleColumns: tableVisibleColumns:
ref: Referencia ref: Referencia
@ -722,62 +691,6 @@ travel:
destination: Destino destination: Destino
thermograph: Termógrafo thermograph: Termógrafo
travelFileDescription: 'Id envío { travelId }' travelFileDescription: 'Id envío { travelId }'
item:
descriptor:
buyer: Comprador
color: Color
category: Categoría
available: Disponible
warehouseText: 'Calculado sobre el almacén de { warehouseName }'
itemDiary: Registro de compra-venta
list:
id: Identificador
stems: Tallos
category: Reino
typeName: Tipo
isActive: Activo
weightByPiece: Peso (gramos)/tallo
userName: Comprador
stemMultiplier: Multiplicador
fixedPrice:
itemFk: ID Artículo
groupingPrice: Precio grouping
packingPrice: Precio packing
hasMinPrice: Tiene precio mínimo
minPrice: Precio min
started: Inicio
ended: Fin
create:
priority: Prioridad
summary:
otherData: Otros datos
tax: IVA
botanical: Botánico
barcode: Código de barras
completeName: Nombre completo
family: Familia
stems: Tallos
multiplier: Multiplicador
buyer: Comprador
doPhoto: Hacer foto
intrastatCode: Código intrastat
ref: Referencia
relevance: Relevancia
weight: Peso (gramos)/tallo
units: Unidades/caja
expense: Gasto
generic: Genérico
recycledPlastic: Plástico reciclado
nonRecycledPlastic: Plástico no reciclado
minSalesQuantity: Cantidad mínima de venta
genus: Genus
specie: Specie
buyRequest:
requester: Solicitante
requested: Solicitado
attender: Comprador
achieved: Conseguido
concept: Concepto
components: components:
topbar: {} topbar: {}
itemsFilterPanel: itemsFilterPanel:

View File

@ -4,7 +4,6 @@ import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import axios from 'axios'; import axios from 'axios';
import { useStateStore } from 'src/stores/useStateStore';
import { toDate, toPercentage, toCurrency } from 'filters/index'; import { toDate, toPercentage, toCurrency } from 'filters/index';
import { tMobile } from 'src/composables/tMobile'; import { tMobile } from 'src/composables/tMobile';
import CrudModel from 'src/components/CrudModel.vue'; import CrudModel from 'src/components/CrudModel.vue';
@ -13,11 +12,11 @@ import VnSelect from 'src/components/common/VnSelect.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue'; import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import RightMenu from 'src/components/common/RightMenu.vue';
const { t } = useI18n(); const { t } = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
const route = useRoute(); const route = useRoute();
const stateStore = computed(() => useStateStore());
const claim = ref(null); const claim = ref(null);
const claimRef = ref(); const claimRef = ref();
const claimId = route.params.id; const claimId = route.params.id;
@ -201,58 +200,62 @@ async function post(query, params) {
auto-load auto-load
@on-fetch="(data) => (destinationTypes = data)" @on-fetch="(data) => (destinationTypes = data)"
/> />
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted() && claim"> <RightMenu v-if="claim">
<QCard class="totalClaim q-my-md q-pa-sm no-box-shadow"> <template #right-panel>
{{ `${t('Total claimed')}: ${toCurrency(totalClaimed)}` }} <QCard class="totalClaim q-my-md q-pa-sm no-box-shadow">
</QCard> {{ `${t('Total claimed')}: ${toCurrency(totalClaimed)}` }}
<QCard class="q-mb-md q-pa-sm no-box-shadow"> </QCard>
<QItem class="justify-between"> <QCard class="q-mb-md q-pa-sm no-box-shadow">
<QItemLabel class="slider-container"> <QItem class="justify-between">
<p class="text-primary"> <QItemLabel class="slider-container">
{{ t('claim.actions') }} <p class="text-primary">
</p> {{ t('claim.actions') }}
<QSlider </p>
class="responsibility { 'background-color:primary': quasar.platform.is.mobile }" <QSlider
v-model="claim.responsibility" class="responsibility { 'background-color:primary': quasar.platform.is.mobile }"
:label-value="t('claim.responsibility')" v-model="claim.responsibility"
@change="(value) => save({ responsibility: value })" :label-value="t('claim.responsibility')"
label-always @change="(value) => save({ responsibility: value })"
color="primary" label-always
markers color="primary"
:marker-labels="marker_labels" markers
:min="DEFAULT_MIN_RESPONSABILITY" :marker-labels="marker_labels"
:max="DEFAULT_MAX_RESPONSABILITY" :min="DEFAULT_MIN_RESPONSABILITY"
:max="DEFAULT_MAX_RESPONSABILITY"
/>
</QItemLabel>
</QItem>
</QCard>
<QCard class="q-mb-md q-pa-sm no-box-shadow" style="margin-bottom: 1em">
<QItemLabel class="mana q-mb-md">
<QCheckbox
v-model="claim.isChargedToMana"
@update:model-value="(value) => save({ isChargedToMana: value })"
/> />
<span>{{ t('mana') }}</span>
</QItemLabel> </QItemLabel>
</QItem> </QCard>
</QCard> <QCard class="q-mb-md q-pa-sm no-box-shadow" style="position: static">
<QCard class="q-mb-md q-pa-sm no-box-shadow" style="margin-bottom: 1em"> <QInput
<QItemLabel class="mana q-mb-md"> :disable="
<QCheckbox !(
v-model="claim.isChargedToMana" claim.responsibility >=
@update:model-value="(value) => save({ isChargedToMana: value })" Math.ceil(DEFAULT_MAX_RESPONSABILITY) / 2
)
"
:label="t('confirmGreuges')"
class="q-field__native text-grey-2"
type="number"
placeholder="0"
id="multiplicatorValue"
name="multiplicatorValue"
min="0"
max="50"
v-model="multiplicatorValue"
/> />
<span>{{ t('mana') }}</span> </QCard>
</QItemLabel> </template>
</QCard> </RightMenu>
<QCard class="q-mb-md q-pa-sm no-box-shadow" style="position: static">
<QInput
:disable="
!(claim.responsibility >= Math.ceil(DEFAULT_MAX_RESPONSABILITY) / 2)
"
:label="t('confirmGreuges')"
class="q-field__native text-grey-2"
type="number"
placeholder="0"
id="multiplicatorValue"
name="multiplicatorValue"
min="0"
max="50"
v-model="multiplicatorValue"
/>
</QCard>
</Teleport>
<Teleport to="#st-data" v-if="stateStore.isSubToolbarShown()"> </Teleport>
<CrudModel <CrudModel
v-if="claim" v-if="claim"
data-key="ClaimEnds" data-key="ClaimEnds"

View File

@ -134,7 +134,7 @@ const STATE_COLOR = {
order: ['cs.priority ASC', 'created ASC'], order: ['cs.priority ASC', 'created ASC'],
}" }"
> >
<template #rightMenu> <template #advanced-menu>
<ClaimFilter data-key="ClaimList" ref="claimFilterRef" /> <ClaimFilter data-key="ClaimList" ref="claimFilterRef" />
</template> </template>
<template #body> <template #body>

View File

@ -152,7 +152,8 @@ const updateDateParams = (value, params) => {
v-if="campaignList" v-if="campaignList"
data-key="CustomerConsumption" data-key="CustomerConsumption"
url="Clients/consumption" url="Clients/consumption"
:order="['itemTypeFk', 'itemName', 'itemSize', 'description']" :order="['itemTypeFk', 'itemName', 'itemSize', 'description']"
:filter="{ where: { clientFk: route.params.id } }"
:columns="columns" :columns="columns"
search-url="consumption" search-url="consumption"
:user-params="userParams" :user-params="userParams"

View File

@ -187,14 +187,18 @@ const debtWarning = computed(() => {
</QBtn> </QBtn>
<QBtn <QBtn
:to="{ :to="{
name: 'AccountSummary', name: 'OrderList',
params: { id: entity.id }, query: {
createForm: JSON.stringify({
clientFk: entity.id,
}),
},
}" }"
size="md" size="md"
icon="face" icon="vn:basketadd"
color="primary" color="primary"
> >
<QTooltip>{{ t('Go to user') }}</QTooltip> <QTooltip>{{ t('globals.pageTitles.createOrder') }}</QTooltip>
</QBtn> </QBtn>
<QBtn <QBtn
v-if="entity.supplier" v-if="entity.supplier"
@ -218,14 +222,9 @@ en:
unpaidDated: 'Date {dated}' unpaidDated: 'Date {dated}'
unpaidAmount: 'Amount {amount}' unpaidAmount: 'Amount {amount}'
es: es:
Go to module index: Ir al índice del módulo
Customer ticket list: Listado de tickets del cliente Customer ticket list: Listado de tickets del cliente
Customer invoice out list: Listado de facturas del cliente Customer invoice out list: Listado de facturas del cliente
New order: Nuevo pedido
New ticket: Nuevo ticket
Go to user: Ir al usuario
Go to supplier: Ir al proveedor Go to supplier: Ir al proveedor
Customer unpaid: Cliente impago
Unpaid: Impagado Unpaid: Impagado
unpaidDated: 'Fecha {dated}' unpaidDated: 'Fecha {dated}'
unpaidAmount: 'Importe {amount}' unpaidAmount: 'Importe {amount}'

View File

@ -51,7 +51,6 @@ const openCreateForm = (type) => {
}; };
const clientFk = { const clientFk = {
ticket: 'clientId', ticket: 'clientId',
order: 'clientFk',
}; };
const key = clientFk[type]; const key = clientFk[type];
if (!key) return; if (!key) return;
@ -70,11 +69,6 @@ const openCreateForm = (type) => {
{{ t('globals.pageTitles.createTicket') }} {{ t('globals.pageTitles.createTicket') }}
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem v-ripple clickable @click="openCreateForm('order')">
<QItemSection>
{{ t('globals.pageTitles.createOrder') }}
</QItemSection>
</QItem>
<QItem v-ripple clickable> <QItem v-ripple clickable>
<QItemSection @click="showSmsDialog()">{{ t('Send SMS') }}</QItemSection> <QItemSection @click="showSmsDialog()">{{ t('Send SMS') }}</QItemSection>
</QItem> </QItem>

View File

@ -50,7 +50,8 @@ const filterClientFindOne = {
> >
<template #form="{ data }"> <template #form="{ data }">
<VnRow> <VnRow>
<QCheckbox :label="t('Unpaid client')" v-model="data.unpaid" /> <QCheckbox :label="t('Unpaid client')" v-model="data.unpaid"
data-cy="UnpaidCheckBox" />
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md" v-show="data.unpaid"> <VnRow class="row q-gutter-md q-mb-md" v-show="data.unpaid">

View File

@ -408,7 +408,7 @@ function handleLocation(data, location) {
order: ['id DESC'], order: ['id DESC'],
}" }"
> >
<template #rightMenu> <template #advanced-menu>
<CustomerFilter data-key="CustomerList" /> <CustomerFilter data-key="CustomerList" />
</template> </template>
<template #body> <template #body>

View File

@ -11,14 +11,14 @@ import VnInput from 'src/components/common/VnInput.vue';
import CustomerDefaulterAddObservation from './CustomerDefaulterAddObservation.vue'; import CustomerDefaulterAddObservation from './CustomerDefaulterAddObservation.vue';
import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue'; import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue';
import VnTable from 'src/components/VnTable/VnTable.vue'; import VnTable from 'src/components/VnTable/VnTable.vue';
import { useArrayData } from 'src/composables/useArrayData';
const { t } = useI18n(); const { t } = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
const dataRef = ref(null); const dataRef = ref(null);
const balanceDueTotal = ref(0);
const selected = ref([]); const selected = ref([]);
const arrayData = useArrayData('CustomerDefaulter');
const columns = computed(() => [ const columns = computed(() => [
{ {
align: 'left', align: 'left',
@ -165,26 +165,37 @@ const viewAddObservation = (rowsSelected) => {
}); });
}; };
const onFetch = async (data) => {
balanceDueTotal.value = data.reduce((acc, { amount = 0 }) => acc + amount, 0);
};
function exprBuilder(param, value) { function exprBuilder(param, value) {
switch (param) { switch (param) {
case 'clientFk':
return { [`d.${param}`]: value };
case 'creditInsurance':
case 'amount':
case 'workerFk':
case 'departmentFk':
case 'countryFk':
case 'payMethod':
case 'salesPersonFk': case 'salesPersonFk':
case 'creditInsurance':
case 'countryFk':
return { [`c.${param}`]: value };
case 'payMethod':
return { [`c.payMethodFk`]: value };
case 'workerFk':
return { [`co.${param}`]: value };
case 'departmentFk':
return { [`wd.${param}`]: value };
case 'amount':
case 'clientFk':
return { [`d.${param}`]: value }; return { [`d.${param}`]: value };
case 'created': case 'created':
return { 'd.created': { between: dateRange(value) } }; return { 'd.created': { between: dateRange(value) } };
case 'defaulterSinced': case 'defaulterSinced':
return { 'd.defaulterSinced': { between: dateRange(value) } }; return { 'd.defaulterSinced': { between: dateRange(value) } };
case 'isWorker': {
if (value == undefined) return;
const search = value ? 'worker' : { neq: 'worker' };
return { 'c.businessTypeFk': search };
}
case 'hasRecovery': {
if (value == undefined) return;
const search = value ? null : { neq: null };
return { 'r.finished': search };
}
case 'observation':
return { 'co.text': { like: `%${value}%` } };
} }
} }
</script> </script>
@ -192,7 +203,7 @@ function exprBuilder(param, value) {
<template> <template>
<VnSubToolbar> <VnSubToolbar>
<template #st-data> <template #st-data>
<CustomerBalanceDueTotal :amount="balanceDueTotal" /> <CustomerBalanceDueTotal :amount="arrayData.store.data?.amount" />
</template> </template>
<template #st-actions> <template #st-actions>
<QBtn <QBtn
@ -211,8 +222,6 @@ function exprBuilder(param, value) {
url="Defaulters/filter" url="Defaulters/filter"
:expr-builder="exprBuilder" :expr-builder="exprBuilder"
:columns="columns" :columns="columns"
@on-fetch="onFetch"
:use-model="true"
:table="{ :table="{
'row-key': 'clientFk', 'row-key': 'clientFk',
selection: 'multiple', selection: 'multiple',
@ -221,6 +230,7 @@ function exprBuilder(param, value) {
:disable-option="{ card: true }" :disable-option="{ card: true }"
auto-load auto-load
:order="['amount DESC']" :order="['amount DESC']"
key-data="defaulters"
> >
<template #column-clientFk="{ row }"> <template #column-clientFk="{ row }">
<span class="link" @click.stop> <span class="link" @click.stop>

View File

@ -214,7 +214,7 @@ const toCustomerSamples = () => {
<template #custom-buttons> <template #custom-buttons>
<QBtn <QBtn
:disabled="isLoading || !sampleType?.hasPreview" :disabled="isLoading || !sampleType?.hasPreview"
:label="t('Preview')" :label="t('globals.preview')"
:loading="isLoading" :loading="isLoading"
@click.stop="getPreview()" @click.stop="getPreview()"
color="primary" color="primary"
@ -353,7 +353,6 @@ es:
Its only used when sample is sent: Se utiliza únicamente cuando se envía la plantilla Its only used when sample is sent: Se utiliza únicamente cuando se envía la plantilla
To who should the recipient replay?: ¿A quien debería responder el destinatario? To who should the recipient replay?: ¿A quien debería responder el destinatario?
Edit address: Editar dirección Edit address: Editar dirección
Preview: Vista previa
Email cannot be blank: Debes introducir un email Email cannot be blank: Debes introducir un email
Choose a sample: Selecciona una plantilla Choose a sample: Selecciona una plantilla
Choose a company: Selecciona una empresa Choose a company: Selecciona una empresa

View File

@ -0,0 +1,33 @@
import { describe, it, expect, vi, afterEach } from 'vitest';
import axios from 'axios';
import { getAddresses } from 'src/pages/Customer/composables/getAddresses';
vi.mock('axios');
describe('getAddresses', () => {
afterEach(() => {
vi.clearAllMocks();
});
it('should fetch addresses with correct parameters for a valid clientId', async () => {
const clientId = '12345';
await getAddresses(clientId);
expect(axios.get).toHaveBeenCalledWith(`Clients/${clientId}/addresses`, {
params: {
filter: JSON.stringify({
fields: ['nickname', 'street', 'city', 'id'],
where: { isActive: true },
order: 'nickname ASC',
}),
},
});
});
it('should return undefined when clientId is not provided', async () => {
await getAddresses(undefined);
expect(axios.get).not.toHaveBeenCalled();
});
});

View File

@ -0,0 +1,41 @@
import { describe, it, expect, vi, afterEach } from 'vitest';
import axios from 'axios';
import { getClient } from 'src/pages/Customer/composables/getClient';
vi.mock('axios');
describe('getClient', () => {
afterEach(() => {
vi.clearAllMocks();
});
const generateParams = (clientId) => ({
params: {
filter: JSON.stringify({
include: {
relation: 'defaultAddress',
scope: {
fields: ['id', 'agencyModeFk'],
},
},
where: { id: clientId },
}),
},
});
it('should fetch client data with correct parameters for a valid clientId', async () => {
const clientId = '12345';
await getClient(clientId);
expect(axios.get).toHaveBeenCalledWith('Clients', generateParams(clientId));
});
it('should return undefined when clientId is not provided', async () => {
const clientId = undefined;
await getClient(clientId);
expect(axios.get).toHaveBeenCalledWith('Clients', generateParams(clientId));
});
});

View File

@ -0,0 +1,14 @@
import axios from 'axios';
export async function getAddresses(clientId) {
if (!clientId) return;
const filter = {
fields: ['nickname', 'street', 'city', 'id'],
where: { isActive: true },
order: 'nickname ASC',
};
const params = { filter: JSON.stringify(filter) };
return await axios.get(`Clients/${clientId}/addresses`, {
params,
});
};

View File

@ -0,0 +1,15 @@
import axios from 'axios';
export async function getClient(clientId) {
const filter = {
include: {
relation: 'defaultAddress',
scope: {
fields: ['id', 'agencyModeFk'],
},
},
where: { id: clientId },
};
const params = { filter: JSON.stringify(filter) };
return await axios.get('Clients', { params });
};

View File

@ -147,7 +147,7 @@ es:
Supplier card: Ficha del proveedor Supplier card: Ficha del proveedor
All travels with current agency: Todos los envíos con la agencia actual All travels with current agency: Todos los envíos con la agencia actual
All entries with current supplier: Todas las entradas con el proveedor actual All entries with current supplier: Todas las entradas con el proveedor actual
Go to module index: Ir al índice del modulo Show entry report: Ver informe del pedido
Inventory entry: Es inventario Inventory entry: Es inventario
Virtual entry: Es una redada Virtual entry: Es una redada
</i18n> </i18n>

View File

@ -205,7 +205,7 @@ const columns = computed(() => [
userFilter: entryFilter, userFilter: entryFilter,
}" }"
> >
<template #rightMenu> <template #advanced-menu>
<EntryFilter data-key="EntryList" /> <EntryFilter data-key="EntryList" />
</template> </template>
<template #body> <template #body>
@ -231,7 +231,7 @@ const columns = computed(() => [
> >
<QTooltip>{{ <QTooltip>{{
t( t(
'entry.list.tableVisibleColumns.isExcludedFromAvailable' 'entry.list.tableVisibleColumns.isExcludedFromAvailable',
) )
}}</QTooltip> }}</QTooltip>
</QIcon> </QIcon>

View File

@ -1,19 +1,11 @@
<script setup> <script setup>
import InvoiceOutDescriptor from './InvoiceOutDescriptor.vue'; import InvoiceOutDescriptor from './InvoiceOutDescriptor.vue';
import VnCard from 'components/common/VnCard.vue'; import VnCardBeta from 'components/common/VnCardBeta.vue';
import InvoiceOutFilter from '../InvoiceOutFilter.vue';
</script> </script>
<template> <template>
<VnCard <VnCardBeta
data-key="InvoiceOut" data-key="InvoiceOut"
base-url="InvoiceOuts" base-url="InvoiceOuts"
:descriptor="InvoiceOutDescriptor" :descriptor="InvoiceOutDescriptor"
:filter-panel="InvoiceOutFilter"
search-data-key="InvoiceOutList"
:searchbar-props="{
url: 'InvoiceOuts/filter',
label: 'Search invoice',
info: 'You can search by invoice reference',
}"
/> />
</template> </template>

View File

@ -3,7 +3,6 @@ import { ref, computed, watchEffect } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { usePrintService } from 'src/composables/usePrintService'; import { usePrintService } from 'src/composables/usePrintService';
@ -12,12 +11,12 @@ import InvoiceOutSummary from './Card/InvoiceOutSummary.vue';
import { toCurrency, toDate } from 'src/filters/index'; import { toCurrency, toDate } from 'src/filters/index';
import { QBtn } from 'quasar'; import { QBtn } from 'quasar';
import axios from 'axios'; import axios from 'axios';
import RightMenu from 'src/components/common/RightMenu.vue';
import InvoiceOutFilter from './InvoiceOutFilter.vue'; import InvoiceOutFilter from './InvoiceOutFilter.vue';
import VnRow from 'src/components/ui/VnRow.vue'; import VnRow from 'src/components/ui/VnRow.vue';
import VnRadio from 'src/components/common/VnRadio.vue'; import VnRadio from 'src/components/common/VnRadio.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue';
import VnSection from 'src/components/common/VnSection.vue';
const { t } = useI18n(); const { t } = useI18n();
const { viewSummary } = useSummaryDialog(); const { viewSummary } = useSummaryDialog();
@ -30,9 +29,11 @@ const MODEL = 'InvoiceOuts';
const { openReport } = usePrintService(); const { openReport } = usePrintService();
const addressOptions = ref([]); const addressOptions = ref([]);
const selectedOption = ref('ticket'); const selectedOption = ref('ticket');
const dataKey = 'InvoiceOutList';
async function fetchClientAddress(id) { async function fetchClientAddress(id) {
const { data } = await axios.get( const { data } = await axios.get(
`Clients/${id}/addresses?filter[order]=isActive DESC` `Clients/${id}/addresses?filter[order]=isActive DESC`,
); );
addressOptions.value = data; addressOptions.value = data;
} }
@ -180,223 +181,239 @@ watchEffect(selectedRows);
</script> </script>
<template> <template>
<VnSearchbar <VnSection
:info="t('youCanSearchByInvoiceReference')" :data-key="dataKey"
:label="t('Search invoice')"
data-key="invoiceOutList"
/>
<RightMenu>
<template #right-panel>
<InvoiceOutFilter data-key="invoiceOutList" />
</template>
</RightMenu>
<VnSubToolbar>
<template #st-actions>
<QBtn
color="primary"
icon-right="cloud_download"
@click="downloadPdf()"
:disable="!hasSelectedCards"
data-cy="InvoiceOutDownloadPdfBtn"
>
<QTooltip>{{ t('downloadPdf') }}</QTooltip>
</QBtn>
</template>
</VnSubToolbar>
<VnTable
ref="tableRef"
data-key="invoiceOutList"
:url="`${MODEL}/filter`"
:create="{
urlCreate: 'InvoiceOuts/createManualInvoice',
title: t('createManualInvoice'),
onDataSaved: ({ id }) => tableRef.redirect(id),
formInitialData: { active: true },
}"
:right-search="false"
v-model:selected="selectedRows"
order="id DESC"
:columns="columns" :columns="columns"
redirect="invoice-out" prefix="invoiceOut"
:table="{ :array-data-props="{
'row-key': 'id', url: 'InvoiceOuts/filter',
selection: 'multiple', order: ['id DESC'],
}" }"
> >
<template #column-clientFk="{ row }"> <template #advanced-menu>
<span class="link" @click.stop> <InvoiceOutFilter data-key="InvoiceOutList" />
{{ row.clientSocialName }}
<CustomerDescriptorProxy :id="row.clientFk" />
</span>
</template> </template>
<template #more-create-dialog="{ data }"> <template #body>
<div class="row q-col-gutter-xs"> <VnSubToolbar>
<div class="col-12"> <template #st-actions>
<div class="q-col-gutter-xs"> <QBtn
<VnRow fixed> color="primary"
<VnRadio icon-right="cloud_download"
v-model="selectedOption" @click="downloadPdf()"
val="ticket" :disable="!hasSelectedCards"
:label="t('globals.ticket')" data-cy="InvoiceOutDownloadPdfBtn"
class="q-my-none q-mr-md" >
/> <QTooltip>{{ t('globals.downloadPdf') }}</QTooltip>
</QBtn>
</template>
</VnSubToolbar>
<VnTable
ref="tableRef"
:data-key="dataKey"
:create="{
urlCreate: 'InvoiceOuts/createManualInvoice',
title: t('createManualInvoice'),
onDataSaved: ({ id }) => tableRef.redirect(id),
formInitialData: { active: true },
}"
:right-search="false"
v-model:selected="selectedRows"
:columns="columns"
redirect="invoice-out"
:table="{
'row-key': 'id',
selection: 'multiple',
}"
>
<template #column-clientFk="{ row }">
<span class="link" @click.stop>
{{ row.clientSocialName }}
<CustomerDescriptorProxy :id="row.clientFk" />
</span>
</template>
<template #more-create-dialog="{ data }">
<div class="row q-col-gutter-xs">
<div class="col-12">
<div class="q-col-gutter-xs">
<VnRow fixed>
<VnRadio
v-model="selectedOption"
val="ticket"
:label="t('globals.ticket')"
class="q-my-none q-mr-md"
/>
<VnInput <VnInput
v-show="selectedOption === 'ticket'" v-show="selectedOption === 'ticket'"
v-model="data.ticketFk" v-model="data.ticketFk"
:label="t('globals.ticket')" :label="t('globals.ticket')"
style="flex: 1" style="flex: 1"
data-cy="InvoiceOutCreateTicketinput" data-cy="InvoiceOutCreateTicketinput"
/> />
<div <div
class="row q-col-gutter-xs q-ml-none" class="row q-col-gutter-xs q-ml-none"
v-show="selectedOption !== 'ticket'" v-show="selectedOption !== 'ticket'"
> >
<div class="col"> <div class="col">
<VnSelect <VnSelect
v-model="data.clientFk" v-model="data.clientFk"
:label="t('globals.client')"
url="Clients"
:options="customerOptions"
option-label="name"
option-value="id"
@update:model-value="fetchClientAddress"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
#{{ scope.opt?.id }} -
{{ scope.opt?.name }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</div>
<div class="col">
<VnSelect
v-model="data.addressFk"
:label="t('ticket.summary.consignee')"
:options="addressOptions"
option-label="nickname"
option-value="id"
v-if="
data.clientFk &&
selectedOption === 'consignatario'
"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel
:class="{
'color-vn-label':
!scope.opt
?.isActive,
}"
>
{{
`${
!scope.opt
?.isActive
? t(
'inactive',
)
: ''
} `
}}
<span>{{
scope.opt?.nickname
}}</span>
<span
v-if="
scope.opt
?.province ||
scope.opt?.city ||
scope.opt?.street
"
>
,
{{
scope.opt?.street
}},
{{ scope.opt?.city }},
{{
scope.opt
?.province
?.name
}}
-
{{
scope.opt
?.agencyMode
?.name
}}
</span>
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</div>
</div>
</VnRow>
<VnRow fixed>
<VnRadio
v-model="selectedOption"
val="cliente"
:label="t('globals.client')" :label="t('globals.client')"
url="Clients" class="q-my-none q-mr-md"
:options="customerOptions" />
option-label="name" </VnRow>
option-value="id" <VnRow fixed>
@update:model-value="fetchClientAddress" <VnRadio
> v-model="selectedOption"
<template #option="scope"> val="consignatario"
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
#{{ scope.opt?.id }} -
{{ scope.opt?.name }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</div>
<div class="col">
<VnSelect
v-model="data.addressFk"
:label="t('ticket.summary.consignee')" :label="t('ticket.summary.consignee')"
:options="addressOptions" class="q-my-none q-mr-md"
option-label="nickname" />
option-value="id" </VnRow>
v-if="
data.clientFk &&
selectedOption === 'consignatario'
"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel
:class="{
'color-vn-label':
!scope.opt?.isActive,
}"
>
{{
`${
!scope.opt?.isActive
? t('inactive')
: ''
} `
}}
<span>{{
scope.opt?.nickname
}}</span>
<span
v-if="
scope.opt?.province ||
scope.opt?.city ||
scope.opt?.street
"
>
, {{ scope.opt?.street }},
{{ scope.opt?.city }},
{{
scope.opt?.province?.name
}}
-
{{
scope.opt?.agencyMode
?.name
}}
</span>
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</div>
</div> </div>
</VnRow> </div>
<VnRow fixed> <div class="full-width">
<VnRadio <VnRow class="row q-col-gutter-xs">
v-model="selectedOption" <VnSelect
val="cliente" url="InvoiceOutSerials"
:label="t('globals.client')" v-model="data.serial"
class="q-my-none q-mr-md" :label="t('invoicein.serial')"
/> :options="invoiceOutSerialsOptions"
</VnRow> option-label="description"
<VnRow fixed> option-value="code"
<VnRadio option-filter
v-model="selectedOption" :expr-builder="exprBuilder"
val="consignatario" data-cy="InvoiceOutCreateSerialSelect"
:label="t('ticket.summary.consignee')" >
class="q-my-none q-mr-md" <template #option="scope">
/> <QItem v-bind="scope.itemProps">
</VnRow> <QItemSection>
<QItemLabel>
{{ scope.opt?.code }} -
{{ scope.opt?.description }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
<VnInputDate
:label="t('invoiceOut.summary.dued')"
v-model="data.maxShipped"
/>
</VnRow>
<VnRow class="row q-col-gutter-xs">
<VnSelect
url="TaxAreas"
v-model="data.taxArea"
:label="
t('invoiceOutList.tableVisibleColumns.taxArea')
"
:options="taxAreasOptions"
option-label="code"
option-value="code"
/>
<VnInput
v-model="data.reference"
:label="t('globals.reference')"
/>
</VnRow>
</div>
</div> </div>
</div> </template>
<div class="full-width"> </VnTable>
<VnRow class="row q-col-gutter-xs">
<VnSelect
url="InvoiceOutSerials"
v-model="data.serial"
:label="t('invoicein.serial')"
:options="invoiceOutSerialsOptions"
option-label="description"
option-value="code"
option-filter
:expr-builder="exprBuilder"
data-cy="InvoiceOutCreateSerialSelect"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt?.code }} -
{{ scope.opt?.description }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
<VnInputDate
:label="t('invoiceOut.summary.dued')"
v-model="data.maxShipped"
/>
</VnRow>
<VnRow class="row q-col-gutter-xs">
<VnSelect
url="TaxAreas"
v-model="data.taxArea"
:label="t('invoiceOutList.tableVisibleColumns.taxArea')"
:options="taxAreasOptions"
option-label="code"
option-value="code"
/>
<VnInput
v-model="data.reference"
:label="t('globals.reference')"
/>
</VnRow>
</div>
</div>
</template> </template>
</VnTable> </VnSection>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -39,7 +39,7 @@ const columns = computed(() => [
{ {
align: 'left', align: 'left',
name: 'country', name: 'country',
label: t('negativeBases.country'), label: t('invoiceOut.negativeBases.country'),
component: 'select', component: 'select',
attrs: { attrs: {
url: 'Countries', url: 'Countries',
@ -53,7 +53,7 @@ const columns = computed(() => [
{ {
align: 'left', align: 'left',
name: 'clientId', name: 'clientId',
label: t('negativeBases.clientId'), label: t('invoiceOut.negativeBases.clientId'),
cardVisible: true, cardVisible: true,
}, },
{ {
@ -85,28 +85,28 @@ const columns = computed(() => [
{ {
align: 'left', align: 'left',
name: 'taxableBase', name: 'taxableBase',
label: t('negativeBases.base'), label: t('invoiceOut.negativeBases.base'),
}, },
{ {
align: 'left', align: 'left',
name: 'ticketFk', name: 'ticketFk',
label: t('negativeBases.ticketId'), label: t('invoiceOut.negativeBases.ticketId'),
cardVisible: true, cardVisible: true,
}, },
{ {
align: 'left', align: 'left',
name: 'isActive', name: 'isActive',
label: t('negativeBases.active'), label: t('invoiceOut.negativeBases.active'),
}, },
{ {
align: 'left', align: 'left',
name: 'hasToInvoice', name: 'hasToInvoice',
label: t('negativeBases.hasToInvoice'), label: t('invoiceOut.negativeBases.hasToInvoice'),
}, },
{ {
align: 'left', align: 'left',
name: 'hasVerifiedData', name: 'hasVerifiedData',
label: t('negativeBases.verifiedData'), label: t('invoiceOut.negativeBases.verifiedData'),
}, },
{ {
align: 'left', align: 'left',

View File

@ -105,28 +105,3 @@ const props = defineProps({
</template> </template>
</VnFilterPanel> </VnFilterPanel>
</template> </template>
<i18n>
en:
params:
from: From
to: To
company: Company
country: Country
clientId: Client Id
clientSocialName: Client
amount: Amount
comercialName: Comercial
es:
params:
from: Desde
to: Hasta
company: Empresa
country: País
clientId: Id cliente
clientSocialName: Cliente
amount: Importe
comercialName: Comercial
Date is required: La fecha es requerida
</i18n>

View File

@ -1,3 +1,60 @@
invoiceOut:
search: Search invoice
searchInfo: You can search by invoice reference
params:
company: Company
country: Country
clientId: Client ID
clientSocialName: Client
taxableBase: Base
ticketFk: Ticket
isActive: Active
hasToInvoice: Has to invoice
hasVerifiedData: Verified data
workerName: Worker
card:
issued: Issued
customerCard: Customer card
ticketList: Ticket List
summary:
issued: Issued
dued: Due
booked: Booked
taxBreakdown: Tax breakdown
taxableBase: Taxable base
rate: Rate
fee: Fee
tickets: Tickets
totalWithVat: Amount
globalInvoices:
errors:
chooseValidClient: Choose a valid client
chooseValidCompany: Choose a valid company
chooseValidPrinter: Choose a valid printer
chooseValidSerialType: Choose a serial type
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 tickets to invoice
criticalInvoiceError: 'Critical invoicing error, process stopped'
invalidSerialTypeForAll: The serial type must be global when invoicing all clients
table:
addressId: Address id
streetAddress: Street
statusCard:
percentageText: '{getPercentage}% {getAddressNumber} of {getNAddresses}'
pdfsNumberText: '{nPdfs} of {totalPdfs} PDFs'
negativeBases:
country: Country
clientId: Client Id
base: Base
ticketId: Ticket
active: Active
hasToInvoice: Has to Invoice
verifiedData: Verified Data
comercial: Commercial
errors:
downloadCsvFailed: CSV download failed
invoiceOutModule: invoiceOutModule:
customer: Client customer: Client
amount: Amount amount: Amount
@ -13,27 +70,4 @@ invoiceOutList:
invoiceOutSerial: Serial invoiceOutSerial: Serial
ticket: Ticket ticket: Ticket
taxArea: Tax area taxArea: Tax area
customsAgent: Custom Agent customsAgent: Custom Agent
DownloadPdf: Download PDF
InvoiceOutSummary: Summary
negativeBases:
country: Country
clientId: Client ID
base: Base
ticketId: Ticket
active: Active
hasToInvoice: Has to invoice
verifiedData: Verified data
commercial: Commercial
invoiceout:
params:
company: Company
country: Country
clientId: Client ID
clientSocialName: Client
taxableBase: Base
ticketFk: Ticket
isActive: Active
hasToInvoice: Has to invoice
hasVerifiedData: Verified data
workerName: Worker

View File

@ -1,5 +1,60 @@
Search invoice: Buscar factura emitida invoiceOut:
You can search by invoice reference: Puedes buscar por referencia de la factura search: Buscar factura emitida
searchInfo: Puedes buscar por referencia de la factura
params:
company: Empresa
country: País
clientId: ID del cliente
clientSocialName: Cliente
taxableBase: Base
ticketFk: Ticket
isActive: Activo
hasToInvoice: Debe facturar
hasVerifiedData: Datos verificados
workerName: Comercial
card:
issued: Fecha emisión
customerCard: Ficha del cliente
ticketList: Listado de tickets
summary:
issued: Fecha
dued: Fecha límite
booked: Contabilizada
taxBreakdown: Desglose impositivo
taxableBase: Base imp.
rate: Tarifa
fee: Cuota
tickets: Tickets
totalWithVat: Importe
globalInvoices:
errors:
chooseValidClient: Selecciona un cliente válido
chooseValidCompany: Selecciona una empresa válida
chooseValidPrinter: Selecciona una impresora válida
chooseValidSerialType: Selecciona una tipo de serie 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 existen tickets para facturar
criticalInvoiceError: Error crítico en la facturación proceso detenido
invalidSerialTypeForAll: El tipo de serie debe ser global cuando se facturan todos los clientes
table:
addressId: Id dirección
streetAddress: Dirección fiscal
statusCard:
percentageText: '{getPercentage}% {getAddressNumber} de {getNAddresses}'
pdfsNumberText: '{nPdfs} de {totalPdfs} PDFs'
negativeBases:
country: País
clientId: Id cliente
base: Base
ticketId: Ticket
active: Activo
hasToInvoice: Facturar
verifiedData: Datos comprobados
comercial: Comercial
errors:
downloadCsvFailed: Error al descargar CSV
invoiceOutModule: invoiceOutModule:
customer: Cliente customer: Cliente
amount: Importe amount: Importe
@ -15,28 +70,4 @@ invoiceOutList:
invoiceOutSerial: Serial invoiceOutSerial: Serial
ticket: Ticket ticket: Ticket
taxArea: Area taxArea: Area
customsAgent: Agente de aduanas customsAgent: Agente de aduanas
DownloadPdf: Descargar PDF
InvoiceOutSummary: Resumen
negativeBases:
country: País
clientId: ID del cliente
client: Cliente
base: Base
ticketId: Ticket
active: Activo
hasToInvoice: Debe facturar
verifiedData: Datos verificados
commercial: Comercial
invoiceout:
params:
company: Empresa
country: País
clientId: ID del cliente
clientSocialName: Cliente
taxableBase: Base
ticketFk: Ticket
isActive: Activo
hasToInvoice: Debe facturar
hasVerifiedData: Datos verificados
workerName: Comercial

View File

@ -1,19 +1,11 @@
<script setup> <script setup>
import VnCard from 'components/common/VnCard.vue'; import VnCardBeta from 'components/common/VnCardBeta.vue';
import ItemDescriptor from './ItemDescriptor.vue'; import ItemDescriptor from './ItemDescriptor.vue';
import ItemListFilter from '../ItemListFilter.vue';
</script> </script>
<template> <template>
<VnCard <VnCardBeta
data-key="Item" data-key="Item"
base-url="Items" base-url="Items"
:descriptor="ItemDescriptor" :descriptor="ItemDescriptor"
:filter-panel="ItemListFilter"
search-data-key="ItemList"
:searchbar-props="{
url: 'Items/filter',
label: 'searchbar.label',
info: 'searchbar.info',
}"
/> />
</template> </template>

View File

@ -6,18 +6,17 @@ import VnImg from 'src/components/ui/VnImg.vue';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import { toDate } from 'src/filters'; import { toDate } from 'src/filters';
import FetchedTags from 'src/components/ui/FetchedTags.vue'; import FetchedTags from 'src/components/ui/FetchedTags.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import ItemSummary from '../Item/Card/ItemSummary.vue'; import ItemSummary from '../Item/Card/ItemSummary.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import ItemDescriptorProxy from './Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from './Card/ItemDescriptorProxy.vue';
import ItemTypeDescriptorProxy from './ItemType/Card/ItemTypeDescriptorProxy.vue'; import ItemTypeDescriptorProxy from './ItemType/Card/ItemTypeDescriptorProxy.vue';
import { cloneItem } from 'src/pages/Item/composables/cloneItem'; import { cloneItem } from 'src/pages/Item/composables/cloneItem';
import RightMenu from 'src/components/common/RightMenu.vue';
import ItemListFilter from './ItemListFilter.vue'; import ItemListFilter from './ItemListFilter.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import axios from 'axios'; import axios from 'axios';
import VnSection from 'src/components/common/VnSection.vue';
const entityId = computed(() => route.params.id); const entityId = computed(() => route.params.id);
const { openCloneDialog } = cloneItem(); const { openCloneDialog } = cloneItem();
@ -25,9 +24,11 @@ const { viewSummary } = useSummaryDialog();
const { t } = useI18n(); const { t } = useI18n();
const tableRef = ref(); const tableRef = ref();
const route = useRoute(); const route = useRoute();
const dataKey = 'ItemList';
const validPriorities = ref([]); const validPriorities = ref([]);
const defaultTag = ref(); const defaultTag = ref();
const defaultPriority = ref(); const defaultPriority = ref();
const itemFilter = { const itemFilter = {
include: [ include: [
{ {
@ -324,166 +325,174 @@ onBeforeMount(async () => {
}); });
}); });
</script> </script>
<template> <template>
<VnSearchbar <VnSection
data-key="ItemList" :data-key="dataKey"
:label="t('item.searchbar.label')" :columns="columns"
:info="t('item.searchbar.info')" prefix="item"
/> :array-data-props="{
<RightMenu> url: 'Items/filter',
<template #right-panel> order: ['isActive DESC', 'name', 'id'],
userFilter: itemFilter,
}"
>
<template #advanced-menu>
<ItemListFilter data-key="ItemList" /> <ItemListFilter data-key="ItemList" />
</template> </template>
</RightMenu> <template #body>
<VnTable <VnTable
v-if="defaultTag" v-if="defaultTag"
ref="tableRef" ref="tableRef"
data-key="ItemList" :data-key="dataKey"
url="Items/filter" :columns="columns"
:create="{ :right-search="false"
urlCreate: 'Items/new', redirect="Item"
title: t('item.list.newItem'), :create="{
onDataSaved: ({ id }) => tableRef.redirect(`${id}/basic-data`), urlCreate: 'Items/new',
formInitialData: { title: t('item.list.newItem'),
editorFk: entityId, onDataSaved: ({ id }) => tableRef.redirect(`${id}/basic-data`),
tag: defaultTag, formInitialData: {
priority: defaultPriority, editorFk: entityId,
}, tag: defaultTag,
}" priority: defaultPriority,
:order="['isActive DESC', 'name', 'id']" },
:columns="columns" }"
redirect="Item" :is-editable="false"
:is-editable="false"
:right-search="false"
:filter="itemFilter"
>
<template #column-image="{ row }">
<VnImg
:id="row?.id"
zoom-resolution="1600x900"
:zoom="true"
class="rounded"
/>
</template>
<template #column-id="{ row }">
<span class="link" @click.stop>
{{ row.id }}
<ItemDescriptorProxy :id="row.id" />
</span>
</template>
<template #column-typeName="{ row }">
<span class="link" @click.stop>
{{ row.typeName }}
{{ row.typeFk }}
<ItemTypeDescriptorProxy :id="row.typeFk" />
</span>
</template>
<template #column-userName="{ row }">
<span class="link" @click.stop>
{{ row.userName }}
<WorkerDescriptorProxy :id="row.buyerFk" />
</span>
</template>
<template #column-description="{ row }">
<div class="row column full-width justify-between items-start">
{{ row?.name }}
<div v-if="row?.subName" class="subName">
{{ row?.subName.toUpperCase() }}
</div>
</div>
<FetchedTags :item="row" />
</template>
<template #more-create-dialog="{ data }">
<VnInput
v-model="data.provisionalName"
:label="t('globals.description')"
:is-required="true"
/>
<VnSelect
url="Tags"
v-model="data.tag"
:label="t('globals.tag')"
:fields="['id', 'name']"
option-label="name"
option-value="id"
:is-required="true"
:sort-by="['name ASC']"
> >
<template #option="scope"> <template #column-image="{ row }">
<QItem v-bind="scope.itemProps"> <VnImg
<QItemSection> :id="row?.id"
<QItemLabel>{{ scope.opt?.name }}</QItemLabel> zoom-resolution="1600x900"
<QItemLabel caption> #{{ scope.opt?.id }} </QItemLabel> :zoom="true"
</QItemSection> class="rounded"
</QItem> />
</template> </template>
</VnSelect> <template #column-id="{ row }">
<VnSelect <span class="link" @click.stop>
:options="validPriorities" {{ row.id }}
v-model="data.priority" <ItemDescriptorProxy :id="row.id" />
:label="t('item.create.priority')" </span>
:is-required="true"
/>
<VnSelect
url="ItemTypes"
v-model="data.typeFk"
:label="t('item.list.typeName')"
:fields="['id', 'code', 'name']"
option-label="name"
option-value="id"
:is-required="true"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption>
{{ scope.opt?.code }} #{{ scope.opt?.id }}
</QItemLabel>
</QItemSection>
</QItem>
</template> </template>
</VnSelect> <template #column-typeName="{ row }">
<VnSelect <span class="link" @click.stop>
url="Intrastats" {{ row.typeName }}
v-model="data.intrastatFk" {{ row.typeFk }}
:label="t('globals.intrastat')" <ItemTypeDescriptorProxy :id="row.typeFk" />
:fields="['id', 'description']" </span>
option-label="description"
option-value="id"
:is-required="true"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.description }}</QItemLabel>
<QItemLabel caption> #{{ scope.opt?.id }} </QItemLabel>
</QItemSection>
</QItem>
</template> </template>
</VnSelect> <template #column-userName="{ row }">
<VnSelect <span class="link" @click.stop>
url="Origins" {{ row.userName }}
v-model="data.originFk" <WorkerDescriptorProxy :id="row.buyerFk" />
:label="t('globals.origin')" </span>
:fields="['id', 'code', 'name']"
option-label="code"
option-value="id"
:is-required="true"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption>
{{ scope.opt?.code }} #{{ scope.opt?.id }}
</QItemLabel>
</QItemSection>
</QItem>
</template> </template>
</VnSelect> <template #column-description="{ row }">
<div class="row column full-width justify-between items-start">
{{ row?.name }}
<div v-if="row?.subName" class="subName">
{{ row?.subName.toUpperCase() }}
</div>
</div>
<FetchedTags :item="row" :columns="3" />
</template>
<template #more-create-dialog="{ data }">
<VnInput
v-model="data.provisionalName"
:label="t('globals.description')"
:is-required="true"
/>
<VnSelect
url="Tags"
v-model="data.tag"
:label="t('globals.tag')"
:fields="['id', 'name']"
option-label="name"
option-value="id"
:is-required="true"
:sort-by="['name ASC']"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption>
#{{ scope.opt?.id }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
<VnSelect
:options="validPriorities"
v-model="data.priority"
:label="t('item.create.priority')"
:is-required="true"
/>
<VnSelect
url="ItemTypes"
v-model="data.typeFk"
:label="t('item.list.typeName')"
:fields="['id', 'code', 'name']"
option-label="name"
option-value="id"
:is-required="true"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption>
{{ scope.opt?.code }} #{{ scope.opt?.id }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
<VnSelect
url="Intrastats"
v-model="data.intrastatFk"
:label="t('globals.intrastat')"
:fields="['id', 'description']"
option-label="description"
option-value="id"
:is-required="true"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.description }}</QItemLabel>
<QItemLabel caption>
#{{ scope.opt?.id }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
<VnSelect
url="Origins"
v-model="data.originFk"
:label="t('globals.origin')"
:fields="['id', 'code', 'name']"
option-label="code"
option-value="id"
:is-required="true"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption>
{{ scope.opt?.code }} #{{ scope.opt?.id }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</template>
</VnTable>
</template> </template>
</VnTable> </VnSection>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.subName { .subName {
@ -497,5 +506,4 @@ es:
New item: Nuevo artículo New item: Nuevo artículo
Create Item: Crear artículo Create Item: Crear artículo
You can search by id: Puedes buscar por id You can search by id: Puedes buscar por id
Preview: Vista previa
</i18n> </i18n>

View File

@ -1,7 +1,6 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref, onMounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { onMounted } from 'vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
@ -233,7 +232,7 @@ onMounted(async () => {
<QItemSection> <QItemSection>
<VnSelect <VnSelect
:label="t('params.buyerFk')" :label="t('params.buyerFk')"
v-model="params.buyerFk" v-model="params.workerFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"
:options="buyersOptions" :options="buyersOptions"
option-value="id" option-value="id"
@ -266,10 +265,10 @@ onMounted(async () => {
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
<QItemSection> <QItemSection>
<QItemLabel> <QItemLabel>
{{ scope.opt?.name}} {{ scope.opt?.name }}
</QItemLabel> </QItemLabel>
<QItemLabel caption> <QItemLabel caption>
{{ `#${scope.opt?.id } , ${ scope.opt?.nickname}` }} {{ `#${scope.opt?.id} , ${scope.opt?.nickname}` }}
</QItemLabel> </QItemLabel>
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -369,7 +368,7 @@ onMounted(async () => {
:model-value="fieldFilter.selectedField" :model-value="fieldFilter.selectedField"
:options="moreFields" :options="moreFields"
option-label="label" option-label="label"
option-value="label" option-value="label"
dense dense
outlined outlined
rounded rounded

View File

@ -1,20 +1,12 @@
<script setup> <script setup>
import VnCard from 'components/common/VnCard.vue'; import VnCardBeta from 'components/common/VnCardBeta.vue';
import ItemTypeDescriptor from 'src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue'; import ItemTypeDescriptor from 'src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue';
import ItemTypeFilter from 'src/pages/Item/ItemType/ItemTypeFilter.vue';
import ItemTypeSearchbar from '../ItemTypeSearchbar.vue';
</script> </script>
<template> <template>
<VnCard <VnCardBeta
data-key="ItemTypeSummary" data-key="ItemTypeSummary"
base-url="ItemTypes" base-url="ItemTypes"
:descriptor="ItemTypeDescriptor" :descriptor="ItemTypeDescriptor"
:filter-panel="ItemTypeFilter" />
search-data-key="ItemTypeList"
search-url="ItemTypes"
>
<template #searchbar>
<ItemTypeSearchbar />
</template>
</VnCard>
</template> </template>

View File

@ -63,7 +63,3 @@ const setData = (entity) => (data.value = useCardDescription(entity.code, entity
</CardDescriptor> </CardDescriptor>
</template> </template>
<i18n>
es:
Go to module index: Ir al índice del módulo
</i18n>

View File

@ -1,90 +0,0 @@
<script setup>
import { useI18n } from 'vue-i18n';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const emit = defineEmits(['search']);
const exprBuilder = (param, value) => {
switch (param) {
case 'name':
return {
name: { like: `%${value}%` },
};
case 'code':
return {
code: { like: `%${value}%` },
};
case 'search':
if (value) {
if (!isNaN(value)) {
return { id: value };
} else {
return {
or: [
{
name: {
like: `%${value}%`,
},
},
{
code: {
like: `%${value}%`,
},
},
],
};
}
}
}
};
</script>
<template>
<VnFilterPanel
:data-key="props.dataKey"
:search-button="true"
@search="emit('search')"
search-url="table"
:expr-builder="exprBuilder"
>
<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 }">
<QItem>
<QItemSection>
<VnInput :label="t('Name')" v-model="params.name" is-outlined />
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput v-model="params.code" :label="t('Code')" is-outlined />
</QItemSection>
</QItem>
</template>
</VnFilterPanel>
</template>
<i18n>
en:
params:
name: Name
code: Code
es:
params:
name: Nombre
code: Código
Name: Nombre
Code: Código
</i18n>

View File

@ -1,19 +0,0 @@
<script setup>
import VnSearchbar from 'components/ui/VnSearchbar.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>
<template>
<VnSearchbar
data-key="ItemTypeList"
url="ItemTypes"
:label="t('Search item type')"
:info="t('Search itemType by id, name or code')"
/>
</template>
<i18n>
es:
Search item type: Buscar familia
Search itemType by id, name or code: Buscar familia por id, nombre o código
</i18n>

View File

@ -15,3 +15,5 @@ itemType:
promo: Promo promo: Promo
itemPackingType: Item packing type itemPackingType: Item packing type
isUnconventionalSize: Is unconventional size isUnconventionalSize: Is unconventional size
search: Search item type
searchInfo: Search item type by id, name or code

View File

@ -15,3 +15,5 @@ itemType:
promo: Promoción promo: Promoción
itemPackingType: Tipo de embalaje itemPackingType: Tipo de embalaje
isUnconventionalSize: Es de tamaño poco convencional isUnconventionalSize: Es de tamaño poco convencional
search: Buscar familia
searchInfo: Buscar familia por id, nombre o código

View File

@ -1,17 +1,50 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import ItemTypeSearchbar from 'src/pages/Item/ItemType/ItemTypeSearchbar.vue';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import ItemTypeFilter from './ItemType/ItemTypeFilter.vue';
import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue';
import VnSection from 'src/components/common/VnSection.vue';
const { t } = useI18n(); const { t } = useI18n();
const tableRef = ref(); const tableRef = ref();
const itemCategoriesOptions = ref([]); const itemCategoriesOptions = ref([]);
const temperatureOptions = ref([]); const temperatureOptions = ref([]);
const dataKey = 'ItemTypeList';
const exprBuilder = (param, value) => {
switch (param) {
case 'name':
return {
name: { like: `%${value}%` },
};
case 'code':
return {
code: { like: `%${value}%` },
};
case 'search':
if (value) {
if (!isNaN(value)) {
return { id: value };
} else {
return {
or: [
{
name: {
like: `%${value}%`,
},
},
{
code: {
like: `%${value}%`,
},
},
],
};
}
}
}
};
const columns = computed(() => [ const columns = computed(() => [
{ {
@ -103,49 +136,53 @@ const columns = computed(() => [
@on-fetch="(data) => (temperatureOptions = data)" @on-fetch="(data) => (temperatureOptions = data)"
auto-load auto-load
/> />
<RightMenu> <VnSection
<template #right-panel> :data-key="dataKey"
<ItemTypeFilter data-key="ItemTypeList" /> :columns="columns"
</template> prefix="itemType"
</RightMenu> :array-data-props="{
<ItemTypeSearchbar /> url: 'ItemTypes',
<VnTable order: 'name ASC',
ref="tableRef" exprBuilder,
data-key="ItemTypeList" userFilter: {
url="ItemTypes" include: {
:create="{ relation: 'worker',
urlCreate: 'ItemTypes', scope: {
title: t('Create ItemTypes'), fields: ['id'],
onDataSaved: () => tableRef.reload(), include: {
formInitialData: {}, relation: 'user',
}" scope: {
:user-filter="{ fields: ['id', 'name'],
include: { },
relation: 'worker',
scope: {
fields: ['id'],
include: {
relation: 'user',
scope: {
fields: ['id', 'name'],
}, },
}, },
}, },
}, },
}" }"
order="name ASC"
:columns="columns"
auto-load
:right-search="false"
redirect="item/item-type"
> >
<template #column-workerFk="{ row }"> <template #body>
<span class="link" @click.stop> <VnTable
{{ row.worker?.user?.name }} ref="tableRef"
<WorkerDescriptorProxy :id="row.workerFk" /> :data-key="dataKey"
</span> :create="{
urlCreate: 'ItemTypes',
title: t('Create ItemTypes'),
onDataSaved: () => tableRef.reload(),
formInitialData: {},
}"
:columns="columns"
auto-load
redirect="item/item-type"
>
<template #column-workerFk="{ row }">
<span class="link" @click.stop>
{{ row.worker?.user?.name }}
<WorkerDescriptorProxy :id="row.workerFk" />
</span>
</template>
</VnTable>
</template> </template>
</VnTable> </VnSection>
</template> </template>
<i18n> <i18n>

View File

@ -180,44 +180,46 @@ item:
intrastat: Intrastat intrastat: Intrastat
origin: Origin origin: Origin
buyRequest: buyRequest:
ticketId: 'Ticket ID' ticketId: Ticket ID
shipped: 'Shipped' shipped: Shipped
requester: 'Requester' requester: Requester
requested: 'Requested' requested: Requested
price: 'Price' price: Price
attender: 'Attender' attender: Attender
item: 'Item' item: Item
achieved: 'Achieved' achieved: Achieved
concept: 'Concept' concept: Concept
state: 'State' state: State
summary: summary:
basicData: 'Basic data' basicData: Basic data
otherData: 'Other data' otherData: Other data
description: 'Description' description: Description
tax: 'Tax' tax: Tax
tags: 'Tags' tags: Tags
botanical: 'Botanical' botanical: Botanical
barcode: 'Barcode' barcode: Barcode
name: 'Nombre' name: Name
completeName: 'Nombre completo' completeName: Complete name
family: 'Familia' family: Family
size: 'Medida' size: Size
origin: 'Origen' origin: Origin
stems: 'Tallos' stems: Stems
multiplier: 'Multiplicador' multiplier: Multiplier
buyer: 'Comprador' buyer: Buyer
doPhoto: 'Do photo' doPhoto: Do photo
intrastatCode: 'Código intrastat' intrastatCode: Intrastat code
intrastat: 'Intrastat' intrastat: 'Intrastat'
ref: 'Referencia' ref: Reference
relevance: 'Relevancia' relevance: Relevance
weight: 'Peso (gramos)/tallo' weight: Weight (gram)/stem
units: 'Unidades/caja' units: Units/box
expense: 'Gasto' expense: Expense
generic: 'Genérico' generic: Generic
recycledPlastic: 'Plástico reciclado' recycledPlastic: Recycled plastic
nonRecycledPlastic: 'Plástico no reciclado' nonRecycledPlastic: Non recycled plastic
minSalesQuantity: 'Cantidad mínima de venta' minSalesQuantity: Min sales quantity
genus: 'Genus' genus: Genus
specie: 'Specie' specie: Specie
regularizeStock: Regularize stock search: 'Search item'
searchInfo: 'You can search by id'
regularizeStock: Regularize stock

View File

@ -73,9 +73,6 @@ itemTags:
addTag: Añadir etiqueta addTag: Añadir etiqueta
tag: Etiqueta tag: Etiqueta
value: Valor value: Valor
searchbar:
label: Buscar artículo
info: Buscar por id de artículo
itemType: itemType:
shared: shared:
code: Código code: Código
@ -108,9 +105,6 @@ item:
concept: Concepto concept: Concepto
denyOptions: Denegado denyOptions: Denegado
scopeDays: Días en adelante scopeDays: Días en adelante
searchbar:
label: Buscar artículo
info: Puedes buscar por id
descriptor: descriptor:
item: Artículo item: Artículo
buyer: Comprador buyer: Comprador
@ -182,35 +176,35 @@ item:
intrastat: Intrastat intrastat: Intrastat
origin: Origen origin: Origen
summary: summary:
basicData: 'Datos básicos' basicData: Datos básicos
otherData: 'Otros datos' otherData: Otros datos
description: 'Descripción' description: Descripción
tax: 'IVA' tax: IVA
tags: 'Etiquetas' tags: Etiquetas
botanical: 'Botánico' botanical: Botánico
barcode: 'Código de barras' barcode: Código de barras
name: 'Nombre' name: Nombre
completeName: 'Nombre completo' completeName: Nombre completo
family: 'Familia' family: Familia
size: 'Medida' size: Medida
origin: 'Origen' origin: Origen
stems: 'Tallos' stems: Tallos
multiplier: 'Multiplicador' multiplier: Multiplicador
buyer: 'Comprador' buyer: Comprador
doPhoto: 'Hacer foto' doPhoto: Hacer foto
intrastatCode: 'Código intrastat' intrastatCode: Código intrastat
intrastat: 'Intrastat' intrastat: Intrastat
ref: 'Referencia' ref: Referencia
relevance: 'Relevancia' relevance: Relevancia
weight: 'Peso (gramos)/tallo' weight: Peso (gramos)/tallo
units: 'Unidades/caja' units: Unidades/caja
expense: 'Gasto' expense: Gasto
generic: 'Genérico' generic: Genérico
recycledPlastic: 'Plástico reciclado' recycledPlastic: Plástico reciclado
nonRecycledPlastic: 'Plástico no reciclado' nonRecycledPlastic: Plástico no reciclado
minSalesQuantity: 'Cantidad mínima de venta' minSalesQuantity: Cantidad mínima de venta
genus: 'Genus' genus: Genus
specie: 'Specie' specie: Specie
regularizeStock: Regularizar stock regularizeStock: Regularizar stock
buyRequest: buyRequest:
ticketId: 'ID Ticket' ticketId: 'ID Ticket'
@ -222,4 +216,6 @@ item:
item: 'Artículo' item: 'Artículo'
achieved: 'Conseguido' achieved: 'Conseguido'
concept: 'Concepto' concept: 'Concepto'
state: 'Estado' state: 'Estado'
search: 'Buscar artículo'
searchInfo: 'Puedes buscar por id'

View File

@ -290,7 +290,7 @@ const columns = computed(() => [
}, },
}, },
{ {
title: t('salesTicketsTable.preview'), title: t('globals.preview'),
icon: 'preview', icon: 'preview',
color: 'primary', color: 'primary',
action: (row) => viewSummary(row.id, TicketSummary), action: (row) => viewSummary(row.id, TicketSummary),

View File

@ -33,7 +33,6 @@ salesTicketsTable:
isFragile: Is fragile isFragile: Is fragile
zone: Zone zone: Zone
goToLines: Go to lines goToLines: Go to lines
preview: Preview
total: Total total: Total
preparation: H.Prep preparation: H.Prep
payMethod: Pay method payMethod: Pay method

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { onMounted, ref, computed, watch } from 'vue'; import { onMounted, ref, watch } from 'vue';
import axios from 'axios'; import axios from 'axios';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnPaginate from 'src/components/ui/VnPaginate.vue'; import VnPaginate from 'src/components/ui/VnPaginate.vue';
@ -9,6 +9,7 @@ import CatalogItem from 'src/components/ui/CatalogItem.vue';
import OrderCatalogFilter from 'src/pages/Order/Card/OrderCatalogFilter.vue'; import OrderCatalogFilter from 'src/pages/Order/Card/OrderCatalogFilter.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import RightMenu from 'src/components/common/RightMenu.vue';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@ -89,14 +90,16 @@ watch(
:search-remove-params="false" :search-remove-params="false"
/> />
</Teleport> </Teleport>
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> <RightMenu>
<OrderCatalogFilter <template #right-panel>
:data-key="dataKey" <OrderCatalogFilter
:tag-value="tagValue" :data-key="dataKey"
:tags="tags" :tag-value="tagValue"
:initial-catalog-params="catalogParams" :tags="tags"
/> :initial-catalog-params="catalogParams"
</Teleport> />
</template>
</RightMenu>
<QPage class="column items-center q-pa-md" data-cy="orderCatalogPage"> <QPage class="column items-center q-pa-md" data-cy="orderCatalogPage">
<div class="full-width"> <div class="full-width">
<VnPaginate :data-key="dataKey"> <VnPaginate :data-key="dataKey">
@ -141,5 +144,5 @@ watch(
<i18n> <i18n>
es: es:
You can search items by name or id: Puedes buscar items por nombre o id You can search items by name or id: Puedes buscar items por nombre o id
Search items: Buscar items Search items: Buscar artículos
</i18n> </i18n>

View File

@ -115,6 +115,7 @@ const removeTagGroupParam = (params, search, valIndex) => {
} else { } else {
params.tagGroups.splice(valIndex, 1); params.tagGroups.splice(valIndex, 1);
} }
search();
}; };
const setCategoryList = (data) => { const setCategoryList = (data) => {

View File

@ -4,7 +4,6 @@ import { useI18n } from 'vue-i18n';
import { ref, computed, watch } from 'vue'; import { ref, computed, watch } from 'vue';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import axios from 'axios'; import axios from 'axios';
import { useStateStore } from 'stores/useStateStore';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import { confirm } from 'src/pages/Order/composables/confirmOrder'; import { confirm } from 'src/pages/Order/composables/confirmOrder';
import { toCurrency, toDate } from 'src/filters'; import { toCurrency, toDate } from 'src/filters';
@ -16,9 +15,9 @@ import VnImg from 'src/components/ui/VnImg.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import FetchedTags from 'src/components/ui/FetchedTags.vue'; import FetchedTags from 'src/components/ui/FetchedTags.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
const router = useRouter(); const router = useRouter();
const stateStore = useStateStore();
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
@ -264,23 +263,27 @@ watch(
@on-fetch="(data) => (orderSummary.vat = data)" @on-fetch="(data) => (orderSummary.vat = data)"
auto-load auto-load
/> />
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> <RightMenu>
<QCard <template #right-panel>
class="order-lines-summary q-pa-lg" <QCard
v-if="orderSummary.vat && orderSummary.total" class="order-lines-summary q-pa-lg"
> v-if="orderSummary.vat && orderSummary.total"
<p class="header text-right block"> >
{{ t('summary') }} <p class="header text-right block">
</p> {{ t('summary') }}
<VnLv </p>
:label="t('subtotal') + ': '" <VnLv
:value="toCurrency(orderSummary.total - orderSummary.vat)" :label="t('subtotal') + ': '"
/> :value="toCurrency(orderSummary.total - orderSummary.vat)"
<VnLv :label="t('VAT') + ': '" :value="toCurrency(orderSummary?.vat)" /> />
<VnLv :label="t('total') + ': '" :value="toCurrency(orderSummary?.total)" /> <VnLv :label="t('VAT') + ': '" :value="toCurrency(orderSummary?.vat)" />
</QCard> <VnLv
</Teleport> :label="t('total') + ': '"
:value="toCurrency(orderSummary?.total)"
/>
</QCard>
</template>
</RightMenu>
<VnTable <VnTable
ref="tableLinesRef" ref="tableLinesRef"
data-key="OrderLines" data-key="OrderLines"

View File

@ -136,7 +136,7 @@ const columns = computed(() => [
name: 'tableActions', name: 'tableActions',
actions: [ actions: [
{ {
title: t('InvoiceOutSummary'), title: t('globals.pageTitles.summary'),
icon: 'preview', icon: 'preview',
action: (row) => viewSummary(row.id, OrderSummary), action: (row) => viewSummary(row.id, OrderSummary),
isPrimary: true, isPrimary: true,
@ -190,7 +190,7 @@ const getDateColor = (date) => {
order: ['landed DESC', 'clientFk ASC', 'id DESC'], order: ['landed DESC', 'clientFk ASC', 'id DESC'],
}" }"
> >
<template #rightMenu> <template #advanced-menu>
<OrderFilter data-key="OrderList" /> <OrderFilter data-key="OrderList" />
</template> </template>
<template #body> <template #body>

View File

@ -1,19 +1,12 @@
<script setup> <script setup>
import VnCard from 'components/common/VnCard.vue'; import VnCardBeta from 'components/common/VnCardBeta.vue';
import ParkingDescriptor from 'pages/Parking/Card/ParkingDescriptor.vue'; import ParkingDescriptor from 'pages/Parking/Card/ParkingDescriptor.vue';
import ParkingFilter from 'pages/Parking/ParkingFilter.vue';
</script> </script>
<template> <template>
<VnCard <VnCardBeta
data-key="Parking" data-key="Parking"
base-url="Parkings" base-url="Parkings"
:descriptor="ParkingDescriptor" :descriptor="ParkingDescriptor"
:filter-panel="ParkingFilter"
search-data-key="ParkingList"
:searchbar-props="{
url: 'Parkings',
label: 'parking.searchBar.label',
info: 'parking.searchBar.info',
}"
/> />
</template> </template>

View File

@ -5,17 +5,17 @@ import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import VnPaginate from 'components/ui/VnPaginate.vue'; import VnPaginate from 'components/ui/VnPaginate.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import CardList from 'components/ui/CardList.vue'; import CardList from 'components/ui/CardList.vue';
import VnLv from 'components/ui/VnLv.vue'; import VnLv from 'components/ui/VnLv.vue';
import ParkingFilter from './ParkingFilter.vue'; import ParkingFilter from './ParkingFilter.vue';
import ParkingSummary from './Card/ParkingSummary.vue'; import ParkingSummary from './Card/ParkingSummary.vue';
import RightMenu from 'src/components/common/RightMenu.vue'; import VnSection from 'src/components/common/VnSection.vue';
const stateStore = useStateStore(); const stateStore = useStateStore();
const { push } = useRouter(); const { push } = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const { viewSummary } = useSummaryDialog(); const { viewSummary } = useSummaryDialog();
const dataKey = 'ParkingList';
onMounted(() => (stateStore.rightDrawer = true)); onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false)); onUnmounted(() => (stateStore.rightDrawer = false));
@ -37,58 +37,55 @@ function exprBuilder(param, value) {
</script> </script>
<template> <template>
<template> <VnSection
<VnSearchbar :data-key="dataKey"
data-key="ParkingList" prefix="parking"
:label="t('Search parking')" :array-data-props="{
:info="t('You can search by parking code')" url: 'Parkings',
/> order: ['code'],
</template> userFilter: filter,
<RightMenu> exprBuilder,
<template #right-panel> }"
>
<template #advanced-menu>
<ParkingFilter data-key="ParkingList" /> <ParkingFilter data-key="ParkingList" />
</template> </template>
</RightMenu> <template #body>
<QPage class="column items-center q-pa-md"> <QPage class="column items-center q-pa-md">
<div class="vn-card-list"> <div class="vn-card-list">
<VnPaginate <VnPaginate :data-key="dataKey">
data-key="ParkingList" <template #body="{ rows }">
url="Parkings" <CardList
:user-filter="filter" v-for="row of rows"
:expr-builder="exprBuilder" :key="row.id"
:limit="20" :id="row.id"
order="code" :title="row.code"
> @click="
<template #body="{ rows }"> push({ path: `/shelving/parking/${row.id}/summary` })
<CardList "
v-for="row of rows" >
:key="row.id" <template #list-items>
:id="row.id" <VnLv
:title="row.code" label="Sector"
@click="push({ path: `/parking/${row.id}` })" :value="row.sector?.description"
> />
<template #list-items> <VnLv
<VnLv label="Sector" :value="row.sector?.description" /> :label="t('parking.pickingOrder')"
<VnLv :value="row.pickingOrder"
:label="t('parking.pickingOrder')" />
:value="row.pickingOrder" </template>
/> <template #actions>
<QBtn
:label="t('components.smartCard.openSummary')"
@click.stop="viewSummary(row.id, ParkingSummary)"
color="primary"
/>
</template>
</CardList>
</template> </template>
<template #actions> </VnPaginate>
<QBtn </div>
:label="t('components.smartCard.openSummary')" </QPage>
@click.stop="viewSummary(row.id, ParkingSummary)" </template>
color="primary" </VnSection>
/>
</template>
</CardList>
</template>
</VnPaginate>
</div>
</QPage>
</template> </template>
<i18n>
es:
Search parking: Buscar parking
You can search by parking code: Puede buscar por el código del parking
</i18n>

View File

@ -0,0 +1,7 @@
parking:
pickingOrder: Picking order
sector: Sector
row: Row
column: Column
search: Search parking
searchInfo: You can search by parking code

View File

@ -0,0 +1,7 @@
parking:
pickingOrder: Orden de recogida
row: Fila
sector: Sector
column: Columna
search: Buscar parking
searchInfo: Puedes buscar por código de parking

View File

@ -0,0 +1,55 @@
import { describe, it, expect, vi, afterEach } from 'vitest';
import axios from 'axios';
import { getAgencies } from 'src/pages/Route/Agency/composables/getAgencies';
vi.mock('axios');
describe('getAgencies', () => {
afterEach(() => {
vi.clearAllMocks();
});
const generateParams = (formData) => ({
params: {
warehouseFk: formData.warehouseId,
addressFk: formData.addressId,
landed: formData.landed,
},
});
it('should fetch agencies data with correct parameters for valid formData', async () => {
const formData = {
warehouseId: '123',
addressId: '456',
landed: 'true',
};
await getAgencies(formData);
expect(axios.get).toHaveBeenCalledWith('Agencies/getAgenciesWithWarehouse', generateParams(formData));
});
it('should not call API when formData is missing required landed field', async () => {
const formData = { warehouseId: '123', addressId: '456' };
await getAgencies(formData);
expect(axios.get).not.toHaveBeenCalled();
});
it('should not call API when formData is missing required addressId field', async () => {
const formData = { warehouseId: '123', landed: 'true' };
await getAgencies(formData);
expect(axios.get).not.toHaveBeenCalled();
});
it('should not call API when formData is missing required warehouseId field', async () => {
const formData = { addressId: '456', landed: 'true' };
await getAgencies(formData);
expect(axios.get).not.toHaveBeenCalled();
});
});

View File

@ -0,0 +1,12 @@
import axios from 'axios';
export async function getAgencies(formData) {
if (!formData.warehouseId || !formData.addressId || !formData.landed) return;
let params = {
warehouseFk: formData.warehouseId,
addressFk: formData.addressId,
landed: formData.landed,
};
return await axios.get('Agencies/getAgenciesWithWarehouse', { params });
}

View File

@ -142,7 +142,7 @@ const total = computed(() => {
const openDmsUploadDialog = async () => { const openDmsUploadDialog = async () => {
dmsDialog.value.rowsToCreateInvoiceIn = selectedRows.value dmsDialog.value.rowsToCreateInvoiceIn = selectedRows.value
.filter( .filter(
(agencyTerm) => agencyTerm.supplierFk === selectedRows.value?.[0].supplierFk (agencyTerm) => agencyTerm.supplierFk === selectedRows.value?.[0].supplierFk,
) )
.map((agencyTerm) => ({ .map((agencyTerm) => ({
routeFk: agencyTerm.routeFk, routeFk: agencyTerm.routeFk,
@ -277,5 +277,4 @@ es:
Price: Precio Price: Precio
Received: Recibida Received: Recibida
Autonomous: Autónomos Autonomous: Autónomos
Preview: Vista previa
</i18n> </i18n>

View File

@ -112,7 +112,7 @@ const removeSelection = async () => {
await Promise.all( await Promise.all(
selectedRows.value.map((roadmap) => { selectedRows.value.map((roadmap) => {
axios.delete(`Roadmaps/${roadmap.id}`); axios.delete(`Roadmaps/${roadmap.id}`);
}) }),
); );
}; };
@ -236,6 +236,5 @@ es:
Plate: Matrícula Plate: Matrícula
Price: Precio Price: Precio
Observations: Observaciones Observations: Observaciones
Preview: Vista previa
Select the estimated date of departure (ETD): Selecciona la fecha estimada de salida Select the estimated date of departure (ETD): Selecciona la fecha estimada de salida
</i18n> </i18n>

View File

@ -33,7 +33,6 @@ route:
Mark as served: Mark as served Mark as served: Mark as served
Download selected routes as PDF: Download selected routes as PDF Download selected routes as PDF: Download selected routes as PDF
Add ticket: Add ticket Add ticket: Add ticket
Preview: Preview
Summary: Summary Summary: Summary
Route is closed: Route is closed Route is closed: Route is closed
Route is not served: Route is not served Route is not served: Route is not served

View File

@ -33,7 +33,7 @@ route:
Mark as served: Marcar como servidas Mark as served: Marcar como servidas
Download selected routes as PDF: Descargar rutas seleccionadas como PDF Download selected routes as PDF: Descargar rutas seleccionadas como PDF
Add ticket: Añadir tickets Add ticket: Añadir tickets
Preview: Vista previa preview: Vista previa
Summary: Resumen Summary: Resumen
Route is closed: La ruta está cerrada Route is closed: La ruta está cerrada
Route is not served: La ruta no está servida Route is not served: La ruta no está servida

View File

@ -1,19 +1,12 @@
<script setup> <script setup>
import VnCard from 'components/common/VnCard.vue'; import VnCardBeta from 'components/common/VnCardBeta.vue';
import ShelvingDescriptor from 'pages/Shelving/Card/ShelvingDescriptor.vue'; import ShelvingDescriptor from 'pages/Shelving/Card/ShelvingDescriptor.vue';
import ShelvingFilter from './ShelvingFilter.vue';
import ShelvingSearchbar from './ShelvingSearchbar.vue';
</script> </script>
<template> <template>
<VnCard <VnCardBeta
data-key="Shelving" data-key="Shelving"
base-url="Shelvings" base-url="Shelvings"
:descriptor="ShelvingDescriptor" :descriptor="ShelvingDescriptor"
:filter-panel="ShelvingFilter" />
search-data-key="ShelvingList"
>
<template #searchbar>
<ShelvingSearchbar />
</template>
</VnCard>
</template> </template>

View File

@ -6,13 +6,14 @@ import VnLv from 'components/ui/VnLv.vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import ShelvingFilter from 'pages/Shelving/Card/ShelvingFilter.vue'; import ShelvingFilter from 'pages/Shelving/Card/ShelvingFilter.vue';
import ShelvingSummary from 'pages/Shelving/Card/ShelvingSummary.vue'; import ShelvingSummary from 'pages/Shelving/Card/ShelvingSummary.vue';
import ShelvingSearchbar from 'pages/Shelving/Card/ShelvingSearchbar.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import RightMenu from 'src/components/common/RightMenu.vue'; import VnSection from 'src/components/common/VnSection.vue';
const router = useRouter(); const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const { viewSummary } = useSummaryDialog(); const { viewSummary } = useSummaryDialog();
const dataKey = 'ShelvingList';
const filter = { const filter = {
include: [{ relation: 'parking' }], include: [{ relation: 'parking' }],
}; };
@ -34,58 +35,62 @@ function exprBuilder(param, value) {
</script> </script>
<template> <template>
<ShelvingSearchbar /> <VnSection
<RightMenu> :data-key="dataKey"
<template #right-panel> prefix="shelving"
:array-data-props="{
url: 'Shelvings',
order: ['code'],
userFilter: filter,
exprBuilder,
}"
>
<template #advanced-menu>
<ShelvingFilter data-key="ShelvingList" /> <ShelvingFilter data-key="ShelvingList" />
</template> </template>
</RightMenu> <template #body>
<QPage class="column items-center q-pa-md"> <QPage class="column items-center q-pa-md">
<div class="vn-card-list"> <div class="vn-card-list">
<VnPaginate <VnPaginate :data-key="dataKey">
data-key="ShelvingList" <template #body="{ rows }">
url="Shelvings" <CardList
:filter="filter" v-for="row of rows"
:expr-builder="exprBuilder" :key="row.id"
:limit="20" :id="row.id"
> :title="row.code"
<template #body="{ rows }"> @click="navigate(row.id)"
<CardList >
v-for="row of rows" <template #list-items>
:key="row.id" <VnLv
:id="row.id" :label="t('shelving.list.parking')"
:title="row.code" :title-label="t('shelving.list.parking')"
@click="navigate(row.id)" :value="row.parking?.code"
> />
<template #list-items> <VnLv
<VnLv :label="t('shelving.list.priority')"
:label="t('shelving.list.parking')" :value="row?.priority"
:title-label="t('shelving.list.parking')" />
:value="row.parking?.code" </template>
/> <template #actions>
<VnLv <QBtn
:label="t('shelving.list.priority')" :label="t('components.smartCard.openSummary')"
:value="row?.priority" @click.stop="viewSummary(row.id, ShelvingSummary)"
/> color="primary"
/>
</template>
</CardList>
</template> </template>
<template #actions> </VnPaginate>
<QBtn </div>
:label="t('components.smartCard.openSummary')" <QPageSticky :offset="[20, 20]">
@click.stop="viewSummary(row.id, ShelvingSummary)" <RouterLink :to="{ name: 'ShelvingCreate' }">
color="primary" <QBtn fab icon="add" color="primary" shortcut="+" />
/> <QTooltip>
</template> {{ t('shelving.list.newShelving') }}
</CardList> </QTooltip>
</template> </RouterLink>
</VnPaginate> </QPageSticky>
</div> </QPage>
<QPageSticky :offset="[20, 20]"> </template>
<RouterLink :to="{ name: 'ShelvingCreate' }"> </VnSection>
<QBtn fab icon="add" color="primary" shortcut="+" />
<QTooltip>
{{ t('shelving.list.newShelving') }}
</QTooltip>
</RouterLink>
</QPageSticky>
</QPage>
</template> </template>

View File

@ -0,0 +1,9 @@
shelving:
list:
parking: Parking
priority: Priority
newShelving: New Shelving
summary:
recyclable: Recyclable
search: Search shelving
searchInfo: You can search by shelving reference

View File

@ -0,0 +1,9 @@
shelving:
list:
parking: Parking
priority: Prioridad
newShelving: Nuevo Carro
summary:
recyclable: Reciclable
search: Buscar carro
searchInfo: Puedes buscar por referencia del carro

View File

@ -188,7 +188,6 @@ const getEntryQueryParams = (supplier) => {
es: es:
All entries with current supplier: Todas las entradas con proveedor actual All entries with current supplier: Todas las entradas con proveedor actual
Go to client: Ir a cliente Go to client: Ir a cliente
Go to module index: Ir al índice del módulo
Inactive supplier: Proveedor inactivo Inactive supplier: Proveedor inactivo
Unverified supplier: Proveedor no verificado Unverified supplier: Proveedor no verificado
</i18n> </i18n>

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import axios from 'axios'; import axios from 'axios';
import { date, useQuasar } from 'quasar'; import { date, useQuasar } from 'quasar';
import { useStateStore } from 'src/stores/useStateStore'; import RightMenu from 'src/components/common/RightMenu.vue';
import { computed, onMounted, reactive, ref } from 'vue'; import { computed, onMounted, reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@ -9,9 +9,7 @@ import { useRouter } from 'vue-router';
const router = useRouter(); const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
const stateStore = useStateStore();
onMounted(async () => { onMounted(async () => {
stateStore.rightDrawer = true;
await fetch(); await fetch();
}); });
@ -86,69 +84,73 @@ async function getVideoList(expeditionId, timed) {
</script> </script>
<template> <template>
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> <RightMenu>
<QList bordered separator style="max-width: 318px"> <template #right-panel>
<QItem v-if="lastExpedition && videoList.length"> <QList bordered separator style="max-width: 318px">
<QItemSection> <QItem v-if="lastExpedition && videoList.length">
<QItemLabel class="text-h6"> <QItemSection>
{{ t('ticket.boxing.selectTime') }} ({{ time.min }}-{{ <QItemLabel class="text-h6">
time.max {{ t('ticket.boxing.selectTime') }} ({{ time.min }}-{{
}}) time.max
</QItemLabel> }})
<QRange </QItemLabel>
v-model="time" <QRange
@change="getVideoList(lastExpedition, time)" v-model="time"
:min="0" @change="getVideoList(lastExpedition, time)"
:max="24" :min="0"
:step="1" :max="24"
:left-label-value="time.min + ':00'" :step="1"
:right-label-value="time.max + ':00'" :left-label-value="time.min + ':00'"
label :right-label-value="time.max + ':00'"
markers label
snap markers
color="primary" snap
/> color="primary"
</QItemSection> />
</QItem> </QItemSection>
<QItem v-if="lastExpedition && videoList.length"> </QItem>
<QItemSection> <QItem v-if="lastExpedition && videoList.length">
<QSelect <QItemSection>
color="primary" <QSelect
v-model="slide" color="primary"
:options="videoList" v-model="slide"
:label="t('ticket.boxing.selectVideo')" :options="videoList"
emit-value :label="t('ticket.boxing.selectVideo')"
map-options emit-value
> map-options
<template #prepend> >
<QIcon name="schedule" /> <template #prepend>
</template> <QIcon name="schedule" />
</QSelect> </template>
</QItemSection> </QSelect>
</QItem> </QItemSection>
<QItem </QItem>
v-for="expedition in expeditions" <QItem
:key="expedition.id" v-for="expedition in expeditions"
@click="getVideoList(expedition.id)" :key="expedition.id"
clickable @click="getVideoList(expedition.id)"
v-ripple clickable
> v-ripple
<QItemSection> >
<QItemLabel class="text-h6">#{{ expedition.id }}</QItemLabel> <QItemSection>
</QItemSection> <QItemLabel class="text-h6">#{{ expedition.id }}</QItemLabel>
<QItemSection> </QItemSection>
<QItemLabel caption>{{ t('globals.created') }}</QItemLabel> <QItemSection>
<QItemLabel> <QItemLabel caption>{{ t('globals.created') }}</QItemLabel>
{{ date.formatDate(expedition.created, 'YYYY-MM-DD HH:mm:ss') }} <QItemLabel>
</QItemLabel> {{
<QItemLabel caption>{{ t('globals.item') }}</QItemLabel> date.formatDate(expedition.created, 'YYYY-MM-DD HH:mm:ss')
<QItemLabel>{{ expedition.packagingItemFk }}</QItemLabel> }}
<QItemLabel caption>{{ t('ticket.boxing.worker') }}</QItemLabel> </QItemLabel>
<QItemLabel>{{ expedition.userName }}</QItemLabel> <QItemLabel caption>{{ t('globals.item') }}</QItemLabel>
</QItemSection> <QItemLabel>{{ expedition.packagingItemFk }}</QItemLabel>
</QItem> <QItemLabel caption>{{ t('ticket.boxing.worker') }}</QItemLabel>
</QList> <QItemLabel>{{ expedition.userName }}</QItemLabel>
</Teleport> </QItemSection>
</QItem>
</QList>
</template>
</RightMenu>
<QCard> <QCard>
<QCarousel animated v-model="slide" height="max-content"> <QCarousel animated v-model="slide" height="max-content">
<QCarouselSlide <QCarouselSlide

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, computed, onMounted, watch, nextTick } from 'vue'; import { ref, computed, watch, nextTick } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
@ -9,15 +9,14 @@ import FetchData from 'components/FetchData.vue';
import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue'; import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue';
import VnImg from 'src/components/ui/VnImg.vue'; import VnImg from 'src/components/ui/VnImg.vue';
import { useStateStore } from 'stores/useStateStore';
import { dashIfEmpty } from 'src/filters'; import { dashIfEmpty } from 'src/filters';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import { toCurrency } from 'filters/index'; import { toCurrency } from 'filters/index';
import axios from 'axios'; import axios from 'axios';
import VnTable from 'src/components/VnTable/VnTable.vue'; import VnTable from 'src/components/VnTable/VnTable.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
const route = useRoute(); const route = useRoute();
const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const salesRef = ref(null); const salesRef = ref(null);
const arrayData = useArrayData('ticketData'); const arrayData = useArrayData('ticketData');
@ -164,10 +163,6 @@ const getTicketVolume = async () => {
const { data } = await axios.get(`Tickets/${ticketData.value.id}/getVolume`); const { data } = await axios.get(`Tickets/${ticketData.value.id}/getVolume`);
ticketVolume.value = data[0].volume; ticketVolume.value = data[0].volume;
}; };
onMounted(() => {
stateStore.rightDrawer = true;
});
</script> </script>
<template> <template>
@ -178,93 +173,121 @@ onMounted(() => {
@on-fetch="(data) => (components = data)" @on-fetch="(data) => (components = data)"
auto-load auto-load
/> />
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> <RightMenu>
<QCard class="q-pa-sm color-vn-text" bordered flat style="border-color: black"> <template #right-panel>
<QCardSection horizontal> <QCard
<span class="text-weight-bold text-subtitle1 text-center full-width"> class="q-pa-sm color-vn-text"
{{ t('basicData.total') }} bordered
</span> flat
</QCardSection> style="border-color: black"
<QCardSection horizontal>
<span class="q-mr-xs color-vn-label"
>{{ t('ticketComponents.baseToCommission') }}:
</span>
<span>{{ toCurrency(getBase) }}</span>
</QCardSection>
<QCardSection horizontal>
<span class="q-mr-xs color-vn-label"
>{{ t('ticketComponents.totalWithoutVat') }}:
</span>
<span>{{ toCurrency(getTotal) }}</span>
</QCardSection>
</QCard>
<QCard class="q-pa-sm color-vn-text" bordered flat style="border-color: black">
<QCardSection horizontal>
<span class="text-weight-bold text-subtitle1 text-center full-width">
{{ t('ticketComponents.components') }}
</span>
</QCardSection>
<QCardSection
v-for="(component, index) in componentsList"
:key="index"
horizontal
> >
<span v-if="component.name" class="q-mr-xs color-vn-label"> <QCardSection horizontal>
{{ component.name }}: <span class="text-weight-bold text-subtitle1 text-center full-width">
</span> {{ t('basicData.total') }}
<span v-if="component.value">{{ </span>
toCurrency(component.value, 'EUR', 3) </QCardSection>
}}</span> <QCardSection horizontal>
</QCardSection> <span class="q-mr-xs color-vn-label"
</QCard> >{{ t('ticketComponents.baseToCommission') }}:
<QCard class="q-pa-sm color-vn-text" bordered flat style="border-color: black"> </span>
<QCardSection horizontal> <span>{{ toCurrency(getBase) }}</span>
<span class="text-weight-bold text-subtitle1 text-center full-width"> </QCardSection>
{{ t('ticketComponents.zoneBreakdown') }} <QCardSection horizontal>
</span> <span class="q-mr-xs color-vn-label"
</QCardSection> >{{ t('ticketComponents.totalWithoutVat') }}:
<QCardSection horizontal> </span>
<span class="q-mr-xs color-vn-label"> {{ t('basicData.price') }}: </span> <span>{{ toCurrency(getTotal) }}</span>
<span>{{ toCurrency(ticketData?.zonePrice, 'EUR', 2) }}</span> </QCardSection>
</QCardSection> </QCard>
<QCardSection horizontal> <QCard
<span class="q-mr-xs color-vn-label"> class="q-pa-sm color-vn-text"
{{ t('ticketComponents.bonus') }}: bordered
</span> flat
<span>{{ toCurrency(ticketData?.zoneBonus, 'EUR', 2) }}</span> style="border-color: black"
</QCardSection> >
<QCardSection horizontal> <QCardSection horizontal>
<span class="q-mr-xs color-vn-label"> {{ t('ticketList.zone') }}: </span> <span class="text-weight-bold text-subtitle1 text-center full-width">
<span class="link"> {{ t('ticketComponents.components') }}
{{ dashIfEmpty(ticketData?.zone?.name) }} </span>
<ZoneDescriptorProxy :id="ticketData?.zone?.id" /> </QCardSection>
</span> <QCardSection
</QCardSection> v-for="(component, index) in componentsList"
<QCardSection v-if="ticketData?.zone?.isVolumetric" horizontal> :key="index"
<span class="q-mr-xs color-vn-label"> {{ t('volume.volume') }}: </span> horizontal
<span>{{ ticketVolume }}</span> >
</QCardSection> <span v-if="component.name" class="q-mr-xs color-vn-label">
<QCardSection horizontal> {{ component.name }}:
<span class="q-mr-xs color-vn-label"> </span>
{{ t('ticketComponents.packages') }}: <span v-if="component.value">{{
</span> toCurrency(component.value, 'EUR', 3)
<span>{{ dashIfEmpty(ticketData?.packages) }}</span> }}</span>
</QCardSection> </QCardSection>
</QCard> </QCard>
<QCard class="q-pa-sm color-vn-text" bordered flat style="border-color: black"> <QCard
<QCardSection horizontal> class="q-pa-sm color-vn-text"
<span class="text-weight-bold text-subtitle1 text-center full-width"> bordered
{{ t('ticketComponents.theoricalCost') }} flat
</span> style="border-color: black"
</QCardSection> >
<QCardSection horizontal> <QCardSection horizontal>
<span class="q-mr-xs color-vn-label"> <span class="text-weight-bold text-subtitle1 text-center full-width">
{{ t('ticketComponents.totalPrice') }}: {{ t('ticketComponents.zoneBreakdown') }}
</span> </span>
<span>{{ toCurrency(theoricalCost, 'EUR', 2) }}</span> </QCardSection>
</QCardSection> <QCardSection horizontal>
</QCard> <span class="q-mr-xs color-vn-label">
</Teleport> {{ t('basicData.price') }}:
</span>
<span>{{ toCurrency(ticketData?.zonePrice, 'EUR', 2) }}</span>
</QCardSection>
<QCardSection horizontal>
<span class="q-mr-xs color-vn-label">
{{ t('ticketComponents.bonus') }}:
</span>
<span>{{ toCurrency(ticketData?.zoneBonus, 'EUR', 2) }}</span>
</QCardSection>
<QCardSection horizontal>
<span class="q-mr-xs color-vn-label">
{{ t('ticketList.zone') }}:
</span>
<span class="link">
{{ dashIfEmpty(ticketData?.zone?.name) }}
<ZoneDescriptorProxy :id="ticketData?.zone?.id" />
</span>
</QCardSection>
<QCardSection v-if="ticketData?.zone?.isVolumetric" horizontal>
<span class="q-mr-xs color-vn-label">
{{ t('volume.volume') }}:
</span>
<span>{{ ticketVolume }}</span>
</QCardSection>
<QCardSection horizontal>
<span class="q-mr-xs color-vn-label">
{{ t('ticketComponents.packages') }}:
</span>
<span>{{ dashIfEmpty(ticketData?.packages) }}</span>
</QCardSection>
</QCard>
<QCard
class="q-pa-sm color-vn-text"
bordered
flat
style="border-color: black"
>
<QCardSection horizontal>
<span class="text-weight-bold text-subtitle1 text-center full-width">
{{ t('ticketComponents.theoricalCost') }}
</span>
</QCardSection>
<QCardSection horizontal>
<span class="q-mr-xs color-vn-label">
{{ t('ticketComponents.totalPrice') }}:
</span>
<span>{{ toCurrency(theoricalCost, 'EUR', 2) }}</span>
</QCardSection>
</QCard>
</template>
</RightMenu>
<VnTable <VnTable
ref="tableRef" ref="tableRef"
data-key="TicketComponents" data-key="TicketComponents"

View File

@ -239,7 +239,6 @@ function ticketFilter(ticket) {
<i18n> <i18n>
es: es:
This ticket is deleted: Este ticket está eliminado This ticket is deleted: Este ticket está eliminado
Go to module index: Ir al índice del modulo
Client inactive: Cliente inactivo Client inactive: Cliente inactivo
Client not checked: Cliente no verificado Client not checked: Cliente no verificado
Client has debt: Cliente con deuda Client has debt: Cliente con deuda

View File

@ -16,6 +16,8 @@ import VnInputTime from 'src/components/common/VnInputTime.vue';
import { useAcl } from 'src/composables/useAcl'; import { useAcl } from 'src/composables/useAcl';
import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { getAddresses } from 'src/pages/Customer/composables/getAddresses';
import { getClient } from 'src/pages/Customer/composables/getClient';
const props = defineProps({ const props = defineProps({
ticket: { ticket: {
@ -40,7 +42,10 @@ const { openReport, sendEmail } = usePrintService();
const ticketSummary = useArrayData('TicketSummary'); const ticketSummary = useArrayData('TicketSummary');
const { ticket } = toRefs(props); const { ticket } = toRefs(props);
const ticketId = computed(() => props.ticket.id ?? currentRoute.value.params.id); const ticketId = computed(() => props.ticket.id ?? currentRoute.value.params.id);
const client = ref(); const client = ref(null);
const address = ref(null);
const addressesOptions = ref([]);
const selectedClient = ref();
const showTransferDialog = ref(false); const showTransferDialog = ref(false);
const showTurnDialog = ref(false); const showTurnDialog = ref(false);
const showChangeTimeDialog = ref(false); const showChangeTimeDialog = ref(false);
@ -52,6 +57,31 @@ const weight = ref();
const hasDocuwareFile = ref(); const hasDocuwareFile = ref();
const quasar = useQuasar(); const quasar = useQuasar();
const canRestoreTicket = ref(false); const canRestoreTicket = ref(false);
const onClientSelected = async(clientId) =>{
client.value = clientId;
await fetchClient();
await fetchAddresses();
};
const onAddressSelected = (addressId) => {
address.value = addressId;
}
const fetchClient = async () => {
const { data } = await getClient(client.value)
const [retrievedClient] = data;
selectedClient.value = retrievedClient;
};
const fetchAddresses = async () => {
const { data } = await getAddresses(client.value);
addressesOptions.value = data;
const { defaultAddress } = selectedClient.value;
address.value = defaultAddress.id;
};
const actions = { const actions = {
clone: async () => { clone: async () => {
const opts = { message: t('Ticket cloned'), type: 'positive' }; const opts = { message: t('Ticket cloned'), type: 'positive' };
@ -260,17 +290,14 @@ async function makeInvoice() {
window.location.reload(); window.location.reload();
} }
async function transferClient(client) { async function transferClient() {
const params = { const params = {
clientFk: client, clientFk: client.value,
addressFk: address.value,
}; };
const { data } = await axios.patch( await axios.patch( `Tickets/${ticketId.value}/transferClient`, params );
`Tickets/${ticketId.value}/transferClient`, window.location.reload();
params
);
if (data) window.location.reload();
} }
async function addTurn(day) { async function addTurn(day) {
@ -446,7 +473,7 @@ async function ticketToRestore() {
</QItem> </QItem>
<QDialog ref="dialogRef" v-model="showTransferDialog"> <QDialog ref="dialogRef" v-model="showTransferDialog">
<FormPopup <FormPopup
@on-submit="transferClient(client)" @on-submit="transferClient()"
:title="t('Transfer client')" :title="t('Transfer client')"
:custom-submit-button-label="t('Transfer client')" :custom-submit-button-label="t('Transfer client')"
:default-cancel-button="false" :default-cancel-button="false"
@ -454,10 +481,11 @@ async function ticketToRestore() {
<template #form-inputs> <template #form-inputs>
<VnSelect <VnSelect
url="Clients" url="Clients"
:fields="['id', 'name']" :fields="['id', 'name', 'defaultAddressFk']"
v-model="client" v-model="client"
:label="t('Client')" :label="t('Client')"
auto-load auto-load
@update:model-value="() => onClientSelected(client)"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -470,6 +498,29 @@ async function ticketToRestore() {
</QItem> </QItem>
</template> </template>
</VnSelect> </VnSelect>
<VnSelect
:disable="!client"
:options="addressesOptions"
:fields="['id', 'nickname']"
option-value="id"
option-label="nickname"
v-model="address"
:label="t('ticketList.addressNickname')"
auto-load
:hint="!client ? t('Select a client to enable') : ''"
@update:model-value="() => onAddressSelected(address)"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ `#${scope.opt.id} - ` }}
{{ scope.opt.nickname }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</template> </template>
</FormPopup> </FormPopup>
</QDialog> </QDialog>
@ -762,7 +813,7 @@ async function ticketToRestore() {
en: en:
addTurn: Add turn addTurn: Add turn
invoiceIds: "Invoices have been generated with the following ids: {invoiceIds}" invoiceIds: "Invoices have been generated with the following ids: {invoiceIds}"
es: es:
Show Delivery Note...: Ver albarán... Show Delivery Note...: Ver albarán...
Send Delivery Note...: Enviar albarán... Send Delivery Note...: Enviar albarán...
@ -814,4 +865,5 @@ es:
Are you sure you want to restore the ticket?: ¿Seguro que quieres restaurar el ticket? Are you sure you want to restore the ticket?: ¿Seguro que quieres restaurar el ticket?
You are going to restore this ticket: Vas a restaurar este ticket You are going to restore this ticket: Vas a restaurar este ticket
Ticket restored: Ticket restaurado Ticket restored: Ticket restaurado
Select a client to enable: Selecciona un cliente para habilitar
</i18n> </i18n>

View File

@ -16,7 +16,6 @@ import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import TicketSaleMoreActions from './TicketSaleMoreActions.vue'; import TicketSaleMoreActions from './TicketSaleMoreActions.vue';
import TicketTransfer from './TicketTransfer.vue'; import TicketTransfer from './TicketTransfer.vue';
import { useStateStore } from 'stores/useStateStore';
import { toCurrency, toPercentage } from 'src/filters'; import { toCurrency, toPercentage } from 'src/filters';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import { useVnConfirm } from 'composables/useVnConfirm'; import { useVnConfirm } from 'composables/useVnConfirm';
@ -25,10 +24,10 @@ import axios from 'axios';
import VnTable from 'src/components/VnTable/VnTable.vue'; import VnTable from 'src/components/VnTable/VnTable.vue';
import VnUsesMana from 'src/components/ui/VnUsesMana.vue'; import VnUsesMana from 'src/components/ui/VnUsesMana.vue';
import VnConfirm from 'src/components/ui/VnConfirm.vue'; import VnConfirm from 'src/components/ui/VnConfirm.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const { notify } = useNotify(); const { notify } = useNotify();
const { openConfirmationModal } = useVnConfirm(); const { openConfirmationModal } = useVnConfirm();
@ -419,7 +418,6 @@ const setTransferParams = async () => {
}; };
onMounted(async () => { onMounted(async () => {
stateStore.rightDrawer = true;
getConfig(); getConfig();
}); });
@ -620,29 +618,38 @@ watch(
</QBtnGroup> </QBtnGroup>
</template> </template>
</VnSubToolbar> </VnSubToolbar>
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> <RightMenu>
<div <template #right-panel>
class="q-pa-md q-mb-md q-ma-md color-vn-text" <div
style="border: 2px solid black" class="q-pa-md q-mb-md q-ma-md color-vn-text"
> style="border: 2px solid black"
<QCardSection class="justify-end text-subtitle1" horizontal> >
<span class="q-mr-xs color-vn-label" <QCardSection class="justify-end text-subtitle1" horizontal>
>{{ t('ticketSale.subtotal') }}: <span class="q-mr-xs color-vn-label"
</span> >{{ t('ticketSale.subtotal') }}:
<span>{{ toCurrency(store.data?.totalWithoutVat) }}</span> </span>
</QCardSection> <span>{{ toCurrency(store.data?.totalWithoutVat) }}</span>
<QCardSection class="justify-end text-subtitle1" horizontal> </QCardSection>
<span class="q-mr-xs color-vn-label"> {{ t('ticketSale.tax') }}: </span> <QCardSection class="justify-end text-subtitle1" horizontal>
<span>{{ <span class="q-mr-xs color-vn-label">
toCurrency(store.data?.totalWithVat - store.data?.totalWithoutVat) {{ t('ticketSale.tax') }}:
}}</span> </span>
</QCardSection> <span>{{
<QCardSection class="justify-end text-weight-bold text-subtitle1" horizontal> toCurrency(store.data?.totalWithVat - store.data?.totalWithoutVat)
<span class="q-mr-xs color-vn-label"> {{ t('basicData.total') }}: </span> }}</span>
<span>{{ toCurrency(store.data?.totalWithVat) }}</span> </QCardSection>
</QCardSection> <QCardSection
</div> class="justify-end text-weight-bold text-subtitle1"
</Teleport> horizontal
>
<span class="q-mr-xs color-vn-label">
{{ t('basicData.total') }}:
</span>
<span>{{ toCurrency(store.data?.totalWithVat) }}</span>
</QCardSection>
</div>
</template>
</RightMenu>
<VnTable <VnTable
ref="tableRef" ref="tableRef"
data-key="TicketSales" data-key="TicketSales"

View File

@ -259,10 +259,9 @@ const moveTicketsAdvance = async () => {
destinationId: ticket.id, destinationId: ticket.id,
originShipped: ticket.futureShipped, originShipped: ticket.futureShipped,
destinationShipped: ticket.shipped, destinationShipped: ticket.shipped,
workerFk: ticket.workerFk, salesPersonFk: ticket.workerFk,
}); });
} }
const params = { tickets: ticketsToMove }; const params = { tickets: ticketsToMove };
await axios.post('Tickets/merge', params); await axios.post('Tickets/merge', params);
vnTableRef.value.reload(); vnTableRef.value.reload();

View File

@ -9,9 +9,11 @@ import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import { getClient } from 'src/pages/Customer/composables/getClient';
import { getAddresses } from 'src/pages/Customer/composables/getAddresses';
import { getAgencies } from 'src/pages/Route/Agency/composables/getAgencies';
import { useState } from 'composables/useState'; import { useState } from 'composables/useState';
import axios from 'axios';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
@ -37,33 +39,13 @@ onBeforeMount(async () => {
}); });
const fetchClient = async (formData) => { const fetchClient = async (formData) => {
const filter = { const { data } = await getClient(formData.clientId);
include: {
relation: 'defaultAddress',
scope: {
fields: ['id', 'agencyModeFk'],
},
},
where: { id: formData.clientId },
};
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get('Clients', { params });
const [client] = data; const [client] = data;
selectedClient.value = client; selectedClient.value = client;
}; };
const fetchAddresses = async (formData) => { const fetchAddresses = async (formData) => {
if (!formData.clientId) return; const { data } = await getAddresses(formData.clientId);
const filter = {
fields: ['nickname', 'street', 'city', 'id'],
where: { isActive: true },
order: 'nickname ASC',
};
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get(`Clients/${formData.clientId}/addresses`, {
params,
});
addressesOptions.value = data; addressesOptions.value = data;
const { defaultAddress } = selectedClient.value; const { defaultAddress } = selectedClient.value;
@ -76,15 +58,7 @@ const onClientSelected = async (formData) => {
}; };
const fetchAvailableAgencies = async (formData) => { const fetchAvailableAgencies = async (formData) => {
if (!formData.warehouseId || !formData.addressId || !formData.landed) return; const { data } = await getAgencies(formData);
let params = {
warehouseFk: formData.warehouseId,
addressFk: formData.addressId,
landed: formData.landed,
};
const { data } = await axios.get('Agencies/getAgenciesWithWarehouse', { params });
agenciesOptions.value = data; agenciesOptions.value = data;
const defaultAgency = agenciesOptions.value.find( const defaultAgency = agenciesOptions.value.find(

View File

@ -9,9 +9,11 @@ import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import { getClient } from 'src/pages/Customer/composables/getClient';
import { getAddresses } from 'src/pages/Customer/composables/getAddresses';
import { getAgencies } from 'src/pages/Route/Agency/composables/getAgencies';
import { useState } from 'composables/useState'; import { useState } from 'composables/useState';
import axios from 'axios';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
@ -37,33 +39,13 @@ onBeforeMount(async () => {
}); });
const fetchClient = async (formData) => { const fetchClient = async (formData) => {
const filter = { const { data } = await getClient(formData.clientId);
include: {
relation: 'defaultAddress',
scope: {
fields: ['id', 'agencyModeFk'],
},
},
where: { id: formData.clientId },
};
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get('Clients', { params });
const [client] = data; const [client] = data;
selectedClient.value = client; selectedClient.value = client;
}; };
const fetchAddresses = async (formData) => { const fetchAddresses = async (formData) => {
if (!formData.clientId) return; const { data } = await getAddresses(formData.clientId);
const filter = {
fields: ['nickname', 'street', 'city', 'id'],
where: { isActive: true },
order: 'nickname ASC',
};
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get(`Clients/${formData.clientId}/addresses`, {
params,
});
addressesOptions.value = data; addressesOptions.value = data;
const { defaultAddress } = selectedClient.value; const { defaultAddress } = selectedClient.value;
@ -76,15 +58,7 @@ const onClientSelected = async (formData) => {
}; };
const fetchAvailableAgencies = async (formData) => { const fetchAvailableAgencies = async (formData) => {
if (!formData.warehouseId || !formData.addressId || !formData.landed) return; const { data } = await getAgencies(formData);
let params = {
warehouseFk: formData.warehouseId,
addressFk: formData.addressId,
landed: formData.landed,
};
const { data } = await axios.get('Agencies/getAgenciesWithWarehouse', { params });
agenciesOptions.value = data; agenciesOptions.value = data;
const defaultAgency = agenciesOptions.value.find( const defaultAgency = agenciesOptions.value.find(

View File

@ -244,13 +244,15 @@ const totalPriceColor = (totalWithVat) =>
isLessThan50(totalWithVat) ? 'warning' : 'transparent'; isLessThan50(totalWithVat) ? 'warning' : 'transparent';
const moveTicketsFuture = async () => { const moveTicketsFuture = async () => {
const ticketsToMove = selectedTickets.value.map((ticket) => ({ const ticketsToMove = selectedTickets.value.map((ticket) => {
originId: ticket.id, return {
destinationId: ticket.futureId, originId: ticket.id,
originShipped: ticket.shipped, destinationId: ticket.futureId,
destinationShipped: ticket.futureShipped, originShipped: ticket.shipped,
workerFk: ticket.workerFk, destinationShipped: ticket.futureShipped,
})); salesPersonFk: ticket.salesPersonFk,
};
});
let params = { tickets: ticketsToMove }; let params = { tickets: ticketsToMove };
await axios.post('Tickets/merge', params); await axios.post('Tickets/merge', params);

View File

@ -22,6 +22,9 @@ import { toTimeFormat } from 'src/filters/date';
import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue'; import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue';
import TicketProblems from 'src/components/TicketProblems.vue'; import TicketProblems from 'src/components/TicketProblems.vue';
import VnSection from 'src/components/common/VnSection.vue'; import VnSection from 'src/components/common/VnSection.vue';
import { getClient } from 'src/pages/Customer/composables/getClient';
import { getAddresses } from 'src/pages/Customer/composables/getAddresses';
import { getAgencies } from 'src/pages/Route/Agency/composables/getAgencies';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@ -237,15 +240,7 @@ const onClientSelected = async (formData) => {
}; };
const fetchAvailableAgencies = async (formData) => { const fetchAvailableAgencies = async (formData) => {
if (!formData.warehouseId || !formData.addressId || !formData.landed) return; const { data } = await getAgencies(formData);
let params = {
warehouseFk: formData.warehouseId,
addressFk: formData.addressId,
landed: formData.landed,
};
const { data } = await axios.get('Agencies/getAgenciesWithWarehouse', { params });
agenciesOptions.value = data; agenciesOptions.value = data;
const defaultAgency = agenciesOptions.value.find( const defaultAgency = agenciesOptions.value.find(
@ -257,34 +252,13 @@ const fetchAvailableAgencies = async (formData) => {
}; };
const fetchClient = async (formData) => { const fetchClient = async (formData) => {
const filter = { const { data } = await getClient(formData.clientId);
include: {
relation: 'defaultAddress',
scope: {
fields: ['id', 'agencyModeFk'],
},
},
where: { id: formData.clientId },
};
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get('Clients', { params });
const [client] = data; const [client] = data;
selectedClient.value = client; selectedClient.value = client;
}; };
const fetchAddresses = async (formData) => { const fetchAddresses = async (formData) => {
if (!formData.clientId) return; const { data } = await getAddresses(formData.clientId);
const filter = {
fields: ['nickname', 'street', 'city', 'id', 'isActive'],
order: ['isDefaultAddress DESC', 'isActive DESC', 'nickname ASC'],
};
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get(`Clients/${formData.clientId}/addresses`, {
params,
});
addressesOptions.value = data;
addressesOptions.value = data; addressesOptions.value = data;
const { defaultAddress } = selectedClient.value; const { defaultAddress } = selectedClient.value;
@ -465,7 +439,7 @@ function setReference(data) {
exprBuilder, exprBuilder,
}" }"
> >
<template #rightMenu> <template #advanced-menu>
<TicketFilter data-key="TicketList" /> <TicketFilter data-key="TicketList" />
</template> </template>
<template #body> <template #body>

View File

@ -1,9 +1,8 @@
<script setup> <script setup>
import VnCard from 'components/common/VnCard.vue';
import TravelDescriptor from './TravelDescriptor.vue'; import TravelDescriptor from './TravelDescriptor.vue';
import TravelFilter from '../TravelFilter.vue'; import VnCardBeta from 'src/components/common/VnCardBeta.vue';
const filter = { const userFilter = {
fields: [ fields: [
'id', 'id',
'ref', 'ref',
@ -14,6 +13,9 @@ const filter = {
'warehouseOutFk', 'warehouseOutFk',
'cargoSupplierFk', 'cargoSupplierFk',
'agencyModeFk', 'agencyModeFk',
'isRaid',
'isDelivered',
'isReceived',
], ],
include: [ include: [
{ {
@ -32,17 +34,10 @@ const filter = {
}; };
</script> </script>
<template> <template>
<VnCard <VnCardBeta
data-key="Travel" data-key="Travel"
base-url="Travels" base-url="Travels"
search-data-key="TravelList"
:filter="filter"
:descriptor="TravelDescriptor" :descriptor="TravelDescriptor"
:filter-panel="TravelFilter" :user-filter="userFilter"
:searchbar-props="{
url: 'Travels/filter',
searchUrl: 'table',
label: 'Search travel',
}"
/> />
</template> </template>

View File

@ -85,7 +85,6 @@ const setData = (entity) => (data.value = useCardDescription(entity.ref, entity.
<i18n> <i18n>
es: es:
Go to module index: Ir al índice del módulo
The travel will be deleted: El envío será eliminado The travel will be deleted: El envío será eliminado
Do you want to delete this travel?: ¿Quieres eliminar este envío? Do you want to delete this travel?: ¿Quieres eliminar este envío?
All travels with current agency: Todos los envíos con la agencia actual All travels with current agency: Todos los envíos con la agencia actual

View File

@ -5,18 +5,18 @@ import { useRouter, useRoute } from 'vue-router';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import TravelSummary from './Card/TravelSummary.vue'; import TravelSummary from './Card/TravelSummary.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue';
import { toDate } from 'src/filters'; import { toDate } from 'src/filters';
import { getDateQBadgeColor } from 'src/composables/getDateQBadgeColor.js'; import { getDateQBadgeColor } from 'src/composables/getDateQBadgeColor.js';
import RightMenu from 'src/components/common/RightMenu.vue';
import TravelFilter from './TravelFilter.vue'; import TravelFilter from './TravelFilter.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import VnSection from 'src/components/common/VnSection.vue';
const { viewSummary } = useSummaryDialog(); const { viewSummary } = useSummaryDialog();
const router = useRouter(); const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const tableRef = ref(); const tableRef = ref();
const dataKey = 'TravelList';
const $props = defineProps({ const $props = defineProps({
id: { id: {
type: Number, type: Number,
@ -196,90 +196,93 @@ const columns = computed(() => [
</script> </script>
<template> <template>
<VnSearchbar <VnSection
:info="t('You can search by travel id or name')" :data-key="dataKey"
:label="t('Search travel')" :columns="columns"
data-key="TravelList" prefix="travel"
/> :array-data-props="{
<RightMenu> url: 'Travels/filter',
<template #right-panel> order: ['landed DESC'],
userParams: { daysOnward: 7 },
}"
>
<template #advanced-menu>
<TravelFilter data-key="TravelList" /> <TravelFilter data-key="TravelList" />
</template> </template>
</RightMenu> <template #body>
<VnTable <VnTable
ref="tableRef" ref="tableRef"
data-key="TravelList" :data-key="dataKey"
url="Travels/filter" :create="{
redirect="travel" urlCreate: 'Travels',
:create="{ title: t('Create Travels'),
urlCreate: 'Travels', onDataSaved: ({ id }) => tableRef.redirect(id),
title: t('Create Travels'), formInitialData: {
onDataSaved: ({ id }) => tableRef.redirect(id), editorFk: entityId,
formInitialData: { },
editorFk: entityId, }"
}, :right-search="false"
}" redirect="travel"
:right-search="false" :columns="columns"
:user-params="{ daysOnward: 7 }" :is-editable="false"
order="landed DESC" >
:columns="columns" <template #column-status="{ row }">
:is-editable="false" <div class="row">
> <QIcon v-if="!!row.isRaid" name="vn:net" color="primary">
<template #column-status="{ row }"> <QTooltip>
<div class="row"> {{
<QIcon v-if="!!row.isRaid" name="vn:net" color="primary"> t('globals.raid', {
<QTooltip> daysInForward: row.daysInForward,
{{ })
t('globals.raid', { }}</QTooltip
daysInForward: row.daysInForward, >
}) </QIcon>
}}</QTooltip </div>
</template>
<template #column-shipped="{ row }">
<QBadge
text-color="black"
v-if="getDateQBadgeColor(row.shipped)"
:color="getDateQBadgeColor(row.shipped)"
> >
</QIcon> {{ toDate(row.shipped) }}
</div> </QBadge>
<span v-else>{{ toDate(row.shipped) }}</span>
<QIcon
name="flight_takeoff"
size="sm"
:class="{ 'is-active': row.isDelivered }"
/>
</template>
<template #column-landed="{ row }">
<QBadge
text-color="black"
v-if="getDateQBadgeColor(row.landed)"
:color="getDateQBadgeColor(row.landed)"
>
{{ toDate(row.landed) }}
</QBadge>
<span v-else>{{ toDate(row.landed) }}</span>
<QIcon
name="flight_land"
size="sm"
:class="{ 'is-active': row.isReceived }"
/>
</template>
<template #moreFilterPanel="{ params }">
<VnInputNumber
:label="t('params.scopeDays')"
v-model.number="params.scopeDays"
@keyup.enter="(evt) => handleScopeDays(evt.target.value)"
@remove="handleScopeDays()"
class="q-px-xs q-pr-lg"
filled
dense
/>
</template>
</VnTable>
</template> </template>
<template #column-shipped="{ row }"> </VnSection>
<QBadge
text-color="black"
v-if="getDateQBadgeColor(row.shipped)"
:color="getDateQBadgeColor(row.shipped)"
>
{{ toDate(row.shipped) }}
</QBadge>
<span v-else>{{ toDate(row.shipped) }}</span>
<QIcon
name="flight_takeoff"
size="sm"
:class="{ 'is-active': row.isDelivered }"
/>
</template>
<template #column-landed="{ row }">
<QBadge
text-color="black"
v-if="getDateQBadgeColor(row.landed)"
:color="getDateQBadgeColor(row.landed)"
>
{{ toDate(row.landed) }}
</QBadge>
<span v-else>{{ toDate(row.landed) }}</span>
<QIcon
name="flight_land"
size="sm"
:class="{ 'is-active': row.isReceived }"
/>
</template>
<template #moreFilterPanel="{ params }">
<VnInputNumber
:label="t('params.scopeDays')"
v-model.number="params.scopeDays"
@keyup.enter="(evt) => handleScopeDays(evt.target.value)"
@remove="handleScopeDays()"
class="q-px-xs q-pr-lg"
filled
dense
/>
</template>
</VnTable>
</template> </template>
<i18n> <i18n>
en: en:

View File

@ -11,7 +11,7 @@ const { t } = useI18n();
const counters = ref({ const counters = ref({
alquilerBandeja: { count: 0, id: 96001, title: 'CC Bandeja', isTray: true }, alquilerBandeja: { count: 0, id: 96001, title: 'CC Bandeja', isTray: true },
bandejaRota: { count: 0, id: 88381, title: 'CC Bandeja Rota', isTray: true }, bandejaRota: { count: 0, id: 88381, title: 'CC Bandeja Rota', isTray: true },
carryOficial: { count: 0, id: 96000, title: 'CC Carry OFICIAL TAG5' }, carryOficial: { count: 0, id: 96000, title: 'CC Carry OFICIAL TAG6' },
candadoRojo: { count: 0, id: 96002, title: 'CC Carry NO OFICIAL' }, candadoRojo: { count: 0, id: 96002, title: 'CC Carry NO OFICIAL' },
sacadores: { count: 0, id: 142260, title: 'CC Sacadores' }, sacadores: { count: 0, id: 142260, title: 'CC Sacadores' },
sinChapa: { count: 0, id: 2214, title: 'DC Carry Sin Placa CC' }, sinChapa: { count: 0, id: 2214, title: 'DC Carry Sin Placa CC' },

View File

@ -1,18 +1,16 @@
<script setup> <script setup>
import { nextTick, ref, watch } from 'vue'; import { nextTick, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import WorkerCalendarFilter from 'pages/Worker/Card/WorkerCalendarFilter.vue'; import WorkerCalendarFilter from 'pages/Worker/Card/WorkerCalendarFilter.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import WorkerCalendarItem from 'pages/Worker/Card/WorkerCalendarItem.vue'; import WorkerCalendarItem from 'pages/Worker/Card/WorkerCalendarItem.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import { useStateStore } from 'stores/useStateStore';
import axios from 'axios'; import axios from 'axios';
import { useRouter } from 'vue-router';
const router = useRouter(); const router = useRouter();
const stateStore = useStateStore();
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const workerIsFreelance = ref(); const workerIsFreelance = ref();
@ -171,16 +169,18 @@ watch([year, businessFk], () => refreshData());
ref="WorkerFreelanceRef" ref="WorkerFreelanceRef"
auto-load auto-load
/> />
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> <RightMenu>
<WorkerCalendarFilter <template #right-panel>
ref="workerCalendarFilterRef" <WorkerCalendarFilter
v-model:business-fk="businessFk" ref="workerCalendarFilterRef"
v-model:year="year" v-model:business-fk="businessFk"
v-model:absence-type="absenceType" v-model:year="year"
:contract-holidays="contractHolidays" v-model:absence-type="absenceType"
:year-holidays="yearHolidays" :contract-holidays="contractHolidays"
/> :year-holidays="yearHolidays"
</Teleport> />
</template>
</RightMenu>
<QPage class="column items-center"> <QPage class="column items-center">
<QCard v-if="workerIsFreelance"> <QCard v-if="workerIsFreelance">
<QCardSection class="text-center"> <QCardSection class="text-center">

View File

@ -10,6 +10,7 @@ import axios from 'axios';
import VnImg from 'src/components/ui/VnImg.vue'; import VnImg from 'src/components/ui/VnImg.vue';
import EditPictureForm from 'components/EditPictureForm.vue'; import EditPictureForm from 'components/EditPictureForm.vue';
import WorkerDescriptorMenu from './WorkerDescriptorMenu.vue'; import WorkerDescriptorMenu from './WorkerDescriptorMenu.vue';
import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue';
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -115,10 +116,13 @@ const handlePhotoUpdated = (evt = false) => {
:value="entity.user?.emailUser?.email" :value="entity.user?.emailUser?.email"
copy copy
/> />
<VnLv <VnLv :label="t('worker.list.department')">
:label="t('worker.list.department')" <template #value>
:value="entity.department ? entity.department.department.name : null" <span class="link" v-text="entity.department?.department?.name" />
/> <DepartmentDescriptorProxy :id="entity.department?.department?.id" />
</template>
</VnLv>
<VnLv :value="entity.phone"> <VnLv :value="entity.phone">
<template #label> <template #label>
{{ t('globals.phone') }} {{ t('globals.phone') }}

View File

@ -10,6 +10,7 @@ import WorkerTimeForm from 'pages/Worker/Card/WorkerTimeForm.vue';
import WorkerTimeReasonForm from 'pages/Worker/Card/WorkerTimeReasonForm.vue'; import WorkerTimeReasonForm from 'pages/Worker/Card/WorkerTimeReasonForm.vue';
import WorkerDateLabel from './WorkerDateLabel.vue'; import WorkerDateLabel from './WorkerDateLabel.vue';
import WorkerTimeControlCalendar from 'pages/Worker/Card/WorkerTimeControlCalendar.vue'; import WorkerTimeControlCalendar from 'pages/Worker/Card/WorkerTimeControlCalendar.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import axios from 'axios'; import axios from 'axios';
@ -483,33 +484,35 @@ onMounted(async () => {
</QBtnGroup> </QBtnGroup>
</div> </div>
</Teleport> </Teleport>
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> <RightMenu>
<div class="q-pa-md q-mb-md" style="border: 2px solid #222"> <template #right-panel>
<QCardSection horizontal> <div class="q-pa-md q-mb-md" style="border: 2px solid #222">
<span class="text-weight-bold text-subtitle1 text-center full-width"> <QCardSection horizontal>
{{ t('Hours') }} <span class="text-weight-bold text-subtitle1 text-center full-width">
</span> {{ t('Hours') }}
</QCardSection> </span>
<QCardSection class="column items-center" horizontal> </QCardSection>
<div> <QCardSection class="column items-center" horizontal>
<span class="details-label">{{ t('Total semana') }} </span> <div>
<span>: {{ formattedWeekTotalHours }}</span> <span class="details-label">{{ t('Total semana') }} </span>
</div> <span>: {{ formattedWeekTotalHours }}</span>
<div> </div>
<span class="details-label">{{ t('Termina a las') }}: </span> <div>
<span>{{ dashIfEmpty(getFinishTime()) }}</span> <span class="details-label">{{ t('Termina a las') }}: </span>
</div> <span>{{ dashIfEmpty(getFinishTime()) }}</span>
</QCardSection> </div>
</div> </QCardSection>
<WorkerTimeControlCalendar </div>
v-model:model-value="selectedDateFormatted" <WorkerTimeControlCalendar
:selected-dates="selectedCalendarDates" v-model:model-value="selectedDateFormatted"
:active-date="false" :selected-dates="selectedCalendarDates"
:worker-time-control-mails="workerTimeControlMails" :active-date="false"
@click-date="onInputChange" :worker-time-control-mails="workerTimeControlMails"
@on-moved="getMailStates" @click-date="onInputChange"
/> @on-moved="getMailStates"
</Teleport> />
</template>
</RightMenu>
<QPage class="column items-center"> <QPage class="column items-center">
<QTable :columns="columns" :rows="['']" hide-bottom class="full-width"> <QTable :columns="columns" :rows="['']" hide-bottom class="full-width">
<template #header="props"> <template #header="props">

View File

@ -98,6 +98,15 @@ const getLocale = (label) => {
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem>
<QItemSection>
<QCheckbox
:label="t('globals.params.myTeam')"
v-model="params.myTeam"
toggle-indeterminate
/>
</QItemSection>
</QItem>
</template> </template>
</VnFilterPanel> </VnFilterPanel>
</template> </template>

View File

@ -199,7 +199,7 @@ async function autofillBic(worker) {
order: 'id DESC', order: 'id DESC',
}" }"
> >
<template #rightMenu> <template #advanced-menu>
<WorkerFilter data-key="WorkerList" /> <WorkerFilter data-key="WorkerList" />
</template> </template>
<template #body> <template #body>

View File

@ -66,7 +66,3 @@ const setData = (entity) => {
</CardDescriptor> </CardDescriptor>
</template> </template>
<i18n>
es:
Go to module index: Ir al índice del módulo
</i18n>

View File

@ -90,7 +90,7 @@ const redirectToZoneSummary = (id) => {
color="primary" color="primary"
@click.stop="viewSummary(props.row.id, ZoneSummary)" @click.stop="viewSummary(props.row.id, ZoneSummary)"
> >
<QTooltip>{{ t('zoneClosingTable.preview') }}</QTooltip> <QTooltip>{{ t('globals.preview') }}</QTooltip>
</QIcon> </QIcon>
</div> </div>
</QTd> </QTd>

View File

@ -50,8 +50,7 @@ deliveryPanel:
postcode: Postcode postcode: Postcode
query: Query query: Query
noEventsWarning: No service for the specified zone noEventsWarning: No service for the specified zone
zoneClosingTable:
preview: Preview
warehouses: warehouses:
deleteTitle: This item will be deleted deleteTitle: This item will be deleted
deleteSubtitle: Are you sure you want to continue? deleteSubtitle: Are you sure you want to continue?

View File

@ -13,9 +13,7 @@ import Travel from './travel';
import Order from './order'; import Order from './order';
import Entry from './entry'; import Entry from './entry';
import roadmap from './roadmap'; import roadmap from './roadmap';
import Parking from './parking';
import Agency from './agency'; import Agency from './agency';
import ItemType from './itemType';
import Zone from './zone'; import Zone from './zone';
import Account from './account'; import Account from './account';
import Monitor from './monitor'; import Monitor from './monitor';
@ -36,9 +34,7 @@ export default [
invoiceIn, invoiceIn,
Entry, Entry,
roadmap, roadmap,
Parking,
Agency, Agency,
ItemType,
Zone, Zone,
Account, Account,
Monitor, Monitor,

View File

@ -1,34 +1,60 @@
import { RouterView } from 'vue-router'; import { RouterView } from 'vue-router';
const invoiceOutCard = {
name: 'InvoiceOutCard',
path: ':id',
component: () => import('src/pages/InvoiceOut/Card/InvoiceOutCard.vue'),
redirect: { name: 'InvoiceOutSummary' },
meta: {
menu: [],
},
children: [
{
path: 'summary',
name: 'InvoiceOutSummary',
meta: {
title: 'summary',
icon: 'launch',
},
component: () => import('src/pages/InvoiceOut/Card/InvoiceOutSummary.vue'),
}
],
};
export default { export default {
path: '/invoice-out',
name: 'InvoiceOut', name: 'InvoiceOut',
path: '/invoice-out',
meta: { meta: {
title: 'invoiceOuts', title: 'invoiceOuts',
icon: 'vn:invoice-out', icon: 'vn:invoice-out',
moduleName: 'InvoiceOut', moduleName: 'InvoiceOut',
menu: ['InvoiceOutList', 'InvoiceOutGlobal', 'InvoiceOutNegativeBases'],
}, },
component: RouterView, component: RouterView,
redirect: { name: 'InvoiceOutMain' }, redirect: { name: 'InvoiceOutMain' },
menus: {
main: ['InvoiceOutList', 'InvoiceOutGlobal', 'InvoiceOutNegativeBases'],
card: [],
},
children: [ children: [
{ {
path: '',
name: 'InvoiceOutMain', name: 'InvoiceOutMain',
path: '',
component: () => import('src/components/common/VnModule.vue'), component: () => import('src/components/common/VnModule.vue'),
redirect: { name: 'InvoiceOutList' }, redirect: { name: 'InvoiceOutIndexMain' },
children: [ children: [
{ {
path: 'list', path: '',
name: 'InvoiceOutList', name: 'InvoiceOutIndexMain',
meta: { redirect: { name: 'InvoiceOutList' },
title: 'list',
icon: 'view_list',
},
component: () => import('src/pages/InvoiceOut/InvoiceOutList.vue'), component: () => import('src/pages/InvoiceOut/InvoiceOutList.vue'),
children: [
{
name: 'InvoiceOutList',
path: 'list',
meta: {
title: 'list',
icon: 'view_list',
},
},
invoiceOutCard,
],
}, },
{ {
path: 'global-invoicing', path: 'global-invoicing',
@ -51,22 +77,5 @@ export default {
}, },
], ],
}, },
{
name: 'InvoiceOutCard',
path: ':id',
component: () => import('src/pages/InvoiceOut/Card/InvoiceOutCard.vue'),
redirect: { name: 'InvoiceOutSummary' },
children: [
{
name: 'InvoiceOutSummary',
path: 'summary',
meta: {
title: 'summary',
},
component: () =>
import('src/pages/InvoiceOut/Card/InvoiceOutSummary.vue'),
},
],
},
], ],
}; };

View File

@ -1,52 +1,197 @@
import { RouterView } from 'vue-router'; import { RouterView } from 'vue-router';
const itemCard = {
name: 'ItemCard',
path: ':id',
component: () => import('src/pages/Item/Card/ItemCard.vue'),
redirect: { name: 'ItemSummary' },
meta: {
menu: [
'ItemBasicData',
'ItemTags',
'ItemLastEntries',
'ItemTax',
'ItemBotanical',
'ItemShelving',
'ItemBarcode',
'ItemDiary',
'ItemLog',
],
},
children: [
{
name: 'ItemSummary',
path: 'summary',
meta: {
title: 'summary',
icon: 'launch',
},
component: () => import('src/pages/Item/Card/ItemSummary.vue'),
},
{
path: 'basic-data',
name: 'ItemBasicData',
meta: {
title: 'basicData',
icon: 'vn:settings',
},
component: () => import('src/pages/Item/Card/ItemBasicData.vue'),
},
{
path: 'tags',
name: 'ItemTags',
meta: {
title: 'tags',
icon: 'vn:tags',
},
component: () => import('src/pages/Item/Card/ItemTags.vue'),
},
{
path: 'last-entries',
name: 'ItemLastEntries',
meta: {
title: 'lastEntries',
icon: 'vn:regentry',
},
component: () => import('src/pages/Item/Card/ItemLastEntries.vue'),
},
{
path: 'tax',
name: 'ItemTax',
meta: {
title: 'tax',
icon: 'vn:tax',
},
component: () => import('src/pages/Item/Card/ItemTax.vue'),
},
{
path: 'botanical',
name: 'ItemBotanical',
meta: {
title: 'botanical',
icon: 'vn:botanical',
},
component: () => import('src/pages/Item/Card/ItemBotanical.vue'),
},
{
path: 'shelving',
name: 'ItemShelving',
meta: {
title: 'shelving',
icon: 'vn:inventory',
},
component: () => import('src/pages/Item/Card/ItemShelving.vue'),
},
{
path: 'barcode',
name: 'ItemBarcode',
meta: {
title: 'barcode',
icon: 'vn:barcode',
},
component: () => import('src/pages/Item/Card/ItemBarcode.vue'),
},
{
path: 'diary',
name: 'ItemDiary',
meta: {
title: 'diary',
icon: 'vn:transaction',
},
component: () => import('src/pages/Item/Card/ItemDiary.vue'),
},
{
path: 'log',
name: 'ItemLog',
meta: {
title: 'log',
icon: 'vn:History',
},
component: () => import('src/pages/Item/Card/ItemLog.vue'),
},
],
};
const itemTypeCard = {
name: 'ItemTypeCard',
path: ':id',
component: () => import('src/pages/Item/ItemType/Card/ItemTypeCard.vue'),
redirect: { name: 'ItemTypeSummary' },
meta: {
menu: ['ItemTypeBasicData', 'ItemTypeLog'],
},
children: [
{
path: 'summary',
name: 'ItemTypeSummary',
meta: {
title: 'summary',
icon: 'launch',
},
component: () => import('src/pages/Item/ItemType/Card/ItemTypeSummary.vue'),
},
{
path: 'basic-data',
name: 'ItemTypeBasicData',
meta: {
title: 'basicData',
icon: 'vn:settings',
},
component: () => import('src/pages/Item/ItemType/Card/ItemTypeBasicData.vue'),
},
{
path: 'log',
name: 'ItemTypeLog',
meta: {
title: 'log',
icon: 'vn:History',
},
component: () => import('src/pages/Item/ItemType/Card/ItemTypeLog.vue'),
},
],
};
export default { export default {
path: '/item',
name: 'Item', name: 'Item',
path: '/item',
meta: { meta: {
title: 'items', title: 'items',
icon: 'vn:item', icon: 'vn:item',
moduleName: 'Item', moduleName: 'Item',
keyBinding: 'a', keyBinding: 'a',
}, menu: [
component: RouterView,
redirect: { name: 'ItemMain' },
menus: {
main: [
'ItemList', 'ItemList',
'WasteBreakdown', 'WasteBreakdown',
'ItemFixedPrice', 'ItemFixedPrice',
'ItemRequest', 'ItemRequest',
'ItemTypeList', 'ItemTypeList',
], ],
card: [
'ItemBasicData',
'ItemLog',
'ItemDiary',
'ItemTags',
'ItemTax',
'ItemBotanical',
'ItemBarcode',
'ItemShelving',
'ItemLastEntries',
'ItemTags',
],
}, },
component: RouterView,
redirect: { name: 'ItemMain' },
children: [ children: [
{ {
path: '',
name: 'ItemMain', name: 'ItemMain',
path: '',
component: () => import('src/components/common/VnModule.vue'), component: () => import('src/components/common/VnModule.vue'),
redirect: { name: 'ItemList' }, redirect: { name: 'ItemIndexMain' },
children: [ children: [
{ {
path: 'list', path: '',
name: 'ItemList', name: 'ItemIndexMain',
meta: { redirect: { name: 'ItemList' },
title: 'list',
icon: 'view_list',
},
component: () => import('src/pages/Item/ItemList.vue'), component: () => import('src/pages/Item/ItemList.vue'),
children: [
{
name: 'ItemList',
path: 'list',
meta: {
title: 'list',
icon: 'view_list',
},
},
itemCard,
],
}, },
{ {
path: 'request', path: 'request',
@ -80,120 +225,21 @@ export default {
component: () => import('src/pages/Item/ItemFixedPrice.vue'), component: () => import('src/pages/Item/ItemFixedPrice.vue'),
}, },
{ {
path: 'create', path: 'item-type',
name: 'ItemCreate', name: 'ItemTypeMain',
meta: { redirect: { name: 'ItemTypeList' },
title: 'itemCreate',
},
component: () => import('src/pages/Item/ItemCreate.vue'),
},
{
path: 'item-type-list',
name: 'ItemTypeList',
meta: {
title: 'family',
icon: 'contact_support',
},
component: () => import('src/pages/Item/ItemTypeList.vue'), component: () => import('src/pages/Item/ItemTypeList.vue'),
}, children: [
], {
}, name: 'ItemTypeList',
{ path: 'list',
name: 'ItemCard', meta: {
path: ':id', title: 'family',
component: () => import('src/pages/Item/Card/ItemCard.vue'), icon: 'contact_support',
redirect: { name: 'ItemSummary' }, },
children: [ },
{ itemTypeCard,
name: 'ItemSummary', ],
path: 'summary',
meta: {
title: 'summary',
icon: 'launch',
},
component: () => import('src/pages/Item/Card/ItemSummary.vue'),
},
{
path: 'basic-data',
name: 'ItemBasicData',
meta: {
title: 'basicData',
icon: 'vn:settings',
},
component: () => import('src/pages/Item/Card/ItemBasicData.vue'),
},
{
path: 'tags',
name: 'ItemTags',
meta: {
title: 'tags',
icon: 'vn:tags',
},
component: () => import('src/pages/Item/Card/ItemTags.vue'),
},
{
path: 'last-entries',
name: 'ItemLastEntries',
meta: {
title: 'lastEntries',
icon: 'vn:regentry',
},
component: () => import('src/pages/Item/Card/ItemLastEntries.vue'),
},
{
path: 'tax',
name: 'ItemTax',
meta: {
title: 'tax',
icon: 'vn:tax',
},
component: () => import('src/pages/Item/Card/ItemTax.vue'),
},
{
path: 'botanical',
name: 'ItemBotanical',
meta: {
title: 'botanical',
icon: 'local_florist',
},
component: () => import('src/pages/Item/Card/ItemBotanical.vue'),
},
{
path: 'shelving',
name: 'ItemShelving',
meta: {
title: 'shelving',
icon: 'vn:inventory',
},
component: () => import('src/pages/Item/Card/ItemShelving.vue'),
},
{
path: 'barcode',
name: 'ItemBarcode',
meta: {
title: 'barcode',
icon: 'vn:barcode',
},
component: () => import('src/pages/Item/Card/ItemBarcode.vue'),
},
{
path: 'diary',
name: 'ItemDiary',
meta: {
title: 'diary',
icon: 'vn:transaction',
},
component: () => import('src/pages/Item/Card/ItemDiary.vue'),
},
{
path: 'log',
name: 'ItemLog',
meta: {
title: 'log',
icon: 'vn:History',
},
component: () => import('src/pages/Item/Card/ItemLog.vue'),
}, },
], ],
}, },

View File

@ -1,56 +0,0 @@
import { RouterView } from 'vue-router';
export default {
path: '/item/item-type',
name: 'ItemType',
meta: {
title: 'itemType',
icon: 'contact_support',
moduleName: 'ItemType',
},
component: RouterView,
redirect: { name: 'ItemTypeList' },
menus: {
main: [],
card: ['ItemTypeBasicData', 'ItemTypeLog'],
},
children: [
{
name: 'ItemTypeCard',
path: ':id',
component: () => import('src/pages/Item/ItemType/Card/ItemTypeCard.vue'),
redirect: { name: 'ItemTypeSummary' },
children: [
{
name: 'ItemTypeSummary',
path: 'summary',
meta: {
title: 'summary',
},
component: () =>
import('src/pages/Item/ItemType/Card/ItemTypeSummary.vue'),
},
{
name: 'ItemTypeBasicData',
path: 'basic-data',
meta: {
title: 'basicData',
icon: 'vn:settings',
},
component: () =>
import('src/pages/Item/ItemType/Card/ItemTypeBasicData.vue'),
},
{
path: 'log',
name: 'ItemTypeLog',
meta: {
title: 'log',
icon: 'vn:History',
},
component: () =>
import('src/pages/Item/ItemType/Card/ItemTypeLog.vue'),
},
],
},
],
};

View File

@ -1,54 +0,0 @@
import { RouterView } from 'vue-router';
export default {
path: '/parking',
name: 'Parking',
meta: {
title: 'parking',
icon: 'garage_home',
moduleName: 'Parking',
},
component: RouterView,
redirect: { name: 'ParkingCard' },
menus: {
main: [],
card: ['ParkingBasicData', 'ParkingLog'],
},
children: [
{
path: '/parking/:id',
name: 'ParkingCard',
component: () => import('src/pages/Parking/Card/ParkingCard.vue'),
redirect: { name: 'ParkingSummary' },
children: [
{
name: 'ParkingSummary',
path: 'summary',
meta: {
title: 'summary',
icon: 'view_list',
},
component: () => import('src/pages/Parking/Card/ParkingSummary.vue'),
},
{
name: 'ParkingBasicData',
path: 'basic-data',
meta: {
title: 'basicData',
icon: 'vn:settings',
},
component: () => import('pages/Parking/Card/ParkingBasicData.vue'),
},
{
name: 'ParkingLog',
path: 'log',
meta: {
title: 'log',
icon: 'history',
},
component: () => import('src/pages/Parking/Card/ParkingLog.vue'),
},
],
},
],
};

Some files were not shown because too many files have changed in this diff Show More