Compare commits

..

38 Commits

Author SHA1 Message Date
Javier Segarra a54b0fb6c8 Merge branch '8217_mapper' of https://gitea.verdnatura.es/verdnatura/salix-front into 8217_mapper
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-09 15:42:20 +02:00
Javier Segarra ea7dfe1043 Merge branch 'dev' into 8217_mapper 2025-05-09 15:42:12 +02:00
Javier Segarra b054dd14ad Merge branch 'dev' into 8217_mapper 2025-05-09 11:09:45 +00:00
Javier Segarra 443cce305e fix: refs #8217 refactor data fetching and initialization in WorkerBasicData component
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-08 09:50:18 +02:00
Javier Segarra ef1732c6f2 Merge branch 'dev' into 8217_mapper 2025-05-07 20:22:37 +02:00
Javier Segarra 1ff9463aa9 Merge branch 'dev' into 8217_mapper
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-06 20:28:58 +00:00
Javier Segarra d78c775e20 test: add viewport
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-05 12:02:31 +00:00
Javier Segarra d4836a0652 Merge branch 'dev' into 8217_mapper
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-05 10:58:43 +00:00
Javier Segarra fa66b8dce7 Merge branch 'dev' into 8217_mapper
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-30 18:23:24 +00:00
Javier Segarra 5e2ce007fd test: refs #8217 fix test
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-30 17:36:10 +02:00
Javier Segarra 23d1791193 perf: refs #8217 changes
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-30 11:43:05 +02:00
Javier Segarra 0199557463 Merge branch 'dev' into 8217_mapper 2025-04-30 11:01:42 +02:00
Javier Segarra c7b0ccf05e Merge branch 'dev' into 8217_mapper
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-29 16:54:51 +00:00
Javier Segarra ae9cb05dda refactor: refs #8217 simplify handleResponse and create/update
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-29 13:45:39 +02:00
Javier Segarra 6e9d83da1a Merge branch 'dev' into 8217_mapper
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-28 13:49:42 +02:00
Javier Segarra 7ebbb5da9a fix: refs #8217 update FormModel to use toRef for mapper and improve data handling 2025-04-28 13:49:32 +02:00
Javier Segarra a37dacbf57 Merge branch 'dev' into 8217_mapper
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-28 12:04:07 +02:00
Javier Segarra e66f5d5b30 fix: refs #8217 adjust form styling and add id for better accessibility
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-25 10:20:25 +02:00
Javier Segarra 46c031dc14 Merge branch 'dev' into 8217_mapper
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-25 08:11:37 +00:00
Javier Segarra fe5fdf723b refactor: refs #8217 clean up code and improve readability in various components and tests
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-17 22:27:53 +02:00
Javier Segarra dae5fb5241 refactor: refs #8217 eliminate unnecessary onBeforeSave function
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-17 20:39:04 +02:00
Javier Segarra 4599686f77 Merge branch 'dev' into 8217_mapper 2025-04-17 20:18:25 +02:00
Javier Segarra 9010ccc323 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8217_mapper 2025-04-08 11:10:42 +02:00
Javier Segarra 342d0a6b1e perf: refs #8217 getDifferences fix
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-07 17:40:57 +02:00
Javier Segarra c7eadf2ceb Merge branch 'dev' into 8217_mapper
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-07 15:04:37 +02:00
Javier Segarra 5f43c4eaca perf: refs #8217 getDifferences fix 2025-04-07 15:04:26 +02:00
Javier Segarra 9b4f3c71c0 perf: refs #8217 getDifferences
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-07 14:19:39 +02:00
Javier Segarra 1ba41949d1 Merge branch 'dev' into 8217_mapper
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-07 11:43:06 +00:00
Javier Segarra bba94d04ef Merge branch 'dev' into 8217_mapper
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-03-31 22:33:20 +00:00
Javier Segarra 87ddfcd640 Merge branch 'dev' into 8217_mapper 2025-03-27 19:50:55 +00:00
Javier Segarra f059a8abff Merge branch 'dev' into 8217_mapper
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-03-25 23:12:37 +00:00
Javier Segarra 29957e62e3 Merge branch 'dev' into 8217_mapper
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-03-25 11:32:02 +01:00
Javier Segarra 9fb2961022 test: refs #8217 add TEST 2025-03-25 11:31:45 +01:00
Javier Segarra 6407c2e582 fix: refs #8217 handle null obj2 in getDifferences filter
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-03-25 08:55:34 +01:00
Javier Segarra 8050c75a0c Revert "fix: refs #8217 update formModel template to ensure unique ID and handle null obj2 in getDifferences filter"
This reverts commit 6c573f61ca.
2025-03-25 08:55:02 +01:00
Javier Segarra c83fa12f66 Merge branch 'dev' of https: refs #8217//gitea.verdnatura.es/verdnatura/salix-front into 8217_mapper 2025-03-25 08:54:45 +01:00
Javier Segarra 6c573f61ca fix: refs #8217 update formModel template to ensure unique ID and handle null obj2 in getDifferences filter 2025-03-25 08:54:05 +01:00
Javier Segarra 0690fb6c25 feat: refs #8217 implement onBeforeSave function for form data processing
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-03-24 15:08:18 +01:00
12 changed files with 168 additions and 105 deletions

