forked from verdnatura/salix-front
Merge branch 'dev' of https: refs #7553//gitea.verdnatura.es/verdnatura/salix-front into 7553_FixTicketExpedition
This commit is contained in:
commit
d1138529b2
|
@ -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
|
||||
>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -31,6 +31,7 @@ const props = defineProps({
|
|||
});
|
||||
|
||||
defineEmits(['confirm', ...useDialogPluginComponent.emits]);
|
||||
defineExpose({ show: () => dialogRef.value.show(), hide: () => dialogRef.value.hide() });
|
||||
|
||||
const { dialogRef, onDialogOK } = useDialogPluginComponent();
|
||||
|
||||
|
@ -68,8 +69,10 @@ async function confirm() {
|
|||
<QSpace />
|
||||
<QBtn icon="close" :disable="isLoading" flat round dense v-close-popup />
|
||||
</QCardSection>
|
||||
<QCardSection class="row items-center">
|
||||
<QCardSection class="q-pb-none">
|
||||
<span v-if="message !== false" v-html="message" />
|
||||
</QCardSection>
|
||||
<QCardSection class="row items-center q-pt-none">
|
||||
<slot name="customHTML"></slot>
|
||||
</QCardSection>
|
||||
<QCardActions align="right">
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -37,6 +37,10 @@ a {
|
|||
.link {
|
||||
color: $color-link;
|
||||
cursor: pointer;
|
||||
|
||||
&--white {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.tx-color-link {
|
||||
|
|
|
@ -98,6 +98,7 @@ globals:
|
|||
to: To
|
||||
notes: Notes
|
||||
refresh: Refresh
|
||||
weight: Weight
|
||||
pageTitles:
|
||||
logIn: Login
|
||||
summary: Summary
|
||||
|
|
|
@ -100,6 +100,7 @@ globals:
|
|||
to: Hasta
|
||||
notes: Notas
|
||||
refresh: Actualizar
|
||||
weight: Peso
|
||||
pageTitles:
|
||||
logIn: Inicio de sesión
|
||||
summary: Resumen
|
||||
|
|
|
@ -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
|
||||
/>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)"
|
||||
/>
|
||||
|
|
|
@ -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)"
|
||||
>
|
||||
|
|
|
@ -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)"
|
||||
/>
|
||||
|
|
|
@ -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)"
|
||||
/>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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')"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)"
|
||||
>
|
||||
|
|
|
@ -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)"
|
||||
>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -14,6 +14,8 @@ import VnSelect from 'components/common/VnSelect.vue';
|
|||
import FetchData from 'components/FetchData.vue';
|
||||
import VnInputTime from 'src/components/common/VnInputTime.vue';
|
||||
import { useAcl } from 'src/composables/useAcl';
|
||||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
|
||||
const props = defineProps({
|
||||
ticket: {
|
||||
|
@ -26,7 +28,7 @@ const { push, currentRoute } = useRouter();
|
|||
const { dialog, notify } = useQuasar();
|
||||
const { t } = useI18n();
|
||||
const { openReport, sendEmail } = usePrintService();
|
||||
|
||||
const ticketSummary = useArrayData('TicketSummary');
|
||||
const { ticket } = toRefs(props);
|
||||
const ticketId = currentRoute.value.params.id;
|
||||
const client = ref();
|
||||
|
@ -37,6 +39,7 @@ const dialogRef = ref();
|
|||
const isEditable = ref();
|
||||
const hasInvoicing = useAcl('invoicing');
|
||||
const hasPdf = ref();
|
||||
const weight = ref();
|
||||
const actions = {
|
||||
clone: async () => {
|
||||
const opts = { message: t('Ticket cloned'), type: 'positive' };
|
||||
|
@ -59,6 +62,25 @@ const actions = {
|
|||
push({ name: 'TicketSummary', params: { id: clonedTicketId } });
|
||||
}
|
||||
},
|
||||
setWeight: async () => {
|
||||
try {
|
||||
const invoiceIds = (
|
||||
await axios.post(`Tickets/${ticketId}/setWeight`, {
|
||||
weight: weight.value,
|
||||
})
|
||||
).data;
|
||||
|
||||
notify({ message: t('Weight set'), type: 'positive' });
|
||||
if (invoiceIds.length)
|
||||
notify({
|
||||
message: t('invoiceIds', { invoiceIds: invoiceIds.join() }),
|
||||
type: 'positive',
|
||||
});
|
||||
await ticketSummary.fetch({ updateRouter: false });
|
||||
} catch (e) {
|
||||
notify({ message: e.message, type: 'negative' });
|
||||
}
|
||||
},
|
||||
remove: async () => {
|
||||
try {
|
||||
await axios.post(`Tickets/${ticketId}/setDeleted`);
|
||||
|
@ -578,6 +600,12 @@ async function handleInvoiceOutData() {
|
|||
</QList>
|
||||
</QMenu>
|
||||
</QItem>
|
||||
<QItem @click="$refs.weightDialog.show()" v-ripple clickable>
|
||||
<QItemSection avatar>
|
||||
<QIcon name="weight" />
|
||||
</QItemSection>
|
||||
<QItemSection>{{ t('Set weight') }}</QItemSection>
|
||||
</QItem>
|
||||
<template v-if="!ticket.isDeleted">
|
||||
<QSeparator />
|
||||
<QItem @click="openConfirmDialog('remove')" v-ripple clickable>
|
||||
|
@ -587,6 +615,20 @@ async function handleInvoiceOutData() {
|
|||
<QItemSection>{{ t('Delete ticket') }}</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
<VnConfirm
|
||||
ref="weightDialog"
|
||||
:title="t('Set weight')"
|
||||
:message="t('This ticket may be invoiced, do you want to continue?')"
|
||||
:promise="actions.setWeight"
|
||||
>
|
||||
<template #customHTML>
|
||||
<VnInputNumber
|
||||
:label="t('globals.weight')"
|
||||
v-model="weight"
|
||||
class="full-width"
|
||||
/>
|
||||
</template>
|
||||
</VnConfirm>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -594,10 +636,12 @@ async function handleInvoiceOutData() {
|
|||
margin: 1%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
en:
|
||||
addTurn: Add turn
|
||||
en:
|
||||
invoiceIds: "Invoices have been generated with the following ids: {invoiceIds}"
|
||||
|
||||
es:
|
||||
Show Delivery Note...: Ver albarán...
|
||||
Send Delivery Note...: Enviar albarán...
|
||||
|
@ -638,4 +682,8 @@ es:
|
|||
Are you sure you want to invoice this ticket?: ¿Seguro que quieres facturar este ticket?
|
||||
You are going to invoice this ticket: Vas a facturar este ticket
|
||||
Ticket invoiced: Ticket facturado
|
||||
Set weight: Establecer peso
|
||||
Weight set: Peso establecido
|
||||
This ticket may be invoiced, do you want to continue?: Es posible que se facture este ticket, desea continuar?
|
||||
invoiceIds: "Se han generado las facturas con los siguientes ids: {invoiceIds}"
|
||||
</i18n>
|
||||
|
|
|
@ -34,8 +34,7 @@ const $props = defineProps({
|
|||
const entityId = computed(() => $props.id || route.params.id);
|
||||
|
||||
const summaryRef = ref();
|
||||
const ticket = ref();
|
||||
const salesLines = ref(null);
|
||||
const ticket = computed(() => summaryRef.value?.entity);
|
||||
const editableStates = ref([]);
|
||||
const ticketUrl = ref();
|
||||
const grafanaUrl = 'https://grafana.verdnatura.es';
|
||||
|
@ -43,12 +42,6 @@ const grafanaUrl = 'https://grafana.verdnatura.es';
|
|||
onMounted(async () => {
|
||||
ticketUrl.value = (await getUrl('ticket/')) + entityId.value + '/';
|
||||
});
|
||||
async function setData(data) {
|
||||
if (data) {
|
||||
ticket.value = data;
|
||||
salesLines.value = data.sales;
|
||||
}
|
||||
}
|
||||
|
||||
function formattedAddress() {
|
||||
if (!ticket.value) return '';
|
||||
|
@ -120,7 +113,6 @@ function toTicketUrl(section) {
|
|||
<CardSummary
|
||||
ref="summaryRef"
|
||||
:url="`Tickets/${entityId}/summary`"
|
||||
@on-fetch="(data) => setData(data)"
|
||||
data-key="TicketSummary"
|
||||
>
|
||||
<template #header="{ entity }">
|
||||
|
@ -149,7 +141,7 @@ function toTicketUrl(section) {
|
|||
/>
|
||||
</QBtnDropdown>
|
||||
</template>
|
||||
<template #body>
|
||||
<template #body="{ entity }">
|
||||
<QCard class="vn-one">
|
||||
<VnTitle
|
||||
:url="toTicketUrl('basic-data')"
|
||||
|
@ -168,14 +160,14 @@ function toTicketUrl(section) {
|
|||
<VnLv :label="t('ticket.summary.salesPerson')">
|
||||
<template #value>
|
||||
<VnUserLink
|
||||
:name="ticket.client?.salesPersonUser?.name"
|
||||
:worker-id="ticket.client?.salesPersonFk"
|
||||
:name="entity.client?.salesPersonUser?.name"
|
||||
:worker-id="entity.client?.salesPersonFk"
|
||||
/>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv
|
||||
:label="t('ticket.summary.agency')"
|
||||
:value="ticket.agencyMode?.name"
|
||||
:value="entity.agencyMode?.name"
|
||||
/>
|
||||
<VnLv :label="t('ticket.summary.zone')">
|
||||
<template #value>
|
||||
|
@ -187,19 +179,19 @@ function toTicketUrl(section) {
|
|||
</VnLv>
|
||||
<VnLv
|
||||
:label="t('ticket.summary.warehouse')"
|
||||
:value="ticket.warehouse?.name"
|
||||
:value="entity.warehouse?.name"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('ticket.summary.collection')"
|
||||
:value="ticket.ticketCollections[0]?.collectionFk"
|
||||
:value="entity.ticketCollections[0]?.collectionFk"
|
||||
>
|
||||
<template #value>
|
||||
<a
|
||||
:href="`${grafanaUrl}/d/d552ab74-85b4-4e7f-a279-fab7cd9c6124/control-de-expediciones?orgId=1&var-collectionFk=${ticket.ticketCollections[0]?.collectionFk}`"
|
||||
:href="`${grafanaUrl}/d/d552ab74-85b4-4e7f-a279-fab7cd9c6124/control-de-expediciones?orgId=1&var-collectionFk=${entity.ticketCollections[0]?.collectionFk}`"
|
||||
target="_blank"
|
||||
class="grafana"
|
||||
>
|
||||
{{ ticket.ticketCollections[0]?.collectionFk }}
|
||||
{{ entity.ticketCollections[0]?.collectionFk }}
|
||||
</a>
|
||||
</template>
|
||||
</VnLv>
|
||||
|
@ -213,18 +205,18 @@ function toTicketUrl(section) {
|
|||
></VnLv>
|
||||
<VnLv :label="t('ticket.summary.invoice')">
|
||||
<template #value>
|
||||
<span :class="{ link: ticket.refFk }">
|
||||
{{ dashIfEmpty(ticket.refFk) }}
|
||||
<span :class="{ link: entity.refFk }">
|
||||
{{ dashIfEmpty(entity.refFk) }}
|
||||
<InvoiceOutDescriptorProxy
|
||||
:id="ticket.invoiceOut.id"
|
||||
v-if="ticket.refFk"
|
||||
:id="entity.invoiceOut.id"
|
||||
v-if="entity.refFk"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv
|
||||
:label="t('ticket.summary.weight')"
|
||||
:value="dashIfEmpty(ticket.weight)"
|
||||
:value="dashIfEmpty(entity.weight)"
|
||||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-one" style="flex: 2 1">
|
||||
|
@ -234,35 +226,35 @@ function toTicketUrl(section) {
|
|||
/>
|
||||
<VnLv
|
||||
:label="t('ticket.summary.shipped')"
|
||||
:value="toDate(ticket.shipped)"
|
||||
:value="toDate(entity.shipped)"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('ticket.summary.landed')"
|
||||
:value="toDate(ticket.landed)"
|
||||
:value="toDate(entity.landed)"
|
||||
/>
|
||||
<VnLv :label="t('globals.packages')" :value="ticket.packages" />
|
||||
<VnLv :value="ticket.address.phone">
|
||||
<VnLv :label="t('globals.packages')" :value="entity.packages" />
|
||||
<VnLv :value="entity.address.phone">
|
||||
<template #label>
|
||||
{{ t('ticket.summary.consigneePhone') }}
|
||||
<VnLinkPhone :phone-number="ticket.address.phone" />
|
||||
<VnLinkPhone :phone-number="entity.address.phone" />
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv :value="ticket.address.mobile">
|
||||
<VnLv :value="entity.address.mobile">
|
||||
<template #label>
|
||||
{{ t('ticket.summary.consigneeMobile') }}
|
||||
<VnLinkPhone :phone-number="ticket.address.mobile" />
|
||||
<VnLinkPhone :phone-number="entity.address.mobile" />
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv :value="ticket.client.phone">
|
||||
<VnLv :value="entity.client.phone">
|
||||
<template #label>
|
||||
{{ t('ticket.summary.clientPhone') }}
|
||||
<VnLinkPhone :phone-number="ticket.client.phone" />
|
||||
<VnLinkPhone :phone-number="entity.client.phone" />
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv :value="ticket.client.mobile">
|
||||
<VnLv :value="entity.client.mobile">
|
||||
<template #label>
|
||||
{{ t('ticket.summary.clientMobile') }}
|
||||
<VnLinkPhone :phone-number="ticket.client.mobile" />
|
||||
<VnLinkPhone :phone-number="entity.client.mobile" />
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv
|
||||
|
@ -279,13 +271,13 @@ function toTicketUrl(section) {
|
|||
:value="formattedAddress()"
|
||||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-one" v-if="ticket.notes.length">
|
||||
<QCard class="vn-one" v-if="entity.notes.length">
|
||||
<VnTitle
|
||||
:url="toTicketUrl('observation')"
|
||||
:text="t('ticket.pageTitles.notes')"
|
||||
/>
|
||||
<VnLv
|
||||
v-for="note in ticket.notes"
|
||||
v-for="note in entity.notes"
|
||||
:key="note.id"
|
||||
:label="getNoteValue(note.observationType.description)"
|
||||
:value="note.description"
|
||||
|
@ -306,11 +298,11 @@ function toTicketUrl(section) {
|
|||
<div class="bodyCard">
|
||||
<VnLv
|
||||
:label="t('ticket.summary.subtotal')"
|
||||
:value="toCurrency(ticket.totalWithoutVat)"
|
||||
:value="toCurrency(entity.totalWithoutVat)"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('ticket.summary.vat')"
|
||||
:value="toCurrency(ticket.totalWithVat - ticket.totalWithoutVat)"
|
||||
:value="toCurrency(entity.totalWithVat - entity.totalWithoutVat)"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('ticket.summary.total')"
|
||||
|
@ -324,7 +316,7 @@ function toTicketUrl(section) {
|
|||
:url="toTicketUrl('sale')"
|
||||
:text="t('ticket.summary.saleLines')"
|
||||
/>
|
||||
<QTable :rows="ticket.sales" style="text-align: center">
|
||||
<QTable :rows="entity.sales" style="text-align: center">
|
||||
<template #body-cell="{ value }">
|
||||
<QTd>{{ value }}</QTd>
|
||||
</template>
|
||||
|
|
|
@ -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 }">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}'
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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']"
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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'),
|
||||
},
|
||||
|
|
|
@ -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'),
|
||||
},
|
||||
|
|
|
@ -84,7 +84,6 @@ export default {
|
|||
meta: {
|
||||
title: 'basicData',
|
||||
icon: 'vn:settings',
|
||||
roles: ['salesPerson'],
|
||||
},
|
||||
component: () =>
|
||||
import('src/pages/InvoiceIn/Card/InvoiceInBasicData.vue'),
|
||||
|
|
|
@ -76,7 +76,6 @@ export default {
|
|||
meta: {
|
||||
title: 'basicData',
|
||||
icon: 'vn:settings',
|
||||
roles: ['salesPerson'],
|
||||
},
|
||||
component: () => import('pages/Shelving/Card/ShelvingForm.vue'),
|
||||
},
|
||||
|
|
|
@ -54,7 +54,6 @@ export default {
|
|||
meta: {
|
||||
title: 'createTicket',
|
||||
icon: 'vn:ticketAdd',
|
||||
roles: ['developer'],
|
||||
},
|
||||
component: () => import('src/pages/Ticket/TicketCreate.vue'),
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
/// <reference types="cypress" />
|
||||
describe('Ticket descriptor', () => {
|
||||
const toCloneOpt = '[role="menu"] .q-list > :nth-child(5)';
|
||||
const setWeightOpt = '[role="menu"] .q-list > :nth-child(6)';
|
||||
const warehouseValue = ':nth-child(1) > :nth-child(6) > .value > span';
|
||||
const summaryHeader = '.summaryHeader > div';
|
||||
|
||||
const weight = 25;
|
||||
const weightValue = ':nth-child(10) > .value > span';
|
||||
beforeEach(() => {
|
||||
const ticketId = 1;
|
||||
|
||||
cy.login('developer');
|
||||
cy.visit(`/#/ticket/${ticketId}/summary`);
|
||||
cy.waitForElement('.q-page', 7000);
|
||||
cy.viewport(1920, 1080);
|
||||
});
|
||||
|
||||
it('should clone the ticket without warehouse', () => {
|
||||
cy.openLeftMenu();
|
||||
cy.visit('/#/ticket/1/summary');
|
||||
cy.openActionsDescriptor();
|
||||
cy.get(toCloneOpt).click();
|
||||
cy.clickConfirm();
|
||||
|
@ -25,4 +24,15 @@ describe('Ticket descriptor', () => {
|
|||
cy.wrap(owner.trim()).should('eq', 'Bruce Wayne (1101)');
|
||||
});
|
||||
});
|
||||
|
||||
it('should set the weight of the ticket', () => {
|
||||
cy.visit('/#/ticket/10/summary');
|
||||
cy.openActionsDescriptor();
|
||||
cy.get(setWeightOpt).click();
|
||||
cy.intercept('POST', /\/api\/Tickets\/\d+\/setWeight/).as('weight');
|
||||
cy.get('.q-dialog input').type(weight);
|
||||
cy.clickConfirm();
|
||||
cy.wait('@weight');
|
||||
cy.get(weightValue).contains(weight);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue