resolve conflicts
gitea/salix-front/pipeline/pr-dev This commit looks good Details

This commit is contained in:
William Buezas 2024-03-13 11:31:55 -03:00
commit 4925bed845
73 changed files with 2426 additions and 1152 deletions

View File

@ -1,5 +1,6 @@
FROM node:stretch-slim FROM node:stretch-slim
RUN npm install -g @quasar/cli RUN corepack enable pnpm
RUN pnpm install -g @quasar/cli
WORKDIR /app WORKDIR /app
COPY dist/spa ./ COPY dist/spa ./
CMD ["quasar", "serve", "./", "--history", "--hostname", "0.0.0.0"] CMD ["quasar", "serve", "./", "--history", "--hostname", "0.0.0.0"]

4
Jenkinsfile vendored
View File

@ -62,7 +62,7 @@ pipeline {
NODE_ENV = "" NODE_ENV = ""
} }
steps { steps {
sh 'npm install --prefer-offline' sh 'pnpm install --prefer-offline'
} }
} }
stage('Test') { stage('Test') {
@ -73,7 +73,7 @@ pipeline {
NODE_ENV = "" NODE_ENV = ""
} }
steps { steps {
sh 'npm run test:unit:ci' sh 'pnpm run test:unit:ci'
} }
post { post {
always { always {

View File

@ -1,10 +1,11 @@
{ {
"name": "salix-front", "name": "salix-front",
"version": "24.10.0", "version": "24.12.0",
"description": "Salix frontend", "description": "Salix frontend",
"productName": "Salix", "productName": "Salix",
"author": "Verdnatura", "author": "Verdnatura",
"private": true, "private": true,
"packageManager": "pnpm@8.15.1",
"scripts": { "scripts": {
"lint": "eslint --ext .js,.vue ./", "lint": "eslint --ext .js,.vue ./",
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore", "format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
@ -16,12 +17,12 @@
}, },
"dependencies": { "dependencies": {
"@quasar/cli": "^2.3.0", "@quasar/cli": "^2.3.0",
"@quasar/extras": "^1.16.4", "@quasar/extras": "^1.16.9",
"axios": "^1.4.0", "axios": "^1.4.0",
"chromium": "^3.0.3", "chromium": "^3.0.3",
"croppie": "^2.6.5", "croppie": "^2.6.5",
"pinia": "^2.1.3", "pinia": "^2.1.3",
"quasar": "^2.12.0", "quasar": "^2.14.5",
"validator": "^13.9.0", "validator": "^13.9.0",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-i18n": "^9.2.2", "vue-i18n": "^9.2.2",
@ -30,11 +31,11 @@
"devDependencies": { "devDependencies": {
"@intlify/unplugin-vue-i18n": "^0.8.1", "@intlify/unplugin-vue-i18n": "^0.8.1",
"@pinia/testing": "^0.1.2", "@pinia/testing": "^0.1.2",
"@quasar/app-vite": "^1.4.3", "@quasar/app-vite": "^1.7.3",
"@quasar/quasar-app-extension-testing-unit-vitest": "^0.3.0", "@quasar/quasar-app-extension-testing-unit-vitest": "^0.4.0",
"@vue/test-utils": "^2.3.2", "@vue/test-utils": "^2.4.4",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"cypress": "^12.13.0", "cypress": "^13.6.6",
"eslint": "^8.41.0", "eslint": "^8.41.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
"eslint-plugin-cypress": "^2.13.3", "eslint-plugin-cypress": "^2.13.3",
@ -50,8 +51,8 @@
"bun": ">= 1.0.25" "bun": ">= 1.0.25"
}, },
"overrides": { "overrides": {
"@vitejs/plugin-vue": "^4.0.0", "@vitejs/plugin-vue": "^5.0.4",
"vite": "^4.3.5", "vite": "^5.1.4",
"vitest": "^0.31.1" "vitest": "^0.31.1"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -67,7 +67,7 @@ module.exports = configure(function (/* ctx */) {
// analyze: true, // analyze: true,
// env: {}, // env: {},
rawDefine: { rawDefine: {
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
}, },
// ignorePublicFolder: true, // ignorePublicFolder: true,
// minify: false, // minify: false,
@ -92,7 +92,7 @@ module.exports = configure(function (/* ctx */) {
vitePlugins: [ vitePlugins: [
[ [
VueI18nPlugin({ VueI18nPlugin({
runtimeOnly: false runtimeOnly: false,
}), }),
{ {
// if you want to use Vue I18n Legacy API, you need to set `compositionOnly: false` // if you want to use Vue I18n Legacy API, you need to set `compositionOnly: false`
@ -123,9 +123,6 @@ module.exports = configure(function (/* ctx */) {
framework: { framework: {
config: { config: {
config: { config: {
brand: {
primary: 'orange',
},
dark: 'auto', dark: 'auto',
}, },
}, },

View File

@ -71,13 +71,9 @@ const closeForm = () => {
<span ref="closeButton" class="close-icon" v-close-popup> <span ref="closeButton" class="close-icon" v-close-popup>
<QIcon name="close" size="sm" /> <QIcon name="close" size="sm" />
</span> </span>
<h1 class="title"> <span class="title">{{ t('Edit') }}</span>
{{ <span class="countLines">{{ ` ${rows.length} ` }}</span>
t('editBuyTitle', { <span class="title">{{ t('buy(s)') }}</span>
buysAmount: rows.length,
})
}}
</h1>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">
<VnSelectFilter <VnSelectFilter
@ -94,23 +90,23 @@ const closeForm = () => {
</div> </div>
</VnRow> </VnRow>
<div class="q-mt-lg row justify-end"> <div class="q-mt-lg row justify-end">
<QBtn
:label="t('globals.save')"
type="submit"
color="primary"
:disabled="isLoading"
:loading="isLoading"
/>
<QBtn <QBtn
:label="t('globals.cancel')" :label="t('globals.cancel')"
type="reset" type="reset"
color="primary" color="primary"
flat flat
class="q-ml-sm"
:disabled="isLoading" :disabled="isLoading"
:loading="isLoading" :loading="isLoading"
v-close-popup v-close-popup
/> />
<QBtn
:label="t('globals.save')"
type="submit"
color="primary"
class="q-ml-sm"
:disabled="isLoading"
:loading="isLoading"
/>
</div> </div>
</QCard> </QCard>
</QForm> </QForm>
@ -129,13 +125,18 @@ const closeForm = () => {
right: 20px; right: 20px;
cursor: pointer; cursor: pointer;
} }
.countLines {
font-size: 24px;
color: $primary;
font-weight: bold;
}
</style> </style>
<i18n> <i18n>
en: es:
editBuyTitle: Edit {buysAmount} buy(s) Edit: Editar
es: buy(s): compra(s)
editBuyTitle: Editar {buysAmount} compra(s) Field to edit: Campo a editar
Field to edit: Campo a editar Value: Valor
Value: Valor </i18n>
</i18n>

View File

@ -82,7 +82,7 @@ onMounted(async () => {
}); });
// Podemos enviarle al form la estructura de data inicial sin necesidad de fetchearla // Podemos enviarle al form la estructura de data inicial sin necesidad de fetchearla
state.set($props.model, $props.formInitialData ?? {}); state.set($props.model, $props.formInitialData);
if ($props.autoLoad && !$props.formInitialData) { if ($props.autoLoad && !$props.formInitialData) {
await fetch(); await fetch();
} }

View File

@ -0,0 +1,34 @@
<script setup>
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useCapitalize } from 'src/composables/useCapitalize';
import VnInput from 'src/components/common/VnInput.vue';
const props = defineProps({
modelValue: { type: String, default: '' },
});
const { t } = useI18n();
const emit = defineEmits(['update:modelValue']);
const amount = computed({
get() {
return props.modelValue;
},
set(val) {
emit('update:modelValue', val);
},
});
</script>
<template>
<VnInput
v-model="amount"
type="number"
step="any"
:label="useCapitalize(t('amount'))"
/>
</template>
<i18n>
es:
amount: importe
</i18n>

View File

@ -111,7 +111,7 @@ function addDefaultData(data) {
<FormModelPopup <FormModelPopup
:title="formInitialData ? t('globals.edit') : t('globals.create')" :title="formInitialData ? t('globals.edit') : t('globals.create')"
model="dms" model="dms"
:form-initial-data="formInitialData" :form-initial-data="formInitialData ?? {}"
:save-fn="save" :save-fn="save"
> >
<template #form-inputs> <template #form-inputs>

View File

@ -1,6 +1,7 @@
<script setup> <script setup>
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { toDate } from 'src/filters'; import VnInput from 'components/common/VnInput.vue';
import isValidDate from "filters/isValidDate";
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
@ -17,12 +18,25 @@ const props = defineProps({
}, },
}); });
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
const joinDateAndTime = (date, time) => {
if (!date) {
return null;
}
if (!time) {
return new Date(date).toISOString();
}
const [year, month, day] = date.split('/');
return new Date(`${year}-${month}-${day}T${time}`).toISOString();
};
const time = computed(() => (props.modelValue ? props.modelValue.split('T')?.[1] : null));
const value = computed({ const value = computed({
get() { get() {
return props.modelValue; return props.modelValue;
}, },
set(value) { set(value) {
emit('update:modelValue', value ? new Date(value).toISOString() : null); emit('update:modelValue', joinDateAndTime(value, time.value));
}, },
}); });
@ -40,6 +54,16 @@ const formatDate = (dateString) => {
date.getDate() date.getDate()
)}`; )}`;
}; };
const displayDate = (dateString) => {
if (!dateString || !isValidDate(dateString)) {
return '';
}
return new Date(dateString).toLocaleDateString([], {
year: 'numeric',
month: '2-digit',
day: '2-digit',
});
};
const styleAttrs = computed(() => { const styleAttrs = computed(() => {
return props.isOutlined return props.isOutlined
@ -53,12 +77,12 @@ const styleAttrs = computed(() => {
</script> </script>
<template> <template>
<QInput <VnInput
class="vn-input-date" class="vn-input-date"
rounded :model-value="displayDate(value)"
readonly
:model-value="toDate(value)"
v-bind="{ ...$attrs, ...styleAttrs }" v-bind="{ ...$attrs, ...styleAttrs }"
readonly
@click="isPopupOpen = true"
> >
<template #append> <template #append>
<QIcon name="event" class="cursor-pointer"> <QIcon name="event" class="cursor-pointer">
@ -76,7 +100,7 @@ const styleAttrs = computed(() => {
</QPopupProxy> </QPopupProxy>
</QIcon> </QIcon>
</template> </template>
</QInput> </VnInput>
</template> </template>
<style lang="scss"> <style lang="scss">

View File

@ -1,8 +1,8 @@
<script setup> <script setup>
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { toHour } from 'src/filters';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import isValidDate from 'filters/isValidDate'; import isValidDate from 'filters/isValidDate';
import VnInput from "components/common/VnInput.vue";
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
@ -26,8 +26,8 @@ const value = computed({
}, },
set(value) { set(value) {
const [hours, minutes] = value.split(':'); const [hours, minutes] = value.split(':');
const date = new Date(); const date = new Date(props.modelValue);
date.setUTCHours( date.setHours(
Number.parseInt(hours) || 0, Number.parseInt(hours) || 0,
Number.parseInt(minutes) || 0, Number.parseInt(minutes) || 0,
0, 0,
@ -37,6 +37,7 @@ const value = computed({
}, },
}); });
const isPopupOpen = ref(false);
const onDateUpdate = (date) => { const onDateUpdate = (date) => {
internalValue.value = date; internalValue.value = date;
}; };
@ -45,7 +46,7 @@ const save = () => {
value.value = internalValue.value; value.value = internalValue.value;
}; };
const formatTime = (dateString) => { const formatTime = (dateString) => {
if (!isValidDate(dateString)) { if (!dateString || !isValidDate(dateString)) {
return ''; return '';
} }
@ -70,16 +71,17 @@ const styleAttrs = computed(() => {
</script> </script>
<template> <template>
<QInput <VnInput
class="vn-input-time" class="vn-input-time"
rounded
readonly readonly
:model-value="toHour(value)" :model-value="formatTime(value)"
v-bind="{ ...$attrs, ...styleAttrs }" v-bind="{ ...$attrs, ...styleAttrs }"
@click="isPopupOpen = true"
> >
<template #append> <template #append>
<QIcon name="event" class="cursor-pointer"> <QIcon name="event" class="cursor-pointer">
<QPopupProxy <QPopupProxy
v-model="isPopupOpen"
cover cover
transition-show="scale" transition-show="scale"
transition-hide="scale" transition-hide="scale"
@ -109,7 +111,7 @@ const styleAttrs = computed(() => {
</QPopupProxy> </QPopupProxy>
</QIcon> </QIcon>
</template> </template>
</QInput> </VnInput>
</template> </template>
<style lang="scss"> <style lang="scss">

View File

@ -1036,7 +1036,6 @@ en:
claimStateFk: Claim State claimStateFk: Claim State
workerFk: Worker workerFk: Worker
clientFk: Customer clientFk: Customer
rma: RMA
responsibility: Responsibility responsibility: Responsibility
packages: Packages packages: Packages
es: es:
@ -1046,7 +1045,7 @@ es:
tooltips: tooltips:
search: Buscar por identificador o concepto search: Buscar por identificador o concepto
changes: Buscar por cambios. Los atributos deben buscarse por su nombre interno, para obtenerlo situar el cursor sobre el atributo. changes: Buscar por cambios. Los atributos deben buscarse por su nombre interno, para obtenerlo situar el cursor sobre el atributo.
Audit logs: Registros de auditoría Audit logs: Historial
Property: Propiedad Property: Propiedad
Before: Antes Before: Antes
After: Después After: Después
@ -1076,7 +1075,6 @@ es:
claimStateFk: Estado de la reclamación claimStateFk: Estado de la reclamación
workerFk: Trabajador workerFk: Trabajador
clientFk: Cliente clientFk: Cliente
rma: RMA
responsibility: Responsabilidad responsibility: Responsabilidad
packages: Bultos packages: Bultos
</i18n> </i18n>

View File

@ -171,6 +171,7 @@ const emit = defineEmits(['onFetch']);
<style lang="scss"> <style lang="scss">
.body { .body {
background-color: var(--vn-gray);
.text-h5 { .text-h5 {
padding-top: 5px; padding-top: 5px;
padding-bottom: 5px; padding-bottom: 5px;
@ -188,7 +189,8 @@ const emit = defineEmits(['onFetch']);
.label { .label {
color: var(--vn-label); color: var(--vn-label);
font-size: 12px; font-size: 12px;
::after {
&:not(:has(a))::after {
content: ':'; content: ':';
} }
} }
@ -223,8 +225,6 @@ const emit = defineEmits(['onFetch']);
margin-bottom: 15px; margin-bottom: 15px;
} }
.list-box { .list-box {
background-color: var(--vn-gray);
.q-item__label { .q-item__label {
color: var(--vn-label); color: var(--vn-label);
} }

View File

@ -46,7 +46,7 @@ watch(props, async () => {
<QCard class="cardSummary"> <QCard class="cardSummary">
<SkeletonSummary v-if="!entity" /> <SkeletonSummary v-if="!entity" />
<template v-if="entity"> <template v-if="entity">
<div class="summaryHeader bg-primary q-pa-md text-weight-bolder"> <div class="summaryHeader bg-primary q-pa-sm text-weight-bolder">
<slot name="header-left"> <slot name="header-left">
<span></span> <span></span>
</slot> </slot>
@ -85,8 +85,8 @@ watch(props, async () => {
flex-direction: row; flex-direction: row;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-evenly; justify-content: space-evenly;
gap: 15px; gap: 10px;
padding: 15px; padding: 10px;
background-color: var(--vn-gray); background-color: var(--vn-gray);
> .q-card.vn-one { > .q-card.vn-one {
@ -105,14 +105,14 @@ watch(props, async () => {
> .q-card { > .q-card {
width: 100%; width: 100%;
background-color: var(--vn-gray); background-color: var(--vn-gray);
padding: 15px; padding: 7px;
font-size: 16px; font-size: 16px;
min-width: 275px; min-width: 275px;
.vn-label-value { .vn-label-value {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
margin-top: 5px; margin-top: 2px;
.label { .label {
color: var(--vn-label); color: var(--vn-label);
width: 8em; width: 8em;
@ -131,13 +131,26 @@ watch(props, async () => {
.header { .header {
color: $primary; color: $primary;
font-weight: bold; font-weight: bold;
margin-bottom: 25px; margin-bottom: 10px;
font-size: 20px; font-size: 20px;
display: inline-block; display: inline-block;
} }
.header.link:hover { .header.link:hover {
color: lighten($primary, 20%); color: lighten($primary, 20%);
} }
.q-checkbox {
display: flex;
margin-bottom: 9px;
& .q-checkbox__label {
margin-left: 31px;
color: var(--vn-text);
}
& .q-checkbox__inner {
position: absolute;
left: 0;
color: var(--vn-label);
}
}
} }
} }

View File

@ -13,12 +13,49 @@ defineProps({
<template> <template>
<div class="fetchedTags"> <div class="fetchedTags">
<div class="wrap"> <div class="wrap">
<div class="inline-tag" :class="{ empty: !$props.item.value5 }">{{ $props.item.value5 }}</div> <div
<div class="inline-tag" :class="{ empty: !$props.item.value6 }">{{ $props.item.value6 }}</div> class="inline-tag"
<div class="inline-tag" :class="{ empty: !$props.item.value7 }">{{ $props.item.value7 }}</div> :class="{ empty: !$props.item.value5 }"
<div class="inline-tag" :class="{ empty: !$props.item.value8 }">{{ $props.item.value8 }}</div> :title="$props.item.tag5 + ': ' + $props.item.value5"
<div class="inline-tag" :class="{ empty: !$props.item.value9 }">{{ $props.item.value9 }}</div> >
<div class="inline-tag" :class="{ empty: !$props.item.value10 }">{{ $props.item.value10 }}</div> {{ $props.item.value5 }}
</div>
<div
class="inline-tag"
:class="{ empty: !$props.item.tag6 }"
:title="$props.item.tag6 + ': ' + $props.item.value6"
>
{{ $props.item.value6 }}
</div>
<div
class="inline-tag"
:class="{ empty: !$props.item.value7 }"
:title="$props.item.tag7 + ': ' + $props.item.value7"
>
{{ $props.item.value7 }}
</div>
<div
class="inline-tag"
:class="{ empty: !$props.item.value8 }"
:title="$props.item.tag8 + ': ' + $props.item.value8"
>
{{ $props.item.value8 }}
</div>
<div
class="inline-tag"
:class="{ empty: !$props.item.value9 }"
:title="$props.item.tag9 + ': ' + $props.item.value9"
>
{{ $props.item.value9 }}
</div>
<div
class="inline-tag"
:class="{ empty: !$props.item.value10 }"
:title="$props.item.tag10 + ': ' + $props.item.value10"
>
{{ $props.item.value10 }}
</div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,5 +1,4 @@
<script setup> <script setup>
import { computed } from 'vue';
import { dashIfEmpty } from 'src/filters'; import { dashIfEmpty } from 'src/filters';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useClipboard } from 'src/composables/useClipboard'; import { useClipboard } from 'src/composables/useClipboard';
@ -16,7 +15,6 @@ const $props = defineProps({
}); });
const { t } = useI18n(); const { t } = useI18n();
const isBooleanValue = computed(() => typeof $props.value === 'boolean');
const { copyText } = useClipboard(); const { copyText } = useClipboard();
function copyValueText() { function copyValueText() {
@ -42,14 +40,7 @@ function copyValueText() {
</slot> </slot>
</div> </div>
<div class="value"> <div class="value">
<span v-if="isBooleanValue"> <slot name="value">
<QIcon
:name="$props.value ? `check` : `close`"
:color="$props.value ? `positive` : `negative`"
size="sm"
/>
</span>
<slot v-else name="value">
<span :title="$props.value"> <span :title="$props.value">
{{ $props.dash ? dashIfEmpty($props.value) : $props.value }} {{ $props.dash ? dashIfEmpty($props.value) : $props.value }}
</span> </span>

View File

@ -8,7 +8,6 @@ import VnPaginate from './VnPaginate.vue';
import VnUserLink from '../ui/VnUserLink.vue'; import VnUserLink from '../ui/VnUserLink.vue';
const $props = defineProps({ const $props = defineProps({
id: { type: String, required: true },
url: { type: String, default: null }, url: { type: String, default: null },
filter: { type: Object, default: () => {} }, filter: { type: Object, default: () => {} },
body: { type: Object, default: () => {} }, body: { type: Object, default: () => {} },
@ -28,7 +27,7 @@ async function insert() {
} }
</script> </script>
<template> <template>
<div class="column items-center full-height"> <div class="column items-center full-height full-width">
<VnPaginate <VnPaginate
:data-key="$props.url" :data-key="$props.url"
:url="$props.url" :url="$props.url"
@ -39,28 +38,38 @@ async function insert() {
ref="vnPaginateRef" ref="vnPaginateRef"
> >
<template #body="{ rows }"> <template #body="{ rows }">
<QCard class="q-pa-xs q-mb-md" v-for="(note, index) in rows" :key="index"> <div class="column items-center full-width">
<QCardSection horizontal> <QCard
<slot name="picture"> class="q-pa-xs q-mb-sm full-width"
<VnAvatar :descriptor="false" :worker-id="note.workerFk" /> v-for="(note, index) in rows"
</slot> :key="index"
<QItem class="full-width justify-between items-start"> >
<VnUserLink <QCardSection horizontal>
:name="`${note.worker.user.nickname}`" <slot name="picture">
:worker-id="note.worker.id" <VnAvatar
/> :descriptor="false"
:worker-id="note.workerFk"
<slot name="actions"> size="md"
{{ toDateHourMinSec(note.created) }} />
</slot> </slot>
</QItem> <div class="full-width row justify-between q-pa-xs">
</QCardSection> <VnUserLink
<QCardSection class="q-pa-sm"> :name="`${note.worker.user.nickname}`"
<slot name="text"> :worker-id="note.worker.id"
{{ note.text }} />
</slot>
</QCardSection> <slot name="actions">
</QCard> {{ toDateHour(note.created) }}
</slot>
</div>
</QCardSection>
<QCardSection class="q-pa-xs q-my-none q-py-none">
<slot name="text">
{{ note.text }}
</slot>
</QCardSection>
</QCard>
</div>
</template> </template>
</VnPaginate> </VnPaginate>
<QPageSticky position="bottom-right" :offset="[25, 25]" v-if="addNote"> <QPageSticky position="bottom-right" :offset="[25, 25]" v-if="addNote">
@ -108,8 +117,10 @@ async function insert() {
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.q-card { .q-card {
max-width: 80em; width: 90%;
@media (max-width: $breakpoint-sm) {
width: 100%;
}
&__section { &__section {
word-wrap: break-word; word-wrap: break-word;
} }

View File

@ -138,7 +138,7 @@ async function onLoad(...params) {
</script> </script>
<template> <template>
<div> <div class="full-width">
<div <div
v-if="!props.autoLoad && !store.data && !isLoading" v-if="!props.autoLoad && !store.data && !isLoading"
class="info-row q-pa-md text-center" class="info-row q-pa-md text-center"
@ -175,6 +175,7 @@ async function onLoad(...params) {
@load="onLoad" @load="onLoad"
:offset="offset" :offset="offset"
class="full-width full-height" class="full-width full-height"
v-bind="$attrs"
> >
<slot name="body" :rows="store.data"></slot> <slot name="body" :rows="store.data"></slot>
<div v-if="isLoading" class="info-row q-pa-md text-center"> <div v-if="isLoading" class="info-row q-pa-md text-center">

View File

@ -1,16 +1,18 @@
<template> <template>
<div id="row" class="q-gutter-md"> <div id="row" class="q-gutter-md q-mb-md">
<slot></slot> <slot></slot>
</div> </div>
</template> </template>
<style lang="scss" scopped> <style lang="scss" scopped>
#row { #row {
display: grid; display: flex;
grid-template-columns: 1fr 1fr; > * {
flex: 1;
}
} }
@media screen and (max-width: 800px) { @media screen and (max-width: 800px) {
#row { #row {
grid-template-columns: 1fr; flex-direction: column;
} }
} }
</style> </style>

View File

@ -81,8 +81,9 @@ onMounted(() => {
}); });
async function search() { async function search() {
const staticParams = Object.entries(store.userParams) const staticParams = Object.entries(store.userParams).filter(
.filter(([key, value]) => value && (props.staticParams || []).includes(key)); ([key, value]) => value && (props.staticParams || []).includes(key)
);
await arrayData.applyFilter({ await arrayData.applyFilter({
params: { params: {
...Object.fromEntries(staticParams), ...Object.fromEntries(staticParams),
@ -155,11 +156,9 @@ async function search() {
.cursor-info { .cursor-info {
cursor: help; cursor: help;
} }
#searchbar {
.body--light #searchbar {
.q-field--standout.q-field--highlighted .q-field__control { .q-field--standout.q-field--highlighted .q-field__control {
background-color: $grey-7; background-color: var(--vn-text);
color: #333;
} }
} }
</style> </style>

View File

@ -0,0 +1,7 @@
import { useQuasar } from 'quasar';
export default function() {
const quasar = useQuasar();
return quasar.screen.gt.xs ? 'q-pa-md': 'q-pa-xs';
}

View File

@ -1,17 +1,59 @@
// app global css in SCSS form // app global css in SCSS form
@import './icons.scss'; @import './icons.scss';
body.body--light {
--fount-color: black;
--vn-sectionColor: #ffffff;
--vn-pageColor: #e0e0e0;
background-color: var(--vn-pageColor);
.q-header .q-toolbar {
color: var(--fount-color);
}
--vn-text: var(--fount-color);
--vn-gray: var(--vn-sectionColor);
--vn-label: #5f5f5f;
--vn-dark: var(--vn-sectionColor);
--vn-light-gray: #e7e3e3;
}
body.body--dark {
--vn-pageColor: #222;
--vn-SectionColor: #3c3b3b;
background-color: var(--vn-pageColor);
--vn-text: white;
--vn-gray: var(--vn-SectionColor);
--vn-label: #a8a8a8;
--vn-dark: var(--vn-SectionColor);
--vn-light-gray: #424242;
}
a { a {
text-decoration: none; text-decoration: none;
} }
.link { .link {
color: $primary; color: $color-link;
cursor: pointer; cursor: pointer;
} }
.tx-color-link {
color: $color-link !important;
}
.header-link {
color: $color-link !important;
cursor: pointer;
border-bottom: solid $primary;
border-width: 2px;
width: 100%;
.q-icon {
float: right;
}
text-transform: uppercase;
}
.link:hover { .link:hover {
color: $orange-4; text-decoration: underline;
} }
// Removes chrome autofill background // Removes chrome autofill background
@ -24,26 +66,6 @@ select:-webkit-autofill {
background-clip: text !important; background-clip: text !important;
} }
body.body--light {
.q-header .q-toolbar {
background-color: $white;
color: #555;
}
--vn-text: #000000;
--vn-gray: #f5f5f5;
--vn-label: #5f5f5f;
--vn-dark: white;
--vn-light-gray: #e7e3e3;
}
body.body--dark {
--vn-text: #ffffff;
--vn-gray: #313131;
--vn-label: #a8a8a8;
--vn-dark: #292929;
--vn-light-gray: #424242;
}
.bg-vn-dark { .bg-vn-dark {
background-color: var(--vn-dark); background-color: var(--vn-dark);
} }

View File

@ -832,14 +832,14 @@
], ],
"attrs": [], "attrs": [],
"grid": 0, "grid": 0,
"tags": ["defaulter-01"], "tags": ["deaulter-01"],
"defaultCode": 59726 "defaultCode": 59726
}, },
"attrs": [], "attrs": [],
"properties": { "properties": {
"order": 163, "order": 163,
"id": 34, "id": 34,
"name": "defaulter", "name": "deaulter",
"prevSize": 32, "prevSize": 32,
"code": 59726 "code": 59726
}, },

View File

@ -32,6 +32,9 @@
.icon-Client_unpaid:before { .icon-Client_unpaid:before {
content: '\e925'; content: '\e925';
} }
.icon-Client_unpaid:before {
content: '\e925';
}
.icon-History:before { .icon-History:before {
content: '\e964'; content: '\e964';
} }
@ -53,12 +56,18 @@
.icon-agency:before { .icon-agency:before {
content: '\e92a'; content: '\e92a';
} }
.icon-agency:before {
content: '\e92a';
}
.icon-agency-term:before { .icon-agency-term:before {
content: '\e92b'; content: '\e92b';
} }
.icon-albaran:before { .icon-albaran:before {
content: '\e92c'; content: '\e92c';
} }
.icon-albaran:before {
content: '\e92c';
}
.icon-anonymous:before { .icon-anonymous:before {
content: '\e92d'; content: '\e92d';
} }
@ -185,6 +194,9 @@
.icon-grafana:before { .icon-grafana:before {
content: '\e931'; content: '\e931';
} }
.icon-grafana:before {
content: '\e931';
}
.icon-greenery:before { .icon-greenery:before {
content: '\e91e'; content: '\e91e';
} }
@ -371,6 +383,9 @@
.icon-transaction:before { .icon-transaction:before {
content: '\e93b'; content: '\e93b';
} }
.icon-transaction:before {
content: '\e93b';
}
.icon-treatments:before { .icon-treatments:before {
content: '\e91c'; content: '\e91c';
} }

View File

@ -11,26 +11,32 @@
// It's highly recommended to change the default colors // It's highly recommended to change the default colors
// to match your app's branding. // to match your app's branding.
// Tip: Use the "Theme Builder" on Quasar's documentation website. // Tip: Use the "Theme Builder" on Quasar's documentation website.
// Tip: to add new colors https://quasar.dev/style/color-palette/#adding-your-own-colors
$primary: #ec8916; $primary: #ec8916;
$primary-light: lighten($primary, 35%); $secondary: $primary;
$secondary: #26a69a;
$accent: #9c27b0;
$white: #fff;
$positive: #21ba45; $positive: #21ba45;
$negative: #c10015; $negative: #c10015;
$info: #31ccec; $info: #31ccec;
$warning: #f2c037; $warning: #f2c037;
$vnColor: #8ebb27;
// Pendiente de cuadrar con la base de datos // Pendiente de cuadrar con la base de datos
$success: $positive; $success: $positive;
$alert: $negative; $alert: $negative;
$white: #fff;
$dark: #3c3b3b;
// custom
$color-link: #66bfff;
$color-spacer-light: #a3a3a31f;
$color-spacer: #7979794d;
$border-thin-light: 1px solid $color-spacer-light;
$primary-light: lighten($primary, 35%);
$dark-shadow-color: black;
$layout-shadow-dark: 0 0 10px 2px #00000033, 0 0px 10px #0000003d;
$spacing-md: 16px;
.bg-success { .bg-success {
background-color: $positive; background-color: $positive;
} }
.bg-notice { .bg-notice {
background-color: $info; background-color: $info;
} }
@ -40,12 +46,3 @@ $alert: $negative;
.bg-alert { .bg-alert {
background-color: $negative; background-color: $negative;
} }
$color-spacer-light: rgba(255, 255, 255, 0.12);
$color-spacer: rgba(255, 255, 255, 0.3);
$border-thin-light: 1px solid $color-spacer-light;
$dark-shadow-color: #000;
$dark: #292929;
$layout-shadow-dark: 0 0 10px 2px rgba(0, 0, 0, 0.2), 0 0px 10px rgba(0, 0, 0, 0.24);
$spacing-md: 16px;

93
src/filters/date.js Normal file
View File

@ -0,0 +1,93 @@
/**
* Checks if a given date is valid.
*
* @param {number|string|Date} date - The date to be checked.
* @returns {boolean} True if the date is valid, false otherwise.
*
* @example
* // returns true
* isValidDate(new Date());
*
* @example
* // returns false
* isValidDate('invalid date');
*/
export function isValidDate(date) {
return date && !isNaN(new Date(date).getTime());
}
/**
* Converts a given date to a specific format.
*
* @param {number|string|Date} date - The date to be formatted.
* @returns {string} The formatted date as a string in 'dd/mm/yyyy' format. If the provided date is not valid, an empty string is returned.
*
* @example
* // returns "02/12/2022"
* toDateFormat(new Date(2022, 11, 2));
*/
export function toDateFormat(date) {
if (!isValidDate(date)) {
return '';
}
return new Date(date).toLocaleDateString('es-ES', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
});
}
/**
* Converts a given date to a specific time format.
*
* @param {number|string|Date} date - The date to be formatted.
* @param {boolean} [showSeconds=false] - Whether to include seconds in the output format.
* @returns {string} The formatted time as a string in 'hh:mm:ss' format. If the provided date is not valid, an empty string is returned. If showSeconds is false, seconds are not included in the output format.
*
* @example
* // returns "00:00"
* toTimeFormat(new Date(2022, 11, 2));
*
* @example
* // returns "00:00:00"
* toTimeFormat(new Date(2022, 11, 2), true);
*/
export function toTimeFormat(date, showSeconds = false) {
if (!isValidDate(date)) {
return '';
}
return new Date(date).toLocaleDateString('es-ES', {
hour: '2-digit',
minute: '2-digit',
second: showSeconds ? '2-digit' : undefined,
});
}
/**
* Converts a given date to a specific date and time format.
*
* @param {number|string|Date} date - The date to be formatted.
* @param {boolean} [showSeconds=false] - Whether to include seconds in the output format.
* @returns {string} The formatted date as a string in 'dd/mm/yyyy, hh:mm:ss' format. If the provided date is not valid, an empty string is returned. If showSeconds is false, seconds are not included in the output format.
*
* @example
* // returns "02/12/2022, 00:00"
* toDateTimeFormat(new Date(2022, 11, 2));
*
* @example
* // returns "02/12/2022, 00:00:00"
* toDateTimeFormat(new Date(2022, 11, 2), true);
*/
export function toDateTimeFormat(date, showSeconds = false) {
if (!isValidDate(date)) {
return '';
}
return new Date(date).toLocaleDateString('es-ES', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: showSeconds ? '2-digit' : undefined,
});
}

View File

@ -83,6 +83,7 @@ export default {
file: 'File', file: 'File',
selectFile: 'Select a file', selectFile: 'Select a file',
copyClipboard: 'Copy on clipboard', copyClipboard: 'Copy on clipboard',
salesPerson: 'SalesPerson',
}, },
errors: { errors: {
statusUnauthorized: 'Access denied', statusUnauthorized: 'Access denied',
@ -185,6 +186,7 @@ export default {
businessType: 'Business type', businessType: 'Business type',
passwordRequirements: passwordRequirements:
'The password must have at least { length } length characters, {nAlpha} alphabetic characters, {nUpper} capital letters, {nDigits} digits and {nPunct} symbols (Ex: $%&.)\n', 'The password must have at least { length } length characters, {nAlpha} alphabetic characters, {nUpper} capital letters, {nDigits} digits and {nPunct} symbols (Ex: $%&.)\n',
businessTypeFk: 'Business type',
}, },
summary: { summary: {
basicData: 'Basic data', basicData: 'Basic data',
@ -453,6 +455,7 @@ export default {
shipped: 'Shipped', shipped: 'Shipped',
warehouse: 'Warehouse', warehouse: 'Warehouse',
customerCard: 'Customer card', customerCard: 'Customer card',
alias: 'Alias',
}, },
boxing: { boxing: {
expedition: 'Expedition', expedition: 'Expedition',
@ -517,11 +520,9 @@ export default {
claims: 'Claims', claims: 'Claims',
list: 'List', list: 'List',
createClaim: 'Create claim', createClaim: 'Create claim',
rmaList: 'RMA',
summary: 'Summary', summary: 'Summary',
basicData: 'Basic Data', basicData: 'Basic Data',
lines: 'Lines', lines: 'Lines',
rma: 'RMA',
photos: 'Photos', photos: 'Photos',
development: 'Development', development: 'Development',
log: 'Audit logs', log: 'Audit logs',
@ -538,10 +539,6 @@ export default {
code: 'Code', code: 'Code',
records: 'records', records: 'records',
}, },
rma: {
user: 'User',
created: 'Created',
},
card: { card: {
claimId: 'Claim ID', claimId: 'Claim ID',
assignedTo: 'Assigned', assignedTo: 'Assigned',
@ -550,6 +547,8 @@ export default {
ticketId: 'Ticket ID', ticketId: 'Ticket ID',
customerSummary: 'Customer summary', customerSummary: 'Customer summary',
claimedTicket: 'Claimed ticket', claimedTicket: 'Claimed ticket',
saleTracking: 'Sale tracking',
ticketTracking: 'Ticket tracking',
commercial: 'Commercial', commercial: 'Commercial',
province: 'Province', province: 'Province',
zone: 'Zone', zone: 'Zone',
@ -580,7 +579,6 @@ export default {
responsible: 'Responsible', responsible: 'Responsible',
worker: 'Worker', worker: 'Worker',
redelivery: 'Redelivery', redelivery: 'Redelivery',
returnOfMaterial: 'RMA',
}, },
basicData: { basicData: {
customer: 'Customer', customer: 'Customer',
@ -588,7 +586,6 @@ export default {
created: 'Created', created: 'Created',
state: 'State', state: 'State',
picked: 'Picked', picked: 'Picked',
returnOfMaterial: 'Return of material authorization (RMA)',
}, },
photo: { photo: {
fileDescription: 'Claim id {claimId} from client {clientName} id {clientId}', fileDescription: 'Claim id {claimId} from client {clientName} id {clientId}',

View File

@ -83,6 +83,7 @@ export default {
file: 'Fichero', file: 'Fichero',
selectFile: 'Seleccione un fichero', selectFile: 'Seleccione un fichero',
copyClipboard: 'Copiar en portapapeles', copyClipboard: 'Copiar en portapapeles',
salesPerson: 'Comercial',
}, },
errors: { errors: {
statusUnauthorized: 'Acceso denegado', statusUnauthorized: 'Acceso denegado',
@ -184,6 +185,7 @@ export default {
businessType: 'Tipo de negocio', businessType: 'Tipo de negocio',
passwordRequirements: passwordRequirements:
'La contraseña debe tener al menos { length } caracteres de longitud, {nAlpha} caracteres alfabéticos, {nUpper} letras mayúsculas, {nDigits} dígitos y {nPunct} símbolos (Ej: $%&.)', 'La contraseña debe tener al menos { length } caracteres de longitud, {nAlpha} caracteres alfabéticos, {nUpper} letras mayúsculas, {nDigits} dígitos y {nPunct} símbolos (Ej: $%&.)',
businessTypeFk: 'Tipo de negocio',
}, },
summary: { summary: {
basicData: 'Datos básicos', basicData: 'Datos básicos',
@ -453,6 +455,7 @@ export default {
shipped: 'Enviado', shipped: 'Enviado',
warehouse: 'Almacén', warehouse: 'Almacén',
customerCard: 'Ficha del cliente', customerCard: 'Ficha del cliente',
alias: 'Alias',
}, },
boxing: { boxing: {
expedition: 'Expedición', expedition: 'Expedición',
@ -517,14 +520,12 @@ export default {
claims: 'Reclamaciones', claims: 'Reclamaciones',
list: 'Listado', list: 'Listado',
createClaim: 'Crear reclamación', createClaim: 'Crear reclamación',
rmaList: 'RMA',
summary: 'Resumen', summary: 'Resumen',
basicData: 'Datos básicos', basicData: 'Datos básicos',
lines: 'Líneas', lines: 'Líneas',
rma: 'RMA',
development: 'Trazabilidad', development: 'Trazabilidad',
photos: 'Fotos', photos: 'Fotos',
log: 'Registros de auditoría', log: 'Historial',
notes: 'Notas', notes: 'Notas',
action: 'Acción', action: 'Acción',
}, },
@ -538,10 +539,6 @@ export default {
code: 'Código', code: 'Código',
records: 'registros', records: 'registros',
}, },
rma: {
user: 'Usuario',
created: 'Creado',
},
card: { card: {
claimId: 'ID reclamación', claimId: 'ID reclamación',
assignedTo: 'Asignada a', assignedTo: 'Asignada a',
@ -550,6 +547,8 @@ export default {
ticketId: 'ID ticket', ticketId: 'ID ticket',
customerSummary: 'Resumen del cliente', customerSummary: 'Resumen del cliente',
claimedTicket: 'Ticket reclamado', claimedTicket: 'Ticket reclamado',
saleTracking: 'Líneas preparadas',
ticketTracking: 'Estados del ticket',
commercial: 'Comercial', commercial: 'Comercial',
province: 'Provincia', province: 'Provincia',
zone: 'Zona', zone: 'Zona',
@ -580,7 +579,6 @@ export default {
responsible: 'Responsable', responsible: 'Responsable',
worker: 'Trabajador', worker: 'Trabajador',
redelivery: 'Devolución', redelivery: 'Devolución',
returnOfMaterial: 'RMA',
}, },
basicData: { basicData: {
customer: 'Cliente', customer: 'Cliente',
@ -588,7 +586,6 @@ export default {
created: 'Creada', created: 'Creada',
state: 'Estado', state: 'Estado',
picked: 'Recogida', picked: 'Recogida',
returnOfMaterial: 'Autorización de retorno de materiales (RMA)',
}, },
photo: { photo: {
fileDescription: fileDescription:
@ -747,7 +744,7 @@ export default {
create: 'Crear', create: 'Crear',
summary: 'Resumen', summary: 'Resumen',
basicData: 'Datos básicos', basicData: 'Datos básicos',
log: 'Registros de auditoría', log: 'Historial',
}, },
list: { list: {
parking: 'Parking', parking: 'Parking',
@ -779,7 +776,7 @@ export default {
dueDay: 'Vencimiento', dueDay: 'Vencimiento',
intrastat: 'Intrastat', intrastat: 'Intrastat',
corrective: 'Rectificativa', corrective: 'Rectificativa',
log: 'Registros de auditoría', log: 'Historial',
}, },
list: { list: {
ref: 'Referencia', ref: 'Referencia',

View File

@ -40,7 +40,7 @@ const langs = ['en', 'es'];
<template> <template>
<QLayout view="hHh LpR fFf"> <QLayout view="hHh LpR fFf">
<QHeader reveal class="bg-dark"> <QHeader reveal class="bg-vn-dark">
<QToolbar class="justify-end"> <QToolbar class="justify-end">
<QBtn <QBtn
id="switchLanguage" id="switchLanguage"

View File

@ -24,7 +24,6 @@ const claimFilter = {
'workerFk', 'workerFk',
'claimStateFk', 'claimStateFk',
'packages', 'packages',
'rma',
'hasToPickUp', 'hasToPickUp',
], ],
include: [ include: [
@ -169,13 +168,6 @@ const statesFilter = {
type="number" type="number"
/> />
</div> </div>
<div class="col">
<VnInput
v-model="data.rma"
:label="t('claim.basicData.returnOfMaterial')"
:rules="validate('claim.rma')"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">

View File

@ -5,6 +5,7 @@ import { useStateStore } from 'stores/useStateStore';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import ClaimDescriptor from './ClaimDescriptor.vue'; import ClaimDescriptor from './ClaimDescriptor.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import useCardSize from 'src/composables/useCardSize';
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
@ -28,7 +29,9 @@ const { t } = useI18n();
<QPageContainer> <QPageContainer>
<QPage> <QPage>
<VnSubToolbar /> <VnSubToolbar />
<div class="q-pa-md"><RouterView></RouterView></div> <div :class="useCardSize()">
<RouterView></RouterView>
</div>
</QPage> </QPage>
</QPageContainer> </QPageContainer>
</template> </template>

View File

@ -105,7 +105,6 @@ onMounted(async () => {
<ClaimDescriptorMenu :claim="entity" /> <ClaimDescriptorMenu :claim="entity" />
</template> </template>
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('claim.card.created')" :value="toDate(entity.created)" />
<VnLv v-if="entity.claimState" :label="t('claim.card.state')"> <VnLv v-if="entity.claimState" :label="t('claim.card.state')">
<template #value> <template #value>
<QBadge :color="stateColor(entity.claimState.code)" dense> <QBadge :color="stateColor(entity.claimState.code)" dense>
@ -113,13 +112,13 @@ onMounted(async () => {
</QBadge> </QBadge>
</template> </template>
</VnLv> </VnLv>
<VnLv :label="t('claim.card.ticketId')"> <VnLv :label="t('claim.card.created')" :value="toDate(entity.created)" />
<VnLv :label="t('claim.card.commercial')">
<template #value> <template #value>
<span class="link"> <VnUserLink
{{ entity.ticketFk }} :name="entity.client?.salesPersonUser?.name"
:worker-id="entity.client?.salesPersonFk"
<TicketDescriptorProxy :id="entity.ticketFk" /> />
</span>
</template> </template>
</VnLv> </VnLv>
<VnLv <VnLv
@ -134,19 +133,20 @@ onMounted(async () => {
/> />
</template> </template>
</VnLv> </VnLv>
<VnLv :label="t('claim.card.commercial')"> <VnLv :label="t('claim.card.zone')" :value="entity.ticket?.zone?.name" />
<template #value>
<VnUserLink
:name="entity.client?.salesPersonUser?.name"
:worker-id="entity.client?.salesPersonFk"
/>
</template>
</VnLv>
<VnLv <VnLv
:label="t('claim.card.province')" :label="t('claim.card.province')"
:value="entity.ticket?.address?.province?.name" :value="entity.ticket?.address?.province?.name"
/> />
<VnLv :label="t('claim.card.zone')" :value="entity.ticket?.zone?.name" /> <VnLv :label="t('claim.card.ticketId')">
<template #value>
<span class="link">
{{ entity.ticketFk }}
<TicketDescriptorProxy :id="entity.ticketFk" />
</span>
</template>
</VnLv>
<VnLv <VnLv
:label="t('claimRate')" :label="t('claimRate')"
:value="toPercentage(entity.client?.claimsRatio?.claimingRate)" :value="toPercentage(entity.client?.claimsRatio?.claimingRate)"
@ -176,6 +176,7 @@ onMounted(async () => {
color="primary" color="primary"
:href="salixUrl + 'ticket/' + entity.ticketFk + '/sale-tracking'" :href="salixUrl + 'ticket/' + entity.ticketFk + '/sale-tracking'"
> >
<QTooltip>{{ t('claim.card.saleTracking') }}</QTooltip>
</QBtn> </QBtn>
<QBtn <QBtn
size="md" size="md"
@ -183,6 +184,7 @@ onMounted(async () => {
color="primary" color="primary"
:href="salixUrl + 'ticket/' + entity.ticketFk + '/tracking/index'" :href="salixUrl + 'ticket/' + entity.ticketFk + '/tracking/index'"
> >
<QTooltip>{{ t('claim.card.ticketTracking') }}</QTooltip>
</QBtn> </QBtn>
</QCardActions> </QCardActions>
</template> </template>

View File

@ -1,4 +1,5 @@
<script setup> <script setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import VnNotes from 'src/components/ui/VnNotes.vue'; import VnNotes from 'src/components/ui/VnNotes.vue';
@ -6,14 +7,15 @@ import VnNotes from 'src/components/ui/VnNotes.vue';
const route = useRoute(); const route = useRoute();
const state = useState(); const state = useState();
const user = state.getUser(); const user = state.getUser();
const id = route.params.id;
const $props = defineProps({ const $props = defineProps({
id: { type: Number, default: null },
addNote: { type: Boolean, default: true }, addNote: { type: Boolean, default: true },
}); });
const claimId = computed(() => $props.id || route.params.id);
const claimFilter = { const claimFilter = {
where: { claimFk: id }, where: { claimFk: claimId.value },
fields: ['created', 'workerFk', 'text'], fields: ['created', 'workerFk', 'text'],
include: { include: {
relation: 'worker', relation: 'worker',
@ -30,19 +32,16 @@ const claimFilter = {
}; };
const body = { const body = {
claimFk: id, claimFk: claimId.value,
workerFk: user.value.id, workerFk: user.value.id,
}; };
</script> </script>
<template> <template>
<div class="column items-center"> <VnNotes
<VnNotes style="overflow-y: auto"
style="overflow-y: scroll" :add-note="$props.addNote"
:add-note="$props.addNote" url="claimObservations"
:id="id" :filter="claimFilter"
url="claimObservations" :body="body"
:filter="claimFilter" />
:body="body"
/>
</div>
</template> </template>

View File

@ -1,145 +0,0 @@
<script setup>
import axios from 'axios';
import { watch, ref, computed, onUnmounted, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import CrudModel from 'components/CrudModel.vue';
import { useState } from 'src/composables/useState';
import { toDate } from 'src/filters';
const quasar = useQuasar();
const state = useState();
const { t } = useI18n();
const selected = ref([]);
const claimRmaRef = ref();
const claim = computed(() => state.get('ClaimDescriptor'));
const claimRmaFilter = {
include: {
relation: 'worker',
scope: {
include: {
relation: 'user',
},
},
},
order: 'created DESC',
where: {
code: claim.value?.rma,
},
};
async function addRow() {
if (!claim.value.rma) {
return quasar.notify({
message: `This claim is not associated to any RMA`,
type: 'negative',
});
}
const formData = {
code: claim.value.rma,
};
await axios.post(`ClaimRmas`, formData);
await claimRmaRef.value.reload();
quasar.notify({
type: 'positive',
message: t('globals.rowAdded'),
icon: 'check',
});
}
onMounted(() => {
if (claim.value) claimRmaRef.value.reload();
});
watch(
claim,
() => {
claimRmaRef.value.reload();
},
{ deep: true }
);
</script>
<template>
<div class="column items-center">
<div class="list">
<CrudModel
data-key="ClaimRma"
url="ClaimRmas"
model="ClaimRma"
:filter="claimRmaFilter"
v-model:selected="selected"
ref="claimRmaRef"
:default-save="false"
:default-reset="false"
:default-remove="false"
>
<template #body="{ rows }">
<QCard>
<template v-for="(row, index) of rows" :key="row.id">
<QItem class="q-pa-none items-start">
<QItemSection class="q-pa-md">
<QList>
<QItem class="q-pa-none">
<QItemSection>
<QItemLabel caption>
{{ t('claim.rma.user') }}
</QItemLabel>
<QItemLabel>
{{ row?.worker?.user?.name }}
</QItemLabel>
</QItemSection>
</QItem>
<QItem class="q-pa-none">
<QItemSection>
<QItemLabel caption>
{{ t('claim.rma.created') }}
</QItemLabel>
<QItemLabel>
{{
toDate(row.created, {
timeStyle: 'medium',
})
}}
</QItemLabel>
</QItemSection>
</QItem>
</QList>
</QItemSection>
<QCardActions vertical class="justify-between">
<QBtn
flat
round
color="orange"
icon="vn:bin"
@click="claimRmaRef.remove([row])"
>
<QTooltip>{{ t('globals.remove') }}</QTooltip>
</QBtn>
</QCardActions>
</QItem>
<QSeparator v-if="index !== rows.length - 1" />
</template>
</QCard>
</template>
</CrudModel>
</div>
</div>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn fab color="primary" icon="add" @click="addRow()" />
</QPageSticky>
</template>
<style lang="scss" scoped>
.list {
width: 100%;
max-width: 60em;
}
</style>
<i18n>
es:
This claim is not associated to any RMA: Esta reclamación no está asociada a ninguna ARM
</i18n>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { onMounted, ref, computed, watch } from 'vue'; import { onMounted, ref, computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { toDate, toCurrency } from 'src/filters'; import { toDate, toCurrency } from 'src/filters';
@ -70,7 +70,7 @@ const detailsColumns = ref([
}, },
{ {
name: 'description', name: 'description',
label: 'claim.summary.description', label: 'globals.description',
field: (row) => row.sale.concept, field: (row) => row.sale.concept,
}, },
{ {
@ -179,9 +179,9 @@ function openDialog(dmsId) {
</template> </template>
<template #body="{ entity: { claim, salesClaimed, developments } }"> <template #body="{ entity: { claim, salesClaimed, developments } }">
<QCard class="vn-one"> <QCard class="vn-one">
<a class="header" :href="`#/claim/${entityId}/basic-data`"> <a class="header header-link" :href="`#/claim/${entityId}/basic-data`">
{{ t('claim.pageTitles.basicData') }} {{ t('claim.pageTitles.basicData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<VnLv <VnLv
:label="t('claim.summary.created')" :label="t('claim.summary.created')"
@ -194,19 +194,19 @@ function openDialog(dmsId) {
</QChip> </QChip>
</template> </template>
</VnLv> </VnLv>
<VnLv :label="t('claim.summary.assignedTo')"> <VnLv :label="t('globals.salesPerson')">
<template #value> <template #value>
<VnUserLink <VnUserLink
:name="claim.worker?.user?.nickname" :name="claim.client?.salesPersonUser?.name"
:worker-id="claim.workerFk" :worker-id="claim.client?.salesPersonFk"
/> />
</template> </template>
</VnLv> </VnLv>
<VnLv :label="t('claim.summary.attendedBy')"> <VnLv :label="t('claim.summary.attendedBy')">
<template #value> <template #value>
<VnUserLink <VnUserLink
:name="claim.client?.salesPersonUser?.name" :name="claim.worker?.user?.nickname"
:worker-id="claim.client?.salesPersonFk" :worker-id="claim.workerFk"
/> />
</template> </template>
</VnLv> </VnLv>
@ -218,26 +218,37 @@ function openDialog(dmsId) {
/> />
</template> </template>
</VnLv> </VnLv>
<VnLv :label="t('claim.summary.returnOfMaterial')" :value="claim.rma" />
<QCheckbox <QCheckbox
:align-items="right"
:label="t('claim.basicData.picked')" :label="t('claim.basicData.picked')"
v-model="claim.hasToPickUp" v-model="claim.hasToPickUp"
:disable="true"
/> />
</QCard> </QCard>
<QCard class="vn-three claimVnNotes full-height"> <QCard class="vn-three">
<a class="header" :href="`#/claim/${entityId}/notes`"> <a class="header header-link" :href="`#/claim/${entityId}/notes`">
{{ t('claim.summary.notes') }} {{ t('claim.summary.notes') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<ClaimNotes :add-note="false" style="height: 350px" order="created ASC" /> <ClaimNotes
:id="entityId"
:add-note="false"
style="max-height: 300px"
order="created ASC"
/>
</QCard> </QCard>
<QCard class="vn-two" v-if="salesClaimed.length > 0"> <QCard class="vn-two" v-if="salesClaimed.length > 0">
<a class="header" :href="`#/claim/${entityId}/lines`"> <a class="header header-link" :href="`#/claim/${entityId}/lines`">
{{ t('claim.summary.details') }} {{ t('claim.summary.details') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<QTable :columns="detailsColumns" :rows="salesClaimed" flat> <QTable
:columns="detailsColumns"
:rows="salesClaimed"
flat
dense
:rows-per-page-options="[0]"
hide-bottom
>
<template #header="props"> <template #header="props">
<QTr :props="props"> <QTr :props="props">
<QTh v-for="col in props.cols" :key="col.name" :props="props"> <QTh v-for="col in props.cols" :key="col.name" :props="props">
@ -268,11 +279,19 @@ function openDialog(dmsId) {
</QTable> </QTable>
</QCard> </QCard>
<QCard class="vn-two" v-if="developments.length > 0"> <QCard class="vn-two" v-if="developments.length > 0">
<a class="header" :href="claimUrl + 'development'"> <a class="header header-link" :href="claimUrl + 'development'">
{{ t('claim.summary.development') }} {{ t('claim.summary.development') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<QTable :columns="developmentColumns" :rows="developments" flat>
<QTable
:columns="developmentColumns"
:rows="developments"
flat
dense
:rows-per-page-options="[0]"
hide-bottom
>
<template #header="props"> <template #header="props">
<QTr :props="props"> <QTr :props="props">
<QTh v-for="col in props.cols" :key="col.name" :props="props"> <QTh v-for="col in props.cols" :key="col.name" :props="props">
@ -283,9 +302,9 @@ function openDialog(dmsId) {
</QTable> </QTable>
</QCard> </QCard>
<QCard class="vn-max" v-if="claimDms.length > 0"> <QCard class="vn-max" v-if="claimDms.length > 0">
<a class="header" :href="`#/claim/${entityId}/photos`"> <a class="header header-link" :href="`#/claim/${entityId}/photos`">
{{ t('claim.summary.photos') }} {{ t('claim.summary.photos') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<div class="container"> <div class="container">
<div <div
@ -302,7 +321,7 @@ function openDialog(dmsId) {
v-if="media.isVideo" v-if="media.isVideo"
@click.stop="openDialog(media.dmsFk)" @click.stop="openDialog(media.dmsFk)"
> >
<QTooltip>Video</QTooltip> <QTooltip>Video</QTooltip>header
</QIcon> </QIcon>
<QCard class="multimedia relative-position"> <QCard class="multimedia relative-position">
<QImg <QImg
@ -326,9 +345,9 @@ function openDialog(dmsId) {
</QCard> </QCard>
<QCard class="vn-max"> <QCard class="vn-max">
<a class="header" :href="claimUrl + 'action'"> <a class="header header-link" :href="claimUrl + 'action'">
{{ t('claim.summary.actions') }} {{ t('claim.summary.actions') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" class="link" />
</a> </a>
<div id="slider-container" class="q-px-xl q-py-md"> <div id="slider-container" class="q-px-xl q-py-md">
<QSlider <QSlider
@ -336,7 +355,7 @@ function openDialog(dmsId) {
label label
:label-value="t('claim.summary.responsibility')" :label-value="t('claim.summary.responsibility')"
label-always label-always
color="primary" color="var()"
markers markers
:marker-labels="[ :marker-labels="[
{ value: 1, label: t('claim.summary.company') }, { value: 1, label: t('claim.summary.company') },
@ -390,13 +409,7 @@ function openDialog(dmsId) {
</template> </template>
</CardSummary> </CardSummary>
</template> </template>
<style lang="scss">
.claimVnNotes {
.q-card {
max-width: 100%;
}
}
</style>
<style lang="scss" scoped> <style lang="scss" scoped>
.q-dialog__inner--minimized > div { .q-dialog__inner--minimized > div {
max-width: 80%; max-width: 80%;
@ -406,7 +419,6 @@ function openDialog(dmsId) {
flex-direction: row; flex-direction: row;
flex-wrap: wrap; flex-wrap: wrap;
gap: 15px; gap: 15px;
flex-basis: 30%;
} }
.multimedia-container { .multimedia-container {
flex: 1 0 21%; flex: 1 0 21%;

View File

@ -115,12 +115,6 @@ function navigate(event, id) {
</VnLv> </VnLv>
</template> </template>
<template #actions> <template #actions>
<QBtn
:label="t('components.smartCard.openCard')"
@click.stop="navigate(row.id)"
class="bg-vn-dark"
outline
/>
<QBtn <QBtn
:label="t('globals.description')" :label="t('globals.description')"
@click.stop @click.stop

View File

@ -1,171 +0,0 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import VnConfirm from 'src/components/ui/VnConfirm.vue';
import VnInput from 'src/components/common/VnInput.vue';
import { useArrayData } from 'src/composables/useArrayData';
import axios from 'axios';
const quasar = useQuasar();
const { t } = useI18n();
const arrayData = useArrayData('ClaimRmaList');
const isLoading = ref(false);
const input = ref();
const newRma = ref({
code: '',
crated: Date.vnNew(),
});
function onInputUpdate(value) {
newRma.value.code = value.toUpperCase();
}
async function submit() {
const formData = newRma.value;
if (formData.code === '') return;
isLoading.value = true;
await axios.post('ClaimRmas', formData);
await arrayData.refresh();
isLoading.value = false;
input.value.$el.focus();
newRma.value = {
code: '',
created: Date.vnNew(),
};
}
function confirm(id) {
quasar
.dialog({
component: VnConfirm,
componentProps: {
data: { id },
promise: remove,
},
})
.onOk(async () => await arrayData.refresh());
}
async function remove({ id }) {
await axios.delete(`ClaimRmas/${id}`);
quasar.notify({
type: 'positive',
message: t('globals.rowRemoved'),
});
}
</script>
<template>
<QPage class="column items-center q-pa-md sticky">
<QPageSticky expand position="top" :offset="[16, 16]">
<QCard class="card q-pa-md">
<QForm @submit="submit">
<VnInput
ref="input"
v-model="newRma.code"
:label="t('claim.rmaList.code')"
@update:model-value="onInputUpdate"
class="q-mb-md"
:readonly="isLoading"
:loading="isLoading"
autofocus
/>
<div class="text-caption">
{{ arrayData.totalRows }} {{ t('claim.rmaList.records') }}
</div>
</QForm>
</QCard>
</QPageSticky>
<div class="vn-card-list">
<VnPaginate
data-key="ClaimRmaList"
url="ClaimRmas"
order="id DESC"
:offset="50"
auto-load
>
<template #body="{ rows }">
<QCard class="card">
<template v-if="isLoading">
<QItem class="q-pa-none items-start">
<QItemSection class="q-pa-md">
<QList>
<QItem class="q-pa-none">
<QItemSection>
<QItemLabel caption>
<QSkeleton />
</QItemLabel>
<QItemLabel>
<QSkeleton type="text" />
</QItemLabel>
</QItemSection>
</QItem>
</QList>
</QItemSection>
<QCardActions vertical class="justify-between">
<QSkeleton
type="circle"
class="q-mb-md"
size="40px"
/>
</QCardActions>
</QItem>
<QSeparator />
</template>
<template v-for="row of rows" :key="row.id">
<QItem class="q-pa-none items-start">
<QItemSection class="q-pa-md">
<QList>
<QItem class="q-pa-none">
<QItemSection>
<QItemLabel caption>{{
t('claim.rmaList.code')
}}</QItemLabel>
<QItemLabel>{{ row.code }}</QItemLabel>
</QItemSection>
</QItem>
</QList>
</QItemSection>
<QCardActions vertical class="justify-between">
<QBtn
flat
round
color="primary"
icon="vn:bin"
@click="confirm(row.id)"
>
<QTooltip>{{ t('globals.remove') }}</QTooltip>
</QBtn>
</QCardActions>
</QItem>
<QSeparator />
</template>
</QCard>
</template>
</VnPaginate>
</div>
</QPage>
</template>
<style lang="scss" scoped>
.sticky {
padding-top: 156px;
}
.card {
width: 100%;
max-width: 60em;
}
.q-page-sticky {
z-index: 2998;
}
</style>

View File

@ -6,6 +6,7 @@ import CustomerDescriptor from './CustomerDescriptor.vue';
import LeftMenu from 'components/LeftMenu.vue'; import LeftMenu from 'components/LeftMenu.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import useCardSize from 'src/composables/useCardSize';
const stateStore = useStateStore(); const stateStore = useStateStore();
const route = useRoute(); const route = useRoute();
@ -30,7 +31,9 @@ const { t } = useI18n();
<QPageContainer> <QPageContainer>
<QPage> <QPage>
<VnSubToolbar /> <VnSubToolbar />
<div class="q-pa-md"><RouterView></RouterView></div> <div :class="useCardSize()">
<RouterView></RouterView>
</div>
</QPage> </QPage>
</QPageContainer> </QPageContainer>
</template> </template>

View File

@ -64,29 +64,25 @@ const setData = (entity) => (data.value = useCardDescription(entity.name, entity
</template> </template>
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('customer.card.payMethod')" :value="entity.payMethod.name" /> <VnLv :label="t('customer.card.payMethod')" :value="entity.payMethod.name" />
<VnLv :label="t('customer.card.credit')" :value="toCurrency(entity.credit)" /> <VnLv :label="t('customer.card.credit')" :value="toCurrency(entity.credit)" />
<VnLv <VnLv
:label="t('customer.card.securedCredit')" :label="t('customer.card.securedCredit')"
:value="entity.creditInsurance ? toCurrency(entity.creditInsurance) : '-'" :value="toCurrency(entity.creditInsurance)"
/> />
<VnLv
:label="t('customer.card.risk')" <VnLv :label="t('customer.card.debt')" :value="toCurrency(entity.debt)" />
:value="entity.debt ? toCurrency(entity.debt) : '-'" <VnLv v-if="entity.salesPersonUser" :label="t('customer.card.salesPerson')">
:info="t('customer.summary.descriptorInfo')"
/>
<VnLv :label="t('customer.card.salesPerson')">
<template #value> <template #value>
<VnUserLink <VnUserLink
v-if="entity.salesPersonUser"
:name="entity.salesPersonUser?.name" :name="entity.salesPersonUser?.name"
:worker-id="entity.salesPersonFk" :worker-id="entity.salesPersonFk"
/> />
<span v-else>-</span>
</template> </template>
</VnLv> </VnLv>
<VnLv <VnLv
:label="t('customer.card.businessType')" :label="t('customer.card.businessTypeFk')"
:value="entity.businessType.description" :value="entity.businessTypeFk"
/> />
</template> </template>
<template #icons="{ entity }"> <template #icons="{ entity }">

View File

@ -63,9 +63,9 @@ const creditWarning = computed(() => {
<CardSummary ref="summary" :url="`Clients/${entityId}/summary`"> <CardSummary ref="summary" :url="`Clients/${entityId}/summary`">
<template #body="{ entity }"> <template #body="{ entity }">
<QCard class="vn-one"> <QCard class="vn-one">
<a class="header" :href="clientUrl + `basic-data`"> <a class="header header-link" :href="`#/customer/${entityId}/basic-data`">
{{ t('customer.summary.basicData') }} {{ t('customer.summary.basicData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<VnLv :label="t('customer.summary.customerId')" :value="entity.id" /> <VnLv :label="t('customer.summary.customerId')" :value="entity.id" />
<VnLv :label="t('customer.summary.name')" :value="entity.name" /> <VnLv :label="t('customer.summary.name')" :value="entity.name" />
@ -97,9 +97,12 @@ const creditWarning = computed(() => {
/> />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<a class="header" :href="clientUrl + `fiscal-data`"> <a
class="header header-link"
:href="`#/customer/${entityId}/fiscal-data`"
>
{{ t('customer.summary.fiscalAddress') }} {{ t('customer.summary.fiscalAddress') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<VnLv <VnLv
:label="t('customer.summary.socialName')" :label="t('customer.summary.socialName')"
@ -122,37 +125,58 @@ const creditWarning = computed(() => {
<VnLv :label="t('customer.summary.street')" :value="entity.street" /> <VnLv :label="t('customer.summary.street')" :value="entity.street" />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<a class="header link" :href="clientUrl + `fiscal-data`" link> <a
class="header header-link"
:href="`#/customer/${entityId}/fiscal-data`"
link
>
{{ t('customer.summary.fiscalData') }} {{ t('customer.summary.fiscalData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<VnLv <QCheckbox
:label="t('customer.summary.isEqualizated')" :label="t('customer.summary.isEqualizated')"
:value="entity.isEqualizated" v-model="entity.isEqualizated"
:disable="true"
/> />
<VnLv :label="t('customer.summary.isActive')" :value="entity.isActive" /> <QCheckbox
<VnLv :label="t('customer.summary.isActive')"
v-model="entity.isActive"
:disable="true"
/>
<QCheckbox
:label="t('customer.summary.invoiceByAddress')" :label="t('customer.summary.invoiceByAddress')"
:value="entity.hasToInvoiceByAddress" v-model="entity.hasToInvoiceByAddress"
:disable="true"
/> />
<VnLv <QCheckbox
:label="t('customer.summary.verifiedData')" :label="t('customer.summary.verifiedData')"
:value="entity.isTaxDataChecked" v-model="entity.isTaxDataChecked"
:disable="true"
/> />
<VnLv <QCheckbox
:label="t('customer.summary.hasToInvoice')" :label="t('customer.summary.hasToInvoice')"
:value="entity.hasToInvoice" v-model="entity.hasToInvoice"
:disable="true"
/> />
<VnLv <QCheckbox
:label="t('customer.summary.notifyByEmail')" :label="t('customer.summary.notifyByEmail')"
:value="entity.isToBeMailed" v-model="entity.isToBeMailed"
:disable="true"
/>
<QCheckbox
:label="t('customer.summary.vies')"
v-model="entity.isVies"
:disable="true"
/> />
<VnLv :label="t('customer.summary.vies')" :value="entity.isVies" />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<a class="header link" :href="clientUrl + `billing-data`" link> <a
class="header header-link"
:href="`#/customer/${entityId}/billing-data`"
link
>
{{ t('customer.summary.billingData') }} {{ t('customer.summary.billingData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<VnLv <VnLv
:label="t('customer.summary.payMethod')" :label="t('customer.summary.payMethod')"
@ -160,20 +184,32 @@ const creditWarning = computed(() => {
/> />
<VnLv :label="t('customer.summary.bankAccount')" :value="entity.iban" /> <VnLv :label="t('customer.summary.bankAccount')" :value="entity.iban" />
<VnLv :label="t('customer.summary.dueDay')" :value="entity.dueDay" /> <VnLv :label="t('customer.summary.dueDay')" :value="entity.dueDay" />
<VnLv :label="t('customer.summary.hasLcr')" :value="entity.hasLcr" /> <QCheckbox
<VnLv style="padding: 0"
:label="t('customer.summary.hasCoreVnl')" :label="t('customer.summary.hasLcr')"
:value="entity.hasCoreVnl" v-model="entity.hasLcr"
:disable="true"
/> />
<VnLv <QCheckbox
:label="t('customer.summary.hasCoreVnl')"
v-model="entity.hasCoreVnl"
:disable="true"
/>
<QCheckbox
:label="t('customer.summary.hasB2BVnl')" :label="t('customer.summary.hasB2BVnl')"
:value="entity.hasSepaVnl" v-model="entity.hasSepaVnl"
:disable="true"
/> />
</QCard> </QCard>
<QCard class="vn-one" v-if="entity.defaultAddress"> <QCard class="vn-one" v-if="entity.defaultAddress">
<a class="header link" :href="clientUrl + `address/index`" link> <a
class="header header-link"
:href="`#/customer/${entityId}/consignees`"
link
>
{{ t('customer.summary.consignee') }} {{ t('customer.summary.consignee') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<VnLv <VnLv
:label="t('customer.summary.addressName')" :label="t('customer.summary.addressName')"
@ -189,21 +225,22 @@ const creditWarning = computed(() => {
/> />
</QCard> </QCard>
<QCard class="vn-one" v-if="entity.account"> <QCard class="vn-one" v-if="entity.account">
<a class="header link" :href="clientUrl + `web-access`"> <a class="header header-link" :href="`#/customer/${entityId}/web-access`">
{{ t('customer.summary.webAccess') }} {{ t('customer.summary.webAccess') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<VnLv <VnLv
:label="t('customer.summary.username')" :label="t('customer.summary.username')"
:value="entity.account.name" :value="entity.account.name"
/> />
<VnLv <QCheckbox
:label="t('customer.summary.webAccess')" :label="t('customer.summary.webAccess')"
:value="entity.account.active" v-model="entity.account.active"
:disable="true"
/> />
</QCard> </QCard>
<QCard class="vn-one" v-if="entity.account"> <QCard class="vn-one" v-if="entity.account">
<div class="header"> <div class="header header-link">
{{ t('customer.summary.businessData') }} {{ t('customer.summary.businessData') }}
</div> </div>
<VnLv <VnLv
@ -231,13 +268,12 @@ const creditWarning = computed(() => {
</QCard> </QCard>
<QCard class="vn-one" v-if="entity.account"> <QCard class="vn-one" v-if="entity.account">
<a <a
class="header link" class="header header-link"
:href="`https://grafana.verdnatura.es/d/40buzE4Vk/comportamiento-pagos-clientes?orgId=1&var-clientFk=${entityId}`" :href="`https://grafana.verdnatura.es/d/40buzE4Vk/comportamiento-pagos-clientes?orgId=1&var-clientFk=${entityId}`"
link link
> >
{{ t('customer.summary.financialData') }} {{ t('customer.summary.financialData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="vn:grafana" />
<!-- Pendiente de añadir el icono <QIcon name="vn:grafana" color="primary" /> -->
</a> </a>
<VnLv <VnLv
:label="t('customer.summary.risk')" :label="t('customer.summary.risk')"

View File

@ -1,9 +1,9 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
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 VnCurrency from 'src/components/common/VnCurrency.vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@ -12,10 +12,6 @@ const props = defineProps({
required: true, required: true,
}, },
}); });
function isValidNumber(value) {
return /^(\d|\d+(\.|,)?\d+)$/.test(value);
}
</script> </script>
<template> <template>
@ -51,28 +47,9 @@ function isValidNumber(value) {
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput <VnCurrency v-model="params.amount" is-outlined />
:label="t('Amount')"
v-model="params.amount"
is-outlined
@update:model-value="
(value) => {
if (value.includes(','))
params.amount = params.amount.replace(',', '.');
}
"
:rules="[
(val) => isValidNumber(val) || !val || 'Please type a number',
]"
lazy-rules
>
<template #prepend>
<QIcon name="euro" size="sm" />
</template>
</VnInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInputDate v-model="params.from" :label="t('From')" is-outlined /> <VnInputDate v-model="params.from" :label="t('From')" is-outlined />

View File

@ -111,7 +111,7 @@ const onDataSaved = async () => {
:filter="filterBanks" :filter="filterBanks"
@on-fetch="(data) => (bankOptions = data)" @on-fetch="(data) => (bankOptions = data)"
auto-load auto-load
url="Banks" url="Accountings"
/> />
<fetch-data <fetch-data
:filter="filterClientFindOne" :filter="filterClientFindOne"

View File

@ -36,9 +36,12 @@ onMounted(async () => {
</template> </template>
<template #body="{ entity: department }"> <template #body="{ entity: department }">
<QCard class="column"> <QCard class="column">
<a class="header" :href="department + `basic-data`"> <a
class="header header-link"
:href="`#/department/department/${entityId}/basic-data`"
>
{{ t('Basic data') }} {{ t('Basic data') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<div class="full-width row wrap justify-between content-between"> <div class="full-width row wrap justify-between content-between">
<div class="column" style="min-width: 50%"> <div class="column" style="min-width: 50%">

View File

@ -7,6 +7,7 @@ import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import EntryDescriptor from './EntryDescriptor.vue'; import EntryDescriptor from './EntryDescriptor.vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import useCardSize from 'src/composables/useCardSize';
const { t } = useI18n(); const { t } = useI18n();
const stateStore = useStateStore(); const stateStore = useStateStore();
@ -33,7 +34,9 @@ const stateStore = useStateStore();
<QPage> <QPage>
<VnSubToolbar /> <VnSubToolbar />
<div class="q-pa-md"><RouterView></RouterView></div> <div :class="useCardSize()">
<RouterView></RouterView>
</div>
</QPage> </QPage>
</QPageContainer> </QPageContainer>
</template> </template>

View File

@ -50,25 +50,23 @@ onMounted(() => {
:key="index" :key="index"
class="row q-gutter-md q-mb-md" class="row q-gutter-md q-mb-md"
> >
<div class="col-3"> <VnSelectFilter
<VnSelectFilter :label="t('entry.notes.observationType')"
:label="t('entry.notes.observationType')" v-model="row.observationTypeFk"
v-model="row.observationTypeFk" :options="entryObservationsOptions"
:options="entryObservationsOptions" :disable="!!row.id"
:disable="!!row.id" option-label="description"
option-label="description" option-value="id"
option-value="id" hide-selected
hide-selected />
/>
</div> <VnInput
<div class="col"> :label="t('globals.description')"
<VnInput v-model="row.description"
:label="t('globals.description')" :rules="validate('EntryObservation.description')"
v-model="row.description" />
:rules="validate('EntryObservation.description')"
/> <div class="row justify-center items-center">
</div>
<div class="col-1 row justify-center items-center">
<QIcon <QIcon
name="delete" name="delete"
size="sm" size="sm"
@ -82,19 +80,17 @@ onMounted(() => {
</QIcon> </QIcon>
</div> </div>
</VnRow> </VnRow>
<VnRow> <QIcon
<QIcon name="add"
name="add" size="sm"
size="sm" class="cursor-pointer"
class="cursor-pointer" color="primary"
color="primary" @click="entryObservationsRef.insert()"
@click="entryObservationsRef.insert()" >
> <QTooltip>
<QTooltip> {{ t('Add note') }}
{{ t('Add note') }} </QTooltip>
</QTooltip> </QIcon>
</QIcon>
</VnRow>
</QCard> </QCard>
</template> </template>
</CrudModel> </CrudModel>

View File

@ -39,30 +39,47 @@ onMounted(async () => {
const tableColumnComponents = { const tableColumnComponents = {
quantity: { quantity: {
component: () => 'span', component: () => 'span',
props: () => {},
}, },
stickers: { stickers: {
component: () => 'span', component: () => 'span',
props: () => {},
event: () => {},
}, },
packagingFk: { packagingFk: {
component: () => 'span', component: () => 'span',
props: () => {},
event: () => {},
}, },
weight: { weight: {
component: () => 'span', component: () => 'span',
props: () => {},
event: () => {},
}, },
packing: { packing: {
component: () => 'span', component: () => 'span',
props: () => {},
event: () => {},
}, },
grouping: { grouping: {
component: () => 'span', component: () => 'span',
props: () => {},
event: () => {},
}, },
buyingValue: { buyingValue: {
component: () => 'span', component: () => 'span',
props: () => {},
event: () => {},
}, },
amount: { amount: {
component: () => 'span', component: () => 'span',
props: () => {},
event: () => {},
}, },
pvp: { pvp: {
component: () => 'span', component: () => 'span',
props: () => {},
event: () => {},
}, },
}; };
@ -148,7 +165,7 @@ const fetchEntryBuys = async () => {
@on-fetch="(data) => setEntryData(data)" @on-fetch="(data) => setEntryData(data)"
> >
<template #header-left> <template #header-left>
<a class="header link" :href="entryUrl"> <a class="header-link" :href="entryUrl">
<QIcon name="open_in_new" color="white" size="sm" /> <QIcon name="open_in_new" color="white" size="sm" />
</a> </a>
</template> </template>
@ -158,133 +175,104 @@ const fetchEntryBuys = async () => {
<template #body> <template #body>
<QCard class="vn-one"> <QCard class="vn-one">
<a class="header link" :href="entryUrl"> <a class="header header-link" :href="`#/entry/${entityId}/basic-data`">
{{ t('globals.summary.basicData') }} {{ t('globals.summary.basicData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<VnRow>
<div class="col"> <VnLv :label="t('entry.summary.commission')" :value="entry.commission" />
<VnLv
:label="t('entry.summary.commission')" <VnLv :label="t('entry.summary.currency')" :value="entry.currency.name" />
:value="entry.commission"
/> <VnLv :label="t('entry.summary.company')" :value="entry.company.code" />
</div>
<div class="col"> <VnLv :label="t('entry.summary.reference')" :value="entry.reference" />
<VnLv
:label="t('entry.summary.currency')" <VnLv
:value="entry.currency.name" :label="t('entry.summary.invoiceNumber')"
/> :value="entry.invoiceNumber"
</div> />
<div class="col"> <QCheckbox
<VnLv :label="t('entry.summary.ordered')"
:label="t('entry.summary.company')" v-model="entry.isOrdered"
:value="entry.company.code" :disable="true"
/> />
</div> <QCheckbox
<div class="col"> :label="t('entry.summary.confirmed')"
<VnLv v-model="entry.isConfirmed"
:label="t('entry.summary.reference')" :disable="true"
:value="entry.reference" />
/> <QCheckbox
</div> :label="t('entry.summary.booked')"
<div class="col"> v-model="entry.isBooked"
<VnLv :disable="true"
:label="t('entry.summary.invoiceNumber')" />
:value="entry.invoiceNumber" <QCheckbox
/> :label="t('entry.summary.raid')"
</div> v-model="entry.isRaid"
<div class="col"> :disable="true"
<VnLv />
:label="t('entry.summary.ordered')" <QCheckbox
:value="entry.isOrdered" :label="t('entry.summary.excludedFromAvailable')"
/> v-model="entry.isExcludedFromAvailable"
</div> :disable="true"
<div class="col"> />
<VnLv
:label="t('entry.summary.confirmed')"
:value="entry.isConfirmed"
/>
</div>
<div class="col">
<VnLv
:label="t('entry.summary.booked')"
:value="entry.isBooked"
/>
</div>
<div class="col">
<VnLv :label="t('entry.summary.raid')" :value="entry.isRaid" />
</div>
<div class="col">
<VnLv
:label="t('entry.summary.excludedFromAvailable')"
:value="entry.isExcludedFromAvailable"
/>
</div>
</VnRow>
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<a class="header link" :href="entryUrl"> <a class="header header-link" :href="entryUrl">
{{ t('Travel data') }} {{ t('Travel data') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<VnRow>
<div class="col"> <VnLv :label="t('entry.summary.travelReference')">
<VnLv :label="t('entry.summary.travelReference')"> <template #value>
<template #value> <span class="link">
<span class="link"> {{ entry.travel.ref }}
{{ entry.travel.ref }} <TravelDescriptorProxy :id="entry.travel.id" />
<TravelDescriptorProxy :id="entry.travel.id" /> </span>
</span> </template>
</template> </VnLv>
</VnLv>
</div> <VnLv
<div class="col"> :label="t('entry.summary.travelAgency')"
<VnLv :value="entry.travel.agency.name"
:label="t('entry.summary.travelAgency')" />
:value="entry.travel.agency.name"
/> <VnLv
</div> :label="t('entry.summary.travelShipped')"
<div class="col"> :value="toDate(entry.travel.shipped)"
<VnLv />
:label="t('entry.summary.travelShipped')"
:value="toDate(entry.travel.shipped)" <VnLv
/> :label="t('entry.summary.travelWarehouseOut')"
</div> :value="entry.travel.warehouseOut.name"
<div class="col"> />
<VnLv
:label="t('entry.summary.travelWarehouseOut')" <QCheckbox
:value="entry.travel.warehouseOut.name" :label="t('entry.summary.travelDelivered')"
/> v-model="entry.isDelivered"
</div> :disable="true"
<div class="col"> />
<VnLv <VnLv
:label="t('entry.summary.travelDelivered')" :label="t('entry.summary.travelLanded')"
:value="entry.travel.isDelivered" :value="toDate(entry.travel.landed)"
/> />
</div>
<div class="col"> <VnLv
<VnLv :label="t('entry.summary.travelWarehouseIn')"
:label="t('entry.summary.travelLanded')" :value="entry.travel.warehouseIn.name"
:value="toDate(entry.travel.landed)" />
/>
</div> <QCheckbox
<div class="col"> :label="t('entry.summary.travelReceived')"
<VnLv v-model="entry.isReceived"
:label="t('entry.summary.travelWarehouseIn')" :disable="true"
:value="entry.travel.warehouseIn.name" />
/>
</div>
<div class="col">
<VnLv
:label="t('entry.summary.travelReceived')"
:value="entry.travel.isReceived"
/>
</div>
</VnRow>
</QCard> </QCard>
<QCard class="vn-two" style="min-width: 100%"> <QCard class="vn-two" style="min-width: 100%">
<a class="header"> <a class="header header-link">
{{ t('entry.summary.buys') }} {{ t('entry.summary.buys') }}
<QIcon name="open_in_new" />
</a> </a>
<QTable <QTable
:rows="entryBuys" :rows="entryBuys"
@ -297,7 +285,10 @@ const fetchEntryBuys = async () => {
<QTr no-hover> <QTr no-hover>
<QTd v-for="col in cols" :key="col.name"> <QTd v-for="col in cols" :key="col.name">
<component <component
:is="tableColumnComponents[col.name].component()" :is="tableColumnComponents[col.name].component(props)"
v-bind="tableColumnComponents[col.name].props(props)"
@click="tableColumnComponents[col.name].event(props)"
class="col-content"
> >
<template <template
v-if=" v-if="

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { onMounted, ref, computed } from 'vue'; import { onMounted, ref, computed, reactive, onUnmounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@ -8,11 +8,16 @@ import FetchedTags from 'components/ui/FetchedTags.vue';
import EntryDescriptorProxy from './Card/EntryDescriptorProxy.vue'; import EntryDescriptorProxy from './Card/EntryDescriptorProxy.vue';
import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue'; import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue';
import EditTableCellValueForm from 'src/components/EditTableCellValueForm.vue'; import EditTableCellValueForm from 'src/components/EditTableCellValueForm.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import EntryLatestBuysFilter from './EntryLatestBuysFilter.vue';
import ItemDescriptorProxy from '../Item/Card/ItemDescriptorProxy.vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { toDate, toCurrency } from 'src/filters'; import { toDate, toCurrency } from 'src/filters';
import { useSession } from 'composables/useSession'; import { useSession } from 'composables/useSession';
import { dashIfEmpty } from 'src/filters'; import { dashIfEmpty } from 'src/filters';
import { useArrayData } from 'composables/useArrayData';
const router = useRouter(); const router = useRouter();
const session = useSession(); const session = useSession();
@ -21,11 +26,72 @@ const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const rowsFetchDataRef = ref(null); const rowsFetchDataRef = ref(null);
const itemTypesOptions = ref([]);
const originsOptions = ref([]);
const itemFamiliesOptions = ref([]);
const intrastatOptions = ref([]);
const packagingsOptions = ref([]);
const editTableCellDialogRef = ref(null); const editTableCellDialogRef = ref(null);
const visibleColumns = ref([]); const visibleColumns = ref([]);
const allColumnNames = ref([]); const allColumnNames = ref([]);
const rows = ref([]);
const exprBuilder = (param, value) => {
switch (param) {
case 'id':
case 'size':
case 'weightByPiece':
case 'isActive':
case 'family':
case 'minPrice':
case 'packingOut':
return { [`i.${param}`]: value };
case 'name':
case 'description':
return { [`i.${param}`]: { like: `%${value}%` } };
case 'code':
return { 'it.code': value };
case 'intrastat':
return { 'intr.description': value };
case 'origin':
return { 'ori.code': value };
case 'landing':
return { [`lb.${param}`]: value };
case 'packing':
case 'grouping':
case 'quantity':
case 'entryFk':
case 'buyingValue':
case 'freightValue':
case 'comissionValue':
case 'packageValue':
case 'isIgnored':
case 'price2':
case 'price3':
case 'ektFk':
case 'weight':
case 'packagingFk':
return { [`b.${param}`]: value };
}
};
const params = reactive({});
const arrayData = useArrayData('EntryLatestBuys', {
url: 'Buys/latestBuysFilter',
order: ['itemFk DESC'],
exprBuilder: exprBuilder,
});
const store = arrayData.store;
const rows = computed(() => store.data);
const rowsSelected = ref([]); const rowsSelected = ref([]);
const getInputEvents = (col) => {
return col.columnFilter.type === 'select'
? { 'update:modelValue': () => applyColumnFilter(col) }
: {
'keyup.enter': () => applyColumnFilter(col),
};
};
const columns = computed(() => [ const columns = computed(() => [
{ {
label: t('entry.latestBuys.picture'), label: t('entry.latestBuys.picture'),
@ -37,12 +103,32 @@ const columns = computed(() => [
name: 'itemFk', name: 'itemFk',
field: 'itemFk', field: 'itemFk',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.packing'), label: t('entry.latestBuys.packing'),
field: 'packing', field: 'packing',
name: 'packing', name: 'packing',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val), format: (val) => dashIfEmpty(val),
}, },
{ {
@ -50,6 +136,16 @@ const columns = computed(() => [
field: 'grouping', field: 'grouping',
name: 'grouping', name: 'grouping',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val), format: (val) => dashIfEmpty(val),
}, },
{ {
@ -57,12 +153,32 @@ const columns = computed(() => [
field: 'quantity', field: 'quantity',
name: 'quantity', name: 'quantity',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('globals.description'), label: t('globals.description'),
field: 'description', field: 'description',
name: 'description', name: 'description',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val), format: (val) => dashIfEmpty(val),
}, },
{ {
@ -70,35 +186,104 @@ const columns = computed(() => [
field: 'size', field: 'size',
name: 'size', name: 'size',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.tags'), label: t('entry.latestBuys.tags'),
name: 'tags', name: 'tags',
align: 'left', align: 'left',
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.type'), label: t('entry.latestBuys.type'),
field: 'code', field: 'code',
name: 'type', name: 'type',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnSelectFilter,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
options: itemTypesOptions.value,
'option-value': 'code',
'option-label': 'code',
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.intrastat'), label: t('entry.latestBuys.intrastat'),
field: 'intrastat', field: 'intrastat',
name: 'intrastat', name: 'intrastat',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnSelectFilter,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
options: intrastatOptions.value,
'option-value': 'description',
'option-label': 'description',
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.origin'), label: t('entry.latestBuys.origin'),
field: 'origin', field: 'origin',
name: 'origin', name: 'origin',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnSelectFilter,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
options: originsOptions.value,
'option-value': 'code',
'option-label': 'code',
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.weightByPiece'), label: t('entry.latestBuys.weightByPiece'),
field: 'weightByPiece', field: 'weightByPiece',
name: 'weightByPiece', name: 'weightByPiece',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val), format: (val) => dashIfEmpty(val),
}, },
{ {
@ -106,24 +291,67 @@ const columns = computed(() => [
field: 'isActive', field: 'isActive',
name: 'isActive', name: 'isActive',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.family'), label: t('entry.latestBuys.family'),
field: 'family', field: 'family',
name: 'family', name: 'family',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnSelectFilter,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
options: itemFamiliesOptions.value,
'option-value': 'code',
'option-label': 'code',
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.entryFk'), label: t('entry.latestBuys.entryFk'),
field: 'entryFk', field: 'entryFk',
name: 'entryFk', name: 'entryFk',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.buyingValue'), label: t('entry.latestBuys.buyingValue'),
field: 'buyingValue', field: 'buyingValue',
name: 'buyingValue', name: 'buyingValue',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val), format: (val) => toCurrency(val),
}, },
{ {
@ -131,6 +359,16 @@ const columns = computed(() => [
field: 'freightValue', field: 'freightValue',
name: 'freightValue', name: 'freightValue',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val), format: (val) => toCurrency(val),
}, },
{ {
@ -138,6 +376,16 @@ const columns = computed(() => [
field: 'comissionValue', field: 'comissionValue',
name: 'comissionValue', name: 'comissionValue',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val), format: (val) => toCurrency(val),
}, },
{ {
@ -145,6 +393,16 @@ const columns = computed(() => [
field: 'packageValue', field: 'packageValue',
name: 'packageValue', name: 'packageValue',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val), format: (val) => toCurrency(val),
}, },
{ {
@ -152,12 +410,33 @@ const columns = computed(() => [
field: 'isIgnored', field: 'isIgnored',
name: 'isIgnored', name: 'isIgnored',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.price2'), label: t('entry.latestBuys.price2'),
field: 'price2', field: 'price2',
name: 'price2', name: 'price2',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val), format: (val) => toCurrency(val),
}, },
{ {
@ -165,6 +444,16 @@ const columns = computed(() => [
field: 'price3', field: 'price3',
name: 'price3', name: 'price3',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val), format: (val) => toCurrency(val),
}, },
{ {
@ -172,6 +461,16 @@ const columns = computed(() => [
field: 'minPrice', field: 'minPrice',
name: 'minPrice', name: 'minPrice',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val), format: (val) => toCurrency(val),
}, },
{ {
@ -179,6 +478,16 @@ const columns = computed(() => [
field: 'ektFk', field: 'ektFk',
name: 'ektFk', name: 'ektFk',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val), format: (val) => dashIfEmpty(val),
}, },
{ {
@ -186,18 +495,51 @@ const columns = computed(() => [
field: 'weight', field: 'weight',
name: 'weight', name: 'weight',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.packagingFk'), label: t('entry.latestBuys.packagingFk'),
field: 'packagingFk', field: 'packagingFk',
name: 'packagingFk', name: 'packagingFk',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnSelectFilter,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
options: packagingsOptions.value,
'option-value': 'id',
'option-label': 'id',
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.packingOut'), label: t('entry.latestBuys.packingOut'),
field: 'packingOut', field: 'packingOut',
name: 'packingOut', name: 'packingOut',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val), format: (val) => dashIfEmpty(val),
}, },
{ {
@ -205,6 +547,16 @@ const columns = computed(() => [
field: 'landing', field: 'landing',
name: 'landing', name: 'landing',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toDate(val), format: (val) => toDate(val),
}, },
]); ]);
@ -234,20 +586,55 @@ const redirectToEntryBuys = (entryFk) => {
router.push({ name: 'EntryBuys', params: { id: entryFk } }); router.push({ name: 'EntryBuys', params: { id: entryFk } });
}; };
const applyColumnFilter = async (col) => {
try {
params[col.field] = col.columnFilter.filterValue;
await arrayData.addFilter({ params });
} catch (err) {
console.error('Error applying column filter', err);
}
};
onMounted(async () => { onMounted(async () => {
stateStore.rightDrawer = true; stateStore.rightDrawer = true;
const filteredColumns = columns.value.filter((col) => col.name !== 'picture'); const filteredColumns = columns.value.filter((col) => col.name !== 'picture');
allColumnNames.value = filteredColumns.map((col) => col.name); allColumnNames.value = filteredColumns.map((col) => col.name);
await arrayData.fetch({ append: false });
}); });
onUnmounted(() => (stateStore.rightDrawer = false));
</script> </script>
<template> <template>
<FetchData <FetchData
ref="rowsFetchDataRef" url="ItemTypes"
url="Buys/latestBuysFilter" :filter="{ fields: ['code'], order: 'code ASC', limit: 30 }"
:filter="{ order: 'itemFk DESC', limit: 20 }"
@on-fetch="(data) => (rows = data)"
auto-load auto-load
@on-fetch="(data) => (itemTypesOptions = data)"
/>
<FetchData
url="Origins"
:filter="{ fields: ['code'], order: 'code ASC', limit: 30 }"
auto-load
@on-fetch="(data) => (originsOptions = data)"
/>
<FetchData
url="ItemFamilies"
:filter="{ fields: ['code'], order: 'code ASC', limit: 30 }"
auto-load
@on-fetch="(data) => (itemFamiliesOptions = data)"
/>
<FetchData
url="Packagings"
:filter="{ fields: ['id'], order: 'id ASC', limit: 30 }"
auto-load
@on-fetch="(data) => (packagingsOptions = data)"
/>
<FetchData
url="Intrastats"
:filter="{ fields: ['description'], order: 'description ASC', limit: 30 }"
auto-load
@on-fetch="(data) => (intrastatOptions = data)"
/> />
<QToolbar class="bg-vn-dark justify-end"> <QToolbar class="bg-vn-dark justify-end">
<div id="st-data"> <div id="st-data">
@ -261,19 +648,43 @@ onMounted(async () => {
<QSpace /> <QSpace />
<div id="st-actions"></div> <div id="st-actions"></div>
</QToolbar> </QToolbar>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<EntryLatestBuysFilter data-key="EntryLatestBuys" :tags="tags" />
</QScrollArea>
</QDrawer>
<QPage class="column items-center q-pa-md"> <QPage class="column items-center q-pa-md">
<QTable <QTable
:rows="rows" :rows="rows"
:columns="columns" :columns="columns"
hide-bottom
selection="multiple" selection="multiple"
row-key="id" row-key="id"
:pagination="{ rowsPerPage: 0 }" :pagination="{ rowsPerPage: 0 }"
class="full-width q-mt-md" class="full-width q-mt-md"
:visible-columns="visibleColumns" :visible-columns="visibleColumns"
v-model:selected="rowsSelected" v-model:selected="rowsSelected"
:no-data-label="t('globals.noResults')"
@row-click="(_, row) => redirectToEntryBuys(row.entryFk)" @row-click="(_, row) => redirectToEntryBuys(row.entryFk)"
> >
<template #top-row="{ cols }">
<QTr>
<QTd />
<QTd
v-for="(col, index) in cols"
:key="index"
style="max-width: 100px"
>
<component
:is="col.columnFilter.component"
v-if="col.name !== 'picture'"
v-model="col.columnFilter.filterValue"
v-bind="col.columnFilter.attrs"
v-on="col.columnFilter.event(col)"
dense
/>
</QTd>
</QTr>
</template>
<template #body-cell-picture="{ row }"> <template #body-cell-picture="{ row }">
<QTd> <QTd>
<QImg <QImg
@ -288,9 +699,10 @@ onMounted(async () => {
</template> </template>
<template #body-cell-itemFk="{ row }"> <template #body-cell-itemFk="{ row }">
<QTd @click.stop> <QTd @click.stop>
<QBtn flat color="blue"> <QBtn flat color="primary">
{{ row.itemFk }} {{ row.itemFk }}
</QBtn> </QBtn>
<ItemDescriptorProxy :id="row.itemFk" />
</QTd> </QTd>
</template> </template>
<template #body-cell-tags="{ row }"> <template #body-cell-tags="{ row }">
@ -300,7 +712,7 @@ onMounted(async () => {
</template> </template>
<template #body-cell-entryFk="{ row }"> <template #body-cell-entryFk="{ row }">
<QTd @click.stop> <QTd @click.stop>
<QBtn flat color="blue"> <QBtn flat color="primary">
<EntryDescriptorProxy :id="row.entryFk" /> <EntryDescriptorProxy :id="row.entryFk" />
{{ row.entryFk }} {{ row.entryFk }}
</QBtn> </QBtn>

View File

@ -0,0 +1,474 @@
<script setup>
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import VnInput from 'components/common/VnInput.vue';
import FetchData from 'components/FetchData.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
import axios from 'axios';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const itemCategories = ref([]);
const selectedCategoryFk = ref(null);
const selectedTypeFk = ref(null);
const itemTypesOptions = ref([]);
const itemTypeWorkersOptions = ref([]);
const suppliersOptions = ref([]);
const tagOptions = ref([]);
const tagValues = ref([]);
const categoryList = computed(() => {
return (itemCategories.value || [])
.filter((category) => category.display)
.map((category) => ({
...category,
icon: `vn:${(category.icon || '').split('-')[1]}`,
}));
});
const selectedCategory = computed(() =>
(itemCategories.value || []).find(
(category) => category?.id === selectedCategoryFk.value
)
);
const selectedType = computed(() => {
return (itemTypesOptions.value || []).find(
(type) => type?.id === selectedTypeFk.value
);
});
const selectCategory = async (params, categoryId, search) => {
if (params.categoryFk === categoryId) {
resetCategory(params);
search();
return;
}
selectedCategoryFk.value = categoryId;
params.categoryFk = categoryId;
await fetchItemTypes(categoryId);
search();
};
const resetCategory = (params) => {
selectedCategoryFk.value = null;
itemTypesOptions.value = null;
if (params) {
params.categoryFk = null;
params.typeFk = null;
}
};
const applyTags = (params, search) => {
params.tags = tagValues.value
.filter((tag) => tag.selectedTag && tag.value)
.map((tag) => ({
tagFk: tag.selectedTag.id,
tagName: tag.selectedTag.name,
value: tag.value,
}));
search();
};
const fetchItemTypes = async (id) => {
try {
const filter = {
fields: ['id', 'name', 'categoryFk'],
where: { categoryFk: id },
include: 'category',
order: 'name ASC',
};
const { data } = await axios.get('ItemTypes', {
params: { filter: JSON.stringify(filter) },
});
itemTypesOptions.value = data;
} catch (err) {
console.error('Error fetching item types', err);
}
};
const getCategoryClass = (category, params) => {
if (category.id === params?.categoryFk) {
return 'active';
}
};
const getSelectedTagValues = async (tag) => {
try {
tag.value = null;
const filter = {
fields: ['value'],
order: 'value ASC',
limit: 30,
};
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get(`Tags/${tag.selectedTag.id}/filterValue`, {
params,
});
tag.valueOptions = data;
} catch (err) {
console.error('Error getting selected tag values');
}
};
const removeTag = (index, params, search) => {
(tagValues.value || []).splice(index, 1);
applyTags(params, search);
};
</script>
<template>
<FetchData
url="ItemCategories"
limit="30"
auto-load
@on-fetch="(data) => (itemCategories = data)"
/>
<FetchData
url="TicketRequests/getItemTypeWorker"
limit="30"
auto-load
:filter="{ fields: ['id', 'nickname'], order: 'nickname ASC', limit: 30 }"
@on-fetch="(data) => (itemTypeWorkersOptions = data)"
/>
<FetchData
url="Suppliers"
limit="30"
auto-load
:filter="{ fields: ['id', 'name', 'nickname'], order: 'name ASC', limit: 30 }"
@on-fetch="(data) => (suppliersOptions = data)"
/>
<FetchData
url="Tags"
:filter="{ fields: ['id', 'name', 'isFree'] }"
auto-load
limit="30"
@on-fetch="(data) => (tagOptions = data)"
/>
<VnFilterPanel
:data-key="props.dataKey"
:expr-builder="exprBuilder"
:custom-tags="['tags']"
@init="onFilterInit"
@remove="clearFilter"
>
<template #tags="{ tag, formatFn }">
<strong v-if="tag.label === 'categoryFk'">
{{ t(selectedCategory?.name || '') }}
</strong>
<strong v-else-if="tag.label === 'typeFk'">
{{ t(selectedType?.name || '') }}
</strong>
<div v-else class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #customTags="{ tags: customTags, params }">
<template v-for="tag in customTags" :key="tag.label">
<VnFilterPanelChip
v-for="chip in tag.value"
:key="chip"
removable
@remove="removeTagChip(chip, params, searchFn)"
>
<div class="q-gutter-x-xs">
<strong>{{ chip.tagName }}: </strong>
<span>"{{ chip.value }}"</span>
</div>
</VnFilterPanelChip>
</template>
</template>
<template #body="{ params, searchFn }">
<QItem class="category-filter q-mt-md">
<QBtn
dense
flat
round
v-for="category in categoryList"
:key="category.name"
:class="['category', getCategoryClass(category, params)]"
:icon="category.icon"
@click="selectCategory(params, category.id, searchFn)"
>
<QTooltip>
{{ t(category.name) }}
</QTooltip>
</QBtn>
</QItem>
<QItem class="q-my-md">
<QItemSection>
<VnSelectFilter
:label="t('params.typeFk')"
v-model="params.typeFk"
:options="itemTypesOptions"
option-value="id"
option-label="name"
dense
outlined
rounded
use-input
:disable="!selectedCategoryFk"
@update:model-value="
(value) => {
selectedTypeFk = value;
searchFn();
}
"
>
<template #option="{ itemProps, opt }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel>{{ opt.name }}</QItemLabel>
<QItemLabel caption>
{{ opt.categoryName }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</QItemSection>
</QItem>
<QSeparator />
<QItem class="q-my-md">
<QItemSection>
<VnSelectFilter
:label="t('params.salesPersonFk')"
v-model="params.salesPersonFk"
:options="itemTypeWorkersOptions"
option-value="id"
option-label="nickname"
dense
outlined
rounded
use-input
@update:model-value="searchFn()"
/>
</QItemSection>
</QItem>
<QItem class="q-my-md">
<QItemSection>
<VnSelectFilter
:label="t('params.supplier')"
v-model="params.supplierFk"
:options="suppliersOptions"
option-value="id"
option-label="name"
dense
outlined
rounded
use-input
@update:model-value="searchFn()"
>
<template #option="{ itemProps, opt }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel>{{ opt.name }}</QItemLabel>
<QItemLabel caption>
{{ opt.nickname }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</QItemSection>
</QItem>
<QItem class="q-my-md">
<QItemSection>
<VnInputDate
:label="t('params.from')"
v-model="params.from"
is-outlined
@update:model-value="searchFn()"
/>
</QItemSection>
</QItem>
<QItem class="q-my-md">
<QItemSection>
<VnInputDate
:label="t('params.to')"
v-model="params.to"
is-outlined
@update:model-value="searchFn()"
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QCheckbox
:label="t('params.active')"
v-model="params.active"
toggle-indeterminate
@update:model-value="searchFn()"
/>
</QItemSection>
<QItemSection>
<QCheckbox
:label="t('params.visible')"
v-model="params.visible"
toggle-indeterminate
@update:model-value="searchFn()"
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QCheckbox
:label="t('params.floramondo')"
v-model="params.floramondo"
toggle-indeterminate
@update:model-value="searchFn()"
/>
</QItemSection>
</QItem>
<QItem
v-for="(value, index) in tagValues"
:key="value"
class="q-mt-md filter-value"
>
<QItemSection class="col">
<VnSelectFilter
:label="t('params.tag')"
v-model="value.selectedTag"
:options="tagOptions"
option-label="name"
dense
outlined
rounded
:emit-value="false"
use-input
:is-clearable="false"
@update:model-value="getSelectedTagValues(value)"
/>
</QItemSection>
<QItemSection class="col">
<VnSelectFilter
v-if="!value?.selectedTag?.isFree && value.valueOptions"
:label="t('params.value')"
v-model="value.value"
:options="value.valueOptions || []"
option-value="value"
option-label="value"
dense
outlined
rounded
emit-value
use-input
:disable="!value"
:is-clearable="false"
class="filter-input"
@update:model-value="applyTags(params, searchFn)"
/>
<VnInput
v-else
v-model="value.value"
:label="t('params.value')"
:disable="!value"
is-outlined
class="filter-input"
:is-clearable="false"
@keyup.enter="applyTags(params, searchFn)"
/>
</QItemSection>
<QIcon
name="delete"
class="filter-icon"
@click="removeTag(index, params, searchFn)"
/>
</QItem>
<QItem class="q-mt-lg">
<QIcon
name="add_circle"
class="filter-icon"
@click="tagValues.push({})"
/>
</QItem>
</template>
</VnFilterPanel>
</template>
<style lang="scss" scoped>
.category-filter {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
.category {
padding: 8px;
width: 60px;
height: 60px;
font-size: 1.4rem;
background-color: var(--vn-light-gray);
&.active {
background-color: $primary;
}
}
}
.filter-icon {
font-size: 24px;
color: $primary;
padding: 0 4px;
cursor: pointer;
}
.filter-input {
flex-shrink: 1;
min-width: 0;
}
.filter-value {
display: flex;
align-items: center;
}
</style>
<i18n>
en:
params:
supplier: Supplier
from: From
to: To
active: Is active
visible: Is visible
floramondo: Is floramondo
salesPersonFk: Buyer
categoryFk: Category
typeFk: Type
tag: Tag
value: Value
es:
params:
supplier: Proveedor
from: Desde
to: Hasta
active: Activo
visible: Visible
floramondo: Floramondo
salesPersonFk: Comprador
categoryFk: Categoría
typeFk: Tipo
tag: Etiqueta
value: Valor
Plant: Planta
Flower: Flor
Handmade: Confección
Green: Verde
Accessories: Complemento
Fruit: Fruta
</i18n>

View File

@ -8,6 +8,7 @@ import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { onMounted, watch } from 'vue'; import { onMounted, watch } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import useCardSize from 'src/composables/useCardSize';
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
@ -74,7 +75,9 @@ watch(
<QPageContainer> <QPageContainer>
<QPage> <QPage>
<VnSubToolbar /> <VnSubToolbar />
<div class="q-pa-md"><RouterView></RouterView></div> <div :class="useCardSize()">
<RouterView></RouterView>
</div>
</QPage> </QPage>
</QPageContainer> </QPageContainer>
</template> </template>

View File

@ -8,6 +8,7 @@ import { useArrayData } from 'src/composables/useArrayData';
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 VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import VnCurrency from 'src/components/common/VnCurrency.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@ -74,7 +75,12 @@ async function insert() {
} }
</script> </script>
<template> <template>
<FetchData url="Banks" auto-load limit="30" @on-fetch="(data) => (banks = data)" /> <FetchData
url="Accountings"
auto-load
limit="30"
@on-fetch="(data) => (banks = data)"
/>
<CrudModel <CrudModel
v-if="invoiceIn" v-if="invoiceIn"
ref="invoiceInFormRef" ref="invoiceInFormRef"
@ -158,7 +164,12 @@ async function insert() {
</template> </template>
<template #body-cell-amount="{ row }"> <template #body-cell-amount="{ row }">
<QTd> <QTd>
<QInput v-model="row.amount" clearable clear-icon="close" /> <VnCurrency
v-model="row.amount"
:is-outlined="false"
clearable
clear-icon="close"
/>
</QTd> </QTd>
</template> </template>
<template #body-cell-foreignvalue="{ row }"> <template #body-cell-foreignvalue="{ row }">

View File

@ -209,9 +209,9 @@ function getLink(param) {
<!--Basic Data--> <!--Basic Data-->
<QCard class="vn-one"> <QCard class="vn-one">
<QCardSection class="q-pa-none"> <QCardSection class="q-pa-none">
<a class="header" :href="getLink('basic-data')"> <a class="header header-link" :href="getLink('basic-data')">
{{ t('invoiceIn.pageTitles.basicData') }} {{ t('invoiceIn.pageTitles.basicData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
</QCardSection> </QCardSection>
<VnLv <VnLv
@ -233,9 +233,9 @@ function getLink(param) {
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<QCardSection class="q-pa-none"> <QCardSection class="q-pa-none">
<a class="header" :href="getLink('basic-data')"> <a class="header header-link" :href="getLink('basic-data')">
{{ t('invoiceIn.pageTitles.basicData') }} {{ t('invoiceIn.pageTitles.basicData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
</QCardSection> </QCardSection>
<VnLv <VnLv
@ -258,9 +258,9 @@ function getLink(param) {
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<QCardSection class="q-pa-none"> <QCardSection class="q-pa-none">
<a class="header" :href="getLink('basic-data')"> <a class="header header-link" :href="getLink('basic-data')">
{{ t('invoiceIn.pageTitles.basicData') }} {{ t('invoiceIn.pageTitles.basicData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
</QCardSection> </QCardSection>
<VnLv <VnLv
@ -275,16 +275,17 @@ function getLink(param) {
:label="t('invoiceIn.summary.company')" :label="t('invoiceIn.summary.company')"
:value="invoiceIn.company?.code" :value="invoiceIn.company?.code"
/> />
<VnLv <QCheckbox
:label="t('invoiceIn.summary.booked')" :label="t('invoiceIn.summary.booked')"
:value="invoiceIn.isBooked" v-model="invoiceIn.isBooked"
:disable="true"
/> />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<QCardSection class="q-pa-none"> <QCardSection class="q-pa-none">
<a class="header" :href="getLink('basic-data')"> <a class="header header-link" :href="getLink('basic-data')">
{{ t('invoiceIn.pageTitles.basicData') }} {{ t('invoiceIn.pageTitles.basicData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
</QCardSection> </QCardSection>
<QCardSection class="q-pa-none"> <QCardSection class="q-pa-none">
@ -318,9 +319,9 @@ function getLink(param) {
</QCard> </QCard>
<!--Vat--> <!--Vat-->
<QCard v-if="invoiceIn.invoiceInTax.length"> <QCard v-if="invoiceIn.invoiceInTax.length">
<a class="header" :href="getLink('vat')"> <a class="header header-link" :href="getLink('vat')">
{{ t('invoiceIn.card.vat') }} {{ t('invoiceIn.card.vat') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<QTable <QTable
:columns="vatColumns" :columns="vatColumns"
@ -351,9 +352,9 @@ function getLink(param) {
</QCard> </QCard>
<!--Due Day--> <!--Due Day-->
<QCard v-if="invoiceIn.invoiceInDueDay.length"> <QCard v-if="invoiceIn.invoiceInDueDay.length">
<a class="header" :href="getLink('due-day')"> <a class="header header-link" :href="getLink('due-day')">
{{ t('invoiceIn.card.dueDay') }} {{ t('invoiceIn.card.dueDay') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<QTable <QTable
class="full-width" class="full-width"
@ -381,9 +382,9 @@ function getLink(param) {
</QCard> </QCard>
<!--Intrastat--> <!--Intrastat-->
<QCard v-if="invoiceIn.invoiceInIntrastat.length"> <QCard v-if="invoiceIn.invoiceInIntrastat.length">
<a class="header" :href="getUrl('intrastat')"> <a class="header header-link" :href="getLink('intrastat')">
{{ t('invoiceIn.card.intrastat') }} {{ t('invoiceIn.card.intrastat') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<QTable <QTable
:columns="intrastatColumns" :columns="intrastatColumns"

View File

@ -9,6 +9,7 @@ import { toCurrency } from 'src/filters';
import FetchData from 'src/components/FetchData.vue'; import FetchData from 'src/components/FetchData.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import CrudModel from 'src/components/CrudModel.vue'; import CrudModel from 'src/components/CrudModel.vue';
import VnCurrency from 'src/components/common/VnCurrency.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@ -225,7 +226,7 @@ async function addExpense() {
</template> </template>
<template #body-cell-taxablebase="{ row }"> <template #body-cell-taxablebase="{ row }">
<QTd> <QTd>
<QInput <VnCurrency
:class="{ :class="{
'no-pointer-events': isNotEuro(invoiceIn.currency.code), 'no-pointer-events': isNotEuro(invoiceIn.currency.code),
}" }"
@ -234,11 +235,7 @@ async function addExpense() {
clear-icon="close" clear-icon="close"
v-model="row.taxableBase" v-model="row.taxableBase"
clearable clearable
> />
<template #prepend>
<QIcon name="euro" size="xs" flat />
</template>
</QInput>
</QTd> </QTd>
</template> </template>
<template #body-cell-sageiva="{ row, col }"> <template #body-cell-sageiva="{ row, col }">
@ -328,7 +325,7 @@ async function addExpense() {
</VnSelectFilter> </VnSelectFilter>
</QItem> </QItem>
<QItem> <QItem>
<QInput <VnCurrency
:label="t('Taxable base')" :label="t('Taxable base')"
:class="{ :class="{
'no-pointer-events': isNotEuro( 'no-pointer-events': isNotEuro(
@ -340,11 +337,7 @@ async function addExpense() {
clear-icon="close" clear-icon="close"
v-model="props.row.taxableBase" v-model="props.row.taxableBase"
clearable clearable
> />
<template #append>
<QIcon name="euro" size="xs" flat />
</template>
</QInput>
</QItem> </QItem>
<QItem> <QItem>
<VnSelectFilter <VnSelectFilter

View File

@ -8,6 +8,7 @@ import FetchData from 'components/FetchData.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 { useCapitalize } from 'src/composables/useCapitalize'; import { useCapitalize } from 'src/composables/useCapitalize';
import VnCurrency from 'src/components/common/VnCurrency.vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@ -137,16 +138,7 @@ const suppliersRef = ref();
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput <VnCurrency v-model="params.amount" is-outlined />
:label="t('Amount')"
v-model="params.amount"
is-outlined
lazy-rules
>
<template #prepend>
<QIcon name="euro" size="sm"></QIcon>
</template>
</VnInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-md"> <QItem class="q-mb-md">

View File

@ -5,6 +5,7 @@ import InvoiceOutDescriptor from './InvoiceOutDescriptor.vue';
import LeftMenu from 'components/LeftMenu.vue'; import LeftMenu from 'components/LeftMenu.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import useCardSize from 'src/composables/useCardSize';
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
@ -28,7 +29,9 @@ const { t } = useI18n();
<QPageContainer> <QPageContainer>
<QPage> <QPage>
<VnSubToolbar /> <VnSubToolbar />
<div class="q-pa-md"><RouterView></RouterView></div> <div :class="useCardSize()">
<RouterView></RouterView>
</div>
</QPage> </QPage>
</QPageContainer> </QPageContainer>
</template> </template>

View File

@ -101,9 +101,10 @@ const ticketsColumns = ref([
</template> </template>
<template #body="{ entity: { invoiceOut } }"> <template #body="{ entity: { invoiceOut } }">
<QCard class="vn-one"> <QCard class="vn-one">
<div class="header"> <a class="header header-link">
{{ t('invoiceOut.pageTitles.basicData') }} {{ t('invoiceOut.pageTitles.basicData') }}
</div> <QIcon name="open_in_new" />
</a>
<VnLv <VnLv
:label="t('invoiceOut.summary.issued')" :label="t('invoiceOut.summary.issued')"
:value="toDate(invoiceOut.issued)" :value="toDate(invoiceOut.issued)"
@ -126,9 +127,10 @@ const ticketsColumns = ref([
/> />
</QCard> </QCard>
<QCard class="vn-three"> <QCard class="vn-three">
<div class="header"> <a class="header header-link">
{{ t('invoiceOut.summary.taxBreakdown') }} {{ t('invoiceOut.summary.taxBreakdown') }}
</div> <QIcon name="open_in_new" />
</a>
<QTable :columns="taxColumns" :rows="invoiceOut.taxesBreakdown" flat> <QTable :columns="taxColumns" :rows="invoiceOut.taxesBreakdown" flat>
<template #header="props"> <template #header="props">
<QTr :props="props"> <QTr :props="props">
@ -140,9 +142,10 @@ const ticketsColumns = ref([
</QTable> </QTable>
</QCard> </QCard>
<QCard class="vn-three"> <QCard class="vn-three">
<div class="header"> <a class="header header-link">
{{ t('invoiceOut.summary.tickets') }} {{ t('invoiceOut.summary.tickets') }}
</div> <QIcon name="open_in_new" />
</a>
<QTable v-if="tickets" :columns="ticketsColumns" :rows="tickets" flat> <QTable v-if="tickets" :columns="ticketsColumns" :rows="tickets" flat>
<template #header="props"> <template #header="props">
<QTr :props="props"> <QTr :props="props">

View File

@ -6,6 +6,7 @@ import FetchData from 'components/FetchData.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 VnCurrency from 'src/components/common/VnCurrency.vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@ -57,7 +58,11 @@ function setWorkers(data) {
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput :label="t('Amount')" v-model="params.amount" is-outlined /> <VnCurrency
:label="t('Amount')"
v-model="params.amount"
is-outlined
/>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>

View File

@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n';
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 VnCurrency from 'src/components/common/VnCurrency.vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@ -84,7 +85,7 @@ const props = defineProps({
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput <VnCurrency
v-model="params.amount" v-model="params.amount"
:label="t('invoiceOut.negativeBases.amount')" :label="t('invoiceOut.negativeBases.amount')"
is-outlined is-outlined

View File

@ -4,6 +4,7 @@ import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import ItemDescriptor from './ItemDescriptor.vue'; import ItemDescriptor from './ItemDescriptor.vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import useCardSize from 'src/composables/useCardSize';
const stateStore = useStateStore(); const stateStore = useStateStore();
</script> </script>
@ -19,7 +20,9 @@ const stateStore = useStateStore();
<QPage> <QPage>
<VnSubToolbar /> <VnSubToolbar />
<div class="q-pa-md"><RouterView></RouterView></div> <div :class="useCardSize()">
<RouterView></RouterView>
</div>
</QPage> </QPage>
</QPageContainer> </QPageContainer>
</template> </template>

View File

@ -69,12 +69,12 @@ async function onSubmit() {
<template> <template>
<QForm @submit="onSubmit" class="q-gutter-y-md q-pa-lg formCard"> <QForm @submit="onSubmit" class="q-gutter-y-md q-pa-lg formCard">
<VnLogo alt="Logo" fit="contain" :ratio="16 / 9" class="q-mb-md" /> <VnLogo alt="Logo" fit="contain" :ratio="16 / 9" class="q-mb-md" />
<VnInput <VnInput
v-model="username" v-model="username"
:label="t('login.username')" :label="t('login.username')"
lazy-rules lazy-rules
:rules="[(val) => (val && val.length > 0) || t('login.fieldRequired')]" :rules="[(val) => (val && val.length > 0) || t('login.fieldRequired')]"
color="primary"
/> />
<VnInput <VnInput
type="password" type="password"
@ -82,9 +82,8 @@ async function onSubmit() {
:label="t('login.password')" :label="t('login.password')"
lazy-rules lazy-rules
:rules="[(val) => (val && val.length > 0) || t('login.fieldRequired')]" :rules="[(val) => (val && val.length > 0) || t('login.fieldRequired')]"
class="red"
/> />
<QToggle v-model="keepLogin" :label="t('login.keepLogin')" />
<div> <div>
<QBtn <QBtn
:label="t('login.submit')" :label="t('login.submit')"
@ -95,6 +94,7 @@ async function onSubmit() {
unelevated unelevated
/> />
</div> </div>
<QToggle v-model="keepLogin" :label="t('login.keepLogin')" />
</QForm> </QForm>
</template> </template>
@ -104,6 +104,9 @@ async function onSubmit() {
min-width: 300px; min-width: 300px;
} }
.q-input {
color: $primary;
}
@media (max-width: $breakpoint-xs-max) { @media (max-width: $breakpoint-xs-max) {
.formCard { .formCard {
min-width: 100%; min-width: 100%;

View File

@ -99,6 +99,8 @@ onMounted(async () => {
</i18n> </i18n>
<style lang="scss"> <style lang="scss">
$vnColor: #8ebb27;
.formCard { .formCard {
max-width: 1500px; max-width: 1500px;
min-width: 700px; min-width: 700px;

View File

@ -71,11 +71,11 @@ const filter = {
<template #body="{ entity }"> <template #body="{ entity }">
<QCard class="vn-one"> <QCard class="vn-one">
<RouterLink <RouterLink
class="header" class="header header-link"
:to="{ name: 'ShelvingBasicData', params: { id: entityId } }" :to="{ name: 'ShelvingBasicData', params: { id: entityId } }"
> >
{{ t('shelving.pageTitles.basicData') }} {{ t('shelving.pageTitles.basicData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</RouterLink> </RouterLink>
<VnLv :label="t('shelving.summary.code')" :value="entity.code" /> <VnLv :label="t('shelving.summary.code')" :value="entity.code" />
<VnLv <VnLv

View File

@ -5,6 +5,7 @@ import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import LeftMenu from 'components/LeftMenu.vue'; import LeftMenu from 'components/LeftMenu.vue';
import SupplierDescriptor from './SupplierDescriptor.vue'; import SupplierDescriptor from './SupplierDescriptor.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import useCardSize from 'src/composables/useCardSize';
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
@ -30,7 +31,9 @@ const { t } = useI18n();
<QPageContainer> <QPageContainer>
<QPage> <QPage>
<VnSubToolbar /> <VnSubToolbar />
<div class="q-pa-md"><RouterView></RouterView></div> <div :class="useCardSize()">
<RouterView></RouterView>
</div>
</QPage> </QPage>
</QPageContainer> </QPageContainer>
</template> </template>

View File

@ -52,7 +52,7 @@ const isAdministrative = computed(() => {
@on-fetch="(data) => setData(data)" @on-fetch="(data) => setData(data)"
> >
<template #header-left> <template #header-left>
<a v-if="isAdministrative" class="header link" :href="supplierUrl"> <a v-if="isAdministrative" class="header header-link" :href="supplierUrl">
<QIcon name="open_in_new" color="white" size="sm" /> <QIcon name="open_in_new" color="white" size="sm" />
</a> </a>
</template> </template>
@ -62,9 +62,13 @@ const isAdministrative = computed(() => {
<template #body> <template #body>
<QCard class="vn-one"> <QCard class="vn-one">
<a v-if="isAdministrative" class="header link" :href="supplierUrl"> <a
v-if="isAdministrative"
class="header header-link"
:href="`#/supplier/${entityId}/basic-data`"
>
{{ t('globals.summary.basicData') }} {{ t('globals.summary.basicData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<span v-else> {{ t('globals.summary.basicData') }}</span> <span v-else> {{ t('globals.summary.basicData') }}</span>
<VnLv label="Id" :value="supplier.id" /> <VnLv label="Id" :value="supplier.id" />
@ -82,32 +86,25 @@ const isAdministrative = computed(() => {
<span> {{ dashIfEmpty(supplier.note) }} </span> <span> {{ dashIfEmpty(supplier.note) }} </span>
</template> </template>
</VnLv> </VnLv>
<QCheckbox
<VnLv :label="t('supplier.summary.verified')" class="q-mb-xs"> :label="t('supplier.summary.verified')"
<template #value> v-model="supplier.isSerious"
<QCheckbox :disable="true"
v-model="supplier.isSerious" />
dense <QCheckbox
disable :label="t('supplier.summary.isActive')"
class="full-width q-mb-xs" v-model="supplier.isActive"
/> :disable="true"
</template> />
</VnLv>
<VnLv :label="t('supplier.summary.isActive')" class="q-mb-xs">
<template #value>
<QCheckbox
v-model="supplier.isActive"
dense
disable
class="full-width q-mb-xs"
/>
</template>
</VnLv>
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<a v-if="isAdministrative" class="header link" :href="supplierUrl"> <a
v-if="isAdministrative"
class="header header-link"
:href="`#/supplier/${entityId}/billing-data`"
>
{{ t('supplier.summary.billingData') }} {{ t('supplier.summary.billingData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<span v-else> {{ t('supplier.summary.billingData') }}</span> <span v-else> {{ t('supplier.summary.billingData') }}</span>
<VnLv <VnLv
@ -124,9 +121,13 @@ const isAdministrative = computed(() => {
<VnLv :label="t('supplier.summary.account')" :value="supplier.account" /> <VnLv :label="t('supplier.summary.account')" :value="supplier.account" />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<a v-if="isAdministrative" class="header link" :href="supplierUrl"> <a
v-if="isAdministrative"
class="header header-link"
:href="`#/supplier/${entityId}/fiscal-data`"
>
{{ t('supplier.summary.fiscalData') }} {{ t('supplier.summary.fiscalData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<span v-else> {{ t('supplier.summary.fiscalData') }}</span> <span v-else> {{ t('supplier.summary.fiscalData') }}</span>
<VnLv <VnLv
@ -155,9 +156,13 @@ const isAdministrative = computed(() => {
/> />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<a v-if="isAdministrative" class="header link" :href="supplierUrl"> <a
v-if="isAdministrative"
class="header header-link"
:href="`#/supplier/${entityId}/fiscal-data`"
>
{{ t('supplier.summary.fiscalAddress') }} {{ t('supplier.summary.fiscalAddress') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<span v-else> {{ t('supplier.summary.fiscalAddress') }}</span> <span v-else> {{ t('supplier.summary.fiscalAddress') }}</span>
<VnLv :label="t('supplier.summary.socialName')" :value="supplier.name" /> <VnLv :label="t('supplier.summary.socialName')" :value="supplier.name" />

View File

@ -5,6 +5,7 @@ import TicketDescriptor from './TicketDescriptor.vue';
import LeftMenu from 'components/LeftMenu.vue'; import LeftMenu from 'components/LeftMenu.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import useCardSize from 'src/composables/useCardSize';
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
@ -29,7 +30,9 @@ const { t } = useI18n();
<QPage> <QPage>
<VnSubToolbar /> <VnSubToolbar />
<div class="q-pa-md"><RouterView></RouterView></div> <div :class="useCardSize()">
<RouterView></RouterView>
</div>
</QPage> </QPage>
</QPageContainer> </QPageContainer>
</template> </template>

View File

@ -88,14 +88,6 @@ const setData = (entity) =>
<TicketDescriptorMenu :ticket="entity" /> <TicketDescriptorMenu :ticket="entity" />
</template> </template>
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv v-if="entity.ticketState" :label="t('ticket.card.state')">
<template #value>
<QBadge :color="entity.ticketState.state.classColor">
{{ entity.ticketState.state.name }}
</QBadge>
</template>
</VnLv>
<VnLv :label="t('ticket.card.shipped')" :value="toDate(entity.shipped)" />
<VnLv :label="t('ticket.card.customerId')"> <VnLv :label="t('ticket.card.customerId')">
<template #value> <template #value>
<span class="link"> <span class="link">
@ -104,6 +96,13 @@ const setData = (entity) =>
</span> </span>
</template> </template>
</VnLv> </VnLv>
<VnLv v-if="entity.ticketState" :label="t('ticket.card.state')">
<template #value>
<QBadge :color="entity.ticketState.state.classColor">
{{ entity.ticketState.state.name }}
</QBadge>
</template>
</VnLv>
<VnLv :label="t('ticket.summary.salesPerson')"> <VnLv :label="t('ticket.summary.salesPerson')">
<template #value> <template #value>
<VnUserLink <VnUserLink
@ -112,12 +111,14 @@ const setData = (entity) =>
/> />
</template> </template>
</VnLv> </VnLv>
<VnLv :label="t('ticket.card.warehouse')" :value="entity.warehouse?.name" /> <VnLv :label="t('ticket.card.shipped')" :value="toDate(entity.shipped)" />
<VnLv <VnLv
v-if="entity.agencyMode" v-if="entity.agencyMode"
:label="t('ticket.card.agency')" :label="t('ticket.card.agency')"
:value="entity.agencyMode.name" :value="entity.agencyMode.name"
/> />
<VnLv :label="t('ticket.card.warehouse')" :value="entity.warehouse?.name" />
<VnLv :label="t('ticket.card.alias')" :value="entity.nickname" />
</template> </template>
<template #icons="{ entity }"> <template #icons="{ entity }">
<QCardActions> <QCardActions>

View File

@ -147,9 +147,9 @@ async function changeState(value) {
</div> </div>
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<a class="header link" :href="ticketUrl + 'basic-data/step-one'"> <a class="header header-link" :href="ticketUrl + 'basic-data/step-one'">
{{ t('globals.summary.basicData') }} {{ t('globals.summary.basicData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<VnLv :label="t('ticket.summary.state')"> <VnLv :label="t('ticket.summary.state')">
<template #value> <template #value>
@ -193,9 +193,9 @@ async function changeState(value) {
/> />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<a class="header link" :href="ticketUrl + 'basic-data/step-one'"> <a class="header header-link" :href="ticketUrl + 'basic-data/step-one'">
{{ t('globals.summary.basicData') }} {{ t('globals.summary.basicData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<VnLv <VnLv
:label="t('ticket.summary.shipped')" :label="t('ticket.summary.shipped')"
@ -236,9 +236,9 @@ async function changeState(value) {
/> />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<a class="header link" :href="ticketUrl + 'observation'"> <a class="header header-link" :href="ticketUrl + 'observation'">
{{ t('ticket.pageTitles.notes') }} {{ t('ticket.pageTitles.notes') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<VnLv <VnLv
v-for="note in ticket.notes" v-for="note in ticket.notes"
@ -258,9 +258,9 @@ async function changeState(value) {
</VnLv> </VnLv>
</QCard> </QCard>
<QCard class="vn-max"> <QCard class="vn-max">
<a class="header link" :href="ticketUrl + 'sale'"> <a class="header header-link" :href="ticketUrl + 'sale'">
{{ t('ticket.summary.saleLines') }} {{ t('ticket.summary.saleLines') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<QTable :rows="ticket.sales"> <QTable :rows="ticket.sales">
<template #header="props"> <template #header="props">
@ -396,9 +396,9 @@ async function changeState(value) {
class="vn-max" class="vn-max"
v-if="ticket.packagings.length > 0 || ticket.services.length > 0" v-if="ticket.packagings.length > 0 || ticket.services.length > 0"
> >
<a class="header link" :href="ticketUrl + 'package'"> <a class="header header-link" :href="ticketUrl + 'package'">
{{ t('globals.packages') }} {{ t('globals.packages') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<QTable :rows="ticket.packagings" flat> <QTable :rows="ticket.packagings" flat>
<template #header="props"> <template #header="props">
@ -417,9 +417,9 @@ async function changeState(value) {
</template> </template>
</QTable> </QTable>
<a class="header link q-mt-xl" :href="ticketUrl + 'service'"> <a class="header header-link q-mt-xl" :href="ticketUrl + 'service'">
{{ t('ticket.summary.service') }} {{ t('ticket.summary.service') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<QTable :rows="ticket.services" flat> <QTable :rows="ticket.services" flat>
<template #header="props"> <template #header="props">

View File

@ -3,6 +3,7 @@ import { useStateStore } from 'stores/useStateStore';
import TravelDescriptor from './TravelDescriptor.vue'; import TravelDescriptor from './TravelDescriptor.vue';
import LeftMenu from 'components/LeftMenu.vue'; import LeftMenu from 'components/LeftMenu.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import useCardSize from 'src/composables/useCardSize';
const stateStore = useStateStore(); const stateStore = useStateStore();
</script> </script>
@ -17,7 +18,9 @@ const stateStore = useStateStore();
<QPageContainer> <QPageContainer>
<QPage> <QPage>
<VnSubToolbar /> <VnSubToolbar />
<div class="q-pa-md"><RouterView></RouterView></div> <div :class="useCardSize()">
<RouterView></RouterView>
</div>
</QPage> </QPage>
</QPageContainer> </QPageContainer>
</template> </template>

View File

@ -269,15 +269,11 @@ async function setTravelData(travelData) {
:label="t('globals.wareHouseOut')" :label="t('globals.wareHouseOut')"
:value="travel.warehouseOut?.name" :value="travel.warehouseOut?.name"
/> />
<VnLv :label="t('travel.summary.delivered')" class="q-mb-xs"> <QCheckbox
<template #value> :label="t('travel.summary.delivered')"
<QIcon v-model="travel.isDelivered"
:name="travel.isDelivered ? 'check' : 'close'" :disable="true"
:color="travel.isDelivered ? 'positive' : 'negative'" />
size="sm"
/>
</template>
</VnLv>
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<VnLv :label="t('globals.landed')" :value="toDate(travel.landed)" /> <VnLv :label="t('globals.landed')" :value="toDate(travel.landed)" />
@ -285,15 +281,11 @@ async function setTravelData(travelData) {
:label="t('globals.wareHouseIn')" :label="t('globals.wareHouseIn')"
:value="travel.warehouseIn?.name" :value="travel.warehouseIn?.name"
/> />
<VnLv :label="t('travel.summary.received')" class="q-mb-xs"> <QCheckbox
<template #value> :label="t('travel.summary.received')"
<QIcon v-model="travel.isReceived"
:name="travel.isReceived ? 'check' : 'close'" :disable="true"
:color="travel.isReceived ? 'positive' : 'negative'" />
size="sm"
/>
</template>
</VnLv>
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<VnLv :label="t('globals.agency')" :value="travel.agency?.name" /> <VnLv :label="t('globals.agency')" :value="travel.agency?.name" />
@ -302,7 +294,7 @@ async function setTravelData(travelData) {
<VnLv :label="t('globals.totalEntries')" :value="travel.totalEntries" /> <VnLv :label="t('globals.totalEntries')" :value="travel.totalEntries" />
</QCard> </QCard>
<QCard class="full-width" v-if="entriesTableRows.length > 0"> <QCard class="full-width" v-if="entriesTableRows.length > 0">
<span class="header"> <span class="header header-link">
{{ t('travel.summary.entries') }} {{ t('travel.summary.entries') }}
</span> </span>
<QTable <QTable
@ -361,14 +353,14 @@ async function setTravelData(travelData) {
<QCard class="full-width" v-if="thermographs.length > 0"> <QCard class="full-width" v-if="thermographs.length > 0">
<RouterLink <RouterLink
class="header" class="header header-link"
:to="{ :to="{
name: 'TravelThermographsIndex', name: 'TravelThermographsIndex',
params: { id: travel.id }, params: { id: travel.id },
}" }"
> >
{{ t('travel.summary.thermographs') }} {{ t('travel.summary.thermographs') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</RouterLink> </RouterLink>
<QTable <QTable
:rows="thermographs" :rows="thermographs"

View File

@ -3,6 +3,7 @@ import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import LeftMenu from 'components/LeftMenu.vue'; import LeftMenu from 'components/LeftMenu.vue';
import useCardSize from 'src/composables/useCardSize';
const stateStore = useStateStore(); const stateStore = useStateStore();
const route = useRoute(); const route = useRoute();
@ -17,7 +18,9 @@ const { t } = useI18n();
</QDrawer> </QDrawer>
<QPageContainer> <QPageContainer>
<QPage> <QPage>
<div class="q-pa-md"><RouterView></RouterView></div> <div :class="useCardSize()">
<RouterView></RouterView>
</div>
</QPage> </QPage>
</QPageContainer> </QPageContainer>
</template> </template>

View File

@ -5,6 +5,7 @@ import WorkerDescriptor from './WorkerDescriptor.vue';
import LeftMenu from 'components/LeftMenu.vue'; import LeftMenu from 'components/LeftMenu.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import useCardSize from 'src/composables/useCardSize';
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
@ -29,7 +30,9 @@ const { t } = useI18n();
<QPage> <QPage>
<VnSubToolbar /> <VnSubToolbar />
<div class="q-pa-md"><RouterView></RouterView></div> <div :class="useCardSize()">
<RouterView></RouterView>
</div>
</QPage> </QPage>
</QPageContainer> </QPageContainer>
</template> </template>

View File

@ -71,14 +71,14 @@ const filter = {
</template> </template>
<template #body="{ entity: worker }"> <template #body="{ entity: worker }">
<QCard class="vn-one"> <QCard class="vn-one">
<a class="header" :href="workerUrl + `basic-data`"> <a class="header header-link" :href="workerUrl + `basic-data`">
{{ t('worker.summary.basicData') }} {{ t('worker.summary.basicData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </a>
<VnLv :label="t('worker.card.name')" :value="worker.user.nickname" /> <VnLv :label="t('worker.card.name')" :value="worker.user.nickname" />
<VnLv <VnLv
:label="t('worker.list.department')" :label="t('worker.list.department')"
:value="worker.department.department.name" :value="worker.department?.department?.name"
/> />
<VnLv :label="t('worker.list.email')" :value="worker.user.email" copy /> <VnLv :label="t('worker.list.email')" :value="worker.user.email" copy />
<VnLv :label="t('worker.summary.boss')" link> <VnLv :label="t('worker.summary.boss')" link>
@ -102,16 +102,16 @@ const filter = {
<VnLinkPhone :phone-number="worker.phone" /> <VnLinkPhone :phone-number="worker.phone" />
</template> </template>
</VnLv> </VnLv>
<VnLv :value="worker.client.phone"> <VnLv :value="worker.client?.phone">
<template #label> <template #label>
{{ t('worker.summary.personalPhone') }} {{ t('worker.summary.personalPhone') }}
<VnLinkPhone :phone-number="worker.client.phone" /> <VnLinkPhone :phone-number="worker.client?.phone" />
</template> </template>
</VnLv> </VnLv>
<VnLv :label="t('worker.summary.locker')" :value="worker.locker" /> <VnLv :label="t('worker.summary.locker')" :value="worker.locker" />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<div class="header"> <div class="header header-link">
{{ t('worker.summary.userData') }} {{ t('worker.summary.userData') }}
</div> </div>
<VnLv :label="t('worker.summary.userId')" :value="worker.user.id" /> <VnLv :label="t('worker.summary.userId')" :value="worker.user.id" />

View File

@ -10,16 +10,15 @@ export default {
component: RouterView, component: RouterView,
redirect: { name: 'ClaimMain' }, redirect: { name: 'ClaimMain' },
menus: { menus: {
main: ['ClaimList', 'ClaimRmaList'], main: ['ClaimList'],
card: [ card: [
'ClaimBasicData', 'ClaimBasicData',
'ClaimLines', 'ClaimLines',
'ClaimRma',
'ClaimPhotos', 'ClaimPhotos',
'ClaimLog',
'ClaimNotes', 'ClaimNotes',
'ClaimDevelopment', 'ClaimDevelopment',
'ClaimAction', 'ClaimAction',
'ClaimLog',
], ],
}, },
children: [ children: [
@ -38,16 +37,6 @@ export default {
}, },
component: () => import('src/pages/Claim/ClaimList.vue'), component: () => import('src/pages/Claim/ClaimList.vue'),
}, },
{
name: 'ClaimRmaList',
path: 'rma',
meta: {
title: 'rmaList',
icon: 'vn:barcode',
roles: ['claimManager'],
},
component: () => import('src/pages/Claim/ClaimRmaList.vue'),
},
], ],
}, },
{ {
@ -84,16 +73,6 @@ export default {
}, },
component: () => import('src/pages/Claim/Card/ClaimLines.vue'), component: () => import('src/pages/Claim/Card/ClaimLines.vue'),
}, },
{
name: 'ClaimRma',
path: 'rma',
meta: {
title: 'rma',
icon: 'vn:barcode',
roles: ['claimManager'],
},
component: () => import('src/pages/Claim/Card/ClaimRma.vue'),
},
{ {
name: 'ClaimPhotos', name: 'ClaimPhotos',
path: 'photos', path: 'photos',
@ -103,6 +82,15 @@ export default {
}, },
component: () => import('src/pages/Claim/Card/ClaimPhoto.vue'), component: () => import('src/pages/Claim/Card/ClaimPhoto.vue'),
}, },
{
name: 'ClaimNotes',
path: 'notes',
meta: {
title: 'notes',
icon: 'draft',
},
component: () => import('src/pages/Claim/Card/ClaimNotes.vue'),
},
{ {
name: 'ClaimDevelopment', name: 'ClaimDevelopment',
path: 'development', path: 'development',
@ -113,24 +101,6 @@ export default {
}, },
component: () => import('src/pages/Claim/Card/ClaimDevelopment.vue'), component: () => import('src/pages/Claim/Card/ClaimDevelopment.vue'),
}, },
{
name: 'ClaimLog',
path: 'log',
meta: {
title: 'log',
icon: 'history',
},
component: () => import('src/pages/Claim/Card/ClaimLog.vue'),
},
{
name: 'ClaimNotes',
path: 'notes',
meta: {
title: 'notes',
icon: 'draft',
},
component: () => import('src/pages/Claim/Card/ClaimNotes.vue'),
},
{ {
name: 'ClaimAction', name: 'ClaimAction',
path: 'action', path: 'action',
@ -140,6 +110,15 @@ export default {
}, },
component: () => import('src/pages/Claim/Card/ClaimAction.vue'), component: () => import('src/pages/Claim/Card/ClaimAction.vue'),
}, },
{
name: 'ClaimLog',
path: 'log',
meta: {
title: 'log',
icon: 'history',
},
component: () => import('src/pages/Claim/Card/ClaimLog.vue'),
},
], ],
}, },
], ],

View File

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