Merge branch 'branch-PR-2' of https://gitea.verdnatura.es/hyervoni/salix-front-mindshore into hyervoni-branch-PR-2

This commit is contained in:
Alex Moreno 2024-01-03 14:16:26 +01:00
commit 5994b4e08e
112 changed files with 7806 additions and 778 deletions

View File

@ -0,0 +1,99 @@
<script setup>
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import FormModelPopup from './FormModelPopup.vue';
const emit = defineEmits(['onDataSaved']);
const { t } = useI18n();
const bankEntityFormData = reactive({
name: null,
bic: null,
countryFk: null,
id: null,
});
const countriesFilter = {
fields: ['id', 'country', 'code'],
};
const countriesOptions = ref([]);
const onDataSaved = (data) => {
emit('onDataSaved', data);
};
</script>
<template>
<FetchData
url="Countries"
:filter="countriesFilter"
auto-load
@on-fetch="(data) => (countriesOptions = data)"
/>
<FormModelPopup
url-create="bankEntities"
model="bankEntity"
:title="t('title')"
:subtitle="t('subtitle')"
:form-initial-data="bankEntityFormData"
@on-data-saved="onDataSaved($event)"
>
<template #form-inputs="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QInput
:label="t('name')"
v-model="data.name"
:rules="validate('bankEntity.name')"
/>
</div>
<div class="col">
<QInput
:label="t('swift')"
v-model="data.bic"
:rules="validate('bankEntity.bic')"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('country')"
v-model="data.countryFk"
:options="countriesOptions"
option-value="id"
option-label="country"
hide-selected
:rules="validate('bankEntity.countryFk')"
/>
</div>
<div class="col">
<QInput :label="t('id')" v-model="data.id" />
</div>
</VnRow>
</template>
</FormModelPopup>
</template>
<i18n>
en:
title: New bank entity
subtitle: Please, ensure you put the correct data!
name: Name *
swift: Swift *
country: Country
id: Entity code
es:
title: Nueva entidad bancaria
subtitle: ¡Por favor, asegúrate de poner los datos correctos!
name: Nombre *
swift: Swift *
country: País
id: Código de la entidad
</i18n>

View File

