feat: refs #7322 add address selection for ticket transfer #1224

Merged
jtubau merged 5 commits from 7322-addSelectAddressOnTicketTransfer into dev 2025-01-24 14:48:06 +00:00
10 changed files with 253 additions and 109 deletions

View File

@ -0,0 +1,33 @@
import { describe, it, expect, vi, afterEach } from 'vitest';
import axios from 'axios';
import { getAddresses } from 'src/pages/Customer/composables/getAddresses';
vi.mock('axios');
describe('getAddresses', () => {
afterEach(() => {
vi.clearAllMocks();
});
it('should fetch addresses with correct parameters for a valid clientId', async () => {
const clientId = '12345';
await getAddresses(clientId);
expect(axios.get).toHaveBeenCalledWith(`Clients/${clientId}/addresses`, {
params: {
filter: JSON.stringify({
fields: ['nickname', 'street', 'city', 'id'],
where: { isActive: true },
order: 'nickname ASC',
}),
},
});
});
it('should return undefined when clientId is not provided', async () => {
await getAddresses(undefined);
expect(axios.get).not.toHaveBeenCalled();
});
});

View File

@ -0,0 +1,41 @@
import { describe, it, expect, vi, afterEach } from 'vitest';
import axios from 'axios';
import { getClient } from 'src/pages/Customer/composables/getClient';
vi.mock('axios');
describe('getClient', () => {
afterEach(() => {
vi.clearAllMocks();
});
const generateParams = (clientId) => ({
params: {
filter: JSON.stringify({
include: {
relation: 'defaultAddress',
scope: {
fields: ['id', 'agencyModeFk'],
},
},
where: { id: clientId },
}),
},
});
it('should fetch client data with correct parameters for a valid clientId', async () => {
const clientId = '12345';
await getClient(clientId);
expect(axios.get).toHaveBeenCalledWith('Clients', generateParams(clientId));
});
it('should return undefined when clientId is not provided', async () => {
const clientId = undefined;
await getClient(clientId);
expect(axios.get).toHaveBeenCalledWith('Clients', generateParams(clientId));
});
});

View File

@ -0,0 +1,14 @@
import axios from 'axios';
export async function getAddresses(clientId) {
if (!clientId) return;
const filter = {
fields: ['nickname', 'street', 'city', 'id'],
where: { isActive: true },
order: 'nickname ASC',
};
const params = { filter: JSON.stringify(filter) };
return await axios.get(`Clients/${clientId}/addresses`, {
params,
});
};

View File

@ -0,0 +1,15 @@
import axios from 'axios';
export async function getClient(clientId) {
const filter = {
include: {
relation: 'defaultAddress',
scope: {
fields: ['id', 'agencyModeFk'],
},
},
where: { id: clientId },
};
const params = { filter: JSON.stringify(filter) };
return await axios.get('Clients', { params });
};

View File

@ -0,0 +1,55 @@
import { describe, it, expect, vi, afterEach } from 'vitest';
import axios from 'axios';
import { getAgencies } from 'src/pages/Route/Agency/composables/getAgencies';
vi.mock('axios');
describe('getAgencies', () => {
afterEach(() => {
vi.clearAllMocks();
});
const generateParams = (formData) => ({
params: {
warehouseFk: formData.warehouseId,
addressFk: formData.addressId,
landed: formData.landed,
},
});
it('should fetch agencies data with correct parameters for valid formData', async () => {
const formData = {
warehouseId: '123',
addressId: '456',
landed: 'true',
};
await getAgencies(formData);
expect(axios.get).toHaveBeenCalledWith('Agencies/getAgenciesWithWarehouse', generateParams(formData));
});
it('should not call API when formData is missing required landed field', async () => {
const formData = { warehouseId: '123', addressId: '456' };
await getAgencies(formData);
expect(axios.get).not.toHaveBeenCalled();
});
it('should not call API when formData is missing required addressId field', async () => {
const formData = { warehouseId: '123', landed: 'true' };
await getAgencies(formData);
expect(axios.get).not.toHaveBeenCalled();
});
it('should not call API when formData is missing required warehouseId field', async () => {
const formData = { addressId: '456', landed: 'true' };
await getAgencies(formData);
expect(axios.get).not.toHaveBeenCalled();
});
});

View File

@ -0,0 +1,12 @@
import axios from 'axios';
export async function getAgencies(formData) {
if (!formData.warehouseId || !formData.addressId || !formData.landed) return;
let params = {
warehouseFk: formData.warehouseId,
addressFk: formData.addressId,
landed: formData.landed,
};
return await axios.get('Agencies/getAgenciesWithWarehouse', { params });
}

