CambiosSolicitadosEntries #201

Merged
alexm merged 17 commits from :CambiosSolicitadosEntries into dev 2024-03-14 06:39:45 +00:00
121 changed files with 3588 additions and 1642 deletions
Showing only changes of commit f26d14e875 - Show all commits

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"]

18
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,18 +73,14 @@ pipeline {
NODE_ENV = "" NODE_ENV = ""
} }
steps { steps {
sh 'npm run test:unit:ci' sh 'pnpm run test:unit:ci'
} }
post { post {
always { always {
script { junit(
try { testResults: 'junitresults.xml',
junit 'junitresults.xml' allowEmptyResults: true
junit 'junit.xml' )
} catch (e) {
echo e.toString()
}
}
} }
} }
} }

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

@ -1,5 +1,5 @@
<script setup> <script setup>
import { reactive, ref } from 'vue'; import { reactive, ref, onMounted, nextTick } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
@ -16,9 +16,8 @@ const props = defineProps({
}); });
const emit = defineEmits(['onDataSaved']); const emit = defineEmits(['onDataSaved']);
const { t } = useI18n(); const { t } = useI18n();
const bicInputRef = ref(null);
const bankEntityFormData = reactive({ const bankEntityFormData = reactive({
name: null, name: null,
bic: null, bic: null,
@ -32,9 +31,14 @@ const countriesFilter = {
const countriesOptions = ref([]); const countriesOptions = ref([]);
const onDataSaved = (data) => { const onDataSaved = (formData, requestResponse) => {
emit('onDataSaved', data); emit('onDataSaved', formData, requestResponse);
}; };
onMounted(async () => {
await nextTick();
bicInputRef.value.focus();
});
</script> </script>
<template> <template>
@ -50,7 +54,7 @@ const onDataSaved = (data) => {
:title="t('title')" :title="t('title')"
:subtitle="t('subtitle')" :subtitle="t('subtitle')"
:form-initial-data="bankEntityFormData" :form-initial-data="bankEntityFormData"
@on-data-saved="onDataSaved($event)" @on-data-saved="onDataSaved"
> >
<template #form-inputs="{ data, validate }"> <template #form-inputs="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
@ -64,6 +68,7 @@ const onDataSaved = (data) => {
</div> </div>
<div class="col"> <div class="col">
<VnInput <VnInput
ref="bicInputRef"
:label="t('swift')" :label="t('swift')"
v-model="data.bic" v-model="data.bic"
:required="true" :required="true"

View File

@ -176,8 +176,8 @@ async function remove(data) {
.dialog({ .dialog({
component: VnConfirm, component: VnConfirm,
componentProps: { componentProps: {
title: t('confirmDeletion'), title: t('globals.confirmDeletion'),
message: t('confirmDeletionMessage'), message: t('globals.confirmDeletionMessage'),
newData, newData,
ids, ids,
}, },
@ -317,16 +317,3 @@ watch(formUrl, async () => {
color="primary" color="primary"
/> />
</template> </template>
<i18n>
{
"en": {
"confirmDeletion": "Confirm deletion",
"confirmDeletionMessage": "Are you sure you want to delete this?"
},
"es": {
"confirmDeletion": "Confirmar eliminación",
"confirmDeletionMessage": "Seguro que quieres eliminar?"
}
}
</i18n>

View File

@ -272,7 +272,7 @@ const makeRequest = async () => {
class="cursor-pointer q-mr-sm" class="cursor-pointer q-mr-sm"
@click="openInputFile()" @click="openInputFile()"
> >
<!-- <QTooltip>{{ t('Select a file') }}</QTooltip> --> <!-- <QTooltip>{{ t('globals.selectFile') }}</QTooltip> -->
</QIcon> </QIcon>
<QIcon name="info" class="cursor-pointer"> <QIcon name="info" class="cursor-pointer">
<QTooltip>{{ <QTooltip>{{

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

@ -59,11 +59,4 @@ async function fetch(fetchFilter = {}) {
// //
} }
} }
const render = () => {
return h('div', []);
};
</script> </script>
<template>
<render />
</template>

View File

@ -1,6 +1,7 @@
<script setup> <script setup>
import axios from 'axios'; import axios from 'axios';
import { onMounted, onUnmounted, computed, ref, watch, nextTick } from 'vue'; import { onMounted, onUnmounted, computed, ref, watch, nextTick } from 'vue';
import { onBeforeRouteLeave } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
@ -8,6 +9,8 @@ import { useStateStore } from 'stores/useStateStore';
import { useValidator } from 'src/composables/useValidator'; import { useValidator } from 'src/composables/useValidator';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import SkeletonForm from 'components/ui/SkeletonForm.vue'; import SkeletonForm from 'components/ui/SkeletonForm.vue';
import VnConfirm from './ui/VnConfirm.vue';
import { tMobile } from 'src/composables/tMobile';
const quasar = useQuasar(); const quasar = useQuasar();
const state = useState(); const state = useState();
@ -41,6 +44,10 @@ const $props = defineProps({
type: Boolean, type: Boolean,
default: true, default: true,
}, },
defaultButtons: {
type: Object,
default: () => {},
},
autoLoad: { autoLoad: {
type: Boolean, type: Boolean,
default: false, default: false,
@ -63,6 +70,10 @@ const $props = defineProps({
type: Boolean, type: Boolean,
default: true, default: true,
}, },
saveFn: {
type: Function,
default: null,
},
}); });
const emit = defineEmits(['onFetch', 'onDataSaved']); const emit = defineEmits(['onFetch', 'onDataSaved']);
@ -79,9 +90,8 @@ 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
if ($props.formInitialData && !$props.autoLoad) { state.set($props.model, $props.formInitialData);
state.set($props.model, $props.formInitialData); if ($props.autoLoad && !$props.formInitialData) {
} else {
await fetch(); await fetch();
} }
@ -94,6 +104,19 @@ onMounted(async () => {
} }
}); });
onBeforeRouteLeave((to, from, next) => {
if (!hasChanges.value) next();
quasar.dialog({
component: VnConfirm,
componentProps: {
title: t('Unsaved changes will be lost'),
message: t('Are you sure exit without saving?'),
promise: () => next(),
},
});
});
onUnmounted(() => { onUnmounted(() => {
// Restauramos los datos originales en el store si se realizaron cambios en el formulario pero no se guardaron, evitando modificaciones erróneas. // Restauramos los datos originales en el store si se realizaron cambios en el formulario pero no se guardaron, evitando modificaciones erróneas.
if (hasChanges.value) { if (hasChanges.value) {
@ -110,7 +133,19 @@ const hasChanges = ref(!$props.observeFormChanges);
const originalData = ref({ ...$props.formInitialData }); const originalData = ref({ ...$props.formInitialData });
const formData = computed(() => state.get($props.model)); const formData = computed(() => state.get($props.model));
const formUrl = computed(() => $props.url); const formUrl = computed(() => $props.url);
const defaultButtons = computed(() => ({
save: {
color: 'primary',
icon: 'restart_alt',
label: 'globals.save',
},
reset: {
color: 'primary',
icon: 'save',
label: 'globals.reset',
},
...$props.defaultButtons,
}));
const startFormWatcher = () => { const startFormWatcher = () => {
watch( watch(
() => formData.value, () => formData.value,
@ -122,10 +157,6 @@ const startFormWatcher = () => {
); );
}; };
function tMobile(...args) {
if (!quasar.platform.is.mobile) return t(...args);
}
async function fetch() { async function fetch() {
const { data } = await axios.get($props.url, { const { data } = await axios.get($props.url, {
params: { filter: JSON.stringify($props.filter) }, params: { filter: JSON.stringify($props.filter) },
@ -147,17 +178,20 @@ async function save() {
try { try {
const body = $props.mapper ? $props.mapper(formData.value) : formData.value; const body = $props.mapper ? $props.mapper(formData.value) : formData.value;
let response; let response;
if ($props.urlCreate) { if ($props.saveFn) response = await $props.saveFn(body);
response = await axios.post($props.urlCreate, body); else
notify('globals.dataCreated', 'positive'); response = await axios[$props.urlCreate ? 'post' : 'patch'](
} else { $props.urlCreate || $props.urlUpdate || $props.url,
response = await axios.patch($props.urlUpdate || $props.url, body); body
} );
if ($props.urlCreate) notify('globals.dataCreated', 'positive');
emit('onDataSaved', formData.value, response?.data); emit('onDataSaved', formData.value, response?.data);
originalData.value = JSON.parse(JSON.stringify(formData.value)); originalData.value = JSON.parse(JSON.stringify(formData.value));
hasChanges.value = false; hasChanges.value = false;
} catch (err) { } catch (err) {
notify('errors.create', 'negative'); console.error(err);
notify('errors.writeRequest', 'negative');
} }
isLoading.value = false; isLoading.value = false;
} }
@ -221,21 +255,21 @@ watch(formUrl, async () => {
<QBtnGroup push class="q-gutter-x-sm"> <QBtnGroup push class="q-gutter-x-sm">
<slot name="moreActions" /> <slot name="moreActions" />
<QBtn <QBtn
:label="tMobile('globals.reset')" :label="tMobile(defaultButtons.reset.label)"
color="primary" :color="defaultButtons.reset.color"
icon="restart_alt" :icon="defaultButtons.reset.icon"
flat flat
@click="reset" @click="reset"
:disable="!hasChanges" :disable="!hasChanges"
:title="t('globals.reset')" :title="t(defaultButtons.reset.label)"
/> />
<QBtn <QBtn
:label="tMobile('globals.save')" :label="tMobile(defaultButtons.save.label)"
color="primary" :color="defaultButtons.save.color"
icon="save" :icon="defaultButtons.save.icon"
@click="save" @click="save"
:disable="!hasChanges" :disable="!hasChanges"
:title="t('globals.save')" :title="t(defaultButtons.save.label)"
/> />
</QBtnGroup> </QBtnGroup>
</div> </div>
@ -258,3 +292,8 @@ watch(formUrl, async () => {
padding: 32px; padding: 32px;
} }
</style> </style>
<i18n>
es:
Unsaved changes will be lost: Los cambios que no haya guardado se perderán
Are you sure exit without saving?: ¿Seguro que quiere salir sin guardar?
</i18n>

View File

@ -42,8 +42,8 @@ const { t } = useI18n();
const closeButton = ref(null); const closeButton = ref(null);
const isLoading = ref(false); const isLoading = ref(false);
const onDataSaved = (dataSaved) => { const onDataSaved = (formData, requestResponse) => {
emit('onDataSaved', dataSaved); emit('onDataSaved', formData, requestResponse);
closeForm(); closeForm();
}; };
@ -59,7 +59,7 @@ const closeForm = () => {
:default-actions="false" :default-actions="false"
:url-create="urlCreate" :url-create="urlCreate"
:model="model" :model="model"
@on-data-saved="onDataSaved($event)" @on-data-saved="onDataSaved"
> >
<template #form="{ data, validate }"> <template #form="{ data, validate }">
<span ref="closeButton" class="close-icon" v-close-popup> <span ref="closeButton" class="close-icon" v-close-popup>

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

@ -0,0 +1,201 @@
<script setup>
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FormModelPopup from 'components/FormModelPopup.vue';
const route = useRoute();
const { t } = useI18n();
const emit = defineEmits(['onDataSaved']);
const $props = defineProps({
model: {
type: String,
required: true,
},
defaultDmsCode: {
type: String,
default: null,
},
formInitialData: {
type: Object,
default: null,
},
});
const warehouses = ref();
const companies = ref();
const dmsTypes = ref();
const allowedContentTypes = ref();
const inputFileRef = ref();
const dms = ref({});
onMounted(() => {
defaultData();
if (!$props.formInitialData)
dms.value.description = t($props.model + 'Description', dms.value);
});
function onFileChange(files) {
dms.value.hasFileAttached = !!files;
dms.value.file = files?.name;
}
function mapperDms(data) {
const formData = new FormData();
const { files } = data;
if (files) formData.append(files?.name, files);
delete data.files;
const dms = {
hasFile: !!data.hasFile,
hasFileAttached: data.hasFileAttached,
reference: data.reference,
warehouseId: data.warehouseFk,
companyId: data.companyFk,
dmsTypeId: data.dmsTypeFk,
description: data.description,
};
return [formData, { params: dms }];
}
function getUrl() {
if ($props.formInitialData) return 'dms/' + $props.formInitialData.id + '/updateFile';
return `${$props.model}/${route.params.id}/uploadFile`;
}
async function save() {
const body = mapperDms(dms.value);
await axios.post(getUrl(), body[0], body[1]);
emit('onDataSaved', body[1].params);
}
function defaultData() {
if ($props.formInitialData) return (dms.value = $props.formInitialData);
return addDefaultData({
reference: route.params.id,
});
}
function setDmsTypes(data) {
dmsTypes.value = data;
if (!$props.formInitialData && $props.defaultDmsCode) {
const { id } = data.find((dmsType) => dmsType.code == $props.defaultDmsCode);
addDefaultData({ dmsTypeFk: id });
}
}
function addDefaultData(data) {
Object.assign(dms.value, data);
}
</script>
<template>
<FetchData url="Warehouses" @on-fetch="(data) => (warehouses = data)" auto-load />
<FetchData url="Companies" @on-fetch="(data) => (companies = data)" auto-load />
<FetchData url="DmsTypes" @on-fetch="setDmsTypes" auto-load />
<FetchData
url="DmsContainers/allowedContentTypes"
@on-fetch="(data) => (allowedContentTypes = data.join(','))"
auto-load
/>
<FetchData
url="UserConfigs/getUserConfig"
@on-fetch="addDefaultData"
:auto-load="!$props.formInitialData"
/>
<FormModelPopup
:title="formInitialData ? t('globals.edit') : t('globals.create')"
model="dms"
:form-initial-data="formInitialData ?? {}"
:save-fn="save"
>
<template #form-inputs>
<div class="q-gutter-y-ms">
<VnRow>
<VnInput :label="t('globals.reference')" v-model="dms.reference" />
<VnSelectFilter
:label="t('globals.company')"
v-model="dms.companyFk"
:options="companies"
option-value="id"
option-label="code"
input-debounce="0"
/>
</VnRow>
<VnRow>
<VnSelectFilter
:label="t('globals.warehouse')"
v-model="dms.warehouseFk"
:options="warehouses"
option-value="id"
option-label="name"
input-debounce="0"
/>
<VnSelectFilter
:label="t('globals.type')"
v-model="dms.dmsTypeFk"
:options="dmsTypes"
option-value="id"
option-label="name"
input-debounce="0"
/>
</VnRow>
<QInput
:label="t('globals.description')"
v-model="dms.description"
type="textarea"
/>
<QFile
ref="inputFileRef"
:label="t('entry.buys.file')"
v-model="dms.files"
:multiple="false"
:accept="allowedContentTypes"
@update:model-value="onFileChange(dms.files)"
class="required"
:display-value="dms.file"
>
<template #append>
<QIcon
name="vn:attach"
class="cursor-pointer"
@click="inputFileRef.pickFiles()"
>
<QTooltip>{{ t('globals.selectFile') }}</QTooltip>
</QIcon>
<QIcon name="info" class="cursor-pointer">
<QTooltip>{{
t('contentTypesInfo', { allowedContentTypes })
}}</QTooltip>
</QIcon>
</template>
</QFile>
<QCheckbox
v-model="dms.hasFile"
:label="t('Generate identifier for original file')"
/>
</div>
</template>
</FormModelPopup>
</template>
<style scoped>
.q-gutter-y-ms {
display: grid;
row-gap: 20px;
}
</style>
<i18n>
en:
contentTypesInfo: Allowed file types {allowedContentTypes}
EntryDmsDescription: Reference {reference}
es:
Generate identifier for original file: Generar identificador para archivo original
contentTypesInfo: Tipos de archivo permitidos {allowedContentTypes}
EntryDmsDescription: Referencia {reference}
</i18n>

View File

@ -0,0 +1,316 @@
<script setup>
import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { useQuasar, QCheckbox, QBtn, QInput } from 'quasar';
import axios from 'axios';
import FetchData from 'components/FetchData.vue';
import VnDms from 'src/components/common/VnDms.vue';
import VnConfirm from 'components/ui/VnConfirm.vue';
import { downloadFile } from 'src/composables/downloadFile';
const route = useRoute();
const quasar = useQuasar();
const { t } = useI18n();
const rows = ref();
const dmsRef = ref();
const formDialog = ref({});
const $props = defineProps({
model: {
type: String,
required: true,
},
updateModel: {
type: String,
default: null,
},
defaultDmsCode: {
type: String,
required: true,
},
filter: {
type: String,
required: true,
},
});
const dmsFilter = {
include: {
relation: 'dms',
scope: {
fields: [
'dmsTypeFk',
'reference',
'hardCopyNumber',
'workerFk',
'description',
'hasFile',
'file',
'created',
'companyFk',
'warehouseFk',
],
include: [
{
relation: 'dmsType',
scope: {
fields: ['name'],
},
},
{
relation: 'worker',
scope: {
fields: ['id'],
include: {
relation: 'user',
scope: {
fields: ['name'],
},
},
},
},
],
},
},
order: ['dmsFk DESC'],
};
const columns = computed(() => [
{
align: 'left',
field: 'id',
label: t('globals.id'),
name: 'id',
component: 'span',
},
{
align: 'left',
field: 'type',
label: t('globals.type'),
name: 'type',
component: QInput,
props: (prop) => ({
readonly: true,
borderless: true,
'model-value': prop.row.dmsType.name,
}),
},
{
align: 'left',
field: 'order',
label: t('globals.order'),
name: 'order',
component: 'span',
},
{
align: 'left',
field: 'reference',
label: t('globals.reference'),
name: 'reference',
component: 'span',
},
{
align: 'left',
field: 'description',
label: t('globals.description'),
name: 'description',
component: 'span',
},
{
align: 'left',
field: 'hasFile',
label: t('globals.original'),
name: 'hasFile',
component: QCheckbox,
props: (prop) => ({
disable: true,
'model-value': Boolean(prop.value),
}),
},
{
align: 'left',
field: 'file',
label: t('globals.file'),
name: 'file',
component: 'span',
},
{
field: 'options',
name: 'options',
components: [
{
component: QBtn,
props: () => ({
icon: 'cloud_download',
flat: true,
color: 'primary',
}),
click: (prop) => downloadFile(prop.row.id),
},
{
component: QBtn,
props: () => ({
icon: 'edit',
flat: true,
color: 'primary',
}),
click: (prop) => showFormDialog(prop.row),
},
{
component: QBtn,
props: () => ({
icon: 'delete',
flat: true,
color: 'primary',
}),
click: (prop) => deleteDms(prop.row.id),
},
],
},
]);
function setData(data) {
const newData = data.map((value) => value.dms);
rows.value = newData;
}
function deleteDms(dmsFk) {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('globals.confirmDeletion'),
message: t('globals.confirmDeletionMessage'),
},
})
.onOk(async () => {
await axios.post(`${$props.model}/${dmsFk}/removeFile`);
const index = rows.value.findIndex((row) => row.id == dmsFk);
rows.value.splice(index, 1);
});
}
function showFormDialog(dms) {
if (dms) dms = parseDms(dms);
formDialog.value = {
show: true,
dms,
};
}
function parseDms(data) {
for (let prop in data) {
if (prop.endsWith('Fk')) data[prop.replace('Fk', 'Id')] = data[prop];
}
return data;
}
</script>
<template>
<FetchData
ref="dmsRef"
:url="$props.model"
:filter="dmsFilter"
:where="{ [$props.filter]: route.params.id }"
@on-fetch="setData"
auto-load
/>
<QTable
:columns="columns"
:pagination="{ rowsPerPage: 0 }"
:rows="rows"
class="full-width q-mt-md"
hide-bottom
row-key="clientFk"
:grid="$q.screen.lt.sm"
>
<template #body-cell="props">
<QTd :props="props">
<QTr :props="props">
<component
v-if="props.col.component"
:is="props.col.component"
v-bind="props.col.props && props.col.props(props)"
>
<span
v-if="props.col.component == 'span'"
style="white-space: wrap"
>{{ props.value }}</span
>
</component>
</QTr>
<div class="flex justify-center" v-if="props.col.name == 'options'">
<div v-for="button of props.col.components" :key="button.id">
<component
:is="button.component"
v-bind="button.props(props)"
@click="button.click(props)"
/>
</div>
</div>
</QTd>
</template>
<template #item="props">
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
<QCard
bordered
flat
@keyup.ctrl.enter.stop="claimDevelopmentForm?.saveChanges()"
>
<QSeparator />
<QList dense>
<QItem v-for="col in props.cols" :key="col.name">
<div v-if="col.name != 'options'" class="row">
<span class="labelColor">{{ col.label }}:</span>
<span>{{ col.value }}</span>
</div>
<div v-if="col.name == 'options'" class="row">
<div
v-for="button of col.components"
:key="button.id"
class="row"
>
<component
:is="button.component"
v-bind="button.props(col)"
@click="button.click(col)"
/>
</div>
</div>
</QItem>
</QList>
</QCard>
</div>
</template>
</QTable>
<QDialog v-model="formDialog.show">
<VnDms
:model="updateModel ?? model"
:default-dms-code="defaultDmsCode"
:form-initial-data="formDialog.dms"
@on-data-saved="dmsRef.fetch()"
:description="$props.description"
/>
</QDialog>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn fab color="primary" icon="add" @click="showFormDialog()" />
</QPageSticky>
</template>
<style scoped>
.q-gutter-y-ms {
display: grid;
row-gap: 20px;
}
.labelColor {
color: var(--vn-label);
}
</style>
<i18n>
en:
contentTypesInfo: Allowed file types {allowedContentTypes}
es:
contentTypesInfo: Tipos de archivo permitidos {allowedContentTypes}
Generate identifier for original file: Generar identificador para archivo original
</i18n>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { computed } from 'vue'; import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
const emit = defineEmits(['update:modelValue', 'update:options', 'keyup.enter']); const emit = defineEmits(['update:modelValue', 'update:options', 'keyup.enter']);
@ -17,7 +17,7 @@ const $props = defineProps({
const { t } = useI18n(); const { t } = useI18n();
const requiredFieldRule = (val) => !!val || t('globals.fieldRequired'); const requiredFieldRule = (val) => !!val || t('globals.fieldRequired');
const vnInputRef = ref(null);
const value = computed({ const value = computed({
get() { get() {
return $props.modelValue; return $props.modelValue;
@ -40,6 +40,14 @@ const styleAttrs = computed(() => {
const onEnterPress = () => { const onEnterPress = () => {
emit('keyup.enter'); emit('keyup.enter');
}; };
const focus = () => {
vnInputRef.value.focus();
};
defineExpose({
focus,
});
</script> </script>
<template> <template>

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

@ -94,16 +94,6 @@ async function send() {
<QSpace /> <QSpace />
<QBtn icon="close" :disable="isLoading" flat round dense v-close-popup /> <QBtn icon="close" :disable="isLoading" flat round dense v-close-popup />
</QCardSection> </QCardSection>
<QCardSection v-if="props.locale">
<QBanner class="bg-amber text-white" rounded dense>
<template #avatar>
<QIcon name="warning" />
</template>
<span
v-html="t('CustomerDefaultLanguage', { locale: t(props.locale) })"
></span>
</QBanner>
</QCardSection>
<QCardSection class="q-pb-xs"> <QCardSection class="q-pb-xs">
<QSelect <QSelect
:label="t('Language')" :label="t('Language')"
@ -184,11 +174,10 @@ async function send() {
<i18n> <i18n>
en: en:
CustomerDefaultLanguage: This customer uses <strong>{locale}</strong> as their default language
templates: templates:
pendingPayment: 'Your order is pending of payment. pendingPayment: 'Your order is pending of payment.
Please, enter the website and make the payment with a credit card. Thank you.' Please, enter the website and make the payment with a credit card. Thank you.'
minAmount: 'A minimum amount of 50 (VAT excluded) is required for your order minAmount: 'A minimum amount of 50 (VAT excluded) is required for your order
{ orderId } of { shipped } to receive it without additional shipping costs.' { orderId } of { shipped } to receive it without additional shipping costs.'
orderChanges: 'Order {orderId} of { shipped }: { changes }' orderChanges: 'Order {orderId} of { shipped }: { changes }'
en: English en: English
@ -197,7 +186,6 @@ en:
pt: Portuguese pt: Portuguese
es: es:
Send SMS: Enviar SMS Send SMS: Enviar SMS
CustomerDefaultLanguage: Este cliente utiliza <strong>{locale}</strong> como idioma por defecto
Language: Idioma Language: Idioma
Phone: Móvil Phone: Móvil
Subject: Asunto Subject: Asunto
@ -205,7 +193,7 @@ es:
templates: templates:
pendingPayment: 'Su pedido está pendiente de pago. pendingPayment: 'Su pedido está pendiente de pago.
Por favor, entre en la página web y efectue el pago con tarjeta. Muchas gracias.' Por favor, entre en la página web y efectue el pago con tarjeta. Muchas gracias.'
minAmount: 'Es necesario un importe mínimo de 50 (Sin IVA) en su pedido minAmount: 'Es necesario un importe mínimo de 50 (Sin IVA) en su pedido
{ orderId } del día { shipped } para recibirlo sin portes adicionales.' { orderId } del día { shipped } para recibirlo sin portes adicionales.'
orderChanges: 'Pedido {orderId} día { shipped }: { changes }' orderChanges: 'Pedido {orderId} día { shipped }: { changes }'
en: Inglés en: Inglés
@ -222,7 +210,7 @@ fr:
templates: templates:
pendingPayment: 'Votre commande est en attente de paiement. pendingPayment: 'Votre commande est en attente de paiement.
Veuillez vous connecter sur le site web et effectuer le paiement par carte. Merci beaucoup.' Veuillez vous connecter sur le site web et effectuer le paiement par carte. Merci beaucoup.'
minAmount: 'Un montant minimum de 50 (TVA non incluse) est requis pour votre commande minAmount: 'Un montant minimum de 50 (TVA non incluse) est requis pour votre commande
{ orderId } du { shipped } afin de la recevoir sans frais de port supplémentaires.' { orderId } du { shipped } afin de la recevoir sans frais de port supplémentaires.'
orderChanges: 'Commande { orderId } du { shipped }: { changes }' orderChanges: 'Commande { orderId } du { shipped }: { changes }'
en: Anglais en: Anglais
@ -239,7 +227,7 @@ pt:
templates: templates:
pendingPayment: 'Seu pedido está pendente de pagamento. pendingPayment: 'Seu pedido está pendente de pagamento.
Por favor, acesse o site e faça o pagamento com cartão. Muito obrigado.' Por favor, acesse o site e faça o pagamento com cartão. Muito obrigado.'
minAmount: 'É necessário um valor mínimo de 50 (sem IVA) em seu pedido minAmount: 'É necessário um valor mínimo de 50 (sem IVA) em seu pedido
{ orderId } do dia { shipped } para recebê-lo sem custos de envio adicionais.' { orderId } do dia { shipped } para recebê-lo sem custos de envio adicionais.'
orderChanges: 'Pedido { orderId } dia { shipped }: { changes }' orderChanges: 'Pedido { orderId } dia { shipped }: { changes }'
en: Inglês en: Inglês

View File

@ -175,6 +175,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;
@ -192,7 +193,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: ':';
} }
} }
@ -227,8 +229,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

@ -1,11 +1,10 @@
<script setup> <script setup>
import { onMounted, ref, watch } from 'vue'; import { onMounted, ref, watch } from 'vue';
import { useRoute } from 'vue-router';
import axios from 'axios'; import axios from 'axios';
import SkeletonSummary from 'components/ui/SkeletonSummary.vue'; import SkeletonSummary from 'components/ui/SkeletonSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
onMounted(() => fetch());
const entity = ref(); const entity = ref();
const props = defineProps({ const props = defineProps({
url: { url: {
@ -16,14 +15,25 @@ const props = defineProps({
type: Object, type: Object,
default: null, default: null,
}, },
entityId: {
type: Number,
default: null,
},
}); });
const emit = defineEmits(['onFetch']); const emit = defineEmits(['onFetch']);
const route = useRoute();
const isSummary = ref();
defineExpose({ defineExpose({
entity, entity,
fetch, fetch,
}); });
onMounted(() => {
isSummary.value = String(route.path).endsWith('/summary');
fetch();
});
async function fetch() { async function fetch() {
const params = {}; const params = {};
@ -46,9 +56,19 @@ 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> <router-link
v-if="!isSummary && route.meta.moduleName"
class="header link"
:to="{
name: `${route.meta.moduleName}Summary`,
params: { id: entityId || entity.id },
}"
>
<QIcon name="open_in_new" color="white" size="sm" />
</router-link>
<span v-else></span>
</slot> </slot>
<slot name="header" :entity="entity"> <slot name="header" :entity="entity">
<VnLv :label="`${entity.id} -`" :value="entity.name" /> <VnLv :label="`${entity.id} -`" :value="entity.name" />
@ -85,8 +105,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 +125,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 +151,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,8 +1,8 @@
<script setup> <script setup>
import { computed } from 'vue';
import { dashIfEmpty } from 'src/filters'; import { dashIfEmpty } from 'src/filters';
import { useI18n } from 'vue-i18n';
import { useClipboard } from 'src/composables/useClipboard'; import { useClipboard } from 'src/composables/useClipboard';
const $props = defineProps({ const $props = defineProps({
label: { type: String, default: null }, label: { type: String, default: null },
value: { value: {
@ -13,8 +13,8 @@ const $props = defineProps({
dash: { type: Boolean, default: true }, dash: { type: Boolean, default: true },
copy: { type: Boolean, default: false }, copy: { type: Boolean, default: false },
}); });
const isBooleanValue = computed(() => typeof $props.value === 'boolean');
const { t } = useI18n();
const { copyText } = useClipboard(); const { copyText } = useClipboard();
function copyValueText() { function copyValueText() {
@ -40,36 +40,36 @@ 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>
</slot> </slot>
</div> </div>
<div class="info" v-if="$props.info"> <div class="info" v-if="$props.info">
<QIcon name="info"> <QIcon name="info" class="cursor-pointer" size="xs" color="grey">
<QTooltip class="bg-dark text-white shadow-4" :offset="[10, 10]"> <QTooltip class="bg-dark text-white shadow-4" :offset="[10, 10]">
{{ $props.info }} {{ $props.info }}
</QTooltip> </QTooltip>
</QIcon> </QIcon>
</div> </div>
<div class="copy" v-if="$props.copy && $props.value" @click="copyValueText()"> <div class="copy" v-if="$props.copy && $props.value" @click="copyValueText()">
<QIcon name="Content_Copy" color="primary" /> <QIcon name="Content_Copy" color="primary">
<QTooltip>{{ t('globals.copyClipboard') }}</QTooltip>
</QIcon>
</div> </div>
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.vn-label-value:hover .copy {
visibility: visible;
cursor: pointer;
}
.copy { .copy {
&:hover { visibility: hidden;
cursor: pointer; }
} .info {
margin-left: 5px;
} }
</style> </style>

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"
{{ toDateHour(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,9 +1,15 @@
<template> <template>
<div id="row"> <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 {
display: flex;
> * {
flex: 1;
}
}
@media screen and (max-width: 800px) { @media screen and (max-width: 800px) {
#row { #row {
flex-direction: column; flex-direction: column;

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),
@ -117,7 +118,12 @@ async function search() {
autofocus autofocus
> >
<template #prepend> <template #prepend>
<QIcon name="search" v-if="!quasar.platform.is.mobile" /> <QIcon
v-if="!quasar.platform.is.mobile"
class="cursor-pointer"
name="search"
@click="search"
/>
</template> </template>
<template #append> <template #append>
<QIcon <QIcon
@ -155,11 +161,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);
} }

Binary file not shown.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 173 KiB

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -1,10 +1,10 @@
@font-face { @font-face {
font-family: 'icon'; font-family: 'icon';
src: url('fonts/icon.eot?7zbcv0'); src: url('fonts/icon.eot?2omjsr');
src: url('fonts/icon.eot?7zbcv0#iefix') format('embedded-opentype'), src: url('fonts/icon.eot?2omjsr#iefix') format('embedded-opentype'),
url('fonts/icon.ttf?7zbcv0') format('truetype'), url('fonts/icon.ttf?2omjsr') format('truetype'),
url('fonts/icon.woff?7zbcv0') format('woff'), url('fonts/icon.woff?2omjsr') format('woff'),
url('fonts/icon.svg?7zbcv0#icon') format('svg'); url('fonts/icon.svg?2omjsr#icon') format('svg');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
font-display: block; font-display: block;
@ -28,6 +28,9 @@
.icon-100:before { .icon-100:before {
content: "\e926"; content: "\e926";
} }
.icon-Client_unpaid:before {
content: "\e925";
}
.icon-History:before { .icon-History:before {
content: "\e964"; content: "\e964";
} }
@ -46,9 +49,15 @@
.icon-addperson:before { .icon-addperson:before {
content: "\e929"; content: "\e929";
} }
.icon-agency:before {
content: "\e92a";
}
.icon-agency-term:before { .icon-agency-term:before {
content: "\e92b"; content: "\e92b";
} }
.icon-albaran:before {
content: "\e92c";
}
.icon-anonymous:before { .icon-anonymous:before {
content: "\e92d"; content: "\e92d";
} }
@ -172,6 +181,9 @@
.icon-funeral:before { .icon-funeral:before {
content: "\e95f"; content: "\e95f";
} }
.icon-grafana:before {
content: "\e931";
}
.icon-greenery:before { .icon-greenery:before {
content: "\e91e"; content: "\e91e";
} }
@ -355,6 +367,9 @@
.icon-traceability:before { .icon-traceability:before {
content: "\e919"; content: "\e919";
} }
.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

@ -24,12 +24,14 @@ export default {
dataCreated: 'Data created', dataCreated: 'Data created',
add: 'Add', add: 'Add',
create: 'Create', create: 'Create',
edit: 'Edit',
save: 'Save', save: 'Save',
remove: 'Remove', remove: 'Remove',
reset: 'Reset', reset: 'Reset',
close: 'Close', close: 'Close',
cancel: 'Cancel', cancel: 'Cancel',
confirm: 'Confirm', confirm: 'Confirm',
assign: 'Assign',
back: 'Back', back: 'Back',
yes: 'Yes', yes: 'Yes',
no: 'No', no: 'No',
@ -64,12 +66,24 @@ export default {
markAll: 'Mark all', markAll: 'Mark all',
requiredField: 'Required field', requiredField: 'Required field',
class: 'clase', class: 'clase',
type: 'type', type: 'Type',
reason: 'reason', reason: 'reason',
noResults: 'No results', noResults: 'No results',
system: 'System', system: 'System',
warehouse: 'Warehouse',
company: 'Company',
fieldRequired: 'Field required', fieldRequired: 'Field required',
allowedFilesText: 'Allowed file types: { allowedContentTypes }', allowedFilesText: 'Allowed file types: { allowedContentTypes }',
confirmDeletion: 'Confirm deletion',
confirmDeletionMessage: 'Are you sure you want to delete this?',
description: 'Description',
id: 'Id',
order: 'Order',
original: 'Original',
file: 'File',
selectFile: 'Select a file',
copyClipboard: 'Copy on clipboard',
salesPerson: 'SalesPerson',
}, },
errors: { errors: {
statusUnauthorized: 'Access denied', statusUnauthorized: 'Access denied',
@ -77,7 +91,7 @@ export default {
statusBadGateway: 'It seems that the server has fall down', statusBadGateway: 'It seems that the server has fall down',
statusGatewayTimeout: 'Could not contact the server', statusGatewayTimeout: 'Could not contact the server',
userConfig: 'Error fetching user config', userConfig: 'Error fetching user config',
create: 'Error during creation', writeRequest: 'The requested operation could not be completed',
}, },
login: { login: {
title: 'Login', title: 'Login',
@ -160,13 +174,14 @@ export default {
hasDebt: 'Customer has debt', hasDebt: 'Customer has debt',
notChecked: 'Customer not checked', notChecked: 'Customer not checked',
noWebAccess: 'Web access is disabled', noWebAccess: 'Web access is disabled',
businessTypeFk: 'Business type',
}, },
summary: { summary: {
basicData: 'Basic data', basicData: 'Basic data',
fiscalAddress: 'Fiscal address', fiscalAddress: 'Fiscal address',
fiscalData: 'Fiscal data', fiscalData: 'Fiscal data',
billingData: 'Billing data', billingData: 'Billing data',
consignee: 'Consignee', consignee: 'Default consignee',
businessData: 'Business data', businessData: 'Business data',
financialData: 'Financial data', financialData: 'Financial data',
customerId: 'Customer ID', customerId: 'Customer ID',
@ -219,6 +234,8 @@ export default {
recoverySince: 'Recovery since', recoverySince: 'Recovery since',
businessType: 'Business Type', businessType: 'Business Type',
city: 'City', city: 'City',
rating: 'Rating',
recommendCredit: 'Recommended credit',
}, },
basicData: { basicData: {
socialName: 'Fiscal name', socialName: 'Fiscal name',
@ -273,6 +290,7 @@ export default {
basicData: 'Basic data', basicData: 'Basic data',
buys: 'Buys', buys: 'Buys',
notes: 'Notes', notes: 'Notes',
dms: 'File management',
log: 'Log', log: 'Log',
create: 'Create', create: 'Create',
latestBuys: 'Latest buys', latestBuys: 'Latest buys',
@ -344,7 +362,6 @@ export default {
reference: 'Reference', reference: 'Reference',
observations: 'Observations', observations: 'Observations',
item: 'Item', item: 'Item',
description: 'Description',
size: 'Size', size: 'Size',
packing: 'Packing', packing: 'Packing',
grouping: 'Grouping', grouping: 'Grouping',
@ -359,7 +376,6 @@ export default {
}, },
notes: { notes: {
observationType: 'Observation type', observationType: 'Observation type',
description: 'Description',
}, },
descriptor: { descriptor: {
agency: 'Agency', agency: 'Agency',
@ -372,7 +388,6 @@ export default {
packing: 'Packing', packing: 'Packing',
grouping: 'Grouping', grouping: 'Grouping',
quantity: 'Quantity', quantity: 'Quantity',
description: 'Description',
size: 'Size', size: 'Size',
tags: 'Tags', tags: 'Tags',
type: 'Type', type: 'Type',
@ -425,6 +440,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',
@ -458,7 +474,6 @@ export default {
visible: 'Visible', visible: 'Visible',
available: 'Available', available: 'Available',
quantity: 'Quantity', quantity: 'Quantity',
description: 'Description',
price: 'Price', price: 'Price',
discount: 'Discount', discount: 'Discount',
packing: 'Packing', packing: 'Packing',
@ -483,11 +498,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',
@ -504,10 +517,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',
@ -516,6 +525,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',
@ -531,7 +542,6 @@ export default {
landed: 'Landed', landed: 'Landed',
quantity: 'Quantity', quantity: 'Quantity',
claimed: 'Claimed', claimed: 'Claimed',
description: 'Description',
price: 'Price', price: 'Price',
discount: 'Discount', discount: 'Discount',
total: 'Total', total: 'Total',
@ -547,7 +557,6 @@ export default {
responsible: 'Responsible', responsible: 'Responsible',
worker: 'Worker', worker: 'Worker',
redelivery: 'Redelivery', redelivery: 'Redelivery',
returnOfMaterial: 'RMA',
}, },
basicData: { basicData: {
customer: 'Customer', customer: 'Customer',
@ -555,7 +564,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}',
@ -793,7 +801,6 @@ export default {
orderTicketList: 'Order Ticket List', orderTicketList: 'Order Ticket List',
details: 'Details', details: 'Details',
item: 'Item', item: 'Item',
description: 'Description',
quantity: 'Quantity', quantity: 'Quantity',
price: 'Price', price: 'Price',
amount: 'Amount', amount: 'Amount',
@ -827,6 +834,7 @@ export default {
notifications: 'Notifications', notifications: 'Notifications',
workerCreate: 'New worker', workerCreate: 'New worker',
department: 'Department', department: 'Department',
pda: 'PDA',
}, },
list: { list: {
name: 'Name', name: 'Name',
@ -868,6 +876,13 @@ export default {
subscribed: 'Subscribed to the notification', subscribed: 'Subscribed to the notification',
unsubscribed: 'Unsubscribed from the notification', unsubscribed: 'Unsubscribed from the notification',
}, },
pda: {
newPDA: 'New PDA',
currentPDA: 'Current PDA',
model: 'Model',
serialNumber: 'Serial number',
removePDA: 'Deallocate PDA',
},
create: { create: {
name: 'Name', name: 'Name',
lastName: 'Last name', lastName: 'Last name',
@ -1138,7 +1153,6 @@ export default {
warehouse: 'Warehouse', warehouse: 'Warehouse',
travelFileDescription: 'Travel id { travelId }', travelFileDescription: 'Travel id { travelId }',
file: 'File', file: 'File',
description: 'Description',
}, },
}, },
item: { item: {
@ -1172,7 +1186,6 @@ export default {
clone: 'Clone', clone: 'Clone',
openCard: 'View', openCard: 'View',
openSummary: 'Summary', openSummary: 'Summary',
viewDescription: 'Description',
}, },
cardDescriptor: { cardDescriptor: {
mainList: 'Main list', mainList: 'Main list',

View File

@ -24,12 +24,14 @@ export default {
dataCreated: 'Datos creados', dataCreated: 'Datos creados',
add: 'Añadir', add: 'Añadir',
create: 'Crear', create: 'Crear',
edit: 'Modificar',
save: 'Guardar', save: 'Guardar',
remove: 'Eliminar', remove: 'Eliminar',
reset: 'Restaurar', reset: 'Restaurar',
close: 'Cerrar', close: 'Cerrar',
cancel: 'Cancelar', cancel: 'Cancelar',
confirm: 'Confirmar', confirm: 'Confirmar',
assign: 'Asignar',
back: 'Volver', back: 'Volver',
yes: 'Si', yes: 'Si',
no: 'No', no: 'No',
@ -64,12 +66,24 @@ export default {
markAll: 'Marcar todo', markAll: 'Marcar todo',
requiredField: 'Campo obligatorio', requiredField: 'Campo obligatorio',
class: 'clase', class: 'clase',
type: 'tipo', type: 'Tipo',
reason: 'motivo', reason: 'motivo',
noResults: 'Sin resultados', noResults: 'Sin resultados',
system: 'Sistema', system: 'Sistema',
warehouse: 'Almacén',
company: 'Empresa',
fieldRequired: 'Campo requerido', fieldRequired: 'Campo requerido',
allowedFilesText: 'Tipos de archivo permitidos: { allowedContentTypes }', allowedFilesText: 'Tipos de archivo permitidos: { allowedContentTypes }',
confirmDeletion: 'Confirmar eliminación',
confirmDeletionMessage: '¿Seguro que quieres eliminar?',
description: 'Descripción',
id: 'Id',
order: 'Orden',
original: 'Original',
file: 'Fichero',
selectFile: 'Seleccione un fichero',
copyClipboard: 'Copiar en portapapeles',
salesPerson: 'Comercial',
}, },
errors: { errors: {
statusUnauthorized: 'Acceso denegado', statusUnauthorized: 'Acceso denegado',
@ -77,7 +91,7 @@ export default {
statusBadGateway: 'Parece ser que el servidor ha caído', statusBadGateway: 'Parece ser que el servidor ha caído',
statusGatewayTimeout: 'No se ha podido contactar con el servidor', statusGatewayTimeout: 'No se ha podido contactar con el servidor',
userConfig: 'Error al obtener configuración de usuario', userConfig: 'Error al obtener configuración de usuario',
create: 'Error al crear', writeRequest: 'No se pudo completar la operación solicitada',
}, },
login: { login: {
title: 'Inicio de sesión', title: 'Inicio de sesión',
@ -159,13 +173,14 @@ export default {
hasDebt: 'El cliente tiene riesgo', hasDebt: 'El cliente tiene riesgo',
notChecked: 'El cliente no está comprobado', notChecked: 'El cliente no está comprobado',
noWebAccess: 'El acceso web está desactivado', noWebAccess: 'El acceso web está desactivado',
businessTypeFk: 'Tipo de negocio',
}, },
summary: { summary: {
basicData: 'Datos básicos', basicData: 'Datos básicos',
fiscalAddress: 'Dirección fiscal', fiscalAddress: 'Dirección fiscal',
fiscalData: 'Datos fiscales', fiscalData: 'Datos fiscales',
billingData: 'Datos de facturación', billingData: 'Datos de facturación',
consignee: 'Consignatario', consignee: 'Consignatario pred.',
businessData: 'Datos comerciales', businessData: 'Datos comerciales',
financialData: 'Datos financieros', financialData: 'Datos financieros',
customerId: 'ID cliente', customerId: 'ID cliente',
@ -218,6 +233,8 @@ export default {
recoverySince: 'Recobro desde', recoverySince: 'Recobro desde',
businessType: 'Tipo de negocio', businessType: 'Tipo de negocio',
city: 'Población', city: 'Población',
rating: 'Clasificación',
recommendCredit: 'Crédito recomendado',
}, },
basicData: { basicData: {
socialName: 'Nombre fiscal', socialName: 'Nombre fiscal',
@ -272,6 +289,7 @@ export default {
basicData: 'Datos básicos', basicData: 'Datos básicos',
buys: 'Compras', buys: 'Compras',
notes: 'Notas', notes: 'Notas',
dms: 'Gestión documental',
log: 'Historial', log: 'Historial',
create: 'Crear', create: 'Crear',
latestBuys: 'Últimas compras', latestBuys: 'Últimas compras',
@ -292,8 +310,8 @@ export default {
reference: 'Referencia', reference: 'Referencia',
invoiceNumber: 'Núm. factura', invoiceNumber: 'Núm. factura',
ordered: 'Pedida', ordered: 'Pedida',
confirmed: 'Confirmado', confirmed: 'Confirmada',
booked: 'Asentado', booked: 'Contabilizada',
raid: 'Redada', raid: 'Redada',
excludedFromAvailable: 'Inventario', excludedFromAvailable: 'Inventario',
travelReference: 'Referencia', travelReference: 'Referencia',
@ -343,7 +361,6 @@ export default {
reference: 'Referencia', reference: 'Referencia',
observations: 'Observaciónes', observations: 'Observaciónes',
item: 'Artículo', item: 'Artículo',
description: 'Descripción',
size: 'Medida', size: 'Medida',
packing: 'Packing', packing: 'Packing',
grouping: 'Grouping', grouping: 'Grouping',
@ -358,7 +375,6 @@ export default {
}, },
notes: { notes: {
observationType: 'Tipo de observación', observationType: 'Tipo de observación',
description: 'Descripción',
}, },
descriptor: { descriptor: {
agency: 'Agencia', agency: 'Agencia',
@ -371,7 +387,6 @@ export default {
packing: 'Packing', packing: 'Packing',
grouping: 'Grouping', grouping: 'Grouping',
quantity: 'Cantidad', quantity: 'Cantidad',
description: 'Descripción',
size: 'Medida', size: 'Medida',
tags: 'Etiquetas', tags: 'Etiquetas',
type: 'Tipo', type: 'Tipo',
@ -424,6 +439,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',
@ -457,7 +473,6 @@ export default {
visible: 'Visible', visible: 'Visible',
available: 'Disponible', available: 'Disponible',
quantity: 'Cantidad', quantity: 'Cantidad',
description: 'Descripción',
price: 'Precio', price: 'Precio',
discount: 'Descuento', discount: 'Descuento',
packing: 'Encajado', packing: 'Encajado',
@ -482,14 +497,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',
}, },
@ -503,10 +516,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',
@ -515,6 +524,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',
@ -530,7 +541,6 @@ export default {
landed: 'Entregado', landed: 'Entregado',
quantity: 'Cantidad', quantity: 'Cantidad',
claimed: 'Reclamado', claimed: 'Reclamado',
description: 'Descripción',
price: 'Precio', price: 'Precio',
discount: 'Descuento', discount: 'Descuento',
total: 'Total', total: 'Total',
@ -546,7 +556,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',
@ -554,7 +563,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:
@ -701,7 +709,6 @@ export default {
orderTicketList: 'Tickets del pedido', orderTicketList: 'Tickets del pedido',
details: 'Detalles', details: 'Detalles',
item: 'Item', item: 'Item',
description: 'Descripción',
quantity: 'Cantidad', quantity: 'Cantidad',
price: 'Precio', price: 'Precio',
amount: 'Monto', amount: 'Monto',
@ -714,7 +721,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',
@ -746,7 +753,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',
@ -827,6 +834,7 @@ export default {
notifications: 'Notificaciones', notifications: 'Notificaciones',
workerCreate: 'Nuevo trabajador', workerCreate: 'Nuevo trabajador',
department: 'Departamentos', department: 'Departamentos',
pda: 'PDA',
}, },
list: { list: {
name: 'Nombre', name: 'Nombre',
@ -868,6 +876,13 @@ export default {
subscribed: 'Se ha suscrito a la notificación', subscribed: 'Se ha suscrito a la notificación',
unsubscribed: 'Se ha dado de baja de la notificación', unsubscribed: 'Se ha dado de baja de la notificación',
}, },
pda: {
newPDA: 'Nueva PDA',
currentPDA: 'PDA Actual',
model: 'Modelo',
serialNumber: 'Número de serie',
removePDA: 'Desasignar PDA',
},
create: { create: {
name: 'Nombre', name: 'Nombre',
lastName: 'Apellido', lastName: 'Apellido',
@ -1138,7 +1153,6 @@ export default {
warehouse: 'Almacén', warehouse: 'Almacén',
travelFileDescription: 'Id envío { travelId }', travelFileDescription: 'Id envío { travelId }',
file: 'Fichero', file: 'Fichero',
description: 'Descripción',
}, },
}, },
item: { item: {
@ -1172,7 +1186,6 @@ export default {
clone: 'Clonar', clone: 'Clonar',
openCard: 'Ficha', openCard: 'Ficha',
openSummary: 'Detalles', openSummary: 'Detalles',
viewDescription: 'Descripción',
}, },
cardDescriptor: { cardDescriptor: {
mainList: 'Listado principal', mainList: 'Listado principal',

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,
}, },
{ {
@ -172,6 +172,7 @@ function openDialog(dmsId) {
<CardSummary <CardSummary
ref="summary" ref="summary"
:url="`Claims/${entityId}/getSummary`" :url="`Claims/${entityId}/getSummary`"
:entity-id="entityId"
@on-fetch="getClaimDms" @on-fetch="getClaimDms"
> >
<template #header="{ entity: { claim } }"> <template #header="{ entity: { claim } }">
@ -179,9 +180,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 +195,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 +219,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 +280,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 +303,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 +322,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 +346,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 +356,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 +410,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 +420,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

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

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

@ -40,6 +40,15 @@ const setData = (entity) => (data.value = useCardDescription(entity.name, entity
data-key="customerData" data-key="customerData"
> >
<template #body="{ entity }"> <template #body="{ entity }">
<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.securedCredit')"
:value="toCurrency(entity.creditInsurance)"
/>
<VnLv :label="t('customer.card.debt')" :value="toCurrency(entity.debt)" />
<VnLv v-if="entity.salesPersonUser" :label="t('customer.card.salesPerson')"> <VnLv v-if="entity.salesPersonUser" :label="t('customer.card.salesPerson')">
<template #value> <template #value>
<VnUserLink <VnUserLink
@ -48,13 +57,10 @@ const setData = (entity) => (data.value = useCardDescription(entity.name, entity
/> />
</template> </template>
</VnLv> </VnLv>
<VnLv :label="t('customer.card.credit')" :value="toCurrency(entity.credit)" />
<VnLv <VnLv
:label="t('customer.card.securedCredit')" :label="t('customer.card.businessTypeFk')"
:value="toCurrency(entity.creditInsurance)" :value="entity.businessTypeFk"
/> />
<VnLv :label="t('customer.card.payMethod')" :value="entity.payMethod.name" />
<VnLv :label="t('customer.card.debt')" :value="toCurrency(entity.debt)" />
</template> </template>
<template #icons="{ entity }"> <template #icons="{ entity }">
<QCardActions> <QCardActions>

View File

@ -62,9 +62,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" />
@ -96,9 +96,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')"
@ -121,37 +124,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
{{ t('customer.summary.fiscalAddress') }} class="header header-link"
<QIcon name="open_in_new" color="primary" /> :href="`#/customer/${entityId}/fiscal-data`"
link
>
{{ t('customer.summary.fiscalData') }}
<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')"
@ -159,20 +183,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')"
@ -188,21 +224,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
@ -230,12 +267,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="vn:grafana" color="primary" /> <QIcon name="vn:grafana" />
</a> </a>
<VnLv <VnLv
:label="t('customer.summary.risk')" :label="t('customer.summary.risk')"
@ -276,7 +313,30 @@ const creditWarning = computed(() => {
:label="t('customer.summary.recoverySince')" :label="t('customer.summary.recoverySince')"
:value="toDate(entity.recovery.started)" :value="toDate(entity.recovery.started)"
/> />
<VnLv
:label="t('customer.summary.rating')"
:value="entity.rating"
:info="t('valueInfo', { min: 1, max: 20 })"
/>
<VnLv
:label="t('customer.summary.recommendCredit')"
:value="entity.recommendedCredit"
/>
</QCard> </QCard>
</template> </template>
</CardSummary> </CardSummary>
</template> </template>
<style lang="scss" scoped>
@media (min-width: $breakpoint-md) {
.summary .vn-one {
min-width: 300px;
}
}
</style>
<i18n>
en:
valueInfo: Value from {min} to {max}. The higher the better value
es:
valueInfo: Valor de {min} a {max}. Cuanto más alto, mejor valor
</i18n>

View File

@ -13,7 +13,7 @@ import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { dashIfEmpty, toDate } from 'src/filters'; import { toDate } from 'src/filters';
const { t } = useI18n(); const { t } = useI18n();
const router = useRouter(); const router = useRouter();
@ -477,17 +477,11 @@ const stopEventPropagation = (event, col) => {
event.stopPropagation(); event.stopPropagation();
}; };
const navigateToTravelId = (id) => { const navigateToTravelId = (id) => router.push({ path: `/customer/${id}` });
router.push({ path: `/customer/${id}` });
};
const selectCustomerId = (id) => { const selectCustomerId = (id) => (selectedCustomerId.value = id);
selectedCustomerId.value = id;
};
const selectSalesPersonId = (id) => { const selectSalesPersonId = (id) => (selectedSalesPersonId.value = id);
selectedSalesPersonId.value = id;
};
</script> </script>
<template> <template>
@ -521,37 +515,50 @@ const selectSalesPersonId = (id) => {
class="full-width q-mt-md" class="full-width q-mt-md"
row-key="id" row-key="id"
:visible-columns="visibleColumns" :visible-columns="visibleColumns"
@row-click="(evt, row, id) => navigateToTravelId(row.id)"
> >
<template #body="props"> <template #body-cell="{ col, value }">
<QTr <QTd @click="stopEventPropagation($event, col)">
:props="props" {{ value }}
@click="navigateToTravelId(props.row.id)" </QTd>
class="cursor-pointer" </template>
> <template #body-cell-id="props">
<QTd <QTd @click="stopEventPropagation($event, props.col)">
v-for="col in props.cols" <component
:key="col.name" :is="tableColumnComponents[props.col.name].component"
:props="props" class="col-content"
@click="stopEventPropagation($event, col)" v-bind="tableColumnComponents[props.col.name].props(props)"
@click="tableColumnComponents[props.col.name].event(props)"
> >
<component <CustomerDescriptorProxy :id="props.row.id" />
:is="tableColumnComponents[col.name].component" {{ props.row.id }}
class="col-content" </component>
v-bind="tableColumnComponents[col.name].props(props)" </QTd>
@click="tableColumnComponents[col.name].event(props)" </template>
> <template #body-cell-salesPersonFk="props">
{{ dashIfEmpty(col.value) }} <QTd @click="stopEventPropagation($event, props.col)">
<WorkerDescriptorProxy <component
v-if="props.row.salesPersonFk" v-if="props.row.salesPerson"
:id="selectedSalesPersonId" class="col-content"
/> :is="tableColumnComponents[props.col.name].component"
<CustomerDescriptorProxy v-bind="tableColumnComponents[props.col.name].props(props)"
v-if="props.row.id" @click="tableColumnComponents[props.col.name].event(props)"
:id="selectedCustomerId" >
/> <WorkerDescriptorProxy :id="props.row.salesPersonFk" />
</component> {{ props.row.salesPerson }}
</QTd> </component>
</QTr> <span class="col-content" v-else>-</span>
</QTd>
</template>
<template #body-cell-actions="props">
<QTd @click="stopEventPropagation($event, props.col)">
<component
:is="tableColumnComponents[props.col.name].component"
class="col-content"
v-bind="tableColumnComponents[props.col.name].props(props)"
@click="tableColumnComponents[props.col.name].event(props)"
/>
</QTd>
</template> </template>
</QTable> </QTable>
</QPage> </QPage>

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

@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import axios from 'axios'; import axios from 'axios';
import VnLocation from 'src/components/common/VnLocation.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
@ -20,13 +20,9 @@ const router = useRouter();
const formInitialData = reactive({ isDefaultAddress: false }); const formInitialData = reactive({ isDefaultAddress: false });
const townsFetchDataRef = ref(null);
const postcodeFetchDataRef = ref(null);
const urlCreate = ref(''); const urlCreate = ref('');
const postcodesOptions = ref([]); const postcodesOptions = ref([]);
const citiesLocationOptions = ref([]);
const provincesLocationOptions = ref([]);
const agencyModes = ref([]); const agencyModes = ref([]);
const incoterms = ref([]); const incoterms = ref([]);
const customsAgents = ref([]); const customsAgents = ref([]);
@ -36,14 +32,6 @@ onBeforeMount(() => {
getCustomsAgents(); getCustomsAgents();
}); });
const onPostcodeCreated = async ({ code, provinceFk, townFk }, formData) => {
await postcodeFetchDataRef.value.fetch();
await townsFetchDataRef.value.fetch();
formData.postalCode = code;
formData.provinceFk = provinceFk;
formData.city = citiesLocationOptions.value.find((town) => town.id === townFk).name;
};
const getCustomsAgents = async () => { const getCustomsAgents = async () => {
const { data } = await axios.get('CustomsAgents'); const { data } = await axios.get('CustomsAgents');
customsAgents.value = data; customsAgents.value = data;
@ -61,26 +49,16 @@ const toCustomerConsignees = () => {
}, },
}); });
}; };
function handleLocation(data, location) {
const { town, code, provinceFk, countryFk } = location ?? {};
data.postalCode = code;
data.city = town;
data.provinceFk = provinceFk;
data.countryFk = countryFk;
}
</script> </script>
<template> <template>
<FetchData
@on-fetch="(data) => (postcodesOptions = data)"
auto-load
ref="postcodeFetchDataRef"
url="Postcodes/location"
/>
<FetchData
@on-fetch="(data) => (citiesLocationOptions = data)"
auto-load
ref="townsFetchDataRef"
url="Towns/location"
/>
<FetchData
@on-fetch="(data) => (provincesLocationOptions = data)"
auto-load
url="Provinces/location"
/>
<fetch-data <fetch-data
@on-fetch="(data) => (agencyModes = data)" @on-fetch="(data) => (agencyModes = data)"
auto-load auto-load
@ -113,83 +91,17 @@ const toCustomerConsignees = () => {
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">
<VnSelectDialog <VnLocation
:label="t('Postcode')"
:options="postcodesOptions"
:roles-allowed-to-create="['deliveryAssistant']"
:rules="validate('Worker.postcode')" :rules="validate('Worker.postcode')"
hide-selected :roles-allowed-to-create="['deliveryAssistant']"
option-label="code" :options="postcodesOptions"
option-value="code" v-model="data.location"
v-model="data.postalCode" @update:model-value="(location) => handleLocation(data, location)"
> ></VnLocation>
<template #form>
<CustomerCreateNewPostcode
@on-data-saved="onPostcodeCreated($event, data)"
/>
</template>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection v-if="scope.opt">
<QItemLabel>{{ scope.opt.code }}</QItemLabel>
<QItemLabel caption>
{{ scope.opt.code }} -
{{ scope.opt.town.name }}
({{ scope.opt.town.province.name }},
{{ scope.opt.town.province.country.country }})
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectDialog>
</div>
<div class="col">
<!-- ciudades -->
<VnSelectFilter
:label="t('City')"
:options="citiesLocationOptions"
hide-selected
option-label="name"
option-value="name"
v-model="data.city"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt.name }}</QItemLabel>
<QItemLabel caption>
{{
`${scope.opt.name}, ${scope.opt.province.name} (${scope.opt.province.country.country})`
}}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</div> </div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('Province')"
:options="provincesLocationOptions"
hide-selected
option-label="name"
option-value="id"
v-model="data.provinceFk"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{
`${scope.opt.name} (${scope.opt.country.country})`
}}</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</div>
<div class="col"> <div class="col">
<VnSelectFilter <VnSelectFilter
:label="t('Agency')" :label="t('Agency')"

View File

@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import axios from 'axios'; import axios from 'axios';
import VnLocation from 'src/components/common/VnLocation.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
@ -17,13 +17,8 @@ import CustomsNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCus
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const townsFetchDataRef = ref(null);
const postcodeFetchDataRef = ref(null);
const urlUpdate = ref(''); const urlUpdate = ref('');
const postcodesOptions = ref([]); const postcodesOptions = ref([]);
const citiesLocationOptions = ref([]);
const provincesLocationOptions = ref([]);
const agencyModes = ref([]); const agencyModes = ref([]);
const incoterms = ref([]); const incoterms = ref([]);
const customsAgents = ref([]); const customsAgents = ref([]);
@ -34,14 +29,6 @@ onBeforeMount(() => {
urlUpdate.value = `Clients/${route.params.id}/updateAddress/${route.params.consigneeId}`; urlUpdate.value = `Clients/${route.params.id}/updateAddress/${route.params.consigneeId}`;
}); });
const onPostcodeCreated = async ({ code, provinceFk, townFk }, formData) => {
await postcodeFetchDataRef.value.fetch();
await townsFetchDataRef.value.fetch();
formData.postalCode = code;
formData.provinceFk = provinceFk;
formData.city = citiesLocationOptions.value.find((town) => town.id === townFk).name;
};
const getData = async (observations) => { const getData = async (observations) => {
observationTypes.value = observations; observationTypes.value = observations;
@ -97,26 +84,16 @@ const onDataSaved = () => {
}; };
axios.post('AddressObservations/crud', payload); axios.post('AddressObservations/crud', payload);
}; };
function handleLocation(data, location) {
const { town, code, provinceFk, countryFk } = location ?? {};
data.postalCode = code;
data.city = town;
data.provinceFk = provinceFk;
data.countryFk = countryFk;
}
</script> </script>
<template> <template>
<FetchData
ref="postcodeFetchDataRef"
@on-fetch="(data) => (postcodesOptions = data)"
auto-load
url="Postcodes/location"
/>
<FetchData
ref="townsFetchDataRef"
@on-fetch="(data) => (citiesLocationOptions = data)"
auto-load
url="Towns/location"
/>
<FetchData
@on-fetch="(data) => (provincesLocationOptions = data)"
auto-load
url="Provinces/location"
/>
<fetch-data <fetch-data
@on-fetch="(data) => (agencyModes = data)" @on-fetch="(data) => (agencyModes = data)"
auto-load auto-load
@ -168,83 +145,17 @@ const onDataSaved = () => {
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">
<VnSelectDialog <VnLocation
:label="t('Postcode')"
:options="postcodesOptions"
:roles-allowed-to-create="['deliveryAssistant']"
:rules="validate('Worker.postcode')" :rules="validate('Worker.postcode')"
hide-selected :roles-allowed-to-create="['deliveryAssistant']"
option-label="code" :options="postcodesOptions"
option-value="code" v-model="data.location"
v-model="data.postalCode" @update:model-value="(location) => handleLocation(data, location)"
> ></VnLocation>
<template #form>
<CustomerCreateNewPostcode
@on-data-saved="onPostcodeCreated($event, data)"
/>
</template>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection v-if="scope.opt">
<QItemLabel>{{ scope.opt.code }}</QItemLabel>
<QItemLabel caption>
{{ scope.opt.code }} -
{{ scope.opt.town.name }}
({{ scope.opt.town.province.name }},
{{ scope.opt.town.province.country.country }})
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectDialog>
</div>
<div class="col">
<!-- ciudades -->
<VnSelectFilter
:label="t('City')"
:options="citiesLocationOptions"
hide-selected
option-label="name"
option-value="name"
v-model="data.city"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt.name }}</QItemLabel>
<QItemLabel caption>
{{
`${scope.opt.name}, ${scope.opt.province.name} (${scope.opt.province.country.country})`
}}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</div> </div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('Province')"
:options="provincesLocationOptions"
hide-selected
option-label="name"
option-value="id"
v-model="data.provinceFk"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{
`${scope.opt.name} (${scope.opt.country.country})`
}}</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</div>
<div class="col"> <div class="col">
<VnSelectFilter <VnSelectFilter
:label="t('Agency')" :label="t('Agency')"

View File

@ -112,7 +112,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

@ -44,7 +44,7 @@ const columns = computed(() => [
align: 'left', align: 'left',
}, },
{ {
label: t('entry.buys.description'), label: t('globals.description'),
name: 'description', name: 'description',
field: 'description', field: 'description',
align: 'left', align: 'left',
@ -214,7 +214,7 @@ const redirectToBuysView = () => {
class="cursor-pointer" class="cursor-pointer"
@click="inputFileRef.pickFiles()" @click="inputFileRef.pickFiles()"
> >
<QTooltip>{{ t('Select a file') }}</QTooltip> <QTooltip>{{ t('globals.selectFile') }}</QTooltip>
</QIcon> </QIcon>
</template> </template>
</QFile> </QFile>
@ -292,6 +292,6 @@ const redirectToBuysView = () => {
<i18n> <i18n>
es: es:
Select a file: Selecciona un fichero globals.selectFile: Selecciona un fichero
Some of the imported buys does not have an item: Algunas de las compras importadas no tienen un artículo Some of the imported buys does not have an item: Algunas de las compras importadas no tienen un artículo
</i18n> </i18n>

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

@ -0,0 +1,11 @@
<script setup>
import VnDmsList from 'src/components/common/VnDmsList.vue';
</script>
<template>
<VnDmsList
model="EntryDms"
update-model="EntryDms"
default-dms-code="entry"
filter="entryFk"
/>
</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('entry.notes.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: () => {},
}, },
}; };
@ -160,152 +177,121 @@ const fetchEntryBuys = async () => {
<template #header> <template #header>
<span>{{ entry.id }} - {{ entry.supplier.nickname }}</span> <span>{{ entry.id }} - {{ entry.supplier.nickname }}</span>
</template> </template>
<template #body> <template #body>
<QCard class="vn-one"> <QCard class="vn-one">
<router-link <router-link
:to="{ name: 'EntryBasicData', params: { id: entityId } }" :to="{ name: 'EntryBasicData', params: { id: entityId } }"
class="header link" class="header header-link"
> >
{{ t('globals.summary.basicData') }} {{ t('globals.summary.basicData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</router-link> </router-link>
<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">
<VnLv
:label="t('entry.summary.company')"
:value="entry.company.code"
/>
</div>
<div class="col">
<VnLv
:label="t('entry.summary.reference')"
:value="entry.reference"
/>
</div>
<div class="col">
<VnLv
:label="t('entry.summary.invoiceNumber')"
:value="entry.invoiceNumber"
/>
</div>
</VnRow>
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<router-link <router-link
:to="{ name: 'EntryBasicData', params: { id: entityId } }" :to="{ name: 'EntryBasicData', params: { id: entityId } }"
class="header link" class="header header-link"
> >
{{ t('globals.summary.basicData') }} {{ t('globals.summary.basicData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</router-link> </router-link>
<VnRow>
<div class="col"> <VnLv :label="t('entry.summary.travelReference')">
<VnLv <template #value>
:label="t('entry.summary.ordered')" <span class="link">
:value="entry.isOrdered" {{ entry.travel.ref }}
/> <TravelDescriptorProxy :id="entry.travel.id" />
</div> </span>
<div class="col"> </template>
<VnLv </VnLv>
:label="t('entry.summary.confirmed')"
:value="entry.isConfirmed" <VnLv
/> :label="t('entry.summary.travelAgency')"
</div> :value="entry.travel.agency.name"
<div class="col"> />
<VnLv
:label="t('entry.summary.booked')" <VnLv
:value="entry.isBooked" :label="t('entry.summary.travelShipped')"
/> :value="toDate(entry.travel.shipped)"
</div> />
<div class="col">
<VnLv :label="t('entry.summary.raid')" :value="entry.isRaid" /> <VnLv
</div> :label="t('entry.summary.travelWarehouseOut')"
<div class="col"> :value="entry.travel.warehouseOut.name"
<VnLv />
:label="t('entry.summary.excludedFromAvailable')"
:value="entry.isExcludedFromAvailable" <QCheckbox
/></div :label="t('entry.summary.travelDelivered')"
></VnRow> v-model="entry.travel.isDelivered"
:disable="true"
/>
<VnLv
:label="t('entry.summary.travelLanded')"
:value="toDate(entry.travel.landed)"
/>
<VnLv
:label="t('entry.summary.travelWarehouseIn')"
:value="entry.travel.warehouseIn.name"
/>
<QCheckbox
:label="t('entry.summary.travelReceived')"
v-model="entry.travel.isReceived"
:disable="true"
/>
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<router-link <router-link
:to="{ name: 'TravelSummary', params: { id: entry.travel.id } }" :to="{ name: 'TravelSummary', params: { id: entry.travel.id } }"
class="header link" class="header header-link"
> >
{{ t('Travel data') }} {{ t('Travel data') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</router-link> </router-link>
<VnRow> <QCheckbox
<div class="col"> :label="t('entry.summary.ordered')"
<VnLv :label="t('entry.summary.travelReference')"> v-model="entry.isOrdered"
<template #value> :disable="true"
<span class="link"> />
{{ entry.travel.ref }} <QCheckbox
<TravelDescriptorProxy :id="entry.travel.id" /> :label="t('entry.summary.confirmed')"
</span> v-model="entry.isConfirmed"
</template> :disable="true"
</VnLv> />
</div> <QCheckbox
<div class="col"> :label="t('entry.summary.booked')"
<VnLv v-model="entry.isBooked"
:label="t('entry.summary.travelAgency')" :disable="true"
:value="entry.travel.agency.name" />
/> <QCheckbox
</div> :label="t('entry.summary.raid')"
<div class="col"> v-model="entry.isRaid"
<VnLv :disable="true"
:label="t('entry.summary.travelShipped')" />
:value="toDate(entry.travel.shipped)" <QCheckbox
/> :label="t('entry.summary.excludedFromAvailable')"
</div> v-model="entry.isExcludedFromAvailable"
<div class="col"> :disable="true"
<VnLv />
:label="t('entry.summary.travelWarehouseOut')"
:value="entry.travel.warehouseOut.name"
/>
</div>
<div class="col">
<VnLv
:label="t('entry.summary.travelDelivered')"
:value="entry.travel.isDelivered"
/>
</div>
<div class="col">
<VnLv
:label="t('entry.summary.travelLanded')"
:value="toDate(entry.travel.landed)"
/>
</div>
<div class="col">
<VnLv
:label="t('entry.summary.travelWarehouseIn')"
: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%">
jsegarra marked this conversation as resolved
Review

En esta seccion en salix hay 3 columnas/cards, en lilium debe ser igual.
Hay una pequeña modificación:
Lo que ahora hay en la card datos basicos se divide en dos columnas
card 1(Link a basic data): Comisión, Moneda, Empresa ,Referencia, Núm. factura
card 2(Link a basic data): Pedida, Confirmado, Asentado, Redada, Inventario
card 3(Link a travel): Esta seccion igual que esta son los datos de envio

En esta seccion en salix hay 3 columnas/cards, en lilium debe ser igual. Hay una pequeña modificación: Lo que ahora hay en la card datos basicos se divide en dos columnas card 1(Link a basic data): Comisión, Moneda, Empresa ,Referencia, Núm. factura card 2(Link a basic data): Pedida, Confirmado, Asentado, Redada, Inventario card 3(Link a travel): Esta seccion igual que esta son los datos de envio
Review

Implementado!

Commit: 84a0be0a5c

Implementado! Commit: https://gitea.verdnatura.es/verdnatura/salix-front/commit/84a0be0a5c787448d1c34f459bbae4fc166fe977
Review

Resuelvo conversación porque lo he visto bien y he compartido la evidencia del cambio con Javi

Resuelvo conversación porque lo he visto bien y he compartido la evidencia del cambio con Javi
<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"
@ -318,7 +304,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="
@ -368,5 +357,4 @@ const fetchEntryBuys = async () => {
<i18n> <i18n>
es: es:
Travel data: Datos envío Travel data: Datos envío
</i18n> </i18n>

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('entry.latestBuys.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',
jgallego marked this conversation as resolved Outdated

en esta seccion hay que añadir el filtro lateral de busqueda igual que esta en salix.

en esta seccion hay que añadir el filtro lateral de busqueda igual que esta en salix.
https://redmine.verdnatura.es/issues/6537#note-6

Buenas @jgallego por nuestra parte lo teniamos en visto esto en esta tarea: https://mindshore-verdnatura.atlassian.net/jira/software/projects/SCRUM/boards/1/backlog?selectedIssue=SCRUM-162&text=entry

Si te parece lo mandamos en una PR aparte

Buenas @jgallego por nuestra parte lo teniamos en visto esto en esta tarea: https://mindshore-verdnatura.atlassian.net/jira/software/projects/SCRUM/boards/1/backlog?selectedIssue=SCRUM-162&text=entry Si te parece lo mandamos en una PR aparte

perfecto, resuelvo conversacion

perfecto, resuelvo conversacion
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),
}, },
]); ]);
@ -214,7 +566,7 @@ const editTableCellFormFieldsOptions = [
{ field: 'grouping', label: t('entry.latestBuys.grouping') }, { field: 'grouping', label: t('entry.latestBuys.grouping') },
{ field: 'packageValue', label: t('entry.latestBuys.packageValue') }, { field: 'packageValue', label: t('entry.latestBuys.packageValue') },
{ field: 'weight', label: t('entry.latestBuys.weight') }, { field: 'weight', label: t('entry.latestBuys.weight') },
{ field: 'description', label: t('entry.latestBuys.description') }, { field: 'description', label: t('globals.description') },
{ field: 'size', label: t('entry.latestBuys.size') }, { field: 'size', label: t('entry.latestBuys.size') },
{ field: 'weightByPiece', label: t('entry.latestBuys.weightByPiece') }, { field: 'weightByPiece', label: t('entry.latestBuys.weightByPiece') },
{ field: 'packingOut', label: t('entry.latestBuys.packingOut') }, { field: 'packingOut', label: t('entry.latestBuys.packingOut') },
@ -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
@ -291,6 +702,7 @@ onMounted(async () => {
<QBtn flat color="primary"> <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 }">

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

@ -174,7 +174,12 @@ async function upsert() {
@on-fetch="(data) => (userConfig = data)" @on-fetch="(data) => (userConfig = data)"
auto-load auto-load
/> />
<FormModel v-if="invoiceIn" :url="`InvoiceIns/${route.params.id}`" model="invoiceIn"> <FormModel
v-if="invoiceIn"
:url="`InvoiceIns/${route.params.id}`"
model="invoiceIn"
:auto-load="true"
>
<template #form="{ data }"> <template #form="{ data }">
<div class="row q-gutter-md q-mb-md"> <div class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">
@ -509,7 +514,7 @@ async function upsert() {
@click="inputFileRef.pickFiles()" @click="inputFileRef.pickFiles()"
> >
<QTooltip> <QTooltip>
{{ t('Select a file') }} {{ t('globals.selectFile') }}
</QTooltip> </QTooltip>
</QBtn> </QBtn>
<QBtn icon="info" flat round padding="xs"> <QBtn icon="info" flat round padding="xs">
@ -618,7 +623,7 @@ async function upsert() {
@click="inputFileRef.pickFiles()" @click="inputFileRef.pickFiles()"
> >
<QTooltip> <QTooltip>
{{ t('Select a file') }} {{ t('globals.selectFile') }}
</QTooltip> </QTooltip>
</QBtn> </QBtn>
<QBtn icon="info" flat round padding="xs"> <QBtn icon="info" flat round padding="xs">
@ -687,7 +692,6 @@ async function upsert() {
Generate identifier for original file: Generar identificador para archivo original Generate identifier for original file: Generar identificador para archivo original
File: Fichero File: Fichero
Create document: Crear documento Create document: Crear documento
Select a file: Seleccione un fichero
Allowed content types: Tipos de archivo permitidos Allowed content types: Tipos de archivo permitidos
The company can't be empty: La empresa no puede estar vacía The company can't be empty: La empresa no puede estar vacía
The warehouse can't be empty: El almacén no puede estar vacío The warehouse can't be empty: El almacén no puede estar vacío

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

@ -95,15 +95,20 @@ const ticketsColumns = ref([
</script> </script>
<template> <template>
<CardSummary ref="summary" :url="`InvoiceOuts/${entityId}/summary`"> <CardSummary
ref="summary"
:url="`InvoiceOuts/${entityId}/summary`"
:entity-id="entityId"
>
<template #header="{ entity: { invoiceOut } }"> <template #header="{ entity: { invoiceOut } }">
<div>{{ invoiceOut.ref }} - {{ invoiceOut.client?.socialName }}</div> <div>{{ invoiceOut.ref }} - {{ invoiceOut.client?.socialName }}</div>
</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 +131,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 +146,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

@ -31,7 +31,7 @@ const detailsColumns = ref([
}, },
{ {
name: 'description', name: 'description',
label: t('order.summary.description'), label: t('globals.description'),
field: (row) => row?.item?.name, field: (row) => row?.item?.name,
}, },
{ {
@ -167,7 +167,7 @@ const detailsColumns = ref([
<template #header="props"> <template #header="props">
<QTr :props="props"> <QTr :props="props">
<QTh auto-width>{{ t('order.summary.item') }}</QTh> <QTh auto-width>{{ t('order.summary.item') }}</QTh>
<QTh>{{ t('order.summary.description') }}</QTh> <QTh>{{ t('globals.description') }}</QTh>
<QTh auto-width>{{ t('order.summary.quantity') }}</QTh> <QTh auto-width>{{ t('order.summary.quantity') }}</QTh>
<QTh auto-width>{{ t('order.summary.price') }}</QTh> <QTh auto-width>{{ t('order.summary.price') }}</QTh>
<QTh auto-width>{{ t('order.summary.amount') }}</QTh> <QTh auto-width>{{ t('order.summary.amount') }}</QTh>

View File

@ -132,12 +132,11 @@ const openBuscaman = async (route, ticket) => {
<template> <template>
<div class="q-pa-md"> <div class="q-pa-md">
<CardSummary ref="summary" :url="`Routes/${entityId}/summary`"> <CardSummary
<template #header-left> ref="summary"
<RouterLink :to="{ name: `RouteSummary`, params: { id: entityId } }"> :url="`Routes/${entityId}/summary`"
<QIcon name="open_in_new" color="white" size="sm" /> :entity-id="entityId"
</RouterLink> >
</template>
<template #header="{ entity }"> <template #header="{ entity }">
<span>{{ `${entity?.route.id} - ${entity?.route?.description}` }}</span> <span>{{ `${entity?.route.id} - ${entity?.route?.description}` }}</span>
</template> </template>
@ -199,7 +198,7 @@ const openBuscaman = async (route, ticket) => {
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<div class="header"> <div class="header">
{{ t('route.summary.description') }} {{ t('globals.description') }}
</div> </div>
<p> <p>
{{ dashIfEmpty(entity?.route?.description) }} {{ dashIfEmpty(entity?.route?.description) }}

View File

@ -6,7 +6,7 @@ import { useStateStore } from 'stores/useStateStore';
import CardSummary from 'components/ui/CardSummary.vue'; import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'components/ui/VnLv.vue'; import VnLv from 'components/ui/VnLv.vue';
import ShelvingFilter from 'pages/Shelving/Card/ShelvingFilter.vue'; import ShelvingFilter from 'pages/Shelving/Card/ShelvingFilter.vue';
import VnUserLink from "components/ui/VnUserLink.vue"; import VnUserLink from 'components/ui/VnUserLink.vue';
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -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

@ -18,13 +18,16 @@ const quasar = useQuasar();
const { notify } = useNotify(); const { notify } = useNotify();
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const bankEntitiesRef = ref(null);
const supplier = ref(null); const supplier = ref(null);
const supplierAccountRef = ref(null); const supplierAccountRef = ref(null);
const wireTransferFk = ref(null); const wireTransferFk = ref(null);
const bankEntitiesOptions = ref([]); const bankEntitiesOptions = ref([]);
const onBankEntityCreated = (data) => { const onBankEntityCreated = async (dataSaved, rowData) => {
bankEntitiesOptions.value.push(data); await bankEntitiesRef.value.fetch();
rowData.bankEntityFk = dataSaved.id;
}; };
const onChangesSaved = () => { const onChangesSaved = () => {
@ -63,6 +66,7 @@ onMounted(() => {
</script> </script>
<template> <template>
<FetchData <FetchData
ref="bankEntitiesRef"
url="BankEntities" url="BankEntities"
@on-fetch="(data) => (bankEntitiesOptions = data)" @on-fetch="(data) => (bankEntitiesOptions = data)"
auto-load auto-load
@ -114,13 +118,16 @@ onMounted(() => {
:label="t('worker.create.bankEntity')" :label="t('worker.create.bankEntity')"
v-model="row.bankEntityFk" v-model="row.bankEntityFk"
:options="bankEntitiesOptions" :options="bankEntitiesOptions"
option-label="name" option-label="bic"
option-value="id" option-value="id"
hide-selected hide-selected
> >
<template #form> <template #form>
<CreateBankEntityForm <CreateBankEntityForm
@on-data-saved="onBankEntityCreated($event)" @on-data-saved="
(_, requestResponse) =>
onBankEntityCreated(requestResponse, row)
"
:show-entity-field="false" :show-entity-field="false"
/> />
</template> </template>

View File

@ -83,8 +83,13 @@ const redirectToUpdateView = (addressData) => {
<QPageSticky :offset="[20, 20]"> <QPageSticky :offset="[20, 20]">
<QBtn fab icon="add" color="primary" @click="redirectToCreateView()" /> <QBtn fab icon="add" color="primary" @click="redirectToCreateView()" />
<QTooltip> <QTooltip>
{{ t('supplier.list.newSupplier') }} {{ t('New address') }}
</QTooltip> </QTooltip>
</QPageSticky> </QPageSticky>
</QPage> </QPage>
</template> </template>
<i18n>
es:
New address: Nueva dirección
</i18n>

View File

@ -10,6 +10,7 @@ import VnInput from 'src/components/common/VnInput.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import CustomerCreateNewPostcode from 'src/components/CreateNewPostcodeForm.vue'; import CustomerCreateNewPostcode from 'src/components/CreateNewPostcodeForm.vue';
import VnLocation from 'src/components/common/VnLocation.vue';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
@ -55,27 +56,16 @@ onMounted(() => {
updateAddressForm(addressData); updateAddressForm(addressData);
} }
}); });
function handleLocation(data, location) {
const { town, code, provinceFk, countryFk } = location ?? {};
data.postalCode = code;
data.city = town;
data.provinceFk = provinceFk;
data.countryFk = countryFk;
}
</script> </script>
<template> <template>
<FetchData
ref="postcodeFetchDataRef"
url="Postcodes/location"
@on-fetch="(data) => (postcodesOptions = data)"
auto-load
/>
<FetchData
ref="provincesFetchDataRef"
@on-fetch="(data) => (provincesOptions = data)"
auto-load
url="Provinces"
/>
<FetchData
ref="townsFetchDataRef"
@on-fetch="(data) => (townsLocationOptions = data)"
auto-load
url="Towns/location"
/>
<QPage> <QPage>
<FormModel <FormModel
model="supplierAddresses" model="supplierAddresses"
@ -104,59 +94,15 @@ onMounted(() => {
</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">
<VnSelectDialog <VnLocation
v-model="data.postalCode" :rules="validate('Worker.postcode')"
:label="t('supplier.addresses.postcode')"
:rules="validate('supplierAddress.postcode')"
:roles-allowed-to-create="['deliveryAssistant']" :roles-allowed-to-create="['deliveryAssistant']"
:options="postcodesOptions" :options="postcodesOptions"
option-label="code" v-model="data.location"
option-value="code" @update:model-value="
hide-selected (location) => handleLocation(data, location)
> "
<template #form> ></VnLocation>
<CustomerCreateNewPostcode
@on-data-saved="onPostcodeCreated($event)"
/>
</template>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection v-if="scope.opt">
<QItemLabel>{{ scope.opt.code }}</QItemLabel>
<QItemLabel caption
>{{ scope.opt.code }} -
{{ scope.opt.town.name }} ({{
scope.opt.town.province.name
}},
{{
scope.opt.town.province.country.country
}})</QItemLabel
>
</QItemSection>
</QItem>
</template>
</VnSelectDialog>
</div>
<div class="col">
<VnSelectFilter
:label="t('supplier.addresses.city')"
:options="townsLocationOptions"
v-model="data.city"
hide-selected
option-label="name"
option-value="id"
/>
</div>
<div class="col">
<VnSelectFilter
:label="t('supplier.addresses.province')"
:options="provincesOptions"
hide-selected
map-options
option-label="name"
option-value="id"
v-model="data.provinceFk"
/>
</div> </div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">

View File

@ -26,6 +26,7 @@ const workersOptions = ref([]);
:url-update="`Suppliers/${route.params.id}`" :url-update="`Suppliers/${route.params.id}`"
model="supplier" model="supplier"
auto-load auto-load
:clear-store-on-unmount="false"
> >
<template #form="{ data, validate }"> <template #form="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">

View File

@ -33,6 +33,7 @@ const formatPayDems = (data) => {
:url-update="`Suppliers/${route.params.id}`" :url-update="`Suppliers/${route.params.id}`"
model="supplier" model="supplier"
auto-load auto-load
:clear-store-on-unmount="false"
> >
<template #form="{ data, validate }"> <template #form="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">

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

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue'; import { ref, onMounted, nextTick } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@ -11,6 +11,15 @@ const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const supplierContactRef = ref(null); const supplierContactRef = ref(null);
const insertRow = () => {
supplierContactRef.value.insert();
nextTick(() => {
const inputs = document.querySelectorAll('[input-name-focusable]');
const lastInput = inputs[inputs.length - 1];
if (lastInput) lastInput.focus();
});
};
onMounted(() => { onMounted(() => {
if (supplierContactRef.value) supplierContactRef.value.reload(); if (supplierContactRef.value) supplierContactRef.value.reload();
}); });
@ -38,6 +47,7 @@ onMounted(() => {
<VnRow class="row q-gutter-md"> <VnRow class="row q-gutter-md">
<div class="col"> <div class="col">
<VnInput <VnInput
input-name-focusable
:label="t('supplier.contacts.name')" :label="t('supplier.contacts.name')"
v-model="row.name" v-model="row.name"
/> />
@ -92,7 +102,7 @@ onMounted(() => {
size="sm" size="sm"
class="cursor-pointer" class="cursor-pointer"
color="primary" color="primary"
@click="supplierContactRef.insert()" @click="insertRow()"
> >
<QTooltip> <QTooltip>
{{ t('Add contact') }} {{ t('Add contact') }}

View File

@ -9,6 +9,7 @@ import VnLv from 'src/components/ui/VnLv.vue';
import { toDateString } from 'src/filters'; import { toDateString } from 'src/filters';
import useCardDescription from 'src/composables/useCardDescription'; import useCardDescription from 'src/composables/useCardDescription';
import { getUrl } from 'src/composables/getUrl'; import { getUrl } from 'src/composables/getUrl';
import { useState } from 'src/composables/useState';
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -21,6 +22,7 @@ const $props = defineProps({
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const url = ref(); const url = ref();
const state = useState();
const filter = { const filter = {
fields: [ fields: [
@ -71,6 +73,8 @@ const setData = (entity) => {
data.value = useCardDescription(entity.ref, entity.id); data.value = useCardDescription(entity.ref, entity.id);
}; };
const supplier = computed(() => state.get('supplier'));
const getEntryQueryParams = (supplier) => { const getEntryQueryParams = (supplier) => {
if (!supplier) return null; if (!supplier) return null;
@ -101,7 +105,7 @@ const getEntryQueryParams = (supplier) => {
:subtitle="data.subtitle" :subtitle="data.subtitle"
:filter="filter" :filter="filter"
@on-fetch="setData" @on-fetch="setData"
data-key="Supplier" data-key="supplier"
> >
<template #header-extra-action> <template #header-extra-action>
<QBtn <QBtn
@ -133,10 +137,10 @@ const getEntryQueryParams = (supplier) => {
<VnLv :label="t('supplier.summary.payDay')" :value="entity.payDay" /> <VnLv :label="t('supplier.summary.payDay')" :value="entity.payDay" />
<VnLv :label="t('supplier.summary.account')" :value="entity.account" /> <VnLv :label="t('supplier.summary.account')" :value="entity.account" />
</template> </template>
<template #icons="{ entity }"> <template #icons>
<QCardActions class="q-gutter-x-md"> <QCardActions v-if="supplier" class="q-gutter-x-md">
<QIcon <QIcon
v-if="!entity.isActive" v-if="!supplier.isActive"
name="vn:disabled" name="vn:disabled"
color="primary" color="primary"
size="xs" size="xs"
@ -144,7 +148,7 @@ const getEntryQueryParams = (supplier) => {
<QTooltip>{{ t('Inactive supplier') }}</QTooltip> <QTooltip>{{ t('Inactive supplier') }}</QTooltip>
</QIcon> </QIcon>
<QIcon <QIcon
v-if="!entity.isSerious" v-if="!supplier.isSerious"
name="vn:supplierfalse" name="vn:supplierfalse"
color="primary" color="primary"
size="xs" size="xs"
@ -167,6 +171,7 @@ const getEntryQueryParams = (supplier) => {
<QTooltip>{{ t('All entries with current supplier') }}</QTooltip> <QTooltip>{{ t('All entries with current supplier') }}</QTooltip>
</QBtn> </QBtn>
<QBtn <QBtn
v-if="entity.client?.fi"
:to="{ :to="{
name: 'CustomerCard', name: 'CustomerCard',
params: { id: entity.client?.id }, params: { id: entity.client?.id },

View File

@ -21,7 +21,7 @@ const postcodesOptions = ref([]);
function handleLocation(data, location) { function handleLocation(data, location) {
const { town, code, provinceFk, countryFk } = location ?? {}; const { town, code, provinceFk, countryFk } = location ?? {};
data.postcode = code; data.postCode = code;
data.city = town; data.city = town;
data.provinceFk = provinceFk; data.provinceFk = provinceFk;
data.countryFk = countryFk; data.countryFk = countryFk;
@ -53,6 +53,7 @@ function handleLocation(data, location) {
:url-update="`Suppliers/${route.params.id}/updateFiscalData`" :url-update="`Suppliers/${route.params.id}/updateFiscalData`"
model="supplier" model="supplier"
auto-load auto-load
:clear-store-on-unmount="false"
> >
<template #form="{ data, validate }"> <template #form="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">

View File

@ -51,21 +51,20 @@ const isAdministrative = computed(() => {
:url="`Suppliers/${entityId}/getSummary`" :url="`Suppliers/${entityId}/getSummary`"
@on-fetch="(data) => setData(data)" @on-fetch="(data) => setData(data)"
> >
<template #header-left>
<a v-if="isAdministrative" class="header link" :href="supplierUrl">
<QIcon name="open_in_new" color="white" size="sm" />
</a>
</template>
<template #header> <template #header>
<span>{{ supplier.name }} - {{ supplier.id }}</span> <span>{{ supplier.name }} - {{ supplier.id }}</span>
</template> </template>
<template #body> <template #body>
<QCard class="vn-one"> <QCard class="vn-one">
<a v-if="isAdministrative" class="header link" :href="supplierUrl"> <router-link
v-if="isAdministrative"
class="header link"
:to="{ name: 'SupplierBasicData', params: { id: entityId } }"
>
{{ t('globals.summary.basicData') }} {{ t('globals.summary.basicData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </router-link>
<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" />
<VnLv label="Alias" :value="supplier.nickname" /> <VnLv label="Alias" :value="supplier.nickname" />
@ -82,33 +81,26 @@ 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"> <router-link
v-if="isAdministrative"
class="header link"
:to="{ name: 'SupplierBillingData', params: { id: entityId } }"
>
{{ t('supplier.summary.billingData') }} {{ t('supplier.summary.billingData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </router-link>
<span v-else> {{ t('supplier.summary.billingData') }}</span> <span v-else> {{ t('supplier.summary.billingData') }}</span>
<VnLv <VnLv
:label="t('supplier.summary.payMethod')" :label="t('supplier.summary.payMethod')"
@ -124,10 +116,14 @@ 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"> <router-link
v-if="isAdministrative"
class="header link"
:to="{ name: 'SupplierFiscalData', params: { id: entityId } }"
>
{{ t('supplier.summary.fiscalData') }} {{ t('supplier.summary.fiscalData') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </router-link>
<span v-else> {{ t('supplier.summary.fiscalData') }}</span> <span v-else> {{ t('supplier.summary.fiscalData') }}</span>
<VnLv <VnLv
:label="t('supplier.summary.sageTaxType')" :label="t('supplier.summary.sageTaxType')"
@ -155,10 +151,14 @@ const isAdministrative = computed(() => {
/> />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<a v-if="isAdministrative" class="header link" :href="supplierUrl"> <router-link
v-if="isAdministrative"
class="header link"
:to="{ name: 'SupplierFiscalData', params: { id: entityId } }"
>
{{ t('supplier.summary.fiscalAddress') }} {{ t('supplier.summary.fiscalAddress') }}
<QIcon name="open_in_new" color="primary" /> <QIcon name="open_in_new" />
</a> </router-link>
<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" />
<VnLv :label="t('supplier.summary.taxNumber')" :value="supplier.nif" /> <VnLv :label="t('supplier.summary.taxNumber')" :value="supplier.nif" />

View File

@ -42,7 +42,7 @@ const redirectToCreateView = () => {
</QScrollArea> </QScrollArea>
</QDrawer> </QDrawer>
<div class="vn-card-list"> <div class="vn-card-list">
<VnPaginate data-key="SuppliersList" url="Suppliers/filter" auto-load> <VnPaginate data-key="SuppliersList" url="Suppliers/filter">
<template #body="{ rows }"> <template #body="{ rows }">
<CardList <CardList
v-for="row of rows" v-for="row of rows"

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">
@ -270,7 +270,7 @@ async function changeState(value) {
<QTh auto-width>{{ t('ticket.summary.visible') }}</QTh> <QTh auto-width>{{ t('ticket.summary.visible') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.available') }}</QTh> <QTh auto-width>{{ t('ticket.summary.available') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.quantity') }}</QTh> <QTh auto-width>{{ t('ticket.summary.quantity') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.description') }}</QTh> <QTh auto-width>{{ t('globals.description') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.price') }}</QTh> <QTh auto-width>{{ t('ticket.summary.price') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.discount') }}</QTh> <QTh auto-width>{{ t('ticket.summary.discount') }}</QTh>
<QTh auto-width>{{ t('globals.amount') }}</QTh> <QTh auto-width>{{ t('globals.amount') }}</QTh>
@ -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,15 +417,15 @@ 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">
<QTr :props="props"> <QTr :props="props">
<QTh auto-width>{{ t('ticket.summary.quantity') }}</QTh> <QTh auto-width>{{ t('ticket.summary.quantity') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.description') }}</QTh> <QTh auto-width>{{ t('globals.description') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.price') }}</QTh> <QTh auto-width>{{ t('ticket.summary.price') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.taxClass') }}</QTh> <QTh auto-width>{{ t('ticket.summary.taxClass') }}</QTh>
<QTh auto-width>{{ t('globals.amount') }}</QTh> <QTh auto-width>{{ t('globals.amount') }}</QTh>

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

@ -236,19 +236,9 @@ async function setTravelData(travelData) {
:url="`Travels/${entityId}/getTravel`" :url="`Travels/${entityId}/getTravel`"
@on-fetch="(data) => setTravelData(data)" @on-fetch="(data) => setTravelData(data)"
> >
<template #header-left>
<router-link
class="header link"
:to="{ name: 'TravelSummary', params: { id: entityId } }"
>
<QIcon name="open_in_new" color="white" size="sm" />
<QTooltip>{{ t('travel.pageTitles.summary') }}</QTooltip>
</router-link>
</template>
<template #header> <template #header>
<span>{{ travel.ref }} - {{ travel.id }}</span> <span>{{ travel.ref }} - {{ travel.id }}</span>
</template> </template>
<template #header-right> <template #header-right>
<QBtn color="white" dense flat icon="more_vert" round size="md"> <QBtn color="white" dense flat icon="more_vert" round size="md">
<QTooltip> <QTooltip>
@ -269,15 +259,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 +271,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 +284,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 +343,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

@ -300,7 +300,7 @@ const onThermographCreated = async (data) => {
<VnRow v-if="viewAction === 'edit'" class="row q-gutter-md q-mb-md"> <VnRow v-if="viewAction === 'edit'" class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">
<QInput <QInput
:label="t('travel.thermographs.description')" :label="t('globals.description')"
type="textarea" type="textarea"
v-model="thermographForm.description" v-model="thermographForm.description"
fill-input fill-input

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>

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