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

This commit is contained in:
Javier Segarra 2024-06-11 07:37:06 +00:00
commit adbc9dee2c
14 changed files with 189 additions and 24 deletions

View File

@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- (Item) => Se añade la opción de añadir un comentario del motivo de hacer una foto
- (Worker) => Se añade la opción de crear un trabajador ajeno a la empresa
- (Route) => Ahora se muestran todos los cmrs
## [2418.01]

33
src/composables/useAcl.js Normal file
View File

@ -0,0 +1,33 @@
import axios from 'axios';
import { useState } from './useState';
export function useAcl() {
const state = useState();
async function fetch() {
const { data } = await axios.get('VnUsers/acls');
const acls = {};
data.forEach((acl) => {
acls[acl.model] = acls[acl.model] || {};
acls[acl.model][acl.property] = acls[acl.model][acl.property] || {};
acls[acl.model][acl.property][acl.accessType] = true;
});
state.setAcls(acls);
}
function hasAny(model, prop, accessType) {
const acls = state.getAcls().value[model];
if (acls)
return ['*', prop].some((key) => {
const acl = acls[key];
return acl && (acl['*'] || acl[accessType]);
});
}
return {
fetch,
hasAny,
state,
};
}

View File

@ -1,5 +1,6 @@
import { useState } from './useState';
import { useRole } from './useRole';
import { useAcl } from './useAcl';
import { useUserConfig } from './useUserConfig';
import axios from 'axios';
import useNotify from './useNotify';
@ -88,6 +89,7 @@ export function useSession() {
setSession(data);
await useRole().fetch();
await useAcl().fetch();
await useUserConfig().fetch();
await useTokenConfig().fetch();

View File

@ -15,6 +15,7 @@ if (sessionStorage.getItem('user'))
user.value = JSON.parse(sessionStorage.getItem('user'));
const roles = ref([]);
const acls = ref([]);
const tokenConfig = ref({});
const drawer = ref(true);
const headerMounted = ref(false);
@ -42,6 +43,14 @@ export function useState() {
function setRoles(data) {
roles.value = data;
}
function getAcls() {
return computed(() => acls.value);
}
function setAcls(data) {
acls.value = data;
}
function getTokenConfig() {
return computed(() => {
return tokenConfig.value;
@ -69,6 +78,8 @@ export function useState() {
setUser,
getRoles,
setRoles,
getAcls,
setAcls,
getTokenConfig,
setTokenConfig,
set,

View File

@ -962,7 +962,7 @@ roadmap:
route:
pageTitles:
routes: Routes
cmrsList: External CMRs list
cmrsList: CMRs list
RouteList: List
routeCreate: New route
basicData: Basic Data

View File

@ -950,7 +950,7 @@ roadmap:
route:
pageTitles:
routes: Rutas
cmrsList: Listado de CMRs externos
cmrsList: Listado de CMRs
RouteList: Listado
routeCreate: Nueva ruta
basicData: Datos básicos

View File

@ -234,7 +234,7 @@ const showBalancePdf = (balance) => {
<template #body-cell-employee="{ row }">
<QTd auto-width @click.stop>
<QBtn color="blue" flat no-caps>{{ row.userName }}</QBtn>
<WorkerDescriptorProxy :id="row.clientFk" />
<WorkerDescriptorProxy :id="row.workerFk" />
</QTd>
</template>

View File

@ -1,11 +1,11 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
const { t } = useI18n();
const props = defineProps({
@ -16,10 +16,11 @@ const props = defineProps({
});
const countries = ref();
const warehouses = ref();
</script>
<template>
<FetchData url="Countries" @on-fetch="(data) => (countries = data)" auto-load />
<FetchData url="Warehouses" @on-fetch="(data) => (warehouses = data)" auto-load />
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
@ -93,13 +94,13 @@ const countries = ref();
<QItemSection v-if="!countries">
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="countries" class="q-mb-sm">
<QSelect
<QItemSection v-if="countries">
<VnSelect
:label="t('route.cmr.list.country')"
v-model="params.country"
:options="countries"
option-value="country"
option-label="country"
option-label="name"
option-value="id"
transition-show="jump-down"
transition-hide="jump-up"
emit-value
@ -111,9 +112,23 @@ const countries = ref();
<template #prepend>
<QIcon name="flag" size="sm"></QIcon>
</template>
</QSelect>
</VnSelect>
</QItemSection>
</QItem>
<QItem>
<VnSelect
:label="t('globals.warehouse')"
:options="warehouses"
hide-selected
option-label="name"
option-value="id"
v-model="params.warehouseFk"
rounded
dense
outlined
>
</VnSelect>
</QItem>
<QItem>
<QItemSection>
<VnInputDate
@ -126,7 +141,6 @@ const countries = ref();
</template>
</VnFilterPanel>
</template>
<i18n>
en:
params:

View File

@ -14,6 +14,7 @@ const { t } = useI18n();
const { getTokenMultimedia } = useSession();
const token = getTokenMultimedia();
const selected = ref([]);
const warehouses = ref([]);
const columns = computed(() => [
{
@ -63,6 +64,13 @@ const columns = computed(() => [
sortable: true,
headerStyle: 'padding-left: 33px',
},
{
name: 'warehouseFk',
label: t('globals.warehouse'),
field: ({ warehouseFk }) => warehouseFk,
align: 'center',
sortable: true,
},
{
name: 'icons',
align: 'center',
@ -99,7 +107,7 @@ function downloadPdfs() {
<div class="list">
<VnPaginate
data-key="CmrList"
:url="`Routes/getExternalCmrs`"
:url="`Routes/cmrs`"
order="cmrFk DESC"
limit="null"
auto-load
@ -147,6 +155,11 @@ function downloadPdfs() {
<CustomerDescriptorProxy :id="value" />
</QTd>
</template>
<template #body-cell-warehouseFk="{ value }">
<QTd align="center">
{{ warehouses.find(({ id }) => id === value)?.name }}
</QTd>
</template>
<template #body-cell-icons="{ value }">
<QTd align="center">
<a :href="getCmrUrl(value)" target="_blank">

View File

@ -86,7 +86,7 @@ onBeforeMount(async () => {
url-create="Workers/new"
model="worker"
:form-initial-data="formData"
@on-data-saved="({ id }) => $router.push({ path: `/worker/${id}` })"
@on-data-saved="(__, { id }) => $router.push({ path: `/worker/${id}` })"
>
<template #form="{ data, validate }">
<VnRow>

View File

@ -13,6 +13,7 @@ import { useRole } from 'src/composables/useRole';
import { useUserConfig } from 'src/composables/useUserConfig';
import { toLowerCamel } from 'src/filters';
import { useTokenConfig } from 'src/composables/useTokenConfig';
import { useAcl } from 'src/composables/useAcl';
const state = useState();
const session = useSession();
@ -55,6 +56,7 @@ export default route(function (/* { store, ssrContext } */) {
const stateRoles = state.getRoles().value;
if (stateRoles.length === 0) {
await useRole().fetch();
await useAcl().fetch();
await useUserConfig().fetch();
await useTokenConfig().fetch();
}

View File

@ -0,0 +1,88 @@
import { vi, describe, expect, it, beforeAll, afterAll } from 'vitest';
import { axios, flushPromises } from 'app/test/vitest/helper';
import { useAcl } from 'src/composables/useAcl';
describe('useAcl', () => {
const acl = useAcl();
const mockAcls = [
{
model: 'Address',
property: '*',
accessType: '*',
permission: 'ALLOW',
principalType: 'ROLE',
principalId: 'employee',
},
{
model: 'Worker',
property: 'holidays',
accessType: 'READ',
permission: 'ALLOW',
principalType: 'ROLE',
principalId: 'employee',
},
{
model: 'Url',
property: 'getByUser',
accessType: 'READ',
permission: 'ALLOW',
principalType: 'ROLE',
principalId: '$everyone',
},
{
model: 'TpvTransaction',
property: 'start',
accessType: 'WRITE',
permission: 'ALLOW',
principalType: 'ROLE',
principalId: '$authenticated',
},
];
beforeAll(async () => {
vi.spyOn(axios, 'get').mockResolvedValue({ data: mockAcls });
await acl.fetch();
});
afterAll(async () => await flushPromises());
describe('hasAny', () => {
it('should return false if no roles matched', async () => {
expect(acl.hasAny('Worker', 'updateAttributes', 'WRITE')).toBeFalsy();
});
it('should return false if no roles matched', async () => {
expect(acl.hasAny('Worker', 'holidays', 'READ')).toBeTruthy();
});
describe('*', () => {
it('should return true if an acl matched', async () => {
expect(acl.hasAny('Address', '*', 'WRITE')).toBeTruthy();
});
it('should return false if no acls matched', async () => {
expect(acl.hasAny('Worker', '*', 'READ')).toBeFalsy();
});
});
describe('$authenticated', () => {
it('should return false if no acls matched', async () => {
expect(acl.hasAny('Url', 'getByUser', '*')).toBeFalsy();
});
it('should return true if an acl matched', async () => {
expect(acl.hasAny('Url', 'getByUser', 'READ')).toBeTruthy();
});
});
describe('$everyone', () => {
it('should return false if no acls matched', async () => {
expect(acl.hasAny('TpvTransaction', 'start', 'READ')).toBeFalsy();
});
it('should return false if an acl matched', async () => {
expect(acl.hasAny('TpvTransaction', 'start', 'WRITE')).toBeTruthy();
});
});
});
});

View File

@ -1,5 +1,5 @@
import { vi, describe, expect, it, beforeAll, beforeEach } from 'vitest';
import { axios, flushPromises } from 'app/test/vitest/helper';
import { axios } from 'app/test/vitest/helper';
import { useSession } from 'composables/useSession';
import { useState } from 'composables/useState';
@ -87,13 +87,17 @@ describe('session', () => {
},
},
];
beforeEach(() => {
vi.spyOn(axios, 'get').mockImplementation((url) => {
if (url === 'VnUsers/acls') return Promise.resolve({ data: [] });
return Promise.resolve({
data: { roles: rolesData, user: expectedUser },
});
});
});
it('should fetch the user roles and then set token in the sessionStorage', async () => {
const expectedRoles = ['salesPerson', 'admin'];
vi.spyOn(axios, 'get').mockResolvedValue({
data: { roles: rolesData, user: expectedUser },
});
const expectedToken = 'mySessionToken';
const expectedTokenMultimedia = 'mySessionTokenMultimedia';
const keepLogin = false;
@ -117,10 +121,6 @@ describe('session', () => {
it('should fetch the user roles and then set token in the localStorage', async () => {
const expectedRoles = ['salesPerson', 'admin'];
vi.spyOn(axios, 'get').mockResolvedValue({
data: { roles: rolesData, user: expectedUser },
});
const expectedToken = 'myLocalToken';
const expectedTokenMultimedia = 'myLocalTokenMultimedia';
const keepLogin = true;

View File

@ -23,8 +23,9 @@ describe('Login', () => {
},
};
vi.spyOn(axios, 'post').mockResolvedValueOnce({ data: { token: 'token' } });
vi.spyOn(axios, 'get').mockResolvedValue({
data: { roles: [], user: expectedUser , multimediaToken: {id:'multimediaToken' }},
vi.spyOn(axios, 'get').mockImplementation((url) => {
if (url === 'VnUsers/acls') return Promise.resolve({ data: [] });
return Promise.resolve({data: { roles: [], user: expectedUser , multimediaToken: {id:'multimediaToken' }}});
});
vi.spyOn(vm.quasar, 'notify');