Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 6898-supplierMigration
gitea/salix-front/pipeline/pr-dev This commit looks good Details

This commit is contained in:
Carlos Satorres 2024-07-03 10:07:34 +02:00
commit 94680a6c2d
86 changed files with 1860 additions and 912 deletions

View File

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

BIN
public/no-image-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
public/no-image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
public/no-user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@ -90,7 +90,7 @@ const $props = defineProps({
});
const emit = defineEmits(['onFetch', 'onDataSaved']);
const modelValue = computed(
() => $props.model ?? `formModel_${route?.meta?.title ?? route.name}`
() => $props.model ?? `formModel_${route?.meta?.title ?? route.name}`,
).value;
const componentIsRendered = ref(false);
const arrayData = useArrayData(modelValue);
@ -137,7 +137,7 @@ onMounted(async () => {
JSON.stringify(newVal) !== JSON.stringify(originalData.value);
isResetting.value = false;
},
{ deep: true }
{ deep: true },
);
}
});
@ -145,7 +145,7 @@ onMounted(async () => {
if (!$props.url)
watch(
() => arrayData.store.data,
(val) => updateAndEmit('onFetch', val)
(val) => updateAndEmit('onFetch', val),
);
watch(formUrl, async () => {
@ -206,11 +206,11 @@ async function save() {
updateAndEmit('onDataSaved', formData.value, response?.data);
if ($props.reload) await arrayData.fetch({});
hasChanges.value = false;
} catch (err) {
console.error(err);
notify('errors.writeRequest', 'negative');
} finally {
hasChanges.value = false;
isLoading.value = false;
}
}
@ -239,7 +239,7 @@ function filter(value, update, filterOptions) {
(ref) => {
ref.setOptionIndex(-1);
ref.moveOptionSelection(1, true);
}
},
);
}

View File

@ -58,6 +58,7 @@ function addChildren(module, route, parent) {
}
const items = ref([]);
function getRoutes() {
if (props.source === 'main') {
const modules = Object.assign([], navigation.getModules().value);
@ -66,9 +67,8 @@ function getRoutes() {
const moduleDef = routes.find(
(route) => toLowerCamel(route.name) === item.module
);
item.children = [];
if (!moduleDef) continue;
item.children = [];
addChildren(item.module, moduleDef, item.children);
}

View File

@ -1,21 +1,19 @@
<script setup>
import { onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useSession } from 'src/composables/useSession';
import { useState } from 'src/composables/useState';
import { useStateStore } from 'stores/useStateStore';
import { useQuasar } from 'quasar';
import PinnedModules from './PinnedModules.vue';
import UserPanel from 'components/UserPanel.vue';
import VnBreadcrumbs from './common/VnBreadcrumbs.vue';
import VnImg from 'src/components/ui/VnImg.vue';
const { t } = useI18n();
const stateStore = useStateStore();
const quasar = useQuasar();
const state = useState();
const user = state.getUser();
const { getTokenMultimedia } = useSession();
const token = getTokenMultimedia();
const appName = 'Lilium';
onMounted(() => stateStore.setMounted());
@ -83,11 +81,12 @@ const pinnedModulesRef = ref();
id="user"
>
<QAvatar size="lg">
<QImg
:src="`/api/Images/user/160x160/${user.id}/download?access_token=${token}`"
spinner-color="primary"
>
</QImg>
<VnImg
:id="user.id"
collection="user"
size="160x160"
:zoom-size="null"
/>
</QAvatar>
<QTooltip bottom>
{{ t('globals.userPanel') }}

View File

@ -12,12 +12,14 @@ import VnRow from 'components/ui/VnRow.vue';
import FetchData from 'components/FetchData.vue';
import { useClipboard } from 'src/composables/useClipboard';
import VnImg from 'src/components/ui/VnImg.vue';
import { useRole } from 'src/composables/useRole';
const state = useState();
const session = useSession();
const router = useRouter();
const { t, locale } = useI18n();
const { copyText } = useClipboard();
const userLocale = computed({
get() {
return locale.value;
@ -99,6 +101,7 @@ function saveUserData(param, value) {
axios.post('UserConfigs/setUserConfig', { [param]: value });
localUserData();
}
const isEmployee = computed(() => useRole().isEmployee());
</script>
<template>
@ -109,12 +112,14 @@ function saveUserData(param, value) {
auto-load
/>
<FetchData
v-if="isEmployee"
url="Companies"
order="name"
@on-fetch="(data) => (companiesData = data)"
auto-load
/>
<FetchData
v-if="isEmployee"
url="Accountings"
order="name"
@on-fetch="(data) => (accountBankData = data)"

View File

@ -7,6 +7,7 @@ import { useArrayData } from 'composables/useArrayData';
import VnSelect from 'components/common/VnSelect.vue';
import VnInput from 'components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInputTime from 'components/common/VnInputTime.vue';
import VnTableColumn from 'components/VnTable/VnColumn.vue';
const $props = defineProps({
@ -75,6 +76,17 @@ const components = {
},
forceAttrs,
},
time: {
component: markRaw(VnInputTime),
event: updateEvent,
attrs: {
...defaultAttrs,
disable: !$props.isEditable,
},
forceAttrs: {
label: $props.showLabel && $props.column.label,
},
},
checkbox: {
component: markRaw(QCheckbox),
event: updateEvent,

View File

@ -59,6 +59,10 @@ const $props = defineProps({
type: Boolean,
default: false,
},
hasSubtoolbar: {
type: Boolean,
default: true,
},
});
const { t } = useI18n();
const stateStore = useStateStore();
@ -175,11 +179,14 @@ function columnName(col) {
}
function getColAlign(col) {
return 'text-' + (col.align ?? 'left')
return 'text-' + (col.align ?? 'left');
}
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
defineExpose({
reload,
redirect: redirectFn,
selected,
});
</script>
<template>
@ -225,11 +232,18 @@ defineExpose({
:search-url="searchUrl"
:disable-infinite-scroll="mode == TABLE_MODE"
@save-changes="reload"
:has-subtoolbar="isEditable"
:has-subtoolbar="$attrs['hasSubtoolbar'] ?? isEditable"
>
<template
v-for="(_, slotName) in $slots"
#[slotName]="slotData"
:key="slotName"
>
<slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" />
</template>
<template #body="{ rows }">
<QTable
v-bind="$attrs['QTable']"
v-bind="$attrs['q-table']"
class="vnTable"
:columns="splittedColumns.columns"
:rows="rows"
@ -246,6 +260,7 @@ defineExpose({
CrudModelRef.vnPaginateRef.paginate()
"
@row-click="(_, row) => rowClickFunction(row)"
@update:selected="emit('update:selected', $event)"
>
<template #top-left>
<slot name="top-left"></slot>
@ -310,13 +325,15 @@ defineExpose({
class="no-margin q-px-xs"
:class="getColAlign(col)"
>
<VnTableColumn
:column="col"
:row="row"
:is-editable="false"
v-model="row[col.name]"
component-prop="columnField"
/>
<slot :name="`column-${col.name}`" :col="col" :row="row">
<VnTableColumn
:column="col"
:row="row"
:is-editable="false"
v-model="row[col.name]"
component-prop="columnField"
/>
</slot>
</QTd>
</template>
<template #body-cell-tableActions="{ col, row }">
@ -412,14 +429,20 @@ defineExpose({
stopEventPropagation($event)
"
>
<VnTableColumn
:column="col"
<slot
:name="`column-${col.name}`"
:col="col"
:row="row"
:is-editable="false"
v-model="row[col.name]"
component-prop="columnField"
:show-label="true"
/>
>
<VnTableColumn
:column="col"
:row="row"
:is-editable="false"
v-model="row[col.name]"
component-prop="columnField"
:show-label="true"
/>
</slot>
</span>
</template>
</VnLv>
@ -477,6 +500,7 @@ defineExpose({
default="input"
v-model="data[column.name]"
:show-label="true"
component-prop="columnCreate"
/>
<slot name="more-create-dialog" :data="data" />
</div>

View File

@ -1,6 +1,6 @@
<script setup>
import { onBeforeMount, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import { useArrayData } from 'src/composables/useArrayData';
import { useStateStore } from 'stores/useStateStore';
import useCardSize from 'src/composables/useCardSize';
@ -41,6 +41,15 @@ onBeforeMount(async () => {
if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id };
await arrayData.fetch({ append: false });
});
if (props.baseUrl) {
onBeforeRouteUpdate(async (to, from) => {
if (to.params.id !== from.params.id) {
arrayData.store.url = `${props.baseUrl}/${to.params.id}`;
await arrayData.fetch({ append: false, updateRouter: false });
}
});
}
</script>
<template>
<QDrawer

View File

@ -93,7 +93,12 @@ const inputRules = [
name="close"
size="xs"
v-if="hover && value && !$attrs.disabled && $props.clearable"
@click="value = null"
@click="
() => {
value = null;
emit('remove');
}
"
></QIcon>
<QIcon v-if="info" name="info">
<QTooltip max-width="350px">

View File

@ -1,84 +1,31 @@
<script setup>
import { computed, ref } from 'vue';
import { onMounted, watch, computed, ref } from 'vue';
import { date } from 'quasar';
import { useI18n } from 'vue-i18n';
import isValidDate from 'filters/isValidDate';
const props = defineProps({
modelValue: {
type: String,
default: null,
},
readonly: {
type: Boolean,
default: false,
},
const model = defineModel({ type: String });
const $props = defineProps({
isOutlined: {
type: Boolean,
default: false,
},
emitDateFormat: {
type: Boolean,
default: false,
},
});
const hover = ref(false);
const emit = defineEmits(['update:modelValue']);
const { t } = useI18n();
const requiredFieldRule = (val) => !!val || t('globals.fieldRequired');
const joinDateAndTime = (date, time) => {
if (!date) {
return null;
}
if (!time) {
return new Date(date).toISOString();
}
const [year, month, day] = date.split('/');
return new Date(`${year}-${month}-${day}T${time}`).toISOString();
};
const dateFormat = 'DD/MM/YYYY';
const isPopupOpen = ref();
const hover = ref();
const mask = ref();
const time = computed(() => (props.modelValue ? props.modelValue.split('T')?.[1] : null));
const value = computed({
get() {
return props.modelValue;
},
set(value) {
emit(
'update:modelValue',
props.emitDateFormat ? new Date(value) : joinDateAndTime(value, time.value)
);
},
onMounted(() => {
// fix quasar bug
mask.value = '##/##/####';
});
const isPopupOpen = ref(false);
const onDateUpdate = (date) => {
value.value = date;
isPopupOpen.value = false;
};
const padDate = (value) => value.toString().padStart(2, '0');
const formatDate = (dateString) => {
const date = new Date(dateString || '');
return `${date.getFullYear()}/${padDate(date.getMonth() + 1)}/${padDate(
date.getDate()
)}`;
};
const displayDate = (dateString) => {
if (!dateString || !isValidDate(dateString)) {
return '';
}
return new Date(dateString).toLocaleDateString([], {
year: 'numeric',
month: '2-digit',
day: '2-digit',
});
};
const styleAttrs = computed(() => {
return props.isOutlined
return $props.isOutlined
? {
dense: true,
outlined: true,
@ -86,52 +33,101 @@ const styleAttrs = computed(() => {
}
: {};
});
const formattedDate = computed({
get() {
if (!model.value) return model.value;
return date.formatDate(new Date(model.value), dateFormat);
},
set(value) {
if (value == model.value) return;
let newDate;
if (value) {
// parse input
if (value.includes('/') && value.length >= 10) {
if (value.at(2) == '/') value = value.split('/').reverse().join('/');
value = date.formatDate(
new Date(value).toISOString(),
'YYYY-MM-DDTHH:mm:ss.SSSZ'
);
}
let ymd = value.split('-').map((e) => parseInt(e));
newDate = new Date(ymd[0], ymd[1] - 1, ymd[2]);
if (model.value) {
const orgDate =
model.value instanceof Date ? model.value : new Date(model.value);
newDate.setHours(
orgDate.getHours(),
orgDate.getMinutes(),
orgDate.getSeconds(),
orgDate.getMilliseconds()
);
}
}
if (!isNaN(newDate)) model.value = newDate.toISOString();
},
});
const popupDate = computed(() =>
model.value ? date.formatDate(new Date(model.value), 'YYYY/MM/DD') : model.value
);
watch(
() => model.value,
(val) => (formattedDate.value = val),
{ immediate: true }
);
</script>
<template>
<div @mouseover="hover = true" @mouseleave="hover = false">
<QInput
v-model="formattedDate"
class="vn-input-date"
readonly
:model-value="displayDate(value)"
:mask="mask"
placeholder="dd/mm/aaaa"
v-bind="{ ...$attrs, ...styleAttrs }"
:class="{ required: $attrs.required }"
:rules="$attrs.required ? [requiredFieldRule] : null"
@click="isPopupOpen = true"
:clearable="false"
>
<template #append>
<QIcon
name="close"
size="xs"
v-if="hover && value && !readonly"
@click="onDateUpdate(null)"
></QIcon>
<QIcon name="event" class="cursor-pointer">
<QPopupProxy
v-model="isPopupOpen"
cover
transition-show="scale"
transition-hide="scale"
:no-parent-event="props.readonly"
>
<QDate
:today-btn="true"
:model-value="formatDate(value)"
@update:model-value="onDateUpdate"
/>
</QPopupProxy>
</QIcon>
v-if="
($attrs.clearable == undefined || $attrs.clearable) &&
hover &&
model &&
!$attrs.disable
"
@click="
model = null;
isPopupOpen = false;
"
/>
<QIcon name="event" class="cursor-pointer" />
</template>
<QMenu
transition-show="scale"
transition-hide="scale"
v-model="isPopupOpen"
anchor="bottom left"
self="top start"
:no-focus="true"
>
<QDate
v-model="popupDate"
:today-btn="true"
@update:model-value="
(date) => {
formattedDate = date;
isPopupOpen = false;
}
"
/>
</QMenu>
</QInput>
</div>
</template>
<style lang="scss">
.vn-input-date.q-field--standard.q-field--readonly .q-field__control:before {
border-bottom-style: solid;
}
.vn-input-date.q-field--outlined.q-field--readonly .q-field__control:before {
border-style: solid;
}
</style>

View File

@ -1,14 +1,11 @@
<script setup>
import { computed, ref } from 'vue';
import { watch, computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import isValidDate from 'filters/isValidDate';
import { date } from 'quasar';
const model = defineModel({ type: String });
const props = defineProps({
modelValue: {
type: String,
default: null,
},
readonly: {
timeOnly: {
type: Boolean,
default: false,
},
@ -17,43 +14,12 @@ const props = defineProps({
default: false,
},
});
const emit = defineEmits(['update:modelValue']);
const { t } = useI18n();
const requiredFieldRule = (val) => !!val || t('globals.fieldRequired');
const value = computed({
get() {
return props.modelValue;
},
set(value) {
const [hours, minutes] = value.split(':');
const date = new Date(props.modelValue);
date.setHours(Number.parseInt(hours) || 0, Number.parseInt(minutes) || 0, 0, 0);
emit('update:modelValue', value ? date.toISOString() : null);
},
});
const isPopupOpen = ref(false);
const onDateUpdate = (date) => {
internalValue.value = date;
};
const save = () => {
value.value = internalValue.value;
};
const formatTime = (dateString) => {
if (!dateString || !isValidDate(dateString)) {
return '';
}
const date = new Date(dateString || '');
return date.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
});
};
const internalValue = ref(formatTime(value));
const dateFormat = 'HH:mm';
const isPopupOpen = ref();
const hover = ref();
const styleAttrs = computed(() => {
return props.isOutlined
@ -64,65 +30,84 @@ const styleAttrs = computed(() => {
}
: {};
});
const formattedTime = computed({
get() {
if (!model.value || model.value?.length <= 5) return model.value;
return dateToTime(model.value);
},
set(value) {
if (value == model.value) return;
let time = value;
if (time) {
if (time?.length > 5) time = dateToTime(time);
if (!props.timeOnly) {
const hours = time.split(':');
const date = new Date();
date.setHours(hours[0], hours[1], 0);
time = date.toISOString();
}
}
model.value = time;
},
});
function dateToTime(newDate) {
return date.formatDate(new Date(newDate), dateFormat);
}
watch(
() => model.value,
(val) => (formattedTime.value = val),
{ immediate: true }
);
</script>
<template>
<QInput
class="vn-input-time"
readonly
:model-value="formatTime(value)"
v-bind="{ ...$attrs, ...styleAttrs }"
:class="{ required: $attrs.required }"
:rules="$attrs.required ? [requiredFieldRule] : null"
@click="isPopupOpen = true"
>
<template #append>
<QIcon name="Schedule" class="cursor-pointer">
<QPopupProxy
v-model="isPopupOpen"
cover
transition-show="scale"
transition-hide="scale"
:no-parent-event="props.readonly"
>
<QTime
:format24h="false"
:model-value="formatTime(value)"
@update:model-value="onDateUpdate"
>
<div class="row items-center justify-end q-gutter-sm">
<QBtn
:label="t('Cancel')"
color="primary"
flat
v-close-popup
/>
<QBtn
label="Ok"
color="primary"
flat
@click="save"
v-close-popup
/>
</div>
</QTime>
</QPopupProxy>
</QIcon>
</template>
</QInput>
<div @mouseover="hover = true" @mouseleave="hover = false">
<QInput
class="vn-input-time"
mask="##:##"
placeholder="--:--"
v-model="formattedTime"
v-bind="{ ...$attrs, ...styleAttrs }"
:class="{ required: $attrs.required }"
style="min-width: 100px"
:rules="$attrs.required ? [requiredFieldRule] : null"
>
<template #append>
<QIcon
name="close"
size="xs"
v-if="
($attrs.clearable == undefined || $attrs.clearable) &&
hover &&
model &&
!$attrs.disable
"
@click="
model = null;
isPopupOpen = false;
"
/>
<QIcon name="Schedule" class="cursor-pointer" />
</template>
<QMenu
transition-show="scale"
transition-hide="scale"
v-model="isPopupOpen"
anchor="bottom left"
self="top start"
:no-focus="true"
>
<QTime
:format24h="false"
v-model="formattedTime"
mask="HH:mm"
landscape
now-btn
/>
</QMenu>
</QInput>
</div>
</template>
<style lang="scss">
.vn-input-time.q-field--standard.q-field--readonly .q-field__control:before {
border-bottom-style: solid;
}
.vn-input-time.q-field--outlined.q-field--readonly .q-field__control:before {
border-style: solid;
}
</style>
<i18n>
es:
Cancel: Cancelar
</i18n>

View File

@ -61,6 +61,10 @@ const $props = defineProps({
type: Boolean,
default: false,
},
useLike: {
type: Boolean,
default: true,
},
});
const { t } = useI18n();
@ -114,11 +118,14 @@ async function fetchFilter(val) {
if (!$props.url || !dataRef.value) return;
const { fields, sortBy, limit } = $props;
let key = optionLabel.value;
let key = optionFilter.value ?? optionLabel.value;
if (new RegExp(/\d/g).test(val)) key = optionFilter.value ?? optionValue.value;
if (new RegExp(/\d/g).test(val)) key = optionValue.value;
const where = { ...{ [key]: { like: `%${val}%` } }, ...$props.where };
const defaultWhere = $props.useLike
? { [key]: { like: `%${val}%` } }
: { [key]: val };
const where = { ...defaultWhere, ...$props.where };
const fetchOptions = { where, order: sortBy, limit };
if (fields) fetchOptions.fields = fields;
return dataRef.value.fetch(fetchOptions);

View File

@ -46,7 +46,7 @@ let arrayData;
let store;
let entity;
const isLoading = ref(false);
const isSameDataKey = computed(() => $props.dataKey === route.meta.moduleName);
defineExpose({ getData });
onBeforeMount(async () => {
@ -58,10 +58,12 @@ onBeforeMount(async () => {
store = arrayData.store;
entity = computed(() => (Array.isArray(store.data) ? store.data[0] : store.data));
// It enables to load data only once if the module is the same as the dataKey
if ($props.dataKey !== route.meta.moduleName || !route.params.id) await getData();
if (!isSameDataKey.value || !route.params.id) await getData();
watch(
() => [$props.url, $props.filter],
async () => await getData()
async () => {
if (!isSameDataKey.value) await getData();
}
);
});
@ -77,14 +79,50 @@ async function getData() {
isLoading.value = false;
}
}
function getValueFromPath(path) {
if (!path) return;
const keys = path.toString().split('.');
let current = entity.value;
for (let i = 0; i < keys.length; i++) {
if (current[keys[i]] === undefined) return undefined;
else current = current[keys[i]];
}
return current;
}
const emit = defineEmits(['onFetch']);
const iconModule = computed(() => route.matched[1].meta.icon);
const toModule = computed(() =>
route.matched[1].path.split('/').length > 2
? route.matched[1].redirect
: route.matched[1].children[0].redirect
);
</script>
<template>
<div class="descriptor">
<template v-if="entity && !isLoading">
<div class="header bg-primary q-pa-sm justify-between">
<slot name="header-extra-action" />
<slot name="header-extra-action"
><QBtn
round
flat
dense
size="md"
:icon="iconModule"
color="white"
class="link"
:to="toModule"
>
<QTooltip>
{{ t('globals.goToModuleIndex') }}
</QTooltip>
</QBtn></slot
>
<QBtn
@click.stop="viewSummary(entity.id, $props.summary)"
round
@ -139,8 +177,8 @@ const emit = defineEmits(['onFetch']);
<QList dense>
<QItemLabel header class="ellipsis text-h5" :lines="1">
<div class="title">
<span v-if="$props.title" :title="$props.title">
{{ entity[title] ?? $props.title }}
<span v-if="$props.title" :title="getValueFromPath(title)">
{{ getValueFromPath(title) ?? $props.title }}
</span>
<slot v-else name="description" :entity="entity">
<span :title="entity.name">
@ -151,7 +189,7 @@ const emit = defineEmits(['onFetch']);
</QItemLabel>
<QItem dense>
<QItemLabel class="subtitle" caption>
#{{ $props.subtitle ?? entity.id }}
#{{ getValueFromPath(subtitle) ?? entity.id }}
</QItemLabel>
</QItem>
</QList>

View File

@ -10,7 +10,7 @@ const { t } = useI18n();
const $props = defineProps({
modelValue: {
type: Object,
default: () => {}
default: () => {},
},
dataKey: {
type: String,
@ -58,7 +58,14 @@ const $props = defineProps({
},
});
const emit = defineEmits(['refresh', 'clear', 'search', 'init', 'remove']);
const emit = defineEmits([
'update:modelValue',
'refresh',
'clear',
'search',
'init',
'remove',
]);
const arrayData = useArrayData($props.dataKey, {
exprBuilder: $props.exprBuilder,
@ -67,9 +74,9 @@ const arrayData = useArrayData($props.dataKey, {
});
const route = useRoute();
const store = arrayData.store;
const userParams = ref({})
const userParams = ref({});
onMounted(() => {
userParams.value = $props.modelValue ?? {}
userParams.value = $props.modelValue ?? {};
emit('init', { params: userParams.value });
});
@ -92,6 +99,11 @@ watch(
(val) => setUserParams(val)
);
watch(
() => $props.modelValue,
(val) => (userParams.value = val ?? {})
);
const isLoading = ref(false);
async function search(evt) {
if (evt && $props.disableSubmitEvent) return;
@ -102,6 +114,7 @@ async function search(evt) {
store.userParamsChanged = true;
store.filter.skip = 0;
store.skip = 0;
store.page = 1;
const { params: newParams } = await arrayData.addFilter({ params: userParams.value });
userParams.value = newParams;
@ -114,7 +127,8 @@ async function search(evt) {
async function reload() {
isLoading.value = true;
const params = Object.values(userParams.value).filter((param) => param);
store.skip = 0;
store.page = 1;
await arrayData.fetch({ append: false });
if (!$props.showAll && !params.length) store.data = [];
isLoading.value = false;
@ -126,6 +140,7 @@ async function clearFilters() {
store.userParamsChanged = true;
store.filter.skip = 0;
store.skip = 0;
store.page = 1;
// Filtrar los params no removibles
const removableFilters = Object.keys(userParams.value).filter((param) =>
$props.unremovableParams.includes(param)
@ -145,6 +160,7 @@ async function clearFilters() {
isLoading.value = false;
emit('clear');
emit('update:modelValue', userParams.value);
}
const tagsList = computed(() => {
@ -168,6 +184,7 @@ async function remove(key) {
userParams.value[key] = undefined;
search();
emit('remove', key);
emit('update:modelValue', userParams.value);
}
function formatValue(value) {

View File

@ -1,5 +1,5 @@
<script setup>
import { ref, computed, onMounted } from 'vue';
import { ref, computed } from 'vue';
import { useSession } from 'src/composables/useSession';
const $props = defineProps({
@ -28,21 +28,29 @@ const $props = defineProps({
const show = ref(false);
const token = useSession().getTokenMultimedia();
const timeStamp = ref(`timestamp=${Date.now()}`);
const url = computed(
() =>
`/api/${$props.storage}/${$props.collection}/${$props.size}/${$props.id}/download?access_token=${token}&${timeStamp.value}`
);
import noImage from '/public/no-user.png';
import { useRole } from 'src/composables/useRole';
const url = computed(() => {
const isEmployee = useRole().isEmployee();
return isEmployee
? `/api/${$props.storage}/${$props.collection}/${$props.size}/${$props.id}/download?access_token=${token}&${timeStamp.value}`
: noImage;
});
const reload = () => {
timeStamp.value = `timestamp=${Date.now()}`;
};
defineExpose({
reload,
});
onMounted(() => {});
</script>
<template>
<QImg :src="url" v-bind="$attrs" @click="show = !show" spinner-color="primary" />
<QImg
:class="{ zoomIn: $props.zoomSize }"
:src="url"
v-bind="$attrs"
@click="show = !show"
spinner-color="primary"
/>
<QDialog v-model="show" v-if="$props.zoomSize">
<QImg
:src="url"
@ -56,7 +64,9 @@ onMounted(() => {});
<style lang="scss" scoped>
.q-img {
cursor: zoom-in;
&.zoomIn {
cursor: zoom-in;
}
min-width: 50px;
}
.rounded {

View File

@ -104,6 +104,7 @@ async function search() {
([key, value]) => value && (props.staticParams || []).includes(key)
);
store.skip = 0;
store.page = 1;
if (props.makeFetch)
await arrayData.applyFilter({

View File

@ -1,4 +1,4 @@
import { onMounted, ref, computed } from 'vue';
import { onMounted, computed } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import axios from 'axios';
import { useArrayDataStore } from 'stores/useArrayDataStore';
@ -16,8 +16,6 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
const router = useRouter();
let canceller = null;
const page = ref(1);
onMounted(() => {
setOptions();
store.skip = 0;
@ -87,13 +85,13 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
}
Object.assign(filter, store.userFilter, exprFilter);
Object.assign(store.filter, { ...filter, skip: store.skip });
const params = {
filter: JSON.stringify(store.filter),
};
Object.assign(store.filter, filter);
const params = { filter: store.filter };
Object.assign(params, userParams);
params.filter.skip = store.skip;
params.filter = JSON.stringify(params.filter);
store.currentFilter = params;
store.isLoading = true;
const response = await axios.get(store.url, {
@ -154,8 +152,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
store.userParams = userParams;
store.skip = 0;
store.filter.skip = 0;
page.value = 1;
store.page = 1;
await fetch({ append: false });
return { filter, params };
}
@ -187,10 +184,11 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
async function loadMore() {
if (!store.hasMoreData) return;
store.skip = store.limit * page.value;
page.value += 1;
store.skip = store.limit * store.page;
store.page += 1;
await fetch({ append: true });
updateStateParams();
}
async function refresh() {

View File

@ -27,8 +27,12 @@ export function useRole() {
return false;
}
function isEmployee() {
return hasAny(['employee']);
}
return {
isEmployee,
fetch,
hasAny,
state,

View File

@ -100,13 +100,143 @@ globals:
modes: Modes
zones: Zones
zonesList: Zones
deliveryList: Delivery days
upcomingList: Upcoming deliveries
deliveryDays: Delivery days
upcomingDeliveries: Upcoming deliveries
role: Role
alias: Alias
aliasUsers: Users
subRoles: Subroles
inheritedRoles: Inherited Roles
customers: Customers
list: List
webPayments: Web Payments
extendedList: Extended list
notifications: Notifications
defaulter: Defaulter
customerCreate: New customer
fiscalData: Fiscal data
billingData: Billing data
consignees: Consignees
notes: Notes
credits: Credits
greuges: Greuges
balance: Balance
recoveries: Recoveries
webAccess: Web access
sms: Sms
creditManagement: Credit management
creditContracts: Credit contracts
creditOpinion: Credit opinion
others: Others
samples: Samples
consumption: Consumption
mandates: Mandates
contacts: Contacts
webPayment: Web payment
fileManagement: File management
unpaid: Unpaid
entries: Entries
buys: Buys
dms: File management
entryCreate: New entry
latestBuys: Latest buys
tickets: Tickets
ticketCreate: New Tickets
boxing: Boxing
sale: Sale
claims: Claims
claimCreate: New claim
lines: Lines
photos: Photos
development: Development
action: Action
invoiceOuts: Invoice out
negativeBases: Negative Bases
globalInvoicing: Global invoicing
invoiceOutCreate: Create invoice out
shelving: Shelving
shelvingList: Shelving List
shelvingCreate: New shelving
invoiceIns: Invoices In
invoiceInCreate: Create invoice in
vat: VAT
dueDay: Due day
intrastat: Intrastat
corrective: Corrective
order: Orders
orderList: List
orderCreate: New order
catalog: Catalog
volume: Volume
workers: Workers
workerCreate: New worker
department: Department
pda: PDA
pbx: Private Branch Exchange
calendar: Calendar
timeControl: Time control
locker: Locker
wagons: Wagons
wagonsList: Wagons List
wagonCreate: Create wagon
wagonEdit: Edit wagon
typesList: Types List
typeCreate: Create type
typeEdit: Edit type
wagonCounter: Trolley counter
roadmap: Roadmap
stops: Stops
routes: Routes
cmrsList: CMRs list
RouteList: List
routeCreate: New route
RouteRoadmap: Roadmaps
RouteRoadmapCreate: Create roadmap
autonomous: Autonomous
suppliers: Suppliers
supplier: Supplier
labeler: Labeler
supplierCreate: New supplier
accounts: Accounts
addresses: Addresses
agencyTerm: Agency agreement
travel: Travels
extraCommunity: Extra community
travelCreate: New travel
history: Log
thermographs: Thermograph
items: Items
diary: Diary
tags: Tags
create: Create
buyRequest: Buy requests
fixedPrice: Fixed prices
wasteBreakdown: Waste breakdown
itemCreate: New item
barcode: Barcodes
tax: Tax
botanical: Botanical
itemTypeCreate: New item type
family: Item Type
lastEntries: Last entries
itemType: Item type
monitors: Monitors
dashboard: Dashboard
users: Users
createTicket: Create ticket
ticketAdvance: Advance tickets
futureTickets: Future tickets
purchaseRequest: Purchase request
weeklyTickets: Weekly tickets
formation: Formation
locations: Locations
warehouses: Warehouses
roles: Roles
connections: Connections
acls: ACLs
mailForwarding: Mail forwarding
mailAlias: Mail alias
privileges: Privileges
created: Created
worker: Worker
now: Now
@ -114,6 +244,7 @@ globals:
new: New
comment: Comment
observations: Observations
goToModuleIndex: Go to module index
errors:
statusUnauthorized: Access denied
statusInternalServerError: An internal server error has ocurred
@ -148,40 +279,8 @@ verifyEmail:
verifyEmail: Email verification
dashboard:
pageTitles:
dashboard: Dashboard
customer:
pageTitles:
customers: Customers
list: List
webPayments: Web Payments
extendedList: Extended list
notifications: Notifications
defaulter: Defaulter
customerCreate: New customer
summary: Summary
basicData: Basic data
fiscalData: Fiscal data
billingData: Billing data
consignees: Consignees
notes: Notes
credits: Credits
greuges: Greuges
balance: Balance
recoveries: Recoveries
webAccess: Web access
log: Log
sms: Sms
creditManagement: Credit management
creditContracts: Credit contracts
creditOpinion: Credit opinion
others: Others
samples: Samples
consumption: Consumption
mandates: Mandates
contacts: Contacts
webPayment: Web payment
fileManagement: File management
unpaid: Unpaid
list:
phone: Phone
email: Email
@ -311,17 +410,6 @@ customer:
hasCoreVnl: VNL core received
hasSepaVnl: VNL B2B received
entry:
pageTitles:
entries: Entries
list: List
summary: Summary
basicData: Basic data
buys: Buys
notes: Notes
dms: File management
log: Log
entryCreate: New entry
latestBuys: Latest buys
list:
newEntry: New entry
landed: Landed
@ -444,10 +532,14 @@ ticket:
sms: Sms
notes: Notes
sale: Sale
volume: Volume
observation: Notes
ticketAdvance: Advance tickets
futureTickets: Future tickets
purchaseRequest: Purchase request
weeklyTickets: Weekly tickets
services: Service
tracking: Tracking
list:
nickname: Nickname
state: State
@ -522,18 +614,6 @@ ticket:
warehouse: Warehouse
agency: Agency
claim:
pageTitles:
claims: Claims
list: List
claimCreate: New claim
summary: Summary
basicData: Basic Data
lines: Lines
photos: Photos
development: Development
log: Audit logs
notes: Notes
action: Action
list:
customer: Customer
assignedTo: Assigned
@ -597,14 +677,6 @@ claim:
noData: 'There are no images/videos, click here or drag and drop the file'
dragDrop: Drag and drop it here
invoiceOut:
pageTitles:
invoiceOuts: Invoice out
list: List
negativeBases: Negative Bases
globalInvoicing: Global invoicing
invoiceOutCreate: Create invoice out
summary: Summary
basicData: Basic Data
list:
ref: Reference
issued: Issued
@ -672,13 +744,6 @@ invoiceOut:
errors:
downloadCsvFailed: CSV download failed
shelving:
pageTitles:
shelving: Shelving
shelvingList: Shelving List
shelvingCreate: New shelving
summary: Summary
basicData: Basic Data
log: Logs
list:
parking: Parking
priority: Priority
@ -705,17 +770,6 @@ parking:
info: You can search by parking code
label: Search parking...
invoiceIn:
pageTitles:
invoiceIns: Invoices In
list: List
invoiceInCreate: Create invoice in
summary: Summary
basicData: Basic data
vat: VAT
dueDay: Due day
intrastat: Intrastat
corrective: Corrective
log: Logs
list:
ref: Reference
supplier: Supplier
@ -766,15 +820,6 @@ invoiceIn:
stems: Stems
country: Country
order:
pageTitles:
order: Orders
orderList: List
orderCreate: New order
summary: Summary
basicData: Basic Data
catalog: Catalog
volume: Volume
lines: Lines
field:
salesPersonFk: Sales Person
clientFk: Client
@ -850,7 +895,6 @@ worker:
timeControl: Time control
locker: Locker
balance: Balance
formation: Formation
list:
name: Name
email: Email
@ -939,15 +983,6 @@ worker:
credit: Have
concept: Concept
wagon:
pageTitles:
wagons: Wagons
wagonsList: Wagons List
wagonCreate: Create wagon
wagonEdit: Edit wagon
typesList: Types List
typeCreate: Create type
typeEdit: Edit type
wagonCounter: Trolley counter
type:
name: Name
submit: Submit
@ -976,31 +1011,7 @@ wagon:
minHeightBetweenTrays: 'The minimum height between trays is '
maxWagonHeight: 'The maximum height of the wagon is '
uncompleteTrays: There are incomplete trays
route/roadmap:
pageTitles:
roadmap: Roadmap
summary: Summary
basicData: Basic Data
stops: Stops
roadmap:
pageTitles:
roadmap: Roadmap
summary: Summary
basicData: Basic Data
stops: Stops
route:
pageTitles:
routes: Routes
cmrsList: CMRs list
RouteList: List
routeCreate: New route
basicData: Basic Data
summary: Summary
RouteRoadmap: Roadmaps
RouteRoadmapCreate: Create roadmap
tickets: Tickets
log: Log
autonomous: Autonomous
cmr:
list:
results: results
@ -1028,22 +1039,6 @@ route:
volume: Volume
finished: Finished
supplier:
pageTitles:
suppliers: Suppliers
supplier: Supplier
list: List
supplierCreate: New supplier
summary: Summary
basicData: Basic data
fiscalData: Fiscal data
billingData: Billing data
log: Log
accounts: Accounts
contacts: Contacts
addresses: Addresses
consumption: Consumption
agencyTerm: Agency agreement
dms: File management
list:
payMethod: Pay method
payDeadline: Pay deadline
@ -1143,15 +1138,6 @@ supplier:
date: Date
reference: Reference
travel:
pageTitles:
travel: Travels
list: List
summary: Summary
extraCommunity: Extra community
travelCreate: New travel
basicData: Basic data
history: Log
thermographs: Thermograph
summary:
confirmed: Confirmed
entryId: Entry Id
@ -1198,24 +1184,6 @@ travel:
travelFileDescription: 'Travel id { travelId }'
file: File
item:
pageTitles:
items: Items
list: List
diary: Diary
tags: Tags
create: Create
buyRequest: Buy requests
fixedPrice: Fixed prices
wasteBreakdown: Waste breakdown
itemCreate: New item
barcode: Barcodes
tax: Tax
log: Log
botanical: Botanical
shelving: Shelving
itemTypeCreate: New item type
family: Item Type
lastEntries: Last entries
descriptor:
item: Item
buyer: Buyer
@ -1301,22 +1269,6 @@ item:
minSalesQuantity: 'Cantidad mínima de venta'
genus: 'Genus'
specie: 'Specie'
item/itemType:
pageTitles:
itemType: Item type
basicData: Basic data
summary: Summary
monitor:
pageTitles:
monitors: Monitors
list: List
zone:
pageTitles:
zones: Zones
zonesList: Zones
deliveryList: Delivery days
upcomingList: Upcoming deliveries
components:
topbar: {}
itemsFilterPanel:

View File

@ -100,14 +100,144 @@ globals:
modes: Modos
zones: Zonas
zonesList: Zonas
deliveryList: Días de entrega
upcomingList: Próximos repartos
deliveryDays: Días de entrega
upcomingDeliveries: Próximos repartos
role: Role
alias: Alias
aliasUsers: Usuarios
subRoles: Subroles
inheritedRoles: Roles heredados
customers: Clientes
customerCreate: Nuevo cliente
list: Listado
webPayments: Pagos Web
extendedList: Listado extendido
notifications: Notificaciones
defaulter: Morosos
createCustomer: Crear cliente
fiscalData: Datos fiscales
billingData: Forma de pago
consignees: Consignatarios
notes: Notas
credits: Créditos
greuges: Greuges
balance: Balance
recoveries: Recobros
webAccess: Acceso web
sms: Sms
creditManagement: Gestión de crédito
creditContracts: Contratos de crédito
creditOpinion: Opinión de crédito
others: Otros
samples: Plantillas
consumption: Consumo
mandates: Mandatos
contacts: Contactos
webPayment: Pago web
fileManagement: Gestión documental
unpaid: Impago
entries: Entradas
buys: Compras
dms: Gestión documental
entryCreate: Nueva entrada
latestBuys: Últimas compras
tickets: Tickets
ticketCreate: Nuevo ticket
boxing: Encajado
sale: Lineas del pedido
claims: Reclamaciones
claimCreate: Crear reclamación
lines: Líneas
development: Trazabilidad
photos: Fotos
action: Acción
invoiceOuts: Fact. emitidas
negativeBases: Bases Negativas
globalInvoicing: Facturación global
invoiceOutCreate: Crear fact. emitida
order: Cesta
orderList: Listado
orderCreate: Nueva orden
catalog: Catálogo
volume: Volumen
shelving: Carros
shelvingList: Listado de carros
shelvingCreate: Nuevo carro
invoiceIns: Fact. recibidas
invoiceInCreate: Crear fact. recibida
vat: IVA
labeler: Etiquetas
dueDay: Vencimiento
intrastat: Intrastat
corrective: Rectificativa
workers: Trabajadores
workerCreate: Nuevo trabajador
department: Departamentos
pda: PDA
pbx: Centralita
calendar: Calendario
timeControl: Control de horario
locker: Taquilla
wagons: Vagones
wagonsList: Listado vagones
wagonCreate: Crear tipo
wagonEdit: Editar tipo
typesList: Listado tipos
typeCreate: Crear tipo
typeEdit: Editar tipo
wagonCounter: Contador de carros
roadmap: Troncales
stops: Paradas
routes: Rutas
cmrsList: Listado de CMRs
RouteList: Listado
routeCreate: Nueva ruta
RouteRoadmap: Troncales
RouteRoadmapCreate: Crear troncal
autonomous: Autónomos
suppliers: Proveedores
supplier: Proveedor
supplierCreate: Nuevo proveedor
accounts: Cuentas
addresses: Direcciones
agencyTerm: Acuerdo agencia
travel: Envíos
create: Crear
extraCommunity: Extra comunitarios
travelCreate: Nuevo envío
history: Historial
thermographs: Termógrafos
items: Artículos
diary: Histórico
tags: Etiquetas
fixedPrice: Precios fijados
buyRequest: Peticiones de compra
wasteBreakdown: Deglose de mermas
itemCreate: Nuevo artículo
tax: 'IVA'
botanical: 'Botánico'
barcode: 'Código de barras'
itemTypeCreate: Nueva familia
family: Familia
lastEntries: Últimas entradas
itemType: Familia
monitors: Monitores
dashboard: Tablón
users: Usuarios
createTicket: Crear ticket
ticketAdvance: Adelantar tickets
futureTickets: Tickets a futuro
purchaseRequest: Petición de compra
weeklyTickets: Tickets programados
formation: Formación
locations: Ubicaciones
warehouses: Almacenes
roles: Roles
connections: Conexiones
acls: ACLs
mailForwarding: Reenvío de correo
mailAlias: Alias de correo
privileges: Privilegios
created: Fecha creación
worker: Trabajador
now: Ahora
@ -115,6 +245,7 @@ globals:
new: Nuevo
comment: Comentario
observations: Observaciones
goToModuleIndex: Ir al índice del módulo
errors:
statusUnauthorized: Acceso denegado
statusInternalServerError: Ha ocurrido un error interno del servidor
@ -147,41 +278,8 @@ verifyEmail:
verifyEmail: Verificación de correo
dashboard:
pageTitles:
dashboard: Tablón
customer:
pageTitles:
customers: Clientes
customerCreate: Nuevo cliente
list: Listado
webPayments: Pagos Web
extendedList: Listado extendido
notifications: Notificaciones
defaulter: Morosos
createCustomer: Crear cliente
summary: Resumen
basicData: Datos básicos
fiscalData: Datos fiscales
billingData: Forma de pago
consignees: Consignatarios
notes: Notas
credits: Créditos
greuges: Greuges
balance: Balance
recoveries: Recobros
webAccess: Acceso web
log: Historial
sms: Sms
creditManagement: Gestión de crédito
creditContracts: Contratos de crédito
creditOpinion: Opinión de crédito
others: Otros
samples: Plantillas
consumption: Consumo
mandates: Mandatos
contacts: Contactos
webPayment: Pago web
fileManagement: Gestión documental
unpaid: Impago
list:
phone: Teléfono
email: Email
@ -310,17 +408,6 @@ customer:
hasCoreVnl: Recibido core VNL
hasSepaVnl: Recibido B2B VNL
entry:
pageTitles:
entries: Entradas
list: Listado
summary: Resumen
basicData: Datos básicos
buys: Compras
notes: Notas
dms: Gestión documental
log: Historial
entryCreate: Nueva entrada
latestBuys: Últimas compras
list:
newEntry: Nueva entrada
landed: F. entrega
@ -443,10 +530,14 @@ ticket:
sms: Sms
notes: Notas
sale: Lineas del pedido
volume: Volumen
observation: Notas
ticketAdvance: Adelantar tickets
futureTickets: Tickets a futuro
purchaseRequest: Petición de compra
weeklyTickets: Tickets programados
services: Servicios
tracking: Estados
list:
nickname: Alias
state: Estado
@ -521,18 +612,6 @@ ticket:
warehouse: Almacén
agency: Agencia
claim:
pageTitles:
claims: Reclamaciones
list: Listado
claimCreate: Crear reclamación
summary: Resumen
basicData: Datos básicos
lines: Líneas
development: Trazabilidad
photos: Fotos
log: Historial
notes: Notas
action: Acción
list:
customer: Cliente
assignedTo: Asignada a
@ -596,14 +675,6 @@ claim:
noData: No hay imágenes/videos haz click aquí o arrastra y suelta el archivo
dragDrop: Arrástralo y sueltalo aquí
invoiceOut:
pageTitles:
invoiceOuts: Fact. emitidas
list: Listado
negativeBases: Bases Negativas
globalInvoicing: Facturación global
invoiceOutCreate: Crear fact. emitida
summary: Resumen
basicData: Datos básicos
list:
ref: Referencia
issued: Fecha emisión
@ -671,15 +742,6 @@ invoiceOut:
errors:
downloadCsvFailed: Error al descargar CSV
order:
pageTitles:
order: Cesta
orderList: Listado
orderCreate: Nueva orden
summary: Resumen
basicData: Datos básicos
catalog: Catálogo
volume: Volumen
lines: Líneas
field:
salesPersonFk: Comercial
clientFk: Cliente
@ -721,13 +783,6 @@ order:
price: Precio
amount: Monto
shelving:
pageTitles:
shelving: Carros
shelvingList: Listado de carros
shelvingCreate: Nuevo carro
summary: Resumen
basicData: Datos básicos
log: Historial
list:
parking: Parking
priority: Prioridad
@ -753,17 +808,6 @@ parking:
info: Puedes buscar por código de parking
label: Buscar parking...
invoiceIn:
pageTitles:
invoiceIns: Fact. recibidas
list: Listado
invoiceInCreate: Crear fact. recibida
summary: Resumen
basicData: Datos básicos
vat: IVA
dueDay: Vencimiento
intrastat: Intrastat
corrective: Rectificativa
log: Historial
list:
ref: Referencia
supplier: Proveedor
@ -846,7 +890,6 @@ worker:
timeControl: Control de horario
locker: Taquilla
balance: Balance
formation: Formación
list:
name: Nombre
email: Email
@ -926,15 +969,6 @@ worker:
credit: Haber
concept: Concepto
wagon:
pageTitles:
wagons: Vagones
wagonsList: Listado vagones
wagonCreate: Crear tipo
wagonEdit: Editar tipo
typesList: Listado tipos
typeCreate: Crear tipo
typeEdit: Editar tipo
wagonCounter: Contador de carros
type:
name: Nombre
submit: Guardar
@ -963,31 +997,7 @@ wagon:
minHeightBetweenTrays: 'La distancia mínima entre bandejas es '
maxWagonHeight: 'La altura máxima del vagón es '
uncompleteTrays: Hay bandejas sin completar
route/roadmap:
pageTitles:
roadmap: Troncales
summary: Resumen
basicData: Datos básicos
stops: Paradas
roadmap:
pageTitles:
roadmap: Troncales
summary: Resumen
basicData: Datos básicos
stops: Paradas
route:
pageTitles:
routes: Rutas
cmrsList: Listado de CMRs
RouteList: Listado
routeCreate: Nueva ruta
basicData: Datos básicos
summary: Resumen
RouteRoadmap: Troncales
RouteRoadmapCreate: Crear troncal
tickets: Tickets
log: Historial
autonomous: Autónomos
cmr:
list:
results: resultados
@ -1015,22 +1025,6 @@ route:
volume: Volumen
finished: Finalizada
supplier:
pageTitles:
suppliers: Proveedores
supplier: Proveedor
list: Listado
supplierCreate: Nuevo proveedor
summary: Resumen
basicData: Datos básicos
fiscalData: Datos fiscales
billingData: Forma de pago
log: Historial
accounts: Cuentas
contacts: Contactos
addresses: Direcciones
consumption: Consumo
agencyTerm: Acuerdo agencia
dms: Gestión documental
list:
payMethod: Método de pago
payDeadline: Plazo de pago
@ -1130,16 +1124,6 @@ supplier:
date: Fecha
reference: Referencia
travel:
pageTitles:
travel: Envíos
list: Listado
create: Crear
summary: Resumen
extraCommunity: Extra comunitarios
travelCreate: Nuevo envío
basicData: Datos básicos
history: Historial
thermographs: Termógrafos
summary:
confirmed: Confirmado
entryId: Id entrada
@ -1186,24 +1170,6 @@ travel:
travelFileDescription: 'Id envío { travelId }'
file: Fichero
item:
pageTitles:
items: Artículos
list: Listado
diary: Histórico
tags: Etiquetas
fixedPrice: Precios fijados
buyRequest: Peticiones de compra
wasteBreakdown: Deglose de mermas
itemCreate: Nuevo artículo
basicData: 'Datos básicos'
tax: 'IVA'
botanical: 'Botánico'
barcode: 'Código de barras'
log: Historial
shelving: Carros
itemTypeCreate: Nueva familia
family: Familia
lastEntries: Últimas entradas
descriptor:
item: Artículo
buyer: Comprador
@ -1289,27 +1255,6 @@ item:
achieved: 'Conseguido'
concept: 'Concepto'
state: 'Estado'
item/itemType:
pageTitles:
itemType: Familia
basicData: Datos básicos
summary: Resumen
zone:
pageTitles:
zones: Zonas
list: Zonas
deliveryList: Días de entrega
upcomingList: Próximos repartos
role:
pageTitles:
zones: Zonas
list: Zonas
deliveryList: Días de entrega
upcomingList: Próximos repartos
monitor:
pageTitles:
monitors: Monitores
list: Listado
components:
topbar: {}
itemsFilterPanel:

View File

@ -49,20 +49,6 @@ const hasAccount = ref(false);
:title="data.title"
:subtitle="data.subtitle"
>
<template #header-extra-action>
<QBtn
round
flat
size="md"
color="white"
icon="face"
:to="{ name: 'AccountList' }"
>
<QTooltip>
{{ t('Go to module index') }}
</QTooltip>
</QBtn>
</template>
<template #menu>
<AccountDescriptorMenu :has-account="hasAccount" />
</template>

View File

@ -56,8 +56,7 @@ onMounted(async () => {
:url="`Claims/${entityId}`"
:filter="filter"
module="Claim"
:title="data.title"
:subtitle="data.subtitle"
title="client.name"
@on-fetch="setData"
data-key="Claim"
>

View File

@ -224,7 +224,7 @@ async function changeState(value) {
<QCard class="vn-one">
<VnTitle
:url="`#/claim/${entityId}/basic-data`"
:text="t('claim.pageTitles.basicData')"
:text="t('globals.pageTitles.basicData')"
/>
<VnLv
:label="t('claim.summary.created')"

View File

@ -45,20 +45,6 @@ const setData = (entity) => (data.value = useCardDescription(entity.name, entity
:summary="$props.summary"
data-key="customerData"
>
<template #header-extra-action>
<QBtn
round
flat
size="sm"
icon="vn:Person"
color="white"
:to="{ name: 'CustomerList' }"
>
<QTooltip>
{{ t('Go to module index') }}
</QTooltip>
</QBtn>
</template>
<template #menu="{ entity }">
<CustomerDescriptorMenu :customer="entity" />
</template>

View File

@ -65,6 +65,8 @@ const columns = computed(() => [
url: 'Workers/activeWithInheritedRole',
fields: ['id', 'name'],
where: { role: 'salesPerson' },
optionFilter: 'firstName',
useLike: false,
},
create: true,
columnField: {

View File

@ -1,5 +1,5 @@
<script setup>
import { ref, computed, watch } from 'vue';
import { ref, computed, watch, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
@ -10,6 +10,7 @@ import useCardDescription from 'src/composables/useCardDescription';
import { useState } from 'src/composables/useState';
import { toDate } from 'src/filters';
import { usePrintService } from 'composables/usePrintService';
import { getUrl } from 'src/composables/getUrl';
const $props = defineProps({
id: {
@ -17,10 +18,6 @@ const $props = defineProps({
required: false,
default: null,
},
summary: {
type: Object,
default: null,
},
});
const route = useRoute();
@ -28,6 +25,7 @@ const { t } = useI18n();
const { openReport } = usePrintService();
const state = useState();
const entryDescriptorRef = ref(null);
const url = ref();
const entryFilter = {
include: [
@ -69,6 +67,9 @@ const entryFilter = {
const entityId = computed(() => {
return $props.id || route.params.id;
});
onMounted(async () => {
url.value = await getUrl('');
});
const data = ref(useCardDescription());
const setData = (entity) =>
@ -114,6 +115,7 @@ watch;
:subtitle="data.subtitle"
@on-fetch="setData"
data-key="entry"
:summary="$attrs"
>
<template #menu="{ entity }">
<QItem v-ripple clickable @click="showEntryReport(entity)">

View File

@ -1,5 +1,6 @@
<script setup>
import EntryDescriptor from './EntryDescriptor.vue';
import EntrySummary from './EntrySummary.vue';
const $props = defineProps({
id: {
@ -11,6 +12,6 @@ const $props = defineProps({
<template>
<QPopupProxy>
<EntryDescriptor v-if="$props.id" :id="$props.id" />
<EntryDescriptor v-if="$props.id" :id="$props.id" :summary="EntrySummary" />
</QPopupProxy>
</template>

View File

@ -0,0 +1,120 @@
<script setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { QBtn } from 'quasar';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import { usePrintService } from 'composables/usePrintService';
const { openReport } = usePrintService();
const route = useRoute();
const { t } = useI18n();
const $props = defineProps({
id: {
type: String,
required: false,
default: null,
},
});
const entityId = computed(() => $props.id || route.params.id);
const entriesTableColumns = computed(() => [
{
align: 'left',
name: 'itemFk',
field: 'itemFk',
label: t('globals.id'),
},
{
align: 'left',
name: 'item',
label: t('entry.summary.item'),
field: (row) => row.item.name,
},
{
align: 'left',
name: 'packagingFk',
label: t('entry.summary.package'),
field: 'packagingFk',
},
{
align: 'left',
name: 'stickers',
label: t('entry.summary.stickers'),
field: 'stickers',
},
{
align: 'left',
name: 'packing',
label: t('entry.summary.packing'),
field: 'packing',
},
{
align: 'left',
name: 'grouping',
label: t('entry.summary.grouping'),
field: 'grouping',
},
]);
</script>
<template>
<QDialog ref="dialogRef">
<QCard style="min-width: 800px">
<QCardSection class="row items-center q-pb-none">
<QAvatar
:icon="icon"
color="primary"
text-color="white"
size="xl"
v-if="icon"
/>
<span class="text-h6 text-grey">{{ title }}</span>
<QSpace />
<QBtn icon="close" :disable="isLoading" flat round dense v-close-popup />
</QCardSection>
<QCardActions align="right">
<QBtn
:label="t('Print buys')"
color="primary"
icon="print"
:loading="isLoading"
@click="openReport(`Entries/${entityId}/buy-label`)"
unelevated
autofocus
/>
</QCardActions>
<QCardSection class="row items-center">
<VnPaginate
ref="entryBuysPaginateRef"
:limit="0"
data-key="EntryBuys"
:url="`Entries/${entityId}/getBuys`"
auto-load
>
<template #body="{ rows }">
<QTable
:rows="rows"
:columns="entriesTableColumns"
row-key="id"
flat
dense
class="q-ml-lg"
:grid="$q.screen.lt.md"
:no-data-label="t('globals.noResults')"
>
<template #body="props">
<QTr>
<QTd v-for="col in props.cols" :key="col.name">
{{ col.value }}
</QTd>
</QTr>
</template>
</QTable>
</template>
</VnPaginate>
</QCardSection>
</QCard>
</QDialog>
</template>

View File

@ -0,0 +1,106 @@
<script setup>
import { computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useStateStore } from 'stores/useStateStore';
import { toDate } from 'src/filters/index';
import { useQuasar } from 'quasar';
import EntryBuysTableDialog from './EntryBuysTableDialog.vue';
import VnTable from 'components/VnTable/VnTable.vue';
const stateStore = useStateStore();
const { t } = useI18n();
const quasar = useQuasar();
onMounted(async () => {
stateStore.rightDrawer = true;
});
const columns = computed(() => [
{
align: 'left',
name: 'id',
label: t('customer.extendedList.tableVisibleColumns.id'),
chip: {
condition: () => true,
},
isId: true,
isTitle: false,
},
{
align: 'left',
label: t('shipped'),
name: 'shipped',
isTitle: false,
create: true,
cardVisible: true,
format: ({ shipped }) => toDate(shipped),
},
{
align: 'left',
label: t('landed'),
name: 'landed',
isTitle: false,
create: true,
cardVisible: false,
format: ({ landed }) => toDate(landed),
},
{
align: 'left',
label: t('globals.wareHouseIn'),
name: 'warehouseInName',
isTitle: false,
cardVisible: true,
create: false,
},
{
align: 'right',
name: 'tableActions',
computed,
actions: [
{
title: t('printBuys'),
icon: 'print',
action: (row) => printBuys(row.id),
},
],
},
]);
const printBuys = (rowId) => {
quasar.dialog({
component: EntryBuysTableDialog,
componentProps: {
id: rowId,
},
});
};
</script>
<template>
<VnSearchbar
data-key="EntryList"
url="Entries/filter"
:label="t('Search entries')"
:info="t('You can search by entry reference')"
/>
<QPage class="column items-center q-pa-md">
<div class="vn-card-list">
<VnTable
ref="myEntriesRef"
data-key="myEntriesList"
url="Entries/filter"
:order="['landed DESC', 'id DESC']"
:columns="columns"
default-mode="card"
auto-load
>
</VnTable>
</div>
</QPage>
</template>
<i18n>
es:
Search entries: Buscar entradas
You can search by entry reference: Puedes buscar por referencia de la entrada
</i18n>

View File

@ -8,3 +8,4 @@ entryFilter:
reference: Reference
landed: Landed
shipped: Shipped
printBuys: Print buys

View File

@ -1,5 +1,6 @@
Search entries: Buscar entradas
You can search by entry reference: Puedes buscar por referencia de la entrada
entryList:
list:
inventoryEntry: Es inventario
@ -11,3 +12,4 @@ entryFilter:
landed: F. llegada
shipped: F. salida
Print buys: Imprimir etiquetas

View File

@ -1,5 +1,6 @@
<script setup>
import InvoiceInDescriptor from "pages/InvoiceIn/Card/InvoiceInDescriptor.vue";
import InvoiceInDescriptor from 'pages/InvoiceIn/Card/InvoiceInDescriptor.vue';
import InvoiceInSummary from './InvoiceInSummary.vue';
const $props = defineProps({
id: {
@ -10,6 +11,10 @@ const $props = defineProps({
</script>
<template>
<QPopupProxy>
<InvoiceInDescriptor v-if="$props.id" :id="$props.id" />
<InvoiceInDescriptor
v-if="$props.id"
:id="$props.id"
:summary="InvoiceInSummary"
/>
</QPopupProxy>
</template>

View File

@ -209,7 +209,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
<QCardSection class="q-pa-none">
<VnTitle
:url="getLink('basic-data')"
:text="t('invoiceIn.pageTitles.basicData')"
:text="t('globals.pageTitles.basicData')"
/>
</QCardSection>
<VnLv
@ -240,7 +240,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
<QCardSection class="q-pa-none">
<VnTitle
:url="getLink('basic-data')"
:text="t('invoiceIn.pageTitles.basicData')"
:text="t('globals.pageTitles.basicData')"
/>
</QCardSection>
<VnLv
@ -265,7 +265,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
<QCardSection class="q-pa-none">
<VnTitle
:url="getLink('basic-data')"
:text="t('invoiceIn.pageTitles.basicData')"
:text="t('globals.pageTitles.basicData')"
/>
</QCardSection>
<VnLv
@ -289,7 +289,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
<QCardSection class="q-pa-none">
<VnTitle
:url="getLink('basic-data')"
:text="t('invoiceIn.pageTitles.basicData')"
:text="t('globals.pageTitles.basicData')"
/>
</QCardSection>
<QCardSection class="q-pa-none">

View File

@ -200,16 +200,10 @@ onUnmounted(() => (stateStore.rightDrawer = false));
<VnInputDate
:label="t('lastEntries.since')"
dense
emit-date-format
v-model="from"
class="q-mr-lg"
/>
<VnInputDate
:label="t('lastEntries.to')"
dense
emit-date-format
v-model="to"
/>
<VnInputDate :label="t('lastEntries.to')" dense v-model="to" />
</div>
<QSpace />
<div id="st-actions"></div>

View File

@ -192,7 +192,6 @@ const decrement = (paramsObj, key) => {
v-model="params.from"
@update:model-value="searchFn()"
is-outlined
emit-date-format
/>
</QItemSection>
</QItem>
@ -203,7 +202,6 @@ const decrement = (paramsObj, key) => {
v-model="params.to"
@update:model-value="searchFn()"
is-outlined
emit-date-format
/>
</QItemSection>
</QItem>

View File

@ -51,20 +51,6 @@ const setData = (entity) => (data.value = useCardDescription(entity.code, entity
@on-fetch="setData"
data-key="entry"
>
<template #header-extra-action>
<QBtn
round
flat
size="sm"
icon="vn:item"
color="white"
:to="{ name: 'ItemTypeList' }"
>
<QTooltip>
{{ t('Go to module index') }}
</QTooltip>
</QBtn>
</template>
<template #body="{ entity }">
<VnLv :label="t('shared.code')" :value="entity.code" />
<VnLv :label="t('shared.name')" :value="entity.name" />

View File

@ -169,7 +169,6 @@ const columns = computed(() => [
<VnInputDate
:label="t('salesClientsTable.from')"
dense
emit-date-format
v-model="from"
class="q-mr-lg"
style="width: 150px"
@ -177,7 +176,6 @@ const columns = computed(() => [
<VnInputDate
:label="t('salesClientsTable.to')"
dense
emit-date-format
v-model="to"
style="width: 150px"
/>

View File

@ -2,7 +2,6 @@
import VnCard from 'components/common/VnCard.vue';
import OrderDescriptor from 'pages/Order/Card/OrderDescriptor.vue';
import OrderFilter from './OrderFilter.vue';
import OrderSearchbar from './OrderSearchbar.vue';
</script>
<template>
<VnCard
@ -11,9 +10,7 @@ import OrderSearchbar from './OrderSearchbar.vue';
:descriptor="OrderDescriptor"
:filter-panel="OrderFilter"
search-data-key="OrderList"
>
<template #searchbar>
<OrderSearchbar />
</template>
</VnCard>
searchbar-label="Search order"
searchbar-info="ypu can search by order id or name"
/>
</template>

View File

@ -37,6 +37,10 @@ const selectedOrder = ref(null);
const selectedOrderField = ref(null);
const moreFields = ref([]);
const moreFieldsOrder = ref([]);
const selectedTag = ref(null);
const tagValues = ref([{}]);
const tagOptions = ref([]);
const createValue = (val, done) => {
if (val.length > 2) {
if (!tagOptions.value.includes(val)) {
@ -95,10 +99,6 @@ function exprBuilder(param, value) {
}
}
const selectedTag = ref(null);
const tagValues = ref([{}]);
const tagOptions = ref([]);
const applyTagFilter = (params, search) => {
if (!tagValues.value?.length) {
params.tagGroups = null;
@ -139,34 +139,22 @@ const onOrderChange = (value, params) => {
};
const onOrderFieldChange = (value, params) => {
const tagObj = JSON.parse(params.orderBy); // esto donde va
const fields = {
Relevancy: (value) => value + ' DESC, name',
ColorAndPrice: 'showOrder, price',
Name: 'name',
Price: 'price',
};
let tagField = fields[value];
if (!tagField) return;
if (typeof tagField === 'function') tagField = tagField(value);
tagObj.field = tagField;
params.orderBy = JSON.stringify(tagObj);
const tagObj = JSON.parse(params.orderBy);
switch (value) {
case 'Relevancy':
tagObj.field = value + ' DESC, name';
tagObj.name = value + ' DESC, name';
params.orderBy = JSON.stringify(tagObj);
break;
case 'ColorAndPrice':
tagObj.field = 'showOrder, price';
tagObj.name = 'showOrder, price';
params.orderBy = JSON.stringify(tagObj);
break;
case 'Name':
tagObj.field = 'name';
tagObj.name = 'name';
params.orderBy = JSON.stringify(tagObj);
break;
case 'Price':
tagObj.field = 'price';
tagObj.name = 'price';
params.orderBy = JSON.stringify(tagObj);
break;
}
@ -308,6 +296,7 @@ const useLang = (values) => {
v-model="selectedOrder"
:options="moreFields"
option-label="label"
option-value="way"
dense
outlined
rounded

View File

@ -27,7 +27,7 @@ const dialog = ref(null);
<div class="container order-catalog-item overflow-hidden">
<QCard class="card shadow-6">
<div class="img-wrapper">
<VnImg :id="item.id" class="image" />
<VnImg :id="item.id" zoom-size="lg" class="image" />
<div v-if="item.hex" class="item-color-container">
<div
class="item-color"

View File

@ -7,6 +7,8 @@ import VnLv from 'components/ui/VnLv.vue';
import CardSummary from 'components/ui/CardSummary.vue';
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
import FetchedTags from 'components/ui/FetchedTags.vue';
import VnTitle from 'src/components/common/VnTitle.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
const { t } = useI18n();
const route = useRoute();
@ -62,6 +64,10 @@ const detailsColumns = ref([
</template>
<template #body="{ entity }">
<QCard class="vn-one">
<VnTitle
:url="`#/order/${entity.id}/basic-data`"
:text="t('globals.pageTitles.basicData')"
/>
<VnLv label="ID" :value="entity.id" />
<VnLv :label="t('order.summary.nickname')" dash>
<template #value>
@ -81,6 +87,10 @@ const detailsColumns = ref([
/>
</QCard>
<QCard class="vn-one">
<VnTitle
:url="`#/order/${entity.id}/basic-data`"
:text="t('globals.pageTitles.basicData')"
/>
<VnLv
:label="t('order.summary.created')"
:value="toDateHourMinSec(entity?.created)"
@ -116,14 +126,13 @@ const detailsColumns = ref([
/>
</QCard>
<QCard class="vn-one">
<p class="header">
{{ t('order.summary.notes') }}
</p>
<VnTitle :text="t('globals.pageTitles.notes')" />
<p v-if="entity?.note" class="no-margin">
{{ entity?.note }}
</p>
</QCard>
<QCard class="vn-one">
<VnTitle :text="t('order.summary.total')" />
<VnLv>
<template #label>
<span class="text-h6">{{ t('order.summary.subtotal') }}</span>
@ -152,9 +161,7 @@ const detailsColumns = ref([
</VnLv>
</QCard>
<QCard>
<p class="header">
{{ t('order.summary.details') }}
</p>
<VnTitle :text="t('order.summary.details')" />
<QTable :columns="detailsColumns" :rows="entity?.rows" flat>
<template #header="props">
<QTr :props="props">
@ -168,7 +175,10 @@ const detailsColumns = ref([
<template #body="props">
<QTr :props="props">
<QTd key="item" :props="props" class="item">
{{ props.row.item?.id }}
<span class="link">
{{ props.row.item?.id }}
<ItemDescriptorProxy :id="props.row.item?.id" />
</span>
</QTd>
<QTd key="description" :props="props" class="description">
<div class="name">

View File

@ -70,6 +70,7 @@ function extractValueTags(items) {
:user-params="catalogParams"
auto-load
@on-fetch="extractTags"
:update-router="false"
>
<template #body="{ rows }">
<div class="catalog-list">

View File

@ -9,7 +9,7 @@ import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue';
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
import VnPaginate from 'components/ui/VnPaginate.vue';
import VnLv from 'components/ui/VnLv.vue';
import OrderSearchbar from 'pages/Order/Card/OrderSearchbar.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import OrderFilter from 'pages/Order/Card/OrderFilter.vue';
import OrderSummary from 'pages/Order/Card/OrderSummary.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
@ -28,7 +28,11 @@ function navigate(id) {
}
</script>
<template>
<OrderSearchbar />
<VnSearchbar
data-key="OrderList"
:label="t('Search order')"
:info="t('You can search orders by reference')"
/>
<RightMenu>
<template #right-panel>
<OrderFilter data-key="OrderList" />

View File

@ -2,7 +2,6 @@
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { useArrayData } from 'src/composables/useArrayData';
import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'components/ui/VnLv.vue';
@ -17,8 +16,7 @@ const props = defineProps({
const { t } = useI18n();
const route = useRoute();
const entityId = computed(() => props.id || route.params.id);
const { store } = useArrayData('Parking');
const parking = computed(() => store.data);
const filter = {
fields: ['id', 'sectorFk', 'code', 'pickingOrder', 'row', 'column'],
include: [{ relation: 'sector', scope: { fields: ['id', 'description'] } }],
@ -29,15 +27,13 @@ const filter = {
module="Parking"
data-key="Parking"
:url="`Parkings/${entityId}`"
:title="parking?.code"
:subtitle="parking?.id"
title="code"
:filter="filter"
@on-fetch="(data) => (parking = data)"
>
<template #body>
<VnLv :label="t('globals.code')" :value="parking.code" />
<VnLv :label="t('parking.pickingOrder')" :value="parking.pickingOrder" />
<VnLv :label="t('parking.sector')" :value="parking.sector?.description" />
<template #body="{ entity }">
<VnLv :label="t('globals.code')" :value="entity.code" />
<VnLv :label="t('parking.pickingOrder')" :value="entity.pickingOrder" />
<VnLv :label="t('parking.sector')" :value="entity.sector?.description" />
</template>
</CardDescriptor>
</template>

View File

@ -1,10 +1,9 @@
<script setup>
import { ref, computed } from 'vue';
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'components/ui/VnLv.vue';
import { useArrayData } from 'src/composables/useArrayData';
const $props = defineProps({
id: {
@ -15,9 +14,7 @@ const $props = defineProps({
const router = useRoute();
const { t } = useI18n();
const entityId = computed(() => $props.id || router.params.id);
const { store } = useArrayData('Parking');
const parking = ref(store.data);
const filter = {
fields: ['id', 'sectorFk', 'code', 'pickingOrder', 'row', 'column'],
include: [{ relation: 'sector', scope: { fields: ['id', 'description'] } }],
@ -26,14 +23,9 @@ const filter = {
<template>
<div class="q-pa-md">
<CardSummary
:url="`Parkings/${entityId}`"
:filter="filter"
@on-fetch="(data) => (parking = data)"
data-key="Parking"
>
<template #header>{{ parking.code }}</template>
<template #body>
<CardSummary :url="`Parkings/${entityId}`" data-key="Parking" :filter="filter">
<template #header="{ entity }">{{ entity.code }}</template>
<template #body="{ entity }">
<QCard class="vn-one">
<QCardSection class="q-pa-none">
<a
@ -44,17 +36,17 @@ const filter = {
<QIcon name="open_in_new" />
</a>
</QCardSection>
<VnLv :label="t('globals.code')" :value="parking.code" />
<VnLv :label="t('globals.code')" :value="entity.code" />
<VnLv
:label="t('parking.pickingOrder')"
:value="parking.pickingOrder"
:value="entity.pickingOrder"
/>
<VnLv
:label="t('parking.sector')"
:value="parking.sector?.description"
:value="entity.sector?.description"
/>
<VnLv :label="t('parking.row')" :value="parking.row" />
<VnLv :label="t('parking.column')" :value="parking.column" />
<VnLv :label="t('parking.row')" :value="entity.row" />
<VnLv :label="t('parking.column')" :value="entity.column" />
</QCard>
</template>
</CardSummary>

View File

@ -74,8 +74,9 @@ const setData = (entity) => (data.value = useCardDescription(entity.code, entity
:filter="filter"
:title="data.title"
:subtitle="data.subtitle"
data-key="Routes"
data-key="routeData"
@on-fetch="setData"
:summary="$attrs"
>
<template #body="{ entity }">
<VnLv :label="t('Date')" :value="toDate(entity?.created)" />

View File

@ -1,5 +1,6 @@
<script setup>
import RouteDescriptor from 'pages/Route/Card/RouteDescriptor.vue';
import RouteSummary from './RouteSummary.vue';
const $props = defineProps({
id: {
@ -10,6 +11,6 @@ const $props = defineProps({
</script>
<template>
<QPopupProxy>
<RouteDescriptor v-if="$props.id" :id="$props.id" />
<RouteDescriptor v-if="$props.id" :id="$props.id" :summary="RouteSummary" />
</QPopupProxy>
</template>

View File

@ -51,6 +51,7 @@ const setData = (entity) => (data.value = useCardDescription(entity.code, entity
:subtitle="data.subtitle"
data-key="Shelvings"
@on-fetch="setData"
:summary="$attrs"
>
<template #body="{ entity }">
<VnLv :label="t('shelving.summary.code')" :value="entity.code" />

View File

@ -1,5 +1,6 @@
<script setup>
import ShelvingDescriptor from "pages/Shelving/Card/ShelvingDescriptor.vue";
import ShelvingDescriptor from 'pages/Shelving/Card/ShelvingDescriptor.vue';
import ShelvingSummary from './ShelvingSummary.vue';
const $props = defineProps({
id: {
@ -10,6 +11,6 @@ const $props = defineProps({
</script>
<template>
<QPopupProxy>
<ShelvingDescriptor v-if="$props.id" :id="$props.id" />
<ShelvingDescriptor v-if="$props.id" :id="$props.id" :summary="ShelvingSummary" />
</QPopupProxy>
</template>

View File

@ -51,7 +51,7 @@ const filter = {
class="header header-link"
:to="{ name: 'ShelvingBasicData', params: { id: entityId } }"
>
{{ t('shelving.pageTitles.basicData') }}
{{ t('globals.pageTitles.basicData') }}
<QIcon name="open_in_new" />
</RouterLink>
<VnLv :label="t('shelving.summary.code')" :value="entity.code" />

View File

@ -112,22 +112,6 @@ const getEntryQueryParams = (supplier) => {
data-key="supplier"
:summary="$props.summary"
>
<template #header-extra-action>
<QBtn
round
flat
dense
size="md"
icon="vn:supplier"
color="white"
class="link"
:to="{ name: 'SupplierList' }"
>
<QTooltip>
{{ t('Go to module index') }}
</QTooltip>
</QBtn>
</template>
<template #body="{ entity }">
<VnLv :label="t('supplier.summary.taxNumber')" :value="entity.nif" />
<VnLv label="Alias" :value="entity.nickname" />

View File

@ -0,0 +1,49 @@
<script setup>
import { reactive, ref, onMounted, nextTick } from 'vue';
import { useI18n } from 'vue-i18n';
import VnInput from 'src/components/common/VnInput.vue';
import VnRow from 'components/ui/VnRow.vue';
import FormModelPopup from 'components/FormModelPopup.vue';
const { t } = useI18n();
const emit = defineEmits(['onDataSaved']);
const nameInputRef = ref(null);
const serviceFormData = reactive({});
const onDataSaved = (formData, requestResponse) => {
emit('onDataSaved', formData, requestResponse);
};
onMounted(async () => {
await nextTick();
nameInputRef.value.focus();
});
</script>
<template>
<FormModelPopup
url-create="TicketServiceTypes"
model="TicketServiceType"
:title="t('New service type')"
:form-initial-data="serviceFormData"
@on-data-saved="onDataSaved"
>
<template #form-inputs="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<VnInput
ref="nameInputRef"
:label="t('service.description')"
v-model="data.name"
:required="true"
/>
</VnRow>
</template>
</FormModelPopup>
</template>
<i18n>
es:
New service type: Nuevo tipo de servicio
</i18n>

View File

@ -0,0 +1,86 @@
<script setup>
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import FormModelPopup from 'components/FormModelPopup.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue';
import { useState } from 'src/composables/useState';
const emit = defineEmits(['onRequestCreated']);
const route = useRoute();
const { t } = useI18n();
const state = useState();
const user = state.getUser();
const stateFetchDataRef = ref(null);
const statesOptions = ref([]);
const workersOptions = ref([]);
const onStateFkChange = (formData) => (formData.userFk = user.value.id);
</script>
<template>
<FetchData
ref="stateFetchDataRef"
url="States"
auto-load
@on-fetch="(data) => (statesOptions = data)"
/>
<FetchData
url="Workers/search"
:filter="{ fields: ['id', 'name'], order: 'name ASC' }"
auto-load
@on-fetch="(data) => (workersOptions = data)"
/>
<FormModelPopup
:title="t('Create tracking')"
url-create="Tickets/state"
model="CreateTicketTracking"
:form-initial-data="{ ticketFk: route.params.id }"
@on-data-saved="() => emit('onRequestCreated')"
>
<template #form-inputs="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<VnSelect
v-model="data.stateFk"
:label="t('tracking.state')"
:options="statesOptions"
@update:model-value="onStateFkChange(data)"
hide-selected
option-label="name"
option-value="id"
/>
<VnSelect
:label="t('tracking.worker')"
v-model="data.userFk"
:options="workersOptions"
hide-selected
option-label="name"
option-value="id"
>
<template #option="{ opt, itemProps }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel>
{{ opt.name }}
</QItemLabel>
<QItemLabel caption>
{{ opt.nickname }}, {{ opt.code }}
</QItemLabel>
</QItemSection>
</QItem>
</template></VnSelect
>
</VnRow>
</template>
</FormModelPopup>
</template>
<i18n>
es:
Create tracking: Crear estado
</i18n>

View File

@ -70,8 +70,6 @@ const filter = {
};
const data = ref(useCardDescription());
const setData = (entity) =>
(data.value = useCardDescription(entity.client.name, entity.id));
</script>
<template>
@ -82,7 +80,6 @@ const setData = (entity) =>
:title="data.title"
:subtitle="data.subtitle"
data-key="ticketData"
@on-fetch="setData"
>
<template #menu="{ entity }">
<TicketDescriptorMenu :ticket="entity" />
@ -92,7 +89,7 @@ const setData = (entity) =>
<template #value>
<span class="link">
{{ entity.clientFk }}
<CustomerDescriptorProxy :id="entity.client.id" />
<CustomerDescriptorProxy :id="entity.client?.id" />
</span>
</template>
</VnLv>
@ -109,8 +106,8 @@ const setData = (entity) =>
<VnLv :label="t('ticket.summary.salesPerson')">
<template #value>
<VnUserLink
:name="entity.client.salesPersonUser?.name"
:worker-id="entity.client.salesPersonFk"
:name="entity.client?.salesPersonUser?.name"
:worker-id="entity.client?.salesPersonFk"
/>
</template>
</VnLv>
@ -153,4 +150,5 @@ const setData = (entity) =>
<i18n>
es:
This ticket is deleted: Este ticket está eliminado
Go to module index: Ir al índice del modulo
</i18n>

View File

@ -1,5 +1,6 @@
<script setup>
import TicketDescriptor from './TicketDescriptor.vue';
import TicketSummary from './TicketSummary.vue';
const $props = defineProps({
id: {
@ -10,6 +11,6 @@ const $props = defineProps({
</script>
<template>
<QPopupProxy>
<TicketDescriptor v-if="$props.id" :id="$props.id" />
<TicketDescriptor v-if="$props.id" :id="$props.id" :summary="TicketSummary" />
</QPopupProxy>
</template>

View File

@ -0,0 +1,106 @@
<script setup>
import { ref, watch, computed, reactive } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CrudModel from 'components/CrudModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue';
import { useArrayData } from 'src/composables/useArrayData';
const route = useRoute();
const { t } = useI18n();
const ticketNotesCrudRef = ref(null);
const observationTypes = ref([]);
const arrayData = useArrayData('TicketNotes');
const { store } = arrayData;
const crudModelFilter = reactive({
where: { ticketFk: route.params.id },
fields: ['id', 'ticketFk', 'observationTypeFk', 'description'],
});
const crudModelRequiredData = computed(() => ({ ticketFk: route.params.id }));
watch(
() => route.params.id,
async () => {
crudModelFilter.where.ticketFk = route.params.id;
store.filter = crudModelFilter;
await ticketNotesCrudRef.value.reload();
}
);
</script>
<template>
<FetchData
@on-fetch="(data) => (observationTypes = data)"
auto-load
url="ObservationTypes"
/>
<div class="flex justify-center">
<CrudModel
ref="ticketNotesCrudRef"
data-key="TicketNotes"
url="TicketObservations"
model="TicketNotes"
:filter="crudModelFilter"
:data-required="crudModelRequiredData"
:default-remove="false"
auto-load
style="max-width: 800px"
>
<template #body="{ rows }">
<QCard class="q-px-lg q-py-md">
<div
v-for="(row, index) in rows"
:key="index"
class="q-mb-md row items-center q-gutter-x-md"
>
<VnSelect
:label="t('ticketNotes.observationType')"
:options="observationTypes"
hide-selected
option-label="description"
option-value="id"
v-model="row.observationTypeFk"
:disable="!!row.id"
/>
<VnInput
:label="t('ticketNotes.description')"
v-model="row.description"
class="col"
/>
<QIcon
name="delete"
size="sm"
class="cursor-pointer"
color="primary"
@click="ticketNotesCrudRef.remove([row])"
>
<QTooltip>
{{ t('ticketNotes.removeNote') }}
</QTooltip>
</QIcon>
</div>
<VnRow v-if="observationTypes.length > rows.length">
<QIcon
name="add_circle"
class="fill-icon-on-hover q-ml-md"
size="sm"
color="primary"
@click="ticketNotesCrudRef.insert()"
>
<QTooltip>
{{ t('ticketNotes.addNote') }}
</QTooltip>
</QIcon>
</VnRow>
</QCard>
</template>
</CrudModel>
</div>
</template>

View File

@ -0,0 +1,190 @@
<script setup>
import { ref, watch, computed, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CrudModel from 'components/CrudModel.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import FetchData from 'components/FetchData.vue';
import TicketCreateServiceType from './TicketCreateServiceType.vue';
import VnInput from 'src/components/common/VnInput.vue';
import { useArrayData } from 'src/composables/useArrayData';
import useNotify from 'src/composables/useNotify.js';
import axios from 'axios';
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
const ticketServiceTypeFetchRef = ref(null);
const ticketServiceCrudRef = ref(null);
const ticketServiceOptions = ref([]);
const arrayData = useArrayData('TicketNotes');
const { store } = arrayData;
const { notify } = useNotify();
const selected = ref([]);
const defaultTaxClass = ref(null);
const crudModelFilter = computed(() => ({
where: { ticketFk: route.params.id },
}));
const crudModelRequiredData = computed(() => ({
ticketFk: route.params.id,
taxClassFk: defaultTaxClass.value?.id,
}));
watch(
() => route.params.id,
async () => {
store.filter = crudModelFilter.value;
await ticketServiceCrudRef.value.reload();
}
);
onMounted(async () => await getDefaultTaxClass());
const createRefund = async () => {
try {
if (!selected.value.length) return;
const params = {
servicesIds: selected.value.map((s) => +s.ticketFk),
withWarehouse: false,
negative: true,
};
const { data } = await axios.post('Sales/clone', params);
const [refundTicket] = data;
notify(
t('service.createRefundSuccess', {
ticketId: refundTicket.id,
}),
'positive'
);
router.push({ name: 'TicketSale', params: { id: refundTicket.id } });
} catch (error) {
console.error(error);
}
};
const getDefaultTaxClass = async () => {
try {
let filter = {
where: { code: 'G' },
};
const { data } = await axios.get('TaxClasses/findOne', {
params: { filter: JSON.stringify(filter) },
});
defaultTaxClass.value = data;
console.log('defaultTaxClass', defaultTaxClass.value);
} catch (error) {
console.error(error);
}
};
const columns = computed(() => [
{
name: 'description',
label: t('service.description'),
field: (row) => row.ticketServiceTypeFk,
sortable: true,
align: 'left',
},
{
name: 'quantity',
label: t('service.quantity'),
field: (row) => row.quantity,
sortable: true,
align: 'left',
},
{
name: 'price',
label: t('service.price'),
field: (row) => row.price,
sortable: true,
align: 'left',
},
]);
</script>
<template>
<FetchData
ref="ticketServiceTypeFetchRef"
@on-fetch="(data) => (ticketServiceOptions = data)"
auto-load
url="TicketServiceTypes"
/>
<CrudModel
ref="ticketServiceCrudRef"
data-key="TicketService"
url="TicketServices"
model="TicketService"
:filter="crudModelFilter"
:data-required="crudModelRequiredData"
auto-load
v-model:selected="selected"
>
<template #moreBeforeActions>
<QBtn
color="primary"
:label="t('service.pay')"
:disabled="!selected.length"
@click.stop="createRefund()"
/>
</template>
<template #body="{ rows }">
<QTable
:columns="columns"
:rows="rows"
row-key="$index"
selection="multiple"
v-model:selected="selected"
table-header-class="text-left"
>
<template #body-cell-description="{ row, col }">
<QTd auto-width>
<VnSelectDialog
:label="col.label"
v-model="row.ticketServiceTypeFk"
:options="ticketServiceOptions"
option-label="name"
option-value="id"
hide-selected
>
<template #form>
<TicketCreateServiceType
@on-data-saved="ticketServiceTypeFetchRef.fetch()"
/>
</template>
</VnSelectDialog>
</QTd>
</template>
<template #body-cell-quantity="{ row, col }">
<QTd auto-width>
<VnInput
:label="col.label"
v-model.number="row.quantity"
type="number"
min="0"
:info="t('service.quantityInfo')"
/>
</QTd>
</template>
<template #body-cell-price="{ row, col }">
<QTd auto-width>
<VnInput
:label="col.label"
v-model.number="row.price"
type="number"
min="0"
/>
</QTd>
</template>
</QTable>
</template>
</CrudModel>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn fab color="primary" icon="add" @click="ticketServiceCrudRef.insert()" />
</QPageSticky>
</template>

View File

@ -0,0 +1,121 @@
<script setup>
import { ref, computed, watch, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import TicketCreateTracking from './TicketCreateTracking.vue';
import VnPaginate from 'components/ui/VnPaginate.vue';
import { toDateTimeFormat } from 'src/filters/date.js';
const route = useRoute();
const { t } = useI18n();
const createTrackingDialogRef = ref(null);
const paginateRef = ref(null);
watch(
() => route.params.id,
async (val) => {
paginateFilter.where.ticketFk = val;
paginateRef.value.fetch();
}
);
const paginateFilter = reactive({
include: [
{
relation: 'user',
scope: {
fields: ['id', 'name'],
include: {
relation: 'worker',
scope: {
fields: ['id'],
},
},
},
},
{
relation: 'state',
scope: {
fields: ['name'],
},
},
],
order: ['created DESC'],
where: {
ticketFk: route.params.id,
},
});
const columns = computed(() => [
{
label: t('tracking.state'),
name: 'state',
field: 'state',
align: 'left',
format: (val) => val.name,
},
{
label: t('tracking.worker'),
name: 'worker',
align: 'left',
},
{
label: t('tracking.created'),
name: 'created',
field: 'created',
align: 'left',
format: (val) => toDateTimeFormat(val),
},
]);
const openCreateModal = () => createTrackingDialogRef.value.show();
</script>
<template>
<QPage class="column items-center q-pa-md">
<VnPaginate
ref="paginateRef"
data-key="TicketTracking"
:filter="paginateFilter"
url="TicketTrackings"
auto-load
>
<template #body="{ rows }">
<QTable
:rows="rows"
:columns="columns"
row-key="id"
:pagination="{ rowsPerPage: 0 }"
class="full-width q-mt-md"
:no-data-label="t('globals.noResults')"
>
<template #body-cell-worker="{ row }">
<QTd @click.stop>
<QBtn flat color="primary">
{{ row.user?.name }}
<WorkerDescriptorProxy :id="row.user?.worker?.id" />
</QBtn>
</QTd>
</template>
</QTable>
</template>
</VnPaginate>
<QDialog
ref="createTrackingDialogRef"
transition-show="scale"
transition-hide="scale"
>
<TicketCreateTracking @on-request-created="paginateRef.fetch()" />
</QDialog>
<QPageSticky :offset="[20, 20]">
<QBtn @click="openCreateModal()" color="primary" fab icon="add" />
<QTooltip class="text-no-wrap">
{{ t('tracking.addState') }}
</QTooltip>
</QPageSticky>
</QPage>
</template>

View File

@ -0,0 +1,153 @@
<script setup>
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import FetchedTags from 'components/ui/FetchedTags.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import FetchData from 'components/FetchData.vue';
import { useStateStore } from 'stores/useStateStore';
import { dashIfEmpty } from 'src/filters';
import axios from 'axios';
const route = useRoute();
const stateStore = useStateStore();
const { t } = useI18n();
const salesRef = ref(null);
watch(
() => route.params.id,
async () => {
await nextTick();
salesRef.value?.fetch();
}
);
const salesFilter = computed(() => ({
include: { relation: 'item' },
order: 'concept',
where: { ticketFk: route.params.id },
limit: 20,
}));
const sales = ref([]);
const packingTypeVolume = ref([]);
const rows = computed(() => sales.value);
const columns = computed(() => [
{
label: t('volume.item'),
name: 'item',
align: 'left',
},
{
label: t('volume.description'),
name: 'description',
align: 'left',
},
{
label: t('volume.packingType'),
name: 'quantity',
field: (row) => row.item.itemPackingTypeFk,
align: 'left',
format: (val) => dashIfEmpty(val),
},
{
label: t('volume.quantity'),
name: 'quantity',
field: 'quantity',
align: 'left',
},
{
label: t('volume.volumeQuantity'),
name: 'quantity',
field: (row) => row.saleVolume?.volume,
align: 'left',
},
]);
const applyVolumes = async (salesData) => {
try {
if (!salesData.length) return;
sales.value = salesData;
const ticket = sales.value[0].ticketFk;
const { data } = await axios.get(`Tickets/${ticket}/getVolume`);
const volumes = new Map(data.saleVolume.map((volume) => [volume.saleFk, volume]));
sales.value.forEach((sale) => {
sale.saleVolume = volumes.get(sale.id);
});
packingTypeVolume.value = data.packingTypeVolume;
} catch (error) {
console.error(error);
}
};
onMounted(() => {
stateStore.rightDrawer = true;
});
onUnmounted(() => (stateStore.rightDrawer = false));
</script>
<template>
<FetchData
ref="salesRef"
url="sales"
:filter="salesFilter"
@on-fetch="(data) => applyVolumes(data)"
auto-load
/>
<RightMenu v-if="packingTypeVolume.length">
<template #right-panel>
<QCard
v-for="(packingType, index) in packingTypeVolume"
:key="index"
class="q-pa-md q-mb-md q-ma-md color-vn-text"
bordered
flat
style="border-color: black"
>
<QCardSection class="column items-center" horizontal>
<span>
{{ t('volume.type') }}:
{{ dashIfEmpty(packingType.description) }}
</span>
</QCardSection>
<QCardSection class="column items-center" horizontal>
<span> {{ t('volume.volume') }}: {{ packingType.volume }} </span>
</QCardSection>
</QCard>
</template>
</RightMenu>
<QTable
:rows="rows"
:columns="columns"
row-key="id"
:pagination="{ rowsPerPage: 0 }"
class="full-width q-mt-md"
:no-data-label="t('globals.noResults')"
>
<template #body-cell-item="{ row }">
<QTd>
<QBtn flat color="primary">
{{ row.itemFk }}
<ItemDescriptorProxy :id="row.itemFk" />
</QBtn>
</QTd>
</template>
<template #body-cell-description="{ row }">
<QTd>
<div class="column">
<span>{{ row.item.name }}</span>
<span class="color-vn-label">{{ row.item.subName }}</span>
<FetchedTags :item="row.item" :max-length="6" />
</div>
</QTd>
</template>
</QTable>
</template>

View File

@ -17,15 +17,6 @@ const props = defineProps({
},
});
const from = Date.vnNew();
const to = Date.vnNew();
to.setDate(to.getDate() + 1);
const defaultParams = {
from: toDateString(from),
to: toDateString(to),
};
const workers = ref();
const provinces = ref();
const states = ref();
@ -44,11 +35,7 @@ const warehouses = ref();
@on-fetch="(data) => (workers = data)"
auto-load
/>
<VnFilterPanel
:data-key="props.dataKey"
:params="defaultParams"
:search-button="true"
>
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>

View File

@ -26,8 +26,8 @@ const to = Date.vnNew();
to.setDate(to.getDate() + 1);
const userParams = {
from: toDateString(from),
to: toDateString(to),
from: from.toISOString(),
to: to.toISOString(),
};
function navigate(id) {

View File

@ -1,3 +1,19 @@
card:
search: Search tickets
searchInfo: You can search by ticket id or alias
volume:
item: Item
description: Description
packingType: Packing Type
quantity: Quantity
volumeQuantity: m³ per quantity
type: Type
volume: Volume
ticketNotes:
observationType: Observation type
description: Description
removeNote: Remove note
addNote: Add note
ticketSale:
id: Id
visible: Visible
@ -113,9 +129,6 @@ basicData:
negativesConfirmMessage: Negatives are going to be generated, are you sure you want to advance all the lines?
chooseAnOption: Choose an option
unroutedTicket: The ticket has been unrouted
card:
search: Search tickets
searchInfo: You can search by ticket id or alias
purchaseRequest:
id: Id
description: Description
@ -136,3 +149,18 @@ weeklyTickets:
salesperson: Salesperson
search: Search weekly tickets
searchInfo: Search weekly tickets by id or client id
service:
pay: Pay
description: Description
quantity: Quantity
price: Price
removeService: Remove service
newService: New service type
addService: Add service
quantityInfo: To create services with negative amounts mark the service on the source ticket and press the pay button.
createRefundSuccess: 'The following refund ticket have been created: { ticketId }'
tracking:
state: State
worker: Worker
created: Created
addState: Add state

View File

@ -1,6 +1,34 @@
service:
pay: Abonar
description: Descripción
quantity: Cantidad
price: Precio
removeService: Quitar servicio
newService: Nuevo tipo de servicio
addService: Añadir servicio
quantityInfo: Para crear sevicios con cantidades negativas marcar servicio en el ticket origen y apretar el boton abonar.
createRefundSuccess: 'Se ha creado siguiente ticket de abono: { ticketId }'
tracking:
state: Estado
worker: Trabajador
created: Fecha creación
addState: Añadir estado
card:
search: Buscar tickets
searchInfo: Buscar tickets por identificador o alias
volume:
item: Artículo
description: Descripción
packingType: Encajado
quantity: Cantidad
volumeQuantity: m³ por cantidad
type: Tipo
volume: Volumen
ticketNotes:
observationType: Tipo de observación
description: Descripción
removeNote: Quitar nota
addNote: Añadir nota
purchaseRequest:
Id: Id
description: Descripción
@ -112,8 +140,6 @@ futureTickets:
moveTicketSuccess: Tickets movidos correctamente
searchInfo: Buscar tickets por fecha
futureTicket: Tickets a futuro
Search ticket: Buscar tickets
You can search by ticket id or alias: Puedes buscar por id o alias del ticket
ticketSale:
id: Id
visible: Visible
@ -138,3 +164,5 @@ ticketSale:
shipped: F. Envío
agency: Agencia
address: Consignatario
Search ticket: Buscar tickets
You can search by ticket id or alias: Puedes buscar por id o alias del ticket

View File

@ -1,10 +1,11 @@
<script setup>
import { computed } from 'vue';
import { computed, ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import useCardDescription from 'src/composables/useCardDescription';
import TravelDescriptorMenuItems from './TravelDescriptorMenuItems.vue';
import { toDate } from 'src/filters';
@ -51,32 +52,22 @@ const filter = {
const entityId = computed(() => {
return $props.id || route.params.id;
});
const data = ref(useCardDescription());
const setData = (entity) => (data.value = useCardDescription(entity.ref, entity.id));
</script>
<template>
<CardDescriptor
module="Travel"
:url="`Travels/${entityId}`"
title="ref"
:title="data.title"
:subtitle="data.subtitle"
:filter="filter"
data-key="Travel"
data-key="travelData"
:summary="$attrs"
@on-fetch="setData"
>
<template #header-extra-action>
<QBtn
round
flat
dense
size="md"
icon="local_airport"
color="white"
class="link"
:to="{ name: 'TravelList' }"
>
<QTooltip>
{{ t('Go to module index') }}
</QTooltip>
</QBtn>
</template>
<template #menu="{ entity }">
<TravelDescriptorMenuItems :travel="entity" />
</template>

View File

@ -1,5 +1,6 @@
<script setup>
import TravelDescriptor from './TravelDescriptor.vue';
import TravelSummary from './TravelSummary.vue';
const $props = defineProps({
id: {
@ -11,6 +12,6 @@ const $props = defineProps({
<template>
<QPopupProxy>
<TravelDescriptor v-if="$props.id" :id="$props.id" />
<TravelDescriptor v-if="$props.id" :id="$props.id" :summary="TravelSummary" />
</QPopupProxy>
</template>

View File

@ -49,7 +49,7 @@ const entriesTableColumns = computed(() => {
showValue: false,
},
{
label: t('supplier.pageTitles.supplier'),
label: t('globals.pageTitles.supplier'),
field: 'supplierName',
name: 'supplierName',
align: 'left',
@ -248,7 +248,7 @@ const getLink = (param) => `#/travel/${entityId.value}/${param}`;
<QCardSection class="q-pa-none">
<VnTitle
:url="getLink('basic-data')"
:text="t('travel.pageTitles.basicData')"
:text="t('globals.pageTitles.basicData')"
/>
</QCardSection>
<VnLv :label="t('globals.shipped')" :value="toDate(travel.shipped)" />
@ -266,7 +266,7 @@ const getLink = (param) => `#/travel/${entityId.value}/${param}`;
<QCardSection class="q-pa-none">
<VnTitle
:url="getLink('basic-data')"
:text="t('travel.pageTitles.basicData')"
:text="t('globals.pageTitles.basicData')"
/>
</QCardSection>
<VnLv :label="t('globals.landed')" :value="toDate(travel.landed)" />
@ -284,7 +284,7 @@ const getLink = (param) => `#/travel/${entityId.value}/${param}`;
<QCardSection class="q-pa-none">
<VnTitle
:url="getLink('basic-data')"
:text="t('travel.pageTitles.basicData')"
:text="t('globals.pageTitles.basicData')"
/>
</QCardSection>
<VnLv :label="t('globals.agency')" :value="travel.agency?.name" />

View File

@ -140,7 +140,7 @@ const columns = computed(() => [
sortable: true,
},
{
label: t('supplier.pageTitles.supplier'),
label: t('globals.pageTitles.supplier'),
field: 'cargoSupplierNickname',
name: 'cargoSupplierNickname',
align: 'left',

View File

@ -218,7 +218,7 @@ warehouses();
<QItem>
<QItemSection>
<VnSelect
:label="t('supplier.pageTitles.supplier')"
:label="t('globals.pageTitles.supplier')"
v-model="params.cargoSupplierFk"
:options="suppliersOptions"
option-value="id"

View File

@ -17,10 +17,6 @@ const $props = defineProps({
required: false,
default: null,
},
summary: {
type: Object,
default: null,
},
});
const route = useRoute();
@ -115,7 +111,7 @@ const refetch = async () => await cardDescriptorRef.value.getData();
:filter="filter"
:title="data.title"
:subtitle="data.subtitle"
:summary="$props.summary"
:summary="$attrs"
@on-fetch="
(data) => {
worker = data;

View File

@ -74,7 +74,7 @@ const agencyOptions = ref([]);
type="number"
min="0"
/>
<VnInputTime v-model="data.hour" :label="t('Closing')" clearable />
<VnInputTime v-model="data.hour" :label="t('Closing')" />
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">

View File

@ -38,7 +38,6 @@ const entityId = computed(() => {
});
const data = ref(useCardDescription());
const setData = (entity) => {
data.value = useCardDescription(entity.ref, entity.id);
};
@ -53,23 +52,8 @@ const setData = (entity) => {
:filter="filter"
@on-fetch="setData"
data-key="zoneData"
:summary="$attrs"
>
<template #header-extra-action>
<QBtn
round
flat
dense
size="md"
icon="vn:zone"
color="white"
class="link"
:to="{ name: 'ZoneList' }"
>
<QTooltip>
{{ t('Summary') }}
</QTooltip>
</QBtn>
</template>
<template #menu="{ entity }">
<ZoneDescriptorMenuItems :zone="entity" />
</template>
@ -82,3 +66,8 @@ const setData = (entity) => {
</template>
</CardDescriptor>
</template>
<i18n>
es:
Go to module index: Ir al índice del módulo
</i18n>

View File

@ -1,5 +1,6 @@
<script setup>
import ZoneDescriptor from './ZoneDescriptor.vue';
import ZoneSummary from './ZoneSummary.vue';
const $props = defineProps({
id: {
@ -11,6 +12,6 @@ const $props = defineProps({
<template>
<QPopupProxy>
<ZoneDescriptor v-if="$props.id" :id="$props.id" />
<ZoneDescriptor v-if="$props.id" :id="$props.id" :summary="ZoneSummary" />
</QPopupProxy>
</template>

View File

@ -11,7 +11,6 @@ import { useState } from 'src/composables/useState';
import { useSession } from 'src/composables/useSession';
import { useRole } from 'src/composables/useRole';
import { useUserConfig } from 'src/composables/useUserConfig';
import { toLowerCamel } from 'src/filters';
import { useTokenConfig } from 'src/composables/useTokenConfig';
import { useAcl } from 'src/composables/useAcl';
@ -79,13 +78,11 @@ export default route(function (/* { store, ssrContext } */) {
let title = t(`login.title`);
const matches = to.matched;
let moduleName;
if (matches && matches.length > 1) {
const module = matches[1];
const moduleTitle = module.meta && module.meta.title;
moduleName = toLowerCamel(module.name);
if (moduleTitle) {
title = t(`${moduleName}.pageTitles.${moduleTitle}`);
title = t(`globals.pageTitles.${moduleTitle}`);
}
}
@ -94,7 +91,7 @@ export default route(function (/* { store, ssrContext } */) {
if (childPageTitle && matches.length > 2) {
if (title != '') title += ': ';
const moduleLocale = `${moduleName}.pageTitles.${childPageTitle}`;
const moduleLocale = `globals.pageTitles.${childPageTitle}`;
const pageTitle = te(moduleLocale)
? t(moduleLocale)
: t(`globals.pageTitles.${childPageTitle}`);

View File

@ -11,7 +11,7 @@ export default {
component: RouterView,
redirect: { name: 'EntryMain' },
menus: {
main: ['EntryList', 'EntryLatestBuys'],
main: ['EntryList', 'MyEntries', 'EntryLatestBuys'],
card: ['EntryBasicData', 'EntryBuys', 'EntryNotes', 'EntryDms', 'EntryLog'],
},
children: [
@ -30,6 +30,15 @@ export default {
},
component: () => import('src/pages/Entry/EntryList.vue'),
},
{
path: 'my',
name: 'MyEntries',
meta: {
title: 'labeler',
icon: 'sell',
},
component: () => import('src/pages/Entry/MyEntries.vue'),
},
{
path: 'create',
name: 'EntryCreate',

View File

@ -201,15 +201,6 @@ export default {
},
component: () => import('src/pages/Item/Card/ItemLog.vue'),
},
{
path: 'botanical',
name: 'ItemBotanical',
meta: {
title: 'botanical',
icon: 'vn:botanical',
},
component: () => import('src/pages/Item/Card/ItemBotanical.vue'),
},
],
},
],

View File

@ -19,6 +19,10 @@ export default {
'TicketSale',
'TicketLog',
'TicketPurchaseRequest',
'TicketService',
'TicketTracking',
'TicketVolume',
'TicketNotes',
],
},
children: [
@ -29,8 +33,8 @@ export default {
redirect: { name: 'TicketList' },
children: [
{
name: 'TicketList',
path: 'list',
name: 'TicketList',
meta: {
title: 'list',
icon: 'view_list',
@ -38,8 +42,8 @@ export default {
component: () => import('src/pages/Ticket/TicketList.vue'),
},
{
name: 'TicketCreate',
path: 'create',
name: 'TicketCreate',
meta: {
title: 'createTicket',
icon: 'vn:ticketAdd',
@ -48,8 +52,8 @@ export default {
component: () => import('src/pages/Ticket/TicketCreate.vue'),
},
{
name: 'TicketWeekly',
path: 'weekly',
name: 'TicketWeekly',
meta: {
title: 'weeklyTickets',
icon: 'access_time',
@ -57,8 +61,8 @@ export default {
component: () => import('src/pages/Ticket/TicketWeekly.vue'),
},
{
name: 'TicketFuture',
path: 'future',
name: 'TicketFuture',
meta: {
title: 'futureTickets',
icon: 'keyboard_double_arrow_right',
@ -66,8 +70,8 @@ export default {
component: () => import('src/pages/Ticket/TicketFuture.vue'),
},
{
name: 'TicketAdvance',
path: 'advance',
name: 'TicketAdvance',
meta: {
title: 'ticketAdvance',
icon: 'keyboard_double_arrow_left',
@ -83,8 +87,8 @@ export default {
redirect: { name: 'TicketSummary' },
children: [
{
name: 'TicketSummary',
path: 'summary',
name: 'TicketSummary',
meta: {
title: 'summary',
icon: 'launch',
@ -92,8 +96,8 @@ export default {
component: () => import('src/pages/Ticket/Card/TicketSummary.vue'),
},
{
name: 'TicketBasicData',
path: 'basic-data',
name: 'TicketBasicData',
meta: {
title: 'basicData',
icon: 'vn:settings',
@ -102,8 +106,8 @@ export default {
import('src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue'),
},
{
name: 'TicketSale',
path: 'sale',
name: 'TicketSale',
meta: {
title: 'sale',
icon: 'vn:lines',
@ -120,6 +124,15 @@ export default {
component: () =>
import('src/pages/Ticket/Card/TicketPurchaseRequest.vue'),
},
{
path: 'tracking',
name: 'TicketTracking',
meta: {
title: 'tracking',
icon: 'vn:eye',
},
component: () => import('src/pages/Ticket/Card/TicketTracking.vue'),
},
{
path: 'log',
name: 'TicketLog',
@ -147,6 +160,34 @@ export default {
},
component: () => import('src/pages/Ticket/Card/TicketSms.vue'),
},
{
path: 'service',
name: 'TicketService',
meta: {
title: 'services',
icon: 'vn:services',
},
component: () => import('src/pages/Ticket/Card/TicketService.vue'),
},
{
path: 'volume',
name: 'TicketVolume',
meta: {
title: 'volume',
icon: 'vn:volume',
},
component: () => import('src/pages/Ticket/Card/TicketVolume.vue'),
},
{
path: 'observation',
name: 'TicketNotes',
meta: {
title: 'notes',
icon: 'vn:notes',
},
component: () => import('src/pages/Ticket/Card/TicketNotes.vue'),
},
],
},
],

View File

@ -23,6 +23,7 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => {
exprBuilder: null,
searchUrl: 'params',
navigate: null,
page: 1,
};
}

View File

@ -60,7 +60,7 @@ export const useNavigationStore = defineStore('navigationStore', () => {
menuChildren = menuChildren.map(({ name, title, icon }) => ({
name,
icon,
title: `${module}.pageTitles.${title}`,
title: `globals.pageTitles.${title}`,
}));
if (meta && meta.roles && role.hasAny(meta.roles) === false) return;
@ -70,7 +70,7 @@ export const useNavigationStore = defineStore('navigationStore', () => {
children: menuChildren,
};
if (meta) {
item.title = `${module}.pageTitles.${meta.title}`;
item.title = `globals.pageTitles.${meta.title}`;
item.icon = meta.icon;
}

View File

@ -0,0 +1,20 @@
describe('WagonTypeCreate', () => {
beforeEach(() => {
cy.viewport(1920, 1080);
cy.login('customer');
cy.visit(`/#/entry/my`, {
onBeforeLoad(win) {
cy.stub(win, 'open');
},
});
cy.waitForElement('.q-page', 6000);
});
it('should create edit and remove new dms', () => {
cy.get(
'[to="/null/2"] > .q-card > .column > .q-btn > .q-btn__content > .q-icon'
).click();
cy.get('.q-card__actions > .q-btn').click();
cy.window().its('open').should('be.called');
});
});

View File

@ -5,12 +5,11 @@ describe('InvoiceInList', () => {
':nth-child(1) > :nth-child(1) > .justify-between > .flex > .q-chip > .q-chip__content';
const firstDetailBtn = '.q-card:nth-child(1) .q-btn:nth-child(2)';
const summaryHeaders = '.summaryBody .header-link';
const screen = '.q-page-container > .q-drawer-container > .fullscreen';
beforeEach(() => {
cy.viewport(1920, 1080);
cy.login('developer');
cy.visit(`/#/invoice-in/list`);
cy.get(screen).click();
});
it('should redirect on clicking a invoice', () => {

View File

@ -8,9 +8,9 @@ describe('ParkingList', () => {
const summaryHeader = '.summaryBody .header';
beforeEach(() => {
cy.viewport(1920, 1080);
cy.login('developer');
cy.visit(`/#/parking/list`);
cy.closeSideMenu();
});
it('should redirect on clicking a parking', () => {

View File

@ -50,8 +50,8 @@ Cypress.Commands.add('login', (user) => {
});
});
Cypress.Commands.add('waitForElement', (element) => {
cy.get(element, { timeout: 5000 }).should('be.visible');
Cypress.Commands.add('waitForElement', (element, timeout = 5000) => {
cy.get(element, { timeout }).should('be.visible');
});
Cypress.Commands.add('getValue', (selector) => {
@ -221,10 +221,6 @@ Cypress.Commands.add('openLeftMenu', (element) => {
if (element) cy.waitForElement(element);
cy.get('.q-toolbar > .q-btn--round.q-btn--dense > .q-btn__content > .q-icon').click();
});
Cypress.Commands.add('closeSideMenu', (element) => {
if (element) cy.waitForElement(element);
cy.get('.fullscreen.q-drawer__backdrop:not(.hidden)').click();
});
Cypress.Commands.add('clearSearchbar', (element) => {
if (element) cy.waitForElement(element);
@ -245,4 +241,3 @@ Cypress.Commands.add('validateContent', (selector, expectedValue) => {
Cypress.Commands.add('openActionsDescriptor', () => {
cy.get('.descriptor > .header > .q-btn').click();
});
// registerCommands();

View File

@ -78,13 +78,13 @@ describe('Leftmenu', () => {
{
children: null,
name: 'CustomerList',
title: 'customer.pageTitles.list',
title: 'globals.pageTitles.list',
icon: 'view_list',
},
{
children: null,
name: 'CustomerCreate',
title: 'customer.pageTitles.createCustomer',
title: 'globals.pageTitles.createCustomer',
icon: 'vn:addperson',
},
];