forked from verdnatura/salix-front
Merge branch 'test' of https://gitea.verdnatura.es/verdnatura/salix-front into dev
This commit is contained in:
commit
629531cce4
|
@ -249,7 +249,7 @@ function getChanges() {
|
||||||
for (const [i, row] of formData.value.entries()) {
|
for (const [i, row] of formData.value.entries()) {
|
||||||
if (!row[pk]) {
|
if (!row[pk]) {
|
||||||
creates.push(row);
|
creates.push(row);
|
||||||
} else if (originalData.value) {
|
} else if (originalData.value[i]) {
|
||||||
const data = getDifferences(originalData.value[i], row);
|
const data = getDifferences(originalData.value[i], row);
|
||||||
if (!isEmpty(data)) {
|
if (!isEmpty(data)) {
|
||||||
updates.push({
|
updates.push({
|
||||||
|
|
|
@ -85,12 +85,14 @@ const closeForm = () => {
|
||||||
hide-selected
|
hide-selected
|
||||||
option-label="label"
|
option-label="label"
|
||||||
v-model="selectedField"
|
v-model="selectedField"
|
||||||
|
data-cy="field-to-edit"
|
||||||
/>
|
/>
|
||||||
<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"
|
||||||
style="width: 200px"
|
style="width: 200px"
|
||||||
/>
|
/>
|
||||||
</VnRow>
|
</VnRow>
|
||||||
|
|
|
@ -62,6 +62,7 @@ defineExpose({
|
||||||
@click="emit('onDataCanceled')"
|
@click="emit('onDataCanceled')"
|
||||||
v-close-popup
|
v-close-popup
|
||||||
data-cy="FormModelPopup_cancel"
|
data-cy="FormModelPopup_cancel"
|
||||||
|
z-max
|
||||||
/>
|
/>
|
||||||
<QBtn
|
<QBtn
|
||||||
:label="t('globals.save')"
|
:label="t('globals.save')"
|
||||||
|
@ -72,6 +73,7 @@ defineExpose({
|
||||||
:disabled="isLoading"
|
:disabled="isLoading"
|
||||||
:loading="isLoading"
|
:loading="isLoading"
|
||||||
data-cy="FormModelPopup_save"
|
data-cy="FormModelPopup_save"
|
||||||
|
z-max
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -9,6 +9,8 @@ import VnSelect from 'components/common/VnSelect.vue';
|
||||||
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
|
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
|
||||||
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { getParamWhere } from 'src/filters';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
@ -26,28 +28,21 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const itemCategories = ref([]);
|
const route = useRoute();
|
||||||
const selectedCategoryFk = ref(null);
|
|
||||||
const selectedTypeFk = ref(null);
|
|
||||||
const itemTypesOptions = ref([]);
|
const itemTypesOptions = ref([]);
|
||||||
const suppliersOptions = ref([]);
|
const suppliersOptions = ref([]);
|
||||||
const tagOptions = ref([]);
|
const tagOptions = ref([]);
|
||||||
const tagValues = 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(() => {
|
const selectedCategory = computed(() => {
|
||||||
return (itemCategories.value || [])
|
return (categoryList.value || []).find(
|
||||||
.filter((category) => category.display)
|
|
||||||
.map((category) => ({
|
|
||||||
...category,
|
|
||||||
icon: `vn:${(category.icon || '').split('-')[1]}`,
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedCategory = computed(() =>
|
|
||||||
(itemCategories.value || []).find(
|
|
||||||
(category) => category?.id === selectedCategoryFk.value
|
(category) => category?.id === selectedCategoryFk.value
|
||||||
)
|
);
|
||||||
);
|
});
|
||||||
|
|
||||||
const selectedType = computed(() => {
|
const selectedType = computed(() => {
|
||||||
return (itemTypesOptions.value || []).find(
|
return (itemTypesOptions.value || []).find(
|
||||||
|
@ -87,7 +82,7 @@ const applyTags = (params, search) => {
|
||||||
search();
|
search();
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchItemTypes = async (id) => {
|
const fetchItemTypes = async (id = selectedCategoryFk.value) => {
|
||||||
const filter = {
|
const filter = {
|
||||||
fields: ['id', 'name', 'categoryFk'],
|
fields: ['id', 'name', 'categoryFk'],
|
||||||
where: { categoryFk: id },
|
where: { categoryFk: id },
|
||||||
|
@ -126,15 +121,19 @@ const removeTag = (index, params, search) => {
|
||||||
(tagValues.value || []).splice(index, 1);
|
(tagValues.value || []).splice(index, 1);
|
||||||
applyTags(params, search);
|
applyTags(params, search);
|
||||||
};
|
};
|
||||||
|
const setCategoryList = (data) => {
|
||||||
|
categoryList.value = (data || [])
|
||||||
|
.filter((category) => category.display)
|
||||||
|
.map((category) => ({
|
||||||
|
...category,
|
||||||
|
icon: `vn:${(category.icon || '').split('-')[1]}`,
|
||||||
|
}));
|
||||||
|
fetchItemTypes();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FetchData
|
<FetchData url="ItemCategories" limit="30" auto-load @on-fetch="setCategoryList" />
|
||||||
url="ItemCategories"
|
|
||||||
limit="30"
|
|
||||||
auto-load
|
|
||||||
@on-fetch="(data) => (itemCategories = data)"
|
|
||||||
/>
|
|
||||||
<FetchData
|
<FetchData
|
||||||
url="Suppliers"
|
url="Suppliers"
|
||||||
limit="30"
|
limit="30"
|
||||||
|
|
|
@ -67,7 +67,7 @@ const dialog = ref(null);
|
||||||
<QTooltip>{{ t('globals.add') }}</QTooltip>
|
<QTooltip>{{ t('globals.add') }}</QTooltip>
|
||||||
<QPopupProxy ref="dialog">
|
<QPopupProxy ref="dialog">
|
||||||
<OrderCatalogItemDialog
|
<OrderCatalogItemDialog
|
||||||
:prices="item.prices"
|
:item="item"
|
||||||
@added="() => dialog.hide()"
|
@added="() => dialog.hide()"
|
||||||
/>
|
/>
|
||||||
</QPopupProxy>
|
</QPopupProxy>
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { useI18n } from 'vue-i18n';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
|
|
||||||
import { toDateHourMin } from 'src/filters';
|
import { toDateHourMin } from 'src/filters';
|
||||||
import { useState } from 'src/composables/useState';
|
|
||||||
|
|
||||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||||
import VnUserLink from 'components/ui/VnUserLink.vue';
|
import VnUserLink from 'components/ui/VnUserLink.vue';
|
||||||
|
@ -26,9 +25,7 @@ const $props = defineProps({
|
||||||
});
|
});
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const state = useState();
|
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
const currentUser = ref(state.getUser());
|
|
||||||
const newNote = reactive({ text: null, observationTypeFk: null });
|
const newNote = reactive({ text: null, observationTypeFk: null });
|
||||||
const observationTypes = ref([]);
|
const observationTypes = ref([]);
|
||||||
const vnPaginateRef = ref();
|
const vnPaginateRef = ref();
|
||||||
|
|
|
@ -20,7 +20,7 @@ export function useRole() {
|
||||||
|
|
||||||
function hasAny(roles) {
|
function hasAny(roles) {
|
||||||
const roleStore = state.getRoles();
|
const roleStore = state.getRoles();
|
||||||
|
if (typeof roles === 'string') roles = [roles];
|
||||||
for (const role of roles) {
|
for (const role of roles) {
|
||||||
if (roleStore.value.indexOf(role) !== -1) return true;
|
if (roleStore.value.indexOf(role) !== -1) return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
// parsing JSON safely
|
|
||||||
function parseJSON(str, fallback) {
|
function parseJSON(str, fallback) {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(str ?? '{}');
|
return JSON.parse(str ?? '{}');
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref, onMounted } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import VnUserLink from 'src/components/ui/VnUserLink.vue';
|
import VnUserLink from 'src/components/ui/VnUserLink.vue';
|
||||||
|
|
||||||
import { toCurrency, toPercentage, toDate, dashOrCurrency } from 'src/filters';
|
import { toCurrency, toPercentage, toDate, dashOrCurrency } from 'src/filters';
|
||||||
import CardSummary from 'components/ui/CardSummary.vue';
|
import CardSummary from 'components/ui/CardSummary.vue';
|
||||||
import { getUrl } from 'src/composables/getUrl';
|
|
||||||
import VnLv from 'src/components/ui/VnLv.vue';
|
import VnLv from 'src/components/ui/VnLv.vue';
|
||||||
import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue';
|
import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue';
|
||||||
import VnLinkMail from 'src/components/ui/VnLinkMail.vue';
|
import VnLinkMail from 'src/components/ui/VnLinkMail.vue';
|
||||||
|
|
|
@ -12,20 +12,18 @@ import FetchData from 'components/FetchData.vue';
|
||||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||||
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||||
|
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
|
||||||
import { toDateFormat } from 'src/filters/date.js';
|
import { toDateFormat } from 'src/filters/date.js';
|
||||||
import { dashIfEmpty } from 'src/filters';
|
import { dashIfEmpty } from 'src/filters';
|
||||||
import { date } from 'quasar';
|
import { date } from 'quasar';
|
||||||
import { useState } from 'src/composables/useState';
|
import { useState } from 'src/composables/useState';
|
||||||
import { useArrayData } from 'src/composables/useArrayData';
|
import { useArrayData } from 'src/composables/useArrayData';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import VnSubToolbar from 'components/ui/VnSubToolbar.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const stateStore = useStateStore();
|
|
||||||
const state = useState();
|
const state = useState();
|
||||||
|
|
||||||
const user = state.getUser();
|
const user = state.getUser();
|
||||||
const today = ref(Date.vnNew());
|
const today = ref(Date.vnNew());
|
||||||
const warehousesOptions = ref([]);
|
const warehousesOptions = ref([]);
|
||||||
|
@ -203,8 +201,8 @@ async function updateWarehouse(warehouseFk) {
|
||||||
auto-load
|
auto-load
|
||||||
@on-fetch="(data) => (warehousesOptions = data)"
|
@on-fetch="(data) => (warehousesOptions = data)"
|
||||||
/>
|
/>
|
||||||
<template v-if="stateStore.isHeaderMounted()">
|
<VnSubToolbar class="q-mb-md">
|
||||||
<Teleport to="#st-data">
|
<template #st-data>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<VnSelect
|
<VnSelect
|
||||||
:label="t('itemDiary.warehouse')"
|
:label="t('itemDiary.warehouse')"
|
||||||
|
@ -233,9 +231,8 @@ async function updateWarehouse(warehouseFk) {
|
||||||
@update:model-value="fetchItemBalances"
|
@update:model-value="fetchItemBalances"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Teleport>
|
</template>
|
||||||
<Teleport to="#st-actions"> </Teleport>
|
</VnSubToolbar>
|
||||||
</template>
|
|
||||||
<QPage class="column items-center q-pa-md">
|
<QPage class="column items-center q-pa-md">
|
||||||
<QTable
|
<QTable
|
||||||
:rows="itemBalances"
|
:rows="itemBalances"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup>
|
<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 { useI18n } from 'vue-i18n';
|
||||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||||
import FetchedTags from 'components/ui/FetchedTags.vue';
|
import FetchedTags from 'components/ui/FetchedTags.vue';
|
||||||
|
@ -37,11 +37,9 @@ const fixedPrices = ref([]);
|
||||||
const warehousesOptions = ref([]);
|
const warehousesOptions = ref([]);
|
||||||
const rowsSelected = ref([]);
|
const rowsSelected = ref([]);
|
||||||
const itemFixedPriceFilterRef = ref();
|
const itemFixedPriceFilterRef = ref();
|
||||||
const params = reactive({});
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
stateStore.rightDrawer = true;
|
stateStore.rightDrawer = true;
|
||||||
params.warehouseFk = user.value.warehouseFk;
|
|
||||||
});
|
});
|
||||||
onUnmounted(() => (stateStore.rightDrawer = false));
|
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||||
|
|
||||||
|
@ -137,8 +135,17 @@ const columns = computed(() => [
|
||||||
...defaultColumnAttrs,
|
...defaultColumnAttrs,
|
||||||
columnClass: 'shrink',
|
columnClass: 'shrink',
|
||||||
component: 'select',
|
component: 'select',
|
||||||
|
|
||||||
options: warehousesOptions,
|
options: warehousesOptions,
|
||||||
|
columnFilter: {
|
||||||
|
name: 'warehouseFk',
|
||||||
|
inWhere: true,
|
||||||
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
|
options: warehousesOptions,
|
||||||
|
'option-label': 'name',
|
||||||
|
'option-value': 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
align: 'right',
|
align: 'right',
|
||||||
|
@ -210,8 +217,6 @@ const getRowUpdateInputEvents = (props, resetMinPrice, inputType = 'text') => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateMinPrice = async (value, props) => {
|
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;
|
props.row.hasMinPrice = value;
|
||||||
await upsertPrice({
|
await upsertPrice({
|
||||||
row: props.row,
|
row: props.row,
|
||||||
|
@ -220,12 +225,33 @@ 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) => {
|
const upsertPrice = async (props, resetMinPrice = false) => {
|
||||||
const { row } = props;
|
const isValid = validations({ ...props });
|
||||||
if (tableRef.value.CrudModelRef.getChanges().updates.length > 0) {
|
if (!isValid) {
|
||||||
if (resetMinPrice) row.hasMinPrice = 0;
|
return;
|
||||||
await upsertFixedPrice(row);
|
|
||||||
}
|
}
|
||||||
|
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);
|
||||||
|
tableRef.value.CrudModelRef.formData[props.rowIndex] = data;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function upsertFixedPrice(row) {
|
async function upsertFixedPrice(row) {
|
||||||
|
@ -233,13 +259,6 @@ async function upsertFixedPrice(row) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
function checkLastVisibleRow() {
|
||||||
let lastVisibleRow = null;
|
let lastVisibleRow = null;
|
||||||
|
|
||||||
|
@ -255,39 +274,18 @@ function checkLastVisibleRow() {
|
||||||
|
|
||||||
const addRow = (original = null) => {
|
const addRow = (original = null) => {
|
||||||
let copy = null;
|
let copy = null;
|
||||||
if (!original) {
|
const today = Date.vnNew();
|
||||||
const today = Date.vnNew();
|
const millisecsInDay = 86400000;
|
||||||
const millisecsInDay = 86400000;
|
const daysInWeek = 7;
|
||||||
const daysInWeek = 7;
|
const nextWeek = new Date(today.getTime() + daysInWeek * millisecsInDay);
|
||||||
const nextWeek = new Date(today.getTime() + daysInWeek * millisecsInDay);
|
|
||||||
|
|
||||||
copy = {
|
copy = {
|
||||||
id: 0,
|
id: 0,
|
||||||
started: today,
|
started: today,
|
||||||
ended: nextWeek,
|
ended: nextWeek,
|
||||||
hasMinPrice: 0,
|
hasMinPrice: 0,
|
||||||
$index: 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 };
|
return { original, copy };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -300,7 +298,7 @@ function highlightNewRow({ $index: index }) {
|
||||||
row.classList.add('highlight');
|
row.classList.add('highlight');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
row.classList.remove('highlight');
|
row.classList.remove('highlight');
|
||||||
}, 3000); // Duración de la animación en milisegundos
|
}, 3000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const openEditTableCellDialog = () => {
|
const openEditTableCellDialog = () => {
|
||||||
|
@ -411,8 +409,13 @@ function handleOnDataSave({ CrudModelRef }) {
|
||||||
url="FixedPrices/filter"
|
url="FixedPrices/filter"
|
||||||
:order="['itemFk DESC', 'name DESC']"
|
:order="['itemFk DESC', 'name DESC']"
|
||||||
save-url="FixedPrices/crud"
|
save-url="FixedPrices/crud"
|
||||||
:user-params="{ warehouseFk: user.warehouseFk }"
|
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
|
dense
|
||||||
|
:filter="{
|
||||||
|
where: {
|
||||||
|
warehouseFk: user.warehouseFk,
|
||||||
|
},
|
||||||
|
}"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
default-mode="table"
|
default-mode="table"
|
||||||
auto-load
|
auto-load
|
||||||
|
@ -426,7 +429,6 @@ function handleOnDataSave({ CrudModelRef }) {
|
||||||
disableInfiniteScroll: true,
|
disableInfiniteScroll: true,
|
||||||
}"
|
}"
|
||||||
v-model:selected="rowsSelected"
|
v-model:selected="rowsSelected"
|
||||||
:row-click="saveOnRowChange"
|
|
||||||
:create-as-dialog="false"
|
:create-as-dialog="false"
|
||||||
:create="{
|
:create="{
|
||||||
onDataSaved: handleOnDataSave,
|
onDataSaved: handleOnDataSave,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { onMounted, ref, computed, watch } from 'vue';
|
import { onMounted, ref, computed, watch, provide } from 'vue';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||||
|
@ -18,6 +18,7 @@ const dataKey = 'OrderCatalogList';
|
||||||
const arrayData = useArrayData(dataKey);
|
const arrayData = useArrayData(dataKey);
|
||||||
const store = arrayData.store;
|
const store = arrayData.store;
|
||||||
const tags = ref([]);
|
const tags = ref([]);
|
||||||
|
const itemRefs = ref({});
|
||||||
|
|
||||||
let catalogParams = {
|
let catalogParams = {
|
||||||
orderFk: route.params.id,
|
orderFk: route.params.id,
|
||||||
|
@ -74,6 +75,19 @@ watch(
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
const onItemSaved = (updatedItem) => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
scrollToItem(updatedItem.items[0].itemFk);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollToItem = async (id) => {
|
||||||
|
const element = itemRefs.value[id]?.$el;
|
||||||
|
if (element) {
|
||||||
|
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
provide('onItemSaved', onItemSaved);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -113,6 +127,7 @@ watch(
|
||||||
<CatalogItem
|
<CatalogItem
|
||||||
v-for="row in rows"
|
v-for="row in rows"
|
||||||
:key="row.id"
|
:key="row.id"
|
||||||
|
:ref="(el) => (itemRefs[row.id] = el)"
|
||||||
:item="row"
|
:item="row"
|
||||||
is-catalog
|
is-catalog
|
||||||
class="fill-icon"
|
class="fill-icon"
|
||||||
|
|
|
@ -1,41 +1,53 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import toCurrency from '../../../filters/toCurrency';
|
import toCurrency from 'src/filters/toCurrency';
|
||||||
import { ref } from 'vue';
|
import { inject, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import useNotify from 'composables/useNotify';
|
import useNotify from 'composables/useNotify';
|
||||||
import { useArrayData } from 'composables/useArrayData';
|
import { useArrayData } from 'composables/useArrayData';
|
||||||
|
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { notify } = useNotify();
|
const { notify } = useNotify();
|
||||||
const emit = defineEmits(['added']);
|
const emit = defineEmits(['added']);
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
prices: {
|
item: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const onItemSaved = inject('onItemSaved');
|
||||||
const fields = ref((props.prices || []).map((item) => ({ ...item, quantity: 0 })));
|
const prices = ref((props.item.prices || []).map((item) => ({ ...item, quantity: 0 })));
|
||||||
const descriptorData = useArrayData('orderData');
|
const descriptorData = useArrayData('orderData');
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const addToOrder = async () => {
|
const addToOrder = async () => {
|
||||||
if (isLoading.value) return;
|
if (isLoading.value) return;
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
const items = (fields.value || []).filter((item) => Number(item.quantity) > 0);
|
const items = (prices.value || []).filter((item) => Number(item.quantity) > 0);
|
||||||
await axios.post('/OrderRows/addToOrder', {
|
await axios.post('/OrderRows/addToOrder', {
|
||||||
items,
|
items,
|
||||||
orderFk: Number(route.params.id),
|
orderFk: Number(route.params.id),
|
||||||
});
|
});
|
||||||
notify(t('globals.dataSaved'), 'positive');
|
notify(t('globals.dataSaved'), 'positive');
|
||||||
emit('added');
|
await descriptorData.fetch({});
|
||||||
descriptorData.fetch({});
|
onItemSaved({ ...props, items, saved: true });
|
||||||
|
emit('added', items);
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
};
|
};
|
||||||
const canAddToOrder = () => {
|
const canAddToOrder = () => {
|
||||||
return (fields.value || []).some((item) => Number(item.quantity) > 0);
|
let canAddToOrder = (prices.value || []).some((price) => Number(price.quantity) > 0);
|
||||||
|
if (canAddToOrder) {
|
||||||
|
const excedQuantity = prices.value.reduce(
|
||||||
|
(acc, { quantity }) => acc + quantity,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
if (excedQuantity > props.item.available) {
|
||||||
|
canAddToOrder = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return canAddToOrder;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -44,30 +56,33 @@ const canAddToOrder = () => {
|
||||||
<QForm @submit="addToOrder">
|
<QForm @submit="addToOrder">
|
||||||
<QMarkupTable class="shadow-0">
|
<QMarkupTable class="shadow-0">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="item in fields" :key="item.warehouse">
|
<tr v-for="price in prices" :key="price.warehouse">
|
||||||
<td class="text-bold q-pr-md td" style="width: 35%">
|
<td class="text-bold q-pr-md td" style="width: 35%">
|
||||||
{{ item.warehouse }}
|
{{ price.warehouse }}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right" style="width: 35%">
|
<td class="text-right" style="width: 35%">
|
||||||
<span
|
<span
|
||||||
class="link"
|
class="link"
|
||||||
@click="
|
@click.shift="
|
||||||
() => {
|
() => {
|
||||||
item.quantity += item.grouping;
|
price.quantity -= price.grouping;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@click.exact="
|
||||||
|
() => {
|
||||||
|
price.quantity += price.grouping;
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{{ item.grouping }}
|
{{ price.grouping }}
|
||||||
</span>
|
</span>
|
||||||
x {{ toCurrency(item.price) }}
|
x {{ toCurrency(price.price) }}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<QInput
|
<VnInputNumber
|
||||||
v-model.number="item.quantity"
|
v-model.number="price.quantity"
|
||||||
type="number"
|
:step="price.grouping"
|
||||||
:step="item.grouping"
|
|
||||||
min="0"
|
min="0"
|
||||||
dense
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -11,7 +11,7 @@ import VnInput from 'src/components/common/VnInput.vue';
|
||||||
import useNotify from 'src/composables/useNotify.js';
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { toDateFormat } from 'src/filters/date';
|
import { toDateFormat } from 'src/filters/date';
|
||||||
import { useRole } from 'src/composables/useRole';
|
import { useAcl } from 'src/composables/useAcl';
|
||||||
import { useVnConfirm } from 'composables/useVnConfirm';
|
import { useVnConfirm } from 'composables/useVnConfirm';
|
||||||
|
|
||||||
const emit = defineEmits(['updateDiscounts', 'getMana', 'refreshTable']);
|
const emit = defineEmits(['updateDiscounts', 'getMana', 'refreshTable']);
|
||||||
|
@ -48,7 +48,7 @@ const { push } = useRouter();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { dialog } = useQuasar();
|
const { dialog } = useQuasar();
|
||||||
const { notify } = useNotify();
|
const { notify } = useNotify();
|
||||||
const role = useRole();
|
const acl = useAcl();
|
||||||
const btnDropdownRef = ref(null);
|
const btnDropdownRef = ref(null);
|
||||||
const { openConfirmationModal } = useVnConfirm();
|
const { openConfirmationModal } = useVnConfirm();
|
||||||
|
|
||||||
|
@ -58,8 +58,10 @@ const isClaimable = computed(() => {
|
||||||
if (ticket.value) {
|
if (ticket.value) {
|
||||||
const landedPlusWeek = new Date(ticket.value.landed);
|
const landedPlusWeek = new Date(ticket.value.landed);
|
||||||
landedPlusWeek.setDate(landedPlusWeek.getDate() + 7);
|
landedPlusWeek.setDate(landedPlusWeek.getDate() + 7);
|
||||||
const hasClaimManagerRole = role.hasAny('claimManager');
|
const createAfterDeadline = acl.hasAny([
|
||||||
return landedPlusWeek >= Date.vnNew() || hasClaimManagerRole;
|
{ model: 'Claim', props: 'createAfterDeadline', accessType: 'WRITE' },
|
||||||
|
]);
|
||||||
|
return landedPlusWeek >= Date.vnNew() || createAfterDeadline;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,6 +15,9 @@ const columns = computed(() => [
|
||||||
name: 'paymentDate',
|
name: 'paymentDate',
|
||||||
label: t('worker.balance.tableVisibleColumns.paymentDate'),
|
label: t('worker.balance.tableVisibleColumns.paymentDate'),
|
||||||
create: true,
|
create: true,
|
||||||
|
columnCreate: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
component: 'date',
|
component: 'date',
|
||||||
field: 'paymentDate',
|
field: 'paymentDate',
|
||||||
cardVisible: true,
|
cardVisible: true,
|
||||||
|
@ -24,6 +27,9 @@ const columns = computed(() => [
|
||||||
name: 'incomeTypeFk',
|
name: 'incomeTypeFk',
|
||||||
label: t('worker.balance.tableVisibleColumns.incomeType'),
|
label: t('worker.balance.tableVisibleColumns.incomeType'),
|
||||||
create: true,
|
create: true,
|
||||||
|
columnCreate: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
component: 'select',
|
component: 'select',
|
||||||
attrs: {
|
attrs: {
|
||||||
options: payrollComponents,
|
options: payrollComponents,
|
||||||
|
@ -37,6 +43,9 @@ const columns = computed(() => [
|
||||||
name: 'debit',
|
name: 'debit',
|
||||||
label: t('worker.balance.tableVisibleColumns.debit'),
|
label: t('worker.balance.tableVisibleColumns.debit'),
|
||||||
create: true,
|
create: true,
|
||||||
|
columnCreate: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
component: 'input',
|
component: 'input',
|
||||||
field: 'debit',
|
field: 'debit',
|
||||||
cardVisible: true,
|
cardVisible: true,
|
||||||
|
@ -46,6 +55,9 @@ const columns = computed(() => [
|
||||||
name: 'credit',
|
name: 'credit',
|
||||||
label: t('worker.balance.tableVisibleColumns.credit'),
|
label: t('worker.balance.tableVisibleColumns.credit'),
|
||||||
create: true,
|
create: true,
|
||||||
|
columnCreate: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
component: 'input',
|
component: 'input',
|
||||||
field: 'credit',
|
field: 'credit',
|
||||||
cardVisible: true,
|
cardVisible: true,
|
||||||
|
|
|
@ -206,6 +206,8 @@ const handlePhotoUpdated = (evt = false) => {
|
||||||
|
|
||||||
<i18n>
|
<i18n>
|
||||||
es:
|
es:
|
||||||
|
Go to client: Ir a cliente
|
||||||
|
Go to user: Ir al usuario
|
||||||
Click to allow the user to be disabled: Marcar para deshabilitar
|
Click to allow the user to be disabled: Marcar para deshabilitar
|
||||||
Click to exclude the user from getting disabled: Marcar para no deshabilitar
|
Click to exclude the user from getting disabled: Marcar para no deshabilitar
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|
|
@ -7,8 +7,8 @@ describe('Client basic data', () => {
|
||||||
});
|
});
|
||||||
it('Should load layout', () => {
|
it('Should load layout', () => {
|
||||||
cy.get('.q-card').should('be.visible');
|
cy.get('.q-card').should('be.visible');
|
||||||
cy.dataCy('customerPhone').filter('input').should('be.visible');
|
cy.dataCy('customerPhone').find('input').should('be.visible');
|
||||||
cy.dataCy('customerPhone').filter('input').type('123456789');
|
cy.dataCy('customerPhone').find('input').type('123456789');
|
||||||
cy.get('.q-btn-group > .q-btn--standard').click();
|
cy.get('.q-btn-group > .q-btn--standard').click();
|
||||||
cy.intercept('PATCH', '/api/Clients/1102', (req) => {
|
cy.intercept('PATCH', '/api/Clients/1102', (req) => {
|
||||||
const { body } = req;
|
const { body } = req;
|
||||||
|
|
|
@ -22,10 +22,10 @@ describe('Client list', () => {
|
||||||
const data = {
|
const data = {
|
||||||
Name: { val: `Name ${randomInt}` },
|
Name: { val: `Name ${randomInt}` },
|
||||||
'Social name': { val: `TEST ${randomInt}` },
|
'Social name': { val: `TEST ${randomInt}` },
|
||||||
'Tax number': { val: `20852${randomInt.length}3Z` },
|
'Tax number': { val: `20852${randomInt}3Z` },
|
||||||
'Web user': { val: `user_test_${randomInt}` },
|
'Web user': { val: `user_test_${randomInt}` },
|
||||||
Street: { val: `C/ STREET ${randomInt}` },
|
Street: { val: `C/ STREET ${randomInt}` },
|
||||||
Email: { val: 'user.test@1.com' },
|
Email: { val: `user.test${randomInt}@cypress.com` },
|
||||||
'Sales person': { val: 'employee', type: 'select' },
|
'Sales person': { val: 'employee', type: 'select' },
|
||||||
Location: { val: '46000, Valencia(Province one), España', type: 'select' },
|
Location: { val: '46000, Valencia(Province one), España', type: 'select' },
|
||||||
'Business type': { val: 'Otros', type: 'select' },
|
'Business type': { val: 'Otros', type: 'select' },
|
||||||
|
@ -34,7 +34,7 @@ describe('Client list', () => {
|
||||||
|
|
||||||
cy.get('.q-mt-lg > .q-btn--standard').click();
|
cy.get('.q-mt-lg > .q-btn--standard').click();
|
||||||
|
|
||||||
cy.checkNotification('Data saved');
|
cy.checkNotification('Data created');
|
||||||
cy.url().should('include', '/summary');
|
cy.url().should('include', '/summary');
|
||||||
});
|
});
|
||||||
it('Client list search client', () => {
|
it('Client list search client', () => {
|
||||||
|
|
|
@ -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("[data-cy='field-to-edit']", 'Min price');
|
||||||
|
cy.dataCy('value-to-edit').find('input').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');
|
||||||
|
});
|
||||||
|
});
|
|
@ -37,7 +37,7 @@ describe('TicketList', () => {
|
||||||
cy.dataCy('ticketSummary').should('exist');
|
cy.dataCy('ticketSummary').should('exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Client list create new client', () => {
|
it.only('Client list create new client', () => {
|
||||||
cy.dataCy('vnTableCreateBtn').should('exist');
|
cy.dataCy('vnTableCreateBtn').should('exist');
|
||||||
cy.dataCy('vnTableCreateBtn').click();
|
cy.dataCy('vnTableCreateBtn').click();
|
||||||
const data = {
|
const data = {
|
||||||
|
@ -47,7 +47,8 @@ describe('TicketList', () => {
|
||||||
Landed: { val: '01-01-2024', type: 'date' },
|
Landed: { val: '01-01-2024', type: 'date' },
|
||||||
};
|
};
|
||||||
cy.fillInForm(data);
|
cy.fillInForm(data);
|
||||||
cy.get('.q-mt-lg > .q-btn--standard').click();
|
cy.dataCy('Agency_select').click();
|
||||||
|
cy.dataCy('FormModelPopup_save').click();
|
||||||
cy.checkNotification('Data created');
|
cy.checkNotification('Data created');
|
||||||
cy.url().should('match', /\/ticket\/\d+\/summary/);
|
cy.url().should('match', /\/ticket\/\d+\/summary/);
|
||||||
});
|
});
|
||||||
|
|
|
@ -110,14 +110,14 @@ Cypress.Commands.add('fillInForm', (obj, form = '.q-form > .q-card') => {
|
||||||
const { type, val } = field;
|
const { type, val } = field;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'select':
|
case 'select':
|
||||||
cy.wrap(el).type(val);
|
cy.get(el).click();
|
||||||
cy.get('.q-menu .q-item').contains(val).click();
|
cy.get('.q-menu .q-item').contains(val).click();
|
||||||
break;
|
break;
|
||||||
case 'date':
|
case 'date':
|
||||||
cy.wrap(el).type(val.split('-').join(''));
|
cy.get(el).type(val.split('-').join(''));
|
||||||
break;
|
break;
|
||||||
case 'time':
|
case 'time':
|
||||||
cy.wrap(el).click();
|
cy.get(el).click();
|
||||||
cy.get('.q-time .q-time__clock').contains(val.h).click();
|
cy.get('.q-time .q-time__clock').contains(val.h).click();
|
||||||
cy.get('.q-time .q-time__clock').contains(val.m).click();
|
cy.get('.q-time .q-time__clock').contains(val.m).click();
|
||||||
cy.get('.q-time .q-time__link').contains(val.x).click();
|
cy.get('.q-time .q-time__link').contains(val.x).click();
|
||||||
|
|
Loading…
Reference in New Issue