Compare commits

..

No commits in common. "6564-enhanceTicketAdvance" and "dev" have entirely different histories.

7 changed files with 115 additions and 191 deletions

View File

@ -1,22 +1,12 @@
<script setup> <script setup>
import { toCurrency } from 'src/filters'; import { toCurrency } from 'src/filters';
import { getValueFromPath } from 'src/composables/getValueFromPath';
const { row, visibleProblems = null } = defineProps({ defineProps({ row: { type: Object, required: true } });
row: { type: Object, required: true },
visibleProblems: { type: Array },
});
function showProblem(problem) {
const val = getValueFromPath(row, problem);
if (!visibleProblems) return val;
return !!(visibleProblems?.includes(problem) && val);
}
</script> </script>
<template> <template>
<span class="q-gutter-x-xs"> <span class="q-gutter-x-xs">
<router-link <router-link
v-if="showProblem('claim.claimFk')" v-if="row.claim?.claimFk"
:to="{ name: 'ClaimBasicData', params: { id: row.claim?.claimFk } }" :to="{ name: 'ClaimBasicData', params: { id: row.claim?.claimFk } }"
class="link" class="link"
> >
@ -28,7 +18,7 @@ function showProblem(problem) {
</QIcon> </QIcon>
</router-link> </router-link>
<QIcon <QIcon
v-if="showProblem('isDeleted')" v-if="row?.isDeleted"
color="primary" color="primary"
name="vn:deletedTicket" name="vn:deletedTicket"
size="xs" size="xs"
@ -39,7 +29,7 @@ function showProblem(problem) {
</QTooltip> </QTooltip>
</QIcon> </QIcon>
<QIcon <QIcon
v-if="showProblem('hasRisk')" v-if="row?.hasRisk"
name="vn:risk" name="vn:risk"
:color="row.hasHighRisk ? 'negative' : 'primary'" :color="row.hasHighRisk ? 'negative' : 'primary'"
size="xs" size="xs"
@ -50,76 +40,51 @@ function showProblem(problem) {
</QTooltip> </QTooltip>
</QIcon> </QIcon>
<QIcon <QIcon
v-if="showProblem('hasComponentLack')" v-if="row?.hasComponentLack"
name="vn:components" name="vn:components"
color="primary" color="primary"
size="xs" size="xs"
> >
<QTooltip>{{ $t('salesTicketsTable.componentLack') }}</QTooltip> <QTooltip>{{ $t('salesTicketsTable.componentLack') }}</QTooltip>
</QIcon> </QIcon>
<QIcon <QIcon v-if="row?.hasItemDelay" color="primary" size="xs" name="vn:hasItemDelay">
v-if="showProblem('hasItemDelay')"
color="primary"
size="xs"
name="vn:hasItemDelay"
>
<QTooltip> <QTooltip>
{{ $t('ticket.summary.hasItemDelay') }} {{ $t('ticket.summary.hasItemDelay') }}
</QTooltip> </QTooltip>
</QIcon> </QIcon>
<QIcon <QIcon v-if="row?.hasItemLost" color="primary" size="xs" name="vn:hasItemLost">
v-if="showProblem('hasItemLost')"
color="primary"
size="xs"
name="vn:hasItemLost"
>
<QTooltip> <QTooltip>
{{ $t('salesTicketsTable.hasItemLost') }} {{ $t('salesTicketsTable.hasItemLost') }}
</QTooltip> </QTooltip>
</QIcon> </QIcon>
<QIcon <QIcon
v-if="showProblem('hasItemShortage')" v-if="row?.hasItemShortage"
name="vn:unavailable" name="vn:unavailable"
color="primary" color="primary"
size="xs" size="xs"
> >
<QTooltip>{{ $t('salesTicketsTable.notVisible') }}</QTooltip> <QTooltip>{{ $t('salesTicketsTable.notVisible') }}</QTooltip>
</QIcon> </QIcon>
<QIcon <QIcon v-if="row?.hasRounding" color="primary" name="sync_problem" size="xs">
v-if="showProblem('hasRounding')"
color="primary"
name="sync_problem"
size="xs"
>
<QTooltip> <QTooltip>
{{ $t('ticketList.rounding') }} {{ $t('ticketList.rounding') }}
</QTooltip> </QTooltip>
</QIcon> </QIcon>
<QIcon <QIcon
v-if="showProblem('hasTicketRequest')" v-if="row?.hasTicketRequest"
name="vn:buyrequest" name="vn:buyrequest"
color="primary" color="primary"
size="xs" size="xs"
> >
<QTooltip>{{ $t('salesTicketsTable.purchaseRequest') }}</QTooltip> <QTooltip>{{ $t('salesTicketsTable.purchaseRequest') }}</QTooltip>
</QIcon> </QIcon>
<QIcon <QIcon v-if="row?.isTaxDataChecked" name="vn:no036" color="primary" size="xs">
v-if="showProblem('isTaxDataChecked')"
name="vn:no036"
color="primary"
size="xs"
>
<QTooltip>{{ $t('salesTicketsTable.noVerifiedData') }}</QTooltip> <QTooltip>{{ $t('salesTicketsTable.noVerifiedData') }}</QTooltip>
</QIcon> </QIcon>
<QIcon v-if="showProblem('isFreezed')" name="vn:frozen" color="primary" size="xs"> <QIcon v-if="row?.isFreezed" name="vn:frozen" color="primary" size="xs">
<QTooltip>{{ $t('salesTicketsTable.clientFrozen') }}</QTooltip> <QTooltip>{{ $t('salesTicketsTable.clientFrozen') }}</QTooltip>
</QIcon> </QIcon>
<QIcon <QIcon v-if="row?.isTooLittle" name="vn:isTooLittle" color="primary" size="xs">
v-if="showProblem('isTooLittle')"
name="vn:isTooLittle"
color="primary"
size="xs"
>
<QTooltip>{{ $t('salesTicketsTable.tooLittle') }}</QTooltip> <QTooltip>{{ $t('salesTicketsTable.tooLittle') }}</QTooltip>
</QIcon> </QIcon>
</span> </span>

View File

@ -1,63 +1,65 @@
import { vi, describe, expect, it, beforeEach, beforeAll, afterEach } from 'vitest'; import { vi, describe, expect, it, beforeEach, afterEach } from 'vitest';
import { createWrapper } from 'app/test/vitest/helper'; import { createWrapper } from 'app/test/vitest/helper';
import UserPanel from 'src/components/UserPanel.vue'; import UserPanel from 'src/components/UserPanel.vue';
import axios from 'axios'; import axios from 'axios';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
vi.mock('src/utils/quasarLang', () => ({
default: vi.fn(),
}));
describe('UserPanel', () => { describe('UserPanel', () => {
let wrapper; let wrapper;
let vm; let vm;
let state; let state;
beforeEach(() => { beforeEach(() => {
wrapper = createWrapper(UserPanel, {}); wrapper = createWrapper(UserPanel, {});
state = useState(); state = useState();
state.setUser({ state.setUser({
id: 115, id: 115,
name: 'itmanagement', name: 'itmanagement',
nickname: 'itManagementNick', nickname: 'itManagementNick',
lang: 'en', lang: 'en',
darkMode: false, darkMode: false,
companyFk: 442, companyFk: 442,
warehouseFk: 1, warehouseFk: 1,
});
wrapper = wrapper.wrapper;
vm = wrapper.vm;
}); });
wrapper = wrapper.wrapper;
vm = wrapper.vm;
});
afterEach(() => { afterEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
}); });
it('should fetch warehouses data on mounted', async () => { it('should fetch warehouses data on mounted', async () => {
const fetchData = wrapper.findComponent({ name: 'FetchData' }); const fetchData = wrapper.findComponent({ name: 'FetchData' });
expect(fetchData.props('url')).toBe('Warehouses'); expect(fetchData.props('url')).toBe('Warehouses');
expect(fetchData.props('autoLoad')).toBe(true); expect(fetchData.props('autoLoad')).toBe(true);
}); });
it('should toggle dark mode correctly and update preferences', async () => { it('should toggle dark mode correctly and update preferences', async () => {
await vm.saveDarkMode(true); await vm.saveDarkMode(true);
expect(axios.patch).toHaveBeenCalledWith('/UserConfigs/115', { darkMode: true }); expect(axios.patch).toHaveBeenCalledWith('/UserConfigs/115', { darkMode: true });
expect(vm.user.darkMode).toBe(true); expect(vm.user.darkMode).toBe(true);
vm.updatePreferences(); await vm.updatePreferences();
expect(vm.darkMode).toBe(true); expect(vm.darkMode).toBe(true);
}); });
it('should change user language and update preferences', async () => { it('should change user language and update preferences', async () => {
const userLanguage = 'es'; const userLanguage = 'es';
await vm.saveLanguage(userLanguage); await vm.saveLanguage(userLanguage);
expect(axios.patch).toHaveBeenCalledWith('/VnUsers/115', { lang: userLanguage }); expect(axios.patch).toHaveBeenCalledWith('/VnUsers/115', { lang: userLanguage });
expect(vm.user.lang).toBe(userLanguage); expect(vm.user.lang).toBe(userLanguage);
vm.updatePreferences(); await vm.updatePreferences();
expect(vm.locale).toBe(userLanguage); expect(vm.locale).toBe(userLanguage);
}); });
it('should update user data', async () => { it('should update user data', async () => {
const key = 'name'; const key = 'name';
const value = 'itboss'; const value = 'itboss';
await vm.saveUserData(key, value); await vm.saveUserData(key, value);
expect(axios.post).toHaveBeenCalledWith('UserConfigs/setUserConfig', { expect(axios.post).toHaveBeenCalledWith('UserConfigs/setUserConfig', { [key]: value });
[key]: value, });
}); });
});
});

View File

@ -6,7 +6,6 @@ import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useClipboard } from 'src/composables/useClipboard'; import { useClipboard } from 'src/composables/useClipboard';
import VnMoreOptions from './VnMoreOptions.vue'; import VnMoreOptions from './VnMoreOptions.vue';
import { getValueFromPath } from 'src/composables/getValueFromPath';
const entity = defineModel({ type: Object, default: null }); const entity = defineModel({ type: Object, default: null });
const $props = defineProps({ const $props = defineProps({
@ -57,6 +56,18 @@ const routeName = computed(() => {
return `${routeName}Summary`; return `${routeName}Summary`;
}); });
function getValueFromPath(path) {
if (!path) return;
const keys = path.toString().split('.');
let current = entity.value;
for (const key of keys) {
if (current[key] === undefined) return undefined;
else current = current[key];
}
return current;
}
function copyIdText(id) { function copyIdText(id) {
copyText(id, { copyText(id, {
component: { component: {
@ -159,10 +170,10 @@ const toModule = computed(() => {
<div class="title"> <div class="title">
<span <span
v-if="title" v-if="title"
:title="getValueFromPath(entity, title)" :title="getValueFromPath(title)"
:data-cy="`${$attrs['data-cy'] ?? 'vnDescriptor'}_title`" :data-cy="`${$attrs['data-cy'] ?? 'vnDescriptor'}_title`"
> >
{{ getValueFromPath(entity, title) ?? title }} {{ getValueFromPath(title) ?? title }}
</span> </span>
<slot v-else name="description" :entity="entity"> <slot v-else name="description" :entity="entity">
<span <span
@ -178,7 +189,7 @@ const toModule = computed(() => {
class="subtitle" class="subtitle"
:data-cy="`${$attrs['data-cy'] ?? 'vnDescriptor'}_subtitle`" :data-cy="`${$attrs['data-cy'] ?? 'vnDescriptor'}_subtitle`"
> >
#{{ getValueFromPath(entity, subtitle) ?? entity.id }} #{{ getValueFromPath(subtitle) ?? entity.id }}
</QItemLabel> </QItemLabel>
<QBtn <QBtn
round round

View File

@ -1,11 +0,0 @@
export function getValueFromPath(root, path) {
if (!root || !path) return;
const keys = path.toString().split('.');
let current = root;
for (const key of keys) {
if (current[key] === undefined) return undefined;
else current = current[key];
}
return current;
}

View File

@ -46,7 +46,7 @@ async function getClaims() {
originalTicket.value = data[0]?.originalTicketFk; originalTicket.value = data[0]?.originalTicketFk;
} }
async function getProblems() { async function getProblems() {
const { data } = await axios.get(`Tickets/getTicketProblems`, {params: { ids: [entityId.value] }}); const { data } = await axios.get(`Tickets/${entityId.value}/getTicketProblems`);
if (!data) return; if (!data) return;
problems.value = data[0]; problems.value = data[0];
} }

View File

@ -14,8 +14,6 @@ import { useState } from 'src/composables/useState';
import { toDateFormat } from 'src/filters/date.js'; import { toDateFormat } from 'src/filters/date.js';
import axios from 'axios'; import axios from 'axios';
import VnTable from 'src/components/VnTable/VnTable.vue'; import VnTable from 'src/components/VnTable/VnTable.vue';
import { QTable } from 'quasar';
import TicketProblems from 'src/components/TicketProblems.vue';
const state = useState(); const state = useState();
const { t } = useI18n(); const { t } = useI18n();
@ -29,16 +27,6 @@ const selectedTickets = ref([]);
const vnTableRef = ref({}); const vnTableRef = ref({});
const originElRef = ref(null); const originElRef = ref(null);
const destinationElRef = ref(null); const destinationElRef = ref(null);
const actions = {
advance: {
title: t('advanceTickets.advanceTickets'),
cb: moveTicketsAdvance,
},
advanceWithoutNegative: {
title: t('advanceTickets.advanceTicketsWithoutNegatives'),
cb: splitTickets,
},
};
let today = Date.vnNew().toISOString(); let today = Date.vnNew().toISOString();
const tomorrow = new Date(today); const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setDate(tomorrow.getDate() + 1);
@ -90,15 +78,6 @@ const ticketColumns = computed(() => [
headerClass: 'horizontal-separator', headerClass: 'horizontal-separator',
hidden: true, hidden: true,
}, },
{
label: t('globals.agency'),
name: 'agency',
field: 'agency',
align: 'left',
sortable: true,
headerClass: 'horizontal-separator',
columnFilter: false,
},
{ {
label: t('advanceTickets.preparation'), label: t('advanceTickets.preparation'),
name: 'preparation', name: 'preparation',
@ -106,6 +85,7 @@ const ticketColumns = computed(() => [
align: 'left', align: 'left',
sortable: true, sortable: true,
headerClass: 'horizontal-separator', headerClass: 'horizontal-separator',
columnFilter: false,
}, },
{ {
align: 'left', align: 'left',
@ -128,19 +108,12 @@ const ticketColumns = computed(() => [
headerClass: 'horizontal-separator', headerClass: 'horizontal-separator',
format: (row) => toCurrency(row.totalWithVat), format: (row) => toCurrency(row.totalWithVat),
}, },
{
align: 'left',
label: '',
name: 'problems',
headerClass: 'vertical-separator horizontal-separator',
columnClass: 'vertical-separator',
hidden: true,
},
{ {
align: 'left', align: 'left',
label: t('advanceTickets.futureId'), label: t('advanceTickets.futureId'),
name: 'futureId', name: 'futureId',
headerClass: 'horizontal-separator', headerClass: 'vertical-separator horizontal-separator',
columnClass: 'vertical-separator',
}, },
{ {
align: 'left', align: 'left',
@ -269,7 +242,7 @@ const requestComponentUpdate = async (ticket, isWithoutNegatives) => {
return { query, params }; return { query, params };
}; };
async function moveTicketsAdvance() { const moveTicketsAdvance = async () => {
let ticketsToMove = []; let ticketsToMove = [];
for (const ticket of selectedTickets.value) { for (const ticket of selectedTickets.value) {
if (!ticket.id) { if (!ticket.id) {
@ -294,7 +267,7 @@ async function moveTicketsAdvance() {
vnTableRef.value.reload(); vnTableRef.value.reload();
selectedTickets.value = []; selectedTickets.value = [];
if (ticketsToMove.length) notify(t('advanceTickets.moveTicketSuccess'), 'positive'); if (ticketsToMove.length) notify(t('advanceTickets.moveTicketSuccess'), 'positive');
} };
const progressLength = ref(0); const progressLength = ref(0);
const progressPercentage = computed(() => { const progressPercentage = computed(() => {
@ -317,7 +290,7 @@ const progressAdd = () => {
} }
}; };
async function splitTickets() { const splitTickets = async () => {
try { try {
showProgressDialog.value = true; showProgressDialog.value = true;
for (const ticket of selectedTickets.value) { for (const ticket of selectedTickets.value) {
@ -337,7 +310,7 @@ async function splitTickets() {
} finally { } finally {
vnTableRef.value.reload(); vnTableRef.value.reload();
} }
} };
const resetProgressData = () => { const resetProgressData = () => {
if (cancelProgress.value) cancelProgress.value = false; if (cancelProgress.value) cancelProgress.value = false;
@ -353,32 +326,6 @@ const handleCloseProgressDialog = () => {
const handleCancelProgress = () => (cancelProgress.value = true); const handleCancelProgress = () => (cancelProgress.value = true);
const confirmAction = (action) => {
openConfirmationModal(actions[action].title, false, actions[action].cb, null, {
component: QTable,
props: {
columns: [
{
align: 'left',
label: t('advanceTickets.destination'),
name: 'id',
field: (row) => row.id,
},
{
align: 'left',
label: t('advanceTickets.origin'),
name: 'futureId',
field: (row) => row.futureId,
},
],
rows: selectedTickets.value,
class: 'full-width',
dense: true,
flat: true,
},
});
};
watch( watch(
() => vnTableRef.value.tableRef?.$el, () => vnTableRef.value.tableRef?.$el,
($el) => { ($el) => {
@ -452,7 +399,15 @@ watch(
color="primary" color="primary"
class="q-mr-sm" class="q-mr-sm"
:disable="!selectedTickets.length" :disable="!selectedTickets.length"
@click.stop="confirmAction('advance')" @click.stop="
openConfirmationModal(
t('advanceTickets.advanceTicketTitle'),
t(`advanceTickets.advanceTitleSubtitle`, {
selectedTickets: selectedTickets.length,
}),
moveTicketsAdvance,
)
"
> >
<QTooltip> <QTooltip>
{{ t('advanceTickets.advanceTickets') }} {{ t('advanceTickets.advanceTickets') }}
@ -462,7 +417,15 @@ watch(
icon="alt_route" icon="alt_route"
color="primary" color="primary"
:disable="!selectedTickets.length" :disable="!selectedTickets.length"
@click.stop="confirmAction('advanceWithoutNegative')" @click.stop="
openConfirmationModal(
t('advanceTickets.advanceWithoutNegativeTitle'),
t(`advanceTickets.advanceWithoutNegativeSubtitle`, {
selectedTickets: selectedTickets.length,
}),
splitTickets,
)
"
> >
<QTooltip> <QTooltip>
{{ t('advanceTickets.advanceTicketsWithoutNegatives') }} {{ t('advanceTickets.advanceTicketsWithoutNegatives') }}
@ -491,9 +454,9 @@ watch(
}" }"
v-model:selected="selectedTickets" v-model:selected="selectedTickets"
:pagination="{ rowsPerPage: 0 }" :pagination="{ rowsPerPage: 0 }"
:no-data-label="$t('globals.noResults')" :no-data-label="t('globals.noResults')"
:right-search="false" :right-search="false"
:order="['futureTotalWithVat ASC']" :order="['futureTotalWithVat ASC']"
auto-load auto-load
:disable-option="{ card: true }" :disable-option="{ card: true }"
> >
@ -559,9 +522,6 @@ watch(
{{ toCurrency(row.totalWithVat || 0) }} {{ toCurrency(row.totalWithVat || 0) }}
</QBadge> </QBadge>
</template> </template>
<template #column-problems="{ row }">
<TicketProblems :row="row.problems" :visible-problems="['hasRisk']" />
</template>
<template #column-futureId="{ row }"> <template #column-futureId="{ row }">
<QBtn flat class="link" dense> <QBtn flat class="link" dense>
{{ row.futureId }} {{ row.futureId }}

View File

@ -10,7 +10,7 @@ import VnInputDate from 'src/components/common/VnInputDate.vue';
import axios from 'axios'; import axios from 'axios';
import { onMounted } from 'vue'; import { onMounted } from 'vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import VnCheckbox from 'src/components/common/VnCheckbox.vue';
const { t, te } = useI18n(); const { t, te } = useI18n();
const props = defineProps({ const props = defineProps({
dataKey: { dataKey: {
@ -122,20 +122,18 @@ onMounted(async () => await getItemPackingTypes());
<QItemSection> <QItemSection>
<VnInputNumber <VnInputNumber
v-model="params.scopeDays" v-model="params.scopeDays"
:label="t('globals.daysOnward')" :label="t('Days onward')"
filled filled
:step="0"
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnCheckbox <QCheckbox
:label="t('params.isFullMovable')" :label="t('params.isFullMovable')"
v-model="params.isFullMovable" v-model="params.isFullMovable"
toggle-indeterminate toggle-indeterminate
@update:model-value="searchFn()" @update:model-value="searchFn()"
dense
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -168,12 +166,11 @@ onMounted(async () => await getItemPackingTypes());
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnCheckbox <QCheckbox
toggle-indeterminate toggle-indeterminate
:label="t('params.onlyWithDestination')" label="only with destination"
v-model="params.onlyWithDestination" v-model="params.onlyWithDestination"
@update:model-value="searchFn()" @update:model-value="searchFn()"
dense
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -197,8 +194,8 @@ es:
Vertical: Vertical Vertical: Vertical
iptInfo: Encajado iptInfo: Encajado
params: params:
dateFuture: F. origen dateFuture: fecha origen
dateToAdvance: F. destino dateToAdvance: Fecha destino
futureIpt: IPT Origen futureIpt: IPT Origen
ipt: IPT destino ipt: IPT destino
isFullMovable: 100% movible isFullMovable: 100% movible