#7283 #7831 itemMigration #553

Merged
carlossa merged 77 commits from 7283-itemMigration into dev 2024-10-25 07:09:13 +00:00
98 changed files with 1714 additions and 1396 deletions
Showing only changes of commit 04070a5a85 - Show all commits

View File

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

View File

@ -9,16 +9,16 @@ export default {
const keyBindingMap = routes
.filter((route) => route.meta.keyBinding)
.reduce((map, route) => {
map[route.meta.keyBinding.toLowerCase()] = route.path;
map['Key' + route.meta.keyBinding.toUpperCase()] = route.path;
return map;
}, {});
const handleKeyDown = (event) => {
const { ctrlKey, altKey, key } = event;
const { ctrlKey, altKey, code } = event;
if (ctrlKey && altKey && keyBindingMap[key] && !isNotified) {
if (ctrlKey && altKey && keyBindingMap[code] && !isNotified) {
event.preventDefault();
router.push(keyBindingMap[key]);
router.push(keyBindingMap[code]);
isNotified = true;
}
};

View File

@ -0,0 +1,50 @@
<script setup>
import { useI18n } from 'vue-i18n';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FormModelPopup from './FormModelPopup.vue';
const emit = defineEmits(['onDataSaved']);
const { t } = useI18n();
</script>
<template>
<FormModelPopup
url-create="Expenses"
model="Expense"
:title="t('New expense')"
:form-initial-data="{ id: null, isWithheld: false, name: null }"
@on-data-saved="emit('onDataSaved', $event)"
>
<template #form-inputs="{ data, validate }">
<VnRow>
<VnInput
:label="`${t('globals.code')}`"
v-model="data.id"
:required="true"
:rules="validate('expense.code')"
/>
<QCheckbox
dense
size="sm"
:label="`${t('It\'s a withholding')}`"
v-model="data.isWithheld"
:rules="validate('expense.isWithheld')"
/>
</VnRow>
<VnRow>
<VnInput
:label="`${t('globals.description')}`"
v-model="data.name"
:required="true"
:rules="validate('expense.description')"
/>
</VnRow>
</template>
</FormModelPopup>
</template>
<i18n>
es:
New expense: Nuevo gasto
It's a withholding: Es una retención
</i18n>

View File

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

View File

@ -189,11 +189,11 @@ async function saveChanges(data) {
});
}
async function insert() {
async function insert(pushData = $props.dataRequired) {
const $index = formData.value.length
? formData.value[formData.value.length - 1].$index + 1
: 0;
formData.value.push(Object.assign({ $index }, $props.dataRequired));
formData.value.push(Object.assign({ $index }, pushData));
hasChanges.value = true;
}

View File

@ -159,8 +159,8 @@ const removeTag = (index, params, search) => {
/>
<VnFilterPanel
:data-key="props.dataKey"
:expr-builder="exprBuilder"
:custom-tags="customTags"
:expr-builder="props.exprBuilder"
:custom-tags="props.customTags"
>
<template #tags="{ tag, formatFn }">
<strong v-if="tag.label === 'categoryFk'">

View File

@ -1,6 +1,6 @@
<script setup>
import axios from 'axios';
import { onMounted, ref, reactive } from 'vue';
import { onMounted, watch, ref, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import { QSeparator, useQuasar } from 'quasar';
import { useRoute } from 'vue-router';
@ -29,6 +29,15 @@ onMounted(async () => {
getRoutes();
});
watch(
() => route.matched,
() => {
items.value = [];
getRoutes();
},
{ deep: true }
);
function findMatches(search, item) {
const matches = [];
function findRoute(search, item) {

View File

@ -33,7 +33,12 @@ const itemComputed = computed(() => {
<QItemSection avatar v-if="!itemComputed.icon">
<QIcon name="disabled_by_default" />
</QItemSection>
<QItemSection>{{ t(itemComputed.title) }}</QItemSection>
<QItemSection>
{{ t(itemComputed.title) }}
<QTooltip>
{{ 'Ctrl + Alt + ' + item?.keyBinding?.toUpperCase() }}
</QTooltip>
</QItemSection>
<QItemSection side>
<slot name="side" :item="itemComputed" />
</QItemSection>

View File

@ -33,6 +33,7 @@ const invoiceCorrectionTypesOptions = ref([]);
const refund = async () => {
const params = {
id: invoiceParams.id,
withWarehouse: invoiceParams.inheritWarehouse,
cplusRectificationTypeFk: invoiceParams.cplusRectificationTypeFk,
siiTypeInvoiceOutFk: invoiceParams.siiTypeInvoiceOutFk,
invoiceCorrectionTypeFk: invoiceParams.invoiceCorrectionTypeFk,

View File

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

View File

@ -49,6 +49,10 @@ const $props = defineProps({
type: Object,
default: null,
},
createAsDialog: {
type: Boolean,
default: true,
},
cardClass: {
type: String,
default: 'flex-one',
@ -85,6 +89,10 @@ const $props = defineProps({
type: Object,
default: () => ({}),
},
crudModel: {
type: Object,
default: () => ({}),
},
tableHeight: {
type: String,
default: '90vh',
@ -284,10 +292,17 @@ function parseOrder(urlOrders) {
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
defineExpose({
create: createForm,
reload,
redirect: redirectFn,
selected,
CrudModelRef,
});
function handleOnDataSaved(_, res) {
if (_.onDataSaved) _.onDataSaved(this);
else $props.create.onDataSaved(_);
}
</script>
<template>
<QDrawer
@ -374,6 +389,7 @@ defineExpose({
@virtual-scroll="
(event) =>
event.index > rows.length - 2 &&
($props.crudModel?.paginate ?? true) &&
CrudModelRef.vnPaginateRef.paginate()
"
@row-click="(_, row) => rowClickFunction && rowClickFunction(row)"
@ -482,6 +498,7 @@ defineExpose({
>
<QBtn
v-for="(btn, index) of col.actions"
v-show="btn.show ? btn.show(row) : true"
:key="index"
:title="btn.title"
:icon="btn.icon"
@ -616,8 +633,17 @@ defineExpose({
</QTable>
</template>
</CrudModel>
<QPageSticky v-if="create" :offset="[20, 20]" style="z-index: 2">
<QBtn @click="showForm = !showForm" color="primary" fab icon="add" shortcut="+" />
<QPageSticky :offset="[20, 20]" style="z-index: 2">
<QBtn
@click="
() =>
createAsDialog ? (showForm = !showForm) : handleOnDataSaved(create)
"
color="primary"
fab
icon="add"
shortcut="+"
/>
<QTooltip>
{{ createForm.title }}
</QTooltip>

View File

@ -1,6 +1,6 @@
<script setup>
import { onBeforeMount, computed } from 'vue';
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
import { useArrayData } from 'src/composables/useArrayData';
import { useStateStore } from 'stores/useStateStore';
import useCardSize from 'src/composables/useCardSize';
@ -17,10 +17,12 @@ const props = defineProps({
filterPanel: { type: Object, default: undefined },
searchDataKey: { type: String, default: undefined },
searchbarProps: { type: Object, default: undefined },
redirectOnError: { type: Boolean, default: false },
});
const stateStore = useStateStore();
const route = useRoute();
const router = useRouter();
const url = computed(() => {
if (props.baseUrl) return `${props.baseUrl}/${route.params.id}`;
return props.customUrl;
@ -35,8 +37,12 @@ const arrayData = useArrayData(props.dataKey, {
});
onBeforeMount(async () => {
if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id };
await arrayData.fetch({ append: false, updateRouter: false });
try {
if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id };
await arrayData.fetch({ append: false, updateRouter: false });
} catch (e) {
router.push({ name: 'WorkerList' });
}
});
if (props.baseUrl) {

View File

@ -135,6 +135,7 @@ const columns = computed(() => [
field: 'hasFile',
label: t('globals.original'),
name: 'hasFile',
toolTip: t('The documentation is available in paper form'),
component: QCheckbox,
props: (prop) => ({
disable: true,
@ -297,6 +298,14 @@ defineExpose({
row-key="clientFk"
:grid="$q.screen.lt.sm"
>
<template #header="props">
<QTr :props="props" class="bg">
<QTh v-for="col in props.cols" :key="col.name" :props="props">
<QTooltip v-if="col.toolTip">{{ col.toolTip }}</QTooltip
>{{ col.label }}
</QTh>
</QTr>
</template>
<template #body-cell="props">
<QTd :props="props">
<QTr :props="props">
@ -397,8 +406,10 @@ defineExpose({
<i18n>
en:
contentTypesInfo: Allowed file types {allowedContentTypes}
The documentation is available in paper form: The documentation is available in paper form
es:
contentTypesInfo: Tipos de archivo permitidos {allowedContentTypes}
Generate identifier for original file: Generar identificador para archivo original
Upload file: Subir fichero
the documentation is available in paper form: Se tiene la documentación en papel
</i18n>

View File

@ -92,7 +92,6 @@ const mixinRules = [
<slot name="prepend" />
</template>
<template #append>
<slot name="append" v-if="$slots.append && !$attrs.disabled" />
<QIcon
name="close"
size="xs"
@ -104,6 +103,7 @@ const mixinRules = [
}
"
></QIcon>
<slot name="append" v-if="$slots.append && !$attrs.disabled" />
<QIcon v-if="info" name="info">
<QTooltip max-width="350px">
{{ info }}
@ -119,3 +119,8 @@ const mixinRules = [
es:
inputMin: Debe ser mayor a {value}
</i18n>
<style lang="scss">
.q-field__append {
padding-inline: 0;
}
</style>

View File

@ -9,6 +9,10 @@ const $props = defineProps({
type: Boolean,
default: false,
},
showEvent: {
type: Boolean,
default: true,
},
});
const { t } = useI18n();
@ -94,6 +98,7 @@ watch(
:class="{ required: $attrs.required }"
:rules="$attrs.required ? [requiredFieldRule] : null"
:clearable="false"
@click="isPopupOpen = true"
>
<template #append>
<QIcon
@ -111,6 +116,7 @@ watch(
"
/>
<QIcon
v-if="showEvent"
name="event"
class="cursor-pointer"
@click="isPopupOpen = !isPopupOpen"
@ -130,6 +136,7 @@ watch(
v-model="popupDate"
:landscape="true"
:today-btn="true"
:options="$attrs.options"
@update:model-value="
(date) => {
formattedDate = date;

View File

@ -14,6 +14,7 @@ import VnJsonValue from '../common/VnJsonValue.vue';
import FetchData from '../FetchData.vue';
import VnSelect from './VnSelect.vue';
import VnUserLink from '../ui/VnUserLink.vue';
import VnPaginate from '../ui/VnPaginate.vue';
const stateStore = useStateStore();
const validationsStore = useValidator();
@ -66,9 +67,10 @@ const filter = {
},
},
],
where: { and: [{ originFk: route.params.id }] },
};
const workers = ref();
const paginate = ref();
const actions = ref();
const changeInput = ref();
const searchInput = ref();
@ -235,9 +237,7 @@ async function openPointRecord(id, modelLog) {
const locale = validations[modelLog.model]?.locale || {};
pointRecord.value = parseProps(propNames, locale, data);
}
async function setLogTree() {
filter.where = { and: [{ originFk: route.params.id }] };
const { data } = await getLogs(filter);
async function setLogTree(data) {
logTree.value = getLogTree(data);
}
@ -266,15 +266,7 @@ async function applyFilter() {
filter.where.and.push(selectedFilters.value);
}
const { data } = await getLogs(filter);
logTree.value = getLogTree(data);
}
async function getLogs(filter) {
return axios.get(props.url ?? `${props.model}Logs`, {
params: { filter: JSON.stringify(filter) },
});
paginate.value.fetch(filter);
}
function setDate(type) {
@ -377,8 +369,6 @@ async function clearFilter() {
await applyFilter();
}
setLogTree();
onUnmounted(() => {
stateStore.rightDrawer = false;
});
@ -391,16 +381,6 @@ watch(
);
</script>
<template>
<FetchData
:url="`${props.model}Logs/${route.params.id}/editors`"
:filter="{
fields: ['id', 'nickname', 'name', 'image'],
order: 'nickname',
limit: 30,
}"
@on-fetch="(data) => (workers = data)"
auto-load
/>
<FetchData
:url="`${props.model}Logs/${route.params.id}/models`"
:filter="{ order: ['changedModel'] }"
@ -418,231 +398,283 @@ watch(
"
auto-load
/>
<div
class="column items-center logs origin-log q-mt-md"
v-for="(originLog, originLogIndex) in logTree"
:key="originLogIndex"
<VnPaginate
ref="paginate"
:data-key="`${model}Log`"
:url="`${model}Logs`"
:filter="filter"
:skeleton="false"
auto-load
@on-fetch="setLogTree"
>
<QItem class="origin-info items-center q-my-md" v-if="logTree.length > 1">
<h6 class="origin-id text-grey">
{{ useCapitalize(validations[props.model].locale.name) }}
#{{ originLog.originFk }}
</h6>
<div class="line bg-grey"></div>
</QItem>
<div
class="user-log q-mb-sm"
v-for="(userLog, userIndex) in originLog.logs"
:key="userIndex"
>
<div class="timeline">
<div class="user-avatar">
<VnUserLink :worker-id="userLog?.user?.id">
<template #link>
<VnAvatar
:class="{ 'cursor-pointer': userLog?.user?.id }"
:worker-id="userLog?.user?.id"
:title="userLog?.user?.nickname"
:show-letter="!userLog?.user"
size="lg"
/>
</template>
</VnUserLink>
</div>
<div class="arrow bg-panel" v-if="byRecord"></div>
<div class="line"></div>
</div>
<QList class="user-changes" v-if="userLog">
<QItem
class="model-log column q-px-none q-py-xs"
v-for="(modelLog, modelLogIndex) in userLog.logs"
:key="modelLogIndex"
<template #body>
<div
class="column items-center logs origin-log q-mt-md"
v-for="(originLog, originLogIndex) in logTree"
:key="originLogIndex"
>
<QItem class="origin-info items-center q-my-md" v-if="logTree.length > 1">
<h6 class="origin-id text-grey">
{{ useCapitalize(validations[props.model].locale.name) }}
#{{ originLog.originFk }}
</h6>
<div class="line bg-grey"></div>
</QItem>
<div
class="user-log q-mb-sm"
v-for="(userLog, userIndex) in originLog.logs"
:key="userIndex"
>
<QItemSection>
<QItemLabel class="model-info q-mb-xs" v-if="!byRecord">
<QChip
dense
size="md"
class="model-name q-mr-xs text-white"
v-if="
!(modelLog.changedModel && modelLog.changedModelId) &&
modelLog.model
"
:style="{
backgroundColor: useColor(modelLog.model),
}"
:title="`${modelLog.model} #${modelLog.id}`"
>
{{ t(modelLog.modelI18n) }}
</QChip>
<span
class="model-id q-mr-xs"
v-if="modelLog.summaryId"
v-text="`#${modelLog.summaryId}`"
/>
<span
class="model-value"
:title="modelLog.showValue"
v-text="modelLog.showValue"
/>
<QBtn
flat
round
color="grey"
class="q-mr-xs q-ml-auto"
size="sm"
icon="filter_alt"
:title="t('recordChanges')"
@click.stop="filterByRecord(modelLog)"
/>
</QItemLabel>
</QItemSection>
<QItemSection>
<QCard
class="changes-log q-py-none q-mb-xs"
v-for="(log, logIndex) in modelLog.logs"
:key="logIndex"
<div class="timeline">
<div class="user-avatar">
<VnUserLink :worker-id="userLog?.user?.id">
<template #link>
<VnAvatar
:class="{ 'cursor-pointer': userLog?.user?.id }"
:worker-id="userLog?.user?.id"
:title="userLog?.user?.nickname"
:show-letter="!userLog?.user"
size="lg"
/>
</template>
</VnUserLink>
</div>
<div class="arrow bg-panel" v-if="byRecord"></div>
<div class="line"></div>
</div>
<QList class="user-changes" v-if="userLog">
<QItem
class="model-log column q-px-none q-py-xs"
v-for="(modelLog, modelLogIndex) in userLog.logs"
:key="modelLogIndex"
>
<QCardSection class="change-info q-pa-none">
<QItem
class="q-px-sm q-py-xs justify-between items-center"
>
<div
class="date text-grey text-caption q-mr-sm"
:title="
date.formatDate(
log.creationDate,
'DD/MM/YYYY hh:mm:ss'
) ?? `date:'dd/MM/yyyy HH:mm:ss'`
<QItemSection>
<QItemLabel class="model-info q-mb-xs" v-if="!byRecord">
<QChip
dense
size="md"
class="model-name q-mr-xs text-white"
v-if="
!(
modelLog.changedModel &&
modelLog.changedModelId
) && modelLog.model
"
:style="{
backgroundColor: useColor(modelLog.model),
}"
:title="`${modelLog.model} #${modelLog.id}`"
>
{{ toRelativeDate(log.creationDate) }}
</div>
<div>
<QBtn
color="grey"
class="pit"
icon="preview"
flat
round
:title="t('pointRecord')"
padding="none"
v-if="log.action != 'insert'"
@click.stop="
openPointRecord(log.id, modelLog)
"
>
<QPopupProxy>
<QCard v-if="pointRecord">
<div
class="header q-px-sm q-py-xs q-ma-none text-white text-bold bg-primary"
>
{{ modelLog.modelI18n }}
<span v-if="modelLog.id"
>#{{ modelLog.id }}</span
>
</div>
<QCardSection
class="change-detail q-pa-sm"
>
<QItem
v-for="(
value, index
) in pointRecord"
:key="index"
class="q-pa-none"
>
<span
class="json-field q-mr-xs text-grey"
:title="value.name"
>
{{ value.nameI18n }}:
</span>
<VnJsonValue
:value="value.val.val"
/>
</QItem>
</QCardSection>
</QCard>
</QPopupProxy>
</QBtn>
<QIcon
class="action q-ml-xs"
:class="actionsClass[log.action]"
:name="actionsIcon[log.action]"
:title="
t(`actions.${actionsText[log.action]}`)
"
/>
</div>
</QItem>
</QCardSection>
<QCardSection
class="change-detail q-px-sm q-py-xs"
:class="{ expanded: log.expand }"
v-if="log.props.length || log.description"
>
<QIcon
class="cursor-pointer q-mr-md"
color="grey"
name="expand_more"
:title="t('globals.details')"
size="sm"
@click="log.expand = !log.expand"
/>
<span v-if="log.props.length" class="attributes">
<span v-if="!log.expand" class="q-pa-none text-grey">
<span
v-for="(prop, propIndex) in log.props"
:key="propIndex"
class="basic-json"
>
<span class="json-field" :title="prop.name">
{{ prop.nameI18n }}:
</span>
<VnJsonValue :value="prop.val.val" />
<span v-if="propIndex < log.props.length - 1"
>,&nbsp;
</span>
</span>
</span>
{{ t(modelLog.modelI18n) }}
</QChip>
<span
v-if="log.expand"
class="expanded-json column q-pa-none"
>
<div
v-for="(prop, prop2Index) in log.props"
:key="prop2Index"
class="q-pa-none text-grey"
class="model-id q-mr-xs"
v-if="modelLog.summaryId"
v-text="`#${modelLog.summaryId}`"
/>
<span
class="model-value"
:title="modelLog.showValue"
v-text="modelLog.showValue"
/>
<QBtn
flat
round
color="grey"
class="q-mr-xs q-ml-auto"
size="sm"
icon="filter_alt"
:title="t('recordChanges')"
@click.stop="filterByRecord(modelLog)"
/>
</QItemLabel>
</QItemSection>
<QItemSection>
<QCard
class="changes-log q-py-none q-mb-xs"
v-for="(log, logIndex) in modelLog.logs"
:key="logIndex"
>
<QCardSection class="change-info q-pa-none">
<QItem
class="q-px-sm q-py-xs justify-between items-center"
>
<span class="json-field" :title="prop.name">
{{ prop.nameI18n }}:
</span>
<VnJsonValue :value="prop.val.val" />
<span v-if="prop.val.id" class="id-value">
#{{ prop.val.id }}
</span>
<span v-if="log.action == 'update'">
<VnJsonValue :value="prop.old.val" />
<span v-if="prop.old.id" class="id-value">
#{{ prop.old.id }}
<div
class="date text-grey text-caption q-mr-sm"
:title="
date.formatDate(
log.creationDate,
'DD/MM/YYYY hh:mm:ss'
) ?? `date:'dd/MM/yyyy HH:mm:ss'`
"
>
{{ toRelativeDate(log.creationDate) }}
</div>
<div>
<QBtn
color="grey"
class="pit"
icon="preview"
flat
round
:title="t('pointRecord')"
padding="none"
v-if="log.action != 'insert'"
@click.stop="
openPointRecord(log.id, modelLog)
"
>
<QPopupProxy>
<QCard v-if="pointRecord">
<div
class="header q-px-sm q-py-xs q-ma-none text-white text-bold bg-primary"
>
{{ modelLog.modelI18n }}
<span v-if="modelLog.id"
>#{{
modelLog.id
}}</span
>
</div>
<QCardSection
class="change-detail q-pa-sm"
>
<QItem
v-for="(
value, index
) in pointRecord"
:key="index"
class="q-pa-none"
>
<span
class="json-field q-mr-xs text-grey"
:title="
value.name
"
>
{{
value.nameI18n
}}:
</span>
<VnJsonValue
:value="
value.val.val
"
/>
</QItem>
</QCardSection>
</QCard>
</QPopupProxy>
</QBtn>
<QIcon
class="action q-ml-xs"
:class="actionsClass[log.action]"
:name="actionsIcon[log.action]"
:title="
t(
`actions.${
actionsText[log.action]
}`
)
"
/>
</div>
</QItem>
</QCardSection>
<QCardSection
class="change-detail q-px-sm q-py-xs"
:class="{ expanded: log.expand }"
v-if="log.props.length || log.description"
>
<QIcon
class="cursor-pointer q-mr-md"
color="grey"
name="expand_more"
:title="t('globals.details')"
size="sm"
@click="log.expand = !log.expand"
/>
<span v-if="log.props.length" class="attributes">
<span
v-if="!log.expand"
class="q-pa-none text-grey"
>
<span
v-for="(prop, propIndex) in log.props"
:key="propIndex"
class="basic-json"
>
<span
class="json-field"
:title="prop.name"
>
{{ prop.nameI18n }}:
</span>
<VnJsonValue :value="prop.val.val" />
<span
v-if="
propIndex <
log.props.length - 1
"
>,&nbsp;
</span>
</span>
</span>
</div>
</span>
</span>
<span v-if="!log.props.length" class="description">
{{ log.description }}
</span>
</QCardSection>
</QCard>
</QItemSection>
</QItem>
</QList>
</div>
</div>
<span
v-if="log.expand"
class="expanded-json column q-pa-none"
>
<div
v-for="(
prop, prop2Index
) in log.props"
:key="prop2Index"
class="q-pa-none text-grey"
>
<span
class="json-field"
:title="prop.name"
>
{{ prop.nameI18n }}:
</span>
<VnJsonValue :value="prop.val.val" />
<span
v-if="prop.val.id"
class="id-value"
>
#{{ prop.val.id }}
</span>
<span v-if="log.action == 'update'">
<VnJsonValue
:value="prop.old.val"
/>
<span
v-if="prop.old.id"
class="id-value"
>
#{{ prop.old.id }}
</span>
</span>
</div>
</span>
</span>
<span
v-if="!log.props.length"
class="description"
>
{{ log.description }}
</span>
</QCardSection>
</QCard>
</QItemSection>
</QItem>
</QList>
</div>
</div>
</template>
</VnPaginate>
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()">
<QList dense>
<QSeparator />
@ -691,17 +723,16 @@ watch(
</QOptionGroup>
</QItem>
<QItem class="q-mt-sm">
<QItemSection v-if="!workers">
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="workers && userRadio !== null">
<QItemSection v-if="userRadio !== null">
<VnSelect
class="full-width"
:label="t('globals.user')"
v-model="userSelect"
option-label="name"
option-value="id"
:options="workers"
:url="`${model}Logs/${$route.params.id}/editors`"
:fields="['id', 'nickname', 'name', 'image']"
sort-by="nickname"
@update:model-value="selectFilter('userSelect')"
hide-selected
>

View File

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

View File

@ -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">

View File

@ -132,17 +132,6 @@ async function search(evt) {
}
}
async function reload() {
isLoading.value = true;
const params = Object.values(userParams.value).filter((param) => param);
store.skip = 0;
store.page = 1;
await arrayData.fetch({ append: false });
if (!$props.showAll && !params.length) store.data = [];
isLoading.value = false;
emit('refresh');
}
async function clearFilters() {
try {
isLoading.value = true;
@ -231,32 +220,18 @@ function sanitizer(params) {
</QItemLabel>
</QItemSection>
<QItemSection top side>
<div class="q-gutter-xs">
<QBtn
@click="clearFilters"
color="primary"
dense
flat
icon="filter_list_off"
padding="none"
round
size="sm"
>
<QTooltip>{{ t('Remove filters') }}</QTooltip>
</QBtn>
<QBtn
@click="reload"
color="primary"
dense
flat
icon="refresh"
padding="none"
round
size="sm"
>
<QTooltip>{{ t('Refresh') }}</QTooltip>
</QBtn>
</div>
<QBtn
@click="clearFilters"
color="primary"
dense
flat
icon="filter_list_off"
padding="none"
round
size="sm"
>
<QTooltip>{{ t('Remove filters') }}</QTooltip>
</QBtn>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">

View File

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

View File

@ -1,3 +1,6 @@
<script setup>
defineProps({ wrap: { type: Boolean, default: false } });
</script>
<template>
<div class="vn-row q-gutter-md q-mb-md">
<slot />
@ -15,7 +18,9 @@
}
@media screen and (max-width: 800px) {
.vn-row {
flex-direction: column;
&:not(.wrap) {
flex-direction: column;
}
}
}
</style>

View File

@ -0,0 +1,10 @@
import { toCurrency } from 'src/filters';
export function getTotal(rows, key, opts = {}) {
const { currency, cb } = opts;
const total = rows.reduce((acc, row) => acc + +(cb ? cb(row) : row[key] || 0), 0);
return currency
? toCurrency(total, currency == 'default' ? undefined : currency)
: total;
}

View File

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

View File

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

View File

@ -40,6 +40,8 @@ globals:
noChanges: No changes to save
changesToSave: You have changes pending to save
confirmRemove: You are about to delete this row. Are you sure?
rowWillBeRemoved: This row will be removed
sureToContinue: Are you sure you want to continue?
rowAdded: Row added
rowRemoved: Row removed
pleaseWait: Please wait...
@ -84,7 +86,7 @@ globals:
description: Description
id: Id
order: Order
original: Original
original: Phys. Doc
file: File
selectFile: Select a file
copyClipboard: Copy on clipboard
@ -96,6 +98,7 @@ globals:
to: To
notes: Notes
refresh: Refresh
weight: Weight
pageTitles:
logIn: Login
summary: Summary
@ -261,6 +264,7 @@ globals:
clientsActionsMonitor: Clients and actions
serial: Serial
medical: Mutual
supplier: Supplier
created: Created
worker: Worker
now: Now
@ -753,56 +757,6 @@ parking:
searchBar:
info: You can search by parking code
label: Search parking...
invoiceIn:
list:
ref: Reference
supplier: Supplier
supplierRef: Supplier ref.
serialNumber: Serial number
serial: Serial
file: File
issued: Issued
isBooked: Is booked
awb: AWB
amount: Amount
card:
issued: Issued
amount: Amount
client: Client
company: Company
customerCard: Customer card
ticketList: Ticket List
vat: Vat
dueDay: Due day
intrastat: Intrastat
summary:
supplier: Supplier
supplierRef: Supplier ref.
currency: Currency
docNumber: Doc number
issued: Expedition date
operated: Operation date
bookEntried: Entry date
bookedDate: Booked date
sage: Sage withholding
vat: Undeductible VAT
company: Company
booked: Booked
expense: Expense
taxableBase: Taxable base
rate: Rate
sageVat: Sage vat
sageTransaction: Sage transaction
dueDay: Date
bank: Bank
amount: Amount
foreignValue: Foreign value
dueTotal: Due day
noMatch: Do not match
code: Code
net: Net
stems: Stems
country: Country
order:
field:
salesPersonFk: Sales Person
@ -1318,6 +1272,7 @@ components:
active: Is active
visible: Is visible
floramondo: Is floramondo
showBadDates: Show future items
userPanel:
copyToken: Token copied to clipboard
settings: Settings

View File

@ -39,6 +39,8 @@ globals:
noChanges: Sin cambios que guardar
changesToSave: Tienes cambios pendientes de guardar
confirmRemove: Vas a eliminar este registro. ¿Continuar?
rowWillBeRemoved: Esta linea se eliminará
sureToContinue: ¿Seguro que quieres continuar?
rowAdded: Fila añadida
rowRemoved: Fila eliminada
pleaseWait: Por favor espera...
@ -86,7 +88,7 @@ globals:
description: Descripción
id: Id
order: Orden
original: Original
original: Doc. física
file: Fichero
selectFile: Seleccione un fichero
copyClipboard: Copiar en portapapeles
@ -98,6 +100,7 @@ globals:
to: Hasta
notes: Notas
refresh: Actualizar
weight: Peso
pageTitles:
logIn: Inicio de sesión
summary: Resumen
@ -265,6 +268,7 @@ globals:
clientsActionsMonitor: Clientes y acciones
serial: Facturas por serie
medical: Mutua
supplier: Proveedor
created: Fecha creación
worker: Trabajador
now: Ahora
@ -799,54 +803,6 @@ parking:
searchBar:
info: Puedes buscar por código de parking
label: Buscar parking...
invoiceIn:
list:
ref: Referencia
supplier: Proveedor
supplierRef: Ref. proveedor
serialNumber: Num. serie
shortIssued: F. emisión
serial: Serie
file: Fichero
issued: Fecha emisión
isBooked: Conciliada
awb: AWB
amount: Importe
card:
issued: Fecha emisión
amount: Importe
client: Cliente
company: Empresa
customerCard: Ficha del cliente
ticketList: Listado de tickets
vat: Iva
dueDay: Fecha de vencimiento
summary:
supplier: Proveedor
supplierRef: Ref. proveedor
currency: Divisa
docNumber: Número documento
issued: Fecha de expedición
operated: Fecha operación
bookEntried: Fecha asiento
bookedDate: Fecha contable
sage: Retención sage
vat: Iva no deducible
company: Empresa
booked: Contabilizada
expense: Gasto
taxableBase: Base imp.
rate: Tasa
sageTransaction: Sage transación
dueDay: Fecha
bank: Caja
amount: Importe
foreignValue: Divisa
dueTotal: Vencimiento
code: Código
net: Neto
stems: Tallos
country: País
department:
pageTitles:
basicData: Basic data
@ -1298,6 +1254,7 @@ components:
active: Activo
visible: Visible
floramondo: Floramondo
showBadDates: Ver items a futuro
userPanel:
copyToken: Token copiado al portapapeles
settings: Configuración

View File

@ -27,15 +27,15 @@ const filter = {
order: 'created DESC',
};
const urlPath = 'AccessTokens';
const urlPath = 'VnTokens';
const refresh = () => paginateRef.value.fetch();
const navigate = (id) => router.push({ name: 'AccountSummary', params: { id } });
const killSession = async (id) => {
const killSession = async ({ userId, created }) => {
try {
await axios.delete(`${urlPath}/${id}`);
await axios.post(`${urlPath}/killSession`, { userId, created });
paginateRef.value.fetch();
notify(t('Session killed'), 'positive');
} catch (error) {
@ -84,7 +84,7 @@ const killSession = async (id) => {
openConfirmationModal(
t('Session will be killed'),
t('Are you sure you want to continue?'),
() => killSession(row.id)
() => killSession(row)
)
"
outline

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import axios from 'axios';
import { usePrintService } from 'composables/usePrintService';
import { useQuasar } from 'quasar';
import { useState } from 'src/composables/useState';
@ -27,7 +28,7 @@ const router = useRouter();
const state = useState();
const user = state.getUser();
const stateStore = useStateStore();
const { sendEmail } = usePrintService();
const client = ref({});
const hasChanged = ref(false);
const isLoading = ref(false);
@ -156,22 +157,33 @@ const onSubmit = async () => {
}
};
const onDataSaved = async ({
addressId,
companyFk,
companyId,
from,
recipient,
replyTo,
}) => {
await axios.post(`Clients/${route.params.id}/incoterms-authorization-email`, {
addressId,
companyFk,
companyId,
from,
recipient,
replyTo,
});
const getSamples = async () => {
try {
const filter = { where: { id: initialData.typeFk } };
let { data } = await axios.get('Samples', {
params: { filter: JSON.stringify(filter) },
});
return data[0];
} catch (error) {
notify('errors.create', 'negative');
}
};
getSamples();
const onDataSaved = async () => {
try {
const params = {
recipientId: initialData.recipientId,
recipient: initialData.recipient,
replyTo: initialData.replyTo,
};
setParams(params);
const samplesData = await getSamples();
const path = `${samplesData.model}/${route.params.id}/${samplesData.code}-email`;
await sendEmail(path, params);
} catch (error) {
notify('errors.create', 'negative');
}
toCustomerSamples();
};

View File

@ -55,6 +55,15 @@ const pinnedModules = computed(() => navigation.getPinnedModules());
>
<div class="text-center text-primary button-text">
{{ t(item.title) }}
<div v-if="item.keyBinding">
{{ '(' + item.keyBinding + ')' }}
<QTooltip>
{{
'Ctrl + Alt + ' +
item.keyBinding.toUpperCase()
}}
</QTooltip>
</div>
</div>
</QBtn>
</div>

View File

@ -26,7 +26,6 @@ const { notify } = useNotify();
const rowsSelected = ref([]);
const entryBuysPaginateRef = ref(null);
const packagingsOptions = ref(null);
const originalRowDataCopy = ref(null);
const getInputEvents = (colField, props) => {
@ -66,7 +65,10 @@ const tableColumnComponents = computed(() => ({
'map-options': true,
'use-input': true,
'hide-selected': true,
options: packagingsOptions.value,
url: 'Packagings',
fields: ['id'],
where: { freightItemFk: true },
'sort-by': 'id ASC',
dense: true,
},
event: getInputEvents,
@ -304,13 +306,6 @@ const lockIconType = (groupingMode, mode) => {
</script>
<template>
<FetchData
ref="expensesRef"
url="Packagings"
:filter="{ fields: ['id'], where: { freightItemFk: true }, order: 'id ASC' }"
auto-load
@on-fetch="(data) => (packagingsOptions = data)"
/>
<VnSubToolbar>
<template #st-actions>
<QBtnGroup push style="column-gap: 10px">

View File

@ -223,6 +223,10 @@ async function onSubmit() {
autofocus
/>
</VnRow>
<VnRow>
<VnInputDate :label="t('Entry date')" v-model="data.bookEntried" />
<VnInputDate :label="t('Accounted date')" v-model="data.booked" />
</VnRow>
<VnRow>
<VnSelect
:label="t('Undeductible VAT')"
@ -285,10 +289,6 @@ async function onSubmit() {
</template>
</VnInput>
</VnRow>
<VnRow>
<VnInputDate :label="t('Entry date')" v-model="data.bookEntried" />
<VnInputDate :label="t('Accounted date')" v-model="data.booked" />
</VnRow>
<VnRow>
<VnSelect
:label="t('Currency')"

View File

@ -3,6 +3,8 @@ import VnCard from 'components/common/VnCard.vue';
import InvoiceInDescriptor from './InvoiceInDescriptor.vue';
import InvoiceInFilter from '../InvoiceInFilter.vue';
import InvoiceInSearchbar from '../InvoiceInSearchbar.vue';
import { onBeforeRouteUpdate } from 'vue-router';
import { setRectificative } from '../composables/setRectificative';
const filter = {
include: [
@ -20,6 +22,8 @@ const filter = {
{ relation: 'currency' },
],
};
onBeforeRouteUpdate(async (to) => await setRectificative(to));
</script>
<template>
<VnCard

View File

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

View File

@ -5,10 +5,10 @@ import { useI18n } from 'vue-i18n';
import axios from 'axios';
import { toDate } from 'src/filters';
import { useArrayData } from 'src/composables/useArrayData';
import { getTotal } from 'src/composables/getTotal';
import CrudModel from 'src/components/CrudModel.vue';
import FetchData from 'src/components/FetchData.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import { toCurrency } from 'src/filters';
import useNotify from 'src/composables/useNotify.js';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
@ -72,7 +72,6 @@ async function insert() {
await invoiceInFormRef.value.reload();
notify(t('globals.dataSaved'), 'positive');
}
const getTotalAmount = (rows) => rows.reduce((acc, { amount }) => acc + +amount, 0);
</script>
<template>
<FetchData
@ -155,9 +154,17 @@ const getTotalAmount = (rows) => rows.reduce((acc, { amount }) => acc + +amount,
<QTd />
<QTd />
<QTd>
{{ toCurrency(getTotalAmount(rows), currency) }}
{{ getTotal(rows, 'amount', { currency: 'default' }) }}
</QTd>
<QTd>
<template v-if="isNotEuro(invoiceIn.currency.code)">
{{
getTotal(rows, 'foreignValue', {
currency: invoiceIn.currency.code,
})
}}
</template>
</QTd>
<QTd />
</QTr>
</template>
<template #item="props">

View File

@ -2,18 +2,15 @@
import { computed, ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { toCurrency } from 'src/filters';
import { getTotal } from 'src/composables/getTotal';
import CrudModel from 'src/components/CrudModel.vue';
import FetchData from 'src/components/FetchData.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import { useArrayData } from 'src/composables/useArrayData';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
const { t } = useI18n();
const route = useRoute();
const arrayData = useArrayData();
const currency = computed(() => arrayData.store.data?.currency?.code);
const invoceInIntrastat = ref([]);
const rowsSelected = ref([]);
const countries = ref([]);
@ -72,9 +69,6 @@ const columns = computed(() => [
},
]);
const getTotal = (data, key) =>
data.reduce((acc, cur) => acc + +String(cur[key] || 0).replace(',', '.'), 0);
const formatOpt = (row, { model, options }, prop) => {
const obj = row[model];
const option = options.find(({ id }) => id == obj);
@ -154,7 +148,7 @@ const formatOpt = (row, { model, options }, prop) => {
<QTd />
<QTd />
<QTd>
{{ toCurrency(getTotal(rows, 'amount'), currency) }}
{{ getTotal(rows, 'amount', { currency: 'default' }) }}
</QTd>
<QTd>
{{ getTotal(rows, 'net') }}

View File

@ -35,7 +35,7 @@ const vatColumns = ref([
name: 'landed',
label: 'invoiceIn.summary.taxableBase',
field: (row) => row.taxableBase,
format: (value) => toCurrency(value, currency.value),
format: (value) => toCurrency(value),
sortable: true,
align: 'left',
},
@ -64,7 +64,7 @@ const vatColumns = ref([
name: 'rate',
label: 'invoiceIn.summary.rate',
field: (row) => taxRate(row.taxableBase, row.taxTypeSage?.rate),
format: (value) => toCurrency(value, currency.value),
format: (value) => toCurrency(value),
sortable: true,
align: 'left',
},
@ -72,7 +72,7 @@ const vatColumns = ref([
name: 'currency',
label: 'invoiceIn.summary.currency',
field: (row) => row.foreignValue,
format: (value) => value,
format: (val) => val && toCurrency(val, currency.value),
sortable: true,
align: 'left',
},
@ -97,7 +97,7 @@ const dueDayColumns = ref([
name: 'amount',
label: 'invoiceIn.summary.amount',
field: (row) => row.amount,
format: (value) => toCurrency(value, currency.value),
format: (value) => toCurrency(value),
sortable: true,
align: 'left',
},
@ -105,7 +105,7 @@ const dueDayColumns = ref([
name: 'landed',
label: 'invoiceIn.summary.foreignValue',
field: (row) => row.foreignValue,
format: (value) => value,
format: (val) => val && toCurrency(val, currency.value),
sortable: true,
align: 'left',
},
@ -124,7 +124,7 @@ const intrastatColumns = ref([
{
name: 'amount',
label: 'invoiceIn.summary.amount',
field: (row) => toCurrency(row.amount, currency.value),
field: (row) => toCurrency(row.amount),
sortable: true,
align: 'left',
},
@ -179,7 +179,6 @@ const getTotalTax = (tax) =>
const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</script>
<template>
<CardSummary
data-key="InvoiceInSummary"
@ -229,10 +228,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
:label="t('invoiceIn.summary.currency')"
:value="entity.currency?.code"
/>
<VnLv
:label="t('invoiceIn.summary.docNumber')"
:value="`${entity.serial}/${entity.serialNumber}`"
/>
<VnLv :label="t('invoiceIn.serial')" :value="`${entity.serial}`" />
</QCard>
<QCard class="vn-one">
<QCardSection class="q-pa-none">
@ -293,12 +289,9 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
<QCardSection class="q-pa-none">
<VnLv
:label="t('invoiceIn.summary.taxableBase')"
:value="toCurrency(entity.totals.totalTaxableBase, currency)"
/>
<VnLv
label="Total"
:value="toCurrency(entity.totals.totalVat, currency)"
:value="toCurrency(entity.totals.totalTaxableBase)"
/>
<VnLv label="Total" :value="toCurrency(entity.totals.totalVat)" />
<VnLv :label="t('invoiceIn.summary.dueTotal')">
<template #value>
<QChip
@ -311,7 +304,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
: t('invoiceIn.summary.dueTotal')
"
>
{{ toCurrency(entity.totals.totalDueDay, currency) }}
{{ toCurrency(entity.totals.totalDueDay) }}
</QChip>
</template>
</VnLv>
@ -350,15 +343,17 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
<template #bottom-row>
<QTr class="bg">
<QTd></QTd>
<QTd>{{ toCurrency(entity.totals.totalTaxableBase) }}</QTd>
<QTd></QTd>
<QTd></QTd>
<QTd>{{ toCurrency(getTotalTax(entity.invoiceInTax)) }}</QTd>
<QTd>{{
toCurrency(entity.totals.totalTaxableBase, currency)
entity.totals.totalTaxableBaseForeignValue &&
toCurrency(
entity.totals.totalTaxableBaseForeignValue,
currency
)
}}</QTd>
<QTd></QTd>
<QTd></QTd>
<QTd>{{
toCurrency(getTotalTax(entity.invoiceInTax, currency))
}}</QTd>
<QTd></QTd>
</QTr>
</template>
</QTable>
@ -384,9 +379,17 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
<QTd></QTd>
<QTd></QTd>
<QTd>
{{ toCurrency(entity.totals.totalDueDay, currency) }}
{{ toCurrency(entity.totals.totalDueDay) }}
</QTd>
<QTd>
{{
entity.totals.totalDueDayForeignValue &&
toCurrency(
entity.totals.totalDueDayForeignValue,
currency
)
}}
</QTd>
<QTd></QTd>
</QTr>
</template>
</QTable>
@ -421,7 +424,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
<template #bottom-row>
<QTr class="bg">
<QTd></QTd>
<QTd>{{ toCurrency(intrastatTotals.amount, currency) }}</QTd>
<QTd>{{ toCurrency(intrastatTotals.amount) }}</QTd>
<QTd>{{ intrastatTotals.net }}</QTd>
<QTd>{{ intrastatTotals.stems }}</QTd>
<QTd></QTd>

View File

@ -2,18 +2,17 @@
import { ref, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import axios from 'axios';
import { useArrayData } from 'src/composables/useArrayData';
import { getTotal } from 'src/composables/getTotal';
import { toCurrency } from 'src/filters';
import FetchData from 'src/components/FetchData.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import CrudModel from 'src/components/CrudModel.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CreateNewExpenseForm from 'src/components/CreateNewExpenseForm.vue';
const { t } = useI18n();
const quasar = useQuasar();
const arrayData = useArrayData();
const invoiceIn = computed(() => arrayData.store.data);
@ -23,15 +22,7 @@ const expenses = ref([]);
const sageTaxTypes = ref([]);
const sageTransactionTypes = ref([]);
const rowsSelected = ref([]);
const newExpense = ref({
code: undefined,
isWithheld: false,
description: undefined,
});
const invoiceInFormRef = ref();
const expensesRef = ref();
const newExpenseRef = ref();
defineProps({
actionIcon: {
@ -56,7 +47,7 @@ const columns = computed(() => [
{
name: 'taxablebase',
label: t('Taxable base'),
field: (row) => toCurrency(row.taxableBase, currency.value),
field: (row) => row.taxableBase,
model: 'taxableBase',
sortable: true,
tabIndex: 2,
@ -91,7 +82,7 @@ const columns = computed(() => [
label: t('Rate'),
sortable: true,
tabIndex: 5,
field: (row) => toCurrency(taxRate(row, row.taxTypeSageFk), currency.value),
field: (row) => taxRate(row, row.taxTypeSageFk),
align: 'left',
},
{
@ -132,40 +123,6 @@ function taxRate(invoiceInTax) {
return (taxTypeSage / 100) * taxableBase;
}
async function addExpense() {
try {
if (!newExpense.value.code) throw new Error(t(`The code can't be empty`));
if (isNaN(newExpense.value.code))
throw new Error(t(`The code have to be a number`));
if (!newExpense.value.description)
throw new Error(t(`The description can't be empty`));
const data = [
{
id: newExpense.value.code,
isWithheld: newExpense.value.isWithheld,
name: newExpense.value.description,
},
];
await axios.post(`Expenses`, data);
await expensesRef.value.fetch();
quasar.notify({
type: 'positive',
message: t('globals.dataSaved'),
});
newExpenseRef.value.hide();
} catch (error) {
quasar.notify({
type: 'negative',
message: t(`${error.message}`),
});
}
}
const getTotalTaxableBase = (rows) =>
rows.reduce((acc, { taxableBase }) => acc + +(taxableBase || 0), 0);
const getTotalRate = (rows) => rows.reduce((acc, cur) => acc + +taxRate(cur), 0);
const formatOpt = (row, { model, options }, prop) => {
const obj = row[model];
const option = options.find(({ id }) => id == obj);
@ -207,37 +164,25 @@ const formatOpt = (row, { model, options }, prop) => {
>
<template #body-cell-expense="{ row, col }">
<QTd>
<VnSelect
<VnSelectDialog
v-model="row[col.model]"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
:filter-options="['id', 'name']"
:tooltip="t('Create a new expense')"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
{{ `${scope.opt.id}: ${scope.opt.name}` }}
</QItem>
</template>
<template #append>
<QIcon
name="close"
@click.stop="value = null"
class="cursor-pointer"
size="xs"
<template #form>
<CreateNewExpenseForm
@on-data-saved="$refs.expensesRef.fetch()"
/>
<QIcon
@click.stop.prevent="newExpenseRef.show()"
:name="actionIcon"
size="xs"
class="default-icon"
>
<QTooltip>
{{ t('Create expense') }}
</QTooltip>
</QIcon>
</template>
</VnSelect>
</VnSelectDialog>
</QTd>
</template>
<template #body-cell-taxablebase="{ row }">
@ -325,12 +270,24 @@ const formatOpt = (row, { model, options }, prop) => {
<QTd />
<QTd />
<QTd>
{{ toCurrency(getTotalTaxableBase(rows), currency) }}
{{ getTotal(rows, 'taxableBase', { currency: 'default' }) }}
</QTd>
<QTd />
<QTd />
<QTd> {{ toCurrency(getTotalRate(rows), currency) }}</QTd>
<QTd />
<QTd>
{{
getTotal(rows, null, { cb: taxRate, currency: 'default' })
}}</QTd
>
<QTd>
<template v-if="isNotEuro(invoiceIn.currency.code)">
{{
getTotal(rows, 'foreignValue', {
currency: invoiceIn.currency.code,
})
}}
</template>
</QTd>
</QTr>
</template>
<template #item="props">
@ -342,7 +299,7 @@ const formatOpt = (row, { model, options }, prop) => {
<QSeparator />
<QList>
<QItem>
<VnSelect
<VnSelectDialog
:label="t('Expense')"
class="full-width"
v-model="props.row['expenseFk']"
@ -350,13 +307,17 @@ const formatOpt = (row, { model, options }, prop) => {
option-value="id"
option-label="name"
:filter-options="['id', 'name']"
:tooltip="t('Create a new expense')"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
{{ `${scope.opt.id}: ${scope.opt.name}` }}
</QItem>
</template>
</VnSelect>
<template #form>
<CreateNewExpenseForm />
</template>
</VnSelectDialog>
</QItem>
<QItem>
<VnInputNumber
@ -439,44 +400,6 @@ const formatOpt = (row, { model, options }, prop) => {
</QTable>
</template>
</CrudModel>
<QDialog ref="newExpenseRef">
<QCard>
<QCardSection class="q-pb-none">
<QItem class="q-pa-none">
<span class="text-primary text-h6 full-width">
<QIcon name="edit" class="q-mr-xs" />
{{ t('New expense') }}
</span>
<QBtn icon="close" flat round dense v-close-popup />
</QItem>
</QCardSection>
<QCardSection class="q-pt-none">
<QItem>
<VnInput
:label="`${t('Code')}*`"
v-model="newExpense.code"
:required="true"
/>
<QCheckbox
dense
size="sm"
:label="`${t('It\'s a withholding')}`"
v-model="newExpense.isWithheld"
/>
</QItem>
<QItem>
<VnInput
:label="`${t('Descripction')}*`"
v-model="newExpense.description"
/>
</QItem>
</QCardSection>
<QCardActions class="justify-end">
<QBtn flat :label="t('globals.close')" color="primary" v-close-popup />
<QBtn :label="t('globals.save')" color="primary" @click="addExpense" />
</QCardActions>
</QCard>
</QDialog>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn
color="primary"
@ -484,7 +407,9 @@ const formatOpt = (row, { model, options }, prop) => {
size="lg"
round
@click="invoiceInFormRef.insert()"
/>
>
<QTooltip>{{ t('Add tax') }}</QTooltip>
</QBtn>
</QPageSticky>
</template>
@ -524,18 +449,11 @@ const formatOpt = (row, { model, options }, prop) => {
<i18n>
es:
Expense: Gasto
Create expense: Crear gasto
Create a new expense: Crear nuevo gasto
Add tax: Crear gasto
Taxable base: Base imp.
Sage tax: Sage iva
Sage transaction: Sage transacción
Rate: Tasa
Foreign value: Divisa
New expense: Nuevo gasto
Code: Código
It's a withholding: Es una retención
Descripction: Descripción
The code can't be empty: El código no puede estar vacío
The description can't be empty: La descripción no puede estar vacía
The code have to be a number: El código debe ser un número.
</i18n>

View File

@ -28,6 +28,16 @@ const activities = ref([]);
</div>
</template>
<template #body="{ params, searchFn }">
<QItem>
<QItemSection>
<VnInputDate :label="t('From')" v-model="params.from" is-outlined />
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputDate :label="t('To')" v-model="params.to" is-outlined />
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
@ -64,16 +74,6 @@ const activities = ref([]);
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
:label="t('params.serialNumber')"
v-model="params.serialNumber"
is-outlined
lazy-rules
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
@ -84,15 +84,6 @@ const activities = ref([]);
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputDate
:label="t('Issued')"
v-model="params.issued"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
@ -140,22 +131,6 @@ const activities = ref([]);
/>
</QItemSection>
</QItem>
<QExpansionItem :label="t('More options')" expand-separator>
<QItem>
<QItemSection>
<VnInputDate
:label="t('From')"
v-model="params.from"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputDate :label="t('To')" v-model="params.to" is-outlined />
</QItemSection>
</QItem>
</QExpansionItem>
</template>
</VnFilterPanel>
</template>
@ -179,6 +154,7 @@ en:
correctedFk: Rectified
issued: Issued
to: To
from: From
awbCode: AWB
correctingFk: Rectificative
supplierActivityFk: Supplier activity
@ -201,6 +177,8 @@ es:
correctedFk: Rectificada
correctingFk: Rectificativa
supplierActivityFk: Actividad proveedor
from: Desde
to: Hasta
From: Desde
To: Hasta
Amount: Importe

View File

@ -47,12 +47,6 @@ const cols = computed(() => [
name: 'supplierRef',
label: t('invoiceIn.list.supplierRef'),
},
{
align: 'left',
name: 'serialNumber',
label: t('invoiceIn.list.serialNumber'),
},
{
align: 'left',
name: 'serial',
@ -141,7 +135,7 @@ const cols = computed(() => [
v-model="data.supplierFk"
url="Suppliers"
:fields="['id', 'nickname']"
:label="t('Supplier')"
:label="t('globals.supplier')"
option-value="id"
option-label="nickname"
:filter-options="['id', 'name']"
@ -162,7 +156,7 @@ const cols = computed(() => [
/>
<VnSelect
url="Companies"
:label="t('Company')"
:label="t('globals.company')"
:fields="['id', 'code']"
v-model="data.companyFk"
option-value="id"

View File

@ -0,0 +1,14 @@
import axios from 'axios';
export async function setRectificative(route) {
const card = route.matched.find((route) => route.name === 'InvoiceInCard');
const corrective = card.children.find(
(route) => route.name === 'InvoiceInCorrective'
);
corrective.meta.hidden = !(
await axios.get('InvoiceInCorrections', {
params: { filter: { where: { correctingFk: route.params.id } } },
})
).data.length;
}

View File

@ -0,0 +1,49 @@
invoiceIn:
serial: Serial
list:
ref: Reference
supplier: Supplier
supplierRef: Supplier ref.
serial: Serial
file: File
issued: Issued
isBooked: Is booked
awb: AWB
amount: Amount
card:
issued: Issued
amount: Amount
client: Client
company: Company
customerCard: Customer card
ticketList: Ticket List
vat: Vat
dueDay: Due day
intrastat: Intrastat
summary:
supplier: Supplier
supplierRef: Supplier ref.
currency: Currency
issued: Expedition date
operated: Operation date
bookEntried: Entry date
bookedDate: Booked date
sage: Sage withholding
vat: Undeductible VAT
company: Company
booked: Booked
expense: Expense
taxableBase: Taxable base
rate: Rate
sageVat: Sage vat
sageTransaction: Sage transaction
dueDay: Date
bank: Bank
amount: Amount
foreignValue: Foreign value
dueTotal: Due day
noMatch: Do not match
code: Code
net: Net
stems: Stems
country: Country

View File

@ -0,0 +1,47 @@
invoiceIn:
serial: Serie
list:
ref: Referencia
supplier: Proveedor
supplierRef: Ref. proveedor
shortIssued: F. emisión
file: Fichero
issued: Fecha emisión
isBooked: Conciliada
awb: AWB
amount: Importe
card:
issued: Fecha emisión
amount: Importe
client: Cliente
company: Empresa
customerCard: Ficha del cliente
ticketList: Listado de tickets
vat: Iva
dueDay: Fecha de vencimiento
summary:
supplier: Proveedor
supplierRef: Ref. proveedor
currency: Divisa
docNumber: Número documento
issued: Fecha de expedición
operated: Fecha operación
bookEntried: Fecha asiento
bookedDate: Fecha contable
sage: Retención sage
vat: Iva no deducible
company: Empresa
booked: Contabilizada
expense: Gasto
taxableBase: Base imp.
rate: Tasa
sageTransaction: Sage transación
dueDay: Fecha
bank: Caja
amount: Importe
foreignValue: Divisa
dueTotal: Vencimiento
code: Código
net: Neto
stems: Tallos
country: País

View File

@ -10,8 +10,6 @@ import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.v
import VnConfirm from 'components/ui/VnConfirm.vue';
import RegularizeStockForm from 'components/RegularizeStockForm.vue';
import ItemDescriptorImage from 'src/pages/Item/Card/ItemDescriptorImage.vue';
import { useState } from 'src/composables/useState';
import useCardDescription from 'src/composables/useCardDescription';
import { getUrl } from 'src/composables/getUrl';
import axios from 'axios';
@ -35,58 +33,69 @@ const $props = defineProps({
type: Number,
default: null,
},
warehouseFk: {
type: Number,
default: null,
},
});
const quasar = useQuasar();
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
const state = useState();
const user = state.getUser();
const warehouseConfig = ref(null);
const entityId = computed(() => {
return $props.id || route.params.id;
});
const regularizeStockFormDialog = ref(null);
const available = ref(null);
const visible = ref(null);
const _warehouseFk = ref(null);
const salixUrl = ref();
const warehouseFk = computed({
get() {
return _warehouseFk.value;
},
set(val) {
_warehouseFk.value = val;
if (val) updateStock();
},
});
onMounted(async () => {
warehouseFk.value = user.value.warehouseFk;
salixUrl.value = await getUrl('');
await getItemConfigs();
await updateStock();
});
carlossa marked this conversation as resolved Outdated
Outdated
Review

?

?
const data = ref(useCardDescription());
const setData = (entity) => {
if (!entity) return;
data.value = useCardDescription(entity.name, entity.id);
const setData = async (entity) => {
try {
if (!entity) return;
data.value = useCardDescription(entity.name, entity.id);
await updateStock();
} catch (err) {
console.error('Error item');
}
};
const getItemConfigs = async () => {
try {
const { data } = await axios.get('ItemConfigs/findOne');
if (!data) return;
return (warehouseConfig.value = data.warehouseFk);
} catch (err) {
console.error('Error item');
}
};
const updateStock = async () => {
carlossa marked this conversation as resolved
Review

Deberia aparecer el almacen seleccionado al abrir el modal segun el valor del usaurio

Deberia aparecer el almacen seleccionado al abrir el modal segun el valor del usaurio
try {
available.value = null;
visible.value = null;
const params = {
warehouseFk: warehouseFk.value,
warehouseFk: $props.warehouseFk,
dated: $props.dated,
};
await getItemConfigs();
if (!params.warehouseFk) {
params.warehouseFk = warehouseConfig.value;
}
const { data } = await axios.get(`Items/${entityId.value}/getVisibleAvailable`, {
params,
});
available.value = data.available;
visible.value = data.visible;
} catch (err) {

View File

@ -47,8 +47,11 @@ const getWarehouseName = async (warehouseFk) => {
const filter = {
where: { id: warehouseFk },
};
const { data } = await axios.get('Warehouses/findOne', { filter });
const { data } = await axios.get('Warehouses/findOne', {
params: {
filter: JSON.stringify(filter),
},
});
if (!data) return;
warehouseName.value = data.name;
};

View File

@ -15,6 +15,10 @@ const $props = defineProps({
type: Number,
default: null,
},
warehouseFk: {
type: Number,
default: null,
},
});
</script>
@ -26,6 +30,7 @@ const $props = defineProps({
:summary="ItemSummary"
:dated="dated"
:sale-fk="saleFk"
:warehouse-fk="warehouseFk"
/>
</QPopupProxy>
</template>

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -9,18 +9,29 @@ import ItemsFilterPanel from 'src/components/ItemsFilterPanel.vue';
const { t } = useI18n();
defineProps({
const props = defineProps({
dataKey: {
type: String,
required: true,
},
warehousesOptions: {
type: Array,
default: () => [],
},
});
const itemTypeWorkersOptions = ref([]);
const exprBuilder = (param, value) => {
switch (param) {
case 'name':
return { 'i.name': { like: `%${value}%` } };
case 'itemFk':
case 'warehouseFk':
case 'rate2':
case 'rate3':
param = `fp.${param}`;
return { [param]: value };
case 'minPrice':
param = `i.${param}`;
return { [param]: value };
}
};
</script>
<template>
@ -31,7 +42,7 @@ const itemTypeWorkersOptions = ref([]);
:filter="{ fields: ['id', 'nickname'], order: 'nickname ASC', limit: 30 }"
@on-fetch="(data) => (itemTypeWorkersOptions = data)"
/>
<ItemsFilterPanel :data-key="dataKey" :custom-tags="['tags']">
<ItemsFilterPanel :data-key="props.dataKey" :custom-tags="['tags']">
<template #body="{ params, searchFn }">
<QItem class="q-my-md">
<QItemSection>
@ -52,9 +63,11 @@ const itemTypeWorkersOptions = ref([]);
<QItem class="q-my-md">
<QItemSection>
<VnSelect
url="Warehouses"
auto-load
:filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }"
:label="t('components.itemsFilterPanel.warehouseFk')"
v-model="params.warehouseFk"
:options="warehousesOptions"
option-label="name"
option-value="id"
dense
@ -93,8 +106,15 @@ const itemTypeWorkersOptions = ref([]);
toggle-indeterminate
@update:model-value="searchFn()"
/>
</QItemSection>
<QItemSection>
<QCheckbox
v-model="params.showBadDates"
:label="t(`components.itemsFilterPanel.showBadDates`)"
toggle-indeterminate
@update:model-value="searchFn()"
>
</QCheckbox>
<QCheckbox
:label="t('components.itemsFilterPanel.hasMinPrice')"
v-model="params.hasMinPrice"

View File

@ -23,7 +23,7 @@ function confirmRemove() {
.dialog({
component: VnConfirm,
componentProps: {
title: t('confirmDeletion'),
title: t('globals.confirmDeletion'),
message: t('confirmDeletionMessage'),
promise: remove,
},
@ -52,12 +52,10 @@ async function remove() {
<i18n>
en:
deleteOrder: Delete order
confirmDeletion: Confirm deletion
confirmDeletionMessage: Are you sure you want to delete this order?
es:
deleteOrder: Eliminar pedido
confirmDeletion: Confirmar eliminación
confirmDeletionMessage: Seguro que quieres eliminar este pedido?
</i18n>

View File

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

View File

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

View File

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

View File

@ -9,6 +9,8 @@ import SendEmailDialog from 'components/common/SendEmailDialog.vue';
import VnConfirm from 'components/ui/VnConfirm.vue';
import VnSmsDialog from 'components/common/VnSmsDialog.vue';
import toDate from 'filters/toDate';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import { useArrayData } from 'src/composables/useArrayData';
const props = defineProps({
ticket: {
@ -21,9 +23,10 @@ const { push, currentRoute } = useRouter();
const { dialog, notify } = useQuasar();
const { t } = useI18n();
const { openReport, sendEmail } = usePrintService();
const ticketSummary = useArrayData('TicketSummary');
const ticket = ref(props.ticket);
const ticketId = currentRoute.value.params.id;
const weight = ref();
const actions = {
clone: async () => {
const opts = { message: t('Ticket cloned'), type: 'positive' };
@ -46,6 +49,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`);
@ -255,6 +277,12 @@ function openConfirmDialog(callback) {
</QItemSection>
<QItemSection>{{ t('To clone ticket') }}</QItemSection>
</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>
@ -264,9 +292,25 @@ function openConfirmDialog(callback) {
<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>
<i18n>
en:
invoiceIds: "Invoices have been generated with the following ids: {invoiceIds}"
es:
Open Delivery Note...: Abrir albarán...
Send Delivery Note...: Enviar albarán...
@ -284,4 +328,8 @@ es:
To clone ticket: Clonar ticket
Ticket cloned: Ticked clonado
It was not able to clone the ticket: No se pudo clonar el ticket
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>

View File

@ -31,8 +31,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';
@ -40,12 +39,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 '';
@ -89,7 +82,6 @@ async function changeState(value) {
<CardSummary
ref="summaryRef"
:url="`Tickets/${entityId}/summary`"
@on-fetch="(data) => setData(data)"
data-key="TicketSummary"
>
<template #header="{ entity }">
@ -131,7 +123,7 @@ async function changeState(value) {
</QList>
</QBtnDropdown>
</template>
<template #body>
<template #body="{ entity }">
<QCard class="vn-one">
<VnTitle
:url="ticketUrl + 'basic-data/step-one'"
@ -139,57 +131,57 @@ async function changeState(value) {
/>
<VnLv :label="t('ticket.summary.state')">
<template #value>
<QChip :color="ticket.ticketState?.state?.classColor ?? 'dark'">
{{ ticket.ticketState?.state?.name }}
<QChip :color="entity.ticketState?.state?.classColor ?? 'dark'">
{{ entity.ticketState?.state?.name }}
</QChip>
</template>
</VnLv>
<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')" :value="ticket?.zone?.name" />
<VnLv :label="t('ticket.summary.zone')" :value="entity?.zone?.name" />
<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>
<VnLv :label="t('ticket.summary.route')" :value="ticket.routeFk" />
<VnLv :label="t('ticket.summary.route')" :value="entity.routeFk" />
<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">
@ -199,35 +191,35 @@ async function changeState(value) {
/>
<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
@ -235,13 +227,13 @@ async function changeState(value) {
:value="formattedAddress()"
/>
</QCard>
<QCard class="vn-one" v-if="ticket.notes.length">
<QCard class="vn-one" v-if="entity.notes.length">
<VnTitle
:url="ticketUrl + 'observation'"
:text="t('ticket.pageTitles.notes')"
/>
<VnLv
v-for="note in ticket.notes"
v-for="note in entity.notes"
:key="note.id"
:label="note.observationType.description"
:value="note.description"
@ -262,15 +254,15 @@ async function changeState(value) {
<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')"
:value="toCurrency(ticket.totalWithVat)"
:value="toCurrency(entity.totalWithVat)"
/>
</div>
</QCard>
@ -279,7 +271,7 @@ async function changeState(value) {
:url="ticketUrl + '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>
@ -383,7 +375,11 @@ async function changeState(value) {
<QTd>
<QBtn class="link" flat>
{{ props.row.itemFk }}
<ItemDescriptorProxy :id="props.row.itemFk" />
<ItemDescriptorProxy
:id="props.row.itemFk"
:sale-fk="props.row.id"
:warehouse-fk="ticket.warehouseFk"
/>
</QBtn>
</QTd>
<QTd>{{ props.row.visible }}</QTd>
@ -419,10 +415,10 @@ async function changeState(value) {
</QCard>
<QCard
class="vn-max"
v-if="ticket.packagings.length > 0 || ticket.services.length > 0"
v-if="entity.packagings.length || entity.services.length"
>
<VnTitle :url="ticketUrl + 'package'" :text="t('globals.packages')" />
<QTable :rows="ticket.packagings" flat>
<QTable :rows="entity.packagings" flat>
<template #header="props">
<QTr :props="props">
<QTh auto-width>{{ t('ticket.summary.created') }}</QTh>
@ -442,7 +438,7 @@ async function changeState(value) {
:url="ticketUrl + 'service'"
:text="t('ticket.summary.service')"
/>
<QTable :rows="ticket.services" flat>
<QTable :rows="entity.services" flat>
<template #header="props">
<QTr :props="props">
<QTh auto-width>{{ t('ticket.summary.quantity') }}</QTh>

View File

@ -133,6 +133,14 @@ const ticketColumns = computed(() => [
sortable: true,
columnFilter: null,
},
{
label: t('advanceTickets.preparation'),
name: 'preparation',
field: 'preparation',
align: 'left',
sortable: true,
columnFilter: null,
},
{
label: t('advanceTickets.liters'),
name: 'liters',
@ -624,6 +632,7 @@ onMounted(async () => {
</QIcon>
</QTd>
</template>
<template #body-cell-ticketId="{ row }">
<QTd>
<QBtn flat class="link">
@ -635,6 +644,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 +652,7 @@ onMounted(async () => {
>
{{ row.state }}
</QBadge>
<span v-else> {{ dashIfEmpty(row.state) }}</span>
</QTd>
</template>
<template #body-cell-import="{ row }">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,5 +15,6 @@ import WorkerFilter from '../WorkerFilter.vue';
label: 'Search worker',
info: 'You can search by worker id or name',
}"
:redirect-on-error="true"
/>
</template>

View File

@ -1,12 +1,11 @@
<script setup>
import { computed, ref, watch } from 'vue';
import { computed, ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CardDescriptor from 'src/components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue';
import WorkerChangePasswordForm from 'src/pages/Worker/Card/WorkerChangePasswordForm.vue';
import useCardDescription from 'src/composables/useCardDescription';
import { useState } from 'src/composables/useState';
import axios from 'axios';
import VnImg from 'src/components/ui/VnImg.vue';
@ -38,49 +37,6 @@ const entityId = computed(() => {
const worker = ref();
const workerExcluded = ref(false);
const filter = {
include: [
{
relation: 'user',
scope: {
fields: ['email', 'name', 'nickname'],
},
},
{
relation: 'department',
scope: {
include: [
{
relation: 'department',
},
],
},
},
{
relation: 'sip',
},
],
};
const sip = ref(null);
watch(
() => [worker.value?.sip?.extension, state.get('extension')],
([newWorkerSip, newStateExtension], [oldWorkerSip, oldStateExtension]) => {
if (newStateExtension !== oldStateExtension || sip.value === oldStateExtension) {
sip.value = newStateExtension;
} else if (newWorkerSip !== oldWorkerSip && sip.value !== newStateExtension) {
sip.value = newWorkerSip;
}
}
);
const data = ref(useCardDescription());
const setData = (entity) => {
if (!entity) return;
data.value = useCardDescription(entity.user?.nickname, entity.id);
};
const openChangePasswordForm = () => changePasswordFormDialog.value.show();
const getIsExcluded = async () => {
try {
@ -115,14 +71,12 @@ const refetch = async () => await cardDescriptorRef.value.getData();
ref="cardDescriptorRef"
module="Worker"
data-key="workerData"
:url="`Workers/${entityId}`"
:filter="filter"
:title="data.title"
:subtitle="data.subtitle"
url="Workers/descriptor"
:filter="{ where: { id: entityId } }"
title="user.nickname"
@on-fetch="
(data) => {
worker = data;
setData(data);
getIsExcluded();
}
"
@ -141,7 +95,7 @@ const refetch = async () => await cardDescriptorRef.value.getData();
v-if="!worker.user.emailVerified && user.id != worker.id"
v-ripple
clickable
@click="openChangePasswordForm()"
@click="$refs.changePasswordFormDialog.show()"
>
<QItemSection>
{{ t('Change password') }}
@ -209,10 +163,10 @@ const refetch = async () => await cardDescriptorRef.value.getData();
<VnLinkPhone :phone-number="entity.phone" />
</template>
</VnLv>
<VnLv :value="sip">
<VnLv :value="worker?.sip?.extension">
<template #label>
{{ t('worker.summary.sipExtension') }}
<VnLinkPhone v-if="sip" :phone-number="sip" />
<VnLinkPhone :phone-number="worker?.sip?.extension" />
</template>
</VnLv>
</template>

View File

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

View File

@ -34,13 +34,22 @@ const filter = {
{
relation: 'user',
scope: {
fields: ['email', 'name', 'nickname', 'roleFk'],
include: {
relation: 'role',
scope: {
fields: ['name'],
fields: ['name', 'nickname', 'roleFk'],
include: [
{
relation: 'role',
scope: {
fields: ['name'],
},
},
},
{
relation: 'emailUser',
scope: {
fields: ['email'],
},
},
],
},
},
{
@ -153,14 +162,18 @@ const filter = {
</QCard>
<QCard class="vn-one">
<VnTitle :text="t('worker.summary.userData')" />
<VnLv :label="t('worker.summary.userId')" :value="worker.user.id" />
<VnLv :label="t('worker.card.name')" :value="worker.user.nickname" />
<VnLv :label="t('worker.list.email')" :value="worker.user.email" copy />
<VnLv :label="t('worker.summary.userId')" :value="worker?.user?.id" />
<VnLv :label="t('worker.card.name')" :value="worker?.user?.nickname" />
<VnLv
:label="t('worker.list.email')"
:value="worker.user?.emailUser?.email"
copy
/>
<VnLv :label="t('worker.summary.role')">
<template #value>
<span class="link">
{{ worker.user.role.name }}
<RoleDescriptorProxy :id="worker.user.role.id" />
{{ worker?.user?.role?.name }}
<RoleDescriptorProxy :id="worker?.user?.role?.id" />
</span>
</template>
</VnLv>

View File

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

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@ export default {
title: 'suppliers',
icon: 'vn:supplier',
moduleName: 'Supplier',
keyBinding: 'p',
},
component: RouterView,
redirect: { name: 'SupplierMain' },

View File

@ -7,6 +7,7 @@ export default {
title: 'users',
icon: 'face',
moduleName: 'Account',
keyBinding: 'u',
},
component: RouterView,
redirect: { name: 'AccountMain' },
@ -79,7 +80,7 @@ export default {
meta: {
title: 'accounts',
icon: 'accessibility',
roles: ['itManagement'],
acls: [{ model: 'Account', props: '*', accessType: '*' }],
},
component: () => import('src/pages/Account/AccountAccounts.vue'),
},
@ -89,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'),
},
@ -99,7 +100,7 @@ export default {
meta: {
title: 'samba',
icon: 'preview',
roles: ['itManagement'],
acls: [{ model: 'SambaConfig', props: '*', accessType: '*' }],
},
component: () => import('src/pages/Account/AccountSamba.vue'),
},

View File

@ -7,6 +7,7 @@ export default {
title: 'claims',
icon: 'vn:claims',
moduleName: 'Claim',
keyBinding: 'r',
},
component: RouterView,
redirect: { name: 'ClaimMain' },
@ -61,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'),
},
@ -98,7 +99,13 @@ export default {
meta: {
title: 'development',
icon: 'vn:traceability',
roles: ['claimManager'],
acls: [
{
model: 'ClaimDevelopment',
props: '*',
accessType: 'WRITE',
},
],
},
component: () => import('src/pages/Claim/Card/ClaimDevelopment.vue'),
},

View File

@ -7,6 +7,7 @@ export default {
title: 'customers',
icon: 'vn:client',
moduleName: 'Customer',
keyBinding: 'c',
},
component: RouterView,
redirect: { name: 'CustomerMain' },

View File

@ -7,6 +7,7 @@ export default {
title: 'entries',
icon: 'vn:entry',
moduleName: 'Entry',
keyBinding: 'e',
},
component: RouterView,
redirect: { name: 'EntryMain' },

View File

@ -1,5 +1,5 @@
import { RouterView } from 'vue-router';
import { setRectificative } from 'src/pages/InvoiceIn/composables/setRectificative';
export default {
path: '/invoice-in',
name: 'InvoiceIn',
@ -63,6 +63,10 @@ export default {
path: ':id',
component: () => import('src/pages/InvoiceIn/Card/InvoiceInCard.vue'),
redirect: { name: 'InvoiceInSummary' },
beforeEnter: async (to, from, next) => {
await setRectificative(to);
next();
},
children: [
{
name: 'InvoiceInSummary',
@ -80,7 +84,6 @@ export default {
meta: {
title: 'basicData',
icon: 'vn:settings',
roles: ['salesPerson'],
},
component: () =>
import('src/pages/InvoiceIn/Card/InvoiceInBasicData.vue'),

View File

@ -7,6 +7,7 @@ export default {
title: 'items',
icon: 'vn:item',
moduleName: 'Item',
keyBinding: 'a',
},
component: RouterView,
redirect: { name: 'ItemMain' },

View File

@ -7,6 +7,7 @@ export default {
title: 'monitors',
icon: 'grid_view',
moduleName: 'Monitor',
keyBinding: 'm',
},
component: RouterView,
redirect: { name: 'MonitorMain' },

View File

@ -7,6 +7,7 @@ export default {
title: 'order',
icon: 'vn:basket',
moduleName: 'Order',
keyBinding: 'o',
},
component: RouterView,
redirect: { name: 'OrderMain' },

View File

@ -7,7 +7,6 @@ export default {
title: 'routes',
icon: 'vn:delivery',
moduleName: 'Route',
keyBinding: 'r',
},
component: RouterView,
redirect: { name: 'RouteMain' },

View File

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

View File

@ -7,6 +7,7 @@ export default {
title: 'tickets',
icon: 'vn:ticket',
moduleName: 'Ticket',
keyBinding: 't',
},
component: RouterView,
redirect: { name: 'TicketMain' },
@ -53,7 +54,6 @@ export default {
meta: {
title: 'createTicket',
icon: 'vn:ticketAdd',
roles: ['developer'],
},
component: () => import('src/pages/Ticket/TicketCreate.vue'),
},

View File

@ -7,6 +7,7 @@ export default {
title: 'workers',
icon: 'vn:worker',
moduleName: 'Worker',
keyBinding: 'w',
},
component: RouterView,
redirect: { name: 'WorkerMain' },

View File

@ -7,6 +7,7 @@ export default {
title: 'zones',
icon: 'vn:zone',
moduleName: 'Zone',
keyBinding: 'z',
},
component: RouterView,
redirect: { name: 'ZoneMain' },

View File

@ -2,7 +2,7 @@ import axios from 'axios';
import { ref } from 'vue';
import { defineStore } from 'pinia';
import { toLowerCamel } from 'src/filters';
import { useRole } from 'src/composables/useRole';
import { useAcl } from 'src/composables/useAcl';
import routes from 'src/router/modules';
export const useNavigationStore = defineStore('navigationStore', () => {
@ -26,7 +26,7 @@ export const useNavigationStore = defineStore('navigationStore', () => {
'zone',
];
const pinnedModules = ref([]);
const role = useRole();
const acl = useAcl();
function getModules() {
const modulesRoutes = ref([]);
@ -56,6 +56,7 @@ export const useNavigationStore = defineStore('navigationStore', () => {
function addMenuItem(module, route, parent) {
const { meta } = route;
let { menuChildren = null } = meta;
if (meta.hidden) return;
if (menuChildren)
menuChildren = menuChildren.map(({ name, title, icon }) => ({
name,
@ -63,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,
@ -72,6 +73,7 @@ export const useNavigationStore = defineStore('navigationStore', () => {
if (meta) {
item.title = `globals.pageTitles.${meta.title}`;
item.icon = meta.icon;
item.keyBinding = meta.keyBinding;
}
parent.push(item);

View File

@ -36,8 +36,7 @@ describe('InvoiceInBasicData', () => {
});
it('should throw an error creating a new dms if a file is not attached', () => {
cy.get(formInputs).eq(5).click();
cy.get(formInputs).eq(5).type('{selectall}{backspace}');
cy.get(formInputs).eq(7).type('{selectall}{backspace}');
cy.get(documentBtns).eq(0).click();
cy.get(dialogActionBtns).eq(1).click();
cy.get('.q-notification__message').should(

View File

@ -3,13 +3,14 @@ describe('InvoiceInVat', () => {
const thirdRow = 'tbody > :nth-child(3)';
const firstLineVat = 'tbody > :nth-child(1) > :nth-child(4)';
const dialogInputs = '.q-dialog label input';
const dialogBtns = '.q-dialog button';
const acrossInput = 'tbody tr:nth-child(1) td:nth-child(2) .default-icon';
const addBtn = 'tbody tr:nth-child(1) td:nth-child(2) .--add-icon';
const randomInt = Math.floor(Math.random() * 100);
beforeEach(() => {
cy.login('developer');
cy.visit(`/#/invoice-in/1/vat`);
cy.intercept('GET', '/api/InvoiceIns/1/getTotals').as('lastCall');
cy.wait('@lastCall');
});
it('should edit the sage iva', () => {
@ -26,22 +27,15 @@ describe('InvoiceInVat', () => {
});
it('should remove the first line', () => {
cy.removeRow(2);
});
it('should throw an error if there are fields undefined', () => {
cy.get(acrossInput).click();
cy.get(dialogBtns).eq(2).click();
cy.get('.q-notification__message').should('have.text', "The code can't be empty");
cy.removeRow(1);
});
it('should correctly handle expense addition', () => {
cy.get(acrossInput).click();
cy.get(addBtn).click();
cy.get(dialogInputs).eq(0).type(randomInt);
cy.get(dialogInputs).eq(1).click();
cy.get(dialogInputs).eq(1).type('This is a dummy expense');
cy.get(dialogBtns).eq(2).click();
cy.get('.q-notification__message').should('have.text', 'Data saved');
cy.get('button[type="submit"]').click();
cy.get('.q-notification__message').should('have.text', 'Data created');
});
});

View File

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

View File

@ -1,18 +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.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();
@ -24,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);
});
});

View File

@ -0,0 +1,33 @@
/// <reference types="cypress" />
describe('VnShortcuts', () => {
const modules = {
item: 'a',
customer: 'c',
ticket: 't',
claim: 'r',
worker: 'w',
monitor: 'm',
order: 'o',
supplier: 'p',
entry: 'e',
zone: 'z',
account: 'u',
};
beforeEach(() => {
cy.login('developer');
cy.visit('/');
});
for (const [module, shortcut] of Object.entries(modules)) {
it(`should visit ${module} module`, () => {
cy.get('body').trigger('keydown', {
ctrlKey: true,
altKey: true,
code: `Key${shortcut.toUpperCase()}`,
});
cy.url().should('include', module);
});
}
});

View File

@ -3,6 +3,7 @@ describe('WagonTypeCreate', () => {
cy.viewport(1920, 1080);
cy.login('developer');
cy.visit('/#/wagon/type/create');
cy.waitForElement('.q-page', 6000);
});
function chooseColor(color) {

View File

@ -1,12 +1,12 @@
describe('WorkerLocker', () => {
const workerId = 1109;
const productionId = 49;
const lockerCode = '2F';
const input = '.q-card input';
const thirdOpt = '[role="listbox"] .q-item:nth-child(1)';
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('productionBoss');
cy.visit(`/#/worker/${workerId}/locker`);
cy.visit(`/#/worker/${productionId}/locker`);
});
it('should allocates a locker', () => {

View File

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