feat: refs #8441 add VehicleInvoiceIn component with invoice management functionality #1567

Open
jtubau wants to merge 94 commits from 8441-createVehicleInvoiceInSection into dev
16 changed files with 586 additions and 97 deletions

View File

@ -170,7 +170,7 @@ const handleEnter = (event) => {
:input-style="{ color: textColor }"
@click="isPopupOpen = !isPopupOpen"
@keydown="isPopupOpen = false"
@blur="formatDate"
@focusout="formatDate"
@keydown.enter.prevent="handleEnter"
hide-bottom-space
:data-cy="($attrs['data-cy'] ?? $attrs.label) + '_inputDate'"

View File

@ -352,6 +352,7 @@ globals:
vehicle: Vehicle
entryPreAccount: Pre-account
management: Worker management
assignedInvoices: Assigned Invoices
unsavedPopup:
title: Unsaved changes will be lost
subtitle: Are you sure exit without saving?

View File

@ -355,6 +355,7 @@ globals:
vehicle: Vehículo
entryPreAccount: Precontabilizar
management: Gestión de trabajadores
assignedInvoices: Facturas vinculadas
unsavedPopup:
title: Los cambios que no haya guardado se perderán
subtitle: ¿Seguro que quiere salir sin guardar?

View File

@ -0,0 +1,140 @@
<script setup>
import VnTable from 'src/components/VnTable/VnTable.vue';
import InvoiceInDescriptorProxy from 'pages/InvoiceIn/Card/InvoiceInDescriptorProxy.vue';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
import { toDate, toCurrency } from 'src/filters/index';
import { useRoute } from 'vue-router';
import { ref, computed } from 'vue';
import { useVnConfirm } from 'composables/useVnConfirm';
import { useI18n } from 'vue-i18n';
import useNotify from 'src/composables/useNotify.js';
import axios from 'axios';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
const tableRef = ref();
const { t } = useI18n();
const route = useRoute();
const { notify } = useNotify();
const dataKey = 'VehicleInvoiceIn';
const { openConfirmationModal } = useVnConfirm();
const columns = computed(() => [
{
align: 'left',
name: 'issued',
label: t('invoiceIn.list.issued'),
columnFilter: {
component: 'date',
},
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.issued)),
cardVisible: true,
},
{
align: 'left',
name: 'supplierFk',
label: t('invoiceIn.list.supplier'),
columnFilter: {
component: 'select',
attrs: {
url: 'Suppliers',
fields: ['id', 'name'],
},
},
format: ({ supplierName }) => supplierName,
columnClass: 'expand',
cardVisible: true,
},
{
align: 'left',
name: 'supplierRef',
label: t('invoiceIn.supplierRef'),
cardVisible: true,
},
{
align: 'left',
name: 'amount',
label: t('invoiceIn.list.amount'),
format: ({ amount }) => toCurrency(amount),
columnFilter: false,
cardVisible: true,
},
{
align: 'right',
name: 'tableActions',
actions: [
{
title: t('vehicle.ticket.unassignInvoice'),
icon: 'delete',
action: (row) =>
openConfirmationModal(
t('vehicle.ticket.unassignInvoice'),
t('vehicle.ticket.unassignInvoiceConfirmation'),
() => unassignInvoice(row.id),
),
isPrimary: true,
},
],
},
]);
async function unassignInvoice(id) {
try {
await axios.delete(`VehicleInvoiceIns/${id}`);
jtubau marked this conversation as resolved Outdated

Si falla el delete mostrará la notificación de dataSaved Haz algo así:
async () => { try { await axios.delete(Vehicles/${entity.id}); notify('vehicle.remove', 'positive'); $router.push({ name: 'VehicleList' }); } catch (e) { throw e; } }

Si falla el delete mostrará la notificación de _dataSaved_ Haz algo así: ` async () => { try { await axios.delete(`Vehicles/${entity.id}`); notify('vehicle.remove', 'positive'); $router.push({ name: 'VehicleList' }); } catch (e) { throw e; } }`
notify(t('vehicle.ticket.unlinkedInvoice'), 'positive');
tableRef.value.reload();
} catch (e) {
throw e;
jtubau marked this conversation as resolved Outdated

throw e

throw e
}
}
</script>
<template>
<VnTable
ref="tableRef"
:data-key="dataKey"
:url="`vehicles/${route.params.id}/getInvoices`"
:columns="columns"
search-url="vehicleInvoiceIns"
:order="['issued DESC', 'supplierRef ASC']"
:create="{
urlCreate: 'VehicleInvoiceIns',
title: t('vehicle.ticket.assignInvoice'),
formInitialData: {
vehicleFk: parseInt(route.params.id, 10),
},
onDataSaved: ({ id }) => tableRef.reload(),
}"
auto-load
>
<template #column-supplierFk="{ row }">
<span class="link" @click.stop>
{{ row.supplierName }}
<SupplierDescriptorProxy :id="row.supplierId" />
</span>
</template>
<template #column-supplierRef="{ row }">
<span class="link" @click.stop>
{{ row.supplierRef }}
<InvoiceInDescriptorProxy :id="row.invoiceInFk" />
</span>
</template>
<template #more-create-dialog="{ data }">
<VnSelect
url="invoiceIns"
:label="t('invoiceIn.supplierRef')"
:fields="['id', 'supplierRef', 'supplierFk']"
:filter-options="['id', 'supplierRef']"
v-model="data.invoiceInFk"
option-label="supplierRef"
:required="true"
>
</VnSelect>
<VnInputNumber
:label="t('invoiceIn.list.amount')"
v-model="data.amount"
required
/>
</template>
</VnTable>
</template>

