WIP: ItemFixedPrice #791
|
@ -248,7 +248,7 @@ function getChanges() {
|
|||
for (const [i, row] of formData.value.entries()) {
|
||||
if (!row[pk]) {
|
||||
creates.push(row);
|
||||
} else if (originalData.value) {
|
||||
} else if (originalData.value[i]) {
|
||||
const data = getDifferences(originalData.value[i], row);
|
||||
if (!isEmpty(data)) {
|
||||
updates.push({
|
||||
|
|
|
@ -9,6 +9,8 @@ import VnSelect from 'components/common/VnSelect.vue';
|
|||
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
|
||||
|
||||
import axios from 'axios';
|
||||
import { getParamWhere } from 'src/filters';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
|
@ -26,28 +28,21 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const itemCategories = ref([]);
|
||||
const selectedCategoryFk = ref(null);
|
||||
const selectedTypeFk = ref(null);
|
||||
const route = useRoute();
|
||||
|
||||
const itemTypesOptions = ref([]);
|
||||
const suppliersOptions = ref([]);
|
||||
const tagOptions = ref([]);
|
||||
const tagValues = ref([]);
|
||||
const categoryList = ref(null);
|
||||
const selectedCategoryFk = ref(getParamWhere(route.query.table, 'categoryFk', false));
|
||||
const selectedTypeFk = ref(getParamWhere(route.query.table, 'typeFk', false));
|
||||
|
||||
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(
|
||||
const selectedCategory = computed(() => {
|
||||
return (categoryList.value || []).find(
|
||||
(category) => category?.id === selectedCategoryFk.value
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
const selectedType = computed(() => {
|
||||
return (itemTypesOptions.value || []).find(
|
||||
|
@ -87,7 +82,8 @@ const applyTags = (params, search) => {
|
|||
search();
|
||||
};
|
||||
|
||||
const fetchItemTypes = async (id) => {
|
||||
const fetchItemTypes = async (id = selectedCategoryFk.value) => {
|
||||
try {
|
||||
const filter = {
|
||||
fields: ['id', 'name', 'categoryFk'],
|
||||
where: { categoryFk: id },
|
||||
|
@ -98,6 +94,9 @@ const fetchItemTypes = async (id) => {
|
|||
params: { filter: JSON.stringify(filter) },
|
||||
});
|
||||
itemTypesOptions.value = data;
|
||||
} catch (err) {
|
||||
console.error('Error fetching item types', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getCategoryClass = (category, params) => {
|
||||
|
@ -126,15 +125,19 @@ const removeTag = (index, params, search) => {
|
|||
(tagValues.value || []).splice(index, 1);
|
||||
applyTags(params, search);
|
||||
};
|
||||
const setCategoryList = (data) => {
|
||||
categoryList.value = (data || [])
|
||||
.filter((category) => category.display)
|
||||
.map((category) => ({
|
||||
...category,
|
||||
icon: `vn:${(category.icon || '').split('-')[1]}`,
|
||||
}));
|
||||
fetchItemTypes();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
url="ItemCategories"
|
||||
limit="30"
|
||||
auto-load
|
||||
@on-fetch="(data) => (itemCategories = data)"
|
||||
/>
|
||||
<FetchData url="ItemCategories" limit="30" auto-load @on-fetch="setCategoryList" />
|
||||
<FetchData
|
||||
url="Suppliers"
|
||||
limit="30"
|
||||
|
@ -254,6 +257,7 @@ const removeTag = (index, params, search) => {
|
|||
/>
|
||||
</QItemSection>
|
||||
<QItemSection class="col">
|
||||
{{ value }}
|
||||
<VnSelect
|
||||
v-if="!value?.selectedTag?.isFree && value.valueOptions"
|
||||
:label="t('components.itemsFilterPanel.value')"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { useValidator } from 'src/composables/useValidator';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// parsing JSON safely
|
||||
function parseJSON(str, fallback) {
|
||||
try {
|
||||
return JSON.parse(str ?? '{}');
|
||||
|
@ -7,13 +6,15 @@ function parseJSON(str, fallback) {
|
|||
return fallback;
|
||||
}
|
||||
}
|
||||
export default function (route, param) {
|
||||
// catch route query params
|
||||
const params = parseJSON(route?.query?.params, {});
|
||||
|
||||
// extract and parse filter from params
|
||||
export default function (route, param, inFilter = true) {
|
||||
const params = parseJSON(route?.query?.params ?? route, {});
|
||||
let where = null;
|
||||
if (!inFilter) {
|
||||
where = params;
|
||||
} else {
|
||||
const { filter: filterStr = '{}' } = params;
|
||||
const where = parseJSON(filterStr, {})?.where;
|
||||
where = parseJSON(filterStr, {})?.where;
|
||||
}
|
||||
if (where && where[param] !== undefined) {
|
||||
return where[param];
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import { useI18n } from 'vue-i18n';
|
|||
import { QBtn } from 'quasar';
|
||||
|
||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||
import FetchData from 'src/components/FetchData.vue';
|
||||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import FetchedTags from 'components/ui/FetchedTags.vue';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { onMounted, ref, reactive, onUnmounted, nextTick, computed } from 'vue';
|
||||
import { onMounted, ref, onUnmounted, nextTick, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||
import FetchedTags from 'components/ui/FetchedTags.vue';
|
||||
|
@ -37,11 +37,9 @@ const fixedPrices = ref([]);
|
|||
const warehousesOptions = ref([]);
|
||||
const rowsSelected = ref([]);
|
||||
const itemFixedPriceFilterRef = ref();
|
||||
const params = reactive({});
|
||||
|
||||
onMounted(async () => {
|
||||
stateStore.rightDrawer = true;
|
||||
params.warehouseFk = user.value.warehouseFk;
|
||||
});
|
||||
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||
|
||||
|
@ -132,13 +130,21 @@ const columns = computed(() => [
|
|||
|
||||
{
|
||||
label: t('item.fixedPrice.warehouse'),
|
||||
field: 'warehouseFk',
|
||||
name: 'warehouseFk',
|
||||
...defaultColumnAttrs,
|
||||
name: 'warehouseFk',
|
||||
columnClass: 'shrink',
|
||||
component: 'select',
|
||||
|
||||
options: warehousesOptions,
|
||||
columnFilter: {
|
||||
name: 'warehouseFk',
|
||||
inWhere: true,
|
||||
component: 'select',
|
||||
attrs: {
|
||||
options: warehousesOptions,
|
||||
'option-label': 'name',
|
||||
'option-value': 'id',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
|
@ -210,8 +216,6 @@ const getRowUpdateInputEvents = (props, resetMinPrice, inputType = 'text') => {
|
|||
};
|
||||
|
||||
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,
|
||||
|
@ -220,24 +224,46 @@ const updateMinPrice = async (value, props) => {
|
|||
});
|
||||
};
|
||||
|
||||
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) => {
|
||||
try {
|
||||
const isValid = validations({ ...props });
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
const { row } = props;
|
||||
if (tableRef.value.CrudModelRef.getChanges().updates.length > 0) {
|
||||
const changes = tableRef.value.CrudModelRef.getChanges();
|
||||
if (changes?.updates?.length > 0) {
|
||||
if (resetMinPrice) row.hasMinPrice = 0;
|
||||
await upsertFixedPrice(row);
|
||||
}
|
||||
if (!changes.updates && !changes.creates) return;
|
||||
const data = await upsertFixedPrice(row);
|
||||
tableRef.value.CrudModelRef.formData[props.rowIndex] = data;
|
||||
} catch (err) {
|
||||
console.error('Error editing price', err);
|
||||
}
|
||||
};
|
||||
|
||||
async function upsertFixedPrice(row) {
|
||||
try {
|
||||
const { data } = await axios.patch('FixedPrices/upsertFixedPrice', row);
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.error('Error editing price', err);
|
||||
}
|
||||
|
||||
async function saveOnRowChange(row) {
|
||||
if (rowsSelected.value.length > 1) return;
|
||||
if (rowsSelected.value[0]?.id === row.id) return;
|
||||
else if (rowsSelected.value.length === 1) await upsertPrice(rowsSelected.value[0]);
|
||||
rowsSelected.value = [row];
|
||||
}
|
||||
|
||||
function checkLastVisibleRow() {
|
||||
|
@ -255,7 +281,6 @@ function checkLastVisibleRow() {
|
|||
|
||||
const addRow = (original = null) => {
|
||||
let copy = null;
|
||||
if (!original) {
|
||||
const today = Date.vnNew();
|
||||
const millisecsInDay = 86400000;
|
||||
const daysInWeek = 7;
|
||||
|
@ -268,26 +293,6 @@ const addRow = (original = null) => {
|
|||
hasMinPrice: 0,
|
||||
$index: 0,
|
||||
};
|
||||
} else
|
||||
copy = {
|
||||
$index: original.$index - 1,
|
||||
itemFk: original.itemFk,
|
||||
name: original.name,
|
||||
subName: original.subName,
|
||||
value5: original.value5,
|
||||
value6: original.value6,
|
||||
value7: original.value7,
|
||||
value8: original.value8,
|
||||
value9: original.value9,
|
||||
value10: original.value10,
|
||||
warehouseFk: original.warehouseFk,
|
||||
rate2: original.rate2,
|
||||
rate3: original.rate3,
|
||||
hasMinPrice: original.hasMinPrice,
|
||||
minPrice: original.minPrice,
|
||||
started: Date.vnNew(),
|
||||
ended: Date.vnNew(),
|
||||
};
|
||||
return { original, copy };
|
||||
};
|
||||
|
||||
|
@ -300,7 +305,7 @@ function highlightNewRow({ $index: index }) {
|
|||
row.classList.add('highlight');
|
||||
setTimeout(() => {
|
||||
row.classList.remove('highlight');
|
||||
}, 3000); // Duración de la animación en milisegundos
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
const openEditTableCellDialog = () => {
|
||||
|
@ -411,9 +416,13 @@ function handleOnDataSave({ CrudModelRef }) {
|
|||
url="FixedPrices/filter"
|
||||
:order="['itemFk DESC', 'name DESC']"
|
||||
save-url="FixedPrices/crud"
|
||||
:user-params="{ warehouseFk: user.warehouseFk }"
|
||||
ref="tableRef"
|
||||
dense
|
||||
:filter="{
|
||||
where: {
|
||||
warehouseFk: user.warehouseFk,
|
||||
},
|
||||
}"
|
||||
:columns="columns"
|
||||
default-mode="table"
|
||||
auto-load
|
||||
|
@ -427,7 +436,6 @@ function handleOnDataSave({ CrudModelRef }) {
|
|||
disableInfiniteScroll: true,
|
||||
}"
|
||||
v-model:selected="rowsSelected"
|
||||
:row-click="saveOnRowChange"
|
||||
:create-as-dialog="false"
|
||||
:create="{
|
||||
onDataSaved: handleOnDataSave,
|
||||
|
|
|
@ -4,7 +4,6 @@ import { useI18n } from 'vue-i18n';
|
|||
import FetchData from 'components/FetchData.vue';
|
||||
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
|
||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
|
||||
import VnProgress from 'src/components/common/VnProgressModal.vue';
|
||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||
import TicketAdvanceFilter from './TicketAdvanceFilter.vue';
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/// <reference types="cypress" />
|
||||
function goTo(n = 1) {
|
||||
return `.q-virtual-scroll__content > :nth-child(${n})`;
|
||||
}
|
||||
const firstRow = goTo();
|
||||
`.q-virtual-scroll__content > :nth-child(2)`;
|
||||
describe('Handle Items FixedPrice', () => {
|
||||
beforeEach(() => {
|
||||
cy.viewport(1280, 720);
|
||||
cy.login('developer');
|
||||
cy.visit('/#/item/fixed-price', { timeout: 5000 });
|
||||
cy.waitForElement('.q-table');
|
||||
cy.get(
|
||||
'.q-header > .q-toolbar > :nth-child(1) > .q-btn__content > .q-icon'
|
||||
).click();
|
||||
});
|
||||
it('filter', function () {
|
||||
cy.get('.category-filter > :nth-child(1) > .q-btn__content > .q-icon').click();
|
||||
cy.selectOption('.list > :nth-child(2)', 'Alstroemeria');
|
||||
cy.get('.q-gutter-x-sm > .q-btn > .q-btn__content > .q-icon').click();
|
||||
|
||||
cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click();
|
||||
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('Create and delete ', function () {
|
||||
cy.get('.q-gutter-x-sm > .q-btn > .q-btn__content > .q-icon').click();
|
||||
cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click();
|
||||
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('Massive edit', function () {
|
||||
cy.get(' .bg-header > :nth-child(1) > .q-checkbox > .q-checkbox__inner ').click();
|
||||
cy.get('#subToolbar > .q-btn--standard').click();
|
||||
cy.selectOption('.vn-row > :nth-child(2)', 'Min price');
|
||||
cy.get('.vn-row > :nth-child(3)').type('1');
|
||||
cy.get('.countLines').should('have.text', ' 1 ');
|
||||
cy.get('.q-mt-lg > .q-btn--standard').click();
|
||||
cy.get('.q-notification__message').should('have.text', 'Data saved');
|
||||
});
|
||||
it('Massive remove', function () {
|
||||
cy.get(' .bg-header > :nth-child(1) > .q-checkbox > .q-checkbox__inner ').click();
|
||||
cy.get('#subToolbar > .q-btn--flat').click();
|
||||
cy.get(
|
||||
'.q-card__actions > .q-btn--unelevated > .q-btn__content > .block'
|
||||
).click();
|
||||
cy.get('.q-notification__message').should('have.text', 'Data saved');
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue