Merge pull request '7430_devToTest' (!388) from 7430_devToTest into test
gitea/salix-front/pipeline/head This commit looks good Details

Reviewed-on: #388
Reviewed-by: Pablo Natek <pablone@verdnatura.es>
This commit is contained in:
Alex Moreno 2024-05-21 09:26:35 +00:00
commit 05587ece9e
206 changed files with 8752 additions and 3833 deletions

View File

@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- (Worker) => Se crea la sección Taquilla
- (General) => Se mantiene el filtro lateral en cualquier parte de la seccíon.
### Fixed ### Fixed
- (General) => Se vuelven a mostrar los parámetros en la url al aplicar un filtro - (General) => Se vuelven a mostrar los parámetros en la url al aplicar un filtro

View File

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

View File

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

View File

@ -8,12 +8,7 @@ import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import FormModelPopup from './FormModelPopup.vue'; import FormModelPopup from './FormModelPopup.vue';
const props = defineProps({ defineProps({ showEntityField: { type: Boolean, default: true } });
showEntityField: {
type: Boolean,
default: true,
},
});
const emit = defineEmits(['onDataSaved']); const emit = defineEmits(['onDataSaved']);
const { t } = useI18n(); const { t } = useI18n();
@ -26,7 +21,7 @@ const bankEntityFormData = reactive({
}); });
const countriesFilter = { const countriesFilter = {
fields: ['id', 'country', 'code'], fields: ['id', 'name', 'code'],
}; };
const countriesOptions = ref([]); const countriesOptions = ref([]);
@ -58,23 +53,19 @@ onMounted(async () => {
> >
<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">
<div class="col"> <VnInput
<VnInput :label="t('name')"
:label="t('name')" v-model="data.name"
v-model="data.name" :required="true"
:required="true" :rules="validate('bankEntity.name')"
:rules="validate('bankEntity.name')" />
/> <VnInput
</div> ref="bicInputRef"
<div class="col"> :label="t('swift')"
<VnInput v-model="data.bic"
ref="bicInputRef" :required="true"
:label="t('swift')" :rules="validate('bankEntity.bic')"
v-model="data.bic" />
:required="true"
:rules="validate('bankEntity.bic')"
/>
</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">
@ -83,7 +74,7 @@ onMounted(async () => {
v-model="data.countryFk" v-model="data.countryFk"
:options="countriesOptions" :options="countriesOptions"
option-value="id" option-value="id"
option-label="country" option-label="name"
hide-selected hide-selected
:required="true" :required="true"
:rules="validate('bankEntity.countryFk')" :rules="validate('bankEntity.countryFk')"

View File

@ -52,9 +52,7 @@ onMounted(() => {
</span> </span>
<h1 class="title">{{ t('New department') }}</h1> <h1 class="title">{{ t('New department') }}</h1>
<VnRow class="row q-gutter-md q-mb-md" style="min-width: 250px"> <VnRow class="row q-gutter-md q-mb-md" style="min-width: 250px">
<div class="col"> <VnInput :label="t('Name')" v-model="data.name" />
<VnInput :label="t('Name')" v-model="data.name" />
</div>
</VnRow> </VnRow>
<div class="q-mt-lg row justify-end"> <div class="q-mt-lg row justify-end">
<QBtn <QBtn

View File

@ -72,69 +72,57 @@ const onDataSaved = async (formData, requestResponse) => {
{{ t('Invoicing in progress...') }} {{ t('Invoicing in progress...') }}
</span> </span>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelect :label="t('Ticket')"
:label="t('Ticket')" :options="ticketsOptions"
:options="ticketsOptions" hide-selected
hide-selected option-label="id"
option-label="id" option-value="id"
option-value="id" v-model="data.ticketFk"
v-model="data.ticketFk" @update:model-value="data.clientFk = null"
@update:model-value="data.clientFk = null" >
> <template #option="scope">
<template #option="scope"> <QItem v-bind="scope.itemProps">
<QItem v-bind="scope.itemProps"> <QItemSection>
<QItemSection> <QItemLabel> #{{ scope.opt?.id }} </QItemLabel>
<QItemLabel> #{{ scope.opt?.id }} </QItemLabel> <QItemLabel caption>{{ scope.opt?.nickname }}</QItemLabel>
<QItemLabel caption>{{ </QItemSection>
scope.opt?.nickname </QItem>
}}</QItemLabel> </template>
</QItemSection> </VnSelect>
</QItem>
</template>
</VnSelect>
</div>
<span class="row items-center" style="max-width: max-content">{{ <span class="row items-center" style="max-width: max-content">{{
t('Or') t('Or')
}}</span> }}</span>
<div class="col"> <VnSelect
<VnSelect :label="t('Client')"
:label="t('Client')" :options="clientsOptions"
:options="clientsOptions" hide-selected
hide-selected option-label="name"
option-label="name" option-value="id"
option-value="id" v-model="data.clientFk"
v-model="data.clientFk" @update:model-value="data.ticketFk = null"
@update:model-value="data.ticketFk = null" />
/> <VnInputDate :label="t('Max date')" v-model="data.maxShipped" />
</div>
<div class="col">
<VnInputDate :label="t('Max date')" v-model="data.maxShipped" />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelect :label="t('Serial')"
:label="t('Serial')" :options="invoiceOutSerialsOptions"
:options="invoiceOutSerialsOptions" hide-selected
hide-selected option-label="description"
option-label="description" option-value="code"
option-value="code" v-model="data.serial"
v-model="data.serial" :required="true"
:required="true" />
/> <VnSelect
</div> :label="t('Area')"
<div class="col"> :options="taxAreasOptions"
<VnSelect hide-selected
:label="t('Area')" option-label="code"
:options="taxAreasOptions" option-value="code"
hide-selected v-model="data.taxArea"
option-label="code" :required="true"
option-value="code" />
v-model="data.taxArea"
:required="true"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<VnInput <VnInput

View File

@ -40,24 +40,20 @@ const onDataSaved = (dataSaved) => {
> >
<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">
<div class="col"> <VnInput
<VnInput :label="t('Name')"
:label="t('Name')" v-model="data.name"
v-model="data.name" :rules="validate('city.name')"
:rules="validate('city.name')" />
/> <VnSelect
</div> :label="t('Province')"
<div class="col"> :options="provincesOptions"
<VnSelect hide-selected
:label="t('Province')" option-label="name"
:options="provincesOptions" option-value="id"
hide-selected v-model="data.provinceFk"
option-label="name" :rules="validate('city.provinceFk')"
option-value="id" />
v-model="data.provinceFk"
:rules="validate('city.provinceFk')"
/>
</div>
</VnRow> </VnRow>
</template> </template>
</FormModelPopup> </FormModelPopup>

View File

@ -30,21 +30,21 @@ const townsLocationOptions = ref([]);
const onDataSaved = (formData) => { const onDataSaved = (formData) => {
const newPostcode = { const newPostcode = {
...formData ...formData,
}; };
const townObject = townsLocationOptions.value.find( const townObject = townsLocationOptions.value.find(
({id}) => id === formData.townFk ({ id }) => id === formData.townFk
); );
newPostcode.town = townObject?.name; newPostcode.town = townObject?.name;
const provinceObject = provincesOptions.value.find( const provinceObject = provincesOptions.value.find(
({id}) => id === formData.provinceFk ({ id }) => id === formData.provinceFk
); );
newPostcode.province = provinceObject?.name; newPostcode.province = provinceObject?.name;
const countryObject = countriesOptions.value.find( const countryObject = countriesOptions.value.find(
({id}) => id === formData.countryFk ({ id }) => id === formData.countryFk
); );
newPostcode.country = countryObject?.country; newPostcode.country = countryObject?.country;
emit('onDataSaved', newPostcode); emit('onDataSaved', newPostcode);
}; };
const onCityCreated = async ({ name, provinceFk }, formData) => { const onCityCreated = async ({ name, provinceFk }, formData) => {
@ -92,63 +92,55 @@ const onProvinceCreated = async ({ name }, formData) => {
> >
<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">
<div class="col"> <VnInput
<VnInput :label="t('Postcode')"
:label="t('Postcode')" v-model="data.code"
v-model="data.code" :rules="validate('postcode.code')"
:rules="validate('postcode.code')" />
/> <VnSelectDialog
</div> :label="t('City')"
<div class="col"> :options="townsLocationOptions"
<VnSelectDialog v-model="data.townFk"
:label="t('City')" hide-selected
:options="townsLocationOptions" option-label="name"
v-model="data.townFk" option-value="id"
hide-selected :rules="validate('postcode.city')"
option-label="name" :roles-allowed-to-create="['deliveryAssistant']"
option-value="id" >
:rules="validate('postcode.city')" <template #form>
:roles-allowed-to-create="['deliveryAssistant']" <CreateNewCityForm @on-data-saved="onCityCreated($event, data)" />
> </template>
<template #form> </VnSelectDialog>
<CreateNewCityForm
@on-data-saved="onCityCreated($event, data)"
/>
</template>
</VnSelectDialog>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-xl"> <VnRow class="row q-gutter-md q-mb-xl">
<div class="col"> <VnSelectDialog
<VnSelectDialog :label="t('Province')"
:label="t('Province')" :options="provincesOptions"
:options="provincesOptions" hide-selected
hide-selected option-label="name"
option-label="name" option-value="id"
option-value="id" v-model="data.provinceFk"
v-model="data.provinceFk" :rules="validate('postcode.provinceFk')"
:rules="validate('postcode.provinceFk')" :roles-allowed-to-create="['deliveryAssistant']"
:roles-allowed-to-create="['deliveryAssistant']" >
> <template #form>
<template #form> <CreateNewProvinceForm
<CreateNewProvinceForm @on-data-saved="onProvinceCreated($event, data)"
@on-data-saved="onProvinceCreated($event, data)" />
/> </template> </VnSelectDialog
</template> ></VnRow>
</VnSelectDialog> <VnRow class="row q-gutter-md q-mb-xl"
</div> ><VnSelect
<div class="col"> :label="t('Country')"
<VnSelect :options="countriesOptions"
:label="t('Country')" hide-selected
:options="countriesOptions" option-label="name"
hide-selected option-value="id"
option-label="country" v-model="data.countryFk"
option-value="id" :rules="validate('postcode.countryFk')"
v-model="data.countryFk" />
:rules="validate('postcode.countryFk')" </VnRow>
/> </template>
</div> </VnRow
></template>
</FormModelPopup> </FormModelPopup>
</template> </template>

View File

@ -40,24 +40,20 @@ const onDataSaved = (dataSaved) => {
> >
<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">
<div class="col"> <VnInput
<VnInput :label="t('Name')"
:label="t('Name')" v-model="data.name"
v-model="data.name" :rules="validate('province.name')"
:rules="validate('province.name')" />
/> <VnSelect
</div> :label="t('Autonomy')"
<div class="col"> :options="autonomiesOptions"
<VnSelect hide-selected
:label="t('Autonomy')" option-label="name"
:options="autonomiesOptions" option-value="id"
hide-selected v-model="data.autonomyFk"
option-label="name" :rules="validate('province.autonomyFk')"
option-value="id" />
v-model="data.autonomyFk"
:rules="validate('province.autonomyFk')"
/>
</div>
</VnRow> </VnRow>
</template> </template>
</FormModelPopup> </FormModelPopup>

View File

@ -54,51 +54,42 @@ const onDataSaved = (dataSaved) => {
> >
<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">
<div class="col"> <VnInput
<VnInput :label="t('Identifier')"
:label="t('Identifier')" v-model="data.thermographId"
v-model="data.thermographId" :required="true"
:required="true" :rules="validate('thermograph.id')"
:rules="validate('thermograph.id')" />
/> <VnSelect
</div> :label="t('Model')"
:options="thermographsModels"
<div class="col"> hide-selected
<VnSelect option-label="value"
:label="t('Model')" option-value="value"
:options="thermographsModels" v-model="data.model"
hide-selected :required="true"
option-label="value" :rules="validate('thermograph.model')"
option-value="value" />
v-model="data.model"
:required="true"
:rules="validate('thermograph.model')"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-xl"> <VnRow class="row q-gutter-md q-mb-xl">
<div class="col"> <VnSelect
<VnSelect :label="t('Warehouse')"
:label="t('Warehouse')" :options="warehousesOptions"
:options="warehousesOptions" hide-selected
hide-selected option-label="name"
option-label="name" option-value="id"
option-value="id" v-model="data.warehouseId"
v-model="data.warehouseId" :required="true"
:required="true" />
/> <VnSelect
</div> :label="t('Temperature')"
<div class="col"> :options="temperaturesOptions"
<VnSelect hide-selected
:label="t('Temperature')" option-label="name"
:options="temperaturesOptions" option-value="code"
hide-selected v-model="data.temperatureFk"
option-label="name" :required="true"
option-value="code" />
v-model="data.temperatureFk"
:required="true"
/>
</div>
</VnRow> </VnRow>
</template> </template>
</FormModelPopup> </FormModelPopup>

View File

@ -81,6 +81,7 @@ defineExpose({
hasChanges, hasChanges,
saveChanges, saveChanges,
getChanges, getChanges,
formData,
}); });
async function fetch(data) { async function fetch(data) {

View File

@ -246,61 +246,55 @@ const makeRequest = async () => {
<div class="column"> <div class="column">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <QOptionGroup
<QOptionGroup :options="uploadMethodsOptions"
:options="uploadMethodsOptions" type="radio"
type="radio" v-model="uploadMethodSelected"
v-model="uploadMethodSelected" />
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <QFile
<QFile v-if="uploadMethodSelected === 'computer'"
v-if="uploadMethodSelected === 'computer'" ref="inputFileRef"
ref="inputFileRef" :label="t('File')"
:label="t('File')" :multiple="false"
:multiple="false" v-model="newPhoto.files"
v-model="newPhoto.files" @update:model-value="updatePhotoPreview($event)"
@update:model-value="updatePhotoPreview($event)" :accept="allowedContentTypes"
:accept="allowedContentTypes" class="required cursor-pointer"
class="required cursor-pointer" >
> <template #append>
<template #append> <QIcon
<QIcon name="vn:attach"
name="vn:attach" class="cursor-pointer q-mr-sm"
class="cursor-pointer q-mr-sm" @click="openInputFile()"
@click="openInputFile()" >
> <!-- <QTooltip>{{ t('globals.selectFile') }}</QTooltip> -->
<!-- <QTooltip>{{ t('globals.selectFile') }}</QTooltip> --> </QIcon>
</QIcon> <QIcon name="info" class="cursor-pointer">
<QIcon name="info" class="cursor-pointer"> <QTooltip>{{
<QTooltip>{{ t('globals.allowedFilesText', {
t('globals.allowedFilesText', { allowedContentTypes: allowedContentTypes,
allowedContentTypes: allowedContentTypes, })
}) }}</QTooltip>
}}</QTooltip> </QIcon>
</QIcon> </template>
</template> </QFile>
</QFile> <VnInput
<VnInput v-if="uploadMethodSelected === 'URL'"
v-if="uploadMethodSelected === 'URL'" v-model="newPhoto.url"
v-model="newPhoto.url" @update:model-value="updatePhotoPreview($event)"
@update:model-value="updatePhotoPreview($event)" placeholder="https://"
placeholder="https://" />
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelect :label="t('Orientation')"
:label="t('Orientation')" :options="viewportTypes"
:options="viewportTypes" hide-selected
hide-selected option-label="description"
option-label="description" v-model="viewportSelection"
v-model="viewportSelection" />
/>
</div>
</VnRow> </VnRow>
<div class="q-mt-lg row justify-end"> <div class="q-mt-lg row justify-end">
<QBtn <QBtn

View File

@ -1,7 +1,6 @@
<script setup> <script setup>
import { ref, reactive, computed } from 'vue'; import { ref, reactive, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
@ -12,10 +11,16 @@ import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import axios from 'axios'; import axios from 'axios';
import { dashIfEmpty } from 'src/filters'; import { dashIfEmpty } from 'src/filters';
const props = defineProps({
url: {
type: String,
required: true,
},
});
const emit = defineEmits(['itemSelected']); const emit = defineEmits(['itemSelected']);
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute();
const itemFilter = { const itemFilter = {
include: [ include: [
@ -73,7 +78,7 @@ const tableColumns = computed(() => [
{ {
label: t('entry.buys.color'), label: t('entry.buys.color'),
name: 'ink', name: 'ink',
field: 'inkName', field: (row) => row?.ink?.name,
align: 'left', align: 'left',
}, },
]); ]);
@ -100,7 +105,7 @@ const fetchResults = async () => {
} }
filter.where = where; filter.where = where;
const { data } = await axios.get(`Entries/${route.params.id}/lastItemBuys`, { const { data } = await axios.get(props.url, {
params: { filter: JSON.stringify(filter) }, params: { filter: JSON.stringify(filter) },
}); });
tableRows.value = data; tableRows.value = data;
@ -147,48 +152,32 @@ const selectItem = ({ id }) => {
</span> </span>
<h1 class="title">{{ t('Filter item') }}</h1> <h1 class="title">{{ t('Filter item') }}</h1>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInput :label="t('entry.buys.name')" v-model="itemFilterParams.name" />
<VnInput <VnInput :label="t('entry.buys.size')" v-model="itemFilterParams.size" />
:label="t('entry.buys.name')" <VnSelect
v-model="itemFilterParams.name" :label="t('entry.buys.producer')"
/> :options="producersOptions"
</div> hide-selected
<div class="col"> option-label="name"
<VnInput option-value="id"
:label="t('entry.buys.size')" v-model="itemFilterParams.producerFk"
v-model="itemFilterParams.size" />
/> <VnSelect
</div> :label="t('entry.buys.type')"
<div class="col"> :options="ItemTypesOptions"
<VnSelect hide-selected
:label="t('entry.buys.producer')" option-label="name"
:options="producersOptions" option-value="id"
hide-selected v-model="itemFilterParams.typeFk"
option-label="name" />
option-value="id" <VnSelect
v-model="itemFilterParams.producerFk" :label="t('entry.buys.color')"
/> :options="InksOptions"
</div> hide-selected
<div class="col"> option-label="name"
<VnSelect option-value="id"
:label="t('entry.buys.type')" v-model="itemFilterParams.inkFk"
:options="ItemTypesOptions" />
hide-selected
option-label="name"
option-value="id"
v-model="itemFilterParams.typeFk"
/>
</div>
<div class="col">
<VnSelect
:label="t('entry.buys.color')"
:options="InksOptions"
hide-selected
option-label="name"
option-value="id"
v-model="itemFilterParams.inkFk"
/>
</div>
</VnRow> </VnRow>
<div class="q-mt-lg row justify-end"> <div class="q-mt-lg row justify-end">
<QBtn <QBtn

View File

@ -145,48 +145,38 @@ const selectTravel = ({ id }) => {
</span> </span>
<h1 class="title">{{ t('Filter travels') }}</h1> <h1 class="title">{{ t('Filter travels') }}</h1>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelect :label="t('entry.basicData.agency')"
:label="t('entry.basicData.agency')" :options="agenciesOptions"
:options="agenciesOptions" hide-selected
hide-selected option-label="name"
option-label="name" option-value="id"
option-value="id" v-model="travelFilterParams.agencyModeFk"
v-model="travelFilterParams.agencyModeFk" />
/> <VnSelect
</div> :label="t('entry.basicData.warehouseOut')"
<div class="col"> :options="warehousesOptions"
<VnSelect hide-selected
:label="t('entry.basicData.warehouseOut')" option-label="name"
:options="warehousesOptions" option-value="id"
hide-selected v-model="travelFilterParams.warehouseOutFk"
option-label="name" />
option-value="id" <VnSelect
v-model="travelFilterParams.warehouseOutFk" :label="t('entry.basicData.warehouseIn')"
/> :options="warehousesOptions"
</div> hide-selected
<div class="col"> option-label="name"
<VnSelect option-value="id"
:label="t('entry.basicData.warehouseIn')" v-model="travelFilterParams.warehouseInFk"
:options="warehousesOptions" />
hide-selected <VnInputDate
option-label="name" :label="t('entry.basicData.shipped')"
option-value="id" v-model="travelFilterParams.shipped"
v-model="travelFilterParams.warehouseInFk" />
/> <VnInputDate
</div> :label="t('entry.basicData.landed')"
<div class="col"> v-model="travelFilterParams.landed"
<VnInputDate />
:label="t('entry.basicData.shipped')"
v-model="travelFilterParams.shipped"
/>
</div>
<div class="col">
<VnInputDate
:label="t('entry.basicData.landed')"
v-model="travelFilterParams.landed"
/>
</div>
</VnRow> </VnRow>
<div class="q-mt-lg row justify-end"> <div class="q-mt-lg row justify-end">
<QBtn <QBtn

View File

@ -156,9 +156,12 @@ const startFormWatcher = () => {
async function fetch() { async function fetch() {
try { try {
const { data } = await axios.get($props.url, { let { data } = await axios.get($props.url, {
params: { filter: JSON.stringify($props.filter) }, params: { filter: JSON.stringify($props.filter) },
}); });
if (Array.isArray(data)) data = data[0] ?? {};
state.set($props.model, data); state.set($props.model, data);
originalData.value = data && JSON.parse(JSON.stringify(data)); originalData.value = data && JSON.parse(JSON.stringify(data));

View File

@ -6,7 +6,7 @@ import FormModel from 'components/FormModel.vue';
const emit = defineEmits(['onDataSaved']); const emit = defineEmits(['onDataSaved']);
const $props = defineProps({ defineProps({
title: { title: {
type: String, type: String,
default: '', default: '',

View File

@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
const emit = defineEmits(['onSubmit']); const emit = defineEmits(['onSubmit']);
const $props = defineProps({ defineProps({
title: { title: {
type: String, type: String,
default: '', default: '',

View File

@ -50,13 +50,11 @@ const onDataSaved = (data) => {
> >
<template #form-inputs="{ data }"> <template #form-inputs="{ data }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <QInput
<QInput :label="t('Type the visible quantity')"
:label="t('Type the visible quantity')" v-model.number="data.quantity"
v-model.number="data.quantity" autofocus
autofocus />
/>
</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

@ -83,74 +83,66 @@ const transferInvoice = async () => {
> >
<template #form-inputs> <template #form-inputs>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelect :label="t('Client')"
:label="t('Client')" :options="clientsOptions"
:options="clientsOptions" hide-selected
hide-selected option-label="name"
option-label="name" option-value="id"
option-value="id" v-model="transferInvoiceParams.newClientFk"
v-model="transferInvoiceParams.newClientFk" :required="true"
:required="true" >
> <template #option="scope">
<template #option="scope"> <QItem v-bind="scope.itemProps">
<QItem v-bind="scope.itemProps"> <QItemSection>
<QItemSection> <QItemLabel>
<QItemLabel> #{{ scope.opt?.id }} -
#{{ scope.opt?.id }} - {{ scope.opt?.name }}
{{ scope.opt?.name }} </QItemLabel>
</QItemLabel> </QItemSection>
</QItemSection> </QItem>
</QItem> </template>
</template> </VnSelect>
</VnSelect> <VnSelect
</div> :label="t('Rectificative type')"
<div class="col"> :options="rectificativeTypeOptions"
<VnSelect hide-selected
:label="t('Rectificative type')" option-label="description"
:options="rectificativeTypeOptions" option-value="id"
hide-selected v-model="transferInvoiceParams.cplusRectificationTypeFk"
option-label="description" :required="true"
option-value="id" />
v-model="transferInvoiceParams.cplusRectificationTypeFk"
:required="true"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelect :label="t('Class')"
:label="t('Class')" :options="siiTypeInvoiceOutsOptions"
:options="siiTypeInvoiceOutsOptions" hide-selected
hide-selected option-label="description"
option-label="description" option-value="id"
option-value="id" v-model="transferInvoiceParams.siiTypeInvoiceOutFk"
v-model="transferInvoiceParams.siiTypeInvoiceOutFk" :required="true"
:required="true" >
> <template #option="scope">
<template #option="scope"> <QItem v-bind="scope.itemProps">
<QItem v-bind="scope.itemProps"> <QItemSection>
<QItemSection> <QItemLabel>
<QItemLabel> {{ scope.opt?.code }} -
{{ scope.opt?.code }} - {{ scope.opt?.description }}
{{ scope.opt?.description }} </QItemLabel>
</QItemLabel> </QItemSection>
</QItemSection> </QItem>
</QItem> </template>
</template> </VnSelect>
</VnSelect> <VnSelect
</div> :label="t('Type')"
<div class="col"> :options="invoiceCorrectionTypesOptions"
<VnSelect hide-selected
:label="t('Type')" option-label="description"
:options="invoiceCorrectionTypesOptions" option-value="id"
hide-selected v-model="transferInvoiceParams.invoiceCorrectionTypeFk"
option-label="description" :required="true"
option-value="id" />
v-model="transferInvoiceParams.invoiceCorrectionTypeFk"
:required="true"
/>
</div>
</VnRow> </VnRow>
</template> </template>
</FormPopup> </FormPopup>

View File

@ -178,6 +178,8 @@ function copyUserToken() {
:options="warehousesData" :options="warehousesData"
option-label="name" option-label="name"
option-value="id" option-value="id"
input-debounce="0"
hide-selected
/> />
<VnSelect <VnSelect
:label="t('components.userPanel.localBank')" :label="t('components.userPanel.localBank')"
@ -185,6 +187,8 @@ function copyUserToken() {
:options="accountBankData" :options="accountBankData"
option-label="bank" option-label="bank"
option-value="id" option-value="id"
input-debounce="0"
hide-selected
> >
<template #option="{ itemProps, opt }"> <template #option="{ itemProps, opt }">
<QItem v-bind="itemProps"> <QItem v-bind="itemProps">
@ -201,10 +205,11 @@ function copyUserToken() {
<VnSelect <VnSelect
:label="t('components.userPanel.localCompany')" :label="t('components.userPanel.localCompany')"
hide-selected hide-selected
v-model="user.companyFk" v-model="user.localCompanyFk"
:options="companiesData" :options="companiesData"
option-label="code" option-label="code"
option-value="id" option-value="id"
input-debounce="0"
/> />
<VnSelect <VnSelect
:label="t('components.userPanel.userWarehouse')" :label="t('components.userPanel.userWarehouse')"
@ -213,6 +218,7 @@ function copyUserToken() {
:options="warehousesData" :options="warehousesData"
option-label="name" option-label="name"
option-value="id" option-value="id"
input-debounce="0"
/> />
</VnRow> </VnRow>
<VnRow> <VnRow>
@ -224,6 +230,8 @@ function copyUserToken() {
option-label="code" option-label="code"
option-value="id" option-value="id"
style="flex: 0" style="flex: 0"
dense
input-debounce="0"
/> />
</VnRow> </VnRow>
</div> </div>

View File

@ -0,0 +1,55 @@
<script setup>
import { ref, onMounted, useSlots } from 'vue';
import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore';
const slots = useSlots();
const hasContent = ref(false);
const rightPanel = ref(null);
onMounted(() => {
rightPanel.value = document.querySelector('#right-panel');
if (rightPanel.value.childNodes.length) hasContent.value = true;
// Check if there's content to display
const observer = new MutationObserver(() => {
hasContent.value = rightPanel.value.childNodes.length;
});
if (rightPanel.value)
observer.observe(rightPanel.value, {
subtree: true,
childList: true,
attributes: true,
});
if (!slots['right-panel'] && !hasContent.value) stateStore.rightDrawer = false;
});
const { t } = useI18n();
const stateStore = useStateStore();
</script>
<template>
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
v-if="hasContent || $slots['right-panel']"
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<div id="right-panel"></div>
<slot v-if="!hasContent" name="right-panel" />
</QScrollArea>
</QDrawer>
</template>

View File

@ -29,10 +29,12 @@ async function confirm() {
const response = { address: address.value }; const response = { address: address.value };
if (props.promise) { if (props.promise) {
isLoading.value = true; isLoading.value = true;
const { address: _address, ...restData } = props.data;
try { try {
Object.assign(response, restData); const dataCopy = JSON.parse(JSON.stringify({ ...props.data }));
delete dataCopy.address;
Object.assign(response, dataCopy);
await props.promise(response); await props.promise(response);
} finally { } finally {
isLoading.value = false; isLoading.value = false;

View File

@ -1,13 +1,13 @@
<script setup> <script setup>
import { onBeforeMount, computed } from 'vue'; import { onBeforeMount, computed, watchEffect } from 'vue';
import { useRoute, onBeforeRouteUpdate } from 'vue-router'; import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import useCardSize from 'src/composables/useCardSize'; import useCardSize from 'src/composables/useCardSize';
import VnSubToolbar from '../ui/VnSubToolbar.vue'; import VnSubToolbar from '../ui/VnSubToolbar.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue';
import LeftMenu from 'components/LeftMenu.vue'; import LeftMenu from 'components/LeftMenu.vue';
import RightMenu from 'components/common/RightMenu.vue';
const props = defineProps({ const props = defineProps({
dataKey: { type: String, required: true }, dataKey: { type: String, required: true },
@ -15,13 +15,13 @@ const props = defineProps({
customUrl: { type: String, default: undefined }, customUrl: { type: String, default: undefined },
filter: { type: Object, default: () => {} }, filter: { type: Object, default: () => {} },
descriptor: { type: Object, required: true }, descriptor: { type: Object, required: true },
searchbarDataKey: { type: String, default: undefined }, filterPanel: { type: Object, default: undefined },
searchbarUrl: { type: String, default: undefined }, searchDataKey: { type: String, default: undefined },
searchUrl: { type: String, default: undefined },
searchbarLabel: { type: String, default: '' }, searchbarLabel: { type: String, default: '' },
searchbarInfo: { type: String, default: '' }, searchbarInfo: { type: String, default: '' },
}); });
const { t } = useI18n();
const stateStore = useStateStore(); const stateStore = useStateStore();
const route = useRoute(); const route = useRoute();
const url = computed(() => { const url = computed(() => {
@ -47,26 +47,38 @@ if (props.baseUrl) {
} }
}); });
} }
watchEffect(() => {
if (Array.isArray(arrayData.store.data))
arrayData.store.data = arrayData.store.data[0];
});
</script> </script>
<template> <template>
<Teleport <template v-if="stateStore.isHeaderMounted()">
to="#searchbar" <Teleport to="#searchbar" v-if="props.searchDataKey">
v-if="stateStore.isHeaderMounted() && props.searchbarDataKey" <slot name="searchbar">
> <VnSearchbar
<VnSearchbar :data-key="props.searchDataKey"
:data-key="props.searchbarDataKey" :url="props.searchUrl"
:url="props.searchbarUrl" :label="props.searchbarLabel"
:label="t(props.searchbarLabel)" :info="props.searchbarInfo"
:info="t(props.searchbarInfo)" />
/> </slot>
</Teleport> </Teleport>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256"> <slot v-else name="searchbar" />
<QScrollArea class="fit"> <QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<component :is="descriptor" /> <QScrollArea class="fit">
<QSeparator /> <component :is="descriptor" />
<LeftMenu source="card" /> <QSeparator />
</QScrollArea> <LeftMenu source="card" />
</QDrawer> </QScrollArea>
</QDrawer>
<RightMenu>
<template #right-panel v-if="props.filterPanel">
<component :is="props.filterPanel" :data-key="props.searchDataKey" />
</template>
</RightMenu>
</template>
<QPageContainer> <QPageContainer>
<QPage> <QPage>
<VnSubToolbar /> <VnSubToolbar />

View File

@ -37,14 +37,6 @@ const styleAttrs = computed(() => {
: {}; : {};
}); });
const onEnterPress = () => {
emit('keyup.enter');
};
const handleValue = (val = null) => {
value.value = val;
};
const focus = () => { const focus = () => {
vnInputRef.value.focus(); vnInputRef.value.focus();
}; };
@ -52,6 +44,13 @@ const focus = () => {
defineExpose({ defineExpose({
focus, focus,
}); });
const inputRules = [
(val) => {
const { min } = vnInputRef.value.$attrs;
if (min >= 0) if (Math.floor(val) < min) return t('inputMin', { value: min });
},
];
</script> </script>
<template> <template>
@ -66,22 +65,31 @@ defineExpose({
v-bind="{ ...$attrs, ...styleAttrs }" v-bind="{ ...$attrs, ...styleAttrs }"
:type="$attrs.type" :type="$attrs.type"
:class="{ required: $attrs.required }" :class="{ required: $attrs.required }"
@keyup.enter="onEnterPress()" @keyup.enter="emit('keyup.enter')"
:clearable="false" :clearable="false"
:rules="inputRules"
:lazy-rules="true"
hide-bottom-space
> >
<template v-if="$slots.prepend" #prepend> <template v-if="$slots.prepend" #prepend>
<slot name="prepend" /> <slot name="prepend" />
</template> </template>
<template #append> <template #append>
<slot name="append" v-if="$slots.append" /> <slot name="append" v-if="$slots.append && !$attrs.disabled" />
<QIcon <QIcon
name="close" name="close"
size="xs" size="xs"
v-if="hover && value" v-if="$slots.append && hover && value && !$attrs.disabled"
@click="handleValue(null)" @click="value = null"
></QIcon> ></QIcon>
</template> </template>
</QInput> </QInput>
</div> </div>
</template> </template>
<i18n>
en:
inputMin: Must be more than {value}
es:
inputMin: Debe ser mayor a {value}
</i18n>

View File

@ -22,6 +22,10 @@ const $props = defineProps({
type: String, type: String,
default: '', default: '',
}, },
optionFilter: {
type: String,
default: null,
},
url: { url: {
type: String, type: String,
default: '', default: '',
@ -57,9 +61,9 @@ const $props = defineProps({
}); });
const { t } = useI18n(); const { t } = useI18n();
const requiredFieldRule = (val) => !!val || t('globals.fieldRequired'); const requiredFieldRule = (val) => val ?? t('globals.fieldRequired');
const { optionLabel, optionValue, options, modelValue } = toRefs($props); const { optionLabel, optionValue, optionFilter, options, modelValue } = toRefs($props);
const myOptions = ref([]); const myOptions = ref([]);
const myOptionsOriginal = ref([]); const myOptionsOriginal = ref([]);
const vnSelectRef = ref(); const vnSelectRef = ref();
@ -109,9 +113,9 @@ async function fetchFilter(val) {
const { fields, sortBy, limit } = $props; const { fields, sortBy, limit } = $props;
let key = optionLabel.value; let key = optionLabel.value;
if (new RegExp(/\d/g).test(val)) key = optionValue.value; if (new RegExp(/\d/g).test(val)) key = optionFilter.value ?? optionValue.value;
const where = { [key]: { like: `%${val}%` } }; const where = { ...{ [key]: { like: `%${val}%` } }, ...$props.where };
return dataRef.value.fetch({ fields, where, order: sortBy, limit }); return dataRef.value.fetch({ fields, where, order: sortBy, limit });
} }
@ -167,6 +171,7 @@ watch(modelValue, (newValue) => {
hide-selected hide-selected
fill-input fill-input
ref="vnSelectRef" ref="vnSelectRef"
lazy-rules
:class="{ required: $attrs.required }" :class="{ required: $attrs.required }"
:rules="$attrs.required ? [requiredFieldRule] : null" :rules="$attrs.required ? [requiredFieldRule] : null"
virtual-scroll-slice-size="options.length" virtual-scroll-slice-size="options.length"

View File

@ -54,6 +54,13 @@ async function fetch() {
emit('onFetch', Array.isArray(data) ? data[0] : data); emit('onFetch', Array.isArray(data) ? data[0] : data);
isLoading.value = false; isLoading.value = false;
} }
const showRedirectToSummaryIcon = computed(() => {
const routeExists = route.matched.some(
(route) => route.name === `${route.meta.moduleName}Summary`
);
return !isSummary.value && route.meta.moduleName && routeExists;
});
</script> </script>
<template> <template>
@ -64,7 +71,7 @@ async function fetch() {
<div class="summaryHeader bg-primary q-pa-sm text-weight-bolder"> <div class="summaryHeader bg-primary q-pa-sm text-weight-bolder">
<slot name="header-left"> <slot name="header-left">
<router-link <router-link
v-if="!isSummary && route.meta.moduleName" v-if="showRedirectToSummaryIcon"
class="header link" class="header link"
:to="{ :to="{
name: `${route.meta.moduleName}Summary`, name: `${route.meta.moduleName}Summary`,
@ -135,6 +142,9 @@ async function fetch() {
box-shadow: none; box-shadow: none;
.vn-label-value { .vn-label-value {
&.negative > .value span {
color: $alert;
}
display: flex; display: flex;
flex-direction: row; flex-direction: row;
margin-top: 2px; margin-top: 2px;

View File

@ -108,7 +108,7 @@ const containerClasses = computed(() => {
font-size: 13px; font-size: 13px;
&:hover { &:hover {
background-color: var(--vn-accent-color); background-color: var(--vn-label-color);
cursor: pointer; cursor: pointer;
} }
} }

View File

@ -1,28 +1,16 @@
<template> <template>
<div class="q-pa-md"> <div class="q-pa-md">
<div class="row q-gutter-md q-mb-md"> <div class="row q-gutter-md q-mb-md">
<div class="col"> <QSkeleton type="QInput" square />
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" square />
</div>
<div class="col">
<QSkeleton type="QInput" square />
</div>
</div> </div>
<div class="row q-gutter-md q-mb-md"> <div class="row q-gutter-md q-mb-md">
<div class="col"> <QSkeleton type="QInput" square />
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" square />
</div>
<div class="col">
<QSkeleton type="QInput" square />
</div>
</div> </div>
<div class="row q-gutter-md q-mb-md"> <div class="row q-gutter-md q-mb-md">
<div class="col"> <QSkeleton type="QInput" square />
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" square />
</div>
<div class="col">
<QSkeleton type="QInput" square />
</div>
</div> </div>
<div class="row q-gutter-md"> <div class="row q-gutter-md">
<QSkeleton type="QBtn" /> <QSkeleton type="QBtn" />

View File

@ -3,46 +3,36 @@
<QSkeleton type="rect" square /> <QSkeleton type="rect" square />
</div> </div>
<div class="row q-pa-md q-col-gutter-md q-mb-md"> <div class="row q-pa-md q-col-gutter-md q-mb-md">
<div class="col"> <QSkeleton type="rect" class="q-mb-md" square />
<QSkeleton type="rect" class="q-mb-md" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="rect" class="q-mb-md" square />
</div> <QSkeleton type="text" square />
<div class="col"> <QSkeleton type="text" square />
<QSkeleton type="rect" class="q-mb-md" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="rect" class="q-mb-md" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
</div> <QSkeleton type="text" square />
<div class="col"> <QSkeleton type="text" square />
<QSkeleton type="rect" class="q-mb-md" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="rect" class="q-mb-md" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
</div> <QSkeleton type="text" square />
<div class="col"> <QSkeleton type="rect" class="q-mb-md" square />
<QSkeleton type="rect" class="q-mb-md" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square />
</div>
<div class="col">
<QSkeleton type="rect" class="q-mb-md" square />
<QSkeleton type="text" square />
<QSkeleton type="text" square />
<QSkeleton type="text" square />
<QSkeleton type="text" square />
<QSkeleton type="text" square />
</div>
</div> </div>
</template> </template>

View File

@ -1,45 +1,20 @@
<template> <template>
<div class="q-pa-md w"> <div class="q-pa-md w">
<div class="row q-gutter-md q-mb-md"> <div class="row q-gutter-md q-mb-md">
<div class="col-1"> <QSkeleton type="rect" square />
<QSkeleton type="rect" square /> <QSkeleton type="rect" square />
</div> <QSkeleton type="rect" square />
<div class="col"> <QSkeleton type="rect" square />
<QSkeleton type="rect" square /> <QSkeleton type="rect" square />
</div> <QSkeleton type="rect" square />
<div class="col"> </div>
<QSkeleton type="rect" square /> <div class="row q-gutter-md q-mb-md" v-for="n in 5" :key="n">
</div> <QSkeleton type="QInput" square />
<div class="col"> <QSkeleton type="QInput" square />
<QSkeleton type="rect" square /> <QSkeleton type="QInput" square />
</div> <QSkeleton type="QInput" square />
<div class="col"> <QSkeleton type="QInput" square />
<QSkeleton type="rect" square /> <QSkeleton type="QInput" square />
</div>
<div class="col">
<QSkeleton type="rect" square />
</div>
</div>
<div class="row q-gutter-md q-mb-md" v-for="n in 5" :key="n">
<div class="col-1">
<QSkeleton type="QInput" square />
</div>
<div class="col">
<QSkeleton type="QInput" square />
</div>
<div class="col">
<QSkeleton type="QInput" square />
</div>
<div class="col">
<QSkeleton type="QInput" square />
</div>
<div class="col">
<QSkeleton type="QInput" square />
</div>
<div class="col">
<QSkeleton type="QInput" square />
</div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import toDate from 'filters/toDate'; import toDate from 'filters/toDate';
import useRedirect from 'src/composables/useRedirect';
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue'; import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
const { t } = useI18n(); const { t } = useI18n();
@ -56,6 +56,7 @@ const arrayData = useArrayData(props.dataKey, {
const route = useRoute(); const route = useRoute();
const store = arrayData.store; const store = arrayData.store;
const userParams = ref({}); const userParams = ref({});
const { navigate } = useRedirect();
onMounted(() => { onMounted(() => {
if (props.params) userParams.value = JSON.parse(JSON.stringify(props.params)); if (props.params) userParams.value = JSON.parse(JSON.stringify(props.params));
@ -92,6 +93,7 @@ async function search() {
isLoading.value = false; isLoading.value = false;
emit('search'); emit('search');
navigate(store.data, {});
} }
async function reload() { async function reload() {
@ -102,6 +104,7 @@ async function reload() {
if (!props.showAll && !params.length) store.data = []; if (!props.showAll && !params.length) store.data = [];
isLoading.value = false; isLoading.value = false;
emit('refresh'); emit('refresh');
navigate(store.data, {});
} }
async function clearFilters() { async function clearFilters() {
@ -147,7 +150,7 @@ const customTags = computed(() =>
async function remove(key) { async function remove(key) {
userParams.value[key] = null; userParams.value[key] = null;
await search(); await arrayData.applyFilter({ params: userParams.value });
emit('remove', key); emit('remove', key);
} }

View File

@ -1,21 +1,16 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n'; defineProps({ phoneNumber: { type: [String, Number], default: null } });
const props = defineProps({
phoneNumber: { type: [String, Number], default: null },
});
const { t } = useI18n();
</script> </script>
<template> <template>
<QBtn <QBtn
v-if="props.phoneNumber" v-if="phoneNumber"
flat flat
round round
icon="phone" icon="phone"
size="sm" size="sm"
color="primary" color="primary"
padding="none" padding="none"
:href="`sip:${props.phoneNumber}`" :href="`sip:${phoneNumber}`"
@click.stop @click.stop
/> />
</template> </template>
<style scoped></style>

View File

@ -42,6 +42,10 @@ const props = defineProps({
type: Object, type: Object,
default: null, default: null,
}, },
keepOpts: {
type: Array,
default: () => [],
},
offset: { offset: {
type: Number, type: Number,
default: 0, default: 0,
@ -76,6 +80,7 @@ const arrayData = useArrayData(props.dataKey, {
order: props.order, order: props.order,
userParams: props.userParams, userParams: props.userParams,
exprBuilder: props.exprBuilder, exprBuilder: props.exprBuilder,
keepOpts: props.keepOpts,
}); });
const store = arrayData.store; const store = arrayData.store;

View File

@ -1,11 +1,13 @@
<script setup> <script setup>
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import useRedirect from 'src/composables/useRedirect';
import { useI18n } from 'vue-i18n';
const quasar = useQuasar(); const quasar = useQuasar();
const { t } = useI18n();
const props = defineProps({ const props = defineProps({
dataKey: { dataKey: {
@ -65,10 +67,10 @@ const props = defineProps({
}, },
}); });
const router = useRouter();
const arrayData = useArrayData(props.dataKey, { ...props }); const arrayData = useArrayData(props.dataKey, { ...props });
const { store } = arrayData; const { store } = arrayData;
const searchText = ref(''); const searchText = ref('');
const { navigate } = useRedirect();
onMounted(() => { onMounted(() => {
const params = store.userParams; const params = store.userParams;
@ -81,37 +83,20 @@ async function search() {
const staticParams = Object.entries(store.userParams).filter( const staticParams = Object.entries(store.userParams).filter(
([key, value]) => value && (props.staticParams || []).includes(key) ([key, value]) => value && (props.staticParams || []).includes(key)
); );
// const filter =props?.where? { where: JSON.parse(props.where) }: {} store.skip = 0;
await arrayData.applyFilter({ await arrayData.applyFilter({
params: { params: {
// filter ,
...Object.fromEntries(staticParams), ...Object.fromEntries(staticParams),
search: searchText.value, search: searchText.value,
}, },
}); });
if (!props.redirect) return; if (!props.redirect) return;
if (props.customRouteRedirectName) navigate(store.data, {
return router.push({ customRouteRedirectName: props.customRouteRedirectName,
name: props.customRouteRedirectName, searchText: searchText.value,
params: { id: searchText.value }, });
});
const { matched: matches } = router.currentRoute.value;
const { path } = matches.at(-1);
const [, moduleName] = path.split('/');
if (!store.data.length || store.data.length > 1)
return router.push({ path: `/${moduleName}/list` });
const targetId = store.data[0].id;
let targetUrl;
if (path.endsWith('/list')) targetUrl = path.replace('/list', `/${targetId}/summary`);
if (path.endsWith('-list')) targetUrl = path.replace('-list', `/${targetId}/summary`);
else if (path.includes(':id')) targetUrl = path.replace(':id', targetId);
await router.push({ path: targetUrl });
} }
</script> </script>
@ -120,7 +105,7 @@ async function search() {
<VnInput <VnInput
id="searchbar" id="searchbar"
v-model="searchText" v-model="searchText"
:placeholder="props.label" :placeholder="t(props.label)"
dense dense
standout standout
autofocus autofocus
@ -139,7 +124,7 @@ async function search() {
name="info" name="info"
class="cursor-info" class="cursor-info"
> >
<QTooltip>{{ props.info }}</QTooltip> <QTooltip>{{ t(props.info) }}</QTooltip>
</QIcon> </QIcon>
</template> </template>
</VnInput> </VnInput>

View File

@ -90,7 +90,7 @@ const onNodeCreated = async () => {
await fetchNodeLeaves(creationNodeSelectedId.value); await fetchNodeLeaves(creationNodeSelectedId.value);
}; };
onMounted(async (n) => { onMounted(async () => {
const tree = [...state.get('Tree'), 1]; const tree = [...state.get('Tree'), 1];
const lastStateTree = state.get('TreeState'); const lastStateTree = state.get('TreeState');
if (tree) { if (tree) {

View File

@ -47,7 +47,10 @@ export function useArrayData(key, userOptions) {
if (isEmpty || !allowedOptions.includes(option)) continue; if (isEmpty || !allowedOptions.includes(option)) continue;
if (Object.prototype.hasOwnProperty.call(store, option)) { if (Object.prototype.hasOwnProperty.call(store, option)) {
store[option] = userOptions[option]; const defaultOpts = userOptions[option];
store[option] = userOptions.keepOpts?.includes(option)
? Object.assign(defaultOpts, store[option])
: defaultOpts;
} }
} }
} }
@ -127,7 +130,8 @@ export function useArrayData(key, userOptions) {
store.filter = {}; store.filter = {};
if (params) store.userParams = Object.assign({}, params); if (params) store.userParams = Object.assign({}, params);
await fetch({ append: false }); const response = await fetch({ append: false });
return response;
} }
async function addFilter({ filter, params }) { async function addFilter({ filter, params }) {

View File

@ -0,0 +1,25 @@
import { useRouter } from 'vue-router';
export default function useRedirect() {
const router = useRouter();
const navigate = (data, { customRouteRedirectName, searchText }) => {
if (customRouteRedirectName)
return router.push({
name: customRouteRedirectName,
params: { id: searchText },
});
const { matched: matches } = router.currentRoute.value;
const { path } = matches.at(-1);
const to =
data.length === 1
? path.replace(/\/(list|:id)|-list/, `/${data[0].id}`)
: path.replace(/:id.*/, '');
router.push({ path: to });
};
return { navigate };
}

View File

@ -20,28 +20,12 @@ const headerMounted = ref(false);
export function useState() { export function useState() {
function getUser() { function getUser() {
return computed(() => { return computed(() => {
return { return user.value;
id: user.value.id,
name: user.value.name,
nickname: user.value.nickname,
lang: user.value.lang,
darkMode: user.value.darkMode,
companyFk: user.value.companyFk,
warehouseFk: user.value.warehouseFk,
};
}); });
} }
function setUser(data) { function setUser(data) {
user.value = { user.value = data;
id: data.id,
name: data.name,
nickname: data.nickname,
lang: data.lang,
darkMode: data.darkMode,
companyFk: data.companyFk,
warehouseFk: data.warehouseFk,
};
} }
function getRoles() { function getRoles() {

View File

@ -32,6 +32,7 @@ globals:
confirm: Confirm confirm: Confirm
assign: Assign assign: Assign
back: Back back: Back
downloadPdf: Download PDF
yes: 'Yes' yes: 'Yes'
no: 'No' no: 'No'
noChanges: No changes to save noChanges: No changes to save
@ -95,6 +96,10 @@ globals:
agency: Agency agency: Agency
workCenters: Work centers workCenters: Work centers
modes: Modes modes: Modes
zones: Zones
zonesList: Zones
deliveryList: Delivery days
upcomingList: Upcoming deliveries
created: Created created: Created
worker: Worker worker: Worker
now: Now now: Now
@ -267,6 +272,7 @@ customer:
tableVisibleColumns: tableVisibleColumns:
id: Identifier id: Identifier
name: Name name: Name
socialName: Social name
fi: Tax number fi: Tax number
salesPersonFk: Salesperson salesPersonFk: Salesperson
credit: Credit credit: Credit
@ -426,6 +432,7 @@ ticket:
boxing: Boxing boxing: Boxing
sms: Sms sms: Sms
notes: Notes notes: Notes
sale: Sale
list: list:
nickname: Nickname nickname: Nickname
state: State state: State
@ -822,6 +829,8 @@ worker:
log: Log log: Log
calendar: Calendar calendar: Calendar
timeControl: Time control timeControl: Time control
locker: Locker
list: list:
name: Name name: Name
email: Email email: Email
@ -853,6 +862,15 @@ worker:
role: Role role: Role
sipExtension: Extension sipExtension: Extension
locker: Locker locker: Locker
fiDueDate: Fecha de caducidad del DNI
sex: Sexo
seniority: Antigüedad
fi: DNI/NIE/NIF
birth: Cumpleaños
isFreelance: Autónomo
isSsDiscounted: Bonificación SS
hasMachineryAuthorized: Autorizado para llevar maquinaria
isDisable: Trabajador desactivado
notificationsManager: notificationsManager:
activeNotifications: Active notifications activeNotifications: Active notifications
availableNotifications: Available notifications availableNotifications: Available notifications
@ -1137,6 +1155,7 @@ item:
tax: Tax tax: Tax
log: Log log: Log
botanical: Botanical botanical: Botanical
shelving: Shelving
itemTypeCreate: New item type itemTypeCreate: New item type
family: Item Type family: Item Type
lastEntries: Last entries lastEntries: Last entries
@ -1229,12 +1248,10 @@ item/itemType:
itemType: Item type itemType: Item type
basicData: Basic data basicData: Basic data
summary: Summary summary: Summary
zone: monitor:
pageTitles: pageTitles:
zones: Zone monitors: Monitors
zonesList: Zones list: List
deliveryList: Delivery days
upcomingList: Upcoming deliveries
components: components:
topbar: {} topbar: {}
itemsFilterPanel: itemsFilterPanel:

View File

@ -59,6 +59,7 @@ globals:
amount: Importe amount: Importe
packages: Bultos packages: Bultos
download: Descargar download: Descargar
downloadPdf: Descargar PDF
selectRows: 'Seleccionar las { numberRows } filas(s)' selectRows: 'Seleccionar las { numberRows } filas(s)'
allRows: 'Todo { numberRows } filas(s)' allRows: 'Todo { numberRows } filas(s)'
markAll: Marcar todo markAll: Marcar todo
@ -95,6 +96,10 @@ globals:
agency: Agencia agency: Agencia
workCenters: Centros de trabajo workCenters: Centros de trabajo
modes: Modos modes: Modos
zones: Zonas
zonesList: Zonas
deliveryList: Días de entrega
upcomingList: Próximos repartos
created: Fecha creación created: Fecha creación
worker: Trabajador worker: Trabajador
now: Ahora now: Ahora
@ -265,6 +270,7 @@ customer:
tableVisibleColumns: tableVisibleColumns:
id: Identificador id: Identificador
name: Nombre name: Nombre
socialName: Razón social
fi: NIF / CIF fi: NIF / CIF
salesPersonFk: Comercial salesPersonFk: Comercial
credit: Crédito credit: Crédito
@ -424,6 +430,7 @@ ticket:
boxing: Encajado boxing: Encajado
sms: Sms sms: Sms
notes: Notas notes: Notas
sale: Lineas del pedido
list: list:
nickname: Alias nickname: Alias
state: Estado state: Estado
@ -820,6 +827,7 @@ worker:
log: Historial log: Historial
calendar: Calendario calendar: Calendario
timeControl: Control de horario timeControl: Control de horario
locker: Taquilla
list: list:
name: Nombre name: Nombre
email: Email email: Email
@ -1136,6 +1144,7 @@ item:
botanical: 'Botánico' botanical: 'Botánico'
barcode: 'Código de barras' barcode: 'Código de barras'
log: Historial log: Historial
shelving: Carros
itemTypeCreate: Nueva familia itemTypeCreate: Nueva familia
family: Familia family: Familia
lastEntries: Últimas entradas lastEntries: Últimas entradas
@ -1234,6 +1243,10 @@ zone:
zonesList: Zonas zonesList: Zonas
deliveryList: Días de entrega deliveryList: Días de entrega
upcomingList: Próximos repartos upcomingList: Próximos repartos
monitor:
pageTitles:
monitors: Monitores
list: Listado
components: components:
topbar: {} topbar: {}
itemsFilterPanel: itemsFilterPanel:

View File

@ -1,34 +1,14 @@
<script setup> <script setup>
import { onMounted, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useArrayData } from 'src/composables/useArrayData';
import AgencyDescriptor from 'pages/Agency/Card/AgencyDescriptor.vue'; import AgencyDescriptor from 'pages/Agency/Card/AgencyDescriptor.vue';
import VnCard from 'components/common/VnCard.vue'; import VnCard from 'components/common/VnCard.vue';
const route = useRoute();
const arrayData = useArrayData('Agency', {
url: `Agencies/${route.params.id}`,
});
const { store } = arrayData;
onMounted(async () => await arrayData.fetch({ append: false }));
watch(
() => route.params.id,
async (newId) => {
if (newId) {
store.url = `Agencies/${newId}`;
await arrayData.fetch({ append: false });
}
}
);
</script> </script>
<template> <template>
<VnCard <VnCard
data-key="Agency" data-key="Agency"
base-url="Agencies" base-url="Agencies"
:descriptor="AgencyDescriptor" :descriptor="AgencyDescriptor"
searchbar-data-key="AgencyList" search-data-key="AgencyList"
searchbar-url="Agencies" search-url="Agencies"
searchbar-label="agency.searchBar.label" searchbar-label="agency.searchBar.label"
searchbar-info="agency.searchBar.info" searchbar-info="agency.searchBar.info"
/> />

View File

@ -15,8 +15,8 @@ const props = defineProps({
}); });
const { t } = useI18n(); const { t } = useI18n();
const { params } = useRoute(); const route = useRoute();
const entityId = computed(() => props.id || params.id); const entityId = computed(() => props.id || route.params.id);
const { store } = useArrayData('Parking'); const { store } = useArrayData('Parking');
const card = computed(() => store.data); const card = computed(() => store.data);
</script> </script>

View File

@ -3,26 +3,13 @@ import { computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FetchData from 'src/components/FetchData.vue';
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 VnTitle from 'src/components/common/VnTitle.vue'; import VnTitle from 'src/components/common/VnTitle.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
const $props = defineProps({ const $props = defineProps({ id: { type: Number, default: 0 } });
id: {
type: Number,
default: 0,
},
});
const { params } = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const entityId = computed(() => $props.id || params.id); const entityId = computed(() => $props.id || useRoute().params.id);
const filter = {
fields: ['id', 'sectorFk', 'code', 'pickingOrder', 'row', 'column'],
include: [{ relation: 'sector', scope: { fields: ['id', 'description'] } }],
};
</script> </script>
<template> <template>

View File

@ -201,30 +201,7 @@ async function post(query, params) {
auto-load auto-load
@on-fetch="(data) => (destinationTypes = data)" @on-fetch="(data) => (destinationTypes = data)"
/> />
<template v-if="stateStore.isHeaderMounted()"> <Teleport to="#right-panel" v-if="stateStore.isHeaderMounted() && claim">
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
</template>
<QDrawer
v-model="stateStore.rightDrawer"
side="right"
:width="300"
show-if-above
v-if="claim"
>
<QCard class="totalClaim q-my-md q-pa-sm no-box-shadow"> <QCard class="totalClaim q-my-md q-pa-sm no-box-shadow">
{{ `${t('Total claimed')}: ${toCurrency(totalClaimed)}` }} {{ `${t('Total claimed')}: ${toCurrency(totalClaimed)}` }}
</QCard> </QCard>
@ -274,7 +251,7 @@ async function post(query, params) {
v-model="multiplicatorValue" v-model="multiplicatorValue"
/> />
</QCard> </QCard>
</QDrawer> </Teleport>
<Teleport to="#st-data" v-if="stateStore.isSubToolbarShown()"> </Teleport> <Teleport to="#st-data" v-if="stateStore.isSubToolbarShown()"> </Teleport>
<CrudModel <CrudModel
v-if="claim" v-if="claim"

View File

@ -95,83 +95,71 @@ const statesFilter = {
> >
<template #form="{ data, validate, filter }"> <template #form="{ data, validate, filter }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInput
<VnInput v-model="data.client.name"
v-model="data.client.name" :label="t('claim.basicData.customer')"
:label="t('claim.basicData.customer')" disable
disable />
/> <VnInputDate
</div> v-model="data.created"
<div class="col"> :label="t('claim.basicData.created')"
<VnInputDate />
v-model="data.created"
:label="t('claim.basicData.created')"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelect :label="t('claim.basicData.assignedTo')"
:label="t('claim.basicData.assignedTo')" v-model="data.workerFk"
v-model="data.workerFk" :options="workersOptions"
:options="workersOptions" option-value="id"
option-value="id" option-label="name"
option-label="name" emit-value
emit-value auto-load
auto-load :rules="validate('claim.claimStateFk')"
:rules="validate('claim.claimStateFk')" >
> <template #before>
<template #before> <QAvatar color="orange">
<QAvatar color="orange"> <QImg
<QImg v-if="data.workerFk"
v-if="data.workerFk" :src="`/api/Images/user/160x160/${data.workerFk}/download?access_token=${token}`"
:src="`/api/Images/user/160x160/${data.workerFk}/download?access_token=${token}`" spinner-color="white"
spinner-color="white" />
/> </QAvatar>
</QAvatar> </template>
</template> </VnSelect>
</VnSelect> <QSelect
</div> v-model="data.claimStateFk"
<div class="col"> :options="claimStates"
<QSelect option-value="id"
v-model="data.claimStateFk" option-label="description"
:options="claimStates" emit-value
option-value="id" :label="t('claim.basicData.state')"
option-label="description" map-options
emit-value use-input
:label="t('claim.basicData.state')" @filter="(value, update) => filter(value, update, statesFilter)"
map-options :rules="validate('claim.claimStateFk')"
use-input :input-debounce="0"
@filter="(value, update) => filter(value, update, statesFilter)" >
:rules="validate('claim.claimStateFk')" </QSelect>
:input-debounce="0"
>
</QSelect>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <QInput
<QInput v-model.number="data.packages"
v-model.number="data.packages" :label="t('globals.packages')"
:label="t('globals.packages')" :rules="validate('claim.packages')"
:rules="validate('claim.packages')" type="number"
type="number" />
/> <QSelect
</div> v-model="data.pickup"
<div class="col"> :options="optionsList"
<QSelect option-value="id"
v-model="data.pickup" option-label="description"
:options="optionsList" emit-value
option-value="id" :label="t('claim.basicData.pickup')"
option-label="description" map-options
emit-value use-input
:label="t('claim.basicData.pickup')" :input-debounce="0"
map-options >
use-input </QSelect>
:input-debounce="0"
>
</QSelect>
</div>
</VnRow> </VnRow>
</template> </template>
</FormModel> </FormModel>

View File

@ -1,14 +1,16 @@
<script setup> <script setup>
import VnCard from 'components/common/VnCard.vue'; import VnCard from 'components/common/VnCard.vue';
import ClaimDescriptor from './ClaimDescriptor.vue'; import ClaimDescriptor from './ClaimDescriptor.vue';
import ClaimFilter from '../ClaimFilter.vue';
</script> </script>
<template> <template>
<VnCard <VnCard
data-key="Claim" data-key="Claim"
base-url="Claims" base-url="Claims"
:descriptor="ClaimDescriptor" :descriptor="ClaimDescriptor"
searchbar-data-key="ClaimList" :filter-panel="ClaimFilter"
searchbar-url="Claims/filter" search-data-key="ClaimList"
search-url="Claims/filter"
searchbar-label="Search claim" searchbar-label="Search claim"
searchbar-info="You can search by claim id or customer name" searchbar-info="You can search by claim id or customer name"
/> />

View File

@ -12,7 +12,6 @@ import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorP
import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue';
import ClaimSummary from './Card/ClaimSummary.vue'; import ClaimSummary from './Card/ClaimSummary.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { getUrl } from 'src/composables/getUrl';
const stateStore = useStateStore(); const stateStore = useStateStore();
const router = useRouter(); const router = useRouter();

View File

@ -202,9 +202,9 @@ const toCustomerAddressEdit = (addressId) => {
<div v-if="item.observations.length"> <div v-if="item.observations.length">
<div <div
:key="index" :key="obIndex"
class="flex q-mb-sm" class="flex q-mb-sm"
v-for="(observation, index) in item.observations" v-for="(observation, obIndex) in item.observations"
> >
<div class="text-weight-bold q-mr-sm"> <div class="text-weight-bold q-mr-sm">
{{ observation.observationType.description }}: {{ observation.observationType.description }}:

View File

@ -11,6 +11,7 @@ import { useState } from 'src/composables/useState';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useValidator } from 'src/composables/useValidator'; import { useValidator } from 'src/composables/useValidator';
import { usePrintService } from 'src/composables/usePrintService'; import { usePrintService } from 'src/composables/usePrintService';
import { useSession } from 'src/composables/useSession';
import VnPaginate from 'src/components/ui/VnPaginate.vue'; import VnPaginate from 'src/components/ui/VnPaginate.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
@ -19,6 +20,9 @@ import VnSelect from 'src/components/common/VnSelect.vue';
import CustomerNewPayment from 'src/pages/Customer/components/CustomerNewPayment.vue'; import CustomerNewPayment from 'src/pages/Customer/components/CustomerNewPayment.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue'; import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue';
const session = useSession();
const tokenMultimedia = session.getTokenMultimedia();
const { sendEmail } = usePrintService(); const { sendEmail } = usePrintService();
const { t } = useI18n(); const { t } = useI18n();
@ -188,6 +192,11 @@ const saveFieldValue = async (row) => {
const sendEmailAction = () => { const sendEmailAction = () => {
sendEmail(`Suppliers/${route.params.id}/campaign-metrics-email`); sendEmail(`Suppliers/${route.params.id}/campaign-metrics-email`);
}; };
const showBalancePdf = (balance) => {
const url = `api/InvoiceOuts/${balance.id}/download?access_token=${tokenMultimedia}`;
window.open(url, '_blank');
};
</script> </script>
<template> <template>
@ -257,17 +266,28 @@ const sendEmailAction = () => {
<QTd align="center"> <QTd align="center">
<QIcon <QIcon
@click.stop="showDialog = true" @click.stop="showDialog = true"
class="q-ml-md" class="q-ml-md fill-icon"
color="primary" color="primary"
name="outgoing_mail" name="outgoing_mail"
size="sm" size="sm"
style="font-variation-settings: 'FILL' 1"
v-if="row.isCompensation" v-if="row.isCompensation"
> >
<QTooltip> <QTooltip>
{{ t('Send compensation') }} {{ t('Send compensation') }}
</QTooltip> </QTooltip>
</QIcon> </QIcon>
<QIcon
@click="showBalancePdf(row)"
class="q-ml-md fill-icon"
color="primary"
name="cloud_download"
size="sm"
v-if="row.hasPdf"
>
<QTooltip>
{{ t('globals.downloadPdf') }}
</QTooltip>
</QIcon>
<QDialog v-model="showDialog"> <QDialog v-model="showDialog">
<QCard class="q-pa-sm"> <QCard class="q-pa-sm">

View File

@ -70,138 +70,121 @@ const filterOptions = {
<FormModel :url="`Clients/${route.params.id}`" auto-load model="customer"> <FormModel :url="`Clients/${route.params.id}`" auto-load model="customer">
<template #form="{ data, validate, filter }"> <template #form="{ data, validate, filter }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInput
<VnInput :label="t('Comercial name')"
:label="t('Comercial name')" :rules="validate('client.socialName')"
:rules="validate('client.socialName')" autofocus
autofocus clearable
clearable v-model="data.name"
v-model="data.name" />
/>
</div> <QSelect
<div class="col"> :input-debounce="0"
<QSelect :label="t('customer.basicData.businessType')"
:input-debounce="0" :options="businessTypes"
:label="t('customer.basicData.businessType')" :rules="validate('client.businessTypeFk')"
:options="businessTypes" emit-value
:rules="validate('client.businessTypeFk')" map-options
emit-value option-label="description"
map-options option-value="code"
option-label="description" v-model="data.businessTypeFk"
option-value="code" />
v-model="data.businessTypeFk"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInput
<VnInput :label="t('customer.basicData.contact')"
:label="t('customer.basicData.contact')" :rules="validate('client.contact')"
:rules="validate('client.contact')" clearable
clearable v-model="data.contact"
v-model="data.contact" />
/> <VnInput
</div> :label="t('customer.basicData.email')"
<div class="col"> :rules="validate('client.email')"
<VnInput clearable
:label="t('customer.basicData.email')" type="email"
:rules="validate('client.email')" v-model="data.email"
clearable >
type="email" <template #append>
v-model="data.email" <QIcon name="info" class="cursor-info">
> <QTooltip>{{
<template #append> t('customer.basicData.youCanSaveMultipleEmails')
<QIcon name="info" class="cursor-info"> }}</QTooltip>
<QTooltip>{{ </QIcon>
t('customer.basicData.youCanSaveMultipleEmails') </template>
}}</QTooltip> </VnInput>
</QIcon>
</template>
</VnInput>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInput
<VnInput :label="t('customer.basicData.phone')"
:label="t('customer.basicData.phone')" :rules="validate('client.phone')"
:rules="validate('client.phone')" clearable
clearable v-model="data.phone"
v-model="data.phone" />
/> <VnInput
</div> :label="t('customer.basicData.mobile')"
<div class="col"> :rules="validate('client.mobile')"
<VnInput clearable
:label="t('customer.basicData.mobile')" v-model="data.mobile"
:rules="validate('client.mobile')" />
clearable
v-model="data.mobile"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <QSelect
<QSelect :input-debounce="0"
:input-debounce="0" :label="t('customer.basicData.salesPerson')"
:label="t('customer.basicData.salesPerson')" :options="workers"
:options="workers" :rules="validate('client.salesPersonFk')"
:rules="validate('client.salesPersonFk')" @filter="(value, update) => filter(value, update, filterOptions)"
@filter="(value, update) => filter(value, update, filterOptions)" emit-value
emit-value map-options
map-options option-label="name"
option-label="name" option-value="id"
option-value="id" use-input
use-input v-model="data.salesPersonFk"
v-model="data.salesPersonFk" >
> <template #prepend>
<template #prepend> <QAvatar color="orange">
<QAvatar color="orange"> <QImg
<QImg :src="`/api/Images/user/160x160/${data.salesPersonFk}/download?access_token=${token}`"
:src="`/api/Images/user/160x160/${data.salesPersonFk}/download?access_token=${token}`" spinner-color="white"
spinner-color="white" v-if="data.salesPersonFk"
v-if="data.salesPersonFk" />
/> </QAvatar>
</QAvatar> </template>
</template> </QSelect>
</QSelect> <QSelect
</div> v-model="data.contactChannelFk"
<div class="col"> :options="contactChannels"
<QSelect option-value="id"
:input-debounce="0" option-label="name"
:label="t('customer.basicData.contactChannel')" emit-value
:options="contactChannels" :label="t('customer.basicData.contactChannel')"
:rules="validate('client.contactChannelFk')" map-options
emit-value :rules="validate('client.contactChannelFk')"
map-options :input-debounce="0"
option-label="name" />
option-value="id"
v-model="data.contactChannelFk"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <QSelect
<QSelect :input-debounce="0"
:input-debounce="0" :label="t('customer.basicData.previousClient')"
:label="t('customer.basicData.previousClient')" :options="clients"
:options="clients" :rules="validate('client.transferorFk')"
:rules="validate('client.transferorFk')" emit-value
emit-value map-options
map-options option-label="name"
option-label="name" option-value="id"
option-value="id" v-model="data.transferorFk"
v-model="data.transferorFk" >
> <template #append>
<template #append> <QIcon name="info" class="cursor-pointer">
<QIcon name="info" class="cursor-pointer"> <QTooltip>{{
<QTooltip>{{ t(
t( 'In case of a company succession, specify the grantor company'
'In case of a company succession, specify the grantor company' )
) }}</QTooltip>
}}</QTooltip> </QIcon>
</QIcon> </template>
</template> </QSelect>
</QSelect>
</div>
</VnRow> </VnRow>
</template> </template>
</FormModel> </FormModel>

View File

@ -48,74 +48,56 @@ const getBankEntities = (data, formData) => {
> >
<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">
<div class="col"> <VnSelect
<VnSelect :label="t('Billing data')"
:label="t('Billing data')" :options="payMethods"
:options="payMethods" hide-selected
hide-selected option-label="name"
option-label="name" option-value="id"
option-value="id" v-model="data.payMethod"
v-model="data.payMethod" />
/> <VnInput :label="t('Due day')" clearable v-model="data.dueDay" />
</div>
<div class="col">
<VnInput :label="t('Due day')" clearable v-model="data.dueDay" />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInput :label="t('IBAN')" clearable v-model="data.iban">
<VnInput :label="t('IBAN')" clearable v-model="data.iban"> <template #append>
<template #append> <QIcon name="info" class="cursor-info">
<QIcon name="info" class="cursor-info"> <QTooltip>{{ t('components.iban_tooltip') }}</QTooltip>
<QTooltip>{{ t('components.iban_tooltip') }}</QTooltip> </QIcon>
</QIcon> </template>
</template> </VnInput>
</VnInput> <VnSelectDialog
</div> :label="t('Swift / BIC')"
<div class="col"> :options="bankEntitiesOptions"
<VnSelectDialog :roles-allowed-to-create="['salesAssistant', 'hr']"
:label="t('Swift / BIC')" :rules="validate('Worker.bankEntity')"
:options="bankEntitiesOptions" hide-selected
:roles-allowed-to-create="['salesAssistant', 'hr']" option-label="name"
:rules="validate('Worker.bankEntity')" option-value="id"
hide-selected v-model="data.bankEntityFk"
option-label="name" >
option-value="id" <template #form>
v-model="data.bankEntityFk" <CreateBankEntityForm
> @on-data-saved="getBankEntities($event, data)"
<template #form> />
<CreateBankEntityForm </template>
@on-data-saved="getBankEntities($event, data)" <template #option="scope">
/> <QItem v-bind="scope.itemProps">
</template> <QItemSection v-if="scope.opt">
<template #option="scope"> <QItemLabel
<QItem v-bind="scope.itemProps"> >{{ scope.opt.bic }} {{ scope.opt.name }}</QItemLabel
<QItemSection v-if="scope.opt"> >
<QItemLabel </QItemSection>
>{{ scope.opt.bic }} </QItem>
{{ scope.opt.name }}</QItemLabel </template>
> </VnSelectDialog>
</QItemSection>
</QItem>
</template>
</VnSelectDialog>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <QCheckbox :label="t('Received LCR')" v-model="data.hasLcr" />
<QCheckbox :label="t('Received LCR')" v-model="data.hasLcr" /> <QCheckbox :label="t('VNL core received')" v-model="data.hasCoreVnl" />
</div> <QCheckbox :label="t('VNL B2B received')" v-model="data.hasSepaVnl" />
<div class="col">
<QCheckbox
:label="t('VNL core received')"
v-model="data.hasCoreVnl"
/>
</div>
<div class="col">
<QCheckbox :label="t('VNL B2B received')" v-model="data.hasSepaVnl" />
</div>
</VnRow> </VnRow>
</template> </template>
</FormModel> </FormModel>

View File

@ -1,14 +1,16 @@
<script setup> <script setup>
import VnCard from 'components/common/VnCard.vue'; import VnCard from 'components/common/VnCard.vue';
import CustomerDescriptor from './CustomerDescriptor.vue'; import CustomerDescriptor from './CustomerDescriptor.vue';
import CustomerFilter from '../CustomerFilter.vue';
</script> </script>
<template> <template>
<VnCard <VnCard
data-key="Client" data-key="Client"
base-url="Clients" base-url="Clients"
:descriptor="CustomerDescriptor" :descriptor="CustomerDescriptor"
searchbar-data-key="CustomerList" :filter-panel="CustomerFilter"
searchbar-url="Clients/filter" search-data-key="CustomerList"
search-url="Clients/filter"
searchbar-label="Search customer" searchbar-label="Search customer"
searchbar-info="You can search by customer id or name" searchbar-info="You can search by customer id or name"
/> />

View File

@ -105,7 +105,7 @@ const updateData = () => {
color="primary" color="primary"
name="lock" name="lock"
size="md" size="md"
style="font-variation-settings: 'FILL' 1" class="fill-icon"
> >
<QTooltip>{{ t('Close contract') }}</QTooltip> <QTooltip>{{ t('Close contract') }}</QTooltip>
</QIcon> </QIcon>

View File

@ -41,93 +41,75 @@ function handleLocation(data, location) {
> >
<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">
<div class="col"> <VnInput
<VnInput :label="t('Social name')"
:label="t('Social name')" :required="true"
:required="true" :rules="validate('client.socialName')"
:rules="validate('client.socialName')" clearable
clearable v-model="data.socialName"
v-model="data.socialName" >
> <template #append>
<template #append> <QIcon name="info" class="cursor-info">
<QIcon name="info" class="cursor-info"> <QTooltip>{{ t('onlyLetters') }}</QTooltip>
<QTooltip>{{ t('onlyLetters') }}</QTooltip> </QIcon>
</QIcon> </template>
</template> </VnInput>
</VnInput> <VnInput :label="t('Tax number')" clearable v-model="data.fi" />
</div>
<div class="col">
<VnInput :label="t('Tax number')" clearable v-model="data.fi" />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInput :label="t('Street')" clearable v-model="data.street" />
<VnInput :label="t('Street')" clearable v-model="data.street" />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelect :label="t('Sage tax type')"
:label="t('Sage tax type')" :options="typesTaxes"
:options="typesTaxes" hide-selected
hide-selected option-label="vat"
option-label="vat" option-value="id"
option-value="id" v-model="data.sageTaxTypeFk"
v-model="data.sageTaxTypeFk" />
/> <VnSelect
</div> :label="t('Sage transaction type')"
<div class="col"> :options="typesTransactions"
<VnSelect hide-selected
:label="t('Sage transaction type')" option-label="transaction"
:options="typesTransactions" option-value="id"
hide-selected v-model="data.sageTransactionTypeFk"
option-label="transaction" >
option-value="id" <template #option="scope">
v-model="data.sageTransactionTypeFk" <QItem v-bind="scope.itemProps">
> <QItemSection>
<template #option="scope"> <QItemLabel>{{ scope.opt.name }}</QItemLabel>
<QItem v-bind="scope.itemProps"> <QItemLabel caption>
<QItemSection> {{ `${scope.opt.id}: ${scope.opt.transaction}` }}
<QItemLabel>{{ scope.opt.name }}</QItemLabel> </QItemLabel>
<QItemLabel caption> </QItemSection>
{{ `${scope.opt.id}: ${scope.opt.transaction}` }} </QItem>
</QItemLabel> </template>
</QItemSection> </VnSelect>
</QItem>
</template>
</VnSelect>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnLocation
<VnLocation :rules="validate('Worker.postcode')"
:rules="validate('Worker.postcode')" :roles-allowed-to-create="['deliveryAssistant']"
:roles-allowed-to-create="['deliveryAssistant']" :options="postcodesOptions"
:options="postcodesOptions" v-model="data.postcode"
v-model="data.postcode" @update:model-value="(location) => handleLocation(data, location)"
@update:model-value="(location) => handleLocation(data, location)" >
> </VnLocation>
</VnLocation>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow>
<div class="col"> <QCheckbox :label="t('Active')" v-model="data.isActive" />
<QCheckbox :label="t('Active')" v-model="data.isActive" /> <QCheckbox :label="t('Frozen')" v-model="data.isFreezed" />
</div>
<div class="col">
<QCheckbox :label="t('Frozen')" v-model="data.isFreezed" />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow>
<div class="col"> <QCheckbox :label="t('Has to invoice')" v-model="data.hasToInvoice" />
<QCheckbox :label="t('Has to invoice')" v-model="data.hasToInvoice" /> <div>
</div>
<div class="col">
<QCheckbox :label="t('Vies')" v-model="data.isVies" /> <QCheckbox :label="t('Vies')" v-model="data.isVies" />
<QIcon name="info" class="cursor-info q-ml-sm" size="sm"> <QIcon name="info" class="cursor-info q-ml-sm" size="sm">
<QTooltip> <QTooltip>
@ -137,23 +119,16 @@ function handleLocation(data, location) {
</div> </div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow>
<div class="col"> <QCheckbox :label="t('Notify by email')" v-model="data.isToBeMailed" />
<QCheckbox <QCheckbox
:label="t('Notify by email')" :label="t('Invoice by address')"
v-model="data.isToBeMailed" v-model="data.hasToInvoiceByAddress"
/> />
</div>
<div class="col">
<QCheckbox
:label="t('Invoice by address')"
v-model="data.hasToInvoiceByAddress"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow>
<div class="col"> <div>
<QCheckbox <QCheckbox
:label="t('Is equalizated')" :label="t('Is equalizated')"
v-model="data.isEqualizated" v-model="data.isEqualizated"
@ -164,27 +139,18 @@ function handleLocation(data, location) {
</QTooltip> </QTooltip>
</QIcon> </QIcon>
</div> </div>
<div class="col"> <QCheckbox :label="t('Verified data')" v-model="data.isTaxDataChecked" />
<QCheckbox
:label="t('Verified data')"
v-model="data.isTaxDataChecked"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow>
<div class="col"> <QCheckbox
<QCheckbox :label="t('Incoterms authorization')"
:label="t('Incoterms authorization')" v-model="data.hasIncoterms"
v-model="data.hasIncoterms" />
/> <QCheckbox
</div> :label="t('Electronic invoice')"
<div class="col"> v-model="data.hasElectronicInvoice"
<QCheckbox />
:label="t('Electronic invoice')"
v-model="data.hasElectronicInvoice"
/>
</div>
</VnRow> </VnRow>
</template> </template>
</FormModel> </FormModel>

View File

@ -1,20 +1,17 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router'; import { useRoute } from 'vue-router';
import { QBtn } from 'quasar'; import { QBtn } from 'quasar';
import { useStateStore } from 'src/stores/useStateStore';
import { toCurrency } from 'src/filters'; import { toCurrency } from 'src/filters';
import { toDateTimeFormat } from 'src/filters/date'; import { toDateTimeFormat } from 'src/filters/date';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const stateStore = computed(() => useStateStore());
const rows = ref([]); const rows = ref([]);
const totalAmount = ref(0); const totalAmount = ref(0);
@ -105,28 +102,40 @@ const columns = computed(() => [
const setRows = (data) => { const setRows = (data) => {
rows.value = data; rows.value = data;
totalAmount.value = data.reduce((accumulator, currentValue) => { totalAmount.value = data.reduce((acc, { amount = 0 }) => acc + amount, 0);
return accumulator + currentValue.amount;
}, 0);
};
const toCustomerGreugeCreate = () => {
router.push({ name: 'CustomerGreugeCreate' });
}; };
</script> </script>
<template> <template>
<FetchData :filter="filter" @on-fetch="setRows" auto-load url="greuges" /> <FetchData :filter="filter" @on-fetch="setRows" auto-load url="greuges" />
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
</template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="300" show-if-above>
<QCard class="full-width q-pa-sm">
<h6 class="flex justify-end q-my-lg q-pr-lg" v-if="totalAmount">
<span class="color-vn-label q-mr-md">{{ t('Total') }}:</span>
{{ toCurrency(totalAmount) }}
</h6>
<QSkeleton v-else type="QInput" square />
</QCard>
</QDrawer>
<div class="full-width flex justify-center"> <div class="full-width flex justify-center">
<QPage class="card-width q-pa-lg"> <QPage class="card-width q-pa-lg">
<QCard class="full-width q-pa-sm" v-if="totalAmount">
<h6 class="flex justify-end q-my-lg q-pr-lg">
<span class="color-vn-label q-mr-md">{{ t('Total') }}:</span>
{{ toCurrency(totalAmount) }}
</h6>
</QCard>
<QCard class="q-pa-sm q-mt-md"> <QCard class="q-pa-sm q-mt-md">
<QTable <QTable
:columns="columns" :columns="columns"
@ -164,7 +173,7 @@ const toCustomerGreugeCreate = () => {
</div> </div>
<QPageSticky :offset="[18, 18]"> <QPageSticky :offset="[18, 18]">
<QBtn @click.stop="toCustomerGreugeCreate()" color="primary" fab icon="add" /> <QBtn color="primary" fab icon="add" :to="{ name: 'CustomerGreugeCreate' }" />
<QTooltip> <QTooltip>
{{ t('New greuge') }} {{ t('New greuge') }}
</QTooltip> </QTooltip>

View File

@ -11,7 +11,6 @@ import useNotify from 'src/composables/useNotify';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import CustomerChangePassword from 'src/pages/Customer/components/CustomerChangePassword.vue'; import CustomerChangePassword from 'src/pages/Customer/components/CustomerChangePassword.vue';

View File

@ -7,7 +7,6 @@ import axios from 'axios';
import { toCurrency, toDateHourMinSec } from 'src/filters'; import { toCurrency, toDateHourMinSec } from 'src/filters';
import FetchData from 'components/FetchData.vue';
import CustomerCloseIconTooltip from '../components/CustomerCloseIconTooltip.vue'; import CustomerCloseIconTooltip from '../components/CustomerCloseIconTooltip.vue';
import CustomerCheckIconTooltip from '../components/CustomerCheckIconTooltip.vue'; import CustomerCheckIconTooltip from '../components/CustomerCheckIconTooltip.vue';

View File

@ -49,89 +49,69 @@ function handleLocation(data, location) {
> >
<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">
<div class="col"> <QInput :label="t('Comercial name')" v-model="data.name" />
<QInput :label="t('Comercial name')" v-model="data.name" /> <VnSelect
</div> :label="t('Salesperson')"
<div class="col"> :options="workersOptions"
<VnSelect hide-selected
:label="t('Salesperson')" option-label="name"
:options="workersOptions" option-value="id"
hide-selected v-model="data.salesPersonFk"
option-label="name" />
option-value="id"
v-model="data.salesPersonFk"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelect :label="t('Business type')"
:label="t('Business type')" :options="businessTypesOptions"
:options="businessTypesOptions" hide-selected
hide-selected option-label="description"
option-label="description" option-value="code"
option-value="code" v-model="data.businessTypeFk"
v-model="data.businessTypeFk" />
/> <QInput v-model="data.fi" :label="t('Tax number')" />
</div>
<div class="col">
<QInput v-model="data.fi" :label="t('Tax number')" />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <QInput
<QInput :label="t('Business name')"
:label="t('Business name')" :rules="validate('client.socialName')"
:rules="validate('client.socialName')" v-model="data.socialName"
v-model="data.socialName" />
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <QInput
<QInput :label="t('Street')"
:label="t('Street')" :rules="validate('client.street')"
:rules="validate('client.street')" v-model="data.street"
v-model="data.street" />
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnLocation
<VnLocation :rules="validate('Worker.postcode')"
:rules="validate('Worker.postcode')" :roles-allowed-to-create="['deliveryAssistant']"
:roles-allowed-to-create="['deliveryAssistant']" :options="postcodesOptions"
:options="postcodesOptions" v-model="data.location"
v-model="data.location" @update:model-value="(location) => handleLocation(data, location)"
@update:model-value=" >
(location) => handleLocation(data, location) </VnLocation>
"
>
</VnLocation>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <QInput v-model="data.userName" :label="t('Web user')" />
<QInput v-model="data.userName" :label="t('Web user')" /> <QInput
</div> :label="t('Email')"
<div class="col"> :rules="validate('client.email')"
<QInput clearable
:label="t('Email')" type="email"
:rules="validate('client.email')" v-model="data.email"
clearable >
type="email" <template #append>
v-model="data.email" <QIcon name="info" class="cursor-info">
> <QTooltip max-width="400px">{{
<template #append> t('customer.basicData.youCanSaveMultipleEmails')
<QIcon name="info" class="cursor-info"> }}</QTooltip>
<QTooltip max-width="400px">{{ </QIcon>
t('customer.basicData.youCanSaveMultipleEmails') </template>
}}</QTooltip> </QInput>
</QIcon>
</template>
</QInput>
</div>
</VnRow> </VnRow>
<QCheckbox <QCheckbox
:label="t('Is equalizated')" :label="t('Is equalizated')"

View File

@ -15,10 +15,3 @@ const stateStore = useStateStore();
<RouterView></RouterView> <RouterView></RouterView>
</QPageContainer> </QPageContainer>
</template> </template>
<style lang="scss">
#searchbar,
.search-panel {
width: 400px;
}
</style>

View File

@ -3,10 +3,10 @@ import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { QBtn, QCheckbox, useQuasar } from 'quasar'; import { QBtn, QCheckbox, useQuasar } from 'quasar';
import { useStateStore } from 'stores/useStateStore';
import { toCurrency, toDate } from 'filters/index'; import { toCurrency, toDate, dateRange } from 'filters/index';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import FetchData from 'components/FetchData.vue';
import CustomerNotificationsFilter from './CustomerDefaulterFilter.vue'; import CustomerNotificationsFilter from './CustomerDefaulterFilter.vue';
import CustomerBalanceDueTotal from './CustomerBalanceDueTotal.vue'; import CustomerBalanceDueTotal from './CustomerBalanceDueTotal.vue';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
@ -14,13 +14,14 @@ import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.v
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import CustomerDefaulterAddObservation from './CustomerDefaulterAddObservation.vue'; import CustomerDefaulterAddObservation from './CustomerDefaulterAddObservation.vue';
const stateStore = useStateStore();
const { t } = useI18n(); const { t, locale } = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
const dataRef = ref(null);
const balanceDueTotal = ref(0); const balanceDueTotal = ref(0);
const selected = ref([]); const selected = ref([]);
const rows = ref([]);
const tableColumnComponents = { const tableColumnComponents = {
client: { client: {
@ -167,39 +168,61 @@ const columns = computed(() => [
}, },
]); ]);
const setRows = (data) => {
rows.value = data;
balanceDueTotal.value = data.reduce((accumulator, currentValue) => {
return accumulator + (currentValue['amount'] || 0);
}, 0);
};
const viewAddObservation = (rowsSelected) => { const viewAddObservation = (rowsSelected) => {
quasar.dialog({ quasar.dialog({
component: CustomerDefaulterAddObservation, component: CustomerDefaulterAddObservation,
componentProps: { componentProps: {
clients: rowsSelected, clients: rowsSelected,
promise: refreshData, promise: async () => await dataRef.value.fetch(),
}, },
}); });
}; };
const refreshData = () => { const onFetch = (data) => {
setRows(); for (const element of data) element.isWorker = element.businessTypeFk === 'worker';
balanceDueTotal.value = data.reduce((acc, { amount = 0 }) => acc + amount, 0);
}; };
const onFetch = (data) => { function exprBuilder(param, value) {
for (const element of data) { switch (param) {
element.isWorker = element.businessTypeFk === 'worker'; case 'clientFk':
return { [`d.${param}`]: value?.id };
case 'creditInsurance':
case 'amount':
case 'workerFk':
case 'countryFk':
case 'payMethod':
case 'salesPersonFk':
return { [`d.${param}`]: value };
case 'date':
return { 'd.created': { between: dateRange(value) } };
case 'defaulterSinced':
return { 'd.defaulterSinced': { between: dateRange(value) } };
} }
rows.value = data; }
};
</script> </script>
<template> <template>
<FetchData :filter="filter" @on-fetch="onFetch" auto-load url="Defaulters/filter" /> <template v-if="stateStore.isHeaderMounted()">
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
</template>
<QDrawer side="right" :width="256" show-if-above> <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8"> <QScrollArea class="fit text-grey-8">
<CustomerNotificationsFilter data-key="CustomerDefaulter" /> <CustomerNotificationsFilter data-key="CustomerDefaulter" />
</QScrollArea> </QScrollArea>
@ -214,69 +237,107 @@ const onFetch = (data) => {
icon="vn:notes" icon="vn:notes"
:disabled="!selected.length" :disabled="!selected.length"
@click.stop="viewAddObservation(selected)" @click.stop="viewAddObservation(selected)"
/> >
<QTooltip>{{ t('Add observation') }}</QTooltip>
</QBtn>
</div> </div>
</template> </template>
</VnSubToolbar> </VnSubToolbar>
<QPage class="column items-center q-pa-md"> <QPage class="column items-center q-pa-md">
<QTable <VnPaginate
:columns="columns" ref="dataRef"
:rows="rows" @on-fetch="onFetch"
class="full-width q-mt-md" data-key="CustomerDefaulter"
row-key="clientFk" :filter="filter"
selection="multiple" :expr-builder="exprBuilder"
v-model:selected="selected" auto-load
url="Defaulters/filter"
> >
<template #header="props"> <template #body="{ rows }">
<QTr :props="props" class="bg"> <div class="q-pa-md">
<QTh> <QTable
<QCheckbox v-model="props.selected" /> :columns="columns"
</QTh> :rows="rows"
<QTh v-for="col in props.cols" :key="col.name" :props="props"> class="full-width q-mt-md"
{{ t(col.label) }} row-key="clientFk"
<QTooltip v-if="col.tooltip">{{ col.tooltip }}</QTooltip> selection="multiple"
</QTh> v-model:selected="selected"
</QTr> >
</template> <template #header="props">
<QTr :props="props" class="bg">
<QTh>
<QCheckbox v-model="props.selected" />
</QTh>
<QTh
v-for="col in props.cols"
:key="col.name"
:props="props"
>
{{ t(col.label) }}
<QTooltip v-if="col.tooltip">{{
col.tooltip
}}</QTooltip>
</QTh>
</QTr>
</template>
<template #body-cell="props"> <template #body-cell="props">
<QTd :props="props"> <QTd :props="props">
<QTr :props="props" class="cursor-pointer"> <QTr :props="props" class="cursor-pointer">
<component <component
:is="tableColumnComponents[props.col.name].component" :is="
class="col-content" tableColumnComponents[props.col.name]
v-bind="tableColumnComponents[props.col.name].props(props)" .component
@click="tableColumnComponents[props.col.name].event(props)" "
> class="col-content"
<template v-if="props.col.name !== 'isWorker'"> v-bind="
<div v-if="props.col.name === 'lastObservation'"> tableColumnComponents[props.col.name].props(
<VnInput props
type="textarea" )
v-model="props.value" "
autogrow @click="
/> tableColumnComponents[props.col.name].event(
</div> props
<div v-else>{{ props.value }}</div> )
</template> "
>
<template v-if="props.col.name !== 'isWorker'">
<div
v-if="
props.col.name === 'lastObservation'
"
>
<VnInput
type="textarea"
v-model="props.value"
autogrow
:disable="true"
/>
</div>
<div v-else>{{ props.value }}</div>
</template>
<WorkerDescriptorProxy <WorkerDescriptorProxy
:id="props.row.salesPersonFk" :id="props.row.salesPersonFk"
v-if="props.col.name === 'salesPerson'" v-if="props.col.name === 'salesPerson'"
/> />
<WorkerDescriptorProxy <WorkerDescriptorProxy
:id="props.row.workerFk" :id="props.row.workerFk"
v-if="props.col.name === 'author'" v-if="props.col.name === 'author'"
/> />
<CustomerDescriptorProxy <CustomerDescriptorProxy
:id="props.row.clientFk" :id="props.row.clientFk"
v-if="props.col.name === 'client'" v-if="props.col.name === 'client'"
/> />
</component> </component>
</QTr> </QTr>
</QTd> </QTd>
</template>
</QTable>
</div>
</template> </template>
</QTable> </VnPaginate>
</QPage> </QPage>
</template> </template>
@ -289,6 +350,7 @@ const onFetch = (data) => {
<i18n> <i18n>
es: es:
Add observation: Añadir observación
Client: Cliente Client: Cliente
Is worker: Es trabajador Is worker: Es trabajador
Salesperson: Comercial Salesperson: Comercial

View File

@ -61,13 +61,11 @@ const onSubmit = async () => {
}} }}
</div> </div>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <QInput
<QInput :label="t('Message')"
:label="t('Message')" type="textarea"
type="textarea" v-model="newObservation"
v-model="newObservation" />
/>
</div>
</VnRow> </VnRow>
<div class="q-mt-lg row justify-end"> <div class="q-mt-lg row justify-end">
<QBtn <QBtn

View File

@ -57,7 +57,7 @@ const authors = ref();
hide-selected hide-selected
map-options map-options
option-label="name" option-label="name"
option-value="clientTypeFk" option-value="id"
outlined outlined
rounded rounded
use-input use-input
@ -162,7 +162,7 @@ const authors = ref();
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">
<QItemSection> <QItemSection>
<VnInput <VnInputDate
:label="t('L. O. Date')" :label="t('L. O. Date')"
clearable clearable
is-outlined is-outlined

View File

@ -10,7 +10,7 @@ import CustomerExtendedListActions from './CustomerExtendedListActions.vue';
import CustomerExtendedListFilter from './CustomerExtendedListFilter.vue'; import CustomerExtendedListFilter from './CustomerExtendedListFilter.vue';
import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue'; import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { toDate } from 'src/filters'; import { toDate } from 'src/filters';
@ -37,8 +37,6 @@ onMounted(() => {
allColumnNames.value = filteredColumns.map((col) => col.name); allColumnNames.value = filteredColumns.map((col) => col.name);
}); });
const rows = computed(() => arrayData.value.store.data);
const selectedCustomerId = ref(0); const selectedCustomerId = ref(0);
const selectedSalesPersonId = ref(0); const selectedSalesPersonId = ref(0);
const allColumnNames = ref([]); const allColumnNames = ref([]);
@ -70,6 +68,11 @@ const tableColumnComponents = {
props: () => {}, props: () => {},
event: () => {}, event: () => {},
}, },
socialName: {
component: 'span',
props: () => {},
event: () => {},
},
fi: { fi: {
component: 'span', component: 'span',
props: () => {}, props: () => {},
@ -283,6 +286,12 @@ const columns = computed(() => [
label: t('customer.extendedList.tableVisibleColumns.name'), label: t('customer.extendedList.tableVisibleColumns.name'),
name: 'name', name: 'name',
}, },
{
align: 'left',
field: 'socialName',
label: t('customer.extendedList.tableVisibleColumns.socialName'),
name: 'socialName',
},
{ {
align: 'left', align: 'left',
field: 'fi', field: 'fi',
@ -485,6 +494,23 @@ const selectSalesPersonId = (id) => (selectedSalesPersonId.value = id);
</script> </script>
<template> <template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport></template
>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8"> <QScrollArea class="fit text-grey-8">
<CustomerExtendedListFilter <CustomerExtendedListFilter
@ -495,7 +521,7 @@ const selectSalesPersonId = (id) => (selectedSalesPersonId.value = id);
</QScrollArea> </QScrollArea>
</QDrawer> </QDrawer>
<VnSubToolbar> <VnSubToolbar>
<template #st-actions> <template #st-data>
<TableVisibleColumns <TableVisibleColumns
:all-columns="allColumnNames" :all-columns="allColumnNames"
table-code="clientsDetail" table-code="clientsDetail"
@ -508,58 +534,97 @@ const selectSalesPersonId = (id) => (selectedSalesPersonId.value = id);
</VnSubToolbar> </VnSubToolbar>
<QPage class="column items-center q-pa-md"> <QPage class="column items-center q-pa-md">
<QTable <VnPaginate
:columns="columns" data-key="CustomerExtendedList"
:rows="rows" url="Clients/extendedListFilter"
class="full-width q-mt-md" auto-load
row-key="id"
:visible-columns="visibleColumns"
@row-click="(evt, row, id) => navigateToTravelId(row.id)"
> >
<template #body-cell="{ col, value }"> <template #body="{ rows }">
<QTd @click="stopEventPropagation($event, col)"> <div class="q-pa-md">
{{ value }} <QTable
</QTd> :columns="columns"
</template> :rows="rows"
<template #body-cell-id="props"> class="full-width q-mt-md"
<QTd @click="stopEventPropagation($event, props.col)"> row-key="id"
<component :visible-columns="visibleColumns"
:is="tableColumnComponents[props.col.name].component" @row-click="(evt, row, id) => navigateToTravelId(row.id)"
class="col-content"
v-bind="tableColumnComponents[props.col.name].props(props)"
@click="tableColumnComponents[props.col.name].event(props)"
> >
<CustomerDescriptorProxy :id="props.row.id" /> <template #body-cell="{ col, value }">
{{ props.row.id }} <QTd @click="stopEventPropagation($event, col)">
</component> {{ value }}
</QTd> </QTd>
</template>
<template #body-cell-customerStatus="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)
"
>
</component>
</QTd>
</template>
<template #body-cell-id="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)
"
>
<CustomerDescriptorProxy :id="props.row.id" />
{{ props.row.id }}
</component>
</QTd>
</template>
<template #body-cell-salesPersonFk="props">
<QTd @click="stopEventPropagation($event, props.col)">
<component
v-if="props.row.salesPerson"
class="col-content"
:is="tableColumnComponents[props.col.name].component"
v-bind="
tableColumnComponents[props.col.name].props(props)
"
@click="
tableColumnComponents[props.col.name].event(props)
"
>
<WorkerDescriptorProxy
:id="props.row.salesPersonFk"
/>
{{ props.row.salesPerson }}
</component>
<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>
</QTable>
</div>
</template> </template>
<template #body-cell-salesPersonFk="props"> </VnPaginate>
<QTd @click="stopEventPropagation($event, props.col)">
<component
v-if="props.row.salesPerson"
class="col-content"
:is="tableColumnComponents[props.col.name].component"
v-bind="tableColumnComponents[props.col.name].props(props)"
@click="tableColumnComponents[props.col.name].event(props)"
>
<WorkerDescriptorProxy :id="props.row.salesPersonFk" />
{{ props.row.salesPerson }}
</component>
<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>
</QTable>
</QPage> </QPage>
</template> </template>

View File

@ -1,17 +1,16 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { QBtn } from 'quasar'; import { QBtn } from 'quasar';
import FetchData from 'components/FetchData.vue';
import CustomerNotificationsFilter from './CustomerNotificationsFilter.vue'; import CustomerNotificationsFilter from './CustomerNotificationsFilter.vue';
import CustomerDescriptorProxy from '../Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from '../Card/CustomerDescriptorProxy.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import { useStateStore } from 'stores/useStateStore';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import CustomerNotificationsCampaignConsumption from './CustomerNotificationsCampaignConsumption.vue';
const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const rows = ref([]);
const selected = ref([]); const selected = ref([]);
const selectedCustomerId = ref(0); const selectedCustomerId = ref(0);
@ -82,46 +81,82 @@ const selectCustomerId = (id) => {
</script> </script>
<template> <template>
<FetchData <template v-if="stateStore.isHeaderMounted()">
:filter="filter" <Teleport to="#actions-append">
@on-fetch="(data) => (rows = data)" <div class="row q-gutter-x-sm">
auto-load <QBtn
url="Clients" flat
/> @click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
</template>
<QDrawer side="right" :width="256" show-if-above> <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8"> <QScrollArea class="fit text-grey-8">
<CustomerNotificationsFilter data-key="CustomerNotifications" /> <CustomerNotificationsFilter data-key="CustomerNotifications" />
</QScrollArea> </QScrollArea>
</QDrawer> </QDrawer>
<VnSubToolbar class="justify-end">
<VnSubToolbar /> <template #st-data>
<CustomerNotificationsCampaignConsumption
:selected-rows="selected.length > 0"
:clients="selected"
:promise="refreshData"
/>
</template>
</VnSubToolbar>
<QPage class="column items-center q-pa-md"> <QPage class="column items-center q-pa-md">
<QTable <VnPaginate data-key="CustomerNotifications" url="Clients" auto-load>
:columns="columns" <template #body="{ rows }">
:rows="rows" <div class="q-pa-md">
class="full-width q-mt-md" <QTable
row-key="id" :columns="columns"
selection="multiple" :rows="rows"
v-model:selected="selected" class="full-width q-mt-md"
> row-key="id"
<template #body-cell="props"> selection="multiple"
<QTd :props="props"> v-model:selected="selected"
<QTr :props="props" class="cursor-pointer"> >
<component <template #body-cell="props">
:is="tableColumnComponents[props.col.name].component" <QTd :props="props">
class="col-content" <QTr :props="props" class="cursor-pointer">
v-bind="tableColumnComponents[props.col.name].props(props)" <component
@click="tableColumnComponents[props.col.name].event(props)" :is="
> tableColumnComponents[props.col.name]
{{ props.value }} .component
<CustomerDescriptorProxy :id="selectedCustomerId" /> "
</component> class="col-content"
</QTr> v-bind="
</QTd> tableColumnComponents[props.col.name].props(
props
)
"
@click="
tableColumnComponents[props.col.name].event(
props
)
"
>
{{ props.value }}
<CustomerDescriptorProxy
:id="selectedCustomerId"
/>
</component>
</QTr>
</QTd>
</template>
</QTable>
</div>
</template> </template>
</QTable> </VnPaginate>
</QPage> </QPage>
</template> </template>
@ -140,4 +175,5 @@ es:
Phone: Teléfono Phone: Teléfono
City: Población City: Población
Email: Email Email: Email
Campaign consumption: Consumo campaña
</i18n> </i18n>

View File

@ -0,0 +1,152 @@
<script setup>
import { ref, toRefs } from 'vue';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import useNotify from 'src/composables/useNotify';
import { useValidator } from 'src/composables/useValidator';
import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'components/common/VnSelect.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import FetchData from 'src/components/FetchData.vue';
import { watch } from 'vue';
import { onMounted } from 'vue';
const $props = defineProps({
clients: {
type: Array,
required: true,
},
promise: {
type: Function,
required: true,
},
selectedRows: {
type: Boolean,
},
});
const { selectedRows } = toRefs($props);
const { notify } = useNotify();
const { t } = useI18n();
const validationsStore = useValidator();
const campaignParams = ref(null);
const campaignsOptions = ref(null);
const moreFields = ref([]);
const popupProxyRef = ref(null);
const upcomingOptions = ref(null);
const campaignChange = ({ name }) => {
const campaign = campaignsOptions.value.find((c) => c.code === name);
handleDates(campaign);
};
const handleDates = (campaign) => {
const from = new Date(campaign.dated);
from.setDate(from.getDate() - campaign.scopeDays);
campaignParams.value = {
code: campaign.code,
from: from,
to: campaign.dated,
};
};
watch(selectedRows, () => {
handleDates(upcomingOptions.value);
});
const onSubmit = async () => {
try {
const data = {
clients: $props.clients.map((item) => item.id),
from: campaignParams.value.from,
to: campaignParams.value.to,
};
const params = JSON.stringify(data);
await axios.post('ClientConsumptionQueues', { params });
notify('globals.dataSaved', 'positive');
popupProxyRef.value.hide();
} catch (error) {
notify(error.message, 'negative');
}
};
onMounted(async () => {
const { models } = validationsStore;
const properties = models.Item?.properties || {};
const _moreFields = ['valentinesDay', 'mothersDay', 'allSaints'];
_moreFields.forEach((field) => {
let prop = properties[field];
const label = t(`params.${field}`);
moreFields.value.push({
name: field,
label,
type: prop ? prop.type : null,
});
});
});
</script>
<template>
<FetchData
url="Campaigns/latest"
@on-fetch="(data) => (campaignsOptions = data)"
:filter="{ fields: ['id', 'code', 'dated'], order: 'code ASC', limit: 30 }"
auto-load
/>
<FetchData
url="Campaigns/upcoming"
@on-fetch="(data) => (upcomingOptions = data)"
auto-load
/>
<QBtn color="primary" icon="show_chart" :disable="!selectedRows">
<QPopupProxy ref="popupProxyRef">
<QCard class="column q-pa-md">
<span class="text-body1 q-mb-sm">{{ t('Campaign consumption') }}</span>
<VnRow class="row q-gutter-md q-mb-md">
<VnSelect
:options="moreFields"
option-value="code"
option-label="label"
v-model="campaignParams.code"
:label="t('Campaign')"
@update:model-value="campaignChange"
/>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnInputDate v-model="campaignParams.from" :label="t('From')" />
<VnInputDate v-model="campaignParams.to" :label="t('To')" />
</VnRow>
<div class="q-mt-lg row justify-end">
<QBtn
:label="t('globals.cancel')"
color="primary"
flat
class="q-mr-md"
v-close-popup
/>
<QBtn
:label="t('globals.save')"
type="submit"
color="primary"
@click="onSubmit()"
/>
</div>
</QCard>
</QPopupProxy>
<QTooltip>{{ t('Campaign consumption') }}</QTooltip>
</QBtn>
</template>
<i18n>
en:
params:
valentinesDay: Valentine's Day
mothersDay: Mother's Day
allSaints: All Saints' Day
es:
params:
valentinesDay: Día de San Valentín
mothersDay: Día de la Madre
allSaints: Día de Todos los Santos
Campaign consumption: Consumo campaña
Campaign: Campaña
From: Desde
To: Hasta
</i18n>

View File

@ -110,13 +110,7 @@ function stateColor(row) {
</div> </div>
</Teleport> </Teleport>
</template> </template>
<QDrawer <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
v-model="stateStore.rightDrawer"
side="right"
:width="256"
show-if-above
:breakpoint="1600"
>
<QScrollArea class="fit text-grey-8"> <QScrollArea class="fit text-grey-8">
<CustomerPaymentsFilter data-key="CustomerTransactions" /> <CustomerPaymentsFilter data-key="CustomerTransactions" />
</QScrollArea> </QScrollArea>

View File

@ -11,7 +11,6 @@ import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CustomerCreateNewPostcode from 'src/components/CreateNewPostcodeForm.vue';
import CustomerNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCustomsAgent.vue'; import CustomerNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCustomsAgent.vue';
const { t } = useI18n(); const { t } = useI18n();

View File

@ -11,7 +11,6 @@ import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CustomerCreateNewPostcode from 'src/components/CreateNewPostcodeForm.vue';
import CustomsNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCustomsAgent.vue'; import CustomsNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCustomsAgent.vue';
const { t } = useI18n(); const { t } = useI18n();

View File

@ -56,33 +56,25 @@ const toCustomerGreuges = () => {
<template #form="{ data }"> <template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInput
<VnInput :label="t('Amount')"
:label="t('Amount')" clearable
clearable type="number"
type="number" v-model="data.amount"
v-model="data.amount" />
/> <VnInputDate :label="t('Date')" v-model="data.shipped" />
</div>
<div class="col">
<VnInputDate :label="t('Date')" v-model="data.shipped" />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInput :label="t('Comment')" clearable v-model="data.description" />
<VnInput :label="t('Comment')" clearable v-model="data.description" /> <VnSelect
</div> :label="t('Type')"
<div class="col"> :options="greugeTypes"
<VnSelect hide-selected
:label="t('Type')" option-label="name"
:options="greugeTypes" option-value="id"
hide-selected v-model="data.greugeTypeFk"
option-label="name" />
option-value="id"
v-model="data.greugeTypeFk"
/>
</div>
</VnRow> </VnRow>
</template> </template>
</FormModel> </FormModel>

View File

@ -1,5 +1,4 @@
<script setup> <script setup>
import { reactive } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
@ -24,30 +23,22 @@ const onDataSaved = (dataSaved) => {
> >
<template #form-inputs="{ data }"> <template #form-inputs="{ data }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInput
<VnInput :label="t('NIF')"
:label="t('NIF')" :required="true"
:required="true" clearable
clearable v-model="data.nif"
v-model="data.nif" />
/> <VnInput
</div> :label="t('Fiscal name')"
<div class="col"> :required="true"
<VnInput clearable
:label="t('Fiscal name')" v-model="data.fiscalName"
:required="true" />
clearable
v-model="data.fiscalName"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInput :label="t('Street')" clearable v-model="data.street" />
<VnInput :label="t('Street')" clearable v-model="data.street" /> <VnInput :label="t('Phone')" clearable v-model="data.phone" />
</div>
<div class="col">
<VnInput :label="t('Phone')" clearable v-model="data.phone" />
</div>
</VnRow> </VnRow>
</template> </template>
</FormModelPopup> </FormModelPopup>

View File

@ -135,60 +135,52 @@ const onDataSaved = async () => {
<h5 class="q-mt-none">{{ t('New payment') }}</h5> <h5 class="q-mt-none">{{ t('New payment') }}</h5>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInputDate
<VnInputDate :label="t('Date')"
:label="t('Date')" :required="true"
:required="true" v-model="data.payed"
v-model="data.payed" />
/> <VnSelect
</div> :label="t('Company')"
<div class="col"> :options="companyOptions"
<VnSelect :required="true"
:label="t('Company')" :rules="validate('entry.companyFk')"
:options="companyOptions" hide-selected
:required="true" option-label="code"
:rules="validate('entry.companyFk')" option-value="id"
hide-selected v-model="data.companyFk"
option-label="code" />
option-value="id"
v-model="data.companyFk"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelect :label="t('Bank')"
:label="t('Bank')" :options="bankOptions"
:options="bankOptions" :required="true"
:required="true" @update:model-value="setPaymentType($event)"
@update:model-value="setPaymentType($event)" hide-selected
hide-selected option-label="bank"
option-label="bank" option-value="id"
option-value="id" v-model="data.bankFk"
v-model="data.bankFk" >
> <template #option="scope">
<template #option="scope"> <QItem v-bind="scope.itemProps">
<QItem v-bind="scope.itemProps"> <QItemSection>
<QItemSection> <QItemLabel>
<QItemLabel> {{ scope.opt.id }}:&ensp;{{ scope.opt.bank }}
{{ scope.opt.id }}:&ensp;{{ scope.opt.bank }} </QItemLabel>
</QItemLabel> </QItemSection>
</QItemSection> </QItem>
</QItem> </template>
</template> </VnSelect>
</VnSelect> <VnInput
</div> :label="t('Amount')"
<div class="col"> :required="true"
<VnInput @update:model-value="calculateFromAmount($event)"
:label="t('Amount')" clearable
:required="true" type="number"
@update:model-value="calculateFromAmount($event)" v-model.number="data.amountPaid"
clearable />
type="number"
v-model.number="data.amountPaid"
/>
</div>
</VnRow> </VnRow>
<div class="text-h6" v-if="data.bankFk === 3 || data.bankFk === 3117"> <div class="text-h6" v-if="data.bankFk === 3 || data.bankFk === 3117">
@ -203,47 +195,37 @@ const onDataSaved = async () => {
v-model="data.compensationAccount" v-model="data.compensationAccount"
/> />
</div> </div>
<div class="col"> <VnInput
<VnInput :label="t('Reference')"
:label="t('Reference')" :required="true"
:required="true" clearable
clearable v-model="data.description"
v-model="data.description" />
/>
</div>
</VnRow> </VnRow>
<div class="q-mt-lg" v-if="data.bankFk === 2"> <div class="q-mt-lg" v-if="data.bankFk === 2">
<div class="text-h6">{{ t('Cash') }}</div> <div class="text-h6">{{ t('Cash') }}</div>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInput
<VnInput :label="t('Delivered amount')"
:label="t('Delivered amount')" @update:model-value="calculateFromDeliveredAmount($event)"
@update:model-value="calculateFromDeliveredAmount($event)" clearable
clearable type="number"
type="number" v-model="deliveredAmount"
v-model="deliveredAmount" />
/> <VnInput
</div> :label="t('Amount to return')"
<div class="col"> clearable
<VnInput disable
:label="t('Amount to return')" type="number"
clearable v-model="amountToReturn"
disable />
type="number"
v-model="amountToReturn"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <QCheckbox v-model="viewRecipt" />
<QCheckbox v-model="viewRecipt" /> <QCheckbox v-model="sendEmail" />
</div>
<div class="col">
<QCheckbox v-model="sendEmail" />
</div>
</VnRow> </VnRow>
</div> </div>

View File

@ -45,9 +45,7 @@ const toCustomerNotes = () => {
<template #form="{ data }"> <template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <QInput :label="t('Note')" type="textarea" v-model="data.text" />
<QInput :label="t('Note')" type="textarea" v-model="data.text" />
</div>
</VnRow> </VnRow>
</template> </template>
</FormModel> </FormModel>

View File

@ -50,31 +50,23 @@ const toCustomerRecoveries = () => {
<template #form="{ data }"> <template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInputDate :label="t('Since')" v-model="data.started" />
<VnInputDate :label="t('Since')" v-model="data.started" /> <VnInputDate :label="t('To')" v-model="data.finished" />
</div>
<div class="col">
<VnInputDate :label="t('To')" v-model="data.finished" />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInput
<VnInput :label="t('Amount')"
:label="t('Amount')" clearable
clearable type="number"
type="number" v-model="data.amount"
v-model="data.amount" />
/> <VnInput
</div> :label="t('Period')"
<div class="col"> clearable
<VnInput type="number"
:label="t('Period')" v-model="data.period"
clearable />
type="number"
v-model="data.period"
/>
</div>
</VnRow> </VnRow>
</template> </template>
</FormModel> </FormModel>

View File

@ -30,105 +30,83 @@ const clientsOptions = ref([]);
> >
<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">
<div class="col"> <VnInput
<VnInput :label="t('department.name')"
:label="t('department.name')" v-model="data.name"
v-model="data.name" :rules="validate('department.name')"
:rules="validate('department.name')" clearable
clearable autofocus
autofocus />
/> <VnInput
</div> v-model="data.code"
<div class="col"> :label="t('department.code')"
<VnInput :rules="validate('department.code')"
v-model="data.code" clearable
:label="t('department.code')" />
:rules="validate('department.code')"
clearable
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInput
<VnInput :label="t('department.chat')"
:label="t('department.chat')" v-model="data.chatName"
v-model="data.chatName" :rules="validate('department.chat')"
:rules="validate('department.chat')" clearable
clearable />
/> <VnInput
</div> v-model="data.notificationEmail"
<div class="col"> :label="t('department.email')"
<VnInput :rules="validate('department.email')"
v-model="data.notificationEmail" clearable
:label="t('department.email')" />
:rules="validate('department.email')"
clearable
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelect :label="t('department.bossDepartment')"
:label="t('department.bossDepartment')" v-model="data.workerFk"
v-model="data.workerFk" :options="workersOptions"
:options="workersOptions" option-value="id"
option-value="id" option-label="name"
option-label="name" hide-selected
hide-selected map-options
map-options :rules="validate('department.workerFk')"
:rules="validate('department.workerFk')" />
/> <VnSelect
</div> :label="t('department.selfConsumptionCustomer')"
<div class="col"> v-model="data.clientFk"
<VnSelect :options="clientsOptions"
:label="t('department.selfConsumptionCustomer')" option-value="id"
v-model="data.clientFk" option-label="name"
:options="clientsOptions" hide-selected
option-value="id" map-options
option-label="name" :rules="validate('department.clientFk')"
hide-selected />
map-options
:rules="validate('department.clientFk')"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <QCheckbox
<QCheckbox :label="t('department.telework')"
:label="t('department.telework')" v-model="data.isTeleworking"
v-model="data.isTeleworking" />
/> <QCheckbox
</div> :label="t('department.notifyOnErrors')"
<div class="col"> v-model="data.hasToMistake"
<QCheckbox :false-value="0"
:label="t('department.notifyOnErrors')" :true-value="1"
v-model="data.hasToMistake" />
:false-value="0"
:true-value="1"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <QCheckbox
<QCheckbox :label="t('department.worksInProduction')"
:label="t('department.worksInProduction')" v-model="data.isProduction"
v-model="data.isProduction" />
/> <QCheckbox
</div> :label="t('department.hasToRefill')"
<div class="col"> v-model="data.hasToRefill"
<QCheckbox />
:label="t('department.hasToRefill')"
v-model="data.hasToRefill"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <QCheckbox
<QCheckbox :label="t('department.hasToSendMail')"
:label="t('department.hasToSendMail')" v-model="data.hasToSendMail"
v-model="data.hasToSendMail" />
/>
</div>
</VnRow> </VnRow>
</template> </template>
</FormModel> </FormModel>

View File

@ -3,7 +3,7 @@ import { computed, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useVnConfirm } from 'composables/useVnConfirm';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import CardDescriptor from 'src/components/ui/CardDescriptor.vue'; import CardDescriptor from 'src/components/ui/CardDescriptor.vue';
import useCardDescription from 'src/composables/useCardDescription'; import useCardDescription from 'src/composables/useCardDescription';
@ -43,30 +43,17 @@ const setData = (entity) => {
data.value = useCardDescription(entity.name, entity.id); data.value = useCardDescription(entity.name, entity.id);
}; };
const removeDepartment = () => { const removeDepartment = async () => {
quasar try {
.dialog({ await axios.post(`/Departments/${entityId.value}/removeChild`, entityId.value);
title: 'Are you sure you want to delete it?', router.push({ name: 'WorkerDepartment' });
message: 'Delete department', notify('department.departmentRemoved', 'positive');
ok: { } catch (err) {
push: true, console.error('Error removing department');
color: 'primary', }
},
cancel: true,
})
.onOk(async () => {
try {
await axios.post(
`/Departments/${entityId.value}/removeChild`,
entityId.value
);
router.push({ name: 'WorkerDepartment' });
notify('department.departmentRemoved', 'positive');
} catch (err) {
console.error('Error removing department');
}
});
}; };
const { openConfirmationModal } = useVnConfirm();
</script> </script>
<template> <template>
<CardDescriptor <CardDescriptor
@ -84,7 +71,17 @@ const removeDepartment = () => {
" "
> >
<template #menu="{}"> <template #menu="{}">
<QItem v-ripple clickable @click="removeDepartment()"> <QItem
v-ripple
clickable
@click="
openConfirmationModal(
t('Are you sure you want to delete it?'),
t('Delete department'),
removeDepartment
)
"
>
<QItemSection>{{ t('Delete') }}</QItemSection> <QItemSection>{{ t('Delete') }}</QItemSection>
</QItem> </QItem>
</template> </template>

View File

@ -68,152 +68,126 @@ const onFilterTravelSelected = (formData, id) => {
> >
<template #form="{ data }"> <template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelect :label="t('entry.basicData.supplier')"
:label="t('entry.basicData.supplier')" v-model="data.supplierFk"
v-model="data.supplierFk" :options="suppliersOptions"
:options="suppliersOptions" option-value="id"
option-value="id" option-label="nickname"
option-label="nickname" hide-selected
hide-selected :required="true"
:required="true" map-options
map-options >
> <template #option="scope">
<template #option="scope"> <QItem v-bind="scope.itemProps">
<QItem v-bind="scope.itemProps"> <QItemSection>
<QItemSection> <QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel> <QItemLabel caption>
<QItemLabel caption> {{ scope.opt?.nickname }}, {{ scope.opt?.id }}
{{ scope.opt?.nickname }}, {{ scope.opt?.id }} </QItemLabel>
</QItemLabel> </QItemSection>
</QItemSection> </QItem>
</QItem> </template>
</template> </VnSelect>
</VnSelect> <VnSelectDialog
</div> :label="t('entry.basicData.travel')"
<div class="col"> v-model="data.travelFk"
<VnSelectDialog :options="travelsOptions"
:label="t('entry.basicData.travel')" option-value="id"
v-model="data.travelFk" option-label="warehouseInName"
:options="travelsOptions" map-options
option-value="id" hide-selected
option-label="warehouseInName" :required="true"
map-options action-icon="filter_alt"
hide-selected >
:required="true" <template #form>
action-icon="filter_alt" <FilterTravelForm
> @travel-selected="onFilterTravelSelected(data, $event)"
<template #form> />
<FilterTravelForm </template>
@travel-selected="onFilterTravelSelected(data, $event)" <template #option="scope">
/> <QItem v-bind="scope.itemProps">
</template> <QItemSection>
<template #option="scope"> <QItemLabel
<QItem v-bind="scope.itemProps"> >{{ scope.opt?.agencyModeName }} -
<QItemSection> {{ scope.opt?.warehouseInName }} ({{
<QItemLabel toDate(scope.opt?.shipped)
>{{ scope.opt?.agencyModeName }} - }}) &#x2192; {{ scope.opt?.warehouseOutName }} ({{
{{ scope.opt?.warehouseInName }} ({{ toDate(scope.opt?.landed)
toDate(scope.opt?.shipped) }})</QItemLabel
}}) &#x2192; {{ scope.opt?.warehouseOutName }} ({{ >
toDate(scope.opt?.landed) </QItemSection>
}})</QItemLabel </QItem>
> </template>
</QItemSection> </VnSelectDialog>
</QItem>
</template>
</VnSelectDialog>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInput
<VnInput v-model="data.reference"
v-model="data.reference" :label="t('entry.basicData.reference')"
:label="t('entry.basicData.reference')" />
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInput
<VnInput v-model="data.invoiceNumber"
v-model="data.invoiceNumber" :label="t('entry.basicData.invoiceNumber')"
:label="t('entry.basicData.invoiceNumber')" />
/> <VnSelect
</div> :label="t('entry.basicData.company')"
<div class="col"> v-model="data.companyFk"
<VnSelect :options="companiesOptions"
:label="t('entry.basicData.company')" option-value="id"
v-model="data.companyFk" option-label="code"
:options="companiesOptions" map-options
option-value="id" hide-selected
option-label="code" :required="true"
map-options />
hide-selected
:required="true"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelect :label="t('entry.basicData.currency')"
:label="t('entry.basicData.currency')" v-model="data.currencyFk"
v-model="data.currencyFk" :options="currenciesOptions"
:options="currenciesOptions" option-value="id"
option-value="id" option-label="code"
option-label="code" />
/> <QInput
</div> :label="t('entry.basicData.commission')"
<div class="col"> v-model="data.commission"
<QInput type="number"
:label="t('entry.basicData.commission')" autofocus
v-model="data.commission" min="0"
type="number" />
autofocus
min="0"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <QInput
<QInput :label="t('entry.basicData.observation')"
:label="t('entry.basicData.observation')" type="textarea"
type="textarea" v-model="data.observation"
v-model="data.observation" :maxlength="45"
:maxlength="45" counter
counter fill-input
fill-input />
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <QCheckbox
<QCheckbox v-model="data.isOrdered"
v-model="data.isOrdered" :label="t('entry.basicData.ordered')"
:label="t('entry.basicData.ordered')" />
/> <QCheckbox
</div> v-model="data.isConfirmed"
<div class="col"> :label="t('entry.basicData.confirmed')"
<QCheckbox />
v-model="data.isConfirmed" <QCheckbox
:label="t('entry.basicData.confirmed')" v-model="data.isExcludedFromAvailable"
/> :label="t('entry.basicData.excludedFromAvailable')"
</div> />
<div class="col"> <QCheckbox v-model="data.isRaid" :label="t('entry.basicData.raid')" />
<QCheckbox <QCheckbox
v-model="data.isExcludedFromAvailable" v-if="isAdministrative()"
:label="t('entry.basicData.excludedFromAvailable')" v-model="data.isBooked"
/> :label="t('entry.basicData.booked')"
</div> />
<div class="col">
<QCheckbox v-model="data.isRaid" :label="t('entry.basicData.raid')" />
</div>
<div class="col">
<QCheckbox
v-if="isAdministrative()"
v-model="data.isBooked"
:label="t('entry.basicData.booked')"
/>
</div>
</VnRow> </VnRow>
</template> </template>
</FormModel> </FormModel>

View File

@ -198,44 +198,38 @@ const redirectToBuysView = () => {
</Teleport> </Teleport>
<QCard class="q-pa-lg"> <QCard class="q-pa-lg">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <QFile
<QFile ref="inputFileRef"
ref="inputFileRef" :label="t('entry.buys.file')"
:label="t('entry.buys.file')" v-model="importData.file"
v-model="importData.file" :multiple="false"
:multiple="false" accept=".json"
accept=".json" @update:model-value="onFileChange($event)"
@update:model-value="onFileChange($event)" class="required"
class="required" >
> <template #append>
<template #append> <QIcon
<QIcon name="vn:attach"
name="vn:attach" class="cursor-pointer"
class="cursor-pointer" @click="inputFileRef.pickFiles()"
@click="inputFileRef.pickFiles()" >
> <QTooltip>{{ t('globals.selectFile') }}</QTooltip>
<QTooltip>{{ t('globals.selectFile') }}</QTooltip> </QIcon>
</QIcon> </template>
</template> </QFile>
</QFile>
</div>
</VnRow> </VnRow>
<div v-if="importData.file"> <div v-if="importData.file">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInput
<VnInput :label="t('entry.buys.reference')"
:label="t('entry.buys.reference')" v-model="importData.ref"
v-model="importData.ref" />
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInput
<VnInput :label="t('entry.buys.observations')"
:label="t('entry.buys.observations')" v-model="importData.observation"
v-model="importData.observation" />
/>
</div>
</VnRow> </VnRow>
<VnRow> <VnRow>
<QTable :columns="columns" :rows="importData.buys"> <QTable :columns="columns" :rows="importData.buys">
@ -251,6 +245,7 @@ const redirectToBuysView = () => {
> >
<template #form> <template #form>
<FilterItemForm <FilterItemForm
:url="`Entries/${route.params.id}/lastItemBuys`"
@item-selected="row[col.field] = $event" @item-selected="row[col.field] = $event"
/> />
</template> </template>

View File

@ -1,14 +1,16 @@
<script setup> <script setup>
import VnCard from 'components/common/VnCard.vue'; import VnCard from 'components/common/VnCard.vue';
import EntryDescriptor from './EntryDescriptor.vue'; import EntryDescriptor from './EntryDescriptor.vue';
import EntryFilter from '../EntryFilter.vue';
</script> </script>
<template> <template>
<VnCard <VnCard
data-key="Entry" data-key="Entry"
base-url="Entries" base-url="Entries"
:descriptor="EntryDescriptor" :descriptor="EntryDescriptor"
searchbar-data-key="EntryList" :filter-panel="EntryFilter"
searchbar-url="Entries/filter" search-data-key="EntryList"
search-url="Entries/filter"
searchbar-label="Search entries" searchbar-label="Search entries"
searchbar-info="You can search by entry reference" searchbar-info="You can search by entry reference"
/> />

View File

@ -6,7 +6,6 @@ import { useI18n } from 'vue-i18n';
import CardSummary from 'components/ui/CardSummary.vue'; import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue'; import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue';
import VnTitle from 'src/components/common/VnTitle.vue';
import { toDate, toCurrency } from 'src/filters'; import { toDate, toCurrency } from 'src/filters';
import { getUrl } from 'src/composables/getUrl'; import { getUrl } from 'src/composables/getUrl';

View File

@ -79,78 +79,71 @@ const redirectToEntryBasicData = (_, { id }) => {
> >
<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">
<div class="col"> <VnSelect
<VnSelect :label="t('Supplier')"
:label="t('Supplier')" class="full-width"
class="full-width" v-model="data.supplierFk"
v-model="data.supplierFk" :options="suppliersOptions"
:options="suppliersOptions" option-value="id"
option-value="id" option-label="nickname"
option-label="nickname" hide-selected
hide-selected :required="true"
:required="true" :rules="validate('entry.supplierFk')"
:rules="validate('entry.supplierFk')" >
> <template #option="scope">
<template #option="scope"> <QItem v-bind="scope.itemProps">
<QItem v-bind="scope.itemProps"> <QItemSection>
<QItemSection> <QItemLabel>{{ scope.opt?.nickname }}</QItemLabel>
<QItemLabel>{{ scope.opt?.nickname }}</QItemLabel> <QItemLabel caption>
<QItemLabel caption> #{{ scope.opt?.id }}
#{{ scope.opt?.id }} </QItemLabel>
</QItemLabel> </QItemSection>
</QItemSection> </QItem>
</QItem> </template>
</template> </VnSelect>
</VnSelect>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelect :label="t('Travel')"
:label="t('Travel')" class="full-width"
class="full-width" v-model="data.travelFk"
v-model="data.travelFk" :options="travelsOptions"
:options="travelsOptions" option-value="id"
option-value="id" option-label="warehouseInName"
option-label="warehouseInName" map-options
map-options hide-selected
hide-selected :required="true"
:required="true" :rules="validate('entry.travelFk')"
:rules="validate('entry.travelFk')" >
> <template #option="scope">
<template #option="scope"> <QItem v-bind="scope.itemProps">
<QItem v-bind="scope.itemProps"> <QItemSection>
<QItemSection> <QItemLabel
<QItemLabel >{{ scope.opt?.agencyModeName }} -
>{{ scope.opt?.agencyModeName }} - {{ scope.opt?.warehouseInName }} ({{
{{ scope.opt?.warehouseInName }} ({{ toDate(scope.opt?.shipped)
toDate(scope.opt?.shipped) }}) &#x2192; {{ scope.opt?.warehouseOutName }} ({{
}}) &#x2192; toDate(scope.opt?.landed)
{{ scope.opt?.warehouseOutName }} ({{ }})</QItemLabel
toDate(scope.opt?.landed) >
}})</QItemLabel </QItemSection>
> </QItem>
</QItemSection> </template>
</QItem> </VnSelect>
</template>
</VnSelect>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelect :label="t('Company')"
:label="t('Company')" class="full-width"
class="full-width" v-model="data.companyFk"
v-model="data.companyFk" :options="companiesOptions"
:options="companiesOptions" option-value="id"
option-value="id" option-label="code"
option-label="code" map-options
map-options hide-selected
hide-selected :required="true"
:required="true" :rules="validate('entry.companyFk')"
:rules="validate('entry.companyFk')" />
/>
</div>
</VnRow> </VnRow>
</template> </template>
</FormModel> </FormModel>

View File

@ -12,6 +12,7 @@ import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import EntryLatestBuysFilter from './EntryLatestBuysFilter.vue'; import EntryLatestBuysFilter from './EntryLatestBuysFilter.vue';
import ItemDescriptorProxy from '../Item/Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from '../Item/Card/ItemDescriptorProxy.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { toDate, toCurrency } from 'src/filters'; import { toDate, toCurrency } from 'src/filters';
@ -636,18 +637,18 @@ onUnmounted(() => (stateStore.rightDrawer = false));
auto-load auto-load
@on-fetch="(data) => (intrastatOptions = data)" @on-fetch="(data) => (intrastatOptions = data)"
/> />
<QToolbar class="justify-end"> <VnSubToolbar>
<div id="st-data"> <template #st-data>
<TableVisibleColumns <TableVisibleColumns
:all-columns="allColumnNames" :all-columns="allColumnNames"
table-code="latestBuys" table-code="latestBuys"
labels-traductions-path="entry.latestBuys" labels-traductions-path="entry.latestBuys"
@on-config-saved="visibleColumns = ['picture', ...$event]" @on-config-saved="visibleColumns = ['picture', ...$event]"
/> />
</div> </template>
<QSpace /> <QSpace />
<div id="st-actions"></div> <div id="st-actions"></div>
</QToolbar> </VnSubToolbar>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8"> <QScrollArea class="fit text-grey-8">
<EntryLatestBuysFilter data-key="EntryLatestBuys" /> <EntryLatestBuysFilter data-key="EntryLatestBuys" />

View File

@ -3,14 +3,13 @@ import { ref, computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import axios from 'axios';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { downloadFile } from 'src/composables/downloadFile'; import { downloadFile } from 'src/composables/downloadFile';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import FetchData from 'src/components/FetchData.vue'; import FetchData from 'src/components/FetchData.vue';
import VnRow from 'src/components/ui/VnRow.vue';
import axios from 'axios';
const quasar = useQuasar(); const quasar = useQuasar();
const route = useRoute(); const route = useRoute();
@ -181,254 +180,222 @@ async function upsert() {
:auto-load="true" :auto-load="true"
> >
<template #form="{ data }"> <template #form="{ data }">
<div class="row q-gutter-md q-mb-md"> <VnRow>
<div class="col"> <VnSelect
<VnSelect :label="t('supplierFk')"
:label="t('supplierFk')" v-model="data.supplierFk"
v-model="data.supplierFk" option-value="id"
option-value="id" option-label="nickname"
option-label="nickname" url="Suppliers"
url="Suppliers" :fields="['id', 'nickname']"
:fields="['id', 'nickname']" sort-by="nickname"
sort-by="nickname" :is-clearable="false"
:is-clearable="false" >
> <template #option="scope">
<template #option="scope"> <QItem v-bind="scope.itemProps">
<QItem v-bind="scope.itemProps"> <QItemSection>
<QItemSection> <QItemLabel>{{
<QItemLabel>{{ `${scope.opt.id} - ${scope.opt.nickname}`
`${scope.opt.id} - ${scope.opt.nickname}` }}</QItemLabel>
}}</QItemLabel> </QItemSection>
</QItemSection> </QItem>
</QItem> </template>
</template> </VnSelect>
</VnSelect> <QInput
</div> clearable
<div class="col"> clear-icon="close"
<QInput :label="t('Supplier ref')"
clearable v-model="data.supplierRef"
clear-icon="close" />
:label="t('Supplier ref')" </VnRow>
v-model="data.supplierRef" <VnRow>
/> <QInput
</div> :label="t('Expedition date')"
</div> v-model="data.issued"
<div class="row q-gutter-md q-mb-md"> :mask="dateMask"
<div class="col"> >
<QInput <template #append>
:label="t('Expedition date')" <QIcon name="event" class="cursor-pointer" :fill-mask="fillMask">
v-model="data.issued" <QPopupProxy
:mask="dateMask" cover
> transition-show="scale"
<template #append> transition-hide="scale"
<QIcon
name="event"
class="cursor-pointer"
:fill-mask="fillMask"
> >
<QPopupProxy <QDate v-model="data.issued">
cover <div class="row items-center justify-end">
transition-show="scale" <QBtn
transition-hide="scale" v-close-popup
> label="Close"
<QDate v-model="data.issued"> color="primary"
<div class="row items-center justify-end"> flat
<QBtn />
v-close-popup </div>
label="Close" </QDate>
color="primary" </QPopupProxy>
flat </QIcon>
/> </template>
</div> </QInput>
</QDate> <QInput
</QPopupProxy> :label="t('Operation date')"
</QIcon> v-model="data.operated"
</template> :mask="dateMask"
</QInput> :fill-mask="fillMask"
</div> autofocus
<div class="col"> >
<QInput <template #append>
:label="t('Operation date')" <QIcon name="event" class="cursor-pointer">
v-model="data.operated" <QPopupProxy
:mask="dateMask" cover
:fill-mask="fillMask" transition-show="scale"
autofocus transition-hide="scale"
>
<template #append>
<QIcon name="event" class="cursor-pointer">
<QPopupProxy
cover
transition-show="scale"
transition-hide="scale"
>
<QDate v-model="data.operated" :mask="dateMask">
<div class="row items-center justify-end">
<QBtn
v-close-popup
label="Close"
color="primary"
flat
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</div>
</div>
<div class="row q-gutter-md q-mb-md">
<div class="col">
<QInput
:label="t('Undeductible VAT')"
v-model="data.deductibleExpenseFk"
clearable
clear-icon="close"
/>
</div>
<div class="col">
<QInput
:label="t('Document')"
v-model="data.dmsFk"
clearable
clear-icon="close"
@update:model-value="checkFileExists(data.dmsFk)"
>
<template #prepend>
<QBtn
v-if="data.dmsFk"
:class="{
'no-pointer-events': editDownloadDisabled,
}"
:disable="editDownloadDisabled"
icon="cloud_download"
:title="t('Download file')"
padding="xs"
round
@click="downloadFile(data.dmsFk)"
/>
</template>
<template #append>
<QBtn
:class="{
'no-pointer-events': editDownloadDisabled,
}"
:disable="editDownloadDisabled"
v-if="data.dmsFk"
icon="edit"
round
padding="xs"
@click="setEditDms(data.dmsFk)"
> >
<QTooltip>{{ t('Edit document') }}</QTooltip> <QDate v-model="data.operated" :mask="dateMask">
</QBtn> <div class="row items-center justify-end">
<QBtn <QBtn
v-else v-close-popup
icon="add_circle" label="Close"
round color="primary"
padding="xs" flat
@click="setCreateDms()" />
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</VnRow>
<VnRow>
<QInput
:label="t('Undeductible VAT')"
v-model="data.deductibleExpenseFk"
clearable
clear-icon="close"
/>
<QInput
:label="t('Document')"
v-model="data.dmsFk"
clearable
clear-icon="close"
@update:model-value="checkFileExists(data.dmsFk)"
>
<template #prepend>
<QBtn
v-if="data.dmsFk"
:class="{
'no-pointer-events': editDownloadDisabled,
}"
:disable="editDownloadDisabled"
icon="cloud_download"
:title="t('Download file')"
padding="xs"
round
@click="downloadFile(data.dmsFk)"
/>
</template>
<template #append>
<QBtn
:class="{
'no-pointer-events': editDownloadDisabled,
}"
:disable="editDownloadDisabled"
v-if="data.dmsFk"
icon="edit"
round
padding="xs"
@click="setEditDms(data.dmsFk)"
>
<QTooltip>{{ t('Edit document') }}</QTooltip>
</QBtn>
<QBtn
v-else
icon="add_circle"
round
padding="xs"
@click="setCreateDms()"
>
<QTooltip>{{ t('Create document') }}</QTooltip>
</QBtn>
</template>
</QInput>
</VnRow>
<VnRow>
<QInput
:label="t('Entry date')"
v-model="data.bookEntried"
clearable
clear-icon="close"
:mask="dateMask"
:fill-mask="fillMask"
>
<template #append>
<QIcon name="event" class="cursor-pointer">
<QPopupProxy
cover
transition-show="scale"
transition-hide="scale"
> >
<QTooltip>{{ t('Create document') }}</QTooltip> <QDate v-model="data.bookEntried" :mask="dateMask">
</QBtn> <div class="row items-center justify-end">
</template> <QBtn
</QInput> v-close-popup
</div> label="Close"
</div> color="primary"
<div class="row q-gutter-md q-mb-md"> flat
<div class="col"> />
<QInput </div>
:label="t('Entry date')" </QDate>
v-model="data.bookEntried" </QPopupProxy>
clearable </QIcon>
clear-icon="close" </template>
:mask="dateMask" </QInput>
:fill-mask="fillMask" <QInput
> :label="t('Accounted date')"
<template #append> v-model="data.booked"
<QIcon name="event" class="cursor-pointer"> clearable
<QPopupProxy clear-icon="close"
cover :mask="dateMask"
transition-show="scale" :fill-mask="fillMask"
transition-hide="scale" >
> <template #append>
<QDate v-model="data.bookEntried" :mask="dateMask"> <QIcon name="event" class="cursor-pointer">
<div class="row items-center justify-end"> <QPopupProxy
<QBtn cover
v-close-popup transition-show="scale"
label="Close" transition-hide="scale"
color="primary" >
flat <QDate v-model="data.booked" :mask="maskDate">
/> <div class="row items-center justify-end">
</div> <QBtn
</QDate> v-close-popup
</QPopupProxy> label="Close"
</QIcon> color="primary"
</template> flat
</QInput> />
</div> </div>
<div class="col"> </QDate>
<QInput </QPopupProxy>
:label="t('Accounted date')" </QIcon>
v-model="data.booked" </template>
clearable </QInput>
clear-icon="close" </VnRow>
:mask="dateMask" <VnRow>
:fill-mask="fillMask" <VnSelect
> :label="t('Currency')"
<template #append> v-model="data.currencyFk"
<QIcon name="event" class="cursor-pointer"> :options="currencies"
<QPopupProxy option-value="id"
cover option-label="code"
transition-show="scale" />
transition-hide="scale" <VnSelect
> v-if="companiesRef"
<QDate v-model="data.booked" :mask="maskDate"> :label="t('Company')"
<div class="row items-center justify-end"> v-model="data.companyFk"
<QBtn :options="companies"
v-close-popup option-value="id"
label="Close" option-label="code"
color="primary" />
flat </VnRow>
/> <QCheckbox :label="t('invoiceIn.summary.booked')" v-model="data.isBooked" />
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</div>
</div>
<div class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelect
:label="t('Currency')"
v-model="data.currencyFk"
:options="currencies"
option-value="id"
option-label="code"
/>
</div>
<div class="col">
<VnSelect
v-if="companiesRef"
:label="t('Company')"
v-model="data.companyFk"
:options="companies"
option-value="id"
option-label="code"
/>
</div>
</div>
<div class="row q-gutter-md q-mb-md">
<div class="col">
<QCheckbox
:label="t('invoiceIn.summary.booked')"
v-model="data.isBooked"
/>
</div>
<div class="col"></div>
</div>
</template> </template>
</FormModel> </FormModel>
<QDialog ref="editDmsRef"> <QDialog ref="editDmsRef">

View File

@ -1,6 +1,7 @@
<script setup> <script setup>
import VnCard from 'components/common/VnCard.vue'; import VnCard from 'components/common/VnCard.vue';
import InvoiceInDescriptor from './InvoiceInDescriptor.vue'; import InvoiceInDescriptor from './InvoiceInDescriptor.vue';
import InvoiceInFilter from '../InvoiceInFilter.vue';
const filter = { const filter = {
include: [ include: [
@ -25,8 +26,9 @@ const filter = {
base-url="InvoiceIns" base-url="InvoiceIns"
:filter="filter" :filter="filter"
:descriptor="InvoiceInDescriptor" :descriptor="InvoiceInDescriptor"
searchbar-data-key="InvoiceInList" :filter-panel="InvoiceInFilter"
searchbar-url="InvoiceIns/filter" search-data-key="InvoiceInList"
search-url="InvoiceIns/filter"
searchbar-label="Search invoice" searchbar-label="Search invoice"
searchbar-info="You can search by invoice reference" searchbar-info="You can search by invoice reference"
/> />

View File

@ -90,7 +90,7 @@ function getTotal(type) {
url="Countries" url="Countries"
auto-load auto-load
@on-fetch="(data) => (countries = data)" @on-fetch="(data) => (countries = data)"
sort-by="country" sort-by="name"
/> />
<FetchData <FetchData
url="Intrastats" url="Intrastats"

View File

@ -1,14 +1,16 @@
<script setup> <script setup>
import InvoiceOutDescriptor from './InvoiceOutDescriptor.vue'; import InvoiceOutDescriptor from './InvoiceOutDescriptor.vue';
import VnCard from 'components/common/VnCard.vue'; import VnCard from 'components/common/VnCard.vue';
import InvoiceOutFilter from '../InvoiceOutFilter.vue';
</script> </script>
<template> <template>
<VnCard <VnCard
data-key="InvoiceOut" data-key="InvoiceOut"
base-url="InvoiceOuts" base-url="InvoiceOuts"
:descriptor="InvoiceOutDescriptor" :descriptor="InvoiceOutDescriptor"
searchbar-data-key="InvoiceOutList" :filter-panel="InvoiceOutFilter"
searchbar-url="InvoiceOuts/filter" search-data-key="InvoiceOutList"
search-url="InvoiceOuts/filter"
searchbar-label="Search invoice" searchbar-label="Search invoice"
searchbar-info="You can search by invoice reference" searchbar-info="You can search by invoice reference"
/> />

View File

@ -139,7 +139,7 @@ const openCreateInvoiceModal = () => {
icon="cloud_download" icon="cloud_download"
:disable="selectedCards.size === 0" :disable="selectedCards.size === 0"
> >
<QTooltip>{{ t('downloadPdf') }}</QTooltip> <QTooltip>{{ t('globals.downloadPdf') }}</QTooltip>
</QBtn> </QBtn>
<QCheckbox <QCheckbox
left-label left-label
@ -234,13 +234,11 @@ en:
fileDenied: Browser denied file download... fileDenied: Browser denied file download...
fileAllowed: Successful download of CSV file fileAllowed: Successful download of CSV file
youCanSearchByInvoiceReference: You can search by invoice reference youCanSearchByInvoiceReference: You can search by invoice reference
downloadPdf: Download PDF
createInvoice: Make invoice createInvoice: Make invoice
es: es:
searchInvoice: Buscar factura emitida searchInvoice: Buscar factura emitida
fileDenied: El navegador denegó la descarga de archivos... fileDenied: El navegador denegó la descarga de archivos...
fileAllowed: Descarga exitosa de archivo CSV fileAllowed: Descarga exitosa de archivo CSV
youCanSearchByInvoiceReference: Puedes buscar por referencia de la factura youCanSearchByInvoiceReference: Puedes buscar por referencia de la factura
downloadPdf: Descargar PDF
createInvoice: Crear factura createInvoice: Crear factura
</i18n> </i18n>

View File

@ -0,0 +1,52 @@
<script setup>
import { reactive, ref, onMounted, nextTick } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import VnInput from 'src/components/common/VnInput.vue';
import VnRow from 'components/ui/VnRow.vue';
import FormModelPopup from 'components/FormModelPopup.vue';
const { t } = useI18n();
const emit = defineEmits(['onDataSaved']);
const route = useRoute();
const identifierInputRef = ref(null);
const intrastatFormData = reactive({});
const onDataSaved = (formData, requestResponse) => {
emit('onDataSaved', formData, requestResponse);
};
onMounted(async () => {
await nextTick();
identifierInputRef.value.focus();
});
</script>
<template>
<FormModelPopup
:url-update="`Items/${route.params.id}/createIntrastat`"
model="itemGenus"
:title="t('createIntrastatForm.title')"
:form-initial-data="intrastatFormData"
@on-data-saved="onDataSaved"
>
<template #form-inputs="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<VnInput
ref="identifierInputRef"
:label="t('createIntrastatForm.identifier')"
type="number"
v-model.number="data.intrastatId"
:required="true"
/>
<VnInput
:label="t('createIntrastatForm.description')"
v-model="data.description"
:required="true"
/>
</VnRow>
</template>
</FormModelPopup>
</template>

View File

@ -1 +1,233 @@
<template>Item basic data</template> <script setup>
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import FilterItemForm from 'src/components/FilterItemForm.vue';
import CreateIntrastatForm from './CreateIntrastatForm.vue';
const route = useRoute();
const { t } = useI18n();
const itemTypesOptions = ref([]);
const itemsWithNameOptions = ref([]);
const intrastatsOptions = ref([]);
const expensesOptions = ref([]);
const onIntrastatCreated = (response, formData) => {
intrastatsOptions.value = [...intrastatsOptions.value, response];
formData.intrastatFk = response.id;
};
</script>
<template>
<FetchData
url="ItemTypes"
:filter="{
fields: ['id', 'name', 'categoryFk'],
include: 'category',
order: 'name ASC',
}"
@on-fetch="(data) => (itemTypesOptions = data)"
auto-load
/>
<FetchData
url="Items/withName"
:filter="{
fields: ['id', 'name'],
order: 'id DESC',
}"
@on-fetch="(data) => (itemsWithNameOptions = data)"
auto-load
/>
<FetchData
url="Intrastats"
:filter="{
fields: ['id', 'description'],
order: 'description ASC',
}"
@on-fetch="(data) => (intrastatsOptions = data)"
auto-load
/>
<FetchData
url="Expenses"
:filter="{
fields: ['id', 'name'],
order: 'name ASC',
}"
@on-fetch="(data) => (expensesOptions = data)"
auto-load
/>
<FormModel
:url="`Items/${route.params.id}`"
:url-update="`Items/${route.params.id}`"
model="item"
auto-load
:clear-store-on-unmount="false"
>
<template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<VnSelect
:label="t('basicData.type')"
v-model="data.typeFk"
:options="itemTypesOptions"
option-value="id"
option-label="name"
hide-selected
map-options
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption>
{{ scope.opt?.category?.name }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
<VnInput :label="t('basicData.reference')" v-model="data.comment" />
<VnInput :label="t('basicData.relevancy')" v-model="data.relevancy" />
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnInput :label="t('basicData.stems')" v-model="data.stems" />
<VnInput
:label="t('basicData.multiplier')"
v-model="data.stemMultiplier"
/>
<VnSelectDialog
:label="t('basicData.generic')"
v-model="data.genericFk"
:options="itemsWithNameOptions"
option-value="id"
option-label="name"
map-options
hide-selected
action-icon="filter_alt"
>
<template #form>
<FilterItemForm
url="Items/withName"
@item-selected="data.genericFk = $event"
/>
</template>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption> #{{ scope.opt?.id }} </QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectDialog>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnSelectDialog
:label="t('basicData.intrastat')"
v-model="data.intrastatFk"
:options="intrastatsOptions"
option-value="id"
option-label="description"
map-options
hide-selected
>
<template #form>
<CreateIntrastatForm
@on-data-saved="
(_, requestResponse) =>
onIntrastatCreated(requestResponse, data)
"
/>
</template>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.description }}</QItemLabel>
<QItemLabel caption> #{{ scope.opt?.id }} </QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectDialog>
<div class="col">
<VnSelect
:label="t('basicData.expense')"
v-model="data.expenseFk"
:options="expensesOptions"
option-value="id"
option-label="name"
hide-selected
map-options
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnInput
:label="t('basicData.weightByPiece')"
v-model.number="data.weightByPiece"
:min="0"
type="number"
/>
<VnInput
:label="t('basicData.boxUnits')"
v-model.number="data.packingOut"
:min="0"
type="number"
/>
<VnInput
:label="t('basicData.recycledPlastic')"
v-model.number="data.recycledPlastic"
:min="0"
type="number"
/>
<VnInput
:label="t('basicData.nonRecycledPlastic')"
v-model.number="data.nonRecycledPlastic"
:min="0"
type="number"
/>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<QCheckbox v-model="data.isActive" :label="t('basicData.isActive')" />
<QCheckbox v-model="data.hasKgPrice" :label="t('basicData.hasKgPrice')" />
<div>
<QCheckbox
v-model="data.isFragile"
:label="t('basicData.isFragile')"
class="q-mr-sm"
/>
<QIcon name="info" class="cursor-pointer" size="xs">
<QTooltip max-width="300px">
{{ t('basicData.isFragileTooltip') }}
</QTooltip>
</QIcon>
</div>
<div>
<QCheckbox
v-model="data.isPhotoRequested"
:label="t('basicData.isPhotoRequested')"
class="q-mr-sm"
/>
<QIcon name="info" class="cursor-pointer" size="xs">
<QTooltip>
{{ t('basicData.isPhotoRequestedTooltip') }}
</QTooltip>
</QIcon>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<QInput
:label="t('basicData.description')"
type="textarea"
v-model="data.description"
fill-input
/>
</VnRow>
</template>
</FormModel>
</template>

View File

@ -1,7 +1,17 @@
<script setup> <script setup>
import VnCard from 'components/common/VnCard.vue'; import VnCard from 'components/common/VnCard.vue';
import ItemDescriptor from './ItemDescriptor.vue'; import ItemDescriptor from './ItemDescriptor.vue';
import ItemListFilter from '../ItemListFilter.vue';
</script> </script>
<template> <template>
<VnCard data-key="Item" base-url="Items" :descriptor="ItemDescriptor" /> <VnCard
data-key="Item"
base-url="Items"
:descriptor="ItemDescriptor"
:filter-panel="ItemListFilter"
search-data-key="ItemList"
search-url="Items/filter"
searchbar-label="searchbar.label"
searchbar-info="searchbar.info"
/>
</template> </template>

View File

@ -2,7 +2,7 @@
import { onMounted, computed, onUnmounted, reactive, ref } from 'vue'; import { onMounted, computed, onUnmounted, reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { dateRange } from 'src/filters';
import EntryDescriptorProxy from 'src/pages/Entry/Card/EntryDescriptorProxy.vue'; import EntryDescriptorProxy from 'src/pages/Entry/Card/EntryDescriptorProxy.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue';
@ -29,28 +29,19 @@ const exprBuilder = (param, value) => {
case 'landed': case 'landed':
return { return {
'tr.landed': { 'tr.landed': {
between: getDateRange(value), between: dateRange(value),
}, },
}; };
} }
}; };
const dateRange = reactive({ const datedRange = reactive({
from: null, from: null,
to: null, to: null,
}); });
const getDateRange = (val) => {
const minHour = new Date(val);
minHour.setHours(0, 0, 0, 0);
const maxHour = new Date(val);
maxHour.setHours(23, 59, 59, 59);
return [minHour, maxHour];
};
const from = computed({ const from = computed({
get: () => dateRange.from, get: () => datedRange.from,
set: (val) => { set: (val) => {
updateFrom(val); updateFrom(val);
updateFilter(); updateFilter();
@ -58,7 +49,7 @@ const from = computed({
}); });
const to = computed({ const to = computed({
get: () => dateRange.to, get: () => datedRange.to,
set: (val) => { set: (val) => {
updateTo(val); updateTo(val);
updateFilter(); updateFilter();
@ -173,17 +164,17 @@ const fetchItemLastEntries = async () => {
const updateFrom = async (date) => { const updateFrom = async (date) => {
date.setHours(0, 0, 0, 0); date.setHours(0, 0, 0, 0);
dateRange.from = date.toISOString(); datedRange.from = date.toISOString();
}; };
const updateTo = async (date) => { const updateTo = async (date) => {
date.setHours(23, 59, 59, 59); date.setHours(23, 59, 59, 59);
dateRange.to = date.toISOString(); datedRange.to = date.toISOString();
}; };
const updateFilter = async () => { const updateFilter = async () => {
arrayData.store.userFilter.where.landed = { arrayData.store.userFilter.where.landed = {
between: [dateRange.from, dateRange.to], between: [datedRange.from, datedRange.to],
}; };
await fetchItemLastEntries(); await fetchItemLastEntries();
}; };

View File

@ -0,0 +1,279 @@
<script setup>
import { onMounted, ref, computed, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import FetchData from 'components/FetchData.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import { toDateFormat } from 'src/filters/date.js';
import { dashIfEmpty } from 'src/filters';
import { useArrayData } from 'src/composables/useArrayData';
import useNotify from 'src/composables/useNotify.js';
import { useVnConfirm } from 'composables/useVnConfirm';
import axios from 'axios';
import { useStateStore } from 'stores/useStateStore';
const stateStore = useStateStore();
const route = useRoute();
const { t } = useI18n();
const { notify } = useNotify();
const { openConfirmationModal } = useVnConfirm();
const rowsSelected = ref([]);
const parkingsOptions = ref([]);
const shelvingsOptions = ref([]);
const exprBuilder = (param, value) => {
switch (param) {
case 'parking':
case 'shelving':
case 'label':
case 'packing':
case 'itemFk':
return { [param]: value };
}
};
const params = reactive({ itemFk: route.params.id });
const arrayData = useArrayData('ItemShelvings', {
url: 'ItemShelvingPlacementSupplyStocks',
userParams: params,
exprBuilder: exprBuilder,
});
const rows = computed(() => arrayData.store.data || []);
const applyColumnFilter = async (col) => {
try {
const paramKey = col.columnFilter?.filterParamKey || col.field;
params[paramKey] = col.columnFilter.filterValue;
await arrayData.addFilter({ filter: null, params });
} catch (err) {
console.error('Error applying column filter', err);
}
};
const getInputEvents = (col) => {
return col.columnFilter.type === 'select'
? { 'update:modelValue': () => applyColumnFilter(col) }
: {
'keyup.enter': () => applyColumnFilter(col),
};
};
const columns = computed(() => [
{
label: t('shelvings.created'),
name: 'created',
field: 'created',
align: 'left',
sortable: true,
columnFilter: null,
format: (val) => toDateFormat(val),
},
{
label: t('shelvings.item'),
name: 'item',
field: 'itemFk',
align: 'left',
sortable: true,
columnFilter: null,
},
{
label: t('shelvings.concept'),
name: 'concept',
align: 'left',
sortable: true,
columnFilter: null,
},
{
label: t('shelvings.parking'),
name: 'parking',
field: 'parking',
align: 'left',
sortable: true,
format: (val) => dashIfEmpty(val),
columnFilter: {
component: VnSelect,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
options: parkingsOptions.value,
'option-value': 'code',
'option-label': 'code',
dense: true,
},
},
},
{
label: t('shelvings.shelving'),
name: 'shelving',
field: 'shelving',
align: 'left',
sortable: true,
format: (val) => dashIfEmpty(val),
columnFilter: {
component: VnSelect,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
options: shelvingsOptions.value,
'option-value': 'code',
'option-label': 'code',
dense: true,
},
},
},
{
label: t('shelvings.label'),
name: 'label',
align: 'left',
sortable: true,
format: (_, row) => (row.stock / row.packing).toFixed(2),
columnFilter: {
component: VnInput,
type: 'text',
filterParamKey: 'label',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
},
{
label: t('shelvings.packing'),
field: 'packing',
name: 'packing',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val),
},
]);
const totalLabels = computed(() =>
rows.value.reduce((acc, row) => acc + row.stock / row.packing, 0).toFixed(2)
);
const removeLines = async () => {
try {
const itemShelvingIds = rowsSelected.value.map((row) => row.itemShelvingFk);
await axios.post('ItemShelvings/deleteItemShelvings', { itemShelvingIds });
rowsSelected.value = [];
notify('shelvings.shelvingsRemoved', 'positive');
await arrayData.fetch({ append: false });
} catch (err) {
console.error('Error removing lines', err);
}
};
onMounted(async () => {
await arrayData.fetch({ append: false });
});
</script>
<template>
<FetchData
url="parkings"
:filter="{ fields: ['code'], order: 'code ASC' }"
auto-load
@on-fetch="(data) => (parkingsOptions = data)"
/>
<FetchData
url="shelvings"
:filter="{ fields: ['code'], order: 'code ASC' }"
auto-load
@on-fetch="(data) => (shelvingsOptions = data)"
/>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#st-data">
<div class="q-pa-md q-mr-lg q-ma-xs" style="border: 2px solid #222">
<QCardSection horizontal>
<span class="text-weight-bold text-subtitle1 text-center full-width">
{{ t('shelvings.total') }}
</span>
</QCardSection>
<QCardSection class="column items-center" horizontal>
<div>
<span class="details-label"
>{{ t('shelvings.totalLabels') }}
</span>
<span>: {{ totalLabels }}</span>
</div></QCardSection
>
</div>
</Teleport>
<Teleport to="#st-actions">
<QBtn
color="primary"
icon="delete"
:disabled="!rowsSelected.length"
@click="
openConfirmationModal(
t('shelvings.removeConfirmTitle'),
t('shelvings.removeConfirmSubtitle'),
removeLines
)
"
>
<QTooltip>
{{ t('shelvings.removeLines') }}
</QTooltip>
</QBtn>
</Teleport>
</template>
<QPage class="column items-center q-pa-md">
<QTable
:rows="rows"
:columns="columns"
row-key="id"
:pagination="{ rowsPerPage: 0 }"
class="full-width q-mt-md"
selection="multiple"
v-model:selected="rowsSelected"
:no-data-label="t('globals.noResults')"
>
<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.columnFilter"
v-model="col.columnFilter.filterValue"
v-bind="col.columnFilter.attrs"
v-on="col.columnFilter.event(col)"
dense
/>
</QTd>
</QTr>
</template>
<template #body-cell-concept="{ row }">
<QTd @click.stop>
<span class="link">{{ row.longName }}</span>
<ItemDescriptorProxy :id="row.itemFk" />
</QTd>
</template>
</QTable>
</QPage>
</template>

View File

@ -1 +1,191 @@
<template>Item tags (CREAR CUANDO SE DESARROLLE EL MODULO DE ITEMS)</template> <script setup>
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CrudModel from 'components/CrudModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FetchData from 'components/FetchData.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import axios from 'axios';
const route = useRoute();
const { t } = useI18n();
const itemTagsRef = ref(null);
const tagOptions = ref([]);
const valueOptionsMap = ref(new Map());
const getSelectedTagValues = async (tag) => {
try {
if (!tag.tagFk && tag.tag.isFree) return;
const filter = {
fields: ['value'],
order: 'value ASC',
};
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get(`Tags/${tag.tagFk}/filterValue`, {
params,
});
valueOptionsMap.value.set(tag.tagFk, data);
} catch (err) {
console.error('Error getting selected tag values');
}
};
const onItemTagsFetched = async (itemTags) => {
(itemTags || []).forEach((tag) => {
getSelectedTagValues(tag);
});
};
const handleTagSelected = (rows, index, tag) => {
rows[index].tag = tag;
rows[index].tagFk = tag.id;
rows[index].value = null;
getSelectedTagValues(rows[index]);
};
const getHighestPriority = (rows) => {
let max = 0;
rows.forEach((tag) => {
if (tag.priority > max) max = tag.priority;
});
return max + 1;
};
const insertTag = (rows) => {
itemTagsRef.value.insert();
itemTagsRef.value.formData[itemTagsRef.value.formData.length - 1].priority =
getHighestPriority(rows);
};
</script>
<template>
<FetchData
url="Tags"
:filter="{ fields: ['id', 'name', 'isFree', 'sourceTable'] }"
@on-fetch="(data) => (tagOptions = data)"
auto-load
/>
<div class="full-width flex justify-center">
<QPage class="card-width q-pa-lg">
<CrudModel
ref="itemTagsRef"
data-key="ItemTags"
model="ItemTags"
url="ItemTags"
update-url="Tags/onSubmit"
:data-required="{
$index: undefined,
itemFk: route.params.id,
priority: undefined,
tag: {
isFree: undefined,
value: undefined,
name: undefined,
},
tagFk: undefined,
}"
:default-remove="false"
:filter="{
fields: ['id', 'itemFk', 'tagFk', 'value', 'priority'],
where: { itemFk: route.params.id },
order: 'priority ASC',
include: {
relation: 'tag',
scope: {
fields: ['id', 'name', 'isFree', 'sourceTable'],
},
},
}"
auto-load
@on-fetch="onItemTagsFetched"
>
<template #body="{ rows, validate }">
<QCard class="q-pl-lg q-py-md">
<VnRow
v-for="(row, index) in rows"
:key="index"
class="row q-gutter-md q-mb-md"
>
<VnSelect
:label="t('itemTags.tag')"
:options="tagOptions"
:model-value="row.tag"
option-label="name"
hide-selected
@update:model-value="
($event) => handleTagSelected(rows, index, $event)
"
:required="true"
:rules="validate('itemTag.tagFk')"
/>
<VnSelect
v-if="row.tag?.isFree === false"
:key="row.tagFk"
:label="t('Value')"
v-model="row.value"
:options="valueOptionsMap.get(row.tagFk)"
option-label="value"
option-value="value"
emit-value
use-input
class="col"
:is-clearable="false"
:required="false"
:rules="validate('itemTag.tagFk')"
/>
<VnInput
v-else-if="
row.tag?.isFree || row.tag?.isFree == undefined
"
v-model="row.value"
:label="t('itemTags.value')"
:is-clearable="false"
style="width: 100%"
/>
<VnInput
:label="t('itemTags.relevancy')"
type="number"
v-model="row.priority"
:required="true"
:rules="validate('itemTag.priority')"
/>
<div class="col-1 row justify-center items-center">
<QIcon
@click="itemTagsRef.remove([row])"
class="fill-icon-on-hover"
color="primary"
name="delete"
size="sm"
>
<QTooltip>
{{ t('itemTags.removeTag') }}
</QTooltip>
</QIcon>
</div>
</VnRow>
<VnRow>
<QIcon
@click="insertTag(rows)"
class="cursor-pointer"
:disable="!validRow"
color="primary"
name="add"
size="sm"
>
<QTooltip>
{{ t('itemTags.addTag') }}
</QTooltip>
</QIcon>
</VnRow>
</QCard>
</template>
</CrudModel>
</QPage>
</div>
</template>

View File

@ -309,9 +309,9 @@ const addRow = () => {
const lastItemCopy = JSON.parse( const lastItemCopy = JSON.parse(
JSON.stringify(fixedPrices.value[fixedPrices.value.length - 1]) JSON.stringify(fixedPrices.value[fixedPrices.value.length - 1])
); );
const { id, ...restOfItem } = lastItemCopy; delete lastItemCopy.id;
fixedPricesOriginalData.value.push(restOfItem); fixedPricesOriginalData.value.push(lastItemCopy);
fixedPrices.value.push(restOfItem); fixedPrices.value.push(lastItemCopy);
}; };
const openEditTableCellDialog = () => { const openEditTableCellDialog = () => {

View File

@ -457,6 +457,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
:limit="12" :limit="12"
:expr-builder="exprBuilder" :expr-builder="exprBuilder"
:user-params="params" :user-params="params"
:keep-opts="['userParams']"
:offset="50" :offset="50"
auto-load auto-load
> >

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { ref, computed, onMounted, onBeforeMount, watch } from 'vue'; import { ref, computed, onMounted, onBeforeMount, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue'; import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
@ -9,7 +9,7 @@ import VnInput from 'src/components/common/VnInput.vue';
import ItemRequestDenyForm from './ItemRequestDenyForm.vue'; import ItemRequestDenyForm from './ItemRequestDenyForm.vue';
import ItemRequestFilter from './ItemRequestFilter.vue'; import ItemRequestFilter from './ItemRequestFilter.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import VnSelect from 'components/common/VnSelect.vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import { toDateFormat } from 'src/filters/date'; import { toDateFormat } from 'src/filters/date';
@ -21,7 +21,7 @@ import axios from 'axios';
const { t } = useI18n(); const { t } = useI18n();
const { notify } = useNotify(); const { notify } = useNotify();
const stateStore = useStateStore(); const stateStore = useStateStore();
const workersOptions = ref([]);
let filterParams = ref({}); let filterParams = ref({});
const denyFormRef = ref(null); const denyFormRef = ref(null);
const denyRequestId = ref(null); const denyRequestId = ref(null);
@ -147,6 +147,7 @@ const confirmRequest = async (request) => {
const params = { const params = {
itemFk: request.itemFk, itemFk: request.itemFk,
quantity: request.saleQuantity, quantity: request.saleQuantity,
attenderFk: request.attenderFk,
}; };
const { data } = await axios.post( const { data } = await axios.post(
@ -206,6 +207,13 @@ onBeforeMount(() => {
</script> </script>
<template> <template>
<FetchData
url="Workers"
:filter="{ where: { role: 'buyer' } }"
order="id"
@on-fetch="(data) => (workersOptions = data)"
auto-load
/>
<template v-if="stateStore.isHeaderMounted()"> <template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar"> <Teleport to="#searchbar">
<VnSearchbar <VnSearchbar
@ -278,8 +286,14 @@ onBeforeMount(() => {
</template> </template>
<template #body-cell-attender="{ row }"> <template #body-cell-attender="{ row }">
<QTd> <QTd>
<QBtn flat dense color="primary"> {{ row.attenderName }}</QBtn> <VnSelect
<WorkerDescriptorProxy :id="row.attenderFk" /> v-model="row.attenderFk"
:options="workersOptions"
hide-selected
option-label="firstName"
option-value="id"
dense
/>
</QTd> </QTd>
</template> </template>
<template #body-cell-item="{ row }"> <template #body-cell-item="{ row }">

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { dateRange } from 'src/filters';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
@ -44,15 +44,6 @@ const exprBuilder = (param, value) => {
} }
}; };
const dateRange = (value) => {
const minHour = new Date(value);
minHour.setHours(0, 0, 0, 0);
const maxHour = new Date(value);
maxHour.setHours(23, 59, 59, 59);
return [minHour, maxHour];
};
const add = (paramsObj, key) => { const add = (paramsObj, key) => {
if (paramsObj[key] === undefined) { if (paramsObj[key] === undefined) {
paramsObj[key] = 1; paramsObj[key] = 1;
@ -201,6 +192,7 @@ const decrement = (paramsObj, key) => {
v-model="params.from" v-model="params.from"
@update:model-value="searchFn()" @update:model-value="searchFn()"
is-outlined is-outlined
emit-date-format
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -211,6 +203,7 @@ const decrement = (paramsObj, key) => {
v-model="params.to" v-model="params.to"
@update:model-value="searchFn()" @update:model-value="searchFn()"
is-outlined is-outlined
emit-date-format
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>

View File

@ -7,8 +7,7 @@ import VnLv from 'src/components/ui/VnLv.vue';
import CardList from 'src/components/ui/CardList.vue'; import CardList from 'src/components/ui/CardList.vue';
import ItemTypeSummary from 'src/pages/ItemType/Card/ItemTypeSummary.vue'; import ItemTypeSummary from 'src/pages/ItemType/Card/ItemTypeSummary.vue';
import ItemTypeFilter from 'src/pages/ItemType/ItemTypeFilter.vue'; import ItemTypeFilter from 'src/pages/ItemType/ItemTypeFilter.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import ItemTypeSearchbar from '../ItemType/ItemTypeSearchbar.vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
@ -63,13 +62,7 @@ const exprBuilder = (param, value) => {
<template> <template>
<template v-if="stateStore.isHeaderMounted()"> <template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar"> <Teleport to="#searchbar">
<VnSearchbar <ItemTypeSearchbar />
data-key="ItemTypeList"
url="ItemTypes"
:label="t('Search item type')"
:info="t('Search itemType by id, name or code')"
:expr-builder="exprBuilder"
/>
</Teleport> </Teleport>
<Teleport to="#actions-append"> <Teleport to="#actions-append">
<div class="row q-gutter-x-sm"> <div class="row q-gutter-x-sm">
@ -132,11 +125,3 @@ const exprBuilder = (param, value) => {
</QTooltip> </QTooltip>
</QPageSticky> </QPageSticky>
</template> </template>
<i18n>
es:
New item type: Nueva familia
Name: Nombre
Search item type: Buscar familia
Search itemType by id, name or code: Buscar familia por id, nombre o código
</i18n>

View File

@ -1,3 +1,17 @@
shelvings:
created: Created
item: Item
concept: Concept
parking: Parking
shelving: Shelving
label: Label
packing: Packing
total: Total
totalLabels: Total labels
removeLines: Remove selected lines
shelvingsRemoved: ItemShelvings removed
removeConfirmTitle: Selected lines will be deleted
removeConfirmSubtitle: Are you sure you want to continue?
itemDiary: itemDiary:
date: Date date: Date
id: Id id: Id
@ -11,6 +25,30 @@ itemDiary:
showBefore: Show what's before the inventory showBefore: Show what's before the inventory
since: Since since: Since
warehouse: Warehouse warehouse: Warehouse
basicData:
type: Type
reference: Reference
relevancy: Relevancy
stems: Stems
multiplier: Multiplier
generic: Generic
intrastat: Intrastat
expense: Expense
weightByPiece: Weight/Piece
boxUnits: Units/Box
recycledPlastic: Recycled plastic
nonRecycledPlastic: Non recycled plastic
description: Description
isActive: Active
hasKgPrice: Price in kg
isFragile: Fragile
isFragileTooltip: Is shown at website, app that this item cannot travel (wreath, palms, ...)
isPhotoRequested: Do photo
isPhotoRequestedTooltip: This item does need a photo
createIntrastatForm:
title: New intrastat
identifier: Identifier
description: Description
tax: tax:
country: Country country: Country
class: Class class: Class
@ -34,3 +72,12 @@ lastEntries:
package: Package package: Package
freight: Freight freight: Freight
comission: Comission comission: Comission
itemTags:
removeTag: Remove tag
addTag: Add tag
tag: Tag
value: Value
relevancy: Relevancy
searchbar:
label: Search item
info: Search by item id

View File

@ -1,3 +1,17 @@
shelvings:
created: Creado
item: Artículo
concept: Concepto
parking: Parking
shelving: Matrícula
label: Etiqueta
packing: Packing
total: Total
totalLabels: Total etiquetas
removeLines: Eliminar líneas seleccionadas
shelvingsRemoved: Carros eliminados
removeConfirmTitle: Las líneas seleccionadas serán eliminadas
removeConfirmSubtitle: ¿Seguro que quieres continuar?
itemDiary: itemDiary:
date: Fecha date: Fecha
id: Id id: Id
@ -11,6 +25,30 @@ itemDiary:
showBefore: Mostrar lo anterior al inventario showBefore: Mostrar lo anterior al inventario
since: Desde since: Desde
warehouse: Almacén warehouse: Almacén
basicData:
type: Tipo
reference: Referencia
relevancy: Relevancia
stems: Tallos
multiplier: Multiplicador
generic: Genérico
intrastat: Intrastat
expense: Gasto
weightByPiece: Peso (gramos)/tallo
boxUnits: Unidades/caja
recycledPlastic: Plástico reciclado
nonRecycledPlastic: Plástico no reciclado
description: Descripción
isActive: Activo
hasKgPrice: Precio en kg
isFragile: Frágil
isFragileTooltip: Se muestra en la web app, que este artículo no puede viajar (coronas, palmas, ...)
isPhotoRequested: Hacer foto
isPhotoRequestedTooltip: Este artículo necesita una foto
createIntrastatForm:
title: Nuevo intrastat
identifier: Identificador
description: Descripción
tax: tax:
country: País country: País
class: Clase class: Clase
@ -34,3 +72,12 @@ lastEntries:
package: Embalaje package: Embalaje
freight: Porte freight: Porte
comission: Comisión comission: Comisión
itemTags:
removeTag: Quitar etiqueta
addTag: Añadir etiqueta
tag: Etiqueta
value: Valor
relevancy: Relevancia
searchbar:
label: Buscar artículo
info: Buscar por id de artículo

View File

@ -1,12 +1,20 @@
<script setup> <script setup>
import VnCard from 'components/common/VnCard.vue'; import VnCard from 'components/common/VnCard.vue';
import ItemTypeDescriptor from 'src/pages/ItemType/Card/ItemTypeDescriptor.vue'; import ItemTypeDescriptor from 'src/pages/ItemType/Card/ItemTypeDescriptor.vue';
import ItemTypeFilter from 'src/pages/ItemType/ItemTypeFilter.vue';
import ItemTypeSearchbar from '../ItemTypeSearchbar.vue';
</script> </script>
<template> <template>
<VnCard <VnCard
data-key="ItemTypeSummary" data-key="ItemTypeSummary"
base-url="ItemTypes" base-url="ItemTypes"
:descriptor="ItemTypeDescriptor" :descriptor="ItemTypeDescriptor"
/> :filter-panel="ItemTypeFilter"
search-data-key="ItemTypeList"
search-url="ItemTypes"
>
<template #searchbar>
<ItemTypeSearchbar />
</template>
</VnCard>
</template> </template>

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