Compare commits

..

19 Commits

Author SHA1 Message Date
Robert Ferrús 307355ba3f Merge branch 'dev' into 7731-clientViesCode
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-02-07 06:05:56 +00:00
Robert Ferrús 4e85e30e41 feat: refs #7731 commit blanck 2025-02-06 12:39:45 +01:00
Robert Ferrús fe3ea3fa6a Merge branch 'dev' into 7731-clientViesCode 2025-02-06 10:52:10 +00:00
Robert Ferrús 0c3650c9e3 feat: refs #7731 fix branche
gitea/salix-front/pipeline/head This commit looks good Details
2025-01-24 13:36:55 +01:00
Alex Moreno da0540a748 Merge branch 'dev' into 7731-clientViesCode
gitea/salix-front/pipeline/head This commit looks good Details
2025-01-24 11:28:10 +00:00
Robert Ferrús 9ecb734b1b Merge branch 'dev' into 7731-clientViesCode 2025-01-24 08:30:49 +00:00
Robert Ferrús f019c55b72 Merge branch 'dev' into 7731-clientViesCode 2025-01-24 05:56:11 +00:00
Robert Ferrús d966be60da Merge branch 'dev' into 7731-clientViesCode 2025-01-23 08:06:09 +00:00
Robert Ferrús c884c09943 feat: refs #7731 resolve conflicts 2025-01-23 09:05:49 +01:00
Robert Ferrús 32191fe713 Merge branch 'dev' into 7731-clientViesCode 2025-01-22 13:07:22 +00:00
Robert Ferrús fdaa3ea72a Merge branch 'dev' of https: refs #7731//gitea.verdnatura.es/verdnatura/salix-front into 7731-clientViesCode 2025-01-22 10:11:07 +01:00
Robert Ferrús dab45277da Merge branch 'dev' into 7731-clientViesCode
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2024-12-17 10:24:14 +00:00
Robert Ferrús 4de85126cd feat: refs #7731
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2024-12-17 11:02:51 +01:00
Robert Ferrús 08f247900a Merge branch 'dev' of https: refs #7731//gitea.verdnatura.es/verdnatura/salix-front into 7731-clientViesCode 2024-12-17 08:20:40 +01:00
Robert Ferrús 82fce46999 feat: refs #7731 supplierFiscalData
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2024-10-21 13:16:06 +02:00
Robert Ferrús 6b99039e68 Merge branch '7731-clientViesCode' of https://gitea.verdnatura.es/verdnatura/salix-front into 7731-clientViesCode
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2024-10-21 09:59:36 +02:00
Robert Ferrús 6e164cda14 Merge branch 'dev' into 7731-clientViesCode
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2024-10-21 07:56:06 +00:00
Robert Ferrús 6d0e99faa3 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 7731-clientViesCode 2024-10-21 09:55:19 +02:00
Robert Ferrús 5a1317da77 feat: refs #7731 create component vnSelectVies
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2024-10-17 10:58:08 +02:00
183 changed files with 2390 additions and 6495 deletions

View File

