diff --git a/src/components/FormModel.vue b/src/components/FormModel.vue
index 5a59f301e..a92ba29ee 100644
--- a/src/components/FormModel.vue
+++ b/src/components/FormModel.vue
@@ -134,7 +134,8 @@ onMounted(async () => {
if (!$props.formInitialData) {
if ($props.autoLoad && $props.url) await fetch();
- else if (arrayData.store.data) updateAndEmit('onFetch', arrayData.store.data);
+ else if (arrayData.store.data)
+ updateAndEmit('onFetch', { val: arrayData.store.data });
}
if ($props.observeFormChanges) {
watch(
@@ -154,7 +155,7 @@ onMounted(async () => {
if (!$props.url)
watch(
() => arrayData.store.data,
- (val) => updateAndEmit('onFetch', val),
+ (val) => updateAndEmit('onFetch', { val }),
);
watch(
@@ -200,7 +201,7 @@ async function fetch() {
});
if (Array.isArray(data)) data = data[0] ?? {};
- updateAndEmit('onFetch', data);
+ updateAndEmit('onFetch', { val: data });
} catch (e) {
state.set(modelValue, {});
throw e;
@@ -228,7 +229,11 @@ async function save(prevent = false) {
if ($props.urlCreate) notify('globals.dataCreated', 'positive');
- updateAndEmit('onDataSaved', formData.value, response?.data);
+ updateAndEmit('onDataSaved', {
+ val: formData.value,
+ res: response?.data,
+ old: originalData.value,
+ });
if ($props.reload) await arrayData.fetch({});
hasChanges.value = false;
} finally {
@@ -242,8 +247,7 @@ async function saveAndGo() {
}
function reset() {
- formData.value = JSON.parse(JSON.stringify(originalData.value));
- updateAndEmit('onFetch', originalData.value);
+ updateAndEmit('onFetch', { val: originalData.value });
if ($props.observeFormChanges) {
hasChanges.value = false;
isResetting.value = true;
@@ -265,11 +269,11 @@ function filter(value, update, filterOptions) {
);
}
-function updateAndEmit(evt, val, res) {
+function updateAndEmit(evt, { val, res, old } = { val: null, res: null, old: null }) {
state.set(modelValue, val);
if (!$props.url) arrayData.store.data = val;
- emit(evt, state.get(modelValue), res);
+ emit(evt, state.get(modelValue), res, old);
}
function trimData(data) {
diff --git a/src/components/TicketProblems.vue b/src/components/TicketProblems.vue
index a24735a5f..17d9602af 100644
--- a/src/components/TicketProblems.vue
+++ b/src/components/TicketProblems.vue
@@ -1,4 +1,6 @@
@@ -10,7 +12,8 @@ defineProps({ row: { type: Object, required: true } });
size="xs"
>
- {{ $t('salesTicketsTable.risk') }}: {{ row.risk - row.credit }}
+ {{ $t('salesTicketsTable.risk') }}:
+ {{ toCurrency(row.risk - row.credit) }}
{
}
});
-onBeforeRouteUpdate(async (to, from) => {
- const id = to.params.id;
- if (id !== from.params.id) await fetch(id, true);
-});
-
-async function fetch(id, append = false) {
- const regex = /\/(\d+)/;
- if (props.idInWhere) arrayData.store.filter.where = { id };
- else if (!regex.test(props.url)) arrayData.store.url = `${props.url}/${id}`;
- else arrayData.store.url = props.url.replace(regex, `/${id}`);
- await arrayData.fetch({ append, updateRouter: false });
+if (props.baseUrl) {
+ onBeforeRouteUpdate(async (to, from) => {
+ if (hasRouteParam(to.params)) {
+ const { matched } = router.currentRoute.value;
+ const { name } = matched.at(-3);
+ if (name) {
+ router.push({ name, params: to.params });
+ }
+ }
+ if (to.params.id !== from.params.id) {
+ arrayData.store.url = `${props.baseUrl}/${to.params.id}`;
+ await arrayData.fetch({ append: false, updateRouter: false });
+ }
+ });
+}
+function hasRouteParam(params, valueToCheck = ':addressId') {
+ return Object.values(params).includes(valueToCheck);
}
diff --git a/src/components/common/VnLocation.vue b/src/components/common/VnLocation.vue
index 3ede24274..5028e876d 100644
--- a/src/components/common/VnLocation.vue
+++ b/src/components/common/VnLocation.vue
@@ -85,6 +85,7 @@ const handleModelValue = (data) => {
:tooltip="t('Create new location')"
:rules="mixinRules"
:lazy-rules="true"
+ required
>
{
- return useRequired($attrs).isRequired;
-});
-const requiredFieldRule = computed(() => {
- return useRequired($attrs).requiredFieldRule;
-});
+const { isRequired, requiredFieldRule } = useRequired($attrs);
const $props = defineProps({
modelValue: {
diff --git a/src/composables/__tests__/useRequired.spec.js b/src/composables/__tests__/useRequired.spec.js
new file mode 100644
index 000000000..e035a80c6
--- /dev/null
+++ b/src/composables/__tests__/useRequired.spec.js
@@ -0,0 +1,66 @@
+import { describe, it, expect, vi } from 'vitest';
+import { useRequired } from '../useRequired';
+
+vi.mock('../useValidator', () => ({
+ useValidator: () => ({
+ validations: () => ({
+ required: vi.fn((isRequired, val) => {
+ if (!isRequired) return true;
+ return val !== null && val !== undefined && val !== '';
+ }),
+ }),
+ }),
+}));
+
+describe('useRequired', () => {
+ it('should detect required when attr is boolean true', () => {
+ const attrs = { required: true };
+ const { isRequired } = useRequired(attrs);
+ expect(isRequired).toBe(true);
+ });
+
+ it('should detect required when attr is boolean false', () => {
+ const attrs = { required: false };
+ const { isRequired } = useRequired(attrs);
+ expect(isRequired).toBe(false);
+ });
+
+ it('should detect required when attr exists without value', () => {
+ const attrs = { required: '' };
+ const { isRequired } = useRequired(attrs);
+ expect(isRequired).toBe(true);
+ });
+
+ it('should return false when required attr does not exist', () => {
+ const attrs = { someOtherAttr: 'value' };
+ const { isRequired } = useRequired(attrs);
+ expect(isRequired).toBe(false);
+ });
+
+ describe('requiredFieldRule', () => {
+ it('should validate required field with value', () => {
+ const attrs = { required: true };
+ const { requiredFieldRule } = useRequired(attrs);
+ expect(requiredFieldRule('some value')).toBe(true);
+ });
+
+ it('should validate required field with empty value', () => {
+ const attrs = { required: true };
+ const { requiredFieldRule } = useRequired(attrs);
+ expect(requiredFieldRule('')).toBe(false);
+ });
+
+ it('should pass validation when field is not required', () => {
+ const attrs = { required: false };
+ const { requiredFieldRule } = useRequired(attrs);
+ expect(requiredFieldRule('')).toBe(true);
+ });
+
+ it('should handle null and undefined values', () => {
+ const attrs = { required: true };
+ const { requiredFieldRule } = useRequired(attrs);
+ expect(requiredFieldRule(null)).toBe(false);
+ expect(requiredFieldRule(undefined)).toBe(false);
+ });
+ });
+});
diff --git a/src/composables/useRequired.js b/src/composables/useRequired.js
index d211b96b4..4e84b9e48 100644
--- a/src/composables/useRequired.js
+++ b/src/composables/useRequired.js
@@ -2,14 +2,10 @@ import { useValidator } from 'src/composables/useValidator';
export function useRequired($attrs) {
const { validations } = useValidator();
- const hasRequired = Object.keys($attrs).includes('required');
- let isRequired = false;
- if (hasRequired) {
- const required = $attrs['required'];
- if (typeof required === 'boolean') {
- isRequired = required;
- }
- }
+ const isRequired =
+ typeof $attrs['required'] === 'boolean'
+ ? $attrs['required']
+ : Object.keys($attrs).includes('required');
const requiredFieldRule = (val) => validations().required(isRequired, val);
return {
diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml
index 762515ce5..6bf3affc0 100644
--- a/src/i18n/locale/es.yml
+++ b/src/i18n/locale/es.yml
@@ -839,6 +839,7 @@ supplier:
verified: Verificado
isActive: Está activo
billingData: Forma de pago
+ financialData: Datos financieros
payDeadline: Plazo de pago
payDay: Día de pago
account: Cuenta
diff --git a/src/pages/Customer/Card/CustomerConsumption.vue b/src/pages/Customer/Card/CustomerConsumption.vue
index eef9d55b5..f3949bb32 100644
--- a/src/pages/Customer/Card/CustomerConsumption.vue
+++ b/src/pages/Customer/Card/CustomerConsumption.vue
@@ -232,7 +232,6 @@ const updateDateParams = (value, params) => {
:include="'category'"
:sortBy="'name ASC'"
dense
- @update:model-value="(data) => updateDateParams(data, params)"
>
@@ -254,7 +253,6 @@ const updateDateParams = (value, params) => {
:fields="['id', 'name']"
:sortBy="'name ASC'"
dense
- @update:model-value="(data) => updateDateParams(data, params)"
/>
acceptPropagate(data),
+ },
+ });
+ } else if (equalizatedHasChanged) {
+ await acceptPropagate(data);
+ }
+}
+
+async function acceptPropagate({ isEqualizated }) {
+ await axios.patch(`Clients/${route.params.id}/addressesPropagateRe`, {
+ isEqualizated,
+ });
+ notify(t('Equivalent tax spreaded'), 'warning');
+}
@@ -45,6 +76,8 @@ function onBeforeSave(formData, originalData) {
auto-load
model="Customer"
:mapper="onBeforeSave"
+ observe-form-changes
+ @on-data-saved="checkEtChanges"
>
@@ -180,6 +213,9 @@ es:
whenActivatingIt: Al activarlo, no informar el código del país en el campo nif
inOrderToInvoice: Para facturar no se consulta este campo, sino el RE de consignatario. Al modificar este campo si no esta marcada la casilla Facturar por consignatario, se propagará automaticamente el cambio a todos lo consignatarios, en caso contrario preguntará al usuario si quiere o no propagar
Daily invoice: Facturación diaria
+ Equivalent tax spreaded: Recargo de equivalencia propagado
+ You changed the equalization tax: Has cambiado el recargo de equivalencia
+ Do you want to spread the change?: ¿Deseas propagar el cambio a sus consignatarios?
en:
onlyLetters: Only letters, numbers and spaces can be used
whenActivatingIt: When activating it, do not enter the country code in the ID field
diff --git a/src/pages/Customer/Card/CustomerSummary.vue b/src/pages/Customer/Card/CustomerSummary.vue
index d2eb125d7..324da0771 100644
--- a/src/pages/Customer/Card/CustomerSummary.vue
+++ b/src/pages/Customer/Card/CustomerSummary.vue
@@ -270,7 +270,7 @@ const sumRisk = ({ clientRisks }) => {
handleLocation(data, location)"
/>
diff --git a/src/pages/Customer/components/CustomerAddressEdit.vue b/src/pages/Customer/components/CustomerAddressEdit.vue
index 5b36650f7..af1b9c160 100644
--- a/src/pages/Customer/components/CustomerAddressEdit.vue
+++ b/src/pages/Customer/components/CustomerAddressEdit.vue
@@ -96,11 +96,11 @@ const updateObservations = async (payload) => {
await axios.post('AddressObservations/crud', payload);
notes.value = [];
deletes.value = [];
- toCustomerAddress();
};
async function updateAll({ data, payload }) {
await updateObservations(payload);
await updateAddress(data);
+ toCustomerAddress();
}
function getPayload() {
return {
@@ -137,15 +137,12 @@ async function handleDialog(data) {
.onOk(async () => {
await updateAddressTicket();
await updateAll(body);
- toCustomerAddress();
})
.onCancel(async () => {
await updateAll(body);
- toCustomerAddress();
});
} else {
- updateAll(body);
- toCustomerAddress();
+ await updateAll(body);
}
}
diff --git a/src/pages/InvoiceOut/Card/InvoiceOutSummary.vue b/src/pages/InvoiceOut/Card/InvoiceOutSummary.vue
index 3ceb447dd..161f2ab45 100644
--- a/src/pages/InvoiceOut/Card/InvoiceOutSummary.vue
+++ b/src/pages/InvoiceOut/Card/InvoiceOutSummary.vue
@@ -125,7 +125,7 @@ const ticketsColumns = ref([
:value="toDate(invoiceOut.issued)"
/>
diff --git a/src/pages/InvoiceOut/locale/en.yml b/src/pages/InvoiceOut/locale/en.yml
index cb0dfdca7..ee6ba57e6 100644
--- a/src/pages/InvoiceOut/locale/en.yml
+++ b/src/pages/InvoiceOut/locale/en.yml
@@ -19,6 +19,7 @@ invoiceOut:
summary:
issued: Issued
dued: Due
+ expirationDate: Expiration date
booked: Booked
taxBreakdown: Tax breakdown
taxableBase: Taxable base
diff --git a/src/pages/InvoiceOut/locale/es.yml b/src/pages/InvoiceOut/locale/es.yml
index a35c33c4e..a059ce18d 100644
--- a/src/pages/InvoiceOut/locale/es.yml
+++ b/src/pages/InvoiceOut/locale/es.yml
@@ -19,6 +19,7 @@ invoiceOut:
summary:
issued: Fecha
dued: Fecha límite
+ expirationDate: Fecha vencimiento
booked: Contabilizada
taxBreakdown: Desglose impositivo
taxableBase: Base imp.
diff --git a/test/cypress/integration/client/clientAddress.spec.js b/test/cypress/integration/client/clientAddress.spec.js
index db876b64b..434180047 100644
--- a/test/cypress/integration/client/clientAddress.spec.js
+++ b/test/cypress/integration/client/clientAddress.spec.js
@@ -3,11 +3,46 @@ describe('Client consignee', () => {
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');
- cy.visit('#/customer/1110/address', {
- timeout: 5000,
- });
+ cy.visit('#/customer/1107/address');
+ cy.domContentLoad();
});
it('Should load layout', () => {
cy.get('.q-card').should('be.visible');
});
+
+ it('check as equalizated', function () {
+ cy.get('.q-card__section > .address-card').then(($el) => {
+ let addressCards_before = $el.length;
+
+ cy.get('.q-page-sticky > div > .q-btn').click();
+ const addressName = 'test';
+ cy.dataCy('Consignee_input').type(addressName);
+ cy.dataCy('Location_select').click();
+ cy.get('[role="listbox"] .q-item:nth-child(1)').click();
+ cy.dataCy('Street address_input').type('TEST ADDRESS');
+ cy.get('.q-btn-group > .q-btn--standard').click();
+ cy.location('href').should('contain', '#/customer/1107/address');
+ cy.get('.q-card__section > .address-card').should(
+ 'have.length',
+ addressCards_before + 1,
+ );
+ cy.get('.q-card__section > .address-card')
+ .eq(addressCards_before)
+ .should('be.visible')
+ .get('.text-weight-bold')
+ .eq(addressCards_before - 1)
+ .should('contain', addressName)
+ .click();
+ });
+ cy.get(
+ '.q-card > :nth-child(1) > :nth-child(2) > .q-checkbox > .q-checkbox__inner',
+ )
+ .should('have.class', 'q-checkbox__inner--falsy')
+ .click();
+
+ cy.get('.q-btn-group > .q-btn--standard > .q-btn__content').click();
+ cy.get(
+ ':nth-child(2) > :nth-child(2) > .flex > .q-mr-lg > .q-checkbox__inner',
+ ).should('have.class', 'q-checkbox__inner--truthy');
+ });
});
diff --git a/test/cypress/integration/client/clientFiscalData.spec.js b/test/cypress/integration/client/clientFiscalData.spec.js
index 05e0772e9..d189f896a 100644
--- a/test/cypress/integration/client/clientFiscalData.spec.js
+++ b/test/cypress/integration/client/clientFiscalData.spec.js
@@ -3,9 +3,8 @@ describe('Client fiscal data', () => {
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');
- cy.visit('#/customer/1107/fiscal-data', {
- timeout: 5000,
- });
+ cy.visit('#/customer/1107/fiscal-data');
+ cy.domContentLoad();
});
it('Should change required value when change customer', () => {
cy.get('.q-card').should('be.visible');
@@ -15,4 +14,25 @@ describe('Client fiscal data', () => {
cy.get('.q-item > .q-item__label').should('have.text', ' #1');
cy.dataCy('sageTaxTypeFk').filter('input').should('have.attr', 'required');
});
+
+ it('check as equalizated', () => {
+ cy.get(
+ ':nth-child(1) > .q-checkbox > .q-checkbox__inner > .q-checkbox__bg',
+ ).click();
+ cy.get('.q-btn-group > .q-btn--standard > .q-btn__content').click();
+
+ cy.get('.q-card > :nth-child(1) > span').should(
+ 'contain',
+ 'You changed the equalization tax',
+ );
+
+ cy.get('.q-card > :nth-child(2) > span').should(
+ 'have.text',
+ 'Do you want to spread the change?',
+ );
+ cy.get('[data-cy="VnConfirm_confirm"] > .q-btn__content > .block').click();
+ cy.get(
+ '.bg-warning > .q-notification__wrapper > .q-notification__content > .q-notification__message',
+ ).should('have.text', 'Equivalent tax spreaded');
+ });
});