0
0
Fork 0

Merge branch 'master' into Hotfix-ZoneEventsExclusion

This commit is contained in:
Jon Elias 2024-11-28 12:43:41 +00:00
commit 4193710123
30 changed files with 309 additions and 345 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

@ -133,7 +133,7 @@ const addFilter = async (filter, params) => {
async function fetch(params) { async function fetch(params) {
useArrayData(props.dataKey, params); useArrayData(props.dataKey, params);
arrayData.reset(['filter.skip', 'skip', 'page']); arrayData.reset(['filter.skip', 'skip', 'page']);
await arrayData.fetch({ append: false }); await arrayData.fetch({ append: false, updateRouter: mounted.value });
return emitStoreData(); return emitStoreData();
} }

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);
}); });
@ -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`"
/>
<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">
: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
: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;
};
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`"
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"
/> />
<QBtn <QCard>
:disabled="!hasChanged" <FormModel
:label="t('globals.save')" v-if="'unpaid' in initialData"
:loading="isLoading" :observe-form-changes="false"
@click="onSubmit" ref="formModelRef"
color="primary" model="unpaid"
icon="save" url-update="ClientUnpaids"
/> :mapper="(formData) => ({ ...formData, clientFk: route.params.id })"
</QBtnGroup> :form-initial-data="initialData"
</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>
</FormModel>
</QCard> </QCard>
</div>
</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,38 +90,16 @@ 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
ref="ClientDmsRef"
:filter="filter"
@on-fetch="(data) => (rows = data)"
auto-load
url="Clients/transactions"
/>
<QPage class="card-width q-pa-lg"> <QPage class="card-width q-pa-lg">
<QTable <QTable
:columns="columns" :columns="columns"
@ -135,13 +114,9 @@ const refreshData = () => {
<QTr :props="props"> <QTr :props="props">
<component <component
:is="tableColumnComponents[props.col.name].component" :is="tableColumnComponents[props.col.name].component"
@click=" @click="tableColumnComponents[props.col.name].event(props)"
tableColumnComponents[props.col.name].event(props)
"
class="rounded-borders q-pa-sm" class="rounded-borders q-pa-sm"
v-bind=" v-bind="tableColumnComponents[props.col.name].props(props)"
tableColumnComponents[props.col.name].props(props)
"
> >
{{ props.value }} {{ props.value }}
</component> </component>
@ -154,7 +129,6 @@ const refreshData = () => {
{{ 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

@ -82,11 +82,11 @@ const entriesTableColumns = computed(() => [
</QCardSection> </QCardSection>
<QCardActions align="right"> <QCardActions align="right">
<QBtn <QBtn
:label="t('printLabels')" :label="t('myEntries.printLabels')"
color="primary" color="primary"
icon="print" icon="print"
:loading="isLoading" :loading="isLoading"
@click="openReport(`Entries/${entityId}/print`)" @click="openReport(`Entries/${entityId}/labelSupplier`)"
unelevated unelevated
autofocus autofocus
/> />
@ -126,7 +126,9 @@ const entriesTableColumns = computed(() => [
" "
unelevated unelevated
> >
<QTooltip>{{ t('viewLabel') }}</QTooltip> <QTooltip>{{
t('myEntries.viewLabel')
}}</QTooltip>
</QBtn> </QBtn>
</QTr> </QTr>
</template> </template>

View File

@ -101,7 +101,7 @@ const columns = computed(() => [
name: 'tableActions', name: 'tableActions',
actions: [ actions: [
{ {
title: t('printLabels'), title: t('myEntries.printLabels'),
icon: 'print', icon: 'print',
isPrimary: true, isPrimary: true,
action: (row) => printBuys(row.id), action: (row) => printBuys(row.id),

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

@ -181,17 +181,34 @@ const resetChanges = async () => {
arrayData.fetch({ append: false }); arrayData.fetch({ append: false });
tableRef.value.reload(); tableRef.value.reload();
}; };
const rowToUpdate = ref(null);
const changeQuantity = async (sale) => {
canProceed.value = await isSalePrepared(sale);
if (!canProceed.value) return;
if (
!sale.itemFk ||
sale.quantity == null ||
edit.value?.oldQuantity === sale.quantity
)
return;
if (!sale.id) return addSale(sale);
const updateQuantity = async (sale) => {
const params = { quantity: sale.quantity };
try { try {
await axios.post(`Sales/${sale.id}/updateQuantity`, params); if (!rowToUpdate.value) return;
rowToUpdate.value = null;
await updateQuantity(sale);
} catch (e) { } catch (e) {
sale.quantity = tableRef.value.CrudModelRef.originalData.find( const { quantity } = tableRef.value.CrudModelRef.originalData.find(
(s) => s.id === sale.id (s) => s.id === sale.id
).quantity; );
sale.quantity = quantity;
throw e; throw e;
} }
};
const updateQuantity = async ({ quantity, id }) => {
const params = { quantity: quantity };
await axios.post(`Sales/${id}/updateQuantity`, params);
notify('globals.dataSaved', 'positive'); notify('globals.dataSaved', 'positive');
}; };
@ -219,19 +236,6 @@ const addSale = async (sale) => {
window.location.reload(); window.location.reload();
}; };
const changeQuantity = async (sale) => {
canProceed.value = await isSalePrepared(sale);
if (!canProceed.value) return;
if (
!sale.itemFk ||
sale.quantity == null ||
edit.value?.oldQuantity === sale.quantity
)
return;
if (!sale.id) return addSale(sale);
await updateQuantity(sale);
};
const updateConcept = async (sale) => { const updateConcept = async (sale) => {
canProceed.value = await isSalePrepared(sale); canProceed.value = await isSalePrepared(sale);
if (!canProceed.value) return; if (!canProceed.value) return;
@ -768,16 +772,12 @@ watch(
</template> </template>
<template #column-quantity="{ row }"> <template #column-quantity="{ row }">
<VnInput <VnInput
v-if="row.isNew" v-if="row.isNew || isTicketEditable"
v-model.number="row.quantity"
type="number" type="number"
@blur="changeQuantity(row)"
@focus="edit.oldQuantity = row.quantity"
/>
<VnInput
v-else-if="isTicketEditable"
v-model.number="row.quantity" v-model.number="row.quantity"
@blur="changeQuantity(row)" @blur="changeQuantity(row)"
@keyup.enter="changeQuantity(row)"
@update:model-value="() => (rowToUpdate = row)"
@focus="edit.oldQuantity = row.quantity" @focus="edit.oldQuantity = row.quantity"
/> />
<span v-else>{{ row.quantity }}</span> <span v-else>{{ row.quantity }}</span>

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 test buttons ', () => {
it('Should load layout', () => { 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 add unpaid', () => {
it('Should load layout', () => { 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}"]`);
});