Merge branch '7995-CreateHasAcl' of https://gitea.verdnatura.es/verdnatura/salix-front into 7995-CreateHasAcl
gitea/salix-front/pipeline/pr-dev This commit is unstable
Details
gitea/salix-front/pipeline/pr-dev This commit is unstable
Details
This commit is contained in:
commit
8c9c156817
|
@ -1,16 +1,6 @@
|
|||
import {
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
vi,
|
||||
beforeAll,
|
||||
afterEach,
|
||||
beforeEach,
|
||||
afterAll,
|
||||
} from 'vitest';
|
||||
import { describe, it, expect, vi, afterEach, beforeEach, afterAll } from 'vitest';
|
||||
import { createWrapper, axios } from 'app/test/vitest/helper';
|
||||
import VnNotes from 'src/components/ui/VnNotes.vue';
|
||||
import vnDate from 'src/boot/vnDate';
|
||||
|
||||
describe('VnNotes', () => {
|
||||
let vm;
|
||||
|
@ -18,6 +8,7 @@ describe('VnNotes', () => {
|
|||
let spyFetch;
|
||||
let postMock;
|
||||
let patchMock;
|
||||
let deleteMock;
|
||||
let expectedInsertBody;
|
||||
let expectedUpdateBody;
|
||||
const defaultOptions = {
|
||||
|
@ -57,6 +48,7 @@ describe('VnNotes', () => {
|
|||
beforeEach(() => {
|
||||
postMock = vi.spyOn(axios, 'post');
|
||||
patchMock = vi.spyOn(axios, 'patch');
|
||||
deleteMock = vi.spyOn(axios, 'delete');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -153,4 +145,16 @@ describe('VnNotes', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('Should call axios.delete with url and vnPaginateRef.fetch', async () => {
|
||||
generateWrapper();
|
||||
createSpyFetch();
|
||||
|
||||
await vm.deleteNote({ id: 1 });
|
||||
|
||||
expect(deleteMock).toHaveBeenCalledWith(`${vm.$props.url}/1`);
|
||||
expect(spyFetch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
data-cy="descriptor-more-opts"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ $t('components.cardDescriptor.moreOptions') }}
|
||||
{{ $t('components.vnDescriptor.moreOptions') }}
|
||||
</QTooltip>
|
||||
<QMenu ref="menuRef" data-cy="descriptor-more-opts-menu">
|
||||
<QList data-cy="descriptor-more-opts_list">
|
||||
|
|
|
@ -18,10 +18,10 @@ import VnInput from 'components/common/VnInput.vue';
|
|||
|
||||
const emit = defineEmits(['onFetch']);
|
||||
|
||||
const $attrs = useAttrs();
|
||||
|
||||
const isRequired = computed(() => {
|
||||
return Object.keys($attrs).includes('required');
|
||||
const originalAttrs = useAttrs();
|
||||
const $attrs = computed(() => {
|
||||
const { required, deletable, ...rest } = originalAttrs;
|
||||
return rest;
|
||||
});
|
||||
|
||||
const $props = defineProps({
|
||||
|
@ -53,6 +53,11 @@ function handleClick(e) {
|
|||
else insert();
|
||||
}
|
||||
|
||||
async function deleteNote(e) {
|
||||
await axios.delete(`${$props.url}/${e.id}`);
|
||||
await vnPaginateRef.value.fetch();
|
||||
}
|
||||
|
||||
async function insert() {
|
||||
if (!newNote.text || ($props.selectType && !newNote.observationTypeFk)) return;
|
||||
|
||||
|
@ -157,7 +162,7 @@ const handleObservationTypes = (data) => {
|
|||
v-model="newNote.observationTypeFk"
|
||||
option-label="description"
|
||||
style="flex: 0.15"
|
||||
:required="isRequired"
|
||||
:required="'required' in originalAttrs"
|
||||
@keyup.enter.stop="insert"
|
||||
/>
|
||||
<VnInput
|
||||
|
@ -165,11 +170,10 @@ const handleObservationTypes = (data) => {
|
|||
type="textarea"
|
||||
:label="$props.justInput && newNote.text ? '' : t('Add note here...')"
|
||||
filled
|
||||
size="lg"
|
||||
autogrow
|
||||
autofocus
|
||||
@keyup.enter.stop="handleClick"
|
||||
:required="isRequired"
|
||||
:required="'required' in originalAttrs"
|
||||
clearable
|
||||
>
|
||||
<template #append>
|
||||
|
@ -239,6 +243,21 @@ const handleObservationTypes = (data) => {
|
|||
</QBadge>
|
||||
</div>
|
||||
<span v-text="toDateHourMin(note.created)" />
|
||||
<div>
|
||||
<QIcon
|
||||
v-if="'deletable' in originalAttrs"
|
||||
name="delete"
|
||||
size="sm"
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
@click="deleteNote(note)"
|
||||
data-cy="notesRemoveNoteBtn"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('ticketNotes.removeNote') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</div>
|
||||
</div>
|
||||
</QCardSection>
|
||||
<QCardSection class="q-pa-xs q-my-none q-py-none">
|
||||
|
|
|
@ -134,7 +134,7 @@ const columns = computed(() => [
|
|||
|
||||
const STATE_COLOR = {
|
||||
pending: 'bg-warning',
|
||||
managed: 'bg-info',
|
||||
loses: 'bg-negative',
|
||||
resolved: 'bg-positive',
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -20,6 +20,7 @@ import VnFilter from 'components/VnTable/VnFilter.vue';
|
|||
|
||||
import CustomerNewPayment from 'src/pages/Customer/components/CustomerNewPayment.vue';
|
||||
import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue';
|
||||
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||
|
||||
const { openConfirmationModal } = useVnConfirm();
|
||||
const { sendEmail, openReport } = usePrintService();
|
||||
|
@ -89,15 +90,7 @@ const columns = computed(() => [
|
|||
{
|
||||
align: 'left',
|
||||
label: t('Employee'),
|
||||
columnField: {
|
||||
component: 'userLink',
|
||||
attrs: ({ row }) => {
|
||||
return {
|
||||
workerId: row.workerFk,
|
||||
name: row.userName,
|
||||
};
|
||||
},
|
||||
},
|
||||
name: 'workerFk',
|
||||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
|
@ -131,7 +124,6 @@ const columns = computed(() => [
|
|||
align: 'left',
|
||||
name: 'balance',
|
||||
label: t('Balance'),
|
||||
format: ({ balance }) => toCurrency(balance),
|
||||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
|
@ -146,12 +138,14 @@ const columns = computed(() => [
|
|||
actions: [
|
||||
{
|
||||
title: t('globals.downloadPdf'),
|
||||
isPrimary: true,
|
||||
icon: 'cloud_download',
|
||||
show: (row) => row.isInvoice,
|
||||
action: (row) => showBalancePdf(row),
|
||||
},
|
||||
{
|
||||
title: t('Send compensation'),
|
||||
isPrimary: true,
|
||||
icon: 'outgoing_mail',
|
||||
show: (row) => !!row.isCompensation,
|
||||
action: ({ id }) =>
|
||||
|
@ -256,6 +250,12 @@ const showBalancePdf = ({ id }) => {
|
|||
<template #column-balance="{ rowIndex }">
|
||||
{{ toCurrency(balances[rowIndex]?.balance) }}
|
||||
</template>
|
||||
<template #column-workerFk="{ row }">
|
||||
<span class="link" @click.stop>
|
||||
{{ row.userName }}
|
||||
<WorkerDescriptorProxy :id="row.workerFk" />
|
||||
</span>
|
||||
</template>
|
||||
<template #column-description="{ row }">
|
||||
<span class="link" v-if="row.isInvoice" @click.stop>
|
||||
{{ t('bill', { ref: row.description }) }}
|
||||
|
|
|
@ -3,18 +3,20 @@ import { onBeforeMount, reactive, ref } from 'vue';
|
|||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
import { getClientRisk } from '../composables/getClientRisk';
|
||||
import { useDialogPluginComponent } from 'quasar';
|
||||
import FormModelPopup from 'components/FormModelPopup.vue';
|
||||
|
||||
import { getClientRisk } from '../composables/getClientRisk';
|
||||
import { usePrintService } from 'composables/usePrintService';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
|
||||
import FormModelPopup from 'components/FormModelPopup.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import FormModel from 'components/FormModel.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnInputNumber from 'components/common/VnInputNumber.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnAccountNumber from 'src/components/common/VnAccountNumber.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
|
@ -48,7 +50,7 @@ const maxAmount = ref();
|
|||
const accountingType = ref({});
|
||||
const isCash = ref(false);
|
||||
const formModelRef = ref(false);
|
||||
|
||||
const amountToReturn = ref();
|
||||
const filterBanks = {
|
||||
fields: ['id', 'bank', 'accountingTypeFk'],
|
||||
include: { relation: 'accountingType' },
|
||||
|
@ -90,7 +92,7 @@ function setPaymentType(data, accounting) {
|
|||
let descriptions = [];
|
||||
if (accountingType.value.receiptDescription)
|
||||
descriptions.push(accountingType.value.receiptDescription);
|
||||
if (data.description) descriptions.push(data.description);
|
||||
if (data.description > 0) descriptions.push(data.description);
|
||||
data.description = descriptions.join(', ');
|
||||
}
|
||||
|
||||
|
@ -100,7 +102,7 @@ const calculateFromAmount = (event) => {
|
|||
};
|
||||
|
||||
const calculateFromDeliveredAmount = (event) => {
|
||||
initialData.amountToReturn = parseFloat(event) - initialData.amountPaid;
|
||||
amountToReturn.value = event - initialData.amountPaid;
|
||||
};
|
||||
|
||||
function onBeforeSave(data) {
|
||||
|
@ -121,17 +123,16 @@ async function onDataSaved(formData, { id }) {
|
|||
recipient: formData.email,
|
||||
});
|
||||
|
||||
if (viewReceipt.value) openReport(`Receipts/${id}/receipt-pdf`);
|
||||
if (viewReceipt.value) openReport(`Receipts/${id}/receipt-pdf`, {}, '_blank');
|
||||
} finally {
|
||||
if ($props.promise) $props.promise();
|
||||
if (closeButton.value) closeButton.value.click();
|
||||
}
|
||||
}
|
||||
|
||||
async function accountShortToStandard({ target: { value } }) {
|
||||
async function getSupplierClientReferences(value) {
|
||||
if (!value) return (initialData.description = '');
|
||||
initialData.compensationAccount = value.replace('.', '0'.repeat(11 - value.length));
|
||||
const params = { bankAccount: initialData.compensationAccount };
|
||||
const params = { bankAccount: value };
|
||||
const { data } = await axios(`Clients/getClientOrSupplierReference`, { params });
|
||||
if (!data.clientId) {
|
||||
initialData.description = t('Supplier Compensation Reference', {
|
||||
|
@ -241,17 +242,16 @@ async function getAmountPaid() {
|
|||
@update:model-value="getAmountPaid()"
|
||||
/>
|
||||
</VnRow>
|
||||
|
||||
<div v-if="data.bankFk?.accountingType?.code == 'compensation'">
|
||||
<div v-if="accountingType.code == 'compensation'">
|
||||
<div class="text-h6">
|
||||
{{ t('Compensation') }}
|
||||
</div>
|
||||
<VnRow>
|
||||
<VnInputNumber
|
||||
<VnAccountNumber
|
||||
:label="t('Compensation account')"
|
||||
clearable
|
||||
v-model="data.compensationAccount"
|
||||
@blur="accountShortToStandard"
|
||||
@blur="getSupplierClientReferences(data.compensationAccount)"
|
||||
/>
|
||||
</VnRow>
|
||||
</div>
|
||||
|
@ -261,8 +261,7 @@ async function getAmountPaid() {
|
|||
clearable
|
||||
v-model="data.description"
|
||||
/>
|
||||
|
||||
<div v-if="data.bankFk?.accountingType?.code == 'cash'">
|
||||
<div v-if="accountingType.code == 'cash'">
|
||||
<div class="text-h6">{{ t('Cash') }}</div>
|
||||
<VnRow>
|
||||
<VnInputNumber
|
||||
|
@ -274,7 +273,7 @@ async function getAmountPaid() {
|
|||
<VnInputNumber
|
||||
:label="t('Amount to return')"
|
||||
disable
|
||||
v-model="data.amountToReturn"
|
||||
v-model="amountToReturn"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useState } from 'src/composables/useState';
|
||||
import VnNotes from 'src/components/ui/VnNotes.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const state = useState();
|
||||
const user = state.getUser();
|
||||
const vehicleId = computed(() => route.params.id);
|
||||
|
||||
const noteFilter = computed(() => {
|
||||
return {
|
||||
order: 'created DESC',
|
||||
where: { vehicleFk: vehicleId.value },
|
||||
};
|
||||
});
|
||||
|
||||
const body = {
|
||||
vehicleFk: vehicleId.value,
|
||||
workerFk: user.value.id,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VnNotes
|
||||
url="vehicleObservations"
|
||||
:add-note="true"
|
||||
:filter="noteFilter"
|
||||
:body="body"
|
||||
style="overflow-y: auto"
|
||||
required
|
||||
deletable
|
||||
/>
|
||||
</template>
|
|
@ -18,7 +18,6 @@ import { usePrintService } from 'composables/usePrintService';
|
|||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||
import axios from 'axios';
|
||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||
import VnPopup from 'src/components/common/VnPopup.vue';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const { t } = useI18n();
|
||||
|
@ -183,7 +182,6 @@ const columns = computed(() => [
|
|||
align: 'left',
|
||||
showValue: false,
|
||||
sortable: true,
|
||||
style: 'min-width: 170px;',
|
||||
},
|
||||
{
|
||||
label: t('globals.packages'),
|
||||
|
@ -507,6 +505,7 @@ watch(route, () => {
|
|||
:props="props"
|
||||
@click="stopEventPropagation($event, col)"
|
||||
:style="col.style"
|
||||
style="padding-left: 5px"
|
||||
>
|
||||
<component
|
||||
:is="tableColumnComponents[col.name].component"
|
||||
|
@ -613,23 +612,10 @@ watch(route, () => {
|
|||
<QTd class="text-right">
|
||||
<span>{{ entry.volumeKg }}</span>
|
||||
</QTd>
|
||||
<QTd />
|
||||
<QTd />
|
||||
<QTd />
|
||||
<QTd />
|
||||
<QTd>
|
||||
<QBtn
|
||||
v-if="entry.evaNotes"
|
||||
icon="comment"
|
||||
size="md"
|
||||
flat
|
||||
color="primary"
|
||||
>
|
||||
<VnPopup
|
||||
:title="t('globals.observations')"
|
||||
:content="entry.evaNotes"
|
||||
/>
|
||||
</QBtn>
|
||||
<QTd :colspan="5" class="text-right">
|
||||
<span>
|
||||
{{ entry.evaNotes }}
|
||||
</span>
|
||||
</QTd>
|
||||
</QTr>
|
||||
</template>
|
||||
|
@ -643,7 +629,11 @@ watch(route, () => {
|
|||
}
|
||||
|
||||
:deep(.q-table) {
|
||||
table-layout: auto;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
tbody tr td {
|
||||
&:nth-child(1) {
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { ref, nextTick } from 'vue';
|
||||
import { stateQueryGuard } from 'src/router/hooks';
|
||||
import { useStateQueryStore } from 'src/stores/useStateQueryStore';
|
||||
|
||||
vi.mock('src/stores/useStateQueryStore', () => {
|
||||
const isLoading = ref(true);
|
||||
return {
|
||||
useStateQueryStore: () => ({
|
||||
isLoading: () => isLoading,
|
||||
setLoading: isLoading,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('hooks', () => {
|
||||
describe('stateQueryGuard', () => {
|
||||
const foo = { name: 'foo' };
|
||||
it('should wait until the state query is not loading and then call next()', async () => {
|
||||
const next = vi.fn();
|
||||
|
||||
stateQueryGuard(foo, { name: 'bar' }, next);
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
|
||||
useStateQueryStore().setLoading.value = false;
|
||||
await nextTick();
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should ignore if both routes are the same', () => {
|
||||
const next = vi.fn();
|
||||
stateQueryGuard(foo, foo, next);
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,95 @@
|
|||
import { useRole } from 'src/composables/useRole';
|
||||
import { useUserConfig } from 'src/composables/useUserConfig';
|
||||
import { useTokenConfig } from 'src/composables/useTokenConfig';
|
||||
import { useAcl } from 'src/composables/useAcl';
|
||||
import { isLoggedIn } from 'src/utils/session';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
import { useStateQueryStore } from 'src/stores/useStateQueryStore';
|
||||
import { watch } from 'vue';
|
||||
import { i18n } from 'src/boot/i18n';
|
||||
|
||||
let session = null;
|
||||
const { t, te } = i18n.global;
|
||||
|
||||
export async function navigationGuard(to, from, next, Router, state) {
|
||||
if (!session) session = useSession();
|
||||
const outLayout = Router.options.routes[0].children.map((r) => r.name);
|
||||
if (!session.isLoggedIn() && !outLayout.includes(to.name)) {
|
||||
return next({ name: 'Login', query: { redirect: to.fullPath } });
|
||||
}
|
||||
|
||||
if (isLoggedIn()) {
|
||||
const stateRoles = state.getRoles().value;
|
||||
if (stateRoles.length === 0) {
|
||||
await useRole().fetch();
|
||||
await useAcl().fetch();
|
||||
await useUserConfig().fetch();
|
||||
await useTokenConfig().fetch();
|
||||
}
|
||||
const matches = to.matched;
|
||||
const hasRequiredAcls = matches.every((route) => {
|
||||
const meta = route.meta;
|
||||
if (!meta?.acls) return true;
|
||||
return useAcl().hasAny(meta.acls);
|
||||
});
|
||||
if (!hasRequiredAcls) return next({ path: '/' });
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export async function stateQueryGuard(to, from, next) {
|
||||
if (to.name !== from.name) {
|
||||
const stateQuery = useStateQueryStore();
|
||||
await waitUntilFalse(stateQuery.isLoading());
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export function setPageTitle(to) {
|
||||
let title = t(`login.title`);
|
||||
|
||||
const matches = to.matched;
|
||||
if (matches && matches.length > 1) {
|
||||
const module = matches[1];
|
||||
const moduleTitle = module.meta?.title;
|
||||
if (moduleTitle) {
|
||||
title = t(`globals.pageTitles.${moduleTitle}`);
|
||||
}
|
||||
}
|
||||
|
||||
const childPage = to.meta;
|
||||
const childPageTitle = childPage?.title;
|
||||
if (childPageTitle && matches.length > 2) {
|
||||
if (title != '') title += ': ';
|
||||
|
||||
const moduleLocale = `globals.pageTitles.${childPageTitle}`;
|
||||
const pageTitle = te(moduleLocale)
|
||||
? t(moduleLocale)
|
||||
: t(`globals.pageTitles.${childPageTitle}`);
|
||||
const idParam = to.params?.id;
|
||||
const idPageTitle = `${idParam} - ${pageTitle}`;
|
||||
const builtTitle = idParam ? idPageTitle : pageTitle;
|
||||
|
||||
title += builtTitle;
|
||||
}
|
||||
|
||||
document.title = title;
|
||||
}
|
||||
|
||||
function waitUntilFalse(ref) {
|
||||
return new Promise((resolve) => {
|
||||
if (!ref.value) return resolve();
|
||||
const stop = watch(
|
||||
ref,
|
||||
(val) => {
|
||||
if (!val) {
|
||||
stop();
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
});
|
||||
}
|
|
@ -6,101 +6,25 @@ import {
|
|||
createWebHashHistory,
|
||||
} from 'vue-router';
|
||||
import routes from './routes';
|
||||
import { i18n } from 'src/boot/i18n';
|
||||
import { useState } from 'src/composables/useState';
|
||||
import { useRole } from 'src/composables/useRole';
|
||||
import { useUserConfig } from 'src/composables/useUserConfig';
|
||||
import { useTokenConfig } from 'src/composables/useTokenConfig';
|
||||
import { useAcl } from 'src/composables/useAcl';
|
||||
import { isLoggedIn } from 'src/utils/session';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
import { navigationGuard, setPageTitle, stateQueryGuard } from './hooks';
|
||||
|
||||
let session = null;
|
||||
const { t, te } = i18n.global;
|
||||
|
||||
const createHistory = process.env.SERVER
|
||||
? createMemoryHistory
|
||||
: process.env.VUE_ROUTER_MODE === 'history'
|
||||
? createWebHistory
|
||||
: createWebHashHistory;
|
||||
const webHistory =
|
||||
process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory;
|
||||
const createHistory = process.env.SERVER ? createMemoryHistory : webHistory;
|
||||
|
||||
const Router = createRouter({
|
||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||
routes,
|
||||
|
||||
// Leave this as is and make changes in quasar.conf.js instead!
|
||||
// quasar.conf.js -> build -> vueRouterMode
|
||||
// quasar.conf.js -> build -> publicPath
|
||||
history: createHistory(process.env.VUE_ROUTER_BASE),
|
||||
});
|
||||
|
||||
/*
|
||||
* If not building with SSR mode, you can
|
||||
* directly export the Router instantiation;
|
||||
*
|
||||
* The function below can be async too; either use
|
||||
* async/await or return a Promise which resolves
|
||||
* with the Router instance.
|
||||
*/
|
||||
export { Router };
|
||||
export default defineRouter(function (/* { store, ssrContext } */) {
|
||||
export default defineRouter(() => {
|
||||
const state = useState();
|
||||
Router.beforeEach(async (to, from, next) => {
|
||||
if (!session) session = useSession();
|
||||
const outLayout = Router.options.routes[0].children.map((r) => r.name);
|
||||
if (!session.isLoggedIn() && !outLayout.includes(to.name)) {
|
||||
return next({ name: 'Login', query: { redirect: to.fullPath } });
|
||||
}
|
||||
|
||||
if (isLoggedIn()) {
|
||||
const stateRoles = state.getRoles().value;
|
||||
if (stateRoles.length === 0) {
|
||||
await useRole().fetch();
|
||||
await useAcl().fetch();
|
||||
await useUserConfig().fetch();
|
||||
await useTokenConfig().fetch();
|
||||
}
|
||||
const matches = to.matched;
|
||||
const hasRequiredAcls = matches.every((route) => {
|
||||
const meta = route.meta;
|
||||
if (!meta?.acls) return true;
|
||||
return useAcl().hasAny(meta.acls);
|
||||
});
|
||||
if (!hasRequiredAcls) return next({ path: '/' });
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
Router.afterEach((to) => {
|
||||
let title = t(`login.title`);
|
||||
|
||||
const matches = to.matched;
|
||||
if (matches && matches.length > 1) {
|
||||
const module = matches[1];
|
||||
const moduleTitle = module.meta && module.meta.title;
|
||||
if (moduleTitle) {
|
||||
title = t(`globals.pageTitles.${moduleTitle}`);
|
||||
}
|
||||
}
|
||||
|
||||
const childPage = to.meta;
|
||||
const childPageTitle = childPage && childPage.title;
|
||||
if (childPageTitle && matches.length > 2) {
|
||||
if (title != '') title += ': ';
|
||||
|
||||
const moduleLocale = `globals.pageTitles.${childPageTitle}`;
|
||||
const pageTitle = te(moduleLocale)
|
||||
? t(moduleLocale)
|
||||
: t(`globals.pageTitles.${childPageTitle}`);
|
||||
const idParam = to.params && to.params.id;
|
||||
const idPageTitle = `${idParam} - ${pageTitle}`;
|
||||
const builtTitle = idParam ? idPageTitle : pageTitle;
|
||||
|
||||
title += builtTitle;
|
||||
}
|
||||
document.title = title;
|
||||
});
|
||||
Router.beforeEach((to, from, next) => navigationGuard(to, from, next, Router, state));
|
||||
Router.beforeEach(stateQueryGuard);
|
||||
Router.afterEach(setPageTitle);
|
||||
|
||||
Router.onError(({ message }) => {
|
||||
const errorMessages = [
|
||||
|
|
|
@ -166,7 +166,7 @@ const vehicleCard = {
|
|||
component: () => import('src/pages/Route/Vehicle/Card/VehicleCard.vue'),
|
||||
redirect: { name: 'VehicleSummary' },
|
||||
meta: {
|
||||
menu: ['VehicleBasicData'],
|
||||
menu: ['VehicleBasicData', 'VehicleNotes'],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
|
@ -187,6 +187,15 @@ const vehicleCard = {
|
|||
},
|
||||
component: () => import('src/pages/Route/Vehicle/Card/VehicleBasicData.vue'),
|
||||
},
|
||||
{
|
||||
name: 'VehicleNotes',
|
||||
path: 'notes',
|
||||
meta: {
|
||||
title: 'notes',
|
||||
icon: 'vn:notes',
|
||||
},
|
||||
component: () => import('src/pages/Route/Vehicle/Card/VehicleNotes.vue'),
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Cypress.Commands.add('selectTravel', (warehouse = '1') => {
|
||||
cy.get('i[data-cy="Travel_icon"]').click();
|
||||
cy.get('input[data-cy="Warehouse Out_select"]').type(warehouse);
|
||||
cy.selectOption('input[data-cy="Warehouse Out_select"]', warehouse);
|
||||
cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click();
|
||||
cy.get('button[data-cy="save-filter-travel-form"]').click();
|
||||
cy.get('tr').eq(1).click();
|
||||
|
@ -9,7 +9,6 @@ Cypress.Commands.add('selectTravel', (warehouse = '1') => {
|
|||
Cypress.Commands.add('deleteEntry', () => {
|
||||
cy.get('[data-cy="descriptor-more-opts"]').should('be.visible').click();
|
||||
cy.waitForElement('div[data-cy="delete-entry"]').click();
|
||||
cy.url().should('include', 'list');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('createEntry', () => {
|
||||
|
|
|
@ -28,12 +28,8 @@ describe('EntryDescriptor', () => {
|
|||
cy.get('.q-notification__message')
|
||||
.eq(2)
|
||||
.should('have.text', 'Entry prices recalculated');
|
||||
|
||||
cy.get('[data-cy="descriptor-more-opts"]').click();
|
||||
cy.deleteEntry();
|
||||
|
||||
cy.log(previousUrl);
|
||||
|
||||
cy.visit(previousUrl);
|
||||
|
||||
cy.waitForElement('[data-cy="entry-buys"]');
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
describe('Vehicle Notes', () => {
|
||||
const selectors = {
|
||||
addNoteInput: 'Add note here..._input',
|
||||
saveNoteBtn: 'saveNote',
|
||||
deleteNoteBtn: 'notesRemoveNoteBtn',
|
||||
noteCard: '.column.full-width > :nth-child(1) > .q-card__section--vert',
|
||||
};
|
||||
|
||||
const noteText = 'Golpe parachoques trasero';
|
||||
const newNoteText = 'probando';
|
||||
|
||||
beforeEach(() => {
|
||||
cy.viewport(1920, 1080);
|
||||
cy.login('developer');
|
||||
cy.visit(`/#/route/vehicle/1/notes`);
|
||||
});
|
||||
|
||||
it('Should add new note', () => {
|
||||
cy.dataCy(selectors.addNoteInput).should('be.visible').type(newNoteText);
|
||||
cy.dataCy(selectors.saveNoteBtn).click();
|
||||
cy.validateContent(selectors.noteCard, newNoteText);
|
||||
});
|
||||
|
||||
it('Should delete note', () => {
|
||||
cy.dataCy(selectors.deleteNoteBtn).first().should('be.visible').click();
|
||||
cy.get(selectors.noteCard).first().should('have.text', noteText);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue