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 createForm = ref();
const tableFilterRef = ref([]);
const tableRef = ref();
const tableModes = [
{
@ -308,6 +309,7 @@ defineExpose({
selected,
CrudModelRef,
params,
tableRef,
});
function handleOnDataSaved(_) {
@ -398,6 +400,7 @@ function handleOnDataSaved(_) {
</template>
<template #body="{ rows }">
<QTable
ref="tableRef"
v-bind="table"
class="vnTable"
: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
myTeam: My team
departmentFk: Department
changeState: Change state
errors:
statusUnauthorized: Access denied
statusInternalServerError: An internal server error has ocurred
@ -540,7 +541,6 @@ ticket:
package: Package
taxClass: Tax class
services: Services
changeState: Change state
requester: Requester
atender: Atender
request: Request

View File

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

View File

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

View File

@ -1,201 +1,129 @@
<script setup>
import { onMounted, ref, computed, onUnmounted, reactive, watch } from 'vue';
import { onMounted, ref, computed, onUnmounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import VnInput from 'src/components/common/VnInput.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 ExpeditionNewTicket from './ExpeditionNewTicket.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import { useStateStore } from 'stores/useStateStore';
import { toCurrency, toPercentage } from 'src/filters';
import { useArrayData } from 'composables/useArrayData';
import { useVnConfirm } from 'composables/useVnConfirm';
import useNotify from 'src/composables/useNotify.js';
import { toDateTimeFormat } from 'src/filters/date';
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 stateStore = useStateStore();
const { t } = useI18n();
const { notify } = useNotify();
const { openConfirmationModal } = useVnConfirm();
const editPriceProxyRef = ref(null);
const newTicketDialogRef = ref(null);
const logsTableDialogRef = ref(null);
const vnTableRef = ref();
const expeditionsLogsData = ref([]);
const selectedExpeditions = ref([]);
const allColumnNames = ref([]);
const visibleColumns = ref([]);
const newTicketWithRoute = ref(false);
const exprBuilder = (param, value) => {
switch (param) {
case 'expeditionFk':
return { id: value };
case 'packageItemName':
return { packagingItemFk: value };
}
};
const selectedRows = ref([]);
const hasSelectedRows = computed(() => selectedRows.value.length > 0);
const expeditionStateTypes = ref([]);
const expeditionsFilter = computed(() => ({
where: { ticketFk: route.params.id },
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 ticketStore = ticketArrayData.store;
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(() => [
{
align: 'left',
label: t('expedition.id'),
name: 'id',
field: 'id',
align: 'left',
sortable: true,
chip: {
condition: () => true,
},
isId: true,
columnFilter: {
component: VnInput,
type: 'text',
filterParamKey: 'expeditionFk',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
inWhere: true,
},
},
{
label: t('expedition.item'),
name: 'item',
name: 'packagingItemFk',
align: 'left',
cardVisible: true,
columnFilter: {
component: VnInput,
type: 'text',
filterParamKey: 'packageItemName',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
inWhere: true,
},
},
{
label: t('expedition.name'),
name: 'name',
field: 'packageItemName',
name: 'packageItemName',
align: 'left',
isTitle: true,
columnFilter: {
component: VnSelect,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
url: 'Items',
fields: ['id', 'name'],
'sort-by': 'name ASC',
'option-value': 'id',
'option-label': 'name',
dense: true,
},
inWhere: true,
},
},
{
label: t('expedition.packageType'),
name: 'packageType',
field: 'freightItemName',
name: 'freightItemName',
align: 'left',
columnFilter: {
component: VnInput,
type: 'text',
// filterParamKey: 'expeditionFk',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
inWhere: true,
},
},
{
label: t('expedition.counter'),
name: 'counter',
field: 'counter',
align: 'left',
columnFilter: null,
columnFilter: {
inWhere: true,
},
},
{
label: t('expedition.externalId'),
name: 'externalId',
field: 'externalId',
align: 'left',
columnFilter: null,
cardVisible: true,
columnFilter: {
inWhere: true,
},
},
{
label: t('expedition.created'),
name: 'created',
field: 'created',
align: 'left',
columnFilter: null,
format: (value) => toDateTimeFormat(value),
cardVisible: true,
format: (row) => toDateTimeFormat(row.created),
},
{
label: t('expedition.state'),
name: 'state',
field: 'state',
align: 'left',
columnFilter: null,
cardVisible: true,
columnFilter: { inWhere: true },
},
{
label: '',
name: 'history',
align: 'left',
columnFilter: null,
align: 'right',
name: 'tableActions',
actions: [
{
title: t('expedition.historyAction'),
icon: 'history',
isPrimary: true,
action: (row) => showLog(row),
},
],
},
]);
@ -204,23 +132,29 @@ const logTableColumns = computed(() => [
label: t('expedition.state'),
name: 'state',
field: 'state',
align: 'left',
align: 'center',
sortable: true,
},
{
label: t('expedition.name'),
name: 'name',
align: 'name',
field: 'name',
align: 'center',
columnFilter: null,
},
{
label: t('expedition.created'),
name: 'created',
field: 'created',
align: 'left',
columnFilter: null,
align: 'center',
format: (value) => toDateTimeFormat(value),
},
{
label: t('expedition.isScanned'),
name: 'isScanned',
field: 'isScanned',
align: 'center',
},
]);
const showNewTicketDialog = (withRoute = false) => {
@ -230,12 +164,10 @@ const showNewTicketDialog = (withRoute = false) => {
const deleteExpedition = async () => {
try {
const expeditionIds = selectedExpeditions.value.map(
(expedition) => expedition.id
);
const expeditionIds = selectedRows.value.map((expedition) => expedition.id);
const params = { expeditionIds };
await axios.post('Expeditions/deleteExpeditions', params);
await refetchExpeditions();
vnTableRef.value.reload();
selectedExpeditions.value = [];
notify(t('expedition.expeditionRemoved'), 'positive');
} catch (error) {
@ -255,10 +187,20 @@ const getExpeditionState = async (expedition) => {
order: ['created DESC'],
};
const { data } = await axios.get(`ExpeditionStates/filter`, {
const { data: expeditionStates } = await axios.get(`ExpeditionStates/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) {
console.error(error);
}
@ -274,22 +216,39 @@ onUnmounted(() => (stateStore.rightDrawer = false));
</script>
<template>
<FetchData
url="expeditionStateTypes"
@on-fetch="(data) => (expeditionStateTypes = data)"
auto-load
/>
<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>
<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
ref="btnDropdownRef"
color="primary"
:label="t('expedition.move')"
:disable="!selectedExpeditions.length"
:disable="!hasSelectedRows"
>
<template #label>
<QTooltip>{{ t('Select lines to see the options') }}</QTooltip>
@ -322,7 +281,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
</QList>
</QBtnDropdown>
<QBtn
:disable="!selectedExpeditions.length"
:disable="!hasSelectedRows"
icon="delete"
color="primary"
@click="
@ -332,120 +291,50 @@ onUnmounted(() => (stateStore.rightDrawer = false));
deleteExpedition
)
"
/>
>
<QTooltip>{{ t('expedition.removeExpedition') }}</QTooltip>
</QBtn>
</QBtnGroup>
</template>
</VnSubToolbar>
<QTable
:rows="ticketExpeditions"
<VnTable
ref="vnTableRef"
data-key="TicketExpedition"
url="Expeditions/filter"
search-url="expeditions"
:columns="columns"
row-key="id"
:pagination="{ rowsPerPage: 0 }"
class="full-width q-mt-md"
selection="multiple"
v-model:selected="selectedExpeditions"
:visible-columns="visibleColumns"
:no-data-label="t('globals.noResults')"
:filter="expeditionsFilter"
v-model:selected="selectedRows"
:table="{
'row-key': 'id',
selection: 'multiple',
}"
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 }">
<QTr>
<QTd />
<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>
<template #column-packagingItemFk="{ row }">
<span class="link" @click.stop>
{{ row.packagingItemFk }}
<ItemDescriptorProxy :id="row.packagingItemFk" />
</QTd>
</span>
</template>
<template #body-cell-available="{ row }">
<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>
</VnTable>
<QDialog ref="newTicketDialogRef" transition-show="scale" transition-hide="scale">
<ExpeditionNewTicket
:ticket="ticketData"
:with-route="newTicketWithRoute"
:selected-expeditions="selectedExpeditions"
:selected-expeditions="selectedRows"
/>
</QDialog>
<QDialog ref="logsTableDialogRef" transition-show="scale" transition-hide="scale">
@ -454,12 +343,23 @@ onUnmounted(() => (stateStore.rightDrawer = false));
data-key="TicketExpeditionLog"
:rows="expeditionsLogsData"
:columns="logTableColumns"
class="q-pa-sm"
class="q-pa-md full-width"
>
<template #body-cell-name="{ row }">
<QTd auto-width>
<QBtn flat dense color="primary">{{ row.name }}</QBtn>
<WorkerDescriptorProxy :id="row.workerFk" />
<QTd style="text-align: center">
<span class="link" @click.stop>
<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>
</template>
</QTable>

View File

@ -105,7 +105,7 @@ async function changeState(value) {
ref="stateBtnDropdownRef"
color="black"
text-color="white"
:label="t('ticket.summary.changeState')"
:label="t('globals.changeState')"
:disable="!isEditable()"
>
<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);
});
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) => {
// Usar el selector proporcionado para obtener la fila deseada
cy.waitForElement('tbody');