#4074 useAcls #584

Merged
jorgep merged 30 commits from 4074-useAcls into dev 2024-09-10 11:50:45 +00:00
32 changed files with 164 additions and 186 deletions

View File

@ -105,7 +105,7 @@ async function setProvince(id, data) {
option-label="name" option-label="name"
option-value="id" option-value="id"
:rules="validate('postcode.city')" :rules="validate('postcode.city')"
:roles-allowed-to-create="['deliveryAssistant']" :acls="[{ model: 'Town', props: '*', accessType: 'WRITE' }]"
:emit-value="false" :emit-value="false"
clearable clearable
> >

View File

@ -38,7 +38,7 @@ async function onProvinceCreated(_, data) {
hide-selected hide-selected
v-model="provinceFk" v-model="provinceFk"
:rules="validate && validate('postcode.provinceFk')" :rules="validate && validate('postcode.provinceFk')"
:roles-allowed-to-create="['deliveryAssistant']" :acls="[{ model: 'Province', props: '*', accessType: 'WRITE' }]"
> >
<template #option="{ itemProps, opt }"> <template #option="{ itemProps, opt }">
<QItem v-bind="itemProps"> <QItem v-bind="itemProps">

View File

@ -1,6 +1,7 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { useRole } from 'src/composables/useRole'; import { useRole } from 'src/composables/useRole';
import { useAcl } from 'src/composables/useAcl';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
@ -11,6 +12,10 @@ const $props = defineProps({
type: Array, type: Array,
default: () => ['developer'], default: () => ['developer'],
}, },
acls: {
type: Array,
default: () => [],
},
actionIcon: { actionIcon: {
type: String, type: String,
default: 'add', default: 'add',
@ -22,15 +27,13 @@ const $props = defineProps({
}); });
const role = useRole(); const role = useRole();
const acl = useAcl();
const showForm = ref(false); const showForm = ref(false);
const isAllowedToCreate = computed(() => { const isAllowedToCreate = computed(() => {
if ($props.acls.length) return acl.hasAny($props.acls);
return role.hasAny($props.rolesAllowedToCreate); return role.hasAny($props.rolesAllowedToCreate);
Review

Mantenemos retrocompatibilidad.

Mantenemos retrocompatibilidad.
}); });
const toggleForm = () => {
showForm.value = !showForm.value;
};
</script> </script>
<template> <template>
@ -41,7 +44,7 @@ const toggleForm = () => {
> >
<template v-if="isAllowedToCreate" #append> <template v-if="isAllowedToCreate" #append>
<QIcon <QIcon
@click.stop.prevent="toggleForm()" @click.stop.prevent="$refs.dialog.show()"
:name="actionIcon" :name="actionIcon"
:size="actionIcon === 'add' ? 'xs' : 'sm'" :size="actionIcon === 'add' ? 'xs' : 'sm'"
:class="['default-icon', { '--add-icon': actionIcon === 'add' }]" :class="['default-icon', { '--add-icon': actionIcon === 'add' }]"
@ -51,7 +54,7 @@ const toggleForm = () => {
> >
<QTooltip v-if="tooltip">{{ tooltip }}</QTooltip> <QTooltip v-if="tooltip">{{ tooltip }}</QTooltip>
</QIcon> </QIcon>
<QDialog v-model="showForm" transition-show="scale" transition-hide="scale"> <QDialog ref="dialog" transition-show="scale" transition-hide="scale">
<slot name="form" /> <slot name="form" />
</QDialog> </QDialog>
</template> </template>

View File

@ -119,8 +119,8 @@ watch(
); );
watch( watch(
() => [props.url, props.filter], () => [props.url, props.filter, props.userParams],
([url, filter]) => mounted.value && fetch({ url, filter }) ([url, filter, userParams]) => mounted.value && fetch({ url, filter, userParams })
); );
const addFilter = async (filter, params) => { const addFilter = async (filter, params) => {

View File

@ -16,13 +16,18 @@ export function useAcl() {
state.setAcls(acls); state.setAcls(acls);
} }
function hasAny(model, prop, accessType) { function hasAny(acls) {
const acls = state.getAcls().value[model]; for (const acl of acls) {
if (acls) let { model, props, accessType } = acl;
return ['*', prop].some((key) => { const modelAcls = state.getAcls().value[model];
const acl = acls[key]; Array.isArray(props) || (props = [props]);
return acl && (acl['*'] || acl[accessType]); if (modelAcls)
}); return ['*', ...props].some((key) => {
const acl = modelAcls[key];
return acl && (acl['*'] || acl[accessType]);
});
}
return false;
} }
return { return {

View File

@ -37,6 +37,10 @@ a {
.link { .link {
color: $color-link; color: $color-link;
cursor: pointer; cursor: pointer;
&--white {
color: white;
}
} }
.tx-color-link { .tx-color-link {

View File

@ -2,7 +2,7 @@
import { computed, onBeforeMount, ref } from 'vue'; import { computed, onBeforeMount, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useRole } from 'src/composables/useRole'; import { useAcl } from 'src/composables/useAcl';
import axios from 'axios'; import axios from 'axios';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
@ -24,7 +24,7 @@ import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescr
const { openConfirmationModal } = useVnConfirm(); const { openConfirmationModal } = useVnConfirm();
const { sendEmail } = usePrintService(); const { sendEmail } = usePrintService();
const { t } = useI18n(); const { t } = useI18n();
const { hasAny } = useRole(); const { hasAny } = useAcl();
const session = useSession(); const session = useSession();
const tokenMultimedia = session.getTokenMultimedia(); const tokenMultimedia = session.getTokenMultimedia();
@ -284,7 +284,9 @@ const showBalancePdf = ({ id }) => {
> >
<VnInput <VnInput
v-model="scope.value" v-model="scope.value"
:disable="!hasAny(['administrative'])" :disable="
!hasAny([{ model: 'Receipt', props: '*', accessType: 'WRITE' }])
"
@keypress.enter="scope.set" @keypress.enter="scope.set"
autofocus autofocus
/> />

View File

@ -70,7 +70,7 @@ const getBankEntities = (data, formData) => {
<VnSelectDialog <VnSelectDialog
:label="t('Swift / BIC')" :label="t('Swift / BIC')"
:options="bankEntitiesOptions" :options="bankEntitiesOptions"
:roles-allowed-to-create="['salesAssistant', 'hr']" :acls="[{ model: 'BankEntity', props: '*', accessType: 'WRITE' }]"
:rules="validate('Worker.bankEntity')" :rules="validate('Worker.bankEntity')"
hide-selected hide-selected
option-label="name" option-label="name"

View File

@ -93,7 +93,7 @@ function handleLocation(data, location) {
<VnRow> <VnRow>
<VnLocation <VnLocation
:rules="validate('Worker.postcode')" :rules="validate('Worker.postcode')"
:roles-allowed-to-create="['deliveryAssistant']" :acls="[{ model: 'Town', props: '*', accessType: 'WRITE' }]"
v-model="data.postcode" v-model="data.postcode"
@update:model-value="(location) => handleLocation(data, location)" @update:model-value="(location) => handleLocation(data, location)"
/> />

View File

@ -86,7 +86,7 @@ function handleLocation(data, location) {
<VnRow> <VnRow>
<VnLocation <VnLocation
:rules="validate('Worker.postcode')" :rules="validate('Worker.postcode')"
:roles-allowed-to-create="['deliveryAssistant']" :acls="[{ model: 'Town', props: '*', accessType: 'WRITE' }]"
v-model="data.location" v-model="data.location"
@update:model-value="(location) => handleLocation(data, location)" @update:model-value="(location) => handleLocation(data, location)"
> >

View File

@ -412,7 +412,7 @@ function handleLocation(data, location) {
> >
<template #more-create-dialog="{ data }"> <template #more-create-dialog="{ data }">
<VnLocation <VnLocation
:roles-allowed-to-create="['deliveryAssistant']" :acls="[{ model: 'Province', props: '*', accessType: 'WRITE' }]"
v-model="data.location" v-model="data.location"
@update:model-value="(location) => handleLocation(data, location)" @update:model-value="(location) => handleLocation(data, location)"
/> />

View File

@ -92,7 +92,7 @@ function handleLocation(data, location) {
<VnLocation <VnLocation
:rules="validate('Worker.postcode')" :rules="validate('Worker.postcode')"
:roles-allowed-to-create="['deliveryAssistant']" :acls="[{ model: 'Town', props: '*', accessType: 'WRITE' }]"
v-model="data.location" v-model="data.location"
@update:model-value="(location) => handleLocation(data, location)" @update:model-value="(location) => handleLocation(data, location)"
/> />

View File

@ -176,7 +176,7 @@ function handleLocation(data, location) {
<div class="col"> <div class="col">
<VnLocation <VnLocation
:rules="validate('Worker.postcode')" :rules="validate('Worker.postcode')"
:roles-allowed-to-create="['deliveryAssistant']" :acls="[{ model: 'Town', props: '*', accessType: 'WRITE' }]"
v-model="data.postalCode" v-model="data.postalCode"
@update:model-value="(location) => handleLocation(data, location)" @update:model-value="(location) => handleLocation(data, location)"
></VnLocation> ></VnLocation>

View File

@ -5,7 +5,7 @@ import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import axios from 'axios'; import axios from 'axios';
import { toCurrency, toDate } from 'src/filters'; import { toCurrency, toDate } from 'src/filters';
import { useRole } from 'src/composables/useRole'; import { useAcl } from 'src/composables/useAcl';
import { downloadFile } from 'src/composables/downloadFile'; import { downloadFile } from 'src/composables/downloadFile';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { usePrintService } from 'composables/usePrintService'; import { usePrintService } from 'composables/usePrintService';
@ -24,7 +24,7 @@ const $props = defineProps({ id: { type: Number, default: null } });
const { push, currentRoute } = useRouter(); const { push, currentRoute } = useRouter();
const quasar = useQuasar(); const quasar = useQuasar();
const { hasAny } = useRole(); const { hasAny } = useAcl();
const { t } = useI18n(); const { t } = useI18n();
const { openReport, sendEmail } = usePrintService(); const { openReport, sendEmail } = usePrintService();
const arrayData = useArrayData(); const arrayData = useArrayData();
@ -195,7 +195,8 @@ async function cloneInvoice() {
push({ path: `/invoice-in/${data.id}/summary` }); push({ path: `/invoice-in/${data.id}/summary` });
} }
const isAdministrative = () => hasAny(['administrative']); const canEditProp = (props) =>
hasAny([{ model: 'InvoiceIn', props, accessType: 'WRITE' }]);
const isAgricultural = () => { const isAgricultural = () => {
if (!config.value) return false; if (!config.value) return false;
@ -283,7 +284,7 @@ const createInvoiceInCorrection = async () => {
<InvoiceInToBook> <InvoiceInToBook>
<template #content="{ book }"> <template #content="{ book }">
<QItem <QItem
v-if="!entity?.isBooked && isAdministrative()" v-if="!entity?.isBooked && canEditProp('toBook')"
v-ripple v-ripple
clickable clickable
@click="book(entityId)" @click="book(entityId)"
@ -293,7 +294,7 @@ const createInvoiceInCorrection = async () => {
</template> </template>
</InvoiceInToBook> </InvoiceInToBook>
<QItem <QItem
v-if="entity?.isBooked && isAdministrative()" v-if="entity?.isBooked && canEditProp('toUnbook')"
v-ripple v-ripple
clickable clickable
@click="triggerMenu('unbook')" @click="triggerMenu('unbook')"
@ -303,7 +304,7 @@ const createInvoiceInCorrection = async () => {
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem <QItem
v-if="isAdministrative()" v-if="canEditProp('deleteById')"
v-ripple v-ripple
clickable clickable
@click="triggerMenu('delete')" @click="triggerMenu('delete')"
@ -311,7 +312,7 @@ const createInvoiceInCorrection = async () => {
<QItemSection>{{ t('Delete invoice') }}</QItemSection> <QItemSection>{{ t('Delete invoice') }}</QItemSection>
</QItem> </QItem>
<QItem <QItem
v-if="isAdministrative()" v-if="canEditProp('clone')"
v-ripple v-ripple
clickable clickable
@click="triggerMenu('clone')" @click="triggerMenu('clone')"

View File

@ -7,8 +7,7 @@ import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import ItemDescriptorImage from 'src/pages/Item/Card/ItemDescriptorImage.vue'; import ItemDescriptorImage from 'src/pages/Item/Card/ItemDescriptorImage.vue';
import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue';
import VnTitle from 'src/components/common/VnTitle.vue';
Review

Porque se modifica este componente reemplazando por VnTitle??

Porque se modifica este componente reemplazando <component /> por VnTitle??
Review

Porque es un summary, los summary gastan VnTitle. Si te fijas, gasta un router-link o un span con la clase link...

Porque es un summary, los summary gastan VnTitle. Si te fijas, gasta un router-link o un span con la clase link...
import { useRole } from 'src/composables/useRole';
jorgep marked this conversation as resolved
Review

Porque no se hace con el archivo siguiente, crear una funcion llamada getUrl??

Porque no se hace con el archivo siguiente, crear una funcion llamada getUrl??
Review

No veo la necesidad, pero te lo cambio.

No veo la necesidad, pero te lo cambio.
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -19,23 +18,10 @@ const $props = defineProps({
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const roleState = useRole();
const entityId = computed(() => $props.id || route.params.id); const entityId = computed(() => $props.id || route.params.id);
const getUrl = (id, param) => `#/Item/${id}/${param}`;
const isBuyer = computed(() => {
return roleState.hasAny(['buyer']);
});
const isReplenisher = computed(() => {
return roleState.hasAny(['replenisher']);
});
const isAdministrative = computed(() => {
return roleState.hasAny(['administrative']);
});
</script> </script>
<template> <template>
<CardSummary <CardSummary
ref="summary" ref="summary"
@ -44,13 +30,15 @@ const isAdministrative = computed(() => {
data-key="ItemSummary" data-key="ItemSummary"
> >
<template #header-left> <template #header-left>
<router-link <QBtn
v-if="route.name !== 'ItemSummary'" v-if="$route.name !== 'ItemSummary'"
:to="{ name: 'ItemSummary', params: { id: entityId } }" :to="{ name: 'ItemSummary', params: { id: entityId } }"
class="header link" class="header link--white"
> icon="open_in_new"
<QIcon name="open_in_new" color="white" size="sm" /> flat
</router-link> dense
round
/>
</template> </template>
<template #header="{ entity: { item } }"> <template #header="{ entity: { item } }">
{{ item.id }} - {{ item.name }} {{ item.id }} - {{ item.name }}
@ -65,15 +53,10 @@ const isAdministrative = computed(() => {
/> />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<component <VnTitle
:is="isBuyer ? 'router-link' : 'span'" :url="getUrl(entityId, 'basic-data')"
:to="{ name: 'ItemBasicData', params: { id: entityId } }" :text="t('item.summary.basicData')"
class="header" />
:class="{ 'header-link': isBuyer }"
>
{{ t('item.summary.basicData') }}
<QIcon v-if="isBuyer" name="open_in_new" />
</component>
<VnLv :label="t('item.summary.name')" :value="item.name" /> <VnLv :label="t('item.summary.name')" :value="item.name" />
<VnLv :label="t('item.summary.completeName')" :value="item.longName" /> <VnLv :label="t('item.summary.completeName')" :value="item.longName" />
<VnLv :label="t('item.summary.family')" :value="item.itemType.name" /> <VnLv :label="t('item.summary.family')" :value="item.itemType.name" />
@ -104,15 +87,10 @@ const isAdministrative = computed(() => {
</VnLv> </VnLv>
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<component <VnTitle
:is="isBuyer ? 'router-link' : 'span'" :url="getUrl(entityId, 'basic-data')"
:to="{ name: 'ItemBasicData', params: { id: entityId } }" :text="t('item.summary.otherData')"
class="header" />
:class="{ 'header-link': isBuyer }"
>
{{ t('item.summary.otherData') }}
<QIcon v-if="isBuyer" name="open_in_new" />
</component>
<VnLv <VnLv
:label="t('item.summary.intrastatCode')" :label="t('item.summary.intrastatCode')"
:value="item.intrastat.id" :value="item.intrastat.id"
@ -137,15 +115,7 @@ const isAdministrative = computed(() => {
/> />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<component <VnTitle :url="getUrl(entityId, 'tags')" :text="t('item.summary.tags')" />
:is="isBuyer || isReplenisher ? 'router-link' : 'span'"
:to="{ name: 'ItemTags', params: { id: entityId } }"
class="header"
:class="{ 'header-link': isBuyer || isReplenisher }"
>
{{ t('item.summary.tags') }}
<QIcon v-if="isBuyer || isReplenisher" name="open_in_new" />
</component>
<VnLv <VnLv
v-for="(tag, index) in tags" v-for="(tag, index) in tags"
:key="index" :key="index"
@ -154,29 +124,14 @@ const isAdministrative = computed(() => {
/> />
</QCard> </QCard>
<QCard class="vn-one" v-if="item.description"> <QCard class="vn-one" v-if="item.description">
<component <VnTitle
:is="isBuyer ? 'router-link' : 'span'" :url="getUrl(entityId, 'basic-data')"
:to="{ name: 'ItemBasicData', params: { id: entityId } }" :text="t('item.summary.description')"
class="header" />
:class="{ 'header-link': isBuyer }" <p v-text="item.description" />
>
{{ t('item.summary.description') }}
<QIcon v-if="isBuyer" name="open_in_new" />
</component>
<p>
{{ item.description }}
</p>
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<component <VnTitle :url="getUrl(entityId, 'tax')" :text="t('item.summary.tax')" />
:is="isBuyer || isAdministrative ? 'router-link' : 'span'"
:to="{ name: 'ItemTax', params: { id: entityId } }"
class="header"
:class="{ 'header-link': isBuyer || isAdministrative }"
>
{{ t('item.summary.tax') }}
<QIcon v-if="isBuyer || isAdministrative" name="open_in_new" />
</component>
<VnLv <VnLv
v-for="(tax, index) in item.taxes" v-for="(tax, index) in item.taxes"
:key="index" :key="index"
@ -185,15 +140,10 @@ const isAdministrative = computed(() => {
/> />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<component <VnTitle
:is="isBuyer ? 'router-link' : 'span'" :url="getUrl(entityId, 'botanical')"
:to="{ name: 'ItemBotanical', params: { id: entityId } }" :text="t('item.summary.botanical')"
class="header" />
:class="{ 'header-link': isBuyer }"
>
{{ t('item.summary.botanical') }}
<QIcon v-if="isBuyer" name="open_in_new" />
</component>
<VnLv :label="t('item.summary.genus')" :value="botanical?.genus?.name" /> <VnLv :label="t('item.summary.genus')" :value="botanical?.genus?.name" />
<VnLv <VnLv
:label="t('item.summary.specie')" :label="t('item.summary.specie')"
@ -201,23 +151,19 @@ const isAdministrative = computed(() => {
/> />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<component <VnTitle
:is="isBuyer || isReplenisher ? 'router-link' : 'span'" :url="getUrl(entityId, 'barcode')"
:to="{ name: 'ItemBarcode', params: { id: entityId } }" :text="t('item.summary.barcode')"
class="header" />
:class="{ 'header-link': isBuyer || isReplenisher }" <p
> v-for="(barcode, index) in item.itemBarcode"
{{ t('item.summary.barcode') }} :key="index"
<QIcon v-if="isBuyer || isReplenisher" name="open_in_new" /> v-text="barcode.code"
</component> />
<p v-for="(barcode, index) in item.itemBarcode" :key="index">
{{ barcode.code }}
</p>
</QCard> </QCard>
</template> </template>
</CardSummary> </CardSummary>
</template> </template>
<i18n> <i18n>
en: en:
Este artículo necesita una foto: Este artículo necesita una foto Este artículo necesita una foto: Este artículo necesita una foto

View File

@ -83,7 +83,7 @@ function handleLocation(data, location) {
<VnRow> <VnRow>
<VnLocation <VnLocation
:rules="validate('Worker.postcode')" :rules="validate('Worker.postcode')"
:roles-allowed-to-create="['deliveryAssistant']" :acls="[{ model: 'Town', props: '*', accessType: 'WRITE' }]"
v-model="data.location" v-model="data.location"
@update:model-value="(location) => handleLocation(data, location)" @update:model-value="(location) => handleLocation(data, location)"
> >

View File

@ -129,7 +129,7 @@ function handleLocation(data, location) {
<VnRow> <VnRow>
<VnLocation <VnLocation
:rules="validate('Worker.postcode')" :rules="validate('Worker.postcode')"
:roles-allowed-to-create="['deliveryAssistant']" :acls="[{ model: 'Town', props: '*', accessType: 'WRITE' }]"
v-model="data.postCode" v-model="data.postCode"
@update:model-value="(location) => handleLocation(data, location)" @update:model-value="(location) => handleLocation(data, location)"
> >

View File

@ -4,13 +4,11 @@ import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import CardSummary from 'components/ui/CardSummary.vue'; import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import { useRole } from 'src/composables/useRole';
import { dashIfEmpty } from 'src/filters'; import { dashIfEmpty } from 'src/filters';
import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue';
import VnTitle from 'src/components/common/VnTitle.vue'; import VnTitle from 'src/components/common/VnTitle.vue';
const route = useRoute(); const route = useRoute();
const roleState = useRole();
const { t } = useI18n(); const { t } = useI18n();
const $props = defineProps({ const $props = defineProps({
@ -32,13 +30,7 @@ async function setData(data) {
} }
} }
const isAdministrative = computed(() => { const getUrl = (section) => `#/supplier/${entityId.value}/${section}`;
return roleState.hasAny(['administrative']);
});
function getUrl(section) {
return isAdministrative.value && `#/supplier/${entityId.value}/${section}`;
}
</script> </script>
<template> <template>

View File

@ -8,7 +8,7 @@ import VnConfirm from 'components/ui/VnConfirm.vue';
import axios from 'axios'; import axios from 'axios';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import { useRole } from 'src/composables/useRole'; import { useAcl } from 'src/composables/useAcl';
const $props = defineProps({ const $props = defineProps({
travel: { travel: {
@ -21,7 +21,6 @@ const { t } = useI18n();
const router = useRouter(); const router = useRouter();
const quasar = useQuasar(); const quasar = useQuasar();
const { notify } = useNotify(); const { notify } = useNotify();
const role = useRole();
const redirectToCreateView = (queryParams) => { const redirectToCreateView = (queryParams) => {
router.push({ name: 'TravelCreate', query: { travelData: queryParams } }); router.push({ name: 'TravelCreate', query: { travelData: queryParams } });
@ -42,9 +41,7 @@ const cloneTravelWithEntries = async () => {
} }
}; };
const isBuyer = computed(() => { const canDelete = computed(() => useAcl().hasAny('Travel','*','WRITE'));
return role.hasAny(['buyer']);
});
const openDeleteEntryDialog = (id) => { const openDeleteEntryDialog = (id) => {
quasar quasar
@ -81,7 +78,7 @@ const deleteTravel = async (id) => {
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem <QItem
v-if="isBuyer && travel.totalEntries === 0" v-if="canDelete && travel.totalEntries === 0"
v-ripple v-ripple
clickable clickable
@click="openDeleteEntryDialog(travel.id)" @click="openDeleteEntryDialog(travel.id)"

View File

@ -3,13 +3,13 @@ import { ref, computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import axios from 'axios'; import axios from 'axios';
import { useRole } from 'src/composables/useRole'; import { useAcl } from 'src/composables/useAcl';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
const { hasAny } = useRole(); const { hasAny } = useAcl();
const { t } = useI18n(); const { t } = useI18n();
const fetchData = ref(); const fetchData = ref();
const originaLockerId = ref(); const originaLockerId = ref();
@ -57,7 +57,11 @@ const init = async (data) => {
option-label="code" option-label="code"
option-value="id" option-value="id"
hide-selected hide-selected
:readonly="!hasAny(['productionBoss', 'hr'])" :readonly="
!hasAny([
{ model: 'Worker', props: '__get__locker', accessType: 'READ' },
])
"
/> />
</template> </template>
</FormModel> </FormModel>

View File

@ -13,6 +13,7 @@ import WorkerTimeControlCalendar from 'pages/Worker/Card/WorkerTimeControlCalend
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import axios from 'axios'; import axios from 'axios';
import { useRole } from 'src/composables/useRole'; import { useRole } from 'src/composables/useRole';
import { useAcl } from 'src/composables/useAcl';
import { useWeekdayStore } from 'src/stores/useWeekdayStore'; import { useWeekdayStore } from 'src/stores/useWeekdayStore';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
@ -26,7 +27,6 @@ import { date } from 'quasar';
const route = useRoute(); const route = useRoute();
const { t, locale } = useI18n(); const { t, locale } = useI18n();
const { notify } = useNotify(); const { notify } = useNotify();
const { hasAny } = useRole();
const _state = useState(); const _state = useState();
const user = _state.getUser(); const user = _state.getUser();
const stateStore = useStateStore(); const stateStore = useStateStore();
@ -66,9 +66,11 @@ const arrayData = useArrayData('workerData');
const worker = computed(() => arrayData.store?.data); const worker = computed(() => arrayData.store?.data);
const isHr = computed(() => hasAny(['hr'])); const isHr = computed(() => useRole().hasAny(['hr']));
Review

duda: en router(index hemos rremplazado userole por useAcl, aqui proque no?

duda: en router(index hemos rremplazado userole por useAcl, aqui proque no?
Review

En este caso en concreto, no lo he tocado porque lo abordaré en otra tarea, ya que tengo que cambiar permisos y añadir otro acl. En el redmite está indicado.

En este caso en concreto, no lo he tocado porque lo abordaré en otra tarea, ya que tengo que cambiar permisos y añadir otro acl. En el redmite está indicado.
const isHimSelf = computed(() => user.value.id === Number(route.params.id)); const canSend = computed(() => useAcl().hasAny('WorkerTimeControl', 'sendMail', 'WRITE'));
const isHimself = computed(() => user.value.id === Number(route.params.id));
const columns = computed(() => { const columns = computed(() => {
return weekdayStore.getLocales?.map((day, index) => { return weekdayStore.getLocales?.map((day, index) => {
@ -447,7 +449,7 @@ onMounted(async () => {
<div> <div>
<QBtnGroup push class="q-gutter-x-sm" flat> <QBtnGroup push class="q-gutter-x-sm" flat>
<QBtn <QBtn
v-if="isHimSelf && state" v-if="isHimself && state"
:label="t('Satisfied')" :label="t('Satisfied')"
color="primary" color="primary"
type="submit" type="submit"
@ -455,7 +457,7 @@ onMounted(async () => {
@click="isSatisfied()" @click="isSatisfied()"
/> />
<QBtn <QBtn
v-if="isHimSelf && state" v-if="isHimself && state"
:label="t('Not satisfied')" :label="t('Not satisfied')"
color="primary" color="primary"
type="submit" type="submit"
@ -466,14 +468,14 @@ onMounted(async () => {
</QBtnGroup> </QBtnGroup>
<QBtnGroup push class="q-gutter-x-sm q-ml-none" flat> <QBtnGroup push class="q-gutter-x-sm q-ml-none" flat>
<QBtn <QBtn
v-if="reason && state && (isHimSelf || isHr)" v-if="reason && state && (isHimself || isHr)"
:label="t('Reason')" :label="t('Reason')"
color="primary" color="primary"
type="submit" type="submit"
@click="showReasonForm()" @click="showReasonForm()"
/> />
<QBtn <QBtn
v-if="isHr && state !== 'CONFIRMED' && canResend" v-if="canSend && state !== 'CONFIRMED' && canResend"
:label="state ? t('Resend') : t('globals.send')" :label="state ? t('Resend') : t('globals.send')"
color="primary" color="primary"
type="submit" type="submit"
@ -603,7 +605,7 @@ onMounted(async () => {
<WorkerTimeReasonForm <WorkerTimeReasonForm
@on-submit="isUnsatisfied($event)" @on-submit="isUnsatisfied($event)"
:reason="reason" :reason="reason"
:is-him-self="isHimSelf" :is-himself="isHimself"
/> />
</QDialog> </QDialog>
</QPage> </QPage>

View File

@ -9,7 +9,7 @@ const $props = defineProps({
type: String, type: String,
default: '', default: '',
}, },
isHimSelf: { isHimself: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
@ -40,7 +40,7 @@ const closeForm = () => {
v-model="reasonFormData" v-model="reasonFormData"
type="textarea" type="textarea"
autogrow autogrow
:disable="!isHimSelf" :disable="!isHimself"
/> />
</template> </template>
</FormPopup> </FormPopup>

View File

@ -262,7 +262,7 @@ async function autofillBic(worker) {
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnLocation <VnLocation
:roles-allowed-to-create="['deliveryAssistant']" :acls="[{ model: 'Town', props: '*', accessType: 'WRITE' }]"
:options="postcodesOptions" :options="postcodesOptions"
v-model="data.location" v-model="data.location"
@update:model-value="(location) => handleLocation(data, location)" @update:model-value="(location) => handleLocation(data, location)"
@ -311,7 +311,7 @@ async function autofillBic(worker) {
option-label="name" option-label="name"
option-value="id" option-value="id"
hide-selected hide-selected
:roles-allowed-to-create="['salesAssistant', 'hr']" :acls="[{ model: 'BankEntity', props: '*', accessType: 'WRITE' }]"
:disable="data.isFreelance" :disable="data.isFreelance"
@update:model-value="autofillBic(data)" @update:model-value="autofillBic(data)"
:filter-options="['bic', 'name']" :filter-options="['bic', 'name']"

View File

@ -60,15 +60,12 @@ export default route(function (/* { store, ssrContext } */) {
await useTokenConfig().fetch(); await useTokenConfig().fetch();
} }
const matches = to.matched; const matches = to.matched;
const hasRequiredRoles = matches.every((route) => { const hasRequiredAcls = matches.every((route) => {
const meta = route.meta; const meta = route.meta;
if (meta && meta.roles) return useRole().hasAny(meta.roles); if (!meta?.acls) return true;
return true; return useAcl().hasAny(meta.acls);
}); });
if (!hasRequiredAcls) return next({ path: '/' });
if (!hasRequiredRoles) {
return next({ path: '/' });
}
} }
next(); next();

View File

@ -80,7 +80,7 @@ export default {
meta: { meta: {
title: 'accounts', title: 'accounts',
icon: 'accessibility', icon: 'accessibility',
roles: ['itManagement'], acls: [{ model: 'Account', props: '*', accessType: '*' }],
}, },
component: () => import('src/pages/Account/AccountAccounts.vue'), component: () => import('src/pages/Account/AccountAccounts.vue'),
}, },
@ -90,7 +90,7 @@ export default {
meta: { meta: {
title: 'ldap', title: 'ldap',
icon: 'account_tree', icon: 'account_tree',
roles: ['itManagement'], acls: [{ model: 'LdapConfig', props: '*', accessType: '*' }],
}, },
component: () => import('src/pages/Account/AccountLdap.vue'), component: () => import('src/pages/Account/AccountLdap.vue'),
}, },
@ -100,7 +100,7 @@ export default {
meta: { meta: {
title: 'samba', title: 'samba',
icon: 'preview', icon: 'preview',
roles: ['itManagement'], acls: [{ model: 'SambaConfig', props: '*', accessType: '*' }],
}, },
component: () => import('src/pages/Account/AccountSamba.vue'), component: () => import('src/pages/Account/AccountSamba.vue'),
}, },

View File

@ -62,7 +62,7 @@ export default {
meta: { meta: {
title: 'basicData', title: 'basicData',
icon: 'vn:settings', icon: 'vn:settings',
roles: ['salesPerson'], acls: [{ model: 'Claim', props: 'findById', accessType: 'READ' }],
}, },
component: () => import('src/pages/Claim/Card/ClaimBasicData.vue'), component: () => import('src/pages/Claim/Card/ClaimBasicData.vue'),
}, },
@ -99,7 +99,13 @@ export default {
meta: { meta: {
title: 'development', title: 'development',
icon: 'vn:traceability', icon: 'vn:traceability',
roles: ['claimManager'], acls: [
{
model: 'ClaimDevelopment',
props: '*',
accessType: 'WRITE',
},
],
}, },
component: () => import('src/pages/Claim/Card/ClaimDevelopment.vue'), component: () => import('src/pages/Claim/Card/ClaimDevelopment.vue'),
}, },

View File

@ -84,7 +84,6 @@ export default {
meta: { meta: {
title: 'basicData', title: 'basicData',
icon: 'vn:settings', icon: 'vn:settings',
roles: ['salesPerson'],
}, },
component: () => component: () =>
import('src/pages/InvoiceIn/Card/InvoiceInBasicData.vue'), import('src/pages/InvoiceIn/Card/InvoiceInBasicData.vue'),

View File

@ -76,7 +76,6 @@ export default {
meta: { meta: {
title: 'basicData', title: 'basicData',
icon: 'vn:settings', icon: 'vn:settings',
roles: ['salesPerson'],

No tiene sentido. Cualquier employee puede editarlo o verlo.

No tiene sentido. Cualquier employee puede editarlo o verlo.
}, },
component: () => import('pages/Shelving/Card/ShelvingForm.vue'), component: () => import('pages/Shelving/Card/ShelvingForm.vue'),
}, },

View File

@ -54,7 +54,6 @@ export default {
meta: { meta: {
title: 'createTicket', title: 'createTicket',
icon: 'vn:ticketAdd', icon: 'vn:ticketAdd',
roles: ['developer'],
}, },
component: () => import('src/pages/Ticket/TicketCreate.vue'), component: () => import('src/pages/Ticket/TicketCreate.vue'),
}, },

View File

@ -2,7 +2,7 @@ import axios from 'axios';
import { ref } from 'vue'; import { ref } from 'vue';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { toLowerCamel } from 'src/filters'; import { toLowerCamel } from 'src/filters';
import { useRole } from 'src/composables/useRole'; import { useAcl } from 'src/composables/useAcl';
import routes from 'src/router/modules'; import routes from 'src/router/modules';
export const useNavigationStore = defineStore('navigationStore', () => { export const useNavigationStore = defineStore('navigationStore', () => {
@ -26,7 +26,7 @@ export const useNavigationStore = defineStore('navigationStore', () => {
'zone', 'zone',
]; ];
const pinnedModules = ref([]); const pinnedModules = ref([]);
const role = useRole(); const acl = useAcl();
function getModules() { function getModules() {
const modulesRoutes = ref([]); const modulesRoutes = ref([]);
@ -64,7 +64,7 @@ export const useNavigationStore = defineStore('navigationStore', () => {
title: `globals.pageTitles.${title}`, title: `globals.pageTitles.${title}`,
})); }));
if (meta && meta.roles && role.hasAny(meta.roles) === false) return; if (meta && meta.acls && acl.hasAny(meta.acls) === false) return;
const item = { const item = {
name: route.name, name: route.name,

View File

@ -52,9 +52,9 @@ describe('Login', () => {
cy.url().should('contain', '/login'); cy.url().should('contain', '/login');
}); });
it(`should get redirected to dashboard since employee can't create tickets`, () => { it(`should be redirected to dashboard since the employee is not enabled to see ldap`, () => {
cy.visit('/#/ticket/create', { failOnStatusCode: false }); cy.visit('/#/account/ldap', { failOnStatusCode: false });

Habían puesto que solo podía el role developer. Esto en Salix no estaba.

Habían puesto que solo podía el role developer. Esto en Salix no estaba.
jorgep marked this conversation as resolved
Review

Hermos cambiado la URL pero no hemos cambiado el it del test...

Hermos cambiado la URL pero no hemos cambiado el it del test...
cy.url().should('contain', '/#/login?redirect=/ticket/create'); cy.url().should('contain', '/#/login?redirect=/account/ldap');
cy.get('input[aria-label="Username"]').type('employee'); cy.get('input[aria-label="Username"]').type('employee');
cy.get('input[aria-label="Password"]').type('nightmare'); cy.get('input[aria-label="Password"]').type('nightmare');
cy.get('button[type="submit"]').click(); cy.get('button[type="submit"]').click();

View File

@ -48,40 +48,62 @@ describe('useAcl', () => {
Review

Test maravilloso 🤌

Test maravilloso 🤌
describe('hasAny', () => { describe('hasAny', () => {
it('should return false if no roles matched', async () => { it('should return false if no roles matched', async () => {
expect(acl.hasAny('Worker', 'updateAttributes', 'WRITE')).toBeFalsy(); expect(
acl.hasAny([
{ model: 'Worker', props: 'updateAttributes', accessType: 'WRITE' },
])
).toBeFalsy();
}); });
it('should return false if no roles matched', async () => { it('should return false if no roles matched', async () => {
expect(acl.hasAny('Worker', 'holidays', 'READ')).toBeTruthy(); expect(
acl.hasAny([{ model: 'Worker', props: 'holidays', accessType: 'READ' }])
).toBeTruthy();
}); });
describe('*', () => { describe('*', () => {
it('should return true if an acl matched', async () => { it('should return true if an acl matched', async () => {
expect(acl.hasAny('Address', '*', 'WRITE')).toBeTruthy(); expect(
acl.hasAny([{ model: 'Address', props: '*', accessType: 'WRITE' }])
).toBeTruthy();
}); });
it('should return false if no acls matched', async () => { it('should return false if no acls matched', async () => {
expect(acl.hasAny('Worker', '*', 'READ')).toBeFalsy(); expect(
acl.hasAny([{ model: 'Worker', props: '*', accessType: 'READ' }])
).toBeFalsy();
}); });
}); });
describe('$authenticated', () => { describe('$authenticated', () => {
it('should return false if no acls matched', async () => { it('should return false if no acls matched', async () => {
expect(acl.hasAny('Url', 'getByUser', '*')).toBeFalsy(); expect(
acl.hasAny([{ model: 'Url', props: 'getByUser', accessType: '*' }])
).toBeFalsy();
}); });
it('should return true if an acl matched', async () => { it('should return true if an acl matched', async () => {
expect(acl.hasAny('Url', 'getByUser', 'READ')).toBeTruthy(); expect(
acl.hasAny([{ model: 'Url', props: 'getByUser', accessType: 'READ' }])
).toBeTruthy();
}); });
}); });
describe('$everyone', () => { describe('$everyone', () => {
it('should return false if no acls matched', async () => { it('should return false if no acls matched', async () => {
expect(acl.hasAny('TpvTransaction', 'start', 'READ')).toBeFalsy(); expect(
acl.hasAny([
{ model: 'TpvTransaction', props: 'start', accessType: 'READ' },
])
).toBeFalsy();
}); });
it('should return false if an acl matched', async () => { it('should return false if an acl matched', async () => {
expect(acl.hasAny('TpvTransaction', 'start', 'WRITE')).toBeTruthy(); expect(
acl.hasAny([
{ model: 'TpvTransaction', props: 'start', accessType: 'WRITE' },
])
).toBeTruthy();
}); });
}); });
}); });