Compare commits

..

No commits in common. "8217_mapper" and "dev" have entirely different histories.

11 changed files with 100 additions and 136 deletions

View File

@ -12,7 +12,7 @@ import SkeletonForm from 'components/ui/SkeletonForm.vue';
import VnConfirm from './ui/VnConfirm.vue'; import VnConfirm from './ui/VnConfirm.vue';
import { tMobile } from 'src/composables/tMobile'; import { tMobile } from 'src/composables/tMobile';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { onBeforeSave } from 'src/filters'; import { getDifferences, getUpdatedValues } from 'src/filters';
const { push } = useRouter(); const { push } = useRouter();
const quasar = useQuasar(); const quasar = useQuasar();
const state = useState(); const state = useState();
@ -67,7 +67,7 @@ const $props = defineProps({
}, },
mapper: { mapper: {
type: Function, type: Function,
default: onBeforeSave, default: null,
}, },
clearStoreOnUnmount: { clearStoreOnUnmount: {
type: Boolean, type: Boolean,
@ -223,49 +223,32 @@ async function fetch() {
} }
} }
async function handleResponse(response, showCreatedNotification = false) {
if (showCreatedNotification) {
notify('globals.dataCreated', 'positive');
}
updateAndEmit('onDataSaved', {
val: formData.value,
res: response?.data,
old: originalData.value,
});
if ($props.reload) await arrayData.fetch({});
hasChanges.value = false;
}
async function create() {
formData.value = trimData(formData.value);
const url = $props.urlCreate;
const response = await Promise.resolve(
$props.saveFn ? $props.saveFn(formData.value) : axios.post(url, formData.value),
);
await handleResponse(response, true);
}
async function update() {
formData.value = trimData(formData.value);
const body = $props.mapper(originalData.value, formData.value);
const url = $props.urlUpdate || $props.url || arrayData.store.url;
const response = await Promise.resolve(
$props.saveFn ? $props.saveFn(body) : axios.patch(url, body),
);
await handleResponse(response);
}
async function save() { async function save() {
if ($props.observeFormChanges && !hasChanges.value) if ($props.observeFormChanges && !hasChanges.value)
return notify('globals.noChanges', 'negative'); return notify('globals.noChanges', 'negative');
isLoading.value = true; isLoading.value = true;
try { try {
if ($props.urlCreate != null) { formData.value = trimData(formData.value);
await create(); const body = $props.mapper
} else { ? $props.mapper(formData.value, originalData.value)
await update(); : 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');
updateAndEmit('onDataSaved', {
val: formData.value,
res: response?.data,
old: originalData.value,
});
if ($props.reload) await arrayData.fetch({});
hasChanges.value = false;
} finally { } finally {
isLoading.value = false; isLoading.value = false;
} }
@ -314,7 +297,12 @@ function trimData(data) {
} }
return data; return data;
} }
function onBeforeSave(formData, originalData) {
return getUpdatedValues(
Object.keys(getDifferences(formData, originalData)),
formData,
);
}
async function onKeyup(evt) { async function onKeyup(evt) {
if (evt.key === 'Enter' && !$props.preventSubmit) { if (evt.key === 'Enter' && !$props.preventSubmit) {
const input = evt.target; const input = evt.target;
@ -353,6 +341,8 @@ defineExpose({
@reset="reset" @reset="reset"
class="q-pa-md" class="q-pa-md"
:style="maxWidth ? 'max-width: ' + maxWidth : ''" :style="maxWidth ? 'max-width: ' + maxWidth : ''"
id="formModel"
:mapper="onBeforeSave"
> >
<QCard> <QCard>
<slot <slot

View File

@ -115,7 +115,6 @@ describe('CrudModel', () => {
expect(result).toEqual({ expect(result).toEqual({
a: null, a: null,
b: 4, b: 4,
c: 3,
d: 5, d: 5,
}); });
}); });
@ -242,7 +241,7 @@ describe('CrudModel', () => {
await vm.saveChanges(data); await vm.saveChanges(data);
expect(postMock).toHaveBeenCalledWith(`${vm.url}/crud`, data); expect(postMock).toHaveBeenCalledWith(vm.url + '/crud', data);
expect(vm.isLoading).toBe(false); expect(vm.isLoading).toBe(false);
expect(vm.hasChanges).toBe(false); expect(vm.hasChanges).toBe(false);
expect(vm.originalData).toEqual(JSON.parse(JSON.stringify(vm.formData))); expect(vm.originalData).toEqual(JSON.parse(JSON.stringify(vm.formData)));

View File

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

View File

@ -21,7 +21,7 @@ watch(
(newValue) => { (newValue) => {
if (!modelValue.value) return; if (!modelValue.value) return;
modelValue.value = formatLocation(newValue) ?? null; modelValue.value = formatLocation(newValue) ?? null;
}, }
); );
const mixinRules = [requiredFieldRule]; const mixinRules = [requiredFieldRule];
@ -45,7 +45,7 @@ const formatLocation = (obj, properties = locationProperties) => {
}); });
const filteredParts = parts.filter( const filteredParts = parts.filter(
(part) => part !== null && part !== undefined && part !== '', (part) => part !== null && part !== undefined && part !== ''
); );
return filteredParts.join(', '); return filteredParts.join(', ');

View File

@ -1,41 +0,0 @@
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,23 +1,19 @@
export default function getDifferences(initialData = {}, formData = {}) { export default function getDifferences(obj1, obj2) {
const diff = {}; let diff = {};
delete initialData?.$index; delete obj1.$index;
delete formData?.$index; delete obj2.$index;
// Añadimos los valores que están en initialData for (let key in obj1) {
for (const key in initialData) { if (obj2[key] && JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])) {
if (!Object.prototype.hasOwnProperty.call(formData, key)) { diff[key] = obj2[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 (let key in obj2) {
// Añadimos las claves nuevas de formData if (
for (const key in formData) { obj1[key] === undefined ||
if (!Object.prototype.hasOwnProperty.call(initialData, key)) { JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])
diff[key] = formData[key]; ) {
diff[key] = obj2[key];
} }
} }

View File

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

View File

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

View File

@ -8,12 +8,37 @@ import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import { getDifferences, getUpdatedValues } from 'src/filters';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const businessTypes = ref([]); const businessTypes = ref([]);
const contactChannels = 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> </script>
<template> <template>
<FetchData <FetchData
@ -27,7 +52,12 @@ const contactChannels = ref([]);
@on-fetch="(data) => (businessTypes = data)" @on-fetch="(data) => (businessTypes = data)"
auto-load auto-load
/> />
<FormModel :url-update="`Clients/${route.params.id}`" auto-load model="Customer"> <FormModel
:url-update="`Clients/${route.params.id}`"
auto-load
:mapper="onBeforeSave"
model="Customer"
>
<template #form="{ data, validate }"> <template #form="{ data, validate }">
<VnRow> <VnRow>
<VnInput <VnInput

View File

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