Merge branch 'dev' into 8449-vnLogFilter
gitea/salix-front/pipeline/pr-dev This commit looks good Details

This commit is contained in:
Alex Moreno 2025-03-28 10:55:43 +00:00
commit e05d3140a3
11 changed files with 182 additions and 124 deletions

View File

@ -9,7 +9,7 @@
data-cy="descriptor-more-opts" data-cy="descriptor-more-opts"
> >
<QTooltip> <QTooltip>
{{ $t('components.cardDescriptor.moreOptions') }} {{ $t('components.vnDescriptor.moreOptions') }}
</QTooltip> </QTooltip>
<QMenu ref="menuRef" data-cy="descriptor-more-opts-menu"> <QMenu ref="menuRef" data-cy="descriptor-more-opts-menu">
<QList data-cy="descriptor-more-opts_list"> <QList data-cy="descriptor-more-opts_list">

View File

@ -248,7 +248,7 @@ function getBadgeAttrs(row) {
let timeDiff = today - timeTicket; let timeDiff = today - timeTicket;
if (timeDiff > 0) return { color: 'info', 'text-color': 'black' }; if (timeDiff < 0) return { color: 'warning', 'text-color': 'black' };
switch (row.entryTypeCode) { switch (row.entryTypeCode) {
case 'regularization': case 'regularization':
case 'life': case 'life':
@ -273,7 +273,7 @@ function getBadgeAttrs(row) {
default: default:
break; break;
} }
if (timeDiff < 0) return { color: 'warning', 'text-color': 'black' }; if (timeDiff > 0) return { color: 'info', 'text-color': 'black' };
return { color: 'transparent' }; return { color: 'transparent' };
} }

View File

@ -1,13 +1,14 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import DepartmentDescriptorProxy from '../Worker/Department/Card/DepartmentDescriptorProxy.vue';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
import { toDateFormat } from 'src/filters/date.js'; import { toDateFormat } from 'src/filters/date.js';
import VnTable from 'src/components/VnTable/VnTable.vue'; import VnTable from 'src/components/VnTable/VnTable.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnRow from 'src/components/ui/VnRow.vue'; import VnRow from 'src/components/ui/VnRow.vue';
import { dateRange } from 'src/filters'; import { dateRange } from 'src/filters';
import useOpenURL from 'src/composables/useOpenURL';
const { t } = useI18n(); const { t } = useI18n();
const dates = dateRange(Date.vnNew()); const dates = dateRange(Date.vnNew());
@ -94,6 +95,7 @@ const columns = computed(() => [
columnClass: 'no-padding', columnClass: 'no-padding',
}, },
]); ]);
const openTab = (id) => useOpenURL(`#/customer/${id}/summary`);
</script> </script>
<template> <template>
@ -113,6 +115,8 @@ const columns = computed(() => [
:disable-option="{ card: true }" :disable-option="{ card: true }"
dense dense
class="q-px-none" class="q-px-none"
:row-click="({ id }) => openTab(id)"
:row-ctrl-click="(_, { id }) => openTab(id)"
> >
<template #top-left> <template #top-left>
<VnRow> <VnRow>
@ -121,12 +125,16 @@ const columns = computed(() => [
</VnRow> </VnRow>
</template> </template>
<template #column-departmentFk="{ row }"> <template #column-departmentFk="{ row }">
<span class="link" :title="row.department" v-text="row.department" /> <span @click.stop.prevent class="link" :title="row.department">
<WorkerDescriptorProxy :id="row.departmentFk" dense /> {{ row.department }}
<DepartmentDescriptorProxy :id="row.departmentFk" dense
/></span>
</template> </template>
<template #column-clientFk="{ row }"> <template #column-clientFk="{ row }">
<span class="link" :title="row.clientName" v-text="row.clientName" /> <span @click.stop.prevent class="link" :title="row.clientName">
<CustomerDescriptorProxy :id="row.clientFk" /> {{ row.clientName }}
<CustomerDescriptorProxy :id="row.clientFk" dense
/></span>
</template> </template>
</VnTable> </VnTable>
</template> </template>

View File

@ -9,6 +9,7 @@ import { toDateFormat, toDateTimeFormat } from 'src/filters/date.js';
import { toCurrency } from 'src/filters'; import { toCurrency } from 'src/filters';
import { useVnConfirm } from 'composables/useVnConfirm'; import { useVnConfirm } from 'composables/useVnConfirm';
import axios from 'axios'; import axios from 'axios';
import useOpenURL from 'src/composables/useOpenURL';
const { t } = useI18n(); const { t } = useI18n();
const { openConfirmationModal } = useVnConfirm(); const { openConfirmationModal } = useVnConfirm();
@ -108,8 +109,7 @@ const removeOrders = async () => {
await table.value.reload(); await table.value.reload();
}; };
const openTab = (id) => const openTab = (id) => useOpenURL(`#/order/${id}/summary`);
window.open(`#/order/${id}/summary`, '_blank', 'noopener, noreferrer');
</script> </script>
<template> <template>
<VnTable <VnTable
@ -129,6 +129,7 @@ const openTab = (id) =>
}" }"
default-mode="table" default-mode="table"
:row-click="({ id }) => openTab(id)" :row-click="({ id }) => openTab(id)"
:row-ctrl-click="(_, { id }) => openTab(id)"
v-model:selected="selectedRows" v-model:selected="selectedRows"
:disable-option="{ card: true }" :disable-option="{ card: true }"
> >
@ -177,16 +178,16 @@ const openTab = (id) =>
</template> </template>
<template #column-clientFk="{ row }"> <template #column-clientFk="{ row }">
<QTd @click.stop> <span class="link" @click.stop :title="row.clientName">
<span class="link" v-text="row.clientName" :title="row.clientName" /> {{ row.clientName }}
<CustomerDescriptorProxy :id="row.clientFk" /> <CustomerDescriptorProxy :id="row.clientFk" dense
</QTd> /></span>
</template> </template>
<template #column-departmentFk="{ row }"> <template #column-departmentFk="{ row }">
<QTd @click.stop> <span class="link" @click.stop :title="row.departmentName">
<span class="link" v-text="row.departmentName" /> {{ row.departmentName }}
<DepartmentDescriptorProxy :id="row.departmentFk" dense /> <DepartmentDescriptorProxy :id="row.departmentFk" dense
</QTd> /></span>
</template> </template>
</VnTable> </VnTable>
</template> </template>

View File

@ -449,21 +449,19 @@ const openTab = (id) => useOpenURL(`#/ticket/${id}/sale`);
<span :title="row.province" v-text="row.province" /> <span :title="row.province" v-text="row.province" />
</template> </template>
<template #column-state="{ row }"> <template #column-state="{ row }">
<div @click.stop.prevent> <div v-if="row.refFk" @click.stop.prevent>
<div v-if="row.refFk"> <span class="link">{{ row.refFk }}</span>
<span class="link">{{ row.refFk }}</span> <InvoiceOutDescriptorProxy :id="row.invoiceOutId" />
<InvoiceOutDescriptorProxy :id="row.invoiceOutId" />
</div>
<QBadge
v-else
:color="stateColors[row.classColor] || 'transparent'"
:text-color="stateColors[row.classColor] ? 'black' : 'white'"
class="q-pa-sm"
style="font-size: 14px"
>
{{ row.state }}
</QBadge>
</div> </div>
<QBadge
v-else
:color="stateColors[row.classColor] || 'transparent'"
:text-color="stateColors[row.classColor] ? 'black' : 'white'"
class="q-pa-sm"
style="font-size: 14px"
>
{{ row.state }}
</QBadge>
</template> </template>
<template #column-isFragile="{ row }"> <template #column-isFragile="{ row }">
<QIcon v-if="row.isFragile" name="local_bar" color="primary" size="sm"> <QIcon v-if="row.isFragile" name="local_bar" color="primary" size="sm">

View File

@ -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();
});
});
});

