0
0
Fork 0

Merge pull request '#8083 add change state btn' (!854) from 8083-hotfix-changeState into master

Reviewed-on: verdnatura/salix-front#854
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
This commit is contained in:
Jorge Penadés 2024-10-22 12:47:58 +00:00
commit 41509c60dd
9 changed files with 204 additions and 246 deletions

View File

@ -127,6 +127,7 @@ const splittedColumns = ref({ columns: [] });
const columnsVisibilitySkipped = ref(); const columnsVisibilitySkipped = ref();
const createForm = ref(); const createForm = ref();
const tableFilterRef = ref([]); const tableFilterRef = ref([]);
const tableRef = ref();
const tableModes = [ const tableModes = [
{ {
@ -308,6 +309,7 @@ defineExpose({
selected, selected,
CrudModelRef, CrudModelRef,
params, params,
tableRef,
}); });
function handleOnDataSaved(_) { function handleOnDataSaved(_) {
@ -398,6 +400,7 @@ function handleOnDataSaved(_) {
</template> </template>
<template #body="{ rows }"> <template #body="{ rows }">
<QTable <QTable
ref="tableRef"
v-bind="table" v-bind="table"
class="vnTable" class="vnTable"
:columns="splittedColumns.columns" :columns="splittedColumns.columns"

View File

@ -0,0 +1,19 @@
<script setup>
import VnSelect from './VnSelect.vue';
defineProps({
selectProps: { type: Object, required: true },
promise: { type: Function, default: () => {} },
});
</script>
<template>
<QBtnDropdown v-bind="$attrs" color="primary">
<VnSelect
v-bind="selectProps"
hide-selected
hide-dropdown-icon
focus-on-mount
@update:model-value="promise"
/>
</QBtnDropdown>
</template>

View File

@ -298,6 +298,7 @@ globals:
stateFk: State stateFk: State
myTeam: My team myTeam: My team
departmentFk: Department departmentFk: Department
changeState: Change state
errors: errors:
statusUnauthorized: Access denied statusUnauthorized: Access denied
statusInternalServerError: An internal server error has ocurred statusInternalServerError: An internal server error has ocurred
@ -540,7 +541,6 @@ ticket:
package: Package package: Package
taxClass: Tax class taxClass: Tax class
services: Services services: Services
changeState: Change state
requester: Requester requester: Requester
atender: Atender atender: Atender
request: Request request: Request

View File

@ -302,6 +302,7 @@ globals:
stateFk: Estado stateFk: Estado
myTeam: Mi equipo myTeam: Mi equipo
departmentFk: Departamento departmentFk: Departamento
changeState: Cambiar estado
errors: errors:
statusUnauthorized: Acceso denegado statusUnauthorized: Acceso denegado
statusInternalServerError: Ha ocurrido un error interno del servidor statusInternalServerError: Ha ocurrido un error interno del servidor
@ -549,7 +550,6 @@ ticket:
package: Embalaje package: Embalaje
taxClass: Tipo IVA taxClass: Tipo IVA
services: Servicios services: Servicios
changeState: Cambiar estado
requester: Solicitante requester: Solicitante
atender: Comprador atender: Comprador
request: Petición de compra request: Petición de compra

View File

@ -204,7 +204,7 @@ function claimUrl(section) {
top top
color="black" color="black"
text-color="white" text-color="white"
:label="t('ticket.summary.changeState')" :label="t('globals.changeState')"
> >
<QList> <QList>
<QVirtualScroll <QVirtualScroll

View File

@ -1,201 +1,129 @@
<script setup> <script setup>
import { onMounted, ref, computed, onUnmounted, reactive, watch } from 'vue'; import { onMounted, ref, computed, onUnmounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import VnInput from 'src/components/common/VnInput.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import TicketEditManaProxy from './TicketEditMana.vue';
import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import ExpeditionNewTicket from './ExpeditionNewTicket.vue'; import ExpeditionNewTicket from './ExpeditionNewTicket.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { toCurrency, toPercentage } from 'src/filters';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import { useVnConfirm } from 'composables/useVnConfirm'; import { useVnConfirm } from 'composables/useVnConfirm';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import { toDateTimeFormat } from 'src/filters/date'; import { toDateTimeFormat } from 'src/filters/date';
import axios from 'axios'; import axios from 'axios';
import VnTable from 'src/components/VnTable/VnTable.vue';
import VnBtnSelect from 'src/components/common/VnBtnSelect.vue';
import FetchData from 'src/components/FetchData.vue';
const route = useRoute(); const route = useRoute();
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const { notify } = useNotify(); const { notify } = useNotify();
const { openConfirmationModal } = useVnConfirm(); const { openConfirmationModal } = useVnConfirm();
const editPriceProxyRef = ref(null);
const newTicketDialogRef = ref(null); const newTicketDialogRef = ref(null);
const logsTableDialogRef = ref(null); const logsTableDialogRef = ref(null);
const vnTableRef = ref();
const expeditionsLogsData = ref([]); const expeditionsLogsData = ref([]);
const selectedExpeditions = ref([]); const selectedExpeditions = ref([]);
const allColumnNames = ref([]); const allColumnNames = ref([]);
const visibleColumns = ref([]);
const newTicketWithRoute = ref(false); const newTicketWithRoute = ref(false);
const selectedRows = ref([]);
const exprBuilder = (param, value) => { const hasSelectedRows = computed(() => selectedRows.value.length > 0);
switch (param) { const expeditionStateTypes = ref([]);
case 'expeditionFk':
return { id: value };
case 'packageItemName':
return { packagingItemFk: value };
}
};
const expeditionsFilter = computed(() => ({ const expeditionsFilter = computed(() => ({
where: { ticketFk: route.params.id }, where: { ticketFk: route.params.id },
order: ['created DESC'], order: ['created DESC'],
})); }));
const expeditionsArrayData = useArrayData('ticketExpeditions', {
url: 'Expeditions/filter',
filter: expeditionsFilter.value,
exprBuilder: exprBuilder,
});
const expeditionsStore = expeditionsArrayData.store;
const ticketExpeditions = computed(() => expeditionsStore.data);
const ticketArrayData = useArrayData('ticketData'); const ticketArrayData = useArrayData('ticketData');
const ticketStore = ticketArrayData.store; const ticketStore = ticketArrayData.store;
const ticketData = computed(() => ticketStore.data); const ticketData = computed(() => ticketStore.data);
const refetchExpeditions = async () => {
await expeditionsArrayData.applyFilter({
filter: expeditionsFilter.value,
});
};
watch(
() => route.params.id,
async () => await refetchExpeditions(),
{ immediate: true }
);
const params = reactive({});
const applyColumnFilter = async (col) => {
try {
const paramKey = col.columnFilter?.filterParamKey || col.field;
params[paramKey] = col.columnFilter.filterValue;
await expeditionsArrayData.addFilter({ filter: expeditionsFilter.value, params });
} catch (err) {
console.error('Error applying column filter', err);
}
};
const getInputEvents = (col) => {
return col.columnFilter.type === 'select'
? { 'update:modelValue': () => applyColumnFilter(col) }
: {
'keyup.enter': () => applyColumnFilter(col),
};
};
const columns = computed(() => [ const columns = computed(() => [
{ {
align: 'left',
label: t('expedition.id'), label: t('expedition.id'),
name: 'id', name: 'id',
field: 'id', chip: {
align: 'left', condition: () => true,
sortable: true, },
isId: true,
columnFilter: { columnFilter: {
component: VnInput, inWhere: true,
type: 'text',
filterParamKey: 'expeditionFk',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
}, },
}, },
{ {
label: t('expedition.item'), label: t('expedition.item'),
name: 'item', name: 'packagingItemFk',
align: 'left', align: 'left',
cardVisible: true,
columnFilter: { columnFilter: {
component: VnInput, inWhere: true,
type: 'text',
filterParamKey: 'packageItemName',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
}, },
}, },
{ {
label: t('expedition.name'), label: t('expedition.name'),
name: 'name', name: 'packageItemName',
field: 'packageItemName',
align: 'left', align: 'left',
isTitle: true,
columnFilter: { columnFilter: {
component: VnSelect, inWhere: true,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
url: 'Items',
fields: ['id', 'name'],
'sort-by': 'name ASC',
'option-value': 'id',
'option-label': 'name',
dense: true,
},
}, },
}, },
{ {
label: t('expedition.packageType'), label: t('expedition.packageType'),
name: 'packageType', name: 'freightItemName',
field: 'freightItemName',
align: 'left', align: 'left',
columnFilter: { columnFilter: {
component: VnInput, inWhere: true,
type: 'text',
// filterParamKey: 'expeditionFk',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
}, },
}, },
{ {
label: t('expedition.counter'), label: t('expedition.counter'),
name: 'counter', name: 'counter',
field: 'counter',
align: 'left', align: 'left',
columnFilter: null, columnFilter: {
inWhere: true,
},
}, },
{ {
label: t('expedition.externalId'), label: t('expedition.externalId'),
name: 'externalId', name: 'externalId',
field: 'externalId',
align: 'left', align: 'left',
columnFilter: null, cardVisible: true,
columnFilter: {
inWhere: true,
},
}, },
{ {
label: t('expedition.created'), label: t('expedition.created'),
name: 'created', name: 'created',
field: 'created',
align: 'left', align: 'left',
columnFilter: null, cardVisible: true,
format: (value) => toDateTimeFormat(value), format: (row) => toDateTimeFormat(row.created),
}, },
{ {
label: t('expedition.state'), label: t('expedition.state'),
name: 'state', name: 'state',
field: 'state',
align: 'left', align: 'left',
columnFilter: null, cardVisible: true,
columnFilter: { inWhere: true },
}, },
{ {
label: '', align: 'right',
name: 'history', name: 'tableActions',
align: 'left', actions: [
columnFilter: null, {
title: t('expedition.historyAction'),
icon: 'history',
isPrimary: true,
action: (row) => showLog(row),
},
],
}, },
]); ]);
@ -204,23 +132,29 @@ const logTableColumns = computed(() => [
label: t('expedition.state'), label: t('expedition.state'),
name: 'state', name: 'state',
field: 'state', field: 'state',
align: 'left', align: 'center',
sortable: true, sortable: true,
}, },
{ {
label: t('expedition.name'), label: t('expedition.name'),
name: 'name', name: 'name',
align: 'name', field: 'name',
align: 'center',
columnFilter: null, columnFilter: null,
}, },
{ {
label: t('expedition.created'), label: t('expedition.created'),
name: 'created', name: 'created',
field: 'created', field: 'created',
align: 'left', align: 'center',
columnFilter: null,
format: (value) => toDateTimeFormat(value), format: (value) => toDateTimeFormat(value),
}, },
{
label: t('expedition.isScanned'),
name: 'isScanned',
field: 'isScanned',
align: 'center',
},
]); ]);
const showNewTicketDialog = (withRoute = false) => { const showNewTicketDialog = (withRoute = false) => {
@ -230,12 +164,10 @@ const showNewTicketDialog = (withRoute = false) => {
const deleteExpedition = async () => { const deleteExpedition = async () => {
try { try {
const expeditionIds = selectedExpeditions.value.map( const expeditionIds = selectedRows.value.map((expedition) => expedition.id);
(expedition) => expedition.id
);
const params = { expeditionIds }; const params = { expeditionIds };
await axios.post('Expeditions/deleteExpeditions', params); await axios.post('Expeditions/deleteExpeditions', params);
await refetchExpeditions(); vnTableRef.value.reload();
selectedExpeditions.value = []; selectedExpeditions.value = [];
notify(t('expedition.expeditionRemoved'), 'positive'); notify(t('expedition.expeditionRemoved'), 'positive');
} catch (error) { } catch (error) {
@ -255,10 +187,20 @@ const getExpeditionState = async (expedition) => {
order: ['created DESC'], order: ['created DESC'],
}; };
const { data } = await axios.get(`ExpeditionStates/filter`, { const { data: expeditionStates } = await axios.get(`ExpeditionStates/filter`, {
params: { filter: JSON.stringify(filter) }, params: { filter: JSON.stringify(filter) },
}); });
expeditionsLogsData.value = data; const { data: scannedStates } = await axios.get(`ExpeditionStates`, {
params: { filter: JSON.stringify(filter), fields: ['id', 'isScanned'] },
});
expeditionsLogsData.value = expeditionStates.map((state) => {
const scannedState = scannedStates.find((s) => s.id === state.id);
return {
...state,
isScanned: scannedState ? scannedState.isScanned : false,
};
});
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
@ -274,22 +216,39 @@ onUnmounted(() => (stateStore.rightDrawer = false));
</script> </script>
<template> <template>
<FetchData
url="expeditionStateTypes"
@on-fetch="(data) => (expeditionStateTypes = data)"
auto-load
/>
<VnSubToolbar> <VnSubToolbar>
<template #st-data>
<TableVisibleColumns
:all-columns="allColumnNames"
table-code="expeditionIndex"
labels-traductions-path="expedition"
@on-config-saved="visibleColumns = [...$event, 'history']"
/>
</template>
<template #st-actions> <template #st-actions>
<QBtnGroup push class="q-gutter-x-sm" flat> <QBtnGroup push class="q-gutter-x-sm" flat>
<VnBtnSelect
:disable="!hasSelectedRows"
color="primary"
:label="t('globals.changeState')"
:select-props="{
options: expeditionStateTypes,
optionLabel: 'description',
}"
:promise="
async (stateTypeFk) => {
await vnTableRef.CrudModelRef.saveChanges({
updates: selectedRows.map(({ id }) => ({
data: { stateTypeFk },
where: { id },
})),
});
vnTableRef.tableRef.clearSelection();
}
"
/>
<QBtnDropdown <QBtnDropdown
ref="btnDropdownRef" ref="btnDropdownRef"
color="primary" color="primary"
:label="t('expedition.move')" :label="t('expedition.move')"
:disable="!selectedExpeditions.length" :disable="!hasSelectedRows"
> >
<template #label> <template #label>
<QTooltip>{{ t('Select lines to see the options') }}</QTooltip> <QTooltip>{{ t('Select lines to see the options') }}</QTooltip>
@ -322,7 +281,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
</QList> </QList>
</QBtnDropdown> </QBtnDropdown>
<QBtn <QBtn
:disable="!selectedExpeditions.length" :disable="!hasSelectedRows"
icon="delete" icon="delete"
color="primary" color="primary"
@click=" @click="
@ -332,120 +291,50 @@ onUnmounted(() => (stateStore.rightDrawer = false));
deleteExpedition deleteExpedition
) )
" "
/> >
<QTooltip>{{ t('expedition.removeExpedition') }}</QTooltip>
</QBtn>
</QBtnGroup> </QBtnGroup>
</template> </template>
</VnSubToolbar> </VnSubToolbar>
<VnTable
<QTable ref="vnTableRef"
:rows="ticketExpeditions" data-key="TicketExpedition"
url="Expeditions/filter"
search-url="expeditions"
:columns="columns" :columns="columns"
row-key="id" :filter="expeditionsFilter"
:pagination="{ rowsPerPage: 0 }" v-model:selected="selectedRows"
class="full-width q-mt-md" :table="{
selection="multiple" 'row-key': 'id',
v-model:selected="selectedExpeditions" selection: 'multiple',
:visible-columns="visibleColumns" }"
:no-data-label="t('globals.noResults')" save-url="Expeditions/crud"
auto-load
:expr-builder="
(param, value) => {
switch (param) {
case 'expeditionFk':
return { id: value };
case 'packageItemName':
return { packagingItemFk: value };
}
}
"
order="created DESC"
> >
<template #top-row="{ cols }"> <template #column-packagingItemFk="{ row }">
<QTr> <span class="link" @click.stop>
<QTd /> {{ row.packagingItemFk }}
<QTd v-for="(col, index) in cols" :key="index" style="max-width: 100px">
<component
:is="col.columnFilter.component"
v-if="col.columnFilter"
v-model="col.columnFilter.filterValue"
v-bind="col.columnFilter.attrs"
v-on="col.columnFilter.event(col)"
dense
/>
</QTd>
</QTr>
</template>
<template #body-cell-item="{ row }">
<QTd auto-width @click.stop>
<QBtn flat color="primary">{{ row.packagingItemFk }}</QBtn>
<ItemDescriptorProxy :id="row.packagingItemFk" /> <ItemDescriptorProxy :id="row.packagingItemFk" />
</QTd> </span>
</template> </template>
<template #body-cell-available="{ row }"> </VnTable>
<QTd @click.stop>
<QBadge :color="row.available < 0 ? 'alert' : 'transparent'" dense>
{{ row.available }}
</QBadge>
</QTd>
</template>
<template #body-cell-price="{ row }">
<QTd>
<template v-if="isTicketEditable && row.id">
<QBtn flat color="primary" dense @click="onOpenEditPricePopover(row)">
{{ toCurrency(row.price) }}
</QBtn>
<TicketEditManaProxy
ref="editPriceProxyRef"
:mana="mana"
:new-price="getNewPrice"
@save="updatePrice(row)"
>
<VnInput
v-model.number="edit.price"
:label="t('ticketSale.price')"
type="number"
/>
</TicketEditManaProxy>
</template>
<span v-else>{{ toCurrency(row.price) }}</span>
</QTd>
</template>
<template #body-cell-discount="{ row }">
<QTd>
<template v-if="!isLocked && row.id">
<QBtn
flat
color="primary"
dense
@click="onOpenEditDiscountPopover(row)"
>
{{ toPercentage(row.discount / 100) }}
</QBtn>
<TicketEditManaProxy
:mana="mana"
:new-price="getNewPrice"
@save="changeDiscount(row)"
>
<VnInput
v-model.number="edit.discount"
:label="t('ticketSale.discount')"
type="number"
/>
</TicketEditManaProxy>
</template>
<span v-else>{{ toPercentage(row.discount / 100) }}</span>
</QTd>
</template>
<template #body-cell-history="{ row }">
<QTd>
<QBtn
@click.stop="showLog(row)"
color="primary"
icon="history"
size="md"
flat
>
<QTooltip class="text-no-wrap">
{{ t('expedition.historyAction') }}
</QTooltip>
</QBtn>
</QTd>
</template>
</QTable>
<QDialog ref="newTicketDialogRef" transition-show="scale" transition-hide="scale"> <QDialog ref="newTicketDialogRef" transition-show="scale" transition-hide="scale">
<ExpeditionNewTicket <ExpeditionNewTicket
:ticket="ticketData" :ticket="ticketData"
:with-route="newTicketWithRoute" :with-route="newTicketWithRoute"
:selected-expeditions="selectedExpeditions" :selected-expeditions="selectedRows"
/> />
</QDialog> </QDialog>
<QDialog ref="logsTableDialogRef" transition-show="scale" transition-hide="scale"> <QDialog ref="logsTableDialogRef" transition-show="scale" transition-hide="scale">
@ -454,12 +343,23 @@ onUnmounted(() => (stateStore.rightDrawer = false));
data-key="TicketExpeditionLog" data-key="TicketExpeditionLog"
:rows="expeditionsLogsData" :rows="expeditionsLogsData"
:columns="logTableColumns" :columns="logTableColumns"
class="q-pa-sm" class="q-pa-md full-width"
> >
<template #body-cell-name="{ row }"> <template #body-cell-name="{ row }">
<QTd auto-width> <QTd style="text-align: center">
<QBtn flat dense color="primary">{{ row.name }}</QBtn> <span class="link" @click.stop>
<WorkerDescriptorProxy :id="row.workerFk" /> <QBtn flat dense>{{ row.name }}</QBtn>
<WorkerDescriptorProxy :id="row.workerFk" />
</span>
</QTd>
</template>
<template #body-cell-isScanned="{ row }">
<QTd style="text-align: center">
<QCheckbox disable v-model="row.isScanned">
{{
row.isScanned === 1 ? t('expedition.yes') : t('expedition.no')
}}
</QCheckbox>
</QTd> </QTd>
</template> </template>
</QTable> </QTable>

View File

@ -105,7 +105,7 @@ async function changeState(value) {
ref="stateBtnDropdownRef" ref="stateBtnDropdownRef"
color="black" color="black"
text-color="white" text-color="white"
:label="t('ticket.summary.changeState')" :label="t('globals.changeState')"
:disable="!isEditable()" :disable="!isEditable()"
> >
<VnSelect <VnSelect

View File

@ -0,0 +1,28 @@
/// <reference types="cypress" />
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);
});
it('should change the state', () => {
cy.visit('#/ticket/1/expedition');
cy.intercept('GET', /\/api\/Expeditions\/filter/).as('expeditions');
cy.intercept('POST', /\/api\/Expeditions\/crud/).as('crud');
cy.wait('@expeditions');
cy.selectRows([1, 2]);
cy.get('#subToolbar [aria-controls]:nth-child(1)').click();
cy.get('.q-menu .q-item').contains('Perdida').click();
cy.wait('@crud');
cy.get(`${tableContent} tr:nth-child(-n+2) ${stateTd}`).each(($el) => {
cy.wrap($el).contains('Perdida');
});
});
});

View File

@ -152,6 +152,14 @@ Cypress.Commands.add('notificationHas', (selector, text) => {
cy.get(selector).should('have.text', text); cy.get(selector).should('have.text', text);
}); });
Cypress.Commands.add('selectRows', (rows) => {
rows.forEach((row) => {
cy.get('.q-table .q-virtual-scroll__content tr .q-checkbox__inner')
.eq(row - 1)
.click();
});
});
Cypress.Commands.add('fillRow', (rowSelector, data) => { Cypress.Commands.add('fillRow', (rowSelector, data) => {
// Usar el selector proporcionado para obtener la fila deseada // Usar el selector proporcionado para obtener la fila deseada
cy.waitForElement('tbody'); cy.waitForElement('tbody');