diff --git a/src/components/VnTable/VnOrder.vue b/src/components/VnTable/VnOrder.vue
index 47ed9acf4..d39fc8641 100644
--- a/src/components/VnTable/VnOrder.vue
+++ b/src/components/VnTable/VnOrder.vue
@@ -70,7 +70,7 @@ function textAlignToFlex(textAlign) {
:style="textAlignToFlex(align)"
>
{{ label }}
-
+
diff --git a/src/components/VnTable/VnTableFilter.vue b/src/components/VnTable/VnTableFilter.vue
index 79b903e54..109e2b77e 100644
--- a/src/components/VnTable/VnTableFilter.vue
+++ b/src/components/VnTable/VnTableFilter.vue
@@ -26,7 +26,12 @@ function columnName(col) {
}
-
+
-
+
+
+
-import { ref, onUnmounted, watch } from 'vue';
+import { ref, onMounted, onUnmounted, watch, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import axios from 'axios';
@@ -11,11 +11,11 @@ import { useCapitalize } from 'src/composables/useCapitalize';
import { useValidator } from 'src/composables/useValidator';
import VnAvatar from '../ui/VnAvatar.vue';
import VnLogValue from './VnLogValue.vue';
-import FetchData from '../FetchData.vue';
-import VnSelect from './VnSelect.vue';
import VnUserLink from '../ui/VnUserLink.vue';
import VnPaginate from '../ui/VnPaginate.vue';
+import VnLogFilter from 'src/components/common/VnLogFilter.vue';
import RightMenu from './RightMenu.vue';
+import { useFilterParams } from 'src/composables/useFilterParams';
const stateStore = useStateStore();
const validationsStore = useValidator();
@@ -72,39 +72,8 @@ const filter = {
};
const paginate = ref();
-const actions = ref();
-const changeInput = ref();
-const searchInput = ref();
-const userRadio = ref();
-const userSelect = ref();
-const dateFrom = ref();
-const dateFromDialog = 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,
- },
- update: {
- label: 'Edits',
- selected: false,
- },
- delete: {
- label: 'Deletes',
- selected: false,
- },
- select: {
- label: 'Accesses',
- selected: false,
- },
-});
+const dataKey = computed(() => `${props.model}Log`);
+const userParams = ref(useFilterParams(dataKey.value).params);
let validations = models;
let pointRecord = ref(null);
@@ -246,131 +215,54 @@ async function setLogTree(data) {
function filterByRecord(modelLog) {
byRecord.value = true;
const { id, model } = modelLog;
-
- searchInput.value = id;
- selectedFilters.value.changedModelId = id;
- selectedFilters.value.changedModel = model;
- applyFilter();
+ applyFilter({ changedModelId: id, changedModel: model });
}
-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);
- }
-
- paginate.value.fetch({ filter });
+async function applyFilter(params = {}) {
+ paginate.value.arrayData.applyFilter({
+ filter: {},
+ params: { originFk: route.params.id, ...params },
+ });
}
-function setDate(type) {
- let from = dateFrom.value
- ? date.formatDate(dateFrom.value.split('-').reverse().join('-'), 'YYYY-MM-DD')
- : undefined;
- from = date.adjustDate(from, { hour: 0, minute: 0, second: 0, millisecond: 0 }, true);
-
- let to = dateTo.value
- ? date.formatDate(dateTo.value.split('-').reverse().join('-'), 'YYYY-MM-DD')
- : date.formatDate(dateFrom.value.split('-').reverse().join('-'), 'YYYY-MM-DD');
- to = date.adjustDate(
- to,
- { hour: 21, minute: 59, second: 59, millisecond: 999 },
- true,
- );
-
- switch (type) {
- case 'from':
- return { between: [from, to] };
- case 'to': {
- if (dateFrom.value) {
+function exprBuilder(param, value) {
+ switch (param) {
+ case 'changedModelValue':
+ return { [param]: { like: `%${value}%` } };
+ case 'change':
+ if (value)
return {
- between: [from, to],
+ or: [
+ { oldJson: { like: `%${value}%` } },
+ { newJson: { like: `%${value}%` } },
+ { description: { like: `%${value}%` } },
+ ],
};
- }
- return { lte: to };
- }
+ break;
+ case 'action':
+ if (value?.length) return { [param]: { inq: value } };
+ break;
+ case 'from':
+ return { creationDate: { gte: value } };
+ case 'to':
+ return { creationDate: { lte: value } };
+ case 'userType':
+ if (value === 'User') return { userFk: { neq: null } };
+ if (value === 'System') return { userFk: null };
+ break;
+ default:
+ return { [param]: value };
}
}
-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;
- }
- 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 (!dateFrom.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;
- dateFrom.value = undefined;
- dateTo.value = undefined;
- userRadio.value = undefined;
- Object.keys(checkboxOptions.value).forEach(
- (opt) => (checkboxOptions.value[opt].selected = false),
- );
await applyFilter();
}
+onMounted(() => {
+ stateStore.rightDrawerChangeValue(true);
+});
onUnmounted(() => {
stateStore.rightDrawer = false;
});
@@ -383,32 +275,18 @@ watch(
);
-
- (actions = data.map((item) => {
- const changedModel = item.changedModel;
- return {
- locale: useCapitalize(
- validations[changedModel]?.locale?.name ?? changedModel,
- ),
- value: changedModel,
- };
- }))
- "
- auto-load
- />
{{ t(modelLog.modelI18n) }}
@@ -580,6 +459,7 @@ watch(
}`,
)
"
+ data-cy="vnLog-action-icon"
/>
@@ -697,181 +577,12 @@ watch(
-
-
-
- selectFilter('search')"
- @focusout="() => selectFilter('search')"
- @clear="() => selectFilter('search')"
- >
-
-
- {{ t('tooltips.search') }}
-
-
-
-
-
-
-
-
-
-
- {{ t(`Users.${label}`) }}
-
-
-
-
-
-
-
-
-
-
-
-
- {{ opt.name }}
- {{ opt.nickname }}
-
-
-
-
-
-
-
-
-
-
- {{
- t('tooltips.changes')
- }}
-
-
-
-
-
-
-
-
- evt.target.blur()"
- @clear="selectFilter('date', 'to')"
- v-model="dateFrom"
- clearable
- filled
- clear-icon="close"
- />
-
-
- evt.target.blur()"
- @clear="selectFilter('date', 'from')"
- v-model="dateTo"
- clearable
- filled
- clear-icon="close"
- />
-
-
+
-
- {
- dateFromDialog = false;
- dateFrom = date.formatDate(value, 'DD-MM-YYYY');
- selectFilter('date', 'from');
- }
- "
- />
-
-
- {
- dateToDialog = false;
- dateTo = date.formatDate(value, 'DD-MM-YYYY');
- selectFilter('date', 'to');
- }
- "
- />
-
-import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
-import FetchData from 'components/FetchData.vue';
-import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
+import VnTableFilter from '../VnTable/VnTableFilter.vue';
+import VnSelect from './VnSelect.vue';
+import { useRoute } from 'vue-router';
+import VnInput from './VnInput.vue';
+import { ref, computed, watch } from 'vue';
+import VnInputDate from './VnInputDate.vue';
+import { useFilterParams } from 'src/composables/useFilterParams';
+import FetchData from '../FetchData.vue';
+import { useValidator } from 'src/composables/useValidator';
+import { useCapitalize } from 'src/composables/useCapitalize';
-const { t } = useI18n();
-const props = defineProps({
+const $props = defineProps({
dataKey: {
type: String,
- required: true,
+ default: null,
},
});
-const workers = ref();
+const { t } = useI18n();
+const route = useRoute();
+const validationsStore = useValidator();
+const { models } = validationsStore;
+const entities = ref([]);
+const editors = ref([]);
+const userParams = ref(useFilterParams($props.dataKey).params);
+let validations = models;
+const userTypes = [
+ { value: 'All', label: t(`Users.All`) },
+ { value: 'User', label: t(`Users.User`) },
+ { value: 'System', label: t(`Users.System`) },
+];
+const checkboxOptions = ref([
+ { name: 'insert', label: 'Creates', selected: false },
+ { name: 'update', label: 'Edits', selected: false },
+ { name: 'delete', label: 'Deletes', selected: false },
+ { name: 'select', label: 'Accesses', selected: false },
+]);
+const columns = computed(() => [
+ { name: 'changedModelValue' },
+ { name: 'changedModel' },
+ { name: 'userType', orderBy: false },
+ { name: 'userFk' },
+ { name: 'change', orderBy: false },
+ { name: 'action' },
+ { name: 'from', orderBy: 'creationDate' },
+ { name: 'to', orderBy: 'creationDate' },
+]);
+
+const userParamsWatcher = watch(
+ () => userParams.value,
+ (params) => {
+ if (params.action) {
+ params.action.forEach((option) => {
+ checkboxOptions.value.find((o) => o.name === option).selected = true;
+ });
+ userParamsWatcher();
+ }
+ },
+);
+
+function getActions() {
+ const actions = checkboxOptions.value
+ .filter((option) => option.selected)
+ ?.map((o) => o.name);
+ return actions.length ? actions : null;
+}
(workers = data)"
+ :url="`${dataKey}s/${route.params.id}/models`"
+ :filter="{ order: ['changedModel'] }"
+ @on-fetch="
+ (data) =>
+ (entities = data.map((item) => {
+ const changedModel = item.changedModel;
+ return {
+ locale: useCapitalize(
+ validations[changedModel]?.locale?.name ?? changedModel,
+ ),
+ value: changedModel,
+ };
+ }))
+ "
auto-load
/>
-
-
-
-
{{ t(`params.${tag.label}`) }}:
-
{{ formatFn(tag.value) }}
+
(editors = data)"
+ auto-load
+ />
+
+
+
+
+
+ searchFn()"
+ dense
+ filled
+ data-cy="vnLog-entity"
+ />
+
+
+ {
+ params.userFk = null;
+ searchFn();
+ }
+ "
+ />
+
+
+ searchFn()"
+ :disable="params.userType === 'System'"
+ dense
+ filled
+ >
+
+
+
+
+
+
+ {{ opt.name }}
+ {{ opt.nickname }}
+
+
+
+
+
+
+
+
+
+
+ searchFn(undefined, 'action', getActions())
+ "
+ data-cy="vnLog-checkbox"
+ />
-
-
+
-
-
-
-
-
-
-
-
-
-
+ filled
+ @update:modelValue="() => searchFn()"
+ />
-
+
+ searchFn()"
+ />
+
+
-
-en:
- params:
- search: Contains
- userFk: User
- created: Created
es:
+ 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.
+ actions:
+ Creates: Crea
+ Edits: Modifica
+ Deletes: Elimina
+ Accesses: Accede
+ Users:
+ User: Usuario
+ All: Todo
+ System: Sistema
params:
- search: Contiene
- userFk: Usuario
- created: Creada
- User: Usuario
+ changedModel: Entity
+
+en:
+ tooltips:
+ search: Search by identifier or concept
+ changes: Search by changes. Attributes must be searched by their internal name, to get it place the cursor over the attribute.
+ actions:
+ Creates: Creates
+ Edits: Edits
+ Deletes: Deletes
+ Accesses: Accesses
+ Users:
+ User: User
+ All: All
+ System: System
+ params:
+ changedModel: Entidad
diff --git a/src/components/common/__tests__/VnLog.spec.js b/src/components/common/__tests__/VnLog.spec.js
index 53d2732a0..7f33578df 100644
--- a/src/components/common/__tests__/VnLog.spec.js
+++ b/src/components/common/__tests__/VnLog.spec.js
@@ -108,27 +108,4 @@ describe('VnLog', () => {
expect(vm.logTree[0].originFk).toEqual(1);
expect(vm.logTree[0].logs[0].user.name).toEqual('salesPerson');
});
-
- it('should correctly set the selectedFilters when filtering', () => {
- vm.searchInput = '1';
- vm.userSelect = '21';
- vm.checkboxOptions.insert.selected = true;
- vm.checkboxOptions.update.selected = true;
-
- vm.selectFilter('search');
- vm.selectFilter('userSelect');
-
- expect(vm.selectedFilters.changedModelId).toEqual('1');
- expect(vm.selectedFilters.userFk).toEqual('21');
- expect(vm.selectedFilters.action).toEqual({ inq: ['insert', 'update'] });
- });
-
- it('should correctly set the date from', () => {
- vm.dateFrom = '18-09-2023';
- vm.selectFilter('date', 'from');
- expect(vm.selectedFilters.creationDate.between).toEqual([
- new Date('2023-09-18T00:00:00.000Z'),
- new Date('2023-09-18T21:59:59.999Z'),
- ]);
- });
});
diff --git a/src/components/common/__tests__/VnLogFilter.spec.js b/src/components/common/__tests__/VnLogFilter.spec.js
new file mode 100644
index 000000000..a28fa85b1
--- /dev/null
+++ b/src/components/common/__tests__/VnLogFilter.spec.js
@@ -0,0 +1,28 @@
+import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest';
+import { createWrapper } from 'app/test/vitest/helper';
+import VnLogFilter from 'src/components/common/VnLogFilter.vue';
+
+describe('VnLogFilter', () => {
+ let vm;
+ beforeAll(async () => {
+ vm = createWrapper(VnLogFilter, {
+ props: {
+ dataKey: 'ClaimLog',
+ },
+ }).vm;
+ });
+
+ afterEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('should getActions selected', async () => {
+ vm.checkboxOptions.find((o) => o.name == 'insert').selected = true;
+ vm.checkboxOptions.find((o) => o.name == 'update').selected = true;
+
+ const actions = vm.getActions();
+
+ expect(actions.length).toEqual(2);
+ expect(actions).toEqual(['insert', 'update']);
+ });
+});
diff --git a/src/components/common/__tests__/VnNotes.spec.js b/src/components/common/__tests__/VnNotes.spec.js
index 2603bf03c..ea595060a 100644
--- a/src/components/common/__tests__/VnNotes.spec.js
+++ b/src/components/common/__tests__/VnNotes.spec.js
@@ -1,16 +1,6 @@
-import {
- describe,
- it,
- expect,
- vi,
- beforeAll,
- afterEach,
- beforeEach,
- afterAll,
-} from 'vitest';
+import { describe, it, expect, vi, afterEach, beforeEach, afterAll } from 'vitest';
import { createWrapper, axios } from 'app/test/vitest/helper';
import VnNotes from 'src/components/ui/VnNotes.vue';
-import vnDate from 'src/boot/vnDate';
describe('VnNotes', () => {
let vm;
@@ -18,6 +8,7 @@ describe('VnNotes', () => {
let spyFetch;
let postMock;
let patchMock;
+ let deleteMock;
let expectedInsertBody;
let expectedUpdateBody;
const defaultOptions = {
@@ -57,6 +48,7 @@ describe('VnNotes', () => {
beforeEach(() => {
postMock = vi.spyOn(axios, 'post');
patchMock = vi.spyOn(axios, 'patch');
+ deleteMock = vi.spyOn(axios, 'delete');
});
afterEach(() => {
@@ -153,4 +145,16 @@ describe('VnNotes', () => {
);
});
});
+
+ describe('delete', () => {
+ it('Should call axios.delete with url and vnPaginateRef.fetch', async () => {
+ generateWrapper();
+ createSpyFetch();
+
+ await vm.deleteNote({ id: 1 });
+
+ expect(deleteMock).toHaveBeenCalledWith(`${vm.$props.url}/1`);
+ expect(spyFetch).toHaveBeenCalled();
+ });
+ });
});
diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue
index 85cc8cde2..dc9e4e776 100644
--- a/src/components/ui/VnFilterPanel.vue
+++ b/src/components/ui/VnFilterPanel.vue
@@ -61,6 +61,10 @@ const $props = defineProps({
type: Object,
default: null,
},
+ showTagChips: {
+ type: Boolean,
+ default: true,
+ },
});
const emit = defineEmits([
@@ -88,13 +92,14 @@ const userOrders = ref(useFilterParams($props.dataKey).orders);
defineExpose({ search, params: userParams, remove });
const isLoading = ref(false);
-async function search(evt) {
+async function search(evt, name, value) {
try {
if (evt && $props.disableSubmitEvent) return;
store.filter.where = {};
isLoading.value = true;
const filter = { ...userParams.value, ...$props.modelValue };
+ if (name) filter[name] = value;
store.userParamsChanged = true;
await arrayData.addFilter({
params: filter,
@@ -214,7 +219,7 @@ const getLocale = (label) => {
-
+
diff --git a/src/components/ui/VnMoreOptions.vue b/src/components/ui/VnMoreOptions.vue
index 984e2b64f..bc81233d5 100644
--- a/src/components/ui/VnMoreOptions.vue
+++ b/src/components/ui/VnMoreOptions.vue
@@ -9,7 +9,7 @@
data-cy="descriptor-more-opts"
>
- {{ $t('components.cardDescriptor.moreOptions') }}
+ {{ $t('components.vnDescriptor.moreOptions') }}
diff --git a/src/components/ui/VnNotes.vue b/src/components/ui/VnNotes.vue
index b7e6ccbec..9cedbccfa 100644
--- a/src/components/ui/VnNotes.vue
+++ b/src/components/ui/VnNotes.vue
@@ -18,10 +18,10 @@ import VnInput from 'components/common/VnInput.vue';
const emit = defineEmits(['onFetch']);
-const $attrs = useAttrs();
-
-const isRequired = computed(() => {
- return Object.keys($attrs).includes('required');
+const originalAttrs = useAttrs();
+const $attrs = computed(() => {
+ const { required, deletable, ...rest } = originalAttrs;
+ return rest;
});
const $props = defineProps({
@@ -53,6 +53,11 @@ function handleClick(e) {
else insert();
}
+async function deleteNote(e) {
+ await axios.delete(`${$props.url}/${e.id}`);
+ await vnPaginateRef.value.fetch();
+}
+
async function insert() {
if (!newNote.text || ($props.selectType && !newNote.observationTypeFk)) return;
@@ -157,7 +162,7 @@ const handleObservationTypes = (data) => {
v-model="newNote.observationTypeFk"
option-label="description"
style="flex: 0.15"
- :required="isRequired"
+ :required="'required' in originalAttrs"
@keyup.enter.stop="insert"
/>
{
type="textarea"
:label="$props.justInput && newNote.text ? '' : t('Add note here...')"
filled
- size="lg"
autogrow
autofocus
@keyup.enter.stop="handleClick"
- :required="isRequired"
+ :required="'required' in originalAttrs"
clearable
>
@@ -239,6 +243,21 @@ const handleObservationTypes = (data) => {
+
+
+
+ {{ t('ticketNotes.removeNote') }}
+
+
+
diff --git a/src/components/ui/VnPaginate.vue b/src/components/ui/VnPaginate.vue
index b067381f6..7facb7916 100644
--- a/src/components/ui/VnPaginate.vue
+++ b/src/components/ui/VnPaginate.vue
@@ -215,6 +215,7 @@ defineExpose({
paginate,
userParams: arrayData.store.userParams,
currentFilter: arrayData.store.currentFilter,
+ arrayData,
});
diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js
index d1c1b01b8..363580148 100644
--- a/src/composables/useArrayData.js
+++ b/src/composables/useArrayData.js
@@ -189,7 +189,7 @@ export function useArrayData(key, userOptions) {
store.order = order;
resetPagination();
- fetch({});
+ await fetch({});
index++;
return { index, order };
diff --git a/src/composables/useFilterParams.js b/src/composables/useFilterParams.js
index 07dcdf99b..7c3f3bdeb 100644
--- a/src/composables/useFilterParams.js
+++ b/src/composables/useFilterParams.js
@@ -14,7 +14,7 @@ export function useFilterParams(key) {
watch(
() => arrayData.value.store?.currentFilter,
(val, oldValue) => (val || oldValue) && setUserParams(val),
- { immediate: true, deep: true }
+ { immediate: true, deep: true },
);
function parseOrder(urlOrders) {
@@ -54,7 +54,7 @@ export function useFilterParams(key) {
Object.assign(params, item);
});
delete params[key];
- } else if (value && typeof value === 'object') {
+ } else if (value && typeof value === 'object' && !Array.isArray(value)) {
const param = Object.values(value)[0];
if (typeof param == 'string') params[key] = param.replaceAll('%', '');
}
diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml
index 76fc31a5b..7bcf90793 100644
--- a/src/i18n/locale/en.yml
+++ b/src/i18n/locale/en.yml
@@ -370,6 +370,11 @@ globals:
countryCodeFk: Country
companyFk: Company
nickname: Alias
+ changedModel: Entity
+ changedModelValue: Search
+ changedModelId: Entity id
+ userFk: User
+ action: Action
model: Model
fuel: Fuel
active: Active
diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml
index 75fac881c..b2512193d 100644
--- a/src/i18n/locale/es.yml
+++ b/src/i18n/locale/es.yml
@@ -371,6 +371,11 @@ globals:
countryCodeFk: País
companyFk: Empresa
nickname: Alias
+ changedModel: Entidad
+ changedModelValue: Buscar
+ changedModelId: Id de entidad
+ userFk: Usuario
+ action: Acción
errors:
statusUnauthorized: Acceso denegado
statusInternalServerError: Ha ocurrido un error interno del servidor
diff --git a/src/pages/Claim/ClaimList.vue b/src/pages/Claim/ClaimList.vue
index 06996c2c1..e0d9928f9 100644
--- a/src/pages/Claim/ClaimList.vue
+++ b/src/pages/Claim/ClaimList.vue
@@ -134,7 +134,7 @@ const columns = computed(() => [
const STATE_COLOR = {
pending: 'bg-warning',
- managed: 'bg-info',
+ loses: 'bg-negative',
resolved: 'bg-positive',
};
diff --git a/src/pages/Customer/Card/CustomerBalance.vue b/src/pages/Customer/Card/CustomerBalance.vue
index 11db92eab..15f80b2f6 100644
--- a/src/pages/Customer/Card/CustomerBalance.vue
+++ b/src/pages/Customer/Card/CustomerBalance.vue
@@ -20,6 +20,7 @@ import VnFilter from 'components/VnTable/VnFilter.vue';
import CustomerNewPayment from 'src/pages/Customer/components/CustomerNewPayment.vue';
import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue';
+import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
const { openConfirmationModal } = useVnConfirm();
const { sendEmail, openReport } = usePrintService();
@@ -89,15 +90,7 @@ const columns = computed(() => [
{
align: 'left',
label: t('Employee'),
- columnField: {
- component: 'userLink',
- attrs: ({ row }) => {
- return {
- workerId: row.workerFk,
- name: row.userName,
- };
- },
- },
+ name: 'workerFk',
cardVisible: true,
},
{
@@ -131,7 +124,6 @@ const columns = computed(() => [
align: 'left',
name: 'balance',
label: t('Balance'),
- format: ({ balance }) => toCurrency(balance),
cardVisible: true,
},
{
@@ -146,12 +138,14 @@ const columns = computed(() => [
actions: [
{
title: t('globals.downloadPdf'),
+ isPrimary: true,
icon: 'cloud_download',
show: (row) => row.isInvoice,
action: (row) => showBalancePdf(row),
},
{
title: t('Send compensation'),
+ isPrimary: true,
icon: 'outgoing_mail',
show: (row) => !!row.isCompensation,
action: ({ id }) =>
@@ -256,6 +250,12 @@ const showBalancePdf = ({ id }) => {
{{ toCurrency(balances[rowIndex]?.balance) }}
+
+
+ {{ row.userName }}
+
+
+
{{ t('bill', { ref: row.description }) }}
diff --git a/src/pages/Customer/components/CustomerNewPayment.vue b/src/pages/Customer/components/CustomerNewPayment.vue
index ac80fdaa4..fb3804d55 100644
--- a/src/pages/Customer/components/CustomerNewPayment.vue
+++ b/src/pages/Customer/components/CustomerNewPayment.vue
@@ -3,18 +3,20 @@ import { onBeforeMount, reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import axios from 'axios';
-import { getClientRisk } from '../composables/getClientRisk';
import { useDialogPluginComponent } from 'quasar';
-import FormModelPopup from 'components/FormModelPopup.vue';
+
+import { getClientRisk } from '../composables/getClientRisk';
import { usePrintService } from 'composables/usePrintService';
import useNotify from 'src/composables/useNotify.js';
+
+import FormModelPopup from 'components/FormModelPopup.vue';
import FetchData from 'components/FetchData.vue';
-import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInputNumber from 'components/common/VnInputNumber.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue';
+import VnAccountNumber from 'src/components/common/VnAccountNumber.vue';
const { t } = useI18n();
const route = useRoute();
@@ -48,7 +50,7 @@ const maxAmount = ref();
const accountingType = ref({});
const isCash = ref(false);
const formModelRef = ref(false);
-
+const amountToReturn = ref();
const filterBanks = {
fields: ['id', 'bank', 'accountingTypeFk'],
include: { relation: 'accountingType' },
@@ -90,7 +92,7 @@ function setPaymentType(data, accounting) {
let descriptions = [];
if (accountingType.value.receiptDescription)
descriptions.push(accountingType.value.receiptDescription);
- if (data.description) descriptions.push(data.description);
+ if (data.description > 0) descriptions.push(data.description);
data.description = descriptions.join(', ');
}
@@ -100,7 +102,7 @@ const calculateFromAmount = (event) => {
};
const calculateFromDeliveredAmount = (event) => {
- initialData.amountToReturn = parseFloat(event) - initialData.amountPaid;
+ amountToReturn.value = event - initialData.amountPaid;
};
function onBeforeSave(data) {
@@ -121,17 +123,16 @@ async function onDataSaved(formData, { id }) {
recipient: formData.email,
});
- if (viewReceipt.value) openReport(`Receipts/${id}/receipt-pdf`);
+ if (viewReceipt.value) openReport(`Receipts/${id}/receipt-pdf`, {}, '_blank');
} finally {
if ($props.promise) $props.promise();
if (closeButton.value) closeButton.value.click();
}
}
-async function accountShortToStandard({ target: { value } }) {
+async function getSupplierClientReferences(value) {
if (!value) return (initialData.description = '');
- initialData.compensationAccount = value.replace('.', '0'.repeat(11 - value.length));
- const params = { bankAccount: initialData.compensationAccount };
+ const params = { bankAccount: value };
const { data } = await axios(`Clients/getClientOrSupplierReference`, { params });
if (!data.clientId) {
initialData.description = t('Supplier Compensation Reference', {
@@ -241,17 +242,16 @@ async function getAmountPaid() {
@update:model-value="getAmountPaid()"
/>
-
-
+
{{ t('Compensation') }}
-
@@ -261,8 +261,7 @@ async function getAmountPaid() {
clearable
v-model="data.description"
/>
-
-
+
{{ t('Cash') }}
diff --git a/src/pages/Route/RouteRoadmap.vue b/src/pages/Route/RouteRoadmap.vue
index c981b86a5..bdb3d12c4 100644
--- a/src/pages/Route/RouteRoadmap.vue
+++ b/src/pages/Route/RouteRoadmap.vue
@@ -213,7 +213,7 @@ function exprBuilder(param, value) {
}"
>
-
+
+import { computed } from 'vue';
+import { useRoute } from 'vue-router';
+import { useState } from 'src/composables/useState';
+import VnNotes from 'src/components/ui/VnNotes.vue';
+
+const route = useRoute();
+const state = useState();
+const user = state.getUser();
+const vehicleId = computed(() => route.params.id);
+
+const noteFilter = computed(() => {
+ return {
+ order: 'created DESC',
+ where: { vehicleFk: vehicleId.value },
+ };
+});
+
+const body = {
+ vehicleFk: vehicleId.value,
+ workerFk: user.value.id,
+};
+
+
+
+
+
diff --git a/src/pages/Ticket/Card/TicketSale.vue b/src/pages/Ticket/Card/TicketSale.vue
index 2fb305cc3..3960243aa 100644
--- a/src/pages/Ticket/Card/TicketSale.vue
+++ b/src/pages/Ticket/Card/TicketSale.vue
@@ -377,10 +377,12 @@ const newOrderFromTicket = async () => {
const goToLog = (saleId) => {
router.push({
name: 'TicketLog',
- params: {
- originId: route.params.id,
- changedModel: 'Sale',
- changedModelId: saleId,
+ query: {
+ logs: JSON.stringify({
+ originFk: route.params.id,
+ changedModel: 'Sale',
+ changedModelId: saleId,
+ }),
},
});
};
diff --git a/src/pages/Travel/ExtraCommunity.vue b/src/pages/Travel/ExtraCommunity.vue
index d30629a80..ec898719d 100644
--- a/src/pages/Travel/ExtraCommunity.vue
+++ b/src/pages/Travel/ExtraCommunity.vue
@@ -18,7 +18,6 @@ import { usePrintService } from 'composables/usePrintService';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import axios from 'axios';
import RightMenu from 'src/components/common/RightMenu.vue';
-import VnPopup from 'src/components/common/VnPopup.vue';
const stateStore = useStateStore();
const { t } = useI18n();
@@ -183,7 +182,6 @@ const columns = computed(() => [
align: 'left',
showValue: false,
sortable: true,
- style: 'min-width: 170px;',
},
{
label: t('globals.packages'),
@@ -507,6 +505,7 @@ watch(route, () => {
:props="props"
@click="stopEventPropagation($event, col)"
:style="col.style"
+ style="padding-left: 5px"
>
{
{{ entry.volumeKg }}
-
-
-
-
-
-
-
-
+
+
+ {{ entry.evaNotes }}
+
@@ -643,7 +629,11 @@ watch(route, () => {
}
:deep(.q-table) {
+ table-layout: auto;
+ width: 100%;
border-collapse: collapse;
+ overflow: hidden;
+ text-overflow: ellipsis;
tbody tr td {
&:nth-child(1) {
diff --git a/src/router/__tests__/hooks.spec.js b/src/router/__tests__/hooks.spec.js
new file mode 100644
index 000000000..97f5eacdc
--- /dev/null
+++ b/src/router/__tests__/hooks.spec.js
@@ -0,0 +1,36 @@
+import { describe, it, expect, vi } from 'vitest';
+import { ref, nextTick } from 'vue';
+import { stateQueryGuard } from 'src/router/hooks';
+import { useStateQueryStore } from 'src/stores/useStateQueryStore';
+
+vi.mock('src/stores/useStateQueryStore', () => {
+ const isLoading = ref(true);
+ return {
+ useStateQueryStore: () => ({
+ isLoading: () => isLoading,
+ setLoading: isLoading,
+ }),
+ };
+});
+
+describe('hooks', () => {
+ describe('stateQueryGuard', () => {
+ const foo = { name: 'foo' };
+ it('should wait until the state query is not loading and then call next()', async () => {
+ const next = vi.fn();
+
+ stateQueryGuard(foo, { name: 'bar' }, next);
+ expect(next).not.toHaveBeenCalled();
+
+ useStateQueryStore().setLoading.value = false;
+ await nextTick();
+ expect(next).toHaveBeenCalled();
+ });
+
+ it('should ignore if both routes are the same', () => {
+ const next = vi.fn();
+ stateQueryGuard(foo, foo, next);
+ expect(next).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/src/router/hooks.js b/src/router/hooks.js
new file mode 100644
index 000000000..bd9e5334f
--- /dev/null
+++ b/src/router/hooks.js
@@ -0,0 +1,95 @@
+import { useRole } from 'src/composables/useRole';
+import { useUserConfig } from 'src/composables/useUserConfig';
+import { useTokenConfig } from 'src/composables/useTokenConfig';
+import { useAcl } from 'src/composables/useAcl';
+import { isLoggedIn } from 'src/utils/session';
+import { useSession } from 'src/composables/useSession';
+import { useStateQueryStore } from 'src/stores/useStateQueryStore';
+import { watch } from 'vue';
+import { i18n } from 'src/boot/i18n';
+
+let session = null;
+const { t, te } = i18n.global;
+
+export async function navigationGuard(to, from, next, Router, state) {
+ if (!session) session = useSession();
+ const outLayout = Router.options.routes[0].children.map((r) => r.name);
+ if (!session.isLoggedIn() && !outLayout.includes(to.name)) {
+ return next({ name: 'Login', query: { redirect: to.fullPath } });
+ }
+
+ if (isLoggedIn()) {
+ const stateRoles = state.getRoles().value;
+ if (stateRoles.length === 0) {
+ await useRole().fetch();
+ await useAcl().fetch();
+ await useUserConfig().fetch();
+ await useTokenConfig().fetch();
+ }
+ const matches = to.matched;
+ const hasRequiredAcls = matches.every((route) => {
+ const meta = route.meta;
+ if (!meta?.acls) return true;
+ return useAcl().hasAny(meta.acls);
+ });
+ if (!hasRequiredAcls) return next({ path: '/' });
+ }
+
+ next();
+}
+
+export async function stateQueryGuard(to, from, next) {
+ if (to.name !== from.name) {
+ const stateQuery = useStateQueryStore();
+ await waitUntilFalse(stateQuery.isLoading());
+ }
+
+ next();
+}
+
+export function setPageTitle(to) {
+ let title = t(`login.title`);
+
+ const matches = to.matched;
+ if (matches && matches.length > 1) {
+ const module = matches[1];
+ const moduleTitle = module.meta?.title;
+ if (moduleTitle) {
+ title = t(`globals.pageTitles.${moduleTitle}`);
+ }
+ }
+
+ const childPage = to.meta;
+ const childPageTitle = childPage?.title;
+ if (childPageTitle && matches.length > 2) {
+ if (title != '') title += ': ';
+
+ const moduleLocale = `globals.pageTitles.${childPageTitle}`;
+ const pageTitle = te(moduleLocale)
+ ? t(moduleLocale)
+ : t(`globals.pageTitles.${childPageTitle}`);
+ const idParam = to.params?.id;
+ const idPageTitle = `${idParam} - ${pageTitle}`;
+ const builtTitle = idParam ? idPageTitle : pageTitle;
+
+ title += builtTitle;
+ }
+
+ document.title = title;
+}
+
+function waitUntilFalse(ref) {
+ return new Promise((resolve) => {
+ if (!ref.value) return resolve();
+ const stop = watch(
+ ref,
+ (val) => {
+ if (!val) {
+ stop();
+ resolve();
+ }
+ },
+ { immediate: true },
+ );
+ });
+}
diff --git a/src/router/index.js b/src/router/index.js
index 4403901cb..628a53c8e 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -6,101 +6,25 @@ import {
createWebHashHistory,
} from 'vue-router';
import routes from './routes';
-import { i18n } from 'src/boot/i18n';
import { useState } from 'src/composables/useState';
-import { useRole } from 'src/composables/useRole';
-import { useUserConfig } from 'src/composables/useUserConfig';
-import { useTokenConfig } from 'src/composables/useTokenConfig';
-import { useAcl } from 'src/composables/useAcl';
-import { isLoggedIn } from 'src/utils/session';
-import { useSession } from 'src/composables/useSession';
+import { navigationGuard, setPageTitle, stateQueryGuard } from './hooks';
-let session = null;
-const { t, te } = i18n.global;
-
-const createHistory = process.env.SERVER
- ? createMemoryHistory
- : process.env.VUE_ROUTER_MODE === 'history'
- ? createWebHistory
- : createWebHashHistory;
+const webHistory =
+ process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory;
+const createHistory = process.env.SERVER ? createMemoryHistory : webHistory;
const Router = createRouter({
scrollBehavior: () => ({ left: 0, top: 0 }),
routes,
-
- // Leave this as is and make changes in quasar.conf.js instead!
- // quasar.conf.js -> build -> vueRouterMode
- // quasar.conf.js -> build -> publicPath
history: createHistory(process.env.VUE_ROUTER_BASE),
});
-/*
- * If not building with SSR mode, you can
- * directly export the Router instantiation;
- *
- * The function below can be async too; either use
- * async/await or return a Promise which resolves
- * with the Router instance.
- */
export { Router };
-export default defineRouter(function (/* { store, ssrContext } */) {
+export default defineRouter(() => {
const state = useState();
- Router.beforeEach(async (to, from, next) => {
- if (!session) session = useSession();
- const outLayout = Router.options.routes[0].children.map((r) => r.name);
- if (!session.isLoggedIn() && !outLayout.includes(to.name)) {
- return next({ name: 'Login', query: { redirect: to.fullPath } });
- }
-
- if (isLoggedIn()) {
- const stateRoles = state.getRoles().value;
- if (stateRoles.length === 0) {
- await useRole().fetch();
- await useAcl().fetch();
- await useUserConfig().fetch();
- await useTokenConfig().fetch();
- }
- const matches = to.matched;
- const hasRequiredAcls = matches.every((route) => {
- const meta = route.meta;
- if (!meta?.acls) return true;
- return useAcl().hasAny(meta.acls);
- });
- if (!hasRequiredAcls) return next({ path: '/' });
- }
-
- next();
- });
-
- Router.afterEach((to) => {
- let title = t(`login.title`);
-
- const matches = to.matched;
- if (matches && matches.length > 1) {
- const module = matches[1];
- const moduleTitle = module.meta && module.meta.title;
- if (moduleTitle) {
- title = t(`globals.pageTitles.${moduleTitle}`);
- }
- }
-
- const childPage = to.meta;
- const childPageTitle = childPage && childPage.title;
- if (childPageTitle && matches.length > 2) {
- if (title != '') title += ': ';
-
- const moduleLocale = `globals.pageTitles.${childPageTitle}`;
- const pageTitle = te(moduleLocale)
- ? t(moduleLocale)
- : t(`globals.pageTitles.${childPageTitle}`);
- const idParam = to.params && to.params.id;
- const idPageTitle = `${idParam} - ${pageTitle}`;
- const builtTitle = idParam ? idPageTitle : pageTitle;
-
- title += builtTitle;
- }
- document.title = title;
- });
+ Router.beforeEach((to, from, next) => navigationGuard(to, from, next, Router, state));
+ Router.beforeEach(stateQueryGuard);
+ Router.afterEach(setPageTitle);
Router.onError(({ message }) => {
const errorMessages = [
diff --git a/src/router/modules/route.js b/src/router/modules/route.js
index 62765a49c..11133c50a 100644
--- a/src/router/modules/route.js
+++ b/src/router/modules/route.js
@@ -166,7 +166,7 @@ const vehicleCard = {
component: () => import('src/pages/Route/Vehicle/Card/VehicleCard.vue'),
redirect: { name: 'VehicleSummary' },
meta: {
- menu: ['VehicleBasicData'],
+ menu: ['VehicleBasicData', 'VehicleNotes'],
},
children: [
{
@@ -187,6 +187,15 @@ const vehicleCard = {
},
component: () => import('src/pages/Route/Vehicle/Card/VehicleBasicData.vue'),
},
+ {
+ name: 'VehicleNotes',
+ path: 'notes',
+ meta: {
+ title: 'notes',
+ icon: 'vn:notes',
+ },
+ component: () => import('src/pages/Route/Vehicle/Card/VehicleNotes.vue'),
+ }
],
};
diff --git a/test/cypress/integration/entry/commands.js b/test/cypress/integration/entry/commands.js
index 7c96a5440..4d4a8f980 100644
--- a/test/cypress/integration/entry/commands.js
+++ b/test/cypress/integration/entry/commands.js
@@ -1,6 +1,6 @@
Cypress.Commands.add('selectTravel', (warehouse = '1') => {
cy.get('i[data-cy="Travel_icon"]').click();
- cy.get('input[data-cy="Warehouse Out_select"]').type(warehouse);
+ cy.selectOption('input[data-cy="Warehouse Out_select"]', warehouse);
cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click();
cy.get('button[data-cy="save-filter-travel-form"]').click();
cy.get('tr').eq(1).click();
@@ -9,7 +9,6 @@ Cypress.Commands.add('selectTravel', (warehouse = '1') => {
Cypress.Commands.add('deleteEntry', () => {
cy.get('[data-cy="descriptor-more-opts"]').should('be.visible').click();
cy.waitForElement('div[data-cy="delete-entry"]').click();
- cy.url().should('include', 'list');
});
Cypress.Commands.add('createEntry', () => {
diff --git a/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js b/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js
index 554471008..8185866db 100644
--- a/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js
+++ b/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js
@@ -28,12 +28,8 @@ describe('EntryDescriptor', () => {
cy.get('.q-notification__message')
.eq(2)
.should('have.text', 'Entry prices recalculated');
-
- cy.get('[data-cy="descriptor-more-opts"]').click();
cy.deleteEntry();
- cy.log(previousUrl);
-
cy.visit(previousUrl);
cy.waitForElement('[data-cy="entry-buys"]');
diff --git a/test/cypress/integration/route/vehicle/vehicleNotes.spec.js b/test/cypress/integration/route/vehicle/vehicleNotes.spec.js
new file mode 100644
index 000000000..cd92cc4af
--- /dev/null
+++ b/test/cypress/integration/route/vehicle/vehicleNotes.spec.js
@@ -0,0 +1,28 @@
+describe('Vehicle Notes', () => {
+ const selectors = {
+ addNoteInput: 'Add note here..._input',
+ saveNoteBtn: 'saveNote',
+ deleteNoteBtn: 'notesRemoveNoteBtn',
+ noteCard: '.column.full-width > :nth-child(1) > .q-card__section--vert',
+ };
+
+ const noteText = 'Golpe parachoques trasero';
+ const newNoteText = 'probando';
+
+ beforeEach(() => {
+ cy.viewport(1920, 1080);
+ cy.login('developer');
+ cy.visit(`/#/route/vehicle/1/notes`);
+ });
+
+ it('Should add new note', () => {
+ cy.dataCy(selectors.addNoteInput).should('be.visible').type(newNoteText);
+ cy.dataCy(selectors.saveNoteBtn).click();
+ cy.validateContent(selectors.noteCard, newNoteText);
+ });
+
+ it('Should delete note', () => {
+ cy.dataCy(selectors.deleteNoteBtn).first().should('be.visible').click();
+ cy.get(selectors.noteCard).first().should('have.text', noteText);
+ });
+});
diff --git a/test/cypress/integration/vnComponent/VnLog.spec.js b/test/cypress/integration/vnComponent/VnLog.spec.js
index 8ca32b681..57faeac85 100644
--- a/test/cypress/integration/vnComponent/VnLog.spec.js
+++ b/test/cypress/integration/vnComponent/VnLog.spec.js
@@ -1,26 +1,23 @@
///
describe('VnLog', () => {
- const chips = [
- ':nth-child(1) > :nth-child(1) > .q-item__label > .q-chip > .q-chip__content',
- ':nth-child(2) > :nth-child(1) > .q-item__label > .q-chip > .q-chip__content',
- ];
beforeEach(() => {
cy.login('developer');
cy.visit(`/#/claim/${1}/log`);
- cy.openRightMenu();
});
it('should filter by insert actions', () => {
- cy.checkOption(':nth-child(7) > .q-checkbox');
- cy.get('.q-page').click();
- cy.validateContent(chips[0], 'Document');
- cy.validateContent(chips[1], 'Beginning');
+ cy.get('[data-cy="vnLog-checkbox"]').eq(0).click();
+ cy.get('[data-cy="vnLog-action-icon"]').each(($el) => {
+ cy.wrap($el).should('have.attr', 'title', 'Creates');
+ });
});
it('should filter by entity', () => {
- cy.selectOption('.q-drawer--right .q-item > .q-select', 'Claim');
- cy.get('.q-page').click();
- cy.validateContent(chips[0], 'Beginning');
+ const entity = 'Document';
+ cy.selectOption('[data-cy="vnLog-entity"]', entity);
+ cy.get('[data-cy="vnLog-model-chip"]').each(($el) => {
+ cy.wrap($el).should('have.text', entity);
+ });
});
it('should show claimDescriptor', () => {