Merge branch '6321_improve_handleResults' into fix_ticketNegatives

This commit is contained in:
Javier Segarra 2025-02-24 00:25:34 +01:00
commit e1173c2ab1
13 changed files with 617 additions and 17 deletions

View File

@ -0,0 +1,31 @@
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
export function showResultsAsTable() {
const quasar = useQuasar();
const { t } = useI18n();
function openTable(dialog, results, tag, action, title) {
quasar.notify({
message: t('bulkEdit.completed'),
color: 'positive',
actions: [
{
label: t('bulkEdit.openTable'),
color: 'white',
handler: () => {
quasar.dialog({
component: dialog,
componentProps: {
title,
results,
tag,
action,
},
});
},
},
],
});
}
return { openTable };
}

View File

@ -909,3 +909,8 @@ months:
oct: October
nov: November
dec: December
bulkEdit:
completed: Bulk edit completed
failed: Bulk edit failed
edit: Bulk edit
inProgress: Bulk edit in progress

View File

@ -996,3 +996,9 @@ months:
oct: Octubre
nov: Noviembre
dec: Diciembre
bulkEdit:
completed: Edición masiva completada
failed: Edición masiva fallida
edit: Edición masiva
inProgress: Edición masiva en progreso
openTable: Abrir resultados

View File

