diff --git a/Jenkinsfile b/Jenkinsfile
index d87695cc4..06eb85561 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -69,13 +69,13 @@ pipeline {
}
}
}
- /* stage('Backend') {
- steps {
- nodejs('node-lts') {
- sh 'gulp launchBackTest --ci'
- }
- }
- } */
+ // stage('Backend') {
+ // steps {
+ // nodejs('node-lts') {
+ // sh 'gulp launchBackTest --ci'
+ // }
+ // }
+ // }
}
}
stage('Build') {
diff --git a/e2e/helpers/extensions.js b/e2e/helpers/extensions.js
index b38b3f718..71f834476 100644
--- a/e2e/helpers/extensions.js
+++ b/e2e/helpers/extensions.js
@@ -300,9 +300,14 @@ let actions = {
},
waitForNumberOfElements: async function(selector, count) {
- return await this.waitForFunction((selector, count) => {
- return document.querySelectorAll(selector).length == count;
- }, {}, selector, count);
+ try {
+ await this.waitForFunction((selector, count) => {
+ return document.querySelectorAll(selector).length == count;
+ }, {}, selector, count);
+ } catch (error) {
+ const amount = await this.countElement(selector);
+ throw new Error(`actual amount of elements was: ${amount} instead of ${count}, ${error}`);
+ }
},
waitForClassNotPresent: async function(selector, className) {
diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js
index 11087d2d4..0ed3607ad 100644
--- a/e2e/helpers/selectors.js
+++ b/e2e/helpers/selectors.js
@@ -674,7 +674,14 @@ export default {
confirmButton: '.vn-confirm.shown button[response="accept"]',
},
routeIndex: {
- addNewRouteButton: 'vn-route-index a[ui-sref="route.create"]'
+ anyResult: 'vn-table a',
+ firstRouteCheckbox: 'a:nth-child(1) vn-td:nth-child(1) > vn-check',
+ addNewRouteButton: 'vn-route-index a[ui-sref="route.create"]',
+ cloneButton: 'vn-route-index button > vn-icon[icon="icon-clone"]',
+ submitClonationButton: 'tpl-buttons > button[response="accept"]',
+ openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
+ searchAgencyAutocomlete: 'vn-route-search-panel vn-autocomplete[ng-model="filter.agencyModeFk"]',
+ advancedSearchButton: 'vn-route-search-panel button[type=submit]',
},
createRouteView: {
worker: 'vn-route-create vn-autocomplete[ng-model="$ctrl.route.workerFk"]',
diff --git a/e2e/paths/08-route/03_create.spec.js b/e2e/paths/08-route/03_create_and_clone.spec.js
similarity index 63%
rename from e2e/paths/08-route/03_create.spec.js
rename to e2e/paths/08-route/03_create_and_clone.spec.js
index 670f7e17b..be758f788 100644
--- a/e2e/paths/08-route/03_create.spec.js
+++ b/e2e/paths/08-route/03_create_and_clone.spec.js
@@ -57,5 +57,36 @@ describe('Route create path', () => {
it(`should confirm the redirection to the created route summary`, async() => {
await page.waitForState('route.card.summary');
});
+
+ it(`should navigate back to the route index`, async() => {
+ await page.waitToClick(selectors.globalItems.returnToModuleIndexButton);
+ await page.waitForState('route.index');
+ });
+
+ let count;
+ it(`should count the amount of routes before clonation`, async() => {
+ await page.waitForFunction(selector => {
+ return document.querySelectorAll(selector).length > 6;
+ }, {}, selectors.routeIndex.anyResult);
+ count = await page.countElement(selectors.routeIndex.anyResult);
+
+ expect(count).toBeGreaterThanOrEqual(7);
+ });
+
+ it(`should clone the first route`, async() => {
+ await page.waitToClick(selectors.routeIndex.firstRouteCheckbox);
+ await page.waitToClick(selectors.routeIndex.cloneButton);
+ await page.waitToClick(selectors.routeIndex.submitClonationButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it(`should reload the section and count the amount of routes after clonation`, async() => {
+ await page.waitForNumberOfElements(selectors.routeIndex.anyResult, count + 1);
+ const result = await page.countElement(selectors.routeIndex.anyResult);
+
+ expect(result).toEqual(count + 1);
+ });
});
});
diff --git a/loopback/server/connectors/vn-mysql.js b/loopback/server/connectors/vn-mysql.js
index 4e1345cd6..fde0ddcf6 100644
--- a/loopback/server/connectors/vn-mysql.js
+++ b/loopback/server/connectors/vn-mysql.js
@@ -246,3 +246,98 @@ exports.initialize = function initialize(dataSource, callback) {
dataSource.connector.connect(callback);
}
};
+
+MySQL.prototype.connect = function(callback) {
+ const self = this;
+ const options = generateOptions(this.settings);
+
+ if (this.client) {
+ if (callback) {
+ process.nextTick(function() {
+ callback(null, self.client);
+ });
+ }
+ } else
+ this.client = connectionHandler(options, callback);
+
+ function connectionHandler(options, callback) {
+ const client = mysql.createPool(options);
+ client.getConnection(function(err, connection) {
+ const conn = connection;
+ if (!err) {
+ if (self.debug)
+ debug('MySQL connection is established: ' + self.settings || {});
+
+ connection.release();
+ } else {
+ if (err.code == 'ECONNREFUSED' || err.code == 'PROTOCOL_CONNECTION_LOST') { // PROTOCOL_CONNECTION_LOST
+ console.error(`MySQL connection lost (${err.code}). Retrying..`);
+
+ return setTimeout(() =>
+ connectionHandler(options, callback), 5000);
+ }
+ if (self.debug || !callback)
+ console.error('MySQL connection is failed: ' + self.settings || {}, err);
+ }
+ callback && callback(err, conn);
+ });
+
+ return client;
+ }
+};
+
+function generateOptions(settings) {
+ const s = settings || {};
+ if (s.collation) {
+ // Charset should be first 'chunk' of collation.
+ s.charset = s.collation.substr(0, s.collation.indexOf('_'));
+ } else {
+ s.collation = 'utf8_general_ci';
+ s.charset = 'utf8';
+ }
+
+ s.supportBigNumbers = (s.supportBigNumbers || false);
+ s.timezone = (s.timezone || 'local');
+
+ if (isNaN(s.connectionLimit))
+ s.connectionLimit = 10;
+
+ let options;
+ if (s.url) {
+ // use url to override other settings if url provided
+ options = s.url;
+ } else {
+ options = {
+ host: s.host || s.hostname || 'localhost',
+ port: s.port || 3306,
+ user: s.username || s.user,
+ password: s.password,
+ timezone: s.timezone,
+ socketPath: s.socketPath,
+ charset: s.collation.toUpperCase(), // Correct by docs despite seeming odd.
+ supportBigNumbers: s.supportBigNumbers,
+ connectionLimit: s.connectionLimit,
+ };
+
+ // Don't configure the DB if the pool can be used for multiple DBs
+ if (!s.createDatabase)
+ options.database = s.database;
+
+ // Take other options for mysql driver
+ // See https://github.com/strongloop/loopback-connector-mysql/issues/46
+ for (const p in s) {
+ if (p === 'database' && s.createDatabase)
+ continue;
+
+ if (options[p] === undefined)
+ options[p] = s[p];
+ }
+ // Legacy UTC Date Processing fallback - SHOULD BE TRANSITIONAL
+ if (s.legacyUtcDateProcessing === undefined)
+ s.legacyUtcDateProcessing = true;
+
+ if (s.legacyUtcDateProcessing)
+ options.timezone = 'Z';
+ }
+ return options;
+}
diff --git a/modules/route/back/methods/route/clone.js b/modules/route/back/methods/route/clone.js
new file mode 100644
index 000000000..ece232ccf
--- /dev/null
+++ b/modules/route/back/methods/route/clone.js
@@ -0,0 +1,56 @@
+module.exports = Self => {
+ Self.remoteMethod('clone', {
+ description: 'Clones the selected routes',
+ accessType: 'WRITE',
+ accepts: [
+ {
+ arg: 'ids',
+ type: ['number'],
+ required: true,
+ description: 'The routes ids to clone'
+ },
+ {
+ arg: 'started',
+ type: 'date',
+ required: true,
+ description: 'The started date for all routes'
+ }
+ ],
+ returns: {
+ type: ['Object'],
+ root: true
+ },
+ http: {
+ path: `/clone`,
+ verb: 'POST'
+ }
+ });
+
+ Self.clone = async(ids, started) => {
+ const tx = await Self.beginTransaction({});
+ try {
+ const options = {transaction: tx};
+ const originalRoutes = await Self.find({
+ where: {id: {inq: ids}},
+ fields: ['workerFk', 'agencyModeFk', 'vehicleFk', 'description']
+ }, options);
+
+ if (ids.length != originalRoutes.length)
+ throw new Error(`The amount of routes found don't match`);
+
+ const routes = originalRoutes.map(route => {
+ route.started = started;
+ route.created = new Date();
+ return route;
+ });
+
+ const clones = await Self.create(routes, options);
+
+ await tx.commit();
+ return clones;
+ } catch (e) {
+ await tx.rollback();
+ throw e;
+ }
+ };
+};
diff --git a/modules/route/back/methods/route/specs/clone.spec.js b/modules/route/back/methods/route/specs/clone.spec.js
new file mode 100644
index 000000000..c0f5f04f1
--- /dev/null
+++ b/modules/route/back/methods/route/specs/clone.spec.js
@@ -0,0 +1,31 @@
+const app = require('vn-loopback/server/server');
+
+describe('route clone()', () => {
+ const startDate = new Date();
+ it('should throw an error if the amount of ids pased to the clone function do no match the database', async() => {
+ const ids = [996, 997, 998, 999];
+
+ let error;
+
+ try {
+ await app.models.Route.clone(ids, startDate);
+ } catch (e) {
+ error = e;
+ }
+
+ expect(error).toBeDefined();
+ expect(error.message).toEqual(`The amount of routes found don't match`);
+ });
+
+ it('should clone two routes', async() => {
+ const ids = [1, 2];
+
+ const clones = await app.models.Route.clone(ids, startDate);
+
+ expect(clones.length).toEqual(2);
+
+ // restores
+ for (const clone of clones)
+ await app.models.Route.destroyById(clone.id);
+ });
+});
diff --git a/modules/route/back/models/route.js b/modules/route/back/models/route.js
index 6320a888c..4423131bf 100644
--- a/modules/route/back/models/route.js
+++ b/modules/route/back/models/route.js
@@ -6,6 +6,7 @@ module.exports = Self => {
require('../methods/route/updateVolume')(Self);
require('../methods/route/getDeliveryPoint')(Self);
require('../methods/route/insertTicket')(Self);
+ require('../methods/route/clone')(Self);
Self.validate('kmStart', validateDistance, {
message: 'Distance must be lesser than 1000'
diff --git a/modules/route/front/basic-data/locale/es.yml b/modules/route/front/basic-data/locale/es.yml
index f0414b5b1..a98f20215 100644
--- a/modules/route/front/basic-data/locale/es.yml
+++ b/modules/route/front/basic-data/locale/es.yml
@@ -3,3 +3,5 @@ Date started: Fecha inicio
Km start: Km de inicio
Km end: Km de fin
Description: Descripción
+Hour started: Hora inicio
+Hour finished: Hora fin
diff --git a/modules/route/front/index/index.html b/modules/route/front/index/index.html
index d71bcbc48..7bbadefe4 100644
--- a/modules/route/front/index/index.html
+++ b/modules/route/front/index/index.html
@@ -44,7 +44,7 @@
{{::route.agencyName | dashIfEmpty}}
{{::route.vehiclePlateNumber | dashIfEmpty}}
- {{::route.created | date:'dd/MM/yyyy' | dashIfEmpty}}
+ {{::route.created | dashIfEmpty | date:'dd/MM/yyyy'}}
{{::route.m3 | dashIfEmpty}}
{{::route.description | dashIfEmpty}}
@@ -59,19 +59,26 @@
+
+
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/route/front/index/index.js b/modules/route/front/index/index.js
index 4400bdbb1..2dc767f4c 100644
--- a/modules/route/front/index/index.js
+++ b/modules/route/front/index/index.js
@@ -40,6 +40,22 @@ export default class Controller extends Section {
});
}
+ openClonationDialog() {
+ this.startedDate = new Date();
+ this.$.clonationDialog.show();
+ }
+
+ cloneSelectedRoutes() {
+ const routesIds = [];
+ for (let route of this.checked)
+ routesIds.push(route.id);
+
+ return this.$http.post('Routes/clone', {ids: routesIds, started: this.startedDate}).then(() => {
+ this.$.model.refresh();
+ this.vnApp.showSuccess(this.$t('Data saved!'));
+ });
+ }
+
onDrop($event) {
const target = $event.target;
const droppable = target.closest(this.droppableElement);
diff --git a/modules/route/front/index/index.spec.js b/modules/route/front/index/index.spec.js
index b66ecaf00..8346f924c 100644
--- a/modules/route/front/index/index.spec.js
+++ b/modules/route/front/index/index.spec.js
@@ -60,6 +60,16 @@ describe('Component vnRouteIndex', () => {
});
});
+ describe('cloneSelectedRoutes()', () => {
+ it('should perform an http request to Routes/clone', () => {
+ controller.startedDate = new Date();
+
+ $httpBackend.expect('POST', 'Routes/clone').respond();
+ controller.cloneSelectedRoutes();
+ $httpBackend.flush();
+ });
+ });
+
describe('onDrop()', () => {
it('should call the insert method when dragging a ticket number', () => {
jest.spyOn(controller, 'insert');
diff --git a/modules/route/front/index/locale/es.yml b/modules/route/front/index/locale/es.yml
index 0c09b21ee..40cd5f2b5 100644
--- a/modules/route/front/index/locale/es.yml
+++ b/modules/route/front/index/locale/es.yml
@@ -1,2 +1,5 @@
Vehicle: Vehículo
-Download selected routes as PDF: Descargar rutas seleccionadas como PDF
\ No newline at end of file
+Download selected routes as PDF: Descargar rutas seleccionadas como PDF
+Clone selected routes: Clonar rutas seleccionadas
+Select the starting date: Seleccione fecha de inicio
+Starting date: Fecha de inicio
\ No newline at end of file