View File

@ -1,6 +1,15 @@
<script setup>
import axios from 'axios';
import { onMounted, onUnmounted, computed, ref, watch, nextTick, useAttrs } from 'vue';
import {
onMounted,
onUnmounted,
computed,
ref,
watch,
nextTick,
useAttrs,
toRef,
} from 'vue';
import { onBeforeRouteLeave, useRouter, useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
@ -12,7 +21,7 @@ import SkeletonForm from 'components/ui/SkeletonForm.vue';
import VnConfirm from './ui/VnConfirm.vue';
import { tMobile } from 'src/composables/tMobile';
import { useArrayData } from 'src/composables/useArrayData';
import { getDifferences, getUpdatedValues } from 'src/filters';
import { onBeforeSave } from 'src/filters';
const { push } = useRouter();
const quasar = useQuasar();
const state = useState();
@ -67,7 +76,7 @@ const $props = defineProps({
},
mapper: {
type: Function,
default: null,
default: onBeforeSave,
},
clearStoreOnUnmount: {
type: Boolean,
@ -223,25 +232,8 @@ async function fetch() {
}
}
async function save() {
if ($props.observeFormChanges && !hasChanges.value)
return notify('globals.noChanges', 'negative');
isLoading.value = true;
try {
formData.value = trimData(formData.value);
const body = $props.mapper
? $props.mapper(formData.value, originalData.value)
: formData.value;
const method = $props.urlCreate ? 'post' : 'patch';
const url =
$props.urlCreate || $props.urlUpdate || $props.url || arrayData.store.url;
const response = await Promise.resolve(
$props.saveFn ? $props.saveFn(body) : axios[method](url, body),
);
if ($props.urlCreate) notify('globals.dataCreated', 'positive');
async function handleResponse(promise) {
const [response] = await Promise.all([promise]);
updateAndEmit('onDataSaved', {
val: formData.value,
res: response?.data,
@ -249,6 +241,38 @@ async function save() {
});
if ($props.reload) await arrayData.fetch({});
hasChanges.value = false;
}
async function create() {
const promise = $props.saveFn
? $props.saveFn(formData.value)
: axios.post($props.urlCreate, formData.value);
await handleResponse(promise);
notify('globals.dataCreated', 'positive');
}
async function update() {
const body = $props.mapper
? $props.mapper(originalData.value, formData.value)
: formData.value;
const url = $props.urlUpdate || $props.url || arrayData.store.url;
const promise = $props.saveFn ? $props.saveFn(body) : axios.patch(url, body);
await handleResponse(promise);
}
async function save() {
if ($props.observeFormChanges && !hasChanges.value)
return notify('globals.noChanges', 'negative');
isLoading.value = true;
try {
formData.value = trimData(formData.value);
if ($props.urlCreate) {
await create();
} else {
await update();
}
} finally {
isLoading.value = false;
}
@ -297,12 +321,7 @@ function trimData(data) {
}
return data;
}
function onBeforeSave(formData, originalData) {
return getUpdatedValues(
Object.keys(getDifferences(formData, originalData)),
formData,
);
}
async function onKeyup(evt) {
if (evt.key === 'Enter' && !$props.preventSubmit) {
const input = evt.target;
@ -333,16 +352,15 @@ defineExpose({
<template>
<div class="column items-center full-width">
<QForm
id="formModel"
v-on="$attrs"
ref="myForm"
v-if="formData"
@submit.prevent="save"
@keyup.prevent="onKeyup"
@reset="reset"
class="q-pa-md"
class="full-width q-pa-md"
:style="maxWidth ? 'max-width: ' + maxWidth : ''"
id="formModel"
:mapper="onBeforeSave"
>
<QCard>
<slot
@ -440,9 +458,6 @@ defineExpose({
.q-notifications {
color: black;
}
#formModel {
width: 100%;
}
.q-card {
padding: 32px;

View File

@ -115,6 +115,7 @@ describe('CrudModel', () => {
expect(result).toEqual({
a: null,
b: 4,
c: 3,
d: 5,
});
});

View File

@ -3,11 +3,17 @@ import { createWrapper } from 'app/test/vitest/helper';
import { default as axios } from 'axios';
import FormModel from 'src/components/FormModel.vue';
import { useState } from 'src/composables/useState';
describe('FormModel', () => {
const model = 'mockModel';
const url = 'mockUrl';
const formInitialData = { mockKey: 'mockVal' };
let state;
beforeEach(() => {
state = useState();
state.set(model, formInitialData);
});
describe('modelValue', () => {
it('should use the provided model', () => {
@ -94,30 +100,39 @@ describe('FormModel', () => {
expect(vm.hasChanges).toBe(false);
});
it('should call axios.patch with the right data', async () => {
const spy = vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} });
const { vm } = mount({ propsData: { url, model } });
it('should call axios.post with the right data', async () => {
const spy = vi.spyOn(axios, 'post').mockResolvedValue({ data: {} });
const urlCreate = 'mockUrlCreate';
const { vm } = mount({ propsData: { url, urlCreate, model } });
vm.formData = {};
await vm.$nextTick();
vm.formData = { mockKey: 'newVal' };
const formData = { mockKey: 'newVal', mockKey2: 'newVal2' };
vm.formData = formData;
await vm.$nextTick();
await vm.save();
expect(spy).toHaveBeenCalled();
expect(spy).toHaveBeenCalledWith(urlCreate, formData);
vm.formData.mockKey = 'mockVal';
});
it('should call axios.post with the right data', async () => {
const spy = vi.spyOn(axios, 'post').mockResolvedValue({ data: {} });
it('should call axios.patch with the right data', async () => {
const spy = vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} });
const { vm } = mount({
propsData: { url, model, formInitialData, urlCreate: 'mockUrlCreate' },
propsData: {
url,
model,
formInitialData,
},
});
await vm.$nextTick();
vm.formData.mockKey = 'newVal';
await vm.$nextTick();
await vm.save();
expect(spy).toHaveBeenCalled();
expect(spy).toHaveBeenCalledWith(url, { mockKey: 'newVal' });
vm.formData.mockKey = 'mockVal';
});

View File

@ -0,0 +1,41 @@
import getDifferences from '../getDifferences';
describe('getDifferences', () => {
it('should handle empty initialData', () => {
const initialData = {};
const formData = { name: 'John' };
expect(getDifferences(initialData, formData)).toEqual({ name: 'John' });
});
it('should detect when formData has a key not present in initialData', () => {
const initialData = { age: 30 };
const formData = { name: 'John' };
expect(getDifferences(initialData, formData)).toEqual({ age: 30, name: 'John' });
});
it('should detect when formData has different value for existing key', () => {
const initialData = { name: 'John', age: 30 };
const formData = { name: 'Jane', age: 30 };
expect(getDifferences(initialData, formData)).toEqual({ name: 'Jane' });
});
it('should detect when formData has null value for existing key', () => {
const initialData = { name: 'John' };
const formData = { name: null };
expect(getDifferences(initialData, formData)).toEqual({ name: null });
});
it('should handle missing keys in formData', () => {
const initialData = { name: 'John', age: 30 };
const formData = { name: 'John' };
expect(getDifferences(initialData, formData)).toEqual({ age: 30 });
});
it('should handle complex objects', () => {
const initialData = { user: { name: 'John', age: 30 } };
const formData = { user: { name: 'John', age: 31 } };
expect(getDifferences(initialData, formData)).toEqual({
user: { name: 'John', age: 31 },
});
});
});

View File

@ -1,19 +1,23 @@
export default function getDifferences(obj1, obj2) {
export default function getDifferences(initialData = {}, formData = {}) {
const diff = {};
delete obj1.$index;
delete obj2.$index;
delete initialData?.$index;
delete formData?.$index;
for (const key in obj1) {
if (obj2[key] && JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])) {
diff[key] = obj2[key];
// Añadimos los valores que están en initialData
for (const key in initialData) {
if (!Object.prototype.hasOwnProperty.call(formData, key)) {
// Caso 1: initialData tiene una clave que no tiene formData
diff[key] = initialData[key];
} else if (JSON.stringify(initialData[key]) !== JSON.stringify(formData[key])) {
// Caso 2 y 3: valores diferentes o null en formData
diff[key] = formData[key];
}
}
for (const key in obj2) {
if (
obj1[key] === undefined ||
JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])
) {
diff[key] = obj2[key];
// Añadimos las claves nuevas de formData
for (const key in formData) {
if (!Object.prototype.hasOwnProperty.call(initialData, key)) {
diff[key] = formData[key];
}
}

View File

@ -3,6 +3,7 @@ import toDate from './toDate';
import toDateString from './toDateString';
import toDateHourMin from './toDateHourMin';
import toDateHourMinSec from './toDateHourMinSec';
import onBeforeSave from './onBeforeSave';
import toRelativeDate from './toRelativeDate';
import toCurrency from './toCurrency';
import toPercentage from './toPercentage';
@ -20,6 +21,7 @@ import isDialogOpened from './isDialogOpened';
import toCelsius from './toCelsius';
export {
onBeforeSave,
getUpdatedValues,
getDifferences,
isDialogOpened,

View File

@ -0,0 +1,9 @@
import getDifferences from './getDifferences';
import getUpdatedValues from './getUpdatedValues';
export default function onBeforeSave(originalData, formData) {
return getUpdatedValues(
Object.keys(getDifferences(originalData, formData)),
formData,
);
}

View File

@ -15,13 +15,6 @@ import VnAvatar from 'src/components/ui/VnAvatar.vue';
const route = useRoute();
const { t } = useI18n();
const workersOptions = ref([]);
function onBeforeSave(formData, originalData) {
return getUpdatedValues(
Object.keys(getDifferences(formData, originalData)),
formData,
);
}
</script>
<template>
<FetchData
@ -35,7 +28,6 @@ function onBeforeSave(formData, originalData) {
model="Claim"
:go-to="`/claim/${route.params.id}/notes`"
:url-update="`Claims/updateClaim/${route.params.id}`"
:mapper="onBeforeSave"
auto-load
>
<template #form="{ data, validate }">

View File

@ -8,37 +8,12 @@ import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import { getDifferences, getUpdatedValues } from 'src/filters';
const route = useRoute();
const { t } = useI18n();
const businessTypes = ref([]);
const contactChannels = ref([]);
const handleSalesModelValue = (val) => {
if (!val) val = '';
return {
or: [
{ id: val },
{ name: val },
{ nickname: { like: '%' + val + '%' } },
{ code: { like: `${val}%` } },
],
};
};
const exprBuilder = (param, value) => {
return {
and: [{ active: { neq: false } }, handleSalesModelValue(value)],
};
};
function onBeforeSave(formData, originalData) {
return getUpdatedValues(
Object.keys(getDifferences(formData, originalData)),
formData,
);
}
</script>
<template>
<FetchData
@ -52,12 +27,7 @@ function onBeforeSave(formData, originalData) {
@on-fetch="(data) => (businessTypes = data)"
auto-load
/>
<FormModel
:url-update="`Clients/${route.params.id}`"
auto-load
:mapper="onBeforeSave"
model="Customer"
>
<FormModel :url-update="`Clients/${route.params.id}`" auto-load model="Customer">
<template #form="{ data, validate }">
<VnRow>
<VnInput

View File

@ -13,7 +13,6 @@ import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnLocation from 'src/components/common/VnLocation.vue';
import VnCheckbox from 'src/components/common/VnCheckbox.vue';
import { getDifferences, getUpdatedValues } from 'src/filters';
import VnConfirm from 'src/components/ui/VnConfirm.vue';
const quasar = useQuasar();
@ -31,12 +30,6 @@ function handleLocation(data, location) {
data.provinceFk = provinceFk;
data.countryFk = countryFk;
}
function onBeforeSave(formData, originalData) {
return getUpdatedValues(
Object.keys(getDifferences(formData, originalData)),
formData,
);
}
async function checkEtChanges(data, _, originalData) {
const equalizatedHasChanged = originalData.isEqualizated != data.isEqualizated;
@ -75,7 +68,6 @@ async function acceptPropagate({ isEqualizated }) {
:url-update="`Clients/${route.params.id}/updateFiscalData`"
auto-load
model="Customer"
:mapper="onBeforeSave"
observe-form-changes
@on-data-saved="checkEtChanges"
>

View File

@ -39,6 +39,7 @@ const entityId = computed(() => {
where: { itemFk: entityId },
}"
@on-fetch="(data) => (itemBotanicalsForm = data)"
:mapper="(_, data) => data"
>
<template #form="{ data }">
<VnRow>

View File

@ -1,6 +1,7 @@
<script setup>
import { ref, nextTick } from 'vue';
import { ref, onBeforeMount } from 'vue';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import FetchData from 'components/FetchData.vue';
import FormModel from 'src/components/FormModel.vue';
@ -20,11 +21,30 @@ const maritalStatus = [
{ code: 'S', name: t('Single') },
];
const route = useRoute();
async function addAdvancedData(data) {
const advanced = await useAdvancedSummary('Workers', route.params.id);
data.value = { ...data.value, ...advanced };
nextTick(() => (form.value.hasChanges = false));
return { ...data, ...advanced };
}
const initialData = ref(null);
onBeforeMount(async () => {
let baseData = {};
try {
const { data } = await axios.get(`Workers/summary`, {
params: {
filter: JSON.stringify({
where: { id: route.params.id },
}),
},
});
baseData = data[0];
} catch (e) {
baseData = {};
}
const advancedData = await addAdvancedData(baseData);
initialData.value = advancedData;
});
</script>
<template>
<FetchData
@ -40,11 +60,12 @@ async function addAdvancedData(data) {
auto-load
/>
<FormModel
v-if="initialData"
ref="form"
:form-initial-data="initialData"
:url-update="`Workers/${$route.params.id}`"
auto-load
:model
@on-fetch="(data, res, old, formData) => addAdvancedData(formData)"
:reload="false"
>
<template #form="{ data }">
<VnRow>