@ -1,6 +1,7 @@
import axios from 'axios';
import notifyResults from 'src/utils/notifyResults';
import { showResultsAsTable } from 'src/composables/showResultsTable';
import HandleSplitDialog from 'src/pages/Ticket/Negative/components/HandleSplitDialog.vue';
export default async function (data, date) {
const reducedData = data.reduce((acc, item) => {
const existing = acc.find(({ ticketFk }) => ticketFk === item.id);
@ -13,8 +14,10 @@ export default async function (data, date) {
}, []);
const promises = reducedData.map((params) => axios.post(`Tickets/split`, params));
const { openTable } = showResultsAsTable();
const results = await Promise.allSettled(promises);
openTable(HandleSplitDialog, result, 'ticketFk', 'changeState');
notifyResults(results, 'ticketFk');

View File

@ -146,7 +146,12 @@ const showItemProposal = () => {
<template v-slot="{ popup }">
<ChangeItemDialog
ref="changeItemDialogRef"
@update-item="popup.hide()"
@update-item="
() => {
popup.hide();
reload();
}
"
:selected-rows="selectedRows"
/></template>
</VnPopupProxy>
@ -160,7 +165,12 @@ const showItemProposal = () => {
<template v-slot="{ popup }">
<ChangeStateDialog
ref="changeStateDialogRef"
@update-state="popup.hide()"
@update-state="
() => {
popup.hide();
reload();
}
"
:selected-rows="selectedRows"
/></template>
</VnPopupProxy>
@ -175,7 +185,12 @@ const showItemProposal = () => {
<template v-slot="{ popup }">
<ChangeQuantityDialog
ref="changeQuantityDialogRef"
@update-quantity="popup.hide()"
@update-quantity="
() => {
popup.hide();
reload();
}
"
:selected-rows="selectedRows"
/></template>
</VnPopupProxy> </QBtnGroup

View File

@ -5,7 +5,7 @@ import { computed, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import FetchData from 'src/components/FetchData.vue';
import { toDate, toHour } from 'src/filters';
import { toDate } from 'src/filters';
import useNotify from 'src/composables/useNotify.js';
import ZoneDescriptorProxy from 'pages/Zone/Card/ZoneDescriptorProxy.vue';
import { useRoute } from 'vue-router';
@ -159,7 +159,7 @@ function onBuysFetched(data) {
<FetchData
ref="fetchItemLack"
:url="`Tickets/itemLack`"
:params="{ id: entityId }"
:params="{ itemFk: entityId }"
@on-fetch="(data) => (itemLack = data[0])"
auto-load
/>

View File

@ -1,9 +1,18 @@
<script setup>
import { ref } from 'vue';
import axios from 'axios';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import VnSelect from 'src/components/common/VnSelect.vue';
import notifyResults from 'src/utils/notifyResults';
import { showResultsAsTable } from 'src/composables/showResultsTable';
import VnConfirm from 'src/components/ui/VnConfirm.vue';
import HandleSplitDialog from './HandleSplitDialog.vue';
const { openTable } = showResultsAsTable();
const emit = defineEmits(['update-item']);
const quasar = useQuasar();
const { t } = useI18n();
const showChangeItemDialog = ref(false);
const newItem = ref(null);
@ -17,15 +26,18 @@ const $props = defineProps({
const updateItem = async () => {
try {
showChangeItemDialog.value = true;
const rowsToUpdate = $props.selectedRows.map(({ saleFk, quantity }) =>
const rowsToUpdate = $props.selectedRows.map(({ ticketFk, saleFk, quantity }) =>
axios.post(`Sales/replaceItem`, {
ticketFk,
saleFk,
substitutionFk: newItem.value,
quantity,
}),
);
const result = await Promise.allSettled(rowsToUpdate);
notifyResults(result, 'saleFk');
openTable(HandleSplitDialog, result, 'ticketFk', 'changeItem');
// notifyResults(result, 'saleFk');
emit('update-item', newItem.value);
} catch (err) {
console.error('Error updating item:', err);

View File

@ -2,8 +2,10 @@
import { ref } from 'vue';
import axios from 'axios';
import VnInput from 'src/components/common/VnInput.vue';
import notifyResults from 'src/utils/notifyResults';
// import notifyResults from 'src/utils/notifyResults';
import { showResultsAsTable } from 'src/composables/showResultsTable';
import HandleSplitDialog from './HandleSplitDialog.vue';
const { openTable } = showResultsAsTable();
const showChangeQuantityDialog = ref(false);
const newQuantity = ref(null);
const $props = defineProps({
@ -24,7 +26,9 @@ const updateQuantity = async () => {
);
const result = await Promise.allSettled(rowsToUpdate);
notifyResults(result, 'saleFk');
openTable(HandleSplitDialog, result, 'ticketFk', 'changeQuantity');
// notifyResults(result, 'saleFk');
emit('update-quantity', newQuantity.value);
} catch (err) {

View File

@ -3,7 +3,10 @@ import { ref } from 'vue';
import axios from 'axios';
import VnSelect from 'src/components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue';
import notifyResults from 'src/utils/notifyResults';
import { showResultsAsTable } from 'src/composables/showResultsTable';
import HandleSplitDialog from './HandleSplitDialog.vue';
// import notifyResults from 'src/utils/notifyResults';
const { openTable } = showResultsAsTable();
const emit = defineEmits(['update-state']);
const editableStates = ref([]);
@ -25,7 +28,9 @@ const updateState = async () => {
}),
);
const result = await Promise.allSettled(rowsToUpdate);
notifyResults(result, 'ticketFk');
openTable(HandleSplitDialog, result, 'ticketFk', 'changeState');
// notifyResults(result, 'ticketFk');
emit('update-state', newState.value);
} catch (err) {

View File

@ -0,0 +1,239 @@
<script setup>
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
import FetchData from 'components/FetchData.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
const { t } = useI18n();
const newState = ref(null);
const $props = defineProps({
results: {
type: Array,
default: () => [],
},
tag: {
type: String,
default: 'ticketFk',
},
title: {
type: String,
default: 'bulkEdit.edit',
},
action: {
type: String,
default: '',
},
});
console.error($props.tag, 'results', $props.results);
const results = ref($props.results);
// const rowsSelected = ref([]);
const columns = computed(() => [
{
name: 'status',
label: t('negative.split.status'),
field: ({ status }) => status,
sortable: true,
align: 'center',
},
{
name: 'value',
label: t('negative.split.ticket'),
sortable: true,
align: 'center',
},
// {
// name: 'newTicket',
// label: t('negative.split.newTicket'),
// field: ({ newTicket }) => newTicket,
// sortable: true,
// },
{
name: 'statusText',
label: t('negative.split.message'),
sortable: true,
align: 'center',
},
// {
// name: 'actions',
// align: 'center',
// label: t('negative.split.actions'),
// // style: 'padding-left: 100px',
// // headerStyle: 'padding-left: 100px',
// },
]);
function handleMessage(value) {
const actions = {
changeState: {},
};
}
function getIcon(value) {
const icons = {
fulfilled: {
name: 'check_circle',
color: 'secondary',
},
noSplit: {
name: 'warning',
color: 'primary',
},
rejected: {
name: 'close',
color: 'negative',
},
};
return icons[value];
}
const states = ref([]);
</script>
<template>
<QDialog ref="dialogRef" @hide="onDialogHide">
<FetchData
v-if="$props.action === 'changeState'"
url="States"
@on-fetch="(data) => (states = data)"
auto-load
/>
<QCard class="q-pa-sm">
<QCardSection class="row items-center q-pb-none">
<QAvatar
:icon="icon"
color="primary"
text-color="white"
size="xl"
v-if="icon"
/>
<span class="text-h6 text-grey">{{ t($props.title) }}</span>
<QSpace />
<QBtn icon="close" flat round dense v-close-popup />
</QCardSection>
<QCardSection class="row items-center justify-center column items-stretch">
{{ results }}
<QTable
:rows="results"
:columns="columns"
row-key="newTicket"
:no-data-label="t('globals.noResults')"
flat
dense
hide-bottom
auto-load
>
<template #header="props">
<!-- {{ props }} -->
<QTr :props="props">
<!-- <QTh></QTh> -->
<QTh v-for="col in props.cols" :key="col.name" :props="props">
{{ t(col.label) }}
</QTh>
</QTr>
</template>
<template #body-cell-status="{ value }">
<QTd align="center">
<QIcon
:name="`${getIcon(value)?.name}`"
size="xs"
class="cursor-pointer"
:color="getIcon(value)?.color"
>
</QIcon
></QTd>
</template>
<template #body-cell-value="{ row }">
<QTd align="center">
<div class="link" @click.stop>
<span v-if="row.status === 'fulfilled'">
*{{ row.value.data[$props.tag] }}
<TicketDescriptorProxy
:id="row.value.data[$props.tag]"
>
</TicketDescriptorProxy>
</span>
<span v-else>
{{ JSON.parse(row.reason.config.data)?.ticketFk
}}<TicketDescriptorProxy
:id="JSON.parse(row.reason.config.data)?.ticketFk"
>
</TicketDescriptorProxy
></span>
</div>
</QTd>
</template>
<template #body-cell-statusText="{ row }">
<QTd align="center">
<div v-if="row.status === 'fulfilled'">
<div v-if="$props.action === 'changeState'">
{{
states.find(
(state) =>
state.id === row.value.data.stateFk,
)?.name
}}
</div>
<div v-if="$props.action === 'split'">
<span class="link" @click.stop>
{{ value }}
<TicketDescriptorProxy :id="value">
</TicketDescriptorProxy>
</span>
</div>
<div v-if="$props.action === 'changeItem'">
<span class="link" @click.stop>
{{ value }}
<ItemDescriptorProxy :id="value">
</ItemDescriptorProxy>
</span>
</div>
<div v-if="$props.action === 'changeQuantity'">
{{ row.value.data.quantity }}
</div>
</div>
<div v-else>{{ row.reason.response.data.error.message }}</div>
</QTd>
</template>
</QTable>
</QCardSection>
<QCardActions align="right">
<QBtn
:label="t('globals.cancel')"
color="primary"
flat
v-close-popup
/> </QCardActions
></QCard>
</QDialog>
</template>
<style lang="scss" scoped>
.splitRow {
border: 1px solid #ec8916;
border-width: 1px 0 1px 0;
}
.list {
max-height: 100%;
padding: 15px;
width: 100%;
}
.grid-style-transition {
transition:
transform 0.28s,
background-color 0.28s;
}
#true {
background-color: $positive;
}
#false {
background-color: $negative;
}
div.q-dialog__inner > div {
max-width: fit-content !important;
}
</style>

View File

@ -0,0 +1,281 @@
<script setup>
import { computed, onMounted, ref, toRefs } from 'vue';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import { useDialogPluginComponent } from 'quasar';
import VnSelect from 'src/components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnInput from 'src/components/common/VnInput.vue';
const { t } = useI18n();
const showSplitDialog = ref(false);
const newState = ref(null);
const resultSplit = ref([]);
const { dialogRef, onDialogHide } = useDialogPluginComponent();
const $props = defineProps({
tickets: {
type: Array,
default: () => [],
},
});
const tickets = ref($props.tickets ?? []);
const rowBtnDisable = () =>
!(
formData.value?.agencyModeFk &&
formData.value?.date &&
rowsSelected.value.length > 0
);
const rowsSelected = ref([]);
const columns = computed(() => [
{
name: 'status',
label: t('negative.split.status'),
field: ({ status }) => status,
sortable: true,
},
{
name: 'ticket',
label: t('negative.split.ticket'),
field: ({ ticket }) => ticket,
sortable: true,
},
{
name: 'newTicket',
label: t('negative.split.newTicket'),
field: ({ newTicket }) => newTicket,
sortable: true,
},
{
name: 'message',
label: t('negative.split.message'),
field: ({ message }) => message,
sortable: true,
},
// {
// name: 'actions',
// align: 'center',
// label: t('negative.split.actions'),
// // style: 'padding-left: 100px',
// // headerStyle: 'padding-left: 100px',
// },
]);
const formData = ref({ agencies: [] });
const handleDateChanged = async () => {
const { data: agencyData } = await axios.get('Agencies/getLanded', {
params: {
addressFk: 123,
agencyModeFk: 8,
warehouseFk: 1,
shipped: '2001-02-08T23:00:00.000Z',
},
});
if (!agencyData) formData.value.agencies = [];
const { zoneFk } = agencyData;
const { data: zoneData } = await axios.get('Zones/Includingexpired', {
params: { filter: { fields: ['id', 'name'], where: { id: zoneFk } } },
});
formData.value.agencies = zoneData;
if (zoneData.length === 1) formData.value.agencyModeFk = zoneData[0];
// formData.value.dateChanged = false;
};
const ticketsSelected = ref([]);
onMounted(() => {
ticketsSelected.value = [...new Set($props.tickets.map(({ ticketFk }) => ticketFk))];
});
const updateState = async () => {
try {
showSplitDialog.value = true;
const rowsToUpdate = $props.tickets.map(({ ticketFk }) =>
axios.post(`Tickets/state`, {
ticketFk,
code: newState.value,
})
);
await Promise.all(rowsToUpdate);
} catch (err) {
return err;
} finally {
dialogRef.value.hide({ type: 'refresh', refresh: true });
}
};
function getIcon(value) {
const icons = {
split: {
name: 'check_circle',
color: 'secondary',
},
noSplit: {
name: 'warning',
color: 'primary',
},
error: {
name: 'close',
color: 'negative',
},
};
return icons[value];
}
const updateNewTickets = async () => {
tickets.value = $props.tickets.filter((ticket) => ticket.newTicket !== 1000005);
console.log('updateNewTickets');
rowsSelected.value = [];
};
</script>
<template>
<QDialog ref="dialogRef" @hide="onDialogHide" v-model="showSplitDialog">
<QCard class="q-pa-sm">
<QCardSection class="row items-center q-pb-none">
<QAvatar
:icon="icon"
color="primary"
text-color="white"
size="xl"
v-if="icon"
/>
<span class="text-h6 text-grey">{{
t('negative.detail.modal.handleSplited.title')
}}</span>
<QSpace />
<QBtn icon="close" flat round dense v-close-popup />
</QCardSection>
<QCardSection class="row items-center justify-center column items-stretch">
<Qform>
<VnRow class="row q-gutter-md q-mb-md">
<VnInputDate
:label="t('Max date')"
v-model="formData.date"
@update:model-value="(evt) => handleDateChanged()" />
<VnSelect
:disable="formData.agencies.length < 1"
:label="t('Agency')"
v-model="formData.agencyModeFk"
:options="formData.agencies"
option-label="name"
option-value="id" />
<QBtn
icon="save"
:disable="rowBtnDisable()"
color="primary"
flat
rounded
@click="updateNewTickets"
/></VnRow>
</Qform>
<VnPaginate data-key="splitLack" :data="tickets">
<template #body="{ rows }">
<QTable
:rows="rows"
:columns="columns"
selection="multiple"
row-key="newTicket"
v-model:selected="rowsSelected"
:no-data-label="t('globals.noResults')"
flat
dense
hide-bottom
auto-load
:rows-per-page-options="[0]"
hide-pagination
:pagination="{ rowsPerPage: null }"
>
<template #header="props">
<QTr :props="props">
<QTh></QTh>
<QTh
v-for="col in props.cols"
:key="col.name"
:props="props"
>
{{ t(col.label) }}
</QTh>
</QTr>
</template>
<template #body="props">
<QTr :props="props">
<Qtd>
<QCheckbox v-model="props.selected" />
</Qtd>
<QTd
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<span
v-if="
![
'status',
'message',
'actions',
].includes(col.name)
"
>
{{ col.value }}
</span>
<span v-if="'status' === col.name">
<QIcon
:name="`${getIcon(col.value).name}`"
size="xs"
class="cursor-pointer"
:color="getIcon(col.value).color"
>
</QIcon>
</span>
<span v-if="'message' === col.name">message</span>
</QTd></QTr
></template
>
</QTable></template
>
</VnPaginate>
</QCardSection>
<QCardActions align="right">
<QBtn :label="t('globals.cancel')" color="primary" flat v-close-popup />
<QBtn
:label="t('globals.confirm')"
color="primary"
:disable="!newState"
@click="updateState"
unelevated
autofocus
/> </QCardActions
></QCard>
</QDialog>
</template>
<style lang="scss" scoped>
.splitRow {
border: 1px solid #ec8916;
border-width: 1px 0 1px 0;
}
.list {
max-height: 100%;
padding: 15px;
width: 100%;
}
.grid-style-transition {
transition: transform 0.28s, background-color 0.28s;
}
#true {
background-color: $positive;
}
#false {
background-color: $negative;
}
div.q-dialog__inner > div {
max-width: fit-content !important;
}
</style>

View File

@ -286,7 +286,7 @@ negative:
title: Gestionar tickets spliteados
subTitle: Confir fecha y agencia
split:
ticket: Ticket viejo
ticket: Ticket origen
newTicket: Ticket nuevo
status: Estado
message: Mensaje

View File

@ -1,19 +1,18 @@
/// <reference types="cypress" />
// https://redmine.verdnatura.es/issues/8423
describe.skip('Ticket expedtion', () => {
describe('Ticket expedtion', () => {
const tableContent = '.q-table .q-virtual-scroll__content';
const stateTd = 'td:nth-child(9)';
beforeEach(() => {
cy.login('developer');
cy.viewport(1920, 1080);
cy.visit('#/ticket/1/expedition');
});
it('should change the state', () => {
cy.visit('#/ticket/1/expedition');
cy.intercept('GET', /\/api\/Expeditions\/filter/).as('show');
cy.intercept('POST', /\/api\/ExpeditionStates\/addExpeditionState/).as('add');
cy.wait('@show');
cy.selectRows([1, 2]);