0
0
Fork 0

Merge branch 'dev' into 4666-invoiceInCorrection

This commit is contained in:
Javier Segarra 2024-02-07 07:52:14 +00:00
commit 532c88c7c3
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,
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,8 +91,16 @@ 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 { path } = matches[matches.length - 1];
const newRoute = path.replace(':id', searchText.value);
await router.push(newRoute);
}

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

@ -270,6 +270,8 @@ export default {
buys: 'Buys',
notes: 'Notes',
log: 'Log',
create: 'Create',
latestBuys: 'Latest buys',
},
list: {
newEntry: 'New entry',
@ -325,6 +327,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',
@ -339,6 +347,11 @@ export default {
buyingValue: 'Buying value',
packagingFk: 'Box',
file: 'File',
name: 'Name',
producer: 'Producer',
type: 'Type',
color: 'Color',
id: 'ID',
},
notes: {
observationType: 'Observation type',
@ -349,6 +362,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

@ -269,6 +269,8 @@ export default {
buys: 'Compras',
notes: 'Notas',
log: 'Historial',
create: 'Crear',
latestBuys: 'Últimas compras',
},
list: {
newEntry: 'Nueva entrada',
@ -324,6 +326,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',
@ -338,6 +346,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',
@ -348,6 +361,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 ?? {}
function handleLocation(data, 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';
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
: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);
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: {
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
: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"
@ -49,6 +55,7 @@ onMounted(() => {
:label="t('entry.notes.observationType')"
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,79 +58,99 @@ 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">
<VnSelectFilter
:label="t('Supplier')"
class="full-width"
v-model="data.supplierFk"
:options="suppliersOptions"
option-value="id"
option-label="nickname"
hide-selected
:required="true"
: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>
<div class="col">
<VnSelectFilter
:label="t('Supplier')"
class="full-width"
v-model="data.supplierFk"
:options="suppliersOptions"
option-value="id"
option-label="nickname"
hide-selected
:required="true"
: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>
</div>
</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
:required="true"
: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>
<div class="col">
<VnSelectFilter
:label="t('Travel')"
class="full-width"
v-model="data.travelFk"
:options="travelsOptionsOptions"
option-value="id"
option-label="warehouseInName"
map-options
hide-selected
:required="true"
: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>
</div>
</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
:required="true"
:rules="validate('entry.companyFk')"
/>
<div class="col">
<VnSelectFilter
:label="t('Company')"
class="full-width"
v-model="data.companyFk"
:options="companiesOptions"
option-value="id"
option-label="code"
map-options
hide-selected
: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">
<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"
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>
<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 ?? {}
function handleLocation(data, location) {
const { town, postcode: code, provinceFk, countryFk } = location ?? {};
data.postcode = code;
data.city = town;
data.provinceFk = provinceFk;
@ -162,9 +161,9 @@ onMounted(async () => {
:options="postcodesOptions"
v-model="data.location"
@update:model-value="
(location) => handleLocation(data, location)
"
>
(location) => handleLocation(data, location)
"
>
</VnLocation>
</div>
</VnRow>
@ -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'),
},
],
},
{