View File

@ -16,6 +16,8 @@ import VnInputTime from 'src/components/common/VnInputTime.vue';
import { useAcl } from 'src/composables/useAcl'; import { useAcl } from 'src/composables/useAcl';
import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { getAddresses } from 'src/pages/Customer/composables/getAddresses';
import { getClient } from 'src/pages/Customer/composables/getClient';
const props = defineProps({ const props = defineProps({
ticket: { ticket: {
@ -40,7 +42,10 @@ const { openReport, sendEmail } = usePrintService();
const ticketSummary = useArrayData('TicketSummary'); const ticketSummary = useArrayData('TicketSummary');
const { ticket } = toRefs(props); const { ticket } = toRefs(props);
const ticketId = computed(() => props.ticket.id ?? currentRoute.value.params.id); const ticketId = computed(() => props.ticket.id ?? currentRoute.value.params.id);
const client = ref(); const client = ref(null);
const address = ref(null);
const addressesOptions = ref([]);
const selectedClient = ref();
const showTransferDialog = ref(false); const showTransferDialog = ref(false);
const showTurnDialog = ref(false); const showTurnDialog = ref(false);
const showChangeTimeDialog = ref(false); const showChangeTimeDialog = ref(false);
@ -52,6 +57,31 @@ const weight = ref();
const hasDocuwareFile = ref(); const hasDocuwareFile = ref();
const quasar = useQuasar(); const quasar = useQuasar();
const canRestoreTicket = ref(false); const canRestoreTicket = ref(false);
const onClientSelected = async(clientId) =>{
client.value = clientId;
await fetchClient();
await fetchAddresses();
jgallego marked this conversation as resolved Outdated

Misma solución que para fetchAddresses

Revisar también el arrow function fetchAvailableAgencies que se repite en 3 ocasiones (TicketCreate, TicketCreateDialog y TicketList)

Misma solución que para fetchAddresses Revisar también el arrow function fetchAvailableAgencies que se repite en 3 ocasiones (TicketCreate, TicketCreateDialog y TicketList)
};
const onAddressSelected = (addressId) => {
address.value = addressId;
}
const fetchClient = async () => {
const { data } = await getClient(client.value)
const [retrievedClient] = data;
selectedClient.value = retrievedClient;
};
const fetchAddresses = async () => {
const { data } = await getAddresses(client.value);
addressesOptions.value = data;
jgallego marked this conversation as resolved
Review

@jsegarra esta estructura ya la tenemos con esta 3 veces, y necesitaria su test, pero serian 3 test idénticos, como hacemos para tener esta funcion disponible en el proyecto en algun sitio comun solo testeada una vez? (que por cierto ahora no esta ninguna)

@jsegarra esta estructura ya la tenemos con esta 3 veces, y necesitaria su test, pero serian 3 test idénticos, como hacemos para tener esta funcion disponible en el proyecto en algun sitio comun solo testeada una vez? (que por cierto ahora no esta ninguna)
Review

La idea seria crear un composable en el directorio de customer (src/pages/Customer/composables) que haga el tema de la gestion de consignatarios

La idea seria crear un composable en el directorio de customer (src/pages/Customer/composables) que haga el tema de la gestion de consignatarios
Review

Esto se consideraría refactor y lo abordaria dentro de esta tarea porque al añadir funcionalidad se genera esta situación.

Esto se consideraría refactor y lo abordaria dentro de esta tarea porque al añadir funcionalidad se genera esta situación.
const { defaultAddress } = selectedClient.value;
address.value = defaultAddress.id;
};
const actions = { const actions = {
clone: async () => { clone: async () => {
const opts = { message: t('Ticket cloned'), type: 'positive' }; const opts = { message: t('Ticket cloned'), type: 'positive' };
@ -260,17 +290,14 @@ async function makeInvoice() {
window.location.reload(); window.location.reload();
} }
async function transferClient(client) { async function transferClient() {
const params = { const params = {
clientFk: client, clientFk: client.value,
addressFk: address.value,
}; };
const { data } = await axios.patch( await axios.patch( `Tickets/${ticketId.value}/transferClient`, params );
`Tickets/${ticketId.value}/transferClient`, window.location.reload();
params
);
if (data) window.location.reload();
} }
async function addTurn(day) { async function addTurn(day) {
@ -446,7 +473,7 @@ async function ticketToRestore() {
</QItem> </QItem>
<QDialog ref="dialogRef" v-model="showTransferDialog"> <QDialog ref="dialogRef" v-model="showTransferDialog">
<FormPopup <FormPopup
@on-submit="transferClient(client)" @on-submit="transferClient()"
:title="t('Transfer client')" :title="t('Transfer client')"
:custom-submit-button-label="t('Transfer client')" :custom-submit-button-label="t('Transfer client')"
:default-cancel-button="false" :default-cancel-button="false"
@ -454,10 +481,11 @@ async function ticketToRestore() {
<template #form-inputs> <template #form-inputs>
<VnSelect <VnSelect
url="Clients" url="Clients"
:fields="['id', 'name']" :fields="['id', 'name', 'defaultAddressFk']"
v-model="client" v-model="client"
:label="t('Client')" :label="t('Client')"
auto-load auto-load
@update:model-value="() => onClientSelected(client)"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -470,6 +498,29 @@ async function ticketToRestore() {
</QItem> </QItem>
</template> </template>
</VnSelect> </VnSelect>
<VnSelect
:disable="!client"
:options="addressesOptions"
:fields="['id', 'nickname']"
option-value="id"
option-label="nickname"
v-model="address"
:label="t('ticketList.addressNickname')"
auto-load
:hint="!client ? t('Select a client to enable') : ''"
@update:model-value="() => onAddressSelected(address)"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ `#${scope.opt.id} - ` }}
{{ scope.opt.nickname }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
jgallego marked this conversation as resolved
Review

trabaja el proyecto con la interfaz en español para detectar que posiblement aquí falte la traduccion

trabaja el proyecto con la interfaz en español para detectar que posiblement aquí falte la traduccion
Review

la traducción en español esta entre las etiquetas i18n de todas formas, es una traducción de una feat nueva que tenia que preguntarte si se autoriza su uso

la traducción en español esta entre las etiquetas i18n de todas formas, es una traducción de una feat nueva que tenia que preguntarte si se autoriza su uso
</VnSelect>
</template> </template>
</FormPopup> </FormPopup>
</QDialog> </QDialog>
@ -814,4 +865,5 @@ es:
Are you sure you want to restore the ticket?: ¿Seguro que quieres restaurar el ticket? Are you sure you want to restore the ticket?: ¿Seguro que quieres restaurar el ticket?
You are going to restore this ticket: Vas a restaurar este ticket You are going to restore this ticket: Vas a restaurar este ticket
Ticket restored: Ticket restaurado Ticket restored: Ticket restaurado
Select a client to enable: Selecciona un cliente para habilitar
</i18n> </i18n>

View File

@ -9,9 +9,11 @@ import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import { getClient } from 'src/pages/Customer/composables/getClient';
import { getAddresses } from 'src/pages/Customer/composables/getAddresses';
import { getAgencies } from 'src/pages/Route/Agency/composables/getAgencies';
import { useState } from 'composables/useState'; import { useState } from 'composables/useState';
import axios from 'axios';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
@ -37,33 +39,13 @@ onBeforeMount(async () => {
}); });
const fetchClient = async (formData) => { const fetchClient = async (formData) => {
const filter = { const { data } = await getClient(formData.clientId);
include: {
relation: 'defaultAddress',
scope: {
fields: ['id', 'agencyModeFk'],
},
},
where: { id: formData.clientId },
};
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get('Clients', { params });
const [client] = data; const [client] = data;
selectedClient.value = client; selectedClient.value = client;
}; };
const fetchAddresses = async (formData) => { const fetchAddresses = async (formData) => {
if (!formData.clientId) return; const { data } = await getAddresses(formData.clientId);
const filter = {
fields: ['nickname', 'street', 'city', 'id'],
where: { isActive: true },
order: 'nickname ASC',
};
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get(`Clients/${formData.clientId}/addresses`, {
params,
});
addressesOptions.value = data; addressesOptions.value = data;
const { defaultAddress } = selectedClient.value; const { defaultAddress } = selectedClient.value;
@ -76,15 +58,7 @@ const onClientSelected = async (formData) => {
}; };
const fetchAvailableAgencies = async (formData) => { const fetchAvailableAgencies = async (formData) => {
if (!formData.warehouseId || !formData.addressId || !formData.landed) return; const { data } = await getAgencies(formData);
let params = {
warehouseFk: formData.warehouseId,
addressFk: formData.addressId,
landed: formData.landed,
};
const { data } = await axios.get('Agencies/getAgenciesWithWarehouse', { params });
agenciesOptions.value = data; agenciesOptions.value = data;
const defaultAgency = agenciesOptions.value.find( const defaultAgency = agenciesOptions.value.find(

View File

@ -9,9 +9,11 @@ import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import { getClient } from 'src/pages/Customer/composables/getClient';
import { getAddresses } from 'src/pages/Customer/composables/getAddresses';
import { getAgencies } from 'src/pages/Route/Agency/composables/getAgencies';
import { useState } from 'composables/useState'; import { useState } from 'composables/useState';
import axios from 'axios';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
@ -37,33 +39,13 @@ onBeforeMount(async () => {
}); });
const fetchClient = async (formData) => { const fetchClient = async (formData) => {
const filter = { const { data } = await getClient(formData.clientId);
include: {
relation: 'defaultAddress',
scope: {
fields: ['id', 'agencyModeFk'],
},
},
where: { id: formData.clientId },
};
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get('Clients', { params });
const [client] = data; const [client] = data;
selectedClient.value = client; selectedClient.value = client;
}; };
const fetchAddresses = async (formData) => { const fetchAddresses = async (formData) => {
if (!formData.clientId) return; const { data } = await getAddresses(formData.clientId);
const filter = {
fields: ['nickname', 'street', 'city', 'id'],
where: { isActive: true },
order: 'nickname ASC',
};
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get(`Clients/${formData.clientId}/addresses`, {
params,
});
addressesOptions.value = data; addressesOptions.value = data;
const { defaultAddress } = selectedClient.value; const { defaultAddress } = selectedClient.value;
@ -76,15 +58,7 @@ const onClientSelected = async (formData) => {
}; };
const fetchAvailableAgencies = async (formData) => { const fetchAvailableAgencies = async (formData) => {
if (!formData.warehouseId || !formData.addressId || !formData.landed) return; const { data } = await getAgencies(formData);
let params = {
warehouseFk: formData.warehouseId,
addressFk: formData.addressId,
landed: formData.landed,
};
const { data } = await axios.get('Agencies/getAgenciesWithWarehouse', { params });
agenciesOptions.value = data; agenciesOptions.value = data;
const defaultAgency = agenciesOptions.value.find( const defaultAgency = agenciesOptions.value.find(

View File

@ -22,6 +22,9 @@ import { toTimeFormat } from 'src/filters/date';
import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue'; import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue';
import TicketProblems from 'src/components/TicketProblems.vue'; import TicketProblems from 'src/components/TicketProblems.vue';
import VnSection from 'src/components/common/VnSection.vue'; import VnSection from 'src/components/common/VnSection.vue';
import { getClient } from 'src/pages/Customer/composables/getClient';
import { getAddresses } from 'src/pages/Customer/composables/getAddresses';
import { getAgencies } from 'src/pages/Route/Agency/composables/getAgencies';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@ -237,15 +240,7 @@ const onClientSelected = async (formData) => {
}; };
const fetchAvailableAgencies = async (formData) => { const fetchAvailableAgencies = async (formData) => {
if (!formData.warehouseId || !formData.addressId || !formData.landed) return; const { data } = await getAgencies(formData);
let params = {
warehouseFk: formData.warehouseId,
addressFk: formData.addressId,
landed: formData.landed,
};
const { data } = await axios.get('Agencies/getAgenciesWithWarehouse', { params });
agenciesOptions.value = data; agenciesOptions.value = data;
const defaultAgency = agenciesOptions.value.find( const defaultAgency = agenciesOptions.value.find(
@ -257,34 +252,13 @@ const fetchAvailableAgencies = async (formData) => {
}; };
const fetchClient = async (formData) => { const fetchClient = async (formData) => {
const filter = { const { data } = await getClient(formData.clientId);
include: {
relation: 'defaultAddress',
scope: {
fields: ['id', 'agencyModeFk'],
},
},
where: { id: formData.clientId },
};
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get('Clients', { params });
const [client] = data; const [client] = data;
selectedClient.value = client; selectedClient.value = client;
}; };
const fetchAddresses = async (formData) => { const fetchAddresses = async (formData) => {
if (!formData.clientId) return; const { data } = await getAddresses(formData.clientId);
const filter = {
fields: ['nickname', 'street', 'city', 'id', 'isActive'],
order: ['isDefaultAddress DESC', 'isActive DESC', 'nickname ASC'],
};
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get(`Clients/${formData.clientId}/addresses`, {
params,
});
addressesOptions.value = data;
addressesOptions.value = data; addressesOptions.value = data;
const { defaultAddress } = selectedClient.value; const { defaultAddress } = selectedClient.value;