0
0
Fork 0

Merge branch 'dev' into 7663-setWeight

This commit is contained in:
Jorge Penadés 2024-09-11 07:40:36 +00:00
commit 2d5602e784
39 changed files with 195 additions and 213 deletions

View File

@ -1,6 +1,6 @@
{
"name": "salix-front",
"version": "24.36.0",
"version": "24.40.0",
"description": "Salix frontend",
"productName": "Salix",
"author": "Verdnatura",
@ -62,4 +62,4 @@
"vite": "^5.1.4",
"vitest": "^0.31.1"
}
}
}

View File

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

View File

@ -36,7 +36,7 @@ const itemComputed = computed(() => {
<QItemSection>
{{ t(itemComputed.title) }}
<QTooltip>
{{ 'Ctrl + Alt + ' + item.keyBinding.toUpperCase() }}
{{ 'Ctrl + Alt + ' + item?.keyBinding?.toUpperCase() }}
</QTooltip>
</QItemSection>
<QItemSection side>

View File

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

View File

@ -1,6 +1,7 @@
<script setup>
import { ref, computed } from 'vue';
import { useRole } from 'src/composables/useRole';
import { useAcl } from 'src/composables/useAcl';
import VnSelect from 'src/components/common/VnSelect.vue';
const emit = defineEmits(['update:modelValue']);
@ -11,6 +12,10 @@ const $props = defineProps({
type: Array,
default: () => ['developer'],
},
acls: {
type: Array,
default: () => [],
},
actionIcon: {
type: String,
default: 'add',
@ -22,15 +27,13 @@ const $props = defineProps({
});
const role = useRole();
const acl = useAcl();
const showForm = ref(false);
const isAllowedToCreate = computed(() => {
if ($props.acls.length) return acl.hasAny($props.acls);
return role.hasAny($props.rolesAllowedToCreate);
});
const toggleForm = () => {
showForm.value = !showForm.value;
};
</script>
<template>
@ -41,7 +44,7 @@ const toggleForm = () => {
>
<template v-if="isAllowedToCreate" #append>
<QIcon
@click.stop.prevent="toggleForm()"
@click.stop.prevent="$refs.dialog.show()"
:name="actionIcon"
:size="actionIcon === 'add' ? 'xs' : 'sm'"
:class="['default-icon', { '--add-icon': actionIcon === 'add' }]"
@ -51,7 +54,7 @@ const toggleForm = () => {
>
<QTooltip v-if="tooltip">{{ tooltip }}</QTooltip>
</QIcon>
<QDialog v-model="showForm" transition-show="scale" transition-hide="scale">
<QDialog ref="dialog" transition-show="scale" transition-hide="scale">
<slot name="form" />
</QDialog>
</template>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@ import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import axios from 'axios';
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 { useArrayData } from 'src/composables/useArrayData';
import { usePrintService } from 'composables/usePrintService';
@ -24,7 +24,7 @@ const $props = defineProps({ id: { type: Number, default: null } });
const { push, currentRoute } = useRouter();
const quasar = useQuasar();
const { hasAny } = useRole();
const { hasAny } = useAcl();
const { t } = useI18n();
const { openReport, sendEmail } = usePrintService();
const arrayData = useArrayData();
@ -195,7 +195,8 @@ async function cloneInvoice() {
push({ path: `/invoice-in/${data.id}/summary` });
}
const isAdministrative = () => hasAny(['administrative']);
const canEditProp = (props) =>
hasAny([{ model: 'InvoiceIn', props, accessType: 'WRITE' }]);
const isAgricultural = () => {
if (!config.value) return false;
@ -283,7 +284,7 @@ const createInvoiceInCorrection = async () => {
<InvoiceInToBook>
<template #content="{ book }">
<QItem
v-if="!entity?.isBooked && isAdministrative()"
v-if="!entity?.isBooked && canEditProp('toBook')"
v-ripple
clickable
@click="book(entityId)"
@ -293,7 +294,7 @@ const createInvoiceInCorrection = async () => {
</template>
</InvoiceInToBook>
<QItem
v-if="entity?.isBooked && isAdministrative()"
v-if="entity?.isBooked && canEditProp('toUnbook')"
v-ripple
clickable
@click="triggerMenu('unbook')"
@ -303,7 +304,7 @@ const createInvoiceInCorrection = async () => {
</QItemSection>
</QItem>
<QItem
v-if="isAdministrative()"
v-if="canEditProp('deleteById')"
v-ripple
clickable
@click="triggerMenu('delete')"
@ -311,7 +312,7 @@ const createInvoiceInCorrection = async () => {
<QItemSection>{{ t('Delete invoice') }}</QItemSection>
</QItem>
<QItem
v-if="isAdministrative()"
v-if="canEditProp('clone')"
v-ripple
clickable
@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 ItemDescriptorImage from 'src/pages/Item/Card/ItemDescriptorImage.vue';
import VnUserLink from 'src/components/ui/VnUserLink.vue';
import { useRole } from 'src/composables/useRole';
import VnTitle from 'src/components/common/VnTitle.vue';
const $props = defineProps({
id: {
@ -19,23 +18,10 @@ const $props = defineProps({
const route = useRoute();
const { t } = useI18n();
const roleState = useRole();
const entityId = computed(() => $props.id || route.params.id);
const isBuyer = computed(() => {
return roleState.hasAny(['buyer']);
});
const isReplenisher = computed(() => {
return roleState.hasAny(['replenisher']);
});
const isAdministrative = computed(() => {
return roleState.hasAny(['administrative']);
});
const getUrl = (id, param) => `#/Item/${id}/${param}`;
</script>
<template>
<CardSummary
ref="summary"
@ -44,13 +30,15 @@ const isAdministrative = computed(() => {
data-key="ItemSummary"
>
<template #header-left>
<router-link
v-if="route.name !== 'ItemSummary'"
<QBtn
v-if="$route.name !== 'ItemSummary'"
:to="{ name: 'ItemSummary', params: { id: entityId } }"
class="header link"
>
<QIcon name="open_in_new" color="white" size="sm" />
</router-link>
class="header link--white"
icon="open_in_new"
flat
dense
round
/>
</template>
<template #header="{ entity: { item } }">
{{ item.id }} - {{ item.name }}
@ -65,15 +53,10 @@ const isAdministrative = computed(() => {
/>
</QCard>
<QCard class="vn-one">
<component
:is="isBuyer ? 'router-link' : 'span'"
:to="{ name: 'ItemBasicData', params: { id: entityId } }"
class="header"
:class="{ 'header-link': isBuyer }"
>
{{ t('item.summary.basicData') }}
<QIcon v-if="isBuyer" name="open_in_new" />
</component>
<VnTitle
:url="getUrl(entityId, 'basic-data')"
:text="t('item.summary.basicData')"
/>
<VnLv :label="t('item.summary.name')" :value="item.name" />
<VnLv :label="t('item.summary.completeName')" :value="item.longName" />
<VnLv :label="t('item.summary.family')" :value="item.itemType.name" />
@ -104,15 +87,10 @@ const isAdministrative = computed(() => {
</VnLv>
</QCard>
<QCard class="vn-one">
<component
:is="isBuyer ? 'router-link' : 'span'"
:to="{ name: 'ItemBasicData', params: { id: entityId } }"
class="header"
:class="{ 'header-link': isBuyer }"
>
{{ t('item.summary.otherData') }}
<QIcon v-if="isBuyer" name="open_in_new" />
</component>
<VnTitle
:url="getUrl(entityId, 'basic-data')"
:text="t('item.summary.otherData')"
/>
<VnLv
:label="t('item.summary.intrastatCode')"
:value="item.intrastat.id"
@ -137,15 +115,7 @@ const isAdministrative = computed(() => {
/>
</QCard>
<QCard class="vn-one">
<component
: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>
<VnTitle :url="getUrl(entityId, 'tags')" :text="t('item.summary.tags')" />
<VnLv
v-for="(tag, index) in tags"
:key="index"
@ -154,29 +124,14 @@ const isAdministrative = computed(() => {
/>
</QCard>
<QCard class="vn-one" v-if="item.description">
<component
:is="isBuyer ? 'router-link' : 'span'"
:to="{ name: 'ItemBasicData', params: { id: entityId } }"
class="header"
:class="{ 'header-link': isBuyer }"
>
{{ t('item.summary.description') }}
<QIcon v-if="isBuyer" name="open_in_new" />
</component>
<p>
{{ item.description }}
</p>
<VnTitle
:url="getUrl(entityId, 'basic-data')"
:text="t('item.summary.description')"
/>
<p v-text="item.description" />
</QCard>
<QCard class="vn-one">
<component
: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>
<VnTitle :url="getUrl(entityId, 'tax')" :text="t('item.summary.tax')" />
<VnLv
v-for="(tax, index) in item.taxes"
:key="index"
@ -185,15 +140,10 @@ const isAdministrative = computed(() => {
/>
</QCard>
<QCard class="vn-one">
<component
:is="isBuyer ? 'router-link' : 'span'"
:to="{ name: 'ItemBotanical', params: { id: entityId } }"
class="header"
:class="{ 'header-link': isBuyer }"
>
{{ t('item.summary.botanical') }}
<QIcon v-if="isBuyer" name="open_in_new" />
</component>
<VnTitle
:url="getUrl(entityId, 'botanical')"
:text="t('item.summary.botanical')"
/>
<VnLv :label="t('item.summary.genus')" :value="botanical?.genus?.name" />
<VnLv
:label="t('item.summary.specie')"
@ -201,23 +151,19 @@ const isAdministrative = computed(() => {
/>
</QCard>
<QCard class="vn-one">
<component
:is="isBuyer || isReplenisher ? 'router-link' : 'span'"
:to="{ name: 'ItemBarcode', params: { id: entityId } }"
class="header"
:class="{ 'header-link': isBuyer || isReplenisher }"
>
{{ t('item.summary.barcode') }}
<QIcon v-if="isBuyer || isReplenisher" name="open_in_new" />
</component>
<p v-for="(barcode, index) in item.itemBarcode" :key="index">
{{ barcode.code }}
</p>
<VnTitle
:url="getUrl(entityId, 'barcode')"
:text="t('item.summary.barcode')"
/>
<p
v-for="(barcode, index) in item.itemBarcode"
:key="index"
v-text="barcode.code"
/>
</QCard>
</template>
</CardSummary>
</template>
<i18n>
en:
Este artículo necesita una foto: Este artículo necesita una foto

View File

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

View File

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

View File

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

View File

@ -635,6 +635,7 @@ onMounted(async () => {
<template #body-cell-state="{ row }">
<QTd>
<QBadge
v-if="row.state"
text-color="black"
:color="row.classColor"
class="q-ma-none"
@ -642,6 +643,7 @@ onMounted(async () => {
>
{{ row.state }}
</QBadge>
<span v-else> {{ dashIfEmpty(row.state) }}</span>
</QTd>
</template>
<template #body-cell-import="{ row }">

View File

@ -55,7 +55,7 @@ onMounted(async () => await getItemPackingTypes());
:data-key="props.dataKey"
:search-button="true"
:hidden-tags="['search']"
:un-removable-params="['warehouseFk', 'dateFuture', 'dateToAdvance']"
:unremovable-params="['warehouseFk', 'dateFuture', 'dateToAdvance']"
>
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
@ -119,10 +119,9 @@ onMounted(async () => await getItemPackingTypes());
<QItem>
<QItemSection>
<QCheckbox
:label="t('params.itemPackingTypes')"
v-model="params.itemPackingTypes"
:label="t('params.isFullMovable')"
v-model="params.isFullMovable"
toggle-indeterminate
:false-value="null"
@update:model-value="searchFn()"
/>
</QItemSection>
@ -155,7 +154,7 @@ en:
dateToAdvance: Destination date
futureIpt: Origin IPT
ipt: Destination IPT
itemPackingTypes: 100% movable
isFullMovable: 100% movable
warehouseFk: Warehouse
es:
Horizontal: Horizontal
@ -166,6 +165,6 @@ es:
dateToAdvance: Fecha destino
futureIpt: IPT Origen
ipt: IPT destino
itemPackingTypes: 100% movible
isFullMovable: 100% movible
warehouseFk: Almacén
</i18n>

View File

@ -49,8 +49,8 @@ const exprBuilder = (param, value) => {
};
const userParams = reactive({
futureDated: Date.vnNew().toISOString(),
originDated: Date.vnNew().toISOString(),
futureScopeDays: Date.vnNew().toISOString(),
originScopeDays: Date.vnNew().toISOString(),
warehouseFk: user.value.warehouseFk,
});
@ -62,8 +62,8 @@ const arrayData = useArrayData('FutureTickets', {
const { store } = arrayData;
const params = reactive({
futureDated: Date.vnNew(),
originDated: Date.vnNew(),
futureScopeDays: Date.vnNew(),
originScopeDays: Date.vnNew(),
warehouseFk: user.value.warehouseFk,
});
@ -172,7 +172,7 @@ const ticketColumns = computed(() => [
label: t('futureTickets.availableLines'),
name: 'lines',
field: 'lines',
align: 'left',
align: 'center',
sortable: true,
columnFilter: {
component: VnInput,
@ -234,7 +234,7 @@ const ticketColumns = computed(() => [
{
label: t('futureTickets.futureState'),
name: 'futureState',
align: 'left',
align: 'right',
sortable: true,
columnFilter: null,
format: (val) => dashIfEmpty(val),
@ -458,7 +458,7 @@ onMounted(async () => {
</QTd>
</template>
<template #body-cell-shipped="{ row }">
<QTd>
<QTd class="shipped">
<QBadge
text-color="black"
:color="getDateQBadgeColor(row.shipped)"
@ -505,7 +505,7 @@ onMounted(async () => {
</QTd>
</template>
<template #body-cell-futureShipped="{ row }">
<QTd>
<QTd class="shipped">
<QBadge
text-color="black"
:color="getDateQBadgeColor(row.futureShipped)"
@ -532,6 +532,9 @@ onMounted(async () => {
</template>
<style scoped lang="scss">
.shipped {
min-width: 132px;
}
.vertical-separator {
border-left: 4px solid white !important;
}

View File

@ -68,7 +68,7 @@ onMounted(async () => {
<VnFilterPanel
:data-key="props.dataKey"
:hidden-tags="['search']"
:un-removable-params="['warehouseFk', 'originDated', 'futureDated']"
:un-removable-params="['warehouseFk', 'originScopeDays ', 'futureScopeDays']"
>
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
@ -80,8 +80,8 @@ onMounted(async () => {
<QItem class="q-my-sm">
<QItemSection>
<VnInputDate
v-model="params.originDated"
:label="t('params.originDated')"
v-model="params.originScopeDays"
:label="t('params.originScopeDays')"
is-outlined
/>
</QItemSection>
@ -89,8 +89,8 @@ onMounted(async () => {
<QItem class="q-my-sm">
<QItemSection>
<VnInputDate
v-model="params.futureDated"
:label="t('params.futureDated')"
v-model="params.futureScopeDays"
:label="t('params.futureScopeDays')"
is-outlined
/>
</QItemSection>
@ -214,8 +214,8 @@ onMounted(async () => {
en:
iptInfo: IPT
params:
originDated: Origin date
futureDated: Destination date
originScopeDays: Origin date
futureScopeDays: Destination date
futureIpt: Destination IPT
ipt: Origin IPT
warehouseFk: Warehouse
@ -229,8 +229,8 @@ es:
Vertical: Vertical
iptInfo: Encajado
params:
originDated: Fecha origen
futureDated: Fecha destino
originScopeDays: Fecha origen
futureScopeDays: Fecha destino
futureIpt: IPT destino
ipt: IPT Origen
warehouseFk: Almacén

View File

@ -105,7 +105,7 @@ advanceTickets:
futureLines: Líneas
futureImport: Importe
advanceTickets: Adelantar tickets con negativos
advanceTicketTitle: Advance tickets
advanceTicketTitle: Adelantar tickets
advanceTitleSubtitle: '¿Desea adelantar {selectedTickets} tickets?'
noDeliveryZone: No hay una zona de reparto disponible para la fecha de envío seleccionada
moveTicketSuccess: 'Tickets movidos correctamente {ticketsNumber}'

View File

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

View File

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

View File

@ -13,6 +13,7 @@ import WorkerTimeControlCalendar from 'pages/Worker/Card/WorkerTimeControlCalend
import useNotify from 'src/composables/useNotify.js';
import axios from 'axios';
import { useRole } from 'src/composables/useRole';
import { useAcl } from 'src/composables/useAcl';
import { useWeekdayStore } from 'src/stores/useWeekdayStore';
import { useStateStore } from 'stores/useStateStore';
import { useState } from 'src/composables/useState';
@ -26,7 +27,6 @@ import { date } from 'quasar';
const route = useRoute();
const { t, locale } = useI18n();
const { notify } = useNotify();
const { hasAny } = useRole();
const _state = useState();
const user = _state.getUser();
const stateStore = useStateStore();
@ -66,9 +66,11 @@ const arrayData = useArrayData('workerData');
const worker = computed(() => arrayData.store?.data);
const isHr = computed(() => hasAny(['hr']));
const isHr = computed(() => useRole().hasAny(['hr']));
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(() => {
return weekdayStore.getLocales?.map((day, index) => {
@ -447,7 +449,7 @@ onMounted(async () => {
<div>
<QBtnGroup push class="q-gutter-x-sm" flat>
<QBtn
v-if="isHimSelf && state"
v-if="isHimself && state"
:label="t('Satisfied')"
color="primary"
type="submit"
@ -455,7 +457,7 @@ onMounted(async () => {
@click="isSatisfied()"
/>
<QBtn
v-if="isHimSelf && state"
v-if="isHimself && state"
:label="t('Not satisfied')"
color="primary"
type="submit"
@ -466,14 +468,14 @@ onMounted(async () => {
</QBtnGroup>
<QBtnGroup push class="q-gutter-x-sm q-ml-none" flat>
<QBtn
v-if="reason && state && (isHimSelf || isHr)"
v-if="reason && state && (isHimself || isHr)"
:label="t('Reason')"
color="primary"
type="submit"
@click="showReasonForm()"
/>
<QBtn
v-if="isHr && state !== 'CONFIRMED' && canResend"
v-if="canSend && state !== 'CONFIRMED' && canResend"
:label="state ? t('Resend') : t('globals.send')"
color="primary"
type="submit"
@ -603,7 +605,7 @@ onMounted(async () => {
<WorkerTimeReasonForm
@on-submit="isUnsatisfied($event)"
:reason="reason"
:is-him-self="isHimSelf"
:is-himself="isHimself"
/>
</QDialog>
</QPage>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@ import axios from 'axios';
import { ref } from 'vue';
import { defineStore } from 'pinia';
import { toLowerCamel } from 'src/filters';
import { useRole } from 'src/composables/useRole';
import { useAcl } from 'src/composables/useAcl';
import routes from 'src/router/modules';
export const useNavigationStore = defineStore('navigationStore', () => {
@ -26,7 +26,7 @@ export const useNavigationStore = defineStore('navigationStore', () => {
'zone',
];
const pinnedModules = ref([]);
const role = useRole();
const acl = useAcl();
function getModules() {
const modulesRoutes = ref([]);
@ -64,7 +64,7 @@ export const useNavigationStore = defineStore('navigationStore', () => {
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 = {
name: route.name,

View File

@ -52,9 +52,9 @@ describe('Login', () => {
cy.url().should('contain', '/login');
});
it(`should get redirected to dashboard since employee can't create tickets`, () => {
cy.visit('/#/ticket/create', { failOnStatusCode: false });
cy.url().should('contain', '/#/login?redirect=/ticket/create');
it(`should be redirected to dashboard since the employee is not enabled to see ldap`, () => {
cy.visit('/#/account/ldap', { failOnStatusCode: false });
cy.url().should('contain', '/#/login?redirect=/account/ldap');
cy.get('input[aria-label="Username"]').type('employee');
cy.get('input[aria-label="Password"]').type('nightmare');
cy.get('button[type="submit"]').click();

View File

@ -48,40 +48,62 @@ describe('useAcl', () => {
describe('hasAny', () => {
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 () => {
expect(acl.hasAny('Worker', 'holidays', 'READ')).toBeTruthy();
expect(
acl.hasAny([{ model: 'Worker', props: 'holidays', accessType: 'READ' }])
).toBeTruthy();
});
describe('*', () => {
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 () => {
expect(acl.hasAny('Worker', '*', 'READ')).toBeFalsy();
expect(
acl.hasAny([{ model: 'Worker', props: '*', accessType: 'READ' }])
).toBeFalsy();
});
});
describe('$authenticated', () => {
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 () => {
expect(acl.hasAny('Url', 'getByUser', 'READ')).toBeTruthy();
expect(
acl.hasAny([{ model: 'Url', props: 'getByUser', accessType: 'READ' }])
).toBeTruthy();
});
});
describe('$everyone', () => {
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 () => {
expect(acl.hasAny('TpvTransaction', 'start', 'WRITE')).toBeTruthy();
expect(
acl.hasAny([
{ model: 'TpvTransaction', props: 'start', accessType: 'WRITE' },
])
).toBeTruthy();
});
});
});