#8363: Modified ItemFixedPrice #1561
|
@ -332,6 +332,7 @@ watch(formUrl, async () => {
|
||||||
:disable="!selected?.length"
|
:disable="!selected?.length"
|
||||||
:title="t('globals.remove')"
|
:title="t('globals.remove')"
|
||||||
v-if="$props.defaultRemove"
|
v-if="$props.defaultRemove"
|
||||||
|
data-cy="crudModelDefaultRemoveBtn"
|
||||||
/>
|
/>
|
||||||
<QBtn
|
<QBtn
|
||||||
:label="tMobile('globals.reset')"
|
:label="tMobile('globals.reset')"
|
||||||
|
|
|
@ -229,6 +229,7 @@ watch(
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
create: createForm,
|
create: createForm,
|
||||||
|
showForm,
|
||||||
reload,
|
reload,
|
||||||
redirect: redirectFn,
|
redirect: redirectFn,
|
||||||
selected,
|
selected,
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
import { createWrapper } from 'app/test/vitest/helper';
|
|
||||||
import { default as axios } from 'axios';
|
|
||||||
import EditForm from 'components/EditTableCellValueForm.vue';
|
|
||||||
import { vi, afterEach, beforeAll, describe, expect, it } from 'vitest';
|
|
||||||
|
|
||||||
const fieldA = 'fieldA';
|
|
||||||
const fieldB = 'fieldB';
|
|
||||||
|
|
||||||
describe('EditForm', () => {
|
|
||||||
let vm;
|
|
||||||
const mockRows = [
|
|
||||||
{ id: 1, itemFk: 101 },
|
|
||||||
{ id: 2, itemFk: 102 },
|
|
||||||
];
|
|
||||||
const mockFieldsOptions = [
|
|
||||||
{ label: 'Field A', field: fieldA, component: 'input', attrs: {} },
|
|
||||||
{ label: 'Field B', field: fieldB, component: 'date', attrs: {} },
|
|
||||||
];
|
|
||||||
const editUrl = '/api/edit';
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
vi.spyOn(axios, 'post').mockResolvedValue({ status: 200 });
|
|
||||||
vm = createWrapper(EditForm, {
|
|
||||||
props: {
|
|
||||||
rows: mockRows,
|
|
||||||
fieldsOptions: mockFieldsOptions,
|
|
||||||
editUrl,
|
|
||||||
},
|
|
||||||
}).vm;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
vi.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('onSubmit()', () => {
|
|
||||||
it('should call axios.post with the correct parameters in the payload', async () => {
|
|
||||||
const selectedField = { field: fieldA, component: 'input', attrs: {} };
|
|
||||||
const newValue = 'Test Value';
|
|
||||||
|
|
||||||
vm.selectedField = selectedField;
|
|
||||||
vm.newValue = newValue;
|
|
||||||
|
|
||||||
await vm.onSubmit();
|
|
||||||
|
|
||||||
const payload = axios.post.mock.calls[0][1];
|
|
||||||
|
|
||||||
expect(axios.post).toHaveBeenCalledWith(editUrl, expect.any(Object));
|
|
||||||
expect(payload.field).toEqual(fieldA);
|
|
||||||
expect(payload.newValue).toEqual(newValue);
|
|
||||||
|
|
||||||
expect(payload.lines).toEqual(expect.arrayContaining(mockRows));
|
|
||||||
|
|
||||||
expect(vm.isLoading).toEqual(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,4 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
colors: {
|
colors: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -6,9 +8,9 @@ const $props = defineProps({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const colorArray = JSON.parse($props.colors)?.value;
|
const colorArray = computed(() => JSON.parse($props.colors)?.value);
|
||||||
const maxHeight = 30;
|
const maxHeight = 30;
|
||||||
const colorHeight = maxHeight / colorArray?.length;
|
const colorHeight = maxHeight / colorArray.value?.length;
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div v-if="colors" class="color-div" :style="{ height: `${maxHeight}px` }">
|
<div v-if="colors" class="color-div" :style="{ height: `${maxHeight}px` }">
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export async function beforeSave(data, getChanges, modelOrigin) {
|
||||||
|
try {
|
||||||
|
const changes = data.updates;
|
||||||
|
if (!changes) return data;
|
||||||
|
const patchPromises = [];
|
||||||
|
|
||||||
|
for (const change of changes) {
|
||||||
|
let patchData = {};
|
||||||
|
|
||||||
|
if ('hasMinPrice' in change.data) {
|
||||||
|
patchData.hasMinPrice = change.data?.hasMinPrice;
|
||||||
|
delete change.data.hasMinPrice;
|
||||||
|
}
|
||||||
|
if ('minPrice' in change.data) {
|
||||||
|
patchData.minPrice = change.data?.minPrice;
|
||||||
|
delete change.data.minPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(patchData).length > 0) {
|
||||||
|
const promise = axios
|
||||||
|
.get(`${modelOrigin}/findOne`, {
|
||||||
|
params: {
|
||||||
|
filter: {
|
||||||
|
fields: ['itemFk'],
|
||||||
|
where: { id: change.where.id },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((row) => {
|
||||||
|
return axios.patch(`Items/${row.data.itemFk}`, patchData);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error processing change: ', change, error);
|
||||||
|
});
|
||||||
|
|
||||||
|
patchPromises.push(promise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(patchPromises);
|
||||||
|
|
||||||
|
data.updates = changes.filter((change) => Object.keys(change.data).length > 0);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in beforeSave:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
|
@ -877,6 +877,11 @@ components:
|
||||||
active: Is active
|
active: Is active
|
||||||
floramondo: Is floramondo
|
floramondo: Is floramondo
|
||||||
showBadDates: Show future items
|
showBadDates: Show future items
|
||||||
|
name: Nombre
|
||||||
|
rate2: Grouping price
|
||||||
|
rate3: Packing price
|
||||||
|
minPrice: Min. Price
|
||||||
|
itemFk: Item id
|
||||||
userPanel:
|
userPanel:
|
||||||
copyToken: Token copied to clipboard
|
copyToken: Token copied to clipboard
|
||||||
settings: Settings
|
settings: Settings
|
||||||
|
|
|
@ -961,6 +961,11 @@ components:
|
||||||
to: Hasta
|
to: Hasta
|
||||||
floramondo: Floramondo
|
floramondo: Floramondo
|
||||||
showBadDates: Ver items a futuro
|
showBadDates: Ver items a futuro
|
||||||
|
name: Nombre
|
||||||
|
rate2: Precio grouping
|
||||||
|
rate3: Precio packing
|
||||||
|
minPrice: Precio mínimo
|
||||||
|
itemFk: Id item
|
||||||
userPanel:
|
userPanel:
|
||||||
copyToken: Token copiado al portapapeles
|
copyToken: Token copiado al portapapeles
|
||||||
settings: Configuración
|
settings: Configuración
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { checkEntryLock } from 'src/composables/checkEntryLock';
|
||||||
import VnRow from 'src/components/ui/VnRow.vue';
|
import VnRow from 'src/components/ui/VnRow.vue';
|
||||||
import VnInput from 'src/components/common/VnInput.vue';
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||||
|
import { beforeSave } from 'src/composables/updateMinPriceBeforeSave';
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
id: {
|
id: {
|
||||||
|
@ -415,56 +416,6 @@ function getAmountStyle(row) {
|
||||||
return { color: 'var(--vn-label-color)' };
|
return { color: 'var(--vn-label-color)' };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function beforeSave(data, getChanges) {
|
|
||||||
try {
|
|
||||||
const changes = data.updates;
|
|
||||||
if (!changes) return data;
|
|
||||||
const patchPromises = [];
|
|
||||||
|
|
||||||
for (const change of changes) {
|
|
||||||
let patchData = {};
|
|
||||||
|
|
||||||
if ('hasMinPrice' in change.data) {
|
|
||||||
patchData.hasMinPrice = change.data?.hasMinPrice;
|
|
||||||
delete change.data.hasMinPrice;
|
|
||||||
}
|
|
||||||
if ('minPrice' in change.data) {
|
|
||||||
patchData.minPrice = change.data?.minPrice;
|
|
||||||
delete change.data.minPrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.keys(patchData).length > 0) {
|
|
||||||
const promise = axios
|
|
||||||
.get('Buys/findOne', {
|
|
||||||
params: {
|
|
||||||
filter: {
|
|
||||||
fields: ['itemFk'],
|
|
||||||
where: { id: change.where.id },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((buy) => {
|
|
||||||
return axios.patch(`Items/${buy.data.itemFk}`, patchData);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Error processing change: ', change, error);
|
|
||||||
});
|
|
||||||
|
|
||||||
patchPromises.push(promise);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(patchPromises);
|
|
||||||
|
|
||||||
data.updates = changes.filter((change) => Object.keys(change.data).length > 0);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error in beforeSave:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function invertQuantitySign(rows, sign) {
|
function invertQuantitySign(rows, sign) {
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
if (sign > 0) row.quantity = Math.abs(row.quantity);
|
if (sign > 0) row.quantity = Math.abs(row.quantity);
|
||||||
|
@ -697,7 +648,7 @@ onMounted(() => {
|
||||||
:right-search="false"
|
:right-search="false"
|
||||||
:row-click="false"
|
:row-click="false"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:beforeSaveFn="beforeSave"
|
:beforeSaveFn="(data, getChanges) => beforeSave(data, getChanges, 'Buys')"
|
||||||
class="buyList"
|
class="buyList"
|
||||||
:table-height="$props.tableHeight ?? '84vh'"
|
:table-height="$props.tableHeight ?? '84vh'"
|
||||||
auto-load
|
auto-load
|
||||||
|
|
|
@ -1,574 +1,386 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, ref, onUnmounted, nextTick, computed } from 'vue';
|
import { onMounted, ref, onUnmounted, computed, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
|
||||||
import FetchedTags from 'components/ui/FetchedTags.vue';
|
|
||||||
import VnInput from 'src/components/common/VnInput.vue';
|
|
||||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
|
||||||
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
|
||||||
import EditTableCellValueForm from 'src/components/EditTableCellValueForm.vue';
|
|
||||||
import ItemFixedPriceFilter from './ItemFixedPriceFilter.vue';
|
|
||||||
import { useQuasar } from 'quasar';
|
|
||||||
import ItemDescriptorProxy from './Card/ItemDescriptorProxy.vue';
|
|
||||||
import { tMobile } from 'src/composables/tMobile';
|
|
||||||
import VnConfirm from 'components/ui/VnConfirm.vue';
|
|
||||||
import FetchData from 'src/components/FetchData.vue';
|
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
import { toDate } from 'src/filters';
|
|
||||||
import { useVnConfirm } from 'composables/useVnConfirm';
|
|
||||||
import { useState } from 'src/composables/useState';
|
import { useState } from 'src/composables/useState';
|
||||||
import useNotify from 'src/composables/useNotify.js';
|
|
||||||
import axios from 'axios';
|
import { beforeSave } from 'src/composables/updateMinPriceBeforeSave';
|
||||||
import { isLower, isBigger } from 'src/filters/date.js';
|
|
||||||
|
import FetchedTags from 'components/ui/FetchedTags.vue';
|
||||||
|
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||||
|
import EditFixedPriceForm from 'src/pages/Item/components/EditFixedPriceForm.vue';
|
||||||
|
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||||
import VnTable from 'src/components/VnTable/VnTable.vue';
|
import VnTable from 'src/components/VnTable/VnTable.vue';
|
||||||
import { QCheckbox } from 'quasar';
|
import VnColor from 'src/components/common/VnColor.vue';
|
||||||
|
|
||||||
|
import { toDate } from 'src/filters';
|
||||||
|
import { isLower, isBigger } from 'src/filters/date.js';
|
||||||
|
import ItemFixedPriceFilter from './ItemFixedPriceFilter.vue';
|
||||||
|
import ItemDescriptorProxy from './Card/ItemDescriptorProxy.vue';
|
||||||
|
import { toCurrency } from 'src/filters';
|
||||||
|
|
||||||
const quasar = useQuasar();
|
|
||||||
const stateStore = useStateStore();
|
const stateStore = useStateStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openConfirmationModal } = useVnConfirm();
|
|
||||||
const state = useState();
|
|
||||||
const { notify } = useNotify();
|
|
||||||
const tableRef = ref();
|
const tableRef = ref();
|
||||||
const editTableCellDialogRef = ref(null);
|
const editFixedPriceForm = ref(null);
|
||||||
const user = state.getUser();
|
const selectedRows = ref([]);
|
||||||
const fixedPrices = ref([]);
|
const hasSelectedRows = computed(() => selectedRows.value.length > 0);
|
||||||
const warehousesOptions = ref([]);
|
const isToClone = ref(false);
|
||||||
const hasSelectedRows = computed(() => rowsSelected.value.length > 0);
|
const dateColor = 'var(--vn-label-text-color)';
|
||||||
const rowsSelected = ref([]);
|
const state = useState();
|
||||||
const itemFixedPriceFilterRef = ref();
|
const user = state.getUser().fn();
|
||||||
|
const warehouse = computed(() => user.warehouseFk);
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
stateStore.rightDrawer = true;
|
stateStore.rightDrawer = true;
|
||||||
});
|
});
|
||||||
onUnmounted(() => (stateStore.rightDrawer = false));
|
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||||
|
|
||||||
const defaultColumnAttrs = {
|
|
||||||
align: 'left',
|
|
||||||
sortable: true,
|
|
||||||
};
|
|
||||||
const columns = computed(() => [
|
const columns = computed(() => [
|
||||||
{
|
{
|
||||||
label: t('item.fixedPrice.itemFk'),
|
|
||||||
name: 'itemFk',
|
name: 'itemFk',
|
||||||
...defaultColumnAttrs,
|
label: t('item.fixedPrice.itemFk'),
|
||||||
isId: true,
|
labelAbbreviation: 'Id',
|
||||||
columnField: {
|
toolTip: t('item.fixedPrice.itemFk'),
|
||||||
component: 'input',
|
component: 'input',
|
||||||
type: 'number',
|
|
||||||
},
|
|
||||||
columnClass: 'shrink',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('globals.name'),
|
|
||||||
name: 'name',
|
|
||||||
...defaultColumnAttrs,
|
|
||||||
create: true,
|
|
||||||
columnFilter: {
|
columnFilter: {
|
||||||
component: 'select',
|
|
||||||
attrs: {
|
attrs: {
|
||||||
|
component: 'select',
|
||||||
url: 'Items',
|
url: 'Items',
|
||||||
fields: ['id', 'name', 'subName'],
|
fields: ['id', 'name', 'subName'],
|
||||||
optionLabel: 'name',
|
optionLabel: 'name',
|
||||||
optionValue: 'name',
|
optionValue: 'id',
|
||||||
uppercase: false,
|
uppercase: false,
|
||||||
},
|
},
|
||||||
|
inWhere: true,
|
||||||
},
|
},
|
||||||
|
width: '55px',
|
||||||
|
isEditable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
labelAbbreviation: '',
|
||||||
|
name: 'hex',
|
||||||
|
columnSearch: false,
|
||||||
|
isEditable: false,
|
||||||
|
width: '9px',
|
||||||
pablone marked this conversation as resolved
Outdated
|
|||||||
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
|
url: 'Inks',
|
||||||
|
fields: ['id', 'name'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('globals.name'),
|
||||||
|
name: 'name',
|
||||||
|
create: true,
|
||||||
|
component: 'input',
|
||||||
|
isEditable: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('item.fixedPrice.groupingPrice'),
|
label: t('item.fixedPrice.groupingPrice'),
|
||||||
|
labelAbbreviation: 'P. Group',
|
||||||
|
toolTip: t('item.fixedPrice.groupingPrice'),
|
||||||
name: 'rate2',
|
name: 'rate2',
|
||||||
...defaultColumnAttrs,
|
component: 'number',
|
||||||
component: 'input',
|
create: true,
|
||||||
type: 'number',
|
createOrder: 3,
|
||||||
|
createAttrs: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
width: '50px',
|
||||||
pablone
commented
Al final hablaste con Alex sobre las clases para los tamaños de las columnas Al final hablaste con Alex sobre las clases para los tamaños de las columnas
jon
commented
Se ha creado un redmine para ello: https://redmine.verdnatura.es/issues/8896 Se ha creado un redmine para ello: https://redmine.verdnatura.es/issues/8896
|
|||||||
|
format: (row) => toCurrency(row.rate2),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('item.fixedPrice.packingPrice'),
|
label: t('item.fixedPrice.packingPrice'),
|
||||||
|
labelAbbreviation: 'P. Pack',
|
||||||
|
toolTip: t('item.fixedPrice.packingPrice'),
|
||||||
name: 'rate3',
|
name: 'rate3',
|
||||||
...defaultColumnAttrs,
|
component: 'number',
|
||||||
component: 'input',
|
create: true,
|
||||||
type: 'number',
|
createOrder: 4,
|
||||||
|
createAttrs: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
width: '50px',
|
||||||
|
format: (row) => toCurrency(row.rate3),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'hasMinPrice',
|
||||||
|
label: t('item.fixedPrice.hasMinPrice'),
|
||||||
|
labelAbbreviation: t('item.fixedPrice.MP'),
|
||||||
|
toolTip: t('item.fixedPrice.hasMinPrice'),
|
||||||
|
label: t('item.fixedPrice.hasMinPrice'),
|
||||||
|
component: 'checkbox',
|
||||||
|
attrs: {
|
||||||
|
toggleIndeterminate: false,
|
||||||
|
},
|
||||||
|
width: '50px',
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
label: t('item.fixedPrice.minPrice'),
|
label: t('item.fixedPrice.minPrice'),
|
||||||
|
labelAbbreviation: 'Min.P',
|
||||||
|
toolTip: t('item.fixedPrice.minPrice'),
|
||||||
name: 'minPrice',
|
name: 'minPrice',
|
||||||
...defaultColumnAttrs,
|
component: 'number',
|
||||||
component: 'input',
|
width: '50px',
|
||||||
type: 'number',
|
style: (row) => {
|
||||||
|
if (!row?.hasMinPrice) return { color: 'var(--vn-label-color)' };
|
||||||
|
},
|
||||||
|
format: (row) => toCurrency(row.minPrice),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('item.fixedPrice.started'),
|
label: t('item.fixedPrice.started'),
|
||||||
field: 'started',
|
|
||||||
name: 'started',
|
name: 'started',
|
||||||
format: ({ started }) => toDate(started),
|
component: 'date',
|
||||||
...defaultColumnAttrs,
|
|
||||||
columnField: {
|
|
||||||
component: 'date',
|
|
||||||
class: 'shrink',
|
|
||||||
},
|
|
||||||
columnFilter: {
|
columnFilter: {
|
||||||
component: 'date',
|
component: 'date',
|
||||||
},
|
},
|
||||||
columnClass: 'expand',
|
createAttrs: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
create: true,
|
||||||
|
createOrder: 5,
|
||||||
|
width: '65px',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('item.fixedPrice.ended'),
|
label: t('item.fixedPrice.ended'),
|
||||||
name: 'ended',
|
name: 'ended',
|
||||||
...defaultColumnAttrs,
|
component: 'date',
|
||||||
columnField: {
|
|
||||||
component: 'date',
|
|
||||||
class: 'shrink',
|
|
||||||
},
|
|
||||||
columnFilter: {
|
columnFilter: {
|
||||||
component: 'date',
|
component: 'date',
|
||||||
},
|
},
|
||||||
columnClass: 'expand',
|
createAttrs: {
|
||||||
format: (row) => toDate(row.ended),
|
required: true,
|
||||||
|
},
|
||||||
|
create: true,
|
||||||
|
createOrder: 6,
|
||||||
|
width: '65px',
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
align: 'center',
|
||||||
label: t('globals.warehouse'),
|
label: t('globals.warehouse'),
|
||||||
name: 'warehouseFk',
|
name: 'warehouseFk',
|
||||||
...defaultColumnAttrs,
|
|
||||||
columnClass: 'shrink',
|
|
||||||
component: 'select',
|
component: 'select',
|
||||||
options: warehousesOptions,
|
attrs: {
|
||||||
columnFilter: {
|
url: 'Warehouses',
|
||||||
name: 'warehouseFk',
|
fields: ['id', 'name'],
|
||||||
inWhere: true,
|
optionLabel: 'name',
|
||||||
component: 'select',
|
optionValue: 'id',
|
||||||
attrs: {
|
|
||||||
options: warehousesOptions,
|
|
||||||
'option-label': 'name',
|
|
||||||
'option-value': 'id',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
create: true,
|
||||||
|
format: (row, dashIfEmpty) => dashIfEmpty(row.warehouseName),
|
||||||
|
width: '80px',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
align: 'right',
|
align: 'right',
|
||||||
name: 'tableActions',
|
name: 'tableActions',
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
title: t('delete'),
|
title: t('globals.clone'),
|
||||||
icon: 'delete',
|
icon: 'vn:clone',
|
||||||
action: (row) => confirmRemove(row),
|
action: (row) => openCloneFixedPriceForm(row),
|
||||||
jon marked this conversation as resolved
pablone
commented
Si le das a clonar un registro cancelas y despues le das a crear tienes los valores puestos por defecto del item al que anteriormente le habías dado a clonar Si le das a clonar un registro cancelas y despues le das a crear tienes los valores puestos por defecto del item al que anteriormente le habías dado a clonar
|
|||||||
isPrimary: true,
|
isPrimary: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const editTableFieldsOptions = [
|
const openEditFixedPriceForm = () => {
|
||||||
{
|
editFixedPriceForm.value.show();
|
||||||
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: 'started',
|
|
||||||
label: t('item.fixedPrice.started'),
|
|
||||||
component: 'date',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'ended',
|
|
||||||
label: t('item.fixedPrice.ended'),
|
|
||||||
component: 'date',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'warehouseFk',
|
|
||||||
label: t('globals.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 updateMinPrice = async (value, props) => {
|
const openCloneFixedPriceForm = (row) => {
|
||||||
props.row.hasMinPrice = value;
|
tableRef.value.showForm = true;
|
||||||
await upsertPrice({
|
isToClone.value = true;
|
||||||
row: props.row,
|
tableRef.value.create.title = t('Clone fixed price');
|
||||||
col: { field: 'hasMinPrice' },
|
tableRef.value.create.formInitialData = (({
|
||||||
rowIndex: props.rowIndex,
|
itemFk,
|
||||||
});
|
rate2,
|
||||||
|
rate3,
|
||||||
|
started,
|
||||||
|
ended,
|
||||||
|
warehouseFk,
|
||||||
|
}) => ({
|
||||||
|
itemFk,
|
||||||
|
rate2,
|
||||||
|
rate3,
|
||||||
|
started,
|
||||||
|
ended,
|
||||||
|
warehouseFk,
|
||||||
|
}))(JSON.parse(JSON.stringify(row)));
|
||||||
};
|
};
|
||||||
|
|
||||||
const validations = ({ row }) => {
|
|
||||||
const requiredFields = [
|
|
||||||
'itemFk',
|
|
||||||
'started',
|
|
||||||
'ended',
|
|
||||||
'rate2',
|
|
||||||
'rate3',
|
|
||||||
'warehouseFk',
|
|
||||||
];
|
|
||||||
const isValid = requiredFields.every(
|
|
||||||
(field) => row[field] !== null && row[field] !== undefined
|
|
||||||
);
|
|
||||||
return isValid;
|
|
||||||
};
|
|
||||||
const upsertPrice = async (props, resetMinPrice = false) => {
|
|
||||||
const isValid = validations({ ...props });
|
|
||||||
if (!isValid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { row } = props;
|
|
||||||
const changes = tableRef.value.CrudModelRef.getChanges();
|
|
||||||
if (changes?.updates?.length > 0) {
|
|
||||||
if (resetMinPrice) row.hasMinPrice = 0;
|
|
||||||
}
|
|
||||||
if (!changes.updates && !changes.creates) return;
|
|
||||||
const data = await upsertFixedPrice(row);
|
|
||||||
Object.assign(tableRef.value.CrudModelRef.formData[props.rowIndex], data);
|
|
||||||
notify(t('globals.dataSaved'), 'positive');
|
|
||||||
tableRef.value.reload();
|
|
||||||
};
|
|
||||||
|
|
||||||
async function upsertFixedPrice(row) {
|
|
||||||
const { data } = await axios.patch('FixedPrices/upsertFixedPrice', row);
|
|
||||||
data.hasMinPrice = data.hasMinPrice ? 1 : 0;
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkLastVisibleRow() {
|
|
||||||
let lastVisibleRow = null;
|
|
||||||
|
|
||||||
getTableRows().forEach((row, index) => {
|
|
||||||
const rect = row.getBoundingClientRect();
|
|
||||||
if (rect.top >= 0 && rect.bottom <= window.innerHeight) {
|
|
||||||
lastVisibleRow = index;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return lastVisibleRow;
|
|
||||||
}
|
|
||||||
|
|
||||||
const addRow = (original = null) => {
|
|
||||||
let copy = null;
|
|
||||||
const today = Date.vnNew();
|
|
||||||
const millisecsInDay = 86400000;
|
|
||||||
const daysInWeek = 7;
|
|
||||||
const nextWeek = new Date(today.getTime() + daysInWeek * millisecsInDay);
|
|
||||||
|
|
||||||
copy = {
|
|
||||||
id: 0,
|
|
||||||
started: today,
|
|
||||||
ended: nextWeek,
|
|
||||||
hasMinPrice: 0,
|
|
||||||
$index: 0,
|
|
||||||
};
|
|
||||||
return { original, copy };
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTableRows = () =>
|
|
||||||
document.getElementsByClassName('q-table')[0].querySelectorAll('tr.cursor-pointer');
|
|
||||||
|
|
||||||
function highlightNewRow({ $index: index }) {
|
|
||||||
const row = getTableRows()[index];
|
|
||||||
if (row) {
|
|
||||||
row.classList.add('highlight');
|
|
||||||
setTimeout(() => {
|
|
||||||
row.classList.remove('highlight');
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const openEditTableCellDialog = () => {
|
|
||||||
editTableCellDialogRef.value.show();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onEditCellDataSaved = async () => {
|
|
||||||
rowsSelected.value = [];
|
|
||||||
tableRef.value.reload();
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeFuturePrice = async () => {
|
|
||||||
rowsSelected.value.forEach(({ id }) => {
|
|
||||||
const rowIndex = fixedPrices.value.findIndex(({ id }) => id === id);
|
|
||||||
removePrice(id, rowIndex);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function confirmRemove(item, isFuture) {
|
|
||||||
const promise = async () =>
|
|
||||||
isFuture ? removeFuturePrice(item.id) : removePrice(item.id);
|
|
||||||
quasar.dialog({
|
|
||||||
component: VnConfirm,
|
|
||||||
componentProps: {
|
|
||||||
title: t('globals.rowWillBeRemoved'),
|
|
||||||
message: t('globals.confirmDeletion'),
|
|
||||||
promise,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const removePrice = async (id) => {
|
|
||||||
await axios.delete(`FixedPrices/${id}`);
|
|
||||||
notify(t('globals.dataSaved'), 'positive');
|
|
||||||
tableRef.value.reload({});
|
|
||||||
};
|
|
||||||
const dateStyle = (date) =>
|
const dateStyle = (date) =>
|
||||||
date
|
date
|
||||||
? {
|
? {
|
||||||
'bg-color': 'warning',
|
color: 'var(--vn-black-text-color)',
|
||||||
'is-outlined': true,
|
|
||||||
}
|
}
|
||||||
: {};
|
: { color: dateColor, 'background-color': 'transparent' };
|
||||||
|
|
||||||
function handleOnDataSave({ CrudModelRef }) {
|
const onDataSaved = () => {
|
||||||
const { original, copy } = addRow(CrudModelRef.formData[checkLastVisibleRow()]);
|
tableRef.value.CrudModelRef.saveChanges();
|
||||||
if (original) {
|
selectedRows.value = [];
|
||||||
CrudModelRef.formData.splice(original?.$index ?? 0, 0, copy);
|
};
|
||||||
} else {
|
|
||||||
CrudModelRef.insert(copy);
|
onMounted(() => {
|
||||||
|
if (tableRef.value) {
|
||||||
|
tableRef.value.showForm = false;
|
||||||
}
|
}
|
||||||
nextTick(() => {
|
});
|
||||||
highlightNewRow(original ?? { $index: 0 });
|
|
||||||
});
|
watch(
|
||||||
}
|
() => tableRef.value?.showForm,
|
||||||
|
(newVal) => {
|
||||||
|
if (!newVal) {
|
||||||
|
tableRef.value.create.title = '';
|
||||||
|
tableRef.value.create.formInitialData = { warehouseFk: warehouse };
|
||||||
|
if (tableRef.value) {
|
||||||
|
isToClone.value = false;
|
||||||
|
tableRef.value.create.title = t('Create fixed price');
|
||||||
|
}
|
||||||
jon marked this conversation as resolved
Outdated
pablone
commented
Te faltan las traducciones de las tags de algunos filtros, revisa los filtros de las columnas Te faltan las traducciones de las tags de algunos filtros, revisa los filtros de las columnas
|
|||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FetchData
|
|
||||||
@on-fetch="(data) => (warehousesOptions = data)"
|
|
||||||
auto-load
|
|
||||||
url="Warehouses"
|
|
||||||
:filter="{ fields: ['id', 'name'], order: 'name ASC' }"
|
|
||||||
/>
|
|
||||||
<RightMenu>
|
<RightMenu>
|
||||||
jon marked this conversation as resolved
Outdated
pablone
commented
Después de editar múltiples filas y guardar a pesar de no tener seleccionado ningun registro se queda activado Después de editar múltiples filas y guardar a pesar de no tener seleccionado ningun registro se queda activado
|
|||||||
<template #right-panel>
|
<template #right-panel>
|
||||||
<ItemFixedPriceFilter
|
<ItemFixedPriceFilter data-key="ItemFixedPrices" />
|
||||||
data-key="ItemFixedPrices"
|
|
||||||
ref="itemFixedPriceFilterRef"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</RightMenu>
|
</RightMenu>
|
||||||
<VnSubToolbar>
|
<VnSubToolbar />
|
||||||
<template #st-actions>
|
<Teleport to="#st-data" v-if="stateStore?.isSubToolbarShown()">
|
||||||
|
<QBtnGroup push style="column-gap: 10px">
|
||||||
<QBtn
|
<QBtn
|
||||||
:disable="!hasSelectedRows"
|
:disable="!hasSelectedRows"
|
||||||
@click="openEditTableCellDialog()"
|
@click="openEditFixedPriceForm()"
|
||||||
color="primary"
|
color="primary"
|
||||||
icon="edit"
|
icon="edit"
|
||||||
|
flat
|
||||||
|
:label="t('globals.edit')"
|
||||||
|
data-cy="FixedPriceToolbarEditBtn"
|
||||||
>
|
>
|
||||||
<QTooltip>
|
<QTooltip>
|
||||||
{{ t('Edit fixed price(s)') }}
|
{{ t('Edit fixed price(s)') }}
|
||||||
</QTooltip>
|
</QTooltip>
|
||||||
</QBtn>
|
</QBtn>
|
||||||
<QBtn
|
</QBtnGroup>
|
||||||
:disable="!hasSelectedRows"
|
</Teleport>
|
||||||
:label="tMobile('globals.remove')"
|
|
||||||
color="primary"
|
|
||||||
icon="delete"
|
|
||||||
flat
|
|
||||||
@click="(row) => confirmRemove(row, true)"
|
|
||||||
:title="t('globals.remove')"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</VnSubToolbar>
|
|
||||||
<VnTable
|
<VnTable
|
||||||
:default-remove="false"
|
ref="tableRef"
|
||||||
:default-reset="false"
|
|
||||||
:default-save="false"
|
|
||||||
data-key="ItemFixedPrices"
|
data-key="ItemFixedPrices"
|
||||||
url="FixedPrices/filter"
|
url="FixedPrices/filter"
|
||||||
:order="['name DESC', 'itemFk DESC']"
|
:order="'name DESC'"
|
||||||
save-url="FixedPrices/crud"
|
save-url="FixedPrices/crud"
|
||||||
ref="tableRef"
|
|
||||||
dense
|
|
||||||
:filter="{
|
|
||||||
where: {
|
|
||||||
warehouseFk: user.warehouseFk,
|
|
||||||
},
|
|
||||||
}"
|
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
default-mode="table"
|
|
||||||
auto-load
|
|
||||||
:is-editable="true"
|
:is-editable="true"
|
||||||
:right-search="false"
|
:right-search="false"
|
||||||
:table="{
|
:table="{
|
||||||
'row-key': 'id',
|
'row-key': 'id',
|
||||||
selection: 'multiple',
|
selection: 'multiple',
|
||||||
}"
|
}"
|
||||||
v-model:selected="rowsSelected"
|
v-model:selected="selectedRows"
|
||||||
:create-as-dialog="false"
|
|
||||||
:create="{
|
:create="{
|
||||||
onDataSaved: handleOnDataSave,
|
urlCreate: 'FixedPrices',
|
||||||
|
title: t('Create fixed price'),
|
||||||
|
formInitialData: { warehouseFk: warehouse },
|
||||||
|
onDataSaved: () => tableRef.reload(),
|
||||||
|
showSaveAndContinueBtn: true,
|
||||||
}"
|
}"
|
||||||
:disable-option="{ card: true }"
|
:disable-option="{ card: true }"
|
||||||
:has-sub-toolbar="false"
|
auto-load
|
||||||
|
:beforeSaveFn="(data, getChanges) => beforeSave(data, getChanges, 'FixedPrices')"
|
||||||
>
|
>
|
||||||
jon marked this conversation as resolved
Outdated
pablone
commented
pon el item descriptor proxy dentro del span sino al hacer click en la casilla te abre el descriptor pon el item descriptor proxy dentro del span sino al hacer click en la casilla te abre el descriptor
|
|||||||
<template #header-selection="scope">
|
<template #column-hex="{ row }">
|
||||||
<QCheckbox v-model="scope.selected" />
|
<VnColor :colors="row?.hexJson" style="height: 100%; min-width: 2000px" />
|
||||||
</template>
|
</template>
|
||||||
<template #body-selection="scope">
|
<template #column-name="{ row }">
|
||||||
{{ scope }}
|
<span class="link">
|
||||||
<QCheckbox flat v-model="scope.selected" />
|
{{ row.name }}
|
||||||
|
<ItemDescriptorProxy :id="row.itemFk" />
|
||||||
|
</span>
|
||||||
|
<span class="subName">{{ row.subName }}</span>
|
||||||
|
<FetchedTags :item="row" :columns="6" />
|
||||||
</template>
|
</template>
|
||||||
|
<template #column-started="{ row }">
|
||||||
<template #column-itemFk="props">
|
<div class="editable-text q-pb-xxs">
|
||||||
|
<QBadge class="badge" :style="dateStyle(isLower(row?.ended))">
|
||||||
|
{{ toDate(row?.started) }}
|
||||||
|
</QBadge>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #column-ended="{ row }">
|
||||||
|
<div class="editable-text q-pb-xxs">
|
||||||
|
<QBadge class="badge" :style="dateStyle(isBigger(row?.ended))">
|
||||||
|
{{ toDate(row?.ended) }}
|
||||||
|
</QBadge>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #column-create-name="{ data }">
|
||||||
<VnSelect
|
<VnSelect
|
||||||
style="max-width: 100px"
|
url="Items/search"
|
||||||
url="Items/withName"
|
v-model="data.itemFk"
|
||||||
hide-selected
|
:label="t('item.fixedPrice.itemName')"
|
||||||
option-label="id"
|
:fields="['id', 'name']"
|
||||||
|
:filter-options="['id', 'name']"
|
||||||
|
option-label="name"
|
||||||
option-value="id"
|
option-value="id"
|
||||||
v-model="props.row.itemFk"
|
:required="true"
|
||||||
v-on="getRowUpdateInputEvents(props, true, 'select')"
|
sort-by="name ASC"
|
||||||
|
data-cy="FixedPriceCreateNameSelect"
|
||||||
>
|
>
|
||||||
<template #option="scope">
|
<template #option="scope">
|
||||||
<QItem v-bind="scope.itemProps">
|
<QItem v-bind="scope.itemProps">
|
||||||
<QItemSection>
|
<QItemSection>
|
||||||
<QItemLabel> #{{ scope.opt?.id }} </QItemLabel>
|
<QItemLabel>
|
||||||
<QItemLabel caption>{{ scope.opt?.name }}</QItemLabel>
|
{{ scope.opt.name }}
|
||||||
|
</QItemLabel>
|
||||||
|
<QItemLabel caption> #{{ scope.opt.id }} </QItemLabel>
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
</QItem>
|
</QItem>
|
||||||
</template>
|
</template>
|
||||||
</VnSelect>
|
</VnSelect>
|
||||||
</template>
|
</template>
|
||||||
<template #column-name="{ row }">
|
<template #column-create-warehouseFk="{ data }">
|
||||||
<span class="link">
|
<VnSelect
|
||||||
{{ row.name }}
|
:label="t('globals.warehouse')"
|
||||||
</span>
|
url="Warehouses"
|
||||||
<span class="subName">{{ row.subName }}</span>
|
v-model="data.warehouseFk"
|
||||||
<ItemDescriptorProxy :id="row.itemFk" />
|
:fields="['id', 'name']"
|
||||||
<FetchedTags :item="row" :columns="3" />
|
option-label="name"
|
||||||
</template>
|
option-value="id"
|
||||||
<template #column-rate2="props">
|
hide-selected
|
||||||
<QTd class="col">
|
:required="true"
|
||||||
<VnInput
|
sort-by="name ASC"
|
||||||
type="currency"
|
data-cy="FixedPriceCreateWarehouseSelect"
|
||||||
style="width: 75px"
|
|
||||||
v-model.number="props.row.rate2"
|
|
||||||
v-on="getRowUpdateInputEvents(props)"
|
|
||||||
>
|
|
||||||
<template #append>€</template>
|
|
||||||
</VnInput>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #column-rate3="props">
|
|
||||||
<QTd class="col">
|
|
||||||
<VnInput
|
|
||||||
style="width: 75px"
|
|
||||||
type="currency"
|
|
||||||
v-model.number="props.row.rate3"
|
|
||||||
v-on="getRowUpdateInputEvents(props)"
|
|
||||||
>
|
|
||||||
<template #append>€</template>
|
|
||||||
</VnInput>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #column-minPrice="props">
|
|
||||||
<QTd class="col">
|
|
||||||
<div class="row" style="align-items: center">
|
|
||||||
<QCheckbox
|
|
||||||
:model-value="props.row.hasMinPrice"
|
|
||||||
@update:model-value="updateMinPrice($event, props)"
|
|
||||||
:false-value="0"
|
|
||||||
:true-value="1"
|
|
||||||
:toggle-indeterminate="false"
|
|
||||||
/>
|
|
||||||
<VnInput
|
|
||||||
class="col"
|
|
||||||
type="currency"
|
|
||||||
mask="###.##"
|
|
||||||
:disable="props.row.hasMinPrice === 0"
|
|
||||||
v-model.number="props.row.minPrice"
|
|
||||||
v-on="getRowUpdateInputEvents(props)"
|
|
||||||
>
|
|
||||||
<template #append>€</template>
|
|
||||||
</VnInput>
|
|
||||||
</div>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #column-started="props">
|
|
||||||
<VnInputDate
|
|
||||||
class="vnInputDate"
|
|
||||||
:show-event="true"
|
|
||||||
v-model="props.row.started"
|
|
||||||
v-on="getRowUpdateInputEvents(props, false, 'date')"
|
|
||||||
v-bind="dateStyle(isBigger(props.row.started))"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template #column-ended="props">
|
|
||||||
<VnInputDate
|
|
||||||
class="vnInputDate"
|
|
||||||
:show-event="true"
|
|
||||||
v-model="props.row.ended"
|
|
||||||
v-on="getRowUpdateInputEvents(props, false, 'date')"
|
|
||||||
v-bind="dateStyle(isLower(props.row.ended))"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template #column-warehouseFk="props">
|
|
||||||
<QTd class="col">
|
|
||||||
<VnSelect
|
|
||||||
style="max-width: 150px"
|
|
||||||
:options="warehousesOptions"
|
|
||||||
hide-selected
|
|
||||||
option-label="name"
|
|
||||||
option-value="id"
|
|
||||||
v-model="props.row.warehouseFk"
|
|
||||||
v-on="getRowUpdateInputEvents(props, false, 'select')"
|
|
||||||
/>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #column-deleteAction="{ row, rowIndex }">
|
|
||||||
<QIcon
|
|
||||||
name="delete"
|
|
||||||
size="sm"
|
|
||||||
class="cursor-pointer fill-icon-on-hover"
|
|
||||||
color="primary"
|
|
||||||
@click.stop="
|
|
||||||
openConfirmationModal(
|
|
||||||
t('globals.rowWillBeRemoved'),
|
|
||||||
t('Do you want to clone this item?'),
|
|
||||||
() => removePrice(row.id, rowIndex)
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<QTooltip class="text-no-wrap">
|
<template #option="scope">
|
||||||
{{ t('globals.delete') }}
|
<QItem v-bind="scope.itemProps">
|
||||||
</QTooltip>
|
<QItemSection>
|
||||||
</QIcon>
|
<QItemLabel>
|
||||||
|
{{ scope.opt.name }}
|
||||||
|
</QItemLabel>
|
||||||
|
<QItemLabel caption> #{{ scope.opt.id }} </QItemLabel>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
</template>
|
||||||
|
</VnSelect>
|
||||||
</template>
|
</template>
|
||||||
</VnTable>
|
</VnTable>
|
||||||
|
<QDialog ref="editFixedPriceForm">
|
||||||
<QDialog ref="editTableCellDialogRef">
|
<EditFixedPriceForm
|
||||||
<EditTableCellValueForm
|
|
||||||
edit-url="FixedPrices/editFixedPrice"
|
edit-url="FixedPrices/editFixedPrice"
|
||||||
:rows="rowsSelected"
|
:rows="selectedRows"
|
||||||
:fields-options="editTableFieldsOptions"
|
:fields-options="
|
||||||
@on-data-saved="onEditCellDataSaved()"
|
columns.filter(
|
||||||
|
({ isEditable, component, name }) =>
|
||||||
|
isEditable !== false && component && name !== 'itemFk',
|
||||||
|
)
|
||||||
|
"
|
||||||
|
:beforeSave="beforeSave"
|
||||||
|
@on-data-saved="onDataSaved"
|
||||||
/>
|
/>
|
||||||
</QDialog>
|
</QDialog>
|
||||||
</template>
|
</template>
|
||||||
|
@ -623,8 +435,17 @@ tbody tr.highlight .q-td {
|
||||||
color: var(--vn-label-color);
|
color: var(--vn-label-color);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.badge {
|
||||||
|
background-color: $warning;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<i18n>
|
<i18n>
|
||||||
es:
|
es:
|
||||||
Add fixed price: Añadir precio fijado
|
Add fixed price: Añadir precio fijado
|
||||||
Edit fixed price(s): Editar precio(s) fijado(s)
|
Edit fixed price(s): Editar precio(s) fijado(s)
|
||||||
|
Create fixed price: Crear precio fijado
|
||||||
|
Clone fixed price: Clonar precio fijado
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|
|
@ -29,6 +29,7 @@ const props = defineProps({
|
||||||
dense
|
dense
|
||||||
filled
|
filled
|
||||||
use-input
|
use-input
|
||||||
|
:use-like="false"
|
||||||
@update:model-value="searchFn()"
|
@update:model-value="searchFn()"
|
||||||
sort-by="nickname ASC"
|
sort-by="nickname ASC"
|
||||||
/>
|
/>
|
||||||
|
@ -50,21 +51,19 @@ const props = defineProps({
|
||||||
/>
|
/>
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
</QItem>
|
</QItem>
|
||||||
<QItem class="q-my-md">
|
<QItem>
|
||||||
<QItemSection>
|
<QItemSection>
|
||||||
<VnInputDate
|
<VnInputDate
|
||||||
:label="t('params.started')"
|
|
||||||
v-model="params.started"
|
v-model="params.started"
|
||||||
|
:label="t('params.started')"
|
||||||
filled
|
filled
|
||||||
@update:model-value="searchFn()"
|
@update:model-value="searchFn()"
|
||||||
/>
|
/>
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
</QItem>
|
|
||||||
<QItem class="q-my-md">
|
|
||||||
<QItemSection>
|
<QItemSection>
|
||||||
<VnInputDate
|
<VnInputDate
|
||||||
:label="t('params.ended')"
|
|
||||||
v-model="params.ended"
|
v-model="params.ended"
|
||||||
|
:label="t('params.ended')"
|
||||||
filled
|
filled
|
||||||
@update:model-value="searchFn()"
|
@update:model-value="searchFn()"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -8,11 +8,6 @@ import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||||
import VnRow from 'components/ui/VnRow.vue';
|
import VnRow from 'components/ui/VnRow.vue';
|
||||||
import { QCheckbox } from 'quasar';
|
import { QCheckbox } from 'quasar';
|
||||||
|
|
||||||
import axios from 'axios';
|
|
||||||
import useNotify from 'src/composables/useNotify.js';
|
|
||||||
|
|
||||||
const emit = defineEmits(['onDataSaved']);
|
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
rows: {
|
rows: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
@ -26,10 +21,14 @@ const $props = defineProps({
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
beforeSave: {
|
||||||
pablone
commented
hay algunos warning al abrir el create o clone form hay algunos warning al abrir el create o clone form
jon
commented
Los warnings se han corregido en Los warnings se han corregido en https://gitea.verdnatura.es/verdnatura/salix-front/commit/a74e1102ed6a7cdcf6bad98ed2b37312cc2fcb5f
|
|||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { notify } = useNotify();
|
const emit = defineEmits(['onDataSaved']);
|
||||||
|
|
||||||
const inputs = {
|
const inputs = {
|
||||||
input: markRaw(VnInput),
|
input: markRaw(VnInput),
|
||||||
|
@ -44,24 +43,13 @@ const selectedField = ref(null);
|
||||||
const closeButton = ref(null);
|
const closeButton = ref(null);
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
|
|
||||||
const onDataSaved = () => {
|
|
||||||
notify('globals.dataSaved', 'positive');
|
|
||||||
emit('onDataSaved');
|
|
||||||
closeForm();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
const rowsToEdit = $props.rows.map((row) => ({ id: row.id, itemFk: row.itemFk }));
|
$props.rows.forEach((row) => {
|
||||||
const payload = {
|
row[selectedField.value.name] = newValue.value;
|
||||||
field: selectedField.value.field,
|
});
|
||||||
newValue: newValue.value,
|
emit('onDataSaved', $props.rows);
|
||||||
lines: rowsToEdit,
|
closeForm();
|
||||||
};
|
|
||||||
|
|
||||||
await axios.post($props.editUrl, payload);
|
|
||||||
onDataSaved();
|
|
||||||
isLoading.value = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeForm = () => {
|
const closeForm = () => {
|
||||||
|
@ -78,21 +66,24 @@ const closeForm = () => {
|
||||||
<span class="title">{{ t('Edit') }}</span>
|
<span class="title">{{ t('Edit') }}</span>
|
||||||
<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>
|
<VnRow class="q-mt-md">
|
||||||
<VnSelect
|
<VnSelect
|
||||||
|
class="editOption"
|
||||||
: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"
|
v-model="selectedField"
|
||||||
data-cy="field-to-edit"
|
data-cy="EditFixedPriceSelectOption"
|
||||||
|
@update:model-value="newValue = null"
|
||||||
|
:class="{ 'is-select': selectedField?.component === 'select' }"
|
||||||
/>
|
/>
|
||||||
<component
|
<component
|
||||||
:is="inputs[selectedField?.component || 'input']"
|
:is="inputs[selectedField?.component || 'input']"
|
||||||
v-bind="selectedField?.attrs || {}"
|
v-bind="selectedField?.attrs || {}"
|
||||||
v-model="newValue"
|
v-model="newValue"
|
||||||
:label="t('Value')"
|
:label="t('Value')"
|
||||||
data-cy="value-to-edit"
|
data-cy="EditFixedPriceValueOption"
|
||||||
style="width: 200px"
|
style="width: 200px"
|
||||||
/>
|
/>
|
||||||
</VnRow>
|
</VnRow>
|
||||||
|
@ -140,6 +131,15 @@ const closeForm = () => {
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.editOption .q-field__inner .q-field__control {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
.editOption.is-select .q-field__inner .q-field__control {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<i18n>
|
<i18n>
|
||||||
es:
|
es:
|
||||||
Edit: Editar
|
Edit: Editar
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
||||||
|
import { createWrapper } from 'app/test/vitest/helper';
|
||||||
|
import EditFixedPriceForm from 'src/pages/Item/components/EditFixedPriceForm.vue';
|
||||||
|
|
||||||
|
describe('EditFixedPriceForm.vue', () => {
|
||||||
|
let wrapper;
|
||||||
|
let vm;
|
||||||
|
|
||||||
|
const mockRows = [
|
||||||
|
{ id: 1, itemFk: 101 },
|
||||||
|
{ id: 2, itemFk: 102 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockFieldsOptions = [
|
||||||
|
{
|
||||||
|
name: 'price',
|
||||||
|
label: 'Price',
|
||||||
|
component: 'input',
|
||||||
|
attrs: { type: 'number' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = createWrapper(EditFixedPriceForm, {
|
||||||
|
props: {
|
||||||
|
rows: JSON.parse(JSON.stringify(mockRows)),
|
||||||
|
fieldsOptions: mockFieldsOptions,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
wrapper = wrapper.wrapper;
|
||||||
|
vm = wrapper.vm;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit "onDataSaved" with updated rows on submit', async () => {
|
||||||
|
vm.selectedField = mockFieldsOptions[0];
|
||||||
|
vm.newValue = 199.99;
|
||||||
|
|
||||||
|
await vm.onSubmit();
|
||||||
|
|
||||||
|
expect(wrapper.emitted('onDataSaved')).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -167,6 +167,8 @@ item:
|
||||||
started: Started
|
started: Started
|
||||||
ended: Ended
|
ended: Ended
|
||||||
warehouse: Warehouse
|
warehouse: Warehouse
|
||||||
|
MP: MP
|
||||||
|
itemName: Item
|
||||||
create:
|
create:
|
||||||
name: Name
|
name: Name
|
||||||
tag: Tag
|
tag: Tag
|
||||||
|
|
|
@ -173,6 +173,8 @@ item:
|
||||||
started: Inicio
|
started: Inicio
|
||||||
ended: Fin
|
ended: Fin
|
||||||
warehouse: Almacén
|
warehouse: Almacén
|
||||||
|
MP: PM
|
||||||
|
itemName: Nombre
|
||||||
create:
|
create:
|
||||||
name: Nombre
|
name: Nombre
|
||||||
tag: Etiqueta
|
tag: Etiqueta
|
||||||
|
|
|
@ -295,13 +295,11 @@ watch(
|
||||||
:user-filter="lineFilter"
|
:user-filter="lineFilter"
|
||||||
>
|
>
|
||||||
<template #column-image="{ row }">
|
<template #column-image="{ row }">
|
||||||
<div class="image-wrapper">
|
<VnImg
|
||||||
<VnImg
|
:id="parseInt(row?.item?.image)"
|
||||||
:id="parseInt(row?.item?.image)"
|
class="rounded"
|
||||||
class="rounded"
|
zoom-resolution="1600x900"
|
||||||
zoom-resolution="1600x900"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template #column-id="{ row }">
|
<template #column-id="{ row }">
|
||||||
<span class="link" @click.stop>
|
<span class="link" @click.stop>
|
||||||
|
@ -361,12 +359,6 @@ watch(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-wrapper {
|
|
||||||
height: 50px;
|
|
||||||
width: 50px;
|
|
||||||
margin-left: 30%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
color: $primary;
|
color: $primary;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
/// <reference types="cypress" />
|
/// <reference types="cypress" />
|
||||||
function goTo(n = 1) {
|
|
||||||
return `.q-virtual-scroll__content > :nth-child(${n})`;
|
|
||||||
}
|
|
||||||
const firstRow = goTo();
|
|
||||||
describe('Handle Items FixedPrice', () => {
|
describe('Handle Items FixedPrice', () => {
|
||||||
|
const grouping = 'Grouping price';
|
||||||
|
const saveEditBtn = '.q-mt-lg > .q-btn--standard';
|
||||||
|
const createForm = {
|
||||||
|
'Grouping price': { val: '5' },
|
||||||
|
'Packing price': { val: '5' },
|
||||||
|
Started: { val: '01-01-2001', type: 'date' },
|
||||||
|
Ended: { val: '15-01-2001', type: 'date' },
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.viewport(1280, 720);
|
cy.viewport(1280, 720);
|
||||||
cy.login('developer');
|
cy.login('developer');
|
||||||
|
@ -13,50 +18,57 @@ describe('Handle Items FixedPrice', () => {
|
||||||
'.q-header > .q-toolbar > :nth-child(1) > .q-btn__content > .q-icon',
|
'.q-header > .q-toolbar > :nth-child(1) > .q-btn__content > .q-icon',
|
||||||
).click();
|
).click();
|
||||||
});
|
});
|
||||||
it.skip('filter', function () {
|
|
||||||
|
it('filter by category', () => {
|
||||||
cy.get('.category-filter > :nth-child(1) > .q-btn__content > .q-icon').click();
|
cy.get('.category-filter > :nth-child(1) > .q-btn__content > .q-icon').click();
|
||||||
cy.selectOption('.list > :nth-child(2)', 'Alstroemeria');
|
cy.get('.q-table__middle').should('be.visible').should('have.length', 1);
|
||||||
cy.get('.q-gutter-x-sm > .q-btn > .q-btn__content > .q-icon').click();
|
|
||||||
|
|
||||||
cy.addBtnClick();
|
|
||||||
cy.selectOption(`${firstRow} > :nth-child(2)`, '#13');
|
|
||||||
cy.get(`${firstRow} > :nth-child(4)`).find('input').type(1);
|
|
||||||
cy.get(`${firstRow} > :nth-child(5)`).find('input').type('2');
|
|
||||||
cy.selectOption(`${firstRow} > :nth-child(9)`, 'Warehouse One');
|
|
||||||
cy.get('.q-notification__message').should('have.text', 'Data saved');
|
|
||||||
/* ==== End Cypress Studio ==== */
|
|
||||||
});
|
|
||||||
it.skip('Create and delete ', function () {
|
|
||||||
cy.get('.q-gutter-x-sm > .q-btn > .q-btn__content > .q-icon').click();
|
|
||||||
cy.addBtnClick();
|
|
||||||
cy.selectOption(`${firstRow} > :nth-child(2)`, '#11');
|
|
||||||
cy.get(`${firstRow} > :nth-child(4)`).type('1');
|
|
||||||
cy.get(`${firstRow} > :nth-child(5)`).type('2');
|
|
||||||
cy.selectOption(`${firstRow} > :nth-child(9)`, 'Warehouse One');
|
|
||||||
cy.get('.q-notification__message').should('have.text', 'Data saved');
|
|
||||||
cy.get('.q-gutter-x-sm > .q-btn > .q-btn__content > .q-icon').click();
|
|
||||||
cy.get(`${firstRow} > .text-right > .q-btn > .q-btn__content > .q-icon`).click();
|
|
||||||
cy.get(
|
|
||||||
'.q-card__actions > .q-btn--unelevated > .q-btn__content > .block',
|
|
||||||
).click();
|
|
||||||
cy.get('.q-notification__message').should('have.text', 'Data saved');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('Massive edit', function () {
|
it('should create a new fixed price, then delete it', () => {
|
||||||
cy.get(' .bg-header > :nth-child(1) > .q-checkbox > .q-checkbox__inner ').click();
|
cy.dataCy('vnTableCreateBtn').click();
|
||||||
cy.get('#subToolbar > .q-btn--standard').click();
|
cy.dataCy('FixedPriceCreateNameSelect').type('Melee weapon combat fist 15cm');
|
||||||
cy.selectOption("[data-cy='field-to-edit']", 'Min price');
|
cy.get('.q-menu .q-item').contains('Melee weapon combat fist 15cm').click();
|
||||||
cy.dataCy('value-to-edit').find('input').type('1');
|
cy.dataCy('FixedPriceCreateWarehouseSelect').type('Warehouse One');
|
||||||
cy.get('.countLines').should('have.text', ' 1 ');
|
cy.get('.q-menu .q-item').contains('Warehouse One').click();
|
||||||
cy.get('.q-mt-lg > .q-btn--standard').click();
|
cy.get('.q-menu').then(($menu) => {
|
||||||
cy.get('.q-notification__message').should('have.text', 'Data saved');
|
if ($menu.is(':visible')) {
|
||||||
|
cy.dataCy('FixedPriceCreateWarehouseSelect').as('focusedElement').focus();
|
||||||
|
cy.dataCy('FixedPriceCreateWarehouseSelect').blur();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cy.fillInForm(createForm);
|
||||||
|
cy.dataCy('FormModelPopup_save').click();
|
||||||
|
cy.checkNotification('Data created');
|
||||||
|
cy.get('[data-col-field="name"]').each(($el) => {
|
||||||
|
cy.wrap($el)
|
||||||
|
.invoke('text')
|
||||||
|
.then((text) => {
|
||||||
|
if (text.includes('Melee weapon combat fist 15cm')) {
|
||||||
|
cy.wrap($el).parent().find('.q-checkbox').click();
|
||||||
|
cy.get('[data-cy="crudModelDefaultRemoveBtn"]').click();
|
||||||
|
cy.dataCy('VnConfirm_confirm').click().click();
|
||||||
|
cy.checkNotification('Data saved');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
it.skip('Massive remove', function () {
|
|
||||||
cy.get(' .bg-header > :nth-child(1) > .q-checkbox > .q-checkbox__inner ').click();
|
it('should edit all items', () => {
|
||||||
cy.get('#subToolbar > .q-btn--flat').click();
|
cy.get('.bg-header > :nth-child(1) > .q-checkbox > .q-checkbox__inner').click();
|
||||||
cy.get(
|
cy.dataCy('FixedPriceToolbarEditBtn').should('not.be.disabled');
|
||||||
'.q-card__actions > .q-btn--unelevated > .q-btn__content > .block',
|
cy.dataCy('FixedPriceToolbarEditBtn').click();
|
||||||
).click();
|
cy.dataCy('EditFixedPriceSelectOption').type(grouping);
|
||||||
cy.get('.q-notification__message').should('have.text', 'Data saved');
|
cy.get('.q-menu .q-item').contains(grouping).click();
|
||||||
|
cy.dataCy('EditFixedPriceValueOption').type('5');
|
||||||
|
cy.get(saveEditBtn).click();
|
||||||
|
cy.checkNotification('Data saved');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove all items', () => {
|
||||||
|
cy.get('.bg-header > :nth-child(1) > .q-checkbox > .q-checkbox__inner').click();
|
||||||
|
cy.dataCy('crudModelDefaultRemoveBtn').should('not.be.disabled');
|
||||||
|
cy.dataCy('crudModelDefaultRemoveBtn').click();
|
||||||
|
cy.dataCy('VnConfirm_confirm').click();
|
||||||
|
cy.checkNotification('Data saved');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue
yo pondria un input para el filtro de columna "Name" ya que así se puede aplicar un like en el back, es más cómodo para los compradores , y el select lo pondria en la columna del id