View File

@ -1,16 +1,20 @@
<script setup>
import { computed } from 'vue';
import { computed, ref } from 'vue';
import { useRoute } from 'vue-router';
import { dashIfEmpty, toCurrency, toDate } from 'src/filters';
import { downloadFile } from 'src/composables/downloadFile';
import FetchData from 'src/components/FetchData.vue';
import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import VnTitle from 'src/components/common/VnTitle.vue';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
import InvoiceInDescriptorProxy from 'src/pages/InvoiceIn/Card/InvoiceInDescriptorProxy.vue';
import VehicleFilter from '../VehicleFilter.js';
import { downloadFile } from 'src/composables/downloadFile';
import { dashIfEmpty } from 'src/filters';
const props = defineProps({ id: { type: [Number, String], default: null } });
const invoices = ref([]);
const route = useRoute();
const entityId = computed(() => props.id || +route.params.id);
const baseLink = `#/${route.meta.moduleName.toLowerCase()}/vehicle/${entityId.value}`;
@ -23,6 +27,11 @@ const links = {
};
</script>
<template>
<FetchData
:url="`Vehicles/${entityId}/getInvoices`"
auto-load
@on-fetch="(data) => (invoices = data)"
/>
<CardSummary
data-key="Vehicle"
:url="`Vehicles/${entityId}`"
@ -132,6 +141,45 @@ const links = {
</QList>
</QCardSection>
</QCard>
<QCard class="vn-max">
<VnTitle
:url="links['invoice-in']"
:text="$t('globals.pageTitles.assignedInvoices')"
/>
<QTable :rows="invoices" style="text-align: center">
<template #body-cell="{ value }">
<QTd>{{ value }}</QTd>
</template>
<template #header="props">
<QTr class="tr-header" :props="props">
<QTh auto-width>{{ $t('invoiceIn.list.issued') }}</QTh>
<QTh auto-width>{{ $t('invoiceIn.list.supplier') }}</QTh>
<QTh auto-width>{{ $t('invoiceIn.supplierRef') }}</QTh>
<QTh auto-width>{{ $t('invoiceIn.list.amount') }}</QTh>
</QTr>
</template>
<template #body="props">
<QTr :props="props">
<QTd>{{ toDate(props.row.issued) }}</QTd>
<QTd>
<span class="link">
{{ props.row.supplierName }}
<SupplierDescriptorProxy :id="props.row.supplierId" />
</span>
</QTd>
<QTd>
<span class="link">
{{ props.row.supplierRef }}
<InvoiceInDescriptorProxy
:id="props.row.supplierId"
/>
</span>
</QTd>
<QTd>{{ toCurrency(props.row.amount) }}</QTd>
</QTr>
</template>
</QTable>
</QCard>
</template>
</CardSummary>
</template>

View File

@ -108,6 +108,13 @@ const columns = computed(() => [
options: countries.value,
},
},
{
name: 'isActive',
label: t('globals.active'),
visible: false,
cardVisible: false,
component: 'checkbox',
},
{
align: 'right',
name: 'tableActions',

View File

@ -14,11 +14,16 @@ vehicle:
amountCooler: Amount cooler
remove: Vehicle removed
search: Search Vehicle
searchInfo: Search by id or number plate
searchInfo: Search by id
jorgep marked this conversation as resolved
Review

Asegurate de que solo queremos buscar por id

Asegurate de que solo queremos buscar por id
Review

Lo mismo referido al Front, a petición de Gallego quitar la búsqueda del searchbar por matrícula.

Lo mismo referido al Front, a petición de Gallego quitar la búsqueda del searchbar por matrícula.
deleteTitle: This item will be deleted
deleteSubtitle: Are you sure you want to continue?
params:
vehicleTypeFk: Type
vehicleStateFk: State
ticket:
assignInvoice: Assign invoice
jtubau marked this conversation as resolved Outdated

el adjetivo va delante en inglés

el adjetivo va delante en inglés

cambiado

cambiado
unassignedInvoice: Unassigned invoice
unassignInvoice: Unassign invoice
unassignInvoiceConfirmation: This invoice will be unassigned from this vehicle! Continue anyway?
errors:
documentIdEmpty: The document identifier can't be empty

View File

@ -14,11 +14,16 @@ vehicle:
nLeasing: Nº leasing
remove: Vehículo eliminado
search: Buscar Vehículo
searchInfo: Buscar por id o matrícula
searchInfo: Buscar por id
deleteTitle: Este elemento será eliminado
deleteSubtitle: ¿Seguro que quieres continuar?
params:
vehicleTypeFk: Tipo
vehicleStateFk: Estado
ticket:
assignInvoice: Vincular factura
unassignedInvoice: Factura desvinculada
unassignInvoice: Desvincular factura
unassignInvoiceConfirmation: Esta factura se desvinculará de este vehículo! ¿Continuar de todas formas?
errors:
documentIdEmpty: El número de documento no puede estar vacío

View File

@ -51,6 +51,8 @@ route:
agencyModeName: Agency route
isOwn: Own
isAnyVolumeAllowed: Any volume allowed
isActive: Active
jtubau marked this conversation as resolved Outdated

Creo que esta traduccion es global, o por lo menos es exactamente la misma que en invoiceIn, hazla global o mira a ver si ya está.

Creo que esta traduccion es global, o por lo menos es exactamente la misma que en invoiceIn, hazla global o mira a ver si ya está.
issued: Issued
created: Created
addressFromFk: Sender
addressToFk: Destination

View File

@ -52,6 +52,8 @@ route:
agencyAgreement: Agencia Acuerdo
isOwn: Propio
isAnyVolumeAllowed: Cualquier volumen
isActive: Activo
jtubau marked this conversation as resolved Outdated

Lo mismo que en inglés

Lo mismo que en inglés

esta ahí por el filtro automático de vnTable, ya que busca la traducción en moduleName.params y en este caso es route.params

esta ahí por el filtro automático de vnTable, ya que busca la traducción en moduleName.params y en este caso es route.params
issued: F. emisión
created: Creado
addressFromFk: Remitente
addressToFk: Destinatario
@ -87,6 +89,7 @@ route:
routeFk: Id ruta
country: País
clientFk: Id cliente
shipped: Fecha preparación
warehouseFk: Almacén
shipped: F. preparación
viewCmr: Ver CMR
downloadCmrs: Descargar CMRs

View File

@ -79,6 +79,7 @@ const agencyCard = {
path: 'workCenter',
name: 'AgencyWorkCenterCard',
redirect: { name: 'AgencyWorkCenters' },
component: () => import('src/pages/Route/Agency/Card/AgencyWorkcenter.vue'),
children: [
{
path: '',
@ -87,8 +88,6 @@ const agencyCard = {
icon: 'apartment',
title: 'workCenters',
},
component: () =>
import('src/pages/Route/Agency/Card/AgencyWorkcenter.vue'),
},
],
},
@ -96,6 +95,7 @@ const agencyCard = {
path: 'modes',
name: 'AgencyModesCard',
redirect: { name: 'AgencyModes' },
component: () => import('src/pages/Route/Agency/Card/AgencyModes.vue'),
children: [
{
path: '',
@ -104,8 +104,6 @@ const agencyCard = {
icon: 'format_list_bulleted',
title: 'modes',
},
component: () =>
import('src/pages/Route/Agency/Card/AgencyModes.vue'),
},
],
},
@ -166,7 +164,13 @@ const vehicleCard = {
component: () => import('src/pages/Route/Vehicle/Card/VehicleCard.vue'),
redirect: { name: 'VehicleSummary' },
meta: {
menu: ['VehicleBasicData', 'VehicleNotes', 'VehicleDms', 'VehicleEvents'],
menu: [
'VehicleBasicData',
'VehicleInvoiceIn',
'VehicleEvents',
'VehicleDms',
'VehicleNotes',
],
},
children: [
{
@ -188,13 +192,22 @@ const vehicleCard = {
component: () => import('src/pages/Route/Vehicle/Card/VehicleBasicData.vue'),
},
jorgep marked this conversation as resolved Outdated

Hay un icono para facturas recibidas

Hay un icono para facturas recibidas
{
name: 'VehicleNotes',
path: 'notes',
name: 'VehicleInvoiceIn',
path: 'invoice-in',
meta: {
title: 'notes',
icon: 'vn:notes',
title: 'invoiceIns',
icon: 'vn:invoice-in',
},
component: () => import('src/pages/Route/Vehicle/Card/VehicleNotes.vue'),
component: () => import('src/pages/Route/Vehicle/Card/VehicleInvoiceIn.vue'),
},
{
name: 'VehicleEvents',
path: 'events',
meta: {
title: 'calendar',
icon: 'vn:calendar',
},
component: () => import('src/pages/Route/Vehicle/Card/VehicleEvents.vue'),
},
{
name: 'VehicleDms',
@ -206,13 +219,13 @@ const vehicleCard = {
component: () => import('src/pages/Route/Vehicle/VehicleDms.vue'),
},
{
name: 'VehicleEvents',
path: 'events',
name: 'VehicleNotes',
path: 'notes',
meta: {
title: 'calendar',
icon: 'vn:calendar',
title: 'notes',
icon: 'vn:notes',
},
component: () => import('src/pages/Route/Vehicle/Card/VehicleEvents.vue'),
component: () => import('src/pages/Route/Vehicle/Card/VehicleNotes.vue'),
},
],
};

View File

@ -1,4 +1,4 @@
describe.skip('Route extended list', () => {
describe('Route extended list', () => {
const getSelector = (colField) => `tr:last-child > [data-col-field="${colField}"]`;
const selectors = {
@ -8,6 +8,7 @@ describe.skip('Route extended list', () => {
date: getSelector('dated'),
description: getSelector('description'),
served: getSelector('isOk'),
id: getSelector('id'),
firstRowSelectCheckBox:
'tbody > tr:first-child > :nth-child(1) .q-checkbox__inner',
lastRowSelectCheckBox: 'tbody > tr:last-child > :nth-child(1) .q-checkbox__inner',
@ -22,13 +23,18 @@ describe.skip('Route extended list', () => {
searchbar: 'searchbar',
firstTicketsRowSelectCheckBox:
'.q-card .q-table > tbody > :nth-child(1) .q-checkbox',
tableActionOpen: 'tableAction-1',
jorgep marked this conversation as resolved Outdated

tableOpenSummary, tableGoToSummary. o tableActionOpen, tableActionRedirect

tableOpenSummary, tableGoToSummary. o tableActionOpen, tableActionRedirect
tableActionRedirect: 'tableAction-2',
summaryGoToSummaryBtn: '[data-cy="goToSummaryBtn"]',
descriptorSubtitle: '[data-cy="vnDescriptor_subtitle"]',
hideRightMenu: 'toggle-right-drawer',
};
const checkboxState = {
check: 'check',
uncheck: 'close',
};
const url = '/#/route/extended-list';
const summaryUrlRegex = /route\/\d+\/summary/;
const dataCreated = 'Data created';
const dataSaved = 'Data saved';
@ -45,7 +51,7 @@ describe.skip('Route extended list', () => {
{ selector: selectors.worker, type: 'select', value: 'salesperson' },
{ selector: selectors.agency, type: 'select', value: 'Super-Man delivery' },
{ selector: selectors.vehicle, type: 'select', value: '1111-IMK' },
{ selector: selectors.date, type: 'date', value: '11/01/2001' },
{ selector: selectors.date, type: 'date', value: '02/02/2001' },
{ selector: selectors.description, type: 'input', value: 'Description updated' },
{ selector: selectors.served, type: 'checkbox', value: checkboxState.check },
];
@ -77,8 +83,9 @@ describe.skip('Route extended list', () => {
beforeEach(() => {
cy.login('developer');
cy.visit(url);
cy.visit('/#/route/extended-list');
cy.typeSearchbar('{enter}');
cy.dataCy(selectors.hideRightMenu).click();
});
it('Should list routes', () => {
@ -106,7 +113,7 @@ describe.skip('Route extended list', () => {
cy.fillInForm(data);
cy.dataCy(selectors.saveFormBtn).click();
cy.url().should('include', '/summary');
cy.location().should('match', summaryUrlRegex);
cy.checkNotification(dataCreated);
});
@ -122,7 +129,7 @@ describe.skip('Route extended list', () => {
});
});
it('Should clone selected route and add ticket', () => {
it('Should clone selected route, add ticket and mark as served', () => {
cy.get(selectors.firstRowSelectCheckBox).click();
cy.get(selectors.cloneBtn).click();
cy.dataCy('Starting date_inputDate').type('01-01-2001');
@ -134,6 +141,11 @@ describe.skip('Route extended list', () => {
cy.get('.q-card__actions > .q-btn--standard > .q-btn__content').click();
cy.checkNotification(dataSaved);
cy.get(selectors.lastRowSelectCheckBox).click();
cy.get(selectors.markServedBtn).click();
cy.typeSearchbar('{enter}');
cy.validateContent(selectors.served, checkboxState.check);
cy.get(selectors.lastRowSelectCheckBox).click();
cy.get(selectors.removeBtn).click();
cy.dataCy(selectors.confirmBtn).click();
@ -149,22 +161,6 @@ describe.skip('Route extended list', () => {
cy.readFile(`${downloadsFolder}/${fileName}`).should('exist');
});
it('Should mark as served the selected route', () => {
cy.get(selectors.lastRowSelectCheckBox).click();
cy.get(selectors.markServedBtn).click();
cy.typeSearchbar('{enter}');
cy.validateContent(selectors.served, checkboxState.check);
});
it('Should delete the selected route', () => {
cy.get(selectors.lastRowSelectCheckBox).click();
cy.get(selectors.removeBtn).click();
cy.dataCy(selectors.confirmBtn).click();
cy.checkNotification(dataSaved);
});
it('Should save changes in route', () => {
updateFields.forEach(({ selector, type, value }) => {
fillField(selector, type, value);
@ -175,28 +171,41 @@ describe.skip('Route extended list', () => {
cy.typeSearchbar('{enter}');
updateFields.forEach(({ selector, value, type }) => {
if (type === 'date') {
const [month, day, year] = value.split('/');
value = `${day}/${month}/${year}`;
}
cy.get(selector).should('contain', value);
updateFields.forEach(({ selector, value }) => {
cy.validateContent(selector, value);
});
});
it('Should open summary pop-up when click summuary icon', () => {
cy.dataCy('tableAction-1').last().click();
cy.get('.summaryHeader > :nth-child(2').should('contain', updateFields[4].value);
it('Should delete the selected route', () => {
cy.get(selectors.lastRowSelectCheckBox).click();
cy.get(selectors.removeBtn).click();
cy.dataCy(selectors.confirmBtn).click();
cy.checkNotification(dataSaved);
});
it('Should redirect to the summary from the route summary pop-up', () => {

Este test y el siguiente son práctimente iguales, crea una fn con el parametro del selector openSummaryBtn y goToSummaryBtn

Este test y el siguiente son práctimente iguales, crea una fn con el parametro del selector openSummaryBtn y goToSummaryBtn

En cuanto fusione la tarea del vehicleDms (#8442) sustituyo esos test por el comando nuevo que lleva la otra PR

En cuanto fusione la tarea del vehicleDms (#8442) sustituyo esos test por el comando nuevo que lleva la otra PR
cy.dataCy('tableAction-1').last().click();
cy.get('.header > .q-icon').should('be.visible').click();
cy.url().should('include', '/summary');
cy.get(selectors.id)
Review

Usar comando nuevo para evitar repetir código

Usar comando nuevo para evitar repetir código
Review

el comando nuevo es para los links, no estaba contemplado para los botones de las acciones de una tabla, ya que el comando recoge el texto del link al que se hace click para compararlo luego con el expectedTextSelector

el comando nuevo es para los links, no estaba contemplado para los botones de las acciones de una tabla, ya que el comando recoge el texto del link al que se hace click para compararlo luego con el expectedTextSelector
.last()
.invoke('text')
.then((routeId) => {
routeId = routeId.trim();
cy.dataCy(selectors.tableActionOpen).last().click();
cy.get(selectors.summaryGoToSummaryBtn).should('be.visible').click();
cy.location().should('match', summaryUrlRegex);
cy.containContent(selectors.descriptorSubtitle, routeId);
});
});
it('Should redirect to the summary when click go to summary icon', () => {
cy.dataCy('tableAction-2').last().click();
cy.url().should('include', '/summary');
cy.get(selectors.id)
.last()
.invoke('text')
.then((routeId) => {
routeId = routeId.trim();
cy.dataCy(selectors.tableActionRedirect).last().click();
cy.location().should('match', summaryUrlRegex);
cy.containContent(selectors.descriptorSubtitle, routeId);
});
});
});

View File

@ -1,16 +1,17 @@
describe('Vehicle DMS', () => {
const getSelector = (btnPosition) =>
`tr:last-child > .text-right > .no-wrap > :nth-child(${btnPosition}) > .q-btn > .q-btn__content > .q-icon`;
const getBtnSelector = (trPosition, btnPosition) =>
`tr:${trPosition}-child > .text-right > .no-wrap > :nth-child(${btnPosition}) > .q-btn > .q-btn__content > .q-icon`;
const selectors = {
lastRowDownloadBtn: getSelector(1),
lastRowEditBtn: getSelector(2),
lastRowDeleteBtn: getSelector(3),
firstRowDownloadBtn: getBtnSelector('first', 1),
firstRowEditBtn: getBtnSelector('first', 2),
firstRowDeleteBtn: getBtnSelector('first', 3),
lastRowDeleteBtn: getBtnSelector('last', 3),
lastRowReference: 'tr:last-child > :nth-child(5) > .q-tr > :nth-child(1) > span',
firstRowReference:
'tr:first-child > :nth-child(5) > .q-tr > :nth-child(1) > span',
firstRowId: 'tr:first-child > :nth-child(2) > .q-tr > :nth-child(1) > span',
lastRowWorkerLink: 'tr:last-child > :nth-child(8) > .q-tr > .link',
firstRowWorkerLink: 'tr:first-child > :nth-child(8) > .q-tr > .link',
descriptorTitle: '.descriptor .title',
descriptorOpenSummaryBtn: '.q-menu .descriptor [data-cy="openSummaryBtn"]',
descriptorGoToSummaryBtn: '.q-menu .descriptor [data-cy="goToSummaryBtn"]',
@ -55,19 +56,6 @@ describe('Vehicle DMS', () => {
.should('have.length.greaterThan', 0);
});
it.skip('Should download DMS', () => {
const fileName = '11.jpg';
cy.intercept('GET', /\/api\/dms\/11\/downloadFile/).as('download');
cy.get(selectors.lastRowDownloadBtn).click();
cy.wait('@download').then((interception) => {
expect(interception.response.statusCode).to.equal(200);
expect(interception.response.headers['content-disposition']).to.contain(
fileName,
);
});
});
it('Should create new DMS', () => {
const formSelectors = {
actionBtn: selectors.addBtn,
@ -78,6 +66,27 @@ describe('Vehicle DMS', () => {
cy.testDmsAction('create', formSelectors, data, 'Data saved');
});
/*
TODO: #8946 REDMINE
*/
it.skip('Should download DMS', () => {
let fileName;
cy.get(selectors.firstRowId)
.invoke('text')
.then((label) => {
label = label.trim();
fileName = `${label}.jpg`;
});
cy.intercept('GET', /\/api\/dms\/\d+\/downloadFile/).as('download');
cy.get(selectors.firstRowDownloadBtn).click();
cy.wait('@download').then((interception) => {
expect(interception.response.headers['content-disposition']).to.contain(
fileName,
);
});
});
it('Should import DMS', () => {
const data = {
Document: { val: '10', type: 'select' },
@ -89,12 +98,14 @@ describe('Vehicle DMS', () => {
};
cy.testDmsAction('import', formSelectors, data, 'Data saved', '1');
cy.get(selectors.lastRowDeleteBtn).click();
cy.clickConfirm();
});
it('Should edit DMS', () => {
const formSelectors = {
actionBtn: selectors.lastRowEditBtn,
selectorContentToCheck: selectors.lastRowReference,
actionBtn: selectors.firstRowEditBtn,
selectorContentToCheck: selectors.firstRowReference,
saveFormBtn: selectors.saveFormBtn,
};
@ -109,8 +120,8 @@ describe('Vehicle DMS', () => {
it('Should delete DMS', () => {
const formSelectors = {
actionBtn: selectors.lastRowDeleteBtn,
selectorContentToCheck: selectors.lastRowReference,
actionBtn: selectors.firstRowDeleteBtn,
selectorContentToCheck: selectors.firstRowReference,
};
cy.testDmsAction(
@ -125,7 +136,7 @@ describe('Vehicle DMS', () => {
describe('Worker pop-ups', () => {
it('Should redirect to worker summary from worker descriptor pop-up', () => {
cy.checkRedirectionFromPopUp({
selectorToClick: selectors.lastRowWorkerLink,
selectorToClick: selectors.firstRowWorkerLink,
steps: [selectors.descriptorGoToSummaryBtn],
expectedUrlRegex: workerSummaryUrlRegex,
expectedTextSelector: selectors.descriptorTitle,
@ -134,7 +145,7 @@ describe('Vehicle DMS', () => {
it('Should redirect to worker summary from summary pop-up from worker descriptor pop-up', () => {
cy.checkRedirectionFromPopUp({
selectorToClick: selectors.lastRowWorkerLink,
selectorToClick: selectors.firstRowWorkerLink,
steps: [
selectors.descriptorOpenSummaryBtn,
selectors.summaryGoToSummaryBtn,

View File

@ -0,0 +1,93 @@
describe('Vehicle Invoice In', () => {
const getLinkSelector = (colField) =>
jtubau marked this conversation as resolved Outdated

getLinkSelector

getLinkSelector
`tr:first-child > [data-col-field="${colField}"] > .no-padding > .link`;
const selectors = {
firstRowSupplier: getLinkSelector('supplierFk'),
firstRowInvoice: getLinkSelector('supplierRef'),
descriptorSupplierTitle: '[data-cy="vnDescriptor_description"]',
descriptorInvoiceInTitle: '[data-cy="vnDescriptor_title"]',
descriptorOpenSummaryBtn: '.q-menu > .descriptor [data-cy="openSummaryBtn"]',
descriptorGoToSummaryBtn: '.q-menu > .descriptor [data-cy="goToSummaryBtn"]',
summaryGoToSummaryBtn: '.summaryHeader [data-cy="goToSummaryBtn"]',
unassignBtn: 'tableAction-0',
};
const supplierSummaryUrlRegex = /supplier\/\d+\/summary/;
const invoiceInSummaryUrlRegex = /invoice-in\/\d+\/summary/;
beforeEach(() => {
cy.viewport(1920, 1080);
cy.login('deliveryAssistant');
cy.visit(`/#/route/vehicle/1/invoice-in`);
});
it('Should show assigned tickets', () => {
cy.get('.q-table')
.children()
.should('be.visible')
.should('have.length.greaterThan', 0);
});
it('Should assign a new invoice', () => {
const data = {
'Invoice nº': { val: '1243', type: 'select' },
Amount: { val: '1000' },
};
cy.addBtnClick();
cy.fillInForm(data);
cy.dataCy('FormModelPopup_save').click();
cy.checkNotification('Data created');
});
it('Should unassign an invoice', () => {
cy.dataCy(selectors.unassignBtn).last().click();
cy.clickConfirm();
cy.checkNotification('Unlinked invoice');
});
describe('Supplier pop-ups', () => {
jtubau marked this conversation as resolved Outdated

Creo que con validar que se abra el popup es suficiente.

Creo que con validar que se abra el popup es suficiente.
it('Should redirect to the supplier summary from the supplier descriptor pop-up', () => {
cy.checkRedirectionFromPopUp({
selectorToClick: selectors.firstRowSupplier,
steps: [selectors.descriptorGoToSummaryBtn],
expectedUrlRegex: supplierSummaryUrlRegex,
expectedTextSelector: selectors.descriptorSupplierTitle,
});
});
it('Should redirect to the supplier summary from summary pop-up from the supplier descriptor pop-up', () => {
cy.checkRedirectionFromPopUp({
selectorToClick: selectors.firstRowSupplier,
steps: [
selectors.descriptorOpenSummaryBtn,
selectors.summaryGoToSummaryBtn,
],
expectedUrlRegex: supplierSummaryUrlRegex,
expectedTextSelector: selectors.descriptorSupplierTitle,
});
});
jtubau marked this conversation as resolved Outdated

Creo que con validar que se abra el popup es suficiente.

Creo que con validar que se abra el popup es suficiente.
});
describe('Invoice pop-ups', () => {
it('Should redirect to the invoiceIn summary from the invoice descriptor pop-up', () => {
jtubau marked this conversation as resolved Outdated

guardar en variable con nombre descriptivo o usasr data-cy

guardar en variable con nombre descriptivo o usasr data-cy
cy.checkRedirectionFromPopUp({
selectorToClick: selectors.firstRowInvoice,
steps: [selectors.descriptorGoToSummaryBtn],
expectedUrlRegex: invoiceInSummaryUrlRegex,
expectedTextSelector: selectors.descriptorInvoiceInTitle,
});
});
it('Should redirect to the invoiceIn summary from summary pop-up from the invoice descriptor pop-up', () => {
cy.checkRedirectionFromPopUp({
selectorToClick: selectors.firstRowInvoice,
steps: [
selectors.descriptorOpenSummaryBtn,
selectors.summaryGoToSummaryBtn,
],
expectedUrlRegex: invoiceInSummaryUrlRegex,
expectedTextSelector: selectors.descriptorInvoiceInTitle,
});
});
});
});

View File

@ -1,16 +1,16 @@
describe('Vehicle list', () => {
const selectors = {
saveFormBtn: 'FormModelPopup_save',
descriptorTitle: '[data-cy="vnDescriptor_title"]',
summaryPopupBtn: 'tr:last-child > .q-table--col-auto-width > .q-btn',
summaryGoToSummaryBtn: '.header > .q-icon',
summaryHeader: '.summaryHeader > div',
summaryGoToSummaryBtn: '[data-cy="goToSummaryBtn"]',
numberPlate: 'tr:last-child > [data-col-field="numberPlate"] > .no-padding',
};
const data = {
'Nº Plate': { val: '9465-LPA' },
'Trade Mark': { val: 'WAYNE INDUSTRIES' },
Model: { val: 'BATREMOLQUE' },
Model: { val: 'BATHAUL' },
Type: { val: 'remolque', type: 'select' },
Warehouse: { val: 'Warehouse One', type: 'select' },
Country: { val: 'Portugal', type: 'select' },
@ -18,7 +18,7 @@ describe('Vehicle list', () => {
};
const expected = data['Nº Plate'].val;
const summaryUrl = '/summary';
const summaryUrlRegex = /vehicle\/\d+\/summary/;
beforeEach(() => {
cy.login('developer');
@ -34,25 +34,42 @@ describe('Vehicle list', () => {
});
it('Should add new vehicle', () => {
cy.intercept('POST', '/api/Vehicles').as('postRequest');
cy.addBtnClick();
cy.fillInForm(data);
cy.dataCy(selectors.saveFormBtn).should('be.visible').click();
cy.checkNotification('Data created');
cy.get(selectors.summaryHeader).should('contain', expected);
cy.url().should('include', summaryUrl);
cy.wait('@postRequest').then((interception) => {
expect(interception.response.statusCode).to.eq(200);
cy.url().should(
'include',
`vehicle/${interception.response.body.id}/summary`,
);
});
cy.containContent(selectors.descriptorTitle, expected);
});
it('should open summary by clicking a vehicle', () => {
Review

Este test y el siguiente son práctimente iguales, crea una fn con el parametro del selector openSummaryBtn y goToSummaryBtn.

Este test y el siguiente son práctimente iguales, crea una fn con el parametro del selector openSummaryBtn y goToSummaryBtn.
cy.get(selectors.numberPlate).click();
cy.get(selectors.summaryHeader).should('contain', expected);
cy.url().should('include', summaryUrl);
cy.get(selectors.numberPlate)
.click()
.invoke('text')
.then((numberPlate) => {
numberPlate = numberPlate.trim();
cy.location().should('match', summaryUrlRegex);
cy.containContent(selectors.descriptorTitle, numberPlate);
});
});
it('should redirect to vehicle summary when click summary icon on summary pop-up', () => {
cy.get(selectors.summaryPopupBtn).click();
cy.get(selectors.summaryHeader).should('contain', expected);
cy.get(selectors.summaryGoToSummaryBtn).click();
cy.url().should('include', summaryUrl);
cy.get(selectors.numberPlate)
.invoke('text')
.then((numberPlate) => {
numberPlate = numberPlate.trim();
cy.get(selectors.summaryPopupBtn).click();
cy.get(selectors.summaryGoToSummaryBtn).click();
cy.location().should('match', summaryUrlRegex);
cy.containContent(selectors.descriptorTitle, numberPlate);
});
});
});

View File

@ -0,0 +1,134 @@
describe('Vehicle summary', () => {
jtubau marked this conversation as resolved
Review

Pasar a data-cy.

Pasar a data-cy.
const selectors = {
summaryTitle: '.summaryHeader',
descriptorTitle: '.q-item__label--header > .title > span',
summaryAssignedInvoicesLink: '.vn-max > .q-pb-lg > .header-link > .link',
summaryBasicDataLink: '[dense=""] > .q-pb-lg > .header-link > .link',
basicDataSupplierLink: ':nth-child(4) > .value > .link',
assignedInvoicesSupplierLink: 'tbody > :nth-child(1) > :nth-child(2) > .link',
assignedInvoicesInvoiceLink: 'tbody > :nth-child(1) > :nth-child(3) > .link',
descriptorOpenSummaryBtn: '.q-menu > .descriptor [data-cy="openSummaryBtn"]',
descriptorGoToSummaryBtn: '.q-menu > .descriptor [data-cy="goToSummaryBtn"]',
summaryGoToSummaryBtn: '.summaryHeader [data-cy="goToSummaryBtn"]',
};
const vehiclePlate = '3333-BAT';
const supplierSummaryUrlRegex = /supplier\/\d+\/summary/;
const invoiceInSummaryUrlRegex = /invoice-in\/\d+\/summary/;
beforeEach(() => {
cy.viewport(1920, 1080);
cy.login('deliveryAssistant');
cy.visit(`/#/route/vehicle/1/summary`);
});
it('Should load summary', () => {
cy.containContent(selectors.summaryTitle, vehiclePlate);
cy.containContent(selectors.descriptorTitle, vehiclePlate);
});
it('Should redirect to vehicle basic-data', () => {
cy.get(selectors.summaryBasicDataLink).click();
cy.url().should('include', '/vehicle/1/basic-data');
});
it('Should redirect to vehicle invoice-ins', () => {
cy.get(selectors.summaryAssignedInvoicesLink).click();
cy.url().should('include', '/vehicle/1/invoice-in');
});
describe('Supplier basic data pop-ups', () => {
it('Should redirect to the supplier summary from the supplier descriptor pop-up', () => {
cy.get(selectors.basicDataSupplierLink)
.click()
.invoke('text')
.then((supplierName) => {
supplierName = supplierName.trim();
cy.get(selectors.descriptorGoToSummaryBtn)
.should('be.visible')
.click();
cy.url().should('match', supplierSummaryUrlRegex);
cy.containContent(selectors.descriptorTitle, supplierName);
});
});
jtubau marked this conversation as resolved Outdated

Con que lo abra es suficiente.

Con que lo abra es suficiente.
it('Should redirect to the supplier summary from summary pop-up from the supplier descriptor pop-up', () => {
cy.get(selectors.basicDataSupplierLink)
.click()
.invoke('text')
.then((supplierName) => {
supplierName = supplierName.trim();
cy.get(selectors.descriptorOpenSummaryBtn)
.should('be.visible')
.click();
cy.get(selectors.summaryGoToSummaryBtn).should('be.visible').click();
cy.url().should('match', supplierSummaryUrlRegex);
cy.containContent(selectors.descriptorTitle, supplierName);
});
});
});
describe('Supplier assigned invoices pop-ups', () => {
it('Should redirect to the supplier summary from the invoice descriptor pop-up', () => {
cy.get(selectors.assignedInvoicesSupplierLink)
.click()
jtubau marked this conversation as resolved Outdated

Con que lo abra es suficiente

Con que lo abra es suficiente
.invoke('text')
.then((supplierName) => {
supplierName = supplierName.trim();
cy.get(selectors.descriptorGoToSummaryBtn)
.should('be.visible')
.click();
cy.url().should('match', supplierSummaryUrlRegex);
cy.containContent(selectors.descriptorTitle, supplierName);
});
});
it('Should redirect to the supplier summary from summary pop-up from the supplier descriptor pop-up', () => {
cy.get(selectors.assignedInvoicesSupplierLink)
.click()
.invoke('text')
.then((supplierName) => {
supplierName = supplierName.trim();
cy.get(selectors.descriptorOpenSummaryBtn)
.should('be.visible')
.click();
cy.get(selectors.summaryGoToSummaryBtn).should('be.visible').click();
cy.url().should('match', supplierSummaryUrlRegex);
cy.containContent(selectors.descriptorTitle, supplierName);
});
});
});
// redmine: #8872
describe.skip('Invoice assigned invoices pop-ups', () => {
it('Should redirect to the invoiceIn summary from the invoice descriptor pop-up', () => {
cy.get(selectors.assignedInvoicesInvoiceLink)
.should('be.visible')
.click()
.invoke('text')
.then((invoice) => {
invoice = invoice.trim();
cy.get(selectors.descriptorGoToSummaryBtn)
.should('be.visible')
.click();
cy.url().should('match', invoiceInSummaryUrlRegex);
cy.containContent(selectors.descriptorTitle, invoice);
});
});
it('Should redirect to the invoiceIn summary from summary pop-up from the invoice descriptor pop-up', () => {
cy.get(selectors.assignedInvoicesInvoiceLink)
.should('be.visible')
.click()
.invoke('text')
.then((invoice) => {
invoice = invoice.trim();
cy.get(selectors.descriptorOpenSummaryBtn)
.should('be.visible')
.click();
cy.get(selectors.summaryGoToSummaryBtn).should('be.visible').click();
cy.url().should('match', invoiceInSummaryUrlRegex);
cy.containContent(selectors.descriptorTitle, invoice);
});
});
});
});