Customer description corrections and creation of tickets create view

This commit is contained in:
William Buezas 2024-02-22 10:38:15 -03:00
parent d06271b91d
commit 1b67b7ab36
9 changed files with 348 additions and 48 deletions

View File

@ -170,6 +170,7 @@ export default {
hasDebt: 'Customer has debt', hasDebt: 'Customer has debt',
notChecked: 'Customer not checked', notChecked: 'Customer not checked',
noWebAccess: 'Web access is disabled', noWebAccess: 'Web access is disabled',
businessType: 'Business type',
passwordRequirements: passwordRequirements:
'The password must have at least { length } length characters, {nAlpha} alphabetic characters, {nUpper} capital letters, {nDigits} digits and {nPunct} symbols (Ex: $%&.)\n', 'The password must have at least { length } length characters, {nAlpha} alphabetic characters, {nUpper} capital letters, {nDigits} digits and {nPunct} symbols (Ex: $%&.)\n',
}, },
@ -492,6 +493,13 @@ export default {
weight: 'Weight', weight: 'Weight',
goTo: 'Go to', goTo: 'Go to',
}, },
create: {
client: 'Client',
address: 'Address',
landed: 'Landed',
warehouse: 'Warehouse',
agency: 'Agency',
},
}, },
claim: { claim: {
pageTitles: { pageTitles: {

View File

@ -169,6 +169,7 @@ export default {
hasDebt: 'El cliente tiene riesgo', hasDebt: 'El cliente tiene riesgo',
notChecked: 'El cliente no está comprobado', notChecked: 'El cliente no está comprobado',
noWebAccess: 'El acceso web está desactivado', noWebAccess: 'El acceso web está desactivado',
businessType: 'Tipo de negocio',
passwordRequirements: passwordRequirements:
'La contraseña debe tener al menos { length } caracteres de longitud, {nAlpha} caracteres alfabéticos, {nUpper} letras mayúsculas, {nDigits} dígitos y {nPunct} símbolos (Ej: $%&.)', 'La contraseña debe tener al menos { length } caracteres de longitud, {nAlpha} caracteres alfabéticos, {nUpper} letras mayúsculas, {nDigits} dígitos y {nPunct} símbolos (Ej: $%&.)',
}, },
@ -492,6 +493,13 @@ export default {
weight: 'Peso', weight: 'Peso',
goTo: 'Ir a', goTo: 'Ir a',
}, },
create: {
client: 'Cliente',
address: 'Dirección',
landed: 'F. entrega',
warehouse: 'Almacén',
agency: 'Agencia',
},
}, },
claim: { claim: {
pageTitles: { pageTitles: {

View File

@ -3,15 +3,14 @@ import { ref, computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { toCurrency } from 'src/filters';
import useCardDescription from 'src/composables/useCardDescription';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue';
import CustomerDescriptorMenu from './CustomerDescriptorMenu.vue'; import CustomerDescriptorMenu from './CustomerDescriptorMenu.vue';
import useCardDescription from 'src/composables/useCardDescription';
import { toCurrency } from 'src/filters';
const $props = defineProps({ const $props = defineProps({
id: { id: {
type: Number, type: Number,
@ -23,8 +22,10 @@ const $props = defineProps({
default: null, default: null,
}, },
}); });
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const entityId = computed(() => { const entityId = computed(() => {
return $props.id || route.params.id; return $props.id || route.params.id;
}); });
@ -43,54 +44,68 @@ const setData = (entity) => (data.value = useCardDescription(entity.name, entity
:summary="$props.summary" :summary="$props.summary"
data-key="customerData" data-key="customerData"
> >
<template #header-extra-action>
<QBtn
round
flat
size="sm"
icon="vn:Person"
color="white"
:to="{ name: 'CustomerList' }"
>
<QTooltip>
{{ t('Go to module index') }}
</QTooltip>
</QBtn>
</template>
<template #menu="{ entity }"> <template #menu="{ entity }">
<CustomerDescriptorMenu :customer="entity" /> <CustomerDescriptorMenu :customer="entity" />
</template> </template>
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv v-if="entity.salesPersonUser" :label="t('customer.card.salesPerson')"> <VnLv :label="t('customer.card.payMethod')" :value="entity.payMethod.name" />
<VnLv :label="t('customer.card.credit')" :value="toCurrency(entity.credit)" />
<VnLv
:label="t('customer.card.securedCredit')"
:value="entity.creditInsurance ? toCurrency(entity.creditInsurance) : '-'"
/>
<VnLv
:label="t('customer.card.risk')"
:value="entity.debt ? toCurrency(entity.debt) : '-'"
:info="t('customer.summary.descriptorInfo')"
/>
<VnLv :label="t('customer.card.salesPerson')">
<template #value> <template #value>
<VnUserLink <VnUserLink
v-if="entity.salesPersonUser"
:name="entity.salesPersonUser?.name" :name="entity.salesPersonUser?.name"
:worker-id="entity.salesPersonFk" :worker-id="entity.salesPersonFk"
/> />
<span v-else>-</span>
</template> </template>
</VnLv> </VnLv>
<VnLv :label="t('customer.card.credit')" :value="toCurrency(entity.credit)" />
<VnLv <VnLv
:label="t('customer.card.risk')" :label="t('customer.card.businessType')"
:value="toCurrency(entity.debt)" :value="entity.businessType.description"
:info="t('customer.summary.descriptorInfo')"
/> />
<VnLv
:label="t('customer.card.securedCredit')"
:value="toCurrency(entity.creditInsurance)"
/>
<VnLv :label="t('customer.card.payMethod')" :value="entity.payMethod.name" />
<VnLv :label="t('customer.card.debt')" :value="toCurrency(entity.debt)" />
</template> </template>
<template #icons="{ entity }"> <template #icons="{ entity }">
<QCardActions> <QCardActions class="q-gutter-x-md">
<QIcon <QIcon
v-if="entity.isActive == false" v-if="!entity.isActive"
name="vn:disabled" name="vn:disabled"
size="xs" size="xs"
color="primary" color="primary"
> >
<QTooltip>{{ t('customer.card.isDisabled') }}</QTooltip> <QTooltip>{{ t('customer.card.isDisabled') }}</QTooltip>
</QIcon> </QIcon>
<QIcon <QIcon v-if="entity.isFreezed" name="vn:frozen" size="xs" color="primary">
v-if="entity.isFreezed == true"
name="vn:frozen"
size="xs"
color="primary"
>
<QTooltip>{{ t('customer.card.isFrozen') }}</QTooltip> <QTooltip>{{ t('customer.card.isFrozen') }}</QTooltip>
</QIcon> </QIcon>
<QIcon <QIcon
v-if="!entity.account.active"
color="primary" color="primary"
name="vn:noweb" name="vn:noweb"
size="sm" size="xs"
v-if="entity.account.active == false"
> >
<QTooltip>{{ t('customer.card.webAccountInactive') }}</QTooltip> <QTooltip>{{ t('customer.card.webAccountInactive') }}</QTooltip>
</QIcon> </QIcon>
@ -103,13 +118,25 @@ const setData = (entity) => (data.value = useCardDescription(entity.name, entity
<QTooltip>{{ t('customer.card.hasDebt') }}</QTooltip> <QTooltip>{{ t('customer.card.hasDebt') }}</QTooltip>
</QIcon> </QIcon>
<QIcon <QIcon
v-if="entity.isTaxDataChecked == false" v-if="!entity.isTaxDataChecked"
name="vn:no036" name="vn:no036"
size="xs" size="xs"
color="primary" color="primary"
> >
<QTooltip>{{ t('customer.card.notChecked') }}</QTooltip> <QTooltip>{{ t('customer.card.notChecked') }}</QTooltip>
</QIcon> </QIcon>
<QBtn
v-if="entity.unpaid"
flat
size="sm"
icon="vn:noPayMethod"
color="primary"
:to="{ name: 'CustomerUnpaid' }"
>
<QTooltip>
{{ t('Customer unpaid') }}
</QTooltip>
</QBtn>
</QCardActions> </QCardActions>
</template> </template>
<template #actions="{ entity }"> <template #actions="{ entity }">
@ -123,7 +150,7 @@ const setData = (entity) => (data.value = useCardDescription(entity.name, entity
icon="vn:ticket" icon="vn:ticket"
color="primary" color="primary"
> >
<QTooltip>{{ t('ticketList') }}</QTooltip> <QTooltip>{{ t('Customer ticket list') }}</QTooltip>
</QBtn> </QBtn>
<QBtn <QBtn
:to="{ :to="{
@ -134,35 +161,33 @@ const setData = (entity) => (data.value = useCardDescription(entity.name, entity
icon="vn:invoice-out" icon="vn:invoice-out"
color="primary" color="primary"
> >
<QTooltip>{{ t('invoiceOutList') }}</QTooltip> <QTooltip>{{ t('Customer invoice out list') }}</QTooltip>
</QBtn> </QBtn>
<QBtn <QBtn
:to="{ :to="{
name: 'OrderCreate', name: 'OrderCreate',
params: { id: entity.id }, query: { clientFk: entity.id },
}" }"
size="md" size="md"
icon="vn:basketadd" icon="vn:basketadd"
color="primary" color="primary"
> >
<QTooltip>{{ t('invoiceOutList') }}</QTooltip> <QTooltip>{{ t('New order') }}</QTooltip>
</QBtn> </QBtn>
<QBtn size="md" icon="vn:Person" color="primary"> <QBtn size="md" icon="face" color="primary">
<QTooltip>{{ t('invoiceOutList') }}</QTooltip> <!-- TODO:: Redirigir a la vista de usuario cuando exista -->
<QTooltip>{{ t('Go to user') }}</QTooltip>
</QBtn> </QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </CardDescriptor>
</template> </template>
<i18n> <i18n>
{ es:
"en": { Go to module index: Ir al índice del módulo
"ticketList": "Customer ticket list", Customer ticket list: Listado de tickets del cliente
"invoiceOutList": "Customer invoice out list" Customer invoice out list: Listado de facturas del cliente
}, New order: Nuevo pedido
"es": { Go to user: Ir al usuario
"ticketList": "Listado de tickets del cliente", Customer unpaid: Cliente impago
"invoiceOutList": "Listado de facturas del cliente"
}
}
</i18n> </i18n>

View File

@ -25,7 +25,7 @@ const showSmsDialog = () => {
quasar.dialog({ quasar.dialog({
component: VnSmsDialog, component: VnSmsDialog,
componentProps: { componentProps: {
phone: $props.customer.phone, phone: $props.customer.phone || $props.customer.mobile,
promise: sendSms, promise: sendSms,
}, },
}); });
@ -45,7 +45,13 @@ const sendSms = async (payload) => {
<template> <template>
<QItem v-ripple clickable> <QItem v-ripple clickable>
<QItemSection> <QItemSection>
<RouterLink :to="{ name: 'TicketCreate' }" class="color-vn-text"> <RouterLink
:to="{
name: 'TicketCreate',
query: { clientFk: customer.id },
}"
class="color-vn-text"
>
{{ t('Simple ticket') }} {{ t('Simple ticket') }}
</RouterLink> </RouterLink>
</QItemSection> </QItemSection>

View File

@ -1,6 +1,6 @@
<script setup> <script setup>
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { reactive, ref } from 'vue'; import { reactive, ref, onMounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import axios from 'axios'; import axios from 'axios';
import { useState } from 'composables/useState'; import { useState } from 'composables/useState';
@ -27,6 +27,23 @@ const clientList = ref([]);
const agencyList = ref([]); const agencyList = ref([]);
const addressList = ref([]); const addressList = ref([]);
const onClientsFetched = async (data) => {
try {
clientList.value = data;
initialFormState.clientFk = Number(route.query?.clientFk) || null;
if (initialFormState.clientFk) {
const { defaultAddressFk } = clientList.value.find(
(client) => client.id === initialFormState.clientFk
);
if (defaultAddressFk) await fetchAddressList(defaultAddressFk);
}
} catch (err) {
console.error('Error fetching clients', err);
}
};
const fetchAddressList = async (addressId) => { const fetchAddressList = async (addressId) => {
try { try {
const { data } = await axios.get('addresses', { const { data } = await axios.get('addresses', {
@ -47,6 +64,7 @@ const fetchAddressList = async (addressId) => {
return err.response; return err.response;
} }
}; };
const fetchAgencyList = async (landed, addressFk) => { const fetchAgencyList = async (landed, addressFk) => {
if (!landed || !addressFk) { if (!landed || !addressFk) {
return; return;
@ -108,7 +126,7 @@ const orderFilter = {
<template> <template>
<FetchData <FetchData
url="Clients" url="Clients"
@on-fetch="(data) => (clientList = data)" @on-fetch="(data) => onClientsFetched(data)"
:filter="{ fields: ['id', 'name', 'defaultAddressFk'] }" :filter="{ fields: ['id', 'name', 'defaultAddressFk'] }"
auto-load auto-load
/> />

View File

@ -168,7 +168,7 @@ async function changeState(value) {
</VnLv> </VnLv>
<VnLv <VnLv
:label="t('ticket.summary.agency')" :label="t('ticket.summary.agency')"
:value="ticket.agencyMode.name" :value="ticket.agencyMode?.name"
/> />
<VnLv :label="t('ticket.summary.zone')" :value="ticket?.zone?.name" /> <VnLv :label="t('ticket.summary.zone')" :value="ticket?.zone?.name" />
<VnLv <VnLv

View File

@ -0,0 +1,227 @@
<script setup>
import { useRoute, useRouter } from 'vue-router';
import { onBeforeMount, reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FormModel from 'components/FormModel.vue';
import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import { useState } from 'composables/useState';
import axios from 'axios';
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const state = useState();
const user = state.getUser();
const initialFormState = reactive({
clientId: Number(route.query?.clientFk) || null,
addressId: null,
agencyModeId: null,
warehouseId: user.value.warehouseFk,
landed: null,
});
const clientOptions = ref([]);
const agenciesOptions = ref([]);
const addressesOptions = ref([]);
const warehousesOptions = ref([]);
const selectedClient = ref(null);
onBeforeMount(async () => {
await onClientSelected(initialFormState);
});
const fetchClient = async (formData) => {
try {
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 [client] = data;
selectedClient.value = client;
} catch (err) {
console.error('Error fetching client');
}
};
const fetchAddresses = async (formData) => {
try {
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,
});
addressesOptions.value = data;
const { defaultAddress } = selectedClient.value;
formData.addressId = defaultAddress.id;
console.log();
} catch (err) {
console.error(`Error fetching addresses`, err);
return err.response;
}
};
const onClientSelected = async (formData) => {
await fetchClient(formData);
await fetchAddresses(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 });
agenciesOptions.value = data;
const defaultAgency = agenciesOptions.value.find(
(agency) =>
agency.agencyModeFk === selectedClient.value.defaultAddress.agencyModeFk
);
if (defaultAgency) formData.agencyModeId = defaultAgency.agencyModeFk;
};
const redirectToTicketList = (_, { id }) => {
router.push({ name: 'TicketSummary', params: { id } });
};
</script>
<template>
<FetchData
url="Clients"
@on-fetch="(data) => (clientOptions = data)"
:filter="{ fields: ['id', 'name', 'defaultAddressFk'], order: 'id' }"
auto-load
/>
<FetchData
url="Warehouses"
@on-fetch="(data) => (warehousesOptions = data)"
order="name"
auto-load
/>
<VnSubToolbar />
<div class="q-pa-md">
<FormModel
url-create="Tickets/new"
model="ticket"
:form-initial-data="initialFormState"
@on-data-saved="redirectToTicketList"
>
<template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('ticket.create.client')"
v-model="data.clientId"
:options="clientOptions"
option-value="id"
option-label="name"
hide-selected
@update:model-value="(client) => onClientSelected(data)"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt.name }}
</QItemLabel>
<QItemLabel caption>
{{ `#${scope.opt.id}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('ticket.create.address')"
v-model="data.addressId"
:options="addressesOptions"
option-value="id"
option-label="nickname"
hide-selected
:disable="!data.clientId"
@update:model-value="() => fetchAvailableAgencies(data)"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt.nickname }}
</QItemLabel>
<QItemLabel caption>
{{ `${scope.opt.street}, ${scope.opt.city}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInputDate
placeholder="dd-mm-aaa"
:label="t('ticket.create.landed')"
v-model="data.landed"
@update:model-value="() => fetchAvailableAgencies(data)"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('ticket.create.warehouse')"
v-model="data.warehouseId"
:options="warehousesOptions"
option-value="id"
option-label="name"
hide-selected
@update:model-value="() => fetchAvailableAgencies(data)"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('ticket.create.agency')"
v-model="data.agencyModeId"
:options="agenciesOptions"
option-value="agencyModeFk"
option-label="agencyMode"
hide-selected
:disable="!data.clientId || !data.landed || !data.warehouseId"
/>
</div>
</VnRow>
</template>
</FormModel>
</div>
</template>

View File

@ -122,6 +122,13 @@ function navigate(id) {
</template> </template>
</VnPaginate> </VnPaginate>
</div> </div>
<QPageSticky :offset="[20, 20]">
<QBtn :to="{ name: 'TicketCreate' }" fab icon="add" color="primary">
<QTooltip>
{{ t('New ticket') }}
</QTooltip>
</QBtn>
</QPageSticky>
</QPage> </QPage>
</template> </template>
@ -130,4 +137,5 @@ es:
Search ticket: Buscar ticket Search ticket: Buscar ticket
You can search by ticket id or alias: Puedes buscar por id o alias del ticket You can search by ticket id or alias: Puedes buscar por id o alias del ticket
Zone: Zona Zone: Zona
New ticket: Nuevo ticket
</i18n> </i18n>

View File

@ -37,7 +37,7 @@ export default {
icon: 'vn:ticketAdd', icon: 'vn:ticketAdd',
roles: ['developer'], roles: ['developer'],
}, },
component: () => import('src/pages/Ticket/TicketList.vue'), component: () => import('src/pages/Ticket/TicketCreate.vue'),
}, },
], ],
}, },