@ -14,8 +14,8 @@ export default defineConfig({
downloadsFolder: 'test/cypress/downloads', downloadsFolder: 'test/cypress/downloads',
video: false, video: false,
specPattern: 'test/cypress/integration/**/*.spec.js', specPattern: 'test/cypress/integration/**/*.spec.js',
experimentalRunAllSpecs: false, experimentalRunAllSpecs: true,
watchForFileChanges: false, watchForFileChanges: true,
reporter: 'cypress-mochawesome-reporter', reporter: 'cypress-mochawesome-reporter',
reporterOptions: { reporterOptions: {
charts: true, charts: true,

View File

@ -30,6 +30,7 @@ export default configure(function (/* ctx */) {
// --> boot files are part of "main.js" // --> boot files are part of "main.js"
// https://v2.quasar.dev/quasar-cli/boot-files // https://v2.quasar.dev/quasar-cli/boot-files
boot: ['i18n', 'axios', 'vnDate', 'validations', 'quasar', 'quasar.defaults'], boot: ['i18n', 'axios', 'vnDate', 'validations', 'quasar', 'quasar.defaults'],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
css: ['app.scss'], css: ['app.scss'],

View File

@ -1,2 +0,0 @@
export const langs = ['en', 'es'];
export const decimalPlaces = 2;

View File

@ -51,5 +51,4 @@ export default boot(({ app }) => {
await useCau(response, message); await useCau(response, message);
}; };
app.provide('app', app);
}); });

View File

@ -64,10 +64,6 @@ const $props = defineProps({
type: Function, type: Function,
default: null, default: null,
}, },
beforeSaveFn: {
type: Function,
default: null,
},
goTo: { goTo: {
type: String, type: String,
default: '', default: '',
@ -180,11 +176,7 @@ async function saveChanges(data) {
hasChanges.value = false; hasChanges.value = false;
return; return;
} }
let changes = data || getChanges(); const changes = data || getChanges();
if ($props.beforeSaveFn) {
changes = await $props.beforeSaveFn(changes, getChanges);
}
try { try {
await axios.post($props.saveUrl || $props.url + '/crud', changes); await axios.post($props.saveUrl || $props.url + '/crud', changes);
} finally { } finally {
@ -237,12 +229,12 @@ async function remove(data) {
componentProps: { componentProps: {
title: t('globals.confirmDeletion'), title: t('globals.confirmDeletion'),
message: t('globals.confirmDeletionMessage'), message: t('globals.confirmDeletionMessage'),
data: { deletes: ids }, newData,
ids, ids,
promise: saveChanges,
}, },
}) })
.onOk(async () => { .onOk(async () => {
await saveChanges({ deletes: ids });
newData = newData.filter((form) => !ids.some((id) => id == form[pk])); newData = newData.filter((form) => !ids.some((id) => id == form[pk]));
fetch(newData); fetch(newData);
}); });
@ -382,8 +374,6 @@ watch(formUrl, async () => {
@click="onSubmit" @click="onSubmit"
:disable="!hasChanges" :disable="!hasChanges"
:title="t('globals.save')" :title="t('globals.save')"
v-shortcut="'s'"
shortcut="s"
data-cy="crudModelDefaultSaveBtn" data-cy="crudModelDefaultSaveBtn"
/> />
<slot name="moreAfterActions" /> <slot name="moreAfterActions" />

View File

@ -181,7 +181,6 @@ const selectTravel = ({ id }) => {
color="primary" color="primary"
:disabled="isLoading" :disabled="isLoading"
:loading="isLoading" :loading="isLoading"
data-cy="save-filter-travel-form"
/> />
</div> </div>
<QTable <QTable
@ -192,10 +191,9 @@ const selectTravel = ({ id }) => {
:no-data-label="t('Enter a new search')" :no-data-label="t('Enter a new search')"
class="q-mt-lg" class="q-mt-lg"
@row-click="(_, row) => selectTravel(row)" @row-click="(_, row) => selectTravel(row)"
data-cy="table-filter-travel-form"
> >
<template #body-cell-id="{ row }"> <template #body-cell-id="{ row }">
<QTd auto-width @click.stop data-cy="travelFk-travel-form"> <QTd auto-width @click.stop>
<QBtn flat color="blue">{{ row.id }}</QBtn> <QBtn flat color="blue">{{ row.id }}</QBtn>
<TravelDescriptorProxy :id="row.id" /> <TravelDescriptorProxy :id="row.id" />
</QTd> </QTd>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue'; import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
@ -15,30 +15,23 @@ defineProps({
type: String, type: String,
default: '', default: '',
}, },
showSaveAndContinueBtn: {
type: Boolean,
default: false,
},
}); });
const { t } = useI18n(); const { t } = useI18n();
const formModelRef = ref(null); const formModelRef = ref(null);
const closeButton = ref(null); const closeButton = ref(null);
const isSaveAndContinue = ref(false);
const onDataSaved = (formData, requestResponse) => { const onDataSaved = (formData, requestResponse) => {
if (closeButton.value && isSaveAndContinue) closeButton.value.click(); if (closeButton.value) closeButton.value.click();
emit('onDataSaved', formData, requestResponse); emit('onDataSaved', formData, requestResponse);
}; };
const isLoading = computed(() => formModelRef.value?.isLoading); const isLoading = computed(() => formModelRef.value?.isLoading);
const reset = computed(() => formModelRef.value?.reset);
defineExpose({ defineExpose({
isLoading, isLoading,
onDataSaved, onDataSaved,
isSaveAndContinue,
reset,
}); });
</script> </script>
@ -58,19 +51,6 @@ defineExpose({
<p>{{ subtitle }}</p> <p>{{ subtitle }}</p>
<slot name="form-inputs" :data="data" :validate="validate" /> <slot name="form-inputs" :data="data" :validate="validate" />
<div class="q-mt-lg row justify-end"> <div class="q-mt-lg row justify-end">
<QBtn
v-if="showSaveAndContinueBtn"
:label="t('globals.isSaveAndContinue')"
:title="t('globals.isSaveAndContinue')"
type="submit"
color="primary"
class="q-ml-sm"
:disabled="isLoading"
:loading="isLoading"
data-cy="FormModelPopup_isSaveAndContinue"
z-max
@click="() => (isSaveAndContinue = true)"
/>
<QBtn <QBtn
:label="t('globals.cancel')" :label="t('globals.cancel')"
:title="t('globals.cancel')" :title="t('globals.cancel')"
@ -79,15 +59,10 @@ defineExpose({
flat flat
:disabled="isLoading" :disabled="isLoading"
:loading="isLoading" :loading="isLoading"
data-cy="FormModelPopup_cancel" @click="emit('onDataCanceled')"
v-close-popup v-close-popup
data-cy="FormModelPopup_cancel"
z-max z-max
@click="
() => {
isSaveAndContinue = false;
emit('onDataCanceled');
}
"
/> />
<QBtn <QBtn
:label="t('globals.save')" :label="t('globals.save')"
@ -99,7 +74,6 @@ defineExpose({
:loading="isLoading" :loading="isLoading"
data-cy="FormModelPopup_save" data-cy="FormModelPopup_save"
z-max z-max
@click="() => (isSaveAndContinue = false)"
/> />
</div> </div>
</template> </template>

View File

@ -328,6 +328,7 @@ en:
active: Is active active: Is active
visible: Is visible visible: Is visible
floramondo: Is floramondo floramondo: Is floramondo
salesPersonFk: Buyer
categoryFk: Category categoryFk: Category
es: es:
@ -338,6 +339,7 @@ es:
active: Activo active: Activo
visible: Visible visible: Visible
floramondo: Floramondo floramondo: Floramondo
salesPersonFk: Comprador
categoryFk: Categoría categoryFk: Categoría
Plant: Planta natural Plant: Planta natural
Flower: Flor fresca Flower: Flor fresca

View File

@ -26,7 +26,6 @@ const itemComputed = computed(() => {
:to="{ name: itemComputed.name }" :to="{ name: itemComputed.name }"
clickable clickable
v-ripple v-ripple
:data-cy="`${itemComputed.name}-menu-item`"
> >
<QItemSection avatar v-if="itemComputed.icon"> <QItemSection avatar v-if="itemComputed.icon">
<QIcon :name="itemComputed.icon" /> <QIcon :name="itemComputed.icon" />

View File

@ -9,7 +9,6 @@ import VnSelect from 'components/common/VnSelect.vue';
import FormPopup from './FormPopup.vue'; import FormPopup from './FormPopup.vue';
import axios from 'axios'; import axios from 'axios';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import VnCheckbox from 'src/components/common/VnCheckbox.vue';
const $props = defineProps({ const $props = defineProps({
invoiceOutData: { invoiceOutData: {
@ -132,11 +131,15 @@ const refund = async () => {
:required="true" :required="true"
/> </VnRow /> </VnRow
><VnRow> ><VnRow>
<VnCheckbox <div>
v-model="invoiceParams.inheritWarehouse" <QCheckbox
:label="t('Inherit warehouse')" :label="t('Inherit warehouse')"
:info="t('Inherit warehouse tooltip')" v-model="invoiceParams.inheritWarehouse"
/> />
<QIcon name="info" class="cursor-info q-ml-sm" size="sm">
<QTooltip>{{ t('Inherit warehouse tooltip') }}</QTooltip>
</QIcon>
</div>
</VnRow> </VnRow>
</template> </template>
</FormPopup> </FormPopup>

View File

@ -10,7 +10,6 @@ import VnSelect from 'components/common/VnSelect.vue';
import FormPopup from './FormPopup.vue'; import FormPopup from './FormPopup.vue';
import axios from 'axios'; import axios from 'axios';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import VnCheckbox from './common/VnCheckbox.vue';
const $props = defineProps({ const $props = defineProps({
invoiceOutData: { invoiceOutData: {
@ -187,11 +186,15 @@ const makeInvoice = async () => {
/> />
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnCheckbox <div>
v-model="checked" <QCheckbox
:label="t('Bill destination client')" :label="t('Bill destination client')"
:info="t('transferInvoiceInfo')" v-model="checked"
/> />
<QIcon name="info" class="cursor-info q-ml-sm" size="sm">
<QTooltip>{{ t('transferInvoiceInfo') }}</QTooltip>
</QIcon>
</div>
</VnRow> </VnRow>
</template> </template>
</FormPopup> </FormPopup>

View File

@ -1,8 +1,9 @@
<script setup> <script setup>
import { markRaw, computed } from 'vue'; import { markRaw, computed } from 'vue';
import { QIcon, QCheckbox, QToggle } from 'quasar'; import { QIcon, QCheckbox } from 'quasar';
import { dashIfEmpty } from 'src/filters'; import { dashIfEmpty } from 'src/filters';
/* basic input */
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import VnSelectCache from 'components/common/VnSelectCache.vue'; import VnSelectCache from 'components/common/VnSelectCache.vue';
import VnInput from 'components/common/VnInput.vue'; import VnInput from 'components/common/VnInput.vue';
@ -11,11 +12,8 @@ import VnInputDate from 'components/common/VnInputDate.vue';
import VnInputTime from 'components/common/VnInputTime.vue'; import VnInputTime from 'components/common/VnInputTime.vue';
import VnComponent from 'components/common/VnComponent.vue'; import VnComponent from 'components/common/VnComponent.vue';
import VnUserLink from 'components/ui/VnUserLink.vue'; import VnUserLink from 'components/ui/VnUserLink.vue';
import VnSelectEnum from '../common/VnSelectEnum.vue';
import VnCheckbox from '../common/VnCheckbox.vue';
const model = defineModel(undefined, { required: true }); const model = defineModel(undefined, { required: true });
const emit = defineEmits(['blur']);
const $props = defineProps({ const $props = defineProps({
column: { column: {
type: Object, type: Object,
@ -41,18 +39,10 @@ const $props = defineProps({
type: Object, type: Object,
default: null, default: null,
}, },
autofocus: {
type: Boolean,
default: false,
},
showLabel: { showLabel: {
type: Boolean, type: Boolean,
default: null, default: null,
}, },
eventHandlers: {
type: Object,
default: null,
},
}); });
const defaultSelect = { const defaultSelect = {
@ -109,8 +99,7 @@ const defaultComponents = {
}, },
}, },
checkbox: { checkbox: {
ref: 'checkbox', component: markRaw(QCheckbox),
component: markRaw(VnCheckbox),
attrs: ({ model }) => { attrs: ({ model }) => {
const defaultAttrs = { const defaultAttrs = {
disable: !$props.isEditable, disable: !$props.isEditable,
@ -126,10 +115,6 @@ const defaultComponents = {
}, },
forceAttrs: { forceAttrs: {
label: $props.showLabel && $props.column.label, label: $props.showLabel && $props.column.label,
autofocus: true,
},
events: {
blur: () => emit('blur'),
}, },
}, },
select: { select: {
@ -140,19 +125,12 @@ const defaultComponents = {
component: markRaw(VnSelect), component: markRaw(VnSelect),
...defaultSelect, ...defaultSelect,
}, },
selectEnum: {
component: markRaw(VnSelectEnum),
...defaultSelect,
},
icon: { icon: {
component: markRaw(QIcon), component: markRaw(QIcon),
}, },
userLink: { userLink: {
component: markRaw(VnUserLink), component: markRaw(VnUserLink),
}, },
toggle: {
component: markRaw(QToggle),
},
}; };
const value = computed(() => { const value = computed(() => {
@ -182,28 +160,7 @@ const col = computed(() => {
return newColumn; return newColumn;
}); });
const components = computed(() => { const components = computed(() => $props.components ?? defaultComponents);
const sourceComponents = $props.components ?? defaultComponents;
return Object.keys(sourceComponents).reduce((acc, key) => {
const component = sourceComponents[key];
if (!component || typeof component !== 'object') {
acc[key] = component;
return acc;
}
acc[key] = {
...component,
attrs: {
...(component.attrs || {}),
autofocus: $props.autofocus,
},
event: { ...component?.event, ...$props?.eventHandlers },
};
return acc;
}, {});
});
</script> </script>
<template> <template>
<div class="row no-wrap"> <div class="row no-wrap">

View File

@ -1,12 +1,14 @@
<script setup> <script setup>
import { markRaw, computed } from 'vue'; import { markRaw, computed } from 'vue';
import { QCheckbox, QToggle } from 'quasar'; import { QCheckbox } from 'quasar';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
/* basic input */
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import VnInput from 'components/common/VnInput.vue'; import VnInput from 'components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import VnInputTime from 'components/common/VnInputTime.vue'; import VnInputTime from 'components/common/VnInputTime.vue';
import VnColumn from 'components/VnTable/VnColumn.vue'; import VnTableColumn from 'components/VnTable/VnColumn.vue';
const $props = defineProps({ const $props = defineProps({
column: { column: {
@ -25,10 +27,6 @@ const $props = defineProps({
type: String, type: String,
default: 'table', default: 'table',
}, },
customClass: {
type: String,
default: '',
},
}); });
defineExpose({ addFilter, props: $props }); defineExpose({ addFilter, props: $props });
@ -36,7 +34,7 @@ defineExpose({ addFilter, props: $props });
const model = defineModel(undefined, { required: true }); const model = defineModel(undefined, { required: true });
const arrayData = useArrayData( const arrayData = useArrayData(
$props.dataKey, $props.dataKey,
$props.searchUrl ? { searchUrl: $props.searchUrl } : null, $props.searchUrl ? { searchUrl: $props.searchUrl } : null
); );
const columnFilter = computed(() => $props.column?.columnFilter); const columnFilter = computed(() => $props.column?.columnFilter);
@ -48,18 +46,19 @@ const enterEvent = {
const defaultAttrs = { const defaultAttrs = {
filled: !$props.showTitle, filled: !$props.showTitle,
class: 'q-px-xs q-pb-xs q-pt-none fit',
dense: true, dense: true,
}; };
const forceAttrs = { const forceAttrs = {
label: $props.showTitle ? '' : (columnFilter.value?.label ?? $props.column.label), label: $props.showTitle ? '' : columnFilter.value?.label ?? $props.column.label,
}; };
const selectComponent = { const selectComponent = {
component: markRaw(VnSelect), component: markRaw(VnSelect),
event: updateEvent, event: updateEvent,
attrs: { attrs: {
class: `q-pt-none fit ${$props.customClass}`, class: 'q-px-sm q-pb-xs q-pt-none fit',
dense: true, dense: true,
filled: !$props.showTitle, filled: !$props.showTitle,
}, },
@ -110,24 +109,14 @@ const components = {
component: markRaw(QCheckbox), component: markRaw(QCheckbox),
event: updateEvent, event: updateEvent,
attrs: { attrs: {
class: $props.showTitle ? 'q-py-sm' : 'q-px-md q-py-xs fit', dense: true,
class: $props.showTitle ? 'q-py-sm q-mt-md' : 'q-px-md q-py-xs fit',
'toggle-indeterminate': true, 'toggle-indeterminate': true,
size: 'sm',
}, },
forceAttrs, forceAttrs,
}, },
select: selectComponent, select: selectComponent,
rawSelect: selectComponent, rawSelect: selectComponent,
toggle: {
component: markRaw(QToggle),
event: updateEvent,
attrs: {
class: $props.showTitle ? 'q-py-sm' : 'q-px-md q-py-xs fit',
'toggle-indeterminate': true,
size: 'sm',
},
forceAttrs,
},
}; };
async function addFilter(value, name) { async function addFilter(value, name) {
@ -143,8 +132,19 @@ async function addFilter(value, name) {
await arrayData.addFilter({ params: { [field]: value } }); await arrayData.addFilter({ params: { [field]: value } });
} }
function alignRow() {
switch ($props.column.align) {
case 'left':
return 'justify-start items-start';
case 'right':
return 'justify-end items-end';
default:
return 'flex-center';
}
}
const showFilter = computed( const showFilter = computed(
() => $props.column?.columnFilter !== false && $props.column.name != 'tableActions', () => $props.column?.columnFilter !== false && $props.column.name != 'tableActions'
); );
const onTabPressed = async () => { const onTabPressed = async () => {
@ -152,8 +152,13 @@ const onTabPressed = async () => {
}; };
</script> </script>
<template> <template>
<div v-if="showFilter" class="full-width flex-center" style="overflow: hidden"> <div
<VnColumn v-if="showFilter"
class="full-width"
:class="alignRow()"
style="max-height: 45px; overflow: hidden"
>
<VnTableColumn
:column="$props.column" :column="$props.column"
default="input" default="input"
v-model="model" v-model="model"
@ -163,8 +168,3 @@ const onTabPressed = async () => {
/> />
</div> </div>
</template> </template>
<style lang="scss" scoped>
label.vn-label-padding > .q-field__inner > .q-field__control {
padding: inherit !important;
}
</style>

View File

@ -41,7 +41,6 @@ async function orderBy(name, direction) {
break; break;
} }
if (!direction) return await arrayData.deleteOrder(name); if (!direction) return await arrayData.deleteOrder(name);
await arrayData.addOrder(name, direction); await arrayData.addOrder(name, direction);
} }
@ -52,11 +51,11 @@ defineExpose({ orderBy });
@mouseenter="hover = true" @mouseenter="hover = true"
@mouseleave="hover = false" @mouseleave="hover = false"
@click="orderBy(name, model?.direction)" @click="orderBy(name, model?.direction)"
class="row items-center no-wrap cursor-pointer title" class="row items-center no-wrap cursor-pointer"
> >
<span :title="label">{{ label }}</span> <span :title="label">{{ label }}</span>
<sup v-if="name && model?.index">
<QChip <QChip
v-if="name"
:label="!vertical ? model?.index : ''" :label="!vertical ? model?.index : ''"
:icon=" :icon="
(model?.index || hover) && !vertical (model?.index || hover) && !vertical
@ -72,7 +71,7 @@ defineExpose({ orderBy });
]" ]"
class="no-box-shadow" class="no-box-shadow"
:clickable="true" :clickable="true"
style="min-width: 40px; max-height: 30px" style="min-width: 40px"
> >
<div <div
class="column flex-center" class="column flex-center"
@ -92,20 +91,5 @@ defineExpose({ orderBy });
/> />
</div> </div>
</QChip> </QChip>
</sup>
</div> </div>
</template> </template>
<style lang="scss" scoped>
.title {
display: flex;
justify-content: center;
align-items: center;
height: 30px;
width: 100%;
color: var(--vn-label-color);
}
sup {
vertical-align: super; /* Valor predeterminado */
/* También puedes usar otros valores como "baseline", "top", "text-top", etc. */
}
</style>

View File

@ -1,37 +1,22 @@
<script setup> <script setup>
import { import { ref, onBeforeMount, onMounted, computed, watch, useAttrs } from 'vue';
ref,
onBeforeMount,
onMounted,
onUnmounted,
computed,
watch,
h,
render,
inject,
useAttrs,
} from 'vue';
import { useArrayData } from 'src/composables/useArrayData';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useFilterParams } from 'src/composables/useFilterParams'; import { useFilterParams } from 'src/composables/useFilterParams';
import { dashIfEmpty } from 'src/filters';
import CrudModel from 'src/components/CrudModel.vue'; import CrudModel from 'src/components/CrudModel.vue';
import FormModelPopup from 'components/FormModelPopup.vue'; import FormModelPopup from 'components/FormModelPopup.vue';
import VnColumn from 'components/VnTable/VnColumn.vue'; import VnTableColumn from 'components/VnTable/VnColumn.vue';
import VnFilter from 'components/VnTable/VnFilter.vue'; import VnFilter from 'components/VnTable/VnFilter.vue';
import VnTableChip from 'components/VnTable/VnChip.vue'; import VnTableChip from 'components/VnTable/VnChip.vue';
import VnVisibleColumn from 'src/components/VnTable/VnVisibleColumn.vue'; import VnVisibleColumn from 'src/components/VnTable/VnVisibleColumn.vue';
import VnLv from 'components/ui/VnLv.vue'; import VnLv from 'components/ui/VnLv.vue';
import VnTableOrder from 'src/components/VnTable/VnOrder.vue'; import VnTableOrder from 'src/components/VnTable/VnOrder.vue';
import VnTableFilter from './VnTableFilter.vue'; import VnTableFilter from './VnTableFilter.vue';
import { getColAlign } from 'src/composables/getColAlign';
const arrayData = useArrayData(useAttrs()['data-key']);
const $props = defineProps({ const $props = defineProps({
columns: { columns: {
type: Array, type: Array,
@ -57,6 +42,10 @@ const $props = defineProps({
type: [Function, Boolean], type: [Function, Boolean],
default: null, default: null,
}, },
rowCtrlClick: {
type: [Function, Boolean],
default: null,
},
redirect: { redirect: {
type: String, type: String,
default: null, default: null,
@ -125,19 +114,7 @@ const $props = defineProps({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
withFilters: {
type: Boolean,
default: true,
},
overlay: {
type: Boolean,
default: false,
},
createComplement: {
type: Object,
},
}); });
const { t } = useI18n(); const { t } = useI18n();
const stateStore = useStateStore(); const stateStore = useStateStore();
const route = useRoute(); const route = useRoute();
@ -155,18 +132,10 @@ const showForm = ref(false);
const splittedColumns = ref({ columns: [] }); const splittedColumns = ref({ columns: [] });
const columnsVisibilitySkipped = ref(); const columnsVisibilitySkipped = ref();
const createForm = ref(); const createForm = ref();
const createRef = ref(null);
const tableRef = ref(); const tableRef = ref();
const params = ref(useFilterParams($attrs['data-key']).params); const params = ref(useFilterParams($attrs['data-key']).params);
const orders = ref(useFilterParams($attrs['data-key']).orders); const orders = ref(useFilterParams($attrs['data-key']).orders);
const app = inject('app');
const editingRow = ref(null);
const editingField = ref(null);
const isTableMode = computed(() => mode.value == TABLE_MODE);
const showRightIcon = computed(() => $props.rightSearch || $props.rightSearchIcon);
const selectRegex = /select/;
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
const tableModes = [ const tableModes = [
{ {
icon: 'view_column', icon: 'view_column',
@ -187,8 +156,7 @@ onBeforeMount(() => {
hasParams.value = urlParams && Object.keys(urlParams).length !== 0; hasParams.value = urlParams && Object.keys(urlParams).length !== 0;
}); });
onMounted(async () => { onMounted(() => {
if ($props.isEditable) document.addEventListener('click', clickHandler);
mode.value = mode.value =
quasar.platform.is.mobile && !$props.disableOption?.card quasar.platform.is.mobile && !$props.disableOption?.card
? CARD_MODE ? CARD_MODE
@ -210,25 +178,14 @@ onMounted(async () => {
} }
}); });
onUnmounted(async () => {
if ($props.isEditable) document.removeEventListener('click', clickHandler);
});
watch( watch(
() => $props.columns, () => $props.columns,
(value) => splitColumns(value), (value) => splitColumns(value),
{ immediate: true }, { immediate: true },
); );
defineExpose({ const isTableMode = computed(() => mode.value == TABLE_MODE);
create: createForm, const showRightIcon = computed(() => $props.rightSearch || $props.rightSearchIcon);
reload,
redirect: redirectFn,
selected,
CrudModelRef,
params,
tableRef,
});
function splitColumns(columns) { function splitColumns(columns) {
splittedColumns.value = { splittedColumns.value = {
@ -274,6 +231,16 @@ const rowClickFunction = computed(() => {
return () => {}; return () => {};
}); });
const rowCtrlClickFunction = computed(() => {
if ($props.rowCtrlClick != undefined) return $props.rowCtrlClick;
if ($props.redirect)
return (evt, { id }) => {
stopEventPropagation(evt);
window.open(`/#/${$props.redirect}/${id}`, '_blank');
};
return () => {};
});
function redirectFn(id) { function redirectFn(id) {
router.push({ path: `/${$props.redirect}/${id}` }); router.push({ path: `/${$props.redirect}/${id}` });
} }
@ -295,6 +262,21 @@ function columnName(col) {
return name; return name;
} }
function getColAlign(col) {
return 'text-' + (col.align ?? 'left');
}
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
defineExpose({
create: createForm,
reload,
redirect: redirectFn,
selected,
CrudModelRef,
params,
tableRef,
});
function handleOnDataSaved(_) { function handleOnDataSaved(_) {
if (_.onDataSaved) _.onDataSaved({ CrudModelRef: CrudModelRef.value }); if (_.onDataSaved) _.onDataSaved({ CrudModelRef: CrudModelRef.value });
else $props.create.onDataSaved(_); else $props.create.onDataSaved(_);
@ -322,214 +304,6 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
} }
} }
} }
function isEditableColumn(column) {
const isEditableCol = column?.isEditable ?? true;
const isVisible = column?.visible ?? true;
const hasComponent = column?.component;
return $props.isEditable && isVisible && hasComponent && isEditableCol;
}
function hasEditableFormat(column) {
if (isEditableColumn(column)) return 'editable-text';
}
const clickHandler = async (event) => {
const clickedElement = event.target.closest('td');
const isDateElement = event.target.closest('.q-date');
const isTimeElement = event.target.closest('.q-time');
if (isDateElement || isTimeElement) return;
if (clickedElement === null) {
destroyInput(editingRow.value, editingField.value);
return;
}
const rowIndex = clickedElement.getAttribute('data-row-index');
const colField = clickedElement.getAttribute('data-col-field');
const column = $props.columns.find((col) => col.name === colField);
if (editingRow.value !== null && editingField.value !== null) {
if (editingRow.value === rowIndex && editingField.value === colField) {
return;
}
destroyInput(editingRow.value, editingField.value);
}
if (isEditableColumn(column))
await renderInput(Number(rowIndex), colField, clickedElement);
};
async function handleTabKey(event, rowIndex, colField) {
if (editingRow.value == rowIndex && editingField.value == colField)
destroyInput(editingRow.value, editingField.value);
const direction = event.shiftKey ? -1 : 1;
const { nextRowIndex, nextColumnName } = await handleTabNavigation(
rowIndex,
colField,
direction,
);
if (nextRowIndex < 0 || nextRowIndex >= arrayData.store.data.length) return;
event.preventDefault();
await renderInput(nextRowIndex, nextColumnName, null);
}
async function renderInput(rowId, field, clickedElement) {
editingField.value = field;
editingRow.value = rowId;
const originalColumn = $props.columns.find((col) => col.name === field);
const column = { ...originalColumn, ...{ label: '' } };
const row = CrudModelRef.value.formData[rowId];
const oldValue = CrudModelRef.value.formData[rowId][column?.name];
if (!clickedElement)
clickedElement = document.querySelector(
`[data-row-index="${rowId}"][data-col-field="${field}"]`,
);
Array.from(clickedElement.childNodes).forEach((child) => {
child.style.visibility = 'hidden';
child.style.position = 'relative';
});
const isSelect = selectRegex.test(column?.component);
if (isSelect) column.attrs = { ...column.attrs, 'emit-value': false };
const node = h(VnColumn, {
row: row,
class: 'temp-input',
column: column,
modelValue: row[column.name],
componentProp: 'columnField',
autofocus: true,
focusOnMount: true,
eventHandlers: {
'update:modelValue': async (value) => {
if (isSelect) {
row[column.name] = value[column.attrs?.optionValue ?? 'id'];
row[column?.name + 'TextValue'] =
value[column.attrs?.optionLabel ?? 'name'];
await column?.cellEvent?.['update:modelValue']?.(
value,
oldValue,
row,
);
} else row[column.name] = value;
await column?.cellEvent?.['update:modelValue']?.(value, oldValue, row);
},
keyup: async (event) => {
if (event.key === 'Enter') handleBlur(rowId, field, clickedElement);
},
keydown: async (event) => {
switch (event.key) {
case 'Tab':
await handleTabKey(event, rowId, field);
event.stopPropagation();
break;
case 'Escape':
destroyInput(rowId, field, clickedElement);
break;
default:
break;
}
},
click: (event) => {
column?.cellEvent?.['click']?.(event, row);
},
},
});
node.appContext = app._context;
render(node, clickedElement);
if (['checkbox', 'toggle', undefined].includes(column?.component))
node.el?.querySelector('span > div').focus();
}
function destroyInput(rowIndex, field, clickedElement) {
if (!clickedElement)
clickedElement = document.querySelector(
`[data-row-index="${rowIndex}"][data-col-field="${field}"]`,
);
if (clickedElement) {
render(null, clickedElement);
Array.from(clickedElement.childNodes).forEach((child) => {
child.style.visibility = 'visible';
child.style.position = '';
});
}
if (editingRow.value !== rowIndex || editingField.value !== field) return;
editingRow.value = null;
editingField.value = null;
}
function handleBlur(rowIndex, field, clickedElement) {
destroyInput(rowIndex, field, clickedElement);
}
async function handleTabNavigation(rowIndex, colName, direction) {
const columns = $props.columns;
const totalColumns = columns.length;
let currentColumnIndex = columns.findIndex((col) => col.name === colName);
let iterations = 0;
let newColumnIndex = currentColumnIndex;
do {
iterations++;
newColumnIndex = (newColumnIndex + direction + totalColumns) % totalColumns;
if (isEditableColumn(columns[newColumnIndex])) break;
} while (iterations < totalColumns);
if (iterations >= totalColumns) {
return;
}
if (direction === 1 && newColumnIndex <= currentColumnIndex) {
rowIndex++;
} else if (direction === -1 && newColumnIndex >= currentColumnIndex) {
rowIndex--;
}
return { nextRowIndex: rowIndex, nextColumnName: columns[newColumnIndex].name };
}
function getCheckboxIcon(value) {
switch (typeof value) {
case 'boolean':
return value ? 'check' : 'close';
case 'number':
return value === 0 ? 'close' : 'check';
case 'undefined':
return 'indeterminate_check_box';
default:
return 'indeterminate_check_box';
}
}
function getToggleIcon(value) {
if (value === null) return 'help_outline';
return value ? 'toggle_on' : 'toggle_off';
}
function formatColumnValue(col, row, dashIfEmpty) {
if (col?.format) {
if (selectRegex.test(col?.component) && row[col?.name + 'TextValue']) {
return dashIfEmpty(row[col?.name + 'TextValue']);
} else {
return col.format(row, dashIfEmpty);
}
} else {
return dashIfEmpty(row[col?.name]);
}
}
const checkbox = ref(null);
</script> </script>
<template> <template>
<QDrawer <QDrawer
@ -537,7 +311,7 @@ const checkbox = ref(null);
v-model="stateStore.rightDrawer" v-model="stateStore.rightDrawer"
side="right" side="right"
:width="256" :width="256"
:overlay="$props.overlay" show-if-above
> >
<QScrollArea class="fit"> <QScrollArea class="fit">
<VnTableFilter <VnTableFilter
@ -558,7 +332,7 @@ const checkbox = ref(null);
<CrudModel <CrudModel
v-bind="$attrs" v-bind="$attrs"
:class="$attrs['class'] ?? 'q-px-md'" :class="$attrs['class'] ?? 'q-px-md'"
:limit="$attrs['limit'] ?? 100" :limit="$attrs['limit'] ?? 20"
ref="CrudModelRef" ref="CrudModelRef"
@on-fetch="(...args) => emit('onFetch', ...args)" @on-fetch="(...args) => emit('onFetch', ...args)"
:search-url="searchUrl" :search-url="searchUrl"
@ -574,12 +348,8 @@ const checkbox = ref(null);
<QTable <QTable
ref="tableRef" ref="tableRef"
v-bind="table" v-bind="table"
:class="[ class="vnTable"
'vnTable', :class="{ 'last-row-sticky': $props.footer }"
table ? 'selection-cell' : '',
$props.footer ? 'last-row-sticky' : '',
]"
wrap-cells
:columns="splittedColumns.columns" :columns="splittedColumns.columns"
:rows="rows" :rows="rows"
v-model:selected="selected" v-model:selected="selected"
@ -598,7 +368,6 @@ const checkbox = ref(null);
<slot name="top-left"></slot> <slot name="top-left"></slot>
</template> </template>
<template #top-right v-if="!$props.withoutHeader"> <template #top-right v-if="!$props.withoutHeader">
<slot name="top-right"></slot>
<VnVisibleColumn <VnVisibleColumn
v-if="isTableMode" v-if="isTableMode"
v-model="splittedColumns.columns" v-model="splittedColumns.columns"
@ -612,7 +381,6 @@ const checkbox = ref(null);
dense dense
:options="tableModes.filter((mode) => !mode.disable)" :options="tableModes.filter((mode) => !mode.disable)"
/> />
<QBtn <QBtn
v-if="showRightIcon" v-if="showRightIcon"
icon="filter_alt" icon="filter_alt"
@ -624,38 +392,32 @@ const checkbox = ref(null);
<template #header-cell="{ col }"> <template #header-cell="{ col }">
<QTh <QTh
v-if="col.visible ?? true" v-if="col.visible ?? true"
class="body-cell" :style="col.headerStyle"
:style="col?.width ? `max-width: ${col?.width}` : ''" :class="col.headerClass"
style="padding: inherit"
> >
<div <div
class="no-padding" class="column ellipsis"
:style=" :class="`text-${col?.align ?? 'left'}`"
withFilters && $props.columnSearch ? 'height: 75px' : '' :style="$props.columnSearch ? 'height: 75px' : ''"
"
> >
<div class="text-center" style="height: 30px"> <div class="row items-center no-wrap" style="height: 30px">
<QTooltip v-if="col.toolTip">{{ col.toolTip }}</QTooltip> <QTooltip v-if="col.toolTip">{{ col.toolTip }}</QTooltip>
<VnTableOrder <VnTableOrder
v-model="orders[col.orderBy ?? col.name]" v-model="orders[col.orderBy ?? col.name]"
:name="col.orderBy ?? col.name" :name="col.orderBy ?? col.name"
:label="col?.labelAbbreviation ?? col?.label" :label="col?.label"
:data-key="$attrs['data-key']" :data-key="$attrs['data-key']"
:search-url="searchUrl" :search-url="searchUrl"
/> />
</div> </div>
<VnFilter <VnFilter
v-if=" v-if="$props.columnSearch"
$props.columnSearch &&
col.columnSearch !== false &&
withFilters
"
:column="col" :column="col"
:show-title="true" :show-title="true"
:data-key="$attrs['data-key']" :data-key="$attrs['data-key']"
v-model="params[columnName(col)]" v-model="params[columnName(col)]"
:search-url="searchUrl" :search-url="searchUrl"
customClass="header-filter" class="full-width"
/> />
</div> </div>
</QTh> </QTh>
@ -673,27 +435,16 @@ const checkbox = ref(null);
</QTd> </QTd>
</template> </template>
<template #body-cell="{ col, row, rowIndex }"> <template #body-cell="{ col, row, rowIndex }">
<!-- Columns -->
<QTd <QTd
class="no-margin q-px-xs" auto-width
class="no-margin"
:class="[getColAlign(col), col.columnClass]"
:style="col.style"
v-if="col.visible ?? true" v-if="col.visible ?? true"
:style="{ @click.ctrl="
'max-width': col?.width ?? false, ($event) =>
position: 'relative', rowCtrlClickFunction && rowCtrlClickFunction($event, row)
}"
:class="[
col.columnClass,
'body-cell no-margin no-padding',
getColAlign(col),
]"
:data-row-index="rowIndex"
:data-col-field="col?.name"
>
<div
class="no-padding no-margin peter"
style="
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
" "
> >
<slot <slot
@ -702,34 +453,14 @@ const checkbox = ref(null);
:row="row" :row="row"
:row-index="rowIndex" :row-index="rowIndex"
> >
<QIcon <VnTableColumn
v-if="col?.component === 'toggle'" :column="col"
:name=" :row="row"
col?.getIcon :is-editable="col.isEditable ?? isEditable"
? col.getIcon(row[col?.name]) v-model="row[col.name]"
: getToggleIcon(row[col?.name]) component-prop="columnField"
"
style="color: var(--vn-text-color)"
:class="hasEditableFormat(col)"
size="14px"
/> />
<QIcon
v-else-if="col?.component === 'checkbox'"
:name="getCheckboxIcon(row[col?.name])"
style="color: var(--vn-text-color)"
:class="hasEditableFormat(col)"
size="14px"
/>
<span
v-else
:class="hasEditableFormat(col)"
:style="col?.style ? col.style(row) : null"
style="bottom: 0"
>
{{ formatColumnValue(col, row, dashIfEmpty) }}
</span>
</slot> </slot>
</div>
</QTd> </QTd>
</template> </template>
<template #body-cell-tableActions="{ col, row }"> <template #body-cell-tableActions="{ col, row }">
@ -832,7 +563,7 @@ const checkbox = ref(null);
:row="row" :row="row"
:row-index="index" :row-index="index"
> >
<VnColumn <VnTableColumn
:column="col" :column="col"
:row="row" :row="row"
:is-editable="false" :is-editable="false"
@ -872,17 +603,14 @@ const checkbox = ref(null);
</component> </component>
</template> </template>
<template #bottom-row="{ cols }" v-if="$props.footer"> <template #bottom-row="{ cols }" v-if="$props.footer">
<QTr v-if="rows.length" style="height: 45px"> <QTr v-if="rows.length" style="height: 30px">
<QTh v-if="table.selection" />
<QTh <QTh
v-for="col of cols.filter((cols) => cols.visible ?? true)" v-for="col of cols.filter((cols) => cols.visible ?? true)"
:key="col?.id" :key="col?.id"
class="text-center"
:class="getColAlign(col)" :class="getColAlign(col)"
> >
<slot <slot :name="`column-footer-${col.name}`" />
:name="`column-footer-${col.name}`"
:isEditableColumn="isEditableColumn(col)"
/>
</QTh> </QTh>
</QTr> </QTr>
</template> </template>
@ -926,33 +654,14 @@ const checkbox = ref(null);
{{ createForm?.title }} {{ createForm?.title }}
</QTooltip> </QTooltip>
</QPageSticky> </QPageSticky>
<QDialog <QDialog v-model="showForm" transition-show="scale" transition-hide="scale">
v-model="showForm"
transition-show="scale"
transition-hide="scale"
:full-width="createComplement?.isFullWidth ?? false"
@before-hide="
() => {
if (createRef.isSaveAndContinue) {
showForm = true;
createForm.formInitialData = { ...create.formInitialData };
}
}
"
data-cy="vn-table-create-dialog"
>
<FormModelPopup <FormModelPopup
ref="createRef"
v-bind="createForm" v-bind="createForm"
:model="$attrs['data-key'] + 'Create'" :model="$attrs['data-key'] + 'Create'"
@on-data-saved="(_, res) => createForm.onDataSaved(res)" @on-data-saved="(_, res) => createForm.onDataSaved(res)"
> >
<template #form-inputs="{ data }"> <template #form-inputs="{ data }">
<div :style="createComplement?.containerStyle"> <div class="grid-create">
<div>
<slot name="previous-create-dialog" :data="data" />
</div>
<div class="grid-create" :style="createComplement?.columnGridStyle">
<slot <slot
v-for="column of splittedColumns.create" v-for="column of splittedColumns.create"
:key="column.name" :key="column.name"
@ -961,19 +670,17 @@ const checkbox = ref(null);
:column-name="column.name" :column-name="column.name"
:label="column.label" :label="column.label"
> >
<VnColumn <VnTableColumn
:column="column" :column="column"
:row="{}" :row="{}"
default="input" default="input"
v-model="data[column.name]" v-model="data[column.name]"
:show-label="true" :show-label="true"
component-prop="columnCreate" component-prop="columnCreate"
:data-cy="`${column.name}-create-popup`"
/> />
</slot> </slot>
<slot name="more-create-dialog" :data="data" /> <slot name="more-create-dialog" :data="data" />
</div> </div>
</div>
</template> </template>
</FormModelPopup> </FormModelPopup>
</QDialog> </QDialog>
@ -990,42 +697,6 @@ es:
</i18n> </i18n>
<style lang="scss"> <style lang="scss">
.selection-cell {
table td:first-child {
padding: 0px;
}
}
.side-padding {
padding-left: 1px;
padding-right: 1px;
}
.editable-text:hover {
border-bottom: 1px dashed var(--q-primary);
@extend .side-padding;
}
.editable-text {
border-bottom: 1px dashed var(--vn-label-color);
@extend .side-padding;
}
.cell-input {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding-top: 0px !important;
}
.q-field--labeled .q-field__native,
.q-field--labeled .q-field__prefix,
.q-field--labeled .q-field__suffix {
padding-top: 20px;
}
.body-cell {
padding-left: 2px !important;
padding-right: 2px !important;
position: relative;
}
.bg-chip-secondary { .bg-chip-secondary {
background-color: var(--vn-page-color); background-color: var(--vn-page-color);
color: var(--vn-text-color); color: var(--vn-text-color);
@ -1042,7 +713,7 @@ es:
.grid-three { .grid-three {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, max-content)); grid-template-columns: repeat(auto-fit, minmax(350px, max-content));
max-width: 100%; max-width: 100%;
grid-gap: 20px; grid-gap: 20px;
margin: 0 auto; margin: 0 auto;
@ -1051,6 +722,7 @@ es:
.grid-create { .grid-create {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, max-content)); grid-template-columns: repeat(auto-fit, minmax(150px, max-content));
max-width: 100%;
grid-gap: 20px; grid-gap: 20px;
margin: 0 auto; margin: 0 auto;
} }
@ -1066,9 +738,7 @@ es:
} }
} }
} }
.q-table tbody tr td {
position: relative;
}
.q-table { .q-table {
th { th {
padding: 0; padding: 0;
@ -1168,15 +838,4 @@ es:
.q-table__middle.q-virtual-scroll.q-virtual-scroll--vertical.scroll { .q-table__middle.q-virtual-scroll.q-virtual-scroll--vertical.scroll {
background-color: var(--vn-section-color); background-color: var(--vn-section-color);
} }
.temp-input {
top: 0;
position: absolute;
width: 100%;
height: 100%;
display: flex;
}
label.header-filter > .q-field__inner > .q-field__control {
padding: inherit;
}
</style> </style>

View File

@ -27,13 +27,12 @@ function columnName(col) {
</script> </script>
<template> <template>
<VnFilterPanel v-bind="$attrs" :search-button="true" :disable-submit-event="true"> <VnFilterPanel v-bind="$attrs" :search-button="true" :disable-submit-event="true">
<template #body="{ params, orders, searchFn }"> <template #body="{ params, orders }">
<div <div
class="container" class="row no-wrap flex-center"
v-for="col of columns.filter((c) => c.columnFilter ?? true)" v-for="col of columns.filter((c) => c.columnFilter ?? true)"
:key="col.id" :key="col.id"
> >
<div class="filter">
<VnFilter <VnFilter
ref="tableFilterRef" ref="tableFilterRef"
:column="col" :column="col"
@ -41,8 +40,6 @@ function columnName(col) {
v-model="params[columnName(col)]" v-model="params[columnName(col)]"
:search-url="searchUrl" :search-url="searchUrl"
/> />
</div>
<div class="order">
<VnTableOrder <VnTableOrder
v-if="col?.columnFilter !== false && col?.name !== 'tableActions'" v-if="col?.columnFilter !== false && col?.name !== 'tableActions'"
v-model="orders[col.orderBy ?? col.name]" v-model="orders[col.orderBy ?? col.name]"
@ -52,11 +49,9 @@ function columnName(col) {
:vertical="true" :vertical="true"
/> />
</div> </div>
</div>
<slot <slot
name="moreFilterPanel" name="moreFilterPanel"
:params="params" :params="params"
:search-fn="searchFn"
:orders="orders" :orders="orders"
:columns="columns" :columns="columns"
/> />
@ -72,21 +67,3 @@ function columnName(col) {
</template> </template>
</VnFilterPanel> </VnFilterPanel>
</template> </template>
<style lang="scss" scoped>
.container {
display: flex;
justify-content: center;
align-items: center;
height: 45px;
gap: 10px;
}
.filter {
width: 70%;
height: 40px;
text-align: center;
}
.order {
width: 10%;
}
</style>

View File

@ -32,21 +32,16 @@ const areAllChecksMarked = computed(() => {
function setUserConfigViewData(data, isLocal) { function setUserConfigViewData(data, isLocal) {
if (!data) return; if (!data) return;
// Importante: El name de las columnas de la tabla debe conincidir con el name de las variables que devuelve la view config
if (!isLocal) localColumns.value = []; if (!isLocal) localColumns.value = [];
// Array to Object
const skippeds = $props.skip.reduce((a, v) => ({ ...a, [v]: v }), {}); const skippeds = $props.skip.reduce((a, v) => ({ ...a, [v]: v }), {});
for (let column of columns.value) { for (let column of columns.value) {
const { label, name, labelAbbreviation } = column; const { label, name } = column;
if (skippeds[name]) continue; if (skippeds[name]) continue;
column.visible = data[name] ?? true; column.visible = data[name] ?? true;
if (!isLocal) if (!isLocal) localColumns.value.push({ name, label, visible: column.visible });
localColumns.value.push({
name,
label,
labelAbbreviation,
visible: column.visible,
});
} }
} }
@ -157,11 +152,7 @@ onMounted(async () => {
<QCheckbox <QCheckbox
v-for="col in localColumns" v-for="col in localColumns"
:key="col.name" :key="col.name"
:label=" :label="col.label ?? col.name"
col?.labelAbbreviation
? col.labelAbbreviation + ` (${col.label ?? col.name})`
: (col.label ?? col.name)
"
v-model="col.visible" v-model="col.visible"
/> />
</div> </div>

View File

@ -1,13 +1,9 @@
import { vi, describe, expect, it, beforeEach, afterEach } from 'vitest'; import { vi, describe, expect, it, beforeEach, beforeAll, afterEach } from 'vitest';
import { createWrapper } from 'app/test/vitest/helper'; import { createWrapper } from 'app/test/vitest/helper';
import UserPanel from 'src/components/UserPanel.vue'; import UserPanel from 'src/components/UserPanel.vue';
import axios from 'axios'; import axios from 'axios';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
vi.mock('src/utils/quasarLang', () => ({
default: vi.fn(),
}));
describe('UserPanel', () => { describe('UserPanel', () => {
let wrapper; let wrapper;
let vm; let vm;
@ -43,7 +39,7 @@ describe('UserPanel', () => {
await vm.saveDarkMode(true); await vm.saveDarkMode(true);
expect(axios.patch).toHaveBeenCalledWith('/UserConfigs/115', { darkMode: true }); expect(axios.patch).toHaveBeenCalledWith('/UserConfigs/115', { darkMode: true });
expect(vm.user.darkMode).toBe(true); expect(vm.user.darkMode).toBe(true);
await vm.updatePreferences(); vm.updatePreferences();
expect(vm.darkMode).toBe(true); expect(vm.darkMode).toBe(true);
}); });
@ -52,7 +48,7 @@ describe('UserPanel', () => {
await vm.saveLanguage(userLanguage); await vm.saveLanguage(userLanguage);
expect(axios.patch).toHaveBeenCalledWith('/VnUsers/115', { lang: userLanguage }); expect(axios.patch).toHaveBeenCalledWith('/VnUsers/115', { lang: userLanguage });
expect(vm.user.lang).toBe(userLanguage); expect(vm.user.lang).toBe(userLanguage);
await vm.updatePreferences(); vm.updatePreferences();
expect(vm.locale).toBe(userLanguage); expect(vm.locale).toBe(userLanguage);
}); });

View File

@ -1,43 +0,0 @@
<script setup>
import { computed } from 'vue';
const model = defineModel({ type: [Number, Boolean] });
const $props = defineProps({
info: {
type: String,
default: null,
},
});
const checkboxModel = computed({
get() {
if (typeof model.value === 'number') {
return model.value !== 0;
}
return model.value;
},
set(value) {
if (typeof model.value === 'number') {
model.value = value ? 1 : 0;
} else {
model.value = value;
}
},
});
</script>
<template>
<div>
<QCheckbox v-bind="$attrs" v-on="$attrs" v-model="checkboxModel" />
<QIcon
v-if="info"
v-bind="$attrs"
class="cursor-info q-ml-sm"
name="info"
size="sm"
>
<QTooltip>
{{ info }}
</QTooltip>
</QIcon>
</div>
</template>

View File

@ -1,32 +0,0 @@
<script setup>
const $props = defineProps({
colors: {
type: String,
default: '{"value":[]}',
},
});
const colorArray = JSON.parse($props.colors)?.value;
const maxHeight = 30;
const colorHeight = maxHeight / colorArray?.length;
</script>
<template>
<div class="color-div" :style="{ height: `${maxHeight}px` }">
<div
v-for="(color, index) in colorArray"
:key="index"
:style="{
backgroundColor: `#${color}`,
height: `${colorHeight}px`,
}"
>
&nbsp;
</div>
</div>
</template>
<style scoped>
.color-div {
display: flex;
flex-direction: column;
}
</style>

View File

@ -17,8 +17,6 @@ const $props = defineProps({
}, },
}); });
const emit = defineEmits(['blur']);
const componentArray = computed(() => { const componentArray = computed(() => {
if (typeof $props.prop === 'object') return [$props.prop]; if (typeof $props.prop === 'object') return [$props.prop];
return $props.prop; return $props.prop;
@ -56,7 +54,6 @@ function toValueAttrs(attrs) {
v-bind="mix(toComponent).attrs" v-bind="mix(toComponent).attrs"
v-on="mix(toComponent).event ?? {}" v-on="mix(toComponent).event ?? {}"
v-model="model" v-model="model"
@blur="emit('blur')"
/> />
</span> </span>
</template> </template>

View File

@ -11,7 +11,6 @@ const emit = defineEmits([
'update:options', 'update:options',
'keyup.enter', 'keyup.enter',
'remove', 'remove',
'blur',
]); ]);
const $props = defineProps({ const $props = defineProps({
@ -137,7 +136,6 @@ const handleUppercase = () => {
:type="$attrs.type" :type="$attrs.type"
:class="{ required: isRequired }" :class="{ required: isRequired }"
@keyup.enter="emit('keyup.enter')" @keyup.enter="emit('keyup.enter')"
@blur="emit('blur')"
@keydown="handleKeydown" @keydown="handleKeydown"
:clearable="false" :clearable="false"
:rules="mixinRules" :rules="mixinRules"
@ -145,7 +143,7 @@ const handleUppercase = () => {
hide-bottom-space hide-bottom-space
:data-cy="$attrs.dataCy ?? $attrs.label + '_input'" :data-cy="$attrs.dataCy ?? $attrs.label + '_input'"
> >
<template #prepend v-if="$slots.prepend"> <template #prepend>
<slot name="prepend" /> <slot name="prepend" />
</template> </template>
<template #append> <template #append>
@ -174,7 +172,7 @@ const handleUppercase = () => {
<QIcon <QIcon
name="match_case" name="match_case"
size="xs" size="xs"
v-if="!$attrs.disabled && !$attrs.readonly && $props.uppercase" v-if="!$attrs.disabled && !($attrs.readonly) && $props.uppercase"
@click="handleUppercase" @click="handleUppercase"
class="uppercase-icon" class="uppercase-icon"
> >
@ -196,9 +194,7 @@ const handleUppercase = () => {
<style> <style>
.uppercase-icon { .uppercase-icon {
transition: transition: color 0.3s, transform 0.2s;
color 0.3s,
transform 0.2s;
cursor: pointer; cursor: pointer;
} }

View File

@ -42,7 +42,7 @@ const formattedDate = computed({
if (value.at(2) == '/') value = value.split('/').reverse().join('/'); if (value.at(2) == '/') value = value.split('/').reverse().join('/');
value = date.formatDate( value = date.formatDate(
new Date(value).toISOString(), new Date(value).toISOString(),
'YYYY-MM-DDTHH:mm:ss.SSSZ', 'YYYY-MM-DDTHH:mm:ss.SSSZ'
); );
} }
const [year, month, day] = value.split('-').map((e) => parseInt(e)); const [year, month, day] = value.split('-').map((e) => parseInt(e));
@ -55,7 +55,7 @@ const formattedDate = computed({
orgDate.getHours(), orgDate.getHours(),
orgDate.getMinutes(), orgDate.getMinutes(),
orgDate.getSeconds(), orgDate.getSeconds(),
orgDate.getMilliseconds(), orgDate.getMilliseconds()
); );
} }
} }
@ -64,7 +64,7 @@ const formattedDate = computed({
}); });
const popupDate = computed(() => const popupDate = computed(() =>
model.value ? date.formatDate(new Date(model.value), 'YYYY/MM/DD') : model.value, model.value ? date.formatDate(new Date(model.value), 'YYYY/MM/DD') : model.value
); );
onMounted(() => { onMounted(() => {
// fix quasar bug // fix quasar bug
@ -73,7 +73,7 @@ onMounted(() => {
watch( watch(
() => model.value, () => model.value,
(val) => (formattedDate.value = val), (val) => (formattedDate.value = val),
{ immediate: true }, { immediate: true }
); );
const styleAttrs = computed(() => { const styleAttrs = computed(() => {

View File

@ -8,7 +8,6 @@ defineProps({
}); });
const model = defineModel({ type: [Number, String] }); const model = defineModel({ type: [Number, String] });
const emit = defineEmits(['blur']);
</script> </script>
<template> <template>
<VnInput <VnInput
@ -25,6 +24,5 @@ const emit = defineEmits(['blur']);
model = parseFloat(val).toFixed(decimalPlaces); model = parseFloat(val).toFixed(decimalPlaces);
} }
" "
@blur="emit('blur')"
/> />
</template> </template>

View File

@ -1,38 +0,0 @@
<script setup>
import { ref } from 'vue';
defineProps({
label: {
type: String,
default: '',
},
icon: {
type: String,
required: true,
default: null,
},
color: {
type: String,
default: 'primary',
},
tooltip: {
type: String,
default: null,
},
});
const popupProxyRef = ref(null);
</script>
<template>
<QBtn :color="$props.color" :icon="$props.icon" :label="$t($props.label)">
<template #default>
<slot name="extraIcon"></slot>
<QPopupProxy ref="popupProxyRef" style="max-width: none">
<QCard>
<slot :popup="popupProxyRef"></slot>
</QCard>
</QPopupProxy>
<QTooltip>{{ $t($props.tooltip) }}</QTooltip>
</template>
</QBtn>
</template>

View File

@ -106,14 +106,7 @@ function checkIsMain() {
:data-key="dataKey" :data-key="dataKey"
:array-data="arrayData" :array-data="arrayData"
:columns="columns" :columns="columns"
>
<template #moreFilterPanel="{ params, orders, searchFn }">
<slot
name="moreFilterPanel"
v-bind="{ params, orders, searchFn }"
/> />
</template>
</VnTableFilter>
</slot> </slot>
</template> </template>
</RightAdvancedMenu> </RightAdvancedMenu>

View File

@ -171,8 +171,7 @@ onMounted(() => {
}); });
const arrayDataKey = const arrayDataKey =
$props.dataKey ?? $props.dataKey ?? ($props.url?.length > 0 ? $props.url : $attrs.name ?? $attrs.label);
($props.url?.length > 0 ? $props.url : ($attrs.name ?? $attrs.label));
const arrayData = useArrayData(arrayDataKey, { const arrayData = useArrayData(arrayDataKey, {
url: $props.url, url: $props.url,
@ -221,7 +220,7 @@ async function fetchFilter(val) {
optionFilterValue.value ?? optionFilterValue.value ??
(new RegExp(/\d/g).test(val) (new RegExp(/\d/g).test(val)
? optionValue.value ? optionValue.value
: (optionFilter.value ?? optionLabel.value)); : optionFilter.value ?? optionLabel.value);
let defaultWhere = {}; let defaultWhere = {};
if ($props.filterOptions.length) { if ($props.filterOptions.length) {
@ -240,7 +239,7 @@ async function fetchFilter(val) {
const { data } = await arrayData.applyFilter( const { data } = await arrayData.applyFilter(
{ filter: filterOptions }, { filter: filterOptions },
{ updateRouter: false }, { updateRouter: false }
); );
setOptions(data); setOptions(data);
return data; return data;
@ -273,7 +272,7 @@ async function filterHandler(val, update) {
ref.setOptionIndex(-1); ref.setOptionIndex(-1);
ref.moveOptionSelection(1, true); ref.moveOptionSelection(1, true);
} }
}, }
); );
} }
@ -309,7 +308,7 @@ function handleKeyDown(event) {
if (inputValue) { if (inputValue) {
const matchingOption = myOptions.value.find( const matchingOption = myOptions.value.find(
(option) => (option) =>
option[optionLabel.value].toLowerCase() === inputValue.toLowerCase(), option[optionLabel.value].toLowerCase() === inputValue.toLowerCase()
); );
if (matchingOption) { if (matchingOption) {
@ -321,11 +320,11 @@ function handleKeyDown(event) {
} }
const focusableElements = document.querySelectorAll( const focusableElements = document.querySelectorAll(
'a:not([disabled]), button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), details:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])', 'a:not([disabled]), button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), details:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])'
); );
const currentIndex = Array.prototype.indexOf.call( const currentIndex = Array.prototype.indexOf.call(
focusableElements, focusableElements,
event.target, event.target
); );
if (currentIndex >= 0 && currentIndex < focusableElements.length - 1) { if (currentIndex >= 0 && currentIndex < focusableElements.length - 1) {
focusableElements[currentIndex + 1].focus(); focusableElements[currentIndex + 1].focus();

View File

@ -14,7 +14,7 @@ const $props = defineProps({
}, },
}); });
const options = ref([]); const options = ref([]);
const emit = defineEmits(['blur']);
onBeforeMount(async () => { onBeforeMount(async () => {
const { url, optionValue, optionLabel } = useAttrs(); const { url, optionValue, optionLabel } = useAttrs();
const findBy = $props.find ?? url?.charAt(0)?.toLocaleLowerCase() + url?.slice(1, -1); const findBy = $props.find ?? url?.charAt(0)?.toLocaleLowerCase() + url?.slice(1, -1);
@ -35,5 +35,5 @@ onBeforeMount(async () => {
}); });
</script> </script>
<template> <template>
<VnSelect v-bind="$attrs" :options="$attrs.options ?? options" @blur="emit('blur')" /> <VnSelect v-bind="$attrs" :options="$attrs.options ?? options" />
</template> </template>

View File

@ -37,6 +37,7 @@ const isAllowedToCreate = computed(() => {
defineExpose({ vnSelectDialogRef: select }); defineExpose({ vnSelectDialogRef: select });
</script> </script>
<template> <template>
<VnSelect <VnSelect
ref="select" ref="select"
@ -66,6 +67,7 @@ defineExpose({ vnSelectDialogRef: select });
</template> </template>
</VnSelect> </VnSelect>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.default-icon { .default-icon {
cursor: pointer; cursor: pointer;

View File

@ -1,7 +1,9 @@
<script setup> <script setup>
import { computed } from 'vue';
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
const model = defineModel({ type: [String, Number, Object] }); const model = defineModel({ type: [String, Number, Object] });
const url = 'Suppliers';
</script> </script>
<template> <template>
@ -9,13 +11,11 @@ const model = defineModel({ type: [String, Number, Object] });
:label="$t('globals.supplier')" :label="$t('globals.supplier')"
v-bind="$attrs" v-bind="$attrs"
v-model="model" v-model="model"
url="Suppliers" :url="url"
option-value="id" option-value="id"
option-label="nickname" option-label="nickname"
:fields="['id', 'name', 'nickname', 'nif']" :fields="['id', 'name', 'nickname', 'nif']"
:filter-options="['id', 'name', 'nickname', 'nif']"
sort-by="name ASC" sort-by="name ASC"
data-cy="vnSupplierSelect"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">

View File

@ -1,50 +0,0 @@
<script setup>
import VnSelectDialog from './VnSelectDialog.vue';
import FilterTravelForm from 'src/components/FilterTravelForm.vue';
import { useI18n } from 'vue-i18n';
import { toDate } from 'src/filters';
const { t } = useI18n();
const $props = defineProps({
data: {
type: Object,
required: true,
},
onFilterTravelSelected: {
type: Function,
required: true,
},
});
</script>
<template>
<VnSelectDialog
:label="t('entry.basicData.travel')"
v-bind="$attrs"
url="Travels/filter"
:fields="['id', 'warehouseInName']"
option-value="id"
option-label="warehouseInName"
map-options
hide-selected
:required="true"
action-icon="filter_alt"
:roles-allowed-to-create="['buyer']"
>
<template #form>
<FilterTravelForm @travel-selected="onFilterTravelSelected(data, $event)" />
</template>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt?.agencyModeName }} -
{{ scope.opt?.warehouseInName }}
({{ toDate(scope.opt?.shipped) }})
{{ scope.opt?.warehouseOutName }}
({{ toDate(scope.opt?.landed) }})
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectDialog>
</template>

View File

@ -0,0 +1,81 @@
<script setup>
import { ref } from 'vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const model = defineModel({ type: String, required: true });
const countriesOption = ref([]);
const countriesOptionCopy = ref([]);
const lastVal = ref();
function handleBlur() {
if (lastVal.value == '') lastVal.value = null;
model.value = lastVal.value && lastVal.value.toUpperCase().slice(0, 2);
}
function filterFn(val, update) {
update(
() => {
if (val === '') {
countriesOptionCopy.value = JSON.parse(
JSON.stringify(countriesOption.value)
);
return;
}
const exist = countriesOption.value.filter((c) =>
c.code.toLowerCase().includes(val.toLowerCase())
);
if (exist) return (countriesOptionCopy.value = exist);
countriesOptionCopy.value = JSON.parse(JSON.stringify([{ code: val }]));
},
(ref) => {
if (val !== '' && ref.options.length > 0) {
ref.setOptionIndex(-1);
ref.moveOptionSelection(1, true);
}
}
);
}
</script>
<template>
<FetchData auto-load @on-fetch="(data) => (countriesOption = data)" url="Countries" />
<VnSelect
:label="t('Vies')"
v-model="model"
:input-debounce="0"
:options="countriesOptionCopy"
@input-value="(evt) => (lastVal = evt) && handleBlur()"
@filter="filterFn"
option-label="code"
option-value="code"
v-bind="$attrs"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.code }}</QItemLabel>
<QItemLabel caption>
{{ scope.opt?.code }},
{{ scope.opt?.name }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
<template #after>
<QIcon name="info" class="cursor-pointer">
<QTooltip>{{ t('viesTip') }}</QTooltip>
</QIcon>
</template>
</VnSelect>
</template>
<i18n>
es:
viesTip: El campo puede contener valores que no este en la lista
en:
viesTip: The field may contain value that are not in the list
</i18n>

View File

@ -29,6 +29,10 @@ const $props = defineProps({
type: String, type: String,
default: null, default: null,
}, },
module: {
type: String,
default: null,
},
summary: { summary: {
type: Object, type: Object,
default: null, default: null,
@ -53,7 +57,7 @@ defineExpose({ getData });
onBeforeMount(async () => { onBeforeMount(async () => {
arrayData = useArrayData($props.dataKey, { arrayData = useArrayData($props.dataKey, {
url: $props.url, url: $props.url,
userFilter: $props.filter, filter: $props.filter,
skip: 0, skip: 0,
oneRecord: true, oneRecord: true,
}); });
@ -144,9 +148,7 @@ const toModule = computed(() =>
{{ t('components.smartCard.openSummary') }} {{ t('components.smartCard.openSummary') }}
</QTooltip> </QTooltip>
</QBtn> </QBtn>
<RouterLink <RouterLink :to="{ name: `${module}Summary`, params: { id: entity.id } }">
:to="{ name: `${dataKey}Summary`, params: { id: entity.id } }"
>
<QBtn <QBtn
class="link" class="link"
color="white" color="white"

View File

@ -1,32 +1,53 @@
<script setup>
defineProps({
hasImage: {
type: Boolean,
default: false,
},
});
</script>
<template> <template>
<div id="descriptor-skeleton" class="bg-vn-page"> <div id="descriptor-skeleton">
<div class="row justify-between q-pa-sm"> <div class="row justify-between q-pa-sm">
<QSkeleton square size="30px" v-for="i in 3" :key="i" /> <QSkeleton square size="40px" />
<QSkeleton square size="40px" />
<QSkeleton square height="40px" width="20px" />
</div> </div>
<div class="q-pa-xs" v-if="hasImage"> <div class="col justify-between q-pa-sm q-gutter-y-xs">
<QSkeleton square height="200px" width="100%" /> <QSkeleton square height="40px" width="150px" />
<QSkeleton square height="30px" width="70px" />
</div> </div>
<div class="col justify-between q-pa-md q-gutter-y-xs"> <div class="col q-pl-sm q-pa-sm q-mb-md">
<QSkeleton square height="25px" width="150px" /> <div class="row justify-between">
<QSkeleton square height="15px" width="70px" /> <QSkeleton type="text" square height="30px" width="20%" />
<QSkeleton type="text" square height="30px" width="60%" />
</div> </div>
<div class="q-pl-sm q-pa-sm q-mb-md"> <div class="row justify-between">
<div class="row q-gutter-x-sm q-pa-none q-ma-none" v-for="i in 5" :key="i"> <QSkeleton type="text" square height="30px" width="20%" />
<QSkeleton type="text" square height="20px" width="30%" /> <QSkeleton type="text" square height="30px" width="60%" />
<QSkeleton type="text" square height="20px" width="60%" /> </div>
<div class="row justify-between">
<QSkeleton type="text" square height="30px" width="20%" />
<QSkeleton type="text" square height="30px" width="60%" />
</div>
<div class="row justify-between">
<QSkeleton type="text" square height="30px" width="20%" />
<QSkeleton type="text" square height="30px" width="60%" />
</div>
<div class="row justify-between">
<QSkeleton type="text" square height="30px" width="20%" />
<QSkeleton type="text" square height="30px" width="60%" />
</div>
<div class="row justify-between">
<QSkeleton type="text" square height="30px" width="20%" />
<QSkeleton type="text" square height="30px" width="60%" />
</div> </div>
</div> </div>
<QCardActions class="q-gutter-x-sm justify-between"> <QCardActions>
<QSkeleton size="40px" v-for="i in 5" :key="i" /> <QSkeleton size="40px" />
<QSkeleton size="40px" />
<QSkeleton size="40px" />
<QSkeleton size="40px" />
<QSkeleton size="40px" />
</QCardActions> </QCardActions>
</div> </div>
</template> </template>
<style lang="scss" scoped>
#descriptor-skeleton .q-card__actions {
justify-content: space-between;
}
</style>

View File

@ -82,7 +82,7 @@ function cancel() {
@click="cancel()" @click="cancel()"
/> />
</QCardSection> </QCardSection>
<QCardSection class="q-pb-none" data-cy="VnConfirm_message"> <QCardSection class="q-pb-none">
<span v-if="message !== false" v-html="message" /> <span v-if="message !== false" v-html="message" />
</QCardSection> </QCardSection>
<QCardSection class="row items-center q-pt-none"> <QCardSection class="row items-center q-pt-none">
@ -95,7 +95,6 @@ function cancel() {
:disable="isLoading" :disable="isLoading"
flat flat
@click="cancel()" @click="cancel()"
data-cy="VnConfirm_cancel"
/> />
<QBtn <QBtn
:label="t('globals.confirm')" :label="t('globals.confirm')"

View File

@ -293,9 +293,6 @@ const getLocale = (label) => {
/> />
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.q-field__label.no-pointer-events.absolute.ellipsis {
margin-left: 6px !important;
}
.list { .list {
width: 256px; width: 256px;
} }

View File

@ -11,7 +11,7 @@
<QTooltip> <QTooltip>
{{ $t('components.cardDescriptor.moreOptions') }} {{ $t('components.cardDescriptor.moreOptions') }}
</QTooltip> </QTooltip>
<QMenu ref="menuRef" data-cy="descriptor-more-opts-menu"> <QMenu ref="menuRef">
<QList> <QList>
<slot name="menu" :menu-ref="$refs.menuRef" /> <slot name="menu" :menu-ref="$refs.menuRef" />
</QList> </QList>

View File

@ -18,12 +18,7 @@ import VnInput from 'components/common/VnInput.vue';
const emit = defineEmits(['onFetch']); const emit = defineEmits(['onFetch']);
const originalAttrs = useAttrs(); const $attrs = useAttrs();
const $attrs = computed(() => {
const { style, ...rest } = originalAttrs;
return rest;
});
const isRequired = computed(() => { const isRequired = computed(() => {
return Object.keys($attrs).includes('required') return Object.keys($attrs).includes('required')

View File

@ -1,41 +0,0 @@
<script setup>
import { toPercentage } from 'filters/index';
import { computed } from 'vue';
const props = defineProps({
value: {
type: Number,
required: true,
},
});
const valueClass = computed(() =>
props.value === 0 ? 'neutral' : props.value > 0 ? 'positive' : 'negative',
);
const iconName = computed(() =>
props.value === 0 ? 'equal' : props.value > 0 ? 'arrow_upward' : 'arrow_downward',
);
const formattedValue = computed(() => props.value);
</script>
<template>
<span :class="valueClass">
<QIcon :name="iconName" size="sm" class="value-icon" />
{{ toPercentage(formattedValue) }}
</span>
</template>
<style lang="scss" scoped>
.positive {
color: $secondary;
}
.negative {
color: $negative;
}
.neutral {
color: $primary;
}
.value-icon {
margin-right: 4px;
}
</style>

View File

@ -1,65 +0,0 @@
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import axios from 'axios';
import VnConfirm from 'components/ui/VnConfirm.vue';
export async function checkEntryLock(entryFk, userFk) {
const { t } = useI18n();
const quasar = useQuasar();
const { push } = useRouter();
const { data } = await axios.get(`Entries/${entryFk}`, {
params: {
filter: JSON.stringify({
fields: ['id', 'locked', 'lockerUserFk'],
include: { relation: 'user', scope: { fields: ['id', 'nickname'] } },
}),
},
});
const entryConfig = await axios.get('EntryConfigs/findOne');
if (data?.lockerUserFk && data?.locked) {
const now = new Date(Date.vnNow()).getTime();
const lockedTime = new Date(data.locked).getTime();
const timeDiff = (now - lockedTime) / 1000;
const isMaxTimeLockExceeded = entryConfig.data.maxLockTime > timeDiff;
if (data?.lockerUserFk !== userFk && isMaxTimeLockExceeded) {
quasar
.dialog({
component: VnConfirm,
componentProps: {
'data-cy': 'entry-lock-confirm',
title: t('entry.lock.title'),
message: t('entry.lock.message', {
userName: data?.user?.nickname,
time: timeDiff / 60,
}),
},
})
.onOk(
async () =>
await axios.patch(`Entries/${entryFk}`, {
locked: Date.vnNow(),
lockerUserFk: userFk,
}),
)
.onCancel(() => {
push({ path: `summary` });
});
}
} else {
await axios
.patch(`Entries/${entryFk}`, {
locked: Date.vnNow(),
lockerUserFk: userFk,
})
.then(
quasar.notify({
message: t('entry.lock.success'),
color: 'positive',
group: false,
}),
);
}
}

View File

@ -1,21 +0,0 @@
export function getColAlign(col) {
let align;
switch (col.component) {
case 'select':
align = 'left';
break;
case 'number':
align = 'right';
break;
case 'date':
case 'checkbox':
align = 'center';
break;
default:
align = col?.align;
}
if (/^is[A-Z]/.test(col.name) || /^has[A-Z]/.test(col.name)) align = 'center';
return 'text-' + (align ?? 'center');
}

View File

@ -27,15 +27,6 @@ export function useRole() {
return false; return false;
} }
function likeAny(roles) {
const roleStore = state.getRoles();
for (const role of roles) {
if (!roleStore.value.findIndex((rs) => rs.startsWith(role)) !== -1)
return true;
}
return false;
}
function isEmployee() { function isEmployee() {
return hasAny(['employee']); return hasAny(['employee']);
} }
@ -44,7 +35,6 @@ export function useRole() {
isEmployee, isEmployee,
fetch, fetch,
hasAny, hasAny,
likeAny,
state, state,
}; };
} }

View File

@ -21,10 +21,7 @@ body.body--light {
.q-header .q-toolbar { .q-header .q-toolbar {
color: var(--vn-text-color); color: var(--vn-text-color);
} }
--vn-color-negative: $negative;
} }
body.body--dark { body.body--dark {
--vn-header-color: #5d5d5d; --vn-header-color: #5d5d5d;
--vn-page-color: #222; --vn-page-color: #222;
@ -40,8 +37,6 @@ body.body--dark {
--vn-text-color-contrast: black; --vn-text-color-contrast: black;
background-color: var(--vn-page-color); background-color: var(--vn-page-color);
--vn-color-negative: $negative;
} }
a { a {
@ -80,6 +75,7 @@ a {
text-decoration: underline; text-decoration: underline;
} }
// Removes chrome autofill background
input:-webkit-autofill, input:-webkit-autofill,
select:-webkit-autofill { select:-webkit-autofill {
color: var(--vn-text-color); color: var(--vn-text-color);
@ -153,6 +149,11 @@ select:-webkit-autofill {
cursor: pointer; cursor: pointer;
} }
.vn-table-separation-row {
height: 16px !important;
background-color: var(--vn-section-color) !important;
}
/* Estilo para el asterisco en campos requeridos */ /* Estilo para el asterisco en campos requeridos */
.q-field.required .q-field__label:after { .q-field.required .q-field__label:after {
content: ' *'; content: ' *';
@ -229,12 +230,10 @@ input::-webkit-inner-spin-button {
max-width: 100%; max-width: 100%;
} }
.remove-bg {
filter: brightness(1.1);
mix-blend-mode: multiply;
}
.q-table__container { .q-table__container {
/* ===== Scrollbar CSS ===== /
/ Firefox */
* { * {
scrollbar-width: auto; scrollbar-width: auto;
scrollbar-color: var(--vn-label-color) transparent; scrollbar-color: var(--vn-label-color) transparent;
@ -275,6 +274,8 @@ input::-webkit-inner-spin-button {
font-size: 11pt; font-size: 11pt;
} }
td { td {
font-size: 11pt;
border-top: 1px solid var(--vn-page-color);
border-collapse: collapse; border-collapse: collapse;
} }
} }
@ -318,6 +319,9 @@ input::-webkit-inner-spin-button {
max-width: fit-content; max-width: fit-content;
} }
.row > .column:has(.q-checkbox) {
max-width: fit-content;
}
.q-field__inner { .q-field__inner {
.q-field__control { .q-field__control {
min-height: auto !important; min-height: auto !important;

View File

@ -13,7 +13,7 @@
// Tip: Use the "Theme Builder" on Quasar's documentation website. // Tip: Use the "Theme Builder" on Quasar's documentation website.
// Tip: to add new colors https://quasar.dev/style/color-palette/#adding-your-own-colors // Tip: to add new colors https://quasar.dev/style/color-palette/#adding-your-own-colors
$primary: #ec8916; $primary: #ec8916;
$secondary: #89be34; $secondary: $primary;
$positive: #c8e484; $positive: #c8e484;
$negative: #fb5252; $negative: #fb5252;
$info: #84d0e2; $info: #84d0e2;
@ -30,9 +30,7 @@ $color-spacer: #7979794d;
$border-thin-light: 1px solid $color-spacer-light; $border-thin-light: 1px solid $color-spacer-light;
$primary-light: #f5b351; $primary-light: #f5b351;
$dark-shadow-color: black; $dark-shadow-color: black;
$layout-shadow-dark: $layout-shadow-dark: 0 0 10px 2px #00000033, 0 0px 10px #0000003d;
0 0 10px 2px #00000033,
0 0px 10px #0000003d;
$spacing-md: 16px; $spacing-md: 16px;
$color-font-secondary: #777; $color-font-secondary: #777;
$width-xs: 400px; $width-xs: 400px;

View File

@ -33,7 +33,6 @@ globals:
reset: Reset reset: Reset
close: Close close: Close
cancel: Cancel cancel: Cancel
isSaveAndContinue: Save and continue
clone: Clone clone: Clone
confirm: Confirm confirm: Confirm
assign: Assign assign: Assign
@ -168,7 +167,6 @@ globals:
workCenters: Work centers workCenters: Work centers
modes: Modes modes: Modes
zones: Zones zones: Zones
negative: Negative
zonesList: List zonesList: List
deliveryDays: Delivery days deliveryDays: Delivery days
upcomingDeliveries: Upcoming deliveries upcomingDeliveries: Upcoming deliveries
@ -176,7 +174,6 @@ globals:
alias: Alias alias: Alias
aliasUsers: Users aliasUsers: Users
subRoles: Subroles subRoles: Subroles
myAccount: Mi cuenta
inheritedRoles: Inherited Roles inheritedRoles: Inherited Roles
customers: Customers customers: Customers
customerCreate: New customer customerCreate: New customer
@ -409,106 +406,6 @@ cau:
subtitle: By sending this ticket, all the data related to the error, the section, the user, etc., are already sent. subtitle: By sending this ticket, all the data related to the error, the section, the user, etc., are already sent.
inputLabel: Explain why this error should not appear inputLabel: Explain why this error should not appear
askPrivileges: Ask for privileges askPrivileges: Ask for privileges
entry:
list:
newEntry: New entry
tableVisibleColumns:
isExcludedFromAvailable: Exclude from inventory
isOrdered: Ordered
isConfirmed: Ready to label
isReceived: Received
isRaid: Raid
landed: Date
supplierFk: Supplier
reference: Ref/Alb/Guide
invoiceNumber: Invoice
agencyModeId: Agency
isBooked: Booked
companyFk: Company
evaNotes: Notes
warehouseOutFk: Origin
warehouseInFk: Destiny
entryTypeDescription: Entry type
invoiceAmount: Import
travelFk: Travel
summary:
invoiceAmount: Amount
commission: Commission
currency: Currency
invoiceNumber: Invoice number
ordered: Ordered
booked: Booked
excludedFromAvailable: Inventory
travelReference: Reference
travelAgency: Agency
travelShipped: Shipped
travelDelivered: Delivered
travelLanded: Landed
travelReceived: Received
buys: Buys
stickers: Stickers
package: Package
packing: Pack.
grouping: Group.
buyingValue: Buying value
import: Import
pvp: PVP
basicData:
travel: Travel
currency: Currency
commission: Commission
observation: Observation
booked: Booked
excludedFromAvailable: Inventory
buys:
observations: Observations
packagingFk: Box
color: Color
printedStickers: Printed stickers
notes:
observationType: Observation type
latestBuys:
tableVisibleColumns:
image: Picture
itemFk: Item ID
weightByPiece: Weight/Piece
isActive: Active
family: Family
entryFk: Entry
freightValue: Freight value
comissionValue: Commission value
packageValue: Package value
isIgnored: Is ignored
price2: Grouping
price3: Packing
minPrice: Min
ektFk: Ekt
packingOut: Package out
landing: Landing
isExcludedFromAvailable: Exclude from inventory
isRaid: Raid
invoiceNumber: Invoice
reference: Ref/Alb/Guide
params:
isExcludedFromAvailable: Excluir del inventario
isOrdered: Pedida
isConfirmed: Lista para etiquetar
isReceived: Recibida
isRaid: Redada
landed: Fecha
supplierFk: Proveedor
invoiceNumber: Nº Factura
reference: Ref/Alb/Guía
agencyModeId: Agencia
isBooked: Asentado
companyFk: Empresa
travelFk: Envio
evaNotes: Notas
warehouseOutFk: Origen
warehouseInFk: Destino
entryTypeDescription: Tipo entrada
invoiceAmount: Importe
dated: Fecha
ticket: ticket:
params: params:
ticketFk: Ticket ID ticketFk: Ticket ID
@ -738,8 +635,6 @@ wagon:
name: Name name: Name
supplier: supplier:
search: Search supplier
searchInfo: Search supplier by id or name
list: list:
payMethod: Pay method payMethod: Pay method
account: Account account: Account

View File

@ -33,11 +33,9 @@ globals:
reset: Restaurar reset: Restaurar
close: Cerrar close: Cerrar
cancel: Cancelar cancel: Cancelar
isSaveAndContinue: Guardar y continuar
clone: Clonar clone: Clonar
confirm: Confirmar confirm: Confirmar
assign: Asignar assign: Asignar
replace: Sustituir
back: Volver back: Volver
yes: Si yes: Si
no: No no: No
@ -50,7 +48,6 @@ globals:
rowRemoved: Fila eliminada rowRemoved: Fila eliminada
pleaseWait: Por favor espera... pleaseWait: Por favor espera...
noPinnedModules: No has fijado ningún módulo noPinnedModules: No has fijado ningún módulo
split: Split
summary: summary:
basicData: Datos básicos basicData: Datos básicos
daysOnward: Días adelante daysOnward: Días adelante
@ -58,8 +55,8 @@ globals:
today: Hoy today: Hoy
yesterday: Ayer yesterday: Ayer
dateFormat: es-ES dateFormat: es-ES
noSelectedRows: No tienes ninguna línea seleccionada
microsip: Abrir en MicroSIP microsip: Abrir en MicroSIP
noSelectedRows: No tienes ninguna línea seleccionada
downloadCSVSuccess: Descarga de CSV exitosa downloadCSVSuccess: Descarga de CSV exitosa
reference: Referencia reference: Referencia
agency: Agencia agency: Agencia
@ -79,10 +76,8 @@ globals:
requiredField: Campo obligatorio requiredField: Campo obligatorio
class: clase class: clase
type: Tipo type: Tipo
reason: Motivo reason: motivo
removeSelection: Eliminar selección
noResults: Sin resultados noResults: Sin resultados
results: resultados
system: Sistema system: Sistema
notificationSent: Notificación enviada notificationSent: Notificación enviada
warehouse: Almacén warehouse: Almacén
@ -171,7 +166,6 @@ globals:
agency: Agencia agency: Agencia
workCenters: Centros de trabajo workCenters: Centros de trabajo
modes: Modos modes: Modos
negative: Tickets negativos
zones: Zonas zones: Zonas
zonesList: Listado zonesList: Listado
deliveryDays: Días de entrega deliveryDays: Días de entrega
@ -292,9 +286,9 @@ globals:
buyRequest: Peticiones de compra buyRequest: Peticiones de compra
wasteBreakdown: Deglose de mermas wasteBreakdown: Deglose de mermas
itemCreate: Nuevo artículo itemCreate: Nuevo artículo
tax: IVA tax: 'IVA'
botanical: Botánico botanical: 'Botánico'
barcode: Código de barras barcode: 'Código de barras'
itemTypeCreate: Nueva familia itemTypeCreate: Nueva familia
family: Familia family: Familia
lastEntries: Últimas entradas lastEntries: Últimas entradas
@ -358,7 +352,7 @@ globals:
from: Desde from: Desde
to: Hasta to: Hasta
supplierFk: Proveedor supplierFk: Proveedor
supplierRef: Nº factura supplierRef: Ref. proveedor
serial: Serie serial: Serie
amount: Importe amount: Importe
awbCode: AWB awbCode: AWB
@ -403,87 +397,6 @@ cau:
subtitle: Al enviar este cau ya se envían todos los datos relacionados con el error, la sección, el usuario, etc subtitle: Al enviar este cau ya se envían todos los datos relacionados con el error, la sección, el usuario, etc
inputLabel: Explique el motivo por el que no deberia aparecer este fallo inputLabel: Explique el motivo por el que no deberia aparecer este fallo
askPrivileges: Solicitar permisos askPrivileges: Solicitar permisos
entry:
list:
newEntry: Nueva entrada
tableVisibleColumns:
isExcludedFromAvailable: Excluir del inventario
isOrdered: Pedida
isConfirmed: Lista para etiquetar
isReceived: Recibida
isRaid: Redada
landed: Fecha
supplierFk: Proveedor
invoiceNumber: Nº Factura
reference: Ref/Alb/Guía
agencyModeId: Agencia
isBooked: Asentado
companyFk: Empresa
travelFk: Envio
evaNotes: Notas
warehouseOutFk: Origen
warehouseInFk: Destino
entryTypeDescription: Tipo entrada
invoiceAmount: Importe
summary:
invoiceAmount: Importe
commission: Comisión
currency: Moneda
invoiceNumber: Núm. factura
ordered: Pedida
booked: Contabilizada
excludedFromAvailable: Inventario
travelReference: Referencia
travelAgency: Agencia
travelShipped: F. envio
travelWarehouseOut: Alm. salida
travelDelivered: Enviada
travelLanded: F. entrega
travelReceived: Recibida
buys: Compras
stickers: Etiquetas
package: Embalaje
packing: Pack.
grouping: Group.
buyingValue: Coste
import: Importe
pvp: PVP
basicData:
travel: Envío
currency: Moneda
observation: Observación
commission: Comisión
booked: Asentado
excludedFromAvailable: Inventario
buys:
observations: Observaciónes
packagingFk: Embalaje
color: Color
printedStickers: Etiquetas impresas
notes:
observationType: Tipo de observación
latestBuys:
tableVisibleColumns:
image: Foto
itemFk: Id Artículo
weightByPiece: Peso (gramos)/tallo
isActive: Activo
family: Familia
entryFk: Entrada
freightValue: Porte
comissionValue: Comisión
packageValue: Embalaje
isIgnored: Ignorado
price2: Grouping
price3: Packing
minPrice: Min
ektFk: Ekt
packingOut: Embalaje envíos
landing: Llegada
isExcludedFromAvailable: Excluir del inventario
isRaid: Redada
invoiceNumber: Nº Factura
reference: Ref/Alb/Guía
ticket: ticket:
params: params:
ticketFk: ID de ticket ticketFk: ID de ticket
@ -497,38 +410,6 @@ ticket:
freightItemName: Nombre freightItemName: Nombre
packageItemName: Embalaje packageItemName: Embalaje
longName: Descripción longName: Descripción
pageTitles:
tickets: Tickets
list: Listado
ticketCreate: Nuevo ticket
summary: Resumen
basicData: Datos básicos
boxing: Encajado
sms: Sms
notes: Notas
sale: Lineas del pedido
dms: Gestión documental
negative: Tickets negativos
volume: Volumen
observation: Notas
ticketAdvance: Adelantar tickets
futureTickets: Tickets a futuro
expedition: Expedición
purchaseRequest: Petición de compra
weeklyTickets: Tickets programados
saleTracking: Líneas preparadas
services: Servicios
tracking: Estados
components: Componentes
pictures: Fotos
packages: Bultos
list:
nickname: Alias
state: Estado
shipped: Enviado
landed: Entregado
salesPerson: Comercial
total: Total
card: card:
customerId: ID cliente customerId: ID cliente
customerCard: Ficha del cliente customerCard: Ficha del cliente
@ -575,48 +456,6 @@ ticket:
consigneeStreet: Dirección consigneeStreet: Dirección
create: create:
address: Dirección address: Dirección
invoiceOut:
card:
issued: Fecha emisión
customerCard: Ficha del cliente
ticketList: Listado de tickets
summary:
issued: Fecha
dued: Fecha límite
booked: Contabilizada
taxBreakdown: Desglose impositivo
taxableBase: Base imp.
rate: Tarifa
fee: Cuota
tickets: Tickets
totalWithVat: Importe
globalInvoices:
errors:
chooseValidClient: Selecciona un cliente válido
chooseValidCompany: Selecciona una empresa válida
chooseValidPrinter: Selecciona una impresora válida
chooseValidSerialType: Selecciona una tipo de serie válida
fillDates: La fecha de la factura y la fecha máxima deben estar completas
invoiceDateLessThanMaxDate: La fecha de la factura no puede ser menor que la fecha máxima
invoiceWithFutureDate: Existe una factura con una fecha futura
noTicketsToInvoice: No existen tickets para facturar
criticalInvoiceError: Error crítico en la facturación proceso detenido
invalidSerialTypeForAll: El tipo de serie debe ser global cuando se facturan todos los clientes
table:
addressId: Id dirección
streetAddress: Dirección fiscal
statusCard:
percentageText: '{getPercentage}% {getAddressNumber} de {getNAddresses}'
pdfsNumberText: '{nPdfs} de {totalPdfs} PDFs'
negativeBases:
clientId: Id cliente
base: Base
active: Activo
hasToInvoice: Facturar
verifiedData: Datos comprobados
comercial: Comercial
errors:
downloadCsvFailed: Error al descargar CSV
order: order:
field: field:
salesPersonFk: Comercial salesPersonFk: Comercial
@ -627,34 +466,15 @@ order:
list: list:
newOrder: Nuevo Pedido newOrder: Nuevo Pedido
summary: summary:
basket: Cesta issued: Fecha
notConfirmed: No confirmada dued: Fecha límite
created: Creado booked: Contabilizada
createdFrom: Creado desde taxBreakdown: Desglose impositivo
address: Dirección taxableBase: Base imp.
total: Total rate: Tarifa
vat: IVA fee: Cuota
state: Estado tickets: Tickets
alias: Alias totalWithVat: Importe
items: Artículos
orderTicketList: Tickets del pedido
amount: Monto
confirm: Confirmar
confirmLines: Confirmar lineas
shelving:
list:
parking: Parking
priority: Prioridad
newShelving: Nuevo Carro
summary:
recyclable: Reciclable
parking:
pickingOrder: Orden de recogida
row: Fila
column: Columna
searchBar:
info: Puedes buscar por código de parking
label: Buscar parking...
department: department:
chat: Chat chat: Chat
bossDepartment: Jefe de departamento bossDepartment: Jefe de departamento
@ -815,8 +635,8 @@ wagon:
volumeNotEmpty: El volumen no puede estar vacío volumeNotEmpty: El volumen no puede estar vacío
typeNotEmpty: El tipo no puede estar vacío typeNotEmpty: El tipo no puede estar vacío
maxTrays: Has alcanzado el número máximo de bandejas maxTrays: Has alcanzado el número máximo de bandejas
minHeightBetweenTrays: La distancia mínima entre bandejas es minHeightBetweenTrays: 'La distancia mínima entre bandejas es '
maxWagonHeight: La altura máxima del vagón es maxWagonHeight: 'La altura máxima del vagón es '
uncompleteTrays: Hay bandejas sin completar uncompleteTrays: Hay bandejas sin completar
params: params:
label: Etiqueta label: Etiqueta
@ -824,8 +644,6 @@ wagon:
volume: Volumen volume: Volumen
name: Nombre name: Nombre
supplier: supplier:
search: Buscar proveedor
searchInfo: Buscar proveedor por id o nombre
list: list:
payMethod: Método de pago payMethod: Método de pago
account: Cuenta account: Cuenta
@ -963,7 +781,7 @@ components:
cardDescriptor: cardDescriptor:
mainList: Listado principal mainList: Listado principal
summary: Resumen summary: Resumen
moreOptions: Más opciones moreOptions: 'Más opciones'
leftMenu: leftMenu:
addToPinned: Añadir a fijados addToPinned: Añadir a fijados
removeFromPinned: Eliminar de fijados removeFromPinned: Eliminar de fijados

View File

@ -1,12 +1,12 @@
<script setup> <script setup>
import { Dark, Quasar } from 'quasar'; import { Dark, Quasar } from 'quasar';
import { computed, onMounted } from 'vue'; import { computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { localeEquivalence } from 'src/i18n/index'; import { localeEquivalence } from 'src/i18n/index';
import quasarLang from 'src/utils/quasarLang'; import quasarLang from 'src/utils/quasarLang';
import { langs } from 'src/boot/defaults/constants.js';
const { t, locale } = useI18n(); const { t, locale } = useI18n();
const userLocale = computed({ const userLocale = computed({
get() { get() {
return locale.value; return locale.value;
@ -28,6 +28,7 @@ const darkMode = computed({
Dark.set(value); Dark.set(value);
}, },
}); });
const langs = ['en', 'es'];
</script> </script>
<template> <template>

View File

@ -51,6 +51,7 @@ const removeAlias = () => {
<CardDescriptor <CardDescriptor
ref="descriptor" ref="descriptor"
:url="`MailAliases/${entityId}`" :url="`MailAliases/${entityId}`"
module="Alias"
data-key="Alias" data-key="Alias"
title="alias" title="alias"
> >

View File

@ -23,7 +23,8 @@ onMounted(async () => {
<CardDescriptor <CardDescriptor
ref="descriptor" ref="descriptor"
:url="`VnUsers/preview`" :url="`VnUsers/preview`"
:filter="{ ...filter, where: { id: entityId } }" :filter="filter"
module="Account"
data-key="Account" data-key="Account"
title="nickname" title="nickname"
> >

View File

@ -12,7 +12,6 @@ import VnInputPassword from 'src/components/common/VnInputPassword.vue';
import VnChangePassword from 'src/components/common/VnChangePassword.vue'; import VnChangePassword from 'src/components/common/VnChangePassword.vue';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import VnCheckbox from 'src/components/common/VnCheckbox.vue';
const $props = defineProps({ const $props = defineProps({
hasAccount: { hasAccount: {
@ -122,14 +121,18 @@ onMounted(() => {
:promise="sync" :promise="sync"
> >
<template #customHTML> <template #customHTML>
<VnCheckbox {{ shouldSyncPassword }}
v-model="shouldSyncPassword" <QCheckbox
:label="t('account.card.actions.sync.checkbox')" :label="t('account.card.actions.sync.checkbox')"
:info="t('account.card.actions.sync.tooltip')" v-model="shouldSyncPassword"
class="full-width"
clearable clearable
clear-icon="close" clear-icon="close"
color="primary" >
/> <QIcon style="padding-left: 10px" color="primary" name="info" size="sm">
<QTooltip>{{ t('account.card.actions.sync.tooltip') }}</QTooltip>
</QIcon></QCheckbox
>
<VnInputPassword <VnInputPassword
v-if="shouldSyncPassword" v-if="shouldSyncPassword"
:label="t('login.password')" :label="t('login.password')"

View File

@ -35,6 +35,7 @@ const removeRole = async () => {
<CardDescriptor <CardDescriptor
url="VnRoles" url="VnRoles"
:filter="{ where: { id: entityId } }" :filter="{ where: { id: entityId } }"
module="Role"
data-key="Role" data-key="Role"
:summary="$props.summary" :summary="$props.summary"
> >

View File

@ -46,6 +46,7 @@ onMounted(async () => {
<CardDescriptor <CardDescriptor
:url="`Claims/${entityId}`" :url="`Claims/${entityId}`"
:filter="filter" :filter="filter"
module="Claim"
title="client.name" title="client.name"
data-key="Claim" data-key="Claim"
> >
@ -85,7 +86,7 @@ onMounted(async () => {
/> />
</template> </template>
</VnLv> </VnLv>
<VnLv v-if="entity.ticket?.zone?.id" :label="t('claim.zone')"> <VnLv :label="t('claim.zone')">
<template #value> <template #value>
<span class="link"> <span class="link">
{{ entity.ticket?.zone?.name }} {{ entity.ticket?.zone?.name }}
@ -97,10 +98,11 @@ onMounted(async () => {
:label="t('claim.province')" :label="t('claim.province')"
:value="entity.ticket?.address?.province?.name" :value="entity.ticket?.address?.province?.name"
/> />
<VnLv v-if="entity.ticketFk" :label="t('claim.ticketId')"> <VnLv :label="t('claim.ticketId')">
<template #value> <template #value>
<span class="link"> <span class="link">
{{ entity.ticketFk }} {{ entity.ticketFk }}
<TicketDescriptorProxy :id="entity.ticketFk" /> <TicketDescriptorProxy :id="entity.ticketFk" />
</span> </span>
</template> </template>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { computed, useAttrs } from 'vue'; import { computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import VnNotes from 'src/components/ui/VnNotes.vue'; import VnNotes from 'src/components/ui/VnNotes.vue';
@ -7,7 +7,6 @@ import VnNotes from 'src/components/ui/VnNotes.vue';
const route = useRoute(); const route = useRoute();
const state = useState(); const state = useState();
const user = state.getUser(); const user = state.getUser();
const $attrs = useAttrs();
const $props = defineProps({ const $props = defineProps({
id: { type: [Number, String], default: null }, id: { type: [Number, String], default: null },

View File

@ -131,7 +131,7 @@ const STATE_COLOR = {
prefix="claim" prefix="claim"
:array-data-props="{ :array-data-props="{
url: 'Claims/filter', url: 'Claims/filter',
order: 'cs.priority ASC, created ASC', order: ['cs.priority ASC', 'created ASC'],
}" }"
> >
<template #advanced-menu> <template #advanced-menu>

View File

@ -117,7 +117,7 @@ const toCustomerAddressEdit = (addressId) => {
data-key="CustomerAddresses" data-key="CustomerAddresses"
order="id DESC" order="id DESC"
ref="vnPaginateRef" ref="vnPaginateRef"
:filter="addressFilter" :user-filter="addressFilter"
:url="`Clients/${route.params.id}/addresses`" :url="`Clients/${route.params.id}/addresses`"
/> />
<div class="full-width flex justify-center"> <div class="full-width flex justify-center">
@ -189,11 +189,11 @@ const toCustomerAddressEdit = (addressId) => {
<QSeparator <QSeparator
class="q-mx-lg" class="q-mx-lg"
v-if="item?.observations?.length" v-if="item.observations.length"
vertical vertical
/> />
<div v-if="item?.observations?.length"> <div v-if="item.observations.length">
<div <div
:key="obIndex" :key="obIndex"
class="flex q-mb-sm" class="flex q-mb-sm"

View File

@ -61,23 +61,6 @@ const columns = computed(() => [
columnFilter: false, columnFilter: false,
cardVisible: true, cardVisible: true,
}, },
{
align: 'left',
name: 'buyerId',
label: t('customer.params.buyerId'),
component: 'select',
attrs: {
url: 'TicketRequests/getItemTypeWorker',
optionLabel: 'nickname',
optionValue: 'id',
fields: ['id', 'nickname'],
sortBy: ['nickname ASC'],
optionFilter: 'firstName',
},
cardVisible: false,
visible: false,
},
{ {
name: 'description', name: 'description',
align: 'left', align: 'left',
@ -91,7 +74,6 @@ const columns = computed(() => [
name: 'quantity', name: 'quantity',
label: t('globals.quantity'), label: t('globals.quantity'),
cardVisible: true, cardVisible: true,
visible: true,
columnFilter: { columnFilter: {
inWhere: true, inWhere: true,
}, },
@ -137,7 +119,7 @@ const openSendEmailDialog = async () => {
openConfirmationModal( openConfirmationModal(
t('The consumption report will be sent'), t('The consumption report will be sent'),
t('Please, confirm'), t('Please, confirm'),
() => sendCampaignMetricsEmail({ address: arrayData.store.data.email }), () => sendCampaignMetricsEmail({ address: arrayData.store.data.email })
); );
}; };
const sendCampaignMetricsEmail = ({ address }) => { const sendCampaignMetricsEmail = ({ address }) => {
@ -156,11 +138,11 @@ const updateDateParams = (value, params) => {
const campaign = campaignList.value.find((c) => c.id === value); const campaign = campaignList.value.find((c) => c.id === value);
if (!campaign) return; if (!campaign) return;
const { dated, scopeDays } = campaign; const { dated, previousDays, scopeDays } = campaign;
const from = new Date(dated); const _date = new Date(dated);
from.setDate(from.getDate() - scopeDays); const [from, to] = dateRange(_date);
params.from = from; params.from = new Date(from.setDate(from.getDate() - previousDays)).toISOString();
params.to = dated; params.to = new Date(to.setDate(to.getDate() + scopeDays)).toISOString();
return params; return params;
}; };
</script> </script>
@ -218,62 +200,29 @@ const updateDateParams = (value, params) => {
<div v-if="row.subName" class="subName"> <div v-if="row.subName" class="subName">
{{ row.subName }} {{ row.subName }}
</div> </div>
<FetchedTags :item="row" /> <FetchedTags :item="row" :max-length="3" />
</template> </template>
<template #moreFilterPanel="{ params }"> <template #moreFilterPanel="{ params }">
<div class="column no-wrap flex-center q-gutter-y-md q-mt-xs q-pr-xl"> <div class="column no-wrap flex-center q-gutter-y-md q-mt-xs q-pr-xl">
<VnSelect
:filled="true"
class="q-px-sm q-pt-none fit"
url="ItemTypes"
v-model="params.typeId"
:label="t('item.list.typeName')"
:fields="['id', 'name', 'categoryFk']"
:include="'category'"
:sortBy="'name ASC'"
dense
@update:model-value="(data) => updateDateParams(data, params)"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption>{{
scope.opt?.category?.name
}}</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
<VnSelect
:filled="true"
class="q-px-sm q-pt-none fit"
url="ItemCategories"
v-model="params.categoryId"
:label="t('item.list.category')"
:fields="['id', 'name']"
:sortBy="'name ASC'"
dense
@update:model-value="(data) => updateDateParams(data, params)"
/>
<VnSelect <VnSelect
v-model="params.campaign" v-model="params.campaign"
:options="campaignList" :options="campaignList"
:label="t('globals.campaign')" :label="t('globals.campaign')"
:filled="true" :filled="true"
class="q-px-sm q-pt-none fit" class="q-px-sm q-pt-none fit"
:option-label="(opt) => t(opt.code)"
:fields="['id', 'code', 'dated', 'scopeDays']"
@update:model-value="(data) => updateDateParams(data, params)"
dense dense
option-label="code"
@update:model-value="(data) => updateDateParams(data, params)"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
<QItemSection> <QItemSection>
<QItemLabel> {{ t(scope.opt?.code) }} </QItemLabel> <QItemLabel>
<QItemLabel caption> {{ scope.opt?.code }}
{{ new Date(scope.opt?.dated).getFullYear() }} {{
</QItemLabel> new Date(scope.opt?.dated).getFullYear()
}}</QItemLabel
>
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>
@ -298,19 +247,7 @@ const updateDateParams = (value, params) => {
</template> </template>
<i18n> <i18n>
en:
valentinesDay: Valentine's Day
mothersDay: Mother's Day
allSaints: All Saints' Day
es: es:
Enter a new search: Introduce una nueva búsqueda Enter a new search: Introduce una nueva búsqueda
Group by items: Agrupar por artículos Group by items: Agrupar por artículos
valentinesDay: Día de San Valentín
mothersDay: Día de la Madre
allSaints: Día de Todos los Santos
Campaign consumption: Consumo campaña
Campaign: Campaña
From: Desde
To: Hasta
</i18n> </i18n>

View File

@ -55,6 +55,7 @@ const debtWarning = computed(() => {
<template> <template>
<CardDescriptor <CardDescriptor
module="Customer"
:url="`Clients/${entityId}/getCard`" :url="`Clients/${entityId}/getCard`"
:summary="$props.summary" :summary="$props.summary"
data-key="Customer" data-key="Customer"
@ -109,21 +110,7 @@ const debtWarning = computed(() => {
> >
<QTooltip>{{ t('customer.card.isDisabled') }}</QTooltip> <QTooltip>{{ t('customer.card.isDisabled') }}</QTooltip>
</QIcon> </QIcon>
<QIcon v-if="entity.isFreezed" name="vn:frozen" size="xs" color="primary">
<QIcon
v-if="entity?.substitutionAllowed"
name="help"
size="xs"
color="primary"
>
<QTooltip>{{ t('Allowed substitution') }}</QTooltip>
</QIcon>
<QIcon
v-if="customer?.isFreezed"
name="vn:frozen"
size="xs"
color="primary"
>
<QTooltip>{{ t('customer.card.isFrozen') }}</QTooltip> <QTooltip>{{ t('customer.card.isFrozen') }}</QTooltip>
</QIcon> </QIcon>
<QIcon <QIcon

View File

@ -61,16 +61,6 @@ const openCreateForm = (type) => {
.join('&'); .join('&');
useOpenURL(`/#/${type}/list?${params}`); useOpenURL(`/#/${type}/list?${params}`);
}; };
const updateSubstitutionAllowed = async () => {
try {
await axios.patch(`Clients/${route.params.id}`, {
substitutionAllowed: !$props.customer.substitutionAllowed,
});
notify('globals.notificationSent', 'positive');
} catch (error) {
notify(error.message, 'positive');
}
};
</script> </script>
<template> <template>
@ -79,13 +69,6 @@ const updateSubstitutionAllowed = async () => {
{{ t('globals.pageTitles.createTicket') }} {{ t('globals.pageTitles.createTicket') }}
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem v-ripple clickable>
<QItemSection @click="updateSubstitutionAllowed()">{{
$props.customer.substitutionAllowed
? t('Disable substitution')
: t('Allow substitution')
}}</QItemSection>
</QItem>
<QItem v-ripple clickable> <QItem v-ripple clickable>
<QItemSection @click="showSmsDialog()">{{ t('Send SMS') }}</QItemSection> <QItemSection @click="showSmsDialog()">{{ t('Send SMS') }}</QItemSection>
</QItem> </QItem>

View File

@ -8,8 +8,8 @@ import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnSelectVies from 'src/components/common/VnSelectVies.vue';
import VnLocation from 'src/components/common/VnLocation.vue'; import VnLocation from 'src/components/common/VnLocation.vue';
import VnCheckbox from 'src/components/common/VnCheckbox.vue';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
@ -104,6 +104,7 @@ function handleLocation(data, location) {
:required="true" :required="true"
@update:model-value="(location) => handleLocation(data, location)" @update:model-value="(location) => handleLocation(data, location)"
/> />
<VnSelectVies v-model="data.viesCode" style="max-width: 30%" />
</VnRow> </VnRow>
<VnRow> <VnRow>
<QCheckbox :label="t('Active')" v-model="data.isActive" /> <QCheckbox :label="t('Active')" v-model="data.isActive" />
@ -111,11 +112,7 @@ function handleLocation(data, location) {
</VnRow> </VnRow>
<VnRow> <VnRow>
<QCheckbox :label="t('Has to invoice')" v-model="data.hasToInvoice" /> <QCheckbox :label="t('Has to invoice')" v-model="data.hasToInvoice" />
<VnCheckbox <QCheckbox :label="t('Verified data')" v-model="data.isTaxDataChecked" />
v-model="data.isVies"
:label="t('globals.isVies')"
:info="t('whenActivatingIt')"
/>
</VnRow> </VnRow>
<VnRow> <VnRow>
@ -127,11 +124,17 @@ function handleLocation(data, location) {
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnCheckbox <div>
v-model="data.isEqualizated" <QCheckbox
:label="t('Is equalizated')" :label="t('Is equalizated')"
:info="t('inOrderToInvoice')" v-model="data.isEqualizated"
/> />
<QIcon class="cursor-info q-ml-sm" name="info" size="sm">
<QTooltip>
{{ t('inOrderToInvoice') }}
</QTooltip>
</QIcon>
</div>
<QCheckbox :label="t('Daily invoice')" v-model="data.hasDailyInvoice" /> <QCheckbox :label="t('Daily invoice')" v-model="data.hasDailyInvoice" />
</VnRow> </VnRow>
@ -139,9 +142,6 @@ function handleLocation(data, location) {
<QCheckbox <QCheckbox
:label="t('Electronic invoice')" :label="t('Electronic invoice')"
v-model="data.hasElectronicInvoice" v-model="data.hasElectronicInvoice"
/><QCheckbox
:label="t('Verified data')"
v-model="data.isTaxDataChecked"
/> />
</VnRow> </VnRow>
</template> </template>
@ -169,11 +169,9 @@ es:
Incoterms authorization: Autorización incoterms Incoterms authorization: Autorización incoterms
Electronic invoice: Factura electrónica Electronic invoice: Factura electrónica
onlyLetters: Sólo se pueden usar letras, números y espacios onlyLetters: Sólo se pueden usar letras, números y espacios
whenActivatingIt: Al activarlo, no informar el código del país en el campo nif
inOrderToInvoice: Para facturar no se consulta este campo, sino el RE de consignatario. Al modificar este campo si no esta marcada la casilla Facturar por consignatario, se propagará automaticamente el cambio a todos lo consignatarios, en caso contrario preguntará al usuario si quiere o no propagar inOrderToInvoice: Para facturar no se consulta este campo, sino el RE de consignatario. Al modificar este campo si no esta marcada la casilla Facturar por consignatario, se propagará automaticamente el cambio a todos lo consignatarios, en caso contrario preguntará al usuario si quiere o no propagar
Daily invoice: Facturación diaria Daily invoice: Facturación diaria
en: en:
onlyLetters: Only letters, numbers and spaces can be used onlyLetters: Only letters, numbers and spaces can be used
whenActivatingIt: When activating it, do not enter the country code in the ID field
inOrderToInvoice: In order to invoice, this field is not contulted, but the consignee's ET. When modifiying this field if the invoice by address option is not checked, the change will be automatically propagated to all addresses, otherwise the user will be asked if he wants to propagate it or not inOrderToInvoice: In order to invoice, this field is not contulted, but the consignee's ET. When modifiying this field if the invoice by address option is not checked, the change will be automatically propagated to all addresses, otherwise the user will be asked if he wants to propagate it or not
</i18n> </i18n>

View File

@ -1,3 +1,4 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
@ -51,7 +52,11 @@ const exprBuilder = (param, value) => {
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">
<QItemSection> <QItemSection>
<VnInput :label="t('Name')" v-model="params.name" is-outlined /> <VnInput
:label="t('globals.name')"
v-model="params.name"
is-outlined
/>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">

View File

@ -264,7 +264,6 @@ const columns = computed(() => [
align: 'left', align: 'left',
name: 'isActive', name: 'isActive',
label: t('customer.summary.isActive'), label: t('customer.summary.isActive'),
component: 'checkbox',
chip: { chip: {
color: null, color: null,
condition: (value) => !value, condition: (value) => !value,
@ -303,7 +302,6 @@ const columns = computed(() => [
align: 'left', align: 'left',
name: 'isFreezed', name: 'isFreezed',
label: t('customer.extendedList.tableVisibleColumns.isFreezed'), label: t('customer.extendedList.tableVisibleColumns.isFreezed'),
component: 'checkbox',
chip: { chip: {
color: null, color: null,
condition: (value) => value, condition: (value) => value,
@ -421,7 +419,7 @@ function handleLocation(data, location) {
<VnTable <VnTable
ref="tableRef" ref="tableRef"
:data-key="dataKey" :data-key="dataKey"
url="Clients/extendedListFilter" url="Clients/filter"
:create="{ :create="{
urlCreate: 'Clients/createWithUser', urlCreate: 'Clients/createWithUser',
title: t('globals.pageTitles.customerCreate'), title: t('globals.pageTitles.customerCreate'),

View File

@ -9,7 +9,7 @@ import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.v
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import CustomerDefaulterAddObservation from './CustomerDefaulterAddObservation.vue'; import CustomerDefaulterAddObservation from './CustomerDefaulterAddObservation.vue';
import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue';
import VnTable from 'src/components/VnTable/VnTable.vue'; import VnTable from 'src/components/VnTable/VnTable.vue';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';

View File

@ -107,9 +107,6 @@ customer:
defaulterSinced: Defaulted Since defaulterSinced: Defaulted Since
hasRecovery: Has Recovery hasRecovery: Has Recovery
socialName: Social name socialName: Social name
typeId: Type
buyerId: Buyer
categoryId: Category
city: City city: City
phone: Phone phone: Phone
postcode: Postcode postcode: Postcode

View File

@ -108,9 +108,6 @@ customer:
hasRecovery: Tiene recobro hasRecovery: Tiene recobro
socialName: Razón social socialName: Razón social
campaign: Campaña campaign: Campaña
typeId: Familia
buyerId: Comprador
categoryId: Reino
city: Ciudad city: Ciudad
phone: Teléfono phone: Teléfono
postcode: Código postal postcode: Código postal

View File

@ -1,6 +1,6 @@
<script setup> <script setup>
import VnCardBeta from 'components/common/VnCardBeta.vue'; import VnCardBeta from 'components/common/VnCardBeta.vue';
import DepartmentDescriptor from 'pages/Worker/Department/Card/DepartmentDescriptor.vue'; import DepartmentDescriptor from 'pages/Department/Card/DepartmentDescriptor.vue';
</script> </script>
<template> <template>
<VnCardBeta <VnCardBeta

View File

@ -42,6 +42,7 @@ const { openConfirmationModal } = useVnConfirm();
<template> <template>
<CardDescriptor <CardDescriptor
ref="DepartmentDescriptorRef" ref="DepartmentDescriptorRef"
module="Department"
:url="`Departments/${entityId}`" :url="`Departments/${entityId}`"
:summary="$props.summary" :summary="$props.summary"
:to-module="{ name: 'WorkerDepartment' }" :to-module="{ name: 'WorkerDepartment' }"

View File

@ -1,32 +1,30 @@
<script setup> <script setup>
import { onMounted, ref } from 'vue'; import { ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRole } from 'src/composables/useRole'; import { useRole } from 'src/composables/useRole';
import { useState } from 'src/composables/useState';
import { checkEntryLock } from 'src/composables/checkEntryLock';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import FilterTravelForm from 'src/components/FilterTravelForm.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import VnSelectTravelExtended from 'src/components/common/VnSelectTravelExtended.vue'; import { toDate } from 'src/filters';
import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue'; import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const { hasAny } = useRole(); const { hasAny } = useRole();
const isAdministrative = () => hasAny(['administrative']); const isAdministrative = () => hasAny(['administrative']);
const state = useState();
const user = state.getUser().fn();
const companiesOptions = ref([]); const companiesOptions = ref([]);
const currenciesOptions = ref([]); const currenciesOptions = ref([]);
onMounted(() => { const onFilterTravelSelected = (formData, id) => {
checkEntryLock(route.params.id, user.id); formData.travelFk = id;
}); };
</script> </script>
<template> <template>
@ -54,24 +52,46 @@ onMounted(() => {
> >
<template #form="{ data }"> <template #form="{ data }">
<VnRow> <VnRow>
<VnSelectTravelExtended
:data="data"
v-model="data.travelFk"
:onFilterTravelSelected="(data, result) => (data.travelFk = result)"
/>
<VnSelectSupplier <VnSelectSupplier
v-model="data.supplierFk" v-model="data.supplierFk"
hide-selected hide-selected
:required="true" :required="true"
map-options
/> />
<VnSelectDialog
:label="t('entry.basicData.travel')"
v-model="data.travelFk"
url="Travels/filter"
:fields="['id', 'warehouseInName']"
option-value="id"
option-label="warehouseInName"
map-options
hide-selected
:required="true"
action-icon="filter_alt"
>
<template #form>
<FilterTravelForm
@travel-selected="onFilterTravelSelected(data, $event)"
/>
</template>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt?.agencyModeName }} -
{{ scope.opt?.warehouseInName }}
({{ toDate(scope.opt?.shipped) }})
{{ scope.opt?.warehouseOutName }}
({{ toDate(scope.opt?.landed) }})
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectDialog>
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnInput v-model="data.reference" :label="t('globals.reference')" /> <VnInput v-model="data.reference" :label="t('globals.reference')" />
<VnInputNumber
v-model="data.invoiceAmount"
:label="t('entry.summary.invoiceAmount')"
:positive="false"
/>
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnInput <VnInput
@ -93,7 +113,8 @@ onMounted(() => {
<VnInputNumber <VnInputNumber
:label="t('entry.summary.commission')" :label="t('entry.summary.commission')"
v-model="data.commission" v-model="data.commission"
:step="1" step="1"
autofocus
:positive="false" :positive="false"
/> />
<VnSelect <VnSelect
@ -140,7 +161,7 @@ onMounted(() => {
:label="t('entry.summary.excludedFromAvailable')" :label="t('entry.summary.excludedFromAvailable')"
/> />
<QCheckbox <QCheckbox
:disable="!isAdministrative()" v-if="isAdministrative()"
v-model="data.isBooked" v-model="data.isBooked"
:label="t('entry.basicData.booked')" :label="t('entry.basicData.booked')"
/> />

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +1,12 @@
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { toDate } from 'src/filters';
import { getUrl } from 'src/composables/getUrl';
import { useQuasar } from 'quasar';
import { usePrintService } from 'composables/usePrintService';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue'; import { toDate } from 'src/filters';
import axios from 'axios'; import { getUrl } from 'src/composables/getUrl';
import EntryDescriptorMenu from './EntryDescriptorMenu.vue';
const quasar = useQuasar();
const { push } = useRouter();
const { openReport } = usePrintService();
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -90,63 +83,12 @@ const getEntryRedirectionFilter = (entry) => {
to, to,
}); });
}; };
function showEntryReport() {
openReport(`Entries/${entityId.value}/entry-order-pdf`);
}
function showNotification(type, message) {
quasar.notify({
type: type,
message: t(message),
});
}
async function recalculateRates(entity) {
try {
const entryConfig = await axios.get('EntryConfigs/findOne');
if (entryConfig.data?.inventorySupplierFk === entity.supplierFk) {
showNotification(
'negative',
'Cannot recalculate prices because this is an inventory entry',
);
return;
}
await axios.post(`Entries/${entityId.value}/recalcEntryPrices`);
showNotification('positive', 'Entry prices recalculated');
} catch (error) {
showNotification('negative', 'Failed to recalculate rates');
console.error(error);
}
}
async function cloneEntry() {
try {
const response = await axios.post(`Entries/${entityId.value}/cloneEntry`);
push({ path: `/entry/${response.data}` });
showNotification('positive', 'Entry cloned');
} catch (error) {
showNotification('negative', 'Failed to clone entry');
console.error(error);
}
}
async function deleteEntry() {
try {
await axios.post(`Entries/${entityId.value}/deleteEntry`);
push({ path: `/entry/list` });
showNotification('positive', 'Entry deleted');
} catch (error) {
showNotification('negative', 'Failed to delete entry');
console.error(error);
}
}
</script> </script>
<template> <template>
<CardDescriptor <CardDescriptor
ref="entryDescriptorRef" ref="entryDescriptorRef"
module="Entry"
:url="`Entries/${entityId}`" :url="`Entries/${entityId}`"
:userFilter="entryFilter" :userFilter="entryFilter"
title="supplier.nickname" title="supplier.nickname"
@ -154,56 +96,15 @@ async function deleteEntry() {
width="lg-width" width="lg-width"
> >
<template #menu="{ entity }"> <template #menu="{ entity }">
<QItem <EntryDescriptorMenu :id="entity.id" />
v-ripple
clickable
@click="showEntryReport(entity)"
data-cy="show-entry-report"
>
<QItemSection>{{ t('Show entry report') }}</QItemSection>
</QItem>
<QItem
v-ripple
clickable
@click="recalculateRates(entity)"
data-cy="recalculate-rates"
>
<QItemSection>{{ t('Recalculate rates') }}</QItemSection>
</QItem>
<QItem v-ripple clickable @click="cloneEntry(entity)" data-cy="clone-entry">
<QItemSection>{{ t('Clone') }}</QItemSection>
</QItem>
<QItem v-ripple clickable @click="deleteEntry(entity)" data-cy="delete-entry">
<QItemSection>{{ t('Delete') }}</QItemSection>
</QItem>
</template> </template>
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('Travel')"> <VnLv :label="t('globals.agency')" :value="entity.travel?.agency?.name" />
<template #value> <VnLv :label="t('shipped')" :value="toDate(entity.travel?.shipped)" />
<span class="link" v-if="entity?.travelFk"> <VnLv :label="t('landed')" :value="toDate(entity.travel?.landed)" />
{{ entity.travel?.agency?.name }}
{{ entity.travel?.warehouseOut?.code }} &rarr;
{{ entity.travel?.warehouseIn?.code }}
<TravelDescriptorProxy :id="entity?.travelFk" />
</span>
</template>
</VnLv>
<VnLv <VnLv
:label="t('entry.summary.travelShipped')" :label="t('globals.warehouseOut')"
:value="toDate(entity.travel?.shipped)" :value="entity.travel?.warehouseOut?.name"
/>
<VnLv
:label="t('entry.summary.travelLanded')"
:value="toDate(entity.travel?.landed)"
/>
<VnLv :label="t('entry.summary.currency')" :value="entity?.currency?.code" />
<VnLv
:label="t('entry.summary.invoiceAmount')"
:value="entity?.invoiceAmount"
/>
<VnLv
:label="t('entry.summary.entryType')"
:value="entity?.entryType?.description"
/> />
</template> </template>
<template #icons="{ entity }"> <template #icons="{ entity }">
@ -230,14 +131,6 @@ async function deleteEntry() {
}}</QTooltip }}</QTooltip
> >
</QIcon> </QIcon>
<QIcon
v-if="!entity?.travelFk"
name="vn:deletedTicket"
size="xs"
color="primary"
>
<QTooltip>{{ t('This entry is deleted') }}</QTooltip>
</QIcon>
</QCardActions> </QCardActions>
</template> </template>
<template #actions="{ entity }"> <template #actions="{ entity }">
@ -250,6 +143,21 @@ async function deleteEntry() {
> >
<QTooltip>{{ t('Supplier card') }}</QTooltip> <QTooltip>{{ t('Supplier card') }}</QTooltip>
</QBtn> </QBtn>
<QBtn
:to="{
name: 'TravelMain',
query: {
params: JSON.stringify({
agencyModeFk: entity.travel?.agencyModeFk,
}),
},
}"
size="md"
icon="local_airport"
color="primary"
>
<QTooltip>{{ t('All travels with current agency') }}</QTooltip>
</QBtn>
<QBtn <QBtn
:to="{ :to="{
name: 'EntryMain', name: 'EntryMain',
@ -269,24 +177,10 @@ async function deleteEntry() {
</template> </template>
<i18n> <i18n>
es: es:
Travel: Envío
Supplier card: Ficha del proveedor Supplier card: Ficha del proveedor
All travels with current agency: Todos los envíos con la agencia actual All travels with current agency: Todos los envíos con la agencia actual
All entries with current supplier: Todas las entradas con el proveedor actual All entries with current supplier: Todas las entradas con el proveedor actual
Show entry report: Ver informe del pedido Show entry report: Ver informe del pedido
Inventory entry: Es inventario Inventory entry: Es inventario
Virtual entry: Es una redada Virtual entry: Es una redada
shipped: Enviado
landed: Recibido
This entry is deleted: Esta entrada está eliminada
Cannot recalculate prices because this is an inventory entry: No se pueden recalcular los precios porque es una entrada de inventario
Entry deleted: Entrada eliminada
Entry cloned: Entrada clonada
Entry prices recalculated: Precios de la entrada recalculados
Failed to recalculate rates: No se pudieron recalcular las tarifas
Failed to clone entry: No se pudo clonar la entrada
Failed to delete entry: No se pudo eliminar la entrada
Recalculate rates: Recalcular tarifas
Clone: Clonar
Delete: Eliminar
</i18n> </i18n>

View File

@ -54,8 +54,8 @@ const transferEntry = async () => {
<i18n> <i18n>
en: en:
transferEntryDialog: The entries will be transferred to the next day transferEntryDialog: The entries will be transferred to the next day
transferEntry: Partial delay transferEntry: Transfer Entry
es: es:
transferEntryDialog: Se van a transferir las compras al dia siguiente transferEntryDialog: Se van a transferir las compras al dia siguiente
transferEntry: Retraso parcial transferEntry: Transferir Entrada
</i18n> </i18n>

View File

@ -9,7 +9,6 @@ export default {
'shipped', 'shipped',
'agencyModeFk', 'agencyModeFk',
'warehouseOutFk', 'warehouseOutFk',
'warehouseInFk',
'daysInForward', 'daysInForward',
], ],
include: [ include: [
@ -22,13 +21,13 @@ export default {
{ {
relation: 'warehouseOut', relation: 'warehouseOut',
scope: { scope: {
fields: ['name', 'code'], fields: ['name'],
}, },
}, },
{ {
relation: 'warehouseIn', relation: 'warehouseIn',
scope: { scope: {
fields: ['name', 'code'], fields: ['name'],
}, },
}, },
], ],
@ -40,17 +39,5 @@ export default {
fields: ['id', 'nickname'], fields: ['id', 'nickname'],
}, },
}, },
{
relation: 'currency',
scope: {
fields: ['id', 'code'],
},
},
{
relation: 'entryType',
scope: {
fields: ['code', 'description'],
},
},
], ],
}; };

View File

@ -2,17 +2,19 @@
import { onMounted, ref, computed } from 'vue'; import { onMounted, ref, computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { toDate } from 'src/filters';
import { getUrl } from 'src/composables/getUrl';
import axios from 'axios';
import CardSummary from 'components/ui/CardSummary.vue'; import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue'; import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue';
import EntryBuys from './EntryBuys.vue';
import VnTitle from 'src/components/common/VnTitle.vue'; import { toDate, toCurrency, toCelsius } from 'src/filters';
import VnCheckbox from 'src/components/common/VnCheckbox.vue'; import { getUrl } from 'src/composables/getUrl';
import axios from 'axios';
import FetchedTags from 'src/components/ui/FetchedTags.vue';
import VnToSummary from 'src/components/ui/VnToSummary.vue'; import VnToSummary from 'src/components/ui/VnToSummary.vue';
import EntryDescriptorMenu from './EntryDescriptorMenu.vue';
import VnRow from 'src/components/ui/VnRow.vue';
import VnTitle from 'src/components/common/VnTitle.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@ -31,6 +33,117 @@ const entry = ref();
const entryBuys = ref([]); const entryBuys = ref([]);
const entryUrl = ref(); const entryUrl = ref();
onMounted(async () => {
entryUrl.value = (await getUrl('entry/')) + entityId.value;
});
const tableColumnComponents = {
quantity: {
component: () => 'span',
props: () => {},
},
stickers: {
component: () => 'span',
props: () => {},
event: () => {},
},
packagingFk: {
component: () => 'span',
props: () => {},
event: () => {},
},
weight: {
component: () => 'span',
props: () => {},
event: () => {},
},
packing: {
component: () => 'span',
props: () => {},
event: () => {},
},
grouping: {
component: () => 'span',
props: () => {},
event: () => {},
},
buyingValue: {
component: () => 'span',
props: () => {},
event: () => {},
},
amount: {
component: () => 'span',
props: () => {},
event: () => {},
},
pvp: {
component: () => 'span',
props: () => {},
event: () => {},
},
};
const entriesTableColumns = computed(() => {
return [
{
label: t('globals.quantity'),
field: 'quantity',
name: 'quantity',
align: 'left',
},
{
label: t('entry.summary.stickers'),
field: 'stickers',
name: 'stickers',
align: 'left',
},
{
label: t('entry.summary.package'),
field: 'packagingFk',
name: 'packagingFk',
align: 'left',
},
{
label: t('globals.weight'),
field: 'weight',
name: 'weight',
align: 'left',
},
{
label: t('entry.summary.packing'),
field: 'packing',
name: 'packing',
align: 'left',
},
{
label: t('entry.summary.grouping'),
field: 'grouping',
name: 'grouping',
align: 'left',
},
{
label: t('entry.summary.buyingValue'),
field: 'buyingValue',
name: 'buyingValue',
align: 'left',
format: (value) => toCurrency(value),
},
{
label: t('entry.summary.import'),
name: 'amount',
align: 'left',
format: (_, row) => toCurrency(row.buyingValue * row.quantity),
},
{
label: t('entry.summary.pvp'),
name: 'pvp',
align: 'left',
format: (_, row) => toCurrency(row.price2) + ' / ' + toCurrency(row.price3),
},
];
});
async function setEntryData(data) { async function setEntryData(data) {
if (data) entry.value = data; if (data) entry.value = data;
await fetchEntryBuys(); await fetchEntryBuys();
@ -40,18 +153,14 @@ const fetchEntryBuys = async () => {
const { data } = await axios.get(`Entries/${entry.value.id}/getBuys`); const { data } = await axios.get(`Entries/${entry.value.id}/getBuys`);
if (data) entryBuys.value = data; if (data) entryBuys.value = data;
}; };
onMounted(async () => {
entryUrl.value = (await getUrl('entry/')) + entityId.value;
});
</script> </script>
<template> <template>
<CardSummary <CardSummary
ref="summaryRef" ref="summaryRef"
:url="`Entries/${entityId}/getEntry`" :url="`Entries/${entityId}/getEntry`"
@on-fetch="(data) => setEntryData(data)" @on-fetch="(data) => setEntryData(data)"
data-key="EntrySummary" data-key="EntrySummary"
data-cy="entry-summary"
> >
<template #header-left> <template #header-left>
<VnToSummary <VnToSummary
@ -64,67 +173,40 @@ onMounted(async () => {
<template #header> <template #header>
<span>{{ entry.id }} - {{ entry.supplier.nickname }}</span> <span>{{ entry.id }} - {{ entry.supplier.nickname }}</span>
</template> </template>
<template #menu="{ entity }">
<EntryDescriptorMenu :id="entity.id" />
</template>
<template #body> <template #body>
<QCard class="vn-one"> <QCard class="vn-one">
<VnTitle <VnTitle
:url="`#/entry/${entityId}/basic-data`" :url="`#/entry/${entityId}/basic-data`"
:text="t('globals.summary.basicData')" :text="t('globals.summary.basicData')"
/> />
<div class="card-group"> <VnLv :label="t('entry.summary.commission')" :value="entry.commission" />
<div class="card-content">
<VnLv
:label="t('entry.summary.commission')"
:value="entry?.commission"
/>
<VnLv <VnLv
:label="t('entry.summary.currency')" :label="t('entry.summary.currency')"
:value="entry?.currency?.name" :value="entry.currency?.name"
/> />
<VnLv <VnLv :label="t('globals.company')" :value="entry.company.code" />
:label="t('globals.company')" <VnLv :label="t('globals.reference')" :value="entry.reference" />
:value="entry?.company?.code"
/>
<VnLv :label="t('globals.reference')" :value="entry?.reference" />
<VnLv <VnLv
:label="t('entry.summary.invoiceNumber')" :label="t('entry.summary.invoiceNumber')"
:value="entry?.invoiceNumber" :value="entry.invoiceNumber"
/> />
</div> <VnLv
<div class="card-content"> :label="t('entry.basicData.initialTemperature')"
<VnCheckbox :value="toCelsius(entry.initialTemperature)"
:label="t('entry.summary.ordered')"
v-model="entry.isOrdered"
:disable="true"
size="xs"
/> />
<VnCheckbox <VnLv
:label="t('globals.confirmed')" :label="t('entry.basicData.finalTemperature')"
v-model="entry.isConfirmed" :value="toCelsius(entry.finalTemperature)"
:disable="true"
size="xs"
/> />
<VnCheckbox
:label="t('entry.summary.booked')"
v-model="entry.isBooked"
:disable="true"
size="xs"
/>
<VnCheckbox
:label="t('entry.summary.excludedFromAvailable')"
v-model="entry.isExcludedFromAvailable"
:disable="true"
size="xs"
/>
</div>
</div>
</QCard> </QCard>
<QCard class="vn-one" v-if="entry?.travelFk"> <QCard class="vn-one">
<VnTitle <VnTitle
:url="`#/travel/${entry.travel.id}/summary`" :url="`#/entry/${entityId}/basic-data`"
:text="t('Travel')" :text="t('globals.summary.basicData')"
/> />
<div class="card-group">
<div class="card-content">
<VnLv :label="t('entry.summary.travelReference')"> <VnLv :label="t('entry.summary.travelReference')">
<template #value> <template #value>
<span class="link"> <span class="link">
@ -138,7 +220,7 @@ onMounted(async () => {
:value="entry.travel.agency?.name" :value="entry.travel.agency?.name"
/> />
<VnLv <VnLv
:label="t('entry.summary.travelShipped')" :label="t('globals.shipped')"
:value="toDate(entry.travel.shipped)" :value="toDate(entry.travel.shipped)"
/> />
<VnLv <VnLv
@ -146,72 +228,104 @@ onMounted(async () => {
:value="entry.travel.warehouseOut?.name" :value="entry.travel.warehouseOut?.name"
/> />
<VnLv <VnLv
:label="t('entry.summary.travelLanded')" :label="t('entry.summary.travelDelivered')"
:value="toDate(entry.travel.landed)" :value="entry.travel.isDelivered"
/> />
<VnLv :label="t('globals.landed')" :value="toDate(entry.travel.landed)" />
<VnLv <VnLv
:label="t('globals.warehouseIn')" :label="t('globals.warehouseIn')"
:value="entry.travel.warehouseIn?.name" :value="entry.travel.warehouseIn?.name"
/> />
</div> <VnLv
<div class="card-content">
<VnCheckbox
:label="t('entry.summary.travelDelivered')"
v-model="entry.travel.isDelivered"
:disable="true"
size="xs"
/>
<VnCheckbox
:label="t('entry.summary.travelReceived')" :label="t('entry.summary.travelReceived')"
v-model="entry.travel.isReceived" :value="entry.travel.isReceived"
:disable="true"
size="xs"
/> />
</div> </QCard>
</div> <QCard class="vn-one">
<VnTitle :url="`#/travel/${entityId}/summary`" :text="t('Travel data')" />
<VnRow class="block">
<VnLv :label="t('entry.summary.ordered')" :value="entry.isOrdered" />
<VnLv :label="t('globals.confirmed')" :value="entry.isConfirmed" />
<VnLv :label="t('entry.summary.booked')" :value="entry.isBooked" />
<VnLv
:label="t('entry.summary.excludedFromAvailable')"
:value="entry.isExcludedFromAvailable"
/>
</VnRow>
</QCard> </QCard>
<QCard class="vn-max"> <QCard class="vn-max">
<VnTitle <VnTitle
:url="`#/entry/${entityId}/buys`" :url="`#/entry/${entityId}/buys`"
:text="t('entry.summary.buys')" :text="t('entry.summary.buys')"
/> />
<EntryBuys <QTable
v-if="entityId" :rows="entryBuys"
:id="Number(entityId)" :columns="entriesTableColumns"
:editable-mode="false" row-key="index"
table-height="49vh" class="full-width q-mt-md"
/> :no-data-label="t('globals.noResults')"
>
<template #body="{ cols, row, rowIndex }">
<QTr no-hover>
<QTd v-for="col in cols" :key="col?.name">
<component
:is="tableColumnComponents[col?.name].component()"
v-bind="tableColumnComponents[col?.name].props()"
@click="tableColumnComponents[col?.name].event()"
class="col-content"
>
<template
v-if="
col?.name !== 'observation' &&
col?.name !== 'isConfirmed'
"
>{{ col.value }}</template
>
<QTooltip v-if="col.toolTip">{{
col.toolTip
}}</QTooltip>
</component>
</QTd>
</QTr>
<QTr no-hover>
<QTd>
<span>{{ row.item.itemType.code }}</span>
</QTd>
<QTd>
<span>{{ row.item.id }}</span>
</QTd>
<QTd>
<span>{{ row.item.size }}</span>
</QTd>
<QTd>
<span>{{ toCurrency(row.item.minPrice) }}</span>
</QTd>
<QTd colspan="6">
<span>{{ row.item.concept }}</span>
<span v-if="row.item.subName" class="subName">
{{ row.item.subName }}
</span>
<FetchedTags :item="row.item" />
</QTd>
</QTr>
<!-- Esta última row es utilizada para agregar un espaciado y así marcar una diferencia visual entre los diferentes buys -->
<QTr v-if="rowIndex !== entryBuys.length - 1">
<QTd colspan="10" class="vn-table-separation-row" />
</QTr>
</template>
</QTable>
</QCard> </QCard>
</template> </template>
</CardSummary> </CardSummary>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.card-group { .separation-row {
display: flex; background-color: var(--vn-section-color) !important;
flex-direction: column;
}
.card-content {
display: flex;
flex-direction: column;
text-overflow: ellipsis;
> div {
max-height: 24px;
}
}
@media (min-width: 1010px) {
.card-group {
flex-direction: row;
}
.card-content {
flex: 1;
margin-right: 16px;
}
} }
</style> </style>
<i18n> <i18n>
es: es:
Travel: Envío Travel data: Datos envío
InvoiceIn data: Datos factura
</i18n> </i18n>

View File

@ -19,7 +19,6 @@ const props = defineProps({
const currenciesOptions = ref([]); const currenciesOptions = ref([]);
const companiesOptions = ref([]); const companiesOptions = ref([]);
const entryFilterPanel = ref();
</script> </script>
<template> <template>
@ -39,7 +38,7 @@ const entryFilterPanel = ref();
@on-fetch="(data) => (currenciesOptions = data)" @on-fetch="(data) => (currenciesOptions = data)"
auto-load auto-load
/> />
<VnFilterPanel ref="entryFilterPanel" :data-key="props.dataKey" :search-button="true"> <VnFilterPanel :data-key="props.dataKey" :search-button="true">
<template #tags="{ tag, formatFn }"> <template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs"> <div class="q-gutter-x-xs">
<strong>{{ t(`entryFilter.params.${tag.label}`) }}: </strong> <strong>{{ t(`entryFilter.params.${tag.label}`) }}: </strong>
@ -49,65 +48,70 @@ const entryFilterPanel = ref();
<template #body="{ params, searchFn }"> <template #body="{ params, searchFn }">
<QItem> <QItem>
<QItemSection> <QItemSection>
<QCheckbox <VnInput
:label="t('params.isExcludedFromAvailable')" v-model="params.search"
v-model="params.isExcludedFromAvailable" :label="t('entryFilter.params.search')"
toggle-indeterminate
>
<QTooltip>
{{ t('params.isExcludedFromAvailable') }}
</QTooltip>
</QCheckbox>
</QItemSection>
<QItemSection>
<QCheckbox
:label="t('params.isOrdered')"
v-model="params.isOrdered"
toggle-indeterminate
>
<QTooltip>
{{ t('entry.list.tableVisibleColumns.isOrdered') }}
</QTooltip>
</QCheckbox>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QCheckbox
:label="t('params.isReceived')"
v-model="params.isReceived"
toggle-indeterminate
>
<QTooltip>
{{ t('entry.list.tableVisibleColumns.isReceived') }}
</QTooltip>
</QCheckbox>
</QItemSection>
<QItemSection>
<QCheckbox
:label="t('entry.list.tableVisibleColumns.isConfirmed')"
v-model="params.isConfirmed"
toggle-indeterminate
>
<QTooltip>
{{ t('entry.list.tableVisibleColumns.isConfirmed') }}
</QTooltip>
</QCheckbox>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputDate
:label="t('params.landed')"
v-model="params.landed"
@update:model-value="searchFn()"
is-outlined is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput v-model="params.id" label="Id" is-outlined /> <VnInput
v-model="params.reference"
:label="t('entryFilter.params.reference')"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
v-model="params.invoiceNumber"
:label="t('entryFilter.params.invoiceNumber')"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
v-model="params.travelFk"
:label="t('entryFilter.params.travelFk')"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
:label="t('entryFilter.params.companyFk')"
v-model="params.companyFk"
@update:model-value="searchFn()"
:options="companiesOptions"
option-value="id"
option-label="code"
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
:label="t('entryFilter.params.currencyFk')"
v-model="params.currencyFk"
@update:model-value="searchFn()"
:options="currenciesOptions"
option-value="id"
option-label="name"
hide-selected
dense
outlined
rounded
/>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
@ -121,165 +125,62 @@ const entryFilterPanel = ref();
rounded rounded
/> />
</QItemSection> </QItemSection>
<QItemSection>
<VnInput
v-model="params.invoiceNumber"
:label="t('params.invoiceNumber')"
is-outlined
/>
</QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput <VnInputDate
v-model="params.reference" :label="t('entryFilter.params.created')"
:label="t('entry.list.tableVisibleColumns.reference')" v-model="params.created"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
:label="t('params.agencyModeId')"
v-model="params.agencyModeId"
@update:model-value="searchFn()" @update:model-value="searchFn()"
url="AgencyModes"
:fields="['id', 'name']"
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
v-model="params.evaNotes"
:label="t('params.evaNotes')"
is-outlined is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnSelect <VnInputDate
:label="t('params.warehouseOutFk')" :label="t('entryFilter.params.from')"
v-model="params.warehouseOutFk" v-model="params.from"
@update:model-value="searchFn()" @update:model-value="searchFn()"
url="Warehouses"
:fields="['id', 'name']"
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
:label="t('params.warehouseInFk')"
v-model="params.warehouseInFk"
@update:model-value="searchFn()"
url="Warehouses"
:fields="['id', 'name']"
hide-selected
dense
outlined
rounded
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt?.name }}
</QItemLabel>
<QItemLabel caption>
{{ `#${scope.opt?.id} , ${scope.opt?.nickname}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
v-model="params.invoiceNumber"
:label="t('params.invoiceNumber')"
is-outlined is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnSelect <VnInputDate
:label="t('params.entryTypeCode')" :label="t('entryFilter.params.to')"
v-model="params.entryTypeCode" v-model="params.to"
@update:model-value="searchFn()" @update:model-value="searchFn()"
url="EntryTypes" is-outlined
:fields="['code', 'description']"
option-value="code"
option-label="description"
hide-selected
dense
outlined
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput <QCheckbox
v-model="params.evaNotes" :label="t('entryFilter.params.isBooked')"
:label="t('params.evaNotes')" v-model="params.isBooked"
is-outlined toggle-indeterminate
/>
</QItemSection>
<QItemSection>
<QCheckbox
:label="t('entryFilter.params.isConfirmed')"
v-model="params.isConfirmed"
toggle-indeterminate
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QCheckbox
:label="t('entryFilter.params.isOrdered')"
v-model="params.isOrdered"
toggle-indeterminate
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>
</VnFilterPanel> </VnFilterPanel>
</template> </template>
<i18n>
en:
params:
isExcludedFromAvailable: Inventory
isOrdered: Ordered
isReceived: Received
isConfirmed: Confirmed
isRaid: Raid
landed: Date
id: Id
supplierFk: Supplier
invoiceNumber: Invoice number
reference: Ref/Alb/Guide
agencyModeId: Agency mode
evaNotes: Notes
warehouseOutFk: Origin
warehouseInFk: Destiny
entryTypeCode: Entry type
hasToShowDeletedEntries: Show deleted entries
es:
params:
isExcludedFromAvailable: Inventario
isOrdered: Pedida
isConfirmed: Confirmado
isReceived: Recibida
isRaid: Raid
landed: Fecha
id: Id
supplierFk: Proveedor
invoiceNumber: Núm. factura
reference: Ref/Alb/Guía
agencyModeId: Modo agencia
evaNotes: Notas
warehouseOutFk: Origen
warehouseInFk: Destino
entryTypeCode: Tipo de entrada
hasToShowDeletedEntries: Mostrar entradas eliminadas
</i18n>

View File

@ -1,25 +1,21 @@
<script setup> <script setup>
import axios from 'axios';
import VnSection from 'src/components/common/VnSection.vue';
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useState } from 'src/composables/useState';
import { onBeforeMount } from 'vue';
import EntryFilter from './EntryFilter.vue'; import EntryFilter from './EntryFilter.vue';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import { toCelsius, toDate } from 'src/filters';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import EntrySummary from './Card/EntrySummary.vue';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
import VnSelectTravelExtended from 'src/components/common/VnSelectTravelExtended.vue'; import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue';
import { toDate } from 'src/filters'; import VnSection from 'src/components/common/VnSection.vue';
const { t } = useI18n(); const { t } = useI18n();
const tableRef = ref(); const tableRef = ref();
const defaultEntry = ref({});
const state = useState();
const user = state.getUser();
const dataKey = 'EntryList'; const dataKey = 'EntryList';
const entryQueryFilter = { const { viewSummary } = useSummaryDialog();
const entryFilter = {
include: [ include: [
{ {
relation: 'suppliers', relation: 'suppliers',
@ -44,53 +40,44 @@ const entryQueryFilter = {
const columns = computed(() => [ const columns = computed(() => [
{ {
label: 'Ex', name: 'status',
toolTip: t('entry.list.tableVisibleColumns.isExcludedFromAvailable'), columnFilter: false,
name: 'isExcludedFromAvailable',
component: 'checkbox',
width: '35px',
},
{
label: 'Pe',
toolTip: t('entry.list.tableVisibleColumns.isOrdered'),
name: 'isOrdered',
component: 'checkbox',
width: '35px',
},
{
label: 'Le',
toolTip: t('entry.list.tableVisibleColumns.isConfirmed'),
name: 'isConfirmed',
component: 'checkbox',
width: '35px',
},
{
label: 'Re',
toolTip: t('entry.list.tableVisibleColumns.isReceived'),
name: 'isReceived',
component: 'checkbox',
width: '35px',
},
{
label: t('entry.list.tableVisibleColumns.landed'),
name: 'landed',
component: 'date',
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.landed)),
width: '105px',
}, },
{ {
align: 'left',
label: t('globals.id'), label: t('globals.id'),
name: 'id', name: 'id',
isId: true, isId: true,
component: 'number',
chip: { chip: {
condition: () => true, condition: () => true,
}, },
}, },
{ {
align: 'left',
label: t('globals.reference'),
name: 'reference',
isTitle: true,
component: 'input',
columnField: {
component: null,
},
create: true,
cardVisible: true,
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.created'),
name: 'created',
create: true,
cardVisible: true,
component: 'date',
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.created)),
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.supplierFk'), label: t('entry.list.tableVisibleColumns.supplierFk'),
name: 'supplierFk', name: 'supplierFk',
create: true, create: true,
@ -100,205 +87,164 @@ const columns = computed(() => [
url: 'suppliers', url: 'suppliers',
fields: ['id', 'name'], fields: ['id', 'name'],
}, },
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(row.supplierName), format: (row, dashIfEmpty) => dashIfEmpty(row.supplierName),
}, },
{ {
align: 'left', align: 'left',
label: t('entry.list.tableVisibleColumns.invoiceNumber'), label: t('entry.list.tableVisibleColumns.isBooked'),
name: 'invoiceNumber', name: 'isBooked',
component: 'input',
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.reference'),
name: 'reference',
isTitle: true,
component: 'input',
columnField: {
component: null,
},
cardVisible: true, cardVisible: true,
},
{
align: 'left',
label: 'AWB',
name: 'awbCode',
component: 'input',
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.agencyModeId'),
name: 'agencyModeId',
cardVisible: true,
component: 'select',
attrs: {
url: 'agencyModes',
fields: ['id', 'name'],
},
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(row.agencyModeName),
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.evaNotes'),
name: 'evaNotes',
component: 'input',
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.warehouseOutFk'),
name: 'warehouseOutFk',
cardVisible: true,
component: 'select',
attrs: {
url: 'warehouses',
fields: ['id', 'name'],
},
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(row.warehouseOutName),
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.warehouseInFk'),
name: 'warehouseInFk',
cardVisible: true,
component: 'select',
attrs: {
url: 'warehouses',
fields: ['id', 'name'],
},
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(row.warehouseInName),
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.entryTypeDescription'),
name: 'entryTypeCode',
cardVisible: true,
},
{
name: 'dated',
label: t('entry.list.tableVisibleColumns.dated'),
component: 'date',
cardVisible: false,
visible: false,
create: true, create: true,
component: 'checkbox',
}, },
{ {
name: 'companyFk', align: 'left',
label: t('entry.list.tableVisibleColumns.isConfirmed'),
name: 'isConfirmed',
cardVisible: true,
create: true,
component: 'checkbox',
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.isOrdered'),
name: 'isOrdered',
cardVisible: true,
create: true,
component: 'checkbox',
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.companyFk'), label: t('entry.list.tableVisibleColumns.companyFk'),
cardVisible: false, name: 'companyFk',
visible: false,
create: true,
component: 'select', component: 'select',
attrs: { attrs: {
optionValue: 'id', url: 'companies',
fields: ['id', 'code'],
optionLabel: 'code', optionLabel: 'code',
url: 'Companies', optionValue: 'id',
},
columnField: {
component: null,
},
create: true,
format: (row, dashIfEmpty) => dashIfEmpty(row.companyCode),
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.travelFk'),
name: 'travelFk',
component: 'select',
attrs: {
url: 'travels',
fields: ['id', 'ref'],
optionLabel: 'ref',
optionValue: 'id',
},
columnField: {
component: null,
},
create: true,
format: (row, dashIfEmpty) => dashIfEmpty(row.travelRef),
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.invoiceAmount'),
name: 'invoiceAmount',
cardVisible: true,
},
{
align: 'left',
name: 'initialTemperature',
label: t('entry.basicData.initialTemperature'),
field: 'initialTemperature',
format: (row) => toCelsius(row.initialTemperature),
},
{
align: 'left',
name: 'finalTemperature',
label: t('entry.basicData.finalTemperature'),
field: 'finalTemperature',
format: (row) => toCelsius(row.finalTemperature),
},
{
label: t('entry.list.tableVisibleColumns.isExcludedFromAvailable'),
name: 'isExcludedFromAvailable',
columnFilter: {
inWhere: true,
}, },
}, },
{ {
name: 'travelFk', align: 'right',
label: t('entry.list.tableVisibleColumns.travelFk'), name: 'tableActions',
cardVisible: false, actions: [
visible: false, {
create: true, title: t('components.smartCard.viewSummary'),
icon: 'preview',
action: (row) => viewSummary(row.id, EntrySummary),
isPrimary: true,
},
],
}, },
]); ]);
function getBadgeAttrs(row) {
const date = row.landed;
let today = Date.vnNew();
today.setHours(0, 0, 0, 0);
let timeTicket = new Date(date);
timeTicket.setHours(0, 0, 0, 0);
let timeDiff = today - timeTicket;
if (timeDiff > 0) return { color: 'warning', 'text-color': 'black' };
switch (row.entryTypeCode) {
case 'regularization':
case 'life':
case 'internal':
case 'inventory':
if (!row.isOrdered || !row.isConfirmed)
return { color: 'negative', 'text-color': 'black' };
break;
case 'product':
case 'packaging':
case 'devaluation':
case 'payment':
case 'transport':
if (
row.invoiceAmount === null ||
(row.invoiceNumber === null && row.reference === null) ||
!row.isOrdered ||
!row.isConfirmed
)
return { color: 'negative', 'text-color': 'black' };
break;
default:
break;
}
if (timeDiff < 0) return { color: 'info', 'text-color': 'black' };
return { color: 'transparent' };
}
onBeforeMount(async () => {
defaultEntry.value = (await axios.get('EntryConfigs/findOne')).data;
});
</script> </script>
<template> <template>
<VnSection <VnSection
:data-key="dataKey" :data-key="dataKey"
:columns="columns"
prefix="entry" prefix="entry"
url="Entries/filter" url="Entries/filter"
:array-data-props="{ :array-data-props="{
url: 'Entries/filter', url: 'Entries/filter',
order: 'landed DESC', order: 'id DESC',
userFilter: EntryFilter, userFilter: entryFilter,
}" }"
> >
<template #advanced-menu> <template #advanced-menu>
<EntryFilter :data-key="dataKey" /> <EntryFilter data-key="EntryList" />
</template> </template>
<template #body> <template #body>
<VnTable <VnTable
v-if="defaultEntry.defaultSupplierFk"
ref="tableRef" ref="tableRef"
:data-key="dataKey" :data-key="dataKey"
url="Entries/filter"
:filter="entryQueryFilter"
order="landed DESC"
:create="{ :create="{
urlCreate: 'Entries', urlCreate: 'Entries',
title: t('Create entry'), title: t('entry.list.newEntry'),
onDataSaved: ({ id }) => tableRef.redirect(id), onDataSaved: ({ id }) => tableRef.redirect(id),
formInitialData: { formInitialData: {},
supplierFk: defaultEntry.defaultSupplierFk,
dated: Date.vnNew(),
companyFk: user?.companyFk,
},
}" }"
:columns="columns" :columns="columns"
redirect="entry" redirect="entry"
:right-search="false" :right-search="false"
> >
<template #column-landed="{ row }"> <template #column-status="{ row }">
<QBadge <div class="row q-gutter-xs">
v-if="row?.travelFk" <QIcon
v-bind="getBadgeAttrs(row)" v-if="!!row.isExcludedFromAvailable"
class="q-pa-sm" name="vn:inventory"
style="font-size: 14px" color="primary"
> >
{{ toDate(row.landed) }} <QTooltip>{{
</QBadge> t(
'entry.list.tableVisibleColumns.isExcludedFromAvailable',
)
}}</QTooltip>
</QIcon>
<QIcon v-if="!!row.isRaid" name="vn:net" color="primary">
<QTooltip>
{{
t('globals.raid', {
daysInForward: row.daysInForward,
})
}}</QTooltip
>
</QIcon>
</div>
</template> </template>
<template #column-supplierFk="{ row }"> <template #column-supplierFk="{ row }">
<span class="link" @click.stop> <span class="link" @click.stop>
@ -306,26 +252,13 @@ onBeforeMount(async () => {
<SupplierDescriptorProxy :id="row.supplierFk" /> <SupplierDescriptorProxy :id="row.supplierFk" />
</span> </span>
</template> </template>
<template #column-create-travelFk="{ data }"> <template #column-travelFk="{ row }">
<VnSelectTravelExtended <span class="link" @click.stop>
:data="data" {{ row.travelRef }}
v-model="data.travelFk" <TravelDescriptorProxy :id="row.travelFk" />
:onFilterTravelSelected=" </span>
(data, result) => (data.travelFk = result)
"
data-cy="entry-travel-select"
/>
</template> </template>
</VnTable> </VnTable>
</template> </template>
</VnSection> </VnSection>
</template> </template>
<i18n>
es:
Inventory entry: Es inventario
Virtual entry: Es una redada
Search entries: Buscar entradas
You can search by entry reference: Puedes buscar por referencia de la entrada
Create entry: Crear entrada
</i18n>

View File

@ -1,36 +1,21 @@
entry: entry:
lock:
title: Lock entry
message: This entry has been locked by {userName} for {time} minutes. Do you want to unlock it?
success: The entry has been locked successfully
list: list:
newEntry: New entry newEntry: New entry
tableVisibleColumns: tableVisibleColumns:
isExcludedFromAvailable: Exclude from inventory created: Creation
isOrdered: Ordered
isConfirmed: Ready to label
isReceived: Received
isRaid: Raid
landed: Date
supplierFk: Supplier supplierFk: Supplier
reference: Ref/Alb/Guide
invoiceNumber: Invoice
agencyModeId: Agency
isBooked: Booked isBooked: Booked
isConfirmed: Confirmed
isOrdered: Ordered
companyFk: Company companyFk: Company
evaNotes: Notes
warehouseOutFk: Origin
warehouseInFk: Destiny
entryTypeDescription: Entry type
invoiceAmount: Import
travelFk: Travel travelFk: Travel
dated: Dated isExcludedFromAvailable: Inventory
invoiceAmount: Import
inventoryEntry: Inventory entry inventoryEntry: Inventory entry
summary: summary:
commission: Commission commission: Commission
currency: Currency currency: Currency
invoiceNumber: Invoice number invoiceNumber: Invoice number
invoiceAmount: Invoice amount
ordered: Ordered ordered: Ordered
booked: Booked booked: Booked
excludedFromAvailable: Inventory excludedFromAvailable: Inventory
@ -48,7 +33,6 @@ entry:
buyingValue: Buying value buyingValue: Buying value
import: Import import: Import
pvp: PVP pvp: PVP
entryType: Entry type
basicData: basicData:
travel: Travel travel: Travel
currency: Currency currency: Currency
@ -85,55 +69,17 @@ entry:
landing: Landing landing: Landing
isExcludedFromAvailable: Es inventory isExcludedFromAvailable: Es inventory
params: params:
isExcludedFromAvailable: Exclude from inventory toShipped: To
isOrdered: Ordered fromShipped: From
isConfirmed: Ready to label daysOnward: Days onward
isReceived: Received daysAgo: Days ago
isIgnored: Ignored warehouseInFk: Warehouse in
isRaid: Raid
landed: Date
supplierFk: Supplier
reference: Ref/Alb/Guide
invoiceNumber: Invoice
agencyModeId: Agency
isBooked: Booked
companyFk: Company
evaNotes: Notes
warehouseOutFk: Origin
warehouseInFk: Destiny
entryTypeDescription: Entry type
invoiceAmount: Import
travelFk: Travel
dated: Dated
itemFk: Item id
hex: Color
name: Item name
size: Size
stickers: Stickers
packagingFk: Packaging
weight: Kg
groupingMode: Grouping selector
grouping: Grouping
quantity: Quantity
buyingValue: Buying value
price2: Package
price3: Box
minPrice: Minumum price
hasMinPrice: Has minimum price
packingOut: Packing out
comment: Comment
subName: Supplier name
tags: Tags
company_name: Company name
itemTypeFk: Item type
workerFk: Worker id
search: Search entries search: Search entries
searchInfo: You can search by entry reference searchInfo: You can search by entry reference
descriptorMenu: descriptorMenu:
showEntryReport: Show entry report showEntryReport: Show entry report
entryFilter: entryFilter:
params: params:
isExcludedFromAvailable: Exclude from inventory
invoiceNumber: Invoice number invoiceNumber: Invoice number
travelFk: Travel travelFk: Travel
companyFk: Company companyFk: Company
@ -145,16 +91,8 @@ entryFilter:
isBooked: Booked isBooked: Booked
isConfirmed: Confirmed isConfirmed: Confirmed
isOrdered: Ordered isOrdered: Ordered
isReceived: Received
search: General search search: General search
reference: Reference reference: Reference
landed: Landed
id: Id
agencyModeId: Agency
evaNotes: Notes
warehouseOutFk: Origin
warehouseInFk: Destiny
entryTypeCode: Entry type
myEntries: myEntries:
id: ID id: ID
landed: Landed landed: Landed

View File

@ -1,36 +1,21 @@
entry: entry:
lock:
title: Entrada bloqueada
message: Esta entrada ha sido bloqueada por {userName} hace {time} minutos. ¿Quieres desbloquearla?
success: La entrada ha sido bloqueada correctamente
list: list:
newEntry: Nueva entrada newEntry: Nueva entrada
tableVisibleColumns: tableVisibleColumns:
isExcludedFromAvailable: Excluir del inventario created: Creación
isOrdered: Pedida
isConfirmed: Lista para etiquetar
isReceived: Recibida
isRaid: Redada
landed: Fecha
supplierFk: Proveedor supplierFk: Proveedor
invoiceNumber: Nº Factura
reference: Ref/Alb/Guía
agencyModeId: Agencia
isBooked: Asentado isBooked: Asentado
isConfirmed: Confirmado
isOrdered: Pedida
companyFk: Empresa companyFk: Empresa
travelFk: Envio travelFk: Envio
evaNotes: Notas isExcludedFromAvailable: Inventario
warehouseOutFk: Origen
warehouseInFk: Destino
entryTypeDescription: Tipo entrada
invoiceAmount: Importe invoiceAmount: Importe
dated: Fecha
inventoryEntry: Es inventario inventoryEntry: Es inventario
summary: summary:
commission: Comisión commission: Comisión
currency: Moneda currency: Moneda
invoiceNumber: Núm. factura invoiceNumber: Núm. factura
invoiceAmount: Importe
ordered: Pedida ordered: Pedida
booked: Contabilizada booked: Contabilizada
excludedFromAvailable: Inventario excludedFromAvailable: Inventario
@ -49,13 +34,12 @@ entry:
buyingValue: Coste buyingValue: Coste
import: Importe import: Importe
pvp: PVP pvp: PVP
entryType: Tipo entrada
basicData: basicData:
travel: Envío travel: Envío
currency: Moneda currency: Moneda
observation: Observación observation: Observación
commission: Comisión commission: Comisión
booked: Contabilizada booked: Asentado
excludedFromAvailable: Inventario excludedFromAvailable: Inventario
initialTemperature: Ini °C initialTemperature: Ini °C
finalTemperature: Fin °C finalTemperature: Fin °C
@ -85,70 +69,31 @@ entry:
packingOut: Embalaje envíos packingOut: Embalaje envíos
landing: Llegada landing: Llegada
isExcludedFromAvailable: Es inventario isExcludedFromAvailable: Es inventario
params:
toShipped: Hasta
fromShipped: Desde
warehouseInFk: Alm. entrada
daysOnward: Días adelante
daysAgo: Días atras
descriptorMenu:
showEntryReport: Ver informe del pedido
search: Buscar entradas search: Buscar entradas
searchInfo: Puedes buscar por referencia de entrada searchInfo: Puedes buscar por referencia de entrada
params:
isExcludedFromAvailable: Excluir del inventario
isOrdered: Pedida
isConfirmed: Lista para etiquetar
isReceived: Recibida
isRaid: Redada
isIgnored: Ignorado
landed: Fecha
supplierFk: Proveedor
invoiceNumber: Nº Factura
reference: Ref/Alb/Guía
agencyModeId: Agencia
isBooked: Asentado
companyFk: Empresa
travelFk: Envio
evaNotes: Notas
warehouseOutFk: Origen
warehouseInFk: Destino
entryTypeDescription: Tipo entrada
invoiceAmount: Importe
dated: Fecha
itemFk: Id artículo
hex: Color
name: Nombre artículo
size: Medida
stickers: Etiquetas
packagingFk: Embalaje
weight: Kg
groupinMode: Selector de grouping
grouping: Grouping
quantity: Quantity
buyingValue: Precio de compra
price2: Paquete
price3: Caja
minPrice: Precio mínimo
hasMinPrice: Tiene precio mínimo
packingOut: Packing out
comment: Referencia
subName: Nombre proveedor
tags: Etiquetas
company_name: Nombre empresa
itemTypeFk: Familia
workerFk: Comprador
entryFilter: entryFilter:
params: params:
isExcludedFromAvailable: Inventario
isOrdered: Pedida
isConfirmed: Confirmado
isReceived: Recibida
isRaid: Raid
landed: Fecha
id: Id
supplierFk: Proveedor
invoiceNumber: Núm. factura invoiceNumber: Núm. factura
reference: Ref/Alb/Guía travelFk: Envío
agencyModeId: Modo agencia companyFk: Empresa
evaNotes: Notas currencyFk: Moneda
warehouseOutFk: Origen supplierFk: Proveedor
warehouseInFk: Destino from: Desde
entryTypeCode: Tipo de entrada to: Hasta
hasToShowDeletedEntries: Mostrar entradas eliminadas created: Fecha creación
isBooked: Asentado
isConfirmed: Confirmado
isOrdered: Pedida
search: Búsqueda general
reference: Referencia
myEntries: myEntries:
id: ID id: ID
landed: F. llegada landed: F. llegada

View File

@ -125,7 +125,7 @@ function deleteFile(dmsFk) {
<VnInput <VnInput
clearable clearable
clear-icon="close" clear-icon="close"
:label="t('invoiceIn.supplierRef')" :label="t('Supplier ref')"
v-model="data.supplierRef" v-model="data.supplierRef"
/> />
</VnRow> </VnRow>
@ -149,7 +149,6 @@ function deleteFile(dmsFk) {
option-value="id" option-value="id"
option-label="id" option-label="id"
:filter-options="['id', 'name']" :filter-options="['id', 'name']"
data-cy="UnDeductibleVatSelect"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -311,6 +310,7 @@ function deleteFile(dmsFk) {
supplierFk: Supplier supplierFk: Supplier
es: es:
supplierFk: Proveedor supplierFk: Proveedor
Supplier ref: Ref. proveedor
Expedition date: Fecha expedición Expedition date: Fecha expedición
Operation date: Fecha operación Operation date: Fecha operación
Undeductible VAT: Iva no deducible Undeductible VAT: Iva no deducible

View File

@ -90,6 +90,7 @@ async function setInvoiceCorrection(id) {
<template> <template>
<CardDescriptor <CardDescriptor
ref="cardDescriptorRef" ref="cardDescriptorRef"
module="InvoiceIn"
data-key="InvoiceIn" data-key="InvoiceIn"
:url="`InvoiceIns/${entityId}`" :url="`InvoiceIns/${entityId}`"
:filter="filter" :filter="filter"

View File

@ -186,7 +186,7 @@ const createInvoiceInCorrection = async () => {
clickable clickable
@click="book(entityId)" @click="book(entityId)"
> >
<QItemSection>{{ t('invoiceIn.descriptorMenu.book') }}</QItemSection> <QItemSection>{{ t('invoiceIn.descriptorMenu.toBook') }}</QItemSection>
</QItem> </QItem>
</template> </template>
</InvoiceInToBook> </InvoiceInToBook>
@ -197,7 +197,7 @@ const createInvoiceInCorrection = async () => {
@click="triggerMenu('unbook')" @click="triggerMenu('unbook')"
> >
<QItemSection> <QItemSection>
{{ t('invoiceIn.descriptorMenu.unbook') }} {{ t('invoiceIn.descriptorMenu.toUnbook') }}
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem <QItem

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, computed, onBeforeMount } from 'vue'; import { ref, computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import axios from 'axios'; import axios from 'axios';
@ -12,7 +12,6 @@ import VnSelect from 'src/components/common/VnSelect.vue';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import { toCurrency } from 'filters/index';
const route = useRoute(); const route = useRoute();
const { notify } = useNotify(); const { notify } = useNotify();
@ -27,7 +26,7 @@ const invoiceInFormRef = ref();
const invoiceId = +route.params.id; const invoiceId = +route.params.id;
const filter = { where: { invoiceInFk: invoiceId } }; const filter = { where: { invoiceInFk: invoiceId } };
const areRows = ref(false); const areRows = ref(false);
const totals = ref();
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'duedate', name: 'duedate',
@ -67,8 +66,6 @@ const columns = computed(() => [
}, },
]); ]);
const totalAmount = computed(() => getTotal(invoiceInFormRef.value.formData, 'amount'));
const isNotEuro = (code) => code != 'EUR'; const isNotEuro = (code) => code != 'EUR';
async function insert() { async function insert() {
@ -76,10 +73,6 @@ async function insert() {
await invoiceInFormRef.value.reload(); await invoiceInFormRef.value.reload();
notify(t('globals.dataSaved'), 'positive'); notify(t('globals.dataSaved'), 'positive');
} }
onBeforeMount(async () => {
totals.value = (await axios.get(`InvoiceIns/${invoiceId}/getTotals`)).data;
});
</script> </script>
<template> <template>
<FetchData <FetchData
@ -160,7 +153,7 @@ onBeforeMount(async () => {
<QTd /> <QTd />
<QTd /> <QTd />
<QTd> <QTd>
{{ toCurrency(totalAmount) }} {{ getTotal(rows, 'amount', { currency: 'default' }) }}
</QTd> </QTd>
<QTd> <QTd>
<template v-if="isNotEuro(invoiceIn.currency.code)"> <template v-if="isNotEuro(invoiceIn.currency.code)">
@ -242,16 +235,7 @@ onBeforeMount(async () => {
v-shortcut="'+'" v-shortcut="'+'"
size="lg" size="lg"
round round
@click=" @click="!areRows ? insert() : invoiceInFormRef.insert()"
() => {
if (!areRows) insert();
else
invoiceInFormRef.insert({
amount: (totals.totalTaxableBase - totalAmount).toFixed(2),
invoiceInFk: invoiceId,
});
}
"
/> />
</QPageSticky> </QPageSticky>
</template> </template>

View File

@ -193,7 +193,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
<InvoiceIntoBook> <InvoiceIntoBook>
<template #content="{ book }"> <template #content="{ book }">
<QBtn <QBtn
:label="t('Book')" :label="t('To book')"
color="orange-11" color="orange-11"
text-color="black" text-color="black"
@click="book(entityId)" @click="book(entityId)"
@ -224,7 +224,10 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</span> </span>
</template> </template>
</VnLv> </VnLv>
<VnLv :label="t('invoiceIn.supplierRef')" :value="entity.supplierRef" /> <VnLv
:label="t('invoiceIn.list.supplierRef')"
:value="entity.supplierRef"
/>
<VnLv <VnLv
:label="t('invoiceIn.summary.currency')" :label="t('invoiceIn.summary.currency')"
:value="entity.currency?.code" :value="entity.currency?.code"
@ -354,7 +357,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
entity.totals.totalTaxableBaseForeignValue && entity.totals.totalTaxableBaseForeignValue &&
toCurrency( toCurrency(
entity.totals.totalTaxableBaseForeignValue, entity.totals.totalTaxableBaseForeignValue,
currency, currency
) )
}}</QTd> }}</QTd>
</QTr> </QTr>
@ -389,7 +392,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
entity.totals.totalDueDayForeignValue && entity.totals.totalDueDayForeignValue &&
toCurrency( toCurrency(
entity.totals.totalDueDayForeignValue, entity.totals.totalDueDayForeignValue,
currency, currency
) )
}} }}
</QTd> </QTd>
@ -469,5 +472,5 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
Search invoice: Buscar factura recibida Search invoice: Buscar factura recibida
You can search by invoice reference: Puedes buscar por referencia de la factura You can search by invoice reference: Puedes buscar por referencia de la factura
Totals: Totales Totals: Totales
Book: Contabilizar To book: Contabilizar
</i18n> </i18n>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, computed, nextTick } from 'vue'; import { ref, computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
@ -25,6 +25,7 @@ const sageTaxTypes = ref([]);
const sageTransactionTypes = ref([]); const sageTransactionTypes = ref([]);
const rowsSelected = ref([]); const rowsSelected = ref([]);
const invoiceInFormRef = ref(); const invoiceInFormRef = ref();
const expenseRef = ref();
defineProps({ defineProps({
actionIcon: { actionIcon: {
@ -96,20 +97,6 @@ const columns = computed(() => [
}, },
]); ]);
const taxableBaseTotal = computed(() => {
return getTotal(invoiceInFormRef.value.formData, 'taxableBase');
});
const taxRateTotal = computed(() => {
return getTotal(invoiceInFormRef.value.formData, null, {
cb: taxRate,
});
});
const combinedTotal = computed(() => {
return +taxableBaseTotal.value + +taxRateTotal.value;
});
const filter = { const filter = {
fields: [ fields: [
'id', 'id',
@ -138,7 +125,7 @@ function taxRate(invoiceInTax) {
return ((taxTypeSage / 100) * taxableBase).toFixed(2); return ((taxTypeSage / 100) * taxableBase).toFixed(2);
} }
function autocompleteExpense(evt, row, col, ref) { function autocompleteExpense(evt, row, col) {
const val = evt.target.value; const val = evt.target.value;
if (!val) return; if (!val) return;
@ -147,17 +134,22 @@ function autocompleteExpense(evt, row, col, ref) {
({ id }) => id == useAccountShortToStandard(param), ({ id }) => id == useAccountShortToStandard(param),
); );
ref.vnSelectDialogRef.vnSelectRef.toggleOption(lookup); expenseRef.value.vnSelectDialogRef.vnSelectRef.toggleOption(lookup);
} }
function setCursor(ref) { const taxableBaseTotal = computed(() => {
nextTick(() => { return getTotal(invoiceInFormRef.value.formData, 'taxableBase');
const select = ref.vnSelectDialogRef });
? ref.vnSelectDialogRef.vnSelectRef
: ref.vnSelectRef; const taxRateTotal = computed(() => {
select.$el.querySelector('input').setSelectionRange(0, 0); return getTotal(invoiceInFormRef.value.formData, null, {
cb: taxRate,
});
});
const combinedTotal = computed(() => {
return +taxableBaseTotal.value + +taxRateTotal.value;
}); });
}
</script> </script>
<template> <template>
<FetchData <FetchData
@ -195,24 +187,14 @@ function setCursor(ref) {
<template #body-cell-expense="{ row, col }"> <template #body-cell-expense="{ row, col }">
<QTd> <QTd>
<VnSelectDialog <VnSelectDialog
:ref="`expenseRef-${row.$index}`" ref="expenseRef"
v-model="row[col.model]" v-model="row[col.model]"
:options="col.options" :options="col.options"
:option-value="col.optionValue" :option-value="col.optionValue"
:option-label="col.optionLabel" :option-label="col.optionLabel"
:filter-options="['id', 'name']" :filter-options="['id', 'name']"
:tooltip="t('Create a new expense')" :tooltip="t('Create a new expense')"
@keydown.tab=" @keydown.tab="autocompleteExpense($event, row, col)"
autocompleteExpense(
$event,
row,
col,
$refs[`expenseRef-${row.$index}`],
)
"
@update:model-value="
setCursor($refs[`expenseRef-${row.$index}`])
"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -228,7 +210,7 @@ function setCursor(ref) {
</QTd> </QTd>
</template> </template>
<template #body-cell-taxablebase="{ row }"> <template #body-cell-taxablebase="{ row }">
<QTd shrink> <QTd>
<VnInputNumber <VnInputNumber
clear-icon="close" clear-icon="close"
v-model="row.taxableBase" v-model="row.taxableBase"
@ -239,16 +221,12 @@ function setCursor(ref) {
<template #body-cell-sageiva="{ row, col }"> <template #body-cell-sageiva="{ row, col }">
<QTd> <QTd>
<VnSelect <VnSelect
:ref="`sageivaRef-${row.$index}`"
v-model="row[col.model]" v-model="row[col.model]"
:options="col.options" :options="col.options"
:option-value="col.optionValue" :option-value="col.optionValue"
:option-label="col.optionLabel" :option-label="col.optionLabel"
:filter-options="['id', 'vat']" :filter-options="['id', 'vat']"
data-cy="vat-sageiva" data-cy="vat-sageiva"
@update:model-value="
setCursor($refs[`sageivaRef-${row.$index}`])
"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -266,15 +244,11 @@ function setCursor(ref) {
<template #body-cell-sagetransaction="{ row, col }"> <template #body-cell-sagetransaction="{ row, col }">
<QTd> <QTd>
<VnSelect <VnSelect
:ref="`sagetransactionRef-${row.$index}`"
v-model="row[col.model]" v-model="row[col.model]"
:options="col.options" :options="col.options"
:option-value="col.optionValue" :option-value="col.optionValue"
:option-label="col.optionLabel" :option-label="col.optionLabel"
:filter-options="['id', 'transaction']" :filter-options="['id', 'transaction']"
@update:model-value="
setCursor($refs[`sagetransactionRef-${row.$index}`])
"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -292,7 +266,7 @@ function setCursor(ref) {
</QTd> </QTd>
</template> </template>
<template #body-cell-foreignvalue="{ row }"> <template #body-cell-foreignvalue="{ row }">
<QTd shrink> <QTd>
<VnInputNumber <VnInputNumber
:class="{ :class="{
'no-pointer-events': !isNotEuro(currency), 'no-pointer-events': !isNotEuro(currency),

View File

@ -29,7 +29,6 @@ const cols = computed(() => [
name: 'isBooked', name: 'isBooked',
label: t('invoiceIn.isBooked'), label: t('invoiceIn.isBooked'),
columnFilter: false, columnFilter: false,
component: 'checkbox',
}, },
{ {
align: 'left', align: 'left',
@ -57,7 +56,7 @@ const cols = computed(() => [
{ {
align: 'left', align: 'left',
name: 'supplierRef', name: 'supplierRef',
label: t('invoiceIn.supplierRef'), label: t('invoiceIn.list.supplierRef'),
}, },
{ {
align: 'left', align: 'left',
@ -178,7 +177,7 @@ const cols = computed(() => [
:required="true" :required="true"
/> />
<VnInput <VnInput
:label="t('invoiceIn.supplierRef')" :label="t('invoiceIn.list.supplierRef')"
v-model="data.supplierRef" v-model="data.supplierRef"
/> />
<VnSelect <VnSelect

View File

@ -4,7 +4,6 @@ import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnConfirm from 'src/components/ui/VnConfirm.vue'; import VnConfirm from 'src/components/ui/VnConfirm.vue';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import qs from 'qs';
const { notify, dialog } = useQuasar(); const { notify, dialog } = useQuasar();
const { t } = useI18n(); const { t } = useI18n();
@ -13,51 +12,29 @@ defineExpose({ checkToBook });
const { store } = useArrayData(); const { store } = useArrayData();
async function checkToBook(id) { async function checkToBook(id) {
let messages = []; let directBooking = true;
const hasProblemWithTax = (
await axios.get('InvoiceInTaxes/count', {
params: {
where: JSON.stringify({
invoiceInFk: id,
or: [{ taxTypeSageFk: null }, { transactionTypeSageFk: null }],
}),
},
})
).data?.count;
if (hasProblemWithTax)
messages.push(t('The VAT and Transaction fields have not been informed'));
const { data: totals } = await axios.get(`InvoiceIns/${id}/getTotals`); const { data: totals } = await axios.get(`InvoiceIns/${id}/getTotals`);
const taxableBaseNotEqualDueDay = totals.totalDueDay != totals.totalTaxableBase; const taxableBaseNotEqualDueDay = totals.totalDueDay != totals.totalTaxableBase;
const vatNotEqualDueDay = totals.totalDueDay != totals.totalVat; const vatNotEqualDueDay = totals.totalDueDay != totals.totalVat;
if (taxableBaseNotEqualDueDay && vatNotEqualDueDay) if (taxableBaseNotEqualDueDay && vatNotEqualDueDay) directBooking = false;
messages.push(t('The sum of the taxable bases does not match the due dates'));
const dueDaysCount = ( const { data: dueDaysCount } = await axios.get('InvoiceInDueDays/count', {
await axios.get('InvoiceInDueDays/count', { where: {
params: {
where: JSON.stringify({
invoiceInFk: id, invoiceInFk: id,
dueDated: { gte: Date.vnNew() }, dueDated: { gte: Date.vnNew() },
}),
}, },
}) });
).data?.count;
if (dueDaysCount) messages.push(t('Some due dates are less than or equal to today')); if (dueDaysCount) directBooking = false;
if (directBooking) return toBook(id);
if (!messages.length) toBook(id);
else
dialog({ dialog({
component: VnConfirm, component: VnConfirm,
componentProps: { componentProps: { title: t('Are you sure you want to book this invoice?') },
title: t('Are you sure you want to book this invoice?'), }).onOk(async () => await toBook(id));
message: messages.reduce((acc, msg) => `${acc}<p>${msg}</p>`, ''),
},
}).onOk(() => toBook(id));
} }
async function toBook(id) { async function toBook(id) {
@ -82,7 +59,4 @@ async function toBook(id) {
es: es:
Are you sure you want to book this invoice?: ¿Estás seguro de querer asentar esta factura? Are you sure you want to book this invoice?: ¿Estás seguro de querer asentar esta factura?
It was not able to book the invoice: No se pudo contabilizar la factura It was not able to book the invoice: No se pudo contabilizar la factura
Some due dates are less than or equal to today: Algún vencimiento tiene una fecha menor o igual que hoy
The sum of the taxable bases does not match the due dates: La suma de las bases imponibles no coincide con la de los vencimientos
The VAT and Transaction fields have not been informed: No se han informado los campos de iva y/o transacción
</i18n> </i18n>

View File

@ -3,10 +3,10 @@ invoiceIn:
searchInfo: Search incoming invoices by ID or supplier fiscal name searchInfo: Search incoming invoices by ID or supplier fiscal name
serial: Serial serial: Serial
isBooked: Is booked isBooked: Is booked
supplierRef: Invoice nº
list: list:
ref: Reference ref: Reference
supplier: Supplier supplier: Supplier
supplierRef: Supplier ref.
file: File file: File
issued: Issued issued: Issued
dueDated: Due dated dueDated: Due dated
@ -19,6 +19,8 @@ invoiceIn:
unbook: Unbook unbook: Unbook
delete: Delete delete: Delete
clone: Clone clone: Clone
toBook: To book
toUnbook: To unbook
deleteInvoice: Delete invoice deleteInvoice: Delete invoice
invoiceDeleted: invoice deleted invoiceDeleted: invoice deleted
cloneInvoice: Clone invoice cloneInvoice: Clone invoice
@ -68,3 +70,4 @@ invoiceIn:
isBooked: Is booked isBooked: Is booked
account: Ledger account account: Ledger account
correctingFk: Rectificative correctingFk: Rectificative

View File

@ -3,10 +3,10 @@ invoiceIn:
searchInfo: Buscar facturas recibidas por ID o nombre fiscal del proveedor searchInfo: Buscar facturas recibidas por ID o nombre fiscal del proveedor
serial: Serie serial: Serie
isBooked: Contabilizada isBooked: Contabilizada
supplierRef: Nº factura
list: list:
ref: Referencia ref: Referencia
supplier: Proveedor supplier: Proveedor
supplierRef: Ref. proveedor
issued: F. emisión issued: F. emisión
dueDated: F. vencimiento dueDated: F. vencimiento
file: Fichero file: Fichero
@ -15,10 +15,12 @@ invoiceIn:
descriptor: descriptor:
ticketList: Listado de tickets ticketList: Listado de tickets
descriptorMenu: descriptorMenu:
book: Contabilizar book: Asentar
unbook: Descontabilizar unbook: Desasentar
delete: Eliminar delete: Eliminar
clone: Clonar clone: Clonar
toBook: Contabilizar
toUnbook: Descontabilizar
deleteInvoice: Eliminar factura deleteInvoice: Eliminar factura
invoiceDeleted: Factura eliminada invoiceDeleted: Factura eliminada
cloneInvoice: Clonar factura cloneInvoice: Clonar factura
@ -66,3 +68,4 @@ invoiceIn:
isBooked: Contabilizada isBooked: Contabilizada
account: Cuenta contable account: Cuenta contable
correctingFk: Rectificativa correctingFk: Rectificativa

View File

@ -36,6 +36,7 @@ function ticketFilter(invoice) {
<template> <template>
<CardDescriptor <CardDescriptor
ref="descriptor" ref="descriptor"
module="InvoiceOut"
:url="`InvoiceOuts/${entityId}`" :url="`InvoiceOuts/${entityId}`"
:filter="filter" :filter="filter"
title="ref" title="ref"

View File

@ -97,19 +97,12 @@ const columns = computed(() => [
}, },
{ {
align: 'left', align: 'left',
name: 'companyFk', name: 'companyCode',
label: t('globals.company'), label: t('globals.company'),
cardVisible: true, cardVisible: true,
component: 'select', component: 'select',
attrs: { attrs: { url: 'Companies', optionLabel: 'code', optionValue: 'id' },
url: 'Companies', columnField: { component: null },
optionLabel: 'code',
optionValue: 'id',
},
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(row.companyCode),
}, },
{ {
align: 'left', align: 'left',

View File

@ -11,7 +11,6 @@ import VnSelect from 'src/components/common/VnSelect.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import FilterItemForm from 'src/components/FilterItemForm.vue'; import FilterItemForm from 'src/components/FilterItemForm.vue';
import CreateIntrastatForm from './CreateIntrastatForm.vue'; import CreateIntrastatForm from './CreateIntrastatForm.vue';
import VnCheckbox from 'src/components/common/VnCheckbox.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@ -209,20 +208,30 @@ const onIntrastatCreated = (response, formData) => {
/> />
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<VnCheckbox <div>
<QCheckbox
v-model="data.isFragile" v-model="data.isFragile"
:label="t('item.basicData.isFragile')" :label="t('item.basicData.isFragile')"
:info="t('item.basicData.isFragileTooltip')"
class="q-mr-sm" class="q-mr-sm"
size="xs"
/> />
<VnCheckbox <QIcon name="info" class="cursor-pointer" size="xs">
<QTooltip max-width="300px">
{{ t('item.basicData.isFragileTooltip') }}
</QTooltip>
</QIcon>
</div>
<div>
<QCheckbox
v-model="data.isPhotoRequested" v-model="data.isPhotoRequested"
:label="t('item.basicData.isPhotoRequested')" :label="t('item.basicData.isPhotoRequested')"
:info="t('item.basicData.isPhotoRequestedTooltip')"
class="q-mr-sm" class="q-mr-sm"
size="xs"
/> />
<QIcon name="info" class="cursor-pointer" size="xs">
<QTooltip>
{{ t('item.basicData.isPhotoRequestedTooltip') }}
</QTooltip>
</QIcon>
</div>
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnInput <VnInput

View File

@ -7,8 +7,8 @@ import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CreateGenusForm from '../components/CreateGenusForm.vue'; import CreateGenusForm from './CreateGenusForm.vue';
import CreateSpecieForm from '../components/CreateSpecieForm.vue'; import CreateSpecieForm from './CreateSpecieForm.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();

View File

@ -34,10 +34,6 @@ const $props = defineProps({
type: Number, type: Number,
default: null, default: null,
}, },
proxyRender: {
type: Boolean,
default: false,
},
}); });
const route = useRoute(); const route = useRoute();
@ -92,6 +88,7 @@ const updateStock = async () => {
<template> <template>
<CardDescriptor <CardDescriptor
data-key="Item" data-key="Item"
module="Item"
:summary="$props.summary" :summary="$props.summary"
:url="`Items/${entityId}/getCard`" :url="`Items/${entityId}/getCard`"
@on-fetch="setData" @on-fetch="setData"
@ -115,7 +112,7 @@ const updateStock = async () => {
<template #value> <template #value>
<span class="link"> <span class="link">
{{ entity.itemType?.worker?.user?.name }} {{ entity.itemType?.worker?.user?.name }}
<WorkerDescriptorProxy :id="entity.itemType?.worker?.id ?? NaN" /> <WorkerDescriptorProxy :id="entity.itemType?.worker?.id" />
</span> </span>
</template> </template>
</VnLv> </VnLv>
@ -150,7 +147,7 @@ const updateStock = async () => {
</QCardActions> </QCardActions>
</template> </template>
<template #actions="{}"> <template #actions="{}">
<QCardActions class="row justify-center" v-if="proxyRender"> <QCardActions class="row justify-center">
<QBtn <QBtn
:to="{ :to="{
name: 'ItemDiary', name: 'ItemDiary',
@ -163,16 +160,6 @@ const updateStock = async () => {
> >
<QTooltip>{{ t('item.descriptor.itemDiary') }}</QTooltip> <QTooltip>{{ t('item.descriptor.itemDiary') }}</QTooltip>
</QBtn> </QBtn>
<QBtn
:to="{
name: 'ItemLastEntries',
}"
size="md"
icon="vn:regentry"
color="primary"
>
<QTooltip>{{ t('item.descriptor.itemLastEntries') }}</QTooltip>
</QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </CardDescriptor>

View File

@ -4,7 +4,7 @@ import ItemSummary from './ItemSummary.vue';
const $props = defineProps({ const $props = defineProps({
id: { id: {
type: [Number, String], type: Number,
required: true, required: true,
}, },
dated: { dated: {
@ -21,8 +21,9 @@ const $props = defineProps({
}, },
}); });
</script> </script>
<template> <template>
<QPopupProxy style="max-width: 10px"> <QPopupProxy>
<ItemDescriptor <ItemDescriptor
v-if="$props.id" v-if="$props.id"
:id="$props.id" :id="$props.id"
@ -30,7 +31,6 @@ const $props = defineProps({
:dated="dated" :dated="dated"
:sale-fk="saleFk" :sale-fk="saleFk"
:warehouse-fk="warehouseFk" :warehouse-fk="warehouseFk"
:proxy-render="true"
/> />
</QPopupProxy> </QPopupProxy>
</template> </template>

View File

@ -65,19 +65,10 @@ const columns = computed(() => [
name: 'name', name: 'name',
...defaultColumnAttrs, ...defaultColumnAttrs,
create: true, create: true,
columnFilter: {
component: 'select',
attrs: {
url: 'Items',
fields: ['id', 'name', 'subName'],
optionLabel: 'name',
optionValue: 'name',
uppercase: false,
},
},
}, },
{ {
label: t('item.fixedPrice.groupingPrice'), label: t('item.fixedPrice.groupingPrice'),
field: 'rate2',
name: 'rate2', name: 'rate2',
...defaultColumnAttrs, ...defaultColumnAttrs,
component: 'input', component: 'input',
@ -85,6 +76,7 @@ const columns = computed(() => [
}, },
{ {
label: t('item.fixedPrice.packingPrice'), label: t('item.fixedPrice.packingPrice'),
field: 'rate3',
name: 'rate3', name: 'rate3',
...defaultColumnAttrs, ...defaultColumnAttrs,
component: 'input', component: 'input',
@ -93,6 +85,7 @@ const columns = computed(() => [
{ {
label: t('item.fixedPrice.minPrice'), label: t('item.fixedPrice.minPrice'),
field: 'minPrice',
name: 'minPrice', name: 'minPrice',
...defaultColumnAttrs, ...defaultColumnAttrs,
component: 'input', component: 'input',
@ -115,6 +108,7 @@ const columns = computed(() => [
}, },
{ {
label: t('item.fixedPrice.ended'), label: t('item.fixedPrice.ended'),
field: 'ended',
name: 'ended', name: 'ended',
...defaultColumnAttrs, ...defaultColumnAttrs,
columnField: { columnField: {
@ -130,6 +124,7 @@ const columns = computed(() => [
{ {
label: t('globals.warehouse'), label: t('globals.warehouse'),
field: 'warehouseFk',
name: 'warehouseFk', name: 'warehouseFk',
...defaultColumnAttrs, ...defaultColumnAttrs,
columnClass: 'shrink', columnClass: 'shrink',
@ -420,6 +415,7 @@ function handleOnDataSave({ CrudModelRef }) {
'row-key': 'id', 'row-key': 'id',
selection: 'multiple', selection: 'multiple',
}" }"
:use-model="true"
v-model:selected="rowsSelected" v-model:selected="rowsSelected"
:create-as-dialog="false" :create-as-dialog="false"
:create="{ :create="{

View File

@ -26,6 +26,7 @@ const entityId = computed(() => {
</script> </script>
<template> <template>
<CardDescriptor <CardDescriptor
module="ItemType"
:url="`ItemTypes/${entityId}`" :url="`ItemTypes/${entityId}`"
:filter="filter" :filter="filter"
title="code" title="code"

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