Merge branch 'dev' into 7543-StandarizeDescriptors
gitea/salix-front/pipeline/pr-dev This commit looks good Details

This commit is contained in:
Jon Elias 2024-07-03 07:52:35 +00:00
commit f8d263c347
42 changed files with 598 additions and 336 deletions

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

@ -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

@ -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

@ -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,6 +79,19 @@ 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);
@ -162,8 +177,8 @@ const toModule = computed(() =>
<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">
@ -174,7 +189,7 @@ const toModule = computed(() =>
</QItemLabel>
<QItem dense>
<QItemLabel class="subtitle" caption>
#{{ $props.subtitle ?? entity.id }}
#{{ getValueFromPath(subtitle) ?? entity.id }}
</QItemLabel>
</QItem>
</QList>

View File

@ -114,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;
@ -126,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;
@ -138,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)

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

@ -195,6 +195,7 @@ globals:
autonomous: Autonomous
suppliers: Suppliers
supplier: Supplier
labeler: Labeler
supplierCreate: New supplier
accounts: Accounts
addresses: Addresses

View File

@ -166,6 +166,7 @@ globals:
invoiceIns: Fact. recibidas
invoiceInCreate: Crear fact. recibida
vat: IVA
labeler: Etiquetas
dueDay: Vencimiento
intrastat: Intrastat
corrective: Rectificativa

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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

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

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

@ -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) => {
@ -241,4 +241,3 @@ Cypress.Commands.add('validateContent', (selector, expectedValue) => {
Cypress.Commands.add('openActionsDescriptor', () => {
cy.get('.descriptor > .header > .q-btn').click();
});
// registerCommands();