#7119 add VehicleList page and routing configuration #1129

Merged
jorgep merged 71 commits from 7119-createVehicle into dev 2025-02-06 09:24:16 +00:00
14 changed files with 293 additions and 131 deletions
Showing only changes of commit a06696df01 - Show all commits

View File

@ -1,6 +1,6 @@
<script setup>
import axios from 'axios';
import { computed, ref, watch } from 'vue';
import { computed, ref, useAttrs, watch } from 'vue';
import { useRouter, onBeforeRouteLeave } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
@ -17,6 +17,7 @@ const quasar = useQuasar();
const stateStore = useStateStore();
const { t } = useI18n();
const { validate } = useValidator();
const $attrs = useAttrs();
const $props = defineProps({
model: {
@ -113,9 +114,11 @@ onBeforeRouteLeave((to, from, next) => {
});
async function fetch(data) {
resetData(data);
emit('onFetch', data);
return data;
const keyData = $attrs['key-data'];
const rows = keyData ? data[keyData] : data;
resetData(rows);
emit('onFetch', rows);
return rows;
}
function resetData(data) {

View File

@ -78,6 +78,10 @@ const props = defineProps({
type: String,
default: '',
},
keyData: {
type: String,
default: undefined,
},
});
const emit = defineEmits(['onFetch', 'onPaginate', 'onChange']);
@ -255,7 +259,7 @@ defineExpose({
:disable="disableInfiniteScroll || !store.hasMoreData"
v-bind="$attrs"
>
<slot name="body" :rows="store.data"></slot>
<slot name="body" :rows="keyData ? store.data[keyData] : store.data"></slot>
<div v-if="isLoading" class="spinner info-row q-pa-md text-center">
<QSpinner color="primary" size="md" />
</div>

View File

@ -202,6 +202,7 @@ export function useArrayData(key, userOptions) {
}
function toArray(str = []) {
if (!str) return [];
if (Array.isArray(str)) return str;
if (typeof str === 'string') return str.split(',').map((item) => item.trim());
}

View File

@ -11,14 +11,14 @@ import VnInput from 'src/components/common/VnInput.vue';
import CustomerDefaulterAddObservation from './CustomerDefaulterAddObservation.vue';
import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue';
import VnTable from 'src/components/VnTable/VnTable.vue';
import { useArrayData } from 'src/composables/useArrayData';
const { t } = useI18n();
const quasar = useQuasar();
const dataRef = ref(null);
const balanceDueTotal = ref(0);
const selected = ref([]);
const arrayData = useArrayData('CustomerDefaulter');
const columns = computed(() => [
{
align: 'left',
@ -165,26 +165,37 @@ const viewAddObservation = (rowsSelected) => {
});
};
const onFetch = async (data) => {
balanceDueTotal.value = data.reduce((acc, { amount = 0 }) => acc + amount, 0);
};
function exprBuilder(param, value) {
switch (param) {
case 'clientFk':
return { [`d.${param}`]: value };
case 'creditInsurance':
case 'amount':
case 'workerFk':
case 'departmentFk':
case 'countryFk':
case 'payMethod':
case 'salesPersonFk':
case 'creditInsurance':
case 'countryFk':
return { [`c.${param}`]: value };
case 'payMethod':
return { [`c.payMethodFk`]: value };
case 'workerFk':
return { [`co.${param}`]: value };
case 'departmentFk':
return { [`wd.${param}`]: value };
case 'amount':
case 'clientFk':
return { [`d.${param}`]: value };
case 'created':
return { 'd.created': { between: dateRange(value) } };
case 'defaulterSinced':
return { 'd.defaulterSinced': { between: dateRange(value) } };
case 'isWorker': {
if (value == undefined) return;
const search = value ? 'worker' : { neq: 'worker' };
return { 'c.businessTypeFk': search };
}
case 'hasRecovery': {
if (value == undefined) return;
const search = value ? null : { neq: null };
return { 'r.finished': search };
}
case 'observation':
return { 'co.text': { like: `%${value}%` } };
}
}
</script>
@ -192,7 +203,7 @@ function exprBuilder(param, value) {
<template>
<VnSubToolbar>
<template #st-data>
<CustomerBalanceDueTotal :amount="balanceDueTotal" />
<CustomerBalanceDueTotal :amount="arrayData.store.data?.amount" />
</template>
<template #st-actions>
<QBtn
@ -211,8 +222,6 @@ function exprBuilder(param, value) {
url="Defaulters/filter"
:expr-builder="exprBuilder"
:columns="columns"
@on-fetch="onFetch"
:use-model="true"
:table="{
'row-key': 'clientFk',
selection: 'multiple',
@ -221,6 +230,7 @@ function exprBuilder(param, value) {
:disable-option="{ card: true }"
auto-load
:order="['amount DESC']"
key-data="defaulters"
>
<template #column-clientFk="{ row }">
<span class="link" @click.stop>

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 VnInputNumber from 'src/components/common/VnInputNumber.vue';
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({
ticket: {
@ -40,7 +42,10 @@ const { openReport, sendEmail } = usePrintService();
const ticketSummary = useArrayData('TicketSummary');
const { ticket } = toRefs(props);
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 showTurnDialog = ref(false);
const showChangeTimeDialog = ref(false);
@ -52,6 +57,31 @@ const weight = ref();
const hasDocuwareFile = ref();
const quasar = useQuasar();
const canRestoreTicket = ref(false);
const onClientSelected = async(clientId) =>{
client.value = clientId;
await fetchClient();
await fetchAddresses();
};
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;
const { defaultAddress } = selectedClient.value;
address.value = defaultAddress.id;
};
const actions = {
clone: async () => {
const opts = { message: t('Ticket cloned'), type: 'positive' };
@ -260,17 +290,14 @@ async function makeInvoice() {
window.location.reload();
}
async function transferClient(client) {
async function transferClient() {
const params = {
clientFk: client,
clientFk: client.value,
addressFk: address.value,
};
const { data } = await axios.patch(
`Tickets/${ticketId.value}/transferClient`,
params
);
if (data) window.location.reload();
await axios.patch( `Tickets/${ticketId.value}/transferClient`, params );
window.location.reload();
}
async function addTurn(day) {
@ -446,7 +473,7 @@ async function ticketToRestore() {
</QItem>
<QDialog ref="dialogRef" v-model="showTransferDialog">
<FormPopup
@on-submit="transferClient(client)"
@on-submit="transferClient()"
:title="t('Transfer client')"
:custom-submit-button-label="t('Transfer client')"
:default-cancel-button="false"
@ -454,10 +481,11 @@ async function ticketToRestore() {
<template #form-inputs>
<VnSelect
url="Clients"
:fields="['id', 'name']"
:fields="['id', 'name', 'defaultAddressFk']"
v-model="client"
:label="t('Client')"
auto-load
@update:model-value="() => onClientSelected(client)"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
@ -470,6 +498,29 @@ async function ticketToRestore() {
</QItem>
</template>
</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>
</VnSelect>
</template>
</FormPopup>
</QDialog>
@ -814,4 +865,5 @@ es:
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
Ticket restored: Ticket restaurado
Select a client to enable: Selecciona un cliente para habilitar
</i18n>

View File

@ -9,9 +9,11 @@ import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'components/common/VnSelect.vue';
import VnInputDate from 'components/common/VnInputDate.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 axios from 'axios';
const { t } = useI18n();
const route = useRoute();
@ -37,33 +39,13 @@ onBeforeMount(async () => {
});
const fetchClient = async (formData) => {
const filter = {
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 { data } = await getClient(formData.clientId);
const [client] = data;
selectedClient.value = client;
};
const fetchAddresses = async (formData) => {
if (!formData.clientId) return;
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,
});
const { data } = await getAddresses(formData.clientId);
addressesOptions.value = data;
const { defaultAddress } = selectedClient.value;
@ -76,15 +58,7 @@ const onClientSelected = async (formData) => {
};
const fetchAvailableAgencies = async (formData) => {
if (!formData.warehouseId || !formData.addressId || !formData.landed) return;
let params = {
warehouseFk: formData.warehouseId,
addressFk: formData.addressId,
landed: formData.landed,
};
const { data } = await axios.get('Agencies/getAgenciesWithWarehouse', { params });
const { data } = await getAgencies(formData);
agenciesOptions.value = data;
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 VnSelect from 'components/common/VnSelect.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 axios from 'axios';
const { t } = useI18n();
const route = useRoute();
@ -37,33 +39,13 @@ onBeforeMount(async () => {
});
const fetchClient = async (formData) => {
const filter = {
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 { data } = await getClient(formData.clientId);
const [client] = data;
selectedClient.value = client;
};
const fetchAddresses = async (formData) => {
if (!formData.clientId) return;
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,
});
const { data } = await getAddresses(formData.clientId);
addressesOptions.value = data;
const { defaultAddress } = selectedClient.value;
@ -76,15 +58,7 @@ const onClientSelected = async (formData) => {
};
const fetchAvailableAgencies = async (formData) => {
if (!formData.warehouseId || !formData.addressId || !formData.landed) return;
let params = {
warehouseFk: formData.warehouseId,
addressFk: formData.addressId,
landed: formData.landed,
};
const { data } = await axios.get('Agencies/getAgenciesWithWarehouse', { params });
const { data } = await getAgencies(formData);
agenciesOptions.value = data;
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 TicketProblems from 'src/components/TicketProblems.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 router = useRouter();
@ -237,15 +240,7 @@ const onClientSelected = async (formData) => {
};
const fetchAvailableAgencies = async (formData) => {
if (!formData.warehouseId || !formData.addressId || !formData.landed) return;
let params = {
warehouseFk: formData.warehouseId,
addressFk: formData.addressId,
landed: formData.landed,
};
const { data } = await axios.get('Agencies/getAgenciesWithWarehouse', { params });
const { data } = await getAgencies(formData);
agenciesOptions.value = data;
const defaultAgency = agenciesOptions.value.find(
@ -257,34 +252,13 @@ const fetchAvailableAgencies = async (formData) => {
};
const fetchClient = async (formData) => {
const filter = {
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 { data } = await getClient(formData.clientId);
const [client] = data;
selectedClient.value = client;
};
const fetchAddresses = async (formData) => {
if (!formData.clientId) return;
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;
const { data } = await getAddresses(formData.clientId);
addressesOptions.value = data;
const { defaultAddress } = selectedClient.value;