forked from verdnatura/salix-front
Merge branch 'dev' into 7663-setWeight
This commit is contained in:
commit
2d5602e784
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "salix-front",
|
"name": "salix-front",
|
||||||
"version": "24.36.0",
|
"version": "24.40.0",
|
||||||
"description": "Salix frontend",
|
"description": "Salix frontend",
|
||||||
"productName": "Salix",
|
"productName": "Salix",
|
||||||
"author": "Verdnatura",
|
"author": "Verdnatura",
|
||||||
|
|
|
@ -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
|
||||||
>
|
>
|
||||||
|
|
|
@ -36,7 +36,7 @@ const itemComputed = computed(() => {
|
||||||
<QItemSection>
|
<QItemSection>
|
||||||
{{ t(itemComputed.title) }}
|
{{ t(itemComputed.title) }}
|
||||||
<QTooltip>
|
<QTooltip>
|
||||||
{{ 'Ctrl + Alt + ' + item.keyBinding.toUpperCase() }}
|
{{ 'Ctrl + Alt + ' + item?.keyBinding?.toUpperCase() }}
|
||||||
</QTooltip>
|
</QTooltip>
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
<QItemSection side>
|
<QItemSection side>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
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>
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -16,14 +16,19 @@ 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]);
|
||||||
|
if (modelAcls)
|
||||||
|
return ['*', ...props].some((key) => {
|
||||||
|
const acl = modelAcls[key];
|
||||||
return acl && (acl['*'] || acl[accessType]);
|
return acl && (acl['*'] || acl[accessType]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fetch,
|
fetch,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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)"
|
||||||
>
|
>
|
||||||
|
|
|
@ -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)"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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)"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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')"
|
||||||
|
|
|
@ -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';
|
||||||
import { useRole } from 'src/composables/useRole';
|
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -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)"
|
||||||
>
|
>
|
||||||
|
|
|
@ -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)"
|
||||||
>
|
>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -635,6 +635,7 @@ onMounted(async () => {
|
||||||
<template #body-cell-state="{ row }">
|
<template #body-cell-state="{ row }">
|
||||||
<QTd>
|
<QTd>
|
||||||
<QBadge
|
<QBadge
|
||||||
|
v-if="row.state"
|
||||||
text-color="black"
|
text-color="black"
|
||||||
:color="row.classColor"
|
:color="row.classColor"
|
||||||
class="q-ma-none"
|
class="q-ma-none"
|
||||||
|
@ -642,6 +643,7 @@ onMounted(async () => {
|
||||||
>
|
>
|
||||||
{{ row.state }}
|
{{ row.state }}
|
||||||
</QBadge>
|
</QBadge>
|
||||||
|
<span v-else> {{ dashIfEmpty(row.state) }}</span>
|
||||||
</QTd>
|
</QTd>
|
||||||
</template>
|
</template>
|
||||||
<template #body-cell-import="{ row }">
|
<template #body-cell-import="{ row }">
|
||||||
|
|
|
@ -55,7 +55,7 @@ onMounted(async () => await getItemPackingTypes());
|
||||||
:data-key="props.dataKey"
|
:data-key="props.dataKey"
|
||||||
:search-button="true"
|
:search-button="true"
|
||||||
:hidden-tags="['search']"
|
:hidden-tags="['search']"
|
||||||
:un-removable-params="['warehouseFk', 'dateFuture', 'dateToAdvance']"
|
:unremovable-params="['warehouseFk', 'dateFuture', 'dateToAdvance']"
|
||||||
>
|
>
|
||||||
<template #tags="{ tag, formatFn }">
|
<template #tags="{ tag, formatFn }">
|
||||||
<div class="q-gutter-x-xs">
|
<div class="q-gutter-x-xs">
|
||||||
|
@ -119,10 +119,9 @@ onMounted(async () => await getItemPackingTypes());
|
||||||
<QItem>
|
<QItem>
|
||||||
<QItemSection>
|
<QItemSection>
|
||||||
<QCheckbox
|
<QCheckbox
|
||||||
:label="t('params.itemPackingTypes')"
|
:label="t('params.isFullMovable')"
|
||||||
v-model="params.itemPackingTypes"
|
v-model="params.isFullMovable"
|
||||||
toggle-indeterminate
|
toggle-indeterminate
|
||||||
:false-value="null"
|
|
||||||
@update:model-value="searchFn()"
|
@update:model-value="searchFn()"
|
||||||
/>
|
/>
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
|
@ -155,7 +154,7 @@ en:
|
||||||
dateToAdvance: Destination date
|
dateToAdvance: Destination date
|
||||||
futureIpt: Origin IPT
|
futureIpt: Origin IPT
|
||||||
ipt: Destination IPT
|
ipt: Destination IPT
|
||||||
itemPackingTypes: 100% movable
|
isFullMovable: 100% movable
|
||||||
warehouseFk: Warehouse
|
warehouseFk: Warehouse
|
||||||
es:
|
es:
|
||||||
Horizontal: Horizontal
|
Horizontal: Horizontal
|
||||||
|
@ -166,6 +165,6 @@ es:
|
||||||
dateToAdvance: Fecha destino
|
dateToAdvance: Fecha destino
|
||||||
futureIpt: IPT Origen
|
futureIpt: IPT Origen
|
||||||
ipt: IPT destino
|
ipt: IPT destino
|
||||||
itemPackingTypes: 100% movible
|
isFullMovable: 100% movible
|
||||||
warehouseFk: Almacén
|
warehouseFk: Almacén
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|
|
@ -49,8 +49,8 @@ const exprBuilder = (param, value) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const userParams = reactive({
|
const userParams = reactive({
|
||||||
futureDated: Date.vnNew().toISOString(),
|
futureScopeDays: Date.vnNew().toISOString(),
|
||||||
originDated: Date.vnNew().toISOString(),
|
originScopeDays: Date.vnNew().toISOString(),
|
||||||
warehouseFk: user.value.warehouseFk,
|
warehouseFk: user.value.warehouseFk,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -62,8 +62,8 @@ const arrayData = useArrayData('FutureTickets', {
|
||||||
const { store } = arrayData;
|
const { store } = arrayData;
|
||||||
|
|
||||||
const params = reactive({
|
const params = reactive({
|
||||||
futureDated: Date.vnNew(),
|
futureScopeDays: Date.vnNew(),
|
||||||
originDated: Date.vnNew(),
|
originScopeDays: Date.vnNew(),
|
||||||
warehouseFk: user.value.warehouseFk,
|
warehouseFk: user.value.warehouseFk,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -172,7 +172,7 @@ const ticketColumns = computed(() => [
|
||||||
label: t('futureTickets.availableLines'),
|
label: t('futureTickets.availableLines'),
|
||||||
name: 'lines',
|
name: 'lines',
|
||||||
field: 'lines',
|
field: 'lines',
|
||||||
align: 'left',
|
align: 'center',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
columnFilter: {
|
columnFilter: {
|
||||||
component: VnInput,
|
component: VnInput,
|
||||||
|
@ -234,7 +234,7 @@ const ticketColumns = computed(() => [
|
||||||
{
|
{
|
||||||
label: t('futureTickets.futureState'),
|
label: t('futureTickets.futureState'),
|
||||||
name: 'futureState',
|
name: 'futureState',
|
||||||
align: 'left',
|
align: 'right',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
columnFilter: null,
|
columnFilter: null,
|
||||||
format: (val) => dashIfEmpty(val),
|
format: (val) => dashIfEmpty(val),
|
||||||
|
@ -458,7 +458,7 @@ onMounted(async () => {
|
||||||
</QTd>
|
</QTd>
|
||||||
</template>
|
</template>
|
||||||
<template #body-cell-shipped="{ row }">
|
<template #body-cell-shipped="{ row }">
|
||||||
<QTd>
|
<QTd class="shipped">
|
||||||
<QBadge
|
<QBadge
|
||||||
text-color="black"
|
text-color="black"
|
||||||
:color="getDateQBadgeColor(row.shipped)"
|
:color="getDateQBadgeColor(row.shipped)"
|
||||||
|
@ -505,7 +505,7 @@ onMounted(async () => {
|
||||||
</QTd>
|
</QTd>
|
||||||
</template>
|
</template>
|
||||||
<template #body-cell-futureShipped="{ row }">
|
<template #body-cell-futureShipped="{ row }">
|
||||||
<QTd>
|
<QTd class="shipped">
|
||||||
<QBadge
|
<QBadge
|
||||||
text-color="black"
|
text-color="black"
|
||||||
:color="getDateQBadgeColor(row.futureShipped)"
|
:color="getDateQBadgeColor(row.futureShipped)"
|
||||||
|
@ -532,6 +532,9 @@ onMounted(async () => {
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.shipped {
|
||||||
|
min-width: 132px;
|
||||||
|
}
|
||||||
.vertical-separator {
|
.vertical-separator {
|
||||||
border-left: 4px solid white !important;
|
border-left: 4px solid white !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ onMounted(async () => {
|
||||||
<VnFilterPanel
|
<VnFilterPanel
|
||||||
:data-key="props.dataKey"
|
:data-key="props.dataKey"
|
||||||
:hidden-tags="['search']"
|
:hidden-tags="['search']"
|
||||||
:un-removable-params="['warehouseFk', 'originDated', 'futureDated']"
|
:un-removable-params="['warehouseFk', 'originScopeDays ', 'futureScopeDays']"
|
||||||
>
|
>
|
||||||
<template #tags="{ tag, formatFn }">
|
<template #tags="{ tag, formatFn }">
|
||||||
<div class="q-gutter-x-xs">
|
<div class="q-gutter-x-xs">
|
||||||
|
@ -80,8 +80,8 @@ onMounted(async () => {
|
||||||
<QItem class="q-my-sm">
|
<QItem class="q-my-sm">
|
||||||
<QItemSection>
|
<QItemSection>
|
||||||
<VnInputDate
|
<VnInputDate
|
||||||
v-model="params.originDated"
|
v-model="params.originScopeDays"
|
||||||
:label="t('params.originDated')"
|
:label="t('params.originScopeDays')"
|
||||||
is-outlined
|
is-outlined
|
||||||
/>
|
/>
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
|
@ -89,8 +89,8 @@ onMounted(async () => {
|
||||||
<QItem class="q-my-sm">
|
<QItem class="q-my-sm">
|
||||||
<QItemSection>
|
<QItemSection>
|
||||||
<VnInputDate
|
<VnInputDate
|
||||||
v-model="params.futureDated"
|
v-model="params.futureScopeDays"
|
||||||
:label="t('params.futureDated')"
|
:label="t('params.futureScopeDays')"
|
||||||
is-outlined
|
is-outlined
|
||||||
/>
|
/>
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
|
@ -214,8 +214,8 @@ onMounted(async () => {
|
||||||
en:
|
en:
|
||||||
iptInfo: IPT
|
iptInfo: IPT
|
||||||
params:
|
params:
|
||||||
originDated: Origin date
|
originScopeDays: Origin date
|
||||||
futureDated: Destination date
|
futureScopeDays: Destination date
|
||||||
futureIpt: Destination IPT
|
futureIpt: Destination IPT
|
||||||
ipt: Origin IPT
|
ipt: Origin IPT
|
||||||
warehouseFk: Warehouse
|
warehouseFk: Warehouse
|
||||||
|
@ -229,8 +229,8 @@ es:
|
||||||
Vertical: Vertical
|
Vertical: Vertical
|
||||||
iptInfo: Encajado
|
iptInfo: Encajado
|
||||||
params:
|
params:
|
||||||
originDated: Fecha origen
|
originScopeDays: Fecha origen
|
||||||
futureDated: Fecha destino
|
futureScopeDays: Fecha destino
|
||||||
futureIpt: IPT destino
|
futureIpt: IPT destino
|
||||||
ipt: IPT Origen
|
ipt: IPT Origen
|
||||||
warehouseFk: Almacén
|
warehouseFk: Almacén
|
||||||
|
|
|
@ -105,7 +105,7 @@ advanceTickets:
|
||||||
futureLines: Líneas
|
futureLines: Líneas
|
||||||
futureImport: Importe
|
futureImport: Importe
|
||||||
advanceTickets: Adelantar tickets con negativos
|
advanceTickets: Adelantar tickets con negativos
|
||||||
advanceTicketTitle: Advance tickets
|
advanceTicketTitle: Adelantar tickets
|
||||||
advanceTitleSubtitle: '¿Desea adelantar {selectedTickets} tickets?'
|
advanceTitleSubtitle: '¿Desea adelantar {selectedTickets} tickets?'
|
||||||
noDeliveryZone: No hay una zona de reparto disponible para la fecha de envío seleccionada
|
noDeliveryZone: No hay una zona de reparto disponible para la fecha de envío seleccionada
|
||||||
moveTicketSuccess: 'Tickets movidos correctamente {ticketsNumber}'
|
moveTicketSuccess: 'Tickets movidos correctamente {ticketsNumber}'
|
||||||
|
|
|
@ -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)"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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']));
|
||||||
|
|
||||||
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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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']"
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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'),
|
||||||
},
|
},
|
||||||
|
|
|
@ -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'),
|
||||||
},
|
},
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -76,7 +76,6 @@ export default {
|
||||||
meta: {
|
meta: {
|
||||||
title: 'basicData',
|
title: 'basicData',
|
||||||
icon: 'vn:settings',
|
icon: 'vn:settings',
|
||||||
roles: ['salesPerson'],
|
|
||||||
},
|
},
|
||||||
component: () => import('pages/Shelving/Card/ShelvingForm.vue'),
|
component: () => import('pages/Shelving/Card/ShelvingForm.vue'),
|
||||||
},
|
},
|
||||||
|
|
|
@ -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'),
|
||||||
},
|
},
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 });
|
||||||
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();
|
||||||
|
|
|
@ -48,40 +48,62 @@ describe('useAcl', () => {
|
||||||
|
|
||||||
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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue