4774-traducciones #853

Open
carlossa wants to merge 34 commits from 4774-traducciones into dev
197 changed files with 2032 additions and 1486 deletions
Showing only changes of commit a2d9868c14 - Show all commits

View File

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

View File

@ -1,4 +1,3 @@
import { Notify } from 'quasar';
import { onRequest, onResponseError } from 'src/boot/axios'; import { onRequest, onResponseError } from 'src/boot/axios';
import { describe, expect, it, vi } from 'vitest'; import { describe, expect, it, vi } from 'vitest';
@ -27,6 +26,7 @@ describe('Axios boot', () => {
expect(resultConfig).toEqual( expect(resultConfig).toEqual(
expect.objectContaining({ expect.objectContaining({
headers: { headers: {
'Accept-Language': 'en-US',
Authorization: 'DEFAULT_TOKEN', Authorization: 'DEFAULT_TOKEN',
}, },
}) })

View File

@ -3,12 +3,12 @@ import { useSession } from 'src/composables/useSession';
import { Router } from 'src/router'; import { Router } from 'src/router';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import { useStateQueryStore } from 'src/stores/useStateQueryStore'; import { useStateQueryStore } from 'src/stores/useStateQueryStore';
import { i18n } from 'src/boot/i18n';
const session = useSession(); const session = useSession();
const { notify } = useNotify(); const { notify } = useNotify();
const stateQuery = useStateQueryStore(); const stateQuery = useStateQueryStore();
const baseUrl = '/api/'; const baseUrl = '/api/';
axios.defaults.baseURL = baseUrl; axios.defaults.baseURL = baseUrl;
const axiosNoError = axios.create({ baseURL: baseUrl }); const axiosNoError = axios.create({ baseURL: baseUrl });
@ -16,6 +16,7 @@ const onRequest = (config) => {
const token = session.getToken(); const token = session.getToken();
if (token.length && !config.headers.Authorization) { if (token.length && !config.headers.Authorization) {
config.headers.Authorization = token; config.headers.Authorization = token;
config.headers['Accept-Language'] = i18n.global.locale.value;
} }
stateQuery.add(config); stateQuery.add(config);
return config; return config;

View File

@ -1,9 +1,11 @@
import { boot } from 'quasar/wrappers'; import { boot } from 'quasar/wrappers';
import { createI18n } from 'vue-i18n'; import { createI18n } from 'vue-i18n';
import messages from 'src/i18n'; import messages from 'src/i18n';
import { useState } from 'src/composables/useState';
const user = useState().getUser();
const i18n = createI18n({ const i18n = createI18n({
locale: navigator.language || navigator.userLanguage, locale: user.value.lang || navigator.language || navigator.userLanguage,
fallbackLocale: 'en', fallbackLocale: 'en',
globalInjection: true, globalInjection: true,
messages, messages,

View File

@ -1,6 +1,5 @@
function focusFirstInput(input) { function focusFirstInput(input) {
input.focus(); input.focus();
return;
} }
export default { export default {
mounted: function () { mounted: function () {

View File

@ -293,6 +293,7 @@ defineExpose({
class="q-pa-md" class="q-pa-md"
:style="maxWidth ? 'max-width: ' + maxWidth : ''" :style="maxWidth ? 'max-width: ' + maxWidth : ''"
id="formModel" id="formModel"
:prevent-submit="$attrs['prevent-submit']"
> >
<QCard> <QCard>
<slot <slot

View File

@ -17,12 +17,10 @@ const stateQuery = useStateQueryStore();
const state = useState(); const state = useState();
const user = state.getUser(); const user = state.getUser();
const appName = 'Lilium'; const appName = 'Lilium';
const pinnedModulesRef = ref();
onMounted(() => stateStore.setMounted()); onMounted(() => stateStore.setMounted());
const pinnedModulesRef = ref();
</script> </script>
<template> <template>
<QHeader color="white" elevated> <QHeader color="white" elevated>
<QToolbar class="q-py-sm q-px-md"> <QToolbar class="q-py-sm q-px-md">
@ -65,16 +63,6 @@ const pinnedModulesRef = ref();
<QSpace /> <QSpace />
<div class="q-pl-sm q-gutter-sm row items-center no-wrap"> <div class="q-pl-sm q-gutter-sm row items-center no-wrap">
<div id="actions-prepend"></div> <div id="actions-prepend"></div>
<QBtn
flat
v-if="!quasar.platform.is.mobile"
@click="pinnedModulesRef.redirect($route.params.id)"
icon="more_up"
>
<QTooltip>
{{ t('Go to Salix') }}
</QTooltip>
</QBtn>
<QBtn <QBtn
:class="{ 'q-pa-none': quasar.platform.is.mobile }" :class="{ 'q-pa-none': quasar.platform.is.mobile }"
id="pinnedModules" id="pinnedModules"
@ -106,7 +94,6 @@ const pinnedModulesRef = ref();
<VnBreadcrumbs v-if="$q.screen.lt.md" class="q-ml-md" /> <VnBreadcrumbs v-if="$q.screen.lt.md" class="q-ml-md" />
</QHeader> </QHeader>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.searchbar { .searchbar {
width: max-content; width: max-content;
@ -115,9 +102,3 @@ const pinnedModulesRef = ref();
background-color: var(--vn-section-color); background-color: var(--vn-section-color);
} }
</style> </style>
<i18n>
en:
Go to Salix: Go to Salix
es:
Go to Salix: Ir a Salix
</i18n>

View File

@ -87,10 +87,10 @@ async function saveDarkMode(value) {
async function saveLanguage(value) { async function saveLanguage(value) {
const query = `/VnUsers/${user.value.id}`; const query = `/VnUsers/${user.value.id}`;
try { try {
await axios.patch(query, { await axios.patch(query, { lang: value });
lang: value,
});
user.value.lang = value; user.value.lang = value;
useState().setUser(user.value);
onDataSaved(); onDataSaved();
} catch (error) { } catch (error) {
onDataError(); onDataError();

View File

@ -150,6 +150,7 @@ const tableModes = [
disable: $props.disableOption?.card, disable: $props.disableOption?.card,
}, },
]; ];
onBeforeMount(() => { onBeforeMount(() => {
const urlParams = route.query[$props.searchUrl]; const urlParams = route.query[$props.searchUrl];
hasParams.value = urlParams && Object.keys(urlParams).length !== 0; hasParams.value = urlParams && Object.keys(urlParams).length !== 0;
@ -162,7 +163,9 @@ onMounted(() => {
: $props.defaultMode; : $props.defaultMode;
stateStore.rightDrawer = quasar.screen.gt.xs; stateStore.rightDrawer = quasar.screen.gt.xs;
columnsVisibilitySkipped.value = [ columnsVisibilitySkipped.value = [
...splittedColumns.value.columns.filter((c) => !c.visible).map((c) => c.name), ...splittedColumns.value.columns
.filter((c) => c.visible === false)
.map((c) => c.name),
...['tableActions'], ...['tableActions'],
]; ];
createForm.value = $props.create; createForm.value = $props.create;
@ -485,7 +488,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
:class="col.headerClass" :class="col.headerClass"
> >
<div <div
class="column self-start q-ml-xs ellipsis" class="column ellipsis"
:class="`text-${col?.align ?? 'left'}`" :class="`text-${col?.align ?? 'left'}`"
:style="$props.columnSearch ? 'height: 75px' : ''" :style="$props.columnSearch ? 'height: 75px' : ''"
> >
@ -527,7 +530,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
<!-- Columns --> <!-- Columns -->
<QTd <QTd
auto-width auto-width
class="no-margin q-px-xs" class="no-margin"
:class="[getColAlign(col), col.columnClass]" :class="[getColAlign(col), col.columnClass]"
:style="col.style" :style="col.style"
v-if="col.visible ?? true" v-if="col.visible ?? true"
@ -664,13 +667,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
:key="col.name" :key="col.name"
class="fields" class="fields"
> >
<VnLv <VnLv :label="col.label + ':'">
:label="
!col.component && col.label
? `${col.label}:`
: ''
"
>
<template #value> <template #value>
<span <span
@click="stopEventPropagation($event)" @click="stopEventPropagation($event)"
@ -811,7 +808,7 @@ es:
.grid-three { .grid-three {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 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;
@ -860,21 +857,6 @@ es:
top: 0; top: 0;
padding: 12px 0; padding: 12px 0;
} }
tbody {
.q-checkbox {
display: flex;
margin-bottom: 9px;
& .q-checkbox__label {
margin-left: 31px;
color: var(--vn-text-color);
}
& .q-checkbox__inner {
position: absolute;
left: 0;
color: var(--vn-label-color);
}
}
}
.sticky { .sticky {
position: sticky; position: sticky;
right: 0; right: 0;

View File

@ -152,7 +152,7 @@ onMounted(async () => {
<QCheckbox <QCheckbox
v-for="col in localColumns" v-for="col in localColumns"
:key="col.name" :key="col.name"
:label="col.label" :label="col.label ?? col.name"
v-model="col.visible" v-model="col.visible"
/> />
</div> </div>

View File

@ -2,7 +2,11 @@
import { ref, onMounted, useSlots } from 'vue'; import { ref, onMounted, useSlots } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useQuasar } from 'quasar';
const { t } = useI18n();
const quasar = useQuasar();
const stateStore = useStateStore();
const slots = useSlots(); const slots = useSlots();
const hasContent = ref(false); const hasContent = ref(false);
const rightPanel = ref(null); const rightPanel = ref(null);
@ -11,7 +15,6 @@ onMounted(() => {
rightPanel.value = document.querySelector('#right-panel'); rightPanel.value = document.querySelector('#right-panel');
if (!rightPanel.value) return; if (!rightPanel.value) return;
// Check if there's content to display
const observer = new MutationObserver(() => { const observer = new MutationObserver(() => {
hasContent.value = rightPanel.value.childNodes.length; hasContent.value = rightPanel.value.childNodes.length;
}); });
@ -21,12 +24,9 @@ onMounted(() => {
childList: true, childList: true,
attributes: true, attributes: true,
}); });
if ((!slots['right-panel'] && !hasContent.value) || quasar.platform.is.mobile)
if (!slots['right-panel'] && !hasContent.value) stateStore.rightDrawer = false; stateStore.rightDrawer = false;
}); });
const { t } = useI18n();
const stateStore = useStateStore();
</script> </script>
<template> <template>
<Teleport to="#actions-append" v-if="stateStore.isHeaderMounted()"> <Teleport to="#actions-append" v-if="stateStore.isHeaderMounted()">
@ -45,7 +45,7 @@ const stateStore = useStateStore();
</QBtn> </QBtn>
</div> </div>
</Teleport> </Teleport>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256">
<QScrollArea class="fit"> <QScrollArea class="fit">
<div id="right-panel"></div> <div id="right-panel"></div>
<slot v-if="!hasContent" name="right-panel" /> <slot v-if="!hasContent" name="right-panel" />

View File

@ -0,0 +1,31 @@
<script setup>
import { toDateFormat } from 'src/filters/date.js';
defineProps({ date: { type: [Date, String], required: true } });
function getBadgeAttrs(date) {
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' };
if (timeDiff < 0) return { color: 'success', 'text-color': 'black' };
return { color: 'transparent', 'text-color': 'white' };
}
function formatShippedDate(date) {
if (!date) return '-';
const dateSplit = date.split('T');
const [year, month, day] = dateSplit[0].split('-');
const newDate = new Date(year, month - 1, day);
return toDateFormat(newDate);
}
</script>
<template>
<QBadge v-bind="getBadgeAttrs(date)" class="q-pa-sm" style="font-size: 14px">
{{ formatShippedDate(date) }}
</QBadge>
</template>

View File

@ -106,7 +106,7 @@ const manageDate = (date) => {
:class="{ required: isRequired }" :class="{ required: isRequired }"
:rules="mixinRules" :rules="mixinRules"
:clearable="false" :clearable="false"
@click="isPopupOpen = true" @click="isPopupOpen = !isPopupOpen"
hide-bottom-space hide-bottom-space
> >
<template #append> <template #append>
@ -125,13 +125,6 @@ const manageDate = (date) => {
isPopupOpen = false; isPopupOpen = false;
" "
/> />
<QIcon
v-if="showEvent"
name="event"
class="cursor-pointer"
@click="isPopupOpen = !isPopupOpen"
:title="t('Open date')"
/>
</template> </template>
<QMenu <QMenu
v-if="$q.screen.gt.xs" v-if="$q.screen.gt.xs"
@ -151,15 +144,6 @@ const manageDate = (date) => {
</QInput> </QInput>
</div> </div>
</template> </template>
<style lang="scss">
.vn-input-date.q-field--standard.q-field--readonly .q-field__control:before {
border-bottom-style: solid;
}
.vn-input-date.q-field--outlined.q-field--readonly .q-field__control:before {
border-style: solid;
}
</style>
<i18n> <i18n>
es: es:
Open date: Abrir fecha Open date: Abrir fecha

View File

@ -1,13 +1,28 @@
<script setup> <script setup>
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import { ref } from 'vue';
import { useAttrs } from 'vue'; defineProps({
step: { type: Number, default: 0.01 },
decimalPlaces: { type: Number, default: 2 },
positive: { type: Boolean, default: true },
});
const model = defineModel({ type: [Number, String] }); const model = defineModel({ type: [Number, String] });
const $attrs = useAttrs();
const step = ref($attrs.step || 0.01);
</script> </script>
<template> <template>
<VnInput v-bind="$attrs" v-model.number="model" type="number" :step="step" /> <VnInput
v-bind="$attrs"
v-model.number="model"
type="number"
:step="step"
@input="
(evt) => {
const val = evt.target.value;
if (positive && val < 0) return (model = 0);
const [, decimal] = val.split('.');
if (val && decimal?.length > decimalPlaces)
model = parseFloat(val).toFixed(decimalPlaces);
}
"
/>
</template> </template>

View File

@ -80,7 +80,7 @@ function dateToTime(newDate) {
:class="{ required: isRequired }" :class="{ required: isRequired }"
style="min-width: 100px" style="min-width: 100px"
:rules="mixinRules" :rules="mixinRules"
@click="isPopupOpen = false" @click="isPopupOpen = !isPopupOpen"
type="time" type="time"
hide-bottom-space hide-bottom-space
> >
@ -100,12 +100,6 @@ function dateToTime(newDate) {
isPopupOpen = false; isPopupOpen = false;
" "
/> />
<QIcon
name="Schedule"
class="cursor-pointer"
@click="isPopupOpen = !isPopupOpen"
:title="t('Open time')"
/>
</template> </template>
<QMenu <QMenu
v-if="$q.screen.gt.xs" v-if="$q.screen.gt.xs"

View File

@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { useRequired } from 'src/composables/useRequired'; import { useRequired } from 'src/composables/useRequired';
import dataByOrder from 'src/utils/dataByOrder'; import dataByOrder from 'src/utils/dataByOrder';
import { QItemLabel } from 'quasar';
const emit = defineEmits(['update:modelValue', 'update:options', 'remove']); const emit = defineEmits(['update:modelValue', 'update:options', 'remove']);
const $attrs = useAttrs(); const $attrs = useAttrs();
@ -33,6 +34,10 @@ const $props = defineProps({
type: String, type: String,
default: 'id', default: 'id',
}, },
optionCaption: {
type: String,
default: null,
},
optionFilter: { optionFilter: {
type: String, type: String,
default: null, default: null,
@ -101,6 +106,10 @@ const $props = defineProps({
type: String, type: String,
default: null, default: null,
}, },
isOutlined: {
type: Boolean,
default: false,
},
}); });
const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])]; const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])];
@ -115,6 +124,15 @@ const noOneOpt = ref({
[optionValue.value]: false, [optionValue.value]: false,
[optionLabel.value]: noOneText, [optionLabel.value]: noOneText,
}); });
const styleAttrs = computed(() => {
return $props.isOutlined
? {
dense: true,
outlined: true,
rounded: true,
}
: {};
});
const isLoading = ref(false); const isLoading = ref(false);
const useURL = computed(() => $props.url); const useURL = computed(() => $props.url);
const value = computed({ const value = computed({
@ -288,7 +306,7 @@ function handleKeyDown(event) {
} }
const focusableElements = document.querySelectorAll( const focusableElements = document.querySelectorAll(
'a, button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])' '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,
@ -307,9 +325,8 @@ function handleKeyDown(event) {
:options="myOptions" :options="myOptions"
:option-label="optionLabel" :option-label="optionLabel"
:option-value="optionValue" :option-value="optionValue"
v-bind="$attrs" v-bind="{ ...$attrs, ...styleAttrs }"
@filter="filterHandler" @filter="filterHandler"
@keydown="handleKeyDown"
:emit-value="nullishToTrue($attrs['emit-value'])" :emit-value="nullishToTrue($attrs['emit-value'])"
:map-options="nullishToTrue($attrs['map-options'])" :map-options="nullishToTrue($attrs['map-options'])"
:use-input="nullishToTrue($attrs['use-input'])" :use-input="nullishToTrue($attrs['use-input'])"
@ -324,13 +341,15 @@ function handleKeyDown(event) {
:input-debounce="useURL ? '300' : '0'" :input-debounce="useURL ? '300' : '0'"
:loading="isLoading" :loading="isLoading"
@virtual-scroll="onScroll" @virtual-scroll="onScroll"
@keydown="handleKeyDown"
:data-cy="$attrs.dataCy ?? $attrs.label + '_select'" :data-cy="$attrs.dataCy ?? $attrs.label + '_select'"
:data-url="url"
> >
<template #append> <template #append>
<QIcon <QIcon
v-show="isClearable && value" v-show="isClearable && value"
name="close" name="close"
@click.stop=" @click="
() => { () => {
value = null; value = null;
emit('remove'); emit('remove');
@ -358,6 +377,22 @@ function handleKeyDown(event) {
</div> </div>
<slot v-else :name="slotName" v-bind="slotData ?? {}" :key="slotName" /> <slot v-else :name="slotName" v-bind="slotData ?? {}" :key="slotName" />
</template> </template>
<template #option="{ opt, itemProps }">
<QItem v-bind="itemProps">
<QItemSection v-if="typeof opt !== 'object'"> {{ opt }}</QItemSection>
<QItemSection v-else-if="opt[optionValue] == opt[optionLabel]">
<QItemLabel>{{ opt[optionLabel] }}</QItemLabel>
</QItemSection>
<QItemSection v-else>
<QItemLabel>
{{ opt[optionLabel] }}
</QItemLabel>
<QItemLabel caption v-if="optionCaption !== false">
{{ `#${opt[optionCaption] || opt[optionValue]}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</QSelect> </QSelect>
</template> </template>

View File

@ -0,0 +1,28 @@
import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest';
import { createWrapper } from 'app/test/vitest/helper';
import VnDiscount from 'components/common/vnDiscount.vue';
describe('VnDiscount', () => {
let vm;
beforeAll(() => {
vm = createWrapper(VnDiscount, {
props: {
data: {},
price: 100,
quantity: 2,
discount: 10,
}
}).vm;
});
afterEach(() => {
vi.clearAllMocks();
});
describe('total', () => {
it('should calculate total correctly', () => {
expect(vm.total).toBe(180);
});
});
});

View File

@ -222,8 +222,8 @@ const toModule = computed(() =>
/> />
</template> </template>
<style lang="scss"> <style lang="scss" scoped>
.body { :deep(.body) {
background-color: var(--vn-section-color); background-color: var(--vn-section-color);
.text-h5 { .text-h5 {
font-size: 20px; font-size: 20px;
@ -262,9 +262,7 @@ const toModule = computed(() =>
} }
} }
} }
</style>
<style lang="scss" scoped>
.title { .title {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;

View File

@ -1,10 +1,10 @@
<script setup> <script setup>
import { ref, computed, watch, onBeforeMount } from 'vue'; import { ref, computed, watch, onBeforeMount, onMounted } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import SkeletonSummary from 'components/ui/SkeletonSummary.vue'; import SkeletonSummary from 'components/ui/SkeletonSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { isDialogOpened } from 'src/filters'; import { isDialogOpened } from 'src/filters';
import { useStateStore } from 'src/stores/useStateStore';
const props = defineProps({ const props = defineProps({
url: { url: {
@ -40,6 +40,7 @@ const { store } = arrayData;
const entity = computed(() => (Array.isArray(store.data) ? store.data[0] : store.data)); const entity = computed(() => (Array.isArray(store.data) ? store.data[0] : store.data));
const isLoading = ref(false); const isLoading = ref(false);
const stateStore = useStateStore();
defineExpose({ defineExpose({
entity, entity,
fetch, fetch,
@ -51,6 +52,9 @@ onBeforeMount(async () => {
watch(props, async () => await fetch()); watch(props, async () => await fetch());
}); });
onMounted(() => {
stateStore.rightDrawerChangeValue(false);
});
async function fetch() { async function fetch() {
store.url = props.url; store.url = props.url;
store.filter = props.filter ?? {}; store.filter = props.filter ?? {};
@ -60,7 +64,6 @@ async function fetch() {
isLoading.value = false; isLoading.value = false;
} }
</script> </script>
<template> <template>
<div class="summary container"> <div class="summary container">
<QCard class="cardSummary"> <QCard class="cardSummary">
@ -81,7 +84,7 @@ async function fetch() {
<span v-else></span> <span v-else></span>
</slot> </slot>
<slot name="header" :entity="entity" dense> <slot name="header" :entity="entity" dense>
<VnLv :label="`${entity.id} -`" :value="entity.name" /> {{ entity.id + ' - ' + entity.name }}
</slot> </slot>
<slot name="header-right" :entity="entity"> <slot name="header-right" :entity="entity">
<span></span> <span></span>
@ -94,7 +97,6 @@ async function fetch() {
</QCard> </QCard>
</div> </div>
</template> </template>
<style lang="scss"> <style lang="scss">
.summary.container { .summary.container {
display: flex; display: flex;

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref, toRef } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnLv from 'components/ui/VnLv.vue'; import VnLv from 'components/ui/VnLv.vue';
@ -13,7 +13,7 @@ const DEFAULT_PRICE_KG = 0;
const { t } = useI18n(); const { t } = useI18n();
defineProps({ const props = defineProps({
item: { item: {
type: Object, type: Object,
required: true, required: true,
@ -25,57 +25,63 @@ defineProps({
}); });
const dialog = ref(null); const dialog = ref(null);
const card = toRef(props, 'item');
</script> </script>
<template> <template>
<div class="container order-catalog-item overflow-hidden"> <div class="container order-catalog-item overflow-hidden">
<QCard class="card shadow-6"> <QCard class="card shadow-6">
<div class="img-wrapper"> <div class="img-wrapper">
<VnImg :id="item.id" class="image" zoom-resolution="1600x900" /> <VnImg :id="card.id" class="image" zoom-resolution="1600x900" />
<div v-if="item.hex && isCatalog" class="item-color-container"> <div v-if="card.hex && isCatalog" class="item-color-container">
<div <div
class="item-color" class="item-color"
:style="{ backgroundColor: `#${item.hex}` }" :style="{ backgroundColor: `#${card.hex}` }"
></div> ></div>
</div> </div>
</div> </div>
<div class="content"> <div class="content">
<span class="link"> <span class="link">
{{ item.name }} {{ card.name }}
<ItemDescriptorProxy :id="item.id" /> <ItemDescriptorProxy :id="card.id" />
</span> </span>
<p class="subName">{{ item.subName }}</p> <p class="subName">{{ card.subName }}</p>
<template v-for="index in 4" :key="`tag-${index}`"> <template v-for="index in 4" :key="`tag-${index}`">
<VnLv <VnLv
v-if="item?.[`tag${index + 4}`]" v-if="card?.[`tag${index + 4}`]"
:label="item?.[`tag${index + 4}`] + ':'" :label="card?.[`tag${index + 4}`] + ':'"
:value="item?.[`value${index + 4}`]" :value="card?.[`value${index + 4}`]"
/> />
</template> </template>
<div v-if="item.minQuantity" class="min-quantity"> <div v-if="card.minQuantity" class="min-quantity">
<QIcon name="production_quantity_limits" size="xs" /> <QIcon name="production_quantity_limits" size="xs" />
{{ item.minQuantity }} {{ card.minQuantity }}
</div> </div>
<div class="footer"> <div class="footer">
<div class="price"> <div class="price">
<p v-if="isCatalog"> <p v-if="isCatalog">
{{ item.available }} {{ t('to') }} {{ card.available }} {{ t('to') }}
{{ toCurrency(item.price) }} {{ toCurrency(card.price) }}
</p> </p>
<slot name="price" /> <slot name="price" />
<QIcon v-if="isCatalog" name="add_circle" class="icon"> <QIcon v-if="isCatalog" name="add_circle" class="icon">
<QTooltip>{{ t('globals.add') }}</QTooltip> <QTooltip>{{ t('globals.add') }}</QTooltip>
<QPopupProxy ref="dialog"> <QPopupProxy ref="dialog">
<OrderCatalogItemDialog <OrderCatalogItemDialog
:item="item" :item="card"
@added="() => dialog.hide()" @added="
(quantityAdded) => {
card.available += quantityAdded;
dialog.hide();
}
"
/> />
</QPopupProxy> </QPopupProxy>
</QIcon> </QIcon>
</div> </div>
<p v-if="item.priceKg" class="price-kg"> <p v-if="card.priceKg" class="price-kg">
{{ t('price-kg') }} {{ t('price-kg') }}
{{ toCurrency(item.priceKg) || DEFAULT_PRICE_KG }} {{ toCurrency(card.priceKg) || DEFAULT_PRICE_KG }}
</p> </p>
</div> </div>
</div> </div>

View File

@ -16,7 +16,13 @@ const $props = defineProps({
required: false, required: false,
default: 'value', default: 'value',
}, },
columns: {
type: Number,
required: false,
default: null,
},
}); });
const tags = computed(() => { const tags = computed(() => {
return Object.keys($props.item) return Object.keys($props.item)
.filter((i) => i.startsWith(`${$props.tag}`)) .filter((i) => i.startsWith(`${$props.tag}`))
@ -28,10 +34,21 @@ const tags = computed(() => {
return acc; return acc;
}, {}); }, {});
}); });
const columnStyle = computed(() => {
if ($props.columns) {
return {
'grid-template-columns': `repeat(${$props.columns}, 1fr)`,
'max-width': `${$props.columns * 4}rem`,
};
}
return {};
});
</script> </script>
<template> <template>
<div class="fetchedTags"> <div class="fetchedTags">
<div class="wrap"> <div class="wrap" :style="columnStyle">
<div <div
v-for="(val, key) in tags" v-for="(val, key) in tags"
:key="key" :key="key"
@ -39,37 +56,43 @@ const tags = computed(() => {
:title="`${key}: ${val}`" :title="`${key}: ${val}`"
:class="{ empty: !val }" :class="{ empty: !val }"
> >
{{ val }} <span class="text">{{ val }} </span>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.fetchedTags { .fetchedTags {
align-items: center; align-items: center;
.wrap { .wrap {
width: 100%; display: grid;
flex-wrap: wrap;
display: flex;
} }
.inline-tag { .inline-tag {
display: flex;
align-items: center;
justify-content: center;
height: 1rem; height: 1rem;
margin: 0.05rem; margin: 0.05rem;
color: $color-font-secondary; color: var(--vn-label-color);
text-align: center; text-align: center;
font-size: smaller; font-size: smaller;
padding: 1px; padding: 1px;
flex: 1; border: 1px solid var(--vn-label-color);
border: 1px solid $color-spacer;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
min-width: 4rem; min-width: 4rem;
max-width: 4rem; max-width: 4rem;
} }
.text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: smaller;
}
.empty { .empty {
border: 1px solid #2b2b2b; border: 1px solid var(--vn-empty-tag);
} }
} }
</style> </style>

View File

@ -98,6 +98,7 @@ function cancel() {
/> />
<QBtn <QBtn
:label="t('globals.confirm')" :label="t('globals.confirm')"
:title="t('globals.confirm')"
color="primary" color="primary"
:loading="isLoading" :loading="isLoading"
@click="confirm()" @click="confirm()"

View File

@ -6,7 +6,7 @@ import { useRoute } from 'vue-router';
import toDate from 'filters/toDate'; import toDate from 'filters/toDate';
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue'; import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
const { t } = useI18n(); const { t, te } = useI18n();
const $props = defineProps({ const $props = defineProps({
modelValue: { modelValue: {
type: Object, type: Object,
@ -228,6 +228,14 @@ function sanitizer(params) {
} }
return params; return params;
} }
const getLocale = (label) => {
const param = label.split('.').at(-1);
const globalLocale = `globals.params.${param}`;
if (te(globalLocale)) return t(globalLocale);
else if (te(t(`params.${param}`)));
else return t(`${route.meta.moduleName}.params.${param}`);
};
</script> </script>
<template> <template>
@ -277,7 +285,12 @@ function sanitizer(params) {
@remove="remove(chip.label)" @remove="remove(chip.label)"
data-cy="vnFilterPanelChip" data-cy="vnFilterPanelChip"
> >
<slot name="tags" :tag="chip" :format-fn="formatValue"> <slot
name="tags"
:tag="chip"
:format-fn="formatValue"
:get-locale="getLocale"
>
<div class="q-gutter-x-xs"> <div class="q-gutter-x-xs">
<strong>{{ chip.label }}:</strong> <strong>{{ chip.label }}:</strong>
<span>"{{ formatValue(chip.value) }}"</span> <span>"{{ formatValue(chip.value) }}"</span>
@ -290,6 +303,7 @@ function sanitizer(params) {
:params="userParams" :params="userParams"
:tags="customTags" :tags="customTags"
:format-fn="formatValue" :format-fn="formatValue"
:get-locale="getLocale"
:search-fn="search" :search-fn="search"
/> />
</div> </div>
@ -297,7 +311,12 @@ function sanitizer(params) {
<QSeparator /> <QSeparator />
</QList> </QList>
<QList dense class="list q-gutter-y-sm q-mt-sm"> <QList dense class="list q-gutter-y-sm q-mt-sm">
<slot name="body" :params="sanitizer(userParams)" :search-fn="search"></slot> <slot
name="body"
:params="sanitizer(userParams)"
:get-locale="getLocale"
:search-fn="search"
></slot>
</QList> </QList>
</QForm> </QForm>
<QInnerLoading <QInnerLoading

View File

@ -39,7 +39,7 @@ const val = computed(() => $props.value);
<template v-else> <template v-else>
<div v-if="label || $slots.label" class="label"> <div v-if="label || $slots.label" class="label">
<slot name="label"> <slot name="label">
<span>{{ label }}</span> <span style="color: var(--vn-label-color)">{{ label }}</span>
</slot> </slot>
</div> </div>
<div class="value"> <div class="value">

View File

@ -74,6 +74,10 @@ const props = defineProps({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
mapKey: {
type: String,
default: '',
},
}); });
const emit = defineEmits(['onFetch', 'onPaginate', 'onChange']); const emit = defineEmits(['onFetch', 'onPaginate', 'onChange']);
@ -96,6 +100,7 @@ const arrayData = useArrayData(props.dataKey, {
exprBuilder: props.exprBuilder, exprBuilder: props.exprBuilder,
keepOpts: props.keepOpts, keepOpts: props.keepOpts,
searchUrl: props.searchUrl, searchUrl: props.searchUrl,
mapKey: props.mapKey,
}); });
const store = arrayData.store; const store = arrayData.store;

View File

@ -51,10 +51,6 @@ const props = defineProps({
type: Object, type: Object,
default: null, default: null,
}, },
staticParams: {
type: Array,
default: () => [],
},
exprBuilder: { exprBuilder: {
type: Function, type: Function,
default: null, default: null,

View File

@ -4,7 +4,11 @@ import VnPaginate from 'src/components/ui/VnPaginate.vue';
describe('VnPaginate', () => { describe('VnPaginate', () => {
const expectedUrl = '/api/customers'; const expectedUrl = '/api/customers';
const defaultData = [
{ id: 1, name: 'Tony Stark' },
{ id: 2, name: 'Jessica Jones' },
{ id: 3, name: 'Bruce Wayne' },
];
let vm; let vm;
beforeAll(() => { beforeAll(() => {
const options = { const options = {
@ -28,11 +32,7 @@ describe('VnPaginate', () => {
describe('paginate()', () => { describe('paginate()', () => {
it('should call to the paginate() method and set the data on the rows property', async () => { it('should call to the paginate() method and set the data on the rows property', async () => {
vi.spyOn(vm.arrayData, 'loadMore'); vi.spyOn(vm.arrayData, 'loadMore');
vm.store.data = [ vm.store.data = defaultData;
{ id: 1, name: 'Tony Stark' },
{ id: 2, name: 'Jessica Jones' },
{ id: 3, name: 'Bruce Wayne' },
];
await vm.paginate(); await vm.paginate();
@ -42,26 +42,25 @@ describe('VnPaginate', () => {
it('should call to the paginate() method and then call it again to paginate', async () => { it('should call to the paginate() method and then call it again to paginate', async () => {
vi.spyOn(axios, 'get').mockResolvedValue({ vi.spyOn(axios, 'get').mockResolvedValue({
data: [ data: defaultData,
{ id: 1, name: 'Tony Stark' },
{ id: 2, name: 'Jessica Jones' },
{ id: 3, name: 'Bruce Wayne' },
],
}); });
vm.store.hasMoreData = true; vm.store.hasMoreData = true;
await vm.$nextTick(); await vm.$nextTick();
vm.store.data = [ vm.store.data = defaultData;
{ id: 1, name: 'Tony Stark' },
{ id: 2, name: 'Jessica Jones' },
{ id: 3, name: 'Bruce Wayne' },
];
await vm.paginate(); await vm.paginate();
expect(vm.store.skip).toEqual(3); expect(vm.store.skip).toEqual(3);
expect(vm.store.data.length).toEqual(6); expect(vm.store.data.length).toEqual(6);
vi.spyOn(axios, 'get').mockResolvedValue({
data: [
{ id: 4, name: 'Peter Parker' },
{ id: 5, name: 'Clark Kent' },
{ id: 6, name: 'Barry Allen' },
],
});
await vm.paginate(); await vm.paginate();
expect(vm.store.skip).toEqual(6); expect(vm.store.skip).toEqual(6);
@ -85,11 +84,7 @@ describe('VnPaginate', () => {
const index = 1; const index = 1;
const done = vi.fn(); const done = vi.fn();
vm.store.data = [ vm.store.data = defaultData;
{ id: 1, name: 'Tony Stark' },
{ id: 2, name: 'Jessica Jones' },
{ id: 3, name: 'Bruce Wayne' },
];
await vm.onLoad(index, done); await vm.onLoad(index, done);
@ -105,11 +100,7 @@ describe('VnPaginate', () => {
], ],
}); });
vm.store.data = [ vm.store.data = defaultData;
{ id: 1, name: 'Tony Stark' },
{ id: 2, name: 'Jessica Jones' },
{ id: 3, name: 'Bruce Wayne' },
];
expect(vm.pagination.page).toEqual(1); expect(vm.pagination.page).toEqual(1);

View File

@ -0,0 +1,45 @@
import { describe, expect, it, vi } from 'vitest';
import axios from 'axios';
import { getExchange } from 'src/composables/getExchange';
vi.mock('axios');
describe('getExchange()', () => {
it('should return the correct exchange rate', async () => {
axios.get.mockResolvedValue({
data: { value: 1.2 },
});
const amount = 100;
const currencyFk = 1;
const dated = '2023-01-01';
const result = await getExchange(amount, currencyFk, dated);
expect(result).toBe('83.33');
});
it('should return the correct exchange rate with custom decimal places', async () => {
axios.get.mockResolvedValue({
data: { value: 1.2 },
});
const amount = 100;
const currencyFk = 1;
const dated = '2023-01-01';
const decimalPlaces = 3;
const result = await getExchange(amount, currencyFk, dated, decimalPlaces);
expect(result).toBe('83.333');
});
it('should return null if the API call fails', async () => {
axios.get.mockRejectedValue(new Error('Network error'));
const amount = 100;
const currencyFk = 1;
const dated = '2023-01-01';
const result = await getExchange(amount, currencyFk, dated);
expect(result).toBeNull();
});
});

View File

@ -0,0 +1,55 @@
import { vi, describe, expect, it } from 'vitest';
import { getTotal } from 'src/composables/getTotal';
vi.mock('src/filters', () => ({
toCurrency: vi.fn((value, currency) => `${currency} ${value.toFixed(2)}`),
}));
describe('getTotal()', () => {
const rows = [
{ amount: 10.5, tax: 2.1 },
{ amount: 20.75, tax: 3.25 },
{ amount: 30.25, tax: 4.75 },
];
it('should calculate the total for a given key', () => {
const total = getTotal(rows, 'amount');
expect(total).toBe('61.50');
});
it('should calculate the total with a callback function', () => {
const total = getTotal(rows, null, { cb: (row) => row.amount + row.tax });
expect(total).toBe('71.60');
});
it('should format the total as currency', () => {
const total = getTotal(rows, 'amount', { currency: 'USD' });
expect(total).toBe('USD 61.50');
});
it('should format the total as currency with default currency', () => {
const total = getTotal(rows, 'amount', { currency: 'default' });
expect(total).toBe('undefined 61.50');
});
it('should calculate the total with integer formatting', () => {
const total = getTotal(rows, 'amount', { decimalPlaces: 0 });
expect(total).toBe('62');
});
it('should calculate the total with custom decimal places', () => {
const total = getTotal(rows, 'amount', { decimalPlaces: 1 });
expect(total).toBe('61.5');
});
it('should handle rows with missing keys', () => {
const rowsWithMissingKeys = [{ amount: 10.5 }, { amount: 20.75 }, {}];
const total = getTotal(rowsWithMissingKeys, 'amount');
expect(total).toBe('31.25');
});
it('should handle empty rows', () => {
const total = getTotal([], 'amount');
expect(total).toBe('0.00');
});
});

View File

@ -0,0 +1,9 @@
import { describe, expect, it } from 'vitest';
import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard';
describe('useAccountShortToStandard()', () => {
it('should pad the decimal part with zeros for short numbers', () => {
expect(useAccountShortToStandard('123.45')).toBe('1230000045');
expect(useAccountShortToStandard('123.')).toBe('1230000000');
});
});

View File

@ -0,0 +1,16 @@
import axios from 'axios';
export async function getExchange(amount, currencyFk, dated, decimalPlaces = 2) {
try {
const { data } = await axios.get('ReferenceRates/findOne', {
params: {
filter: {
fields: ['value'],
where: { currencyFk, dated },
},
},
});
return (amount / data.value).toFixed(decimalPlaces);
} catch (e) {
return null;
}
}

View File

@ -1,10 +1,10 @@
import { toCurrency } from 'src/filters'; import { toCurrency } from 'src/filters';
export function getTotal(rows, key, opts = {}) { export function getTotal(rows, key, opts = {}) {
const { currency, cb } = opts; const { currency, cb, decimalPlaces } = opts;
const total = rows.reduce((acc, row) => acc + +(cb ? cb(row) : row[key] || 0), 0); const total = rows.reduce((acc, row) => acc + +(cb ? cb(row) : row[key] || 0), 0);
return currency return currency
? toCurrency(total, currency == 'default' ? undefined : currency) ? toCurrency(total, currency == 'default' ? undefined : currency)
: total; : parseFloat(total).toFixed(decimalPlaces ?? 2);
} }

View File

@ -0,0 +1,4 @@
export function useAccountShortToStandard(val) {
if (!val || !/^\d+(\.\d*)$/.test(val)) return;
return val?.replace('.', '0'.repeat(11 - val.length));
}

View File

@ -49,6 +49,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
'exprBuilder', 'exprBuilder',
'searchUrl', 'searchUrl',
'navigate', 'navigate',
'mapKey',
]; ];
if (typeof userOptions === 'object') { if (typeof userOptions === 'object') {
for (const option in userOptions) { for (const option in userOptions) {
@ -119,17 +120,12 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
const { limit } = filter; const { limit } = filter;
store.hasMoreData = limit && response.data.length >= limit; store.hasMoreData = limit && response.data.length >= limit;
if (append) { processData(response.data, { map: !!store.mapKey, append });
if (!store.data) store.data = []; if (!append && !isDialogOpened()) updateRouter && updateStateParams();
for (const row of response.data) store.data.push(row);
} else {
store.data = response.data;
if (!isDialogOpened()) updateRouter && updateStateParams();
}
store.isLoading = false; store.isLoading = false;
canceller = null; canceller = null;
return response; return response;
} }
@ -288,6 +284,31 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
router.replace(newUrl); router.replace(newUrl);
} }
function processData(data, { map = true, append = true }) {
if (!append) {
store.data = [];
store.map = new Map();
}
if (!Array.isArray(data)) store.data = data;
else if (!map && append) for (const row of data) store.data.push(row);
else
for (const row of data) {
const key = row[store.mapKey];
const val = { ...row, key };
if (store.map.has(key)) {
const { position } = store.map.get(key);
val.position = position;
store.map.set(key, val);
store.data[position] = val;
} else {
val.position = store.map.size;
store.map.set(key, val);
store.data.push(val);
}
}
}
const totalRows = computed(() => (store.data && store.data.length) || 0); const totalRows = computed(() => (store.data && store.data.length) || 0);
const isLoading = computed(() => store.isLoading || false); const isLoading = computed(() => store.isLoading || false);

View File

@ -11,6 +11,7 @@ body.body--light {
--vn-text-color: var(--font-color); --vn-text-color: var(--font-color);
--vn-label-color: #5f5f5f; --vn-label-color: #5f5f5f;
--vn-accent-color: #e7e3e3; --vn-accent-color: #e7e3e3;
--vn-empty-tag: #acacac;
background-color: var(--vn-page-color); background-color: var(--vn-page-color);
@ -26,6 +27,7 @@ body.body--dark {
--vn-text-color: white; --vn-text-color: white;
--vn-label-color: #a8a8a8; --vn-label-color: #a8a8a8;
--vn-accent-color: #424242; --vn-accent-color: #424242;
--vn-empty-tag: #2d2d2d;
background-color: var(--vn-page-color); background-color: var(--vn-page-color);
} }
@ -240,7 +242,7 @@ input::-webkit-inner-spin-button {
.q-table { .q-table {
th, th,
td { td {
padding: 1px 10px 1px 10px; padding: 1px 3px 1px 3px;
max-width: 130px; max-width: 130px;
div span { div span {
overflow: hidden; overflow: hidden;
@ -264,6 +266,10 @@ input::-webkit-inner-spin-button {
.shrink { .shrink {
max-width: 75px; max-width: 75px;
} }
.number {
text-align: right;
width: 96px;
}
.expand { .expand {
max-width: 400px; max-width: 400px;
} }
@ -292,3 +298,20 @@ input::-webkit-inner-spin-button {
.no-visible { .no-visible {
visibility: hidden; visibility: hidden;
} }
.q-field__inner {
.q-field__control {
min-height: auto !important;
display: flex;
align-items: flex-end;
padding-bottom: 2px;
.q-field__native.row {
min-height: auto !important;
}
}
}
.q-date__header-today {
border-radius: 12px;
border: 1px solid;
box-shadow: 0 4px 6px #00000000;
}

View File

@ -129,6 +129,7 @@ globals:
small: Small small: Small
medium: Medium medium: Medium
big: Big big: Big
email: Email
pageTitles: pageTitles:
logIn: Login logIn: Login
addressEdit: Update address addressEdit: Update address
@ -141,7 +142,7 @@ globals:
workCenters: Work centers workCenters: Work centers
modes: Modes modes: Modes
zones: Zones zones: Zones
zonesList: Zones zonesList: List
deliveryDays: Delivery days deliveryDays: Delivery days
upcomingDeliveries: Upcoming deliveries upcomingDeliveries: Upcoming deliveries
role: Role role: Role
@ -330,13 +331,26 @@ globals:
email: Email email: Email
SSN: SSN SSN: SSN
fi: FI fi: FI
packing: ITP
myTeam: My team myTeam: My team
departmentFk: Department departmentFk: Department
from: From
to: To
supplierFk: Supplier
supplierRef: Supplier ref
serial: Serial
amount: Importe
awbCode: AWB
correctedFk: Rectified
correctingFk: Rectificative
daysOnward: Days onward
countryFk: Country countryFk: Country
companyFk: Company
changePass: Change password changePass: Change password
deleteConfirmTitle: Delete selected elements deleteConfirmTitle: Delete selected elements
changeState: Change state changeState: Change state
raid: 'Raid {daysInForward} days' raid: 'Raid {daysInForward} days'
isVies: Vies
errors: errors:
statusUnauthorized: Access denied statusUnauthorized: Access denied
statusInternalServerError: An internal server error has ocurred statusInternalServerError: An internal server error has ocurred
@ -730,7 +744,6 @@ supplier:
sageTransactionTypeFk: Sage transaction type sageTransactionTypeFk: Sage transaction type
supplierActivityFk: Supplier activity supplierActivityFk: Supplier activity
isTrucker: Trucker isTrucker: Trucker
isVies: Vies
billingData: billingData:
payMethodFk: Billing data payMethodFk: Billing data
payDemFk: Payment deadline payDemFk: Payment deadline
@ -849,6 +862,7 @@ components:
ended: To ended: To
mine: For me mine: For me
hasMinPrice: Minimum price hasMinPrice: Minimum price
warehouseFk: Warehouse
# LatestBuysFilter # LatestBuysFilter
salesPersonFk: Buyer salesPersonFk: Buyer
from: From from: From

View File

@ -131,6 +131,7 @@ globals:
small: Pequeño/a small: Pequeño/a
medium: Mediano/a medium: Mediano/a
big: Grande big: Grande
email: Correo
pageTitles: pageTitles:
logIn: Inicio de sesión logIn: Inicio de sesión
addressEdit: Modificar consignatario addressEdit: Modificar consignatario
@ -143,7 +144,7 @@ globals:
workCenters: Centros de trabajo workCenters: Centros de trabajo
modes: Modos modes: Modos
zones: Zonas zones: Zonas
zonesList: Zonas zonesList: Listado
deliveryDays: Días de entrega deliveryDays: Días de entrega
upcomingDeliveries: Próximos repartos upcomingDeliveries: Próximos repartos
role: Role role: Role
@ -336,11 +337,22 @@ globals:
SSN: NSS SSN: NSS
fi: NIF fi: NIF
myTeam: Mi equipo myTeam: Mi equipo
from: Desde
to: Hasta
supplierFk: Proveedor
supplierRef: Ref. proveedor
serial: Serie
amount: Importe
awbCode: AWB
daysOnward: Días adelante
packing: ITP
countryFk: País countryFk: País
companyFk: Empresa
changePass: Cambiar contraseña changePass: Cambiar contraseña
deleteConfirmTitle: Eliminar los elementos seleccionados deleteConfirmTitle: Eliminar los elementos seleccionados
changeState: Cambiar estado changeState: Cambiar estado
raid: 'Redada {daysInForward} días' raid: 'Redada {daysInForward} días'
isVies: Vies
errors: errors:
statusUnauthorized: Acceso denegado statusUnauthorized: Acceso denegado
statusInternalServerError: Ha ocurrido un error interno del servidor statusInternalServerError: Ha ocurrido un error interno del servidor
@ -498,7 +510,7 @@ invoiceOut:
ticketList: Listado de tickets ticketList: Listado de tickets
summary: summary:
issued: Fecha issued: Fecha
dued: Vencimiento dued: Fecha límite
booked: Contabilizada booked: Contabilizada
taxBreakdown: Desglose impositivo taxBreakdown: Desglose impositivo
taxableBase: Base imp. taxableBase: Base imp.
@ -725,7 +737,6 @@ supplier:
sageTransactionTypeFk: Tipo de transacción sage sageTransactionTypeFk: Tipo de transacción sage
supplierActivityFk: Actividad proveedor supplierActivityFk: Actividad proveedor
isTrucker: Transportista isTrucker: Transportista
isVies: Vies
billingData: billingData:
payMethodFk: Forma de pago payMethodFk: Forma de pago
payDemFk: Plazo de pago payDemFk: Plazo de pago
@ -843,6 +854,7 @@ components:
ended: Hasta ended: Hasta
mine: Para mi mine: Para mi
hasMinPrice: Precio mínimo hasMinPrice: Precio mínimo
wareHouseFk: Almacén
# LatestBuysFilter # LatestBuysFilter
salesPersonFk: Comprador salesPersonFk: Comprador
active: Activo active: Activo

View File

@ -6,6 +6,7 @@ import CrudModel from 'components/CrudModel.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import { tMobile } from 'composables/tMobile'; import { tMobile } from 'composables/tMobile';
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
const route = useRoute(); const route = useRoute();
@ -157,19 +158,14 @@ const columns = computed(() => [
auto-width auto-width
@keyup.ctrl.enter.stop="claimDevelopmentForm.saveChanges()" @keyup.ctrl.enter.stop="claimDevelopmentForm.saveChanges()"
> >
<VnSelect <VnSelectWorker
v-if="col.name == 'worker'"
v-model="row[col.model]" v-model="row[col.model]"
:url="col.url"
:where="col.where"
:sort-by="col.sortBy"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
:autofocus="col.tabIndex == 1" :autofocus="col.tabIndex == 1"
input-debounce="0" input-debounce="0"
hide-selected hide-selected
> >
<template #option="scope" v-if="col.name == 'worker'"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
<QItemSection> <QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel> <QItemLabel>{{ scope.opt?.name }}</QItemLabel>
@ -180,7 +176,20 @@ const columns = computed(() => [
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>
</VnSelect> </VnSelectWorker>
<VnSelect
v-else
v-model="row[col.model]"
:url="col.url"
:where="col.where"
:sort-by="col.sortBy"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
:autofocus="col.tabIndex == 1"
input-debounce="0"
hide-selected
/>
</QTd> </QTd>
</template> </template>
<template #item="props"> <template #item="props">

View File

@ -57,6 +57,7 @@ function onFetch(rows, newRows) {
const price = row.quantity * sale.price; const price = row.quantity * sale.price;
const discount = (sale.discount * price) / 100; const discount = (sale.discount * price) / 100;
amountClaimed.value = amountClaimed.value + (price - discount); amountClaimed.value = amountClaimed.value + (price - discount);
} }
} }
@ -207,9 +208,10 @@ async function saveWhenHasChanges() {
selection="multiple" selection="multiple"
v-model:selected="selected" v-model:selected="selected"
:grid="$q.screen.lt.md" :grid="$q.screen.lt.md"
> >
<template #body-cell-claimed="{ row }"> <template #body-cell-claimed="{ row }">
<QTd auto-width align="right" class="text-primary"> <QTd auto-width align="right" class="text-primary shrink">
<QInput <QInput
v-model.number="row.quantity" v-model.number="row.quantity"
type="number" type="number"
@ -220,7 +222,7 @@ async function saveWhenHasChanges() {
</QTd> </QTd>
</template> </template>
<template #body-cell-description="{ row, value }"> <template #body-cell-description="{ row, value }">
<QTd auto-width align="right" class="text-primary"> <QTd auto-width align="right" class="link expand">
{{ value }} {{ value }}
<ItemDescriptorProxy <ItemDescriptorProxy
:id="row.sale.itemFk" :id="row.sale.itemFk"
@ -228,7 +230,7 @@ async function saveWhenHasChanges() {
</QTd> </QTd>
</template> </template>
<template #body-cell-discount="{ row, value, rowIndex }"> <template #body-cell-discount="{ row, value, rowIndex }">
<QTd auto-width align="right" class="text-primary"> <QTd auto-width align="right" class="text-primary shrink">
{{ value }} {{ value }}
<VnDiscount <VnDiscount
:quantity="row.quantity" :quantity="row.quantity"
@ -264,7 +266,7 @@ async function saveWhenHasChanges() {
</QItemSection> </QItemSection>
<QItemSection side> <QItemSection side>
<template v-if="column.name === 'claimed'"> <template v-if="column.name === 'claimed'">
<QItemLabel class="text-primary"> <QItemLabel class="text-primary shrink">
<QInput <QInput
v-model.number=" v-model.number="
props.row.quantity props.row.quantity
@ -282,7 +284,7 @@ async function saveWhenHasChanges() {
<template <template
v-else-if="column.name === 'discount'" v-else-if="column.name === 'discount'"
> >
<QItemLabel class="text-primary"> <QItemLabel class="text-primary shrink">
{{ column.value }} {{ column.value }}
<VnDiscount <VnDiscount
:quantity="props.row.quantity" :quantity="props.row.quantity"
@ -330,6 +332,7 @@ async function saveWhenHasChanges() {
.grid-style-transition { .grid-style-transition {
transition: transform 0.28s, background-color 0.28s; transition: transform 0.28s, background-color 0.28s;
} }
</style> </style>
<i18n> <i18n>

View File

@ -120,13 +120,13 @@ const developmentColumns = ref([
{ {
name: 'claimReason', name: 'claimReason',
label: 'claim.reason', label: 'claim.reason',
field: (row) => row.claimReason.description, field: (row) => row.claimReason?.description,
sortable: true, sortable: true,
}, },
{ {
name: 'claimResult', name: 'claimResult',
label: 'claim.result', label: 'claim.result',
field: (row) => row.claimResult.description, field: (row) => row.claimResult?.description,
sortable: true, sortable: true,
}, },
{ {
@ -345,12 +345,9 @@ function claimUrl(section) {
<span v-if="col.name != 'description'">{{ <span v-if="col.name != 'description'">{{
t(col.value) t(col.value)
}}</span> }}</span>
<QBtn <span class="link" v-if="col.name === 'description'">{{
v-if="col.name == 'description'" t(col.value)
flat }}</span>
color="blue"
>{{ col.value }}</QBtn
>
<ItemDescriptorProxy <ItemDescriptorProxy
v-if="col.name == 'description'" v-if="col.name == 'description'"
:id="props.row.sale.itemFk" :id="props.row.sale.itemFk"

View File

@ -105,6 +105,7 @@ const columns = computed(() => [
title: t('components.smartCard.viewSummary'), title: t('components.smartCard.viewSummary'),
icon: 'preview', icon: 'preview',
action: (row) => viewSummary(row.id, ClaimSummary), action: (row) => viewSummary(row.id, ClaimSummary),
isPrimary: true,
}, },
], ],
}, },
@ -135,7 +136,6 @@ const STATE_COLOR = {
:columns="columns" :columns="columns"
redirect="claim" redirect="claim"
:right-search="false" :right-search="false"
auto-load
> >
<template #column-clientFk="{ row }"> <template #column-clientFk="{ row }">
<span class="link" @click.stop> <span class="link" @click.stop>

View File

@ -110,7 +110,7 @@ function handleLocation(data, location) {
<VnRow> <VnRow>
<QCheckbox :label="t('Has to invoice')" v-model="data.hasToInvoice" /> <QCheckbox :label="t('Has to invoice')" v-model="data.hasToInvoice" />
<div> <div>
<QCheckbox :label="t('Vies')" v-model="data.isVies" /> <QCheckbox :label="t('globals.isVies')" v-model="data.isVies" />
<QIcon name="info" class="cursor-info q-ml-sm" size="sm"> <QIcon name="info" class="cursor-info q-ml-sm" size="sm">
<QTooltip> <QTooltip>
{{ t('whenActivatingIt') }} {{ t('whenActivatingIt') }}
@ -169,7 +169,6 @@ es:
Active: Activo Active: Activo
Frozen: Congelado Frozen: Congelado
Has to invoice: Factura Has to invoice: Factura
Vies: Vies
Notify by email: Notificar vía e-mail Notify by email: Notificar vía e-mail
Invoice by address: Facturar por consignatario Invoice by address: Facturar por consignatario
Is equalizated: Recargo de equivalencia Is equalizated: Recargo de equivalencia

View File

@ -173,7 +173,7 @@ const sumRisk = ({ clientRisks }) => {
:label="t('customer.summary.notifyByEmail')" :label="t('customer.summary.notifyByEmail')"
:value="entity.isToBeMailed" :value="entity.isToBeMailed"
/> />
<VnLv :label="t('customer.summary.vies')" :value="entity.isVies" /> <VnLv :label="t('globals.isVies')" :value="entity.isVies" />
</VnRow> </VnRow>
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">

View File

@ -2,22 +2,21 @@
import { ref, computed, markRaw } from 'vue'; import { ref, computed, markRaw } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { toDate } from 'src/filters';
import RightMenu from 'src/components/common/RightMenu.vue';
import CustomerSummary from './Card/CustomerSummary.vue';
import CustomerFilter from './CustomerFilter.vue';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import VnLocation from 'src/components/common/VnLocation.vue'; import VnLocation from 'src/components/common/VnLocation.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue';
import CustomerSummary from './Card/CustomerSummary.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import RightMenu from 'src/components/common/RightMenu.vue';
import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue';
import { toDate } from 'src/filters';
import CustomerFilter from './CustomerFilter.vue';
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
const { t } = useI18n(); const { t } = useI18n();
const router = useRouter(); const router = useRouter();
const tableRef = ref(); const tableRef = ref();
const columns = computed(() => [ const columns = computed(() => [
{ {
align: 'left', align: 'left',
@ -263,7 +262,7 @@ const columns = computed(() => [
}, },
{ {
align: 'left', align: 'left',
label: t('customer.extendedList.tableVisibleColumns.isVies'), label: t('globals.isVies'),
name: 'isVies', name: 'isVies',
columnFilter: { columnFilter: {
inWhere: true, inWhere: true,
@ -405,6 +404,7 @@ function handleLocation(data, location) {
ref="tableRef" ref="tableRef"
data-key="CustomerList" data-key="CustomerList"
url="Clients/filter" url="Clients/filter"
order="id DESC"
:create="{ :create="{
urlCreate: 'Clients/createWithUser', urlCreate: 'Clients/createWithUser',
title: t('globals.pageTitles.customerCreate'), title: t('globals.pageTitles.customerCreate'),
@ -414,11 +414,9 @@ function handleLocation(data, location) {
isEqualizated: false, isEqualizated: false,
}, },
}" }"
order="id DESC"
:columns="columns" :columns="columns"
redirect="customer"
:right-search="false" :right-search="false"
auto-load redirect="customer"
> >
<template #more-create-dialog="{ data }"> <template #more-create-dialog="{ data }">
<VnSelectWorker <VnSelectWorker
@ -431,7 +429,26 @@ function handleLocation(data, location) {
:id-value="data.salesPersonFk" :id-value="data.salesPersonFk"
emit-value emit-value
auto-load auto-load
>
<template #prepend>
<VnAvatar
:worker-id="data.salesPersonFk"
color="primary"
:title="title"
/> />
</template>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption
>{{ scope.opt?.nickname }},
{{ scope.opt?.code }}</QItemLabel
>
</QItemSection>
</QItem>
</template>
</VnSelectWorker>
<VnLocation <VnLocation
:acls="[{ model: 'Province', props: '*', accessType: 'WRITE' }]" :acls="[{ model: 'Province', props: '*', accessType: 'WRITE' }]"
v-model="data.location" v-model="data.location"
@ -452,7 +469,7 @@ function handleLocation(data, location) {
</template> </template>
<i18n> <i18n>
es: es:
Web user: Usuario Web Web user: Usuario web
</i18n> </i18n>
<style lang="scss" scoped> <style lang="scss" scoped>
.col-content { .col-content {

View File

@ -12,6 +12,7 @@ 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 VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CustomerNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCustomsAgent.vue'; import CustomerNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCustomsAgent.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
@ -144,7 +145,6 @@ function handleLocation(data, location) {
:url="`Addresses/${route.params.addressId}`" :url="`Addresses/${route.params.addressId}`"
@on-data-saved="onDataSaved()" @on-data-saved="onDataSaved()"
auto-load auto-load
model="customer"
> >
<template #moreActions> <template #moreActions>
<QBtn <QBtn
@ -220,7 +220,6 @@ function handleLocation(data, location) {
</div> </div>
</VnRow> </VnRow>
<VnRow> <VnRow>
<div class="col">
<VnSelect <VnSelect
:label="t('Incoterms')" :label="t('Incoterms')"
:options="incoterms" :options="incoterms"
@ -229,8 +228,6 @@ function handleLocation(data, location) {
option-value="code" option-value="code"
v-model="data.incotermsFk" v-model="data.incotermsFk"
/> />
</div>
<div class="col">
<VnSelectDialog <VnSelectDialog
:label="t('Customs agent')" :label="t('Customs agent')"
:options="customsAgents" :options="customsAgents"
@ -244,7 +241,14 @@ function handleLocation(data, location) {
<CustomerNewCustomsAgent /> <CustomerNewCustomsAgent />
</template> </template>
</VnSelectDialog> </VnSelectDialog>
</div> </VnRow>
<VnRow>
<VnInputNumber
:label="t('Longitude')"
clearable
v-model="data.longitude"
/>
<VnInputNumber :label="t('Latitude')" clearable v-model="data.latitude" />
</VnRow> </VnRow>
<h4 class="q-mb-xs">{{ t('Notes') }}</h4> <h4 class="q-mb-xs">{{ t('Notes') }}</h4>
<VnRow <VnRow
@ -322,4 +326,6 @@ es:
Description: Descripción Description: Descripción
Add note: Añadir nota Add note: Añadir nota
Remove note: Eliminar nota Remove note: Eliminar nota
Longitude: Longitud
Latitude: Latitud
</i18n> </i18n>

View File

@ -189,6 +189,7 @@ async function getAmountPaid() {
:url-create="urlCreate" :url-create="urlCreate"
:mapper="onBeforeSave" :mapper="onBeforeSave"
@on-data-saved="onDataSaved" @on-data-saved="onDataSaved"
:prevent-submit="true"
> >
<template #form="{ data, validate }"> <template #form="{ data, validate }">
<span ref="closeButton" class="row justify-end close-icon" v-close-popup> <span ref="closeButton" class="row justify-end close-icon" v-close-popup>
@ -303,7 +304,7 @@ async function getAmountPaid() {
:label="t('globals.save')" :label="t('globals.save')"
:loading="formModelRef.isLoading" :loading="formModelRef.isLoading"
color="primary" color="primary"
type="submit" @click="formModelRef.save()"
/> />
</div> </div>
</template> </template>

View File

@ -88,7 +88,6 @@ customer:
businessTypeFk: Business type businessTypeFk: Business type
sageTaxTypeFk: Sage tax type sageTaxTypeFk: Sage tax type
sageTransactionTypeFk: Sage tr. type sageTransactionTypeFk: Sage tr. type
isVies: Vies
isTaxDataChecked: Verified data isTaxDataChecked: Verified data
isFreezed: Freezed isFreezed: Freezed
hasToInvoice: Invoice hasToInvoice: Invoice

View File

@ -90,7 +90,6 @@ customer:
businessTypeFk: Tipo de negocio businessTypeFk: Tipo de negocio
sageTaxTypeFk: Tipo de impuesto Sage sageTaxTypeFk: Tipo de impuesto Sage
sageTransactionTypeFk: Tipo tr. sage sageTransactionTypeFk: Tipo tr. sage
isVies: Vies
isTaxDataChecked: Datos comprobados isTaxDataChecked: Datos comprobados
isFreezed: Congelado isFreezed: Congelado
hasToInvoice: Factura hasToInvoice: Factura

View File

@ -83,7 +83,7 @@ const { openConfirmationModal } = useVnConfirm();
</template> </template>
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('department.chat')" :value="entity.chatName" /> <VnLv :label="t('department.chat')" :value="entity.chatName" />
<VnLv :label="t('department.email')" :value="entity.notificationEmail" copy /> <VnLv :label="t('globals.email')" :value="entity.notificationEmail" copy />
<VnLv <VnLv
:label="t('department.selfConsumptionCustomer')" :label="t('department.selfConsumptionCustomer')"
:value="entity.client?.name" :value="entity.client?.name"

View File

@ -58,7 +58,7 @@ onMounted(async () => {
dash dash
/> />
<VnLv <VnLv
:label="t('department.email')" :label="t('globals.email')"
:value="department.notificationEmail" :value="department.notificationEmail"
dash dash
/> />

View File

@ -1,8 +1,6 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { onMounted } from 'vue';
import { useStateStore } from 'stores/useStateStore';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
@ -20,11 +18,6 @@ const props = defineProps({
const currenciesOptions = ref([]); const currenciesOptions = ref([]);
const companiesOptions = ref([]); const companiesOptions = ref([]);
const stateStore = useStateStore();
onMounted(async () => {
stateStore.rightDrawer = true;
});
</script> </script>
<template> <template>

View File

@ -49,8 +49,10 @@ const columns = computed(() => [
align: 'left', align: 'left',
label: t('globals.id'), label: t('globals.id'),
name: 'id', name: 'id',
isTitle: true, isId: true,
cardVisible: true, chip: {
condition: () => true,
},
}, },
{ {
align: 'left', align: 'left',
@ -177,9 +179,6 @@ const columns = computed(() => [
], ],
}, },
]); ]);
onMounted(async () => {
stateStore.rightDrawer = true;
});
</script> </script>
<template> <template>
<VnSearchbar <VnSearchbar
@ -207,7 +206,6 @@ onMounted(async () => {
order="id DESC" order="id DESC"
:columns="columns" :columns="columns"
redirect="entry" redirect="entry"
auto-load
:right-search="false" :right-search="false"
> >
<template #column-status="{ row }"> <template #column-status="{ row }">

View File

@ -116,6 +116,7 @@ function deleteFile(dmsFk) {
<template #form="{ data }"> <template #form="{ data }">
<VnRow> <VnRow>
<VnSelect <VnSelect
:required="true"
:label="t('supplierFk')" :label="t('supplierFk')"
v-model="data.supplierFk" v-model="data.supplierFk"
option-value="id" option-value="id"
@ -244,14 +245,19 @@ function deleteFile(dmsFk) {
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnSelect <VnSelect
:required="true"
:is-clearable="false"
:label="t('Currency')" :label="t('Currency')"
v-model="data.currencyFk" v-model="data.currencyFk"
:options="currencies" :options="currencies"
option-value="id" option-value="id"
option-label="code" option-label="code"
sort-by="id"
/> />
<VnSelect <VnSelect
:required="true"
:is-clearable="false"
v-if="companiesRef" v-if="companiesRef"
:label="t('Company')" :label="t('Company')"
v-model="data.companyFk" v-model="data.companyFk"
@ -262,7 +268,7 @@ function deleteFile(dmsFk) {
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnSelect <VnSelect
:label="t('invoiceIn.summary.sage')" :label="t('InvoiceIn.summary.sage')"
v-model="data.withholdingSageFk" v-model="data.withholdingSageFk"
:options="sageWithholdings" :options="sageWithholdings"
option-value="id" option-value="id"

View File

@ -1,23 +1,21 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed, capitalize } from 'vue';
import { useRouter } 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';
import { useCapitalize } from 'src/composables/useCapitalize';
import CrudModel from 'src/components/CrudModel.vue'; import CrudModel from 'src/components/CrudModel.vue';
import FetchData from 'src/components/FetchData.vue'; import FetchData from 'src/components/FetchData.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
const { push, currentRoute } = useRouter(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const invoiceId = +currentRoute.value.params.id;
const arrayData = useArrayData(); const arrayData = useArrayData();
const invoiceIn = computed(() => arrayData.store.data); const invoiceIn = computed(() => arrayData.store.data);
const invoiceInCorrectionRef = ref(); const invoiceInCorrectionRef = ref();
const filter = { const filter = {
include: { relation: 'invoiceIn' }, include: { relation: 'invoiceIn' },
where: { correctingFk: invoiceId }, where: { correctingFk: route.params.id },
}; };
const columns = computed(() => [ const columns = computed(() => [
{ {
@ -31,7 +29,7 @@ const columns = computed(() => [
}, },
{ {
name: 'type', name: 'type',
label: useCapitalize(t('globals.type')), label: capitalize(t('globals.type')),
field: (row) => row.cplusRectificationTypeFk, field: (row) => row.cplusRectificationTypeFk,
options: cplusRectificationTypes.value, options: cplusRectificationTypes.value,
model: 'cplusRectificationTypeFk', model: 'cplusRectificationTypeFk',
@ -43,10 +41,10 @@ const columns = computed(() => [
}, },
{ {
name: 'class', name: 'class',
label: useCapitalize(t('globals.class')), label: capitalize(t('globals.class')),
field: (row) => row.siiTypeInvoiceOutFk, field: (row) => row.siiTypeInvoiceInFk,
options: siiTypeInvoiceOuts.value, options: siiTypeInvoiceIns.value,
model: 'siiTypeInvoiceOutFk', model: 'siiTypeInvoiceInFk',
optionValue: 'id', optionValue: 'id',
optionLabel: 'code', optionLabel: 'code',
sortable: true, sortable: true,
@ -55,7 +53,7 @@ const columns = computed(() => [
}, },
{ {
name: 'reason', name: 'reason',
label: useCapitalize(t('globals.reason')), label: capitalize(t('globals.reason')),
field: (row) => row.invoiceCorrectionTypeFk, field: (row) => row.invoiceCorrectionTypeFk,
options: invoiceCorrectionTypes.value, options: invoiceCorrectionTypes.value,
model: 'invoiceCorrectionTypeFk', model: 'invoiceCorrectionTypeFk',
@ -67,13 +65,10 @@ const columns = computed(() => [
}, },
]); ]);
const cplusRectificationTypes = ref([]); const cplusRectificationTypes = ref([]);
const siiTypeInvoiceOuts = ref([]); const siiTypeInvoiceIns = ref([]);
const invoiceCorrectionTypes = ref([]); const invoiceCorrectionTypes = ref([]);
const rowsSelected = ref([]);
const requiredFieldRule = (val) => val || t('globals.requiredField'); const requiredFieldRule = (val) => val || t('globals.requiredField');
const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary`);
</script> </script>
<template> <template>
<FetchData <FetchData
@ -82,9 +77,9 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary`
auto-load auto-load
/> />
<FetchData <FetchData
url="SiiTypeInvoiceOuts" url="SiiTypeInvoiceIns"
:where="{ code: { like: 'R%' } }" :where="{ code: { like: 'R%' } }"
@on-fetch="(data) => (siiTypeInvoiceOuts = data)" @on-fetch="(data) => (siiTypeInvoiceIns = data)"
auto-load auto-load
/> />
<FetchData <FetchData
@ -99,17 +94,14 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary`
url="InvoiceInCorrections" url="InvoiceInCorrections"
:filter="filter" :filter="filter"
auto-load auto-load
v-model:selected="rowsSelected"
primary-key="correctingFk" primary-key="correctingFk"
@save-changes="onSave" :default-remove="false"
> >
<template #body="{ rows }"> <template #body="{ rows }">
<QTable <QTable
v-model:selected="rowsSelected"
:columns="columns" :columns="columns"
:rows="rows" :rows="rows"
row-key="$index" row-key="$index"
selection="single"
:grid="$q.screen.lt.sm" :grid="$q.screen.lt.sm"
:pagination="{ rowsPerPage: 0 }" :pagination="{ rowsPerPage: 0 }"
> >
@ -121,8 +113,17 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary`
:options="col.options" :options="col.options"
:option-value="col.optionValue" :option-value="col.optionValue"
:option-label="col.optionLabel" :option-label="col.optionLabel"
:readonly="row.invoiceIn.isBooked" :disable="row.invoiceIn.isBooked"
/> :filter-options="['description']"
>
<template #option="{ opt, itemProps }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel>{{ opt.description }}</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QTd> </QTd>
</template> </template>
<template #body-cell-class="{ row, col }"> <template #body-cell-class="{ row, col }">
@ -134,8 +135,20 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary`
:option-value="col.optionValue" :option-value="col.optionValue"
:option-label="col.optionLabel" :option-label="col.optionLabel"
:rules="[requiredFieldRule]" :rules="[requiredFieldRule]"
:readonly="row.invoiceIn.isBooked" :filter-options="['code', 'description']"
/> :disable="row.invoiceIn.isBooked"
>
<template #option="{ opt, itemProps }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel
>{{ opt.code }} -
{{ opt.description }}</QItemLabel
>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QTd> </QTd>
</template> </template>
<template #body-cell-reason="{ row, col }"> <template #body-cell-reason="{ row, col }">
@ -147,7 +160,7 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary`
:option-value="col.optionValue" :option-value="col.optionValue"
:option-label="col.optionLabel" :option-label="col.optionLabel"
:rules="[requiredFieldRule]" :rules="[requiredFieldRule]"
:readonly="row.invoiceIn.isBooked" :disable="row.invoiceIn.isBooked"
/> />
</QTd> </QTd>
</template> </template>
@ -155,7 +168,6 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary`
</template> </template>
</CrudModel> </CrudModel>
</template> </template>
<style lang="scss" scoped></style>
<i18n> <i18n>
es: es:
Original invoice: Factura origen Original invoice: Factura origen

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, reactive, computed, onBeforeMount } from 'vue'; import { ref, reactive, computed, onBeforeMount, capitalize } from 'vue';
import { useRouter, onBeforeRouteUpdate } from 'vue-router'; import { useRouter, onBeforeRouteUpdate } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
@ -15,7 +15,6 @@ import FetchData from 'src/components/FetchData.vue';
import SendEmailDialog from 'components/common/SendEmailDialog.vue'; import SendEmailDialog from 'components/common/SendEmailDialog.vue';
import VnConfirm from 'src/components/ui/VnConfirm.vue'; import VnConfirm from 'src/components/ui/VnConfirm.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import { useCapitalize } from 'src/composables/useCapitalize';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
import InvoiceInToBook from '../InvoiceInToBook.vue'; import InvoiceInToBook from '../InvoiceInToBook.vue';
@ -37,7 +36,7 @@ const totalAmount = ref();
const currentAction = ref(); const currentAction = ref();
const config = ref(); const config = ref();
const cplusRectificationTypes = ref([]); const cplusRectificationTypes = ref([]);
const siiTypeInvoiceOuts = ref([]); const siiTypeInvoiceIns = ref([]);
const invoiceCorrectionTypes = ref([]); const invoiceCorrectionTypes = ref([]);
const actions = { const actions = {
unbook: { unbook: {
@ -91,7 +90,7 @@ const routes = reactive({
return { return {
name: 'InvoiceInList', name: 'InvoiceInList',
query: { query: {
params: JSON.stringify({ supplierFk: id }), table: JSON.stringify({ supplierFk: id }),
}, },
}; };
}, },
@ -100,7 +99,7 @@ const routes = reactive({
return { return {
name: 'InvoiceInList', name: 'InvoiceInList',
query: { query: {
params: JSON.stringify({ correctedFk: entityId.value }), table: JSON.stringify({ correctedFk: entityId.value }),
}, },
}; };
} }
@ -119,21 +118,21 @@ const routes = reactive({
const correctionFormData = reactive({ const correctionFormData = reactive({
invoiceReason: 2, invoiceReason: 2,
invoiceType: 2, invoiceType: 2,
invoiceClass: 6, invoiceClass: 8,
}); });
const isNotFilled = computed(() => Object.values(correctionFormData).includes(null)); const isNotFilled = computed(() => Object.values(correctionFormData).includes(null));
onBeforeMount(async () => { onBeforeMount(async () => {
await setInvoiceCorrection(entityId.value); await setInvoiceCorrection(entityId.value);
const { data } = await axios.get(`InvoiceIns/${entityId.value}/getTotals`); const { data } = await axios.get(`InvoiceIns/${entityId.value}/getTotals`);
totalAmount.value = data.totalDueDay; totalAmount.value = data.totalTaxableBase;
}); });
onBeforeRouteUpdate(async (to, from) => { onBeforeRouteUpdate(async (to, from) => {
if (to.params.id !== from.params.id) { if (to.params.id !== from.params.id) {
await setInvoiceCorrection(to.params.id); await setInvoiceCorrection(to.params.id);
const { data } = await axios.get(`InvoiceIns/${to.params.id}/getTotals`); const { data } = await axios.get(`InvoiceIns/${to.params.id}/getTotals`);
totalAmount.value = data.totalDueDay; totalAmount.value = data.totalTaxableBase;
} }
}); });
@ -207,7 +206,8 @@ const isAgricultural = () => {
}; };
function showPdfInvoice() { function showPdfInvoice() {
if (isAgricultural()) openReport(`InvoiceIns/${entityId.value}/invoice-in-pdf`); if (isAgricultural())
openReport(`InvoiceIns/${entityId.value}/invoice-in-pdf`, null, '_blank');
} }
function sendPdfInvoiceConfirmation() { function sendPdfInvoiceConfirmation() {
@ -262,9 +262,9 @@ const createInvoiceInCorrection = async () => {
auto-load auto-load
/> />
<FetchData <FetchData
url="SiiTypeInvoiceOuts" url="siiTypeInvoiceIns"
:where="{ code: { like: 'R%' } }" :where="{ code: { like: 'R%' } }"
@on-fetch="(data) => (siiTypeInvoiceOuts = data)" @on-fetch="(data) => (siiTypeInvoiceIns = data)"
auto-load auto-load
/> />
<FetchData <FetchData
@ -355,10 +355,13 @@ const createInvoiceInCorrection = async () => {
</QItem> </QItem>
</template> </template>
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('invoiceIn.list.issued')" :value="toDate(entity.issued)" /> <VnLv :label="t('InvoiceIn.list.issued')" :value="toDate(entity.issued)" />
<VnLv :label="t('invoiceIn.summary.booked')" :value="toDate(entity.booked)" /> <VnLv
<VnLv :label="t('invoiceIn.list.amount')" :value="toCurrency(totalAmount)" /> :label="t('InvoiceIn.summary.bookedDate')"
<VnLv :label="t('invoiceIn.list.supplier')"> :value="toDate(entity.booked)"
/>
<VnLv :label="t('InvoiceIn.list.amount')" :value="toCurrency(totalAmount)" />
<VnLv :label="t('InvoiceIn.list.supplier')">
<template #value> <template #value>
<span class="link"> <span class="link">
{{ entity?.supplier?.nickname }} {{ entity?.supplier?.nickname }}
@ -375,7 +378,7 @@ const createInvoiceInCorrection = async () => {
color="primary" color="primary"
:to="routes.getSupplier(entity.supplierFk)" :to="routes.getSupplier(entity.supplierFk)"
> >
<QTooltip>{{ t('invoiceIn.list.supplier') }}</QTooltip> <QTooltip>{{ t('InvoiceIn.list.supplier') }}</QTooltip>
</QBtn> </QBtn>
<QBtn <QBtn
size="md" size="md"
@ -391,7 +394,7 @@ const createInvoiceInCorrection = async () => {
color="primary" color="primary"
:to="routes.getTickets(entity.supplierFk)" :to="routes.getTickets(entity.supplierFk)"
> >
<QTooltip>{{ t('invoiceOut.card.ticketList') }}</QTooltip> <QTooltip>{{ t('InvoiceIn.descriptor.ticketList') }}</QTooltip>
</QBtn> </QBtn>
<QBtn <QBtn
v-if=" v-if="
@ -435,9 +438,9 @@ const createInvoiceInCorrection = async () => {
readonly readonly
/> />
<VnSelect <VnSelect
:label="`${useCapitalize(t('globals.class'))}`" :label="`${capitalize(t('globals.class'))}`"
v-model="correctionFormData.invoiceClass" v-model="correctionFormData.invoiceClass"
:options="siiTypeInvoiceOuts" :options="siiTypeInvoiceIns"
option-value="id" option-value="id"
option-label="code" option-label="code"
:required="true" :required="true"
@ -445,15 +448,27 @@ const createInvoiceInCorrection = async () => {
</QItemSection> </QItemSection>
<QItemSection> <QItemSection>
<VnSelect <VnSelect
:label="`${useCapitalize(t('globals.type'))}`" :label="`${capitalize(t('globals.type'))}`"
v-model="correctionFormData.invoiceType" v-model="correctionFormData.invoiceType"
:options="cplusRectificationTypes" :options="cplusRectificationTypes"
option-value="id" option-value="id"
option-label="description" option-label="description"
:required="true" :required="true"
/> >
<template #option="{ opt }">
<QItem>
<QItemSection>
<QItemLabel
>{{ opt.code }} -
{{ opt.description }}</QItemLabel
>
</QItemSection>
</QItem>
<div></div>
</template>
</VnSelect>
<VnSelect <VnSelect
:label="`${useCapitalize(t('globals.reason'))}`" :label="`${capitalize(t('globals.reason'))}`"
v-model="correctionFormData.invoiceReason" v-model="correctionFormData.invoiceReason"
:options="invoiceCorrectionTypes" :options="invoiceCorrectionTypes"
option-value="id" option-value="id"

View File

@ -25,6 +25,7 @@ const banks = ref([]);
const invoiceInFormRef = ref(); 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 columns = computed(() => [ const columns = computed(() => [
{ {
@ -143,8 +144,6 @@ async function insert() {
}" }"
:disable="!isNotEuro(currency)" :disable="!isNotEuro(currency)"
v-model="row.foreignValue" v-model="row.foreignValue"
clearable
clear-icon="close"
/> />
</QTd> </QTd>
</template> </template>
@ -230,7 +229,14 @@ async function insert() {
</template> </template>
</CrudModel> </CrudModel>
<QPageSticky position="bottom-right" :offset="[25, 25]"> <QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn color="primary" icon="add" shortcut="+" size="lg" round @click="insert" /> <QBtn
color="primary"
icon="add"
shortcut="+"
size="lg"
round
@click="!areRows ? insert() : invoiceInFormRef.insert()"
/>
</QPageSticky> </QPageSticky>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -26,7 +26,7 @@ const columns = computed(() => [
options: intrastats.value, options: intrastats.value,
model: 'intrastatFk', model: 'intrastatFk',
optionValue: 'id', optionValue: 'id',
optionLabel: 'description', optionLabel: (row) => `${row.id}: ${row.description}`,
sortable: true, sortable: true,
tabIndex: 1, tabIndex: 1,
align: 'left', align: 'left',
@ -68,12 +68,6 @@ const columns = computed(() => [
align: 'left', align: 'left',
}, },
]); ]);
const formatOpt = (row, { model, options }, prop) => {
const obj = row[model];
const option = options.find(({ id }) => id == obj);
return option ? `${obj}:${option[prop]}` : '';
};
</script> </script>
<template> <template>
<FetchData <FetchData
@ -118,12 +112,10 @@ const formatOpt = (row, { model, options }, prop) => {
<VnSelect <VnSelect
v-model="row[col.model]" v-model="row[col.model]"
:options="col.options" :options="col.options"
option-value="id" :option-value="col.optionValue"
option-label="description" :option-label="col.optionLabel"
:filter-options="['id', 'description']" :filter-options="['id', 'description']"
:hide-selected="false" data-cy="intrastat-code"
:fill-input="false"
:display-value="formatOpt(row, col, 'description')"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -138,8 +130,8 @@ const formatOpt = (row, { model, options }, prop) => {
<VnSelect <VnSelect
v-model="row[col.model]" v-model="row[col.model]"
:options="col.options" :options="col.options"
option-value="id" :option-value="col.optionValue"
option-label="code" :option-label="col.optionLabel"
/> />
</QTd> </QTd>
</template> </template>
@ -154,7 +146,7 @@ const formatOpt = (row, { model, options }, prop) => {
{{ getTotal(rows, 'net') }} {{ getTotal(rows, 'net') }}
</QTd> </QTd>
<QTd> <QTd>
{{ getTotal(rows, 'stems') }} {{ getTotal(rows, 'stems', { decimalPlaces: 0 }) }}
</QTd> </QTd>
<QTd /> <QTd />
</QTr> </QTr>
@ -174,7 +166,9 @@ const formatOpt = (row, { model, options }, prop) => {
v-model="props.row['intrastatFk']" v-model="props.row['intrastatFk']"
:options="intrastats" :options="intrastats"
option-value="id" option-value="id"
option-label="description" :option-label="
(row) => `${row.id}:${row.description}`
"
:filter-options="['id', 'description']" :filter-options="['id', 'description']"
> >
<template #option="scope"> <template #option="scope">
@ -248,11 +242,6 @@ const formatOpt = (row, { model, options }, prop) => {
} }
} }
</style> </style>
<style lang="scss" scoped>
:deep(.q-table tr .q-td:nth-child(2) input) {
display: none;
}
</style>
<i18n> <i18n>
en: en:
amount: Amount amount: Amount
@ -261,7 +250,7 @@ const formatOpt = (row, { model, options }, prop) => {
country: Country country: Country
es: es:
Code: Código Code: Código
amount: Cantidad amount: Valor mercancía
net: Neto net: Neto
stems: Tallos stems: Tallos
country: País country: País

View File

@ -26,14 +26,14 @@ const intrastatTotals = ref({ amount: 0, net: 0, stems: 0 });
const vatColumns = ref([ const vatColumns = ref([
{ {
name: 'expense', name: 'expense',
label: 'invoiceIn.summary.expense', label: 'InvoiceIn.summary.expense',
field: (row) => row.expenseFk, field: (row) => row.expenseFk,
sortable: true, sortable: true,
align: 'left', align: 'left',
}, },
{ {
name: 'landed', name: 'landed',
label: 'invoiceIn.summary.taxableBase', label: 'InvoiceIn.summary.taxableBase',
field: (row) => row.taxableBase, field: (row) => row.taxableBase,
format: (value) => toCurrency(value), format: (value) => toCurrency(value),
sortable: true, sortable: true,
@ -41,7 +41,7 @@ const vatColumns = ref([
}, },
{ {
name: 'vat', name: 'vat',
label: 'invoiceIn.summary.sageVat', label: 'InvoiceIn.summary.sageVat',
field: (row) => { field: (row) => {
if (row.taxTypeSage) return `#${row.taxTypeSage.id} : ${row.taxTypeSage.vat}`; if (row.taxTypeSage) return `#${row.taxTypeSage.id} : ${row.taxTypeSage.vat}`;
}, },
@ -51,7 +51,7 @@ const vatColumns = ref([
}, },
{ {
name: 'transaction', name: 'transaction',
label: 'invoiceIn.summary.sageTransaction', label: 'InvoiceIn.summary.sageTransaction',
field: (row) => { field: (row) => {
if (row.transactionTypeSage) if (row.transactionTypeSage)
return `#${row.transactionTypeSage.id} : ${row.transactionTypeSage?.transaction}`; return `#${row.transactionTypeSage.id} : ${row.transactionTypeSage?.transaction}`;
@ -62,7 +62,7 @@ const vatColumns = ref([
}, },
{ {
name: 'rate', name: 'rate',
label: 'invoiceIn.summary.rate', label: 'InvoiceIn.summary.rate',
field: (row) => taxRate(row.taxableBase, row.taxTypeSage?.rate), field: (row) => taxRate(row.taxableBase, row.taxTypeSage?.rate),
format: (value) => toCurrency(value), format: (value) => toCurrency(value),
sortable: true, sortable: true,
@ -70,7 +70,7 @@ const vatColumns = ref([
}, },
{ {
name: 'currency', name: 'currency',
label: 'invoiceIn.summary.currency', label: 'InvoiceIn.summary.currency',
field: (row) => row.foreignValue, field: (row) => row.foreignValue,
format: (val) => val && toCurrency(val, currency.value), format: (val) => val && toCurrency(val, currency.value),
sortable: true, sortable: true,
@ -81,21 +81,21 @@ const vatColumns = ref([
const dueDayColumns = ref([ const dueDayColumns = ref([
{ {
name: 'date', name: 'date',
label: 'invoiceIn.summary.dueDay', label: 'InvoiceIn.summary.dueDay',
field: (row) => toDate(row.dueDated), field: (row) => toDate(row.dueDated),
sortable: true, sortable: true,
align: 'left', align: 'left',
}, },
{ {
name: 'bank', name: 'bank',
label: 'invoiceIn.summary.bank', label: 'InvoiceIn.summary.bank',
field: (row) => row.bank.bank, field: (row) => row.bank.bank,
sortable: true, sortable: true,
align: 'left', align: 'left',
}, },
{ {
name: 'amount', name: 'amount',
label: 'invoiceIn.list.amount', label: 'InvoiceIn.list.amount',
field: (row) => row.amount, field: (row) => row.amount,
format: (value) => toCurrency(value), format: (value) => toCurrency(value),
sortable: true, sortable: true,
@ -103,7 +103,7 @@ const dueDayColumns = ref([
}, },
{ {
name: 'landed', name: 'landed',
label: 'invoiceIn.summary.foreignValue', label: 'InvoiceIn.summary.foreignValue',
field: (row) => row.foreignValue, field: (row) => row.foreignValue,
format: (val) => val && toCurrency(val, currency.value), format: (val) => val && toCurrency(val, currency.value),
sortable: true, sortable: true,
@ -114,7 +114,7 @@ const dueDayColumns = ref([
const intrastatColumns = ref([ const intrastatColumns = ref([
{ {
name: 'code', name: 'code',
label: 'invoiceIn.summary.code', label: 'InvoiceIn.summary.code',
field: (row) => { field: (row) => {
return `${row.intrastat.id}: ${row.intrastat?.description}`; return `${row.intrastat.id}: ${row.intrastat?.description}`;
}, },
@ -123,21 +123,21 @@ const intrastatColumns = ref([
}, },
{ {
name: 'amount', name: 'amount',
label: 'invoiceIn.list.amount', label: 'InvoiceIn.list.amount',
field: (row) => toCurrency(row.amount), field: (row) => toCurrency(row.amount),
sortable: true, sortable: true,
align: 'left', align: 'left',
}, },
{ {
name: 'net', name: 'net',
label: 'invoiceIn.summary.net', label: 'InvoiceIn.summary.net',
field: (row) => row.net, field: (row) => row.net,
sortable: true, sortable: true,
align: 'left', align: 'left',
}, },
{ {
name: 'stems', name: 'stems',
label: 'invoiceIn.summary.stems', label: 'InvoiceIn.summary.stems',
field: (row) => row.stems, field: (row) => row.stems,
format: (value) => value, format: (value) => value,
sortable: true, sortable: true,
@ -145,7 +145,7 @@ const intrastatColumns = ref([
}, },
{ {
name: 'landed', name: 'landed',
label: 'invoiceIn.summary.country', label: 'InvoiceIn.summary.country',
field: (row) => row.country?.code, field: (row) => row.country?.code,
format: (value) => value, format: (value) => value,
sortable: true, sortable: true,
@ -210,7 +210,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
/> />
</QCardSection> </QCardSection>
<VnLv <VnLv
:label="t('invoiceIn.list.supplier')" :label="t('InvoiceIn.list.supplier')"
:value="entity.supplier?.name" :value="entity.supplier?.name"
> >
<template #value> <template #value>
@ -221,14 +221,18 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</template> </template>
</VnLv> </VnLv>
<VnLv <VnLv
:label="t('invoiceIn.list.supplierRef')" :label="t('InvoiceIn.list.supplierRef')"
:value="entity.supplierRef" :value="entity.supplierRef"
/> />
<VnLv <VnLv
:label="t('invoiceIn.summary.currency')" :label="t('InvoiceIn.summary.currency')"
:value="entity.currency?.code" :value="entity.currency?.code"
/> />
<VnLv :label="t('invoiceIn.serial')" :value="`${entity.serial}`" /> <VnLv :label="t('InvoiceIn.serial')" :value="`${entity.serial}`" />
<VnLv
:label="t('globals.country')"
:value="entity.supplier?.country?.code"
/>
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<QCardSection class="q-pa-none"> <QCardSection class="q-pa-none">
@ -239,21 +243,22 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</QCardSection> </QCardSection>
<VnLv <VnLv
:ellipsis-value="false" :ellipsis-value="false"
:label="t('invoiceIn.summary.issued')" :label="t('InvoiceIn.summary.issued')"
:value="toDate(entity.issued)" :value="toDate(entity.issued)"
/> />
<VnLv <VnLv
:label="t('invoiceIn.summary.operated')" :label="t('InvoiceIn.summary.operated')"
:value="toDate(entity.operated)" :value="toDate(entity.operated)"
/> />
<VnLv <VnLv
:label="t('invoiceIn.summary.bookEntried')" :label="t('InvoiceIn.summary.bookEntried')"
:value="toDate(entity.bookEntried)" :value="toDate(entity.bookEntried)"
/> />
<VnLv <VnLv
:label="t('invoiceIn.summary.bookedDate')" :label="t('InvoiceIn.summary.bookedDate')"
:value="toDate(entity.booked)" :value="toDate(entity.booked)"
/> />
<VnLv :label="t('globals.isVies')" :value="entity.supplier?.isVies" />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<QCardSection class="q-pa-none"> <QCardSection class="q-pa-none">
@ -263,18 +268,18 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
/> />
</QCardSection> </QCardSection>
<VnLv <VnLv
:label="t('invoiceIn.summary.sage')" :label="t('InvoiceIn.summary.sage')"
:value="entity.sageWithholding?.withholding" :value="entity.sageWithholding?.withholding"
/> />
<VnLv <VnLv
:label="t('invoiceIn.summary.vat')" :label="t('InvoiceIn.summary.vat')"
:value="entity.expenseDeductible?.name" :value="entity.expenseDeductible?.name"
/> />
<VnLv <VnLv
:label="t('invoiceIn.card.company')" :label="t('InvoiceIn.card.company')"
:value="entity.company?.code" :value="entity.company?.code"
/> />
<VnLv :label="t('invoiceIn.isBooked')" :value="invoiceIn?.isBooked" /> <VnLv :label="t('InvoiceIn.isBooked')" :value="invoiceIn?.isBooked" />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<QCardSection class="q-pa-none"> <QCardSection class="q-pa-none">
@ -285,11 +290,11 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</QCardSection> </QCardSection>
<QCardSection class="q-pa-none"> <QCardSection class="q-pa-none">
<VnLv <VnLv
:label="t('invoiceIn.summary.taxableBase')" :label="t('InvoiceIn.summary.taxableBase')"
:value="toCurrency(entity.totals.totalTaxableBase)" :value="toCurrency(entity.totals.totalTaxableBase)"
/> />
<VnLv label="Total" :value="toCurrency(entity.totals.totalVat)" /> <VnLv label="Total" :value="toCurrency(entity.totals.totalVat)" />
<VnLv :label="t('invoiceIn.summary.dueTotal')"> <VnLv :label="t('InvoiceIn.summary.dueTotal')">
<template #value> <template #value>
<QChip <QChip
dense dense
@ -297,8 +302,8 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
:color="amountsNotMatch ? 'negative' : 'transparent'" :color="amountsNotMatch ? 'negative' : 'transparent'"
:title=" :title="
amountsNotMatch amountsNotMatch
? t('invoiceIn.summary.noMatch') ? t('InvoiceIn.summary.noMatch')
: t('invoiceIn.summary.dueTotal') : t('InvoiceIn.summary.dueTotal')
" "
> >
{{ toCurrency(entity.totals.totalDueDay) }} {{ toCurrency(entity.totals.totalDueDay) }}
@ -309,7 +314,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</QCard> </QCard>
<!--Vat--> <!--Vat-->
<QCard v-if="entity.invoiceInTax.length" class="vat"> <QCard v-if="entity.invoiceInTax.length" class="vat">
<VnTitle :url="getLink('vat')" :text="t('invoiceIn.card.vat')" /> <VnTitle :url="getLink('vat')" :text="t('InvoiceIn.card.vat')" />
<QTable <QTable
:columns="vatColumns" :columns="vatColumns"
:rows="entity.invoiceInTax" :rows="entity.invoiceInTax"
@ -357,7 +362,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</QCard> </QCard>
<!--Due Day--> <!--Due Day-->
<QCard v-if="entity.invoiceInDueDay.length" class="due-day"> <QCard v-if="entity.invoiceInDueDay.length" class="due-day">
<VnTitle :url="getLink('due-day')" :text="t('invoiceIn.card.dueDay')" /> <VnTitle :url="getLink('due-day')" :text="t('InvoiceIn.card.dueDay')" />
<QTable :columns="dueDayColumns" :rows="entity.invoiceInDueDay" flat> <QTable :columns="dueDayColumns" :rows="entity.invoiceInDueDay" flat>
<template #header="dueDayProps"> <template #header="dueDayProps">
<QTr :props="dueDayProps" class="bg"> <QTr :props="dueDayProps" class="bg">
@ -395,7 +400,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
<QCard v-if="entity.invoiceInIntrastat.length"> <QCard v-if="entity.invoiceInIntrastat.length">
<VnTitle <VnTitle
:url="getLink('intrastat')" :url="getLink('intrastat')"
:text="t('invoiceIn.card.intrastat')" :text="t('InvoiceIn.card.intrastat')"
/> />
<QTable <QTable
:columns="intrastatColumns" :columns="intrastatColumns"

View File

@ -11,12 +11,14 @@ import CrudModel from 'src/components/CrudModel.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CreateNewExpenseForm from 'src/components/CreateNewExpenseForm.vue'; import CreateNewExpenseForm from 'src/components/CreateNewExpenseForm.vue';
import { getExchange } from 'src/composables/getExchange';
import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard';
const { t } = useI18n(); const { t } = useI18n();
const arrayData = useArrayData(); const arrayData = useArrayData();
const route = useRoute();
const invoiceIn = computed(() => arrayData.store.data); const invoiceIn = computed(() => arrayData.store.data);
const invoiceId = +useRoute().params.id;
const currency = computed(() => invoiceIn.value?.currency?.code); const currency = computed(() => invoiceIn.value?.currency?.code);
const expenses = ref([]); const expenses = ref([]);
const sageTaxTypes = ref([]); const sageTaxTypes = ref([]);
@ -39,9 +41,8 @@ const columns = computed(() => [
options: expenses.value, options: expenses.value,
model: 'expenseFk', model: 'expenseFk',
optionValue: 'id', optionValue: 'id',
optionLabel: 'id', optionLabel: (row) => `${row.id}: ${row.name}`,
sortable: true, sortable: true,
tabIndex: 1,
align: 'left', align: 'left',
}, },
{ {
@ -50,7 +51,6 @@ const columns = computed(() => [
field: (row) => row.taxableBase, field: (row) => row.taxableBase,
model: 'taxableBase', model: 'taxableBase',
sortable: true, sortable: true,
tabIndex: 2,
align: 'left', align: 'left',
}, },
{ {
@ -60,9 +60,8 @@ const columns = computed(() => [
options: sageTaxTypes.value, options: sageTaxTypes.value,
model: 'taxTypeSageFk', model: 'taxTypeSageFk',
optionValue: 'id', optionValue: 'id',
optionLabel: 'id', optionLabel: (row) => `${row.id}: ${row.vat}`,
sortable: true, sortable: true,
tabindex: 3,
align: 'left', align: 'left',
}, },
{ {
@ -72,16 +71,14 @@ const columns = computed(() => [
options: sageTransactionTypes.value, options: sageTransactionTypes.value,
model: 'transactionTypeSageFk', model: 'transactionTypeSageFk',
optionValue: 'id', optionValue: 'id',
optionLabel: 'id', optionLabel: (row) => `${row.id}: ${row.transaction}`,
sortable: true, sortable: true,
tabIndex: 4,
align: 'left', align: 'left',
}, },
{ {
name: 'rate', name: 'rate',
label: t('Rate'), label: t('Rate'),
sortable: true, sortable: true,
tabIndex: 5,
field: (row) => taxRate(row, row.taxTypeSageFk), field: (row) => taxRate(row, row.taxTypeSageFk),
align: 'left', align: 'left',
}, },
@ -89,7 +86,6 @@ const columns = computed(() => [
name: 'foreignvalue', name: 'foreignvalue',
label: t('Foreign value'), label: t('Foreign value'),
sortable: true, sortable: true,
tabIndex: 6,
field: (row) => row.foreignValue, field: (row) => row.foreignValue,
align: 'left', align: 'left',
}, },
@ -106,7 +102,7 @@ const filter = {
'transactionTypeSageFk', 'transactionTypeSageFk',
], ],
where: { where: {
invoiceInFk: invoiceId, invoiceInFk: route.params.id,
}, },
}; };
@ -120,14 +116,20 @@ function taxRate(invoiceInTax) {
const taxTypeSage = taxRateSelection?.rate ?? 0; const taxTypeSage = taxRateSelection?.rate ?? 0;
const taxableBase = invoiceInTax?.taxableBase ?? 0; const taxableBase = invoiceInTax?.taxableBase ?? 0;
return (taxTypeSage / 100) * taxableBase; return ((taxTypeSage / 100) * taxableBase).toFixed(2);
} }
const formatOpt = (row, { model, options }, prop) => { function autocompleteExpense(evt, row, col) {
const obj = row[model]; const val = evt.target.value;
const option = options.find(({ id }) => id == obj); if (!val) return;
return option ? `${obj}:${option[prop]}` : '';
}; const param = isNaN(val) ? row[col.model] : val;
const lookup = expenses.value.find(
({ id }) => id == useAccountShortToStandard(param)
);
if (lookup) row[col.model] = lookup;
}
</script> </script>
<template> <template>
<FetchData <FetchData
@ -148,10 +150,10 @@ const formatOpt = (row, { model, options }, prop) => {
data-key="InvoiceInTaxes" data-key="InvoiceInTaxes"
url="InvoiceInTaxes" url="InvoiceInTaxes"
:filter="filter" :filter="filter"
:data-required="{ invoiceInFk: invoiceId }" :data-required="{ invoiceInFk: $route.params.id }"
auto-load auto-load
v-model:selected="rowsSelected" v-model:selected="rowsSelected"
:go-to="`/invoice-in/${invoiceId}/due-day`" :go-to="`/invoice-in/${$route.params.id}/due-day`"
> >
<template #body="{ rows }"> <template #body="{ rows }">
<QTable <QTable
@ -171,6 +173,7 @@ const formatOpt = (row, { model, options }, prop) => {
: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="autocompleteExpense($event, row, col)"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -187,13 +190,7 @@ const formatOpt = (row, { model, options }, prop) => {
</template> </template>
<template #body-cell-taxablebase="{ row }"> <template #body-cell-taxablebase="{ row }">
<QTd> <QTd>
{{ currency }}
<VnInputNumber <VnInputNumber
:class="{
'no-pointer-events': isNotEuro(currency),
}"
:disable="isNotEuro(currency)"
label=""
clear-icon="close" clear-icon="close"
v-model="row.taxableBase" v-model="row.taxableBase"
clearable clearable
@ -208,9 +205,7 @@ const formatOpt = (row, { model, options }, prop) => {
:option-value="col.optionValue" :option-value="col.optionValue"
:option-label="col.optionLabel" :option-label="col.optionLabel"
:filter-options="['id', 'vat']" :filter-options="['id', 'vat']"
:hide-selected="false" data-cy="vat-sageiva"
:fill-input="false"
:display-value="formatOpt(row, col, 'vat')"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -233,11 +228,6 @@ const formatOpt = (row, { model, options }, prop) => {
:option-value="col.optionValue" :option-value="col.optionValue"
:option-label="col.optionLabel" :option-label="col.optionLabel"
:filter-options="['id', 'transaction']" :filter-options="['id', 'transaction']"
:autofocus="col.tabIndex == 1"
input-debounce="0"
:hide-selected="false"
:fill-input="false"
:display-value="formatOpt(row, col, 'transaction')"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -262,6 +252,16 @@ const formatOpt = (row, { model, options }, prop) => {
}" }"
:disable="!isNotEuro(currency)" :disable="!isNotEuro(currency)"
v-model="row.foreignValue" v-model="row.foreignValue"
@update:model-value="
async (val) => {
if (!isNotEuro(currency)) return;
row.taxableBase = await getExchange(
val,
row.currencyFk,
invoiceIn.issued
);
}
"
/> />
</QTd> </QTd>
</template> </template>
@ -305,7 +305,7 @@ const formatOpt = (row, { model, options }, prop) => {
v-model="props.row['expenseFk']" v-model="props.row['expenseFk']"
:options="expenses" :options="expenses"
option-value="id" option-value="id"
option-label="name" :option-label="(row) => `${row.id}:${row.name}`"
:filter-options="['id', 'name']" :filter-options="['id', 'name']"
:tooltip="t('Create a new expense')" :tooltip="t('Create a new expense')"
> >
@ -339,7 +339,7 @@ const formatOpt = (row, { model, options }, prop) => {
v-model="props.row['taxTypeSageFk']" v-model="props.row['taxTypeSageFk']"
:options="sageTaxTypes" :options="sageTaxTypes"
option-value="id" option-value="id"
option-label="vat" :option-label="(row) => `${row.id}:${row.vat}`"
:filter-options="['id', 'vat']" :filter-options="['id', 'vat']"
> >
<template #option="scope"> <template #option="scope">
@ -362,7 +362,9 @@ const formatOpt = (row, { model, options }, prop) => {
v-model="props.row['transactionTypeSageFk']" v-model="props.row['transactionTypeSageFk']"
:options="sageTransactionTypes" :options="sageTransactionTypes"
option-value="id" option-value="id"
option-label="transaction" :option-label="
(row) => `${row.id}:${row.transaction}`
"
:filter-options="['id', 'transaction']" :filter-options="['id', 'transaction']"
> >
<template #option="scope"> <template #option="scope">
@ -418,11 +420,6 @@ const formatOpt = (row, { model, options }, prop) => {
.bg { .bg {
background-color: var(--vn-light-gray); background-color: var(--vn-light-gray);
} }
:deep(.q-table tr td:nth-child(n + 4):nth-child(-n + 5) input) {
display: none;
}
@media (max-width: $breakpoint-xs) { @media (max-width: $breakpoint-xs) {
.q-dialog { .q-dialog {
.q-card { .q-card {

View File

@ -83,7 +83,7 @@ const redirectToInvoiceInBasicData = (__, { id }) => {
</template> </template>
</VnSelect> </VnSelect>
<VnInput <VnInput
:label="t('invoiceIn.list.supplierRef')" :label="t('InvoiceIn.list.supplierRef')"
v-model="data.supplierRef" v-model="data.supplierRef"
/> />
</VnRow> </VnRow>
@ -97,10 +97,10 @@ const redirectToInvoiceInBasicData = (__, { id }) => {
map-options map-options
hide-selected hide-selected
:required="true" :required="true"
:rules="validate('invoiceIn.companyFk')" :rules="validate('InvoiceIn.companyFk')"
/> />
<VnInputDate <VnInputDate
:label="t('invoiceIn.summary.issued')" :label="t('InvoiceIn.summary.issued')"
v-model="data.issued" v-model="data.issued"
/> />
</VnRow> </VnRow>

View File

@ -1,41 +1,66 @@
<script setup> <script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import FetchData from 'components/FetchData.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import { dateRange } from 'src/filters';
import { date } from 'quasar';
const { t } = useI18n();
defineProps({ dataKey: { type: String, required: true } }); defineProps({ dataKey: { type: String, required: true } });
const activities = ref([]); const dateFormat = 'YYYY-MM-DDTHH:mm:ss.SSSZ';
function handleDaysAgo(params, daysAgo) {
const [from, to] = dateRange(Date.vnNew());
if (!daysAgo && daysAgo !== 0) {
Object.assign(params, { from: undefined, to: undefined });
} else {
from.setDate(from.getDate() - daysAgo);
Object.assign(params, {
from: date.formatDate(from, dateFormat),
to: date.formatDate(to, dateFormat),
});
}
}
</script> </script>
<template> <template>
<FetchData <VnFilterPanel :data-key="dataKey" :search-button="true" :hidden-tags="['daysAgo']">
url="SupplierActivities" <template #tags="{ tag, formatFn, getLocale }">
auto-load
@on-fetch="(data) => (activities = data)"
/>
<VnFilterPanel :data-key="dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs"> <div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong> <strong>{{ getLocale(tag.label) }}: </strong>
<span>{{ formatFn(tag.value) }}</span> <span>{{ formatFn(tag.value) }}</span>
</div> </div>
</template> </template>
<template #body="{ params, searchFn }"> <template #body="{ params, searchFn, getLocale }">
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInputDate :label="t('From')" v-model="params.from" is-outlined /> <VnInputDate
:label="$t('globals.from')"
v-model="params.from"
is-outlined
/>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInputDate :label="t('To')" v-model="params.to" is-outlined /> <VnInputDate
:label="$t('globals.to')"
v-model="params.to"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputNumber
:label="$t('globals.daysAgo')"
v-model="params.daysAgo"
is-outlined
:step="0"
@update:model-value="(val) => handleDaysAgo(params, val)"
@remove="(val) => handleDaysAgo(params, val)"
/>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
@ -44,20 +69,18 @@ const activities = ref([]);
v-model="params.supplierFk" v-model="params.supplierFk"
url="Suppliers" url="Suppliers"
:fields="['id', 'nickname']" :fields="['id', 'nickname']"
:label="t('params.supplierFk')" :label="getLocale('supplierFk')"
option-value="id"
option-label="nickname" option-label="nickname"
dense dense
outlined outlined
rounded rounded
:filter-options="['id', 'name']"
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput <VnInput
:label="t('params.supplierRef')" :label="getLocale('supplierRef')"
v-model="params.supplierRef" v-model="params.supplierRef"
is-outlined is-outlined
lazy-rules lazy-rules
@ -67,7 +90,7 @@ const activities = ref([]);
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput <VnInput
:label="t('params.fi')" :label="getLocale('fi')"
v-model="params.fi" v-model="params.fi"
is-outlined is-outlined
lazy-rules lazy-rules
@ -77,7 +100,7 @@ const activities = ref([]);
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput <VnInput
:label="t('params.serial')" :label="getLocale('serial')"
v-model="params.serial" v-model="params.serial"
is-outlined is-outlined
lazy-rules lazy-rules
@ -87,7 +110,7 @@ const activities = ref([]);
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput <VnInput
:label="t('params.account')" :label="getLocale('account')"
v-model="params.account" v-model="params.account"
is-outlined is-outlined
lazy-rules lazy-rules
@ -97,7 +120,7 @@ const activities = ref([]);
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput <VnInput
:label="t('params.awb')" :label="getLocale('globals.params.awbCode')"
v-model="params.awbCode" v-model="params.awbCode"
is-outlined is-outlined
lazy-rules lazy-rules
@ -107,24 +130,34 @@ const activities = ref([]);
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInputNumber <VnInputNumber
:label="t('Amount')" :label="$t('globals.amount')"
v-model="params.amount" v-model="params.amount"
is-outlined is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem>
<QItemSection>
<VnSelect
v-model="params.companyFk"
:label="$t('globals.company')"
url="Companies"
option-label="code"
:fields="['id', 'code']"
is-outlined
/>
</QItemSection>
</QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QCheckbox <QCheckbox
:label="t('invoiceIn.isBooked')" :label="$t('InvoiceIn.isBooked')"
v-model="params.isBooked" v-model="params.isBooked"
@update:model-value="searchFn()" @update:model-value="searchFn()"
toggle-indeterminate toggle-indeterminate
/> />
</QItemSection>
<QItemSection>
<QCheckbox <QCheckbox
:label="t('params.correctingFk')" :label="getLocale('params.correctingFk')"
v-model="params.correctingFk" v-model="params.correctingFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"
toggle-indeterminate toggle-indeterminate
@ -134,54 +167,3 @@ const activities = ref([]);
</template> </template>
</VnFilterPanel> </VnFilterPanel>
</template> </template>
<i18n>
en:
params:
search: Id or supplier name
supplierRef: Supplier ref.
supplierFk: Supplier
fi: Supplier fiscal id
clientFk: Customer
amount: Amount
created: Created
awb: AWB
dued: Dued
serialNumber: Serial Number
serial: Serial
account: Ledger account
isBooked: is booked
correctedFk: Rectified
issued: Issued
to: To
from: From
awbCode: AWB
correctingFk: Rectificative
supplierActivityFk: Supplier activity
es:
params:
search: Id o nombre proveedor
supplierRef: Ref. proveedor
supplierFk: Proveedor
clientFk: Cliente
fi: CIF proveedor
serialNumber: Num. serie
serial: Serie
awb: AWB
amount: Importe
issued: Emitida
isBooked: Contabilizada
account: Cuenta contable
created: Creada
dued: Vencida
correctedFk: Rectificada
correctingFk: Rectificativa
supplierActivityFk: Actividad proveedor
from: Desde
to: Hasta
From: Desde
To: Hasta
Amount: Importe
Issued: Fecha factura
Id or supplier: Id o proveedor
</i18n>

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'; import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore'; import { useState } from 'src/composables/useState';
import { downloadFile } from 'src/composables/downloadFile'; import { downloadFile } from 'src/composables/downloadFile';
import { toDate, toCurrency } from 'src/filters/index'; import { toDate, toCurrency } from 'src/filters/index';
import InvoiceInFilter from './InvoiceInFilter.vue'; import InvoiceInFilter from './InvoiceInFilter.vue';
@ -14,25 +14,34 @@ import VnTable from 'src/components/VnTable/VnTable.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue';
import FetchData from 'src/components/FetchData.vue';
const stateStore = useStateStore(); const user = useState().getUser();
const { viewSummary } = useSummaryDialog(); const { viewSummary } = useSummaryDialog();
const { t } = useI18n(); const { t } = useI18n();
onMounted(async () => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false));
const tableRef = ref(); const tableRef = ref();
const companies = ref([]);
const cols = computed(() => [ const cols = computed(() => [
{
align: 'left',
name: 'isBooked',
label: t('InvoiceIn.isBooked'),
columnFilter: false,
},
{ {
align: 'left', align: 'left',
name: 'id', name: 'id',
label: 'Id', label: 'Id',
isId: true,
chip: {
condition: () => true,
},
}, },
{ {
align: 'left', align: 'left',
name: 'supplierFk', name: 'supplierFk',
label: t('invoiceIn.list.supplier'), label: t('InvoiceIn.list.supplier'),
columnFilter: { columnFilter: {
component: 'select', component: 'select',
attrs: { attrs: {
@ -41,20 +50,21 @@ const cols = computed(() => [
}, },
}, },
columnClass: 'expand', columnClass: 'expand',
cardVisible: true,
}, },
{ {
align: 'left', align: 'left',
name: 'supplierRef', name: 'supplierRef',
label: t('invoiceIn.list.supplierRef'), label: t('InvoiceIn.list.supplierRef'),
}, },
{ {
align: 'left', align: 'left',
name: 'serial', name: 'serial',
label: t('invoiceIn.serial'), label: t('InvoiceIn.serial'),
}, },
{ {
align: 'left', align: 'left',
label: t('invoiceIn.list.issued'), label: t('InvoiceIn.list.issued'),
name: 'issued', name: 'issued',
component: null, component: null,
columnFilter: { columnFilter: {
@ -64,20 +74,52 @@ const cols = computed(() => [
}, },
{ {
align: 'left', align: 'left',
name: 'isBooked', label: t('InvoiceIn.list.dueDated'),
label: t('invoiceIn.isBooked'), name: 'dueDated',
columnFilter: false, component: null,
columnFilter: {
component: 'date',
},
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.dueDated)),
cardVisible: true,
}, },
{ {
align: 'left', align: 'left',
name: 'awbCode', name: 'awbCode',
label: t('invoiceIn.list.awb'), label: t('InvoiceIn.list.awb'),
}, },
{ {
align: 'left', align: 'left',
name: 'amount', name: 'amount',
label: t('invoiceIn.list.amount'), label: t('InvoiceIn.list.amount'),
format: ({ amount }) => toCurrency(amount), format: ({ amount }) => toCurrency(amount),
cardVisible: true,
},
{
name: 'companyFk',
label: t('globals.company'),
columnFilter: {
component: 'select',
attrs: {
url: 'Companies',
fields: ['id', 'code'],
optionLabel: 'code',
},
},
format: (row) => row.code,
},
{
name: 'companyFk',
label: t('globals.company'),
columnFilter: {
component: 'select',
attrs: {
url: 'Companies',
fields: ['id', 'code'],
optionLabel: 'code',
},
},
format: (row) => row.code,
}, },
{ {
align: 'right', align: 'right',
@ -87,6 +129,7 @@ const cols = computed(() => [
title: t('components.smartCard.openSummary'), title: t('components.smartCard.openSummary'),
icon: 'preview', icon: 'preview',
type: 'submit', type: 'submit',
isPrimary: true,
action: (row) => viewSummary(row.id, InvoiceInSummary), action: (row) => viewSummary(row.id, InvoiceInSummary),
}, },
{ {
@ -101,6 +144,7 @@ const cols = computed(() => [
]); ]);
</script> </script>
<template> <template>
<FetchData url="Companies" @on-fetch="(data) => (companies = data)" auto-load />
<InvoiceInSearchbar /> <InvoiceInSearchbar />
<RightMenu> <RightMenu>
<template #right-panel> <template #right-panel>
@ -116,7 +160,7 @@ const cols = computed(() => [
urlCreate: 'InvoiceIns', urlCreate: 'InvoiceIns',
title: t('globals.createInvoiceIn'), title: t('globals.createInvoiceIn'),
onDataSaved: ({ id }) => tableRef.redirect(id), onDataSaved: ({ id }) => tableRef.redirect(id),
formInitialData: {}, formInitialData: { companyFk: user.companyFk, issued: Date.vnNew() },
}" }"
redirect="invoice-in" redirect="invoice-in"
:columns="cols" :columns="cols"
@ -151,7 +195,7 @@ const cols = computed(() => [
</template> </template>
</VnSelect> </VnSelect>
<VnInput <VnInput
:label="t('invoiceIn.list.supplierRef')" :label="t('InvoiceIn.list.supplierRef')"
v-model="data.supplierRef" v-model="data.supplierRef"
/> />
<VnSelect <VnSelect
@ -163,7 +207,7 @@ const cols = computed(() => [
option-label="code" option-label="code"
:required="true" :required="true"
/> />
<VnInputDate :label="t('invoiceIn.summary.issued')" v-model="data.issued" /> <VnInputDate :label="t('InvoiceIn.summary.issued')" v-model="data.issued" />
</template> </template>
</VnTable> </VnTable>
</template> </template>

View File

@ -58,6 +58,14 @@ onBeforeMount(async () => {
:right-search="false" :right-search="false"
:user-params="{ daysAgo }" :user-params="{ daysAgo }"
:disable-option="{ card: true }" :disable-option="{ card: true }"
:row-click="
(row) => {
$router.push({
name: 'InvoiceInList',
query: { table: JSON.stringify({ serial: row.serial }) },
});
}
"
auto-load auto-load
/> />
</template> </template>

View File

@ -8,7 +8,11 @@ defineProps({ dataKey: { type: String, required: true } });
const { t } = useI18n(); const { t } = useI18n();
</script> </script>
<template> <template>
<VnFilterPanel :data-key="dataKey" :search-button="true"> <VnFilterPanel
:data-key="dataKey"
:search-button="true"
:unremovable-params="['daysAgo']"
>
<template #tags="{ tag, formatFn }"> <template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs"> <div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong> <strong>{{ t(`params.${tag.label}`) }}: </strong>

View File

@ -1,4 +1,4 @@
invoiceIn: InvoiceIn:
serial: Serial serial: Serial
isBooked: Is booked isBooked: Is booked
list: list:
@ -7,8 +7,11 @@ invoiceIn:
supplierRef: Supplier ref. supplierRef: Supplier ref.
file: File file: File
issued: Issued issued: Issued
dueDated: Due dated
awb: AWB awb: AWB
amount: Amount amount: Amount
descriptor:
ticketList: Ticket list
card: card:
client: Client client: Client
company: Company company: Company
@ -39,3 +42,9 @@ invoiceIn:
net: Net net: Net
stems: Stems stems: Stems
country: Country country: Country
params:
search: Id or supplier name
account: Ledger account
correctingFk: Rectificative
correctedFk: Corrected
isBooked: Is booked

View File

@ -1,15 +1,17 @@
invoiceIn: InvoiceIn:
serial: Serie serial: Serie
isBooked: Contabilizada isBooked: Contabilizada
list: list:
ref: Referencia ref: Referencia
supplier: Proveedor supplier: Proveedor
supplierRef: Ref. proveedor supplierRef: Ref. proveedor
shortIssued: F. emisión issued: F. emisión
dueDated: F. vencimiento
file: Fichero file: Fichero
issued: Fecha emisión
awb: AWB awb: AWB
amount: Importe amount: Importe
descriptor:
ticketList: Listado de tickets
card: card:
client: Cliente client: Cliente
company: Empresa company: Empresa
@ -38,3 +40,8 @@ invoiceIn:
net: Neto net: Neto
stems: Tallos stems: Tallos
country: País country: País
params:
search: Id o nombre proveedor
account: Cuenta contable
correctingFk: Rectificativa
correctedFk: Rectificada

View File

@ -115,6 +115,9 @@ onMounted(async () => {
<VnSelect <VnSelect
:label="t('invoiceOutSerialType')" :label="t('invoiceOutSerialType')"
v-model="formData.serialType" v-model="formData.serialType"
@update:model-value="
invoiceOutGlobalStore.fetchInvoiceOutConfig(formData)
"
:options="serialTypesOptions" :options="serialTypesOptions"
option-value="type" option-value="type"
option-label="type" option-label="type"

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { onMounted, onUnmounted, ref, computed, watchEffect } from 'vue'; import { ref, computed, watchEffect } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue';
@ -10,7 +10,6 @@ import { usePrintService } from 'src/composables/usePrintService';
import VnTable from 'src/components/VnTable/VnTable.vue'; import VnTable from 'src/components/VnTable/VnTable.vue';
import InvoiceOutSummary from './Card/InvoiceOutSummary.vue'; import InvoiceOutSummary from './Card/InvoiceOutSummary.vue';
import { toCurrency, toDate } from 'src/filters/index'; import { toCurrency, toDate } from 'src/filters/index';
import { useStateStore } from 'stores/useStateStore';
import { QBtn } from 'quasar'; import { QBtn } from 'quasar';
import axios from 'axios'; import axios from 'axios';
import RightMenu from 'src/components/common/RightMenu.vue'; import RightMenu from 'src/components/common/RightMenu.vue';
@ -21,7 +20,6 @@ import VnInput from 'src/components/common/VnInput.vue';
import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue';
const { t } = useI18n(); const { t } = useI18n();
const stateStore = useStateStore();
const { viewSummary } = useSummaryDialog(); const { viewSummary } = useSummaryDialog();
const tableRef = ref(); const tableRef = ref();
const invoiceOutSerialsOptions = ref([]); const invoiceOutSerialsOptions = ref([]);
@ -147,8 +145,6 @@ const columns = computed(() => [
], ],
}, },
]); ]);
onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false));
function openPdf(id) { function openPdf(id) {
openReport(`${MODEL}/${id}/download`); openReport(`${MODEL}/${id}/download`);
@ -214,7 +210,6 @@ watchEffect(selectedRows);
order="id DESC" order="id DESC"
:columns="columns" :columns="columns"
redirect="invoice-out" redirect="invoice-out"
auto-load
:table="{ :table="{
'row-key': 'id', 'row-key': 'id',
selection: 'multiple', selection: 'multiple',

View File

@ -16,7 +16,7 @@ import { cloneItem } from 'src/pages/Item/composables/cloneItem';
const $props = defineProps({ const $props = defineProps({
id: { id: {
type: Number, type: [Number, String],
required: false, required: false,
default: null, default: null,
}, },
@ -29,7 +29,7 @@ const $props = defineProps({
default: null, default: null,
}, },
saleFk: { saleFk: {
type: Number, type: [Number, String],
default: null, default: null,
}, },
warehouseFk: { warehouseFk: {
@ -61,7 +61,7 @@ onMounted(async () => {
const data = ref(useCardDescription()); const data = ref(useCardDescription());
const setData = async (entity) => { const setData = async (entity) => {
if (!entity) return; if (!entity) return;
data.value = useCardDescription(entity.name, entity.id); data.value = useCardDescription(entity?.name, entity?.id);
await updateStock(); await updateStock();
}; };

View File

@ -16,7 +16,7 @@ const $props = defineProps({
default: null, default: null,
}, },
entityId: { entityId: {
type: String, type: [String, Number],
default: null, default: null,
}, },
showEditButton: { showEditButton: {

View File

@ -5,14 +5,26 @@ import { useRoute } from 'vue-router';
import { dateRange } from 'src/filters'; import { dateRange } from 'src/filters';
import EntryDescriptorProxy from 'src/pages/Entry/Card/EntryDescriptorProxy.vue'; import EntryDescriptorProxy from 'src/pages/Entry/Card/EntryDescriptorProxy.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue';
import { toDateTimeFormat } from 'src/filters/date.js'; import VnDateBadge from 'src/components/common/VnDateBadge.vue';
import { dashIfEmpty } from 'src/filters'; import { dashIfEmpty } from 'src/filters';
import { toCurrency } from 'filters/index'; import { toCurrency } from 'filters/index';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import axios from 'axios';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const from = ref();
const to = ref();
const hideInventory = ref(true);
const inventorySupplierFk = ref();
async function getInventorySupplier() {
inventorySupplierFk.value = (
await axios.get(`InventoryConfigs`)
)?.data[0]?.supplierFk;
}
const exprBuilder = (param, value) => { const exprBuilder = (param, value) => {
switch (param) { switch (param) {
@ -33,25 +45,27 @@ const exprBuilder = (param, value) => {
} }
}; };
const from = ref(); const where = {
const to = ref(); itemFk: route.params.id,
};
if (hideInventory.value) {
where.supplierFk = { neq: inventorySupplierFk };
}
const arrayData = useArrayData('ItemLastEntries', { const arrayData = useArrayData('ItemLastEntries', {
url: 'Items/lastEntriesFilter', url: 'Items/lastEntriesFilter',
order: ['landed DESC', 'buyFk DESC'], order: ['landed DESC', 'buyFk DESC'],
exprBuilder: exprBuilder, exprBuilder: exprBuilder,
userFilter: { userFilter: {
where: { where: where,
itemFk: route.params.id,
},
}, },
}); });
const itemLastEntries = ref([]); const itemLastEntries = ref([]);
const columns = computed(() => [ const columns = computed(() => [
{ {
label: t('lastEntries.ig'), label: 'Nv',
name: 'ig', name: 'ig',
align: 'center', align: 'center',
}, },
@ -59,33 +73,38 @@ const columns = computed(() => [
label: t('itemDiary.warehouse'), label: t('itemDiary.warehouse'),
name: 'warehouse', name: 'warehouse',
field: 'warehouse', field: 'warehouse',
align: 'left', align: 'center',
}, },
{ {
label: t('lastEntries.landed'), label: t('lastEntries.landed'),
name: 'id', name: 'date',
field: 'landed', field: 'landed',
align: 'left', align: 'center',
format: (val) => toDateTimeFormat(val),
}, },
{ {
label: t('lastEntries.entry'), label: t('lastEntries.entry'),
name: 'entry', name: 'entry',
field: 'stateName', field: 'stateName',
align: 'left', align: 'center',
format: (val) => dashIfEmpty(val), format: (val) => dashIfEmpty(val),
}, },
{ {
label: t('lastEntries.pvp'), label: t('lastEntries.pvp'),
name: 'pvp', name: 'pvp',
field: 'reference', field: 'reference',
align: 'left', align: 'center',
format: (_, row) => toCurrency(row.price2) + ' / ' + toCurrency(row.price3), format: (_, row) => toCurrency(row.price2) + ' / ' + toCurrency(row.price3),
}, },
{
label: t('lastEntries.printedStickers'),
name: 'printedStickers',
field: 'printedStickers',
align: 'center',
format: (val) => dashIfEmpty(val),
},
{ {
label: t('lastEntries.label'), label: t('lastEntries.label'),
name: 'label', name: 'stickers',
field: 'stickers', field: 'stickers',
align: 'center', align: 'center',
format: (val) => dashIfEmpty(val), format: (val) => dashIfEmpty(val),
@ -93,11 +112,13 @@ const columns = computed(() => [
{ {
label: t('shelvings.packing'), label: t('shelvings.packing'),
name: 'packing', name: 'packing',
field: 'packing',
align: 'center', align: 'center',
}, },
{ {
label: t('lastEntries.grouping'), label: t('lastEntries.grouping'),
name: 'grouping', name: 'grouping',
field: 'grouping',
align: 'center', align: 'center',
}, },
{ {
@ -108,18 +129,19 @@ const columns = computed(() => [
}, },
{ {
label: t('lastEntries.quantity'), label: t('lastEntries.quantity'),
name: 'stems', name: 'quantity',
field: 'quantity', field: 'quantity',
align: 'center', align: 'center',
}, },
{ {
label: t('lastEntries.cost'), label: t('lastEntries.cost'),
name: 'cost', name: 'cost',
align: 'left', field: 'cost',
align: 'center',
}, },
{ {
label: t('lastEntries.kg'), label: 'Kg',
name: 'stems', name: 'weight',
field: 'weight', field: 'weight',
align: 'center', align: 'center',
}, },
@ -131,9 +153,9 @@ const columns = computed(() => [
}, },
{ {
label: t('lastEntries.supplier'), label: t('lastEntries.supplier'),
name: 'stems', name: 'supplier',
field: 'supplier', field: 'supplier',
align: 'left', align: 'center',
}, },
]); ]);
@ -157,11 +179,18 @@ const updateFilter = async () => {
else if (from.value && !to.value) filter = { gte: from.value }; else if (from.value && !to.value) filter = { gte: from.value };
else if (from.value && to.value) filter = { between: [from.value, to.value] }; else if (from.value && to.value) filter = { between: [from.value, to.value] };
arrayData.store.userFilter.where.landed = filter; const userFilter = arrayData.store.userFilter.where;
userFilter.landed = filter;
if (hideInventory.value) userFilter.supplierFk = { neq: inventorySupplierFk };
else delete userFilter.supplierFk;
await fetchItemLastEntries(); await fetchItemLastEntries();
}; };
onMounted(async () => { onMounted(async () => {
await getInventorySupplier();
const _from = Date.vnNew(); const _from = Date.vnNew();
_from.setDate(_from.getDate() - 75); _from.setDate(_from.getDate() - 75);
from.value = getDate(_from, 'from'); from.value = getDate(_from, 'from');
@ -171,14 +200,13 @@ onMounted(async () => {
updateFilter(); updateFilter();
watch([from, to], ([nFrom, nTo], [oFrom, oTo]) => { watch([from, to, hideInventory], ([nFrom, nTo], [oFrom, oTo]) => {
if (nFrom && nFrom != oFrom) nFrom = getDate(new Date(nFrom), 'from'); if (nFrom && nFrom != oFrom) nFrom = getDate(new Date(nFrom), 'from');
if (nTo && nTo != oTo) nTo = getDate(new Date(nTo), 'to'); if (nTo && nTo != oTo) nTo = getDate(new Date(nTo), 'to');
updateFilter(); updateFilter();
}); });
}); });
</script> </script>
<template> <template>
<VnSubToolbar> <VnSubToolbar>
<template #st-data> <template #st-data>
@ -187,27 +215,45 @@ onMounted(async () => {
dense dense
v-model="from" v-model="from"
class="q-mr-lg" class="q-mr-lg"
data-cy="from"
/>
<VnInputDate
:label="t('lastEntries.to')"
v-model="to"
dense
class="q-mr-lg"
data-cy="to"
/>
<QCheckbox
:label="t('Hide inventory supplier')"
v-model="hideInventory"
dense
class="q-mr-lg"
data-cy="hideInventory"
/> />
<VnInputDate :label="t('lastEntries.to')" dense v-model="to" />
</template> </template>
</VnSubToolbar> </VnSubToolbar>
<QPage class="column items-center q-pa-xd"> <QPage class="column items-center q-pa-xd">
<QTable <QTable
:rows="itemLastEntries" :rows="itemLastEntries"
:columns="columns" :columns="columns"
class="full-width q-mt-md" class="table full-width q-mt-md"
:no-data-label="t('globals.noResults')" :no-data-label="t('globals.noResults')"
> >
<template #body-cell-ig="{ row }"> <template #body-cell-ig="{ row }">
<QTd @click.stop> <QTd class="text-center">
<QCheckbox <QIcon
v-model="row.isIgnored" :name="row.isIgnored ? 'check_box' : 'check_box_outline_blank'"
:disable="true" style="color: var(--vn-label-color)"
:false-value="0" size="sm"
:true-value="1"
/> />
</QTd> </QTd>
</template> </template>
<template #body-cell-date="{ row }">
<QTd class="text-center">
<VnDateBadge :date="row.landed" />
</QTd>
</template>
<template #body-cell-entry="{ row }"> <template #body-cell-entry="{ row }">
<QTd @click.stop> <QTd @click.stop>
<div class="full-width flex justify-center"> <div class="full-width flex justify-center">
@ -229,8 +275,8 @@ onMounted(async () => {
</QTd> </QTd>
</template> </template>
<template #body-cell-pvp="{ value }"> <template #body-cell-pvp="{ value }">
<QTd @click.stop <QTd @click.stop class="text-center">
><span> {{ value }}</span> <span> {{ value }}</span>
<QTooltip> <QTooltip>
{{ t('lastEntries.grouping') }}/{{ t('lastEntries.packing') }} {{ t('lastEntries.grouping') }}/{{ t('lastEntries.packing') }}
</QTooltip></QTd </QTooltip></QTd
@ -249,7 +295,7 @@ onMounted(async () => {
</QTd> </QTd>
</template> </template>
<template #body-cell-cost="{ row }"> <template #body-cell-cost="{ row }">
<QTd @click.stop> <QTd @click.stop class="text-center">
<span> <span>
{{ toCurrency(row.cost, 'EUR', 3) }} {{ toCurrency(row.cost, 'EUR', 3) }}
<QTooltip> <QTooltip>
@ -267,10 +313,25 @@ onMounted(async () => {
</span> </span>
</QTd> </QTd>
</template> </template>
<template #body-cell-supplier="{ row }">
<QTd @click.stop>
<div class="full-width flex justify-center">
<SupplierDescriptorProxy
:id="row.supplierFk"
class="q-ma-none"
dense
/>
<span class="link">{{ row.supplier }}</span>
</div>
</QTd>
</template>
</QTable> </QTable>
</QPage> </QPage>
</template> </template>
<i18n>
es:
Hide inventory supplier: Ocultar proveedor inventario
</i18n>
<style lang="scss" scoped> <style lang="scss" scoped>
.q-badge--rounded { .q-badge--rounded {
border-radius: 50%; border-radius: 50%;
@ -282,4 +343,10 @@ onMounted(async () => {
padding: 0 11px; padding: 0 11px;
height: 28px; height: 28px;
} }
.th :first-child {
.td {
text-align: center;
background-color: red;
}
}
</style> </style>

View File

@ -46,7 +46,7 @@ const getUrl = (id, param) => `#/Item/${id}/${param}`;
<template #body="{ entity: { item, tags, visible, available, botanical } }"> <template #body="{ entity: { item, tags, visible, available, botanical } }">
<QCard class="vn-one photo"> <QCard class="vn-one photo">
<ItemDescriptorImage <ItemDescriptorImage
:entity-id="entityId" :entity-id="Number(entityId)"
:visible="visible" :visible="visible"
:available="available" :available="available"
:show-edit-button="false" :show-edit-button="false"

View File

@ -361,7 +361,7 @@ function handleOnDataSave({ CrudModelRef }) {
@on-fetch="(data) => (warehousesOptions = data)" @on-fetch="(data) => (warehousesOptions = data)"
auto-load auto-load
url="Warehouses" url="Warehouses"
:filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }" :filter="{ fields: ['id', 'name'], order: 'name ASC' }"
/> />
<RightMenu> <RightMenu>
<template #right-panel> <template #right-panel>
@ -394,7 +394,6 @@ function handleOnDataSave({ CrudModelRef }) {
/> />
</template> </template>
</VnSubToolbar> </VnSubToolbar>
<QPage>
<VnTable <VnTable
@on-fetch=" @on-fetch="
(data) => (data) =>
@ -425,15 +424,12 @@ function handleOnDataSave({ CrudModelRef }) {
'row-key': 'id', 'row-key': 'id',
selection: 'multiple', selection: 'multiple',
}" }"
:crud-model="{ :use-model="true"
disableInfiniteScroll: true,
}"
v-model:selected="rowsSelected" v-model:selected="rowsSelected"
:create-as-dialog="false" :create-as-dialog="false"
:create="{ :create="{
onDataSaved: handleOnDataSave, onDataSaved: handleOnDataSave,
}" }"
:use-model="true"
:disable-option="{ card: true }" :disable-option="{ card: true }"
> >
<template #header-selection="scope"> <template #header-selection="scope">
@ -470,7 +466,7 @@ function handleOnDataSave({ CrudModelRef }) {
</span> </span>
<span class="subName">{{ row.subName }}</span> <span class="subName">{{ row.subName }}</span>
<ItemDescriptorProxy :id="row.itemFk" /> <ItemDescriptorProxy :id="row.itemFk" />
<FetchedTags :item="row" /> <FetchedTags :item="row" :columns="3" />
</template> </template>
<template #column-rate2="props"> <template #column-rate2="props">
<QTd class="col"> <QTd class="col">
@ -578,7 +574,6 @@ function handleOnDataSave({ CrudModelRef }) {
@on-data-saved="onEditCellDataSaved()" @on-data-saved="onEditCellDataSaved()"
/> />
</QDialog> </QDialog>
</QPage>
</template> </template>
<style lang="scss"> <style lang="scss">
.q-table th, .q-table th,

View File

@ -32,7 +32,7 @@ const itemTypeWorkersOptions = ref([]);
<QItem class="q-my-md"> <QItem class="q-my-md">
<QItemSection> <QItemSection>
<VnSelect <VnSelect
:label="t('components.itemsFilterPanel.buyerFk')" :label="t('params.buyerFk')"
v-model="params.buyerFk" v-model="params.buyerFk"
:options="itemTypeWorkersOptions" :options="itemTypeWorkersOptions"
option-value="id" option-value="id"
@ -51,7 +51,7 @@ const itemTypeWorkersOptions = ref([]);
url="Warehouses" url="Warehouses"
auto-load auto-load
:filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }" :filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }"
:label="t('globals.warehouse')" :label="t('params.warehouseFk')"
v-model="params.warehouseFk" v-model="params.warehouseFk"
option-label="name" option-label="name"
option-value="id" option-value="id"
@ -66,7 +66,7 @@ const itemTypeWorkersOptions = ref([]);
<QItem class="q-my-md"> <QItem class="q-my-md">
<QItemSection> <QItemSection>
<VnInputDate <VnInputDate
:label="t('components.itemsFilterPanel.started')" :label="t('params.started')"
v-model="params.started" v-model="params.started"
is-outlined is-outlined
@update:model-value="searchFn()" @update:model-value="searchFn()"
@ -76,7 +76,7 @@ const itemTypeWorkersOptions = ref([]);
<QItem class="q-my-md"> <QItem class="q-my-md">
<QItemSection> <QItemSection>
<VnInputDate <VnInputDate
:label="t('components.itemsFilterPanel.ended')" :label="t('params.ended')"
v-model="params.ended" v-model="params.ended"
is-outlined is-outlined
@update:model-value="searchFn()" @update:model-value="searchFn()"
@ -86,7 +86,7 @@ const itemTypeWorkersOptions = ref([]);
<QItem> <QItem>
<QItemSection> <QItemSection>
<QCheckbox <QCheckbox
:label="t('components.itemsFilterPanel.mine')" :label="t('params.mine')"
v-model="params.mine" v-model="params.mine"
toggle-indeterminate toggle-indeterminate
@update:model-value="searchFn()" @update:model-value="searchFn()"
@ -94,14 +94,14 @@ const itemTypeWorkersOptions = ref([]);
<QCheckbox <QCheckbox
v-model="params.showBadDates" v-model="params.showBadDates"
:label="t(`components.itemsFilterPanel.showBadDates`)" :label="t(`params.showBadDates`)"
toggle-indeterminate toggle-indeterminate
@update:model-value="searchFn()" @update:model-value="searchFn()"
> >
</QCheckbox> </QCheckbox>
<QCheckbox <QCheckbox
:label="t('components.itemsFilterPanel.hasMinPrice')" :label="t('params.hasMinPrice')"
v-model="params.hasMinPrice" v-model="params.hasMinPrice"
toggle-indeterminate toggle-indeterminate
@update:model-value="searchFn()" @update:model-value="searchFn()"
@ -111,3 +111,23 @@ const itemTypeWorkersOptions = ref([]);
</template> </template>
</ItemsFilterPanel> </ItemsFilterPanel>
</template> </template>
<i18n>
en:
params:
buyerFk: Buyer
warehouseFk: Warehouse
started: Started
ended: Ended
mine: Mine
showBadDates: Show future items
hasMinPrice: Has Min Price
es:
params:
buyerFk: Comprador
warehouseFk: Almacén
started: Desde
ended: Hasta
mine: Para mi
showBadDates: Ver items a futuro
hasMinPrice: Precio mínimo
</i18n>

View File

@ -11,6 +11,7 @@ import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import ItemSummary from '../Item/Card/ItemSummary.vue'; import ItemSummary from '../Item/Card/ItemSummary.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import ItemDescriptorProxy from './Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from './Card/ItemDescriptorProxy.vue';
import ItemTypeDescriptorProxy from './ItemType/Card/ItemTypeDescriptorProxy.vue';
import { cloneItem } from 'src/pages/Item/composables/cloneItem'; import { cloneItem } from 'src/pages/Item/composables/cloneItem';
import RightMenu from 'src/components/common/RightMenu.vue'; import RightMenu from 'src/components/common/RightMenu.vue';
import ItemListFilter from './ItemListFilter.vue'; import ItemListFilter from './ItemListFilter.vue';
@ -56,7 +57,6 @@ const columns = computed(() => [
name: 'image', name: 'image',
align: 'left', align: 'left',
columnFilter: false, columnFilter: false,
cardVisible: true,
}, },
{ {
label: t('item.list.id'), label: t('item.list.id'),
@ -94,7 +94,7 @@ const columns = computed(() => [
columnFilter: { columnFilter: {
name: 'search', name: 'search',
}, },
cardVisible: true, columnClass: 'expand',
}, },
{ {
label: t('item.list.stems'), label: t('item.list.stems'),
@ -132,10 +132,13 @@ const columns = computed(() => [
fields: ['id', 'name'], fields: ['id', 'name'],
}, },
}, },
columnField: {
component: null,
},
create: true, create: true,
visible: false,
},
{
label: t('item.list.typeName'),
name: 'typeName',
align: 'left',
}, },
{ {
label: t('item.list.category'), label: t('item.list.category'),
@ -220,7 +223,6 @@ const columns = computed(() => [
label: t('item.list.weight'), label: t('item.list.weight'),
toolTip: t('item.list.weightByPiece'), toolTip: t('item.list.weightByPiece'),
name: 'weightByPiece', name: 'weightByPiece',
align: 'left',
component: 'input', component: 'input',
columnField: { columnField: {
component: null, component: null,
@ -296,7 +298,6 @@ const columns = computed(() => [
}, },
]); ]);
</script> </script>
<template> <template>
<VnSearchbar <VnSearchbar
data-key="ItemList" data-key="ItemList"
@ -341,6 +342,13 @@ const columns = computed(() => [
<ItemDescriptorProxy :id="row.id" /> <ItemDescriptorProxy :id="row.id" />
</span> </span>
</template> </template>
<template #column-typeName="{ row }">
<span class="link" @click.stop>
{{ row.typeName }}
{{ row.typeFk }}
<ItemTypeDescriptorProxy :id="row.typeFk" />
</span>
</template>
<template #column-userName="{ row }"> <template #column-userName="{ row }">
<span class="link" @click.stop> <span class="link" @click.stop>
{{ row.userName }} {{ row.userName }}
@ -354,15 +362,15 @@ const columns = computed(() => [
{{ row?.subName.toUpperCase() }} {{ row?.subName.toUpperCase() }}
</div> </div>
</div> </div>
<FetchedTags :item="row" :max-length="6" /> <FetchedTags :item="row" :columns="3" />
</template> </template>
</VnTable> </VnTable>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.subName { .subName {
text-transform: uppercase; text-transform: uppercase;
color: var(--vn-label-color); color: var(--vn-label-color);
font-size: small;
} }
</style> </style>
<i18n> <i18n>

View File

@ -1,10 +1,9 @@
<script setup> <script setup>
import { ref, computed, onMounted, watch } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue'; import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useArrayData } from 'composables/useArrayData';
import { toCurrency } from 'filters/index'; import { toCurrency } from 'filters/index';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import axios from 'axios'; import axios from 'axios';
@ -15,31 +14,22 @@ import VnInput from 'src/components/common/VnInput.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import ItemRequestFilter from './ItemRequestFilter.vue'; import ItemRequestFilter from './ItemRequestFilter.vue';
import RightMenu from 'src/components/common/RightMenu.vue'; import RightMenu from 'src/components/common/RightMenu.vue';
const { t } = useI18n(); const { t } = useI18n();
const { notify } = useNotify(); const { notify } = useNotify();
const stateStore = useStateStore(); const stateStore = useStateStore();
let filterParams = ref({});
const denyFormRef = ref(null); const denyFormRef = ref(null);
const denyRequestId = ref(null); const denyRequestId = ref(null);
const denyRequestIndex = ref(null); const denyRequestIndex = ref(null);
const itemRequestsOptions = ref([]); const itemRequestsOptions = ref([]);
const arrayData = useArrayData('ItemRequests', {
url: 'TicketRequests/filter',
userParams: filterParams,
order: ['shippedDate ASC', 'isOk ASC'],
});
const store = arrayData.store;
const userParams = { const userParams = {
state: 'pending', state: 'pending',
daysOnward: 7, daysOnward: 7,
}; };
const tableRef = ref(); const tableRef = ref();
watch( onMounted(async () => {
() => store.data, stateStore.rightDrawer = true;
(value) => (itemRequestsOptions.value = value) });
);
const columns = computed(() => [ const columns = computed(() => [
{ {
@ -223,11 +213,6 @@ const onDenyAccept = (_, responseData) => {
denyRequestIndex.value = null; denyRequestIndex.value = null;
tableRef.value.reload(); tableRef.value.reload();
}; };
onMounted(async () => {
await arrayData.fetch({ append: false });
stateStore.rightDrawer = true;
});
</script> </script>
<template> <template>
@ -244,10 +229,10 @@ onMounted(async () => {
:columns="columns" :columns="columns"
:user-params="userParams" :user-params="userParams"
:is-editable="true" :is-editable="true"
:right-search="false"
auto-load auto-load
:disable-option="{ card: true }" :disable-option="{ card: true }"
chip-locale="item.params" chip-locale="item.params"
:right-search="false"
> >
<template #column-ticketFk="{ row }"> <template #column-ticketFk="{ row }">
<span class="link"> <span class="link">
@ -306,14 +291,17 @@ onMounted(async () => {
<template #moreFilterPanel="{ params }"> <template #moreFilterPanel="{ params }">
<VnInputNumber <VnInputNumber
:label="t('params.scopeDays')" :label="t('params.scopeDays')"
v-model.number="params.scopeDays" v-model.number="params.daysOnward"
@keyup.enter="(evt) => handleScopeDays(evt.target.value)" @keyup.enter="(evt) => handleScopeDays(evt.target.value)"
@remove="handleScopeDays()" @remove="handleScopeDays()"
class="q-px-xs q-pr-lg" class="q-px-xs q-pr-lg"
filled filled
dense dense
lazy-rules
is-outlined
/> />
</template> </template>
<template #column-denyOptions="{ row, rowIndex }"> <template #column-denyOptions="{ row, rowIndex }">
<QIcon <QIcon
v-if="row.response?.length" v-if="row.response?.length"

View File

@ -6,7 +6,6 @@ import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
@ -46,20 +45,6 @@ const exprBuilder = (param, value) => {
} }
}; };
const add = (paramsObj, key) => {
if (paramsObj[key] === undefined) {
paramsObj[key] = 1;
} else {
paramsObj[key]++;
}
};
const decrement = (paramsObj, key) => {
if (paramsObj[key] === 0) return;
paramsObj[key]--;
};
onMounted(async () => { onMounted(async () => {
if (arrayData.store?.userParams) { if (arrayData.store?.userParams) {
fieldFiltersValues.value = Object.entries(arrayData.store.userParams).map( fieldFiltersValues.value = Object.entries(arrayData.store.userParams).map(
@ -95,8 +80,7 @@ onMounted(async () => {
<template #tags="{ tag, formatFn }"> <template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs"> <div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong> <strong>{{ t(`params.${tag.label}`) }}: </strong>
<span v-if="tag.label !== 'state'">{{ formatFn(tag.value) }}</span> <span>{{ formatFn(tag.value) }}</span>
<span v-else>{{ t(`${tag.value}`) }}</span>
</div> </div>
</template> </template>
<template #body="{ params, searchFn }"> <template #body="{ params, searchFn }">
@ -174,83 +158,6 @@ onMounted(async () => {
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem>
<QItemSection>
<QCheckbox
v-model="params.myTeam"
:label="t('params.myTeam')"
@update:model-value="searchFn()"
toggle-indeterminate
/>
</QItemSection>
</QItem>
<QCard bordered>
<QItem>
<QItemSection>
<VnInputDate
:label="t('params.from')"
v-model="params.from"
@update:model-value="searchFn()"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputDate
:label="t('params.to')"
v-model="params.to"
@update:model-value="searchFn()"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
v-model="params.scopeDays"
:label="t('globals.daysOnward')"
type="number"
dense
outlined
rounded
:min="0"
>
<template #append>
<QBtn
icon="add"
flat
dense
size="12px"
shortcut="+"
@click="add(params, 'scopeDays')"
/>
<QBtn
icon="remove"
flat
dense
size="12px"
@click="decrement(params, 'scopeDays')"
/>
</template>
</VnInput>
</QItemSection>
</QItem>
<QIcon name="info" style="position: absolute; top: 4px; right: 4px">
<QTooltip max-width="300px">
{{ t('dateFiltersTooltip') }}
</QTooltip>
</QIcon>
</QCard>
<QItem>
<QItemSection>
<QCheckbox
:label="t('params.mine')"
v-model="params.mine"
toggle-indeterminate
/>
</QItemSection>
</QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnSelect <VnSelect
@ -267,6 +174,16 @@ onMounted(async () => {
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem>
<QItemSection>
<VnInput
:label="t('params.daysOnward')"
v-model="params.daysOnward"
lazy-rules
is-outlined
/>
</QItemSection>
</QItem>
</template> </template>
</VnFilterPanel> </VnFilterPanel>
</template> </template>

View File

@ -6,13 +6,11 @@ import { useI18n } from 'vue-i18n';
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 WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import useCardDescription from 'src/composables/useCardDescription'; import useCardDescription from 'src/composables/useCardDescription';
const $props = defineProps({ const $props = defineProps({
id: { id: {
type: Number, type: Number,
required: false,
default: null, default: null,
}, },
summary: { summary: {
@ -24,6 +22,10 @@ const $props = defineProps({
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const entityId = computed(() => {
return $props.id || route.params.id;
});
const itemTypeFilter = { const itemTypeFilter = {
include: [ include: [
{ relation: 'worker' }, { relation: 'worker' },
@ -33,10 +35,6 @@ const itemTypeFilter = {
], ],
}; };
const entityId = computed(() => {
return $props.id || route.params.id;
});
const data = ref(useCardDescription()); const data = ref(useCardDescription());
const setData = (entity) => (data.value = useCardDescription(entity.code, entity.id)); const setData = (entity) => (data.value = useCardDescription(entity.code, entity.id));
</script> </script>
@ -48,8 +46,8 @@ const setData = (entity) => (data.value = useCardDescription(entity.code, entity
:filter="itemTypeFilter" :filter="itemTypeFilter"
:title="data.title" :title="data.title"
:subtitle="data.subtitle" :subtitle="data.subtitle"
data-key="itemTypeDescriptor"
@on-fetch="setData" @on-fetch="setData"
data-key="entry"
> >
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('shared.code')" :value="entity.code" /> <VnLv :label="t('shared.code')" :value="entity.code" />

View File

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

View File

@ -78,29 +78,32 @@ async function setItemTypeData(data) {
{{ t('globals.summary.basicData') }} {{ t('globals.summary.basicData') }}
<QIcon name="open_in_new" /> <QIcon name="open_in_new" />
</router-link> </router-link>
<VnLv :label="t('summary.id')" :value="itemType.id" /> <VnLv :label="t('itemType.summary.id')" :value="itemType.id" />
<VnLv :label="t('shared.code')" :value="itemType.code" /> <VnLv :label="t('itemType.shared.code')" :value="itemType.code" />
<VnLv :label="t('shared.name')" :value="itemType.name" /> <VnLv :label="t('itemType.shared.name')" :value="itemType.name" />
<VnLv :label="t('shared.worker')"> <VnLv :label="t('itemType.shared.worker')">
<template #value> <template #value>
<span class="link">{{ itemType.worker?.firstName }}</span> <span class="link">{{ itemType.worker?.firstName }}</span>
<WorkerDescriptorProxy :id="itemType.worker?.id" /> <WorkerDescriptorProxy :id="itemType.worker?.id" />
</template> </template>
</VnLv> </VnLv>
<VnLv :label="t('shared.category')" :value="itemType.category?.name" />
<VnLv <VnLv
:label="t('shared.temperature')" :label="t('itemType.shared.category')"
:value="itemType.category?.name"
/>
<VnLv
:label="t('itemType.shared.temperature')"
:value="itemType.temperature?.name" :value="itemType.temperature?.name"
/> />
<VnLv :label="t('summary.life')" :value="itemType.life" /> <VnLv :label="t('itemType.summary.life')" :value="itemType.life" />
<VnLv :label="t('summary.promo')" :value="itemType.promo" /> <VnLv :label="t('itemType.summary.promo')" :value="itemType.promo" />
<VnLv <VnLv
:label="t('summary.itemPackingType')" :label="t('itemType.summary.itemPackingType')"
:value="itemType.itemPackingType?.description" :value="itemType.itemPackingType?.description"
/> />
<VnLv <VnLv
class="large-label" class="large-label"
:label="t('summary.isUnconventionalSize')" :label="t('itemType.summary.isUnconventionalSize')"
:value="itemType.isUnconventionalSize" :value="itemType.isUnconventionalSize"
/> />
</QCard> </QCard>

View File

@ -1,4 +1,5 @@
shared: itemType:
shared:
code: Code code: Code
name: Name name: Name
worker: Worker worker: Worker
@ -8,7 +9,7 @@ shared:
itemPackingType: Item packing type itemPackingType: Item packing type
maxRefs: Maximum references maxRefs: Maximum references
fragile: Fragile fragile: Fragile
summary: summary:
id: id id: id
life: Life life: Life
promo: Promo promo: Promo

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