Item Fixed prices #307
|
@ -1,9 +1,12 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive } from 'vue';
|
import { ref, markRaw } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||||
import VnInput from 'src/components/common/VnInput.vue';
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
|
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||||
|
import VnRow from 'components/ui/VnRow.vue';
|
||||||
|
import { QCheckbox } from 'quasar';
|
||||||
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import useNotify from 'src/composables/useNotify.js';
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
@ -28,11 +31,16 @@ const $props = defineProps({
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { notify } = useNotify();
|
const { notify } = useNotify();
|
||||||
|
|
||||||
const formData = reactive({
|
const inputs = {
|
||||||
field: null,
|
input: markRaw(VnInput),
|
||||||
newValue: null,
|
number: markRaw(VnInput),
|
||||||
});
|
date: markRaw(VnInputDate),
|
||||||
|
checkbox: markRaw(QCheckbox),
|
||||||
|
select: markRaw(VnSelectFilter),
|
||||||
|
};
|
||||||
|
|
||||||
|
const newValue = ref(null);
|
||||||
|
const selectedField = ref(null);
|
||||||
const closeButton = ref(null);
|
const closeButton = ref(null);
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
|
|
||||||
|
@ -47,8 +55,8 @@ const submitData = async () => {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
const rowsToEdit = $props.rows.map((row) => ({ id: row.id, itemFk: row.itemFk }));
|
const rowsToEdit = $props.rows.map((row) => ({ id: row.id, itemFk: row.itemFk }));
|
||||||
const payload = {
|
const payload = {
|
||||||
field: formData.field,
|
field: selectedField.value.field,
|
||||||
newValue: formData.newValue,
|
newValue: newValue.value,
|
||||||
lines: rowsToEdit,
|
lines: rowsToEdit,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -75,19 +83,20 @@ const closeForm = () => {
|
||||||
<span class="countLines">{{ ` ${rows.length} ` }}</span>
|
<span class="countLines">{{ ` ${rows.length} ` }}</span>
|
||||||
<span class="title">{{ t('buy(s)') }}</span>
|
<span class="title">{{ t('buy(s)') }}</span>
|
||||||
<VnRow class="row q-gutter-md q-mb-md">
|
<VnRow class="row q-gutter-md q-mb-md">
|
||||||
<div class="col">
|
<VnSelectFilter
|
||||||
<VnSelectFilter
|
:label="t('Field to edit')"
|
||||||
:label="t('Field to edit')"
|
:options="fieldsOptions"
|
||||||
:options="fieldsOptions"
|
hide-selected
|
||||||
hide-selected
|
option-label="label"
|
||||||
option-label="label"
|
v-model="selectedField"
|
||||||
option-value="field"
|
/>
|
||||||
v-model="formData.field"
|
<component
|
||||||
/>
|
:is="inputs[selectedField?.component || 'input']"
|
||||||
</div>
|
v-bind="selectedField?.attrs || {}"
|
||||||
<div class="col">
|
v-model="newValue"
|
||||||
<VnInput :label="t('Value')" v-model="formData.newValue" />
|
:label="t('Value')"
|
||||||
</div>
|
style="width: 200px"
|
||||||
|
/>
|
||||||
</VnRow>
|
</VnRow>
|
||||||
<div class="q-mt-lg row justify-end">
|
<div class="q-mt-lg row justify-end">
|
||||||
<QBtn
|
<QBtn
|
||||||
|
|
|
@ -0,0 +1,359 @@
|
||||||
|
<script setup>
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import VnInput from 'components/common/VnInput.vue';
|
||||||
|
import FetchData from 'components/FetchData.vue';
|
||||||
|
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||||
|
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
|
||||||
|
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const props = defineProps({
|
||||||
|
dataKey: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
customTags: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
exprBuilder: {
|
||||||
|
type: Function,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const itemCategories = ref([]);
|
||||||
|
const selectedCategoryFk = ref(null);
|
||||||
|
const selectedTypeFk = ref(null);
|
||||||
|
const itemTypesOptions = ref([]);
|
||||||
|
const suppliersOptions = ref([]);
|
||||||
|
const tagOptions = ref([]);
|
||||||
|
const tagValues = ref([]);
|
||||||
|
|
||||||
|
const categoryList = computed(() => {
|
||||||
|
return (itemCategories.value || [])
|
||||||
|
.filter((category) => category.display)
|
||||||
|
.map((category) => ({
|
||||||
|
...category,
|
||||||
|
icon: `vn:${(category.icon || '').split('-')[1]}`,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedCategory = computed(() =>
|
||||||
|
(itemCategories.value || []).find(
|
||||||
|
(category) => category?.id === selectedCategoryFk.value
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedType = computed(() => {
|
||||||
|
return (itemTypesOptions.value || []).find(
|
||||||
|
(type) => type?.id === selectedTypeFk.value
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectCategory = async (params, categoryId, search) => {
|
||||||
|
if (params.categoryFk === categoryId) {
|
||||||
|
resetCategory(params);
|
||||||
|
search();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectedCategoryFk.value = categoryId;
|
||||||
|
params.categoryFk = categoryId;
|
||||||
|
await fetchItemTypes(categoryId);
|
||||||
|
search();
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetCategory = (params) => {
|
||||||
|
selectedCategoryFk.value = null;
|
||||||
|
itemTypesOptions.value = null;
|
||||||
|
if (params) {
|
||||||
|
params.categoryFk = null;
|
||||||
|
params.typeFk = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyTags = (params, search) => {
|
||||||
|
params.tags = tagValues.value
|
||||||
|
.filter((tag) => tag.selectedTag && tag.value)
|
||||||
|
.map((tag) => ({
|
||||||
|
tagFk: tag.selectedTag.id,
|
||||||
|
tagName: tag.selectedTag.name,
|
||||||
|
value: tag.value,
|
||||||
|
}));
|
||||||
|
search();
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchItemTypes = async (id) => {
|
||||||
|
try {
|
||||||
|
const filter = {
|
||||||
|
fields: ['id', 'name', 'categoryFk'],
|
||||||
|
where: { categoryFk: id },
|
||||||
|
include: 'category',
|
||||||
|
order: 'name ASC',
|
||||||
|
};
|
||||||
|
const { data } = await axios.get('ItemTypes', {
|
||||||
|
params: { filter: JSON.stringify(filter) },
|
||||||
|
});
|
||||||
|
itemTypesOptions.value = data;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching item types', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCategoryClass = (category, params) => {
|
||||||
|
if (category.id === params?.categoryFk) {
|
||||||
|
return 'active';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSelectedTagValues = async (tag) => {
|
||||||
|
try {
|
||||||
|
tag.value = null;
|
||||||
|
const filter = {
|
||||||
|
fields: ['value'],
|
||||||
|
order: 'value ASC',
|
||||||
|
limit: 30,
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = { filter: JSON.stringify(filter) };
|
||||||
|
const { data } = await axios.get(`Tags/${tag.selectedTag.id}/filterValue`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
tag.valueOptions = data;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error getting selected tag values');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeTag = (index, params, search) => {
|
||||||
|
(tagValues.value || []).splice(index, 1);
|
||||||
|
applyTags(params, search);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<FetchData
|
||||||
|
url="ItemCategories"
|
||||||
|
limit="30"
|
||||||
|
auto-load
|
||||||
|
@on-fetch="(data) => (itemCategories = data)"
|
||||||
|
/>
|
||||||
|
<FetchData
|
||||||
|
url="Suppliers"
|
||||||
|
limit="30"
|
||||||
|
auto-load
|
||||||
|
:filter="{ fields: ['id', 'name', 'nickname'], order: 'name ASC', limit: 30 }"
|
||||||
|
@on-fetch="(data) => (suppliersOptions = data)"
|
||||||
|
/>
|
||||||
|
<FetchData
|
||||||
|
url="Tags"
|
||||||
|
:filter="{ fields: ['id', 'name', 'isFree'] }"
|
||||||
|
auto-load
|
||||||
|
limit="30"
|
||||||
|
@on-fetch="(data) => (tagOptions = data)"
|
||||||
|
/>
|
||||||
|
<VnFilterPanel
|
||||||
|
:data-key="props.dataKey"
|
||||||
|
:expr-builder="exprBuilder"
|
||||||
|
:custom-tags="customTags"
|
||||||
|
>
|
||||||
|
<template #tags="{ tag, formatFn }">
|
||||||
|
<strong v-if="tag.label === 'categoryFk'">
|
||||||
|
{{ t(selectedCategory?.name || '') }}
|
||||||
|
</strong>
|
||||||
|
<strong v-else-if="tag.label === 'typeFk'">
|
||||||
|
{{ t(selectedType?.name || '') }}
|
||||||
|
</strong>
|
||||||
|
<div v-else class="q-gutter-x-xs">
|
||||||
|
<strong>{{ t(`components.itemsFilterPanel.${tag.label}`) }}: </strong>
|
||||||
|
<span>{{ formatFn(tag.value) }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #customTags="{ tags, params }">
|
||||||
|
<template v-for="tag in tags" :key="tag.label">
|
||||||
|
<VnFilterPanelChip
|
||||||
|
v-for="chip in tag.value"
|
||||||
|
:key="chip"
|
||||||
|
removable
|
||||||
|
@remove="removeTagChip(chip, params, searchFn)"
|
||||||
|
>
|
||||||
|
<div class="q-gutter-x-xs">
|
||||||
|
<strong>{{ chip.tagName }}: </strong>
|
||||||
|
<span>"{{ chip.value }}"</span>
|
||||||
|
</div>
|
||||||
|
</VnFilterPanelChip>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #body="{ params, searchFn }">
|
||||||
|
<QItem class="category-filter q-mt-md">
|
||||||
|
<QBtn
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
v-for="category in categoryList"
|
||||||
|
:key="category.name"
|
||||||
|
:class="['category', getCategoryClass(category, params)]"
|
||||||
|
:icon="category.icon"
|
||||||
|
@click="selectCategory(params, category.id, searchFn)"
|
||||||
|
>
|
||||||
|
<QTooltip>
|
||||||
|
{{ t(category.name) }}
|
||||||
|
</QTooltip>
|
||||||
|
</QBtn>
|
||||||
|
</QItem>
|
||||||
|
<QItem class="q-my-md">
|
||||||
|
<QItemSection>
|
||||||
|
<VnSelectFilter
|
||||||
|
:label="t('components.itemsFilterPanel.typeFk')"
|
||||||
|
v-model="params.typeFk"
|
||||||
|
:options="itemTypesOptions"
|
||||||
|
option-value="id"
|
||||||
|
option-label="name"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
rounded
|
||||||
|
use-input
|
||||||
|
:disable="!selectedCategoryFk"
|
||||||
|
@update:model-value="
|
||||||
|
(value) => {
|
||||||
|
selectedTypeFk = value;
|
||||||
|
searchFn();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #option="{ itemProps, opt }">
|
||||||
|
<QItem v-bind="itemProps">
|
||||||
|
<QItemSection>
|
||||||
|
<QItemLabel>{{ opt.name }}</QItemLabel>
|
||||||
|
<QItemLabel caption>
|
||||||
|
{{ opt.categoryName }}
|
||||||
|
</QItemLabel>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
</template>
|
||||||
|
</VnSelectFilter>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QSeparator />
|
||||||
|
<slot name="body" :params="params" :search-fn="searchFn" />
|
||||||
|
<QItem
|
||||||
|
v-for="(value, index) in tagValues"
|
||||||
|
:key="value"
|
||||||
|
class="q-mt-md filter-value"
|
||||||
|
>
|
||||||
|
<QItemSection class="col">
|
||||||
|
<VnSelectFilter
|
||||||
|
:label="t('components.itemsFilterPanel.tag')"
|
||||||
|
v-model="value.selectedTag"
|
||||||
|
:options="tagOptions"
|
||||||
|
option-label="name"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
rounded
|
||||||
|
:emit-value="false"
|
||||||
|
use-input
|
||||||
|
:is-clearable="false"
|
||||||
|
@update:model-value="getSelectedTagValues(value)"
|
||||||
|
/>
|
||||||
|
</QItemSection>
|
||||||
|
<QItemSection class="col">
|
||||||
|
<VnSelectFilter
|
||||||
|
v-if="!value?.selectedTag?.isFree && value.valueOptions"
|
||||||
|
:label="t('components.itemsFilterPanel.value')"
|
||||||
|
v-model="value.value"
|
||||||
|
:options="value.valueOptions || []"
|
||||||
|
option-value="value"
|
||||||
|
option-label="value"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
rounded
|
||||||
|
emit-value
|
||||||
|
use-input
|
||||||
|
:disable="!value"
|
||||||
|
:is-clearable="false"
|
||||||
|
@update:model-value="applyTags(params, searchFn)"
|
||||||
|
/>
|
||||||
|
<VnInput
|
||||||
|
v-else
|
||||||
|
v-model="value.value"
|
||||||
|
:label="t('components.itemsFilterPanel.value')"
|
||||||
|
:disable="!value"
|
||||||
|
is-outlined
|
||||||
|
:is-clearable="false"
|
||||||
|
@keyup.enter="applyTags(params, searchFn)"
|
||||||
|
/>
|
||||||
|
</QItemSection>
|
||||||
|
<QIcon
|
||||||
|
name="delete"
|
||||||
|
class="fill-icon-on-hover q-px-xs"
|
||||||
|
color="primary"
|
||||||
|
size="sm"
|
||||||
|
@click="removeTag(index, params, searchFn)"
|
||||||
|
/>
|
||||||
|
</QItem>
|
||||||
|
<QItem class="q-mt-lg">
|
||||||
|
<QIcon
|
||||||
|
name="add_circle"
|
||||||
|
class="fill-icon-on-hover q-px-xs"
|
||||||
|
color="primary"
|
||||||
|
size="sm"
|
||||||
|
@click="tagValues.push({})"
|
||||||
|
/>
|
||||||
|
</QItem>
|
||||||
|
</template>
|
||||||
|
</VnFilterPanel>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.category-filter {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.category {
|
||||||
|
padding: 8px;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
background-color: var(--vn-accent-color);
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: $primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-value {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
en:
|
||||||
|
params:
|
||||||
|
supplier: Supplier
|
||||||
|
from: From
|
||||||
|
to: To
|
||||||
|
active: Is active
|
||||||
|
visible: Is visible
|
||||||
|
floramondo: Is floramondo
|
||||||
|
salesPersonFk: Buyer
|
||||||
|
categoryFk: Category
|
||||||
|
|
||||||
|
es:
|
||||||
|
params:
|
||||||
|
supplier: Proveedor
|
||||||
|
from: Desde
|
||||||
|
to: Hasta
|
||||||
|
active: Activo
|
||||||
|
visible: Visible
|
||||||
|
floramondo: Floramondo
|
||||||
|
salesPersonFk: Comprador
|
||||||
|
categoryFk: Categoría
|
||||||
|
</i18n>
|
|
@ -151,9 +151,10 @@ export function useArrayData(key, userOptions) {
|
||||||
delete store.userParams[param];
|
delete store.userParams[param];
|
||||||
delete params[param];
|
delete params[param];
|
||||||
if (store.filter?.where) {
|
if (store.filter?.where) {
|
||||||
delete store.filter.where[
|
const key = Object.keys(
|
||||||
Object.keys(exprBuilder ? exprBuilder(param) : param)[0]
|
exprBuilder && exprBuilder(param) ? exprBuilder(param) : param
|
||||||
];
|
);
|
||||||
|
if (key[0]) delete store.filter.where[key[0]];
|
||||||
if (Object.keys(store.filter.where).length === 0) {
|
if (Object.keys(store.filter.where).length === 0) {
|
||||||
delete store.filter.where;
|
delete store.filter.where;
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,6 +119,11 @@ select:-webkit-autofill {
|
||||||
font-variation-settings: 'FILL' 1;
|
font-variation-settings: 'FILL' 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fill-icon-on-hover:hover {
|
||||||
|
font-variation-settings: 'FILL' 1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.vn-table-separation-row {
|
.vn-table-separation-row {
|
||||||
height: 16px !important;
|
height: 16px !important;
|
||||||
background-color: var(--vn-section-color) !important;
|
background-color: var(--vn-section-color) !important;
|
||||||
|
@ -183,17 +188,6 @@ input::-webkit-inner-spin-button {
|
||||||
-moz-appearance: none;
|
-moz-appearance: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clases para modificar el color de fecha seleccionada en componente QCalendarMonth
|
|
||||||
.q-dark div .q-calendar-mini .q-calendar-month__day.q-selected .q-calendar__button {
|
|
||||||
background-color: $primary !important;
|
|
||||||
color: white !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.q-calendar-mini .q-calendar-month__day.q-selected .q-calendar__button {
|
|
||||||
background-color: $primary !important;
|
|
||||||
color: white !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.q-scrollarea__content {
|
.q-scrollarea__content {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,3 +112,21 @@ export function secondsToHoursMinutes(seconds, includeHSuffix = true) {
|
||||||
// Return formatted string
|
// Return formatted string
|
||||||
return formattedHours + ':' + formattedMinutes + suffix;
|
return formattedHours + ':' + formattedMinutes + suffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getTimeDifferenceWithToday(date) {
|
||||||
|
let today = Date.vnNew();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
date = new Date(date);
|
||||||
|
date.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
return today - date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isLower(date) {
|
||||||
|
return getTimeDifferenceWithToday(date) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isBigger(date) {
|
||||||
|
return getTimeDifferenceWithToday(date) < 0;
|
||||||
|
}
|
||||||
|
|
|
@ -1119,6 +1119,7 @@ item:
|
||||||
list: List
|
list: List
|
||||||
diary: Diary
|
diary: Diary
|
||||||
tags: Tags
|
tags: Tags
|
||||||
|
fixedPrice: Fixed prices
|
||||||
wasteBreakdown: Waste breakdown
|
wasteBreakdown: Waste breakdown
|
||||||
itemCreate: New item
|
itemCreate: New item
|
||||||
descriptor:
|
descriptor:
|
||||||
|
@ -1148,6 +1149,15 @@ item:
|
||||||
stemMultiplier: Multiplier
|
stemMultiplier: Multiplier
|
||||||
producer: Producer
|
producer: Producer
|
||||||
landed: Landed
|
landed: Landed
|
||||||
|
fixedPrice:
|
||||||
|
itemId: Item ID
|
||||||
|
groupingPrice: Grouping price
|
||||||
|
packingPrice: Packing price
|
||||||
|
hasMinPrice: Has min price
|
||||||
|
minPrice: Min price
|
||||||
|
started: Started
|
||||||
|
ended: Ended
|
||||||
|
warehouse: Warehouse
|
||||||
create:
|
create:
|
||||||
name: Name
|
name: Name
|
||||||
tag: Tag
|
tag: Tag
|
||||||
|
@ -1157,6 +1167,25 @@ item:
|
||||||
origin: Origin
|
origin: Origin
|
||||||
components:
|
components:
|
||||||
topbar: {}
|
topbar: {}
|
||||||
|
itemsFilterPanel:
|
||||||
|
typeFk: Type
|
||||||
|
tag: Tag
|
||||||
|
value: Value
|
||||||
|
# ItemFixedPriceFilter
|
||||||
|
buyerFk: Buyer
|
||||||
|
warehouseFk: Warehouse
|
||||||
|
started: From
|
||||||
|
ended: To
|
||||||
|
mine: For me
|
||||||
|
hasMinPrice: Minimum price
|
||||||
|
# LatestBuysFilter
|
||||||
|
salesPersonFk: Buyer
|
||||||
|
supplierFk: Supplier
|
||||||
|
from: From
|
||||||
|
to: To
|
||||||
|
active: Is active
|
||||||
|
visible: Is visible
|
||||||
|
floramondo: Is floramondo
|
||||||
userPanel:
|
userPanel:
|
||||||
copyToken: Token copied to clipboard
|
copyToken: Token copied to clipboard
|
||||||
settings: Settings
|
settings: Settings
|
||||||
|
|
|
@ -1118,6 +1118,7 @@ item:
|
||||||
list: Listado
|
list: Listado
|
||||||
diary: Histórico
|
diary: Histórico
|
||||||
tags: Etiquetas
|
tags: Etiquetas
|
||||||
|
fixedPrice: Precios fijados
|
||||||
wasteBreakdown: Deglose de mermas
|
wasteBreakdown: Deglose de mermas
|
||||||
itemCreate: Nuevo artículo
|
itemCreate: Nuevo artículo
|
||||||
descriptor:
|
descriptor:
|
||||||
|
@ -1147,6 +1148,15 @@ item:
|
||||||
stemMultiplier: Multiplicador
|
stemMultiplier: Multiplicador
|
||||||
producer: Productor
|
producer: Productor
|
||||||
landed: F. entrega
|
landed: F. entrega
|
||||||
|
fixedPrice:
|
||||||
|
itemId: ID Artículo
|
||||||
|
groupingPrice: Precio grouping
|
||||||
|
packingPrice: Precio packing
|
||||||
|
hasMinPrice: Tiene precio mínimo
|
||||||
|
minPrice: Precio min
|
||||||
|
started: Inicio
|
||||||
|
ended: Fin
|
||||||
|
warehouse: Almacén
|
||||||
create:
|
create:
|
||||||
name: Nombre
|
name: Nombre
|
||||||
tag: Etiqueta
|
tag: Etiqueta
|
||||||
|
@ -1156,6 +1166,25 @@ item:
|
||||||
origin: Origen
|
origin: Origen
|
||||||
components:
|
components:
|
||||||
topbar: {}
|
topbar: {}
|
||||||
|
itemsFilterPanel:
|
||||||
|
typeFk: Tipo
|
||||||
|
tag: Etiqueta
|
||||||
|
value: Valor
|
||||||
|
# ItemFixedPriceFilter
|
||||||
|
buyerFk: Comprador
|
||||||
|
warehouseFk: Almacén
|
||||||
|
started: Desde
|
||||||
|
ended: Hasta
|
||||||
|
mine: Para mi
|
||||||
|
hasMinPrice: Precio mínimo
|
||||||
|
# LatestBuysFilter
|
||||||
|
salesPersonFk: Comprador
|
||||||
|
supplierFk: Proveedor
|
||||||
|
from: Desde
|
||||||
|
to: Hasta
|
||||||
|
active: Activo
|
||||||
|
visible: Visible
|
||||||
|
floramondo: Floramondo
|
||||||
userPanel:
|
userPanel:
|
||||||
copyToken: Token copiado al portapapeles
|
copyToken: Token copiado al portapapeles
|
||||||
settings: Configuración
|
settings: Configuración
|
||||||
|
|
|
@ -650,7 +650,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
||||||
</QToolbar>
|
</QToolbar>
|
||||||
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
|
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
|
||||||
<QScrollArea class="fit text-grey-8">
|
<QScrollArea class="fit text-grey-8">
|
||||||
<EntryLatestBuysFilter data-key="EntryLatestBuys" :tags="tags" />
|
<EntryLatestBuysFilter data-key="EntryLatestBuys" />
|
||||||
</QScrollArea>
|
</QScrollArea>
|
||||||
</QDrawer>
|
</QDrawer>
|
||||||
<QPage class="column items-center q-pa-md">
|
<QPage class="column items-center q-pa-md">
|
||||||
|
|
|
@ -1,141 +1,26 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import VnInput from 'components/common/VnInput.vue';
|
|
||||||
import FetchData from 'components/FetchData.vue';
|
import FetchData from 'components/FetchData.vue';
|
||||||
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
|
||||||
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
|
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
|
||||||
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
|
import ItemsFilterPanel from 'src/components/ItemsFilterPanel.vue';
|
||||||
|
|
||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const props = defineProps({
|
|
||||||
|
defineProps({
|
||||||
dataKey: {
|
dataKey: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const itemCategories = ref([]);
|
|
||||||
const selectedCategoryFk = ref(null);
|
|
||||||
const selectedTypeFk = ref(null);
|
|
||||||
const itemTypesOptions = ref([]);
|
|
||||||
const itemTypeWorkersOptions = ref([]);
|
const itemTypeWorkersOptions = ref([]);
|
||||||
const suppliersOptions = ref([]);
|
const suppliersOptions = ref([]);
|
||||||
const tagOptions = ref([]);
|
|
||||||
const tagValues = ref([]);
|
|
||||||
|
|
||||||
const categoryList = computed(() => {
|
|
||||||
return (itemCategories.value || [])
|
|
||||||
.filter((category) => category.display)
|
|
||||||
.map((category) => ({
|
|
||||||
...category,
|
|
||||||
icon: `vn:${(category.icon || '').split('-')[1]}`,
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedCategory = computed(() =>
|
|
||||||
(itemCategories.value || []).find(
|
|
||||||
(category) => category?.id === selectedCategoryFk.value
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectedType = computed(() => {
|
|
||||||
return (itemTypesOptions.value || []).find(
|
|
||||||
(type) => type?.id === selectedTypeFk.value
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectCategory = async (params, categoryId, search) => {
|
|
||||||
if (params.categoryFk === categoryId) {
|
|
||||||
resetCategory(params);
|
|
||||||
search();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
selectedCategoryFk.value = categoryId;
|
|
||||||
params.categoryFk = categoryId;
|
|
||||||
await fetchItemTypes(categoryId);
|
|
||||||
search();
|
|
||||||
};
|
|
||||||
|
|
||||||
const resetCategory = (params) => {
|
|
||||||
selectedCategoryFk.value = null;
|
|
||||||
itemTypesOptions.value = null;
|
|
||||||
if (params) {
|
|
||||||
params.categoryFk = null;
|
|
||||||
params.typeFk = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const applyTags = (params, search) => {
|
|
||||||
params.tags = tagValues.value
|
|
||||||
.filter((tag) => tag.selectedTag && tag.value)
|
|
||||||
.map((tag) => ({
|
|
||||||
tagFk: tag.selectedTag.id,
|
|
||||||
tagName: tag.selectedTag.name,
|
|
||||||
value: tag.value,
|
|
||||||
}));
|
|
||||||
search();
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchItemTypes = async (id) => {
|
|
||||||
try {
|
|
||||||
const filter = {
|
|
||||||
fields: ['id', 'name', 'categoryFk'],
|
|
||||||
where: { categoryFk: id },
|
|
||||||
include: 'category',
|
|
||||||
order: 'name ASC',
|
|
||||||
};
|
|
||||||
const { data } = await axios.get('ItemTypes', {
|
|
||||||
params: { filter: JSON.stringify(filter) },
|
|
||||||
});
|
|
||||||
itemTypesOptions.value = data;
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error fetching item types', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCategoryClass = (category, params) => {
|
|
||||||
if (category.id === params?.categoryFk) {
|
|
||||||
return 'active';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSelectedTagValues = async (tag) => {
|
|
||||||
try {
|
|
||||||
tag.value = null;
|
|
||||||
const filter = {
|
|
||||||
fields: ['value'],
|
|
||||||
order: 'value ASC',
|
|
||||||
limit: 30,
|
|
||||||
};
|
|
||||||
|
|
||||||
const params = { filter: JSON.stringify(filter) };
|
|
||||||
const { data } = await axios.get(`Tags/${tag.selectedTag.id}/filterValue`, {
|
|
||||||
params,
|
|
||||||
});
|
|
||||||
tag.valueOptions = data;
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error getting selected tag values');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeTag = (index, params, search) => {
|
|
||||||
(tagValues.value || []).splice(index, 1);
|
|
||||||
applyTags(params, search);
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FetchData
|
|
||||||
url="ItemCategories"
|
|
||||||
limit="30"
|
|
||||||
auto-load
|
|
||||||
@on-fetch="(data) => (itemCategories = data)"
|
|
||||||
/>
|
|
||||||
<FetchData
|
<FetchData
|
||||||
url="TicketRequests/getItemTypeWorker"
|
url="TicketRequests/getItemTypeWorker"
|
||||||
limit="30"
|
limit="30"
|
||||||
|
@ -150,102 +35,12 @@ const removeTag = (index, params, search) => {
|
||||||
:filter="{ fields: ['id', 'name', 'nickname'], order: 'name ASC', limit: 30 }"
|
:filter="{ fields: ['id', 'name', 'nickname'], order: 'name ASC', limit: 30 }"
|
||||||
@on-fetch="(data) => (suppliersOptions = data)"
|
@on-fetch="(data) => (suppliersOptions = data)"
|
||||||
/>
|
/>
|
||||||
<FetchData
|
<ItemsFilterPanel :data-key="dataKey" :custom-tags="['tags']">
|
||||||
url="Tags"
|
|
||||||
:filter="{ fields: ['id', 'name', 'isFree'] }"
|
|
||||||
auto-load
|
|
||||||
limit="30"
|
|
||||||
@on-fetch="(data) => (tagOptions = data)"
|
|
||||||
/>
|
|
||||||
<VnFilterPanel
|
|
||||||
:data-key="props.dataKey"
|
|
||||||
:expr-builder="exprBuilder"
|
|
||||||
:custom-tags="['tags']"
|
|
||||||
@init="onFilterInit"
|
|
||||||
@remove="clearFilter"
|
|
||||||
>
|
|
||||||
<template #tags="{ tag, formatFn }">
|
|
||||||
<strong v-if="tag.label === 'categoryFk'">
|
|
||||||
{{ t(selectedCategory?.name || '') }}
|
|
||||||
</strong>
|
|
||||||
<strong v-else-if="tag.label === 'typeFk'">
|
|
||||||
{{ t(selectedType?.name || '') }}
|
|
||||||
</strong>
|
|
||||||
<div v-else class="q-gutter-x-xs">
|
|
||||||
<strong>{{ t(`params.${tag.label}`) }}: </strong>
|
|
||||||
<span>{{ formatFn(tag.value) }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #customTags="{ tags: customTags, params }">
|
|
||||||
<template v-for="tag in customTags" :key="tag.label">
|
|
||||||
<VnFilterPanelChip
|
|
||||||
v-for="chip in tag.value"
|
|
||||||
:key="chip"
|
|
||||||
removable
|
|
||||||
@remove="removeTagChip(chip, params, searchFn)"
|
|
||||||
>
|
|
||||||
<div class="q-gutter-x-xs">
|
|
||||||
<strong>{{ chip.tagName }}: </strong>
|
|
||||||
<span>"{{ chip.value }}"</span>
|
|
||||||
</div>
|
|
||||||
</VnFilterPanelChip>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
<template #body="{ params, searchFn }">
|
<template #body="{ params, searchFn }">
|
||||||
<QItem class="category-filter q-mt-md">
|
|
||||||
<QBtn
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
round
|
|
||||||
v-for="category in categoryList"
|
|
||||||
:key="category.name"
|
|
||||||
:class="['category', getCategoryClass(category, params)]"
|
|
||||||
:icon="category.icon"
|
|
||||||
@click="selectCategory(params, category.id, searchFn)"
|
|
||||||
>
|
|
||||||
<QTooltip>
|
|
||||||
{{ t(category.name) }}
|
|
||||||
</QTooltip>
|
|
||||||
</QBtn>
|
|
||||||
</QItem>
|
|
||||||
<QItem class="q-my-md">
|
<QItem class="q-my-md">
|
||||||
<QItemSection>
|
<QItemSection>
|
||||||
<VnSelectFilter
|
<VnSelectFilter
|
||||||
:label="t('params.typeFk')"
|
:label="t('components.itemsFilterPanel.salesPersonFk')"
|
||||||
v-model="params.typeFk"
|
|
||||||
:options="itemTypesOptions"
|
|
||||||
option-value="id"
|
|
||||||
option-label="name"
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
use-input
|
|
||||||
:disable="!selectedCategoryFk"
|
|
||||||
@update:model-value="
|
|
||||||
(value) => {
|
|
||||||
selectedTypeFk = value;
|
|
||||||
searchFn();
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<template #option="{ itemProps, opt }">
|
|
||||||
<QItem v-bind="itemProps">
|
|
||||||
<QItemSection>
|
|
||||||
<QItemLabel>{{ opt.name }}</QItemLabel>
|
|
||||||
<QItemLabel caption>
|
|
||||||
{{ opt.categoryName }}
|
|
||||||
</QItemLabel>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
</template>
|
|
||||||
</VnSelectFilter>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QSeparator />
|
|
||||||
<QItem class="q-my-md">
|
|
||||||
<QItemSection>
|
|
||||||
<VnSelectFilter
|
|
||||||
:label="t('params.salesPersonFk')"
|
|
||||||
v-model="params.salesPersonFk"
|
v-model="params.salesPersonFk"
|
||||||
:options="itemTypeWorkersOptions"
|
:options="itemTypeWorkersOptions"
|
||||||
option-value="id"
|
option-value="id"
|
||||||
|
@ -261,7 +56,7 @@ const removeTag = (index, params, search) => {
|
||||||
<QItem class="q-my-md">
|
<QItem class="q-my-md">
|
||||||
<QItemSection>
|
<QItemSection>
|
||||||
<VnSelectFilter
|
<VnSelectFilter
|
||||||
:label="t('params.supplier')"
|
:label="t('components.itemsFilterPanel.supplierFk')"
|
||||||
v-model="params.supplierFk"
|
v-model="params.supplierFk"
|
||||||
:options="suppliersOptions"
|
:options="suppliersOptions"
|
||||||
option-value="id"
|
option-value="id"
|
||||||
|
@ -288,7 +83,7 @@ const removeTag = (index, params, search) => {
|
||||||
<QItem class="q-my-md">
|
<QItem class="q-my-md">
|
||||||
<QItemSection>
|
<QItemSection>
|
||||||
<VnInputDate
|
<VnInputDate
|
||||||
:label="t('params.from')"
|
:label="t('components.itemsFilterPanel.from')"
|
||||||
v-model="params.from"
|
v-model="params.from"
|
||||||
is-outlined
|
is-outlined
|
||||||
@update:model-value="searchFn()"
|
@update:model-value="searchFn()"
|
||||||
|
@ -298,7 +93,7 @@ const removeTag = (index, params, search) => {
|
||||||
<QItem class="q-my-md">
|
<QItem class="q-my-md">
|
||||||
<QItemSection>
|
<QItemSection>
|
||||||
<VnInputDate
|
<VnInputDate
|
||||||
:label="t('params.to')"
|
:label="t('components.itemsFilterPanel.to')"
|
||||||
v-model="params.to"
|
v-model="params.to"
|
||||||
is-outlined
|
is-outlined
|
||||||
@update:model-value="searchFn()"
|
@update:model-value="searchFn()"
|
||||||
|
@ -308,7 +103,7 @@ const removeTag = (index, params, search) => {
|
||||||
<QItem>
|
<QItem>
|
||||||
<QItemSection>
|
<QItemSection>
|
||||||
<QCheckbox
|
<QCheckbox
|
||||||
:label="t('params.active')"
|
:label="t('components.itemsFilterPanel.active')"
|
||||||
v-model="params.active"
|
v-model="params.active"
|
||||||
toggle-indeterminate
|
toggle-indeterminate
|
||||||
@update:model-value="searchFn()"
|
@update:model-value="searchFn()"
|
||||||
|
@ -316,7 +111,7 @@ const removeTag = (index, params, search) => {
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
<QItemSection>
|
<QItemSection>
|
||||||
<QCheckbox
|
<QCheckbox
|
||||||
:label="t('params.visible')"
|
:label="t('components.itemsFilterPanel.visible')"
|
||||||
v-model="params.visible"
|
v-model="params.visible"
|
||||||
toggle-indeterminate
|
toggle-indeterminate
|
||||||
@update:model-value="searchFn()"
|
@update:model-value="searchFn()"
|
||||||
|
@ -326,149 +121,13 @@ const removeTag = (index, params, search) => {
|
||||||
<QItem>
|
<QItem>
|
||||||
<QItemSection>
|
<QItemSection>
|
||||||
<QCheckbox
|
<QCheckbox
|
||||||
:label="t('params.floramondo')"
|
:label="t('components.itemsFilterPanel.floramondo')"
|
||||||
v-model="params.floramondo"
|
v-model="params.floramondo"
|
||||||
toggle-indeterminate
|
toggle-indeterminate
|
||||||
@update:model-value="searchFn()"
|
@update:model-value="searchFn()"
|
||||||
/>
|
/>
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
</QItem>
|
</QItem>
|
||||||
|
|
||||||
<QItem
|
|
||||||
v-for="(value, index) in tagValues"
|
|
||||||
:key="value"
|
|
||||||
class="q-mt-md filter-value"
|
|
||||||
>
|
|
||||||
<QItemSection class="col">
|
|
||||||
<VnSelectFilter
|
|
||||||
:label="t('params.tag')"
|
|
||||||
v-model="value.selectedTag"
|
|
||||||
:options="tagOptions"
|
|
||||||
option-label="name"
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
:emit-value="false"
|
|
||||||
use-input
|
|
||||||
:is-clearable="false"
|
|
||||||
@update:model-value="getSelectedTagValues(value)"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
<QItemSection class="col">
|
|
||||||
<VnSelectFilter
|
|
||||||
v-if="!value?.selectedTag?.isFree && value.valueOptions"
|
|
||||||
:label="t('params.value')"
|
|
||||||
v-model="value.value"
|
|
||||||
:options="value.valueOptions || []"
|
|
||||||
option-value="value"
|
|
||||||
option-label="value"
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
emit-value
|
|
||||||
use-input
|
|
||||||
:disable="!value"
|
|
||||||
:is-clearable="false"
|
|
||||||
class="filter-input"
|
|
||||||
@update:model-value="applyTags(params, searchFn)"
|
|
||||||
/>
|
|
||||||
<VnInput
|
|
||||||
v-else
|
|
||||||
v-model="value.value"
|
|
||||||
:label="t('params.value')"
|
|
||||||
:disable="!value"
|
|
||||||
is-outlined
|
|
||||||
class="filter-input"
|
|
||||||
:is-clearable="false"
|
|
||||||
@keyup.enter="applyTags(params, searchFn)"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
<QIcon
|
|
||||||
name="delete"
|
|
||||||
class="filter-icon"
|
|
||||||
@click="removeTag(index, params, searchFn)"
|
|
||||||
/>
|
|
||||||
</QItem>
|
|
||||||
<QItem class="q-mt-lg">
|
|
||||||
<QIcon
|
|
||||||
name="add_circle"
|
|
||||||
class="filter-icon"
|
|
||||||
@click="tagValues.push({})"
|
|
||||||
/>
|
|
||||||
</QItem>
|
|
||||||
</template>
|
</template>
|
||||||
</VnFilterPanel>
|
</ItemsFilterPanel>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.category-filter {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 12px;
|
|
||||||
|
|
||||||
.category {
|
|
||||||
padding: 8px;
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
font-size: 1.4rem;
|
|
||||||
background-color: var(--vn-accent-color);
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background-color: $primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-icon {
|
|
||||||
font-size: 24px;
|
|
||||||
color: $primary;
|
|
||||||
padding: 0 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-input {
|
|
||||||
flex-shrink: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-value {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<i18n>
|
|
||||||
en:
|
|
||||||
params:
|
|
||||||
supplier: Supplier
|
|
||||||
from: From
|
|
||||||
to: To
|
|
||||||
active: Is active
|
|
||||||
visible: Is visible
|
|
||||||
floramondo: Is floramondo
|
|
||||||
salesPersonFk: Buyer
|
|
||||||
categoryFk: Category
|
|
||||||
typeFk: Type
|
|
||||||
tag: Tag
|
|
||||||
value: Value
|
|
||||||
es:
|
|
||||||
params:
|
|
||||||
supplier: Proveedor
|
|
||||||
from: Desde
|
|
||||||
to: Hasta
|
|
||||||
active: Activo
|
|
||||||
visible: Visible
|
|
||||||
floramondo: Floramondo
|
|
||||||
salesPersonFk: Comprador
|
|
||||||
categoryFk: Categoría
|
|
||||||
typeFk: Tipo
|
|
||||||
tag: Etiqueta
|
|
||||||
value: Valor
|
|
||||||
Plant: Planta
|
|
||||||
Flower: Flor
|
|
||||||
Handmade: Confección
|
|
||||||
Green: Verde
|
|
||||||
Accessories: Complemento
|
|
||||||
Fruit: Fruta
|
|
||||||
</i18n>
|
|
||||||
|
|
|
@ -0,0 +1,605 @@
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, ref, reactive, computed, onUnmounted, watch } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import FetchData from 'components/FetchData.vue';
|
||||||
|
import FetchedTags from 'components/ui/FetchedTags.vue';
|
||||||
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
|
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||||
|
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||||
|
import EditTableCellValueForm from 'src/components/EditTableCellValueForm.vue';
|
||||||
|
import ItemFixedPriceFilter from './ItemFixedPriceFilter.vue';
|
||||||
|
import ItemDescriptorProxy from './Card/ItemDescriptorProxy.vue';
|
||||||
|
|
||||||
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
|
import { dashIfEmpty } from 'src/filters';
|
||||||
|
import { useVnConfirm } from 'composables/useVnConfirm';
|
||||||
|
import { useState } from 'src/composables/useState';
|
||||||
|
import { toCurrency } from 'filters/index';
|
||||||
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useArrayData } from 'composables/useArrayData';
|
||||||
|
import { isLower, isBigger } from 'src/filters/date.js';
|
||||||
|
|
||||||
|
const stateStore = useStateStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { openConfirmationModal } = useVnConfirm();
|
||||||
|
const state = useState();
|
||||||
|
const { notify } = useNotify();
|
||||||
|
|
||||||
|
const editTableCellDialogRef = ref(null);
|
||||||
|
const user = state.getUser();
|
||||||
|
const fixedPrices = ref([]);
|
||||||
|
const fixedPricesOriginalData = ref([]);
|
||||||
|
const warehousesOptions = ref([]);
|
||||||
|
const itemsWithNameOptions = ref([]);
|
||||||
|
const rowsSelected = ref([]);
|
||||||
|
|
||||||
|
const exprBuilder = (param, value) => {
|
||||||
|
switch (param) {
|
||||||
|
case 'name':
|
||||||
|
return { 'i.name': { like: `%${value}%` } };
|
||||||
|
case 'itemFk':
|
||||||
|
case 'warehouseFk':
|
||||||
|
case 'rate2':
|
||||||
|
case 'rate3':
|
||||||
|
param = `fp.${param}`;
|
||||||
|
return { [param]: value };
|
||||||
|
case 'minPrice':
|
||||||
|
param = `i.${param}`;
|
||||||
|
return { [param]: value };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = reactive({});
|
||||||
|
const arrayData = useArrayData('ItemFixedPrices', {
|
||||||
|
url: 'FixedPrices/filter',
|
||||||
|
userParams: params,
|
||||||
|
order: ['itemFk'],
|
||||||
|
exprBuilder: exprBuilder,
|
||||||
|
});
|
||||||
|
const store = arrayData.store;
|
||||||
|
|
||||||
|
const fetchFixedPrices = async () => {
|
||||||
|
await arrayData.fetch({ append: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFixedPricesFetched = (data) => {
|
||||||
|
fixedPrices.value = data;
|
||||||
|
// el objetivo de guardar una copia de las rows es evitar guardar cambios si la data no cambió al disparar los eventos
|
||||||
|
fixedPricesOriginalData.value = JSON.parse(JSON.stringify(data));
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => store.data,
|
||||||
|
(data) => onFixedPricesFetched(data)
|
||||||
|
);
|
||||||
|
|
||||||
|
const applyColumnFilter = async (col) => {
|
||||||
|
try {
|
||||||
|
const paramKey = col.columnFilter?.filterParamKey || col.field;
|
||||||
|
params[paramKey] = col.columnFilter.filterValue;
|
||||||
|
await arrayData.addFilter({ params });
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error applying column filter', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getColumnInputEvents = (col) => {
|
||||||
|
return col.columnFilter.type === 'select'
|
||||||
|
? { 'update:modelValue': () => applyColumnFilter(col) }
|
||||||
|
: {
|
||||||
|
'keyup.enter': () => applyColumnFilter(col),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultColumnFilter = {
|
||||||
|
component: VnInput,
|
||||||
|
type: 'text',
|
||||||
|
filterValue: null,
|
||||||
|
event: getColumnInputEvents,
|
||||||
|
attrs: {
|
||||||
|
dense: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultColumnAttrs = {
|
||||||
|
align: 'left',
|
||||||
|
sortable: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = computed(() => [
|
||||||
|
{
|
||||||
|
label: t('item.fixedPrice.itemId'),
|
||||||
|
name: 'itemId',
|
||||||
|
field: 'itemFk',
|
||||||
|
...defaultColumnAttrs,
|
||||||
|
columnFilter: {
|
||||||
|
...defaultColumnFilter,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('globals.description'),
|
||||||
|
field: 'name',
|
||||||
|
name: 'description',
|
||||||
|
...defaultColumnAttrs,
|
||||||
|
columnFilter: {
|
||||||
|
...defaultColumnFilter,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('item.fixedPrice.groupingPrice'),
|
||||||
|
field: 'rate2',
|
||||||
|
name: 'groupingPrice',
|
||||||
|
...defaultColumnAttrs,
|
||||||
|
columnFilter: {
|
||||||
|
...defaultColumnFilter,
|
||||||
|
},
|
||||||
|
format: (val) => toCurrency(val),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('item.fixedPrice.packingPrice'),
|
||||||
|
field: 'rate3',
|
||||||
|
name: 'packingPrice',
|
||||||
|
...defaultColumnAttrs,
|
||||||
|
columnFilter: {
|
||||||
|
...defaultColumnFilter,
|
||||||
|
},
|
||||||
|
format: (val) => dashIfEmpty(val),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label: t('item.fixedPrice.minPrice'),
|
||||||
|
field: 'minPrice',
|
||||||
|
name: 'minPrice',
|
||||||
|
...defaultColumnAttrs,
|
||||||
|
columnFilter: {
|
||||||
|
...defaultColumnFilter,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('item.fixedPrice.started'),
|
||||||
|
field: 'started',
|
||||||
|
name: 'started',
|
||||||
|
...defaultColumnAttrs,
|
||||||
|
columnFilter: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('item.fixedPrice.ended'),
|
||||||
|
field: 'ended',
|
||||||
|
name: 'ended',
|
||||||
|
...defaultColumnAttrs,
|
||||||
|
columnFilter: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label: t('item.fixedPrice.warehouse'),
|
||||||
|
field: 'warehouseFk',
|
||||||
|
name: 'warehouse',
|
||||||
|
...defaultColumnAttrs,
|
||||||
|
columnFilter: {
|
||||||
|
component: VnSelectFilter,
|
||||||
|
type: 'select',
|
||||||
|
filterValue: null,
|
||||||
|
event: getColumnInputEvents,
|
||||||
|
attrs: {
|
||||||
|
options: warehousesOptions.value,
|
||||||
|
'option-value': 'id',
|
||||||
|
'option-label': 'name',
|
||||||
|
dense: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ name: 'deleteAction', align: 'center' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const editTableFieldsOptions = [
|
||||||
|
{
|
||||||
|
field: 'rate2',
|
||||||
|
label: t('item.fixedPrice.groupingPrice'),
|
||||||
|
component: 'input',
|
||||||
|
attrs: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'rate3',
|
||||||
|
label: t('item.fixedPrice.packingPrice'),
|
||||||
|
component: 'input',
|
||||||
|
attrs: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'minPrice',
|
||||||
|
label: t('item.fixedPrice.minPrice'),
|
||||||
|
component: 'input',
|
||||||
|
attrs: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'hasMinPrice',
|
||||||
|
label: t('item.fixedPrice.hasMinPrice'),
|
||||||
|
component: 'checkbox',
|
||||||
|
attrs: {
|
||||||
|
'false-value': 0,
|
||||||
|
'true-value': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'started',
|
||||||
|
label: t('item.fixedPrice.started'),
|
||||||
|
component: 'date',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'ended',
|
||||||
|
label: t('item.fixedPrice.ended'),
|
||||||
|
component: 'date',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'warehouseFk',
|
||||||
|
label: t('item.fixedPrice.warehouse'),
|
||||||
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
|
options: [],
|
||||||
|
'option-label': 'name',
|
||||||
|
'option-value': 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getRowUpdateInputEvents = (props, resetMinPrice, inputType = 'text') => {
|
||||||
|
return inputType === 'text'
|
||||||
|
? {
|
||||||
|
'keyup.enter': () => upsertPrice(props, resetMinPrice),
|
||||||
|
blur: () => upsertPrice(props, resetMinPrice),
|
||||||
|
}
|
||||||
|
: { 'update:modelValue': () => upsertPrice(props, resetMinPrice) };
|
||||||
|
};
|
||||||
|
|
||||||
|
const validations = (row, rowIndex, col) => {
|
||||||
|
const isNew = !row.id;
|
||||||
|
// Si la row no tiene id significa que fue agregada con addRow y no se ha guardado en la base de datos
|
||||||
|
// Si isNew es falso no se checkea si el valor es igual a la original
|
||||||
|
if (!isNew)
|
||||||
|
if (fixedPricesOriginalData.value[rowIndex][col.field] == row[col.field])
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const requiredFields = ['itemFk', 'started', 'ended', 'rate2', 'rate3'];
|
||||||
|
return requiredFields.every(
|
||||||
|
(field) => row[field] !== null && row[field] !== undefined
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const upsertPrice = async ({ row, col, rowIndex }, resetMinPrice = false) => {
|
||||||
|
if (!validations(row, rowIndex, col)) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (resetMinPrice) row.hasMinPrice = 0;
|
||||||
|
|
||||||
|
const { data } = await axios.patch('FixedPrices/upsertFixedPrice', row);
|
||||||
|
row = data;
|
||||||
|
fixedPricesOriginalData.value[rowIndex][col.field] = row[col.field];
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error editing price', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addRow = () => {
|
||||||
|
if (!fixedPrices.value || fixedPrices.value.length === 0) {
|
||||||
|
fixedPrices.value = [];
|
||||||
|
|
||||||
|
const today = Date.vnNew();
|
||||||
|
const millisecsInDay = 86400000;
|
||||||
|
const daysInWeek = 7;
|
||||||
|
const nextWeek = new Date(today.getTime() + daysInWeek * millisecsInDay);
|
||||||
|
|
||||||
|
const newPrice = {
|
||||||
|
started: today,
|
||||||
|
ended: nextWeek,
|
||||||
|
hasMinPrice: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
fixedPricesOriginalData.value.push({ ...newPrice });
|
||||||
|
fixedPrices.value.push({ ...newPrice });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastItemCopy = JSON.parse(
|
||||||
|
JSON.stringify(fixedPrices.value[fixedPrices.value.length - 1])
|
||||||
|
);
|
||||||
|
const { id, ...restOfItem } = lastItemCopy;
|
||||||
|
fixedPricesOriginalData.value.push(restOfItem);
|
||||||
|
fixedPrices.value.push(restOfItem);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openEditTableCellDialog = () => {
|
||||||
|
editTableCellDialogRef.value.show();
|
||||||
|
};
|
||||||
|
|
||||||
jsegarra marked this conversation as resolved
|
|||||||
|
const onEditCellDataSaved = async () => {
|
||||||
|
rowsSelected.value = [];
|
||||||
|
await fetchFixedPrices();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onWarehousesFetched = (data) => {
|
||||||
|
warehousesOptions.value = data;
|
||||||
|
// Actualiza las 'options' del elemento con field 'warehouseFk' en 'editTableFieldsOptions'.
|
||||||
|
const warehouseField = editTableFieldsOptions.find(
|
||||||
|
(field) => field.field === 'warehouseFk'
|
||||||
|
);
|
||||||
|
warehouseField.attrs.options = data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const removePrice = async (id, rowIndex) => {
|
||||||
|
try {
|
||||||
|
await axios.delete(`FixedPrices/${id}`);
|
||||||
|
fixedPrices.value.splice(rowIndex, 1);
|
||||||
|
fixedPricesOriginalData.value.splice(rowIndex, 1);
|
||||||
|
notify(t('globals.dataSaved'), 'positive');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error removing price', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateMinPrice = async (value, props) => {
|
||||||
|
// El checkbox hasMinPrice se encuentra en la misma columna que el input hasMinPrice
|
||||||
|
// Por lo tanto le mandamos otro objeto con las mismas propiedades pero con el campo 'field' cambiado
|
||||||
|
props.row.hasMinPrice = value;
|
||||||
|
await upsertPrice({
|
||||||
|
row: props.row,
|
||||||
|
col: { field: 'hasMinPrice' },
|
||||||
|
rowIndex: props.rowIndex,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
stateStore.rightDrawer = true;
|
||||||
|
params.warehouseFk = user.value.warehouseFk;
|
||||||
|
await fetchFixedPrices();
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<FetchData
|
||||||
|
url="Warehouses"
|
||||||
|
:filter="{ order: ['name'] }"
|
||||||
|
auto-load
|
||||||
|
@on-fetch="(data) => onWarehousesFetched(data)"
|
||||||
|
/>
|
||||||
|
<FetchData
|
||||||
|
url="Items/withName"
|
||||||
|
:filter="{ fields: ['id', 'name'], order: 'id DESC' }"
|
||||||
|
auto-load
|
||||||
|
@on-fetch="(data) => (itemsWithNameOptions = data)"
|
||||||
|
/>
|
||||||
|
<template v-if="stateStore.isHeaderMounted()">
|
||||||
|
<Teleport to="#actions-append">
|
||||||
|
<div class="row q-gutter-x-sm">
|
||||||
|
<QBtn
|
||||||
|
flat
|
||||||
|
@click="stateStore.toggleRightDrawer()"
|
||||||
|
round
|
||||||
|
dense
|
||||||
|
icon="menu"
|
||||||
|
>
|
||||||
|
<QTooltip bottom anchor="bottom right">
|
||||||
|
{{ t('globals.collapseMenu') }}
|
||||||
|
</QTooltip>
|
||||||
|
</QBtn>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
|
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
|
||||||
|
<QScrollArea class="fit text-grey-8">
|
||||||
|
<ItemFixedPriceFilter
|
||||||
|
data-key="ItemFixedPrices"
|
||||||
|
:warehouses-options="warehousesOptions"
|
||||||
|
/>
|
||||||
|
</QScrollArea>
|
||||||
|
</QDrawer>
|
||||||
|
<QPage class="column items-center q-pa-md">
|
||||||
|
<QTable
|
||||||
|
:rows="fixedPrices"
|
||||||
|
:columns="columns"
|
||||||
|
row-key="id"
|
||||||
|
selection="multiple"
|
||||||
|
v-model:selected="rowsSelected"
|
||||||
|
:pagination="{ rowsPerPage: 0 }"
|
||||||
|
class="full-width q-mt-md"
|
||||||
|
:no-data-label="t('globals.noResults')"
|
||||||
|
>
|
||||||
|
<template #top-row="{ cols }">
|
||||||
|
<QTr>
|
||||||
|
<QTd />
|
||||||
|
<QTd
|
||||||
|
v-for="(col, index) in cols"
|
||||||
|
:key="index"
|
||||||
|
style="max-width: 100px"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="col.columnFilter.component"
|
||||||
|
v-if="col.columnFilter"
|
||||||
|
v-model="col.columnFilter.filterValue"
|
||||||
|
v-bind="col.columnFilter.attrs"
|
||||||
|
v-on="col.columnFilter.event(col)"
|
||||||
|
dense
|
||||||
|
/>
|
||||||
|
</QTd>
|
||||||
|
</QTr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body-cell-itemId="props">
|
||||||
|
<QTd>
|
||||||
|
<VnSelectFilter
|
||||||
|
:options="itemsWithNameOptions"
|
||||||
|
hide-selected
|
||||||
|
option-label="id"
|
||||||
|
option-value="id"
|
||||||
|
v-model="props.row.itemFk"
|
||||||
|
v-on="getRowUpdateInputEvents(props, true, 'select')"
|
||||||
|
>
|
||||||
|
<template #option="scope">
|
||||||
|
<QItem v-bind="scope.itemProps">
|
||||||
|
<QItemSection>
|
||||||
|
<QItemLabel> #{{ scope.opt?.id }} </QItemLabel>
|
||||||
|
<QItemLabel caption>{{ scope.opt?.name }}</QItemLabel>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
</template>
|
||||||
|
</VnSelectFilter>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-description="{ row }">
|
||||||
|
<QTd class="col">
|
||||||
|
<span class="link">
|
||||||
|
{{ row.name }}
|
||||||
|
</span>
|
||||||
|
<ItemDescriptorProxy :id="row.itemFk" />
|
||||||
|
<fetched-tags :item="row" :max-length="6" />
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-groupingPrice="props">
|
||||||
|
<QTd class="col">
|
||||||
|
<VnInput
|
||||||
|
v-model.number="props.row.rate2"
|
||||||
|
v-on="getRowUpdateInputEvents(props)"
|
||||||
|
>
|
||||||
|
<template #append>€</template>
|
||||||
|
</VnInput>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-packingPrice="props">
|
||||||
|
<QTd class="col">
|
||||||
|
<VnInput
|
||||||
|
v-model.number="props.row.rate3"
|
||||||
|
v-on="getRowUpdateInputEvents(props)"
|
||||||
|
>
|
||||||
|
<template #append>€</template>
|
||||||
|
</VnInput>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-minPrice="props">
|
||||||
|
<QTd class="col">
|
||||||
|
<div class="row">
|
||||||
|
<QCheckbox
|
||||||
|
class="col"
|
||||||
|
:model-value="props.row.hasMinPrice"
|
||||||
|
@update:model-value="updateMinPrice($event, props)"
|
||||||
|
:false-value="0"
|
||||||
|
:true-value="1"
|
||||||
|
:toggle-indeterminate="false"
|
||||||
|
/>
|
||||||
|
<VnInput
|
||||||
|
class="col"
|
||||||
|
:disable="!props.row.hasMinPrice"
|
||||||
|
v-model.number="props.row.minPrice"
|
||||||
|
v-on="getRowUpdateInputEvents(props)"
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-started="props">
|
||||||
|
<QTd class="col" style="min-width: 160px">
|
||||||
|
<VnInputDate
|
||||||
|
v-model="props.row.started"
|
||||||
|
v-on="getRowUpdateInputEvents(props, false, 'date')"
|
||||||
|
v-bind="
|
||||||
|
isBigger(props.row.started)
|
||||||
|
? { 'bg-color': 'warning', 'is-outlined': true }
|
||||||
|
: {}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-ended="props">
|
||||||
|
<QTd class="col" style="min-width: 150px">
|
||||||
|
<VnInputDate
|
||||||
|
v-model="props.row.ended"
|
||||||
|
v-on="getRowUpdateInputEvents(props, false, 'date')"
|
||||||
|
v-bind="
|
||||||
|
isLower(props.row.ended)
|
||||||
|
? { 'bg-color': 'warning', 'is-outlined': true }
|
||||||
|
: {}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-warehouse="props">
|
||||||
|
<QTd class="col">
|
||||||
|
<VnSelectFilter
|
||||||
|
:options="warehousesOptions"
|
||||||
|
hide-selected
|
||||||
|
option-label="name"
|
||||||
|
option-value="id"
|
||||||
|
v-model="props.row.warehouseFk"
|
||||||
|
v-on="getRowUpdateInputEvents(props, false, 'select')"
|
||||||
|
/>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-deleteAction="{ row, rowIndex }">
|
||||||
|
<QTd class="col">
|
||||||
|
<QIcon
|
||||||
|
name="delete"
|
||||||
|
size="sm"
|
||||||
|
class="cursor-pointer fill-icon-on-hover"
|
||||||
|
color="primary"
|
||||||
|
@click.stop="
|
||||||
|
openConfirmationModal(
|
||||||
|
t('This row will be removed'),
|
||||||
|
t('Do you want to clone this item?'),
|
||||||
|
() => removePrice(row.id, rowIndex)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<QTooltip class="text-no-wrap">
|
||||||
|
{{ t('Delete') }}
|
||||||
|
</QTooltip>
|
||||||
|
</QIcon>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #bottom-row>
|
||||||
|
<QTd align="center">
|
||||||
|
<QIcon
|
||||||
|
@click.stop="addRow()"
|
||||||
|
class="fill-icon-on-hover"
|
||||||
|
color="primary"
|
||||||
|
name="add_circle"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<QTooltip>
|
||||||
|
{{ t('Add fixed price') }}
|
||||||
|
</QTooltip>
|
||||||
|
</QIcon>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
</QTable>
|
||||||
|
<QPageSticky v-if="rowsSelected.length > 0" :offset="[20, 20]">
|
||||||
|
<QBtn @click="openEditTableCellDialog()" color="primary" fab icon="edit" />
|
||||||
|
<QTooltip>
|
||||||
|
{{ t('Edit fixed price(s)') }}
|
||||||
|
</QTooltip>
|
||||||
|
</QPageSticky>
|
||||||
|
<QDialog ref="editTableCellDialogRef">
|
||||||
|
<EditTableCellValueForm
|
||||||
|
edit-url="FixedPrices/editFixedPrice"
|
||||||
|
:rows="rowsSelected"
|
||||||
|
:fields-options="editTableFieldsOptions"
|
||||||
|
@on-data-saved="onEditCellDataSaved()"
|
||||||
|
/>
|
||||||
|
</QDialog>
|
||||||
|
</QPage>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
Add fixed price: Añadir precio fijado
|
||||||
|
Edit fixed price(s): Editar precio(s) fijado(s)
|
||||||
|
This row will be removed: Esta linea se eliminará
|
||||||
|
Are you sure you want to continue?: ¿Seguro que quieres continuar?
|
||||||
|
Delete: Eliminar
|
||||||
|
</i18n>
|
|
@ -0,0 +1,108 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import FetchData from 'components/FetchData.vue';
|
||||||
|
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||||
|
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
|
||||||
|
import ItemsFilterPanel from 'src/components/ItemsFilterPanel.vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
dataKey: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
warehousesOptions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const itemTypeWorkersOptions = ref([]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<FetchData
|
||||||
|
url="TicketRequests/getItemTypeWorker"
|
||||||
|
limit="30"
|
||||||
|
auto-load
|
||||||
|
:filter="{ fields: ['id', 'nickname'], order: 'nickname ASC', limit: 30 }"
|
||||||
|
@on-fetch="(data) => (itemTypeWorkersOptions = data)"
|
||||||
|
/>
|
||||||
|
<ItemsFilterPanel :data-key="dataKey" :custom-tags="['tags']">
|
||||||
|
<template #body="{ params, searchFn }">
|
||||||
|
<QItem class="q-my-md">
|
||||||
|
<QItemSection>
|
||||||
|
<VnSelectFilter
|
||||||
|
:label="t('components.itemsFilterPanel.buyerFk')"
|
||||||
|
v-model="params.buyerFk"
|
||||||
|
:options="itemTypeWorkersOptions"
|
||||||
|
option-value="id"
|
||||||
|
option-label="nickname"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
rounded
|
||||||
|
use-input
|
||||||
|
@update:model-value="searchFn()"
|
||||||
|
/>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem class="q-my-md">
|
||||||
|
<QItemSection>
|
||||||
|
<VnSelectFilter
|
||||||
|
:label="t('components.itemsFilterPanel.warehouseFk')"
|
||||||
|
v-model="params.warehouseFk"
|
||||||
|
:options="warehousesOptions"
|
||||||
|
option-label="name"
|
||||||
|
option-value="id"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
rounded
|
||||||
|
use-input
|
||||||
|
@update:model-value="searchFn()"
|
||||||
|
/>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem class="q-my-md">
|
||||||
|
<QItemSection>
|
||||||
|
<VnInputDate
|
||||||
|
:label="t('components.itemsFilterPanel.started')"
|
||||||
|
v-model="params.started"
|
||||||
|
is-outlined
|
||||||
|
@update:model-value="searchFn()"
|
||||||
|
/>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem class="q-my-md">
|
||||||
|
<QItemSection>
|
||||||
|
<VnInputDate
|
||||||
|
:label="t('components.itemsFilterPanel.ended')"
|
||||||
|
v-model="params.ended"
|
||||||
|
is-outlined
|
||||||
|
@update:model-value="searchFn()"
|
||||||
|
/>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QItemSection>
|
||||||
|
<QCheckbox
|
||||||
|
:label="t('components.itemsFilterPanel.mine')"
|
||||||
|
v-model="params.mine"
|
||||||
|
toggle-indeterminate
|
||||||
|
@update:model-value="searchFn()"
|
||||||
|
/>
|
||||||
|
</QItemSection>
|
||||||
|
<QItemSection>
|
||||||
|
<QCheckbox
|
||||||
|
:label="t('components.itemsFilterPanel.hasMinPrice')"
|
||||||
|
v-model="params.hasMinPrice"
|
||||||
|
toggle-indeterminate
|
||||||
|
@update:model-value="searchFn()"
|
||||||
|
/>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
</template>
|
||||||
|
</ItemsFilterPanel>
|
||||||
|
</template>
|
|
@ -10,7 +10,7 @@ export default {
|
||||||
component: RouterView,
|
component: RouterView,
|
||||||
redirect: { name: 'ItemMain' },
|
redirect: { name: 'ItemMain' },
|
||||||
menus: {
|
menus: {
|
||||||
main: ['ItemList', 'WasteBreakdown'],
|
main: ['ItemList', 'WasteBreakdown', 'ItemFixedPrice'],
|
||||||
card: ['ItemBasicData'],
|
card: ['ItemBasicData'],
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
|
@ -29,11 +29,20 @@ export default {
|
||||||
},
|
},
|
||||||
component: () => import('src/pages/Item/ItemList.vue'),
|
component: () => import('src/pages/Item/ItemList.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'fixed-price',
|
||||||
|
name: 'ItemFixedPrice',
|
||||||
|
meta: {
|
||||||
|
title: 'fixedPrice',
|
||||||
|
icon: 'vn:fixedPrice',
|
||||||
|
},
|
||||||
|
component: () => import('src/pages/Item/ItemFixedPrice.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'create',
|
path: 'create',
|
||||||
name: 'ItemCreate',
|
name: 'ItemCreate',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'create',
|
title: 'itemCreate',
|
||||||
},
|
},
|
||||||
component: () => import('src/pages/Item/ItemCreate.vue'),
|
component: () => import('src/pages/Item/ItemCreate.vue'),
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue
Mismo objeto
Cambiado
Commit:
dce48b536d