-
+
+
-
+
+es:
+ I do not remember my password: No recuerdo mi contraseña
+
diff --git a/src/pages/Login/RecoverPassword.vue b/src/pages/Login/RecoverPassword.vue
new file mode 100644
index 000000000..5e0fd367a
--- /dev/null
+++ b/src/pages/Login/RecoverPassword.vue
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/Login/ResetPassword.vue b/src/pages/Login/ResetPassword.vue
new file mode 100644
index 000000000..eff718e97
--- /dev/null
+++ b/src/pages/Login/ResetPassword.vue
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/Login/TwoFactor.vue b/src/pages/Login/TwoFactor.vue
index 31b4ccc79..dd404094f 100644
--- a/src/pages/Login/TwoFactor.vue
+++ b/src/pages/Login/TwoFactor.vue
@@ -8,6 +8,7 @@ import axios from 'axios';
import { useSession } from 'src/composables/useSession';
import { useLogin } from 'src/composables/useLogin';
import VnInput from 'src/components/common/VnInput.vue';
+import VnOutForm from 'src/components/ui/VnOutForm.vue';
const quasar = useQuasar();
const session = useSession();
@@ -38,24 +39,22 @@ async function onSubmit() {
}
-
-
-
-
{{ t('twoFactor.insert') }}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
+
+
-
diff --git a/src/pages/Parking/Card/ParkingCard.vue b/src/pages/Parking/Card/ParkingCard.vue
index 5c29e5665..ad37eb630 100644
--- a/src/pages/Parking/Card/ParkingCard.vue
+++ b/src/pages/Parking/Card/ParkingCard.vue
@@ -16,7 +16,7 @@ const filter = {
:descriptor="ParkingDescriptor"
:filter-panel="ParkingFilter"
search-data-key="ParkingList"
- :searchbarProps="{
+ :searchbar-props="{
url: 'Parkings',
label: 'parking.searchBar.label',
info: 'parking.searchBar.info',
diff --git a/src/pages/Route/Agency/Card/AgencyCard.vue b/src/pages/Route/Agency/Card/AgencyCard.vue
index 65a095e48..9935fc6eb 100644
--- a/src/pages/Route/Agency/Card/AgencyCard.vue
+++ b/src/pages/Route/Agency/Card/AgencyCard.vue
@@ -8,7 +8,7 @@ import VnCard from 'components/common/VnCard.vue';
base-url="Agencies"
:descriptor="AgencyDescriptor"
search-data-key="AgencyList"
- :searchbarProps="{
+ :searchbar-props="{
url: 'Agencies',
label: 'agency.searchBar.label',
info: 'agency.searchBar.info',
diff --git a/src/pages/Route/Card/RouteFilter.vue b/src/pages/Route/Card/RouteFilter.vue
index 24b155389..a6cd149f1 100644
--- a/src/pages/Route/Card/RouteFilter.vue
+++ b/src/pages/Route/Card/RouteFilter.vue
@@ -119,6 +119,7 @@ const emit = defineEmits(['search']);
sort-by="numberPlate ASC"
option-value="id"
option-label="numberPlate"
+ option-filter-value="numberPlate"
dense
outlined
rounded
diff --git a/src/pages/Route/Card/RouteForm.vue b/src/pages/Route/Card/RouteForm.vue
index b80b4536d..8c89718fa 100644
--- a/src/pages/Route/Card/RouteForm.vue
+++ b/src/pages/Route/Card/RouteForm.vue
@@ -142,11 +142,6 @@ const onSave = (data, response) => {
@@ -194,7 +189,7 @@ es:
Description: Descripción
Is served: Se ha servido
Created: Creado
- Distance must be lesser than: La distancia debe ser inferior a {maxDistance}
+ The km can not exceed: La distancia debe ser inferior a {maxDistance}
en:
- Distance must be lesser than: Distance must be lesser than {maxDistance}
+ The km can not exceed: Distance must be lesser than {maxDistance}
diff --git a/src/pages/Route/Roadmap/RoadmapCard.vue b/src/pages/Route/Roadmap/RoadmapCard.vue
index 6bd6816ff..148d16ac9 100644
--- a/src/pages/Route/Roadmap/RoadmapCard.vue
+++ b/src/pages/Route/Roadmap/RoadmapCard.vue
@@ -10,7 +10,7 @@ import RoadmapFilter from 'pages/Route/Roadmap/RoadmapFilter.vue';
:descriptor="RoadmapDescriptor"
:filter-panel="RoadmapFilter"
search-data-key="RoadmapList"
- :searchbarProps="{
+ :searchbar-props="{
url: 'Roadmaps',
label: 'Search roadmap',
info: 'You can search by roadmap id or customer name',
diff --git a/src/pages/Route/RouteAutonomous.vue b/src/pages/Route/RouteAutonomous.vue
index 8e7900652..5ad349942 100644
--- a/src/pages/Route/RouteAutonomous.vue
+++ b/src/pages/Route/RouteAutonomous.vue
@@ -214,7 +214,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
@click="openDmsUploadDialog"
>
- {{ t('Create invoiceIn') }}
+ {{ t('globals.createInvoiceIn') }}
@@ -267,7 +267,6 @@ onUnmounted(() => (stateStore.rightDrawer = false));
es:
Search autonomous: Buscar autónomos
You can search by autonomous reference: Puedes buscar por referencia del autónomo
- Create invoiceIn: Crear factura recibida
Two autonomous cannot be counted at the same time: Dos autonónomos no pueden ser contabilizados al mismo tiempo
Date: Fecha
Agency route: Agencia Ruta
diff --git a/src/pages/Route/RouteList.vue b/src/pages/Route/RouteList.vue
index a978d8fda..e24ed33ed 100644
--- a/src/pages/Route/RouteList.vue
+++ b/src/pages/Route/RouteList.vue
@@ -99,6 +99,7 @@ const columns = computed(() => [
url: 'vehicles',
fields: ['id', 'numberPlate'],
optionLabel: 'numberPlate',
+ optionFilterValue: 'numberPlate',
find: {
value: 'vehicleFk',
label: 'vehiclePlateNumber',
diff --git a/src/pages/Supplier/Card/SupplierDescriptor.vue b/src/pages/Supplier/Card/SupplierDescriptor.vue
index a31a3bc8d..6e60a336c 100644
--- a/src/pages/Supplier/Card/SupplierDescriptor.vue
+++ b/src/pages/Supplier/Card/SupplierDescriptor.vue
@@ -177,7 +177,7 @@ const getEntryQueryParams = (supplier) => {
icon="vn:invoice-in-create"
color="primary"
>
-
{{ t('Create invoiceIn') }}
+
{{ t('globals.createInvoiceIn') }}
@@ -188,7 +188,6 @@ const getEntryQueryParams = (supplier) => {
es:
All entries with current supplier: Todas las entradas con proveedor actual
Go to client: Ir a cliente
- Create invoiceIn: Crear factura recibida
Go to module index: Ir al índice del módulo
Inactive supplier: Proveedor inactivo
Unverified supplier: Proveedor no verificado
diff --git a/src/pages/Ticket/Card/TicketBoxing.vue b/src/pages/Ticket/Card/TicketBoxing.vue
index 05bde8977..bff95c0e2 100644
--- a/src/pages/Ticket/Card/TicketBoxing.vue
+++ b/src/pages/Ticket/Card/TicketBoxing.vue
@@ -105,14 +105,14 @@ async function getVideoList(expeditionId, timed) {
label
markers
snap
- color="orange"
+ color="primary"
/>
routeName.value);
:filter-panel="TicketFilter"
:descriptor="TicketDescriptor"
search-data-key="TicketList"
- :searchbarProps="{
+ :searchbar-props="{
customRouteRedirectName,
label: t('card.search'),
info: t('card.searchInfo'),
diff --git a/src/pages/Ticket/Card/TicketEditMana.vue b/src/pages/Ticket/Card/TicketEditMana.vue
index 721057515..428e5e8c2 100644
--- a/src/pages/Ticket/Card/TicketEditMana.vue
+++ b/src/pages/Ticket/Card/TicketEditMana.vue
@@ -34,7 +34,7 @@ const cancel = () => {
-
+
diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue
index eda2b1b10..cbd102317 100644
--- a/src/pages/Ticket/TicketList.vue
+++ b/src/pages/Ticket/TicketList.vue
@@ -298,6 +298,7 @@ onMounted(() => (stateStore.rightDrawer = true));
-
+
{{ props.title }}
diff --git a/src/pages/Worker/Card/WorkerCard.vue b/src/pages/Worker/Card/WorkerCard.vue
index f5157424d..0abcdcafd 100644
--- a/src/pages/Worker/Card/WorkerCard.vue
+++ b/src/pages/Worker/Card/WorkerCard.vue
@@ -10,7 +10,7 @@ import WorkerFilter from '../WorkerFilter.vue';
:descriptor="WorkerDescriptor"
:filter-panel="WorkerFilter"
search-data-key="WorkerList"
- :searchbarProps="{
+ :searchbar-props="{
url: 'Workers/filter',
label: 'Search worker',
info: 'You can search by worker id or name',
diff --git a/src/pages/Worker/Card/WorkerDescriptor.vue b/src/pages/Worker/Card/WorkerDescriptor.vue
index e8dc7d878..8fbb23ef8 100644
--- a/src/pages/Worker/Card/WorkerDescriptor.vue
+++ b/src/pages/Worker/Card/WorkerDescriptor.vue
@@ -147,7 +147,7 @@ const refetch = async () => await cardDescriptorRef.value.getData();
diff --git a/src/pages/Zone/Card/ZoneCard.vue b/src/pages/Zone/Card/ZoneCard.vue
index eeece3bd8..02ec12fe7 100644
--- a/src/pages/Zone/Card/ZoneCard.vue
+++ b/src/pages/Zone/Card/ZoneCard.vue
@@ -29,7 +29,7 @@ const searchBarDataKeys = {
:descriptor="ZoneDescriptor"
:search-data-key="searchBarDataKeys[routeName]"
:filter-panel="ZoneFilterPanel"
- :searchbarProps="{
+ :searchbar-props="{
url: 'Zones',
label: t('list.searchZone'),
info: t('list.searchInfo'),
diff --git a/src/router/index.js b/src/router/index.js
index faa3ab5d4..686da2dde 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -46,7 +46,7 @@ export { Router };
export default route(function (/* { store, ssrContext } */) {
Router.beforeEach(async (to, from, next) => {
const { isLoggedIn } = session;
- const outLayout = ['Login', 'TwoFactor', 'VerifyEmail'];
+ const outLayout = Router.options.routes[0].children.map((r) => r.name);
if (!isLoggedIn() && !outLayout.includes(to.name)) {
return next({ name: 'Login', query: { redirect: to.fullPath } });
}
diff --git a/src/router/modules/account.js b/src/router/modules/account.js
index 3faa00fbc..cfec2b95d 100644
--- a/src/router/modules/account.js
+++ b/src/router/modules/account.js
@@ -65,13 +65,13 @@ export default {
component: () => import('src/pages/Account/AccountAliasList.vue'),
},
{
- path: 'connections',
- name: 'AccountConnections',
+ path: 'acls',
+ name: 'AccountAcls',
meta: {
- title: 'connections',
+ title: 'acls',
icon: 'check',
},
- component: () => import('src/pages/Account/AccountConnections.vue'),
+ component: () => import('src/pages/Account/AccountAcls.vue'),
},
{
path: 'accounts',
@@ -104,13 +104,13 @@ export default {
component: () => import('src/pages/Account/AccountSamba.vue'),
},
{
- path: 'acls',
- name: 'AccountAcls',
+ path: 'connections',
+ name: 'AccountConnections',
meta: {
- title: 'acls',
- icon: 'check',
+ title: 'connections',
+ icon: 'share',
},
- component: () => import('src/pages/Account/AccountAcls.vue'),
+ component: () => import('src/pages/Account/AccountConnections.vue'),
},
{
path: 'acl-form',
diff --git a/src/router/routes.js b/src/router/routes.js
index 805eefb8c..cced308b5 100644
--- a/src/router/routes.js
+++ b/src/router/routes.js
@@ -46,6 +46,18 @@ const routes = [
meta: { title: 'verifyEmail' },
component: () => import('../pages/Login/VerifyEmail.vue'),
},
+ {
+ path: '/recoverPassword',
+ name: 'RecoverPassword',
+ meta: { title: 'recoverPassword' },
+ component: () => import('../pages/Login/RecoverPassword.vue'),
+ },
+ {
+ path: '/resetPassword',
+ name: 'ResetPassword',
+ meta: { title: 'resetPassword' },
+ component: () => import('../pages/Login/ResetPassword.vue'),
+ },
],
},
{
diff --git a/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js b/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js
index a297a60f4..f6dac4c73 100644
--- a/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js
+++ b/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js
@@ -1,8 +1,9 @@
///
describe('InvoiceInIntrastat', () => {
- const inputBtns = 'label button';
+ const firstRow = 'tbody > :nth-child(1)';
const thirdRow = 'tbody > :nth-child(3)';
- const firstLineCode = 'tbody > :nth-child(1) > :nth-child(2)';
+ const firstRowCode = `${firstRow} > :nth-child(2)`;
+ const firstRowAmount = `${firstRow} > :nth-child(3)`;
beforeEach(() => {
cy.login('developer');
@@ -10,10 +11,10 @@ describe('InvoiceInIntrastat', () => {
});
it('should edit the first line', () => {
- cy.selectOption(firstLineCode, 'Plantas vivas: Esqueje/injerto, Vid');
- cy.get(inputBtns).eq(1).click();
+ cy.selectOption(firstRowCode, 'Plantas vivas: Esqueje/injerto, Vid');
+ cy.get(firstRowAmount).clear();
cy.saveCard();
- cy.get(`${firstLineCode} span`).should(
+ cy.get(`${firstRowCode} span`).should(
'have.text',
'6021010:Plantas vivas: Esqueje/injerto, Vid'
);
diff --git a/test/cypress/integration/invoiceIn/invoiceInList.spec.js b/test/cypress/integration/invoiceIn/invoiceInList.spec.js
index b2fa10d52..fa0d1c5e4 100644
--- a/test/cypress/integration/invoiceIn/invoiceInList.spec.js
+++ b/test/cypress/integration/invoiceIn/invoiceInList.spec.js
@@ -1,23 +1,23 @@
///
describe('InvoiceInList', () => {
- const firstCard = '.q-card:nth-child(1)';
- const firstChipId =
- ':nth-child(1) > :nth-child(1) > .justify-between > .flex > .q-chip > .q-chip__content';
- const firstDetailBtn = '.q-card:nth-child(1) .q-btn:nth-child(2)';
+ const firstRow = 'tbody.q-virtual-scroll__content tr:nth-child(1)';
+ const firstId = `${firstRow} > td:nth-child(1) span`;
+ const firstDetailBtn = `${firstRow} .q-btn:nth-child(1)`;
const summaryHeaders = '.summaryBody .header-link';
beforeEach(() => {
cy.viewport(1920, 1080);
cy.login('developer');
cy.visit(`/#/invoice-in/list`);
+ cy.get('#searchbar input').type('{enter}');
});
it('should redirect on clicking a invoice', () => {
- cy.get(firstChipId)
+ cy.get(firstId)
.invoke('text')
.then((content) => {
const id = content.replace(/\D/g, '');
- cy.get(firstCard).click();
+ cy.get(firstRow).click();
cy.url().should('include', `/invoice-in/${id}/summary`);
});
});
diff --git a/test/cypress/integration/invoiceIn/invoiceInVat.spec.js b/test/cypress/integration/invoiceIn/invoiceInVat.spec.js
index 932aca96d..018ae7a53 100644
--- a/test/cypress/integration/invoiceIn/invoiceInVat.spec.js
+++ b/test/cypress/integration/invoiceIn/invoiceInVat.spec.js
@@ -4,8 +4,7 @@ describe('InvoiceInVat', () => {
const firstLineVat = 'tbody > :nth-child(1) > :nth-child(4)';
const dialogInputs = '.q-dialog label input';
const dialogBtns = '.q-dialog button';
- const acrossInput =
- ':nth-child(1) > .q-td.q-table--col-auto-width > .q-field > .q-field__inner > .q-field__control > :nth-child(2) > .default-icon';
+ const acrossInput = 'tbody tr:nth-child(1) td:nth-child(2) .default-icon';
const randomInt = Math.floor(Math.random() * 100);
beforeEach(() => {
diff --git a/test/cypress/integration/login.spec.js b/test/cypress/integration/outLogin/login.spec.js
similarity index 100%
rename from test/cypress/integration/login.spec.js
rename to test/cypress/integration/outLogin/login.spec.js
diff --git a/test/cypress/integration/logout.spec.js b/test/cypress/integration/outLogin/logout.spec.js
similarity index 84%
rename from test/cypress/integration/logout.spec.js
rename to test/cypress/integration/outLogin/logout.spec.js
index b35a8415a..423189908 100644
--- a/test/cypress/integration/logout.spec.js
+++ b/test/cypress/integration/outLogin/logout.spec.js
@@ -7,10 +7,8 @@ describe('Logout', () => {
});
describe('by user', () => {
it('should logout', () => {
- cy.get(
- '#user > .q-btn__content > .q-avatar > .q-avatar__content > .q-img > .q-img__container > .q-img__image'
- ).click();
- cy.get('.block').click();
+ cy.get('#user').click();
+ cy.get('#logout').click();
});
});
describe('not user', () => {
diff --git a/test/cypress/integration/outLogin/recoverPassword.spec.js b/test/cypress/integration/outLogin/recoverPassword.spec.js
new file mode 100755
index 000000000..eec81b661
--- /dev/null
+++ b/test/cypress/integration/outLogin/recoverPassword.spec.js
@@ -0,0 +1,54 @@
+///
+describe('Recover Password', () => {
+ const username = 'trainee';
+ beforeEach(() => {
+ cy.visit('/#/login');
+ cy.get('#switchLanguage').click();
+ cy.get('.q-menu > :nth-child(1) > .q-item').click();
+ });
+
+ it('should go to recover password section and send notification', () => {
+ cy.get('input[aria-label="Username"]').type(username);
+ cy.get(`a[href="#/recoverPassword?user=${username}"]`).click();
+
+ cy.waitForElement('input[aria-label="User or recovery email"]');
+ cy.get('input[aria-label="User or recovery email"]').should(
+ 'have.value',
+ username
+ );
+
+ cy.get('button[type="submit"]').click();
+ cy.get('.q-notification__message').should('have.text', 'Notification sent');
+ });
+
+ it('should change password to user', () => {
+ // Get token from mail
+ cy.request(
+ `http://localhost:3000/api/Mails?filter=%7B%22where%22%3A%20%7B%22receiver%22%3A%20%22${username}%40mydomain.com%22%7D%2C%20%22order%22%3A%20%5B%22id%20DESC%22%5D%7D&access_token=DEFAULT_TOKEN`
+ ).then((response) => {
+ const regex = /access_token=([a-zA-Z0-9]+)/;
+ const [match] = response.body[0].body.match(regex);
+
+ const resetUrl = '/#/resetPassword?' + match;
+ cy.visit(resetUrl);
+ });
+
+ // Change password
+ const newPassword = 'test.1234';
+ cy.waitForElement('input[aria-label="Password"]');
+ cy.waitForElement('input[aria-label="Repeat password"]');
+ cy.get('input[aria-label="Password"]').type(newPassword);
+ cy.get('input[aria-label="Repeat password"]').type(newPassword);
+
+ cy.get('button[type="submit"]').click();
+ cy.get('.q-notification__message').should('have.text', 'Password changed');
+
+ // Try to login successfully
+ cy.get('input[aria-label="Username"]').type(username);
+ cy.get('input[aria-label="Password"]').type(newPassword);
+ cy.get('button[type="submit"]').click();
+ cy.url().should('contain', '/dashboard');
+
+ // ❗The password cannot be returned because "nightmare" does not meet the requirements
+ });
+});
diff --git a/test/cypress/integration/outLogin/twoFactor.spec.js b/test/cypress/integration/outLogin/twoFactor.spec.js
new file mode 100755
index 000000000..4d8561f0f
--- /dev/null
+++ b/test/cypress/integration/outLogin/twoFactor.spec.js
@@ -0,0 +1,56 @@
+///
+describe('Two Factor', () => {
+ const username = 'sysadmin';
+ const userId = 66;
+ beforeEach(() => {
+ cy.visit('/#/login');
+ cy.get('#switchLanguage').click();
+ cy.get('.q-menu > :nth-child(1) > .q-item').click();
+ });
+
+ it('should enable two factor to sysadmin', () => {
+ cy.request(
+ 'PATCH',
+ `http://localhost:3000/api/VnUsers/${userId}/update-user?access_token=DEFAULT_TOKEN`,
+ { twoFactor: 'email' }
+ );
+ });
+
+ it('should fail when login with incorrect two factor', () => {
+ cy.get('input[aria-label="Username"]').type(username);
+ cy.get('input[aria-label="Password"]').type('nightmare');
+ cy.get('button[type="submit"]').click();
+ cy.get('.q-notification__message').should(
+ 'have.text',
+ 'Two-factor verification required'
+ );
+
+ cy.get('input[type="text"]').type('123456');
+ cy.get('button[type="submit"]').click();
+ cy.url().should('contain', '/twoFactor');
+ });
+
+ it('should login with correct two factor', () => {
+ cy.get('input[aria-label="Username"]').type(username);
+ cy.get('input[aria-label="Password"]').type('nightmare');
+ cy.get('button[type="submit"]').click();
+ cy.get('.q-notification__message').should(
+ 'have.text',
+ 'Two-factor verification required'
+ );
+
+ // Get code from mail
+ cy.request(
+ `http://localhost:3000/api/Mails?filter=%7B%22where%22%3A%20%7B%22receiver%22%3A%20%22${username}%40mydomain.com%22%7D%2C%20%22order%22%3A%20%5B%22id%20DESC%22%5D%7D&access_token=DEFAULT_TOKEN`
+ ).then((response) => {
+ const tempDiv = document.createElement('div');
+ tempDiv.innerHTML = response.body[0].body;
+ const codeElement = tempDiv.querySelector('.code');
+ const code = codeElement ? codeElement.textContent.trim() : null;
+
+ cy.get('input[type="text"]').type(code);
+ cy.get('button[type="submit"]').click();
+ cy.url().should('contain', '/dashboard');
+ });
+ });
+});