232201_test_to_master #62
File diff suppressed because it is too large
Load Diff
52
package.json
52
package.json
|
@ -15,40 +15,42 @@
|
||||||
"test:unit:ci": "vitest run"
|
"test:unit:ci": "vitest run"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@quasar/extras": "^1.15.11",
|
"@quasar/cli": "^2.2.1",
|
||||||
"axios": "^1.2.1",
|
"@quasar/extras": "^1.16.4",
|
||||||
"pinia": "^2.0.28",
|
"axios": "^1.4.0",
|
||||||
"quasar": "^2.11.7",
|
"chromium": "^3.0.3",
|
||||||
"validator": "^13.7.0",
|
"pinia": "^2.1.3",
|
||||||
"vue": "^3.2.45",
|
"quasar": "^2.12.0",
|
||||||
|
"validator": "^13.9.0",
|
||||||
|
"vue": "^3.3.4",
|
||||||
"vue-i18n": "^9.2.2",
|
"vue-i18n": "^9.2.2",
|
||||||
"vue-router": "^4.1.6",
|
"vue-router": "^4.2.1",
|
||||||
"vue-router-mock": "^0.1.9"
|
"vue-router-mock": "^0.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@intlify/unplugin-vue-i18n": "^0.8.1",
|
"@intlify/unplugin-vue-i18n": "^0.8.1",
|
||||||
"@pinia/testing": "^0.0.14",
|
"@pinia/testing": "^0.1.2",
|
||||||
"@quasar/app-vite": "^1.2.1",
|
"@quasar/app-vite": "^1.4.3",
|
||||||
"@quasar/quasar-app-extension-testing-unit-vitest": "^0.2.1",
|
"@quasar/quasar-app-extension-testing-unit-vitest": "^0.3.0",
|
||||||
"@vue/test-utils": "^2.0.0",
|
"@vue/test-utils": "^2.3.2",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.14",
|
||||||
"cypress": "^12.2.0",
|
"cypress": "^12.13.0",
|
||||||
"eslint": "^8.30.0",
|
"eslint": "^8.41.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.8.0",
|
||||||
"eslint-plugin-cypress": "^2.12.1",
|
"eslint-plugin-cypress": "^2.13.3",
|
||||||
"eslint-plugin-vue": "^9.8.0",
|
"eslint-plugin-vue": "^9.14.1",
|
||||||
"postcss": "^8.4.20",
|
"postcss": "^8.4.23",
|
||||||
"prettier": "^2.8.1",
|
"prettier": "^2.8.8",
|
||||||
"vitest": "^0.26.3"
|
"vitest": "^0.31.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18 || ^16 || ^14.19",
|
"node": "^20 || ^18 || ^16",
|
||||||
"npm": ">= 6.13.4",
|
"npm": ">= 8.1.2",
|
||||||
"yarn": ">= 1.21.1"
|
"yarn": ">= 1.21.1"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
"vite": "^4.0.3",
|
"vite": "^4.3.5",
|
||||||
"vitest": "^0.26.3"
|
"vitest": "^0.31.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,16 +39,20 @@ onMounted(async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
async function fetch() {
|
async function fetch() {
|
||||||
const filter = Object.assign({}, $props.filter);
|
try {
|
||||||
if ($props.where) filter.where = $props.where;
|
const filter = Object.assign({}, $props.filter); // eslint-disable-line vue/no-dupe-keys
|
||||||
if ($props.sortBy) filter.order = $props.sortBy;
|
if ($props.where) filter.where = $props.where;
|
||||||
if ($props.limit) filter.limit = $props.limit;
|
if ($props.sortBy) filter.order = $props.sortBy;
|
||||||
|
if ($props.limit) filter.limit = $props.limit;
|
||||||
|
|
||||||
const { data } = await axios.get($props.url, {
|
const { data } = await axios.get($props.url, {
|
||||||
params: { filter },
|
params: { filter },
|
||||||
});
|
});
|
||||||
|
|
||||||
emit('onFetch', data);
|
emit('onFetch', data);
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const render = () => {
|
const render = () => {
|
||||||
|
|
|
@ -77,7 +77,7 @@ function reset() {
|
||||||
state.set($props.model, originalData.value);
|
state.set($props.model, originalData.value);
|
||||||
hasChanges.value = false;
|
hasChanges.value = false;
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line vue/no-dupe-keys
|
||||||
function filter(value, update, filterOptions) {
|
function filter(value, update, filterOptions) {
|
||||||
update(
|
update(
|
||||||
() => {
|
() => {
|
||||||
|
|
|
@ -11,7 +11,7 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const item = computed(() => props.item);
|
const item = computed(() => props.item); // eslint-disable-line vue/no-dupe-keys
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<QItem active-class="text-primary" :to="{ name: item.name }" clickable v-ripple>
|
<QItem active-class="text-primary" :to="{ name: item.name }" clickable v-ripple>
|
||||||
|
|
|
@ -18,7 +18,7 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const item = computed(() => props.item);
|
const item = computed(() => props.item); // eslint-disable-line vue/no-dupe-keys
|
||||||
const isOpened = computed(() => {
|
const isOpened = computed(() => {
|
||||||
const { matched } = route;
|
const { matched } = route;
|
||||||
const { name } = item.value;
|
const { name } = item.value;
|
||||||
|
|
|
@ -0,0 +1,208 @@
|
||||||
|
<script setup>
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useSession } from 'src/composables/useSession';
|
||||||
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
|
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||||
|
import VnLogFilter from 'src/components/common/VnLogFilter.vue';
|
||||||
|
|
||||||
|
import { toDate } from 'src/filters';
|
||||||
|
|
||||||
|
const stateStore = useStateStore();
|
||||||
|
const route = useRoute();
|
||||||
|
const session = useSession();
|
||||||
|
const token = session.getToken();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
model: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
name: 'property',
|
||||||
|
label: 'Property',
|
||||||
|
field: (row) => t(`properties.${row.property}`),
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'before',
|
||||||
|
label: 'Before',
|
||||||
|
field: (row) => formatValue(row.before),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'after',
|
||||||
|
label: 'After',
|
||||||
|
field: (row) => formatValue(row.after),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function formatValue(value) {
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
return value ? t('Yes') : t('No');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(value) && !isNaN(Date.parse(value))) {
|
||||||
|
return toDate(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === undefined) {
|
||||||
|
return t('Nothing');
|
||||||
|
}
|
||||||
|
|
||||||
|
return `"${value}"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function actionColor(action) {
|
||||||
|
if (action === 'insert') return 'positive';
|
||||||
|
if (action === 'update') return 'positive';
|
||||||
|
if (action === 'delete') return 'negative';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="column items-center">
|
||||||
|
<QTimeline class="q-pa-md">
|
||||||
|
<QTimelineEntry heading tag="h4"> {{ t('Audit logs') }} </QTimelineEntry>
|
||||||
|
<VnPaginate
|
||||||
|
:data-key="`${props.model}Logs`"
|
||||||
|
:url="`${props.model}s/${route.params.id}/logs`"
|
||||||
|
order="id DESC"
|
||||||
|
:offset="100"
|
||||||
|
:limit="5"
|
||||||
|
auto-load
|
||||||
|
>
|
||||||
|
<template #body="{ rows }">
|
||||||
|
<template v-for="log of rows" :key="log.id">
|
||||||
|
<QTimelineEntry
|
||||||
|
:avatar="`/api/Images/user/160x160/${log.userFk}/download?access_token=${token}`"
|
||||||
|
>
|
||||||
|
<template #subtitle>
|
||||||
|
{{ log.userName }} -
|
||||||
|
{{
|
||||||
|
toDate(log.created, {
|
||||||
|
dateStyle: 'medium',
|
||||||
|
timeStyle: 'short',
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</template>
|
||||||
|
<template #title>
|
||||||
|
<QChip :color="actionColor(log.action)">
|
||||||
|
{{ t(`actions.${log.action}`) }}
|
||||||
|
</QChip>
|
||||||
|
{{ t(`models.${log.model}`) }}
|
||||||
|
</template>
|
||||||
|
<QTable
|
||||||
|
:rows="log.changes"
|
||||||
|
:columns="columns"
|
||||||
|
row-key="property"
|
||||||
|
hide-pagination
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
>
|
||||||
|
<template #header="propsLabel">
|
||||||
|
<QTr :props="propsLabel">
|
||||||
|
<QTh
|
||||||
|
v-for="col in propsLabel.cols"
|
||||||
|
:key="col.name"
|
||||||
|
:props="propsLabel"
|
||||||
|
>
|
||||||
|
{{ t(col.label) }}
|
||||||
|
</QTh>
|
||||||
|
</QTr>
|
||||||
|
</template>
|
||||||
|
</QTable>
|
||||||
|
</QTimelineEntry>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</VnPaginate>
|
||||||
|
</QTimeline>
|
||||||
|
</div>
|
||||||
|
<Teleport v-if="stateStore.isHeaderMounted()" 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>
|
||||||
|
<QDrawer v-model="stateStore.rightDrawer" show-if-above side="right" :width="300">
|
||||||
|
<QScrollArea class="fit text-grey-8">
|
||||||
|
<VnLogFilter :data-key="`${props.model}Logs`" />
|
||||||
|
</QScrollArea>
|
||||||
|
</QDrawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.q-timeline {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 80em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<i18n>
|
||||||
|
en:
|
||||||
|
actions:
|
||||||
|
insert: Creates
|
||||||
|
update: Updates
|
||||||
|
delete: Deletes
|
||||||
|
models:
|
||||||
|
Claim: Claim
|
||||||
|
ClaimDms: Document
|
||||||
|
ClaimBeginning: Claimed Sales
|
||||||
|
ClaimObservation: Observation
|
||||||
|
properties:
|
||||||
|
id: ID
|
||||||
|
claimFk: Claim ID
|
||||||
|
saleFk: Sale ID
|
||||||
|
quantity: Quantity
|
||||||
|
observation: Observation
|
||||||
|
ticketCreated: Created
|
||||||
|
created: Created
|
||||||
|
isChargedToMana: Charged to mana
|
||||||
|
hasToPickUp: Has to pick Up
|
||||||
|
dmsFk: Document ID
|
||||||
|
text: Description
|
||||||
|
claimStateFk: Claim State
|
||||||
|
workerFk: Worker
|
||||||
|
clientFk: Customer
|
||||||
|
rma: RMA
|
||||||
|
responsibility: Responsibility
|
||||||
|
packages: Packages
|
||||||
|
es:
|
||||||
|
Audit logs: Registros de auditoría
|
||||||
|
Property: Propiedad
|
||||||
|
Before: Antes
|
||||||
|
After: Después
|
||||||
|
Yes: Si
|
||||||
|
Nothing: Nada
|
||||||
|
actions:
|
||||||
|
insert: Crea
|
||||||
|
update: Actualiza
|
||||||
|
delete: Elimina
|
||||||
|
models:
|
||||||
|
Claim: Reclamación
|
||||||
|
ClaimDms: Documento
|
||||||
|
ClaimBeginning: Línea reclamada
|
||||||
|
ClaimObservation: Observación
|
||||||
|
properties:
|
||||||
|
id: ID
|
||||||
|
claimFk: ID reclamación
|
||||||
|
saleFk: ID linea de venta
|
||||||
|
quantity: Cantidad
|
||||||
|
observation: Observación
|
||||||
|
ticketCreated: Creado
|
||||||
|
created: Creado
|
||||||
|
isChargedToMana: Cargado a maná
|
||||||
|
hasToPickUp: Se debe recoger
|
||||||
|
dmsFk: ID documento
|
||||||
|
text: Descripción
|
||||||
|
claimStateFk: Estado de la reclamación
|
||||||
|
workerFk: Trabajador
|
||||||
|
clientFk: Cliente
|
||||||
|
rma: RMA
|
||||||
|
responsibility: Responsabilidad
|
||||||
|
packages: Bultos
|
||||||
|
</i18n>
|
|
@ -36,7 +36,7 @@ const props = defineProps({
|
||||||
|
|
||||||
const emit = defineEmits(['onUpdate']);
|
const emit = defineEmits(['onUpdate']);
|
||||||
|
|
||||||
const discount = ref(0);
|
const discount = ref(0); // eslint-disable-line vue/no-dupe-keys
|
||||||
let canceller;
|
let canceller;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
@ -13,7 +13,6 @@ export function useRole() {
|
||||||
name: data.user.name,
|
name: data.user.name,
|
||||||
nickname: data.user.nickname,
|
nickname: data.user.nickname,
|
||||||
lang: data.user.lang || 'es',
|
lang: data.user.lang || 'es',
|
||||||
darkMode: data.user.userConfig.darkMode,
|
|
||||||
};
|
};
|
||||||
state.setUser(userData);
|
state.setUser(userData);
|
||||||
state.setRoles(roles);
|
state.setRoles(roles);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { useState } from './useState';
|
import { useState } from './useState';
|
||||||
import { useRole } from './useRole';
|
import { useRole } from './useRole';
|
||||||
|
import { useUserConfig } from './useUserConfig';
|
||||||
|
|
||||||
|
|
||||||
export function useSession() {
|
export function useSession() {
|
||||||
function getToken() {
|
function getToken() {
|
||||||
|
@ -36,11 +38,10 @@ export function useSession() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function login(token, keepLogin) {
|
async function login(token, keepLogin) {
|
||||||
const { fetch } = useRole();
|
|
||||||
|
|
||||||
setToken({ token, keepLogin });
|
setToken({ token, keepLogin });
|
||||||
|
|
||||||
await fetch();
|
await useRole().fetch();
|
||||||
|
await useUserConfig().fetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLoggedIn() {
|
function isLoggedIn() {
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useState } from './useState';
|
||||||
|
|
||||||
|
export function useUserConfig() {
|
||||||
|
const state = useState();
|
||||||
|
|
||||||
|
async function fetch() {
|
||||||
|
const { data } = await axios.get('UserConfigs/getUserConfig');
|
||||||
|
const user = state.getUser().value;
|
||||||
|
user.darkMode = data.darkMode;
|
||||||
|
state.setUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
fetch,
|
||||||
|
};
|
||||||
|
}
|
|
@ -361,6 +361,7 @@ export default {
|
||||||
list: 'List',
|
list: 'List',
|
||||||
basicData: 'Basic data',
|
basicData: 'Basic data',
|
||||||
summary: 'Summary',
|
summary: 'Summary',
|
||||||
|
notifications: 'Notifications',
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
|
@ -394,6 +395,12 @@ export default {
|
||||||
role: 'Role',
|
role: 'Role',
|
||||||
sipExtension: 'Extension',
|
sipExtension: 'Extension',
|
||||||
},
|
},
|
||||||
|
notificationsManager: {
|
||||||
|
activeNotifications: 'Active notifications',
|
||||||
|
availableNotifications: 'Available notifications',
|
||||||
|
subscribed: 'Subscribed to the notification',
|
||||||
|
unsubscribed: 'Unsubscribed from the notification',
|
||||||
|
},
|
||||||
imageNotFound: 'Image not found',
|
imageNotFound: 'Image not found',
|
||||||
},
|
},
|
||||||
wagon: {
|
wagon: {
|
||||||
|
|
|
@ -361,6 +361,7 @@ export default {
|
||||||
list: 'Listado',
|
list: 'Listado',
|
||||||
basicData: 'Datos básicos',
|
basicData: 'Datos básicos',
|
||||||
summary: 'Resumen',
|
summary: 'Resumen',
|
||||||
|
notifications: 'Notificaciones',
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
name: 'Nombre',
|
name: 'Nombre',
|
||||||
|
@ -394,6 +395,12 @@ export default {
|
||||||
role: 'Rol',
|
role: 'Rol',
|
||||||
sipExtension: 'Extensión',
|
sipExtension: 'Extensión',
|
||||||
},
|
},
|
||||||
|
notificationsManager: {
|
||||||
|
activeNotifications: 'Notificaciones activas',
|
||||||
|
availableNotifications: 'Notificaciones disponibles',
|
||||||
|
subscribed: 'Se ha suscrito a la notificación',
|
||||||
|
unsubscribed: 'Se ha dado de baja de la notificación',
|
||||||
|
},
|
||||||
imageNotFound: 'No se ha encontrado la imagen',
|
imageNotFound: 'No se ha encontrado la imagen',
|
||||||
},
|
},
|
||||||
wagon: {
|
wagon: {
|
||||||
|
|
|
@ -1,201 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useI18n } from 'vue-i18n';
|
import VnLog from 'src/components/common/VnLog.vue';
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
import { useSession } from 'src/composables/useSession';
|
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
|
||||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
|
||||||
import ClaimLogFilter from './ClaimLogFilter.vue';
|
|
||||||
|
|
||||||
import { toDate } from 'src/filters';
|
|
||||||
|
|
||||||
const stateStore = useStateStore();
|
|
||||||
const route = useRoute();
|
|
||||||
const session = useSession();
|
|
||||||
const token = session.getToken();
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
name: 'property',
|
|
||||||
label: 'Property',
|
|
||||||
field: (row) => t(`properties.${row.property}`),
|
|
||||||
align: 'left',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'before',
|
|
||||||
label: 'Before',
|
|
||||||
field: (row) => formatValue(row.before),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'after',
|
|
||||||
label: 'After',
|
|
||||||
field: (row) => formatValue(row.after),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
function formatValue(value) {
|
|
||||||
if (typeof value === 'boolean') {
|
|
||||||
return value ? t('Yes') : t('No');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNaN(value) && !isNaN(Date.parse(value))) {
|
|
||||||
return toDate(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value === undefined) {
|
|
||||||
return t('Nothing');
|
|
||||||
}
|
|
||||||
|
|
||||||
return `"${value}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function actionColor(action) {
|
|
||||||
if (action === 'insert') return 'positive';
|
|
||||||
if (action === 'update') return 'positive';
|
|
||||||
if (action === 'delete') return 'negative';
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="column items-center">
|
<VnLog model="Claim"></VnLog>
|
||||||
<QTimeline class="q-pa-md">
|
|
||||||
<QTimelineEntry heading tag="h4"> {{ t('Audit logs') }} </QTimelineEntry>
|
|
||||||
<VnPaginate
|
|
||||||
data-key="ClaimLogs"
|
|
||||||
:url="`Claims/${route.params.id}/logs`"
|
|
||||||
order="id DESC"
|
|
||||||
:offset="100"
|
|
||||||
:limit="5"
|
|
||||||
auto-load
|
|
||||||
>
|
|
||||||
<template #body="{ rows }">
|
|
||||||
<template v-for="log of rows" :key="log.id">
|
|
||||||
<QTimelineEntry
|
|
||||||
:avatar="`/api/Images/user/160x160/${log.userFk}/download?access_token=${token}`"
|
|
||||||
>
|
|
||||||
<template #subtitle>
|
|
||||||
{{ log.userName }} -
|
|
||||||
{{
|
|
||||||
toDate(log.created, {
|
|
||||||
dateStyle: 'medium',
|
|
||||||
timeStyle: 'short',
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
</template>
|
|
||||||
<template #title>
|
|
||||||
<QChip :color="actionColor(log.action)">
|
|
||||||
{{ t(`actions.${log.action}`) }}
|
|
||||||
</QChip>
|
|
||||||
{{ t(`models.${log.model}`) }}
|
|
||||||
</template>
|
|
||||||
<QTable
|
|
||||||
:rows="log.changes"
|
|
||||||
:columns="columns"
|
|
||||||
row-key="property"
|
|
||||||
hide-pagination
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
>
|
|
||||||
<template #header="props">
|
|
||||||
<QTr :props="props">
|
|
||||||
<QTh
|
|
||||||
v-for="col in props.cols"
|
|
||||||
:key="col.name"
|
|
||||||
:props="props"
|
|
||||||
>
|
|
||||||
{{ t(col.label) }}
|
|
||||||
</QTh>
|
|
||||||
</QTr>
|
|
||||||
</template>
|
|
||||||
</QTable>
|
|
||||||
</QTimelineEntry>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</VnPaginate>
|
|
||||||
</QTimeline>
|
|
||||||
</div>
|
|
||||||
<Teleport v-if="stateStore.isHeaderMounted()" 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>
|
|
||||||
<QDrawer v-model="stateStore.rightDrawer" show-if-above side="right" :width="300">
|
|
||||||
<QScrollArea class="fit text-grey-8">
|
|
||||||
<ClaimLogFilter data-key="ClaimLogs" />
|
|
||||||
</QScrollArea>
|
|
||||||
</QDrawer>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.q-timeline {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 80em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<i18n>
|
|
||||||
en:
|
|
||||||
actions:
|
|
||||||
insert: Creates
|
|
||||||
update: Updates
|
|
||||||
delete: Deletes
|
|
||||||
models:
|
|
||||||
Claim: Claim
|
|
||||||
ClaimDms: Document
|
|
||||||
ClaimBeginning: Claimed Sales
|
|
||||||
ClaimObservation: Observation
|
|
||||||
properties:
|
|
||||||
id: ID
|
|
||||||
claimFk: Claim ID
|
|
||||||
saleFk: Sale ID
|
|
||||||
quantity: Quantity
|
|
||||||
observation: Observation
|
|
||||||
ticketCreated: Created
|
|
||||||
created: Created
|
|
||||||
isChargedToMana: Charged to mana
|
|
||||||
hasToPickUp: Has to pick Up
|
|
||||||
dmsFk: Document ID
|
|
||||||
text: Description
|
|
||||||
claimStateFk: Claim State
|
|
||||||
workerFk: Worker
|
|
||||||
clientFk: Customer
|
|
||||||
rma: RMA
|
|
||||||
responsibility: Responsibility
|
|
||||||
packages: Packages
|
|
||||||
es:
|
|
||||||
Audit logs: Registros de auditoría
|
|
||||||
Property: Propiedad
|
|
||||||
Before: Antes
|
|
||||||
After: Después
|
|
||||||
Yes: Si
|
|
||||||
Nothing: Nada
|
|
||||||
actions:
|
|
||||||
insert: Crea
|
|
||||||
update: Actualiza
|
|
||||||
delete: Elimina
|
|
||||||
models:
|
|
||||||
Claim: Reclamación
|
|
||||||
ClaimDms: Documento
|
|
||||||
ClaimBeginning: Línea reclamada
|
|
||||||
ClaimObservation: Observación
|
|
||||||
properties:
|
|
||||||
id: ID
|
|
||||||
claimFk: ID reclamación
|
|
||||||
saleFk: ID linea de venta
|
|
||||||
quantity: Cantidad
|
|
||||||
observation: Observación
|
|
||||||
ticketCreated: Creado
|
|
||||||
created: Creado
|
|
||||||
isChargedToMana: Cargado a maná
|
|
||||||
hasToPickUp: Se debe recoger
|
|
||||||
dmsFk: ID documento
|
|
||||||
text: Descripción
|
|
||||||
claimStateFk: Estado de la reclamación
|
|
||||||
workerFk: Trabajador
|
|
||||||
clientFk: Cliente
|
|
||||||
rma: RMA
|
|
||||||
responsibility: Responsabilidad
|
|
||||||
packages: Bultos
|
|
||||||
</i18n>
|
|
||||||
|
|
|
@ -0,0 +1,142 @@
|
||||||
|
<script setup>
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
import { computed, onMounted, onUpdated, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
id: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const entityId = computed(() => $props.id || route.params.id);
|
||||||
|
|
||||||
|
onMounted(() => fetch());
|
||||||
|
onUpdated(() => fetch());
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const quasar = useQuasar();
|
||||||
|
|
||||||
|
const notifications = ref([]);
|
||||||
|
|
||||||
|
async function fetch() {
|
||||||
|
try {
|
||||||
|
await axios
|
||||||
|
.get(`NotificationSubscriptions/${entityId.value}/getList`)
|
||||||
|
.then(async (res) => {
|
||||||
|
if (res.data) {
|
||||||
|
notifications.value = res.data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function disableNotification(notification) {
|
||||||
|
await axios
|
||||||
|
.delete(`NotificationSubscriptions/${notification.id}`)
|
||||||
|
.catch(() => (notification.active = true))
|
||||||
|
.then((res) => {
|
||||||
|
if (res.data) {
|
||||||
|
notification.id = null;
|
||||||
|
notification.active = false;
|
||||||
|
quasar.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: t('worker.notificationsManager.unsubscribed'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleNotification(notification) {
|
||||||
|
if (!notification.active) {
|
||||||
|
await disableNotification(notification);
|
||||||
|
} else {
|
||||||
|
await axios
|
||||||
|
.post(`NotificationSubscriptions`, {
|
||||||
|
notificationFk: notification.notificationFk,
|
||||||
|
userFk: entityId.value,
|
||||||
|
})
|
||||||
|
.catch(() => (notification.active = false))
|
||||||
|
.then((res) => {
|
||||||
|
if (res.data) {
|
||||||
|
notification.id = res.data.id;
|
||||||
|
quasar.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: t('worker.notificationsManager.subscribed'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<QPage>
|
||||||
|
<QCard class="q-pa-md">
|
||||||
|
<QList>
|
||||||
|
<div
|
||||||
|
v-show="
|
||||||
|
notifications.filter(
|
||||||
|
(notification) => notification.active == true
|
||||||
|
).length
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<QItemLabel header class="text-h6">
|
||||||
|
{{ t('worker.notificationsManager.activeNotifications') }}
|
||||||
|
</QItemLabel>
|
||||||
|
<QItem>
|
||||||
|
<div
|
||||||
|
v-for="notification in notifications.filter(
|
||||||
|
(notification) => notification.active == true
|
||||||
|
)"
|
||||||
|
:key="notification.id"
|
||||||
|
>
|
||||||
|
<QChip
|
||||||
|
:key="notification.id"
|
||||||
|
:label="notification.name"
|
||||||
|
text-color="white"
|
||||||
|
color="primary"
|
||||||
|
class="q-mr-sm"
|
||||||
|
removable
|
||||||
|
@remove="disableNotification(notification)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</QItem>
|
||||||
|
</div>
|
||||||
|
<div v-show="notifications.length">
|
||||||
|
<QItemLabel header class="text-h6">
|
||||||
|
{{ t('worker.notificationsManager.availableNotifications') }}
|
||||||
|
</QItemLabel>
|
||||||
|
<div class="row">
|
||||||
|
<QItem
|
||||||
|
class="col-3"
|
||||||
|
:key="notification.notificationFk"
|
||||||
|
v-for="notification in notifications"
|
||||||
|
>
|
||||||
|
<QItemSection>
|
||||||
|
<QItemLabel>{{ notification.name }}</QItemLabel>
|
||||||
|
<QItemLabel caption>{{
|
||||||
|
notification.description
|
||||||
|
}}</QItemLabel>
|
||||||
|
</QItemSection>
|
||||||
|
<QItemSection side top>
|
||||||
|
<QToggle
|
||||||
|
checked-icon="check"
|
||||||
|
unchecked-icon="close"
|
||||||
|
indeterminate-icon="block"
|
||||||
|
v-model="notification.active"
|
||||||
|
@update:model-value="toggleNotification(notification)"
|
||||||
|
/>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</QList>
|
||||||
|
</QCard>
|
||||||
|
</QPage>
|
||||||
|
</template>
|
|
@ -10,11 +10,11 @@ import { i18n } from 'src/boot/i18n';
|
||||||
import { useState } from 'src/composables/useState';
|
import { useState } from 'src/composables/useState';
|
||||||
import { useSession } from 'src/composables/useSession';
|
import { useSession } from 'src/composables/useSession';
|
||||||
import { useRole } from 'src/composables/useRole';
|
import { useRole } from 'src/composables/useRole';
|
||||||
|
import { useUserConfig } from 'src/composables/useUserConfig';
|
||||||
import { toLowerCamel } from 'src/filters';
|
import { toLowerCamel } from 'src/filters';
|
||||||
|
|
||||||
const state = useState();
|
const state = useState();
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
const role = useRole();
|
|
||||||
const { t } = i18n.global;
|
const { t } = i18n.global;
|
||||||
|
|
||||||
const createHistory = process.env.SERVER
|
const createHistory = process.env.SERVER
|
||||||
|
@ -53,12 +53,13 @@ export default route(function (/* { store, ssrContext } */) {
|
||||||
if (isLoggedIn()) {
|
if (isLoggedIn()) {
|
||||||
const stateRoles = state.getRoles().value;
|
const stateRoles = state.getRoles().value;
|
||||||
if (stateRoles.length === 0) {
|
if (stateRoles.length === 0) {
|
||||||
await role.fetch();
|
await useRole().fetch();
|
||||||
|
await useUserConfig().fetch();
|
||||||
}
|
}
|
||||||
const matches = to.matched;
|
const matches = to.matched;
|
||||||
const hasRequiredRoles = matches.every((route) => {
|
const hasRequiredRoles = matches.every((route) => {
|
||||||
const meta = route.meta;
|
const meta = route.meta;
|
||||||
if (meta && meta.roles) return role.hasAny(meta.roles);
|
if (meta && meta.roles) return useRole().hasAny(meta.roles);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ export default {
|
||||||
redirect: { name: 'WorkerMain' },
|
redirect: { name: 'WorkerMain' },
|
||||||
menus: {
|
menus: {
|
||||||
main: ['WorkerList'],
|
main: ['WorkerList'],
|
||||||
card: [],
|
// card: ['WorkerNotificationsManager'],
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
@ -42,9 +42,19 @@ export default {
|
||||||
path: 'summary',
|
path: 'summary',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'summary',
|
title: 'summary',
|
||||||
|
icon: 'launch',
|
||||||
},
|
},
|
||||||
component: () => import('src/pages/Worker/Card/WorkerSummary.vue'),
|
component: () => import('src/pages/Worker/Card/WorkerSummary.vue'),
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// name: 'WorkerNotificationsManager',
|
||||||
|
// path: 'notifications',
|
||||||
|
// meta: {
|
||||||
|
// title: 'notifications',
|
||||||
|
// icon: 'notifications',
|
||||||
|
// },
|
||||||
|
// component: () => import('src/pages/Worker/Card/WorkerNotificationsManager.vue'),
|
||||||
|
// },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
describe('WorkerNotificationsManager', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const workerId = 1110;
|
||||||
|
cy.viewport(1280, 720);
|
||||||
|
cy.login('salesBoss');
|
||||||
|
cy.visit(`/#/worker/${workerId}/notifications`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should unsubscribe 2 notifications, check the unsubscription has been saved, subscribe to other one and should check the data has been saved', () => {
|
||||||
|
cy.get('.q-chip').should('have.length', 3);
|
||||||
|
cy.get('.q-toggle__thumb').eq(0).click();
|
||||||
|
cy.get('.q-notification__message').should('have.text', 'Unsubscribed from the notification');
|
||||||
|
cy.get('.q-chip > .q-icon').eq(0).click();
|
||||||
|
|
||||||
|
cy.reload();
|
||||||
|
|
||||||
|
cy.get('.q-chip').should('have.length', 1);
|
||||||
|
cy.get('.q-toggle__thumb').should('have.length', 3).eq(0).click();
|
||||||
|
cy.get('.q-notification__message').should('have.text', 'Subscribed to the notification');
|
||||||
|
cy.get('.q-toggle__thumb').should('have.length', 3).eq(1).click();
|
||||||
|
cy.get('.q-notification__message').should('have.text', 'Subscribed to the notification');
|
||||||
|
|
||||||
|
cy.reload();
|
||||||
|
|
||||||
|
cy.get('.q-chip').should('have.length', 3);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest';
|
||||||
|
import { createWrapper } from 'app/test/vitest/helper';
|
||||||
|
import VnLog from 'src/components/common/VnLog.vue';
|
||||||
|
|
||||||
|
describe('VnLog', () => {
|
||||||
|
let vm;
|
||||||
|
beforeAll(() => {
|
||||||
|
vm = createWrapper(VnLog, {
|
||||||
|
global: {
|
||||||
|
stubs: ['FetchData', 'VnPaginate'],
|
||||||
|
mocks: {
|
||||||
|
fetch: vi.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
propsData: {
|
||||||
|
model: "Claim",
|
||||||
|
},
|
||||||
|
}).vm;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('formatValue()', () => {
|
||||||
|
it('should return Yes if has a true boolean', async () => {
|
||||||
|
const result = vm.formatValue(true);
|
||||||
|
|
||||||
|
expect(result).toEqual('Yes');
|
||||||
|
});
|
||||||
|
it('should return No if has a true boolean', async () => {
|
||||||
|
const result = vm.formatValue(false);
|
||||||
|
|
||||||
|
expect(result).toEqual('No');
|
||||||
|
});
|
||||||
|
it('should return Nothing if has no params', async () => {
|
||||||
|
const result = vm.formatValue();
|
||||||
|
|
||||||
|
expect(result).toEqual('Nothing');
|
||||||
|
});
|
||||||
|
it('should return a string from a string value', async () => {
|
||||||
|
const result = vm.formatValue('Something');
|
||||||
|
|
||||||
|
expect(result).toEqual(`"Something"`);
|
||||||
|
});
|
||||||
|
it('should call to format a date', async () => {
|
||||||
|
vi.mock('src/filters', () => ({
|
||||||
|
toDate: ()=>{
|
||||||
|
return "Date formatted"
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const result = vm.formatValue('01-01-1970');
|
||||||
|
expect(result).toEqual("Date formatted");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('actionColor()', () => {
|
||||||
|
it('should return positive if insert', async () => {
|
||||||
|
const result = vm.actionColor('insert');
|
||||||
|
|
||||||
|
expect(result).toEqual('positive');
|
||||||
|
});
|
||||||
|
it('should return positive if update', async () => {
|
||||||
|
const result = vm.actionColor('update');
|
||||||
|
|
||||||
|
expect(result).toEqual('positive');
|
||||||
|
});
|
||||||
|
it('should return negative if delete', async () => {
|
||||||
|
const result = vm.actionColor('delete');
|
||||||
|
|
||||||
|
expect(result).toEqual('negative');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -23,21 +23,18 @@ describe('useRole', () => {
|
||||||
name: `T'Challa`,
|
name: `T'Challa`,
|
||||||
nickname: 'Black Panther',
|
nickname: 'Black Panther',
|
||||||
lang: 'en',
|
lang: 'en',
|
||||||
userConfig: {
|
|
||||||
darkMode: false,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
const expectedUser = {
|
const expectedUser = {
|
||||||
id: 999,
|
id: 999,
|
||||||
name: `T'Challa`,
|
name: `T'Challa`,
|
||||||
nickname: 'Black Panther',
|
nickname: 'Black Panther',
|
||||||
lang: 'en',
|
lang: 'en',
|
||||||
darkMode: false,
|
|
||||||
};
|
};
|
||||||
const expectedRoles = ['salesPerson', 'admin'];
|
const expectedRoles = ['salesPerson', 'admin'];
|
||||||
vi.spyOn(axios, 'get').mockResolvedValue({
|
vi.spyOn(axios, 'get')
|
||||||
|
.mockResolvedValueOnce({
|
||||||
data: { roles: rolesData, user: fetchedUser },
|
data: { roles: rolesData, user: fetchedUser },
|
||||||
});
|
})
|
||||||
|
|
||||||
vi.spyOn(role.state, 'setUser');
|
vi.spyOn(role.state, 'setUser');
|
||||||
vi.spyOn(role.state, 'setRoles');
|
vi.spyOn(role.state, 'setRoles');
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest';
|
||||||
|
import { createWrapper, axios } from 'app/test/vitest/helper';
|
||||||
|
import WorkerNotificationsManager from 'src/pages/Worker/Card/WorkerNotificationsManager.vue';
|
||||||
|
|
||||||
|
describe('WorkerNotificationsManager', () => {
|
||||||
|
let vm;
|
||||||
|
const entityId = 1110;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
vm = createWrapper(WorkerNotificationsManager, {
|
||||||
|
propsData: {
|
||||||
|
id: entityId,
|
||||||
|
},
|
||||||
|
}).vm;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fetch()', () => {
|
||||||
|
it('should fetch notification subscriptions and role mappings', async () => {
|
||||||
|
vi.spyOn(axios, 'get')
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Name 1',
|
||||||
|
description: 'Description 1',
|
||||||
|
notificationFk: 1,
|
||||||
|
active: true
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await vm.fetch();
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`NotificationSubscriptions/${entityId}/getList`);
|
||||||
|
expect(vm.notifications).toEqual([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
notificationFk: 1,
|
||||||
|
name: 'Name 1',
|
||||||
|
description: 'Description 1',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('disableNotification()', () => {
|
||||||
|
it('should disable the notification', async () => {
|
||||||
|
vi.spyOn(axios, 'delete').mockResolvedValue({ data: { count: 1 } });
|
||||||
|
vi.spyOn(vm.quasar, 'notify');
|
||||||
|
const subscriptionId = 1;
|
||||||
|
vm.notifications = [{ id: 1, active: true }];
|
||||||
|
|
||||||
|
await vm.disableNotification(vm.notifications[0]);
|
||||||
|
|
||||||
|
expect(axios.delete).toHaveBeenCalledWith(
|
||||||
|
`NotificationSubscriptions/${subscriptionId}`
|
||||||
|
);
|
||||||
|
expect(vm.notifications[0].id).toBeNull();
|
||||||
|
expect(vm.notifications[0].id).toBeFalsy();
|
||||||
|
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ type: 'positive' })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toggleNotification()', () => {
|
||||||
|
it('should activate the notification', async () => {
|
||||||
|
vi.spyOn(axios, 'post').mockResolvedValue({
|
||||||
|
data: { id: 1, notificationFk: 1 },
|
||||||
|
});
|
||||||
|
vm.notifications = [{ id: null, active: true, notificationFk: 1 }];
|
||||||
|
|
||||||
|
await vm.toggleNotification(vm.notifications[0]);
|
||||||
|
|
||||||
|
expect(axios.post).toHaveBeenCalledWith('NotificationSubscriptions', {
|
||||||
|
notificationFk: 1,
|
||||||
|
userFk: entityId,
|
||||||
|
});
|
||||||
|
expect(vm.notifications[0].id).toBe(1);
|
||||||
|
expect(vm.notifications[0].active).toBeTruthy();
|
||||||
|
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ type: 'positive' })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should disable the notification', async () => {
|
||||||
|
vi.spyOn(vm, 'disableNotification');
|
||||||
|
vm.notifications = [{ id: 1, active: false, notificationFk: 1 }];
|
||||||
|
|
||||||
|
await vm.toggleNotification(vm.notifications[0]);
|
||||||
|
|
||||||
|
expect(vm.notifications[0].id).toBe(null);
|
||||||
|
expect(vm.notifications[0].active).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue