HOTFIX #8217 Update Customer Credit #986

Merged
jsegarra merged 26 commits from hotfix_8217_updateCustomerCredit into master 2024-11-28 12:34:12 +00:00
26 changed files with 277 additions and 315 deletions

View File

@ -10,6 +10,7 @@ import VnPaginate from 'components/ui/VnPaginate.vue';
import VnConfirm from 'components/ui/VnConfirm.vue'; import VnConfirm from 'components/ui/VnConfirm.vue';
import SkeletonTable from 'components/ui/SkeletonTable.vue'; import SkeletonTable from 'components/ui/SkeletonTable.vue';
import { tMobile } from 'src/composables/tMobile'; import { tMobile } from 'src/composables/tMobile';
import getDifferences from 'src/filters/getDifferences';
const { push } = useRouter(); const { push } = useRouter();
const quasar = useQuasar(); const quasar = useQuasar();
@ -268,28 +269,6 @@ function getChanges() {
return changes; return changes;
} }
function getDifferences(obj1, obj2) {
let diff = {};
delete obj1.$index;
delete obj2.$index;
for (let key in obj1) {
if (obj2[key] && JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])) {
diff[key] = obj2[key];
}
}
for (let key in obj2) {
if (
obj1[key] === undefined ||
JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])
) {
diff[key] = obj2[key];
}
}
return diff;
}
function isEmpty(obj) { function isEmpty(obj) {
if (obj == null) return true; if (obj == null) return true;
if (obj === undefined) return true; if (obj === undefined) return true;

View File

@ -106,6 +106,7 @@ const originalData = ref({});
const formData = computed(() => state.get(modelValue)); const formData = computed(() => state.get(modelValue));
const defaultButtons = computed(() => ({ const defaultButtons = computed(() => ({
save: { save: {
dataCy: 'saveDefaultBtn',
color: 'primary', color: 'primary',
icon: 'save', icon: 'save',
label: 'globals.save', label: 'globals.save',
@ -113,6 +114,7 @@ const defaultButtons = computed(() => ({
type: 'submit', type: 'submit',
}, },
reset: { reset: {
dataCy: 'resetDefaultBtn',
color: 'primary', color: 'primary',
icon: 'restart_alt', icon: 'restart_alt',
label: 'globals.reset', label: 'globals.reset',
@ -203,7 +205,9 @@ async function save() {
isLoading.value = true; isLoading.value = true;
try { try {
formData.value = trimData(formData.value); formData.value = trimData(formData.value);
const body = $props.mapper ? $props.mapper(formData.value) : formData.value; const body = $props.mapper
? $props.mapper(formData.value, originalData.value)
: formData.value;
const method = $props.urlCreate ? 'post' : 'patch'; const method = $props.urlCreate ? 'post' : 'patch';
const url = const url =
$props.urlCreate || $props.urlUpdate || $props.url || arrayData.store.url; $props.urlCreate || $props.urlUpdate || $props.url || arrayData.store.url;
@ -317,6 +321,7 @@ defineExpose({
:title="t(defaultButtons.reset.label)" :title="t(defaultButtons.reset.label)"
/> />
<QBtnDropdown <QBtnDropdown
data-cy="saveAndContinueDefaultBtn"
v-if="$props.goTo" v-if="$props.goTo"
@click="saveAndGo" @click="saveAndGo"
:label="tMobile('globals.saveAndContinue')" :label="tMobile('globals.saveAndContinue')"

View File

@ -101,6 +101,7 @@ onBeforeRouteLeave((to, from, next) => {
@click="insert" @click="insert"
class="q-mb-xs" class="q-mb-xs"
dense dense
data-cy="saveNote"
/> />
</template> </template>
</VnInput> </VnInput>

View File

@ -0,0 +1,21 @@
export default function getDifferences(obj1, obj2) {
let diff = {};
delete obj1.$index;
delete obj2.$index;
for (let key in obj1) {
if (obj2[key] && JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])) {
diff[key] = obj2[key];
}
}
for (let key in obj2) {
if (
obj1[key] === undefined ||
JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])
) {
diff[key] = obj2[key];
}
}
return diff;
}

View File

@ -0,0 +1,6 @@
export default function getUpdatedValues(keys, formData) {
return keys.reduce((acc, key) => {
acc[key] = formData[key];
return acc;
}, {});
}

View File

@ -11,11 +11,15 @@ import dashIfEmpty from './dashIfEmpty';
import dateRange from './dateRange'; import dateRange from './dateRange';
import toHour from './toHour'; import toHour from './toHour';
import dashOrCurrency from './dashOrCurrency'; import dashOrCurrency from './dashOrCurrency';
import getDifferences from './getDifferences';
import getUpdatedValues from './getUpdatedValues';
import getParamWhere from './getParamWhere'; import getParamWhere from './getParamWhere';
import parsePhone from './parsePhone'; import parsePhone from './parsePhone';
import isDialogOpened from './isDialogOpened'; import isDialogOpened from './isDialogOpened';
export { export {
getUpdatedValues,
getDifferences,
isDialogOpened, isDialogOpened,
parsePhone, parsePhone,
toLowerCase, toLowerCase,

View File

@ -2,6 +2,7 @@
import { onBeforeMount, ref, watch } from 'vue'; import { onBeforeMount, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import FetchData from 'components/FetchData.vue';
import axios from 'axios'; import axios from 'axios';
@ -52,7 +53,6 @@ const addressFilter = {
onBeforeMount(() => { onBeforeMount(() => {
const { id } = route.params; const { id } = route.params;
getAddressesData(id);
getClientData(id); getClientData(id);
jsegarra marked this conversation as resolved Outdated
Outdated
Review

Comentado?

Comentado?
}); });
@ -60,23 +60,10 @@ watch(
() => route.params.id, () => route.params.id,
(newValue) => { (newValue) => {
if (!newValue) return; if (!newValue) return;
getAddressesData(newValue);
getClientData(newValue); getClientData(newValue);
} }
); );
const getAddressesData = async (id) => {
try {
const { data } = await axios.get(`Clients/${id}/addresses`, {
params: { filter: JSON.stringify(addressFilter) },
});
addresses.value = data;
sortAddresses();
} catch (error) {
return error;
}
};
const getClientData = async (id) => { const getClientData = async (id) => {
try { try {
const { data } = await axios.get(`Clients/${id}`); const { data } = await axios.get(`Clients/${id}`);
@ -101,9 +88,9 @@ const setDefault = (address) => {
}); });
}; };
const sortAddresses = () => { const sortAddresses = (data) => {
if (!client.value || !addresses.value) return; if (!client.value || !data) return;
addresses.value = addresses.value.sort((a, b) => { addresses.value = data.sort((a, b) => {
return isDefaultAddress(b) - isDefaultAddress(a); return isDefaultAddress(b) - isDefaultAddress(a);
}); });
}; };
@ -124,8 +111,17 @@ const toCustomerAddressEdit = (addressId) => {
</script> </script>
<template> <template>
<FetchData
@on-fetch="sortAddresses"
auto-load
data-key="CustomerAddresses"
order="id DESC"
ref="vnPaginateRef"
:filter="addressFilter"
:url="`Clients/${route.params.id}/addresses`"
/>
Outdated
Review

Ahora tiene un VnPaginate?

Ahora tiene un VnPaginate?

Me parece "rudimentario" dejar el codigo como estaba y preferia usar componentes nuestros.
Sin embargo, veo que el VnPagiante no tiene contenido, pero el resultado es el mismo
Voy a cambiarlo por FetchData

Me parece "rudimentario" dejar el codigo como estaba y preferia usar componentes nuestros. Sin embargo, veo que el VnPagiante no tiene contenido, pero el resultado es el mismo Voy a cambiarlo por FetchData
<div class="full-width flex justify-center"> <div class="full-width flex justify-center">
<QCard class="card-width q-pa-lg" v-if="addresses.length"> <QCard class="card-width q-pa-lg">
<QCardSection> <QCardSection>
<div <div
v-for="(item, index) in addresses" v-for="(item, index) in addresses"

View File

@ -9,6 +9,7 @@ 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 VnAvatar from 'src/components/ui/VnAvatar.vue'; import VnAvatar from 'src/components/ui/VnAvatar.vue';
import { getDifferences, getUpdatedValues } from 'src/filters';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@ -30,6 +31,13 @@ const exprBuilder = (param, value) => {
and: [{ active: { neq: false } }, handleSalesModelValue(value)], and: [{ active: { neq: false } }, handleSalesModelValue(value)],
}; };
}; };
function onBeforeSave(formData, originalData) {
return getUpdatedValues(
Object.keys(getDifferences(formData, originalData)),
formData
);
}
</script> </script>
<template> <template>
<FetchData <FetchData
@ -43,7 +51,12 @@ const exprBuilder = (param, value) => {
@on-fetch="(data) => (businessTypes = data)" @on-fetch="(data) => (businessTypes = data)"
auto-load auto-load
/> />
<FormModel :url="`Clients/${route.params.id}`" auto-load model="customer"> <FormModel
:url="`Clients/${route.params.id}`"
auto-load
model="customer"
:mapper="onBeforeSave"
>
<template #form="{ data, validate }"> <template #form="{ data, validate }">
<VnRow> <VnRow>
<VnInput <VnInput
@ -94,6 +107,7 @@ const exprBuilder = (param, value) => {
:rules="validate('client.phone')" :rules="validate('client.phone')"
clearable clearable
v-model="data.phone" v-model="data.phone"
data-cy="customerPhone"
/> />
<VnInput <VnInput
:label="t('customer.summary.mobile')" :label="t('customer.summary.mobile')"

View File

@ -28,12 +28,7 @@ const getBankEntities = (data, formData) => {
</script> </script>
<template> <template>
<FormModel <FormModel :url-update="`Clients/${route.params.id}`" auto-load model="customer">
Review

En salix usaba :url="Clients/${route.params.id}/getCard" dado que venia del card

En salix usaba :url="`Clients/${route.params.id}/getCard`" dado que venia del card
Review

Pero el tema está en que CustomerDescriptor no muestra la misma info siempre. Segun la seccion en la que estes muestra o esconde iconos.
De esta manera no he encontrado fallos en el customer 1109

Pero el tema está en que CustomerDescriptor no muestra la misma info siempre. Segun la seccion en la que estes muestra o esconde iconos. De esta manera no he encontrado fallos en el customer 1109
:url-update="`Clients/${route.params.id}`"
:url="`Clients/${route.params.id}/getCard`"
auto-load
model="customer"
>
<template #form="{ data, validate }"> <template #form="{ data, validate }">
<VnRow> <VnRow>
<VnSelect <VnSelect

View File

@ -93,22 +93,6 @@ const columns = computed(() => [
<WorkerDescriptorProxy :id="row.worker.id" /> <WorkerDescriptorProxy :id="row.worker.id" />
</template> </template>
</VnTable> </VnTable>
<!-- <QTable
Outdated
Review

Comentado?

Comentado?

Parece que es asi como estaba antes cuando se fusionó la #136 pero ahora se ha quitado

Parece que es asi como estaba antes cuando se fusionó la #136 pero ahora se ha quitado
Outdated
Review

Comentado?

Comentado?
:columns="columns"
:pagination="{ rowsPerPage: 0 }"
:rows="rows"
hide-bottom
row-key="id"
v-model:selected="selected"
class="card-width q-px-lg"
>
<template #body-cell-employee="{ row }">
<QTd @click.stop>
<span class="link">{{ row.worker.user.nickname }}</span>
<WorkerDescriptorProxy :id="row.clientFk" />
</QTd>
</template>
</QTable> -->
</template> </template>
<i18n> <i18n>

View File

@ -36,7 +36,10 @@ const entityId = computed(() => {
}); });
const data = ref(useCardDescription()); const data = ref(useCardDescription());
const setData = (entity) => (data.value = useCardDescription(entity?.name, entity?.id)); const setData = (entity) => {
data.value = useCardDescription(entity?.name, entity?.id);
if (customer.value) customer.value.webAccess = data.value?.account?.isActive;
Review

Donde se use, usar data.value?.account?.isActive

Donde se use, usar `data.value?.account?.isActive`
Review

En el icono noWeb,
Hay veces que no se sincroniza bien con el CustomerBasicData/CustomerFiscalData
Probar con el customer 1109

En el icono noWeb, Hay veces que no se sincroniza bien con el CustomerBasicData/CustomerFiscalData Probar con el customer 1109
};
const debtWarning = computed(() => { const debtWarning = computed(() => {
return customer.value?.debt > customer.value?.credit ? 'negative' : 'primary'; return customer.value?.debt > customer.value?.credit ? 'negative' : 'primary';
}); });

View File

@ -34,7 +34,6 @@ function handleLocation(data, location) {
/> />
<FormModel <FormModel
:url-update="`Clients/${route.params.id}/updateFiscalData`" :url-update="`Clients/${route.params.id}/updateFiscalData`"
:url="`Clients/${route.params.id}/getCard`"
Outdated
Review

En salix usaba :url="Clients/${route.params.id}/getCard" dado que venia del card

En salix usaba :url="`Clients/${route.params.id}/getCard`" dado que venia del card

Misma situación que en el comentario anterior
"Pero el tema está en que CustomerDescriptor no muestra la misma info siempre. Segun la seccion en la que estes muestra o esconde iconos.
De esta manera no he encontrado fallos en el customer 1109"

Misma situación que en el comentario anterior "Pero el tema está en que CustomerDescriptor no muestra la misma info siempre. Segun la seccion en la que estes muestra o esconde iconos. De esta manera no he encontrado fallos en el customer 1109"
auto-load auto-load
model="customer" model="customer"
> >

View File

@ -1,164 +1,82 @@
<script setup> <script setup>
import { computed, onBeforeMount, ref, watch, nextTick } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'src/components/common/VnInput.vue'; import FormModel from 'components/FormModel.vue';
import FetchData from 'components/FetchData.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import axios from 'axios'; const formModelRef = ref(false);
import useNotify from 'src/composables/useNotify';
import { useStateStore } from 'stores/useStateStore';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const { notify } = useNotify();
const stateStore = useStateStore();
const amountInputRef = ref(null); const amountInputRef = ref(null);
const initialDated = Date.vnNew();
const unpaidClient = ref(false);
const isLoading = ref(false);
const amount = ref(null);
const dated = ref(initialDated);
const initialData = ref({ const initialData = ref({
dated: initialDated, dated: Date.vnNew(),
amount: null,
}); });
const hasChanged = computed(() => { const filterClientFindOne = {
return ( fields: ['unpaid', 'dated', 'amount'],
initialData.value.dated !== dated.value || where: {
initialData.value.amount !== amount.value
);
});
onBeforeMount(() => {
getData(route.params.id);
});
watch(
() => route.params.id,
(newValue) => {
if (!newValue) return;
getData(newValue);
}
);
const getData = async (id) => {
const filter = { where: { clientFk: id } };
try {
const { data } = await axios.get('ClientUnpaids', {
params: { filter: JSON.stringify(filter) },
});
if (data.length) {
setValues(data[0]);
} else {
defaultValues();
}
} catch (error) {
defaultValues();
}
};
const setValues = (data) => {
unpaidClient.value = true;
amount.value = data.amount;
dated.value = data.dated;
initialData.value = data;
};
const defaultValues = () => {
unpaidClient.value = false;
initialData.value.amount = null;
setInitialData();
};
const setInitialData = () => {
amount.value = initialData.value.amount;
dated.value = initialData.value.dated;
};
const onSubmit = async () => {
isLoading.value = true;
const payload = {
amount: amount.value,
clientFk: route.params.id, clientFk: route.params.id,
dated: dated.value, },
};
try {
await axios.patch('ClientUnpaids', payload);
notify('globals.dataSaved', 'positive');
unpaidClient.value = true;
} catch (error) {
notify('errors.writeRequest', 'negative');
} finally {
isLoading.value = false;
}
}; };
watch(
() => unpaidClient.value,
async (val) => {
await nextTick();
if (val) amountInputRef.value.focus();
}
);
</script> </script>
<template> <template>
<Teleport v-if="stateStore?.isSubToolbarShown()" to="#st-actions"> <FetchData
<QBtnGroup push class="q-gutter-x-sm"> :filter="filterClientFindOne"
<QBtn auto-load
:disabled="!hasChanged" url="ClientUnpaids"
:label="t('globals.reset')" @on-fetch="
:loading="isLoading" (data) => {
@click="setInitialData" const unpaid = data.length == 1;
color="primary" initialData = { ...data[0], unpaid };
flat }
icon="restart_alt" "
type="reset" />
/> <QCard>
<QBtn <FormModel
:disabled="!hasChanged" v-if="'unpaid' in initialData"
:label="t('globals.save')" :observe-form-changes="false"
:loading="isLoading" ref="formModelRef"
@click="onSubmit" model="unpaid"
color="primary" url-update="ClientUnpaids"
icon="save" :mapper="(formData) => ({ ...formData, clientFk: route.params.id })"
Review

formInitialData ?

formInitialData ?
Review

La tienes una linea mas abajo

La tienes una linea mas abajo
/> :form-initial-data="initialData"
</QBtnGroup> >
</Teleport> <template #form="{ data }">
<div class="full-width flex justify-center">
<QCard class="card-width q-pa-lg">
<QForm>
<VnRow> <VnRow>
<div class="col"> <QCheckbox :label="t('Unpaid client')" v-model="data.unpaid" />
<QCheckbox :label="t('Unpaid client')" v-model="unpaidClient" />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md" v-show="unpaidClient"> <VnRow class="row q-gutter-md q-mb-md" v-show="data.unpaid">
<div class="col"> <div class="col">
<VnInputDate :label="t('Date')" v-model="dated" /> <VnInputDate
data-cy="customerUnpaidDate"
:label="t('Date')"
v-model="data.dated"
/>
</div> </div>
<div class="col"> <div class="col">
<VnInput <VnInputNumber
data-cy="customerUnpaidAmount"
ref="amountInputRef" ref="amountInputRef"
:label="t('Amount')" :label="t('Amount')"
clearable clearable
type="number" v-model="data.amount"
v-model="amount"
autofocus autofocus
> >
<template #append></template></VnInput <template #append></template></VnInputNumber
> >
</div> </div>
</VnRow> </VnRow>
</QForm> </template>
</QCard> </FormModel>
</div> </QCard>
</template> </template>
<i18n> <i18n>

View File

@ -25,10 +25,9 @@ async function hasCustomerRole() {
</script> </script>
<template> <template>
<FormModel <FormModel
url="VnUsers/preview"
:url-update="`Clients/${route.params.id}/updateUser`" :url-update="`Clients/${route.params.id}/updateUser`"
:filter="filter" :filter="filter"
model="webAccess" model="customer"
:mapper=" :mapper="
({ active, name, email }) => { ({ active, name, email }) => {
return { return {
@ -42,7 +41,7 @@ async function hasCustomerRole() {
auto-load auto-load
> >
<template #form="{ data, validate }"> <template #form="{ data, validate }">
<QCheckbox :label="t('Enable web access')" v-model="data.active" /> <QCheckbox :label="t('Enable web access')" v-model="data.account.active" />
<VnInput :label="t('User')" clearable v-model="data.name" /> <VnInput :label="t('User')" clearable v-model="data.name" />
<VnInput <VnInput
:label="t('Recovery email')" :label="t('Recovery email')"

View File

@ -1,9 +1,9 @@
<script setup> <script setup>
import { computed, onBeforeMount, ref, watch } from 'vue'; import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import axios from 'axios'; import FetchData from 'src/components/FetchData.vue';
import { toCurrency, toDateHourMin } from 'src/filters'; import { toCurrency, toDateHourMin } from 'src/filters';
@ -20,10 +20,11 @@ const filter = {
{ relation: 'mandateType', scope: { fields: ['id', 'name'] } }, { relation: 'mandateType', scope: { fields: ['id', 'name'] } },
{ relation: 'company', scope: { fields: ['id', 'code'] } }, { relation: 'company', scope: { fields: ['id', 'code'] } },
], ],
where: { clientFk: null }, where: { clientFk: route.params.id },
order: ['created DESC'], order: ['created DESC'],
limit: 20, limit: 20,
}; };
const ClientDmsRef = ref(false);
const tableColumnComponents = { const tableColumnComponents = {
state: { state: {
@ -50,7 +51,7 @@ const tableColumnComponents = {
component: CustomerCheckIconTooltip, component: CustomerCheckIconTooltip,
props: ({ row }) => ({ props: ({ row }) => ({
transaction: row, transaction: row,
promise: refreshData, promise: () => ClientDmsRef.value.fetch(),
}), }),
event: () => {}, event: () => {},
}, },
@ -89,72 +90,45 @@ const columns = computed(() => [
name: 'validate', name: 'validate',
}, },
]); ]);
onBeforeMount(() => {
getData(route.params.id);
});
watch(
() => route.params.id,
(newValue) => {
if (!newValue) return;
getData(newValue);
}
);
const getData = async (id) => {
filter.where.clientFk = id;
try {
const { data } = await axios.get('clients/transactions', {
params: { filter: JSON.stringify(filter) },
});
rows.value = data;
} catch (error) {
return error;
}
};
const refreshData = () => {
getData(route.params.id);
};
</script> </script>
<template> <template>
<div class="full-width flex justify-center"> <FetchData
<QPage class="card-width q-pa-lg"> ref="ClientDmsRef"
<QTable :filter="filter"
:columns="columns" @on-fetch="(data) => (rows = data)"
:pagination="{ rowsPerPage: 12 }" auto-load
:rows="rows" url="Clients/transactions"
class="full-width q-mt-md" />
row-key="id" <QPage class="card-width q-pa-lg">
v-if="rows?.length" <QTable
> :columns="columns"
<template #body-cell="props"> :pagination="{ rowsPerPage: 12 }"
<QTd :props="props"> :rows="rows"
<QTr :props="props"> class="full-width q-mt-md"
<component row-key="id"
:is="tableColumnComponents[props.col.name].component" v-if="rows?.length"
@click=" >
tableColumnComponents[props.col.name].event(props) <template #body-cell="props">
" <QTd :props="props">
class="rounded-borders q-pa-sm" <QTr :props="props">
v-bind=" <component
tableColumnComponents[props.col.name].props(props) :is="tableColumnComponents[props.col.name].component"
" @click="tableColumnComponents[props.col.name].event(props)"
> class="rounded-borders q-pa-sm"
{{ props.value }} v-bind="tableColumnComponents[props.col.name].props(props)"
</component> >
</QTr> {{ props.value }}
</QTd> </component>
</template> </QTr>
</QTable> </QTd>
</template>
</QTable>
<h5 class="flex justify-center color-vn-label" v-else> <h5 class="flex justify-center color-vn-label" v-else>
{{ t('globals.noResults') }} {{ t('globals.noResults') }}
</h5> </h5>
</QPage> </QPage>
</div>
</template> </template>
<i18n> <i18n>

View File

@ -1,9 +1,8 @@
<script setup> <script setup>
import { onBeforeMount, reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import axios from 'axios';
import VnLocation from 'src/components/common/VnLocation.vue'; import VnLocation from 'src/components/common/VnLocation.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
@ -25,20 +24,6 @@ const agencyModes = ref([]);
const incoterms = ref([]); const incoterms = ref([]);
const customsAgents = ref([]); const customsAgents = ref([]);
onBeforeMount(() => {
urlCreate.value = `Clients/${route.params.id}/createAddress`;
getCustomsAgents();
});
const getCustomsAgents = async () => {
const { data } = await axios.get('CustomsAgents');
customsAgents.value = data;
};
const refreshData = () => {
getCustomsAgents();
};
const toCustomerAddress = () => { const toCustomerAddress = () => {
router.push({ router.push({
name: 'CustomerAddress', name: 'CustomerAddress',
@ -54,6 +39,11 @@ function handleLocation(data, location) {
data.provinceFk = provinceFk; data.provinceFk = provinceFk;
data.countryFk = countryFk; data.countryFk = countryFk;
} }
function onAgentCreated(requestResponse, data) {
customsAgents.value.push(requestResponse);
data.customsAgentFk = requestResponse.id;
}
</script> </script>
<template> <template>
@ -139,6 +129,7 @@ function handleLocation(data, location) {
/> />
<VnSelectDialog <VnSelectDialog
url="CustomsAgents"
:label="t('Customs agent')" :label="t('Customs agent')"
:options="customsAgents" :options="customsAgents"
hide-selected hide-selected
@ -148,7 +139,12 @@ function handleLocation(data, location) {
:tooltip="t('Create a new expense')" :tooltip="t('Create a new expense')"
> >
<template #form> <template #form>
<CustomerNewCustomsAgent @on-data-saved="refreshData()" /> <CustomerNewCustomsAgent
@on-data-saved="
(_, requestResponse) =>
onAgentCreated(requestResponse, data)
"
/>
</template> </template>
</VnSelectDialog> </VnSelectDialog>
</VnRow> </VnRow>

View File

@ -144,7 +144,7 @@ function handleLocation(data, location) {
:url="`Addresses/${route.params.addressId}`" :url="`Addresses/${route.params.addressId}`"
@on-data-saved="onDataSaved()" @on-data-saved="onDataSaved()"
auto-load auto-load
model="client" model="customer"
> >
<template #moreActions> <template #moreActions>
<QBtn <QBtn

View File

@ -321,10 +321,6 @@ onUnmounted(() => (stateStore.rightDrawer = false));
<div class="column"> <div class="column">
<span v-for="(saleComponent, index) in row.components" :key="index"> <span v-for="(saleComponent, index) in row.components" :key="index">
{{ toCurrency(saleComponent.value * row.quantity, 'EUR', 3) }} {{ toCurrency(saleComponent.value * row.quantity, 'EUR', 3) }}
<!-- <QTooltip>
{{ saleComponent.component?.name }}:
{{ toCurrency(saleComponent.value, 'EUR', 3) }}
</QTooltip> -->
</span> </span>
</div> </div>
</template> </template>

View File

@ -3,11 +3,17 @@ describe('Client basic data', () => {
beforeEach(() => { beforeEach(() => {
cy.viewport(1280, 720); cy.viewport(1280, 720);
cy.login('developer'); cy.login('developer');
cy.visit('#/customer/1110/basic-data', { cy.visit('#/customer/1102/basic-data');
timeout: 5000,
});
}); });
it('Should load layout', () => { it('Should load layout', () => {
cy.get('.q-card').should('be.visible'); cy.get('.q-card').should('be.visible');
cy.dataCy('customerPhone').filter('input').should('be.visible');
cy.dataCy('customerPhone').filter('input').type('123456789');
cy.get('.q-btn-group > .q-btn--standard').click();
cy.intercept('PATCH', '/api/Clients/1102', (req) => {
const { body } = req;
cy.wrap(body).should('have.property', 'phone', '123456789');
});
cy.get('.q-notification__message').should('have.text', 'Data saved');
}); });
}); });

View File

@ -8,6 +8,6 @@ describe('Client billing data', () => {
}); });
}); });
it('Should load layout', () => { it('Should load layout', () => {
cy.get('.q-card').should('be.visible'); cy.get('.q-page').should('be.visible');
}); });
}); });

View File

@ -17,12 +17,14 @@ describe('Client list', () => {
it('Client list create new client', () => { it('Client list create new client', () => {
cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click();
const randomInt = Math.floor(Math.random() * 90) + 10;
const data = { const data = {
Name: { val: 'Name 1' }, Name: { val: `Name ${randomInt}` },
'Social name': { val: 'TEST 1' }, 'Social name': { val: `TEST ${randomInt}` },
'Tax number': { val: '20852113Z' }, 'Tax number': { val: `20852${randomInt.length}3Z` },
'Web user': { val: 'user_test_1' }, 'Web user': { val: `user_test_${randomInt}` },
Street: { val: 'C/ STREET 1' }, Street: { val: `C/ STREET ${randomInt}` },
Email: { val: 'user.test@1.com' }, Email: { val: 'user.test@1.com' },
'Sales person': { val: 'employee', type: 'select' }, 'Sales person': { val: 'employee', type: 'select' },
Location: { val: '46000, Valencia(Province one), España', type: 'select' }, Location: { val: '46000, Valencia(Province one), España', type: 'select' },
@ -32,7 +34,7 @@ describe('Client list', () => {
cy.get('.q-mt-lg > .q-btn--standard').click(); cy.get('.q-mt-lg > .q-btn--standard').click();
cy.checkNotification('created'); cy.checkNotification('Data saved');
cy.url().should('include', '/summary'); cy.url().should('include', '/summary');
}); });
it('Client list search client', () => { it('Client list search client', () => {
@ -54,8 +56,8 @@ describe('Client list', () => {
cy.openActionDescriptor('Create ticket'); cy.openActionDescriptor('Create ticket');
cy.waitForElement('#formModel'); cy.waitForElement('#formModel');
cy.waitForElement('.q-form'); cy.waitForElement('.q-form');
cy.checkValueSelectForm(1, search); cy.checkValueForm(1, search);
cy.checkValueSelectForm(2, search); cy.checkValueForm(2, search);
}); });
it('Client founded create order', () => { it('Client founded create order', () => {
const search = 'Jessica Jones'; const search = 'Jessica Jones';

View File

@ -0,0 +1,12 @@
/// <reference types="cypress" />
describe('Client notes', () => {
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');
cy.visit('#/customer/1101/sms');
});
it('Should load layout', () => {
cy.get('.q-page').should('be.visible');
cy.get('.q-page > :nth-child(2) > :nth-child(1)').should('be.visible');
});
});

View File

@ -3,11 +3,26 @@ describe('Client web-access', () => {
beforeEach(() => { beforeEach(() => {
cy.viewport(1280, 720); cy.viewport(1280, 720);
cy.login('developer'); cy.login('developer');
cy.visit('#/customer/1110/web-access', {
timeout: 5000,
});
}); });
it('Should load layout', () => { it('Should test buttons ', () => {
cy.visit('#/customer/1101/web-access');
cy.get('.q-page').should('be.visible');
cy.get('#formModel').should('be.visible');
cy.get('.q-card').should('be.visible'); cy.get('.q-card').should('be.visible');
cy.get('.q-btn-group > :nth-child(1)').should('not.be.disabled');
cy.get('.q-checkbox__inner').click();
cy.get('.q-btn-group > .q-btn--standard.q-btn--actionable').should(
'not.be.disabled'
);
cy.get('.q-btn-group > .q-btn--flat').should('not.be.disabled');
cy.get('.q-btn-group > :nth-child(1)').click();
cy.get('.q-dialog__inner > .q-card > :nth-child(1)')
.should('be.visible')
.find('.text-h6')
.should('have.text', 'Change password');
});
it('Should disabled buttons', () => {
cy.visit('#/customer/1110/web-access');
cy.get('.q-btn-group > :nth-child(1)').should('be.disabled');
}); });
}); });

View File

@ -1,5 +1,5 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
describe('Client credit opinion', () => { describe('Client credit contracts', () => {
beforeEach(() => { beforeEach(() => {
cy.viewport(1280, 720); cy.viewport(1280, 720);
cy.login('developer'); cy.login('developer');
@ -8,6 +8,6 @@ describe('Client credit opinion', () => {
}); });
}); });
it('Should load layout', () => { it('Should load layout', () => {
cy.get('.q-card').should('be.visible'); cy.get('.q-page').should('be.visible');
}); });
}); });

View File

@ -3,11 +3,17 @@ describe('Client unpaid', () => {
beforeEach(() => { beforeEach(() => {
cy.viewport(1280, 720); cy.viewport(1280, 720);
cy.login('developer'); cy.login('developer');
cy.visit('#/customer/1110/others/unpaid', {
timeout: 5000,
});
}); });
it('Should load layout', () => { it('Should add unpaid', () => {
cy.visit('#/customer/1102/others/unpaid');
cy.get('.q-card').should('be.visible'); cy.get('.q-card').should('be.visible');
cy.get('.q-checkbox__inner').click();
cy.dataCy('customerUnpaidAmount').find('input').type('100');
cy.dataCy('customerUnpaidDate').find('input').type('01/01/2001');
cy.get('.q-btn-group > .q-btn--standard').click();
cy.reload();
cy.get('.q-checkbox__inner')
.should('be.visible')
.and('not.have.class', 'q-checkbox__inner--active');
}); });
}); });

View File

@ -254,6 +254,7 @@ Cypress.Commands.add('writeSearchbar', (value) => {
value value
); );
}); });
Cypress.Commands.add('validateContent', (selector, expectedValue) => { Cypress.Commands.add('validateContent', (selector, expectedValue) => {
cy.get(selector).should('have.text', expectedValue); cy.get(selector).should('have.text', expectedValue);
}); });
@ -275,16 +276,38 @@ Cypress.Commands.add('clickButtonsDescriptor', (id) => {
.click(); .click();
}); });
Cypress.Commands.add('openActionDescriptor', (opt) => {
cy.openActionsDescriptor();
const listItem = '[role="menu"] .q-list .q-item';
cy.contains(listItem, opt).click();
1;
});
Cypress.Commands.add('clickButtonsDescriptor', (id) => {
cy.get(`.actions > .q-card__actions> .q-btn:nth-child(${id})`)
.invoke('removeAttr', 'target')
.click();
});
Cypress.Commands.add('openActionDescriptor', (opt) => {
cy.openActionsDescriptor();
const listItem = '[role="menu"] .q-list .q-item';
cy.contains(listItem, opt).click();
1;
});
Cypress.Commands.add('clickButtonsDescriptor', (id) => {
cy.get(`.actions > .q-card__actions> .q-btn:nth-child(${id})`)
.invoke('removeAttr', 'target')
.click();
});
Cypress.Commands.add('openUserPanel', () => { Cypress.Commands.add('openUserPanel', () => {
cy.get( cy.get(
'.column > .q-avatar > .q-avatar__content > .q-img > .q-img__container > .q-img__image' '.column > .q-avatar > .q-avatar__content > .q-img > .q-img__container > .q-img__image'
).click(); ).click();
}); });
Cypress.Commands.add('openActions', (row) => {
cy.get('tbody > tr').eq(row).find('.actions > .q-btn').click();
});
Cypress.Commands.add('checkNotification', (text) => { Cypress.Commands.add('checkNotification', (text) => {
cy.get('.q-notification') cy.get('.q-notification')
.should('be.visible') .should('be.visible')
@ -295,10 +318,14 @@ Cypress.Commands.add('checkNotification', (text) => {
}); });
}); });
Cypress.Commands.add('openActions', (row) => {
cy.get('tbody > tr').eq(row).find('.actions > .q-btn').click();
});
Cypress.Commands.add('checkValueForm', (id, search) => { Cypress.Commands.add('checkValueForm', (id, search) => {
cy.get( cy.get(`.grid-create > :nth-child(${id}) `)
`.grid-create > :nth-child(${id}) > .q-field__inner > .q-field__control > .q-field__control-container > .q-field__native > .q-field__input` .find('input')
).should('have.value', search); .should('have.value', search);
}); });
Cypress.Commands.add('checkValueSelectForm', (id, search) => { Cypress.Commands.add('checkValueSelectForm', (id, search) => {
@ -310,3 +337,7 @@ Cypress.Commands.add('checkValueSelectForm', (id, search) => {
Cypress.Commands.add('searchByLabel', (label, value) => { Cypress.Commands.add('searchByLabel', (label, value) => {
cy.get(`[label="${label}"] > .q-field > .q-field__inner`).type(`${value}{enter}`); cy.get(`[label="${label}"] > .q-field > .q-field__inner`).type(`${value}{enter}`);
}); });
Cypress.Commands.add('dataCy', (tag, attr = 'data-cy') => {
return cy.get(`[${attr}="${tag}"]`);
});