0
0
Fork 0

Merge branch 'dev' into 7172-fixClaimBasicData

This commit is contained in:
Carlos Satorres 2024-05-02 08:21:43 +00:00
commit 73ade834e9
30 changed files with 1024 additions and 113 deletions

View File

@ -79,6 +79,7 @@ watch(
const isLoading = ref(false);
async function search() {
store.filter.where = {};
isLoading.value = true;
const params = { ...userParams.value };
store.userParamsChanged = true;

View File

@ -95,6 +95,8 @@ const addFilter = async (filter, params) => {
};
async function fetch() {
store.filter.skip = 0;
store.skip = 0;
await arrayData.fetch({ append: false });
if (!store.hasMoreData) {
isLoading.value = false;

View File

@ -81,8 +81,10 @@ async function search() {
const staticParams = Object.entries(store.userParams).filter(
([key, value]) => value && (props.staticParams || []).includes(key)
);
// const filter =props?.where? { where: JSON.parse(props.where) }: {}
await arrayData.applyFilter({
params: {
// filter ,
...Object.fromEntries(staticParams),
search: searchText.value,
},
@ -106,6 +108,7 @@ async function search() {
let targetUrl;
if (path.endsWith('/list')) targetUrl = path.replace('/list', `/${targetId}/summary`);
if (path.endsWith('-list')) targetUrl = path.replace('-list', `/${targetId}/summary`);
else if (path.includes(':id')) targetUrl = path.replace(':id', targetId);
await router.push({ path: targetUrl });

View File

@ -1,6 +1,5 @@
<script setup>
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
import { useI18n } from 'vue-i18n';
const $props = defineProps({

View File

@ -3,11 +3,14 @@ import { useRole } from './useRole';
import { useUserConfig } from './useUserConfig';
import axios from 'axios';
import useNotify from './useNotify';
import { useTokenConfig } from './useTokenConfig';
const TOKEN_MULTIMEDIA = 'tokenMultimedia';
const TOKEN = 'token';
export function useSession() {
const { notify } = useNotify();
let isCheckingToken = false;
let intervalId = null;
function getToken() {
const localToken = localStorage.getItem(TOKEN);
@ -22,10 +25,24 @@ export function useSession() {
return localTokenMultimedia || sessionTokenMultimedia || '';
}
function setToken(data) {
const storage = data.keepLogin ? localStorage : sessionStorage;
function setSession(data) {
let keepLogin = data.keepLogin;
const storage = keepLogin ? localStorage : sessionStorage;
storage.setItem(TOKEN, data.token);
storage.setItem(TOKEN_MULTIMEDIA, data.tokenMultimedia);
storage.setItem('created', data.created);
storage.setItem('ttl', data.ttl);
sessionStorage.setItem('keepLogin', keepLogin);
}
function keepLogin() {
return sessionStorage.getItem('keepLogin');
}
function setToken({ token, tokenMultimedia }) {
const storage = keepLogin() ? localStorage : sessionStorage;
storage.setItem(TOKEN, token);
storage.setItem(TOKEN_MULTIMEDIA, tokenMultimedia);
}
async function destroyToken(url, storage, key) {
if (storage.getItem(key)) {
@ -45,11 +62,15 @@ export function useSession() {
tokenMultimedia: 'Accounts/logout',
token: 'VnUsers/logout',
};
const storage = keepLogin() ? localStorage : sessionStorage;
for (const [key, url] of Object.entries(tokens)) {
await destroyToken(url, localStorage, key);
await destroyToken(url, sessionStorage, key);
await destroyToken(url, storage, key);
}
localStorage.clear();
sessionStorage.clear();
const { setUser } = useState();
setUser({
@ -59,22 +80,75 @@ export function useSession() {
lang: '',
darkMode: null,
});
stopRenewer();
}
async function login(token, tokenMultimedia, keepLogin) {
setToken({ token, tokenMultimedia, keepLogin });
async function login(data) {
setSession(data);
await useRole().fetch();
await useUserConfig().fetch();
await useTokenConfig().fetch();
startInterval();
}
function isLoggedIn() {
const localToken = localStorage.getItem(TOKEN);
const sessionToken = sessionStorage.getItem(TOKEN);
startInterval();
return !!(localToken || sessionToken);
}
function startInterval() {
stopRenewer();
const renewPeriod = +sessionStorage.getItem('renewPeriod');
if (!renewPeriod) return;
intervalId = setInterval(() => checkValidity(), renewPeriod * 1000);
}
function stopRenewer() {
clearInterval(intervalId);
}
async function renewToken() {
const _token = getToken();
const token = await axios.post('VnUsers/renewToken', {
headers: { Authorization: _token },
});
const _tokenMultimedia = getTokenMultimedia();
const tokenMultimedia = await axios.post('VnUsers/renewToken', {
headers: { Authorization: _tokenMultimedia },
});
setToken({ token: token.data.id, tokenMultimedia: tokenMultimedia.data.id });
}
async function checkValidity() {
const { getTokenConfig } = useState();
const tokenConfig = getTokenConfig() ?? sessionStorage.getItem('tokenConfig');
const storage = keepLogin() ? localStorage : sessionStorage;
const created = +storage.getItem('created');
const ttl = +storage.getItem('ttl');
if (isCheckingToken || !created) return;
isCheckingToken = true;
const renewPeriodInSeconds = Math.min(ttl, tokenConfig.value.renewPeriod) * 1000;
const maxDate = created + renewPeriodInSeconds;
const now = new Date().getTime();
if (isNaN(renewPeriodInSeconds) || now <= maxDate) {
return (isCheckingToken = false);
}
await renewToken();
isCheckingToken = false;
}
return {
getToken,
getTokenMultimedia,
@ -82,5 +156,8 @@ export function useSession() {
destroy,
login,
isLoggedIn,
checkValidity,
setSession,
renewToken,
};
}

View File

@ -13,6 +13,7 @@ const user = ref({
});
const roles = ref([]);
const tokenConfig = ref({});
const drawer = ref(true);
const headerMounted = ref(false);
@ -52,6 +53,15 @@ export function useState() {
function setRoles(data) {
roles.value = data;
}
function getTokenConfig() {
return computed(() => {
return tokenConfig.value;
});
}
function setTokenConfig(data) {
tokenConfig.value = data;
}
function set(name, data) {
state.value[name] = ref(data);
@ -70,6 +80,8 @@ export function useState() {
setUser,
getRoles,
setRoles,
getTokenConfig,
setTokenConfig,
set,
get,
unset,

View File

@ -0,0 +1,28 @@
import axios from 'axios';
import { useState } from './useState';
import useNotify from './useNotify';
export function useTokenConfig() {
const state = useState();
const { notify } = useNotify();
async function fetch() {
try {
const { data } = await axios.get('AccessTokenConfigs/findOne', {
filter: { fields: ['renewInterval', 'renewPeriod'] },
});
if (!data) return;
state.setTokenConfig(data);
sessionStorage.setItem('renewPeriod', data.renewPeriod);
return data;
} catch (error) {
notify('errors.tokenConfig', 'negative');
console.error('Error fetching token config:', error);
}
}
return {
fetch,
state,
};
}

View File

@ -106,6 +106,7 @@ errors:
statusBadGateway: It seems that the server has fall down
statusGatewayTimeout: Could not contact the server
userConfig: Error fetching user config
tokenConfig: Error fetching token config
writeRequest: The requested operation could not be completed
login:
title: Login
@ -1135,6 +1136,8 @@ item:
tax: Tax
barcode: Barcode
botanical: Botanical
itemTypeCreate: New item type
family: Item Type
descriptor:
item: Item
buyer: Buyer
@ -1219,6 +1222,11 @@ item:
minSalesQuantity: 'Cantidad mínima de venta'
genus: 'Genus'
specie: 'Specie'
item/itemType:
pageTitles:
itemType: Item type
basicData: Basic data
summary: Summary
components:
topbar: {}
itemsFilterPanel:

View File

@ -106,6 +106,7 @@ errors:
statusBadGateway: Parece ser que el servidor ha caído
statusGatewayTimeout: No se ha podido contactar con el servidor
userConfig: Error al obtener configuración de usuario
tokenConfig: Error al obtener configuración de token
writeRequest: No se pudo completar la operación solicitada
login:
title: Inicio de sesión
@ -1134,6 +1135,8 @@ item:
botanical: 'Botánico'
barcode: 'Código de barras'
log: Historial
itemTypeCreate: Nueva familia
family: Familia
descriptor:
item: Artículo
buyer: Comprador
@ -1218,6 +1221,11 @@ item:
achieved: 'Conseguido'
concept: 'Concepto'
state: 'Estado'
item/itemType:
pageTitles:
itemType: Familia
basicData: Datos básicos
summary: Resumen
components:
topbar: {}
itemsFilterPanel:

View File

@ -37,6 +37,7 @@ const marker_labels = [
{ value: DEFAULT_MAX_RESPONSABILITY, label: t('claim.summary.person') },
];
const multiplicatorValue = ref();
const loading = ref(false);
const columns = computed(() => [
{
@ -118,7 +119,7 @@ async function updateDestinations(claimDestinationFk) {
async function updateDestination(claimDestinationFk, row, options = {}) {
if (claimDestinationFk) {
await axios.post('Claims/updateClaimDestination', {
await post('Claims/updateClaimDestination', {
claimDestinationFk,
rows: Array.isArray(row) ? row : [row],
});
@ -127,7 +128,7 @@ async function updateDestination(claimDestinationFk, row, options = {}) {
}
async function regularizeClaim() {
await axios.post(`Claims/${claimId}/regularizeClaim`);
await post(`Claims/${claimId}/regularizeClaim`);
await claimRef.value.fetch();
await arrayData.fetch({ append: false });
quasar.notify({
@ -146,7 +147,7 @@ async function onUpdateGreugeAccept() {
const freightPickUpPrice =
(await axios.get(`GreugeConfigs/findOne`)).data.freightPickUpPrice *
multiplicatorValue.value;
await axios.post(`Greuges`, {
await post(`Greuges`, {
clientFk: claim.value.clientFk,
description: `${t('ClaimGreugeDescription')} ${claimId}`.toUpperCase(),
amount: freightPickUpPrice,
@ -165,14 +166,22 @@ async function save(data) {
}
async function importToNewRefundTicket() {
const query = `ClaimBeginnings/${claimId}/importToNewRefundTicket`;
await axios.post(query);
claimActionsForm.value.reload();
await post(`ClaimBeginnings/${claimId}/importToNewRefundTicket`);
await claimActionsForm.value.reload();
quasar.notify({
message: t('globals.dataSaved'),
type: 'positive',
});
}
async function post(query, params) {
loading.value = true;
try {
await axios.post(query, params);
} finally {
loading.value = false;
}
}
</script>
<template>
<FetchData
@ -279,6 +288,7 @@ async function importToNewRefundTicket() {
:default-save="false"
:default-reset="false"
@on-fetch="setData"
:limit="0"
auto-load
>
<template #body>
@ -390,6 +400,7 @@ async function importToNewRefundTicket() {
icon="check"
@click="regularizeClaim"
:disable="claim.claimStateFk == resolvedStateId"
:loading="loading"
/>
<QBtn
@ -401,6 +412,7 @@ async function importToNewRefundTicket() {
:title="t('Change destination')"
icon="swap_horiz"
@click="dialogDestination = !dialogDestination"
:loading="loading"
/>
<QBtn
color="primary"
@ -411,6 +423,7 @@ async function importToNewRefundTicket() {
icon="Upload"
@click="importToNewRefundTicket"
:disable="claim.claimStateFk == resolvedStateId"
:loading="loading"
/>
</template>
</CrudModel>

View File

@ -0,0 +1,91 @@
<script setup>
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import FetchData from 'components/FetchData.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
const { t } = useI18n();
const router = useRouter();
const newItemTypeForm = reactive({});
const workersOptions = ref([]);
const categoriesOptions = ref([]);
const temperaturesOptions = ref([]);
const redirectToItemTypeBasicData = (_, { id }) => {
router.push({ name: 'ItemTypeBasicData', params: { id } });
};
</script>
<template>
<FetchData
url="Workers"
@on-fetch="(data) => (workersOptions = data)"
:filter="{ order: 'firstName ASC', fields: ['id', 'firstName'] }"
auto-load
/>
<FetchData
url="ItemCategories"
@on-fetch="(data) => (categoriesOptions = data)"
:filter="{ order: 'name ASC', fields: ['id', 'name'] }"
auto-load
/>
<FetchData
url="Temperatures"
@on-fetch="(data) => (temperaturesOptions = data)"
:filter="{ order: 'name ASC', fields: ['code', 'name'] }"
auto-load
/>
<QPage>
<VnSubToolbar />
<FormModel
url-create="ItemTypes"
model="itemTypeCreate"
:form-initial-data="newItemTypeForm"
observe-form-changes
@on-data-saved="redirectToItemTypeBasicData"
>
<template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<VnInput v-model="data.code" :label="t('itemType.shared.code')" />
<VnInput v-model="data.name" :label="t('itemType.shared.name')" />
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnSelect
v-model="data.workerFk"
:label="t('itemType.shared.worker')"
:options="workersOptions"
option-value="id"
option-label="firstName"
hide-selected
/>
<VnSelect
v-model="data.categoryFk"
:label="t('itemType.shared.category')"
:options="categoriesOptions"
option-value="id"
option-label="name"
hide-selected
/>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnSelect
v-model="data.temperatureFk"
:label="t('itemType.shared.temperature')"
:options="temperaturesOptions"
option-value="code"
option-label="name"
hide-selected
/>
</VnRow>
</template>
</FormModel>
</QPage>
</template>

View File

@ -0,0 +1,142 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import CardList from 'src/components/ui/CardList.vue';
import ItemTypeSummary from 'src/pages/ItemType/Card/ItemTypeSummary.vue';
import ItemTypeFilter from 'src/pages/ItemType/ItemTypeFilter.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useStateStore } from 'stores/useStateStore';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
const stateStore = useStateStore();
const router = useRouter();
const { t } = useI18n();
const { viewSummary } = useSummaryDialog();
const redirectToItemTypeSummary = (id) => {
router.push({ name: 'ItemTypeSummary', params: { id } });
};
const redirectToCreateView = () => {
router.push({ name: 'ItemTypeCreate' });
};
const exprBuilder = (param, value) => {
switch (param) {
case 'name':
return {
name: { like: `%${value}%` },
};
case 'code':
return {
code: { like: `%${value}%` },
};
case 'search':
if (value) {
if (!isNaN(value)) {
return { id: value };
} else {
return {
or: [
{
name: {
like: `%${value}%`,
},
},
{
code: {
like: `%${value}%`,
},
},
],
};
}
}
}
};
</script>
<template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<VnSearchbar
data-key="ItemTypeList"
url="ItemTypes"
:label="t('Search item type')"
:info="t('Search itemType by id, name or code')"
:expr-builder="exprBuilder"
/>
</Teleport>
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
</template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<ItemTypeFilter data-key="ItemTypeList" />
</QScrollArea>
</QDrawer>
<QPage class="column items-center q-pa-md">
<div class="vn-card-list">
<VnPaginate
data-key="ItemTypeList"
url="ItemTypes"
:order="['name']"
auto-load
:expr-builder="exprBuilder"
>
<template #body="{ rows }">
<CardList
v-for="row of rows"
:key="row.id"
:title="row.code"
@click="redirectToItemTypeSummary(row.id)"
:id="row.id"
>
<template #list-items>
<VnLv :label="t('Name')" :value="row.name" />
</template>
<template #actions>
<QBtn
:label="t('components.smartCard.openSummary')"
@click.stop="viewSummary(row.id, ItemTypeSummary)"
color="primary"
type="submit"
/>
</template>
</CardList>
</template>
</VnPaginate>
</div>
</QPage>
<QPageSticky :offset="[20, 20]">
<QBtn fab icon="add" color="primary" @click="redirectToCreateView()" />
<QTooltip>
{{ t('New item type') }}
</QTooltip>
</QPageSticky>
</template>
<i18n>
es:
New item type: Nueva familia
Name: Nombre
Search item type: Buscar familia
Search itemType by id, name or code: Buscar familia por id, nombre o código
</i18n>

View File

@ -0,0 +1,79 @@
<script setup>
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
const route = useRoute();
const { t } = useI18n();
const workersOptions = ref([]);
const categoriesOptions = ref([]);
const temperaturesOptions = ref([]);
</script>
<template>
<FetchData
url="Workers"
@on-fetch="(data) => (workersOptions = data)"
:filter="{ order: 'firstName ASC', fields: ['id', 'firstName'] }"
auto-load
/>
<FetchData
url="ItemCategories"
@on-fetch="(data) => (categoriesOptions = data)"
:filter="{ order: 'name ASC', fields: ['id', 'name'] }"
auto-load
/>
<FetchData
url="Temperatures"
@on-fetch="(data) => (temperaturesOptions = data)"
:filter="{ order: 'name ASC', fields: ['code', 'name'] }"
auto-load
/>
<FormModel
:url="`ItemTypes/${route.params.id}`"
:url-update="`ItemTypes/${route.params.id}`"
model="itemTypeBasicData"
auto-load
>
<template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<VnInput v-model="data.code" :label="t('shared.code')" />
<VnInput v-model="data.name" :label="t('shared.name')" />
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnSelect
v-model="data.workerFk"
:label="t('shared.worker')"
:options="workersOptions"
option-value="id"
option-label="firstName"
hide-selected
/>
<VnSelect
v-model="data.categoryFk"
:label="t('shared.category')"
:options="categoriesOptions"
option-value="id"
option-label="name"
hide-selected
/>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnSelect
v-model="data.temperatureFk"
:label="t('shared.temperature')"
:options="temperaturesOptions"
option-value="code"
option-label="name"
hide-selected
/>
</VnRow>
</template>
</FormModel>
</template>

View File

@ -0,0 +1,12 @@
<script setup>
import VnCard from 'components/common/VnCard.vue';
import ItemTypeDescriptor from 'src/pages/ItemType/Card/ItemTypeDescriptor.vue';
</script>
<template>
<VnCard
data-key="ItemTypeSummary"
base-url="ItemTypes"
:descriptor="ItemTypeDescriptor"
/>
</template>

View File

@ -0,0 +1,85 @@
<script setup>
import { ref, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import useCardDescription from 'src/composables/useCardDescription';
const $props = defineProps({
id: {
type: Number,
required: false,
default: null,
},
summary: {
type: Object,
default: null,
},
});
const route = useRoute();
const { t } = useI18n();
const itemTypeFilter = {
include: [
{ relation: 'worker' },
{ relation: 'category' },
{ relation: 'itemPackingType' },
{ relation: 'temperature' },
],
};
const entityId = computed(() => {
return $props.id || route.params.id;
});
const data = ref(useCardDescription());
const setData = (entity) => (data.value = useCardDescription(entity.code, entity.id));
</script>
<template>
<CardDescriptor
module="ItemType"
:url="`ItemTypes/${entityId}`"
:filter="itemTypeFilter"
:title="data.title"
:subtitle="data.subtitle"
@on-fetch="setData"
data-key="entry"
>
<template #header-extra-action>
<QBtn
round
flat
size="sm"
icon="vn:item"
color="white"
:to="{ name: 'ItemTypeList' }"
>
<QTooltip>
{{ t('Go to module index') }}
</QTooltip>
</QBtn>
</template>
<template #body="{ entity }">
<VnLv :label="t('shared.code')" :value="entity.code" />
<VnLv :label="t('shared.name')" :value="entity.name" />
<VnLv :label="t('shared.worker')">
<template #value>
<span class="link">{{ entity.worker?.firstName }}</span>
<WorkerDescriptorProxy :id="entity.worker?.id" />
</template>
</VnLv>
<VnLv :label="t('shared.category')" :value="entity.category?.name" />
</template>
</CardDescriptor>
</template>
<i18n>
es:
Go to module index: Ir al índice del módulo
</i18n>

View File

@ -0,0 +1,109 @@
<script setup>
import { ref, computed, onUpdated } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue';
onUpdated(() => summaryRef.value.fetch());
const route = useRoute();
const { t } = useI18n();
const $props = defineProps({
id: {
type: Number,
required: false,
default: null,
},
});
const itemTypeFilter = {
include: [
{ relation: 'worker' },
{ relation: 'category' },
{ relation: 'itemPackingType' },
{ relation: 'temperature' },
],
};
const entityId = computed(() => $props.id || route.params.id);
const summaryRef = ref();
const itemType = ref();
async function setItemTypeData(data) {
if (data) itemType.value = data;
}
</script>
<template>
<CardSummary
ref="summaryRef"
:url="`ItemTypes/${entityId}`"
data-key="ItemTypeSummary"
:filter="itemTypeFilter"
@on-fetch="(data) => setItemTypeData(data)"
class="full-width"
>
<template #header-left>
<router-link
v-if="route.name !== 'ItemTypeSummary'"
:to="{ name: 'ItemTypeSummary', params: { id: entityId } }"
class="header link"
>
<QIcon name="open_in_new" color="white" size="sm" />
</router-link>
</template>
<template #header>
<span>
{{ itemType.id }} - {{ itemType.name }} -
{{ itemType.worker?.firstName }}
{{ itemType.worker?.lastName }}
</span>
</template>
<template #body>
<QCard class="vn-one">
<router-link
:to="{ name: 'ItemTypeBasicData', params: { id: entityId } }"
class="header header-link"
>
{{ t('globals.summary.basicData') }}
<QIcon name="open_in_new" />
</router-link>
<VnLv :label="t('summary.id')" :value="itemType.id" />
<VnLv :label="t('shared.code')" :value="itemType.code" />
<VnLv :label="t('shared.name')" :value="itemType.name" />
<VnLv :label="t('shared.worker')">
<template #value>
<span class="link">{{ itemType.worker?.firstName }}</span>
<WorkerDescriptorProxy :id="itemType.worker?.id" />
</template>
</VnLv>
<VnLv :label="t('shared.category')" :value="itemType.category?.name" />
<VnLv
:label="t('shared.temperature')"
:value="itemType.temperature?.name"
/>
<VnLv :label="t('summary.life')" :value="itemType.life" />
<VnLv :label="t('summary.promo')" :value="itemType.promo" />
<VnLv
:label="t('summary.itemPackingType')"
:value="itemType.itemPackingType?.description"
/>
<VnLv
class="large-label"
:label="t('summary.isUnconventionalSize')"
:value="itemType.isUnconventionalSize"
/>
</QCard>
</template>
</CardSummary>
</template>
<style lang="scss">
.large-label > div.label {
width: 15em !important;
}
</style>

View File

@ -0,0 +1,55 @@
<script setup>
import { useI18n } from 'vue-i18n';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const emit = defineEmits(['search']);
</script>
<template>
<VnFilterPanel
:data-key="props.dataKey"
:search-button="true"
@search="emit('search')"
>
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params }">
<QItem>
<QItemSection>
<VnInput :label="t('Name')" v-model="params.name" is-outlined />
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput v-model="params.code" :label="t('Code')" is-outlined />
</QItemSection>
</QItem>
</template>
</VnFilterPanel>
</template>
<i18n>
en:
params:
name: Name
code: Code
es:
params:
name: Nombre
code: Código
Name: Nombre
Code: Código
</i18n>

View File

@ -0,0 +1,12 @@
shared:
code: Code
name: Name
worker: Worker
category: Category
temperature: Temperature
summary:
id: id
life: Life
promo: Promo
itemPackingType: Item packing type
isUnconventionalSize: Is unconventional size

View File

@ -0,0 +1,17 @@
shared:
code: Código
name: Nombre
worker: Trabajador
category: Reino
temperature: Temperatura
summary:
id: id
code: Código
name: Nombre
worker: Trabajador
category: Reino
temperature: Temperatura
life: Vida
promo: Promoción
itemPackingType: Tipo de embalaje
isUnconventionalSize: Es de tamaño poco convencional

View File

@ -38,7 +38,13 @@ async function onSubmit() {
if (!multimediaToken) return;
await session.login(data.token, multimediaToken.id, keepLogin.value);
const login = {
...data,
created: Date.now(),
tokenMultimedia: multimediaToken.id,
keepLogin: keepLogin.value,
};
await session.login(login);
quasar.notify({
message: t('login.loginSuccess'),

View File

@ -20,14 +20,14 @@ const router = useRouter();
const { t } = useI18n();
const entityId = computed(() => $props.id || route.params.id);
const isDialog = Boolean($props.id);
const hideRightDrawer = () => {
if (!isDialog) {
stateStore.rightDrawer = false;
}
};
onMounted(hideRightDrawer);
onUnmounted(hideRightDrawer);
const isDialog = false;
// const hideRightDrawer = () => {
// if (!isDialog) {
// stateStore.rightDrawer = false;
// }
// };
// onMounted(hideRightDrawer);
// onUnmounted(hideRightDrawer);
const filter = {
include: [
{
@ -46,23 +46,6 @@ const filter = {
</script>
<template>
<template v-if="!isDialog && stateStore.isHeaderMounted()">
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
</template>
<div class="q-pa-md">
<CardSummary ref="summary" :url="`Shelvings/${entityId}`" :filter="filter">
<template #header="{ entity }">
@ -102,18 +85,4 @@ const filter = {
</template>
</CardSummary>
</div>
<QDrawer
v-if="!isDialog"
v-model="stateStore.rightDrawer"
side="right"
:width="256"
show-if-above
>
<QScrollArea class="fit text-grey-8">
<ShelvingFilter
data-key="ShelvingList"
@search="router.push({ name: 'ShelvingList' })"
/>
</QScrollArea>
</QDrawer>
</template>

View File

@ -12,6 +12,7 @@ import { useSession } from 'src/composables/useSession';
import { useRole } from 'src/composables/useRole';
import { useUserConfig } from 'src/composables/useUserConfig';
import { toLowerCamel } from 'src/filters';
import { useTokenConfig } from 'src/composables/useTokenConfig';
const state = useState();
const session = useSession();
@ -55,6 +56,7 @@ export default route(function (/* { store, ssrContext } */) {
if (stateRoles.length === 0) {
await useRole().fetch();
await useUserConfig().fetch();
await useTokenConfig().fetch();
}
const matches = to.matched;
const hasRequiredRoles = matches.every((route) => {

View File

@ -6,6 +6,7 @@ export default {
meta: {
title: 'department',
icon: 'vn:greuge',
moduleName: 'Department',
},
component: RouterView,
redirect: { name: 'DepartmentCard' },

View File

@ -16,6 +16,7 @@ import Entry from './entry';
import roadmap from './roadmap';
import Parking from './parking';
import Agency from './agency';
import ItemType from './itemType';
export default [
Item,
@ -36,4 +37,5 @@ export default [
roadmap,
Parking,
Agency,
ItemType,
];

View File

@ -11,7 +11,13 @@ export default {
component: RouterView,
redirect: { name: 'ItemMain' },
menus: {
main: ['ItemList', 'WasteBreakdown', 'ItemFixedPrice', 'ItemRequest'],
main: [
'ItemList',
'WasteBreakdown',
'ItemFixedPrice',
'ItemRequest',
'ItemTypeList',
],
card: [
'ItemBasicData',
'ItemLog',
@ -68,6 +74,23 @@ export default {
'https://grafana.verdnatura.es/d/TTNXQAxVk';
},
},
{
path: 'item-type-list',
name: 'ItemTypeList',
meta: {
title: 'family',
icon: 'contact_support',
},
component: () => import('src/pages/Item/ItemTypeList.vue'),
},
{
path: 'item-type-list/create',
name: 'ItemTypeCreate',
meta: {
title: 'itemTypeCreate',
},
component: () => import('src/pages/Item/ItemTypeCreate.vue'),
},
{
path: 'request',
name: 'ItemRequest',

View File

@ -0,0 +1,46 @@
import { RouterView } from 'vue-router';
export default {
path: '/item/item-type',
name: 'ItemType',
meta: {
title: 'itemType',
icon: 'contact_support',
moduleName: 'ItemType',
},
component: RouterView,
redirect: { name: 'ItemTypeList' },
menus: {
main: [],
card: ['ItemTypeBasicData'],
},
children: [
{
name: 'ItemTypeCard',
path: ':id',
component: () => import('src/pages/ItemType/Card/ItemTypeCard.vue'),
redirect: { name: 'ItemTypeSummary' },
children: [
{
name: 'ItemTypeSummary',
path: 'summary',
meta: {
title: 'summary',
},
component: () =>
import('src/pages/ItemType/Card/ItemTypeSummary.vue'),
},
{
name: 'ItemTypeBasicData',
path: 'basic-data',
meta: {
title: 'basicData',
icon: 'vn:settings',
},
component: () =>
import('src/pages/ItemType/Card/ItemTypeBasicData.vue'),
},
],
},
],
};

View File

@ -6,6 +6,7 @@ export default {
meta: {
title: 'roadmap',
icon: 'vn:troncales',
moduleName: 'Roadmap',
},
component: RouterView,
redirect: { name: 'RouteMain' },

View File

@ -10,6 +10,7 @@ import supplier from './modules/Supplier';
import route from './modules/route';
import travel from './modules/travel';
import department from './modules/department';
import ItemType from './modules/itemType';
import shelving from 'src/router/modules/shelving';
import order from 'src/router/modules/order';
import entry from 'src/router/modules/entry';
@ -73,6 +74,7 @@ const routes = [
entry,
parking,
agency,
ItemType,
{
path: '/:catchAll(.*)*',
name: 'NotFound',

View File

@ -1,5 +1,5 @@
import { vi, describe, expect, it } from 'vitest';
import { axios } from 'app/test/vitest/helper';
import { vi, describe, expect, it, beforeAll, beforeEach } from 'vitest';
import { axios, flushPromises } from 'app/test/vitest/helper';
import { useSession } from 'composables/useSession';
import { useState } from 'composables/useState';
@ -63,73 +63,148 @@ describe('session', () => {
});
});
describe('login', () => {
const expectedUser = {
id: 999,
name: `T'Challa`,
nickname: 'Black Panther',
lang: 'en',
userConfig: {
darkMode: false,
},
};
const rolesData = [
{
role: {
name: 'salesPerson',
describe(
'login',
() => {
const expectedUser = {
id: 999,
name: `T'Challa`,
nickname: 'Black Panther',
lang: 'en',
userConfig: {
darkMode: false,
},
},
{
role: {
name: 'admin',
};
const rolesData = [
{
role: {
name: 'salesPerson',
},
},
},
];
{
role: {
name: 'admin',
},
},
];
it('should fetch the user roles and then set token in the sessionStorage', async () => {
const expectedRoles = ['salesPerson', 'admin'];
vi.spyOn(axios, 'get').mockResolvedValue({
data: { roles: rolesData, user: expectedUser },
it('should fetch the user roles and then set token in the sessionStorage', async () => {
const expectedRoles = ['salesPerson', 'admin'];
vi.spyOn(axios, 'get').mockResolvedValue({
data: { roles: rolesData, user: expectedUser },
});
const expectedToken = 'mySessionToken';
const expectedTokenMultimedia = 'mySessionTokenMultimedia';
const keepLogin = false;
await session.login({
token: expectedToken,
tokenMultimedia: expectedTokenMultimedia,
keepLogin,
});
const roles = state.getRoles();
const localToken = localStorage.getItem('token');
const sessionToken = sessionStorage.getItem('token');
expect(roles.value).toEqual(expectedRoles);
expect(localToken).toBeNull();
expect(sessionToken).toEqual(expectedToken);
await session.destroy(); // this clears token and user for any other test
});
const expectedToken = 'mySessionToken';
const expectedTokenMultimedia = 'mySessionTokenMultimedia';
const keepLogin = false;
it('should fetch the user roles and then set token in the localStorage', async () => {
const expectedRoles = ['salesPerson', 'admin'];
vi.spyOn(axios, 'get').mockResolvedValue({
data: { roles: rolesData, user: expectedUser },
});
await session.login(expectedToken,expectedTokenMultimedia, keepLogin);
const expectedToken = 'myLocalToken';
const expectedTokenMultimedia = 'myLocalTokenMultimedia';
const keepLogin = true;
const roles = state.getRoles();
const localToken = localStorage.getItem('token');
const sessionToken = sessionStorage.getItem('token');
await session.login({
token: expectedToken,
tokenMultimedia: expectedTokenMultimedia,
keepLogin,
});
expect(roles.value).toEqual(expectedRoles);
expect(localToken).toBeNull();
expect(sessionToken).toEqual(expectedToken);
const roles = state.getRoles();
const localToken = localStorage.getItem('token');
const sessionToken = sessionStorage.getItem('token');
await session.destroy(); // this clears token and user for any other test
expect(roles.value).toEqual(expectedRoles);
expect(localToken).toEqual(expectedToken);
expect(sessionToken).toBeNull();
await session.destroy(); // this clears token and user for any other test
});
},
{}
);
describe('RenewToken', () => {
const expectedToken = 'myToken';
const expectedTokenMultimedia = 'myTokenMultimedia';
const currentDate = new Date();
beforeAll(() => {
const tokenConfig = {
id: 1,
renewPeriod: 21600,
courtesyTime: 60,
renewInterval: 300,
};
state.setTokenConfig(tokenConfig);
sessionStorage.setItem('renewPeriod', 1);
});
it('NOT Should renewToken', async () => {
const data = {
token: expectedToken,
tokenMultimedia: expectedTokenMultimedia,
keepLogin: false,
ttl: 1,
created: Date.now(),
};
session.setSession(data);
expect(sessionStorage.getItem('keepLogin')).toBeFalsy();
expect(sessionStorage.getItem('created')).toBeDefined();
expect(sessionStorage.getItem('ttl')).toEqual(1);
await session.checkValidity();
expect(sessionStorage.getItem('token')).toEqual(expectedToken);
expect(sessionStorage.getItem('tokenMultimedia')).toEqual(
expectedTokenMultimedia
);
});
it('Should renewToken', async () => {
currentDate.setMinutes(currentDate.getMinutes() - 100);
const data = {
token: expectedToken,
tokenMultimedia: expectedTokenMultimedia,
keepLogin: false,
ttl: 1,
created: currentDate,
};
session.setSession(data);
it('should fetch the user roles and then set token in the localStorage', async () => {
const expectedRoles = ['salesPerson', 'admin'];
vi.spyOn(axios, 'get').mockResolvedValue({
data: { roles: rolesData, user: expectedUser },
});
const expectedToken = 'myLocalToken';
const expectedTokenMultimedia = 'myLocalTokenMultimedia';
const keepLogin = true;
await session.login(expectedToken, expectedTokenMultimedia, keepLogin);
const roles = state.getRoles();
const localToken = localStorage.getItem('token');
const sessionToken = sessionStorage.getItem('token');
expect(roles.value).toEqual(expectedRoles);
expect(localToken).toEqual(expectedToken);
expect(sessionToken).toBeNull();
await session.destroy(); // this clears token and user for any other test
vi.spyOn(axios, 'post')
.mockResolvedValueOnce({
data: { id: '' },
})
.mockResolvedValueOnce({
data: {
id: '',
},
});
expect(sessionStorage.getItem('keepLogin')).toBeFalsy();
expect(sessionStorage.getItem('created')).toBeDefined();
expect(sessionStorage.getItem('ttl')).toEqual(1);
await session.checkValidity();
expect(sessionStorage.getItem('token')).not.toEqual(expectedToken);
expect(sessionStorage.getItem('tokenMultimedia')).not.toEqual(
expectedTokenMultimedia
);
});
});
});

View File

@ -0,0 +1,31 @@
import { vi, describe, expect, it } from 'vitest';
import { axios, flushPromises } from 'app/test/vitest/helper';
import { useTokenConfig } from 'composables/useTokenConfig';
const tokenConfig = useTokenConfig();
describe('useTokenConfig', () => {
describe('fetch', () => {
it('should call setTokenConfig of the state with the expected data', async () => {
const data = {
id: 1,
renewPeriod: 21600,
courtesyTime: 60,
renewInterval: 300,
};
vi.spyOn(axios, 'get').mockResolvedValueOnce({
data,
});
vi.spyOn(tokenConfig.state, 'setTokenConfig');
tokenConfig.fetch();
await flushPromises();
expect(tokenConfig.state.setTokenConfig).toHaveBeenCalledWith(data);
const renewPeriod = sessionStorage.getItem('renewPeriod');
expect(renewPeriod).toEqual(data.renewPeriod);
});
});
});