2023-04-18 11:16:52 +00:00
|
|
|
<script setup>
|
2023-09-18 12:35:21 +00:00
|
|
|
import { ref } from 'vue';
|
2023-04-18 11:16:52 +00:00
|
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
import { useRoute } from 'vue-router';
|
2023-09-18 12:35:21 +00:00
|
|
|
import axios from 'axios';
|
2023-04-18 11:16:52 +00:00
|
|
|
import { useStateStore } from 'stores/useStateStore';
|
2023-09-18 12:35:21 +00:00
|
|
|
import { toRelativeDate, toDateString, toDateHour } from 'src/filters';
|
|
|
|
import { useColor } from 'src/composables/useColor';
|
|
|
|
import { useFirstUpper } from 'src/composables/useFirstUpper';
|
|
|
|
import { useIso8601 } from 'src/composables/useIso8601';
|
2023-09-28 11:45:14 +00:00
|
|
|
import { useValidator } from 'src/composables/useValidator';
|
2023-09-18 12:35:21 +00:00
|
|
|
import VnAvatar from '../ui/VnAvatar.vue';
|
|
|
|
import VnJsonValue from '../common/VnJsonValue.vue';
|
|
|
|
import FetchData from '../FetchData.vue';
|
|
|
|
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
2023-04-18 11:16:52 +00:00
|
|
|
|
|
|
|
const stateStore = useStateStore();
|
2023-09-28 11:45:14 +00:00
|
|
|
const validationsStore = useValidator();
|
|
|
|
const { models } = validationsStore;
|
2023-04-18 11:16:52 +00:00
|
|
|
const route = useRoute();
|
|
|
|
const { t } = useI18n();
|
|
|
|
const props = defineProps({
|
|
|
|
model: {
|
|
|
|
type: String,
|
|
|
|
default: null,
|
|
|
|
},
|
|
|
|
});
|
2023-09-26 09:49:57 +00:00
|
|
|
|
2023-09-18 12:35:21 +00:00
|
|
|
const filter = {
|
|
|
|
fields: [
|
|
|
|
'id',
|
|
|
|
'originFk',
|
|
|
|
'userFk',
|
|
|
|
'action',
|
|
|
|
'changedModel',
|
|
|
|
'oldInstance',
|
|
|
|
'newInstance',
|
|
|
|
'creationDate',
|
|
|
|
'changedModel',
|
|
|
|
'changedModelId',
|
|
|
|
'changedModelValue',
|
|
|
|
'description',
|
|
|
|
],
|
|
|
|
include: [
|
|
|
|
{
|
|
|
|
relation: 'user',
|
|
|
|
scope: {
|
|
|
|
fields: ['nickname', 'name', 'image'],
|
|
|
|
include: {
|
|
|
|
relation: 'worker',
|
|
|
|
scope: {
|
|
|
|
fields: ['id'],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
],
|
|
|
|
};
|
2023-04-18 11:16:52 +00:00
|
|
|
|
2023-09-18 12:35:21 +00:00
|
|
|
const workers = ref();
|
|
|
|
const actions = ref();
|
|
|
|
const changeInput = ref();
|
|
|
|
const searchInput = ref();
|
|
|
|
const userRadio = ref();
|
|
|
|
const userSelect = ref();
|
|
|
|
const date = ref();
|
|
|
|
const dateDialog = ref(false);
|
|
|
|
const dateTo = ref();
|
|
|
|
const dateToDialog = ref(false);
|
|
|
|
const selectedFilters = ref({});
|
|
|
|
const userTypes = [
|
|
|
|
{ label: 'All', value: undefined },
|
|
|
|
{ label: 'User', value: { neq: null } },
|
|
|
|
{ label: 'System', value: null },
|
|
|
|
];
|
|
|
|
const checkboxOptions = ref({
|
|
|
|
insert: {
|
|
|
|
label: 'Creates',
|
|
|
|
selected: false,
|
2023-04-18 11:16:52 +00:00
|
|
|
},
|
2023-09-18 12:35:21 +00:00
|
|
|
update: {
|
|
|
|
label: 'Edits',
|
|
|
|
selected: false,
|
2023-04-18 11:16:52 +00:00
|
|
|
},
|
2023-09-18 12:35:21 +00:00
|
|
|
delete: {
|
|
|
|
label: 'Deletes',
|
|
|
|
selected: false,
|
2023-04-18 11:16:52 +00:00
|
|
|
},
|
2023-09-18 12:35:21 +00:00
|
|
|
select: {
|
|
|
|
label: 'Accesses',
|
|
|
|
selected: false,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2023-09-28 11:45:14 +00:00
|
|
|
let validations = models;
|
2023-09-18 12:35:21 +00:00
|
|
|
let pointRecord = ref(null);
|
|
|
|
let byRecord = ref(false);
|
|
|
|
const logTree = ref([]);
|
2023-04-18 11:16:52 +00:00
|
|
|
|
2023-09-18 12:35:21 +00:00
|
|
|
const actionsText = {
|
|
|
|
insert: 'Creates',
|
|
|
|
update: 'Edits',
|
|
|
|
delete: 'Deletes',
|
|
|
|
select: 'Accesses',
|
|
|
|
};
|
|
|
|
const actionsClass = {
|
|
|
|
insert: 'success',
|
|
|
|
update: 'warning',
|
|
|
|
delete: 'alert',
|
|
|
|
select: 'notice',
|
|
|
|
};
|
|
|
|
const actionsIcon = {
|
|
|
|
insert: 'add',
|
|
|
|
update: 'edit',
|
|
|
|
delete: 'remove',
|
|
|
|
select: 'visibility',
|
|
|
|
};
|
|
|
|
const validDate = new RegExp(
|
|
|
|
/^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])/.source +
|
|
|
|
/T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/.source
|
|
|
|
);
|
|
|
|
|
|
|
|
const filteredActions = ref([]);
|
|
|
|
const filteredWorkers = ref([]);
|
|
|
|
|
|
|
|
function castJsonValue(value) {
|
|
|
|
return typeof value === 'string' && validDate.test(value) ? new Date(value) : value;
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseProps(propNames, locale, vals, olds) {
|
|
|
|
const props = [];
|
|
|
|
|
|
|
|
for (const prop of propNames) {
|
|
|
|
if (prop.endsWith('$')) continue;
|
|
|
|
props.push({
|
|
|
|
name: prop,
|
|
|
|
nameI18n: useFirstUpper(locale.columns?.[prop]) || prop,
|
|
|
|
val: getVal(vals, prop),
|
|
|
|
old: olds && getVal(olds, prop),
|
|
|
|
});
|
2023-04-18 11:16:52 +00:00
|
|
|
}
|
2023-09-18 12:35:21 +00:00
|
|
|
props.sort((a, b) => a.nameI18n.localeCompare(b.nameI18n));
|
|
|
|
|
|
|
|
function getVal(vals, prop) {
|
|
|
|
let val;
|
|
|
|
let id;
|
|
|
|
const showProp = `${prop}$`;
|
|
|
|
|
|
|
|
if (vals[showProp] != null) {
|
|
|
|
val = vals[showProp];
|
|
|
|
id = vals[prop];
|
|
|
|
} else val = vals[prop];
|
2023-04-18 11:16:52 +00:00
|
|
|
|
2023-09-18 12:35:21 +00:00
|
|
|
return { val: castJsonValue(val), id };
|
2023-04-18 11:16:52 +00:00
|
|
|
}
|
|
|
|
|
2023-09-18 12:35:21 +00:00
|
|
|
return props;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getLogs(data) {
|
|
|
|
const logs = [];
|
|
|
|
let originLog = null;
|
|
|
|
let userLog = null;
|
|
|
|
let modelLog = null;
|
2023-09-26 09:49:57 +00:00
|
|
|
let prevLog;
|
2023-09-18 12:35:21 +00:00
|
|
|
let nLogs;
|
|
|
|
|
|
|
|
data.forEach((log) => {
|
2023-09-26 09:49:57 +00:00
|
|
|
const locale = validations[log.changedModel]?.locale || {};
|
2023-09-18 12:35:21 +00:00
|
|
|
|
|
|
|
// Origin
|
2023-09-26 09:49:57 +00:00
|
|
|
const originChanged = !prevLog || log.originFk != prevLog.originFk;
|
|
|
|
if (originChanged) {
|
2023-09-18 12:35:21 +00:00
|
|
|
logs.push((originLog = { originFk: log.originFk, logs: [] }));
|
|
|
|
prevLog = log;
|
|
|
|
}
|
|
|
|
// User
|
2023-09-26 09:49:57 +00:00
|
|
|
const userChanged = originChanged || log.userFk != prevLog.userFk;
|
|
|
|
if (userChanged) {
|
2023-09-18 12:35:21 +00:00
|
|
|
originLog.logs.push(
|
|
|
|
(userLog = {
|
|
|
|
user: log.user,
|
|
|
|
userFk: log.userFk,
|
|
|
|
logs: [],
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
// Model
|
2023-09-26 09:49:57 +00:00
|
|
|
const modelChanged =
|
|
|
|
userChanged ||
|
|
|
|
log.changedModel != prevLog.changedModel ||
|
|
|
|
log.changedModelId != prevLog.changedModelId ||
|
|
|
|
nLogs >= 6;
|
|
|
|
if (modelChanged) {
|
2023-09-18 12:35:21 +00:00
|
|
|
userLog.logs.push(
|
|
|
|
(modelLog = {
|
|
|
|
model: log.changedModel,
|
|
|
|
modelI18n: useFirstUpper(locale.name) || log.changedModel,
|
|
|
|
id: log.changedModelId,
|
|
|
|
showValue: log.changedModelValue,
|
|
|
|
logs: [],
|
|
|
|
})
|
|
|
|
);
|
|
|
|
nLogs = 0;
|
|
|
|
}
|
|
|
|
nLogs++;
|
|
|
|
modelLog.logs.push(log);
|
|
|
|
|
|
|
|
// Changes
|
|
|
|
const notDelete = log.action != 'delete';
|
|
|
|
const olds = (notDelete ? log.oldInstance : null) || {};
|
|
|
|
const vals = (notDelete ? log.newInstance : log.oldInstance) || {};
|
|
|
|
|
|
|
|
let propNames = Object.keys(olds).concat(Object.keys(vals));
|
|
|
|
propNames = [...new Set(propNames)];
|
|
|
|
|
|
|
|
log.props = parseProps(propNames, locale, vals, olds);
|
|
|
|
});
|
|
|
|
return logs;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function openPointRecord(id, modelLog) {
|
|
|
|
pointRecord.value = null;
|
|
|
|
const { data } = await axios.get(`${props.model}Logs/${id}/pitInstance`);
|
|
|
|
const propNames = Object.keys(data);
|
2023-09-26 09:49:57 +00:00
|
|
|
const locale = validations[modelLog.model]?.locale || {};
|
2023-09-18 12:35:21 +00:00
|
|
|
pointRecord.value = parseProps(propNames, locale, data);
|
|
|
|
}
|
|
|
|
async function setLogTree() {
|
|
|
|
filter.where = { and: [{ originFk: route.params.id }] };
|
|
|
|
const { data } = await axios.get(`${props.model}Logs`, {
|
|
|
|
params: { filter: JSON.stringify(filter) },
|
|
|
|
});
|
|
|
|
logTree.value = getLogs(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
function filterByRecord(modelLog) {
|
|
|
|
byRecord.value = true;
|
|
|
|
const { id, model } = modelLog;
|
2023-04-18 11:16:52 +00:00
|
|
|
|
2023-09-18 12:35:21 +00:00
|
|
|
searchInput.value = id;
|
|
|
|
selectedFilters.value.changedModelId = id;
|
|
|
|
selectedFilters.value.changedModel = model;
|
|
|
|
applyFilter();
|
2023-04-18 11:16:52 +00:00
|
|
|
}
|
|
|
|
|
2023-09-18 12:35:21 +00:00
|
|
|
async function applyFilter() {
|
|
|
|
filter.where = { and: [] };
|
|
|
|
if (
|
|
|
|
!selectedFilters.value.changedModel ||
|
|
|
|
(!selectedFilters.value.changedModelValue &&
|
|
|
|
!selectedFilters.value.changedModelId)
|
|
|
|
)
|
|
|
|
byRecord.value = false;
|
|
|
|
|
|
|
|
if (!byRecord.value) filter.where.and.push({ originFk: route.params.id });
|
|
|
|
|
|
|
|
if (Object.keys(selectedFilters.value).length) {
|
|
|
|
filter.where.and.push(selectedFilters.value);
|
|
|
|
}
|
|
|
|
|
|
|
|
const { data } = await axios.get(`${props.model}Logs`, {
|
|
|
|
params: { filter: JSON.stringify(filter) },
|
|
|
|
});
|
|
|
|
|
|
|
|
logTree.value = getLogs(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
function setDate(type) {
|
|
|
|
const from = date.value
|
|
|
|
? useIso8601(date.value.split('-').reverse().join('-'))
|
|
|
|
: undefined;
|
|
|
|
const to = dateTo.value
|
|
|
|
? useIso8601(`${dateTo.value.split('-').reverse().join('-')} 21:59:59.999`)
|
|
|
|
: useIso8601(`${date.value.split('-').reverse().join('-')} 21:59:59.999`);
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case 'from':
|
|
|
|
return { between: [from, to] };
|
|
|
|
case 'to': {
|
|
|
|
if (date.value) {
|
|
|
|
return {
|
|
|
|
between: [from, to],
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
return { lte: to };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function selectFilter(type, dateType) {
|
|
|
|
const filter = {};
|
|
|
|
const actions = { inq: [] };
|
|
|
|
let reload = true;
|
|
|
|
|
|
|
|
if (type === 'search') {
|
|
|
|
if (/^\s*[0-9]+\s*$/.test(searchInput.value) || props.byRecord) {
|
|
|
|
selectedFilters.value.changedModelId = searchInput.value.trim();
|
|
|
|
} else if (!searchInput.value) {
|
|
|
|
selectedFilters.value.changedModelId = undefined;
|
|
|
|
selectedFilters.value.changedModelValue = undefined;
|
|
|
|
} else {
|
|
|
|
selectedFilters.value.changedModelValue = { like: `%${searchInput.value}%` };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (type === 'action' && selectedFilters.value.changedModel === null) {
|
|
|
|
selectedFilters.value.changedModel = undefined;
|
|
|
|
reload = false;
|
|
|
|
}
|
|
|
|
if (type === 'userRadio') {
|
|
|
|
selectedFilters.value.userFk = userRadio.value;
|
|
|
|
}
|
|
|
|
if (type === 'change') {
|
|
|
|
if (changeInput.value)
|
|
|
|
selectedFilters.value.or = [
|
|
|
|
{ oldJson: { like: `%${changeInput.value}%` } },
|
|
|
|
{ newJson: { like: `%${changeInput.value}%` } },
|
|
|
|
{ description: { like: `%${changeInput.value}%` } },
|
|
|
|
];
|
|
|
|
else selectedFilters.value.or = undefined;
|
|
|
|
}
|
|
|
|
if (type === 'userSelect') {
|
|
|
|
selectedFilters.value.userFk =
|
|
|
|
userSelect.value !== null ? userSelect.value : undefined;
|
|
|
|
}
|
|
|
|
if (type === 'date') {
|
|
|
|
if (!date.value && !dateTo.value) {
|
|
|
|
selectedFilters.value.creationDate = undefined;
|
|
|
|
} else if (dateType === 'to') {
|
|
|
|
selectedFilters.value.creationDate = setDate('to');
|
|
|
|
} else if (dateType === 'from') {
|
|
|
|
selectedFilters.value.creationDate = setDate('from');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Object.keys(checkboxOptions.value).forEach((key) => {
|
|
|
|
if (checkboxOptions.value[key].selected) actions.inq.push(key);
|
|
|
|
});
|
|
|
|
selectedFilters.value.action = actions.inq.length ? actions : undefined;
|
|
|
|
|
|
|
|
Object.keys(selectedFilters.value).forEach((key) => {
|
|
|
|
if (selectedFilters.value[key]) filter[key] = selectedFilters.value[key];
|
|
|
|
});
|
|
|
|
|
|
|
|
if (reload) applyFilter(filter);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function clearFilter() {
|
|
|
|
selectedFilters.value = {};
|
|
|
|
byRecord.value = false;
|
|
|
|
userSelect.value = undefined;
|
|
|
|
searchInput.value = undefined;
|
|
|
|
changeInput.value = undefined;
|
|
|
|
date.value = undefined;
|
|
|
|
dateTo.value = undefined;
|
|
|
|
Object.keys(checkboxOptions.value).forEach(
|
|
|
|
(opt) => (checkboxOptions.value[opt].selected = false)
|
|
|
|
);
|
|
|
|
await applyFilter();
|
|
|
|
}
|
|
|
|
|
|
|
|
function filterFn(val, update, abortFn, type) {
|
|
|
|
if (!val) {
|
|
|
|
update(() => {
|
|
|
|
if (type === 'actions') filteredActions.value = actions.value;
|
|
|
|
if (type === 'workers') filteredWorkers.value = workers.value;
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
update(() => {
|
|
|
|
const needle = val.toLowerCase();
|
|
|
|
if (type === 'actions')
|
|
|
|
filteredActions.value = actions.value.filter((item) =>
|
2023-09-26 09:49:57 +00:00
|
|
|
t(`models.${item}`).toLowerCase().includes(needle)
|
2023-09-18 12:35:21 +00:00
|
|
|
);
|
|
|
|
if (type === 'workers') {
|
|
|
|
if (isNaN(needle))
|
|
|
|
filteredWorkers.value = workers.value.filter(
|
|
|
|
(item) =>
|
|
|
|
item.name.toLowerCase().includes(needle) ||
|
|
|
|
item.nickname.toLowerCase().includes(needle)
|
|
|
|
);
|
|
|
|
else
|
|
|
|
filteredWorkers.value = workers.value.filter((item) => item.id == needle);
|
|
|
|
}
|
|
|
|
});
|
2023-04-18 11:16:52 +00:00
|
|
|
}
|
2023-09-18 12:35:21 +00:00
|
|
|
|
|
|
|
setLogTree();
|
2023-04-18 11:16:52 +00:00
|
|
|
</script>
|
|
|
|
<template>
|
2023-09-18 12:35:21 +00:00
|
|
|
<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'] }"
|
|
|
|
@on-fetch="(data) => (actions = data.map((item) => item.changedModel))"
|
|
|
|
auto-load
|
|
|
|
/>
|
|
|
|
<div
|
|
|
|
class="column items-center logs origin-log"
|
|
|
|
v-for="(originLog, originLogIndex) in logTree"
|
|
|
|
:key="originLogIndex"
|
|
|
|
>
|
|
|
|
<QItem class="origin-info items-center q-my-md" v-if="logTree.length > 1">
|
2023-09-26 09:49:57 +00:00
|
|
|
<h6 class="origin-id text-grey">
|
|
|
|
{{ useFirstUpper(validations[props.model].locale.name) }}
|
2023-09-18 12:35:21 +00:00
|
|
|
#{{ originLog.originFk }}
|
|
|
|
</h6>
|
2023-09-26 09:49:57 +00:00
|
|
|
<div class="line bg-grey"></div>
|
2023-09-18 12:35:21 +00:00
|
|
|
</QItem>
|
|
|
|
<div
|
|
|
|
class="user-log q-mb-sm row"
|
|
|
|
v-for="(userLog, userIndex) in originLog.logs"
|
|
|
|
:key="userIndex"
|
|
|
|
>
|
|
|
|
<div class="timeline">
|
|
|
|
<div class="user-avatar">
|
|
|
|
<VnAvatar
|
|
|
|
class="cursor-pointer"
|
|
|
|
:worker="userLog.user.id"
|
|
|
|
:title="userLog.user.nickname"
|
|
|
|
/>
|
|
|
|
<WorkerDescriptorProxy
|
|
|
|
v-if="userLog.user.image"
|
|
|
|
:id="userLog.user.id"
|
|
|
|
/>
|
|
|
|
</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"
|
|
|
|
>
|
|
|
|
<QItemSection>
|
|
|
|
<QItemLabel class="model-info q-mb-xs" v-if="!byRecord">
|
|
|
|
<QChip
|
2023-04-18 11:16:52 +00:00
|
|
|
dense
|
2023-09-18 12:35:21 +00:00
|
|
|
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"
|
|
|
|
>
|
|
|
|
{{ t(modelLog.modelI18n) }}
|
|
|
|
</QChip>
|
|
|
|
<span class="model-id" v-if="modelLog.id"
|
|
|
|
>#{{ modelLog.id }}</span
|
|
|
|
>
|
|
|
|
<span class="model-value" :title="modelLog.showValue">
|
|
|
|
{{ modelLog.showValue }}
|
|
|
|
</span>
|
|
|
|
<QBtn
|
2023-04-18 11:16:52 +00:00
|
|
|
flat
|
2023-09-18 12:35:21 +00:00
|
|
|
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"
|
|
|
|
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"
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
class="date text-grey text-caption q-mr-sm"
|
|
|
|
:title="
|
|
|
|
toDateHour(log.creationDate) ??
|
|
|
|
`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="xs"
|
|
|
|
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"
|
2023-04-18 11:16:52 +00:00
|
|
|
>
|
2023-09-18 12:35:21 +00:00
|
|
|
<QIcon
|
|
|
|
class="cursor-pointer q-mr-md"
|
|
|
|
color="grey"
|
|
|
|
name="expand_more"
|
|
|
|
:title="t('globals.details')"
|
|
|
|
size="sm"
|
|
|
|
@click="log.expand = !log.expand"
|
|
|
|
/>
|
|
|
|
<QList v-if="log.props.length" class="attributes">
|
|
|
|
<QItem 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">
|
|
|
|
,
|
|
|
|
</span>
|
|
|
|
</span>
|
|
|
|
</QItem>
|
|
|
|
<QItem
|
|
|
|
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"
|
2023-04-18 11:16:52 +00:00
|
|
|
>
|
2023-09-18 12:35:21 +00:00
|
|
|
<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>
|
|
|
|
</QItem>
|
|
|
|
</QList>
|
|
|
|
<span v-if="!log.props.length" class="description">
|
|
|
|
{{ log.description }}
|
|
|
|
</span>
|
|
|
|
</QCardSection>
|
|
|
|
</QCard>
|
|
|
|
</QItemSection>
|
|
|
|
</QItem>
|
|
|
|
</QList>
|
|
|
|
</div>
|
2023-04-18 11:16:52 +00:00
|
|
|
</div>
|
|
|
|
<Teleport v-if="stateStore.isHeaderMounted()" to="#actions-append">
|
|
|
|
<div class="row q-gutter-x-sm">
|
2023-09-18 12:35:21 +00:00
|
|
|
<QBtn
|
|
|
|
flat
|
|
|
|
@click.stop="stateStore.toggleRightDrawer()"
|
|
|
|
round
|
|
|
|
dense
|
|
|
|
icon="menu"
|
|
|
|
>
|
2023-04-18 11:16:52 +00:00
|
|
|
<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">
|
2023-09-18 12:35:21 +00:00
|
|
|
<QList dense>
|
|
|
|
<QSeparator />
|
|
|
|
<QItem class="q-mt-sm">
|
|
|
|
<QInput
|
|
|
|
:label="t('globals.search')"
|
|
|
|
v-model="searchInput"
|
|
|
|
class="full-width"
|
|
|
|
clearable
|
|
|
|
clear-icon="close"
|
|
|
|
@keyup.enter="() => selectFilter('search')"
|
|
|
|
@focusout="() => selectFilter('search')"
|
|
|
|
@clear="() => selectFilter('search')"
|
|
|
|
>
|
|
|
|
<template #append>
|
|
|
|
<QIcon name="info" class="cursor-pointer">
|
|
|
|
<QTooltip>{{ t('tooltips.search') }}</QTooltip>
|
|
|
|
</QIcon>
|
|
|
|
</template>
|
|
|
|
</QInput>
|
|
|
|
</QItem>
|
|
|
|
<QItem class="q-mt-sm">
|
|
|
|
<QSelect
|
|
|
|
class="full-width"
|
|
|
|
v-model="selectedFilters.changedModel"
|
|
|
|
@update:model-value="selectFilter('action')"
|
|
|
|
:options="filteredActions"
|
|
|
|
:label="t('globals.entity')"
|
|
|
|
use-input
|
|
|
|
clearable
|
|
|
|
clear-icon="close"
|
|
|
|
@filter="
|
|
|
|
(val, update, abortFn) =>
|
|
|
|
filterFn(val, update, abortFn, 'actions')
|
|
|
|
"
|
|
|
|
@clear="() => selectFilter('action')"
|
|
|
|
>
|
|
|
|
<template #option="{ opt, index, itemProps }">
|
|
|
|
<QItem :index="index" v-bind="itemProps">
|
|
|
|
{{ t(`models.${opt}`) }}
|
|
|
|
</QItem>
|
|
|
|
</template>
|
|
|
|
<template #selected-item="{ opt }">
|
2023-09-26 09:49:57 +00:00
|
|
|
{{ t(`models.${opt}`) }}
|
2023-09-18 12:35:21 +00:00
|
|
|
</template>
|
|
|
|
</QSelect>
|
|
|
|
</QItem>
|
|
|
|
<QItem class="q-mt-sm">
|
|
|
|
<QOptionGroup
|
|
|
|
size="sm"
|
|
|
|
v-model="userRadio"
|
|
|
|
:options="userTypes"
|
|
|
|
color="primary"
|
|
|
|
@update:model-value="selectFilter('userRadio')"
|
|
|
|
right-label
|
|
|
|
>
|
|
|
|
<template #label="{ label }">
|
|
|
|
{{ t(`Users.${label}`) }}
|
|
|
|
</template>
|
|
|
|
</QOptionGroup>
|
|
|
|
</QItem>
|
|
|
|
<QItem class="q-mt-sm">
|
|
|
|
<QItemSection v-if="!workers">
|
|
|
|
<QSkeleton type="QInput" class="full-width" />
|
|
|
|
</QItemSection>
|
|
|
|
<QItemSection v-if="workers">
|
|
|
|
<QSelect
|
|
|
|
:label="t('globals.user')"
|
|
|
|
v-model="userSelect"
|
|
|
|
@update:model-value="selectFilter('userSelect')"
|
|
|
|
:options="filteredWorkers"
|
|
|
|
option-value="id"
|
|
|
|
option-label="name"
|
|
|
|
emit-value
|
|
|
|
map-options
|
|
|
|
use-input
|
|
|
|
clearable
|
|
|
|
clear-icon="close"
|
|
|
|
@filter="
|
|
|
|
(val, update, abortFn) =>
|
|
|
|
filterFn(val, update, abortFn, 'workers')
|
|
|
|
"
|
|
|
|
>
|
|
|
|
<template #option="{ opt, itemProps }">
|
|
|
|
<QItem
|
|
|
|
v-bind="itemProps"
|
|
|
|
class="q-pa-xs row items-center"
|
|
|
|
>
|
|
|
|
<QItemSection class="col-3 items-center">
|
|
|
|
<VnAvatar :worker="opt.id" />
|
|
|
|
</QItemSection>
|
|
|
|
<QItemSection class="col-9 justify-center">
|
|
|
|
<span>{{ opt.name }}</span>
|
|
|
|
<span class="text-grey">{{ opt.nickname }}</span>
|
|
|
|
</QItemSection>
|
|
|
|
</QItem>
|
|
|
|
</template>
|
|
|
|
</QSelect>
|
|
|
|
</QItemSection>
|
|
|
|
</QItem>
|
|
|
|
<QItem class="q-mt-sm">
|
|
|
|
<QInput
|
|
|
|
:label="t('globals.changes')"
|
|
|
|
v-model="changeInput"
|
|
|
|
class="full-width"
|
|
|
|
clearable
|
|
|
|
clear-icon="close"
|
|
|
|
@keyup.enter="selectFilter('change')"
|
|
|
|
@focusout="selectFilter('change')"
|
|
|
|
@clear="selectFilter('change')"
|
|
|
|
>
|
|
|
|
<template #append>
|
|
|
|
<QIcon name="info" class="cursor-pointer">
|
|
|
|
<QTooltip max-width="250px">{{
|
|
|
|
t('tooltips.changes')
|
|
|
|
}}</QTooltip>
|
|
|
|
</QIcon>
|
|
|
|
</template>
|
|
|
|
</QInput>
|
|
|
|
</QItem>
|
|
|
|
<QItem
|
|
|
|
:class="index == 'create' ? 'q-mt-md' : 'q-mt-xs'"
|
|
|
|
v-for="(checkboxOption, index) in checkboxOptions"
|
|
|
|
:key="index"
|
|
|
|
>
|
|
|
|
<QCheckbox
|
|
|
|
size="sm"
|
|
|
|
v-model="checkboxOption.selected"
|
|
|
|
:label="t(`actions.${checkboxOption.label}`)"
|
|
|
|
@update:model-value="selectFilter"
|
|
|
|
/>
|
|
|
|
</QItem>
|
|
|
|
<QItem class="q-mt-sm">
|
|
|
|
<QInput
|
|
|
|
class="full-width"
|
|
|
|
:label="t('globals.date')"
|
|
|
|
@click="dateDialog = true"
|
|
|
|
@focus="(evt) => evt.target.blur()"
|
|
|
|
@clear="selectFilter('date', 'to')"
|
|
|
|
v-model="date"
|
|
|
|
clearable
|
|
|
|
clear-icon="close"
|
|
|
|
/>
|
|
|
|
</QItem>
|
|
|
|
<QItem class="q-mt-sm">
|
|
|
|
<QInput
|
|
|
|
class="full-width"
|
|
|
|
:label="t('to')"
|
|
|
|
@click="dateToDialog = true"
|
|
|
|
@focus="(evt) => evt.target.blur()"
|
|
|
|
@clear="selectFilter('date', 'from')"
|
|
|
|
v-model="dateTo"
|
|
|
|
clearable
|
|
|
|
clear-icon="close"
|
|
|
|
/>
|
|
|
|
</QItem>
|
|
|
|
</QList>
|
2023-04-18 11:16:52 +00:00
|
|
|
</QScrollArea>
|
|
|
|
</QDrawer>
|
2023-09-18 12:35:21 +00:00
|
|
|
<QDialog v-model="dateDialog">
|
|
|
|
<QDate
|
|
|
|
:years-in-month-view="false"
|
|
|
|
v-model="date"
|
|
|
|
dense
|
|
|
|
flat
|
|
|
|
minimal
|
|
|
|
@update:model-value="
|
|
|
|
(value) => {
|
|
|
|
dateDialog = false;
|
|
|
|
const formatDate = toDateString(new Date(value));
|
|
|
|
date = formatDate.split('-').reverse().join('-');
|
|
|
|
selectFilter('date', 'from');
|
|
|
|
}
|
|
|
|
"
|
|
|
|
/>
|
|
|
|
</QDialog>
|
|
|
|
<QDialog v-model="dateToDialog">
|
|
|
|
<QDate
|
|
|
|
v-model="dateTo"
|
|
|
|
dense
|
|
|
|
flat
|
|
|
|
minimal
|
|
|
|
@update:model-value="
|
|
|
|
(value) => {
|
|
|
|
dateToDialog = false;
|
|
|
|
const formatDate = toDateString(new Date(value));
|
|
|
|
dateTo = formatDate.split('-').reverse().join('-');
|
|
|
|
selectFilter('date', 'to');
|
|
|
|
}
|
|
|
|
"
|
|
|
|
/>
|
|
|
|
</QDialog>
|
|
|
|
<QPageSticky position="bottom-right" :offset="[25, 25]">
|
|
|
|
<QBtn
|
|
|
|
v-if="Object.values(selectedFilters).some((filter) => filter !== undefined)"
|
|
|
|
color="primary"
|
|
|
|
icon="filter_alt_off"
|
|
|
|
size="md"
|
|
|
|
round
|
|
|
|
@click="clearFilter"
|
|
|
|
/>
|
|
|
|
</QPageSticky>
|
2023-04-18 11:16:52 +00:00
|
|
|
</template>
|
2023-09-18 12:35:21 +00:00
|
|
|
<style lang="scss">
|
|
|
|
.q-timeline__subtitle {
|
|
|
|
opacity: 1 !important;
|
|
|
|
}
|
|
|
|
.logs {
|
|
|
|
.vn-label-value {
|
|
|
|
.label {
|
|
|
|
margin-right: 5px;
|
|
|
|
color: var(--vn-label);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.q-avatar {
|
|
|
|
font-size: 38px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|
2023-04-18 11:16:52 +00:00
|
|
|
<style lang="scss" scoped>
|
2023-09-18 12:35:21 +00:00
|
|
|
.q-card {
|
|
|
|
background-color: var(--vn-gray);
|
|
|
|
}
|
|
|
|
.q-item {
|
|
|
|
min-height: 0px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.origin-log {
|
|
|
|
&:first-child > .origin-info {
|
|
|
|
margin-top: 0;
|
|
|
|
}
|
|
|
|
& > .origin-info {
|
2023-09-26 09:49:57 +00:00
|
|
|
width: 100%;
|
|
|
|
max-width: 42em;
|
|
|
|
margin-top: 28px;
|
2023-09-18 12:35:21 +00:00
|
|
|
gap: 6px;
|
|
|
|
|
|
|
|
& > .origin-id {
|
|
|
|
overflow: hidden;
|
|
|
|
white-space: nowrap;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
margin: 0;
|
|
|
|
}
|
|
|
|
& > .line {
|
|
|
|
flex-grow: 1;
|
|
|
|
height: 2px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.user-log {
|
2023-09-26 09:49:57 +00:00
|
|
|
width: 100%;
|
|
|
|
max-width: 40em;
|
2023-09-18 12:35:21 +00:00
|
|
|
|
|
|
|
& > .timeline {
|
|
|
|
position: relative;
|
|
|
|
padding-right: 5px;
|
|
|
|
width: 50px;
|
|
|
|
min-width: 38px;
|
|
|
|
& > .arrow {
|
|
|
|
height: 8px;
|
|
|
|
width: 8px;
|
|
|
|
position: absolute;
|
|
|
|
transform: rotateY(0deg) rotate(45deg);
|
|
|
|
top: 15px;
|
|
|
|
right: -4px;
|
|
|
|
z-index: 1;
|
|
|
|
}
|
|
|
|
& > .user-avatar {
|
|
|
|
padding: 8px 0;
|
|
|
|
margin-top: -8px;
|
|
|
|
position: sticky;
|
|
|
|
top: 64px;
|
|
|
|
}
|
|
|
|
& > .line {
|
|
|
|
position: absolute;
|
|
|
|
background-color: $primary;
|
|
|
|
width: 2px;
|
|
|
|
left: 23px;
|
|
|
|
z-index: -1;
|
|
|
|
top: 0;
|
|
|
|
bottom: -8px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
&:last-child > .timeline > .line {
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
& > .user-changes {
|
|
|
|
flex-grow: 1;
|
|
|
|
overflow: hidden;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.model-log {
|
|
|
|
.model-info {
|
|
|
|
overflow: hidden;
|
|
|
|
white-space: nowrap;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
.model-value {
|
|
|
|
font-style: italic;
|
|
|
|
}
|
|
|
|
.model-id {
|
|
|
|
color: var(--vn-label);
|
|
|
|
font-size: 0.9rem;
|
|
|
|
}
|
|
|
|
.q-btn {
|
|
|
|
float: right;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.changes-log {
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
&:last-child {
|
|
|
|
margin-bottom: 0;
|
|
|
|
}
|
|
|
|
.change-info {
|
|
|
|
overflow: hidden;
|
2023-09-28 11:56:21 +00:00
|
|
|
background-color: var(--vn-dark);
|
2023-09-18 12:35:21 +00:00
|
|
|
& > .date {
|
|
|
|
overflow: hidden;
|
|
|
|
white-space: nowrap;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
}
|
|
|
|
& > div {
|
|
|
|
white-space: nowrap;
|
|
|
|
.action {
|
|
|
|
color: black;
|
|
|
|
border-radius: 50%;
|
|
|
|
padding: 3px;
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
|
|
&.notice {
|
|
|
|
background-color: $info;
|
|
|
|
}
|
|
|
|
&.success {
|
|
|
|
background-color: $positive;
|
|
|
|
}
|
|
|
|
&.warning {
|
|
|
|
background-color: $warning;
|
|
|
|
}
|
|
|
|
&.alert {
|
|
|
|
background-color: $negative;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
& > .change-detail {
|
|
|
|
background-color: var(--vn-gray);
|
|
|
|
overflow: hidden;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
white-space: nowrap;
|
|
|
|
box-sizing: border-box;
|
|
|
|
& > .q-icon {
|
|
|
|
float: right;
|
|
|
|
transition-property: transform, background-color;
|
|
|
|
transition-duration: 150ms;
|
|
|
|
margin: 0;
|
|
|
|
}
|
|
|
|
&.expanded {
|
|
|
|
text-overflow: initial;
|
|
|
|
white-space: initial;
|
|
|
|
|
|
|
|
& > .q-icon {
|
|
|
|
transform: rotate(180deg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
& > .no-changes {
|
|
|
|
font-style: italic;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.q-menu {
|
|
|
|
display: block;
|
|
|
|
|
|
|
|
& > .loading {
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
}
|
|
|
|
& > .q-card {
|
|
|
|
min-width: 180px;
|
|
|
|
max-width: 400px;
|
|
|
|
|
|
|
|
& > .header {
|
|
|
|
color: $dark;
|
|
|
|
overflow: hidden;
|
|
|
|
white-space: nowrap;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
}
|
|
|
|
}
|
2023-04-18 11:16:52 +00:00
|
|
|
}
|
|
|
|
</style>
|
|
|
|
<i18n>
|
|
|
|
en:
|
2023-09-18 12:35:21 +00:00
|
|
|
to: To
|
|
|
|
pointRecord: View record at this point in time
|
|
|
|
recordChanges: show all record changes
|
|
|
|
tooltips:
|
|
|
|
search: Search by id or concept
|
|
|
|
changes: Search by changes
|
2023-04-18 11:16:52 +00:00
|
|
|
actions:
|
2023-09-18 12:35:21 +00:00
|
|
|
Creates: Creates
|
|
|
|
Edits: Edits
|
|
|
|
Deletes: Deletes
|
|
|
|
Accesses: Accesses
|
2023-04-18 11:16:52 +00:00
|
|
|
models:
|
2023-09-18 12:35:21 +00:00
|
|
|
Claim: Reclamación
|
|
|
|
ClaimDms: Documento
|
|
|
|
ClaimBeginning: Comienzo
|
|
|
|
ClaimObservation: Observación
|
|
|
|
Users:
|
|
|
|
User: Usuario
|
|
|
|
All: Todo
|
|
|
|
System: Sistema
|
2023-04-18 11:16:52 +00:00
|
|
|
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:
|
2023-09-18 12:35:21 +00:00
|
|
|
to: Hasta
|
|
|
|
pointRecord: Ver el registro en este punto
|
|
|
|
recordChanges: Mostrar todos los cambios realizados en el registro
|
|
|
|
tooltips:
|
|
|
|
search: Buscar por identificador o concepto
|
|
|
|
changes: Buscar por cambios. Los atributos deben buscarse por su nombre interno, para obtenerlo situar el cursor sobre el atributo.
|
2023-04-18 11:16:52 +00:00
|
|
|
Audit logs: Registros de auditoría
|
|
|
|
Property: Propiedad
|
|
|
|
Before: Antes
|
|
|
|
After: Después
|
|
|
|
Yes: Si
|
|
|
|
Nothing: Nada
|
|
|
|
actions:
|
2023-09-18 12:35:21 +00:00
|
|
|
Creates: Crea
|
|
|
|
Edits: Modifica
|
|
|
|
Deletes: Elimina
|
|
|
|
Accesses: Accede
|
|
|
|
Users:
|
|
|
|
User: Usuario
|
|
|
|
All: Todo
|
|
|
|
System: Sistema
|
2023-04-18 11:16:52 +00:00
|
|
|
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>
|