Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into EntryLatestBuysFilters
gitea/salix-front/pipeline/pr-dev This commit looks good Details

This commit is contained in:
William Buezas 2024-03-05 08:28:42 -03:00
commit 0274a696f5
50 changed files with 1611 additions and 760 deletions

View File

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

18
Jenkinsfile vendored
View File

@ -62,7 +62,7 @@ pipeline {
NODE_ENV = ""
}
steps {
sh 'npm install --prefer-offline'
sh 'pnpm install --prefer-offline'
}
}
stage('Test') {
@ -73,18 +73,14 @@ pipeline {
NODE_ENV = ""
}
steps {
sh 'npm run test:unit:ci'
sh 'pnpm run test:unit:ci'
}
post {
post {
always {
script {
try {
junit 'junitresults.xml'
junit 'junit.xml'
} catch (e) {
echo e.toString()
}
}
junit(
testResults: 'junitresults.xml',
allowEmptyResults: true
)
}
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -176,8 +176,8 @@ async function remove(data) {
.dialog({
component: VnConfirm,
componentProps: {
title: t('confirmDeletion'),
message: t('confirmDeletionMessage'),
title: t('globals.confirmDeletion'),
message: t('globals.confirmDeletionMessage'),
newData,
ids,
},
@ -317,16 +317,3 @@ watch(formUrl, async () => {
color="primary"
/>
</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"
@click="openInputFile()"
>
<!-- <QTooltip>{{ t('Select a file') }}</QTooltip> -->
<!-- <QTooltip>{{ t('globals.selectFile') }}</QTooltip> -->
</QIcon>
<QIcon name="info" class="cursor-pointer">
<QTooltip>{{

View File

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

View File

@ -1,6 +1,7 @@
<script setup>
import axios from 'axios';
import { onMounted, onUnmounted, computed, ref, watch, nextTick } from 'vue';
import { onBeforeRouteLeave } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import { useState } from 'src/composables/useState';
@ -8,6 +9,7 @@ import { useStateStore } from 'stores/useStateStore';
import { useValidator } from 'src/composables/useValidator';
import useNotify from 'src/composables/useNotify.js';
import SkeletonForm from 'components/ui/SkeletonForm.vue';
import VnConfirm from './ui/VnConfirm.vue';
const quasar = useQuasar();
const state = useState();
@ -59,6 +61,10 @@ const $props = defineProps({
type: Function,
default: null,
},
saveFn: {
type: Function,
default: null,
},
});
const emit = defineEmits(['onFetch', 'onDataSaved']);
@ -75,9 +81,8 @@ onMounted(async () => {
});
// Podemos enviarle al form la estructura de data inicial sin necesidad de fetchearla
if ($props.formInitialData && !$props.autoLoad) {
state.set($props.model, $props.formInitialData);
} else {
state.set($props.model, $props.formInitialData);
if ($props.autoLoad && !$props.formInitialData) {
await fetch();
}
@ -90,6 +95,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(() => {
state.unset($props.model);
});
@ -138,17 +156,20 @@ async function save() {
try {
const body = $props.mapper ? $props.mapper(formData.value) : formData.value;
let response;
if ($props.urlCreate) {
response = await axios.post($props.urlCreate, body);
notify('globals.dataCreated', 'positive');
} else {
response = await axios.patch($props.urlUpdate || $props.url, body);
}
if ($props.saveFn) response = await $props.saveFn(body);
else
response = await axios[$props.urlCreate ? 'post' : 'patch'](
$props.urlCreate || $props.urlUpdate || $props.url,
body
);
if ($props.urlCreate) notify('globals.dataCreated', 'positive');
emit('onDataSaved', formData.value, response?.data);
originalData.value = JSON.parse(JSON.stringify(formData.value));
hasChanges.value = false;
} catch (err) {
notify('errors.create', 'negative');
console.error(err);
notify('errors.writeRequest', 'negative');
}
isLoading.value = false;
}
@ -249,3 +270,8 @@ watch(formUrl, async () => {
padding: 32px;
}
</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

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

@ -94,16 +94,6 @@ async function send() {
<QSpace />
<QBtn icon="close" :disable="isLoading" flat round dense v-close-popup />
</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">
<QSelect
:label="t('Language')"
@ -184,7 +174,6 @@ async function send() {
<i18n>
en:
CustomerDefaultLanguage: This customer uses <strong>{locale}</strong> as their default language
templates:
pendingPayment: 'Your order is pending of payment.
Please, enter the website and make the payment with a credit card. Thank you.'
@ -197,7 +186,6 @@ en:
pt: Portuguese
es:
Send SMS: Enviar SMS
CustomerDefaultLanguage: Este cliente utiliza <strong>{locale}</strong> como idioma por defecto
Language: Idioma
Phone: Móvil
Subject: Asunto

View File

@ -13,12 +13,49 @@ defineProps({
<template>
<div class="fetchedTags">
<div class="wrap">
<div class="inline-tag" :class="{ empty: !$props.item.value5 }">{{ $props.item.value5 }}</div>
<div class="inline-tag" :class="{ empty: !$props.item.value6 }">{{ $props.item.value6 }}</div>
<div class="inline-tag" :class="{ empty: !$props.item.value7 }">{{ $props.item.value7 }}</div>
<div class="inline-tag" :class="{ empty: !$props.item.value8 }">{{ $props.item.value8 }}</div>
<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>
<div
class="inline-tag"
:class="{ empty: !$props.item.value5 }"
:title="$props.item.tag5 + ': ' + $props.item.value5"
>
{{ $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>
</template>

View File

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

View File

@ -24,6 +24,7 @@ export default {
dataCreated: 'Data created',
add: 'Add',
create: 'Create',
edit: 'Edit',
save: 'Save',
remove: 'Remove',
reset: 'Reset',
@ -64,12 +65,22 @@ export default {
markAll: 'Mark all',
requiredField: 'Required field',
class: 'clase',
type: 'type',
type: 'Type',
reason: 'reason',
noResults: 'No results',
system: 'System',
warehouse: 'Warehouse',
company: 'Company',
fieldRequired: 'Field required',
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',
},
errors: {
@ -78,7 +89,7 @@ export default {
statusBadGateway: 'It seems that the server has fall down',
statusGatewayTimeout: 'Could not contact the server',
userConfig: 'Error fetching user config',
create: 'Error during creation',
writeRequest: 'The requested operation could not be completed',
},
login: {
title: 'Login',
@ -276,6 +287,7 @@ export default {
basicData: 'Basic data',
buys: 'Buys',
notes: 'Notes',
dms: 'File management',
log: 'Log',
create: 'Create',
latestBuys: 'Latest buys',
@ -347,7 +359,6 @@ export default {
reference: 'Reference',
observations: 'Observations',
item: 'Item',
description: 'Description',
size: 'Size',
packing: 'Packing',
grouping: 'Grouping',
@ -362,7 +373,6 @@ export default {
},
notes: {
observationType: 'Observation type',
description: 'Description',
},
descriptor: {
agency: 'Agency',
@ -375,7 +385,6 @@ export default {
packing: 'Packing',
grouping: 'Grouping',
quantity: 'Quantity',
description: 'Description',
size: 'Size',
tags: 'Tags',
type: 'Type',
@ -461,7 +470,6 @@ export default {
visible: 'Visible',
available: 'Available',
quantity: 'Quantity',
description: 'Description',
price: 'Price',
discount: 'Discount',
packing: 'Packing',
@ -534,7 +542,6 @@ export default {
landed: 'Landed',
quantity: 'Quantity',
claimed: 'Claimed',
description: 'Description',
price: 'Price',
discount: 'Discount',
total: 'Total',
@ -796,7 +803,6 @@ export default {
orderTicketList: 'Order Ticket List',
details: 'Details',
item: 'Item',
description: 'Description',
quantity: 'Quantity',
price: 'Price',
amount: 'Amount',
@ -1141,7 +1147,6 @@ export default {
warehouse: 'Warehouse',
travelFileDescription: 'Travel id { travelId }',
file: 'File',
description: 'Description',
},
},
item: {
@ -1175,7 +1180,6 @@ export default {
clone: 'Clone',
openCard: 'View',
openSummary: 'Summary',
viewDescription: 'Description',
},
cardDescriptor: {
mainList: 'Main list',

View File

@ -24,6 +24,7 @@ export default {
dataCreated: 'Datos creados',
add: 'Añadir',
create: 'Crear',
edit: 'Modificar',
save: 'Guardar',
remove: 'Eliminar',
reset: 'Restaurar',
@ -64,12 +65,22 @@ export default {
markAll: 'Marcar todo',
requiredField: 'Campo obligatorio',
class: 'clase',
type: 'tipo',
type: 'Tipo',
reason: 'motivo',
noResults: 'Sin resultados',
system: 'Sistema',
warehouse: 'Almacén',
company: 'Empresa',
fieldRequired: 'Campo requerido',
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',
},
errors: {
@ -78,7 +89,7 @@ export default {
statusBadGateway: 'Parece ser que el servidor ha caído',
statusGatewayTimeout: 'No se ha podido contactar con el servidor',
userConfig: 'Error al obtener configuración de usuario',
create: 'Error al crear',
writeRequest: 'No se pudo completar la operación solicitada',
},
login: {
title: 'Inicio de sesión',
@ -275,6 +286,7 @@ export default {
basicData: 'Datos básicos',
buys: 'Compras',
notes: 'Notas',
dms: 'Gestión documental',
log: 'Historial',
create: 'Crear',
latestBuys: 'Últimas compras',
@ -346,7 +358,6 @@ export default {
reference: 'Referencia',
observations: 'Observaciónes',
item: 'Artículo',
description: 'Descripción',
size: 'Medida',
packing: 'Packing',
grouping: 'Grouping',
@ -361,7 +372,6 @@ export default {
},
notes: {
observationType: 'Tipo de observación',
description: 'Descripción',
},
descriptor: {
agency: 'Agencia',
@ -374,7 +384,6 @@ export default {
packing: 'Packing',
grouping: 'Grouping',
quantity: 'Cantidad',
description: 'Descripción',
size: 'Medida',
tags: 'Etiquetas',
type: 'Tipo',
@ -460,7 +469,6 @@ export default {
visible: 'Visible',
available: 'Disponible',
quantity: 'Cantidad',
description: 'Descripción',
price: 'Precio',
discount: 'Descuento',
packing: 'Encajado',
@ -533,7 +541,6 @@ export default {
landed: 'Entregado',
quantity: 'Cantidad',
claimed: 'Reclamado',
description: 'Descripción',
price: 'Precio',
discount: 'Descuento',
total: 'Total',
@ -704,7 +711,6 @@ export default {
orderTicketList: 'Tickets del pedido',
details: 'Detalles',
item: 'Item',
description: 'Descripción',
quantity: 'Cantidad',
price: 'Precio',
amount: 'Monto',
@ -1141,7 +1147,6 @@ export default {
warehouse: 'Almacén',
travelFileDescription: 'Id envío { travelId }',
file: 'Fichero',
description: 'Descripción',
},
},
item: {
@ -1175,7 +1180,6 @@ export default {
clone: 'Clonar',
openCard: 'Ficha',
openSummary: 'Detalles',
viewDescription: 'Descripción',
},
cardDescriptor: {
mainList: 'Listado principal',

View File

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

View File

@ -62,7 +62,7 @@ const creditWarning = computed(() => {
<CardSummary ref="summary" :url="`Clients/${entityId}/summary`">
<template #body="{ entity }">
<QCard class="vn-one">
<a class="header" :href="clientUrl + `basic-data`">
<a class="header" :href="`#/customer/${entityId}/basic-data`">
{{ t('customer.summary.basicData') }}
<QIcon name="open_in_new" color="primary" />
</a>
@ -96,7 +96,7 @@ const creditWarning = computed(() => {
/>
</QCard>
<QCard class="vn-one">
<a class="header" :href="clientUrl + `fiscal-data`">
<a class="header" :href="`#/customer/${entityId}/fiscal-data`">
{{ t('customer.summary.fiscalAddress') }}
<QIcon name="open_in_new" color="primary" />
</a>
@ -121,7 +121,7 @@ const creditWarning = computed(() => {
<VnLv :label="t('customer.summary.street')" :value="entity.street" />
</QCard>
<QCard class="vn-one">
<a class="header link" :href="clientUrl + `fiscal-data`" link>
<a class="header link" :href="`#/customer/${entityId}/fiscal-data`" link>
{{ t('customer.summary.fiscalData') }}
<QIcon name="open_in_new" color="primary" />
</a>
@ -149,7 +149,7 @@ const creditWarning = computed(() => {
<VnLv :label="t('customer.summary.vies')" :value="entity.isVies" />
</QCard>
<QCard class="vn-one">
<a class="header link" :href="clientUrl + `billing-data`" link>
<a class="header link" :href="`#/customer/${entityId}/billing-data`" link>
{{ t('customer.summary.billingData') }}
<QIcon name="open_in_new" color="primary" />
</a>
@ -170,7 +170,7 @@ const creditWarning = computed(() => {
/>
</QCard>
<QCard class="vn-one" v-if="entity.defaultAddress">
<a class="header link" :href="clientUrl + `address/index`" link>
<a class="header link" :href="`#/customer/${entityId}/consignees`" link>
{{ t('customer.summary.consignee') }}
<QIcon name="open_in_new" color="primary" />
</a>
@ -188,7 +188,7 @@ const creditWarning = computed(() => {
/>
</QCard>
<QCard class="vn-one" v-if="entity.account">
<a class="header link" :href="clientUrl + `web-access`">
<a class="header link" :href="`#/customer/${entityId}/web-access`">
{{ t('customer.summary.webAccess') }}
<QIcon name="open_in_new" color="primary" />
</a>

View File

@ -1,9 +1,9 @@
<script setup>
import { useI18n } from 'vue-i18n';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnCurrency from 'src/components/common/VnCurrency.vue';
const { t } = useI18n();
const props = defineProps({
@ -12,10 +12,6 @@ const props = defineProps({
required: true,
},
});
function isValidNumber(value) {
return /^(\d|\d+(\.|,)?\d+)$/.test(value);
}
</script>
<template>
@ -51,28 +47,9 @@ function isValidNumber(value) {
</QItem>
<QItem>
<QItemSection>
<VnInput
: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>
<VnCurrency v-model="params.amount" is-outlined />
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<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 axios from 'axios';
import VnLocation from 'src/components/common/VnLocation.vue';
import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
@ -20,13 +20,9 @@ const router = useRouter();
const formInitialData = reactive({ isDefaultAddress: false });
const townsFetchDataRef = ref(null);
const postcodeFetchDataRef = ref(null);
const urlCreate = ref('');
const postcodesOptions = ref([]);
const citiesLocationOptions = ref([]);
const provincesLocationOptions = ref([]);
const agencyModes = ref([]);
const incoterms = ref([]);
const customsAgents = ref([]);
@ -36,14 +32,6 @@ onBeforeMount(() => {
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 { data } = await axios.get('CustomsAgents');
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>
<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
@on-fetch="(data) => (agencyModes = data)"
auto-load
@ -113,83 +91,17 @@ const toCustomerConsignees = () => {
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectDialog
:label="t('Postcode')"
:options="postcodesOptions"
:roles-allowed-to-create="['deliveryAssistant']"
<VnLocation
:rules="validate('Worker.postcode')"
hide-selected
option-label="code"
option-value="code"
v-model="data.postalCode"
>
<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>
:roles-allowed-to-create="['deliveryAssistant']"
:options="postcodesOptions"
v-model="data.location"
@update:model-value="(location) => handleLocation(data, location)"
></VnLocation>
</div>
</VnRow>
<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">
<VnSelectFilter
:label="t('Agency')"

View File

@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import axios from 'axios';
import VnLocation from 'src/components/common/VnLocation.vue';
import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
@ -17,13 +17,8 @@ import CustomsNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCus
const { t } = useI18n();
const route = useRoute();
const townsFetchDataRef = ref(null);
const postcodeFetchDataRef = ref(null);
const urlUpdate = ref('');
const postcodesOptions = ref([]);
const citiesLocationOptions = ref([]);
const provincesLocationOptions = ref([]);
const agencyModes = ref([]);
const incoterms = ref([]);
const customsAgents = ref([]);
@ -34,14 +29,6 @@ onBeforeMount(() => {
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) => {
observationTypes.value = observations;
@ -97,26 +84,16 @@ const onDataSaved = () => {
};
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>
<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
@on-fetch="(data) => (agencyModes = data)"
auto-load
@ -168,83 +145,17 @@ const onDataSaved = () => {
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectDialog
:label="t('Postcode')"
:options="postcodesOptions"
:roles-allowed-to-create="['deliveryAssistant']"
<VnLocation
:rules="validate('Worker.postcode')"
hide-selected
option-label="code"
option-value="code"
v-model="data.postalCode"
>
<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>
:roles-allowed-to-create="['deliveryAssistant']"
:options="postcodesOptions"
v-model="data.location"
@update:model-value="(location) => handleLocation(data, location)"
></VnLocation>
</div>
</VnRow>
<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">
<VnSelectFilter
:label="t('Agency')"

View File

@ -36,7 +36,10 @@ onMounted(async () => {
</template>
<template #body="{ entity: department }">
<QCard class="column">
<a class="header" :href="department + `basic-data`">
<a
class="header"
:href="`#/department/department/${entityId}/basic-data`"
>
{{ t('Basic data') }}
<QIcon name="open_in_new" color="primary" />
</a>

View File

@ -44,7 +44,7 @@ const columns = computed(() => [
align: 'left',
},
{
label: t('entry.buys.description'),
label: t('globals.description'),
name: 'description',
field: 'description',
align: 'left',
@ -214,7 +214,7 @@ const redirectToBuysView = () => {
class="cursor-pointer"
@click="inputFileRef.pickFiles()"
>
<QTooltip>{{ t('Select a file') }}</QTooltip>
<QTooltip>{{ t('globals.selectFile') }}</QTooltip>
</QIcon>
</template>
</QFile>
@ -292,6 +292,6 @@ const redirectToBuysView = () => {
<i18n>
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
</i18n>

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

View File

@ -39,30 +39,47 @@ onMounted(async () => {
const tableColumnComponents = {
quantity: {
component: () => 'span',
props: () => {},
},
stickers: {
component: () => 'span',
props: () => {},
event: () => {},
},
packagingFk: {
component: () => 'span',
props: () => {},
event: () => {},
},
weight: {
component: () => 'span',
props: () => {},
event: () => {},
},
packing: {
component: () => 'span',
props: () => {},
event: () => {},
},
grouping: {
component: () => 'span',
props: () => {},
event: () => {},
},
buyingValue: {
component: () => 'span',
props: () => {},
event: () => {},
},
amount: {
component: () => 'span',
props: () => {},
event: () => {},
},
pvp: {
component: () => 'span',
props: () => {},
event: () => {},
},
};
@ -147,140 +164,91 @@ const fetchEntryBuys = async () => {
:url="`Entries/${entityId}/getEntry`"
@on-fetch="(data) => setEntryData(data)"
>
<template #header-left>
<a class="header link" :href="entryUrl">
<QIcon name="open_in_new" color="white" size="sm" />
</a>
</template>
<template #header>
<span>{{ entry.id }} - {{ entry.supplier.nickname }}</span>
</template>
<template #body>
<QCard class="vn-one">
<a class="header link" :href="entryUrl">
<a class="header link" :href="`#/entry/${entityId}/basic-data`">
{{ t('globals.summary.basicData') }}
<QIcon name="open_in_new" color="primary" />
</a>
<VnRow>
<div class="col">
<VnLv
:label="t('entry.summary.commission')"
:value="entry.commission"
/>
</div>
<div class="col">
<VnLv
:label="t('entry.summary.currency')"
:value="entry.currency.name"
/>
</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>
<div class="col">
<VnLv
:label="t('entry.summary.ordered')"
:value="entry.isOrdered"
/>
</div>
<div class="col">
<VnLv
:label="t('entry.summary.confirmed')"
:value="entry.isConfirmed"
/>
</div>
<div class="col">
<VnLv
:label="t('entry.summary.booked')"
:value="entry.isBooked"
/>
</div>
<div class="col">
<VnLv :label="t('entry.summary.raid')" :value="entry.isRaid" />
</div>
<div class="col">
<VnLv
:label="t('entry.summary.excludedFromAvailable')"
:value="entry.isExcludedFromAvailable"
/>
</div>
</VnRow>
<VnLv :label="t('entry.summary.commission')" :value="entry.commission" />
<VnLv :label="t('entry.summary.currency')" :value="entry.currency.name" />
<VnLv :label="t('entry.summary.company')" :value="entry.company.code" />
<VnLv :label="t('entry.summary.reference')" :value="entry.reference" />
<VnLv
:label="t('entry.summary.invoiceNumber')"
:value="entry.invoiceNumber"
/>
<VnLv :label="t('entry.summary.ordered')" :value="entry.isOrdered" />
<VnLv :label="t('entry.summary.confirmed')" :value="entry.isConfirmed" />
<VnLv :label="t('entry.summary.booked')" :value="entry.isBooked" />
<VnLv :label="t('entry.summary.raid')" :value="entry.isRaid" />
<VnLv
:label="t('entry.summary.excludedFromAvailable')"
:value="entry.isExcludedFromAvailable"
/>
</QCard>
<QCard class="vn-one">
<a class="header link" :href="entryUrl">
<a class="header">
{{ t('Travel data') }}
<QIcon name="open_in_new" color="primary" />
</a>
<VnRow>
<div class="col">
<VnLv :label="t('entry.summary.travelReference')">
<template #value>
<span class="link">
{{ entry.travel.ref }}
<TravelDescriptorProxy :id="entry.travel.id" />
</span>
</template>
</VnLv>
</div>
<div class="col">
<VnLv
:label="t('entry.summary.travelAgency')"
:value="entry.travel.agency.name"
/>
</div>
<div class="col">
<VnLv
:label="t('entry.summary.travelShipped')"
:value="toDate(entry.travel.shipped)"
/>
</div>
<div class="col">
<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>
<VnLv :label="t('entry.summary.travelReference')">
<template #value>
<span class="link">
{{ entry.travel.ref }}
<TravelDescriptorProxy :id="entry.travel.id" />
</span>
</template>
</VnLv>
<VnLv
:label="t('entry.summary.travelAgency')"
:value="entry.travel.agency.name"
/>
<VnLv
:label="t('entry.summary.travelShipped')"
:value="toDate(entry.travel.shipped)"
/>
<VnLv
:label="t('entry.summary.travelWarehouseOut')"
:value="entry.travel.warehouseOut.name"
/>
<VnLv
:label="t('entry.summary.travelDelivered')"
:value="entry.travel.isDelivered"
/>
<VnLv
:label="t('entry.summary.travelLanded')"
:value="toDate(entry.travel.landed)"
/>
<VnLv
:label="t('entry.summary.travelWarehouseIn')"
:value="entry.travel.warehouseIn.name"
/>
<VnLv
:label="t('entry.summary.travelReceived')"
:value="entry.travel.isReceived"
/>
</QCard>
<QCard class="vn-two" style="min-width: 100%">
<a class="header">
@ -297,7 +265,10 @@ const fetchEntryBuys = async () => {
<QTr no-hover>
<QTd v-for="col in cols" :key="col.name">
<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
v-if="

View File

@ -164,7 +164,7 @@ const columns = computed(() => [
},
},
{
label: t('entry.latestBuys.description'),
label: t('globals.description'),
field: 'description',
name: 'description',
align: 'left',
@ -565,7 +565,7 @@ const editTableCellFormFieldsOptions = [
{ field: 'grouping', label: t('entry.latestBuys.grouping') },
{ field: 'packageValue', label: t('entry.latestBuys.packageValue') },
{ 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: 'weightByPiece', label: t('entry.latestBuys.weightByPiece') },
{ field: 'packingOut', label: t('entry.latestBuys.packingOut') },

View File

@ -174,7 +174,12 @@ async function upsert() {
@on-fetch="(data) => (userConfig = data)"
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 }">
<div class="row q-gutter-md q-mb-md">
<div class="col">
@ -509,7 +514,7 @@ async function upsert() {
@click="inputFileRef.pickFiles()"
>
<QTooltip>
{{ t('Select a file') }}
{{ t('globals.selectFile') }}
</QTooltip>
</QBtn>
<QBtn icon="info" flat round padding="xs">
@ -618,7 +623,7 @@ async function upsert() {
@click="inputFileRef.pickFiles()"
>
<QTooltip>
{{ t('Select a file') }}
{{ t('globals.selectFile') }}
</QTooltip>
</QBtn>
<QBtn icon="info" flat round padding="xs">
@ -687,7 +692,6 @@ async function upsert() {
Generate identifier for original file: Generar identificador para archivo original
File: Fichero
Create document: Crear documento
Select a file: Seleccione un fichero
Allowed content types: Tipos de archivo permitidos
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

View File

@ -8,6 +8,7 @@ import { useArrayData } from 'src/composables/useArrayData';
import CrudModel from 'src/components/CrudModel.vue';
import FetchData from 'src/components/FetchData.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import VnCurrency from 'src/components/common/VnCurrency.vue';
const route = useRoute();
const { t } = useI18n();
@ -158,7 +159,12 @@ async function insert() {
</template>
<template #body-cell-amount="{ row }">
<QTd>
<QInput v-model="row.amount" clearable clear-icon="close" />
<VnCurrency
v-model="row.amount"
:is-outlined="false"
clearable
clear-icon="close"
/>
</QTd>
</template>
<template #body-cell-foreignvalue="{ row }">

View File

@ -381,7 +381,7 @@ function getLink(param) {
</QCard>
<!--Intrastat-->
<QCard v-if="invoiceIn.invoiceInIntrastat.length">
<a class="header" :href="getUrl('intrastat')">
<a class="header" :href="getLink('intrastat')">
{{ t('invoiceIn.card.intrastat') }}
<QIcon name="open_in_new" color="primary" />
</a>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -199,7 +199,7 @@ const openBuscaman = async (route, ticket) => {
</QCard>
<QCard class="vn-one">
<div class="header">
{{ t('route.summary.description') }}
{{ t('globals.description') }}
</div>
<p>
{{ dashIfEmpty(entity?.route?.description) }}

View File

@ -6,7 +6,7 @@ import { useStateStore } from 'stores/useStateStore';
import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'components/ui/VnLv.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({
id: {

View File

@ -10,6 +10,7 @@ import VnInput from 'src/components/common/VnInput.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import VnRow from 'components/ui/VnRow.vue';
import CustomerCreateNewPostcode from 'src/components/CreateNewPostcodeForm.vue';
import VnLocation from 'src/components/common/VnLocation.vue';
const { t } = useI18n();
const route = useRoute();
@ -55,27 +56,16 @@ onMounted(() => {
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>
<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>
<FormModel
model="supplierAddresses"
@ -104,59 +94,15 @@ onMounted(() => {
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectDialog
v-model="data.postalCode"
:label="t('supplier.addresses.postcode')"
:rules="validate('supplierAddress.postcode')"
<VnLocation
:rules="validate('Worker.postcode')"
:roles-allowed-to-create="['deliveryAssistant']"
:options="postcodesOptions"
option-label="code"
option-value="code"
hide-selected
>
<template #form>
<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"
/>
v-model="data.location"
@update:model-value="
(location) => handleLocation(data, location)
"
></VnLocation>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">

View File

@ -21,7 +21,7 @@ const postcodesOptions = ref([]);
function handleLocation(data, location) {
const { town, code, provinceFk, countryFk } = location ?? {};
data.postcode = code;
data.postCode = code;
data.city = town;
data.provinceFk = provinceFk;
data.countryFk = countryFk;

View File

@ -62,7 +62,11 @@ const isAdministrative = computed(() => {
<template #body>
<QCard class="vn-one">
<a v-if="isAdministrative" class="header link" :href="supplierUrl">
<a
v-if="isAdministrative"
class="header link"
:href="`#/supplier/${entityId}/basic-data`"
>
{{ t('globals.summary.basicData') }}
<QIcon name="open_in_new" color="primary" />
</a>
@ -105,7 +109,11 @@ const isAdministrative = computed(() => {
</VnLv>
</QCard>
<QCard class="vn-one">
<a v-if="isAdministrative" class="header link" :href="supplierUrl">
<a
v-if="isAdministrative"
class="header link"
:href="`#/supplier/${entityId}/billing-data`"
>
{{ t('supplier.summary.billingData') }}
<QIcon name="open_in_new" color="primary" />
</a>
@ -124,7 +132,11 @@ const isAdministrative = computed(() => {
<VnLv :label="t('supplier.summary.account')" :value="supplier.account" />
</QCard>
<QCard class="vn-one">
<a v-if="isAdministrative" class="header link" :href="supplierUrl">
<a
v-if="isAdministrative"
class="header link"
:href="`#/supplier/${entityId}/fiscal-data`"
>
{{ t('supplier.summary.fiscalData') }}
<QIcon name="open_in_new" color="primary" />
</a>
@ -155,7 +167,11 @@ const isAdministrative = computed(() => {
/>
</QCard>
<QCard class="vn-one">
<a v-if="isAdministrative" class="header link" :href="supplierUrl">
<a
v-if="isAdministrative"
class="header link"
:href="`#/supplier/${entityId}/fiscal-data`"
>
{{ t('supplier.summary.fiscalAddress') }}
<QIcon name="open_in_new" color="primary" />
</a>

View File

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

View File

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

View File

@ -78,7 +78,7 @@ const filter = {
<VnLv :label="t('worker.card.name')" :value="worker.user.nickname" />
<VnLv
:label="t('worker.list.department')"
:value="worker.department.department.name"
:value="worker.department?.department?.name"
/>
<VnLv :label="t('worker.list.email')" :value="worker.user.email" copy />
<VnLv :label="t('worker.summary.boss')" link>
@ -102,10 +102,10 @@ const filter = {
<VnLinkPhone :phone-number="worker.phone" />
</template>
</VnLv>
<VnLv :value="worker.client.phone">
<VnLv :value="worker.client?.phone">
<template #label>
{{ t('worker.summary.personalPhone') }}
<VnLinkPhone :phone-number="worker.client.phone" />
<VnLinkPhone :phone-number="worker.client?.phone" />
</template>
</VnLv>
<VnLv :label="t('worker.summary.locker')" :value="worker.locker" />

View File

@ -11,7 +11,7 @@ export default {
redirect: { name: 'EntryMain' },
menus: {
main: ['EntryList', 'EntryLatestBuys'],
card: ['EntryBasicData', 'EntryBuys', 'EntryNotes', 'EntryLog'],
card: ['EntryBasicData', 'EntryBuys', 'EntryNotes', 'EntryDms', 'EntryLog'],
},
children: [
{
@ -95,6 +95,15 @@ export default {
},
component: () => import('src/pages/Entry/Card/EntryNotes.vue'),
},
{
path: 'dms',
name: 'EntryDms',
meta: {
title: 'dms',
icon: 'smb_share',
},
component: () => import('src/pages/Entry/Card/EntryDms.vue'),
},
{
path: 'log',
name: 'EntryLog',

View File

@ -13,7 +13,7 @@ describe('ClaimDevelopment', () => {
it('should reset line', () => {
cy.selectOption(firstLineReason, 'Novato');
cy.resetCard();
cy.getValue(firstLineReason).should('have.value', 'Prisas');
cy.getValue(firstLineReason).should('equal', 'Prisas');
});
it('should edit line', () => {
@ -23,7 +23,7 @@ describe('ClaimDevelopment', () => {
cy.login('developer');
cy.visit(`/#/claim/${claimId}/development`);
cy.getValue(firstLineReason).should('have.value', 'Novato');
cy.getValue(firstLineReason).should('equal', 'Novato');
//Restart data
cy.selectOption(firstLineReason, 'Prisas');

View File

@ -0,0 +1,41 @@
describe('WagonTypeCreate', () => {
const entryId = 1;
beforeEach(() => {
cy.viewport(1920, 1080);
cy.login('developer');
cy.visit(`/#/entry/${entryId}/dms`);
});
it('should create edit and remove new dms', () => {
cy.addRow();
cy.get('.icon-attach').click()
cy.get('.q-file').selectFile('test/cypress/fixtures/image.jpg', {
force: true,
});
cy.get("tbody > tr").then((value) => {
//Create and check if exist new row
let newFileTd = Cypress.$(value).length;
cy.get('.q-btn--standard > .q-btn__content > .block').click();
expect(value).to.have.length(newFileTd++);
const newRowSelector = `tbody > :nth-child(${newFileTd})`
cy.waitForElement(newRowSelector);
//Edit new dms
const u = undefined;
cy.validateRow(newRowSelector, [u,u,u,u,'ENTRADA ID 1'])
cy.get(`tbody :nth-child(${newFileTd}) > .text-right > .flex > :nth-child(2) > .q-btn > .q-btn__content > .q-icon`).click();
})
// cy.log('newFileTd', newFileTd)
// //Create and check if exist new row
// cy.log('newFileTd:', newFileTd);
// cy.get(`tbody :nth-child(${newFileTd}) > .text-right > .flex > :nth-child(2) > .q-btn > .q-btn__content > .q-icon`).click()
// cy.get(`tbody :nth-child(${newFileTd}) > :nth-child(5) > .q-tr > :nth-child(1) > span`).then((value) => {
// cy.log(value)
// });
});
});

View File

@ -18,7 +18,7 @@ describe('InvoiceInIntrastat', () => {
cy.visit(`/#/invoice-in/1/intrastat`);
cy.getValue(firstLineCode).should(
'have.value',
'equal',
'Plantas vivas: Esqueje/injerto, Vid'
);
});

View File

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

View File

@ -21,7 +21,7 @@ describe('InvoiceInVat', () => {
cy.saveCard();
cy.visit(`/#/invoice-in/1/vat`);
cy.getValue(firstLineVat).should('have.value', 'H.P. IVA 21% CEE');
cy.getValue(firstLineVat).should('equal', 'H.P. IVA 21% CEE');
});
it('should add a new row', () => {

View File

@ -42,7 +42,7 @@ Cypress.Commands.add('login', (user) => {
});
Cypress.Commands.add('waitForElement', (element) => {
cy.get(element, { timeout: 2000 }).should('be.visible');
cy.get(element, { timeout: 5000 }).should('be.visible');
});
Cypress.Commands.add('getValue', (selector) => {
@ -55,7 +55,13 @@ Cypress.Commands.add('getValue', (selector) => {
return cy.get(
selector +
'> .q-field > .q-field__inner > .q-field__control > .q-field__control-container > .q-field__native > input'
);
).invoke('val')
}
// Si es un QSelect
if ($el.find('span').length) {
return cy.get(
selector + ' span'
).then(($span) => { return $span[0].innerText })
}
// Puedes añadir un log o lanzar un error si el elemento no es reconocido
cy.log('Elemento no soportado');
@ -126,12 +132,13 @@ Cypress.Commands.add('validateRow', (rowSelector, expectedValues) => {
cy.get(rowSelector).within(() => {
for (const [index, value] of expectedValues.entries()) {
cy.log('CHECKING ', index, value);
if(value === undefined) continue
if (typeof value == 'boolean') {
const prefix = value ? '' : 'not.';
cy.getValue(`:nth-child(${index + 1})`).should(`${prefix}be.checked`);
continue;
}
cy.getValue(`:nth-child(${index + 1})`).should('have.value', value);
cy.getValue(`:nth-child(${index + 1})`).should('equal', value)
}
});
});