@ -0,0 +1,100 @@
<script setup>
import { onMounted, reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FormModel from 'components/FormModel.vue';
const emit = defineEmits(['onDataSaved']);
const $props = defineProps({
parentId: {
type: Number,
default: null,
},
});
const { t } = useI18n();
const departmentChildData = reactive({
name: null,
});
const closeButton = ref(null);
const isLoading = ref(false);
const onDataSaved = () => {
emit('onDataSaved');
closeForm();
};
const closeForm = () => {
if (closeButton.value) closeButton.value.click();
};
onMounted(() => {
if ($props.parentId) departmentChildData.parentId = $props.parentId;
});
</script>
<template>
<FormModel
:form-initial-data="departmentChildData"
:observe-form-changes="false"
:default-actions="false"
url-create="departments/createChild"
@on-data-saved="onDataSaved()"
>
<template #form="{ data }">
<span ref="closeButton" class="close-icon" v-close-popup>
<QIcon name="close" size="sm" />
</span>
<h1 class="title">{{ t('New department') }}</h1>
<VnRow class="row q-gutter-md q-mb-md" style="min-width: 250px">
<div class="col">
<VnInput :label="t('Name')" v-model="data.name" />
</div>
</VnRow>
<div class="q-mt-lg row justify-end">
<QBtn
:label="t('globals.cancel')"
type="reset"
color="primary"
flat
class="q-ml-sm"
:disabled="isLoading"
:loading="isLoading"
v-close-popup
/>
<QBtn
:label="t('globals.save')"
type="submit"
color="primary"
:disabled="isLoading"
:loading="isLoading"
/>
</div>
</template>
</FormModel>
</template>
<style lang="scss" scoped>
.close-icon {
position: absolute;
top: 20px;
right: 20px;
cursor: pointer;
}
.title {
font-size: 17px;
font-weight: bold;
line-height: 20px;
}
</style>
<i18n>
es:
Name: Nombre
New department: Nuevo departamento
</i18n>

View File

@ -0,0 +1,72 @@
<script setup>
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FormModelPopup from './FormModelPopup.vue';
const emit = defineEmits(['onDataSaved']);
const { t } = useI18n();
const cityFormData = reactive({
name: null,
provinceFk: null,
});
const provincesOptions = ref([]);
const onDataSaved = () => {
emit('onDataSaved');
};
</script>
<template>
<FetchData
@on-fetch="(data) => (provincesOptions = data)"
auto-load
url="Provinces"
/>
<FormModelPopup
:title="t('New city')"
:subtitle="t('Please, ensure you put the correct data!')"
:form-initial-data="cityFormData"
url-create="towns"
model="city"
@on-data-saved="onDataSaved($event)"
>
<template #form-inputs="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput
:label="t('Name')"
v-model="data.name"
:rules="validate('city.name')"
/>
</div>
<div class="col">
<VnSelectFilter
:label="t('Province')"
:options="provincesOptions"
hide-selected
option-label="name"
option-value="id"
v-model="data.provinceFk"
:rules="validate('city.provinceFk')"
/>
</div>
</VnRow>
</template>
</FormModelPopup>
</template>
<i18n>
es:
New city: Nueva ciudad
Please, ensure you put the correct data!: ¡Por favor, asegúrese de poner los datos correctos!
Name: Nombre
Province: Provincia
</i18n>

View File

@ -0,0 +1,138 @@
<script setup>
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import VnInput from 'src/components/common/VnInput.vue';
import CreateNewCityForm from './CreateNewCityForm.vue';
import CreateNewProvinceForm from './CreateNewProvinceForm.vue';
import VnSelectCreate from 'components/common/VnSelectCreate.vue';
import FormModelPopup from './FormModelPopup.vue';
const emit = defineEmits(['onDataSaved']);
const { t } = useI18n();
const postcodeFormData = reactive({
code: null,
countryFk: null,
provinceFk: null,
townFk: null,
});
const townsFetchDataRef = ref(null);
const provincesFetchDataRef = ref(null);
const countriesOptions = ref([]);
const provincesOptions = ref([]);
const townsLocationOptions = ref([]);
const onDataSaved = () => {
emit('onDataSaved');
};
const onCityCreated = async () => {
await townsFetchDataRef.value.fetch();
};
const onProvinceCreated = async () => {
await provincesFetchDataRef.value.fetch();
};
</script>
<template>
<FetchData
ref="townsFetchDataRef"
@on-fetch="(data) => (townsLocationOptions = data)"
auto-load
url="Towns/location"
/>
<FetchData
ref="provincesFetchDataRef"
@on-fetch="(data) => (provincesOptions = data)"
auto-load
url="Provinces"
/>
<FetchData
@on-fetch="(data) => (countriesOptions = data)"
auto-load
url="Countries"
/>
<FormModelPopup
url-create="postcodes"
model="postcode"
:title="t('New postcode')"
:subtitle="t('Please, ensure you put the correct data!')"
:form-initial-data="postcodeFormData"
@on-data-saved="onDataSaved($event)"
>
<template #form-inputs="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput
:label="t('Postcode')"
v-model="data.code"
:rules="validate('postcode.code')"
/>
</div>
<div class="col">
<VnSelectCreate
:label="t('City')"
:options="townsLocationOptions"
v-model="data.townFk"
hide-selected
option-label="name"
option-value="id"
:rules="validate('postcode.city')"
:roles-allowed-to-create="['deliveryAssistant']"
>
<template #form>
<CreateNewCityForm @on-data-saved="onCityCreated($event)" />
</template>
</VnSelectCreate>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-xl">
<div class="col">
<VnSelectCreate
:label="t('Province')"
:options="provincesOptions"
hide-selected
option-label="name"
option-value="id"
v-model="data.provinceFk"
:rules="validate('postcode.provinceFk')"
:roles-allowed-to-create="['deliveryAssistant']"
>
<template #form>
<CreateNewProvinceForm
@on-data-saved="onProvinceCreated($event)"
/>
</template>
</VnSelectCreate>
</div>
<div class="col">
<VnSelectFilter
:label="t('Country')"
:options="countriesOptions"
hide-selected
option-label="country"
option-value="id"
v-model="data.countryFk"
:rules="validate('postcode.countryFk')"
/>
</div> </VnRow
></template>
</FormModelPopup>
</template>
<i18n>
es:
New postcode: Nuevo código postal
Please, ensure you put the correct data!: ¡Por favor, asegúrese de poner los datos correctos!
City: Ciudad
Province: Provincia
Country: País
Postcode: Código postal
</i18n>

View File

@ -0,0 +1,72 @@
<script setup>
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FormModelPopup from './FormModelPopup.vue';
const emit = defineEmits(['onDataSaved']);
const { t } = useI18n();
const provinceFormData = reactive({
name: null,
autonomyFk: null,
});
const autonomiesOptions = ref([]);
const onDataSaved = () => {
emit('onDataSaved');
};
</script>
<template>
<FetchData
@on-fetch="(data) => (autonomiesOptions = data)"
auto-load
url="Autonomies"
/>
<FormModelPopup
:title="t('New province')"
:subtitle="t('Please, ensure you put the correct data!')"
url-create="provinces"
model="province"
:form-initial-data="provinceFormData"
@on-data-saved="onDataSaved($event)"
>
<template #form-inputs="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput
:label="t('Name')"
v-model="data.name"
:rules="validate('province.name')"
/>
</div>
<div class="col">
<VnSelectFilter
:label="t('Autonomy')"
:options="autonomiesOptions"
hide-selected
option-label="name"
option-value="id"
v-model="data.autonomyFk"
:rules="validate('province.autonomyFk')"
/>
</div>
</VnRow>
</template>
</FormModelPopup>
</template>
<i18n>
es:
New province: Nueva provincia
Please, ensure you put the correct data!: ¡Por favor, asegúrese de poner los datos correctos!
Name: Nombre
Autonomy: Autonomía
</i18n>

View File

@ -27,6 +27,10 @@ const $props = defineProps({
type: String, type: String,
default: '', default: '',
}, },
params: {
type: Object,
default: null,
}
}); });
const emit = defineEmits(['onFetch']); const emit = defineEmits(['onFetch']);
@ -46,7 +50,7 @@ async function fetch() {
if ($props.limit) filter.limit = $props.limit; if ($props.limit) filter.limit = $props.limit;
const { data } = await axios.get($props.url, { const { data } = await axios.get($props.url, {
params: { filter: JSON.stringify(filter) }, params: { filter: JSON.stringify(filter), ...$props.params },
}); });
emit('onFetch', data); emit('onFetch', data);

View File

@ -55,9 +55,13 @@ const $props = defineProps({
description: description:
'Esto se usa principalmente para permitir guardar sin hacer cambios (Útil para la feature de clonar ya que en este caso queremos poder guardar de primeras)', 'Esto se usa principalmente para permitir guardar sin hacer cambios (Útil para la feature de clonar ya que en este caso queremos poder guardar de primeras)',
}, },
mapper: {
type: Function,
default: null,
},
}); });
const emit = defineEmits(['onFetch']); const emit = defineEmits(['onFetch', 'onDataSaved']);
defineExpose({ defineExpose({
save, save,
@ -83,8 +87,9 @@ onUnmounted(() => {
const isLoading = ref(false); const isLoading = ref(false);
// Si elegimos observar los cambios del form significa que inicialmente las actions estaran deshabilitadas // Si elegimos observar los cambios del form significa que inicialmente las actions estaran deshabilitadas
const isResetting = ref(false);
const hasChanges = ref(!$props.observeFormChanges); const hasChanges = ref(!$props.observeFormChanges);
const originalData = ref({...$props.formInitialData}); const originalData = ref({ ...$props.formInitialData });
const formData = computed(() => state.get($props.model)); const formData = computed(() => state.get($props.model));
const formUrl = computed(() => $props.url); const formUrl = computed(() => $props.url);
@ -92,7 +97,8 @@ const startFormWatcher = () => {
watch( watch(
() => formData.value, () => formData.value,
(val) => { (val) => {
if (val) hasChanges.value = true; hasChanges.value = !isResetting.value && val;
isResetting.value = false;
}, },
{ deep: true } { deep: true }
); );
@ -114,19 +120,21 @@ async function fetch() {
} }
async function save() { async function save() {
if (!hasChanges.value) { if ($props.observeFormChanges && !hasChanges.value) {
notify('globals.noChanges', 'negative'); notify('globals.noChanges', 'negative');
return; return;
} }
isLoading.value = true; isLoading.value = true;
try { try {
const body = $props.mapper ? $props.mapper(formData.value) : formData.value;
if ($props.urlCreate) { if ($props.urlCreate) {
await axios.post($props.urlCreate, formData.value); await axios.post($props.urlCreate, body);
notify('globals.dataCreated', 'positive'); notify('globals.dataCreated', 'positive');
} else { } else {
await axios.patch($props.urlUpdate || $props.url, formData.value); await axios.patch($props.urlUpdate || $props.url, body);
} }
emit('onDataSaved', formData.value);
} catch (err) { } catch (err) {
notify('errors.create', 'negative'); notify('errors.create', 'negative');
} }
@ -143,6 +151,7 @@ function reset() {
emit('onFetch', state.get($props.model)); emit('onFetch', state.get($props.model));
if ($props.observeFormChanges) { if ($props.observeFormChanges) {
hasChanges.value = false; hasChanges.value = false;
isResetting.value = true;
} }
} }
@ -168,11 +177,14 @@ watch(formUrl, async () => {
}); });
</script> </script>
<template> <template>
<QBanner v-if="$props.observeFormChanges && hasChanges" class="text-white bg-warning"> <QBanner
v-if="$props.observeFormChanges && hasChanges"
class="text-white bg-warning full-width"
>
<QIcon name="warning" size="md" class="q-mr-md" /> <QIcon name="warning" size="md" class="q-mr-md" />
<span>{{ t('globals.changesToSave') }}</span> <span>{{ t('globals.changesToSave') }}</span>
</QBanner> </QBanner>
<div class="column items-center"> <div class="column items-center full-width">
<QForm <QForm
v-if="formData" v-if="formData"
@submit="save" @submit="save"
@ -219,6 +231,7 @@ watch(formUrl, async () => {
:showing="isLoading" :showing="isLoading"
:label="t('globals.pleaseWait')" :label="t('globals.pleaseWait')"
color="primary" color="primary"
style="min-width: 100%"
/> />
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -0,0 +1,105 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FormModel from 'components/FormModel.vue';
const emit = defineEmits(['onDataSaved']);
const $props = defineProps({
title: {
type: String,
default: '',
},
subtitle: {
type: String,
default: '',
},
url: {
type: String,
default: '',
},
model: {
type: String,
default: '',
},
filter: {
type: Object,
default: null,
},
urlCreate: {
type: String,
default: null,
},
formInitialData: {
type: Object,
default: () => {},
},
});
const { t } = useI18n();
const closeButton = ref(null);
const isLoading = ref(false);
const onDataSaved = () => {
emit('onDataSaved');
closeForm();
};
const closeForm = () => {
if (closeButton.value) closeButton.value.click();
};
</script>
<template>
<FormModel
:form-initial-data="formInitialData"
:observe-form-changes="false"
:default-actions="false"
:url-create="urlCreate"
:model="model"
@on-data-saved="onDataSaved()"
>
<template #form="{ data, validate }">
<span
ref="closeButton"
class="absolute cursor-pointer"
style="top: 20px; right: 20px"
v-close-popup
>
<QIcon name="close" size="sm" />
</span>
<h1 class="title">{{ title }}</h1>
<p>{{ subtitle }}</p>
<slot name="form-inputs" :data="data" :validate="validate" />
<div class="q-mt-lg row justify-end">
<QBtn
:label="t('globals.save')"
type="submit"
color="primary"
:disabled="isLoading"
:loading="isLoading"
/>
<QBtn
:label="t('globals.cancel')"
type="reset"
color="primary"
flat
class="q-ml-sm"
:disabled="isLoading"
:loading="isLoading"
v-close-popup
/>
</div>
</template>
</FormModel>
</template>
<style lang="scss" scoped>
.title {
font-size: 17px;
font-weight: bold;
line-height: 20px;
}
</style>

View File

@ -1,7 +1,9 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useDialogPluginComponent } from 'quasar';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useDialogPluginComponent } from 'quasar';
import VnInput from 'src/components/common/VnInput.vue';
const props = defineProps({ const props = defineProps({
data: { data: {
@ -52,7 +54,7 @@ async function confirm() {
{{ t('The notification will be sent to the following address') }} {{ t('The notification will be sent to the following address') }}
</QCardSection> </QCardSection>
<QCardSection class="q-pt-none"> <QCardSection class="q-pt-none">
<QInput dense v-model="address" rounded outlined autofocus /> <VnInput v-model="address" is-outlined autofocus />
</QCardSection> </QCardSection>
<QCardActions align="right"> <QCardActions align="right">
<QBtn :label="t('globals.cancel')" color="primary" flat v-close-popup /> <QBtn :label="t('globals.cancel')" color="primary" flat v-close-popup />

View File

@ -0,0 +1,140 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { ref, computed } from 'vue';
import FetchData from 'components/FetchData.vue';
import { useState } from 'src/composables/useState';
import axios from 'axios';
const $props = defineProps({
allColumns: {
type: Array,
default: () => [],
},
tableCode: {
type: String,
default: '',
},
labelsTraductionsPath: {
type: String,
default: '',
},
});
const emit = defineEmits(['onConfigSaved']);
const state = useState();
const { t } = useI18n();
const popupProxyRef = ref(null);
const user = state.getUser();
const initialUserConfigViewData = ref(null);
const userConfigFilter = {
where: {
tableCode: $props.tableCode,
userFk: user.id,
},
};
const formattedCols = ref([]);
const areAllChecksMarked = computed(() => {
return formattedCols.value.every((col) => col.active);
});
const setUserConfigViewData = (data) => {
initialUserConfigViewData.value = data;
if (data.length === 0) return;
formattedCols.value = $props.allColumns.map((col) => {
// Importante: El name de las columnas de la tabla debe conincidir con el name de las variables que devuelve la view config
const obj = {
name: col,
active: data[0].configuration[col],
};
return obj;
});
emitSavedConfig();
};
const toggleMarkAll = (val) => {
formattedCols.value.forEach((col) => (col.active = val));
};
const saveConfig = async () => {
try {
const data = {
id: initialUserConfigViewData.value[0].id,
userFk: 9,
tableCode: $props.tableCode,
configuration: {},
};
formattedCols.value.forEach((col) => {
data.configuration[col.name] = col.active;
});
await axios.patch('UserConfigViews', data);
emitSavedConfig();
popupProxyRef.value.hide();
} catch (err) {
console.error('Error saving user view config');
}
};
const emitSavedConfig = () => {
const filteredCols = formattedCols.value.filter((col) => col.active);
const mappedCols = filteredCols.map((col) => col.name);
emit('onConfigSaved', mappedCols);
};
</script>
<template>
<fetch-data
v-if="user"
url="UserConfigViews"
:filter="userConfigFilter"
@on-fetch="(data) => setUserConfigViewData(data)"
auto-load
/>
<QBtn color="primary" icon="view_column">
<QPopupProxy ref="popupProxyRef">
<QCard class="column q-pa-md">
<QIcon name="info" size="sm" class="info-icon">
<QTooltip>{{ t('Check the columns you want to see') }}</QTooltip>
</QIcon>
<span class="text-body1 q-mb-sm">{{ t('Visible columns') }}</span>
<QCheckbox
:label="t('Tick all')"
:model-value="areAllChecksMarked"
@update:model-value="toggleMarkAll($event)"
class="q-mb-sm"
/>
<div
v-if="allColumns.length !== 0 && formattedCols.length !== 0"
class="checks-layout"
>
<QCheckbox
v-for="(col, index) in allColumns"
:key="index"
:label="t(`${$props.labelsTraductionsPath + '.' + col}`)"
v-model="formattedCols[index].active"
/>
</div>
<QBtn class="full-width q-mt-md" color="primary" @click="saveConfig()">{{
t('globals.save')
}}</QBtn>
</QCard>
</QPopupProxy>
</QBtn>
</template>
<style lang="scss" scoped>
.info-icon {
position: absolute;
top: 20px;
right: 20px;
}
.checks-layout {
display: grid;
grid-template-columns: repeat(3, 200px);
}
</style>

View File

@ -0,0 +1,51 @@
<script setup>
import { computed } from 'vue';
const emit = defineEmits(['update:modelValue', 'update:options']);
const $props = defineProps({
modelValue: {
type: [String, Number],
default: null,
},
isOutlined: {
type: Boolean,
default: false,
},
});
const value = computed({
get() {
return $props.modelValue;
},
set(value) {
emit('update:modelValue', value);
},
});
const styleAttrs = computed(() => {
return $props.isOutlined
? {
dense: true,
outlined: true,
rounded: true,
}
: {};
});
</script>
<template>
<QInput
ref="vnInputRef"
v-model="value"
v-bind="{ ...$attrs, ...styleAttrs }"
type="text"
>
<template v-if="$slots.prepend" #prepend>
<slot name="prepend" />
</template>
<template v-if="$slots.append" #append>
<slot name="append" />
</template>
</QInput>
</template>

View File

@ -10,7 +10,11 @@ const props = defineProps({
readonly: { readonly: {
type: Boolean, type: Boolean,
default: false, default: false,
} },
isOutlined: {
type: Boolean,
default: false,
},
}); });
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
const value = computed({ const value = computed({
@ -36,6 +40,16 @@ const formatDate = (dateString) => {
date.getDate() date.getDate()
)}`; )}`;
}; };
const styleAttrs = computed(() => {
return props.isOutlined
? {
dense: true,
outlined: true,
rounded: true,
}
: {};
});
</script> </script>
<template> <template>
@ -44,7 +58,7 @@ const formatDate = (dateString) => {
rounded rounded
readonly readonly
:model-value="toDate(value)" :model-value="toDate(value)"
v-bind="$attrs" v-bind="{ ...$attrs, ...styleAttrs }"
> >
<template #append> <template #append>
<QIcon name="event" class="cursor-pointer"> <QIcon name="event" class="cursor-pointer">

View File

@ -0,0 +1,71 @@
<script setup>
import { ref, computed } from 'vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import { useRole } from 'src/composables/useRole';
const emit = defineEmits(['update:modelValue']);
const $props = defineProps({
modelValue: {
type: [String, Number, Object],
default: null,
},
options: {
type: Array,
default: () => [],
},
rolesAllowedToCreate: {
type: Array,
default: () => ['developer'],
},
});
const role = useRole();
const showForm = ref(false);
const value = computed({
get() {
return $props.modelValue;
},
set(value) {
emit('update:modelValue', value);
},
});
const isAllowedToCreate = computed(() => {
return role.hasAny($props.rolesAllowedToCreate);
});
const toggleForm = () => {
showForm.value = !showForm.value;
};
</script>
<template>
<VnSelectFilter v-model="value" :options="options" v-bind="$attrs">
<template v-if="isAllowedToCreate" #append>
<QIcon
@click.stop.prevent="toggleForm()"
name="add"
size="19px"
class="add-icon"
/>
<QDialog v-model="showForm" transition-show="scale" transition-hide="scale">
<slot name="form" />
</QDialog>
</template>
<template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName">
<slot :name="slotName" v-bind="slotData" :key="slotName" />
</template>
</VnSelectFilter>
</template>
<style lang="scss" scoped>
.add-icon {
cursor: pointer;
background-color: $primary;
border-radius: 50px;
}
</style>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, toRefs, watch, computed } from 'vue'; import { ref, toRefs, computed } from 'vue';
const emit = defineEmits(['update:modelValue', 'update:options']); const emit = defineEmits(['update:modelValue', 'update:options']);
const $props = defineProps({ const $props = defineProps({
@ -24,16 +24,11 @@ const $props = defineProps({
default: true, default: true,
}, },
}); });
const { optionLabel, options } = toRefs($props);
const myOptions = ref([]);
const myOptionsOriginal = ref([]);
const vnSelectRef = ref(null);
function setOptions(data) { const { optionLabel } = toRefs($props);
myOptions.value = JSON.parse(JSON.stringify(data)); const myOptions = ref([]);
myOptionsOriginal.value = JSON.parse(JSON.stringify(data)); const myOptionsOriginal = computed(() => $props.options);
} const vnSelectRef = ref(null);
setOptions(options.value);
const filter = (val, options) => { const filter = (val, options) => {
const search = val.toString().toLowerCase(); const search = val.toString().toLowerCase();
@ -69,10 +64,6 @@ const filterHandler = (val, update) => {
); );
}; };
watch(options, (newValue) => {
setOptions(newValue);
});
const value = computed({ const value = computed({
get() { get() {
return $props.modelValue; return $props.modelValue;
@ -105,8 +96,8 @@ const value = computed({
size="18px" size="18px"
/> />
</template> </template>
<template v-for="(_, slotName) in $slots" #[slotName]="slotData"> <template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName">
<slot :name="slotName" v-bind="slotData ?? {}" /> <slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" />
</template> </template>
</QSelect> </QSelect>
</template> </template>

View File

@ -1,7 +1,9 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { useDialogPluginComponent } from 'quasar';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useDialogPluginComponent } from 'quasar';
import VnInput from 'src/components/common/VnInput.vue';
const { dialogRef, onDialogOK } = useDialogPluginComponent(); const { dialogRef, onDialogOK } = useDialogPluginComponent();
const { t, availableLocales } = useI18n(); const { t, availableLocales } = useI18n();
@ -117,24 +119,10 @@ async function send() {
/> />
</QCardSection> </QCardSection>
<QCardSection class="q-pb-xs"> <QCardSection class="q-pb-xs">
<QInput <VnInput :label="t('Phone')" v-model="phone" is-outlined />
:label="t('Phone')"
v-model="phone"
rounded
outlined
autofocus
dense
/>
</QCardSection> </QCardSection>
<QCardSection class="q-pb-xs"> <QCardSection class="q-pb-xs">
<QInput <VnInput v-model="subject" :label="t('Subject')" is-outlined />
:label="t('Subject')"
v-model="subject"
rounded
outlined
autofocus
dense
/>
</QCardSection> </QCardSection>
<QCardSection class="q-mb-md" q-input> <QCardSection class="q-mb-md" q-input>
<QInput <QInput
@ -198,7 +186,7 @@ async function send() {
en: en:
CustomerDefaultLanguage: This customer uses <strong>{locale}</strong> as their default language CustomerDefaultLanguage: This customer uses <strong>{locale}</strong> as their default language
templates: templates:
pendingPayment: 'Your order is pending of payment. pendingPayment: 'Your order is pending of payment.
Please, enter the website and make the payment with a credit card. Thank you.' Please, enter the website and make the payment with a credit card. Thank you.'
minAmount: 'A minimum amount of 50 (VAT excluded) is required for your order minAmount: 'A minimum amount of 50 (VAT excluded) is required for your order
{ orderId } of { shipped } to receive it without additional shipping costs.' { orderId } of { shipped } to receive it without additional shipping costs.'
@ -215,7 +203,7 @@ es:
Subject: Asunto Subject: Asunto
Message: Mensaje Message: Mensaje
templates: templates:
pendingPayment: 'Su pedido está pendiente de pago. pendingPayment: 'Su pedido está pendiente de pago.
Por favor, entre en la página web y efectue el pago con tarjeta. Muchas gracias.' Por favor, entre en la página web y efectue el pago con tarjeta. Muchas gracias.'
minAmount: 'Es necesario un importe mínimo de 50 (Sin IVA) en su pedido minAmount: 'Es necesario un importe mínimo de 50 (Sin IVA) en su pedido
{ orderId } del día { shipped } para recibirlo sin portes adicionales.' { orderId } del día { shipped } para recibirlo sin portes adicionales.'
@ -249,7 +237,7 @@ pt:
Subject: Assunto Subject: Assunto
Message: Mensagem Message: Mensagem
templates: templates:
pendingPayment: 'Seu pedido está pendente de pagamento. pendingPayment: 'Seu pedido está pendente de pagamento.
Por favor, acesse o site e faça o pagamento com cartão. Muito obrigado.' Por favor, acesse o site e faça o pagamento com cartão. Muito obrigado.'
minAmount: 'É necessário um valor mínimo de 50 (sem IVA) em seu pedido minAmount: 'É necessário um valor mínimo de 50 (sem IVA) em seu pedido
{ orderId } do dia { shipped } para recebê-lo sem custos de envio adicionais.' { orderId } do dia { shipped } para recebê-lo sem custos de envio adicionais.'

View File

@ -24,11 +24,28 @@ const props = defineProps({
type: Boolean, type: Boolean,
default: true, default: true,
}, },
unremovableParams: {
type: Array,
required: false,
default: () => [],
description:
'Algunos filtros vienen con parametros de búsqueda por default y necesitan tener si o si un valor, por eso de ser necesario, esta prop nos sirve para saber que filtros podemos remover y cuales no',
},
exprBuilder: {
type: Function,
default: null,
},
hiddenTags: {
type: Array,
default: () => [],
},
}); });
const emit = defineEmits(['refresh', 'clear', 'search']); const emit = defineEmits(['refresh', 'clear', 'search', 'init']);
const arrayData = useArrayData(props.dataKey); const arrayData = useArrayData(props.dataKey, {
exprBuilder: props.exprBuilder,
});
const store = arrayData.store; const store = arrayData.store;
const userParams = ref({}); const userParams = ref({});
@ -37,9 +54,11 @@ onMounted(() => {
if (Object.keys(store.userParams).length > 0) { if (Object.keys(store.userParams).length > 0) {
userParams.value = JSON.parse(JSON.stringify(store.userParams)); userParams.value = JSON.parse(JSON.stringify(store.userParams));
} }
emit('init', { params: userParams.value });
}); });
const isLoading = ref(false); const isLoading = ref(false);
async function search() { async function search() {
isLoading.value = true; isLoading.value = true;
const params = { ...userParams.value }; const params = { ...userParams.value };
@ -63,27 +82,35 @@ async function reload() {
} }
async function clearFilters() { async function clearFilters() {
userParams.value = {};
isLoading.value = true; isLoading.value = true;
await arrayData.applyFilter({ params: {} });
if (!props.showAll) store.data = [];
isLoading.value = false;
// Filtrar los params no removibles
const removableFilters = Object.keys(userParams.value).filter((param) =>
props.unremovableParams.includes(param)
);
const newParams = {};
// Conservar solo los params que no son removibles
for (const key of removableFilters) {
newParams[key] = userParams.value[key];
}
userParams.value = { ...newParams }; // Actualizar los params con los removibles
await arrayData.applyFilter({ params: userParams.value });
if (!props.showAll) {
store.data = [];
}
isLoading.value = false;
emit('clear'); emit('clear');
} }
const tags = computed(() => { const tags = computed(() => {
const params = []; return Object.entries(userParams.value)
.filter(([key, value]) => value && !(props.hiddenTags || []).includes(key))
for (const param in userParams.value) { .map(([key, value]) => ({
if (!userParams.value[param]) continue; label: key,
params.push({ value: value,
label: param, }));
value: userParams.value[param],
});
}
return params;
}); });
async function remove(key) { async function remove(key) {
@ -156,7 +183,7 @@ function formatValue(value) {
class="text-dark" class="text-dark"
color="primary" color="primary"
icon="label" icon="label"
removable :removable="!unremovableParams.includes(chip.label)"
size="sm" size="sm"
v-for="chip of tags" v-for="chip of tags"
> >

View File

@ -31,7 +31,7 @@ const props = defineProps({
default: null, default: null,
}, },
order: { order: {
type: String, type: [String, Array],
default: '', default: '',
}, },
limit: { limit: {
@ -149,7 +149,7 @@ async function onLoad(...params) {
v-if="props.skeleton && props.autoLoad && !store.data" v-if="props.skeleton && props.autoLoad && !store.data"
class="card-list q-gutter-y-md" class="card-list q-gutter-y-md"
> >
<QCard class="card" v-for="$index in $props.limit" :key="$index"> <QCard class="card" v-for="$index in props.limit" :key="$index">
<QItem v-ripple class="q-pa-none items-start cursor-pointer q-hoverable"> <QItem v-ripple class="q-pa-none items-start cursor-pointer q-hoverable">
<QItemSection class="q-pa-md"> <QItemSection class="q-pa-md">
<QSkeleton type="rect" class="q-mb-md" square /> <QSkeleton type="rect" class="q-mb-md" square />

View File

@ -1,8 +1,12 @@
<script setup> <script setup>
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
import { useArrayData } from 'composables/useArrayData';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import VnInput from 'src/components/common/VnInput.vue';
import { useArrayData } from 'composables/useArrayData';
const quasar = useQuasar(); const quasar = useQuasar();
const props = defineProps({ const props = defineProps({
@ -49,6 +53,14 @@ const props = defineProps({
type: Object, type: Object,
default: null, default: null,
}, },
staticParams: {
type: Array,
default: () => [],
},
exprBuilder: {
type: Function,
default: null,
},
}); });
const router = useRouter(); const router = useRouter();
@ -65,8 +77,11 @@ onMounted(() => {
}); });
async function search() { async function search() {
const staticParams = Object.entries(store.userParams)
.filter(([key, value]) => value && (props.staticParams || []).includes(key));
await arrayData.applyFilter({ await arrayData.applyFilter({
params: { params: {
...Object.fromEntries(staticParams),
search: searchText.value, search: searchText.value,
}, },
}); });
@ -86,7 +101,7 @@ async function search() {
<template> <template>
<QForm @submit="search"> <QForm @submit="search">
<QInput <VnInput
id="searchbar" id="searchbar"
v-model="searchText" v-model="searchText"
:placeholder="props.label" :placeholder="props.label"
@ -113,7 +128,7 @@ async function search() {
<QTooltip>{{ props.info }}</QTooltip> <QTooltip>{{ props.info }}</QTooltip>
</QIcon> </QIcon>
</template> </template>
</QInput> </VnInput>
</QForm> </QForm>
</template> </template>

View File

@ -0,0 +1,158 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { useQuasar } from 'quasar';
import CreateDepartmentChild from '../CreateDepartmentChild.vue';
import axios from 'axios';
import useNotify from 'src/composables/useNotify.js';
const quasar = useQuasar();
const { t } = useI18n();
const router = useRouter();
const { notify } = useNotify();
const treeRef = ref(null);
const showCreateNodeFormVal = ref(false);
const creationNodeSelectedId = ref(null);
const expanded = ref([]);
const nodes = ref([{ id: null, name: t('Departments'), sons: true, children: [{}] }]);
const fetchedChildrensSet = ref(new Set());
const onNodeExpanded = (nodeKeysArray) => {
// Verificar si el nodo ya fue expandido
if (!fetchedChildrensSet.value.has(nodeKeysArray.at(-1))) {
fetchedChildrensSet.value.add(nodeKeysArray.at(-1));
fetchNodeLeaves(nodeKeysArray.at(-1)); // Llamar a la función para obtener los nodos hijos
}
};
const fetchNodeLeaves = async (nodeKey) => {
try {
const node = treeRef.value.getNodeByKey(nodeKey);
if (!node || node.sons === 0) return;
const params = { parentId: node.id };
const response = await axios.get('/departments/getLeaves', { params });
// Si hay datos en la respuesta y tiene hijos, agregarlos al nodo actual
if (response.data) {
node.children = response.data;
node.children.forEach((node) => {
if (node.sons) node.children = [{}];
});
}
} catch (err) {
console.error('Error fetching department leaves');
throw new Error();
}
};
const removeNode = (node) => {
quasar
.dialog({
title: 'Are you sure you want to delete it?',
message: 'Delete department',
ok: {
push: true,
color: 'primary',
},
cancel: true,
})
.onOk(async () => {
try {
await axios.post(`/Departments/${node.id}/removeChild`, node.id);
notify('department.departmentRemoved', 'positive');
await fetchNodeLeaves(node.parentFk);
} catch (err) {
console.log('Error removing department');
}
});
};
const showCreateNodeForm = (nodeId) => {
showCreateNodeFormVal.value = true;
creationNodeSelectedId.value = nodeId;
};
const onNodeCreated = async () => {
await fetchNodeLeaves(creationNodeSelectedId.value);
};
const redirectToDepartmentSummary = (id) => {
if (!id) return;
router.push({ name: 'DepartmentSummary', params: { id: id } });
};
</script>
<template>
<QCard class="full-width" style="max-width: 800px">
<QTree
ref="treeRef"
:nodes="nodes"
node-key="id"
label-key="name"
v-model:expanded="expanded"
@update:expanded="onNodeExpanded($event)"
>
<template #default-header="prop">
<div
class="row justify-between full-width q-pr-md cursor-pointer"
@click.stop="redirectToDepartmentSummary(prop.node.id)"
>
<span class="text-uppercase">
{{ prop.node.name }}
</span>
<div class="row justify-between" style="max-width: max-content">
<QIcon
v-if="prop.node.id"
name="delete"
color="primary"
size="sm"
class="q-pr-xs cursor-pointer"
@click.stop="removeNode(prop.node)"
>
<QTooltip>
{{ t('Remove') }}
</QTooltip>
</QIcon>
<QIcon
name="add"
color="primary"
size="sm"
class="cursor-pointer"
@click.stop="showCreateNodeForm(prop.node.id)"
>
<QTooltip>
{{ t('Create') }}
</QTooltip>
</QIcon>
</div>
</div>
</template>
</QTree>
<QDialog
v-model="showCreateNodeFormVal"
transition-show="scale"
transition-hide="scale"
>
<CreateDepartmentChild
:parent-id="creationNodeSelectedId"
@on-data-saved="onNodeCreated()"
/>
</QDialog>
</QCard>
</template>
<i18n>
es:
Departments: Departamentos
Remove: Quitar
Create: Crear
Are you sure you want to delete it?: ¿Seguro que quieres eliminarlo?
Delete department: Eliminar departamento
</i18n>

View File

@ -90,7 +90,7 @@ export function useArrayData(key, userOptions) {
Object.assign(params, userParams); Object.assign(params, userParams);
store.isLoading = true; store.isLoading = true;
store.currentFilter = params;
const response = await axios.get(store.url, { const response = await axios.get(store.url, {
signal: canceller.signal, signal: canceller.signal,
params, params,
@ -132,6 +132,7 @@ export function useArrayData(key, userOptions) {
async function applyFilter({ filter, params }) { async function applyFilter({ filter, params }) {
if (filter) store.userFilter = filter; if (filter) store.userFilter = filter;
store.filter = {};
if (params) store.userParams = Object.assign({}, params); if (params) store.userParams = Object.assign({}, params);
await fetch({ append: false }); await fetch({ append: false });
@ -155,7 +156,9 @@ export function useArrayData(key, userOptions) {
delete store.userParams[param]; delete store.userParams[param];
delete params[param]; delete params[param];
if (store.filter?.where) { if (store.filter?.where) {
delete store.filter.where[Object.keys(exprBuilder ? exprBuilder(param) : param)[0]]; delete store.filter.where[
Object.keys(exprBuilder ? exprBuilder(param) : param)[0]
];
if (Object.keys(store.filter.where).length === 0) { if (Object.keys(store.filter.where).length === 0) {
delete store.filter.where; delete store.filter.where;
} }

View File

@ -49,7 +49,6 @@ export default {
microsip: 'Open in MicroSIP', microsip: 'Open in MicroSIP',
noSelectedRows: `You don't have any line selected`, noSelectedRows: `You don't have any line selected`,
downloadCSVSuccess: 'CSV downloaded successfully', downloadCSVSuccess: 'CSV downloaded successfully',
// labels compartidos entre vistas
reference: 'Reference', reference: 'Reference',
agency: 'Agency', agency: 'Agency',
wareHouseOut: 'Warehouse Out', wareHouseOut: 'Warehouse Out',
@ -111,6 +110,9 @@ export default {
customers: 'Customers', customers: 'Customers',
list: 'List', list: 'List',
webPayments: 'Web Payments', webPayments: 'Web Payments',
extendedList: 'Extended list',
notifications: 'Notifications',
defaulter: 'Defaulter',
createCustomer: 'Create customer', createCustomer: 'Create customer',
summary: 'Summary', summary: 'Summary',
basicData: 'Basic Data', basicData: 'Basic Data',
@ -204,6 +206,52 @@ export default {
salesPerson: 'Sales person', salesPerson: 'Sales person',
contactChannel: 'Contact channel', contactChannel: 'Contact channel',
}, },
extendedList: {
tableVisibleColumns: {
id: 'Identifier',
name: 'Name',
fi: 'Tax number',
salesPersonFk: 'Salesperson',
credit: 'Credit',
creditInsurance: 'Credit insurance',
phone: 'Phone',
mobile: 'Mobile',
street: 'Street',
countryFk: 'Country',
provinceFk: 'Province',
city: 'City',
postcode: 'Postcode',
email: 'Email',
created: 'Created',
businessTypeFk: 'Business type',
payMethodFk: 'Billing data',
sageTaxTypeFk: 'Sage tax type',
sageTransactionTypeFk: 'Sage tr. type',
isActive: 'Active',
isVies: 'Vies',
isTaxDataChecked: 'Verified data',
isEqualizated: 'Is equalizated',
isFreezed: 'Freezed',
hasToInvoice: 'Invoice',
hasToInvoiceByAddress: 'Invoice by address',
isToBeMailed: 'Mailing',
hasLcr: 'Received LCR',
hasCoreVnl: 'VNL core received',
hasSepaVnl: 'VNL B2B received',
},
},
},
entry: {
pageTitles: {
entries: 'Entries',
list: 'List',
createEntry: 'New entry',
summary: 'Summary',
create: 'Create',
},
list: {
newEntry: 'New entry',
},
}, },
ticket: { ticket: {
pageTitles: { pageTitles: {
@ -548,6 +596,81 @@ export default {
country: 'Country', country: 'Country',
}, },
}, },
order: {
pageTitles: {
order: 'Orders',
orderList: 'List',
create: 'Create',
summary: 'Summary',
basicData: 'Basic Data',
catalog: 'Catalog',
volume: 'Volume',
},
field: {
salesPersonFk: 'Sales Person',
clientFk: 'Client',
isConfirmed: 'Confirmed',
created: 'Created',
landed: 'Landed',
hour: 'Hour',
agency: 'Agency',
total: 'Total',
},
form: {
clientFk: 'Client',
addressFk: 'Address',
landed: 'Landed',
agencyModeFk: 'Agency',
},
list: {
newOrder: 'New Order',
},
summary: {
basket: 'Basket',
nickname: 'Nickname',
company: 'Company',
confirmed: 'Confirmed',
notConfirmed: 'Not confirmed',
created: 'Created',
landed: 'Landed',
phone: 'Phone',
createdFrom: 'Created From',
address: 'Address',
notes: 'Notes',
subtotal: 'Subtotal',
total: 'Total',
vat: 'VAT',
state: 'State',
alias: 'Alias',
items: 'Items',
orderTicketList: 'Order Ticket List',
details: 'Details',
item: 'Item',
description: 'Description',
quantity: 'Quantity',
price: 'Price',
amount: 'Amount',
},
},
department: {
pageTitles: {
basicData: 'Basic data',
department: 'Department',
summary: 'Summary',
},
name: 'Name',
code: 'Code',
chat: 'Chat',
bossDepartment: 'Boss Department',
email: 'Email',
selfConsumptionCustomer: 'Self-consumption customer',
telework: 'Telework',
notifyOnErrors: 'Notify on errors',
worksInProduction: 'Works in production',
hasToRefill: 'Fill in days without physical check-ins',
hasToSendMail: 'Send check-ins by email',
departmentRemoved: 'Department removed',
},
worker: { worker: {
pageTitles: { pageTitles: {
workers: 'Workers', workers: 'Workers',
@ -555,6 +678,8 @@ export default {
basicData: 'Basic data', basicData: 'Basic data',
summary: 'Summary', summary: 'Summary',
notifications: 'Notifications', notifications: 'Notifications',
workerCreate: 'New worker',
department: 'Department',
}, },
list: { list: {
name: 'Name', name: 'Name',
@ -564,6 +689,7 @@ export default {
active: 'Active', active: 'Active',
department: 'Department', department: 'Department',
schedule: 'Schedule', schedule: 'Schedule',
newWorker: 'New worker',
}, },
card: { card: {
workerId: 'Worker ID', workerId: 'Worker ID',
@ -595,6 +721,25 @@ export default {
subscribed: 'Subscribed to the notification', subscribed: 'Subscribed to the notification',
unsubscribed: 'Unsubscribed from the notification', unsubscribed: 'Unsubscribed from the notification',
}, },
create: {
name: 'Name',
lastName: 'Last name',
birth: 'Birth',
fi: 'Fi',
code: 'Worker code',
phone: 'Phone',
postcode: 'Postcode',
province: 'Province',
city: 'City',
street: 'Street',
webUser: 'Web user',
personalEmail: 'Personal email',
company: 'Company',
boss: 'Boss',
payMethods: 'Pay method',
iban: 'IBAN',
bankEntity: 'Swift / BIC',
},
imageNotFound: 'Image not found', imageNotFound: 'Image not found',
}, },
wagon: { wagon: {
@ -670,6 +815,15 @@ export default {
list: 'List', list: 'List',
create: 'Create', create: 'Create',
summary: 'Summary', summary: 'Summary',
basicData: 'Basic data',
fiscalData: 'Fiscal data',
billingData: 'Billing data',
log: 'Log',
accounts: 'Accounts',
contacts: 'Contacts',
addresses: 'Addresses',
consumption: 'Consumption',
agencyTerm: 'Agency agreement',
}, },
list: { list: {
payMethod: 'Pay method', payMethod: 'Pay method',
@ -714,6 +868,10 @@ export default {
create: 'Create', create: 'Create',
summary: 'Summary', summary: 'Summary',
extraCommunity: 'Extra community', extraCommunity: 'Extra community',
travelCreate: 'New travel',
basicData: 'Basic data',
history: 'History',
thermographs: 'Termographs',
}, },
summary: { summary: {
confirmed: 'Confirmed', confirmed: 'Confirmed',

View File

@ -110,6 +110,9 @@ export default {
customers: 'Clientes', customers: 'Clientes',
list: 'Listado', list: 'Listado',
webPayments: 'Pagos Web', webPayments: 'Pagos Web',
extendedList: 'Listado extendido',
notifications: 'Notificaciones',
defaulter: 'Morosos',
createCustomer: 'Crear cliente', createCustomer: 'Crear cliente',
basicData: 'Datos básicos', basicData: 'Datos básicos',
summary: 'Resumen', summary: 'Resumen',
@ -202,6 +205,51 @@ export default {
salesPerson: 'Comercial', salesPerson: 'Comercial',
contactChannel: 'Canal de contacto', contactChannel: 'Canal de contacto',
}, },
extendedList: {
tableVisibleColumns: {
id: 'Identificador',
name: 'Nombre',
fi: 'NIF / CIF',
salesPersonFk: 'Comercial',
credit: 'Crédito',
creditInsurance: 'Crédito asegurado',
phone: 'Teléfono',
mobile: 'Móvil',
street: 'Dirección fiscal',
countryFk: 'País',
provinceFk: 'Provincia',
city: 'Población',
postcode: 'Código postal',
email: 'Email',
created: 'Fecha creación',
businessTypeFk: 'Tipo de negocio',
payMethodFk: 'Forma de pago',
sageTaxTypeFk: 'Tipo de impuesto Sage',
sageTransactionTypeFk: 'Tipo tr. sage',
isActive: 'Activo',
isVies: 'Vies',
isTaxDataChecked: 'Datos comprobados',
isEqualizated: 'Recargo de equivalencias',
isFreezed: 'Congelado',
hasToInvoice: 'Factura',
hasToInvoiceByAddress: 'Factura por consigna',
isToBeMailed: 'Env. emails',
hasLcr: 'Recibido LCR',
hasCoreVnl: 'Recibido core VNL',
hasSepaVnl: 'Recibido B2B VNL',
},
},
},
entry: {
pageTitles: {
entries: 'Entradas',
list: 'Listado',
summary: 'Resumen',
create: 'Crear',
},
list: {
newEntry: 'Nueva entrada',
},
}, },
ticket: { ticket: {
pageTitles: { pageTitles: {
@ -456,6 +504,62 @@ export default {
}, },
}, },
}, },
order: {
pageTitles: {
order: 'Cesta',
orderList: 'Listado',
create: 'Crear',
summary: 'Resumen',
basicData: 'Datos básicos',
catalog: 'Catálogo',
volume: 'Volumen',
},
field: {
salesPersonFk: 'Comercial',
clientFk: 'Cliente',
isConfirmed: 'Confirmada',
created: 'Creado',
landed: 'F. entrega',
hour: 'Hora',
agency: 'Agencia',
total: 'Total',
},
form: {
clientFk: 'Cliente',
addressFk: 'Dirección',
landed: 'F. entrega',
agencyModeFk: 'Agencia',
},
list: {
newOrder: 'Nuevo Pedido',
},
summary: {
basket: 'Cesta',
nickname: 'Alias',
company: 'Empresa',
confirmed: 'Confirmada',
notConfirmed: 'No confirmada',
created: 'Creado',
landed: 'F. entrega',
phone: 'Teléfono',
createdFrom: 'Creado desde',
address: 'Dirección',
notes: 'Notas',
subtotal: 'Subtotal',
total: 'Total',
vat: 'IVA',
state: 'Estado',
alias: 'Alias',
items: 'Items',
orderTicketList: 'Tickets del pedido',
details: 'Detalles',
item: 'Item',
description: 'Descripción',
quantity: 'Cantidad',
price: 'Precio',
amount: 'Monto',
},
},
shelving: { shelving: {
pageTitles: { pageTitles: {
shelving: 'Carros', shelving: 'Carros',
@ -547,6 +651,25 @@ export default {
country: 'País', country: 'País',
}, },
}, },
department: {
pageTitles: {
basicData: 'Basic data',
department: 'Departamentos',
summary: 'Resumen',
},
name: 'Nombre',
code: 'Código',
chat: 'Chat',
bossDepartment: 'Jefe de departamento',
email: 'Email',
selfConsumptionCustomer: 'Cliente autoconsumo',
telework: 'Teletrabaja',
notifyOnErrors: 'Notificar errores',
worksInProduction: 'Pertenece a producción',
hasToRefill: 'Completar días sin registros físicos',
hasToSendMail: 'Enviar fichadas por mail',
departmentRemoved: 'Departamento eliminado',
},
worker: { worker: {
pageTitles: { pageTitles: {
workers: 'Trabajadores', workers: 'Trabajadores',
@ -554,6 +677,8 @@ export default {
basicData: 'Datos básicos', basicData: 'Datos básicos',
summary: 'Resumen', summary: 'Resumen',
notifications: 'Notificaciones', notifications: 'Notificaciones',
workerCreate: 'Nuevo trabajador',
department: 'Departamentos',
}, },
list: { list: {
name: 'Nombre', name: 'Nombre',
@ -563,6 +688,7 @@ export default {
active: 'Activo', active: 'Activo',
department: 'Departamento', department: 'Departamento',
schedule: 'Horario', schedule: 'Horario',
newWorker: 'Nuevo trabajador',
}, },
card: { card: {
workerId: 'ID Trabajador', workerId: 'ID Trabajador',
@ -594,6 +720,25 @@ export default {
subscribed: 'Se ha suscrito a la notificación', subscribed: 'Se ha suscrito a la notificación',
unsubscribed: 'Se ha dado de baja de la notificación', unsubscribed: 'Se ha dado de baja de la notificación',
}, },
create: {
name: 'Nombre',
lastName: 'Apellido',
birth: 'Fecha de nacimiento',
fi: 'DNI/NIF/NIE',
code: 'Código de trabajador',
phone: 'Teléfono',
postcode: 'Código postal',
province: 'Provincia',
city: 'Población',
street: 'Dirección',
webUser: 'Usuario Web',
personalEmail: 'Correo personal',
company: 'Empresa',
boss: 'Jefe',
payMethods: 'Método de pago',
iban: 'IBAN',
bankEntity: 'Swift / BIC',
},
imageNotFound: 'No se ha encontrado la imagen', imageNotFound: 'No se ha encontrado la imagen',
}, },
wagon: { wagon: {
@ -669,6 +814,15 @@ export default {
list: 'Listado', list: 'Listado',
create: 'Crear', create: 'Crear',
summary: 'Resumen', summary: 'Resumen',
basicData: 'Datos básicos',
fiscalData: 'Datos fiscales',
billingData: 'Forma de pago',
log: 'Historial',
accounts: 'Cuentas',
contacts: 'Contactos',
addresses: 'Direcciones',
consumption: 'Consumo',
agencyTerm: 'Acuerdo agencia',
}, },
list: { list: {
payMethod: 'Método de pago', payMethod: 'Método de pago',
@ -713,6 +867,10 @@ export default {
create: 'Crear', create: 'Crear',
summary: 'Resumen', summary: 'Resumen',
extraCommunity: 'Extra comunitarios', extraCommunity: 'Extra comunitarios',
travelCreate: 'Nuevo envío',
basicData: 'Datos básicos',
history: 'Historial',
thermographs: 'Termógrafos',
}, },
summary: { summary: {
confirmed: 'Confirmado', confirmed: 'Confirmado',

View File

@ -3,11 +3,13 @@ import { ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useSession } from 'src/composables/useSession';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnInputDate from "components/common/VnInputDate.vue"; import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import { useSession } from 'src/composables/useSession';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@ -102,14 +104,17 @@ 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"> <div class="col">
<QInput <VnInput
v-model="data.client.name" v-model="data.client.name"
:label="t('claim.basicData.customer')" :label="t('claim.basicData.customer')"
disable disable
/> />
</div> </div>
<div class="col"> <div class="col">
<VnInputDate v-model="data.created" :label="t('claim.basicData.created')" /> <VnInputDate
v-model="data.created"
:label="t('claim.basicData.created')"
/>
</div> </div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
@ -165,7 +170,7 @@ const statesFilter = {
/> />
</div> </div>
<div class="col"> <div class="col">
<QInput <VnInput
v-model="data.rma" v-model="data.rma"
:label="t('claim.basicData.returnOfMaterial')" :label="t('claim.basicData.returnOfMaterial')"
:rules="validate('claim.rma')" :rules="validate('claim.rma')"

View File

@ -1,9 +1,11 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue'; import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
const { t } = useI18n(); const { t } = useI18n();
@ -34,30 +36,31 @@ const states = ref();
</div> </div>
</template> </template>
<template #body="{ params, searchFn }"> <template #body="{ params, searchFn }">
<QList dense> <QList dense class="list">
<QItem> <QItem class="q-my-sm">
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('Customer ID')" :label="t('Customer ID')"
v-model="params.clientFk" v-model="params.clientFk"
lazy-rules lazy-rules
is-outlined
> >
<template #prepend> <template #prepend>
<QIcon name="badge" size="sm"></QIcon> <QIcon name="badge" size="xs"></QIcon> </template
</template> ></VnInput>
</QInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem class="q-mb-sm">
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('Client Name')" :label="t('Client Name')"
v-model="params.clientName" v-model="params.clientName"
lazy-rules lazy-rules
is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem class="q-mb-sm">
<QItemSection v-if="!workers"> <QItemSection v-if="!workers">
<QSkeleton type="QInput" class="full-width" /> <QSkeleton type="QInput" class="full-width" />
</QItemSection> </QItemSection>
@ -72,11 +75,15 @@ const states = ref();
emit-value emit-value
map-options map-options
use-input use-input
hide-selected
dense
outlined
rounded
:input-debounce="0" :input-debounce="0"
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem class="q-mb-sm">
<QItemSection v-if="!workers"> <QItemSection v-if="!workers">
<QSkeleton type="QInput" class="full-width" /> <QSkeleton type="QInput" class="full-width" />
</QItemSection> </QItemSection>
@ -91,11 +98,15 @@ const states = ref();
emit-value emit-value
map-options map-options
use-input use-input
hide-selected
dense
outlined
rounded
:input-debounce="0" :input-debounce="0"
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem class="q-mb-sm">
<QItemSection v-if="!workers"> <QItemSection v-if="!workers">
<QSkeleton type="QInput" class="full-width" /> <QSkeleton type="QInput" class="full-width" />
</QItemSection> </QItemSection>
@ -110,11 +121,15 @@ const states = ref();
emit-value emit-value
map-options map-options
use-input use-input
hide-selected
dense
outlined
rounded
:input-debounce="0" :input-debounce="0"
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-md"> <QItem class="q-mb-sm">
<QItemSection v-if="!states"> <QItemSection v-if="!states">
<QSkeleton type="QInput" class="full-width" /> <QSkeleton type="QInput" class="full-width" />
</QItemSection> </QItemSection>
@ -128,6 +143,10 @@ const states = ref();
option-label="description" option-label="description"
emit-value emit-value
map-options map-options
hide-selected
dense
outlined
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -151,7 +170,11 @@ const states = ref();
</QItem> --> </QItem> -->
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInputDate v-model="params.created" :label="t('Created')" /> <VnInputDate
v-model="params.created"
:label="t('Created')"
is-outlined
/>
</QItemSection> </QItemSection>
</QItem> </QItem>
</QExpansionItem> </QExpansionItem>
@ -160,6 +183,15 @@ const states = ref();
</VnFilterPanel> </VnFilterPanel>
</template> </template>
<style scoped>
.list {
width: 256px;
}
.list * {
max-width: 100%;
}
</style>
<i18n> <i18n>
en: en:
params: params:

View File

@ -96,7 +96,6 @@ function viewDescriptor(id) {
v-for="row of rows" v-for="row of rows"
> >
<template #list-items> <template #list-items>
<VnLv label="ID" :value="row.id" />
<VnLv :label="t('claim.list.customer')" @click.stop> <VnLv :label="t('claim.list.customer')" @click.stop>
<template #value> <template #value>
<span class="link"> <span class="link">

View File

@ -2,10 +2,13 @@
import { ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import axios from 'axios';
import VnPaginate from 'src/components/ui/VnPaginate.vue'; import VnPaginate from 'src/components/ui/VnPaginate.vue';
import { useArrayData } from 'src/composables/useArrayData';
import VnConfirm from 'src/components/ui/VnConfirm.vue'; import VnConfirm from 'src/components/ui/VnConfirm.vue';
import VnInput from 'src/components/common/VnInput.vue';
import { useArrayData } from 'src/composables/useArrayData';
import axios from 'axios';
const quasar = useQuasar(); const quasar = useQuasar();
const { t } = useI18n(); const { t } = useI18n();
@ -65,7 +68,7 @@ async function remove({ id }) {
<QPageSticky expand position="top" :offset="[16, 16]"> <QPageSticky expand position="top" :offset="[16, 16]">
<QCard class="card q-pa-md"> <QCard class="card q-pa-md">
<QForm @submit="submit"> <QForm @submit="submit">
<QInput <VnInput
ref="input" ref="input"
v-model="newRma.code" v-model="newRma.code"
:label="t('claim.rmaList.code')" :label="t('claim.rmaList.code')"

View File

@ -7,6 +7,7 @@ import { useSession } from 'src/composables/useSession';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@ -64,7 +65,7 @@ const filterOptions = {
<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"> <div class="col">
<QInput <VnInput
v-model="data.socialName" v-model="data.socialName"
:label="t('customer.basicData.socialName')" :label="t('customer.basicData.socialName')"
:rules="validate('client.socialName')" :rules="validate('client.socialName')"
@ -87,7 +88,7 @@ const filterOptions = {
</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">
<QInput <VnInput
v-model="data.contact" v-model="data.contact"
:label="t('customer.basicData.contact')" :label="t('customer.basicData.contact')"
:rules="validate('client.contact')" :rules="validate('client.contact')"
@ -95,7 +96,7 @@ const filterOptions = {
/> />
</div> </div>
<div class="col"> <div class="col">
<QInput <VnInput
v-model="data.email" v-model="data.email"
type="email" type="email"
:label="t('customer.basicData.email')" :label="t('customer.basicData.email')"
@ -106,7 +107,7 @@ const filterOptions = {
</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">
<QInput <VnInput
v-model="data.phone" v-model="data.phone"
:label="t('customer.basicData.phone')" :label="t('customer.basicData.phone')"
:rules="validate('client.phone')" :rules="validate('client.phone')"
@ -114,7 +115,7 @@ const filterOptions = {
/> />
</div> </div>
<div class="col"> <div class="col">
<QInput <VnInput
v-model="data.mobile" v-model="data.mobile"
:label="t('customer.basicData.mobile')" :label="t('customer.basicData.mobile')"
:rules="validate('client.mobile')" :rules="validate('client.mobile')"

View File

@ -0,0 +1,269 @@
<script setup>
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import CustomerCreateNewPostcode from 'src/components/CreateNewPostcodeForm.vue';
import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import VnSelectCreate from 'src/components/common/VnSelectCreate.vue';
const { t } = useI18n();
const newClientForm = reactive({
active: true,
name: null,
salesPersonFk: null,
businessTypeFk: null,
fi: null,
socialName: null,
street: null,
postcode: null,
city: null,
provinceFk: null,
countryFk: null,
userName: null,
email: null,
isEqualizated: false,
});
const postcodeFetchDataRef = ref(null);
const workersOptions = ref([]);
const businessTypesOptions = ref([]);
const citiesLocationOptions = ref([]);
const provincesLocationOptions = ref([]);
const countriesOptions = ref([]);
const postcodesOptions = ref([]);
const onPostcodeCreated = async () => {
postcodeFetchDataRef.value.fetch();
};
</script>
<template>
<FetchData
@on-fetch="(data) => (workersOptions = data)"
auto-load
url="Workers/search?departmentCodes"
/>
<FetchData
ref="postcodeFetchDataRef"
url="Postcodes/location"
@on-fetch="(data) => (postcodesOptions = data)"
auto-load
/>
<FetchData
@on-fetch="(data) => (businessTypesOptions = data)"
auto-load
url="BusinessTypes"
/>
<FetchData
@on-fetch="(data) => (citiesLocationOptions = data)"
auto-load
url="Towns/location"
/>
<FetchData
@on-fetch="(data) => (provincesLocationOptions = data)"
auto-load
url="Provinces/location"
/>
<FetchData
@on-fetch="(data) => (countriesOptions = data)"
auto-load
url="Countries"
/>
<QPage>
<QToolbar class="bg-vn-dark">
<div id="st-data"></div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<FormModel
:form-initial-data="newClientForm"
:observe-form-changes="false"
model="client"
url-create="Clients/createWithUser"
>
<template #form="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QInput :label="t('Comercial name')" v-model="data.name" />
</div>
<div class="col">
<VnSelectFilter
:label="t('Salesperson')"
:options="workersOptions"
hide-selected
option-label="name"
option-value="id"
v-model="data.salesPersonFk"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('Business type')"
:options="businessTypesOptions"
hide-selected
option-label="description"
option-value="code"
v-model="data.businessTypeFk"
/>
</div>
<div class="col">
<QInput v-model="data.fi" :label="t('Tax number')" />
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QInput
:label="t('Business name')"
:rules="validate('Client.socialName')"
v-model="data.socialName"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QInput
:label="t('Street')"
:rules="validate('Client.street')"
v-model="data.street"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectCreate
v-model="data.postcode"
:label="t('Postcode')"
:rules="validate('Worker.postcode')"
:roles-allowed-to-create="['deliveryAssistant']"
:options="postcodesOptions"
option-label="code"
option-value="code"
hide-selected
>
<template #form>
<CustomerCreateNewPostcode
@on-data-saved="onPostcodeCreated($event)"
/>
</template>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection v-if="scope.opt">
<QItemLabel>{{ scope.opt.code }}</QItemLabel>
<QItemLabel caption
>{{ scope.opt.code }} -
{{ scope.opt.town.name }} ({{
scope.opt.town.province.name
}},
{{
scope.opt.town.province.country.country
}})</QItemLabel
>
</QItemSection>
</QItem>
</template>
</VnSelectCreate>
</div>
<div class="col">
<!-- ciudades -->
<VnSelectFilter
:label="t('City')"
:options="citiesLocationOptions"
hide-selected
option-label="name"
option-value="name"
v-model="data.city"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt.name }}</QItemLabel>
<QItemLabel caption>
{{
`${scope.opt.name}, ${scope.opt.province.name} (${scope.opt.province.country.country})`
}}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('Province')"
:options="provincesLocationOptions"
hide-selected
option-label="name"
option-value="id"
v-model="data.provinceFk"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{
`${scope.opt.name} (${scope.opt.country.country})`
}}</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</div>
<div class="col">
<VnSelectFilter
:label="t('Country')"
:options="countriesOptions"
hide-selected
option-label="country"
option-value="id"
v-model="data.countryFk"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QInput v-model="data.userName" :label="t('Web user')" />
</div>
<div class="col">
<QInput v-model="data.email" :label="t('Email')" />
</div>
</VnRow>
<QCheckbox
:label="t('Is equalizated')"
v-model="newClientForm.isEqualizated"
/>
</template>
</FormModel>
</QPage>
</template>
<style lang="scss" scoped>
.card {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
grid-gap: 20px;
}
</style>
<i18n>
es:
Comercial name: Nombre comercial
Salesperson: Comercial
Business type: Tipo de negocio
Tax number: NIF / CIF
Business name: Razón social
Street: Dirección fiscal
Postcode: Código postal
City: Población
Province: Provincia
Country: País
Web user: Usuario web
Email: Email
Is equalizated: Recargo de equivalencia
</i18n>

View File

@ -1,9 +1,11 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue'; import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import VnInput from 'src/components/common/VnInput.vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@ -35,31 +37,31 @@ const zones = ref();
</div> </div>
</template> </template>
<template #body="{ params, searchFn }"> <template #body="{ params, searchFn }">
<QList dense> <QList dense class="list">
<QItem> <QItem class="q-my-sm">
<QItemSection> <QItemSection>
<QInput :label="t('FI')" v-model="params.fi" lazy-rules> <VnInput :label="t('FI')" v-model="params.fi" is-outlined>
<template #prepend> <template #prepend>
<QIcon name="badge" size="sm"></QIcon> <QIcon name="badge" size="xs" />
</template> </template>
</QInput> </VnInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem class="q-mb-sm">
<QItemSection> <QItemSection>
<QInput :label="t('Name')" v-model="params.name" lazy-rules /> <VnInput :label="t('Name')" v-model="params.name" is-outlined />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem class="q-mb-sm">
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('Social Name')" :label="t('Social Name')"
v-model="params.socialName" v-model="params.socialName"
lazy-rules is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem class="q-mb-sm">
<QItemSection v-if="!workers"> <QItemSection v-if="!workers">
<QSkeleton type="QInput" class="full-width" /> <QSkeleton type="QInput" class="full-width" />
</QItemSection> </QItemSection>
@ -74,11 +76,15 @@ const zones = ref();
emit-value emit-value
map-options map-options
use-input use-input
hide-selected
dense
outlined
rounded
:input-debounce="0" :input-debounce="0"
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem class="q-mb-sm">
<QItemSection v-if="!provinces"> <QItemSection v-if="!provinces">
<QSkeleton type="QInput" class="full-width" /> <QSkeleton type="QInput" class="full-width" />
</QItemSection> </QItemSection>
@ -92,33 +98,45 @@ const zones = ref();
option-label="name" option-label="name"
emit-value emit-value
map-options map-options
hide-selected
dense
outlined
rounded
:input-debounce="0" :input-debounce="0"
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-md"> <QItem class="q-mb-md">
<QItemSection> <QItemSection>
<QInput :label="t('City')" v-model="params.city" lazy-rules /> <VnInput :label="t('City')" v-model="params.city" is-outlined />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QSeparator /> <QSeparator />
<QExpansionItem :label="t('More options')" expand-separator> <QExpansionItem :label="t('More options')" expand-separator>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput :label="t('Phone')" v-model="params.phone" lazy-rules> <VnInput
:label="t('Phone')"
v-model="params.phone"
is-outlined
>
<template #prepend> <template #prepend>
<QIcon name="phone" size="sm"></QIcon> <QIcon name="phone" size="xs" />
</template> </template>
</QInput> </VnInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput :label="t('Email')" v-model="params.email" lazy-rules> <VnInput
:label="t('Email')"
v-model="params.email"
is-outlined
>
<template #prepend> <template #prepend>
<QIcon name="email" size="sm"></QIcon> <QIcon name="email" size="sm" />
</template> </template>
</QInput> </VnInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
@ -135,15 +153,19 @@ const zones = ref();
option-label="name" option-label="name"
emit-value emit-value
map-options map-options
hide-selected
dense
outlined
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('Postcode')" :label="t('Postcode')"
v-model="params.postcode" v-model="params.postcode"
lazy-rules is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -153,6 +175,15 @@ const zones = ref();
</VnFilterPanel> </VnFilterPanel>
</template> </template>
<style scoped>
.list {
width: 256px;
}
.list * {
max-width: 100%;
}
</style>
<i18n> <i18n>
en: en:
params: params:

View File

@ -28,25 +28,29 @@ function viewSummary(id) {
}, },
}); });
} }
const redirectToCreateView = () => {
router.push({ name: 'CustomerCreate' });
};
</script> </script>
<template> <template>
<template v-if="stateStore.isHeaderMounted()"> <template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar"> <Teleport to="#searchbar">
<VnSearchbar <VnSearchbar
data-key="CustomerList"
:label="t('Search customer')"
:info="t('You can search by customer id or name')" :info="t('You can search by customer id or name')"
:label="t('Search customer')"
data-key="CustomerList"
/> />
</Teleport> </Teleport>
<Teleport to="#actions-append"> <Teleport to="#actions-append">
<div class="row q-gutter-x-sm"> <div class="row q-gutter-x-sm">
<QBtn <QBtn
flat
@click="stateStore.toggleRightDrawer()" @click="stateStore.toggleRightDrawer()"
round
dense dense
flat
icon="menu" icon="menu"
round
> >
<QTooltip bottom anchor="bottom right"> <QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }} {{ t('globals.collapseMenu') }}
@ -55,7 +59,7 @@ function viewSummary(id) {
</div> </div>
</Teleport> </Teleport>
</template> </template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> <QDrawer :width="256" show-if-above side="right" v-model="stateStore.rightDrawer">
<QScrollArea class="fit text-grey-8"> <QScrollArea class="fit text-grey-8">
<CustomerFilter data-key="CustomerList" /> <CustomerFilter data-key="CustomerList" />
</QScrollArea> </QScrollArea>
@ -63,18 +67,18 @@ function viewSummary(id) {
<QPage class="column items-center q-pa-md"> <QPage class="column items-center q-pa-md">
<div class="card-list"> <div class="card-list">
<VnPaginate <VnPaginate
data-key="CustomerList"
url="/Clients/filter"
order="id DESC"
auto-load auto-load
data-key="CustomerList"
order="id DESC"
url="/Clients/filter"
> >
<template #body="{ rows }"> <template #body="{ rows }">
<CardList <CardList
v-for="row of rows" :id="row.id"
:key="row.id" :key="row.id"
:title="row.name" :title="row.name"
:id="row.id"
@click="navigate(row.id)" @click="navigate(row.id)"
v-for="row of rows"
> >
<template #list-items> <template #list-items>
<VnLv :label="t('customer.list.email')" :value="row.email" /> <VnLv :label="t('customer.list.email')" :value="row.email" />
@ -103,6 +107,12 @@ function viewSummary(id) {
</template> </template>
</VnPaginate> </VnPaginate>
</div> </div>
<QPageSticky :offset="[20, 20]">
<QBtn @click="redirectToCreateView()" color="primary" fab icon="add" />
<QTooltip>
{{ t('New client') }}
</QTooltip>
</QPageSticky>
</QPage> </QPage>
</template> </template>
@ -117,4 +127,5 @@ function viewSummary(id) {
es: es:
Search customer: Buscar cliente Search customer: Buscar cliente
You can search by customer id or name: Puedes buscar por id o nombre del cliente You can search by customer id or name: Puedes buscar por id o nombre del cliente
New client: Nuevo cliente
</i18n> </i18n>

View File

@ -0,0 +1,253 @@
<script setup>
import { ref, computed, onBeforeMount } from 'vue';
import { useI18n } from 'vue-i18n';
import { QBtn, QCheckbox } from 'quasar';
import { useArrayData } from 'composables/useArrayData';
import { useStateStore } from 'stores/useStateStore';
import { toCurrency, toDate } from 'filters/index';
import CustomerNotificationsFilter from './CustomerDefaulterFilter.vue';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
const { t } = useI18n();
const stateStore = useStateStore();
const arrayData = ref(null);
onBeforeMount(async () => {
arrayData.value = useArrayData('CustomerDefaulter', {
url: 'Defaulters/filter',
limit: 0,
});
await arrayData.value.fetch({ append: false });
stateStore.rightDrawer = true;
});
const rows = computed(() => arrayData.value.store.data);
const selected = ref([]);
const worderId = ref(0);
const customerId = ref(0);
const tableColumnComponents = {
client: {
component: QBtn,
props: () => ({ flat: true, color: 'blue' }),
event: (prop) => selectClientId(prop.row.clientFk),
},
isWorker: {
component: QCheckbox,
props: (prop) => ({
disable: true,
'model-value': Boolean(prop.value),
}),
event: () => {},
},
salesperson: {
component: QBtn,
props: () => ({ flat: true, color: 'blue' }),
event: (prop) => selectSalespersonId(prop.row.salesPersonFk),
},
country: {
component: 'span',
props: () => {},
event: () => {},
},
paymentMethod: {
component: 'span',
props: () => {},
event: () => {},
},
balance: {
component: 'span',
props: () => {},
event: () => {},
},
author: {
component: QBtn,
props: () => ({ flat: true, color: 'blue' }),
event: (prop) => selectAuthorId(prop.row.workerFk),
},
lastObservation: {
component: 'span',
props: () => {},
event: () => {},
},
date: {
component: 'span',
props: () => {},
event: () => {},
},
credit: {
component: 'span',
props: () => {},
event: () => {},
},
from: {
component: 'span',
props: () => {},
event: () => {},
},
};
const columns = computed(() => {
return [
{
align: 'left',
field: 'clientName',
label: t('Client'),
name: 'client',
},
{
align: 'left',
field: 'isWorker',
label: t('Is worker'),
name: 'isWorker',
},
{
align: 'left',
field: 'salesPersonName',
label: t('Salesperson'),
name: 'salesperson',
},
{
align: 'left',
field: 'country',
label: t('Country'),
name: 'country',
},
{
align: 'left',
field: 'payMethod',
label: t('P. Method'),
name: 'paymentMethod',
},
{
align: 'left',
field: (row) => toCurrency(row.amount),
label: t('Balance D.'),
name: 'balance',
},
{
align: 'left',
field: 'workerName',
label: t('Author'),
name: 'author',
},
{
align: 'left',
field: 'observation',
label: t('Last observation'),
name: 'lastObservation',
},
{
align: 'left',
field: (row) => toDate(row.created),
label: t('L. O. Date'),
name: 'date',
},
{
align: 'left',
field: (row) => toCurrency(row.creditInsurance),
label: t('Credit I.'),
name: 'credit',
},
{
align: 'left',
field: (row) => toDate(row.defaulterSinced),
label: t('From'),
name: 'from',
},
];
});
const selectClientId = (id) => {
worderId.value = 0;
customerId.value = id;
};
const selectSalespersonId = (id) => {
customerId.value = 0;
worderId.value = id;
};
const selectAuthorId = (id) => {
customerId.value = 0;
worderId.value = id;
};
</script>
<template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<CustomerNotificationsFilter data-key="CustomerDefaulter" />
</QScrollArea>
</QDrawer>
<QToolbar class="bg-vn-dark">
<div id="st-data"></div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<QPage class="column items-center q-pa-md">
<QTable
:columns="columns"
:pagination="{ rowsPerPage: 0 }"
:rows="rows"
class="full-width q-mt-md"
hide-bottom
row-key="id"
selection="multiple"
v-model:selected="selected"
>
<template #top>
<div v-if="rows" class="full-width flex justify-end">
{{ `${rows.length} ${t('route.cmr.list.results')}` }}
</div>
</template>
<template #body-cell="props">
<QTd :props="props">
<QTr :props="props" class="cursor-pointer">
<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)"
>
{{ props.value }}
<WorkerDescriptorProxy v-if="worderId" :id="worderId" />
<CustomerDescriptorProxy v-else :id="customerId" />
</component>
</QTr>
</QTd>
</template>
</QTable>
</QPage>
</template>
<style lang="scss" scoped>
.col-content {
border-radius: 4px;
padding: 6px;
}
</style>
<i18n>
es:
Client: Cliente
Is worker: Es trabajador
Salesperson: Comercial
Country: País
P. Method: F. Pago
Balance D.: Saldo V.
Author: Autor
Last observation: Última observación
L. O. Date: Fecha Ú. O.
Credit I.: Crédito A.
From: Desde
</i18n>

View File

@ -0,0 +1,238 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const clients = ref();
const salespersons = ref();
const countries = ref();
const authors = ref();
</script>
<template>
<FetchData @on-fetch="(data) => (clients = data)" auto-load url="Clients" />
<FetchData
:filter="{ where: { role: 'salesPerson' } }"
@on-fetch="(data) => (salespersons = data)"
auto-load
url="Workers/activeWithInheritedRole"
/>
<FetchData @on-fetch="(data) => (countries = data)" auto-load url="Countries" />
<FetchData
@on-fetch="(data) => (authors = data)"
auto-load
url="Workers/activeWithInheritedRole"
/>
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params }">
<QList dense class="list">
<QItem class="q-mb-sm q-mt-sm">
<QItemSection v-if="clients">
<VnSelectFilter
:input-debounce="0"
:label="t('Client')"
:options="clients"
dense
emit-value
hide-selected
map-options
option-label="name"
option-value="clientTypeFk"
outlined
rounded
use-input
v-model="params.clientFk"
/>
</QItemSection>
<QItemSection v-else>
<QSkeleton class="full-width" type="QInput" />
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection v-if="salespersons">
<VnSelectFilter
:input-debounce="0"
:label="t('Salesperson')"
:options="salespersons"
dense
emit-value
hide-selected
map-options
option-label="name"
option-value="id"
outlined
rounded
use-input
v-model="params.salesPersonFk"
/>
</QItemSection>
<QItemSection v-else>
<QSkeleton class="full-width" type="QInput" />
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection v-if="countries">
<VnSelectFilter
:input-debounce="0"
:label="t('Country')"
:options="countries"
dense
emit-value
hide-selected
map-options
option-label="country"
option-value="id"
outlined
rounded
use-input
v-model="params.countryFk"
/>
</QItemSection>
<QItemSection v-else>
<QSkeleton class="full-width" type="QInput" />
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnInput
:label="t('P. Method')"
is-outlined
v-model="params.paymentMethod"
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnInput
:label="t('Balance D.')"
is-outlined
v-model="params.balance"
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection v-if="authors">
<VnSelectFilter
:input-debounce="0"
:label="t('Author')"
:options="authors"
dense
emit-value
hide-selected
map-options
option-label="name"
option-value="id"
outlined
rounded
use-input
v-model="params.workerFk"
/>
</QItemSection>
<QItemSection v-else>
<QSkeleton class="full-width" type="QInput" />
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnInput
:label="t('L. O. Date')"
is-outlined
v-model="params.date"
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnInput
:label="t('Credit I.')"
is-outlined
v-model="params.credit"
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnInputDate
:label="t('From')"
is-outlined
v-model="params.defaulterSinced"
/>
</QItemSection>
</QItem>
<QSeparator />
</QList>
</template>
</VnFilterPanel>
</template>
<style scoped>
.list {
width: 256px;
}
.list * {
max-width: 100%;
}
</style>
<i18n>
en:
params:
clientFk: Client
salesPersonFk: Salesperson
countryFk: Country
paymentMethod: P. Method
balance: Balance D.
workerFk: Author
date: L. O. Date
credit: Credit I.
defaulterSinced: From
es:
params:
clientFk: Cliente
salesPersonFk: Comercial
countryFk: País
paymentMethod: F. Pago
balance: Saldo V.
workerFk: Autor
date: Fecha Ú. O.
credit: Crédito A.
defaulterSinced: Desde
Client: Cliente
Salesperson: Comercial
Country: País
P. Method: F. Pago
Balance D.: Saldo V.
Author: Autor
L. O. Date: Fecha Ú. O.
Credit I.: Crédito A.
From: Desde
</i18n>

View File

@ -0,0 +1,570 @@
<script setup>
import { ref, computed, onBeforeMount, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { QBtn, QIcon } from 'quasar';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
import CustomerExtendedListActions from './CustomerExtendedListActions.vue';
import CustomerExtendedListFilter from './CustomerExtendedListFilter.vue';
import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue';
import { useArrayData } from 'composables/useArrayData';
import { useStateStore } from 'stores/useStateStore';
import { dashIfEmpty, toDate } from 'src/filters';
const { t } = useI18n();
const router = useRouter();
const stateStore = useStateStore();
const arrayData = ref(null);
onBeforeMount(async () => {
arrayData.value = useArrayData('CustomerExtendedList', {
url: 'Clients/extendedListFilter',
limit: 0,
});
await arrayData.value.fetch({ append: false });
stateStore.rightDrawer = true;
});
onMounted(() => {
const filteredColumns = columns.value.filter(
(col) => col.name !== 'actions' && col.name !== 'customerStatus'
);
allColumnNames.value = filteredColumns.map((col) => col.name);
});
const rows = computed(() => arrayData.value.store.data);
const selectedCustomerId = ref(0);
const selectedSalesPersonId = ref(0);
const allColumnNames = ref([]);
const visibleColumns = ref([]);
const tableColumnComponents = {
customerStatus: {
component: QIcon,
props: (prop) => ({
name: !prop.row.isActive
? 'vn:disabled'
: prop.row.isActive && prop.row.isFreezed
? 'vn:frozen'
: '',
color: 'primary',
size: 'sm',
}),
event: () => {},
},
id: {
component: QBtn,
props: () => ({ flat: true, color: 'blue' }),
event: (prop) => {
selectCustomerId(prop.row.id);
},
},
name: {
component: 'span',
props: () => {},
event: () => {},
},
fi: {
component: 'span',
props: () => {},
event: () => {},
},
salesPersonFk: {
component: QBtn,
props: () => ({ flat: true, color: 'blue' }),
event: (prop) => selectSalesPersonId(prop.row.salesPersonFk),
},
credit: {
component: 'span',
props: () => {},
event: () => {},
},
creditInsurance: {
component: 'span',
props: () => {},
event: () => {},
},
phone: {
component: 'span',
props: () => {},
event: () => {},
},
mobile: {
component: 'span',
props: () => {},
event: () => {},
},
street: {
component: 'span',
props: () => {},
event: () => {},
},
countryFk: {
component: 'span',
props: () => {},
event: () => {},
},
provinceFk: {
component: 'span',
props: () => {},
event: () => {},
},
city: {
component: 'span',
props: () => {},
event: () => {},
},
postcode: {
component: 'span',
props: () => {},
event: () => {},
},
email: {
component: 'span',
props: () => {},
event: () => {},
},
created: {
component: 'span',
props: () => {},
event: () => {},
},
businessTypeFk: {
component: 'span',
props: () => {},
event: () => {},
},
payMethodFk: {
component: 'span',
props: () => {},
event: () => {},
},
sageTaxTypeFk: {
component: 'span',
props: () => {},
event: () => {},
},
sageTransactionTypeFk: {
component: 'span',
props: () => {},
event: () => {},
},
isActive: {
component: QIcon,
props: (prop) => ({
name: prop.row.isActive ? 'check' : 'close',
color: prop.row.isActive ? 'positive' : 'negative',
size: 'sm',
}),
event: () => {},
},
isVies: {
component: QIcon,
props: (prop) => ({
name: prop.row.isVies ? 'check' : 'close',
color: prop.row.isVies ? 'positive' : 'negative',
size: 'sm',
}),
event: () => {},
},
isTaxDataChecked: {
component: QIcon,
props: (prop) => ({
name: prop.row.isTaxDataChecked ? 'check' : 'close',
color: prop.row.isTaxDataChecked ? 'positive' : 'negative',
size: 'sm',
}),
event: () => {},
},
isEqualizated: {
component: QIcon,
props: (prop) => ({
name: prop.row.isEqualizated ? 'check' : 'close',
color: prop.row.isEqualizated ? 'positive' : 'negative',
size: 'sm',
}),
event: () => {},
},
isFreezed: {
component: QIcon,
props: (prop) => ({
name: prop.row.isFreezed ? 'check' : 'close',
color: prop.row.isFreezed ? 'positive' : 'negative',
size: 'sm',
}),
event: () => {},
},
hasToInvoice: {
component: QIcon,
props: (prop) => ({
name: prop.row.hasToInvoice ? 'check' : 'close',
color: prop.row.hasToInvoice ? 'positive' : 'negative',
size: 'sm',
}),
event: () => {},
},
hasToInvoiceByAddress: {
component: QIcon,
props: (prop) => ({
name: prop.row.hasToInvoiceByAddress ? 'check' : 'close',
color: prop.row.hasToInvoiceByAddress ? 'positive' : 'negative',
size: 'sm',
}),
event: () => {},
},
isToBeMailed: {
component: QIcon,
props: (prop) => ({
name: prop.row.isToBeMailed ? 'check' : 'close',
color: prop.row.isToBeMailed ? 'positive' : 'negative',
size: 'sm',
}),
event: () => {},
},
hasLcr: {
component: QIcon,
props: (prop) => ({
name: prop.row.hasLcr ? 'check' : 'close',
color: prop.row.hasLcr ? 'positive' : 'negative',
size: 'sm',
}),
event: () => {},
},
hasCoreVnl: {
component: QIcon,
props: (prop) => ({
name: prop.row.hasCoreVnl ? 'check' : 'close',
color: prop.row.hasCoreVnl ? 'positive' : 'negative',
size: 'sm',
}),
event: () => {},
},
hasSepaVnl: {
component: QIcon,
props: (prop) => ({
name: prop.row.hasSepaVnl ? 'check' : 'close',
color: prop.row.hasSepaVnl ? 'positive' : 'negative',
size: 'sm',
}),
event: () => {},
},
actions: {
component: CustomerExtendedListActions,
props: (prop) => ({
id: prop.row.id,
}),
event: () => {},
},
};
const columns = computed(() => {
return [
{
align: 'left',
field: '',
label: '',
name: 'customerStatus',
format: () => ' ',
},
{
align: 'left',
field: 'id',
label: t('customer.extendedList.tableVisibleColumns.id'),
name: 'id',
},
{
align: 'left',
field: 'name',
label: t('customer.extendedList.tableVisibleColumns.name'),
name: 'name',
},
{
align: 'left',
field: 'fi',
label: t('customer.extendedList.tableVisibleColumns.fi'),
name: 'fi',
},
{
align: 'left',
field: 'salesPerson',
label: t('customer.extendedList.tableVisibleColumns.salesPersonFk'),
name: 'salesPersonFk',
},
{
align: 'left',
field: 'credit',
label: t('customer.extendedList.tableVisibleColumns.credit'),
name: 'credit',
},
{
align: 'left',
field: 'creditInsurance',
label: t('customer.extendedList.tableVisibleColumns.creditInsurance'),
name: 'creditInsurance',
},
{
align: 'left',
field: 'phone',
label: t('customer.extendedList.tableVisibleColumns.phone'),
name: 'phone',
},
{
align: 'left',
field: 'mobile',
label: t('customer.extendedList.tableVisibleColumns.mobile'),
name: 'mobile',
},
{
align: 'left',
field: 'street',
label: t('customer.extendedList.tableVisibleColumns.street'),
name: 'street',
},
{
align: 'left',
field: 'country',
label: t('customer.extendedList.tableVisibleColumns.countryFk'),
name: 'countryFk',
},
{
align: 'left',
field: 'province',
label: t('customer.extendedList.tableVisibleColumns.provinceFk'),
name: 'provinceFk',
},
{
align: 'left',
field: 'city',
label: t('customer.extendedList.tableVisibleColumns.city'),
name: 'city',
},
{
align: 'left',
field: 'postcode',
label: t('customer.extendedList.tableVisibleColumns.postcode'),
name: 'postcode',
},
{
align: 'left',
field: 'email',
label: t('customer.extendedList.tableVisibleColumns.email'),
name: 'email',
},
{
align: 'left',
field: 'created',
label: t('customer.extendedList.tableVisibleColumns.created'),
name: 'created',
format: (value) => toDate(value),
},
{
align: 'left',
field: 'businessType',
label: t('customer.extendedList.tableVisibleColumns.businessTypeFk'),
name: 'businessTypeFk',
},
{
align: 'left',
field: 'payMethod',
label: t('customer.extendedList.tableVisibleColumns.payMethodFk'),
name: 'payMethodFk',
},
{
align: 'left',
field: 'sageTaxType',
label: t('customer.extendedList.tableVisibleColumns.sageTaxTypeFk'),
name: 'sageTaxTypeFk',
},
{
align: 'left',
field: 'sageTransactionType',
label: t('customer.extendedList.tableVisibleColumns.sageTransactionTypeFk'),
name: 'sageTransactionTypeFk',
},
{
align: 'left',
field: 'isActive',
label: t('customer.extendedList.tableVisibleColumns.isActive'),
name: 'isActive',
format: () => ' ',
},
{
align: 'left',
field: 'isVies',
label: t('customer.extendedList.tableVisibleColumns.isVies'),
name: 'isVies',
format: () => ' ',
},
{
align: 'left',
field: 'isTaxDataChecked',
label: t('customer.extendedList.tableVisibleColumns.isTaxDataChecked'),
name: 'isTaxDataChecked',
format: () => ' ',
},
{
align: 'left',
field: 'isEqualizated',
label: t('customer.extendedList.tableVisibleColumns.isEqualizated'),
name: 'isEqualizated',
format: () => ' ',
},
{
align: 'left',
field: 'isFreezed',
label: t('customer.extendedList.tableVisibleColumns.isFreezed'),
name: 'isFreezed',
format: () => ' ',
},
{
align: 'left',
field: 'hasToInvoice',
label: t('customer.extendedList.tableVisibleColumns.hasToInvoice'),
name: 'hasToInvoice',
format: () => ' ',
},
{
align: 'left',
field: 'hasToInvoiceByAddress',
label: t('customer.extendedList.tableVisibleColumns.hasToInvoiceByAddress'),
name: 'hasToInvoiceByAddress',
format: () => ' ',
},
{
align: 'left',
field: 'isToBeMailed',
label: t('customer.extendedList.tableVisibleColumns.isToBeMailed'),
name: 'isToBeMailed',
format: () => ' ',
},
{
align: 'left',
field: 'hasLcr',
label: t('customer.extendedList.tableVisibleColumns.hasLcr'),
name: 'hasLcr',
format: () => ' ',
},
{
align: 'left',
field: 'hasCoreVnl',
label: t('customer.extendedList.tableVisibleColumns.hasCoreVnl'),
name: 'hasCoreVnl',
format: () => ' ',
},
{
align: 'left',
field: 'hasSepaVnl',
label: t('customer.extendedList.tableVisibleColumns.hasSepaVnl'),
name: 'hasSepaVnl',
format: () => ' ',
},
{
align: 'right',
field: 'actions',
label: '',
name: 'actions',
},
];
});
const stopEventPropagation = (event, col) => {
if (!['id', 'salesPersonFk'].includes(col.name)) return;
event.preventDefault();
event.stopPropagation();
};
const navigateToTravelId = (id) => {
router.push({ path: `/customer/${id}` });
};
const selectCustomerId = (id) => {
selectedCustomerId.value = id;
};
const selectSalesPersonId = (id) => {
console.log('selectedSalesPersonId:: ', selectedSalesPersonId.value);
selectedSalesPersonId.value = id;
};
</script>
<template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<CustomerExtendedListFilter
v-if="visibleColumns.length !== 0"
data-key="CustomerExtendedList"
:visible-columns="visibleColumns"
/>
</QScrollArea>
</QDrawer>
<QToolbar class="bg-vn-dark">
<div id="st-data">
<TableVisibleColumns
:all-columns="allColumnNames"
table-code="clientsDetail"
labels-traductions-path="customer.extendedList.tableVisibleColumns"
@on-config-saved="
visibleColumns = ['customerStatus', ...$event, 'actions']
"
/>
</div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<QPage class="column items-center q-pa-md">
<QTable
:columns="columns"
:pagination="{ rowsPerPage: 12 }"
:rows="rows"
class="full-width q-mt-md"
row-key="id"
:visible-columns="visibleColumns"
>
<template #body="props">
<QTr
:props="props"
@click="navigateToTravelId(props.row.id)"
class="cursor-pointer"
>
<QTd
v-for="col in props.cols"
:key="col.name"
:props="props"
@click="stopEventPropagation($event, col)"
>
<component
:is="tableColumnComponents[col.name].component"
class="col-content"
v-bind="tableColumnComponents[col.name].props(props)"
@click="tableColumnComponents[col.name].event(props)"
>
{{ dashIfEmpty(col.value) }}
<WorkerDescriptorProxy
v-if="props.row.salesPersonFk"
:id="selectedSalesPersonId"
/>
<CustomerDescriptorProxy
v-if="props.row.id"
:id="selectedCustomerId"
/>
</component>
</QTd>
</QTr>
</template>
</QTable>
</QPage>
</template>
<style lang="scss" scoped>
.col-content {
border-radius: 4px;
padding: 6px;
}
</style>

View File

@ -0,0 +1,71 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { useQuasar } from 'quasar';
import CustomerSummaryDialog from '../Card/CustomerSummaryDialog.vue';
const { t } = useI18n();
const quasar = useQuasar();
const router = useRouter();
const $props = defineProps({
id: {
type: Number,
required: true,
},
});
const redirectToCreateView = () => {
router.push({
name: 'TicketList',
query: {
params: JSON.stringify({
clientFk: $props.id,
}),
},
});
};
const viewSummary = () => {
quasar.dialog({
component: CustomerSummaryDialog,
componentProps: {
id: $props.id,
},
});
};
</script>
<template>
<div>
<QIcon
@click.stop="redirectToCreateView"
color="primary"
name="vn:ticket"
size="sm"
>
<QTooltip>
{{ t('Client ticket list') }}
</QTooltip>
</QIcon>
<QIcon
@click.stop="viewSummary"
class="q-ml-md"
color="primary"
name="preview"
size="sm"
>
<QTooltip>
{{ t('Preview') }}
</QTooltip>
</QIcon>
</div>
</template>
<i18n>
es:
Client ticket list: Listado de tickets del cliente
Preview: Vista previa
</i18n>

View File

@ -0,0 +1,576 @@
<script setup>
import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
const props = defineProps({
dataKey: {
type: String,
required: true,
},
visibleColumns: {
type: Array,
required: true,
},
});
const { t } = useI18n();
const clients = ref();
const workers = ref();
const countriesOptions = ref([]);
const provincesOptions = ref([]);
const paymethodsOptions = ref([]);
const businessTypesOptions = ref([]);
const sageTaxTypesOptions = ref([]);
const sageTransactionTypesOptions = ref([]);
const visibleColumnsSet = computed(() => new Set(props.visibleColumns));
const shouldRenderColumn = (colName) => {
return visibleColumnsSet.value.has(colName);
};
</script>
<template>
<FetchData
url="Clients"
:filter="{ where: { role: 'socialName' } }"
@on-fetch="(data) => (clients = data)"
auto-load
/>
<FetchData
url="Workers/activeWithInheritedRole"
:filter="{ where: { role: 'salesPerson' } }"
@on-fetch="(data) => (workers = data)"
auto-load
/>
<FetchData
url="Workers/activeWithInheritedRole"
:filter="{ where: { role: 'salesPerson' } }"
@on-fetch="(data) => (workers = data)"
auto-load
/>
<FetchData
url="Countries"
:filter="{ fields: ['id', 'country'], order: 'country ASC' }"
@on-fetch="(data) => (countriesOptions = data)"
auto-load
/>
<FetchData
ref="provincesFetchDataRef"
@on-fetch="(data) => (provincesOptions = data)"
auto-load
url="Provinces"
/>
<FetchData
url="Paymethods"
@on-fetch="(data) => (paymethodsOptions = data)"
auto-load
/>
<FetchData
url="BusinessTypes"
@on-fetch="(data) => (businessTypesOptions = data)"
auto-load
/>
<FetchData
url="SageTaxTypes"
auto-load
@on-fetch="(data) => (sageTaxTypesOptions = data)"
/>
<FetchData
url="sageTransactionTypes"
auto-load
@on-fetch="(data) => (sageTransactionTypesOptions = data)"
/>
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong
>{{ t(`customer.extendedList.tableVisibleColumns.${tag.label}`) }}:
</strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params, searchFn }">
<QList dense class="list q-gutter-y-sm q-mt-sm">
<QItem v-if="shouldRenderColumn('id')">
<QItemSection>
<VnInput
:label="t('customer.extendedList.tableVisibleColumns.id')"
v-model="params.id"
is-outlined
clearable
/>
</QItemSection>
</QItem>
<QItem v-if="shouldRenderColumn('name')">
<QItemSection>
<VnInput
:label="t('customer.extendedList.tableVisibleColumns.name')"
v-model="params.name"
is-outlined
/>
</QItemSection>
</QItem>
<!-- <QItem class="q-mb-sm">
<QItemSection v-if="!clients">
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="clients">
<VnSelectFilter
:label="t('Social name')"
v-model="params.socialName"
@update:model-value="searchFn()"
:options="clients"
option-value="socialName"
option-label="socialName"
emit-value
map-options
use-input
hide-selected
dense
outlined
rounded
:input-debounce="0"
/>
</QItemSection>
</QItem> -->
<QItem v-if="shouldRenderColumn('fi')">
<QItemSection>
<VnInput
:label="t('customer.extendedList.tableVisibleColumns.fi')"
v-model="params.fi"
is-outlined
/>
</QItemSection>
</QItem>
<QItem v-if="shouldRenderColumn('salesPersonFk')">
<QItemSection v-if="!workers">
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="workers">
<VnSelectFilter
:label="
t(
'customer.extendedList.tableVisibleColumns.salesPersonFk'
)
"
v-model="params.salesPersonFk"
@update:model-value="searchFn()"
:options="workers"
option-value="id"
option-label="name"
emit-value
map-options
use-input
hide-selected
dense
outlined
rounded
:input-debounce="0"
/>
</QItemSection>
</QItem>
<QItem v-if="shouldRenderColumn('credit')">
<QItemSection>
<VnInput
:label="t('customer.extendedList.tableVisibleColumns.credit')"
v-model="params.credit"
is-outlined
/>
</QItemSection>
</QItem>
<QItem v-if="shouldRenderColumn('creditInsurance')">
<QItemSection>
<VnInput
:label="
t(
'customer.extendedList.tableVisibleColumns.creditInsurance'
)
"
v-model="params.creditInsurance"
is-outlined
/>
</QItemSection>
</QItem>
<QItem v-if="shouldRenderColumn('phone')">
<QItemSection>
<VnInput
:label="t('customer.extendedList.tableVisibleColumns.phone')"
v-model="params.phone"
is-outlined
/>
</QItemSection>
</QItem>
<QItem v-if="shouldRenderColumn('mobile')">
<QItemSection>
<VnInput
:label="t('customer.extendedList.tableVisibleColumns.mobile')"
v-model="params.mobile"
is-outlined
/>
</QItemSection>
</QItem>
<QItem v-if="shouldRenderColumn('street')">
<QItemSection>
<VnInput
:label="t('customer.extendedList.tableVisibleColumns.street')"
v-model="params.street"
is-outlined
/>
</QItemSection>
</QItem>
<QItem v-if="shouldRenderColumn('countryFk')">
<QItemSection>
<VnSelectFilter
:label="
t('customer.extendedList.tableVisibleColumns.countryFk')
"
v-model="params.countryFk"
@update:model-value="searchFn()"
:options="countriesOptions"
option-value="id"
option-label="country"
map-options
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem v-if="shouldRenderColumn('provinceFk')">
<QItemSection>
<VnSelectFilter
:label="
t('customer.extendedList.tableVisibleColumns.provinceFk')
"
v-model="params.provinceFk"
@update:model-value="searchFn()"
:options="provincesOptions"
option-value="id"
option-label="name"
map-options
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem v-if="shouldRenderColumn('city')">
<QItemSection>
<VnInput
:label="t('customer.extendedList.tableVisibleColumns.city')"
v-model="params.city"
is-outlined
/>
</QItemSection>
</QItem>
<QItem v-if="shouldRenderColumn('postcode')">
<QItemSection>
<VnInput
:label="
t('customer.extendedList.tableVisibleColumns.postcode')
"
v-model="params.postcode"
is-outlined
/>
</QItemSection>
</QItem>
<QItem v-if="shouldRenderColumn('email')">
<QItemSection>
<VnInput
:label="t('customer.extendedList.tableVisibleColumns.email')"
v-model="params.email"
is-outlined
/>
</QItemSection>
</QItem>
<QItem v-if="shouldRenderColumn('created')">
<QItemSection>
<VnInputDate
v-model="params.created"
:label="
t('customer.extendedList.tableVisibleColumns.created')
"
@update:model-value="searchFn()"
is-outlined
/>
</QItemSection>
</QItem>
<QItem v-if="shouldRenderColumn('businessTypeFk')">
<QItemSection>
<VnSelectFilter
:label="
t(
'customer.extendedList.tableVisibleColumns.businessTypeFk'
)
"
v-model="params.businessTypeFk"
:options="businessTypesOptions"
@update:model-value="searchFn()"
option-value="code"
option-label="description"
map-options
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem v-if="shouldRenderColumn('payMethodFk')">
<QItemSection>
<VnSelectFilter
:label="
t('customer.extendedList.tableVisibleColumns.payMethodFk')
"
v-model="params.payMethodFk"
:options="paymethodsOptions"
@update:model-value="searchFn()"
option-value="id"
option-label="name"
map-options
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem v-if="shouldRenderColumn('sageTaxTypeFk')">
<QItemSection>
<VnSelectFilter
:label="
t(
'customer.extendedList.tableVisibleColumns.sageTaxTypeFk'
)
"
v-model="params.sageTaxTypeFk"
@update:model-value="searchFn()"
:options="sageTaxTypesOptions"
option-value="id"
option-label="vat"
map-options
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem v-if="shouldRenderColumn('sageTransactionTypeFk')">
<QItemSection>
<VnSelectFilter
:label="
t(
'customer.extendedList.tableVisibleColumns.sageTransactionTypeFk'
)
"
v-model="params.sageTransactionTypeFk"
@update:model-value="searchFn()"
:options="sageTransactionTypesOptions"
option-value="id"
option-label="transaction"
map-options
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem
v-if="shouldRenderColumn('isActive') || shouldRenderColumn('isVies')"
>
<QItemSection v-if="shouldRenderColumn('isActive')">
<QCheckbox
v-model="params.isActive"
@update:model-value="searchFn()"
:label="
t('customer.extendedList.tableVisibleColumns.isActive')
"
toggle-indeterminate
:false-value="undefined"
/>
</QItemSection>
<QItemSection v-if="shouldRenderColumn('isVies')">
<QCheckbox
v-model="params.isVies"
@update:model-value="searchFn()"
:label="t('customer.extendedList.tableVisibleColumns.isVies')"
toggle-indeterminate
:false-value="undefined"
/>
</QItemSection>
</QItem>
<QItem
v-if="
shouldRenderColumn('isEqualizated') ||
shouldRenderColumn('isTaxDataChecked')
"
>
<QItemSection v-if="shouldRenderColumn('isTaxDataChecked')">
<QCheckbox
v-model="params.isTaxDataChecked"
@update:model-value="searchFn()"
:label="
t(
'customer.extendedList.tableVisibleColumns.isTaxDataChecked'
)
"
toggle-indeterminate
:false-value="undefined"
/>
</QItemSection>
<QItemSection v-if="shouldRenderColumn('isEqualizated')">
<QCheckbox
v-model="params.isEqualizated"
@update:model-value="searchFn()"
:label="
t(
'customer.extendedList.tableVisibleColumns.isEqualizated'
)
"
toggle-indeterminate
:false-value="undefined"
/>
</QItemSection>
</QItem>
<QItem
v-if="
shouldRenderColumn('hasToInvoice') ||
shouldRenderColumn('isFreezed')
"
>
<QItemSection v-if="shouldRenderColumn('isFreezed')">
<QCheckbox
v-model="params.isFreezed"
@update:model-value="searchFn()"
:label="
t('customer.extendedList.tableVisibleColumns.isFreezed')
"
toggle-indeterminate
:false-value="undefined"
/>
</QItemSection>
<QItemSection v-if="shouldRenderColumn('hasToInvoice')">
<QCheckbox
v-model="params.hasToInvoice"
@update:model-value="searchFn()"
:label="
t(
'customer.extendedList.tableVisibleColumns.hasToInvoice'
)
"
toggle-indeterminate
:false-value="undefined"
/>
</QItemSection>
</QItem>
<QItem
v-if="
shouldRenderColumn('isToBeMailed') ||
shouldRenderColumn('hasToInvoiceByAddress')
"
>
<QItemSection v-if="shouldRenderColumn('hasToInvoiceByAddress')">
<QCheckbox
v-model="params.hasToInvoiceByAddress"
@update:model-value="searchFn()"
:label="
t(
'customer.extendedList.tableVisibleColumns.hasToInvoiceByAddress'
)
"
toggle-indeterminate
:false-value="undefined"
/>
</QItemSection>
<QItemSection v-if="shouldRenderColumn('isToBeMailed')">
<QCheckbox
v-model="params.isToBeMailed"
@update:model-value="searchFn()"
:label="
t(
'customer.extendedList.tableVisibleColumns.isToBeMailed'
)
"
toggle-indeterminate
:false-value="undefined"
/>
</QItemSection>
</QItem>
<QItem
v-if="
shouldRenderColumn('hasLcr') || shouldRenderColumn('hasCoreVnl')
"
>
<QItemSection v-if="shouldRenderColumn('hasLcr')">
<QCheckbox
v-model="params.hasLcr"
@update:model-value="searchFn()"
:label="t('customer.extendedList.tableVisibleColumns.hasLcr')"
toggle-indeterminate
:false-value="undefined"
/>
</QItemSection>
<QItemSection v-if="shouldRenderColumn('hasCoreVnl')">
<QCheckbox
v-model="params.hasCoreVnl"
@update:model-value="searchFn()"
:label="
t('customer.extendedList.tableVisibleColumns.hasCoreVnl')
"
toggle-indeterminate
:false-value="undefined"
/>
</QItemSection>
</QItem>
<QItem v-if="shouldRenderColumn('hasSepaVnl')">
<QItemSection>
<QCheckbox
v-model="params.hasSepaVnl"
@update:model-value="searchFn()"
:label="
t('customer.extendedList.tableVisibleColumns.hasSepaVnl')
"
toggle-indeterminate
:false-value="undefined"
/>
</QItemSection>
</QItem>
<QSeparator />
</QList>
</template>
</VnFilterPanel>
</template>
<style scoped>
.list {
width: 256px;
}
.list * {
max-width: 100%;
}
</style>
<i18n>
<<<<<<< HEAD:src/pages/Customer/ExtendedList/CustomerExtendedListFilter.vue
es:
Identifier: Identificador
=======
es:
>>>>>>> dev:src/pages/Customer/CustomerExtendedListFilter.vue
Social name: Razón social
</i18n>

View File

@ -0,0 +1,163 @@
<script setup>
import { ref, computed, onBeforeMount } from 'vue';
import { useI18n } from 'vue-i18n';
import { QBtn } from 'quasar';
import { useArrayData } from 'composables/useArrayData';
import { useStateStore } from 'stores/useStateStore';
import CustomerNotificationsFilter from './CustomerNotificationsFilter.vue';
import CustomerDescriptorProxy from '../Card/CustomerDescriptorProxy.vue';
const { t } = useI18n();
const stateStore = useStateStore();
const arrayData = ref(null);
onBeforeMount(async () => {
arrayData.value = useArrayData('CustomerNotifications', {
url: 'Clients',
limit: 0,
});
await arrayData.value.fetch({ append: false });
stateStore.rightDrawer = true;
});
const rows = computed(() => arrayData.value.store.data);
const selected = ref([]);
const selectedCustomerId = ref(0);
const tableColumnComponents = {
id: {
component: QBtn,
props: () => ({ flat: true, color: 'blue' }),
event: (prop) => selectCustomerId(prop.row.id),
},
socialName: {
component: 'span',
props: () => {},
event: () => {},
},
city: {
component: 'span',
props: () => {},
event: () => {},
},
phone: {
component: 'span',
props: () => {},
event: () => {},
},
email: {
component: 'span',
props: () => {},
event: () => {},
},
};
const columns = computed(() => {
return [
{
align: 'left',
field: 'id',
label: t('Identifier'),
name: 'id',
},
{
align: 'left',
field: 'socialName',
label: t('Social name'),
name: 'socialName',
},
{
align: 'left',
field: 'city',
label: t('City'),
name: 'city',
},
{
align: 'left',
field: 'phone',
label: t('Phone'),
name: 'phone',
},
{
align: 'left',
field: 'email',
label: t('Email'),
name: 'email',
},
];
});
const selectCustomerId = (id) => {
selectedCustomerId.value = id;
};
</script>
<template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<CustomerNotificationsFilter data-key="CustomerNotifications" />
</QScrollArea>
</QDrawer>
<QToolbar class="bg-vn-dark">
<div id="st-data"></div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<QPage class="column items-center q-pa-md">
<QTable
:columns="columns"
:pagination="{ rowsPerPage: 0 }"
:rows="rows"
class="full-width q-mt-md"
hide-bottom
row-key="id"
selection="multiple"
v-model:selected="selected"
>
<template #top>
<div v-if="rows" class="full-width flex justify-end">
{{ `${rows.length} ${t('route.cmr.list.results')}` }}
</div>
</template>
<template #body-cell="props">
<QTd :props="props">
<QTr :props="props" class="cursor-pointer">
<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)"
>
{{ props.value }}
<CustomerDescriptorProxy :id="selectedCustomerId" />
</component>
</QTr>
</QTd>
</template>
</QTable>
</QPage>
</template>
<style lang="scss" scoped>
.col-content {
border-radius: 4px;
padding: 6px;
}
</style>
<i18n>
es:
Identifier: Identificador
Social name: Razón social
Salesperson: Comercial
Phone: Teléfono
City: Población
Email: Email
</i18n>

View File

@ -0,0 +1,144 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const cities = ref();
const clients = ref();
</script>
<template>
<FetchData
:filter="{ where: { role: 'socialName' } }"
@on-fetch="(data) => (clients = data)"
auto-load
url="Clients"
/>
<FetchData @on-fetch="(data) => (cities = data)" auto-load url="Towns" />
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params, searchFn }">
<QList dense class="list">
<QItem class="q-mb-sm q-mt-sm">
<QItemSection>
<VnInput
:label="t('Identifier')"
is-outlined
v-model="params.identifier"
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection v-if="!clients">
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="clients">
<VnSelectFilter
:input-debounce="0"
:label="t('Social name')"
:options="clients"
@update:model-value="searchFn()"
dense
emit-value
hide-selected
map-options
option-label="socialName"
option-value="socialName"
outlined
rounded
use-input
v-model="params.socialName"
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection v-if="!cities">
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="cities">
<VnSelectFilter
:input-debounce="0"
:label="t('City')"
:options="cities"
@update:model-value="searchFn()"
dense
emit-value
hide-selected
map-options
option-label="name"
option-value="name"
outlined
rounded
use-input
v-model="params.city"
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnInput :label="t('Phone')" is-outlined v-model="params.phone" />
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnInput :label="t('Email')" is-outlined v-model="params.email" />
</QItemSection>
</QItem>
<QSeparator />
</QList>
</template>
</VnFilterPanel>
</template>
<style scoped>
.list {
width: 256px;
}
.list * {
max-width: 100%;
}
</style>
<i18n>
en:
params:
identifier: Identifier
socialName: Social name
city: City
phone: Phone
email: Email
es:
params:
identifier: Identificador
socialName: Razón social
city: Población
phone: Teléfono
email: Email
Identifier: Identificador
Social name: Razón social
City: Población
Phone: Teléfono
Email: Email
</i18n>

View File

@ -7,7 +7,7 @@ import { useStateStore } from 'stores/useStateStore';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import VnPaginate from 'components/ui/VnPaginate.vue'; import VnPaginate from 'components/ui/VnPaginate.vue';
import VnConfirm from 'components/ui/VnConfirm.vue'; import VnConfirm from 'components/ui/VnConfirm.vue';
import CustomerDescriptorProxy from './Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from '../Card/CustomerDescriptorProxy.vue';
import { toDate, toCurrency } from 'filters/index'; import { toDate, toCurrency } from 'filters/index';
import CustomerPaymentsFilter from './CustomerPaymentsFilter.vue'; import CustomerPaymentsFilter from './CustomerPaymentsFilter.vue';

View File

@ -1,6 +1,8 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
const { t } = useI18n(); const { t } = useI18n();
@ -25,39 +27,39 @@ function isValidNumber(value) {
</div> </div>
</template> </template>
<template #body="{ params }"> <template #body="{ params }">
<QList dense> <QList dense class="q-gutter-y-sm q-mt-sm">
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('Order ID')" :label="t('Order ID')"
v-model="params.orderFk" v-model="params.orderFk"
lazy-rules is-outlined
> >
<template #prepend> <template #prepend>
<QIcon name="vn:basket" size="sm"></QIcon> <QIcon name="vn:basket" size="xs" />
</template> </template>
</QInput> </VnInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('Customer ID')" :label="t('Customer ID')"
v-model="params.clientFk" v-model="params.clientFk"
lazy-rules is-outlined
> >
<template #prepend> <template #prepend>
<QIcon name="vn:client" size="sm"></QIcon> <QIcon name="vn:client" size="xs" />
</template> </template>
</QInput> </VnInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('Amount')" :label="t('Amount')"
v-model="params.amount" v-model="params.amount"
lazy-rules is-outlined
@update:model-value=" @update:model-value="
(value) => { (value) => {
if (value.includes(',')) if (value.includes(','))
@ -68,25 +70,25 @@ function isValidNumber(value) {
(val) => (val) =>
isValidNumber(val) || !val || 'Please type a number', isValidNumber(val) || !val || 'Please type a number',
]" ]"
lazy-rules
> >
<template #prepend> <template #prepend>
<QIcon name="euro" size="sm" /> <QIcon name="euro" size="sm" />
</template> </template>
</QInput> </VnInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInputDate <VnInputDate
v-model="params.from" v-model="params.from"
:label="t('From')" :label="t('From')"
is-outlined
/> />
</QItemSection> </QItemSection>
<QItemSection> <QItemSection>
<VnInputDate <VnInputDate v-model="params.to" :label="t('To')" is-outlined />
v-model="params.to"
:label="t('To')"
/>
</QItemSection> </QItemSection>
</QItem> </QItem>
</QList> </QList>

View File

@ -0,0 +1,135 @@
<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 VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
const route = useRoute();
const { t } = useI18n();
const workersOptions = ref([]);
const clientsOptions = ref([]);
</script>
<template>
<fetch-data
url="Workers/search"
@on-fetch="(data) => (workersOptions = data)"
auto-load
/>
<fetch-data url="Clients" @on-fetch="(data) => (clientsOptions = data)" auto-load />
<FormModel
:url="`Departments/${route.params.id}`"
model="department"
auto-load
class="full-width"
>
<template #form="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput
:label="t('department.name')"
v-model="data.name"
:rules="validate('department.name')"
clearable
autofocus
/>
</div>
<div class="col">
<VnInput
v-model="data.code"
:label="t('department.code')"
:rules="validate('department.code')"
clearable
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput
:label="t('department.chat')"
v-model="data.chatName"
:rules="validate('department.chat')"
clearable
/>
</div>
<div class="col">
<VnInput
v-model="data.notificationEmail"
:label="t('department.email')"
:rules="validate('department.email')"
clearable
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('department.bossDepartment')"
v-model="data.workerFk"
:options="workersOptions"
option-value="id"
option-label="name"
hide-selected
map-options
:rules="validate('department.workerFk')"
/>
</div>
<div class="col">
<VnSelectFilter
:label="t('department.selfConsumptionCustomer')"
v-model="data.clientFk"
:options="clientsOptions"
option-value="id"
option-label="name"
hide-selected
map-options
:rules="validate('department.clientFk')"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QCheckbox
:label="t('department.telework')"
v-model="data.isTeleworking"
/>
</div>
<div class="col">
<QCheckbox
:label="t('department.notifyOnErrors')"
v-model="data.hasToMistake"
:false-value="0"
:true-value="1"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QCheckbox
:label="t('department.worksInProduction')"
v-model="data.isProduction"
/>
</div>
<div class="col">
<QCheckbox
:label="t('department.hasToRefill')"
v-model="data.hasToRefill"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QCheckbox
:label="t('department.hasToSendMail')"
v-model="data.hasToSendMail"
/>
</div>
</VnRow>
</template>
</FormModel>
</template>

View File

@ -0,0 +1,28 @@
<script setup>
import { useStateStore } from 'stores/useStateStore';
import DepartmentDescriptor from 'pages/Department/Card/DepartmentDescriptor.vue';
import LeftMenu from 'components/LeftMenu.vue';
const stateStore = useStateStore();
</script>
<template>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit">
<DepartmentDescriptor />
<QSeparator />
<LeftMenu source="card" />
</QScrollArea>
</QDrawer>
<QPageContainer>
<QPage>
<QToolbar class="bg-vn-dark justify-end">
<div id="st-data"></div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<div class="q-pa-md column items-center">
<RouterView></RouterView>
</div>
</QPage>
</QPageContainer>
</template>

View File

@ -0,0 +1,129 @@
<script setup>
import { computed, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import VnLv from 'src/components/ui/VnLv.vue';
import CardDescriptor from 'src/components/ui/CardDescriptor.vue';
import useCardDescription from 'src/composables/useCardDescription';
import axios from 'axios';
import useNotify from 'src/composables/useNotify.js';
const $props = defineProps({
id: {
type: Number,
required: false,
default: null,
},
summary: {
type: Object,
default: null,
},
});
const quasar = useQuasar();
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
const { notify } = useNotify();
const entityId = computed(() => {
return $props.id || route.params.id;
});
const department = ref();
const data = ref(useCardDescription());
const setData = (entity) => {
if (!entity) return;
data.value = useCardDescription(entity.name, entity.id);
};
const removeDepartment = () => {
console.log('entityId: ', entityId.value);
quasar
.dialog({
title: 'Are you sure you want to delete it?',
message: 'Delete department',
ok: {
push: true,
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.log('Error removing department');
}
});
};
</script>
<template>
<CardDescriptor
module="Department"
data-key="departmentData"
:url="`Departments/${entityId}`"
:title="data.title"
:subtitle="data.subtitle"
:summary="$props.summary"
@on-fetch="
(data) => {
department = data;
setData(data);
}
"
>
<template #menu="{}">
<QItem v-ripple clickable @click="removeDepartment()">
<QItemSection>{{ t('Delete') }}</QItemSection>
</QItem>
</template>
<template #body="{ entity }">
<VnLv :label="t('department.chat')" :value="entity.chatName" dash />
<VnLv :label="t('department.email')" :value="entity.notificationEmail" dash />
<VnLv
:label="t('department.selfConsumptionCustomer')"
:value="entity.client?.name"
dash
/>
<VnLv
:label="t('department.bossDepartment')"
:value="entity.worker?.user?.name"
dash
/>
</template>
<template #actions>
<QCardActions>
<QBtn
size="md"
icon="vn:worker"
color="primary"
:to="{
name: 'WorkerList',
query: {
params: JSON.stringify({ departmentFk: entityId }),
},
}"
>
<QTooltip>{{ t('Department workers') }}</QTooltip>
</QBtn>
</QCardActions>
</template>
</CardDescriptor>
</template>
<i18n>
es:
Department workers: Trabajadores del departamento
</i18n>

View File

@ -0,0 +1,107 @@
<script setup>
import { ref, onMounted, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CardSummary from 'components/ui/CardSummary.vue';
import { getUrl } from 'src/composables/getUrl';
import VnLv from 'src/components/ui/VnLv.vue';
const route = useRoute();
const { t } = useI18n();
const $props = defineProps({
id: {
type: Number,
default: 0,
},
});
const entityId = computed(() => $props.id || route.params.id);
const departmentUrl = ref();
onMounted(async () => {
departmentUrl.value = (await getUrl('')) + `departments/${entityId.value}/`;
});
</script>
<template>
<CardSummary
ref="summary"
:url="`Departments/${entityId}`"
class="full-width"
style="max-width: 900px"
>
<template #header="{ entity }">
<div>{{ entity.name }}</div>
</template>
<template #body="{ entity: department }">
<QCard class="column">
<a class="header" :href="department + `basic-data`">
{{ t('Basic data') }}
<QIcon name="open_in_new" color="primary" />
</a>
<div class="full-width row wrap justify-between content-between">
<div class="column" style="min-width: 50%">
<VnLv
:label="t('department.name')"
:value="department.name"
dash
/>
<VnLv
:label="t('department.code')"
:value="department.code"
dash
/>
<VnLv
:label="t('department.chat')"
:value="department.chatName"
dash
/>
<VnLv
:label="t('department.bossDepartment')"
:value="department.worker?.user?.name"
dash
/>
<VnLv
:label="t('department.email')"
:value="department.notificationEmail"
dash
/>
<VnLv
:label="t('department.selfConsumptionCustomer')"
:value="department.client?.name"
dash
/>
</div>
<div class="column" style="min-width: 50%">
<VnLv
:label="t('department.telework')"
:value="department.isTeleworking"
dash
/>
<VnLv
:label="t('department.notifyOnErrors')"
:value="Boolean(department.hasToMistake)"
dash
/>
<VnLv
:label="t('department.worksInProduction')"
:value="department.isProduction"
dash
/>
<VnLv
:label="t('department.hasToRefill')"
:value="department.hasToRefill"
dash
/>
<VnLv
:label="t('department.hasToSendMail')"
:value="department.hasToSendMail"
dash
/>
</div>
</div>
</QCard>
</template>
</CardSummary>
</template>

View File

@ -0,0 +1,26 @@
<script setup>
import { useStateStore } from 'stores/useStateStore';
import LeftMenu from 'components/LeftMenu.vue';
const stateStore = useStateStore();
</script>
<template>
<!-- Entry searchbar -->
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit">
<!-- EntryDescriptor -->
<QSeparator />
<LeftMenu source="card" />
</QScrollArea>
</QDrawer>
<QPageContainer>
<QPage>
<QToolbar class="bg-vn-dark justify-end">
<div id="st-data"></div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<div class="q-pa-md"><RouterView></RouterView></div>
</QPage>
</QPageContainer>
</template>

View File

@ -0,0 +1,135 @@
<script setup>
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import FetchData from 'components/FetchData.vue';
import { toDate } from 'src/filters';
const { t } = useI18n();
const route = useRoute();
const newEntryForm = reactive({
supplierFk: null,
travelFk: route.query?.travelFk || null,
companyFk: null,
});
const suppliersOptions = ref([]);
const travelsOptionsOptions = ref([]);
const companiesOptions = ref([]);
</script>
<template>
<FetchData
url="Suppliers"
:filter="{ fields: ['id', 'nickname'] }"
order="nickname"
@on-fetch="(data) => (suppliersOptions = data)"
auto-load
/>
<FetchData
url="Travels/filter"
:filter="{ fields: ['id', 'warehouseInName'] }"
order="id"
@on-fetch="(data) => (travelsOptionsOptions = data)"
auto-load
/>
<FetchData
ref="companiesRef"
url="Companies"
:filter="{ fields: ['id', 'code'] }"
order="code"
@on-fetch="(data) => (companiesOptions = data)"
auto-load
/>
<!-- Agregar searchbar de entries -->
<QPage>
<QToolbar class="bg-vn-dark justify-end">
<div id="st-data"></div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<FormModel url-create="Entries" model="entry" :form-initial-data="newEntryForm">
<template #form="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md">
<VnSelectFilter
:label="t('Supplier *')"
class="full-width"
v-model="data.supplierFk"
:options="suppliersOptions"
option-value="id"
option-label="nickname"
hide-selected
:rules="validate('entry.supplierFk')"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.nickname }}</QItemLabel>
<QItemLabel caption>
#{{ scope.opt?.id }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnSelectFilter
:label="t('Travel *')"
class="full-width"
v-model="data.travelFk"
:options="travelsOptionsOptions"
option-value="id"
option-label="warehouseInName"
map-options
hide-selected
:rules="validate('entry.travelFk')"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel
>{{ scope.opt?.agencyModeName }} -
{{ scope.opt?.warehouseInName }} ({{
toDate(scope.opt?.shipped)
}}) &#x2192; {{ scope.opt?.warehouseOutName }} ({{
toDate(scope.opt?.landed)
}})</QItemLabel
>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnSelectFilter
:label="t('Company *')"
class="full-width"
v-model="data.companyFk"
:options="companiesOptions"
option-value="id"
option-label="code"
map-options
hide-selected
:rules="validate('entry.companyFk')"
/>
</VnRow>
</template>
</FormModel>
</QPage>
</template>
<i18n>
es:
Supplier *: Proveedor *
Travel *: Envío *
Company *: Empresa *
</i18n>

View File

@ -0,0 +1,22 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
const router = useRouter();
const { t } = useI18n();
const redirectToCreateView = () => {
router.push({ name: 'EntryCreate' });
};
</script>
<template>
<QPage class="column items-center q-pa-md">
<QPageSticky :offset="[20, 20]">
<QBtn fab icon="add" color="primary" @click="redirectToCreateView()" />
<QTooltip>
{{ t('entry.list.newEntry') }}
</QTooltip>
</QPageSticky>
</QPage>
</template>

View File

@ -0,0 +1,17 @@
<script setup>
import { useStateStore } from 'stores/useStateStore';
import LeftMenu from 'src/components/LeftMenu.vue';
const stateStore = useStateStore();
</script>
<template>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit text-grey-8">
<LeftMenu />
</QScrollArea>
</QDrawer>
<QPageContainer>
<RouterView></RouterView>
</QPageContainer>
</template>

View File

@ -1,9 +1,12 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnSelectFilter from 'components/common/VnSelectFilter.vue'; import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@ -33,28 +36,32 @@ const suppliersRef = ref();
</div> </div>
</template> </template>
<template #body="{ params, searchFn }"> <template #body="{ params, searchFn }">
<QList dense> <QList dense class="list q-gutter-y-sm q-mt-sm">
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput :label="t('Id or Supplier')" v-model="params.search"> <VnInput
:label="t('Id or Supplier')"
v-model="params.search"
is-outlined
>
<template #prepend> <template #prepend>
<QIcon name="badge" size="sm"></QIcon> <QIcon name="badge" size="sm"></QIcon>
</template> </template>
</QInput> </VnInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('params.supplierRef')" :label="t('params.supplierRef')"
v-model="params.supplierRef" v-model="params.supplierRef"
@input. is-outlined
lazy-rules lazy-rules
> >
<template #prepend> <template #prepend>
<QIcon name="vn:client" size="sm"></QIcon> <QIcon name="vn:client" size="sm"></QIcon>
</template> </template>
</QInput> </VnInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
@ -66,52 +73,67 @@ const suppliersRef = ref();
option-value="id" option-value="id"
option-label="nickname" option-label="nickname"
@input-value="suppliersRef.fetch()" @input-value="suppliersRef.fetch()"
dense
outlined
rounded
> >
</VnSelectFilter> </VnSelectFilter>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput :label="t('params.fi')" v-model="params.fi" lazy-rules> <VnInput
:label="t('params.fi')"
v-model="params.fi"
is-outlined
lazy-rules
>
<template #prepend> <template #prepend>
<QIcon name="badge" size="sm"></QIcon> <QIcon name="badge" size="sm"></QIcon>
</template> </template>
</QInput> </VnInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('params.serialNumber')" :label="t('params.serialNumber')"
v-model="params.serialNumber" v-model="params.serialNumber"
is-outlined
lazy-rules lazy-rules
> >
<template #prepend> <template #prepend>
<QIcon name="badge" size="sm"></QIcon> <QIcon name="badge" size="sm"></QIcon>
</template> </template>
</QInput> </VnInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('params.serial')" :label="t('params.serial')"
v-model="params.serial" v-model="params.serial"
is-outlined
lazy-rules lazy-rules
> >
<template #prepend> <template #prepend>
<QIcon name="badge" size="sm"></QIcon> <QIcon name="badge" size="sm"></QIcon>
</template> </template>
</QInput> </VnInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput :label="t('Amount')" v-model="params.amount" lazy-rules> <VnInput
:label="t('Amount')"
v-model="params.amount"
is-outlined
lazy-rules
>
<template #prepend> <template #prepend>
<QIcon name="euro" size="sm"></QIcon> <QIcon name="euro" size="sm"></QIcon>
</template> </template>
</QInput> </VnInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-md"> <QItem class="q-mb-md">
@ -127,137 +149,57 @@ const suppliersRef = ref();
<QExpansionItem :label="t('More options')" expand-separator> <QExpansionItem :label="t('More options')" expand-separator>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('params.awb')" :label="t('params.awb')"
v-model="params.awbCode" v-model="params.awbCode"
is-outlined
lazy-rules lazy-rules
> >
<template #prepend> <template #prepend>
<QIcon name="badge" size="sm"></QIcon> <QIcon name="badge" size="sm"></QIcon>
</template> </template>
</QInput> </VnInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('params.account')" :label="t('params.account')"
v-model="params.account" v-model="params.account"
is-outlined
lazy-rules lazy-rules
> >
<template #prepend> <template #prepend>
<QIcon name="person" size="sm" /> <QIcon name="person" size="sm" />
</template> </template>
</QInput> </VnInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput :label="t('From')" v-model="params.from" mask="date"> <VnInputDate
<template #append> :label="t('From')"
<QIcon name="event" class="cursor-pointer"> v-model="params.from"
<QPopupProxy is-outlined
cover />
transition-show="scale"
transition-hide="scale"
>
<QDate v-model="params.from" landscape>
<div
class="row items-center justify-end q-gutter-sm"
>
<QBtn
:label="t('globals.cancel')"
color="primary"
flat
v-close-popup
/>
<QBtn
:label="t('globals.confirm')"
color="primary"
flat
@click="save"
v-close-popup
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput :label="t('To')" v-model="params.to" mask="date"> <VnInputDate
<template #append> :label="t('To')"
<QIcon name="event" class="cursor-pointer"> v-model="params.to"
<QPopupProxy is-outlined
cover />
transition-show="scale"
transition-hide="scale"
>
<QDate v-model="params.to" landscape>
<div
class="row items-center justify-end q-gutter-sm"
>
<QBtn
:label="t('globals.cancel')"
color="primary"
flat
v-close-popup
/>
<QBtn
:label="t('globals.confirm')"
color="primary"
flat
@click="save"
v-close-popup
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInputDate
:label="t('Issued')" :label="t('Issued')"
v-model="params.issued" v-model="params.issued"
mask="date" is-outlined
> />
<template #append>
<QIcon name="event" class="cursor-pointer">
<QPopupProxy
cover
transition-show="scale"
transition-hide="scale"
>
<QDate v-model="params.issued" landscape>
<div
class="row items-center justify-end q-gutter-sm"
>
<QBtn
:label="t('globals.cancel')"
color="primary"
flat
v-close-popup
/>
<QBtn
:label="t('globals.confirm')"
color="primary"
flat
@click="save"
v-close-popup
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
</QExpansionItem> </QExpansionItem>
@ -266,6 +208,15 @@ const suppliersRef = ref();
</VnFilterPanel> </VnFilterPanel>
</template> </template>
<style scoped>
.list {
width: 256px;
}
.list * {
max-width: 100%;
}
</style>
<i18n> <i18n>
en: en:
params: params:

View File

@ -154,7 +154,7 @@ function viewSummary(id) {
</VnPaginate> </VnPaginate>
</div> </div>
</QPage> </QPage>
<QPageSticky position="bottom-right" :offset="[25, 25]"> <QPageSticky position="bottom-right" :offset="[20, 20]">
<QBtn <QBtn
color="primary" color="primary"
icon="add" icon="add"

View File

@ -1,8 +1,10 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
const { t } = useI18n(); const { t } = useI18n();
@ -39,47 +41,31 @@ function setWorkers(data) {
</div> </div>
</template> </template>
<template #body="{ params, searchFn }"> <template #body="{ params, searchFn }">
<QList dense> <QList dense class="q-gutter-y-sm q-mt-sm">
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('Customer ID')" :label="t('Customer ID')"
class="q-mt-sm"
dense
lazy-rules
outlined
rounded
v-model="params.clientFk" v-model="params.clientFk"
is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput v-model="params.fi" :label="t('FI')" is-outlined />
:label="t('FI')"
class="q-mt-sm"
dense
lazy-rules
outlined
rounded
v-model="params.fi"
/>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('Amount')" :label="t('Amount')"
class="q-mt-sm"
dense
lazy-rules
outlined
rounded
v-model="params.amount" v-model="params.amount"
is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mt-sm"> <QItem>
<QItemSection> <QItemSection>
<QInput <QInput
:label="t('Min')" :label="t('Min')"
@ -103,7 +89,7 @@ function setWorkers(data) {
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-md"> <QItem>
<QItemSection> <QItemSection>
<QCheckbox <QCheckbox
:label="t('Has PDF')" :label="t('Has PDF')"
@ -120,9 +106,7 @@ function setWorkers(data) {
<VnInputDate <VnInputDate
v-model="params.issued" v-model="params.issued"
:label="t('Issued')" :label="t('Issued')"
dense is-outlined
outlined
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -131,9 +115,7 @@ function setWorkers(data) {
<VnInputDate <VnInputDate
v-model="params.created" v-model="params.created"
:label="t('Created')" :label="t('Created')"
dense is-outlined
outlined
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -142,9 +124,7 @@ function setWorkers(data) {
<VnInputDate <VnInputDate
v-model="params.dued" v-model="params.dued"
:label="t('Dued')" :label="t('Dued')"
dense is-outlined
outlined
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>

View File

@ -175,7 +175,7 @@ onUnmounted(() => {
.col-content { .col-content {
border-radius: 4px; border-radius: 4px;
padding: 6px 6px 6px 6px; padding: 6px;
} }
</style> </style>

View File

@ -1,11 +1,13 @@
<script setup> <script setup>
import { onMounted, ref, computed } from 'vue'; import { onMounted, ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useInvoiceOutGlobalStore } from 'src/stores/invoiceOutGlobal.js';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnInputDate from "components/common/VnInputDate.vue"; import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import { useInvoiceOutGlobalStore } from 'src/stores/invoiceOutGlobal.js';
const { t } = useI18n(); const { t } = useI18n();
const invoiceOutGlobalStore = useInvoiceOutGlobalStore(); const invoiceOutGlobalStore = useInvoiceOutGlobalStore();
@ -55,18 +57,6 @@ const getStatus = computed({
}, },
}); });
const onFetchCompanies = (companies) => {
companiesOptions.value = [...companies];
};
const onFetchPrinters = (printers) => {
printersOptions.value = [...printers];
};
const onFetchClients = (clients) => {
clientsOptions.value = [...clients];
};
onMounted(async () => { onMounted(async () => {
await invoiceOutGlobalStore.init(); await invoiceOutGlobalStore.init();
formData.value = { ...formInitialData.value }; formData.value = { ...formInitialData.value };
@ -74,9 +64,13 @@ onMounted(async () => {
</script> </script>
<template> <template>
<FetchData url="Companies" @on-fetch="(data) => onFetchCompanies(data)" auto-load /> <FetchData
<FetchData url="Printers" @on-fetch="(data) => onFetchPrinters(data)" auto-load /> url="Companies"
<FetchData url="Clients" @on-fetch="(data) => onFetchClients(data)" auto-load /> @on-fetch="(data) => (companiesOptions = data)"
auto-load
/>
<FetchData url="Printers" @on-fetch="(data) => (printersOptions = data)" auto-load />
<FetchData url="Clients" @on-fetch="(data) => (clientsOptions = data)" auto-load />
<QForm <QForm
v-if="!initialDataLoading && optionsInitialData" v-if="!initialDataLoading && optionsInitialData"
@ -84,7 +78,7 @@ onMounted(async () => {
class="form-container q-pa-md" class="form-container q-pa-md"
style="max-width: 256px" style="max-width: 256px"
> >
<div class="column q-gutter-y-md"> <div class="column q-gutter-y-sm">
<QRadio <QRadio
v-model="clientsToInvoice" v-model="clientsToInvoice"
dense dense
@ -98,6 +92,7 @@ onMounted(async () => {
val="one" val="one"
:label="t('oneClient')" :label="t('oneClient')"
:dark="true" :dark="true"
class="q-mb-sm"
/> />
<VnSelectFilter <VnSelectFilter
v-if="clientsToInvoice === 'one'" v-if="clientsToInvoice === 'one'"
@ -114,15 +109,13 @@ onMounted(async () => {
<VnInputDate <VnInputDate
v-model="formData.invoiceDate" v-model="formData.invoiceDate"
:label="t('invoiceDate')" :label="t('invoiceDate')"
dense is-outlined
outlined />
rounded />
<VnInputDate <VnInputDate
v-model="formData.maxShipped" v-model="formData.maxShipped"
:label="t('maxShipped')" :label="t('maxShipped')"
dense is-outlined
outlined />
rounded />
<VnSelectFilter <VnSelectFilter
:label="t('company')" :label="t('company')"
v-model="formData.companyFk" v-model="formData.companyFk"

View File

@ -1,41 +1,54 @@
<script setup> <script setup>
import { onMounted, ref, reactive } from 'vue'; import { ref, computed, onBeforeMount } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { QCheckbox, QBtn } from 'quasar';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import invoiceOutService from 'src/services/invoiceOut.service'; import InvoiceOutNegativeFilter from './InvoiceOutNegativeBasesFilter.vue';
import { toCurrency } from 'src/filters'; import { toCurrency } from 'src/filters';
import { QCheckbox, QBtn } from 'quasar';
import { useInvoiceOutGlobalStore } from 'src/stores/invoiceOutGlobal.js'; import { useInvoiceOutGlobalStore } from 'src/stores/invoiceOutGlobal.js';
import VnInputDate from 'components/common/VnInputDate.vue'; import { useStateStore } from 'stores/useStateStore';
import { useArrayData } from 'composables/useArrayData';
const invoiceOutGlobalStore = useInvoiceOutGlobalStore(); const invoiceOutGlobalStore = useInvoiceOutGlobalStore();
const stateStore = useStateStore();
const rows = ref([]);
const { t } = useI18n(); const { t } = useI18n();
const dateRange = reactive({ const arrayData = ref(null);
from: Date.vnFirstDayOfMonth().toISOString(),
to: Date.vnLastDayOfMonth().toISOString(), function exprBuilder(param, value) {
switch (param) {
case 'from':
case 'to':
return;
default:
return { [param]: value };
}
}
onBeforeMount(async () => {
const defaultParams = {
from: Date.vnFirstDayOfMonth().toISOString(),
to: Date.vnLastDayOfMonth().toISOString(),
};
arrayData.value = useArrayData('InvoiceOutNegative', {
url: 'InvoiceOuts/negativeBases',
limit: 0,
userParams: defaultParams,
exprBuilder: exprBuilder,
});
await arrayData.value.fetch({ append: false });
stateStore.rightDrawer = true;
}); });
const rows = computed(() => arrayData.value.store.data);
const selectedCustomerId = ref(0); const selectedCustomerId = ref(0);
const selectedWorkerId = ref(0); const selectedWorkerId = ref(0);
const filter = ref({
company: null,
country: null,
clientId: null,
client: null,
amount: null,
base: null,
ticketId: null,
active: null,
hasToInvoice: null,
verifiedData: null,
comercial: null,
});
const tableColumnComponents = { const tableColumnComponents = {
company: { company: {
component: 'span', component: 'span',
@ -103,70 +116,70 @@ const tableColumnComponents = {
}, },
}; };
const columns = ref([ const columns = computed(() => [
{ {
label: 'company', label: t('invoiceOut.negativeBases.company'),
field: 'company', field: 'company',
name: 'company', name: 'company',
align: 'left', align: 'left',
}, },
{ {
label: 'country', label: t('invoiceOut.negativeBases.country'),
field: 'country', field: 'country',
name: 'country', name: 'country',
align: 'left', align: 'left',
}, },
{ {
label: 'clientId', label: t('invoiceOut.negativeBases.clientId'),
field: 'clientId', field: 'clientId',
name: 'clientId', name: 'clientId',
align: 'left', align: 'left',
}, },
{ {
label: 'client', label: t('invoiceOut.negativeBases.client'),
field: 'clientSocialName', field: 'clientSocialName',
name: 'client', name: 'client',
align: 'left', align: 'left',
}, },
{ {
label: 'amount', label: t('invoiceOut.negativeBases.amount'),
field: 'amount', field: 'amount',
name: 'amount', name: 'amount',
align: 'left', align: 'left',
format: (value) => toCurrency(value), format: (value) => toCurrency(value),
}, },
{ {
label: 'base', label: t('invoiceOut.negativeBases.base'),
field: 'taxableBase', field: 'taxableBase',
name: 'base', name: 'base',
align: 'left', align: 'left',
}, },
{ {
label: 'ticketId', label: t('invoiceOut.negativeBases.ticketId'),
field: 'ticketFk', field: 'ticketFk',
name: 'ticketId', name: 'ticketId',
align: 'left', align: 'left',
}, },
{ {
label: 'active', label: t('invoiceOut.negativeBases.active'),
field: 'isActive', field: 'isActive',
name: 'active', name: 'active',
align: 'left', align: 'left',
}, },
{ {
label: 'hasToInvoice', label: t('invoiceOut.negativeBases.hasToInvoice'),
field: 'hasToInvoice', field: 'hasToInvoice',
name: 'hasToInvoice', name: 'hasToInvoice',
align: 'left', align: 'left',
}, },
{ {
label: 'verifiedData', label: t('invoiceOut.negativeBases.verifiedData'),
field: 'isTaxDataChecked', field: 'isTaxDataChecked',
name: 'verifiedData', name: 'verifiedData',
align: 'left', align: 'left',
}, },
{ {
label: 'comercial', label: t('invoiceOut.negativeBases.comercial'),
field: 'comercialName', field: 'comercialName',
name: 'comercial', name: 'comercial',
align: 'left', align: 'left',
@ -174,8 +187,7 @@ const columns = ref([
]); ]);
const downloadCSV = async () => { const downloadCSV = async () => {
const params = filter.value; const params = {}; // filter.value;
const filterParams = { const filterParams = {
limit: 20, limit: 20,
where: { where: {
@ -187,57 +199,12 @@ const downloadCSV = async () => {
} }
await invoiceOutGlobalStore.getNegativeBasesCsv( await invoiceOutGlobalStore.getNegativeBasesCsv(
dateRange.from, arrayData.value.store.userParams.from,
dateRange.to, arrayData.value.store.userParams.to,
JSON.stringify(filterParams) params
); );
}; };
const search = async () => {
const and = [];
Object.keys(filter.value).forEach((key) => {
if (filter.value[key]) {
and.push({
[key]: filter.value[key],
});
}
});
const searchFilter = {
limit: 20,
};
if (and.length) {
searchFilter.where = {
and,
};
}
const params = {
...dateRange,
filter: JSON.stringify(searchFilter),
};
rows.value = await invoiceOutService.getNegativeBases(params);
};
const refresh = () => {
dateRange.from = Date.vnFirstDayOfMonth().toISOString();
dateRange.to = Date.vnLastDayOfMonth().toISOString();
filter.value = {
company: null,
country: null,
clientId: null,
client: null,
amount: null,
base: null,
ticketId: null,
active: null,
hasToInvoice: null,
verifiedData: null,
comercial: null,
};
search();
};
const selectCustomerId = (id) => { const selectCustomerId = (id) => {
selectedCustomerId.value = id; selectedCustomerId.value = id;
}; };
@ -245,86 +212,33 @@ const selectCustomerId = (id) => {
const selectWorkerId = (id) => { const selectWorkerId = (id) => {
selectedWorkerId.value = id; selectedWorkerId.value = id;
}; };
onMounted(() => refresh());
</script> </script>
<template> <template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
<QBtn color="primary" icon-right="archive" no-caps @click="downloadCSV()" />
</Teleport>
</template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<InvoiceOutNegativeFilter data-key="InvoiceOutNegative" />
</QScrollArea>
</QDrawer>
<QToolbar class="bg-vn-dark">
<div id="st-data"></div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<QPage class="column items-center q-pa-md"> <QPage class="column items-center q-pa-md">
<QTable <QTable
:rows="rows"
:columns="columns" :columns="columns"
:rows="rows"
hide-bottom hide-bottom
row-key="clientId" row-key="clientId"
:pagination="{ rowsPerPage: 0 }" :pagination="{ rowsPerPage: 0 }"
class="full-width q-mt-md" class="full-width q-mt-md"
> >
<template #top-left>
<div class="row justify-start items-end">
<VnInputDate
v-model="dateRange.from"
:label="t('invoiceOut.negativeBases.from')"
class="q-mr-md"
dense
lazy-rules
outlined
rounded
/>
<VnInputDate
v-model="dateRange.to"
:label="t('invoiceOut.negativeBases.to')"
class="q-mr-md"
dense
lazy-rules
outlined
rounded
/>
<QBtn
color="primary"
icon-right="archive"
no-caps
@click="downloadCSV()"
/>
</div>
</template>
<template #top-right>
<div class="row justify-start items-center">
<span class="q-mr-md text-results">
{{ rows.length }} {{ t('results') }}
</span>
<QBtn
color="primary"
icon-right="search"
no-caps
class="q-mr-sm"
@click="search()"
/>
<QBtn color="primary" icon-right="refresh" no-caps @click="refresh" />
</div>
</template>
<template #header="props">
<QTr :props="props" class="full-height">
<QTh v-for="col in props.cols" :key="col.name" :props="props">
<div class="column justify-start items-start full-height">
{{ t(`invoiceOut.negativeBases.${col.label}`) }}
<QInput
:class="{
invisible:
col.field === 'isActive' ||
col.field === 'hasToInvoice' ||
col.field === 'isTaxDataChecked',
}"
dense
outlined
rounded
v-model="filter[col.field]"
type="text"
@keyup.enter="search()"
/>
</div>
</QTh>
</QTr>
</template>
<template #body-cell="props"> <template #body-cell="props">
<QTd :props="props"> <QTd :props="props">
<component <component
@ -341,14 +255,8 @@ onMounted(() => refresh());
" "
>{{ props.value }} >{{ props.value }}
</template> </template>
<CustomerDescriptorProxy <CustomerDescriptorProxy :id="selectedCustomerId" />
v-if="props.col.name === 'clientId'" <WorkerDescriptorProxy :id="selectedWorkerId" />
:id="selectedCustomerId"
/>
<WorkerDescriptorProxy
v-if="props.col.name === 'comercial'"
:id="selectedWorkerId"
/>
</component> </component>
</QTd> </QTd>
</template> </template>
@ -359,11 +267,7 @@ onMounted(() => refresh());
<style lang="scss" scoped> <style lang="scss" scoped>
.col-content { .col-content {
border-radius: 4px; border-radius: 4px;
padding: 6px 6px 6px 6px; padding: 6px;
}
.text-results {
color: var(--vn-label);
} }
</style> </style>

View File

@ -0,0 +1,134 @@
<script setup>
import { useI18n } from 'vue-i18n';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
</script>
<template>
<VnFilterPanel
:data-key="props.dataKey"
:search-button="true"
:unremovable-params="['from', 'to']"
>
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params }">
<QList dense class="q-gutter-y-sm q-mt-sm">
<QItem>
<QItemSection>
<VnInputDate
v-model="params.from"
:label="t('invoiceOut.negativeBases.from')"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputDate
v-model="params.to"
:label="t('invoiceOut.negativeBases.to')"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
v-model="params.company"
:label="t('invoiceOut.negativeBases.company')"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
v-model="params.country"
:label="t('invoiceOut.negativeBases.country')"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
v-model="params.clientId"
:label="t('invoiceOut.negativeBases.clientId')"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
v-model="params.clientSocialName"
:label="t('invoiceOut.negativeBases.client')"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
v-model="params.amount"
:label="t('invoiceOut.negativeBases.amount')"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
v-model="params.comercialName"
:label="t('invoiceOut.negativeBases.comercial')"
is-outlined
/>
</QItemSection>
</QItem>
</QList>
</template>
</VnFilterPanel>
</template>
<style scoped></style>
<i18n>
en:
params:
from: From
to: To
company: Company
country: Country
clientId: Client Id
clientSocialName: Client
amount: Amount
comercialName: Comercial
es:
params:
from: Desde
to: Hasta
company: Empresa
country: País
clientId: Id cliente
clientSocialName: Cliente
amount: Importe
comercialName: Comercial
Date is required: La fecha es requerida
</i18n>

View File

@ -3,12 +3,13 @@ import { ref } from 'vue';
import { Notify, useQuasar } from 'quasar'; import { Notify, useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import axios from 'axios';
import { useSession } from 'src/composables/useSession'; import { useSession } from 'src/composables/useSession';
import { useLogin } from 'src/composables/useLogin'; import { useLogin } from 'src/composables/useLogin';
import VnLogo from 'components/ui/VnLogo.vue'; import VnLogo from 'components/ui/VnLogo.vue';
import VnInput from 'src/components/common/VnInput.vue';
import axios from 'axios';
const quasar = useQuasar(); const quasar = useQuasar();
const session = useSession(); const session = useSession();
@ -68,13 +69,14 @@ async function onSubmit() {
<template> <template>
<QForm @submit="onSubmit" class="q-gutter-y-md q-pa-lg formCard"> <QForm @submit="onSubmit" class="q-gutter-y-md q-pa-lg formCard">
<VnLogo alt="Logo" fit="contain" :ratio="16 / 9" class="q-mb-md" /> <VnLogo alt="Logo" fit="contain" :ratio="16 / 9" class="q-mb-md" />
<QInput
<VnInput
v-model="username" v-model="username"
:label="t('login.username')" :label="t('login.username')"
lazy-rules lazy-rules
:rules="[(val) => (val && val.length > 0) || t('login.fieldRequired')]" :rules="[(val) => (val && val.length > 0) || t('login.fieldRequired')]"
/> />
<QInput <VnInput
type="password" type="password"
v-model="password" v-model="password"
:label="t('login.password')" :label="t('login.password')"

View File

@ -53,7 +53,7 @@ async function onSubmit() {
<QIcon name="phonelink_lock" size="xl" color="primary" /> <QIcon name="phonelink_lock" size="xl" color="primary" />
<h5 class="text-center q-my-md">{{ t('twoFactor.insert') }}</h5> <h5 class="text-center q-my-md">{{ t('twoFactor.insert') }}</h5>
</div> </div>
<QInput <VnInput
v-model="code" v-model="code"
:hint="t('twoFactor.explanation')" :hint="t('twoFactor.explanation')"
mask="# # # # # #" mask="# # # # # #"
@ -64,7 +64,7 @@ async function onSubmit() {
<template #prepend> <template #prepend>
<QIcon name="lock" /> <QIcon name="lock" />
</template> </template>
</QInput> </VnInput>
<div class="q-mt-xl"> <div class="q-mt-xl">
<QBtn <QBtn
:label="t('twoFactor.validate')" :label="t('twoFactor.validate')"

View File

@ -0,0 +1,12 @@
<script setup>
import OrderForm from 'pages/Order/Card/OrderForm.vue';
</script>
<template>
<QToolbar>
<div id="st-data"></div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<OrderForm />
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,23 @@
<script setup>
import LeftMenu from 'components/LeftMenu.vue';
import { useStateStore } from 'stores/useStateStore';
import OrderDescriptor from 'pages/Order/Card/OrderDescriptor.vue';
const stateStore = useStateStore();
</script>
<template>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit">
<OrderDescriptor />
<QSeparator />
<LeftMenu source="card" />
</QScrollArea>
</QDrawer>
<QPageContainer>
<QPage>
<div class="q-pa-md">
<RouterView></RouterView>
</div>
</QPage>
</QPageContainer>
</template>

View File

@ -0,0 +1,206 @@
<script setup>
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import axios from 'axios';
import { useRoute } from 'vue-router';
const { t } = useI18n();
const route = useRoute();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const categoryList = ref(null);
const selectedCategoryFk = ref(null);
const typeList = ref(null);
const selectedTypeFk = ref(null);
const selectCategory = (params, category) => {
if (params.categoryFk === category?.id) {
selectedCategoryFk.value = null;
params.categoryFk = null;
typeList.value = null;
} else {
selectedCategoryFk.value = category?.id;
params.categoryFk = category?.id;
loadTypes(category?.id);
}
};
const loadTypes = async (categoryFk) => {
const { data } = await axios.get(`Orders/${route.params.id}/getItemTypeAvailable`, {
params: { itemCategoryId: categoryFk },
});
typeList.value = data;
};
const onFilterInit = async ({ params }) => {
if (params.typeFk) {
selectedTypeFk.value = params.typeFk;
}
if (params.categoryFk) {
await loadTypes(params.categoryFk);
selectedCategoryFk.value = params.categoryFk;
}
};
const selectedCategory = computed(() =>
(categoryList.value || []).find(
(category) => category?.id === selectedCategoryFk.value
)
);
const selectedType = computed(() => {
return (typeList.value || []).find((type) => type?.id === selectedTypeFk.value);
});
function exprBuilder(param, value) {
switch (param) {
case 'categoryFk':
case 'typeFk':
return { [param]: value };
}
}
const setCategoryList = (data) => {
categoryList.value = (data || [])
.filter((category) => category.display)
.map((category) => ({
...category,
icon: `vn:${(category.icon || '').split('-')[1]}`,
}));
};
const getCategoryClass = (category, params) => {
if (category.id === params?.categoryFk) {
return 'active';
}
};
</script>
<template>
<FetchData url="ItemCategories" limit="30" auto-load @on-fetch="setCategoryList" />
<VnFilterPanel
:data-key="props.dataKey"
:search-button="true"
:hidden-tags="['orderFk', 'orderBy']"
:expr-builder="exprBuilder"
@init="onFilterInit"
>
<template #tags="{ tag, formatFn }">
<strong v-if="tag.label === 'categoryFk'">
{{ t(selectedCategory?.name || '') }}
</strong>
<strong v-else-if="tag.label === 'typeFk'">
{{ t(selectedType?.name || '') }}
</strong>
<div v-else class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params }">
<QList dense>
<QItem class="category-filter q-mt-md">
<div
v-for="category in categoryList"
:key="category.name"
:class="['category', getCategoryClass(category, params)]"
>
<QIcon
:name="category.icon"
class="category-icon"
@click="selectCategory(params, category)"
>
<QTooltip>
{{ t(category.name) }}
</QTooltip>
</QIcon>
</div>
</QItem>
<QItem class="q-mt-md">
<QItemSection>
<VnSelectFilter
:label="t('params.type')"
v-model="params.typeFk"
:options="typeList"
option-value="id"
option-label="name"
dense
outlined
rounded
emit-value
use-input
:disable="!selectedCategoryFk"
@update:model-value="(value) => (selectedTypeFk = value)"
>
<template #option="{ itemProps, opt }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel>{{ opt.name }}</QItemLabel>
<QItemLabel caption>
{{ opt.categoryName }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</QItemSection>
</QItem>
</QList>
</template>
</VnFilterPanel>
</template>
<style lang="scss" scoped>
.category-filter {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
.category {
flex: 1;
flex-shrink: 0;
display: flex;
justify-content: center;
&.active {
.category-icon {
background-color: $primary;
}
}
}
.category-icon {
border-radius: 50%;
background-color: var(--vn-label);
font-size: 2.6rem;
padding: 8px;
cursor: pointer;
}
}
</style>
<i18n>
en:
params:
type: Type
orderBy: Order By
es:
params:
type: Tipo
orderBy: Ordenar por
Plant: Planta
Flower: Flor
Handmade: Confección
Green: Verde
Accessories: Complemento
Fruit: Fruta
</i18n>

View File

@ -0,0 +1,186 @@
<script setup>
import { useSession } from 'composables/useSession';
import VnLv from 'components/ui/VnLv.vue';
import { useI18n } from 'vue-i18n';
import OrderCatalogItemDialog from 'pages/Order/Card/OrderCatalogItemDialog.vue';
import toCurrency from '../../../filters/toCurrency';
import { ref } from 'vue';
const DEFAULT_PRICE_KG = 0;
const session = useSession();
const token = session.getToken();
const { t } = useI18n();
defineProps({
item: {
type: Object,
required: true,
},
});
const dialog = ref(null);
</script>
<template>
<div class="container order-catalog-item overflow-hidden">
<div class="card shadow-6 bg-dark">
<div class="img-wrapper">
<QImg
:src="`/api/Images/catalog/200x200/${item.id}/download?access_token=${token}`"
spinner-color="primary"
:ratio="1"
height="192"
width="192"
class="image"
/>
<div v-if="item.hex" class="item-color-container">
<div
class="item-color"
:style="{ backgroundColor: `#${item.hex}` }"
></div>
</div>
</div>
<div class="content">
<span class="link">{{ item.name }}</span>
<p class="subName">{{ item.subName }}</p>
<template v-for="index in 4" :key="`tag-${index}`">
<VnLv
v-if="item?.[`tag${index + 4}`]"
:label="item?.[`tag${index + 4}`] + ':'"
:value="item?.[`value${index + 4}`]"
/>
</template>
<QRating
:model-value="item.stars"
icon="star"
icon-selected="star"
color="primary"
readonly
/>
<div class="footer">
<div class="price">
<p>{{ item.available }} {{ t('to') }} {{ item.price }}</p>
<QIcon name="add_circle" class="icon">
<QTooltip>{{ t('globals.add') }}</QTooltip>
<QPopupProxy ref="dialog">
<OrderCatalogItemDialog
:prices="item.prices"
@added="() => dialog.hide()"
/>
</QPopupProxy>
</QIcon>
</div>
<p v-if="item.priceKg" class="price-kg">
{{ t('price-kg') }} {{ toCurrency(item.priceKg) || DEFAULT_PRICE_KG }}
</p>
</div>
</div>
</div>
</div>
</template>
<style lang="scss">
.order-catalog-item {
.vn-label-value {
display: flex;
gap: 4px;
font-size: 11px;
.label {
color: var(--vn-label);
}
.value {
color: var(--vn-text);
}
}
}
</style>
<style lang="scss" scoped>
.container {
max-width: 448px;
width: 100%;
}
.card {
display: flex;
height: 100%;
max-height: 192px;
}
.card > * {
flex: 1;
}
.img-wrapper {
position: relative;
max-width: 192px;
}
.content {
padding: 12px;
display: flex;
flex-direction: column;
gap: 4px;
.subName {
color: var(--vn-label);
text-transform: uppercase;
}
p {
margin-bottom: 0;
}
}
.footer {
.price {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
display: flex;
align-items: center;
justify-content: space-between;
p {
font-size: 12px;
}
.icon {
color: $primary;
font-size: 24px;
cursor: pointer;
}
}
.price-kg {
font-size: 12px;
}
}
.item-color-container {
position: absolute;
bottom: 12px;
right: 12px;
background: linear-gradient($dark, $primary);
border-radius: 50%;
width: 40px;
height: 40px;
padding: 4px;
.item-color {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
</style>
<i18n>
es:
to: to
price-kg: Precio por Kg
en:
to: hasta
price-kg: Price per Kg
</i18n>

View File

@ -0,0 +1,82 @@
<script setup>
import toCurrency from '../../../filters/toCurrency';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import { useRoute } from 'vue-router';
import useNotify from 'composables/useNotify';
const { t } = useI18n();
const route = useRoute();
const { notify } = useNotify();
const props = defineProps({
prices: {
type: Array,
required: true,
},
});
const emit = defineEmits(['added']);
const fields = ref((props.prices || []).map((item) => ({ ...item, quantity: 0 })));
const addToOrder = async () => {
const items = (fields.value || []).filter((item) => Number(item.quantity) > 0);
await axios.post('/OrderRows/addToOrder', {
items,
orderFk: Number(route.params.id),
});
notify(t('globals.dataSaved'), 'positive');
emit('added');
};
</script>
<template>
<div class="container order-catalog-item q-pb-md">
<QForm @submit.prevent="addToOrder">
<QMarkupTable class="shadow-0">
<tbody>
<tr v-for="item in fields" :key="item.warehouse">
<td class="text-bold q-py-lg">
{{ item.warehouse }}
</td>
<td class="text-right">
<span
class="link"
@click="
() => {
item.quantity += item.grouping;
}
"
>
{{ item.grouping }}
</span>
x {{ toCurrency(item.price) }}
</td>
<td class="text-right">
<QInput
v-model.number="item.quantity"
type="number"
:step="item.grouping"
min="0"
dense
/>
</td>
</tr>
</tbody>
</QMarkupTable>
<div class="flex justify-center q-mt-lg">
<QBtn color="primary" type="submit">
{{ t('globals.add') }}
</QBtn>
</div>
</QForm>
</div>
</template>
<style lang="scss" scoped>
.container {
max-width: 448px;
width: 100%;
}
</style>

View File

@ -0,0 +1,131 @@
<script setup>
import { ref, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { toCurrency, toDate } from 'src/filters';
import { useState } from 'src/composables/useState';
import useCardDescription from 'src/composables/useCardDescription';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import OrderDescriptorMenu from 'pages/Order/Card/OrderDescriptorMenu.vue';
const DEFAULT_ITEMS = 0;
const $props = defineProps({
id: {
type: Number,
required: false,
default: null,
},
});
const route = useRoute();
const state = useState();
const { t } = useI18n();
const entityId = computed(() => {
return $props.id || route.params.id;
});
const filter = {
include: [
{ relation: 'agencyMode', scope: { fields: ['name'] } },
{
relation: 'address',
scope: { fields: ['nickname'] },
},
{ relation: 'rows', scope: { fields: ['id'] } },
{
relation: 'client',
scope: {
fields: [
'salesPersonFk',
'name',
'isActive',
'isFreezed',
'isTaxDataChecked',
],
include: {
relation: 'salesPersonUser',
scope: { fields: ['id', 'name'] },
},
},
},
],
};
const data = ref(useCardDescription());
const setData = (entity) => {
if (!entity) return;
data.value = useCardDescription(entity.client.name, entity.id);
state.set('ClaimDescriptor', entity);
};
const getConfirmationValue = (isConfirmed) => {
return t(isConfirmed ? 'order.summary.confirmed' : 'order.summary.notConfirmed');
};
</script>
<template>
<CardDescriptor
ref="descriptor"
:url="`Orders/${entityId}`"
:filter="filter"
module="Order"
:title="data.title"
:subtitle="data.subtitle"
@on-fetch="setData"
data-key="orderData"
>
<template #menu="{ entity }">
<OrderDescriptorMenu :order="entity" />
</template>
<template #body="{ entity }">
<VnLv
:label="t('order.summary.state')"
:value="getConfirmationValue(entity.isConfirmed)"
/>
<VnLv :label="t('order.field.salesPersonFk')">
<template #value>
<span class="link">
{{ entity?.client?.salesPersonUser?.name || '-' }}
<WorkerDescriptorProxy :id="entity?.client?.salesPersonFk" />
</span>
</template>
</VnLv>
<VnLv :label="t('order.summary.landed')" :value="toDate(entity?.landed)" />
<VnLv :label="t('order.field.agency')" :value="entity?.agencyMode?.name" />
<VnLv :label="t('order.summary.alias')" :value="entity?.address?.nickname" />
<VnLv
:label="t('order.summary.items')"
:value="(entity?.rows?.length || DEFAULT_ITEMS).toString()"
/>
<VnLv :label="t('order.summary.total')" :value="toCurrency(entity?.total)" />
</template>
<template #actions="{ entity }">
<QCardActions>
<QBtn
size="md"
icon="vn:ticket"
color="primary"
:to="{
name: 'TicketList',
query: { params: JSON.stringify({ orderFk: entity.id }) },
}"
>
<QTooltip>{{ t('order.summary.orderTicketList') }}</QTooltip>
</QBtn>
<QBtn
size="md"
icon="vn:client"
color="primary"
:to="{ name: 'CustomerCard', params: { id: entity.clientFk } }"
>
<QTooltip>{{ t('claim.card.customerSummary') }}</QTooltip>
</QBtn>
</QCardActions>
</template>
</CardDescriptor>
</template>

View File

@ -0,0 +1,63 @@
<script setup>
import axios from 'axios';
import { ref } from 'vue';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import VnConfirm from 'components/ui/VnConfirm.vue';
const $props = defineProps({
order: {
type: Object,
required: true,
},
});
const router = useRouter();
const quasar = useQuasar();
const { t } = useI18n();
const order = ref($props.order);
function confirmRemove() {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('confirmDeletion'),
message: t('confirmDeletionMessage'),
promise: remove,
},
})
.onOk(async () => await router.push({ name: 'OrderList' }));
}
async function remove() {
const id = order.value.id;
await axios.delete(`Orders/${id}`);
quasar.notify({
message: t('globals.dataDeleted'),
type: 'positive',
});
}
</script>
<template>
<QItem @click="confirmRemove()" v-ripple clickable>
<QItemSection avatar>
<QIcon name="delete" />
</QItemSection>
<QItemSection>{{ t('deleteOrder') }}</QItemSection>
</QItem>
</template>
<i18n>
en:
deleteOrder: Delete order,
confirmDeletion: Confirm deletion,
confirmDeletionMessage: Are you sure you want to delete this order?
es:
deleteOrder: Eliminar pedido,
confirmDeletion: Confirmar eliminación,
confirmDeletionMessage: Seguro que quieres eliminar este pedido?
</i18n>

View File

@ -0,0 +1,265 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'components/common/VnInput.vue';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const agencyFilter = { fields: ['id', 'name'] };
const agencyList = ref(null);
const salesPersonFilter = {
fields: ['id', 'nickname'],
};
const salesPersonList = ref(null);
const sourceFilter = { fields: ['value'] };
const sourceList = ref(null);
</script>
<template>
<FetchData
url="AgencyModes/isActive"
:filter="agencyFilter"
limit="30"
sort-by="name ASC"
auto-load
@on-fetch="(data) => (agencyList = data)"
/>
<FetchData
url="Workers/search"
:filter="salesPersonFilter"
limit="30"
sort-by="nickname ASC"
@on-fetch="(data) => (salesPersonList = data)"
:params="{ departmentCodes: ['VT'] }"
auto-load
/>
<FetchData
url="Orders/getSourceValues"
:filter="sourceFilter"
limit="30"
sort-by="value ASC"
@on-fetch="(data) => (sourceList = data)"
auto-load
/>
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params }">
<QList id="orderFilter" dense>
<QItem>
<QItemSection>
<VnInput
is-outlined
:label="t('customerId')"
v-model="params.clientFk"
lazy-rules
>
<template #prepend>
<QIcon name="badge" size="sm"></QIcon>
</template>
</VnInput>
</QItemSection>
</QItem>
<QItem>
<QItemSection v-if="agencyList">
<VnSelectFilter
:label="t('agency')"
v-model="params.agencyModeFk"
:options="agencyList"
option-value="id"
option-label="name"
dense
outlined
rounded
emit-value
map-options
use-input
:input-debounce="0"
/>
</QItemSection>
<QItemSection v-else>
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
</QItem>
<QItem>
<QItemSection v-if="salesPersonList">
<VnSelectFilter
:label="t('salesPerson')"
v-model="params.workerFk"
:options="salesPersonList"
option-value="id"
option-label="name"
dense
outlined
rounded
emit-value
map-options
use-input
:input-debounce="0"
>
<template #option="{ itemProps, opt }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel>{{ opt.name }}</QItemLabel>
<QItemLabel caption>
{{ opt.nickname }},{{ opt.code }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</QItemSection>
<QItemSection v-else>
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputDate
v-model="params.from"
:label="t('fromLanded')"
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputDate
v-model="params.to"
:label="t('toLanded')"
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
:label="t('orderId')"
v-model="params.orderFk"
lazy-rules
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection v-if="sourceList">
<VnSelectFilter
:label="t('application')"
v-model="params.sourceApp"
:options="sourceList"
option-label="value"
emit-value
map-options
use-input
dense
outlined
rounded
:input-debounce="0"
/>
</QItemSection>
<QItemSection v-else>
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QCheckbox
v-model="params.myTeam"
:label="t('myTeam')"
toggle-indeterminate
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QCheckbox
v-model="params.isConfirmed"
:label="t('isConfirmed')"
toggle-indeterminate
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QCheckbox v-model="params.showEmpty" :label="t('showEmpty')" />
</QItemSection>
</QItem>
</QList>
</template>
</VnFilterPanel>
</template>
<style lang="scss">
#orderFilter {
.q-item {
padding-top: 8px;
}
}
</style>
<i18n>
en:
params:
search: Includes
clientFk: Client
agencyModeFk: Agency
salesPersonFk: Sales Person
from: From
to: To
orderFk: Order
sourceApp: Application
myTeam: My Team
isConfirmed: Is Confirmed
showEmpty: Show Empty
customerId: Customer ID
agency: Agency
salesPerson: Sales Person
fromLanded: From Landed
toLanded: To Landed
orderId: Order ID
application: Application
myTeam: My Team
isConfirmed: Order Confirmed
showEmpty: Show Empty
es:
params:
search: Búsqueda
clientFk: Cliente
agencyModeFk: Agencia
salesPersonFk: Comercial
from: Desde
to: Hasta
orderFk: Cesta
sourceApp: Aplicación
myTeam: Mi Equipo
isConfirmed: Confirmado
showEmpty: Mostrar vacías
customerId: ID Cliente
agency: Agencia
salesPerson: Comercial
fromLanded: Desde F. entrega
toLanded: Hasta F. entrega
orderId: ID Cesta
application: Aplicación
myTeam: Mi Equipo
isConfirmed: Confirmado
showEmpty: Mostrar vacías
</i18n>

View File

@ -0,0 +1,209 @@
<script setup>
import { useRoute } from 'vue-router';
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import { useState } from 'composables/useState';
import FormModel from 'components/FormModel.vue';
import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
const { t } = useI18n();
const route = useRoute();
const state = useState();
const ORDER_MODEL = 'order';
const isNew = Boolean(!route.params.id);
const initialFormState = reactive({
clientFk: null,
addressFk: null,
agencyModeFk: null,
landed: null,
});
const clientList = ref([]);
const agencyList = ref([]);
const addressList = ref([]);
const fetchAddressList = async (addressId) => {
try {
const { data } = await axios.get('addresses', {
params: {
filter: JSON.stringify({
fields: ['id', 'nickname', 'street', 'city'],
where: { id: addressId },
}),
},
});
addressList.value = data;
// Set address by default
if (addressList.value?.length === 1) {
state.get(ORDER_MODEL).addressFk = addressList.value[0].id;
}
} catch (err) {
console.error(`Error fetching addresses`, err);
return err.response;
}
};
const fetchAgencyList = async (landed, addressFk) => {
if (!landed || !addressFk) {
return;
}
try {
const { data } = await axios.get('Agencies/landsThatDay', {
params: {
addressFk,
landed: new Date(landed).toISOString(),
},
});
agencyList.value = data;
} catch (err) {
console.error(`Error fetching agencies`, err);
return err.response;
}
};
const fetchOrderDetails = (order) => {
fetchAddressList(order?.addressFk)
fetchAgencyList(order?.landed, order?.addressFk)
};
const orderMapper = (order) => {
return {
addressId: order.addressFk,
agencyModeId: order.agencyModeFk,
landed: new Date(order.landed).toISOString(),
};
};
const orderFilter = {
include: [
{ relation: 'agencyMode', scope: { fields: ['name'] } },
{
relation: 'address',
scope: { fields: ['nickname'] },
},
{ relation: 'rows', scope: { fields: ['id'] } },
{
relation: 'client',
scope: {
fields: [
'salesPersonFk',
'name',
'isActive',
'isFreezed',
'isTaxDataChecked',
],
include: {
relation: 'salesPersonUser',
scope: { fields: ['id', 'name'] },
},
},
},
],
};
</script>
<template>
<QToolbar>
<div id="st-data"></div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<FetchData
url="Clients"
@on-fetch="(data) => (clientList = data)"
:filter="{ fields: ['id', 'name', 'defaultAddressFk'] }"
auto-load
/>
<FormModel
:url="!isNew ? `Orders/${route.params.id}` : null"
:url-create="isNew ? 'Orders/new' : null"
:model="ORDER_MODEL"
:form-initial-data="isNew ? initialFormState : null"
:observe-form-changes="!isNew"
:mapper="isNew ? orderMapper : null"
:filter="orderFilter"
@on-fetch="fetchOrderDetails"
>
<template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('order.form.clientFk')"
v-model="data.clientFk"
:options="clientList"
option-value="id"
option-label="name"
hide-selected
@update:model-value="
(client) => fetchAddressList(client.defaultAddressFk)
"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ `${scope.opt.id}: ${scope.opt.name}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</div>
<div class="col">
<VnSelectFilter
:label="t('order.form.addressFk')"
v-model="data.addressFk"
:options="addressList"
option-value="id"
option-label="nickname"
hide-selected
:disable="!addressList?.length"
@update:model-value="
() => fetchAgencyList(data.landed, data.addressFk)
"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{
`${scope.opt.nickname}: ${scope.opt.street},${scope.opt.city}`
}}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInputDate
placeholder="dd-mm-aaa"
:label="t('order.form.landed')"
v-model="data.landed"
@update:model-value="
() => fetchAgencyList(data.landed, data.addressFk)
"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('order.form.agencyModeFk')"
v-model="data.agencyModeFk"
:options="agencyList"
option-value="agencyModeFk"
option-label="agencyMode"
hide-selected
:disable="!agencyList?.length"
>
</VnSelectFilter>
</div>
</VnRow>
</template>
</FormModel>
</template>

View File

@ -0,0 +1,22 @@
<script setup>
import { useI18n } from 'vue-i18n';
import VnSearchbar from 'components/ui/VnSearchbar.vue';
const { t } = useI18n();
</script>
<template>
<VnSearchbar
data-key="OrderList"
url="Orders/filter"
:label="t('Search order')"
:info="t('You can search orders by reference')"
/>
</template>
<style scoped lang="scss"></style>
<i18n>
es:
Search order: Buscar orden
You can search orders by reference: Puedes buscar por referencia de la orden
</i18n>

View File

@ -0,0 +1,242 @@
<script setup>
import { computed, ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore';
import { dashIfEmpty, toCurrency, toDateHour } from 'src/filters';
import VnLv from 'components/ui/VnLv.vue';
import CardSummary from 'components/ui/CardSummary.vue';
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
import OrderSearchbar from 'pages/Order/Card/OrderSearchbar.vue';
import FetchedTags from 'components/ui/FetchedTags.vue';
const { t } = useI18n();
const route = useRoute();
const stateStore = useStateStore();
const $props = defineProps({
id: {
type: Number,
default: 0,
},
});
const entityId = computed(() => $props.id || route.params.id);
const detailsColumns = ref([
{
name: 'item',
label: t('order.summary.item'),
field: (row) => row?.item?.id,
sortable: true,
},
{
name: 'description',
label: t('order.summary.description'),
field: (row) => row?.item?.name,
},
{
name: 'quantity',
label: t('order.summary.quantity'),
field: (row) => row?.quantity,
},
{
name: 'price',
label: t('order.summary.price'),
field: (row) => toCurrency(row?.price),
},
{
name: 'amount',
label: t('order.summary.amount'),
field: (row) => toCurrency(row?.quantity * row?.price),
},
]);
</script>
<template>
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">
<OrderSearchbar />
</Teleport>
<CardSummary ref="summary" :url="`Orders/${entityId}/summary`">
<template #header="{ entity }">
{{ t('order.summary.basket') }} #{{ entity?.id }} -
{{ entity?.client?.name }} ({{ entity?.clientFk }})
</template>
<template #body="{ entity }">
<QCard class="vn-one">
<VnLv label="ID" :value="entity.id" />
<VnLv :label="t('order.summary.nickname')" dash>
<template #value>
<span class="link">
{{ dashIfEmpty(entity?.address?.nickname) }}
<CustomerDescriptorProxy :id="entity?.clientFk" />
</span>
</template>
</VnLv>
<VnLv
:label="t('order.summary.company')"
:value="entity?.address?.companyFk"
/>
<VnLv
:label="t('order.summary.confirmed')"
:value="Boolean(entity?.isConfirmed)"
/>
</QCard>
<QCard class="vn-one">
<VnLv
:label="t('order.summary.created')"
:value="toDateHour(entity?.created)"
/>
<VnLv
:label="t('order.summary.confirmed')"
:value="toDateHour(entity?.confirmed)"
/>
<VnLv
:label="t('order.summary.landed')"
:value="toDateHour(entity?.landed)"
/>
<VnLv :label="t('order.summary.phone')">
<template #value>
{{ dashIfEmpty(entity?.address?.phone) }}
<a
v-if="entity?.address?.phone"
:href="`tel:${entity?.address?.phone}`"
class="text-primary"
>
<QIcon name="phone" />
</a>
</template>
</VnLv>
<VnLv
:label="t('order.summary.createdFrom')"
:value="entity?.sourceApp"
/>
<VnLv
:label="t('order.summary.address')"
:value="`${entity?.address?.street} - ${entity?.address?.city} (${entity?.address?.province?.name})`"
class="order-summary-address"
/>
</QCard>
<QCard class="vn-one">
<p class="header">
{{ t('order.summary.notes') }}
</p>
<p v-if="entity?.note" class="no-margin">
{{ entity?.note }}
</p>
</QCard>
<QCard class="vn-one">
<VnLv>
<template #label>
<span class="text-h6">{{ t('order.summary.subtotal') }}</span>
</template>
<template #value>
<span class="text-h6">{{ toCurrency(entity?.subTotal) }}</span>
</template>
</VnLv>
<VnLv>
<template #label>
<span class="text-h6">{{ t('order.summary.vat') }}</span>
</template>
<template #value>
<span class="text-h6">{{ toCurrency(entity?.VAT) }}</span>
</template>
</VnLv>
<VnLv>
<template #label>
<span class="text-h6">{{ t('order.summary.total') }}</span>
</template>
<template #value>
<span class="text-h6">{{ toCurrency(entity?.total) }}</span>
</template>
</VnLv>
</QCard>
<QCard>
<p class="header">
{{ t('order.summary.details') }}
</p>
<QTable
:columns="detailsColumns"
:rows="entity?.rows"
flat
hide-pagination
>
<template #header="props">
<QTr :props="props">
<QTh auto-width>{{ t('order.summary.item') }}</QTh>
<QTh>{{ t('order.summary.description') }}</QTh>
<QTh auto-width>{{ t('order.summary.quantity') }}</QTh>
<QTh auto-width>{{ t('order.summary.price') }}</QTh>
<QTh auto-width>{{ t('order.summary.amount') }}</QTh>
</QTr>
</template>
<template #body="props">
<QTr :props="props">
<QTd key="item" :props="props" class="item">
{{ props.row.item?.id }}
</QTd>
<QTd key="description" :props="props" class="description">
<div class="name">
<span>{{ props.row.item.name }}</span>
<span v-if="props.row.item.subName" class="subName">
{{ props.row.item.subName }}
</span>
</div>
<fetched-tags :item="props.row.item" :max-length="5" />
</QTd>
<QTd key="quantity" :props="props">
{{ props.row.quantity }}
</QTd>
<QTd key="price" :props="props">
{{ props.row.price }}
</QTd>
<QTd key="amount" :props="props">
{{ toCurrency(props.row?.quantity * props.row?.price) }}
</QTd>
</QTr>
</template>
</QTable>
</QCard>
</template>
</CardSummary>
</template>
<style lang="scss">
.cardSummary .summaryBody .vn-label-value.order-summary-address {
.label {
flex-shrink: 0;
}
.value {
white-space: normal;
}
}
</style>
<style lang="scss" scoped>
.item {
text-align: center;
}
.description {
display: flex;
flex-direction: column;
justify-content: center;
text-align: left;
height: auto;
padding-top: 12px;
padding-bottom: 12px;
.name {
display: flex;
align-items: center;
padding-bottom: 8px;
& > * {
flex: 1;
}
.subName {
text-transform: uppercase;
color: var(--vn-label);
}
}
}
</style>

View File

@ -0,0 +1,29 @@
<script setup>
import { useDialogPluginComponent } from 'quasar';
import OrderSummary from "pages/Order/Card/OrderSummary.vue";
const $props = defineProps({
id: {
type: Number,
required: true,
},
});
defineEmits([...useDialogPluginComponent.emits]);
const { dialogRef, onDialogHide } = useDialogPluginComponent();
</script>
<template>
<QDialog ref="dialogRef" @hide="onDialogHide">
<OrderSummary v-if="$props.id" :id="$props.id" />
</QDialog>
</template>
<style lang="scss">
.q-dialog .summary .header {
position: sticky;
z-index: $z-max;
top: 0;
}
</style>

View File

@ -0,0 +1,93 @@
<script setup>
import { useStateStore } from 'stores/useStateStore';
import { useRoute } from 'vue-router';
import { onMounted, onUnmounted } from 'vue';
import { useI18n } from 'vue-i18n';
import VnPaginate from 'components/ui/VnPaginate.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue';
import OrderCatalogItem from 'pages/Order/Card/OrderCatalogItem.vue';
import OrderCatalogFilter from 'pages/Order/Card/OrderCatalogFilter.vue';
const route = useRoute();
const stateStore = useStateStore();
const { t } = useI18n();
onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false));
const catalogParams = {
orderFk: route.params.id,
orderBy: JSON.stringify({ field: 'relevancy DESC, name', way: 'ASC', isTag: false }),
};
function exprBuilder(param, value) {
switch (param) {
case 'search':
return { 'i.name': { like: `%${value}%` } };
}
}
</script>
<template>
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">
<VnSearchbar
data-key="OrderCatalogList"
url="Orders/CatalogFilter"
:limit="50"
:user-params="catalogParams"
:expr-builder="exprBuilder"
:static-params="['orderFk', 'orderBy']"
/>
</Teleport>
<Teleport v-if="stateStore.isHeaderMounted()" to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click.stop="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">
<OrderCatalogFilter data-key="OrderCatalogList" />
</QScrollArea>
</QDrawer>
<QPage class="column items-center q-pa-md">
<div class="card-list">
<VnPaginate
data-key="OrderCatalogList"
url="Orders/CatalogFilter"
:limit="50"
:user-params="catalogParams"
auto-load
>
<template #body="{ rows }">
<div class="catalog-list">
<OrderCatalogItem v-for="row in rows" :key="row.id" :item="row" />
</div>
</template>
</VnPaginate>
</div>
</QPage>
</template>
<style lang="scss">
.card-list {
width: 100%;
}
.catalog-list {
display: flex;
align-items: flex-start;
flex-wrap: wrap;
justify-content: center;
gap: 16px;
}
</style>

View File

@ -0,0 +1,162 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { onMounted, onUnmounted } from 'vue';
import { useRouter } from 'vue-router';
import { useStateStore } from 'stores/useStateStore';
import { toCurrency, toDate } from 'src/filters';
import {useQuasar} from "quasar";
import CardList from 'components/ui/CardList.vue';
import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue';
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
import VnPaginate from 'components/ui/VnPaginate.vue';
import VnLv from 'components/ui/VnLv.vue';
import OrderSearchbar from 'pages/Order/Card/OrderSearchbar.vue';
import OrderFilter from 'pages/Order/Card/OrderFilter.vue';
import OrderSummaryDialog from "pages/Order/Card/OrderSummaryDialog.vue";
const stateStore = useStateStore();
const quasar = useQuasar();
const router = useRouter();
const { t } = useI18n();
onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false));
function navigate(id) {
router.push({ path: `/order/${id}` });
}
function viewSummary(id) {
quasar.dialog({
component: OrderSummaryDialog,
componentProps: {
id,
},
});
}
</script>
<template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<OrderSearchbar />
</Teleport>
<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>
<QScrollArea class="fit text-grey-8">
<OrderFilter data-key="OrderList" />
</QScrollArea>
</QDrawer>
<QPage class="column items-center q-pa-md">
<div class="card-list">
<VnPaginate
data-key="OrderList"
url="Orders/filter"
:limit="20"
:order="['landed DESC', 'clientFk', 'id DESC']"
:user-params="{ showEmpty: false }"
auto-load
>
<template #body="{ rows }">
<CardList
v-for="row of rows"
:key="row.id"
:id="row.id"
:title="`${row?.clientName} (${row?.clientFk})`"
@click="navigate(row.id)"
>
<template #list-items>
<VnLv
:label="t('order.field.salesPersonFk')"
:title-label="t('order.field.salesPersonFk')"
>
<template #value>
<span class="link" @click.stop>
{{ row?.name || '-' }}
<WorkerDescriptorProxy :id="row?.salesPersonFk" />
</span>
</template>
</VnLv>
<VnLv
:label="t('order.field.clientFk')"
:title-label="t('order.field.clientFk')"
>
<template #value>
<span class="link" @click.stop>
{{ row?.clientName || '-' }}
<CustomerDescriptorProxy :id="row?.clientFk" />
</span>
</template>
</VnLv>
<VnLv
:label="t('order.field.isConfirmed')"
:value="row?.isConfirmed === 1"
/>
<VnLv
:label="t('order.field.created')"
:value="toDate(row?.created)"
/>
<VnLv :label="t('order.field.landed')">
<template #value>
<QBadge color="positive" dense>
{{ toDate(row?.landed) }}
</QBadge>
</template>
</VnLv>
<VnLv
:label="t('order.field.hour')"
:value="row.hourTheoretical || row.hourEffective"
/>
<VnLv
:label="t('order.field.agency')"
:value="row?.agencyName"
/>
<VnLv
:label="t('order.field.total')"
:value="toCurrency(row?.total)"
/>
</template>
<template #actions>
<QBtn
:label="t('components.smartCard.openSummary')"
@click.stop="viewSummary(row.id)"
color="primary"
style="margin-top: 15px"
/>
</template>
</CardList>
</template>
</VnPaginate>
</div>
<QPageSticky :offset="[20, 20]">
<RouterLink :to="{ name: 'OrderCreate' }">
<QBtn fab icon="add" color="primary" />
<QTooltip>
{{ t('order.list.newOrder') }}
</QTooltip>
</RouterLink>
</QPageSticky>
</QPage>
</template>
<style lang="scss" scoped>
.card-list {
width: 100%;
max-width: 60em;
}
</style>

View File

@ -0,0 +1,17 @@
<script setup>
import { useStateStore } from 'stores/useStateStore';
import LeftMenu from 'src/components/LeftMenu.vue';
const stateStore = useStateStore();
</script>
<template>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit text-grey-8">
<LeftMenu />
</QScrollArea>
</QDrawer>
<QPageContainer>
<RouterView></RouterView>
</QPageContainer>
</template>

View File

@ -0,0 +1,149 @@
<script setup>
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import VnPaginate from 'components/ui/VnPaginate.vue';
import FetchData from 'components/FetchData.vue';
import VnLv from 'components/ui/VnLv.vue';
import { ref } from 'vue';
import { dashIfEmpty } from 'src/filters';
import CardList from 'components/ui/CardList.vue';
import axios from 'axios';
import FetchedTags from 'components/ui/FetchedTags.vue';
const route = useRoute();
const { t } = useI18n();
const volumeSummary = ref(null);
const loadVolumes = async (rows) => {
const { data } = await axios.get(`Orders/${route.params.id}/getVolumes`);
(rows || []).forEach((order) => {
(data.volumes || []).forEach((volume) => {
if (order.itemFk === volume.itemFk) {
order.volume = volume.volume;
}
});
});
};
</script>
<template>
<FetchData
:url="`Orders/${route.params.id}/getTotalVolume`"
@on-fetch="(data) => (volumeSummary = data)"
auto-load
/>
<QPage class="column items-center q-pa-md">
<div class="card-list">
<QCard class="order-volume-summary q-pa-lg">
<p class="header text-right block">
{{ t('summary') }}
</p>
<VnLv
:label="t('total')"
:value="`${volumeSummary?.totalVolume} m³`"
/>
<VnLv
:label="t('boxes')"
:value="`${dashIfEmpty(volumeSummary?.totalBoxes)} U`"
/>
</QCard>
<VnPaginate
data-key="OrderCatalogVolume"
url="OrderRows"
:limit="20"
auto-load
:filter="{
include: {
relation: 'item',
},
where: { orderFk: route.params.id },
}"
order="itemFk"
@on-fetch="(data) => loadVolumes(data)"
>
<template #body="{ rows }">
<div class="catalog-list q-mt-xl">
<CardList
v-for="row in rows"
:key="row.id"
:id="row.id"
:title="row?.item?.name"
class="cursor-inherit"
>
<template #list-items>
<div class="q-mb-sm">
<fetched-tags :item="row.item" :max-length="5" />
</div>
<VnLv :label="t('item')" :value="row.item.id" />
<VnLv :label="t('subName')">
<template #value>
<span class="text-uppercase">
{{ row.item.subName }}
</span>
</template>
</VnLv>
<VnLv :label="t('quantity')" :value="row.quantity" />
<VnLv :label="t('volume')" :value="row.volume" />
</template>
</CardList>
</div>
</template>
</VnPaginate>
</div>
</QPage>
</template>
<style lang="scss">
.order-volume-summary {
.vn-label-value {
display: flex;
justify-content: flex-end;
gap: 2%;
.label {
color: var(--vn-label);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.value {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
</style>
<style lang="scss" scoped>
.card-list {
width: 100%;
max-width: 60em;
}
.header {
color: $primary;
font-weight: bold;
margin-bottom: 25px;
font-size: 20px;
display: inline-block;
}
</style>
<i18n>
en:
summary: Summary
total: Total
boxes: Boxes
item: Item
subName: Subname
quantity: Quantity
volume: per quantity
es:
summary: Resumen
total: Total
boxes: Cajas
item: Artículo
subName: Subname
quantity: Cantidad
volume: por cantidad
</i18n>

View File

@ -1,9 +1,11 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInputDate from "components/common/VnInputDate.vue"; import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'src/components/common/VnInput.vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@ -26,18 +28,18 @@ const countries = ref();
</div> </div>
</template> </template>
<template #body="{ params }"> <template #body="{ params }">
<QList dense> <QList dense class="q-gutter-y-sm q-mt-sm">
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('route.cmr.list.cmrFk')" :label="t('route.cmr.list.cmrFk')"
v-model="params.cmrFk" v-model="params.cmrFk"
lazy-rules is-outlined
> >
<template #prepend> <template #prepend>
<QIcon name="article" size="sm"></QIcon> <QIcon name="article" size="sm"></QIcon>
</template> </template>
</QInput> </VnInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
@ -51,48 +53,48 @@ const countries = ref();
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('route.cmr.list.ticketFk')" :label="t('route.cmr.list.ticketFk')"
v-model="params.ticketFk" v-model="params.ticketFk"
lazy-rules is-outlined
> >
<template #prepend> <template #prepend>
<QIcon name="vn:ticket" size="sm"></QIcon> <QIcon name="vn:ticket" size="sm"></QIcon>
</template> </template>
</QInput> </VnInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('route.cmr.list.routeFk')" :label="t('route.cmr.list.routeFk')"
v-model="params.routeFk" v-model="params.routeFk"
lazy-rules is-outlined
> >
<template #prepend> <template #prepend>
<QIcon name="vn:delivery" size="sm"></QIcon> <QIcon name="vn:delivery" size="sm"></QIcon>
</template> </template>
</QInput> </VnInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('route.cmr.list.clientFk')" :label="t('route.cmr.list.clientFk')"
v-model="params.clientFk" v-model="params.clientFk"
lazy-rules is-outlined
> >
<template #prepend> <template #prepend>
<QIcon name="vn:client" size="sm"></QIcon> <QIcon name="vn:client" size="sm"></QIcon>
</template> </template>
</QInput> </VnInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection v-if="!countries"> <QItemSection v-if="!countries">
<QSkeleton type="QInput" class="full-width" /> <QSkeleton type="QInput" class="full-width" />
</QItemSection> </QItemSection>
<QItemSection v-if="countries"> <QItemSection v-if="countries" class="q-mb-sm">
<QSelect <QSelect
:label="t('route.cmr.list.country')" :label="t('route.cmr.list.country')"
v-model="params.country" v-model="params.country"
@ -103,6 +105,9 @@ const countries = ref();
transition-hide="jump-up" transition-hide="jump-up"
emit-value emit-value
map-options map-options
dense
outlined
rounded
> >
<template #prepend> <template #prepend>
<QIcon name="flag" size="sm"></QIcon> <QIcon name="flag" size="sm"></QIcon>
@ -112,7 +117,11 @@ const countries = ref();
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInputDate v-model="params.shipped" :label="t('route.cmr.list.shipped')" /> <VnInputDate
v-model="params.shipped"
:label="t('route.cmr.list.shipped')"
is-outlined
/>
</QItemSection> </QItemSection>
</QItem> </QItem>
</QList> </QList>

View File

@ -5,6 +5,7 @@ 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';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
import VnInput from 'src/components/common/VnInput.vue';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
@ -15,7 +16,7 @@ const defaultInitialData = {
priority: 0, priority: 0,
code: null, code: null,
isRecyclable: false, isRecyclable: false,
} };
const parkingFilter = { fields: ['id', 'code'] }; const parkingFilter = { fields: ['id', 'code'] };
const parkingList = ref([]); const parkingList = ref([]);
@ -81,7 +82,7 @@ const shelvingFilter = {
<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"> <div class="col">
<QInput <VnInput
v-model="data.code" v-model="data.code"
:label="t('shelving.basicData.code')" :label="t('shelving.basicData.code')"
:rules="validate('Shelving.code')" :rules="validate('Shelving.code')"
@ -107,7 +108,7 @@ const shelvingFilter = {
</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">
<QInput <VnInput
v-model="data.priority" v-model="data.priority"
:label="t('shelving.basicData.priority')" :label="t('shelving.basicData.priority')"
:rules="validate('Shelving.priority')" :rules="validate('Shelving.priority')"

View File

@ -0,0 +1 @@
<template>Supplier accounts</template>

View File

@ -0,0 +1 @@
<template>Supplier addresses</template>

View File

@ -0,0 +1 @@
<template>Supplier agency term</template>

View File

@ -0,0 +1 @@
<template>Supplier basic data</template>

View File

@ -0,0 +1 @@
<template>Supplier billing data</template>

View File

@ -2,6 +2,8 @@
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import LeftMenu from 'components/LeftMenu.vue';
import SupplierDescriptor from './SupplierDescriptor.vue';
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
@ -18,7 +20,9 @@ const { t } = useI18n();
</template> </template>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256"> <QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit"> <QScrollArea class="fit">
<!-- Aca iría left menu y descriptor --> <SupplierDescriptor />
<QSeparator />
<LeftMenu source="card" />
</QScrollArea> </QScrollArea>
</QDrawer> </QDrawer>
<QPageContainer> <QPageContainer>

View File

@ -0,0 +1 @@
<template>Supplier consumption</template>

View File

@ -0,0 +1 @@
<template>Supplier contacts</template>

View File

@ -0,0 +1 @@
<template>Supplier fiscal data</template>

View File

@ -0,0 +1 @@
<template>Supplier log</template>

View File

@ -1,10 +1,12 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n';
import { reactive } from 'vue'; import { reactive } from 'vue';
import { useStateStore } from 'stores/useStateStore'; import { useI18n } from 'vue-i18n';
import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import { useStateStore } from 'stores/useStateStore';
const { t } = useI18n(); const { t } = useI18n();
const stateStore = useStateStore(); const stateStore = useStateStore();
@ -39,7 +41,7 @@ const newSupplierForm = reactive({
<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"> <div class="col">
<QInput <VnInput
v-model="data.name" v-model="data.name"
:label="t('supplier.create.supplierName')" :label="t('supplier.create.supplierName')"
/> />
@ -49,13 +51,3 @@ const newSupplierForm = reactive({
</FormModel> </FormModel>
</QPage> </QPage>
</template> </template>
<style lang="scss" scoped>
.card {
display: flex;
justify-content: center;
width: 100%;
background-color: var(--vn-dark);
padding: 40px;
}
</style>

View File

@ -1,10 +1,13 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue';
import toDateString from 'filters/toDateString'; import toDateString from 'filters/toDateString';
import VnInputDate from "components/common/VnInputDate.vue"; import VnInputDate from 'components/common/VnInputDate.vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@ -53,29 +56,33 @@ const warehouses = ref();
</div> </div>
</template> </template>
<template #body="{ params, searchFn }"> <template #body="{ params, searchFn }">
<QList dense> <QList dense class="q-gutter-y-sm q-mt-sm">
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
v-model="params.clientFk" v-model="params.clientFk"
:label="t('Customer ID')" :label="t('Customer ID')"
lazy-rules is-outlined
/> />
</QItemSection> </QItemSection>
<QItemSection> <QItemSection>
<QInput <VnInput
v-model="params.orderFk" v-model="params.orderFk"
:label="t('Order ID')" :label="t('Order ID')"
lazy-rules is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInputDate v-model="params.from" :label="t('From')" /> <VnInputDate
v-model="params.from"
:label="t('From')"
is-outlined
/>
</QItemSection> </QItemSection>
<QItemSection> <QItemSection>
<VnInputDate v-model="params.to" :label="t('To')" /> <VnInputDate v-model="params.to" :label="t('To')" is-outlined />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
@ -92,6 +99,9 @@ const warehouses = ref();
emit-value emit-value
map-options map-options
use-input use-input
dense
outlined
rounded
:input-debounce="0" :input-debounce="0"
/> />
</QItemSection> </QItemSection>
@ -110,15 +120,18 @@ const warehouses = ref();
option-label="name" option-label="name"
emit-value emit-value
map-options map-options
dense
outlined
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
v-model="params.refFk" v-model="params.refFk"
:label="t('Invoice Ref.')" :label="t('Invoice Ref.')"
lazy-rules is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -184,6 +197,9 @@ const warehouses = ref();
option-label="name" option-label="name"
emit-value emit-value
map-options map-options
dense
outlined
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -201,6 +217,9 @@ const warehouses = ref();
option-label="name" option-label="name"
emit-value emit-value
map-options map-options
dense
outlined
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -218,6 +237,9 @@ const warehouses = ref();
option-label="name" option-label="name"
emit-value emit-value
map-options map-options
dense
outlined
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -232,7 +254,7 @@ en:
params: params:
search: Contains search: Contains
clientFk: Customer clientFk: Customer
orderFK: Order orderFk: Order
from: From from: From
to: To to: To
salesPersonFk: Salesperson salesPersonFk: Salesperson
@ -249,7 +271,7 @@ es:
params: params:
search: Contiene search: Contiene
clientFk: Cliente clientFk: Cliente
orderFK: Pedido orderFk: Pedido
from: Desde from: Desde
to: Hasta to: Hasta
salesPersonFk: Comercial salesPersonFk: Comercial

View File

@ -1,12 +1,16 @@
<script setup> <script setup>
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import TravelDescriptor from './TravelDescriptor.vue';
import LeftMenu from 'components/LeftMenu.vue';
const stateStore = useStateStore(); const stateStore = useStateStore();
</script> </script>
<template> <template>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256"> <QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit"> <QScrollArea class="fit">
<!-- Aca iría left menu y descriptor --> <TravelDescriptor />
<QSeparator />
<LeftMenu source="card" />
</QScrollArea> </QScrollArea>
</QDrawer> </QDrawer>
<QPageContainer> <QPageContainer>

View File

@ -2,15 +2,17 @@
import { onMounted, ref, computed, onUpdated } from 'vue'; import { onMounted, ref, computed, onUpdated } 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 { QCheckbox, QIcon } from 'quasar';
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 { getUrl } from 'src/composables/getUrl';
import { toDate } from 'src/filters';
import travelService from 'src/services/travel.service';
import { QCheckbox, QIcon } from 'quasar';
import { toCurrency } from 'filters/index';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import travelService from 'src/services/travel.service';
import { toDate, toCurrency } from 'src/filters';
import { getUrl } from 'src/composables/getUrl';
import axios from 'axios';
onUpdated(() => summaryRef.value.fetch()); onUpdated(() => summaryRef.value.fetch());
const route = useRoute(); const route = useRoute();
@ -40,9 +42,17 @@ const cloneTravel = () => {
redirectToCreateView(stringifiedTravelData); redirectToCreateView(stringifiedTravelData);
}; };
const cloneTravelWithEntries = () => {
try {
axios.post(`Travels/${$props.id}/cloneWithEntries`);
} catch (err) {
console.err('Error cloning travel with entries');
}
};
const headerMenuOptions = [ const headerMenuOptions = [
{ name: t('travel.summary.cloneShipping'), action: cloneTravel }, { name: t('travel.summary.cloneShipping'), action: cloneTravel },
{ name: t('travel.summary.CloneTravelAndEntries'), action: null }, { name: t('travel.summary.CloneTravelAndEntries'), action: cloneTravelWithEntries },
{ name: t('travel.summary.AddEntry'), action: null }, { name: t('travel.summary.AddEntry'), action: null },
]; ];

View File

@ -8,6 +8,7 @@ import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorP
import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue'; import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import ExtraCommunityFilter from './ExtraCommunityFilter.vue'; import ExtraCommunityFilter from './ExtraCommunityFilter.vue';
import VnInput from 'src/components/common/VnInput.vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { toCurrency } from 'src/filters'; import { toCurrency } from 'src/filters';
@ -314,7 +315,7 @@ onMounted(async () => {
label-set="Save" label-set="Save"
label-cancel="Close" label-cancel="Close"
> >
<QInput <VnInput
v-model="rows[props.pageIndex][col.field]" v-model="rows[props.pageIndex][col.field]"
dense dense
autofocus autofocus
@ -394,7 +395,7 @@ onMounted(async () => {
<style lang="scss" scoped> <style lang="scss" scoped>
.col-content { .col-content {
border-radius: 4px; border-radius: 4px;
padding: 6px 6px 6px 6px; padding: 6px;
} }
.secondary-row { .secondary-row {

View File

@ -1,9 +1,11 @@
<script setup> <script setup>
import { reactive } from 'vue'; import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import FetchData from 'components/FetchData.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
const { t } = useI18n(); const { t } = useI18n();
@ -14,16 +16,10 @@ const props = defineProps({
}, },
}); });
const filtersOptions = reactive({ const warehousesOptions = ref([]);
warehouses: [], const continentsOptions = ref([]);
continents: [], const agenciesOptions = ref([]);
agencies: [], const suppliersOptions = ref([]);
suppliers: [],
});
const updateFilterOptions = (data, optionKey) => {
filtersOptions[optionKey] = [...data];
};
const add = (paramsObj, key) => { const add = (paramsObj, key) => {
if (paramsObj[key] === undefined) { if (paramsObj[key] === undefined) {
@ -43,22 +39,22 @@ const decrement = (paramsObj, key) => {
<template> <template>
<FetchData <FetchData
url="Warehouses" url="Warehouses"
@on-fetch="(data) => updateFilterOptions(data, 'warehouses')" @on-fetch="(data) => (warehousesOptions = data)"
auto-load auto-load
/> />
<FetchData <FetchData
url="Continents" url="Continents"
@on-fetch="(data) => updateFilterOptions(data, 'continents')" @on-fetch="(data) => (continentsOptions = data)"
auto-load auto-load
/> />
<FetchData <FetchData
url="AgencyModes" url="AgencyModes"
@on-fetch="(data) => updateFilterOptions(data, 'agencies')" @on-fetch="(data) => (agenciesOptions = data)"
auto-load auto-load
/> />
<FetchData <FetchData
url="Suppliers" url="Suppliers"
@on-fetch="(data) => updateFilterOptions(data, 'suppliers')" @on-fetch="(data) => (suppliersOptions = data)"
auto-load auto-load
/> />
@ -70,24 +66,22 @@ const decrement = (paramsObj, key) => {
</div> </div>
</template> </template>
<template #body="{ params }"> <template #body="{ params }">
<QList dense style="max-width: 256px" class="list"> <QList dense class="list q-gutter-y-sm q-mt-sm">
<QItem class="q-my-sm"> <QItem>
<QItemSection> <QItemSection>
<QInput label="id" dense outlined rounded v-model="params.id" /> <VnInput label="id" v-model="params.id" is-outlined />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-my-sm"> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('params.ref')" :label="t('params.reference')"
dense
outlined
rounded
v-model="params.reference" v-model="params.reference"
is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem>
<QItemSection> <QItemSection>
<QInput <QInput
v-model="params.totalEntries" v-model="params.totalEntries"
@ -118,12 +112,12 @@ const decrement = (paramsObj, key) => {
</QInput> </QInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem>
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelectFilter
:label="t('params.agencyModeFk')" :label="t('params.agencyModeFk')"
v-model="params.agencyModeFk" v-model="params.agencyModeFk"
:options="filtersOptions.agencies" :options="agenciesOptions"
option-value="agencyFk" option-value="agencyFk"
option-label="name" option-label="name"
hide-selected hide-selected
@ -133,34 +127,30 @@ const decrement = (paramsObj, key) => {
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem>
<QItemSection> <QItemSection>
<VnInputDate <VnInputDate
v-model="params.shippedFrom" v-model="params.shippedFrom"
:label="t('params.shippedFrom')" :label="t('params.shippedFrom')"
dense is-outlined
outlined
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem>
<QItemSection> <QItemSection>
<VnInputDate <VnInputDate
v-model="params.landedTo" v-model="params.landedTo"
:label="t('params.landedTo')" :label="t('params.landedTo')"
dense is-outlined
outlined
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem>
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelectFilter
:label="t('params.warehouseOutFk')" :label="t('params.warehouseOutFk')"
v-model="params.warehouseOutFk" v-model="params.warehouseOutFk"
:options="filtersOptions.warehouses" :options="warehousesOptions"
option-value="id" option-value="id"
option-label="name" option-label="name"
hide-selected hide-selected
@ -170,12 +160,12 @@ const decrement = (paramsObj, key) => {
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem>
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelectFilter
:label="t('params.warehouseInFk')" :label="t('params.warehouseInFk')"
v-model="params.warehouseInFk" v-model="params.warehouseInFk"
:options="filtersOptions.warehouses" :options="warehousesOptions"
option-value="id" option-value="id"
option-label="name" option-label="name"
hide-selected hide-selected
@ -185,12 +175,12 @@ const decrement = (paramsObj, key) => {
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem>
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelectFilter
:label="t('supplier.pageTitles.supplier')" :label="t('supplier.pageTitles.supplier')"
v-model="params.cargoSupplierFk" v-model="params.cargoSupplierFk"
:options="filtersOptions.suppliers" :options="suppliersOptions"
option-value="id" option-value="id"
option-label="name" option-label="name"
hide-selected hide-selected
@ -200,12 +190,12 @@ const decrement = (paramsObj, key) => {
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem>
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelectFilter
:label="t('params.continent')" :label="t('params.continent')"
v-model="params.continent" v-model="params.continent"
:options="filtersOptions.continents" :options="continentsOptions"
option-value="code" option-value="code"
option-label="name" option-label="name"
hide-selected hide-selected
@ -221,6 +211,9 @@ const decrement = (paramsObj, key) => {
</template> </template>
<style scoped> <style scoped>
.list {
width: 256px;
}
.list * { .list * {
max-width: 100%; max-width: 100%;
} }
@ -240,7 +233,8 @@ const decrement = (paramsObj, key) => {
<i18n> <i18n>
en: en:
params: params:
ref: Reference id: Id
reference: Reference
totalEntries: Total entries totalEntries: Total entries
agencyModeFk: Agency agencyModeFk: Agency
warehouseInFk: Warehouse In warehouseInFk: Warehouse In
@ -251,7 +245,8 @@ en:
continent: Continent out continent: Continent out
es: es:
params: params:
ref: Referencia id: Id
reference: Referencia
totalEntries: Ent. totales totalEntries: Ent. totales
agencyModeFk: Agencia agencyModeFk: Agencia
warehouseInFk: Alm. entrada warehouseInFk: Alm. entrada

View File

@ -1,14 +1,15 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { reactive, ref } from 'vue'; import { reactive, ref, onBeforeMount } from 'vue';
import FetchData from 'components/FetchData.vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import FetchData from 'components/FetchData.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import { toDate } from 'src/filters'; import VnInput from 'src/components/common/VnInput.vue';
import { onBeforeMount } from 'vue'; import { toDate } from 'src/filters';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
@ -23,8 +24,8 @@ const newTravelForm = reactive({
}); });
const agenciesOptions = ref([]); const agenciesOptions = ref([]);
const viewAction = ref();
const warehousesOptions = ref([]); const warehousesOptions = ref([]);
const viewAction = ref();
onBeforeMount(() => { onBeforeMount(() => {
// Esto nos permite decirle a FormModel si queremos observar los cambios o no // Esto nos permite decirle a FormModel si queremos observar los cambios o no
@ -42,19 +43,19 @@ onBeforeMount(() => {
} }
} }
}); });
const onFetchAgencies = (agencies) => {
agenciesOptions.value = [...agencies];
};
const onFetchWarehouses = (warehouses) => {
warehousesOptions.value = [...warehouses];
};
</script> </script>
<template> <template>
<FetchData url="AgencyModes" @on-fetch="(data) => onFetchAgencies(data)" auto-load /> <FetchData
<FetchData url="Warehouses" @on-fetch="(data) => onFetchWarehouses(data)" auto-load /> url="AgencyModes"
@on-fetch="(data) => (agenciesOptions = data)"
auto-load
/>
<FetchData
url="Warehouses"
@on-fetch="(data) => (warehousesOptions = data)"
auto-load
/>
<QPage> <QPage>
<QToolbar class="bg-vn-dark"> <QToolbar class="bg-vn-dark">
<div id="st-data"></div> <div id="st-data"></div>
@ -70,7 +71,7 @@ const onFetchWarehouses = (warehouses) => {
<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"> <div class="col">
<QInput v-model="data.ref" :label="t('globals.reference')" /> <VnInput v-model="data.ref" :label="t('globals.reference')" />
</div> </div>
<div class="col"> <div class="col">
<VnSelectFilter <VnSelectFilter
@ -169,11 +170,3 @@ const onFetchWarehouses = (warehouses) => {
</FormModel> </FormModel>
</QPage> </QPage>
</template> </template>
<style lang="scss" scoped>
.card {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
grid-gap: 20px;
}
</style>

View File

@ -1,9 +1,12 @@
<script setup> <script setup>
import { reactive } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import { toDate } from 'src/filters'; import { toDate } from 'src/filters';
const { t } = useI18n(); const { t } = useI18n();
@ -14,15 +17,9 @@ const props = defineProps({
}, },
}); });
const filtersOptions = reactive({ const warehousesOptions = ref([]);
warehouses: [], const continentsOptions = ref([]);
continents: [], const agenciesOptions = ref([]);
agencies: [],
});
const updateFilterOptions = (data, optionKey) => {
filtersOptions[optionKey] = [...data];
};
const add = (paramsObj, key) => { const add = (paramsObj, key) => {
if (paramsObj[key] === undefined) { if (paramsObj[key] === undefined) {
@ -42,17 +39,17 @@ const decrement = (paramsObj, key) => {
<template> <template>
<FetchData <FetchData
url="Warehouses" url="Warehouses"
@on-fetch="(data) => updateFilterOptions(data, 'warehouses')" @on-fetch="(data) => (warehousesOptions = data)"
auto-load auto-load
/> />
<FetchData <FetchData
url="Continents" url="Continents"
@on-fetch="(data) => updateFilterOptions(data, 'continents')" @on-fetch="(data) => (continentsOptions = data)"
auto-load auto-load
/> />
<FetchData <FetchData
url="AgencyModes" url="AgencyModes"
@on-fetch="(data) => updateFilterOptions(data, 'agencies')" @on-fetch="(data) => (agenciesOptions = data)"
auto-load auto-load
/> />
@ -64,24 +61,22 @@ const decrement = (paramsObj, key) => {
</div> </div>
</template> </template>
<template #body="{ params }"> <template #body="{ params }">
<QList dense style="max-width: 256px" class="list"> <QList dense class="list q-gutter-y-sm q-mt-sm">
<QItem class="q-my-sm"> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('params.search')"
dense
outlined
rounded
v-model="params.search" v-model="params.search"
:label="t('params.search')"
is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem>
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelectFilter
:label="t('params.agencyModeFk')" :label="t('params.agencyModeFk')"
v-model="params.agencyModeFk" v-model="params.agencyModeFk"
:options="filtersOptions.agencies" :options="agenciesOptions"
option-value="agencyFk" option-value="agencyFk"
option-label="name" option-label="name"
hide-selected hide-selected
@ -91,12 +86,12 @@ const decrement = (paramsObj, key) => {
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem>
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelectFilter
:label="t('params.warehouseOutFk')" :label="t('params.warehouseOutFk')"
v-model="params.warehouseOutFk" v-model="params.warehouseOutFk"
:options="filtersOptions.warehouses" :options="warehousesOptions"
option-value="id" option-value="id"
option-label="name" option-label="name"
hide-selected hide-selected
@ -106,12 +101,12 @@ const decrement = (paramsObj, key) => {
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem>
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelectFilter
:label="t('params.warehouseInFk')" :label="t('params.warehouseInFk')"
v-model="params.warehouseInFk" v-model="params.warehouseInFk"
:options="filtersOptions.warehouses" :options="warehousesOptions"
option-value="id" option-value="id"
option-label="name" option-label="name"
hide-selected hide-selected
@ -121,7 +116,7 @@ const decrement = (paramsObj, key) => {
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem>
<QItemSection> <QItemSection>
<QInput <QInput
v-model="params.scopeDays" v-model="params.scopeDays"
@ -151,7 +146,7 @@ const decrement = (paramsObj, key) => {
</QInput> </QInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem>
<QItemSection> <QItemSection>
<QInput <QInput
dense dense
@ -184,7 +179,7 @@ const decrement = (paramsObj, key) => {
</QInput> </QInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem>
<QItemSection> <QItemSection>
<QInput <QInput
dense dense
@ -217,12 +212,12 @@ const decrement = (paramsObj, key) => {
</QInput> </QInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem>
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelectFilter
:label="t('params.continent')" :label="t('params.continent')"
v-model="params.continent" v-model="params.continent"
:options="filtersOptions.continents" :options="continentsOptions"
option-value="code" option-value="code"
option-label="name" option-label="name"
hide-selected hide-selected
@ -232,7 +227,7 @@ const decrement = (paramsObj, key) => {
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem>
<QItemSection> <QItemSection>
<QInput <QInput
v-model="params.totalEntries" v-model="params.totalEntries"
@ -269,6 +264,9 @@ const decrement = (paramsObj, key) => {
</template> </template>
<style scoped> <style scoped>
.list {
width: 256px;
}
.list * { .list * {
max-width: 100%; max-width: 100%;
} }

View File

@ -8,9 +8,9 @@ import VnPaginate from 'src/components/ui/VnPaginate.vue';
import CardList from 'src/components/ui/CardList.vue'; import CardList from 'src/components/ui/CardList.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import TravelSummaryDialog from './Card/TravelSummaryDialog.vue'; import TravelSummaryDialog from './Card/TravelSummaryDialog.vue';
import TravelFilter from './TravelFilter.vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import TravelFilter from './TravelFilter.vue';
import { toDate } from 'src/filters/index'; import { toDate } from 'src/filters/index';
const router = useRouter(); const router = useRouter();
@ -31,6 +31,10 @@ const redirectToCreateView = (queryParams) => {
router.push({ name: 'TravelCreate', query: { travelData: queryParams } }); router.push({ name: 'TravelCreate', query: { travelData: queryParams } });
}; };
const redirectCreateEntryView = (travelData) => {
router.push({ name: 'EntryCreate', query: { travelFk: travelData.id } });
};
const viewSummary = (id) => { const viewSummary = (id) => {
quasar.dialog({ quasar.dialog({
component: TravelSummaryDialog, component: TravelSummaryDialog,
@ -103,7 +107,7 @@ onMounted(async () => {
/> />
<QBtn <QBtn
:label="t('addEntry')" :label="t('addEntry')"
@click.stop="viewSummary(row.id)" @click.stop="redirectCreateEntryView(row)"
class="bg-vn-dark" class="bg-vn-dark"
outline outline
style="margin-top: 15px" style="margin-top: 15px"

View File

@ -1,9 +1,12 @@
<script setup> <script setup>
import axios from 'axios';
import { useQuasar } from 'quasar';
import { computed, ref, onMounted, onUpdated } from 'vue'; import { computed, ref, onMounted, onUpdated } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useQuasar } from 'quasar';
import VnInput from 'src/components/common/VnInput.vue';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
onMounted(() => fetch()); onMounted(() => fetch());
onUpdated(() => fetch()); onUpdated(() => fetch());
@ -241,7 +244,7 @@ function exceedMaxHeight(pos) {
<QPage class="q-pa-sm q-mx-xl"> <QPage class="q-pa-sm q-mx-xl">
<QForm @submit="onSubmit()" @reset="onReset()" class="q-pa-sm"> <QForm @submit="onSubmit()" @reset="onReset()" class="q-pa-sm">
<QCard class="q-pa-md"> <QCard class="q-pa-md">
<QInput <VnInput
filled filled
v-model="name" v-model="name"
:label="t('wagon.type.name')" :label="t('wagon.type.name')"

View File

@ -1,9 +1,12 @@
<script setup> <script setup>
import axios from 'axios';
import { QIcon, QInput, QItem, QItemSection, QSelect } from 'quasar';
import { computed, onMounted, onUpdated, ref } from 'vue'; import { computed, onMounted, onUpdated, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { QIcon, QInput, QItem, QItemSection, QSelect } from 'quasar';
import VnInput from 'src/components/common/VnInput.vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import axios from 'axios';
onMounted(() => fetch()); onMounted(() => fetch());
onUpdated(() => fetch()); onUpdated(() => fetch());
@ -100,7 +103,7 @@ function filterType(val, update) {
/> />
</div> </div>
<div class="col"> <div class="col">
<QInput <VnInput
filled filled
v-model="wagon.plate" v-model="wagon.plate"
:label="t('wagon.create.plate')" :label="t('wagon.create.plate')"

View File

@ -0,0 +1,366 @@
<script setup>
import { onMounted, ref } from 'vue';
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 VnInputDate from 'components/common/VnInputDate.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import VnSelectCreate from 'src/components/common/VnSelectCreate.vue';
import CreateBankEntityForm from 'src/components/CreateBankEntityForm.vue';
import CustomerCreateNewPostcode from 'src/components/CreateNewPostcodeForm.vue';
import VnInput from 'src/components/common/VnInput.vue';
import { useUserConfig } from 'src/composables/useUserConfig';
const { t } = useI18n();
const workerConfigFilter = {
field: ['payMethodFk'],
};
const provincesFilter = {
fields: ['id', 'name', 'countryFk'],
};
const townsFilter = {
fields: ['id', 'name', 'provinceFk'],
};
const newWorkerForm = ref({
companyFk: null,
payMethodFk: null,
firstName: null,
lastNames: null,
birth: null,
fi: null,
code: null,
phone: null,
postcode: null,
provinceFk: null,
city: null,
street: null,
name: null,
email: null,
bossFk: null,
iban: null,
bankEntityFk: null,
});
const postcodeFetchDataRef = ref(null);
const provincesOptions = ref([]);
const townsOptions = ref([]);
const companiesOptions = ref([]);
const workersOptions = ref([]);
const payMethodsOptions = ref([]);
const bankEntitiesOptions = ref([]);
const postcodesOptions = ref([]);
const onFetchWorkerConfig = (workerConfig) => {
newWorkerForm.value.payMethodFk = workerConfig.payMethodFk;
};
const onBankEntityCreated = (data) => {
bankEntitiesOptions.value.push(data);
};
const onPostcodeCreated = async () => {
postcodeFetchDataRef.value.fetch();
};
onMounted(async () => {
const userInfo = await useUserConfig().fetch();
newWorkerForm.value = { companyFk: userInfo.companyFk };
});
</script>
<template>
<FetchData
url="WorkerConfigs/findOne"
@on-fetch="(data) => onFetchWorkerConfig(data)"
:filter="workerConfigFilter"
auto-load
/>
<FetchData
ref="postcodeFetchDataRef"
url="Postcodes/location"
@on-fetch="(data) => (postcodesOptions = data)"
auto-load
/>
<FetchData
url="Provinces/location"
@on-fetch="(data) => (provincesOptions = data)"
:filter="provincesFilter"
auto-load
/>
<FetchData
url="Towns/location"
@on-fetch="(data) => (townsOptions = data)"
:filter="townsFilter"
auto-load
/>
<FetchData
url="Companies"
@on-fetch="(data) => (companiesOptions = data)"
auto-load
/>
<FetchData
url="Workers/search"
@on-fetch="(data) => (workersOptions = data)"
auto-load
/>
<FetchData
url="Paymethods"
@on-fetch="(data) => (payMethodsOptions = data)"
auto-load
/>
<FetchData
url="BankEntities"
@on-fetch="(data) => (bankEntitiesOptions = data)"
auto-load
/>
<QPage>
<QToolbar class="bg-vn-dark">
<div id="st-data"></div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<FormModel
url-create="Workers/new"
model="worker"
:form-initial-data="newWorkerForm"
>
<template #form="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput
v-model="data.firstName"
:label="t('worker.create.name')"
:rules="validate('Worker.firstName')"
/>
</div>
<div class="col">
<VnInput
v-model="data.lastNames"
:label="t('worker.create.lastName')"
:rules="validate('Worker.lastNames')"
/>
</div>
<div class="col">
<VnInputDate
v-model="data.birth"
:label="t('worker.create.birth')"
:rules="validate('Worker.birth')"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput
v-model="data.fi"
:label="t('worker.create.fi')"
:rules="validate('Worker.fi')"
/>
</div>
<div class="col">
<VnInput
v-model="data.code"
:label="t('worker.create.code')"
:rules="validate('Worker.code')"
/>
</div>
<div class="col">
<VnInput
v-model="data.phone"
:label="t('worker.create.phone')"
:rules="validate('Worker.phone')"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectCreate
v-model="data.postcode"
:label="t('worker.create.postcode')"
:rules="validate('Worker.postcode')"
:roles-allowed-to-create="['deliveryAssistant']"
:options="postcodesOptions"
option-label="code"
option-value="code"
hide-selected
>
<template #form>
<CustomerCreateNewPostcode
@on-data-saved="onPostcodeCreated($event)"
/>
</template>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection v-if="scope.opt">
<QItemLabel>{{ scope.opt.code }}</QItemLabel>
<QItemLabel caption
>{{ scope.opt.code }} -
{{ scope.opt.town.name }} ({{
scope.opt.town.province.name
}},
{{
scope.opt.town.province.country.country
}})</QItemLabel
>
</QItemSection>
</QItem>
</template>
</VnSelectCreate>
</div>
<div class="col">
<VnSelectFilter
:label="t('worker.create.province')"
v-model="data.provinceFk"
:options="provincesOptions"
option-value="id"
option-label="name"
hide-selected
:rules="validate('Worker.provinceFk')"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('worker.create.city')"
v-model="data.city"
:options="townsOptions"
option-value="name"
option-label="name"
hide-selected
:rules="validate('Worker.city')"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt.name }}</QItemLabel>
<QItemLabel caption
>{{ scope.opt.name }},
{{ scope.opt.province.name }} ({{
scope.opt.province.country.country
}})</QItemLabel
>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</div>
<div class="col">
<VnInput
:label="t('worker.create.street')"
v-model="data.street"
:rules="validate('Worker.street')"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput
v-model="data.name"
:label="t('worker.create.webUser')"
:rules="validate('Worker.name')"
/>
</div>
<div class="col">
<VnInput
v-model="data.email"
:label="t('worker.create.personalEmail')"
:rules="validate('Worker.email')"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('worker.create.company')"
v-model="data.companyFk"
:options="companiesOptions"
option-value="id"
option-label="code"
hide-selected
:rules="validate('Worker.company')"
/>
</div>
<div class="col">
<VnSelectFilter
:label="t('worker.create.boss')"
v-model="data.bossFk"
:options="workersOptions"
option-value="id"
option-label="name"
hide-selected
:rules="validate('Worker.boss')"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt.name }}</QItemLabel>
<QItemLabel caption
>{{ scope.opt.nickname }},
{{ scope.opt.code }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('worker.create.payMethods')"
v-model="data.payMethodFk"
:options="payMethodsOptions"
option-value="id"
option-label="name"
map-options
hide-selected
:rules="validate('Worker.payMethodFk')"
/>
</div>
<div class="col">
<VnInput
v-model="data.iban"
:label="t('worker.create.iban')"
:rules="validate('Worker.iban')"
/>
</div>
<VnSelectCreate
:label="t('worker.create.bankEntity')"
v-model="data.bankEntityFk"
:options="bankEntitiesOptions"
option-label="name"
option-value="id"
hide-selected
:roles-allowed-to-create="['salesAssistant', 'hr']"
:rules="validate('Worker.bankEntity')"
>
<template #form>
<CreateBankEntityForm
@on-data-saved="onBankEntityCreated($event)"
/>
</template>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection v-if="scope.opt">
<QItemLabel
>{{ scope.opt.bic }}
{{ scope.opt.name }}</QItemLabel
>
</QItemSection>
</QItem>
</template>
</VnSelectCreate>
</VnRow>
</template>
</FormModel>
</QPage>
</template>

View File

@ -0,0 +1,15 @@
<script setup>
import VnTree from 'components/ui/VnTree.vue';
</script>
<template>
<QPage class="column items-center q-pa-md">
<VnTree />
</QPage>
</template>
<i18n>
es:
Search worker: Buscar trabajador
You can search by worker id or name: Puedes buscar por id o nombre del trabajador
</i18n>

View File

@ -1,8 +1,10 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@ -25,40 +27,39 @@ const departments = ref();
</div> </div>
</template> </template>
<template #body="{ params, searchFn }"> <template #body="{ params, searchFn }">
<QList dense> <QList dense class="q-gutter-y-sm q-mt-sm">
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput :label="t('FI')" v-model="params.fi" lazy-rules> <VnInput :label="t('FI')" v-model="params.fi" is-outlined
<template #prepend> ><template #prepend>
<QIcon name="badge" size="sm"></QIcon> <QIcon name="badge" size="xs"></QIcon> </template
</template> ></VnInput>
</QInput>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('First Name')" :label="t('First Name')"
v-model="params.firstName" v-model="params.firstName"
lazy-rules is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('Last Name')" :label="t('Last Name')"
v-model="params.lastName" v-model="params.lastName"
lazy-rules is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('User Name')" :label="t('User Name')"
v-model="params.userName" v-model="params.userName"
lazy-rules is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -77,16 +78,19 @@ const departments = ref();
emit-value emit-value
map-options map-options
use-input use-input
dense
outlined
rounded
:input-debounce="0" :input-debounce="0"
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-md"> <QItem>
<QItemSection> <QItemSection>
<QInput <VnInput
:label="t('Extension')" :label="t('Extension')"
v-model="params.extension" v-model="params.extension"
lazy-rules is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -104,6 +108,7 @@ en:
lastName: Last name lastName: Last name
userName: User userName: User
extension: Extension extension: Extension
departmentFk: Department
es: es:
params: params:
search: Contiene search: Contiene
@ -112,6 +117,7 @@ es:
lastName: Apellidos lastName: Apellidos
userName: Usuario userName: Usuario
extension: Extensión extension: Extensión
departmentFk: Departamento
FI: NIF FI: NIF
First Name: Nombre First Name: Nombre
Last Name: Apellidos Last Name: Apellidos

View File

@ -27,6 +27,10 @@ function viewSummary(id) {
}, },
}); });
} }
const redirectToCreateView = () => {
router.push({ name: 'WorkerCreate' });
};
</script> </script>
<template> <template>
@ -101,6 +105,12 @@ function viewSummary(id) {
</template> </template>
</VnPaginate> </VnPaginate>
</div> </div>
<QPageSticky :offset="[20, 20]">
<QBtn fab icon="add" color="primary" @click="redirectToCreateView()" />
<QTooltip>
{{ t('worker.list.newWorker') }}
</QTooltip>
</QPageSticky>
</QPage> </QPage>
</template> </template>

View File

@ -11,7 +11,17 @@ export default {
redirect: { name: 'SupplierMain' }, redirect: { name: 'SupplierMain' },
menus: { menus: {
main: ['SupplierList'], main: ['SupplierList'],
card: [], card: [
'SupplierBasicData',
'SupplierFiscalData',
'SupplierBillingData',
'SupplierLog',
'SupplierAccounts',
'SupplierContacts',
'SupplierAddresses',
'SupplierConsumption',
'SupplierAgencyTerm',
],
}, },
children: [ children: [
{ {
@ -55,6 +65,95 @@ export default {
component: () => component: () =>
import('src/pages/Supplier/Card/SupplierSummary.vue'), import('src/pages/Supplier/Card/SupplierSummary.vue'),
}, },
{
path: 'basic-data',
name: 'SupplierBasicData',
meta: {
title: 'basicData',
icon: 'vn:settings',
},
component: () =>
import('src/pages/Supplier/Card/SupplierBasicData.vue'),
},
{
path: 'fiscal-data',
name: 'SupplierFiscalData',
meta: {
title: 'fiscalData',
icon: 'vn:dfiscales',
},
component: () =>
import('src/pages/Supplier/Card/SupplierFiscalData.vue'),
},
{
path: 'billing-data',
name: 'SupplierBillingData',
meta: {
title: 'billingData',
icon: 'vn:payment',
},
component: () =>
import('src/pages/Supplier/Card/SupplierBillingData.vue'),
},
{
path: 'log',
name: 'SupplierLog',
meta: {
title: 'log',
icon: 'vn:History',
},
component: () => import('src/pages/Supplier/Card/SupplierLog.vue'),
},
{
path: 'account',
name: 'SupplierAccounts',
meta: {
title: 'accounts',
icon: 'vn:account',
},
component: () =>
import('src/pages/Supplier/Card/SupplierAccounts.vue'),
},
{
path: 'contact',
name: 'SupplierContacts',
meta: {
title: 'contacts',
icon: 'contact_phone',
},
component: () =>
import('src/pages/Supplier/Card/SupplierContacts.vue'),
},
{
path: 'address',
name: 'SupplierAddresses',
meta: {
title: 'addresses',
icon: 'vn:delivery',
},
component: () =>
import('src/pages/Supplier/Card/SupplierAddresses.vue'),
},
{
path: 'consumption',
name: 'SupplierConsumption',
meta: {
title: 'consumption',
icon: 'show_chart',
},
component: () =>
import('src/pages/Supplier/Card/SupplierConsumption.vue'),
},
{
path: 'agency-term',
name: 'SupplierAgencyTerm',
meta: {
title: 'agencyTerm',
icon: 'vn:agency-term',
},
component: () =>
import('src/pages/Supplier/Card/SupplierAgencyTerm.vue'),
},
], ],
}, },
], ],

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