feature/EntriesCorrections #177

Merged
alexm merged 24 commits from :feature/EntriesCorrections into dev 2024-02-07 06:44:33 +00:00
32 changed files with 1459 additions and 239 deletions

View File

@ -8,7 +8,7 @@ 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 VnSelectDialog from 'components/common/VnSelectDialog.vue';
import FormModelPopup from './FormModelPopup.vue';
const emit = defineEmits(['onDataSaved']);
@ -85,7 +85,7 @@ const onProvinceCreated = async ({ name }, formData) => {
/>
</div>
<div class="col">
<VnSelectCreate
<VnSelectDialog
:label="t('City')"
:options="townsLocationOptions"
v-model="data.townFk"
@ -100,12 +100,12 @@ const onProvinceCreated = async ({ name }, formData) => {
@on-data-saved="onCityCreated($event, data)"
/>
</template>
</VnSelectCreate>
</VnSelectDialog>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-xl">
<div class="col">
<VnSelectCreate
<VnSelectDialog
:label="t('Province')"
:options="provincesOptions"
hide-selected
@ -120,7 +120,7 @@ const onProvinceCreated = async ({ name }, formData) => {
@on-data-saved="onProvinceCreated($event, data)"
/>
</template>
</VnSelectCreate>
</VnSelectDialog>
</div>
<div class="col">
<VnSelectFilter

View File

@ -0,0 +1,141 @@
<script setup>
import { ref, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import VnInput from 'src/components/common/VnInput.vue';
import axios from 'axios';
import useNotify from 'src/composables/useNotify.js';
const emit = defineEmits(['onDataSaved']);
const $props = defineProps({
rows: {
type: Array,
default: () => [],
},
fieldsOptions: {
type: Array,
default: () => [],
},
editUrl: {
type: String,
jsegarra marked this conversation as resolved Outdated

@alexm Diría que en un componente genérico no definimos valores para un modulo en concreto, puedes confirmar.

Esto se pasaría por parámetro del componente no?

@alexm Diría que en un componente genérico no definimos valores para un modulo en concreto, puedes confirmar. Esto se pasaría por parámetro del componente no?
Outdated
Review

Exacto usar directamente t(...)

Exacto usar directamente t(...)

Se pasó la data clave a props para poder reutilizar en un futuro.

Commit: ddcbc5d8d7

Se pasó la data clave a props para poder reutilizar en un futuro. Commit: https://gitea.verdnatura.es/verdnatura/salix-front/commit/ddcbc5d8d77d6761007bb400b9ce8a39216d3206
default: '',
},
});
const { t } = useI18n();
const { notify } = useNotify();
const formData = reactive({
field: null,
newValue: null,
});
const closeButton = ref(null);
const isLoading = ref(false);
const onDataSaved = () => {
notify('globals.dataSaved', 'positive');
emit('onDataSaved');
closeForm();
};
const submitData = async () => {
try {
isLoading.value = true;
const rowsToEdit = $props.rows.map((row) => ({ id: row.id, itemFk: row.itemFk }));
const payload = {
field: formData.field,
newValue: formData.newValue,
lines: rowsToEdit,
};
await axios.post($props.editUrl, payload);
onDataSaved();
isLoading.value = false;
} catch (err) {
console.error('Error submitting table cell edit');
}
};
const closeForm = () => {
if (closeButton.value) closeButton.value.click();
};
</script>
<template>
<QForm @submit="submitData()" class="all-pointer-events">
<QCard class="q-pa-lg">
<span ref="closeButton" class="close-icon" v-close-popup>
<QIcon name="close" size="sm" />
</span>
<h1 class="title">
{{
t('editBuyTitle', {
buysAmount: rows.length,
})
}}
</h1>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('Field to edit')"
:options="fieldsOptions"
hide-selected
option-label="label"
option-value="field"
v-model="formData.field"
/>
</div>
<div class="col">
<VnInput :label="t('Value')" v-model="formData.newValue" />
</div>
</VnRow>
<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>
</QCard>
</QForm>
</template>
<style lang="scss" scoped>
.title {
font-size: 17px;
font-weight: bold;
line-height: 20px;
}
.close-icon {
position: absolute;
top: 20px;
right: 20px;
cursor: pointer;
}
</style>
<i18n>
en:
editBuyTitle: Edit {buysAmount} buy(s)
es:
editBuyTitle: Editar {buysAmount} compra(s)
Field to edit: Campo a editar
Value: Valor
</i18n>

View File

@ -0,0 +1,242 @@
<script setup>
import { ref, reactive, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import VnRow from 'components/ui/VnRow.vue';
import FetchData from 'components/FetchData.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import axios from 'axios';
import { dashIfEmpty } from 'src/filters';
const emit = defineEmits(['itemSelected']);
const { t } = useI18n();
const route = useRoute();
const itemFilter = {
include: [
{
relation: 'producer',
scope: {
fields: ['name'],
},
},
{
relation: 'ink',
scope: {
fields: ['name'],
},
},
],
};
const itemFilterParams = reactive({});
const closeButton = ref(null);
const isLoading = ref(false);
const producersOptions = ref([]);
const ItemTypesOptions = ref([]);
const InksOptions = ref([]);
const tableRows = ref([]);
const loading = ref(false);
const tableColumns = computed(() => [
{
label: t('entry.buys.id'),
name: 'id',
field: 'id',
align: 'left',
},
{
label: t('entry.buys.name'),
name: 'name',
field: 'name',
align: 'left',
},
{
label: t('entry.buys.size'),
name: 'size',
field: 'size',
align: 'left',
},
{
label: t('entry.buys.producer'),
name: 'producerName',
field: 'producer',
align: 'left',
format: (val) => dashIfEmpty(val),
},
{
label: t('entry.buys.color'),
name: 'ink',
field: 'inkName',
align: 'left',
},
]);
const fetchResults = async () => {
try {
let filter = itemFilter;
const params = itemFilterParams;
const where = {};
for (let key in params) {
const value = params[key];
if (!value) continue;
switch (key) {
case 'name':
where[key] = { like: `%${value}%` };
break;
case 'producerFk':
case 'typeFk':
case 'size':
case 'inkFk':
where[key] = value;
}
}
filter.where = where;
const { data } = await axios.get(`Entries/${route.params.id}/lastItemBuys`, {
params: { filter: JSON.stringify(filter) },
});
tableRows.value = data;
} catch (err) {
console.error('Error fetching entries items');
}
};
const closeForm = () => {
if (closeButton.value) closeButton.value.click();
};
const selectItem = ({ id }) => {
emit('itemSelected', id);
closeForm();
};
</script>
<template>
<FetchData
url="Producers"
@on-fetch="(data) => (producersOptions = data)"
:filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }"
auto-load
/>
<FetchData
url="ItemTypes"
:filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }"
order="name"
@on-fetch="(data) => (ItemTypesOptions = data)"
auto-load
/>
<FetchData
url="Inks"
:filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }"
order="name"
@on-fetch="(data) => (InksOptions = data)"
auto-load
/>
<QForm @submit="fetchResults()" class="all-pointer-events">
<QCard class="column" style="padding: 32px; z-index: 100">
<span ref="closeButton" class="close-icon" v-close-popup>
<QIcon name="close" size="sm" />
</span>
<h1 class="title">{{ t('Filter item') }}</h1>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput
:label="t('entry.buys.name')"
v-model="itemFilterParams.name"
/>
</div>
<div class="col">
<VnInput
:label="t('entry.buys.size')"
v-model="itemFilterParams.size"
/>
</div>
<div class="col">
<VnSelectFilter
:label="t('entry.buys.producer')"
:options="producersOptions"
hide-selected
option-label="name"
option-value="id"
v-model="itemFilterParams.producerFk"
/>
</div>
<div class="col">
<VnSelectFilter
:label="t('entry.buys.type')"
:options="ItemTypesOptions"
hide-selected
option-label="name"
option-value="id"
v-model="itemFilterParams.typeFk"
/>
</div>
<div class="col">
<VnSelectFilter
:label="t('entry.buys.color')"
:options="InksOptions"
hide-selected
option-label="name"
option-value="id"
v-model="itemFilterParams.inkFk"
/>
</div>
</VnRow>
<div class="q-mt-lg row justify-end">
<QBtn
:label="t('globals.search')"
type="submit"
color="primary"
:disabled="isLoading"
:loading="isLoading"
/>
</div>
<QTable
:columns="tableColumns"
:rows="tableRows"
:pagination="{ rowsPerPage: 0 }"
:loading="loading"
:hide-header="!tableRows || !tableRows.length > 0"
:no-data-label="t('Enter a new search')"
class="q-mt-lg"
@row-click="(_, row) => selectItem(row)"
>
<template #body-cell-id="{ row }">
<QTd auto-width @click.stop>
<QBtn flat color="blue">{{ row.id }}</QBtn>
<ItemDescriptorProxy :id="row.id" />
</QTd>
</template>
</QTable>
</QCard>
</QForm>
</template>
<i18n>
es:
Filter item: Filtrar artículo
Enter a new search: Introduce una nueva búsqueda
</i18n>
<style lang="scss" scoped>
.title {
font-size: 17px;
font-weight: bold;
line-height: 20px;
}
.close-icon {
position: absolute;
top: 20px;
right: 20px;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,240 @@
<script setup>
import { ref, reactive, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import VnRow from 'components/ui/VnRow.vue';
import FetchData from 'components/FetchData.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue';
import axios from 'axios';
import { toDate } from 'src/filters';
const emit = defineEmits(['travelSelected']);
const { t } = useI18n();
const travelFilter = {
include: [
{
relation: 'agency',
scope: {
fields: ['name'],
},
},
{
relation: 'warehouseIn',
scope: {
fields: ['name'],
},
},
{
relation: 'warehouseOut',
scope: {
fields: ['name'],
},
},
],
};
const travelFilterParams = reactive({});
const closeButton = ref(null);
const isLoading = ref(false);
const agenciesOptions = ref([]);
const warehousesOptions = ref([]);
const tableRows = ref([]);
const loading = ref(false);
const tableColumns = computed(() => [
{
label: t('entry.basicData.id'),
name: 'id',
field: 'id',
align: 'left',
},
{
label: t('entry.basicData.warehouseOut'),
name: 'warehouseOutFk',
field: 'warehouseOutFk',
align: 'left',
format: (val) =>
warehousesOptions.value.find((warehouse) => warehouse.id === val).name,
},
{
label: t('entry.basicData.warehouseIn'),
name: 'warehouseInFk',
field: 'warehouseInFk',
align: 'left',
format: (val) =>
warehousesOptions.value.find((warehouse) => warehouse.id === val).name,
},
{
label: t('entry.basicData.shipped'),
name: 'shipped',
field: 'shipped',
align: 'left',
format: (val) => toDate(val),
},
{
label: t('entry.basicData.landed'),
name: 'landed',
field: 'landed',
align: 'left',
format: (val) => toDate(val),
},
]);
const fetchResults = async () => {
try {
let filter = travelFilter;
const params = travelFilterParams;
const where = {};
for (let key in params) {
const value = params[key];
if (!value) continue;
switch (key) {
case 'agencyModeFk':
case 'warehouseInFk':
case 'warehouseOutFk':
case 'shipped':
case 'landed':
where[key] = value;
}
}
filter.where = where;
const { data } = await axios.get('Travels', {
params: { filter: JSON.stringify(filter) },
});
tableRows.value = data;
} catch (err) {
console.error('Error fetching travels');
}
};
const closeForm = () => {
if (closeButton.value) closeButton.value.click();
};
const selectTravel = ({ id }) => {
emit('travelSelected', id);
closeForm();
};
</script>
<template>
<FetchData
url="AgencyModes"
@on-fetch="(data) => (agenciesOptions = data)"
:filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }"
auto-load
/>
<FetchData
url="Warehouses"
:filter="{ fields: ['id', 'name'] }"
order="name"
@on-fetch="(data) => (warehousesOptions = data)"
auto-load
/>
<QForm @submit="fetchResults()" class="all-pointer-events">
<QCard class="column" style="padding: 32px; z-index: 100">
<span ref="closeButton" class="close-icon" v-close-popup>
<QIcon name="close" size="sm" />
</span>
<h1 class="title">{{ t('Filter travels') }}</h1>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('entry.basicData.agency')"
:options="agenciesOptions"
hide-selected
option-label="name"
option-value="id"
v-model="travelFilterParams.agencyModeFk"
/>
</div>
<div class="col">
<VnSelectFilter
:label="t('entry.basicData.warehouseOut')"
:options="warehousesOptions"
hide-selected
option-label="name"
option-value="id"
v-model="travelFilterParams.warehouseOutFk"
/>
</div>
<div class="col">
<VnSelectFilter
:label="t('entry.basicData.warehouseIn')"
:options="warehousesOptions"
hide-selected
option-label="name"
option-value="id"
v-model="travelFilterParams.warehouseInFk"
/>
</div>
<div class="col">
<VnInputDate
:label="t('entry.basicData.shipped')"
v-model="travelFilterParams.shipped"
/>
</div>
<div class="col">
<VnInputDate
:label="t('entry.basicData.landed')"
v-model="travelFilterParams.landed"
/>
</div>
</VnRow>
<div class="q-mt-lg row justify-end">
<QBtn
:label="t('globals.search')"
type="submit"
color="primary"
:disabled="isLoading"
:loading="isLoading"
/>
</div>
<QTable
:columns="tableColumns"
:rows="tableRows"
:pagination="{ rowsPerPage: 0 }"
:loading="loading"
:hide-header="!tableRows || !tableRows.length > 0"
:no-data-label="t('Enter a new search')"
class="q-mt-lg"
@row-click="(_, row) => selectTravel(row)"
>
<template #body-cell-id="{ row }">
<QTd auto-width @click.stop>
<QBtn flat color="blue">{{ row.id }}</QBtn>
<TravelDescriptorProxy :id="row.id" />
</QTd>
</template>
</QTable>
</QCard>
</QForm>
</template>
<i18n>
es:
Filter travels: Filtro envíos
Enter a new search: Introduce una nueva búsqueda
</i18n>
<style lang="scss" scoped>
.title {
font-size: 17px;
font-weight: bold;
line-height: 20px;
}
.close-icon {
position: absolute;
top: 20px;
right: 20px;
cursor: pointer;
}
</style>

View File

@ -1,6 +1,6 @@
<script setup>
import axios from 'axios';
import { onMounted, onUnmounted, computed, ref, watch } from 'vue';
import { onMounted, onUnmounted, computed, ref, watch, nextTick } from 'vue';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import { useState } from 'src/composables/useState';
@ -67,7 +67,13 @@ defineExpose({
save,
});
const componentIsRendered = ref(false);
onMounted(async () => {
nextTick(() => {
componentIsRendered.value = true;
});
// Podemos enviarle al form la estructura de data inicial sin necesidad de fetchearla
if ($props.formInitialData && !$props.autoLoad) {
state.set($props.model, $props.formInitialData);
@ -198,7 +204,10 @@ watch(formUrl, async () => {
</QCard>
</QForm>
</div>
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
<Teleport
to="#st-actions"
v-if="stateStore?.isSubToolbarShown() && componentIsRendered"
>
<div v-if="$props.defaultActions">
<QBtnGroup push class="q-gutter-x-sm">
<slot name="moreActions" />

View File

@ -41,7 +41,7 @@ const setUserConfigViewData = (data) => {
// Importante: El name de las columnas de la tabla debe conincidir con el name de las variables que devuelve la view config
formattedCols.value = $props.allColumns.map((col) => ({
name: col,
active: data[col],
active: data[col] == undefined ? true : data[col],
}));
emitSavedConfig();
};

View File

@ -1,7 +1,7 @@
<script setup>
import { ref, toRefs, computed, watch, onMounted } from 'vue';
import CreateNewPostcode from 'src/components/CreateNewPostcodeForm.vue';
import VnSelectCreate from 'components/common/VnSelectCreate.vue';
import VnSelectDialog from 'components/common/VnSelectDialog.vue';
import FetchData from 'components/FetchData.vue';
const emit = defineEmits(['update:modelValue', 'update:options']);
import { useI18n } from 'vue-i18n';
@ -92,7 +92,7 @@ function handleFetch(data) {
url="Postcodes/filter"
@on-fetch="(data) => handleFetch(data)"
/>
<VnSelectCreate
<VnSelectDialog
v-if="postcodesRef"
:option-label="(opt) => showLabel(opt) ?? 'code'"
:option-value="(opt) => opt.code"
@ -123,7 +123,7 @@ function handleFetch(data) {
</QItemSection>
</QItem>
</template>
</VnSelectCreate>
</VnSelectDialog>
</template>
<style lang="scss" scoped>
.add-icon {

View File

@ -20,6 +20,10 @@ const $props = defineProps({
type: Array,
default: () => ['developer'],
},
actionIcon: {
type: String,
default: 'add',
},
});
const role = useRole();
@ -48,9 +52,9 @@ const toggleForm = () => {
<template v-if="isAllowedToCreate" #append>
<QIcon
@click.stop.prevent="toggleForm()"
name="add"
size="xs"
class="add-icon"
:name="actionIcon"
:size="actionIcon === 'add' ? 'xs' : 'sm'"
:class="['default-icon', { '--add-icon': actionIcon === 'add' }]"
/>
<QDialog v-model="showForm" transition-show="scale" transition-hide="scale">
<slot name="form" />
@ -63,9 +67,14 @@ const toggleForm = () => {
</template>
<style lang="scss" scoped>
.add-icon {
.default-icon {
cursor: pointer;
background-color: $primary;
color: $primary;
border-radius: 50px;
&.--add-icon {
color: var(--vn-text);
background-color: $primary;
}
}
</style>

View File

@ -61,6 +61,10 @@ const props = defineProps({
type: Function,
default: null,
},
customRouteRedirectName: {
type: String,
default: '',
},
});
const router = useRouter();
@ -87,6 +91,14 @@ async function search() {
});
if (!props.redirect) return;
if (props.customRouteRedirectName) {
router.push({
name: props.customRouteRedirectName,
params: { id: searchText.value },
});
return;
}
const { matched: matches } = route;
const { path } = matches[matches.length - 1];
const newRoute = path.replace(':id', searchText.value);

View File

@ -71,3 +71,13 @@ body.body--dark {
.q-field.required .q-field__label:after {
content: ' *';
}
input[type='number'] {
-moz-appearance: textfield;
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
}

View File

@ -266,6 +266,8 @@ export default {
buys: 'Buys',
notes: 'Notes',
log: 'Log',
create: 'Create',
latestBuys: 'Latest buys',
},
list: {
newEntry: 'New entry',
@ -321,6 +323,12 @@ export default {
booked: 'Booked',
raid: 'Raid',
excludedFromAvailable: 'Inventory',
agency: 'Agency',
warehouseOut: 'Warehouse Out',
warehouseIn: 'Warehouse In',
shipped: 'Shipped',
landed: 'Landed',
id: 'ID',
},
buys: {
groupingPrice: 'Grouping price',
@ -335,6 +343,11 @@ export default {
buyingValue: 'Buying value',
packagingFk: 'Box',
file: 'File',
name: 'Name',
producer: 'Producer',
type: 'Type',
color: 'Color',
id: 'ID',
},
notes: {
observationType: 'Observation type',
@ -345,6 +358,36 @@ export default {
landed: 'Landed',
warehouseOut: 'Warehouse Out',
},
latestBuys: {
picture: 'Picture',
itemFk: 'Item ID',
packing: 'Packing',
grouping: 'Grouping',
quantity: 'Quantity',
description: 'Description',
size: 'Size',
tags: 'Tags',
type: 'Type',
intrastat: 'Intrastat',
origin: 'Origin',
weightByPiece: 'Weight/Piece',
isActive: 'Active',
family: 'Family',
entryFk: 'Entry',
buyingValue: 'Buying value',
freightValue: 'Freight value',
comissionValue: 'Commission value',
packageValue: 'Package value',
isIgnored: 'Is ignored',
price2: 'Grouping',
price3: 'Packing',
minPrice: 'Min',
ektFk: 'Ekt',
weight: 'Weight',
packagingFk: 'Package',
packingOut: 'Package out',
landing: 'Landing',
},
},
ticket: {
pageTitles: {

View File

@ -265,6 +265,8 @@ export default {
buys: 'Compras',
notes: 'Notas',
log: 'Historial',
create: 'Crear',
latestBuys: 'Últimas compras',
},
list: {
newEntry: 'Nueva entrada',
@ -320,6 +322,12 @@ export default {
booked: 'Asentado',
raid: 'Redada',
excludedFromAvailable: 'Inventario',
agency: 'Agencia',
warehouseOut: 'Alm. salida',
warehouseIn: 'Alm. entrada',
shipped: 'F. envío',
landed: 'F. entrega',
id: 'ID',
},
buys: {
groupingPrice: 'Precio grouping',
@ -334,6 +342,11 @@ export default {
buyingValue: 'Coste',
packagingFk: 'Embalaje',
file: 'Fichero',
name: 'Nombre',
producer: 'Productor',
type: 'Tipo',
color: 'Color',
id: 'ID',
},
notes: {
observationType: 'Tipo de observación',
@ -344,6 +357,36 @@ export default {
landed: 'F. entrega',
warehouseOut: 'Alm. salida',
},
latestBuys: {
picture: 'Foto',
itemFk: 'ID Artículo',
packing: 'Packing',
grouping: 'Grouping',
quantity: 'Cantidad',
description: 'Descripción',
size: 'Medida',
tags: 'Etiquetas',
type: 'Tipo',
intrastat: 'Intrastat',
origin: 'Origen',
weightByPiece: 'Peso (gramos)/tallo',
isActive: 'Activo',
family: 'Familia',
entryFk: 'Entrada',
buyingValue: 'Coste',
freightValue: 'Porte',
comissionValue: 'Comisión',
packageValue: 'Embalaje',
isIgnored: 'Ignorado',
price2: 'Grouping',
price3: 'Packing',
minPrice: 'Min',
ektFk: 'Ekt',
weight: 'Peso',
packagingFk: 'Embalaje',
packingOut: 'Embalaje envíos',
landing: 'Llegada',
},
},
ticket: {
pageTitles: {

View File

@ -10,7 +10,7 @@ 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';
import VnSelectCreate from 'src/components/common/VnSelectCreate.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CreateBankEntityForm from 'src/components/CreateBankEntityForm.vue';
const { t } = useI18n();
@ -79,7 +79,7 @@ const getBankEntities = () => {
</VnInput>
</div>
<div class="col">
<VnSelectCreate
<VnSelectDialog
:label="t('Swift / BIC')"
:options="bankEntitiesOptions"
:roles-allowed-to-create="['salesAssistant', 'hr']"
@ -102,7 +102,7 @@ const getBankEntities = () => {
</QItemSection>
</QItem>
</template>
</VnSelectCreate>
</VnSelectDialog>
</div>
</VnRow>

View File

@ -32,9 +32,8 @@ const workersOptions = ref([]);
const businessTypesOptions = ref([]);
const postcodesOptions = ref([]);
function handleLocation(data, location) {
const { town, code, provinceFk, countryFk } = location ?? {}
const { town, code, provinceFk, countryFk } = location ?? {};
data.postcode = code;
data.city = town;
data.provinceFk = provinceFk;

View File

@ -10,7 +10,7 @@ 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';
import VnSelectCreate from 'src/components/common/VnSelectCreate.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CustomerCreateNewPostcode from 'src/components/CreateNewPostcodeForm.vue';
import CustomsNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCustomsAgent.vue';
@ -113,7 +113,7 @@ const toCustomerConsignees = () => {
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectCreate
<VnSelectDialog
:label="t('Postcode')"
:options="postcodesOptions"
:roles-allowed-to-create="['deliveryAssistant']"
@ -141,7 +141,7 @@ const toCustomerConsignees = () => {
</QItemSection>
</QItem>
</template>
</VnSelectCreate>
</VnSelectDialog>
</div>
<div class="col">
<!-- ciudades -->
@ -223,7 +223,7 @@ const toCustomerConsignees = () => {
/>
</div>
<div class="col">
<VnSelectCreate
<VnSelectDialog
:label="t('Customs agent')"
:options="customsAgents"
hide-selected
@ -234,7 +234,7 @@ const toCustomerConsignees = () => {
<template #form>
<CustomsNewCustomsAgent @on-data-saved="refreshData()" />
</template>
</VnSelectCreate>
</VnSelectDialog>
</div>
</VnRow>
</template>

View File

@ -10,7 +10,7 @@ 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';
import VnSelectCreate from 'src/components/common/VnSelectCreate.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CustomerCreateNewPostcode from 'src/components/CreateNewPostcodeForm.vue';
import CustomsNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCustomsAgent.vue';
@ -168,7 +168,7 @@ const onDataSaved = () => {
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectCreate
<VnSelectDialog
:label="t('Postcode')"
:options="postcodesOptions"
:roles-allowed-to-create="['deliveryAssistant']"
@ -196,7 +196,7 @@ const onDataSaved = () => {
</QItemSection>
</QItem>
</template>
</VnSelectCreate>
</VnSelectDialog>
</div>
<div class="col">
<!-- ciudades -->
@ -278,7 +278,7 @@ const onDataSaved = () => {
/>
</div>
<div class="col">
<VnSelectCreate
<VnSelectDialog
:label="t('Customs agent')"
:options="customsAgents"
hide-selected
@ -289,7 +289,7 @@ const onDataSaved = () => {
<template #form>
<CustomsNewCustomsAgent />
</template>
</VnSelectCreate>
</VnSelectDialog>
</div>
</VnRow>

View File

@ -44,7 +44,6 @@ const setData = (entity) => {
};
const removeDepartment = () => {
console.log('entityId: ', entityId.value);
quasar
.dialog({
title: 'Are you sure you want to delete it?',

View File

@ -8,6 +8,8 @@ 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';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
Review

Los checkbox del final, en español no tienen los mismo valores.
@alexm puedes confirmar si es correcto este cambio de nombre?

Los checkbox del final, en español no tienen los mismo valores. @alexm puedes confirmar si es correcto este cambio de nombre?
import FilterTravelForm from 'src/components/FilterTravelForm.vue';
import { toDate } from 'src/filters';
@ -18,6 +20,10 @@ const suppliersOptions = ref([]);
const travelsOptions = ref([]);
const companiesOptions = ref([]);
const currenciesOptions = ref([]);
const onFilterTravelSelected = (formData, id) => {
formData.travelFk = id;
};
</script>
<template>
<FetchData
@ -82,7 +88,7 @@ const currenciesOptions = ref([]);
</VnSelectFilter>
</div>
<div class="col">
<VnSelectFilter
<VnSelectDialog
jsegarra marked this conversation as resolved
Review

En salix, para la entrada 3 y "Alm. salida", hay un registro que si pulsas en el id(está en azul), te muestra un popup.
En lilium, ese nuevo popup no se muestra y te cierra el dialogo

En salix, para la entrada 3 y "Alm. salida", hay un registro que si pulsas en el id(está en azul), te muestra un popup. En lilium, ese nuevo popup no se muestra y te cierra el dialogo
Review

Corregido.

Commit: a7e653cbce

Corregido. Commit: https://gitea.verdnatura.es/verdnatura/salix-front/commit/a7e653cbce5f02f360eb3f3379eca9ab5c34341f
:label="t('entry.basicData.travel')"
v-model="data.travelFk"
:options="travelsOptions"
@ -91,7 +97,13 @@ const currenciesOptions = ref([]);
map-options
hide-selected
:required="true"
action-icon="filter_alt"
>
<template #form>
<FilterTravelForm
@travel-selected="onFilterTravelSelected(data, $event)"
/>
</template>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
@ -106,7 +118,7 @@ const currenciesOptions = ref([]);
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</VnSelectDialog>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
@ -163,8 +175,9 @@ const currenciesOptions = ref([]);
:label="t('entry.basicData.observation')"
type="textarea"
v-model="data.observation"
:maxlength="45"
counter
fill-input
autogrow
/>
</div>
</VnRow>

View File

@ -2,6 +2,7 @@
import { ref, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { QBtn } from 'quasar';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import FetchData from 'src/components/FetchData.vue';
@ -9,6 +10,7 @@ import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FetchedTags from 'components/ui/FetchedTags.vue';
import VnConfirm from 'components/ui/VnConfirm.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import { useQuasar } from 'quasar';
import { useStateStore } from 'stores/useStateStore';
@ -26,28 +28,38 @@ const { notify } = useNotify();
const rowsSelected = ref([]);
const entryBuysPaginateRef = ref(null);
const packagingsOptions = ref(null);
const originalRowDataCopy = ref(null);
jsegarra marked this conversation as resolved Outdated

Intuyo que esta es la configuración de la columna Articulo de la tabla para entry/:id/buys
Falta itemDescriptor

Intuyo que esta es la configuración de la columna Articulo de la tabla para entry/:id/buys Falta itemDescriptor

Aplicado.

Commit: 0525f5fdd8

Aplicado. Commit: https://gitea.verdnatura.es/verdnatura/salix-front/commit/0525f5fdd80c41bb44b63969b229e4137d324ce6
const getInputEvents = (colField, props) => {
return colField === 'packagingFk'
? { 'update:modelValue': () => saveChange(colField, props) }
: {
'keyup.enter': () => saveChange(colField, props),
blur: () => saveChange(colField, props),
};
};
const tableColumnComponents = computed(() => ({
item: {
component: () => 'span',
props: () => {},
component: QBtn,
props: {
jsegarra marked this conversation as resolved Outdated

8 veces el mismo código para keyenter y blur.
Propuesta, sustituir las ocurrencias por uno genérico

8 veces el mismo código para keyenter y blur. Propuesta, sustituir las ocurrencias por uno genérico

Commit cambios: 2de02723ba

Commit cambios: https://gitea.verdnatura.es/verdnatura/salix-front/commit/2de02723baa04064fbf8817dc55b7af7cbf18d26

Nueva mejora aplicada.

Commit: 5da5bb2047

Nueva mejora aplicada. Commit: https://gitea.verdnatura.es/verdnatura/salix-front/commit/5da5bb204707ac7e0909be1cdc87a689366e17a7
color: 'blue',
flat: true,
},
event: () => ({}),
},
quantity: {
component: () => VnInput,
props: (col) => ({
component: VnInput,
props: {
type: 'number',
min: 0,
label: col.label,
class: 'input-number',
}),
event: (props) => ({
'keyup.enter': () => saveChange(props.row),
}),
},
event: getInputEvents,
},
packagingFk: {
component: () => VnSelectFilter,
props: () => ({
component: VnSelectFilter,
props: {
'option-value': 'id',
'option-label': 'id',
'emit-value': true,
@ -55,92 +67,69 @@ const tableColumnComponents = computed(() => ({
'use-input': true,
'hide-selected': true,
options: packagingsOptions.value,
}),
event: (props) => ({
'update:modelValue': () => saveChange(props.row),
}),
},
event: getInputEvents,
},
stickers: {
component: () => VnInput,
props: (col) => ({
component: VnInput,
props: {
type: 'number',
min: 0,
label: col.label,
class: 'input-number',
}),
event: (props) => ({
'keyup.enter': () => saveChange(props.row),
}),
},
event: getInputEvents,
},
weight: {
component: () => VnInput,
props: (col) => ({
component: VnInput,
props: {
type: 'number',
min: 0,
label: col.label,
}),
event: (props) => ({
'keyup.enter': () => saveChange(props.row),
}),
},
event: getInputEvents,
},
packing: {
component: () => VnInput,
props: (col) => ({
component: VnInput,
props: {
type: 'number',
min: 0,
label: col.label,
}),
event: (props) => ({
'keyup.enter': () => saveChange(props.row),
}),
},
event: getInputEvents,
},
grouping: {
component: () => VnInput,
props: (col) => ({
component: VnInput,
props: {
type: 'number',
min: 0,
label: col.label,
}),
event: (props) => ({
'keyup.enter': () => saveChange(props.row),
}),
},
event: getInputEvents,
},
buyingValue: {
component: () => VnInput,
props: (col) => ({
component: VnInput,
props: {
type: 'number',
min: 0,
label: col.label,
}),
event: (props) => ({
'keyup.enter': () => saveChange(props.row),
}),
},
event: getInputEvents,
},
price2: {
component: () => VnInput,
props: (col) => ({
component: VnInput,
props: {
type: 'number',
min: 0,
label: col.label,
}),
event: (props) => ({
'keyup.enter': () => saveChange(props.row),
}),
},
event: getInputEvents,
},
price3: {
component: () => VnInput,
props: (col) => ({
component: VnInput,
props: {
type: 'number',
min: 0,
label: col.label,
}),
event: (props) => ({
'keyup.enter': () => saveChange(props.row),
}),
},
event: getInputEvents,
},
import: {
component: () => 'span',
props: () => {},
component: 'span',
props: {},
event: () => ({}),
},
}));
@ -217,8 +206,19 @@ const entriesTableColumns = computed(() => {
];
});
const saveChange = async (rowData) => {
await axios.patch(`Buys/${rowData.id}`, rowData);
const copyOriginalRowsData = (rows) => {
// el objetivo de esto es guardar los valores iniciales de todas las rows para evitar guardar cambios si la data no cambió al disparar los eventos
originalRowDataCopy.value = JSON.parse(JSON.stringify(rows));
};
const saveChange = async (field, { rowIndex, row }) => {
try {
if (originalRowDataCopy.value[rowIndex][field] == row[field]) return;
await axios.patch(`Buys/${row.id}`, row);
originalRowDataCopy.value[rowIndex][field] = row[field];
} catch (err) {
console.error('Error saving changes', err);
}
};
const openRemoveDialog = async () => {
@ -256,6 +256,33 @@ const deleteBuys = async () => {
const importBuys = () => {
router.push({ name: 'EntryBuysImport' });
};
const toggleGroupingMode = async (buy, mode) => {
try {
const grouping = 1;
const packing = 2;
const groupingMode = mode === 'grouping' ? grouping : packing;
const newGroupingMode = buy.groupingMode === groupingMode ? 0 : groupingMode;
const params = {
groupingMode: newGroupingMode,
};
await axios.patch(`Buys/${buy.id}`, params);
buy.groupingMode = newGroupingMode;
} catch (err) {
console.error('Error toggling grouping mode');
}
};
const showLockIcon = (groupingMode, mode) => {
if (mode === 'packing') {
return groupingMode === 2 ? 'lock' : 'lock_open';
} else {
return groupingMode === 1 ? 'lock' : 'lock_open';
}
};
</script>
<template>
@ -284,6 +311,7 @@ const importBuys = () => {
ref="entryBuysPaginateRef"
data-key="EntryBuys"
:url="`Entries/${route.params.id}/getBuys`"
@on-fetch="copyOriginalRowsData($event)"
auto-load
>
<template #body="{ rows }">
@ -304,16 +332,44 @@ const importBuys = () => {
</QTd>
<QTd v-for="col in props.cols" :key="col.name">
<component
jsegarra marked this conversation as resolved Outdated

Podemos hacer desaprecer los labels de los inputs, como está hecho para embalaje.
Queda redundante tener el nombre de la columna en el input.

¿Que opinas @alexm ?

Podemos hacer desaprecer los labels de los inputs, como está hecho para embalaje. Queda redundante tener el nombre de la columna en el input. ¿Que opinas @alexm ?
Outdated
Review

Sii es un mejor enfoque

Sii es un mejor enfoque

Aplicado.

Commit: 86bbc7cfd6

Aplicado. Commit: https://gitea.verdnatura.es/verdnatura/salix-front/commit/86bbc7cfd679b948046d913aa368f2bda8b276fb
:is="tableColumnComponents[col.name].component()"
v-bind="tableColumnComponents[col.name].props(col)"
:is="tableColumnComponents[col.name].component"
v-bind="tableColumnComponents[col.name].props"
v-model="props.row[col.field]"
v-on="tableColumnComponents[col.name].event(props)"
v-on="
tableColumnComponents[col.name].event(
col.field,
props
)
"
>
<template
v-if="
col.name === 'grouping' || col.name === 'packing'
"
#append
>
<QBtn
:icon="
showLockIcon(props.row.groupingMode, col.name)
"
@click="toggleGroupingMode(props.row, col.name)"
class="cursor-pointer"
size="sm"
flat
dense
unelevated
push
/>
</template>
<template
v-if="col.name === 'item' || col.name === 'import'"
>
{{ col.value }}
</template>
<ItemDescriptorProxy
v-if="col.name === 'item'"
:id="props.row.id"
/>
</component>
</QTd>
</QTr>
@ -354,13 +410,14 @@ const importBuys = () => {
<QList dense>
<QItem v-for="col in props.cols" :key="col.name">
<component
:is="tableColumnComponents[col.name].component()"
v-bind="
tableColumnComponents[col.name].props(col)
"
:is="tableColumnComponents[col.name].component"
v-bind="tableColumnComponents[col.name].props"
v-model="props.row[col.field]"
v-on="
tableColumnComponents[col.name].event(props)
tableColumnComponents[col.name].event(
col.field,
props
)
"
class="full-width"
>
@ -381,6 +438,7 @@ const importBuys = () => {
</QTable>
</template>
</VnPaginate>
<QPageSticky :offset="[20, 20]">
<QBtn fab icon="upload" color="primary" @click="importBuys()" />
<QTooltip class="text-no-wrap">

View File

@ -7,6 +7,8 @@ import VnInput from 'src/components/common/VnInput.vue';
import VnRow from 'components/ui/VnRow.vue';
import FetchData from 'components/FetchData.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import FilterItemForm from 'src/components/FilterItemForm.vue';
import { useStateStore } from 'stores/useStateStore';
import axios from 'axios';
@ -27,6 +29,7 @@ const importData = ref({
ref: null,
});
const inputFileRef = ref(null);
const lastItemBuysOptions = ref([]);
const packagingsOptions = ref([]);
@ -197,14 +200,20 @@ const redirectToBuysView = () => {
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QFile
ref="inputFileRef"
:label="t('entry.buys.file')"
:multiple="false"
v-model="importData.file"
:multiple="false"
accept=".json"
@update:model-value="onFileChange($event)"
class="required"
>
<template #append>
<QIcon name="vn:attach" class="cursor-pointer">
<QIcon
name="vn:attach"
class="cursor-pointer"
@click="inputFileRef.pickFiles()"
>
<QTooltip>{{ t('Select a file') }}</QTooltip>
</QIcon>
</template>
@ -237,13 +246,19 @@ const redirectToBuysView = () => {
>
<template #body-cell-item="{ row, col }">
<QTd auto-width>
<VnSelectFilter
<VnSelectDialog
v-model="row[col.field]"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
hide-selected
action-icon="filter_alt"
>
<template #form>
<FilterItemForm
@item-selected="row[col.field] = $event"
/>
</template>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
@ -254,7 +269,7 @@ const redirectToBuysView = () => {
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</VnSelectDialog>
</QTd>
</template>
<template #body-cell-packagingFk="{ row, col }">

View File

@ -16,6 +16,7 @@ const stateStore = useStateStore();
<Teleport to="#searchbar">
<VnSearchbar
data-key="EntryList"
url="Entries/filter"
:label="t('Search entries')"
:info="t('You can search by entry reference')"
/>

View File

@ -111,26 +111,19 @@ const showEntryReport = () => {
<QItem v-ripple clickable @click="showEntryReport(entity)">
<QItemSection>{{ t('Show entry report') }}</QItemSection>
</QItem>
<QItem v-ripple clickable>
<QItemSection>
<RouterLink :to="{ name: 'EntryList' }" class="color-vn-text">
{{ t('Go to module index') }}
</RouterLink>
</QItemSection>
</QItem>
</template>
<template #body="{ entity }">
<VnLv
:label="t('entry.descriptor.agency')"
:value="entity.travel.agency.name"
:value="entity.travel?.agency?.name"
/>
<VnLv
:label="t('entry.descriptor.landed')"
:value="toDate(entity.travel.landed)"
:value="toDate(entity.travel?.landed)"
/>
<VnLv
:label="t('entry.descriptor.warehouseOut')"
:value="entity.travel.warehouseOut.name"
:value="entity.travel?.warehouseOut?.name"
/>
</template>
<template #icons="{ entity }">

View File

@ -15,6 +15,12 @@ const { t } = useI18n();
const entryObservationsRef = ref(null);
const entryObservationsOptions = ref([]);
const sortEntryObservationOptions = (data) => {
entryObservationsOptions.value = [...data].sort((a, b) =>
a.description.localeCompare(b.description)
);
};
onMounted(() => {
if (entryObservationsRef.value) entryObservationsRef.value.reload();
});
@ -22,7 +28,7 @@ onMounted(() => {
<template>
<FetchData
url="ObservationTypes"
@on-fetch="(data) => (entryObservationsOptions = data)"
@on-fetch="(data) => sortEntryObservationOptions(data)"
auto-load
/>
<CrudModel
@ -37,7 +43,7 @@ onMounted(() => {
:default-remove="false"
:data-required="{ entryFk: route.params.id }"
>
<template #body="{ rows }">
<template #body="{ rows, validate }">
<QCard class="q-pa-md">
<VnRow
v-for="(row, index) in rows"
jsegarra marked this conversation as resolved
Review

Las opciones aparecen desordenadas. Es verdad que en salix vienen ordenadas de back, y en lilium no.
Propuesta, podemos hacer que estén ordenadas alfabéticamente?

Las opciones aparecen desordenadas. Es verdad que en salix vienen ordenadas de back, y en lilium no. Propuesta, podemos hacer que estén ordenadas alfabéticamente?
Review

Ordenamiento aplicado.

Commit: 28790705ea

Ordenamiento aplicado. Commit: https://gitea.verdnatura.es/verdnatura/salix-front/commit/28790705ea4899d3d816cecbf31589c468ec64db
@ -49,6 +55,7 @@ onMounted(() => {
:label="t('entry.notes.observationType')"
jsegarra marked this conversation as resolved
Review

Para el campo observation falta rules

Para el campo observation falta rules
Review

Corregido.

Commit: cde41ab337

Corregido. Commit: https://gitea.verdnatura.es/verdnatura/salix-front/commit/cde41ab337fcb33c4995f56d0e5c8825f5ff6b46
v-model="row.observationTypeFk"
:options="entryObservationsOptions"
:disable="!!row.id"
option-label="description"
option-value="id"
hide-selected
@ -58,6 +65,7 @@ onMounted(() => {
<VnInput
:label="t('entry.notes.description')"
v-model="row.description"
:rules="validate('EntryObservation.description')"
/>
</div>
<div class="col-1 row justify-center items-center">

View File

@ -1,28 +1,38 @@
<script setup>
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { useRoute, useRouter } 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 VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useStateStore } from 'stores/useStateStore';
import { useState } from 'src/composables/useState';
import { toDate } from 'src/filters';
const state = useState();
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const stateStore = useStateStore();
const user = state.getUser();
const newEntryForm = reactive({
supplierFk: null,
travelFk: route.query?.travelFk || null,
companyFk: null,
companyFk: user.value.companyFk || null,
});
const suppliersOptions = ref([]);
const travelsOptionsOptions = ref([]);
const companiesOptions = ref([]);
const redirectToEntryBasicData = (_, { id }) => {
router.push({ name: 'EntryBasicData', params: { id } });
};
</script>
<template>
@ -48,14 +58,28 @@ const companiesOptions = ref([]);
@on-fetch="(data) => (companiesOptions = data)"
auto-load
/>
<!-- Agregar searchbar de entries -->
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<VnSearchbar
url="Entries/filter"
custom-route-redirect-name="EntrySummary"
data-key="EntrySummary"
:label="t('Search entries')"
:info="t('You can search by entry reference')"
/>
</Teleport>
</template>
<QPage>
<VnSubToolbar />
<FormModel url-create="Entries" model="entry" :form-initial-data="newEntryForm">
<FormModel
url-create="Entries"
model="entry"
:form-initial-data="newEntryForm"
@on-data-saved="redirectToEntryBasicData"
>
<template #form="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md">
Review

Veo demasiado padding entre campos y arriba y abajo

Veo demasiado padding entre campos y arriba y abajo
Review

Corregido.

Commit: 67e2130374

Corregido. Commit: https://gitea.verdnatura.es/verdnatura/salix-front/commit/67e21303741bf441d7ecc89d9db1b399d26b08c5
<div class="col">
<VnSelectFilter
:label="t('Supplier')"
class="full-width"
@ -78,8 +102,10 @@ const companiesOptions = ref([]);
</QItem>
</template>
</VnSelectFilter>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('Travel')"
class="full-width"
@ -99,7 +125,8 @@ const companiesOptions = ref([]);
>{{ scope.opt?.agencyModeName }} -
{{ scope.opt?.warehouseInName }} ({{
toDate(scope.opt?.shipped)
}}) &#x2192; {{ scope.opt?.warehouseOutName }} ({{
}}) &#x2192;
{{ scope.opt?.warehouseOutName }} ({{
toDate(scope.opt?.landed)
}})</QItemLabel
>
@ -107,8 +134,10 @@ const companiesOptions = ref([]);
</QItem>
</template>
</VnSelectFilter>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('Company')"
class="full-width"
@ -121,6 +150,7 @@ const companiesOptions = ref([]);
:required="true"
:rules="validate('entry.companyFk')"
/>
</div>
</VnRow>
</template>
</FormModel>

View File

@ -53,7 +53,7 @@ const suppliersOptions = ref([]);
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params }">
<template #body="{ params, searchFn }">
<QItem>
<QItemSection>
<VnInput
@ -95,6 +95,7 @@ const suppliersOptions = ref([]);
<VnSelectFilter
:label="t('params.companyFk')"
v-model="params.companyFk"
@update:model-value="searchFn()"
:options="companiesOptions"
option-value="id"
option-label="code"
@ -110,6 +111,7 @@ const suppliersOptions = ref([]);
<VnSelectFilter
:label="t('params.currencyFk')"
v-model="params.currencyFk"
@update:model-value="searchFn()"
:options="currenciesOptions"
option-value="id"
option-label="name"
@ -125,6 +127,7 @@ const suppliersOptions = ref([]);
<VnSelectFilter
:label="t('params.supplierFk')"
v-model="params.supplierFk"
@update:model-value="searchFn()"
:options="suppliersOptions"
option-value="id"
option-label="name"
@ -149,8 +152,9 @@ const suppliersOptions = ref([]);
<QItemSection>
<VnInputDate
:label="t('params.created')"
is-outlined
v-model="params.created"
@update:model-value="searchFn()"
is-outlined
/>
</QItemSection>
</QItem>
@ -158,8 +162,9 @@ const suppliersOptions = ref([]);
<QItemSection>
<VnInputDate
:label="t('params.from')"
is-outlined
v-model="params.from"
@update:model-value="searchFn()"
is-outlined
/>
</QItemSection>
</QItem>
@ -167,8 +172,9 @@ const suppliersOptions = ref([]);
<QItemSection>
<VnInputDate
:label="t('params.to')"
is-outlined
v-model="params.to"
@update:model-value="searchFn()"
is-outlined
/>
</QItemSection>
</QItem>

View File

@ -0,0 +1,348 @@
<script setup>
import { onMounted, ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import FetchData from 'components/FetchData.vue';
import FetchedTags from 'components/ui/FetchedTags.vue';
import EntryDescriptorProxy from './Card/EntryDescriptorProxy.vue';
import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue';
import EditTableCellValueForm from 'src/components/EditTableCellValueForm.vue';
import { useStateStore } from 'stores/useStateStore';
import { toDate, toCurrency } from 'src/filters';
import { useSession } from 'composables/useSession';
import { dashIfEmpty } from 'src/filters';
const router = useRouter();
const session = useSession();
const token = session.getToken();
const stateStore = useStateStore();
const { t } = useI18n();
const rowsFetchDataRef = ref(null);
const editTableCellDialogRef = ref(null);
const visibleColumns = ref([]);
const allColumnNames = ref([]);
const rows = ref([]);
const rowsSelected = ref([]);
const columns = computed(() => [
{
label: t('entry.latestBuys.picture'),
name: 'picture',
align: 'left',
},
{
label: t('entry.latestBuys.itemFk'),
name: 'itemFk',
field: 'itemFk',
align: 'left',
},
{
label: t('entry.latestBuys.packing'),
field: 'packing',
name: 'packing',
align: 'left',
format: (val) => dashIfEmpty(val),
},
{
label: t('entry.latestBuys.grouping'),
field: 'grouping',
name: 'grouping',
align: 'left',
format: (val) => dashIfEmpty(val),
},
{
label: t('entry.latestBuys.quantity'),
field: 'quantity',
name: 'quantity',
align: 'left',
},
{
label: t('entry.latestBuys.description'),
field: 'description',
name: 'description',
align: 'left',
format: (val) => dashIfEmpty(val),
},
{
label: t('entry.latestBuys.size'),
field: 'size',
name: 'size',
align: 'left',
},
{
label: t('entry.latestBuys.tags'),
name: 'tags',
align: 'left',
},
{
label: t('entry.latestBuys.type'),
field: 'code',
name: 'type',
align: 'left',
},
{
label: t('entry.latestBuys.intrastat'),
field: 'intrastat',
name: 'intrastat',
align: 'left',
},
{
label: t('entry.latestBuys.origin'),
field: 'origin',
name: 'origin',
align: 'left',
},
{
label: t('entry.latestBuys.weightByPiece'),
field: 'weightByPiece',
name: 'weightByPiece',
align: 'left',
format: (val) => dashIfEmpty(val),
},
{
label: t('entry.latestBuys.isActive'),
field: 'isActive',
name: 'isActive',
align: 'left',
},
{
label: t('entry.latestBuys.family'),
field: 'family',
name: 'family',
align: 'left',
},
{
label: t('entry.latestBuys.entryFk'),
field: 'entryFk',
name: 'entryFk',
align: 'left',
},
{
label: t('entry.latestBuys.buyingValue'),
field: 'buyingValue',
name: 'buyingValue',
align: 'left',
format: (val) => toCurrency(val),
},
{
label: t('entry.latestBuys.freightValue'),
field: 'freightValue',
name: 'freightValue',
align: 'left',
format: (val) => toCurrency(val),
},
{
label: t('entry.latestBuys.comissionValue'),
field: 'comissionValue',
name: 'comissionValue',
align: 'left',
format: (val) => toCurrency(val),
},
{
label: t('entry.latestBuys.packageValue'),
field: 'packageValue',
name: 'packageValue',
align: 'left',
format: (val) => toCurrency(val),
},
{
label: t('entry.latestBuys.isIgnored'),
field: 'isIgnored',
name: 'isIgnored',
align: 'left',
},
{
label: t('entry.latestBuys.price2'),
field: 'price2',
name: 'price2',
align: 'left',
format: (val) => toCurrency(val),
},
{
label: t('entry.latestBuys.price3'),
field: 'price3',
name: 'price3',
align: 'left',
format: (val) => toCurrency(val),
},
{
label: t('entry.latestBuys.minPrice'),
field: 'minPrice',
name: 'minPrice',
align: 'left',
format: (val) => toCurrency(val),
},
{
label: t('entry.latestBuys.ektFk'),
field: 'ektFk',
name: 'ektFk',
align: 'left',
format: (val) => dashIfEmpty(val),
},
{
label: t('entry.latestBuys.weight'),
field: 'weight',
name: 'weight',
align: 'left',
},
{
label: t('entry.latestBuys.packagingFk'),
field: 'packagingFk',
name: 'packagingFk',
align: 'left',
},
{
label: t('entry.latestBuys.packingOut'),
field: 'packingOut',
name: 'packingOut',
align: 'left',
format: (val) => dashIfEmpty(val),
},
{
label: t('entry.latestBuys.landing'),
field: 'landing',
name: 'landing',
align: 'left',
format: (val) => toDate(val),
},
]);
const editTableCellFormFieldsOptions = [
{ field: 'packing', label: t('entry.latestBuys.packing') },
{ field: 'grouping', label: t('entry.latestBuys.grouping') },
{ field: 'packageValue', label: t('entry.latestBuys.packageValue') },
{ field: 'weight', label: t('entry.latestBuys.weight') },
{ field: 'description', label: t('entry.latestBuys.description') },
{ field: 'size', label: t('entry.latestBuys.size') },
{ field: 'weightByPiece', label: t('entry.latestBuys.weightByPiece') },
{ field: 'packingOut', label: t('entry.latestBuys.packingOut') },
{ field: 'landing', label: t('entry.latestBuys.landing') },
];
const openEditTableCellDialog = () => {
editTableCellDialogRef.value.show();
};
const onEditCellDataSaved = async () => {
rowsSelected.value = [];
await rowsFetchDataRef.value.fetch();
};
const redirectToEntryBuys = (entryFk) => {
router.push({ name: 'EntryBuys', params: { id: entryFk } });
};
onMounted(async () => {
stateStore.rightDrawer = true;
const filteredColumns = columns.value.filter((col) => col.name !== 'picture');
allColumnNames.value = filteredColumns.map((col) => col.name);
});
</script>
<template>
<FetchData
ref="rowsFetchDataRef"
url="Buys/latestBuysFilter"
:filter="{ order: 'itemFk DESC', limit: 20 }"
@on-fetch="(data) => (rows = data)"
auto-load
/>
<QToolbar class="bg-vn-dark justify-end">
jsegarra marked this conversation as resolved
Review

Si no se usa o es un comentario, eliminar

Si no se usa o es un comentario, eliminar
Review

Lo había dejado comentado porque es donde tendrían que ir los filtros laterales cuando terminemos de definir que hacer con ellos, pero igualmente eliminé el comentario:

Commit: 7089202fe1

Lo había dejado comentado porque es donde tendrían que ir los filtros laterales cuando terminemos de definir que hacer con ellos, pero igualmente eliminé el comentario: Commit: https://gitea.verdnatura.es/verdnatura/salix-front/commit/7089202fe18bb548b7b3b6c4a573e3faf5785fb2
<div id="st-data">
<TableVisibleColumns
:all-columns="allColumnNames"
table-code="latestBuys"
labels-traductions-path="entry.latestBuys"
@on-config-saved="visibleColumns = ['picture', ...$event]"
/>
</div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<QPage class="column items-center q-pa-md">
<QTable
:rows="rows"
:columns="columns"
hide-bottom
selection="multiple"
row-key="id"
:pagination="{ rowsPerPage: 0 }"
class="full-width q-mt-md"
:visible-columns="visibleColumns"
jsegarra marked this conversation as resolved
Review

Revisar proporciones

Revisar proporciones
Review

Corregido.

Commit: 584511bc62

Corregido. Commit: https://gitea.verdnatura.es/verdnatura/salix-front/commit/584511bc62f1e8f57465f0f11417e84edc488468
v-model:selected="rowsSelected"
@row-click="(_, row) => redirectToEntryBuys(row.entryFk)"
>
<template #body-cell-picture="{ row }">
<QTd>
<QImg
:src="`/api/Images/catalog/50x50/${row.itemFk}/download?access_token=${token}`"
spinner-color="primary"
:ratio="1"
height="50px"
width="50px"
class="image"
/>
</QTd>
</template>
<template #body-cell-itemFk="{ row }">
<QTd @click.stop>
<QBtn flat color="blue">
{{ row.itemFk }}
</QBtn>
</QTd>
</template>
jsegarra marked this conversation as resolved Outdated

Si no se maneja el evento, eliminar

Si no se maneja el evento, eliminar

El @click.stop se utiliza para evitar la propagacion del evento a la row y que te direccione a Entry buys, por lo tanto es necesario, lo que si hice es eliminar el ="" que no era necesario

Commit: 5280a4fa2d

El `@click.stop` se utiliza para evitar la propagacion del evento a la row y que te direccione a `Entry buys`, por lo tanto es necesario, lo que si hice es eliminar el `=""` que no era necesario Commit: https://gitea.verdnatura.es/verdnatura/salix-front/commit/5280a4fa2d4d1016956d4d46ef1b1f279b48373b
<template #body-cell-tags="{ row }">
<QTd>
<fetched-tags :item="row" :max-length="6" />
</QTd>
</template>
<template #body-cell-entryFk="{ row }">
<QTd @click.stop>
<QBtn flat color="blue">
<EntryDescriptorProxy :id="row.entryFk" />
{{ row.entryFk }}
</QBtn>
</QTd>
</template>
<template #body-cell-isIgnored="{ row }">
<QTd>
<QIcon
:name="row.isIgnored ? `check` : `close`"
:color="row.isIgnored ? `positive` : `negative`"
size="sm"
/>
</QTd>
</template>
<template #body-cell-isActive="{ row }">
<QTd>
<QIcon
:name="row.isActive ? `check` : `close`"
:color="row.isActive ? `positive` : `negative`"
size="sm"
/>
</QTd>
</template>
</QTable>
<QPageSticky v-if="rowsSelected.length > 0" :offset="[20, 20]">
<QBtn @click="openEditTableCellDialog()" color="primary" fab icon="edit" />
<QTooltip>
{{ t('Edit buy(s)') }}
</QTooltip>
</QPageSticky>
<QDialog ref="editTableCellDialogRef">
<EditTableCellValueForm
edit-url="Buys/editLatestBuys"
:rows="rowsSelected"
:fields-options="editTableCellFormFieldsOptions"
@on-data-saved="onEditCellDataSaved()"
/>
</QDialog>
</QPage>
</template>
<i18n>
es:
Edit buy(s): Editar compra(s)
</i18n>

View File

@ -9,6 +9,7 @@ import VnLv from 'src/components/ui/VnLv.vue';
import CardList from 'src/components/ui/CardList.vue';
import EntrySummaryDialog from './Card/EntrySummaryDialog.vue';
import EntryFilter from './EntryFilter.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useStateStore } from 'stores/useStateStore';
import { toDate } from 'src/filters/index';
@ -41,6 +42,16 @@ onMounted(async () => {
</script>
<template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<VnSearchbar
data-key="EntryList"
url="Entries/filter"
:label="t('Search entries')"
:info="t('You can search by entry reference')"
/>
</Teleport>
</template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<EntryFilter data-key="EntryList" />
@ -51,7 +62,7 @@ onMounted(async () => {
<VnPaginate
data-key="EntryList"
url="Entries/filter"
order="landed DESC, id DESC"
:order="['landed DESC', 'id DESC']"
auto-load
>
<template #body="{ rows }">
@ -130,8 +141,8 @@ onMounted(async () => {
<i18n>
es:
Search entries: Buscar entradas
You can search by entry reference: Puedes buscar por referencia de la entrada
Inventory entry: Es inventario
Virtual entry: Es una redada
Search entries: Buscar entradas
You can search by entry reference: Puedes buscar por referencia de la entrada
</i18n>

View File

@ -1,24 +1,12 @@
<script setup>
import { useI18n } from 'vue-i18n';
import LeftMenu from 'src/components/LeftMenu.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useStateStore } from 'stores/useStateStore';
const { t } = useI18n();
const stateStore = useStateStore();
</script>
<template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<VnSearchbar
data-key="EntryList"
:label="t('Search entries')"
:info="t('You can search by entry reference')"
/>
</Teleport>
</template>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit text-grey-8">
<LeftMenu />
@ -28,9 +16,3 @@ const stateStore = useStateStore();
<RouterView></RouterView>
</QPageContainer>
</template>
<i18n>
es:
Search entries: Buscar entradas
You can search by entry reference: Puedes buscar por referencia de la entrada
</i18n>

View File

@ -8,7 +8,7 @@ import CrudModel from 'components/CrudModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import CreateBankEntityForm from 'src/components/CreateBankEntityForm.vue';
import VnSelectCreate from 'src/components/common/VnSelectCreate.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import axios from 'axios';
import useNotify from 'src/composables/useNotify.js';
@ -110,8 +110,8 @@ onMounted(() => {
</VnInput>
</div>
<div class="col">
<VnSelectCreate
:label="t('supplier.accounts.bankEntity')"
<VnSelectDialog
:label="t('worker.create.bankEntity')"
v-model="row.bankEntityFk"
:options="bankEntitiesOptions"
option-label="name"
@ -134,7 +134,7 @@ onMounted(() => {
</QItemSection>
</QItem>
</template>
</VnSelectCreate>
</VnSelectDialog>
</div>
<div class="col">
<VnInput

View File

@ -7,7 +7,7 @@ import FetchData from 'components/FetchData.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import FormModel from 'components/FormModel.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelectCreate from 'src/components/common/VnSelectCreate.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import VnRow from 'components/ui/VnRow.vue';
import CustomerCreateNewPostcode from 'src/components/CreateNewPostcodeForm.vue';
@ -104,7 +104,7 @@ onMounted(() => {
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectCreate
<VnSelectDialog
v-model="data.postalCode"
:label="t('supplier.addresses.postcode')"
:rules="validate('supplierAddress.postcode')"
@ -135,7 +135,7 @@ onMounted(() => {
</QItemSection>
</QItem>
</template>
</VnSelectCreate>
</VnSelectDialog>
</div>
<div class="col">
<VnSelectFilter

View File

@ -7,7 +7,7 @@ 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 VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CreateBankEntityForm from 'src/components/CreateBankEntityForm.vue';
import CustomerCreateNewPostcode from 'src/components/CreateNewPostcodeForm.vue';
import VnLocation from 'src/components/common/VnLocation.vue';
@ -56,9 +56,8 @@ const onBankEntityCreated = (data) => {
bankEntitiesOptions.value.push(data);
};
function handleLocation(data, location) {
const { town, postcode: code, provinceFk, countryFk } = location ?? {}
const { town, postcode: code, provinceFk, countryFk } = location ?? {};
data.postcode = code;
data.city = town;
data.provinceFk = provinceFk;
@ -258,7 +257,7 @@ onMounted(async () => {
</template>
</VnInput>
</div>
<VnSelectCreate
<VnSelectDialog
:label="t('worker.create.bankEntity')"
v-model="data.bankEntityFk"
:options="bankEntitiesOptions"
@ -283,7 +282,7 @@ onMounted(async () => {
</QItemSection>
</QItem>
</template>
</VnSelectCreate>
</VnSelectDialog>
</VnRow>
</template>
</FormModel>

View File

@ -10,7 +10,7 @@ export default {
component: RouterView,
redirect: { name: 'EntryMain' },
menus: {
main: ['EntryList'],
main: ['EntryList', 'EntryLatestBuys'],
card: ['EntryBasicData', 'EntryBuys', 'EntryNotes', 'EntryLog'],
},
children: [
@ -37,6 +37,15 @@ export default {
},
component: () => import('src/pages/Entry/EntryCreate.vue'),
},
{
path: 'latest-buys',
name: 'EntryLatestBuys',
meta: {
title: 'latestBuys',
icon: 'contact_support',
},
component: () => import('src/pages/Entry/EntryLatestBuys.vue'),
},
],
},
{