95
src/router/hooks.js Normal file
View File

@ -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 },
);
});
}

View File

@ -6,101 +6,25 @@ import {
createWebHashHistory, createWebHashHistory,
} from 'vue-router'; } from 'vue-router';
import routes from './routes'; import routes from './routes';
import { i18n } from 'src/boot/i18n';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import { useRole } from 'src/composables/useRole'; import { navigationGuard, setPageTitle, stateQueryGuard } from './hooks';
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';
let session = null; const webHistory =
const { t, te } = i18n.global; process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory;
const createHistory = process.env.SERVER ? createMemoryHistory : webHistory;
const createHistory = process.env.SERVER
? createMemoryHistory
: process.env.VUE_ROUTER_MODE === 'history'
? createWebHistory
: createWebHashHistory;
const Router = createRouter({ const Router = createRouter({
scrollBehavior: () => ({ left: 0, top: 0 }), scrollBehavior: () => ({ left: 0, top: 0 }),
routes, 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), 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 { Router };
export default defineRouter(function (/* { store, ssrContext } */) { export default defineRouter(() => {
const state = useState(); const state = useState();
Router.beforeEach(async (to, from, next) => { Router.beforeEach((to, from, next) => navigationGuard(to, from, next, Router, state));
if (!session) session = useSession(); Router.beforeEach(stateQueryGuard);
const outLayout = Router.options.routes[0].children.map((r) => r.name); Router.afterEach(setPageTitle);
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.onError(({ message }) => { Router.onError(({ message }) => {
const errorMessages = [ const errorMessages = [

View File

@ -1,6 +1,6 @@
Cypress.Commands.add('selectTravel', (warehouse = '1') => { Cypress.Commands.add('selectTravel', (warehouse = '1') => {
cy.get('i[data-cy="Travel_icon"]').click(); 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('div[role="listbox"] > div > div[role="option"]').eq(0).click();
cy.get('button[data-cy="save-filter-travel-form"]').click(); cy.get('button[data-cy="save-filter-travel-form"]').click();
cy.get('tr').eq(1).click(); cy.get('tr').eq(1).click();
@ -9,7 +9,6 @@ Cypress.Commands.add('selectTravel', (warehouse = '1') => {
Cypress.Commands.add('deleteEntry', () => { Cypress.Commands.add('deleteEntry', () => {
cy.get('[data-cy="descriptor-more-opts"]').should('be.visible').click(); cy.get('[data-cy="descriptor-more-opts"]').should('be.visible').click();
cy.waitForElement('div[data-cy="delete-entry"]').click(); cy.waitForElement('div[data-cy="delete-entry"]').click();
cy.url().should('include', 'list');
}); });
Cypress.Commands.add('createEntry', () => { Cypress.Commands.add('createEntry', () => {

View File

@ -28,12 +28,8 @@ describe('EntryDescriptor', () => {
cy.get('.q-notification__message') cy.get('.q-notification__message')
.eq(2) .eq(2)
.should('have.text', 'Entry prices recalculated'); .should('have.text', 'Entry prices recalculated');
cy.get('[data-cy="descriptor-more-opts"]').click();
cy.deleteEntry(); cy.deleteEntry();
cy.log(previousUrl);
cy.visit(previousUrl); cy.visit(previousUrl);
cy.waitForElement('[data-cy="entry-buys"]'); cy.waitForElement('[data-cy="entry-buys"]');

View File

@ -44,11 +44,12 @@ describe('EntryList', () => {
}, },
); );
checkBadgeDate( // fix on task https://redmine.verdnatura.es/issues/8638
/* checkBadgeDate(
'td[data-col-field="landed"] > div .bg-info', 'td[data-col-field="landed"] > div .bg-info',
(badgeDate, compareDate) => { (badgeDate, compareDate) => {
expect(badgeDate.getTime()).to.be.lessThan(compareDate.getTime()); expect(badgeDate.getTime()).to.be.lessThan(compareDate.getTime());
}, },
); ); */
}); });
}); });